From 38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Thu, 12 May 2016 23:56:07 -0400 Subject: Moving to glide --- vendor/github.com/mattermost/rsc/fuse/debug.go | 20 + vendor/github.com/mattermost/rsc/fuse/fuse.go | 1650 ++++++++++++++++++++ .../github.com/mattermost/rsc/fuse/fuse_kernel.go | 539 +++++++ .../mattermost/rsc/fuse/fuse_kernel_darwin.go | 58 + .../mattermost/rsc/fuse/fuse_kernel_linux.go | 50 + .../mattermost/rsc/fuse/fuse_kernel_std.go | 1 + vendor/github.com/mattermost/rsc/fuse/fuse_test.go | 594 +++++++ .../mattermost/rsc/fuse/hellofs/hello.go | 62 + .../github.com/mattermost/rsc/fuse/mount_darwin.go | 122 ++ .../github.com/mattermost/rsc/fuse/mount_linux.go | 67 + vendor/github.com/mattermost/rsc/fuse/serve.go | 1022 ++++++++++++ vendor/github.com/mattermost/rsc/fuse/tree.go | 93 ++ 12 files changed, 4278 insertions(+) create mode 100644 vendor/github.com/mattermost/rsc/fuse/debug.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/fuse.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/fuse_kernel.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/fuse_kernel_darwin.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/fuse_kernel_linux.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/fuse_kernel_std.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/fuse_test.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/hellofs/hello.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/mount_darwin.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/mount_linux.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/serve.go create mode 100644 vendor/github.com/mattermost/rsc/fuse/tree.go (limited to 'vendor/github.com/mattermost/rsc/fuse') diff --git a/vendor/github.com/mattermost/rsc/fuse/debug.go b/vendor/github.com/mattermost/rsc/fuse/debug.go new file mode 100644 index 000000000..2dc5d4312 --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/debug.go @@ -0,0 +1,20 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// FUSE service loop, for servers that wish to use it. + +package fuse + +import ( + "runtime" +) + +func stack() string { + buf := make([]byte, 1024) + return string(buf[:runtime.Stack(buf, false)]) +} + +var Debugf = nop + +func nop(string, ...interface{}) {} diff --git a/vendor/github.com/mattermost/rsc/fuse/fuse.go b/vendor/github.com/mattermost/rsc/fuse/fuse.go new file mode 100644 index 000000000..2ad10c93e --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/fuse.go @@ -0,0 +1,1650 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c, +// which carries this notice: +// +// The files in this directory are subject to the following license. +// +// The author of this software is Russ Cox. +// +// Copyright (c) 2006 Russ Cox +// +// Permission to use, copy, modify, and distribute this software for any +// purpose without fee is hereby granted, provided that this entire notice +// is included in all copies of any software which is or includes a copy +// or modification of this software and in all copies of the supporting +// documentation for such software. +// +// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY +// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS +// FITNESS FOR ANY PARTICULAR PURPOSE. + +// Package fuse enables writing FUSE file systems on FreeBSD, Linux, and OS X. +// +// On OS X, it requires OSXFUSE (http://osxfuse.github.com/). +// +// There are two approaches to writing a FUSE file system. The first is to speak +// the low-level message protocol, reading from a Conn using ReadRequest and +// writing using the various Respond methods. This approach is closest to +// the actual interaction with the kernel and can be the simplest one in contexts +// such as protocol translators. +// +// Servers of synthesized file systems tend to share common bookkeeping +// abstracted away by the second approach, which is to call the Conn's +// Serve method to serve the FUSE protocol using +// an implementation of the service methods in the interfaces +// FS (file system), Node (file or directory), and Handle (opened file or directory). +// There are a daunting number of such methods that can be written, +// but few are required. +// The specific methods are described in the documentation for those interfaces. +// +// The hellofs subdirectory contains a simple illustration of the ServeFS approach. +// +// Service Methods +// +// The required and optional methods for the FS, Node, and Handle interfaces +// have the general form +// +// Op(req *OpRequest, resp *OpResponse, intr Intr) Error +// +// where Op is the name of a FUSE operation. Op reads request parameters +// from req and writes results to resp. An operation whose only result is +// the error result omits the resp parameter. Multiple goroutines may call +// service methods simultaneously; the methods being called are responsible +// for appropriate synchronization. +// +// Interrupted Operations +// +// In some file systems, some operations +// may take an undetermined amount of time. For example, a Read waiting for +// a network message or a matching Write might wait indefinitely. If the request +// is cancelled and no longer needed, the package will close intr, a chan struct{}. +// Blocking operations should select on a receive from intr and attempt to +// abort the operation early if the receive succeeds (meaning the channel is closed). +// To indicate that the operation failed because it was aborted, return fuse.EINTR. +// +// If an operation does not block for an indefinite amount of time, the intr parameter +// can be ignored. +// +// Authentication +// +// All requests types embed a Header, meaning that the method can inspect +// req.Pid, req.Uid, and req.Gid as necessary to implement permission checking. +// Alternately, XXX. +// +// Mount Options +// +// XXX +// +package fuse + +// BUG(rsc): The mount code for FreeBSD has not been written yet. + +import ( + "bytes" + "errors" + "fmt" + "io" + "log" + "os" + "runtime" + "sync" + "syscall" + "time" + "unsafe" +) + +// A Conn represents a connection to a mounted FUSE file system. +type Conn struct { + fd int + buf []byte + wio sync.Mutex + + serveConn +} + +// Mount mounts a new FUSE connection on the named directory +// and returns a connection for reading and writing FUSE messages. +func Mount(dir string) (*Conn, error) { + // TODO(rsc): mount options (...string?) + fd, errstr := mount(dir) + if errstr != "" { + return nil, errors.New(errstr) + } + + return &Conn{fd: fd}, nil +} + +// A Request represents a single FUSE request received from the kernel. +// Use a type switch to determine the specific kind. +// A request of unrecognized type will have concrete type *Header. +type Request interface { + // Hdr returns the Header associated with this request. + Hdr() *Header + + // RespondError responds to the request with the given error. + RespondError(Error) + + String() string + + // handle returns the HandleID for the request, or 0. + handle() HandleID +} + +// A RequestID identifies an active FUSE request. +type RequestID uint64 + +// A NodeID is a number identifying a directory or file. +// It must be unique among IDs returned in LookupResponses +// that have not yet been forgotten by ForgetRequests. +type NodeID uint64 + +// A HandleID is a number identifying an open directory or file. +// It only needs to be unique while the directory or file is open. +type HandleID uint64 + +// The RootID identifies the root directory of a FUSE file system. +const RootID NodeID = rootID + +// A Header describes the basic information sent in every request. +type Header struct { + Conn *Conn // connection this request was received on + ID RequestID // unique ID for request + Node NodeID // file or directory the request is about + Uid uint32 // user ID of process making request + Gid uint32 // group ID of process making request + Pid uint32 // process ID of process making request +} + +func (h *Header) String() string { + return fmt.Sprintf("ID=%#x Node=%#x Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid) +} + +func (h *Header) Hdr() *Header { + return h +} + +func (h *Header) handle() HandleID { + return 0 +} + +// An Error is a FUSE error. +type Error interface { + errno() int32 +} + +const ( + // ENOSYS indicates that the call is not supported. + ENOSYS = Errno(syscall.ENOSYS) + + // ESTALE is used by Serve to respond to violations of the FUSE protocol. + ESTALE = Errno(syscall.ESTALE) + + ENOENT = Errno(syscall.ENOENT) + EIO = Errno(syscall.EIO) + EPERM = Errno(syscall.EPERM) +) + +type errno int + +func (e errno) errno() int32 { + return int32(e) +} + +// Errno implements Error using a syscall.Errno. +type Errno syscall.Errno + +func (e Errno) errno() int32 { + return int32(e) +} + +func (e Errno) String() string { + return syscall.Errno(e).Error() +} + +func (h *Header) RespondError(err Error) { + // FUSE uses negative errors! + // TODO: File bug report against OSXFUSE: positive error causes kernel panic. + out := &outHeader{Error: -err.errno(), Unique: uint64(h.ID)} + h.Conn.respond(out, unsafe.Sizeof(*out)) +} + +var maxWrite = syscall.Getpagesize() +var bufSize = 4096 + maxWrite + +// a message represents the bytes of a single FUSE message +type message struct { + conn *Conn + buf []byte // all bytes + hdr *inHeader // header + off int // offset for reading additional fields +} + +func newMessage(c *Conn) *message { + m := &message{conn: c, buf: make([]byte, bufSize)} + m.hdr = (*inHeader)(unsafe.Pointer(&m.buf[0])) + return m +} + +func (m *message) len() uintptr { + return uintptr(len(m.buf) - m.off) +} + +func (m *message) data() unsafe.Pointer { + var p unsafe.Pointer + if m.off < len(m.buf) { + p = unsafe.Pointer(&m.buf[m.off]) + } + return p +} + +func (m *message) bytes() []byte { + return m.buf[m.off:] +} + +func (m *message) Header() Header { + h := m.hdr + return Header{Conn: m.conn, ID: RequestID(h.Unique), Node: NodeID(h.Nodeid), Uid: h.Uid, Gid: h.Gid, Pid: h.Pid} +} + +// fileMode returns a Go os.FileMode from a Unix mode. +func fileMode(unixMode uint32) os.FileMode { + mode := os.FileMode(unixMode & 0777) + switch unixMode & syscall.S_IFMT { + case syscall.S_IFREG: + // nothing + case syscall.S_IFDIR: + mode |= os.ModeDir + case syscall.S_IFCHR: + mode |= os.ModeCharDevice | os.ModeDevice + case syscall.S_IFBLK: + mode |= os.ModeDevice + case syscall.S_IFIFO: + mode |= os.ModeNamedPipe + case syscall.S_IFLNK: + mode |= os.ModeSymlink + case syscall.S_IFSOCK: + mode |= os.ModeSocket + default: + // no idea + mode |= os.ModeDevice + } + if unixMode&syscall.S_ISUID != 0 { + mode |= os.ModeSetuid + } + if unixMode&syscall.S_ISGID != 0 { + mode |= os.ModeSetgid + } + return mode +} + +func (c *Conn) ReadRequest() (Request, error) { + // TODO: Some kind of buffer reuse. + m := newMessage(c) + n, err := syscall.Read(c.fd, m.buf) + if err != nil && err != syscall.ENODEV { + return nil, err + } + if n <= 0 { + return nil, io.EOF + } + m.buf = m.buf[:n] + + if n < inHeaderSize { + return nil, errors.New("fuse: message too short") + } + + // FreeBSD FUSE sends a short length in the header + // for FUSE_INIT even though the actual read length is correct. + if n == inHeaderSize+initInSize && m.hdr.Opcode == opInit && m.hdr.Len < uint32(n) { + m.hdr.Len = uint32(n) + } + + // OSXFUSE sometimes sends the wrong m.hdr.Len in a FUSE_WRITE message. + if m.hdr.Len < uint32(n) && m.hdr.Len >= uint32(unsafe.Sizeof(writeIn{})) && m.hdr.Opcode == opWrite { + m.hdr.Len = uint32(n) + } + + if m.hdr.Len != uint32(n) { + return nil, fmt.Errorf("fuse: read %d opcode %d but expected %d", n, m.hdr.Opcode, m.hdr.Len) + } + + m.off = inHeaderSize + + // Convert to data structures. + // Do not trust kernel to hand us well-formed data. + var req Request + switch m.hdr.Opcode { + default: + println("No opcode", m.hdr.Opcode) + goto unrecognized + + case opLookup: + buf := m.bytes() + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + goto corrupt + } + req = &LookupRequest{ + Header: m.Header(), + Name: string(buf[:n-1]), + } + + case opForget: + in := (*forgetIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &ForgetRequest{ + Header: m.Header(), + N: in.Nlookup, + } + + case opGetattr: + req = &GetattrRequest{ + Header: m.Header(), + } + + case opSetattr: + in := (*setattrIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &SetattrRequest{ + Header: m.Header(), + Valid: SetattrValid(in.Valid), + Handle: HandleID(in.Fh), + Size: in.Size, + Atime: time.Unix(int64(in.Atime), int64(in.AtimeNsec)), + Mtime: time.Unix(int64(in.Mtime), int64(in.MtimeNsec)), + Mode: fileMode(in.Mode), + Uid: in.Uid, + Gid: in.Gid, + Bkuptime: in.BkupTime(), + Chgtime: in.Chgtime(), + Flags: in.Flags(), + } + + case opReadlink: + if len(m.bytes()) > 0 { + goto corrupt + } + req = &ReadlinkRequest{ + Header: m.Header(), + } + + case opSymlink: + // m.bytes() is "newName\0target\0" + names := m.bytes() + if len(names) == 0 || names[len(names)-1] != 0 { + goto corrupt + } + i := bytes.IndexByte(names, '\x00') + if i < 0 { + goto corrupt + } + newName, target := names[0:i], names[i+1:len(names)-1] + req = &SymlinkRequest{ + Header: m.Header(), + NewName: string(newName), + Target: string(target), + } + + case opLink: + in := (*linkIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + newName := m.bytes()[unsafe.Sizeof(*in):] + if len(newName) < 2 || newName[len(newName)-1] != 0 { + goto corrupt + } + newName = newName[:len(newName)-1] + req = &LinkRequest{ + Header: m.Header(), + OldNode: NodeID(in.Oldnodeid), + NewName: string(newName), + } + + case opMknod: + in := (*mknodIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + name := m.bytes()[unsafe.Sizeof(*in):] + if len(name) < 2 || name[len(name)-1] != '\x00' { + goto corrupt + } + name = name[:len(name)-1] + req = &MknodRequest{ + Header: m.Header(), + Mode: fileMode(in.Mode), + Rdev: in.Rdev, + Name: string(name), + } + + case opMkdir: + in := (*mkdirIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + name := m.bytes()[unsafe.Sizeof(*in):] + i := bytes.IndexByte(name, '\x00') + if i < 0 { + goto corrupt + } + req = &MkdirRequest{ + Header: m.Header(), + Name: string(name[:i]), + Mode: fileMode(in.Mode) | os.ModeDir, + } + + case opUnlink, opRmdir: + buf := m.bytes() + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + goto corrupt + } + req = &RemoveRequest{ + Header: m.Header(), + Name: string(buf[:n-1]), + Dir: m.hdr.Opcode == opRmdir, + } + + case opRename: + in := (*renameIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + newDirNodeID := NodeID(in.Newdir) + oldNew := m.bytes()[unsafe.Sizeof(*in):] + // oldNew should be "old\x00new\x00" + if len(oldNew) < 4 { + goto corrupt + } + if oldNew[len(oldNew)-1] != '\x00' { + goto corrupt + } + i := bytes.IndexByte(oldNew, '\x00') + if i < 0 { + goto corrupt + } + oldName, newName := string(oldNew[:i]), string(oldNew[i+1:len(oldNew)-1]) + // log.Printf("RENAME: newDirNode = %d; old = %q, new = %q", newDirNodeID, oldName, newName) + req = &RenameRequest{ + Header: m.Header(), + NewDir: newDirNodeID, + OldName: oldName, + NewName: newName, + } + + case opOpendir, opOpen: + in := (*openIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &OpenRequest{ + Header: m.Header(), + Dir: m.hdr.Opcode == opOpendir, + Flags: in.Flags, + Mode: fileMode(in.Mode), + } + + case opRead, opReaddir: + in := (*readIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &ReadRequest{ + Header: m.Header(), + Dir: m.hdr.Opcode == opReaddir, + Handle: HandleID(in.Fh), + Offset: int64(in.Offset), + Size: int(in.Size), + } + + case opWrite: + in := (*writeIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + r := &WriteRequest{ + Header: m.Header(), + Handle: HandleID(in.Fh), + Offset: int64(in.Offset), + Flags: WriteFlags(in.WriteFlags), + } + buf := m.bytes()[unsafe.Sizeof(*in):] + if uint32(len(buf)) < in.Size { + goto corrupt + } + r.Data = buf + req = r + + case opStatfs: + req = &StatfsRequest{ + Header: m.Header(), + } + + case opRelease, opReleasedir: + in := (*releaseIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &ReleaseRequest{ + Header: m.Header(), + Dir: m.hdr.Opcode == opReleasedir, + Handle: HandleID(in.Fh), + Flags: in.Flags, + ReleaseFlags: ReleaseFlags(in.ReleaseFlags), + LockOwner: in.LockOwner, + } + + case opFsync: + in := (*fsyncIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &FsyncRequest{ + Header: m.Header(), + Handle: HandleID(in.Fh), + Flags: in.FsyncFlags, + } + + case opSetxattr: + var size uint32 + var r *SetxattrRequest + if runtime.GOOS == "darwin" { + in := (*setxattrInOSX)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + r = &SetxattrRequest{ + Flags: in.Flags, + Position: in.Position, + } + size = in.Size + m.off += int(unsafe.Sizeof(*in)) + } else { + in := (*setxattrIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + r = &SetxattrRequest{} + size = in.Size + m.off += int(unsafe.Sizeof(*in)) + } + r.Header = m.Header() + name := m.bytes() + i := bytes.IndexByte(name, '\x00') + if i < 0 { + goto corrupt + } + r.Name = string(name[:i]) + r.Xattr = name[i+1:] + if uint32(len(r.Xattr)) < size { + goto corrupt + } + r.Xattr = r.Xattr[:size] + req = r + + case opGetxattr: + if runtime.GOOS == "darwin" { + in := (*getxattrInOSX)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &GetxattrRequest{ + Header: m.Header(), + Size: in.Size, + Position: in.Position, + } + } else { + in := (*getxattrIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &GetxattrRequest{ + Header: m.Header(), + Size: in.Size, + } + } + + case opListxattr: + if runtime.GOOS == "darwin" { + in := (*getxattrInOSX)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &ListxattrRequest{ + Header: m.Header(), + Size: in.Size, + Position: in.Position, + } + } else { + in := (*getxattrIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &ListxattrRequest{ + Header: m.Header(), + Size: in.Size, + } + } + + case opRemovexattr: + buf := m.bytes() + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + goto corrupt + } + req = &RemovexattrRequest{ + Header: m.Header(), + Name: string(buf[:n-1]), + } + + case opFlush: + in := (*flushIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &FlushRequest{ + Header: m.Header(), + Handle: HandleID(in.Fh), + Flags: in.FlushFlags, + LockOwner: in.LockOwner, + } + + case opInit: + in := (*initIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &InitRequest{ + Header: m.Header(), + Major: in.Major, + Minor: in.Minor, + MaxReadahead: in.MaxReadahead, + Flags: InitFlags(in.Flags), + } + + case opFsyncdir: + panic("opFsyncdir") + case opGetlk: + panic("opGetlk") + case opSetlk: + panic("opSetlk") + case opSetlkw: + panic("opSetlkw") + + case opAccess: + in := (*accessIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &AccessRequest{ + Header: m.Header(), + Mask: in.Mask, + } + + case opCreate: + in := (*openIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + name := m.bytes()[unsafe.Sizeof(*in):] + i := bytes.IndexByte(name, '\x00') + if i < 0 { + goto corrupt + } + req = &CreateRequest{ + Header: m.Header(), + Flags: in.Flags, + Mode: fileMode(in.Mode), + Name: string(name[:i]), + } + + case opInterrupt: + panic("opInterrupt") + case opBmap: + panic("opBmap") + + case opDestroy: + req = &DestroyRequest{ + Header: m.Header(), + } + + // OS X + case opSetvolname: + panic("opSetvolname") + case opGetxtimes: + panic("opGetxtimes") + case opExchange: + panic("opExchange") + } + + return req, nil + +corrupt: + println("malformed message") + return nil, fmt.Errorf("fuse: malformed message") + +unrecognized: + // Unrecognized message. + // Assume higher-level code will send a "no idea what you mean" error. + h := m.Header() + return &h, nil +} + +func (c *Conn) respond(out *outHeader, n uintptr) { + c.wio.Lock() + defer c.wio.Unlock() + out.Len = uint32(n) + msg := (*[1 << 30]byte)(unsafe.Pointer(out))[:n] + nn, err := syscall.Write(c.fd, msg) + if nn != len(msg) || err != nil { + log.Printf("RESPOND WRITE: %d %v", nn, err) + log.Printf("with stack: %s", stack()) + } +} + +func (c *Conn) respondData(out *outHeader, n uintptr, data []byte) { + c.wio.Lock() + defer c.wio.Unlock() + // TODO: use writev + out.Len = uint32(n + uintptr(len(data))) + msg := make([]byte, out.Len) + copy(msg, (*[1 << 30]byte)(unsafe.Pointer(out))[:n]) + copy(msg[n:], data) + syscall.Write(c.fd, msg) +} + +// An InitRequest is the first request sent on a FUSE file system. +type InitRequest struct { + Header + Major uint32 + Minor uint32 + MaxReadahead uint32 + Flags InitFlags +} + +func (r *InitRequest) String() string { + return fmt.Sprintf("Init [%s] %d.%d ra=%d fl=%v", &r.Header, r.Major, r.Minor, r.MaxReadahead, r.Flags) +} + +// An InitResponse is the response to an InitRequest. +type InitResponse struct { + MaxReadahead uint32 + Flags InitFlags + MaxWrite uint32 +} + +func (r *InitResponse) String() string { + return fmt.Sprintf("Init %+v", *r) +} + +// Respond replies to the request with the given response. +func (r *InitRequest) Respond(resp *InitResponse) { + out := &initOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Major: kernelVersion, + Minor: kernelMinorVersion, + MaxReadahead: resp.MaxReadahead, + Flags: uint32(resp.Flags), + MaxWrite: resp.MaxWrite, + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A StatfsRequest requests information about the mounted file system. +type StatfsRequest struct { + Header +} + +func (r *StatfsRequest) String() string { + return fmt.Sprintf("Statfs [%s]\n", &r.Header) +} + +// Respond replies to the request with the given response. +func (r *StatfsRequest) Respond(resp *StatfsResponse) { + out := &statfsOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + St: kstatfs{ + Blocks: resp.Blocks, + Bfree: resp.Bfree, + Bavail: resp.Bavail, + Files: resp.Files, + Bsize: resp.Bsize, + Namelen: resp.Namelen, + Frsize: resp.Frsize, + }, + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A StatfsResponse is the response to a StatfsRequest. +type StatfsResponse struct { + Blocks uint64 // Total data blocks in file system. + Bfree uint64 // Free blocks in file system. + Bavail uint64 // Free blocks in file system if you're not root. + Files uint64 // Total files in file system. + Ffree uint64 // Free files in file system. + Bsize uint32 // Block size + Namelen uint32 // Maximum file name length? + Frsize uint32 // ? +} + +func (r *StatfsResponse) String() string { + return fmt.Sprintf("Statfs %+v", *r) +} + +// An AccessRequest asks whether the file can be accessed +// for the purpose specified by the mask. +type AccessRequest struct { + Header + Mask uint32 +} + +func (r *AccessRequest) String() string { + return fmt.Sprintf("Access [%s] mask=%#x", &r.Header, r.Mask) +} + +// Respond replies to the request indicating that access is allowed. +// To deny access, use RespondError. +func (r *AccessRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respond(out, unsafe.Sizeof(*out)) +} + +// An Attr is the metadata for a single file or directory. +type Attr struct { + Inode uint64 // inode number + Size uint64 // size in bytes + Blocks uint64 // size in blocks + Atime time.Time // time of last access + Mtime time.Time // time of last modification + Ctime time.Time // time of last inode change + Crtime time.Time // time of creation (OS X only) + Mode os.FileMode // file mode + Nlink uint32 // number of links + Uid uint32 // owner uid + Gid uint32 // group gid + Rdev uint32 // device numbers + Flags uint32 // chflags(2) flags (OS X only) +} + +func unix(t time.Time) (sec uint64, nsec uint32) { + nano := t.UnixNano() + sec = uint64(nano / 1e9) + nsec = uint32(nano % 1e9) + return +} + +func (a *Attr) attr() (out attr) { + out.Ino = a.Inode + out.Size = a.Size + out.Blocks = a.Blocks + out.Atime, out.AtimeNsec = unix(a.Atime) + out.Mtime, out.MtimeNsec = unix(a.Mtime) + out.Ctime, out.CtimeNsec = unix(a.Ctime) + out.SetCrtime(unix(a.Crtime)) + out.Mode = uint32(a.Mode) & 0777 + switch { + default: + out.Mode |= syscall.S_IFREG + case a.Mode&os.ModeDir != 0: + out.Mode |= syscall.S_IFDIR + case a.Mode&os.ModeDevice != 0: + if a.Mode&os.ModeCharDevice != 0 { + out.Mode |= syscall.S_IFCHR + } else { + out.Mode |= syscall.S_IFBLK + } + case a.Mode&os.ModeNamedPipe != 0: + out.Mode |= syscall.S_IFIFO + case a.Mode&os.ModeSymlink != 0: + out.Mode |= syscall.S_IFLNK + case a.Mode&os.ModeSocket != 0: + out.Mode |= syscall.S_IFSOCK + } + if a.Mode&os.ModeSetuid != 0 { + out.Mode |= syscall.S_ISUID + } + if a.Mode&os.ModeSetgid != 0 { + out.Mode |= syscall.S_ISGID + } + out.Nlink = a.Nlink + if out.Nlink < 1 { + out.Nlink = 1 + } + out.Uid = a.Uid + out.Gid = a.Gid + out.Rdev = a.Rdev + out.SetFlags(a.Flags) + + return +} + +// A GetattrRequest asks for the metadata for the file denoted by r.Node. +type GetattrRequest struct { + Header +} + +func (r *GetattrRequest) String() string { + return fmt.Sprintf("Getattr [%s]", &r.Header) +} + +// Respond replies to the request with the given response. +func (r *GetattrRequest) Respond(resp *GetattrResponse) { + out := &attrOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A GetattrResponse is the response to a GetattrRequest. +type GetattrResponse struct { + AttrValid time.Duration // how long Attr can be cached + Attr Attr // file attributes +} + +func (r *GetattrResponse) String() string { + return fmt.Sprintf("Getattr %+v", *r) +} + +// A GetxattrRequest asks for the extended attributes associated with r.Node. +type GetxattrRequest struct { + Header + Size uint32 // maximum size to return + Position uint32 // offset within extended attributes +} + +func (r *GetxattrRequest) String() string { + return fmt.Sprintf("Getxattr [%s] %d @%d", &r.Header, r.Size, r.Position) +} + +// Respond replies to the request with the given response. +func (r *GetxattrRequest) Respond(resp *GetxattrResponse) { + out := &getxattrOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Size: uint32(len(resp.Xattr)), + } + r.Conn.respondData(&out.outHeader, unsafe.Sizeof(*out), resp.Xattr) +} + +// A GetxattrResponse is the response to a GetxattrRequest. +type GetxattrResponse struct { + Xattr []byte +} + +func (r *GetxattrResponse) String() string { + return fmt.Sprintf("Getxattr %x", r.Xattr) +} + +// A ListxattrRequest asks to list the extended attributes associated with r.Node. +type ListxattrRequest struct { + Header + Size uint32 // maximum size to return + Position uint32 // offset within attribute list +} + +func (r *ListxattrRequest) String() string { + return fmt.Sprintf("Listxattr [%s] %d @%d", &r.Header, r.Size, r.Position) +} + +// Respond replies to the request with the given response. +func (r *ListxattrRequest) Respond(resp *ListxattrResponse) { + out := &getxattrOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Size: uint32(len(resp.Xattr)), + } + r.Conn.respondData(&out.outHeader, unsafe.Sizeof(*out), resp.Xattr) +} + +// A ListxattrResponse is the response to a ListxattrRequest. +type ListxattrResponse struct { + Xattr []byte +} + +func (r *ListxattrResponse) String() string { + return fmt.Sprintf("Listxattr %x", r.Xattr) +} + +// A RemovexattrRequest asks to remove an extended attribute associated with r.Node. +type RemovexattrRequest struct { + Header + Name string // name of extended attribute +} + +func (r *RemovexattrRequest) String() string { + return fmt.Sprintf("Removexattr [%s] %q", &r.Header, r.Name) +} + +// Respond replies to the request, indicating that the attribute was removed. +func (r *RemovexattrRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respond(out, unsafe.Sizeof(*out)) +} + +// A SetxattrRequest asks to set an extended attribute associated with a file. +type SetxattrRequest struct { + Header + Flags uint32 + Position uint32 // OS X only + Name string + Xattr []byte +} + +func (r *SetxattrRequest) String() string { + return fmt.Sprintf("Setxattr [%s] %q %x fl=%v @%#x", &r.Header, r.Name, r.Xattr, r.Flags, r.Position) +} + +// Respond replies to the request, indicating that the extended attribute was set. +func (r *SetxattrRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respond(out, unsafe.Sizeof(*out)) +} + +// A LookupRequest asks to look up the given name in the directory named by r.Node. +type LookupRequest struct { + Header + Name string +} + +func (r *LookupRequest) String() string { + return fmt.Sprintf("Lookup [%s] %q", &r.Header, r.Name) +} + +// Respond replies to the request with the given response. +func (r *LookupRequest) Respond(resp *LookupResponse) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A LookupResponse is the response to a LookupRequest. +type LookupResponse struct { + Node NodeID + Generation uint64 + EntryValid time.Duration + AttrValid time.Duration + Attr Attr +} + +func (r *LookupResponse) String() string { + return fmt.Sprintf("Lookup %+v", *r) +} + +// An OpenRequest asks to open a file or directory +type OpenRequest struct { + Header + Dir bool // is this Opendir? + Flags uint32 + Mode os.FileMode +} + +func (r *OpenRequest) String() string { + return fmt.Sprintf("Open [%s] dir=%v fl=%v mode=%v", &r.Header, r.Dir, r.Flags, r.Mode) +} + +// Respond replies to the request with the given response. +func (r *OpenRequest) Respond(resp *OpenResponse) { + out := &openOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Fh: uint64(resp.Handle), + OpenFlags: uint32(resp.Flags), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A OpenResponse is the response to a OpenRequest. +type OpenResponse struct { + Handle HandleID + Flags OpenFlags +} + +func (r *OpenResponse) String() string { + return fmt.Sprintf("Open %+v", *r) +} + +// A CreateRequest asks to create and open a file (not a directory). +type CreateRequest struct { + Header + Name string + Flags uint32 + Mode os.FileMode +} + +func (r *CreateRequest) String() string { + return fmt.Sprintf("Create [%s] %q fl=%v mode=%v", &r.Header, r.Name, r.Flags, r.Mode) +} + +// Respond replies to the request with the given response. +func (r *CreateRequest) Respond(resp *CreateResponse) { + out := &createOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + + Fh: uint64(resp.Handle), + OpenFlags: uint32(resp.Flags), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A CreateResponse is the response to a CreateRequest. +// It describes the created node and opened handle. +type CreateResponse struct { + LookupResponse + OpenResponse +} + +func (r *CreateResponse) String() string { + return fmt.Sprintf("Create %+v", *r) +} + +// A MkdirRequest asks to create (but not open) a directory. +type MkdirRequest struct { + Header + Name string + Mode os.FileMode +} + +func (r *MkdirRequest) String() string { + return fmt.Sprintf("Mkdir [%s] %q mode=%v", &r.Header, r.Name, r.Mode) +} + +// Respond replies to the request with the given response. +func (r *MkdirRequest) Respond(resp *MkdirResponse) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A MkdirResponse is the response to a MkdirRequest. +type MkdirResponse struct { + LookupResponse +} + +func (r *MkdirResponse) String() string { + return fmt.Sprintf("Mkdir %+v", *r) +} + +// A ReadRequest asks to read from an open file. +type ReadRequest struct { + Header + Dir bool // is this Readdir? + Handle HandleID + Offset int64 + Size int +} + +func (r *ReadRequest) handle() HandleID { + return r.Handle +} + +func (r *ReadRequest) String() string { + return fmt.Sprintf("Read [%s] %#x %d @%#x dir=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir) +} + +// Respond replies to the request with the given response. +func (r *ReadRequest) Respond(resp *ReadResponse) { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respondData(out, unsafe.Sizeof(*out), resp.Data) +} + +// A ReadResponse is the response to a ReadRequest. +type ReadResponse struct { + Data []byte +} + +func (r *ReadResponse) String() string { + return fmt.Sprintf("Read %x", r.Data) +} + +// A ReleaseRequest asks to release (close) an open file handle. +type ReleaseRequest struct { + Header + Dir bool // is this Releasedir? + Handle HandleID + Flags uint32 // flags from OpenRequest + ReleaseFlags ReleaseFlags + LockOwner uint32 +} + +func (r *ReleaseRequest) handle() HandleID { + return r.Handle +} + +func (r *ReleaseRequest) String() string { + return fmt.Sprintf("Release [%s] %#x fl=%v rfl=%v owner=%#x", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner) +} + +// Respond replies to the request, indicating that the handle has been released. +func (r *ReleaseRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respond(out, unsafe.Sizeof(*out)) +} + +// A DestroyRequest is sent by the kernel when unmounting the file system. +// No more requests will be received after this one, but it should still be +// responded to. +type DestroyRequest struct { + Header +} + +func (r *DestroyRequest) String() string { + return fmt.Sprintf("Destroy [%s]", &r.Header) +} + +// Respond replies to the request. +func (r *DestroyRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respond(out, unsafe.Sizeof(*out)) +} + +// A ForgetRequest is sent by the kernel when forgetting about r.Node +// as returned by r.N lookup requests. +type ForgetRequest struct { + Header + N uint64 +} + +func (r *ForgetRequest) String() string { + return fmt.Sprintf("Forget [%s] %d", &r.Header, r.N) +} + +// Respond replies to the request, indicating that the forgetfulness has been recorded. +func (r *ForgetRequest) Respond() { + // Don't reply to forget messages. +} + +// A Dirent represents a single directory entry. +type Dirent struct { + Inode uint64 // inode this entry names + Type uint32 // ? + Name string // name of entry +} + +// AppendDirent appends the encoded form of a directory entry to data +// and returns the resulting slice. +func AppendDirent(data []byte, dir Dirent) []byte { + de := dirent{ + Ino: dir.Inode, + Namelen: uint32(len(dir.Name)), + Type: dir.Type, + } + de.Off = uint64(len(data) + direntSize + (len(dir.Name)+7)&^7) + data = append(data, (*[direntSize]byte)(unsafe.Pointer(&de))[:]...) + data = append(data, dir.Name...) + n := direntSize + uintptr(len(dir.Name)) + if n%8 != 0 { + var pad [8]byte + data = append(data, pad[:8-n%8]...) + } + return data +} + +// A WriteRequest asks to write to an open file. +type WriteRequest struct { + Header + Handle HandleID + Offset int64 + Data []byte + Flags WriteFlags +} + +func (r *WriteRequest) String() string { + return fmt.Sprintf("Write [%s] %#x %d @%d fl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags) +} + +// Respond replies to the request with the given response. +func (r *WriteRequest) Respond(resp *WriteResponse) { + out := &writeOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Size: uint32(resp.Size), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +func (r *WriteRequest) handle() HandleID { + return r.Handle +} + +// A WriteResponse replies to a write indicating how many bytes were written. +type WriteResponse struct { + Size int +} + +func (r *WriteResponse) String() string { + return fmt.Sprintf("Write %+v", *r) +} + +// A SetattrRequest asks to change one or more attributes associated with a file, +// as indicated by Valid. +type SetattrRequest struct { + Header + Valid SetattrValid + Handle HandleID + Size uint64 + Atime time.Time + Mtime time.Time + Mode os.FileMode + Uid uint32 + Gid uint32 + + // OS X only + Bkuptime time.Time + Chgtime time.Time + Crtime time.Time + Flags uint32 // see chflags(2) +} + +func (r *SetattrRequest) String() string { + var buf bytes.Buffer + fmt.Fprintf(&buf, "Setattr [%s]", &r.Header) + if r.Valid.Mode() { + fmt.Fprintf(&buf, " mode=%v", r.Mode) + } + if r.Valid.Uid() { + fmt.Fprintf(&buf, " uid=%d", r.Uid) + } + if r.Valid.Gid() { + fmt.Fprintf(&buf, " gid=%d", r.Gid) + } + if r.Valid.Size() { + fmt.Fprintf(&buf, " size=%d", r.Size) + } + if r.Valid.Atime() { + fmt.Fprintf(&buf, " atime=%v", r.Atime) + } + if r.Valid.Mtime() { + fmt.Fprintf(&buf, " mtime=%v", r.Mtime) + } + if r.Valid.Handle() { + fmt.Fprintf(&buf, " handle=%#x", r.Handle) + } else { + fmt.Fprintf(&buf, " handle=INVALID-%#x", r.Handle) + } + if r.Valid.Crtime() { + fmt.Fprintf(&buf, " crtime=%v", r.Crtime) + } + if r.Valid.Chgtime() { + fmt.Fprintf(&buf, " chgtime=%v", r.Chgtime) + } + if r.Valid.Bkuptime() { + fmt.Fprintf(&buf, " bkuptime=%v", r.Bkuptime) + } + if r.Valid.Flags() { + fmt.Fprintf(&buf, " flags=%#x", r.Flags) + } + return buf.String() +} + +func (r *SetattrRequest) handle() HandleID { + if r.Valid.Handle() { + return r.Handle + } + return 0 +} + +// Respond replies to the request with the given response, +// giving the updated attributes. +func (r *SetattrRequest) Respond(resp *SetattrResponse) { + out := &attrOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A SetattrResponse is the response to a SetattrRequest. +type SetattrResponse struct { + AttrValid time.Duration // how long Attr can be cached + Attr Attr // file attributes +} + +func (r *SetattrResponse) String() string { + return fmt.Sprintf("Setattr %+v", *r) +} + +// A FlushRequest asks for the current state of an open file to be flushed +// to storage, as when a file descriptor is being closed. A single opened Handle +// may receive multiple FlushRequests over its lifetime. +type FlushRequest struct { + Header + Handle HandleID + Flags uint32 + LockOwner uint64 +} + +func (r *FlushRequest) String() string { + return fmt.Sprintf("Flush [%s] %#x fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner) +} + +func (r *FlushRequest) handle() HandleID { + return r.Handle +} + +// Respond replies to the request, indicating that the flush succeeded. +func (r *FlushRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respond(out, unsafe.Sizeof(*out)) +} + +// A RemoveRequest asks to remove a file or directory. +type RemoveRequest struct { + Header + Name string // name of extended attribute + Dir bool // is this rmdir? +} + +func (r *RemoveRequest) String() string { + return fmt.Sprintf("Remove [%s] %q dir=%v", &r.Header, r.Name, r.Dir) +} + +// Respond replies to the request, indicating that the file was removed. +func (r *RemoveRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respond(out, unsafe.Sizeof(*out)) +} + +// A SymlinkRequest is a request to create a symlink making NewName point to Target. +type SymlinkRequest struct { + Header + NewName, Target string +} + +func (r *SymlinkRequest) String() string { + return fmt.Sprintf("Symlink [%s] from %q to target %q", &r.Header, r.NewName, r.Target) +} + +func (r *SymlinkRequest) handle() HandleID { + return 0 +} + +// Respond replies to the request, indicating that the symlink was created. +func (r *SymlinkRequest) Respond(resp *SymlinkResponse) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A SymlinkResponse is the response to a SymlinkRequest. +type SymlinkResponse struct { + LookupResponse +} + +// A ReadlinkRequest is a request to read a symlink's target. +type ReadlinkRequest struct { + Header +} + +func (r *ReadlinkRequest) String() string { + return fmt.Sprintf("Readlink [%s]", &r.Header) +} + +func (r *ReadlinkRequest) handle() HandleID { + return 0 +} + +func (r *ReadlinkRequest) Respond(target string) { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respondData(out, unsafe.Sizeof(*out), []byte(target)) +} + +// A LinkRequest is a request to create a hard link. +type LinkRequest struct { + Header + OldNode NodeID + NewName string +} + +func (r *LinkRequest) Respond(resp *LookupResponse) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A RenameRequest is a request to rename a file. +type RenameRequest struct { + Header + NewDir NodeID + OldName, NewName string +} + +func (r *RenameRequest) handle() HandleID { + return 0 +} + +func (r *RenameRequest) String() string { + return fmt.Sprintf("Rename [%s] from %q to dirnode %d %q", &r.Header, r.OldName, r.NewDir, r.NewName) +} + +func (r *RenameRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respond(out, unsafe.Sizeof(*out)) +} + +type MknodRequest struct { + Header + Name string + Mode os.FileMode + Rdev uint32 +} + +func (r *MknodRequest) String() string { + return fmt.Sprintf("Mknod [%s] Name %q mode %v rdev %d", &r.Header, r.Name, r.Mode, r.Rdev) +} + +func (r *MknodRequest) Respond(resp *LookupResponse) { + out := &entryOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + Nodeid: uint64(resp.Node), + Generation: resp.Generation, + EntryValid: uint64(resp.EntryValid / time.Second), + EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), + AttrValid: uint64(resp.AttrValid / time.Second), + AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), + Attr: resp.Attr.attr(), + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +type FsyncRequest struct { + Header + Handle HandleID + Flags uint32 +} + +func (r *FsyncRequest) String() string { + return fmt.Sprintf("Fsync [%s] Handle %v Flags %v", &r.Header, r.Handle, r.Flags) +} + +func (r *FsyncRequest) Respond() { + out := &outHeader{Unique: uint64(r.ID)} + r.Conn.respond(out, unsafe.Sizeof(*out)) +} + +/*{ + +// A XXXRequest xxx. +type XXXRequest struct { + Header + xxx +} + +func (r *XXXRequest) String() string { + return fmt.Sprintf("XXX [%s] xxx", &r.Header) +} + +func (r *XXXRequest) handle() HandleID { + return r.Handle +} + +// Respond replies to the request with the given response. +func (r *XXXRequest) Respond(resp *XXXResponse) { + out := &xxxOut{ + outHeader: outHeader{Unique: uint64(r.ID)}, + xxx, + } + r.Conn.respond(&out.outHeader, unsafe.Sizeof(*out)) +} + +// A XXXResponse is the response to a XXXRequest. +type XXXResponse struct { + xxx +} + +func (r *XXXResponse) String() string { + return fmt.Sprintf("XXX %+v", *r) +} + + } +*/ diff --git a/vendor/github.com/mattermost/rsc/fuse/fuse_kernel.go b/vendor/github.com/mattermost/rsc/fuse/fuse_kernel.go new file mode 100644 index 000000000..a57360e63 --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/fuse_kernel.go @@ -0,0 +1,539 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Derived from FUSE's fuse_kernel.h +/* + This file defines the kernel interface of FUSE + Copyright (C) 2001-2007 Miklos Szeredi + + + This -- and only this -- header file may also be distributed under + the terms of the BSD Licence as follows: + + Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +package fuse + +import ( + "fmt" + "unsafe" +) + +// Version is the FUSE version implemented by the package. +const Version = "7.8" + +const ( + kernelVersion = 7 + kernelMinorVersion = 8 + rootID = 1 +) + +type kstatfs struct { + Blocks uint64 + Bfree uint64 + Bavail uint64 + Files uint64 + Ffree uint64 + Bsize uint32 + Namelen uint32 + Frsize uint32 + Padding uint32 + Spare [6]uint32 +} + +type fileLock struct { + Start uint64 + End uint64 + Type uint32 + Pid uint32 +} + +// The SetattrValid are bit flags describing which fields in the SetattrRequest +// are included in the change. +type SetattrValid uint32 + +const ( + SetattrMode SetattrValid = 1 << 0 + SetattrUid SetattrValid = 1 << 1 + SetattrGid SetattrValid = 1 << 2 + SetattrSize SetattrValid = 1 << 3 + SetattrAtime SetattrValid = 1 << 4 + SetattrMtime SetattrValid = 1 << 5 + SetattrHandle SetattrValid = 1 << 6 // TODO: What does this mean? + + // Linux only(?) + SetattrAtimeNow SetattrValid = 1 << 7 + SetattrMtimeNow SetattrValid = 1 << 8 + SetattrLockOwner SetattrValid = 1 << 9 // http://www.mail-archive.com/git-commits-head@vger.kernel.org/msg27852.html + + // OS X only + SetattrCrtime SetattrValid = 1 << 28 + SetattrChgtime SetattrValid = 1 << 29 + SetattrBkuptime SetattrValid = 1 << 30 + SetattrFlags SetattrValid = 1 << 31 +) + +func (fl SetattrValid) Mode() bool { return fl&SetattrMode != 0 } +func (fl SetattrValid) Uid() bool { return fl&SetattrUid != 0 } +func (fl SetattrValid) Gid() bool { return fl&SetattrGid != 0 } +func (fl SetattrValid) Size() bool { return fl&SetattrSize != 0 } +func (fl SetattrValid) Atime() bool { return fl&SetattrAtime != 0 } +func (fl SetattrValid) Mtime() bool { return fl&SetattrMtime != 0 } +func (fl SetattrValid) Handle() bool { return fl&SetattrHandle != 0 } +func (fl SetattrValid) Crtime() bool { return fl&SetattrCrtime != 0 } +func (fl SetattrValid) Chgtime() bool { return fl&SetattrChgtime != 0 } +func (fl SetattrValid) Bkuptime() bool { return fl&SetattrBkuptime != 0 } +func (fl SetattrValid) Flags() bool { return fl&SetattrFlags != 0 } + +func (fl SetattrValid) String() string { + return flagString(uint32(fl), setattrValidNames) +} + +var setattrValidNames = []flagName{ + {uint32(SetattrMode), "SetattrMode"}, + {uint32(SetattrUid), "SetattrUid"}, + {uint32(SetattrGid), "SetattrGid"}, + {uint32(SetattrSize), "SetattrSize"}, + {uint32(SetattrAtime), "SetattrAtime"}, + {uint32(SetattrMtime), "SetattrMtime"}, + {uint32(SetattrHandle), "SetattrHandle"}, + {uint32(SetattrCrtime), "SetattrCrtime"}, + {uint32(SetattrChgtime), "SetattrChgtime"}, + {uint32(SetattrBkuptime), "SetattrBkuptime"}, + {uint32(SetattrFlags), "SetattrFlags"}, +} + +// The OpenFlags are returned in the OpenResponse. +type OpenFlags uint32 + +const ( + OpenDirectIO OpenFlags = 1 << 0 // bypass page cache for this open file + OpenKeepCache OpenFlags = 1 << 1 // don't invalidate the data cache on open + OpenNonSeekable OpenFlags = 1 << 2 // (Linux?) + + OpenPurgeAttr OpenFlags = 1 << 30 // OS X + OpenPurgeUBC OpenFlags = 1 << 31 // OS X +) + +func (fl OpenFlags) String() string { + return flagString(uint32(fl), openFlagNames) +} + +var openFlagNames = []flagName{ + {uint32(OpenDirectIO), "OpenDirectIO"}, + {uint32(OpenKeepCache), "OpenKeepCache"}, + {uint32(OpenPurgeAttr), "OpenPurgeAttr"}, + {uint32(OpenPurgeUBC), "OpenPurgeUBC"}, +} + +// The InitFlags are used in the Init exchange. +type InitFlags uint32 + +const ( + InitAsyncRead InitFlags = 1 << 0 + InitPosixLocks InitFlags = 1 << 1 + + InitCaseSensitive InitFlags = 1 << 29 // OS X only + InitVolRename InitFlags = 1 << 30 // OS X only + InitXtimes InitFlags = 1 << 31 // OS X only +) + +type flagName struct { + bit uint32 + name string +} + +var initFlagNames = []flagName{ + {uint32(InitAsyncRead), "InitAsyncRead"}, + {uint32(InitPosixLocks), "InitPosixLocks"}, + {uint32(InitCaseSensitive), "InitCaseSensitive"}, + {uint32(InitVolRename), "InitVolRename"}, + {uint32(InitXtimes), "InitXtimes"}, +} + +func (fl InitFlags) String() string { + return flagString(uint32(fl), initFlagNames) +} + +func flagString(f uint32, names []flagName) string { + var s string + + if f == 0 { + return "0" + } + + for _, n := range names { + if f&n.bit != 0 { + s += "+" + n.name + f &^= n.bit + } + } + if f != 0 { + s += fmt.Sprintf("%+#x", f) + } + return s[1:] +} + +// The ReleaseFlags are used in the Release exchange. +type ReleaseFlags uint32 + +const ( + ReleaseFlush ReleaseFlags = 1 << 0 +) + +func (fl ReleaseFlags) String() string { + return flagString(uint32(fl), releaseFlagNames) +} + +var releaseFlagNames = []flagName{ + {uint32(ReleaseFlush), "ReleaseFlush"}, +} + +// Opcodes +const ( + opLookup = 1 + opForget = 2 // no reply + opGetattr = 3 + opSetattr = 4 + opReadlink = 5 + opSymlink = 6 + opMknod = 8 + opMkdir = 9 + opUnlink = 10 + opRmdir = 11 + opRename = 12 + opLink = 13 + opOpen = 14 + opRead = 15 + opWrite = 16 + opStatfs = 17 + opRelease = 18 + opFsync = 20 + opSetxattr = 21 + opGetxattr = 22 + opListxattr = 23 + opRemovexattr = 24 + opFlush = 25 + opInit = 26 + opOpendir = 27 + opReaddir = 28 + opReleasedir = 29 + opFsyncdir = 30 + opGetlk = 31 + opSetlk = 32 + opSetlkw = 33 + opAccess = 34 + opCreate = 35 + opInterrupt = 36 + opBmap = 37 + opDestroy = 38 + opIoctl = 39 // Linux? + opPoll = 40 // Linux? + + // OS X + opSetvolname = 61 + opGetxtimes = 62 + opExchange = 63 +) + +// The read buffer is required to be at least 8k but may be much larger +const minReadBuffer = 8192 + +type entryOut struct { + outHeader + Nodeid uint64 // Inode ID + Generation uint64 // Inode generation + EntryValid uint64 // Cache timeout for the name + AttrValid uint64 // Cache timeout for the attributes + EntryValidNsec uint32 + AttrValidNsec uint32 + Attr attr +} + +type forgetIn struct { + Nlookup uint64 +} + +type attrOut struct { + outHeader + AttrValid uint64 // Cache timeout for the attributes + AttrValidNsec uint32 + Dummy uint32 + Attr attr +} + +// OS X +type getxtimesOut struct { + outHeader + Bkuptime uint64 + Crtime uint64 + BkuptimeNsec uint32 + CrtimeNsec uint32 +} + +type mknodIn struct { + Mode uint32 + Rdev uint32 + // "filename\x00" follows. +} + +type mkdirIn struct { + Mode uint32 + Padding uint32 + // filename follows +} + +type renameIn struct { + Newdir uint64 + // "oldname\x00newname\x00" follows +} + +// OS X +type exchangeIn struct { + Olddir uint64 + Newdir uint64 + Options uint64 +} + +type linkIn struct { + Oldnodeid uint64 +} + +type setattrInCommon struct { + Valid uint32 + Padding uint32 + Fh uint64 + Size uint64 + LockOwner uint64 // unused on OS X? + Atime uint64 + Mtime uint64 + Unused2 uint64 + AtimeNsec uint32 + MtimeNsec uint32 + Unused3 uint32 + Mode uint32 + Unused4 uint32 + Uid uint32 + Gid uint32 + Unused5 uint32 +} + +type openIn struct { + Flags uint32 + Mode uint32 +} + +type openOut struct { + outHeader + Fh uint64 + OpenFlags uint32 + Padding uint32 +} + +type createOut struct { + outHeader + + Nodeid uint64 // Inode ID + Generation uint64 // Inode generation + EntryValid uint64 // Cache timeout for the name + AttrValid uint64 // Cache timeout for the attributes + EntryValidNsec uint32 + AttrValidNsec uint32 + Attr attr + + Fh uint64 + OpenFlags uint32 + Padding uint32 +} + +type releaseIn struct { + Fh uint64 + Flags uint32 + ReleaseFlags uint32 + LockOwner uint32 +} + +type flushIn struct { + Fh uint64 + FlushFlags uint32 + Padding uint32 + LockOwner uint64 +} + +type readIn struct { + Fh uint64 + Offset uint64 + Size uint32 + Padding uint32 +} + +type writeIn struct { + Fh uint64 + Offset uint64 + Size uint32 + WriteFlags uint32 +} + +type writeOut struct { + outHeader + Size uint32 + Padding uint32 +} + +// The WriteFlags are returned in the WriteResponse. +type WriteFlags uint32 + +func (fl WriteFlags) String() string { + return flagString(uint32(fl), writeFlagNames) +} + +var writeFlagNames = []flagName{} + +const compatStatfsSize = 48 + +type statfsOut struct { + outHeader + St kstatfs +} + +type fsyncIn struct { + Fh uint64 + FsyncFlags uint32 + Padding uint32 +} + +type setxattrIn struct { + Size uint32 + Flags uint32 +} + +type setxattrInOSX struct { + Size uint32 + Flags uint32 + + // OS X only + Position uint32 + Padding uint32 +} + +type getxattrIn struct { + Size uint32 + Padding uint32 +} + +type getxattrInOSX struct { + Size uint32 + Padding uint32 + + // OS X only + Position uint32 + Padding2 uint32 +} + +type getxattrOut struct { + outHeader + Size uint32 + Padding uint32 +} + +type lkIn struct { + Fh uint64 + Owner uint64 + Lk fileLock +} + +type lkOut struct { + outHeader + Lk fileLock +} + +type accessIn struct { + Mask uint32 + Padding uint32 +} + +type initIn struct { + Major uint32 + Minor uint32 + MaxReadahead uint32 + Flags uint32 +} + +const initInSize = int(unsafe.Sizeof(initIn{})) + +type initOut struct { + outHeader + Major uint32 + Minor uint32 + MaxReadahead uint32 + Flags uint32 + Unused uint32 + MaxWrite uint32 +} + +type interruptIn struct { + Unique uint64 +} + +type bmapIn struct { + Block uint64 + BlockSize uint32 + Padding uint32 +} + +type bmapOut struct { + outHeader + Block uint64 +} + +type inHeader struct { + Len uint32 + Opcode uint32 + Unique uint64 + Nodeid uint64 + Uid uint32 + Gid uint32 + Pid uint32 + Padding uint32 +} + +const inHeaderSize = int(unsafe.Sizeof(inHeader{})) + +type outHeader struct { + Len uint32 + Error int32 + Unique uint64 +} + +type dirent struct { + Ino uint64 + Off uint64 + Namelen uint32 + Type uint32 + Name [0]byte +} + +const direntSize = 8 + 8 + 4 + 4 diff --git a/vendor/github.com/mattermost/rsc/fuse/fuse_kernel_darwin.go b/vendor/github.com/mattermost/rsc/fuse/fuse_kernel_darwin.go new file mode 100644 index 000000000..f7bc37766 --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/fuse_kernel_darwin.go @@ -0,0 +1,58 @@ +package fuse + +import ( + "time" +) + +type attr struct { + Ino uint64 + Size uint64 + Blocks uint64 + Atime uint64 + Mtime uint64 + Ctime uint64 + Crtime_ uint64 // OS X only + AtimeNsec uint32 + MtimeNsec uint32 + CtimeNsec uint32 + CrtimeNsec uint32 // OS X only + Mode uint32 + Nlink uint32 + Uid uint32 + Gid uint32 + Rdev uint32 + Flags_ uint32 // OS X only; see chflags(2) +} + +func (a *attr) SetCrtime(s uint64, ns uint32) { + a.Crtime_, a.CrtimeNsec = s, ns +} + +func (a *attr) SetFlags(f uint32) { + a.Flags_ = f +} + +type setattrIn struct { + setattrInCommon + + // OS X only + Bkuptime_ uint64 + Chgtime_ uint64 + Crtime uint64 + BkuptimeNsec uint32 + ChgtimeNsec uint32 + CrtimeNsec uint32 + Flags_ uint32 // see chflags(2) +} + +func (in *setattrIn) BkupTime() time.Time { + return time.Unix(int64(in.Bkuptime_), int64(in.BkuptimeNsec)) +} + +func (in *setattrIn) Chgtime() time.Time { + return time.Unix(int64(in.Chgtime_), int64(in.ChgtimeNsec)) +} + +func (in *setattrIn) Flags() uint32 { + return in.Flags_ +} diff --git a/vendor/github.com/mattermost/rsc/fuse/fuse_kernel_linux.go b/vendor/github.com/mattermost/rsc/fuse/fuse_kernel_linux.go new file mode 100644 index 000000000..914bc3063 --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/fuse_kernel_linux.go @@ -0,0 +1,50 @@ +package fuse + +import "time" + +type attr struct { + Ino uint64 + Size uint64 + Blocks uint64 + Atime uint64 + Mtime uint64 + Ctime uint64 + AtimeNsec uint32 + MtimeNsec uint32 + CtimeNsec uint32 + Mode uint32 + Nlink uint32 + Uid uint32 + Gid uint32 + Rdev uint32 + // Blksize uint32 // Only in protocol 7.9 + // padding_ uint32 // Only in protocol 7.9 +} + +func (a *attr) Crtime() time.Time { + return time.Time{} +} + +func (a *attr) SetCrtime(s uint64, ns uint32) { + // Ignored on Linux. +} + +func (a *attr) SetFlags(f uint32) { + // Ignored on Linux. +} + +type setattrIn struct { + setattrInCommon +} + +func (in *setattrIn) BkupTime() time.Time { + return time.Time{} +} + +func (in *setattrIn) Chgtime() time.Time { + return time.Time{} +} + +func (in *setattrIn) Flags() uint32 { + return 0 +} diff --git a/vendor/github.com/mattermost/rsc/fuse/fuse_kernel_std.go b/vendor/github.com/mattermost/rsc/fuse/fuse_kernel_std.go new file mode 100644 index 000000000..074cfd322 --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/fuse_kernel_std.go @@ -0,0 +1 @@ +package fuse diff --git a/vendor/github.com/mattermost/rsc/fuse/fuse_test.go b/vendor/github.com/mattermost/rsc/fuse/fuse_test.go new file mode 100644 index 000000000..61533b8c5 --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/fuse_test.go @@ -0,0 +1,594 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "runtime" + "syscall" + "testing" + "time" +) + +var fuseRun = flag.String("fuserun", "", "which fuse test to run. runs all if empty.") + +// umount tries its best to unmount dir. +func umount(dir string) { + err := exec.Command("umount", dir).Run() + if err != nil && runtime.GOOS == "linux" { + exec.Command("/bin/fusermount", "-u", dir).Run() + } +} + +func TestFuse(t *testing.T) { + Debugf = log.Printf + dir, err := ioutil.TempDir("", "fusetest") + if err != nil { + t.Fatal(err) + } + os.MkdirAll(dir, 0777) + + c, err := Mount(dir) + if err != nil { + t.Fatal(err) + } + defer umount(dir) + + go func() { + err := c.Serve(testFS{}) + if err != nil { + fmt.Printf("SERVE ERROR: %v\n", err) + } + }() + + waitForMount(t, dir) + + for _, tt := range fuseTests { + if *fuseRun == "" || *fuseRun == tt.name { + t.Logf("running %T", tt.node) + tt.node.test(dir+"/"+tt.name, t) + } + } +} + +func waitForMount(t *testing.T, dir string) { + // Filename to wait for in dir: + probeEntry := *fuseRun + if probeEntry == "" { + probeEntry = fuseTests[0].name + } + for tries := 0; tries < 100; tries++ { + _, err := os.Stat(dir + "/" + probeEntry) + if err == nil { + return + } + time.Sleep(10 * time.Millisecond) + } + t.Fatalf("mount did not work") +} + +var fuseTests = []struct { + name string + node interface { + Node + test(string, *testing.T) + } +}{ + {"readAll", readAll{}}, + {"readAll1", &readAll1{}}, + {"write", &write{}}, + {"writeAll", &writeAll{}}, + {"writeAll2", &writeAll2{}}, + {"release", &release{}}, + {"mkdir1", &mkdir1{}}, + {"create1", &create1{}}, + {"create2", &create2{}}, + {"symlink1", &symlink1{}}, + {"link1", &link1{}}, + {"rename1", &rename1{}}, + {"mknod1", &mknod1{}}, +} + +// TO TEST: +// Statfs +// Lookup(*LookupRequest, *LookupResponse) +// Getattr(*GetattrRequest, *GetattrResponse) +// Attr with explicit inode +// Setattr(*SetattrRequest, *SetattrResponse) +// Access(*AccessRequest) +// Open(*OpenRequest, *OpenResponse) +// Getxattr, Setxattr, Listxattr, Removexattr +// Write(*WriteRequest, *WriteResponse) +// Flush(*FlushRequest, *FlushResponse) + +// Test Read calling ReadAll. + +type readAll struct{ file } + +const hi = "hello, world" + +func (readAll) ReadAll(intr Intr) ([]byte, Error) { + return []byte(hi), nil +} + +func (readAll) test(path string, t *testing.T) { + data, err := ioutil.ReadFile(path) + if err != nil { + t.Errorf("readAll: %v", err) + return + } + if string(data) != hi { + t.Errorf("readAll = %q, want %q", data, hi) + } +} + +// Test Read. + +type readAll1 struct{ file } + +func (readAll1) Read(req *ReadRequest, resp *ReadResponse, intr Intr) Error { + HandleRead(req, resp, []byte(hi)) + return nil +} + +func (readAll1) test(path string, t *testing.T) { + readAll{}.test(path, t) +} + +// Test Write calling basic Write, with an fsync thrown in too. + +type write struct { + file + data []byte + gotfsync bool +} + +func (w *write) Write(req *WriteRequest, resp *WriteResponse, intr Intr) Error { + w.data = append(w.data, req.Data...) + resp.Size = len(req.Data) + return nil +} + +func (w *write) Fsync(r *FsyncRequest, intr Intr) Error { + w.gotfsync = true + return nil +} + +func (w *write) test(path string, t *testing.T) { + log.Printf("pre-write Create") + f, err := os.Create(path) + if err != nil { + t.Fatalf("Create: %v", err) + } + log.Printf("pre-write Write") + n, err := f.Write([]byte(hi)) + if err != nil { + t.Fatalf("Write: %v", err) + } + if n != len(hi) { + t.Fatalf("short write; n=%d; hi=%d", n, len(hi)) + } + + err = syscall.Fsync(int(f.Fd())) + if err != nil { + t.Fatalf("Fsync = %v", err) + } + if !w.gotfsync { + t.Errorf("never received expected fsync call") + } + + log.Printf("pre-write Close") + err = f.Close() + if err != nil { + t.Fatalf("Close: %v", err) + } + log.Printf("post-write Close") + if string(w.data) != hi { + t.Errorf("writeAll = %q, want %q", w.data, hi) + } +} + +// Test Write calling WriteAll. + +type writeAll struct { + file + data []byte + gotfsync bool +} + +func (w *writeAll) Fsync(r *FsyncRequest, intr Intr) Error { + w.gotfsync = true + return nil +} + +func (w *writeAll) WriteAll(data []byte, intr Intr) Error { + w.data = data + return nil +} + +func (w *writeAll) test(path string, t *testing.T) { + err := ioutil.WriteFile(path, []byte(hi), 0666) + if err != nil { + t.Fatalf("WriteFile: %v", err) + return + } + if string(w.data) != hi { + t.Errorf("writeAll = %q, want %q", w.data, hi) + } +} + +// Test Write calling Setattr+Write+Flush. + +type writeAll2 struct { + file + data []byte + setattr bool + flush bool +} + +func (w *writeAll2) Setattr(req *SetattrRequest, resp *SetattrResponse, intr Intr) Error { + w.setattr = true + return nil +} + +func (w *writeAll2) Flush(req *FlushRequest, intr Intr) Error { + w.flush = true + return nil +} + +func (w *writeAll2) Write(req *WriteRequest, resp *WriteResponse, intr Intr) Error { + w.data = append(w.data, req.Data...) + resp.Size = len(req.Data) + return nil +} + +func (w *writeAll2) test(path string, t *testing.T) { + err := ioutil.WriteFile(path, []byte(hi), 0666) + if err != nil { + t.Errorf("WriteFile: %v", err) + return + } + if !w.setattr || string(w.data) != hi || !w.flush { + t.Errorf("writeAll = %v, %q, %v, want %v, %q, %v", w.setattr, string(w.data), w.flush, true, hi, true) + } +} + +// Test Mkdir. + +type mkdir1 struct { + dir + name string +} + +func (f *mkdir1) Mkdir(req *MkdirRequest, intr Intr) (Node, Error) { + f.name = req.Name + return &mkdir1{}, nil +} + +func (f *mkdir1) test(path string, t *testing.T) { + f.name = "" + err := os.Mkdir(path+"/foo", 0777) + if err != nil { + t.Error(err) + return + } + if f.name != "foo" { + t.Error(err) + return + } +} + +// Test Create (and fsync) + +type create1 struct { + dir + name string + f *writeAll +} + +func (f *create1) Create(req *CreateRequest, resp *CreateResponse, intr Intr) (Node, Handle, Error) { + f.name = req.Name + f.f = &writeAll{} + return f.f, f.f, nil +} + +func (f *create1) test(path string, t *testing.T) { + f.name = "" + ff, err := os.Create(path + "/foo") + if err != nil { + t.Errorf("create1 WriteFile: %v", err) + return + } + + err = syscall.Fsync(int(ff.Fd())) + if err != nil { + t.Fatalf("Fsync = %v", err) + } + + if !f.f.gotfsync { + t.Errorf("never received expected fsync call") + } + + ff.Close() + if f.name != "foo" { + t.Errorf("create1 name=%q want foo", f.name) + } +} + +// Test Create + WriteAll + Remove + +type create2 struct { + dir + name string + f *writeAll + fooExists bool +} + +func (f *create2) Create(req *CreateRequest, resp *CreateResponse, intr Intr) (Node, Handle, Error) { + f.name = req.Name + f.f = &writeAll{} + return f.f, f.f, nil +} + +func (f *create2) Lookup(name string, intr Intr) (Node, Error) { + if f.fooExists && name == "foo" { + return file{}, nil + } + return nil, ENOENT +} + +func (f *create2) Remove(r *RemoveRequest, intr Intr) Error { + if f.fooExists && r.Name == "foo" && !r.Dir { + f.fooExists = false + return nil + } + return ENOENT +} + +func (f *create2) test(path string, t *testing.T) { + f.name = "" + err := ioutil.WriteFile(path+"/foo", []byte(hi), 0666) + if err != nil { + t.Fatalf("create2 WriteFile: %v", err) + } + if string(f.f.data) != hi { + t.Fatalf("create2 writeAll = %q, want %q", f.f.data, hi) + } + + f.fooExists = true + log.Printf("pre-Remove") + err = os.Remove(path + "/foo") + if err != nil { + t.Fatalf("Remove: %v", err) + } + err = os.Remove(path + "/foo") + if err == nil { + t.Fatalf("second Remove = nil; want some error") + } +} + +// Test symlink + readlink + +type symlink1 struct { + dir + newName, target string +} + +func (f *symlink1) Symlink(req *SymlinkRequest, intr Intr) (Node, Error) { + f.newName = req.NewName + f.target = req.Target + return symlink{target: req.Target}, nil +} + +func (f *symlink1) test(path string, t *testing.T) { + const target = "/some-target" + + err := os.Symlink(target, path+"/symlink.file") + if err != nil { + t.Errorf("os.Symlink: %v", err) + return + } + + if f.newName != "symlink.file" { + t.Errorf("symlink newName = %q; want %q", f.newName, "symlink.file") + } + if f.target != target { + t.Errorf("symlink target = %q; want %q", f.target, target) + } + + gotName, err := os.Readlink(path + "/symlink.file") + if err != nil { + t.Errorf("os.Readlink: %v", err) + return + } + if gotName != target { + t.Errorf("os.Readlink = %q; want %q", gotName, target) + } +} + +// Test link + +type link1 struct { + dir + newName string +} + +func (f *link1) Lookup(name string, intr Intr) (Node, Error) { + if name == "old" { + return file{}, nil + } + return nil, ENOENT +} + +func (f *link1) Link(r *LinkRequest, old Node, intr Intr) (Node, Error) { + f.newName = r.NewName + return file{}, nil +} + +func (f *link1) test(path string, t *testing.T) { + err := os.Link(path+"/old", path+"/new") + if err != nil { + t.Fatalf("Link: %v", err) + } + if f.newName != "new" { + t.Fatalf("saw Link for newName %q; want %q", f.newName, "new") + } +} + +// Test Rename + +type rename1 struct { + dir + renames int +} + +func (f *rename1) Lookup(name string, intr Intr) (Node, Error) { + if name == "old" { + return file{}, nil + } + return nil, ENOENT +} + +func (f *rename1) Rename(r *RenameRequest, newDir Node, intr Intr) Error { + if r.OldName == "old" && r.NewName == "new" && newDir == f { + f.renames++ + return nil + } + return EIO +} + +func (f *rename1) test(path string, t *testing.T) { + err := os.Rename(path+"/old", path+"/new") + if err != nil { + t.Fatalf("Rename: %v", err) + } + if f.renames != 1 { + t.Fatalf("expected rename didn't happen") + } + err = os.Rename(path+"/old2", path+"/new2") + if err == nil { + t.Fatal("expected error on second Rename; got nil") + } +} + +// Test Release. + +type release struct { + file + did bool +} + +func (r *release) Release(*ReleaseRequest, Intr) Error { + r.did = true + return nil +} + +func (r *release) test(path string, t *testing.T) { + r.did = false + f, err := os.Open(path) + if err != nil { + t.Error(err) + return + } + f.Close() + time.Sleep(1 * time.Second) + if !r.did { + t.Error("Close did not Release") + } +} + +// Test mknod + +type mknod1 struct { + dir + gotr *MknodRequest +} + +func (f *mknod1) Mknod(r *MknodRequest, intr Intr) (Node, Error) { + f.gotr = r + return fifo{}, nil +} + +func (f *mknod1) test(path string, t *testing.T) { + if os.Getuid() != 0 { + t.Logf("skipping unless root") + return + } + defer syscall.Umask(syscall.Umask(0)) + err := syscall.Mknod(path+"/node", syscall.S_IFIFO|0666, 123) + if err != nil { + t.Fatalf("Mknod: %v", err) + } + if f.gotr == nil { + t.Fatalf("no recorded MknodRequest") + } + if g, e := f.gotr.Name, "node"; g != e { + t.Errorf("got Name = %q; want %q", g, e) + } + if g, e := f.gotr.Rdev, uint32(123); g != e { + if runtime.GOOS == "linux" { + // Linux fuse doesn't echo back the rdev if the node + // isn't a device (we're using a FIFO here, as that + // bit is portable.) + } else { + t.Errorf("got Rdev = %v; want %v", g, e) + } + } + if g, e := f.gotr.Mode, os.FileMode(os.ModeNamedPipe|0666); g != e { + t.Errorf("got Mode = %v; want %v", g, e) + } + t.Logf("Got request: %#v", f.gotr) +} + +type file struct{} +type dir struct{} +type fifo struct{} +type symlink struct { + target string +} + +func (f file) Attr() Attr { return Attr{Mode: 0666} } +func (f dir) Attr() Attr { return Attr{Mode: os.ModeDir | 0777} } +func (f fifo) Attr() Attr { return Attr{Mode: os.ModeNamedPipe | 0666} } +func (f symlink) Attr() Attr { return Attr{Mode: os.ModeSymlink | 0666} } + +func (f symlink) Readlink(*ReadlinkRequest, Intr) (string, Error) { + return f.target, nil +} + +type testFS struct{} + +func (testFS) Root() (Node, Error) { + return testFS{}, nil +} + +func (testFS) Attr() Attr { + return Attr{Mode: os.ModeDir | 0555} +} + +func (testFS) Lookup(name string, intr Intr) (Node, Error) { + for _, tt := range fuseTests { + if tt.name == name { + return tt.node, nil + } + } + return nil, ENOENT +} + +func (testFS) ReadDir(intr Intr) ([]Dirent, Error) { + var dirs []Dirent + for _, tt := range fuseTests { + if *fuseRun == "" || *fuseRun == tt.name { + log.Printf("Readdir; adding %q", tt.name) + dirs = append(dirs, Dirent{Name: tt.name}) + } + } + return dirs, nil +} diff --git a/vendor/github.com/mattermost/rsc/fuse/hellofs/hello.go b/vendor/github.com/mattermost/rsc/fuse/hellofs/hello.go new file mode 100644 index 000000000..d915473f1 --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/hellofs/hello.go @@ -0,0 +1,62 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Hellofs implements a simple "hello world" file system. +package main + +import ( + "log" + "os" + + "github.com/mattermost/rsc/fuse" +) + +func main() { + c, err := fuse.Mount("/mnt/hellofs") + if err != nil { + log.Fatal(err) + } + + c.Serve(FS{}) +} + +// FS implements the hello world file system. +type FS struct{} + +func (FS) Root() (fuse.Node, fuse.Error) { + return Dir{}, nil +} + +// Dir implements both Node and Handle for the root directory. +type Dir struct{} + +func (Dir) Attr() fuse.Attr { + return fuse.Attr{Mode: os.ModeDir | 0555} +} + +func (Dir) Lookup(name string, intr fuse.Intr) (fuse.Node, fuse.Error) { + if name == "hello" { + return File{}, nil + } + return nil, fuse.ENOENT +} + +var dirDirs = []fuse.Dirent{ + {Inode: 2, Name: "hello", Type: 0}, +} + +func (Dir) ReadDir(intr fuse.Intr) ([]fuse.Dirent, fuse.Error) { + return dirDirs, nil +} + +// File implements both Node and Handle for the hello file. +type File struct{} + +func (File) Attr() fuse.Attr { + return fuse.Attr{Mode: 0444} +} + +func (File) ReadAll(intr fuse.Intr) ([]byte, fuse.Error) { + return []byte("hello, world\n"), nil +} diff --git a/vendor/github.com/mattermost/rsc/fuse/mount_darwin.go b/vendor/github.com/mattermost/rsc/fuse/mount_darwin.go new file mode 100644 index 000000000..5e2caaa76 --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/mount_darwin.go @@ -0,0 +1,122 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// TODO: Rewrite using package syscall not cgo + +package fuse + +/* + +// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c, +// which carries this notice: +// +// The files in this directory are subject to the following license. +// +// The author of this software is Russ Cox. +// +// Copyright (c) 2006 Russ Cox +// +// Permission to use, copy, modify, and distribute this software for any +// purpose without fee is hereby granted, provided that this entire notice +// is included in all copies of any software which is or includes a copy +// or modification of this software and in all copies of the supporting +// documentation for such software. +// +// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY +// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS +// FITNESS FOR ANY PARTICULAR PURPOSE. + +#include +#include +#include +#include +#include +#include +#include +#include + +#define nil ((void*)0) + +static int +mountfuse(char *mtpt, char **err) +{ + int i, pid, fd, r; + char buf[200]; + struct vfsconf vfs; + char *f; + + if(getvfsbyname("fusefs", &vfs) < 0){ + if(access(f="/Library/Filesystems/osxfusefs.fs" + "/Support/load_osxfusefs", 0) < 0){ + *err = strdup("cannot find load_fusefs"); + return -1; + } + if((r=system(f)) < 0){ + snprintf(buf, sizeof buf, "%s: %s", f, strerror(errno)); + *err = strdup(buf); + return -1; + } + if(r != 0){ + snprintf(buf, sizeof buf, "load_fusefs failed: exit %d", r); + *err = strdup(buf); + return -1; + } + if(getvfsbyname("osxfusefs", &vfs) < 0){ + snprintf(buf, sizeof buf, "getvfsbyname osxfusefs: %s", strerror(errno)); + *err = strdup(buf); + return -1; + } + } + + // Look for available FUSE device. + for(i=0;; i++){ + snprintf(buf, sizeof buf, "/dev/osxfuse%d", i); + if(access(buf, 0) < 0){ + *err = strdup("no available fuse devices"); + return -1; + } + if((fd = open(buf, O_RDWR)) >= 0) + break; + } + + pid = fork(); + if(pid < 0) + return -1; + if(pid == 0){ + snprintf(buf, sizeof buf, "%d", fd); + setenv("MOUNT_FUSEFS_CALL_BY_LIB", "", 1); + // Different versions of MacFUSE put the + // mount_fusefs binary in different places. + // Try all. + // Leopard location + setenv("MOUNT_FUSEFS_DAEMON_PATH", + "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs", 1); + execl("/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs", + "mount_osxfusefs", + "-o", "iosize=4096", buf, mtpt, nil); + fprintf(stderr, "exec mount_osxfusefs: %s\n", strerror(errno)); + _exit(1); + } + return fd; +} + +*/ +import "C" + +import "unsafe" + +func mount(dir string) (int, string) { + errp := (**C.char)(C.malloc(16)) + *errp = nil + defer C.free(unsafe.Pointer(errp)) + cdir := C.CString(dir) + defer C.free(unsafe.Pointer(cdir)) + fd := C.mountfuse(cdir, errp) + var err string + if *errp != nil { + err = C.GoString(*errp) + } + return int(fd), err +} diff --git a/vendor/github.com/mattermost/rsc/fuse/mount_linux.go b/vendor/github.com/mattermost/rsc/fuse/mount_linux.go new file mode 100644 index 000000000..e5bc58b8a --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/mount_linux.go @@ -0,0 +1,67 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fuse + +import ( + "fmt" + "net" + "os" + "os/exec" + "syscall" +) + +func mount(dir string) (fusefd int, errmsg string) { + fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0) + if err != nil { + return -1, fmt.Sprintf("socketpair error: %v", err) + } + defer syscall.Close(fds[0]) + defer syscall.Close(fds[1]) + + cmd := exec.Command("/bin/fusermount", "--", dir) + cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") + + writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") + defer writeFile.Close() + cmd.ExtraFiles = []*os.File{writeFile} + + out, err := cmd.CombinedOutput() + if len(out) > 0 || err != nil { + return -1, fmt.Sprintf("fusermount: %q, %v", out, err) + } + + readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") + defer readFile.Close() + c, err := net.FileConn(readFile) + if err != nil { + return -1, fmt.Sprintf("FileConn from fusermount socket: %v", err) + } + defer c.Close() + + uc, ok := c.(*net.UnixConn) + if !ok { + return -1, fmt.Sprintf("unexpected FileConn type; expected UnixConn, got %T", c) + } + + buf := make([]byte, 32) // expect 1 byte + oob := make([]byte, 32) // expect 24 bytes + _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob) + scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + return -1, fmt.Sprintf("ParseSocketControlMessage: %v", err) + } + if len(scms) != 1 { + return -1, fmt.Sprintf("expected 1 SocketControlMessage; got scms = %#v", scms) + } + scm := scms[0] + gotFds, err := syscall.ParseUnixRights(&scm) + if err != nil { + return -1, fmt.Sprintf("syscall.ParseUnixRights: %v", err) + } + if len(gotFds) != 1 { + return -1, fmt.Sprintf("wanted 1 fd; got %#v", gotFds) + } + return gotFds[0], "" +} diff --git a/vendor/github.com/mattermost/rsc/fuse/serve.go b/vendor/github.com/mattermost/rsc/fuse/serve.go new file mode 100644 index 000000000..fa4f27e3f --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/serve.go @@ -0,0 +1,1022 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// FUSE service loop, for servers that wish to use it. + +package fuse + +import ( + "fmt" + "hash/fnv" + "io" + "log" + "os" + "path" + "sync" + "syscall" + "time" +) + +// TODO: FINISH DOCS + +// An Intr is a channel that signals that a request has been interrupted. +// Being able to receive from the channel means the request has been +// interrupted. +type Intr chan struct{} + +func (Intr) String() string { return "fuse.Intr" } + +// An FS is the interface required of a file system. +// +// Root() (Node, Error) +// +// Root is called to obtain the Node for the file system root. +// +// Optional Methods +// +// An FS implementation may implement +// additional methods to handle the corresponding FUSE requests: +// +// Init(req *InitRequest, resp *InitResponse) Error +// +// Init is called to initialize the FUSE connection. +// It can inspect the request and adjust the response as desired. +// The default response sets MaxReadahead to 0 and MaxWrite to 4096. +// Init must return promptly. +// +// Statfs(resp *StatfsResponse, intr Intr) Error +// +// Statfs is called to obtain file system metadata. It should write that data to resp. +// +// Rename(req *RenameRequest, intr Intr) Error +// +// XXXX this is not implemented like this. Instead, Rename is a method +// on the source dierctory node, and takes a newDir Node parameter. Fix it like this? +// Rename is called to rename the file req.OldName in the directory req.OldDir to +// become the file req.NewName in the directory req.NewDir. +// +type FS interface { + Root() (Node, Error) +} + +// A Node is the interface required of a file or directory. +// See the documentation for type FS for general information +// pertaining to all methods. +// +// Getattr(resp *GetattrResponse, intr Intr) fuse.Error +// +// Getattr obtains the standard metadata for the receiver. +// It should store that metadata in resp. +// +// Open(xxx, intr Intr) (Handle, fuse.Error) +// +// Open opens the receiver. +// XXX note about access. XXX OpenFlags. +// XXX note that the Node may be a file or directory. +// +// Optional Methods +// +// An Node implementation may implement additional methods +// to handle the corresponding FUSE requests. +// +// These optional requests can be called for both file and directory nodes: +// +// Access +// +// Access checks whether the calling context has permission for +// the given operations on the receiver. If so, Access should return nil. If not, Access should +// return EPERM. Note that this call affects the result of the access(2) system call +// but not the open(2) system call. If Access is not implemented, the Node behaves +// as if it always returns nil (permission granted), relying on checks in Open instead. +// +// Getxattr +// +// Getxattr obtains an extended attribute for the receiver. +// XXX +// +// Listxattr +// +// Listxattr lists the extended attributes recorded for the receiver. +// +// Removexattr +// +// Removexattr removes an extended attribute from the receiver. +// +// Setattr +// +// Setattr sets the standard metadata for the receiver. +// +// Setxattr +// +// Setxattr sets an extended attribute for the receiver. +// +// Optional Directory Methods +// +// These optional requests will be called only for directory nodes: +// +// Create(xxx) +// +// Create creates +// +// Link(xxx) +// +// Link XXX +// +// Lookup(name string, intr Intr) (Node, Error) +// +// Lookup looks up a specific entry in the receiver, +// which must be a directory. Lookup should return a Node +// corresponding to the entry. If the name does not exist in +// the directory, Lookup should return nil, err. +// +// Lookup need not to handle the names "." and "..". +// +// Mkdir +// +// Mkdir creates XXX +// +// Mknod XXX +// +// XXX +// +// Remove +// +// Remove removes the entry with the given name from +// the receiver, which must be a directory. The entry to be removed +// may correspond to a file (unlink) or to a directory (rmdir). +// +// Symlink +// +// Symlink creates a new symbolic link in the receiver, which must be a directory. +// The entry +// +// Optional Symlink Methods +// +// This optional request will be called only for symbolic link nodes: +// +// Readlink +// +// Readlink reads a symbolic link. +type Node interface { + Attr() Attr +} + +var startTime = time.Now() + +func nodeAttr(inode uint64, n Node) (attr Attr) { + attr = n.Attr() + if attr.Nlink == 0 { + attr.Nlink = 1 + } + if attr.Atime.IsZero() { + attr.Atime = startTime + } + if attr.Mtime.IsZero() { + attr.Mtime = startTime + } + if attr.Ctime.IsZero() { + attr.Ctime = startTime + } + if attr.Crtime.IsZero() { + attr.Crtime = startTime + } + if attr.Inode == 0 { + attr.Inode = inode + } + return +} + +// A Handle is the interface required of an opened file or directory. +// See the documentation for type FS for general information +// pertaining to all methods. +// +// Flush +// +// Flush is called each time the file or directory is closed. Because there can be +// multiple file descriptors referring to a single opened file, Flush can be called +// multiple times. +// +// Optional Methods +// +// A Handle implementation may implement additional methods to handle +// the corresponding FUSE requests. The most common to implement are +// Read, ReadDir, and Write. +// +// Fsync +// +// Getlk +// +// Read +// +// Readdir +// +// Release +// +// Setlk +// +// Setlkw +// +// Write +// +type Handle interface { +} + +// Serve serves the FUSE connection by making calls to the methods +// of fs and the Nodes and Handles it makes available. It returns only +// when the connection has been closed or an unexpected error occurs. +func (c *Conn) Serve(fs FS) error { + if c.req != nil { + panic("fuse: Serve called twice") + } + c.req = map[RequestID]*serveRequest{} + + root, err := fs.Root() + if err != nil { + return fmt.Errorf("cannot obtain root node: %v", syscall.Errno(err.(Errno)).Error()) + } + c.node = append(c.node, nil, &serveNode{name: "/", node: root}) + c.handle = append(c.handle, nil) + + for { + req, err := c.ReadRequest() + if err != nil { + if err == io.EOF { + break + } + return err + } + + go c.serve(fs, req) + } + return nil +} + +type serveConn struct { + meta sync.Mutex + req map[RequestID]*serveRequest + node []*serveNode + handle []*serveHandle + freeNode []NodeID + freeHandle []HandleID + nodeGen uint64 + nodeHandles []map[HandleID]bool // open handles for a node; slice index is NodeID +} + +type serveRequest struct { + Request Request + Intr Intr +} + +type serveNode struct { + name string + node Node + inode uint64 + isDir bool +} + +func (sn *serveNode) attr() (attr Attr) { + attr = nodeAttr(sn.inode, sn.node) + if attr.Inode == 0 { + sn.inode = hash(sn.name) + attr.Inode = sn.inode + } + sn.isDir = attr.Mode&os.ModeDir != 0 + return +} + +func hash(s string) uint64 { + f := fnv.New64() + f.Write([]byte(s)) + return f.Sum64() +} + +type serveHandle struct { + handle Handle + readData []byte + trunc bool + writeData []byte + nodeID NodeID +} + +func (c *Conn) saveNode(name string, node Node) (id NodeID, gen uint64, sn *serveNode) { + sn = &serveNode{name: name, node: node} + c.meta.Lock() + if n := len(c.freeNode); n > 0 { + id = c.freeNode[n-1] + c.freeNode = c.freeNode[:n-1] + c.node[id] = sn + c.nodeGen++ + } else { + id = NodeID(len(c.node)) + c.node = append(c.node, sn) + } + gen = c.nodeGen + c.meta.Unlock() + return +} + +func (c *Conn) saveHandle(handle Handle, nodeID NodeID) (id HandleID, shandle *serveHandle) { + c.meta.Lock() + shandle = &serveHandle{handle: handle, nodeID: nodeID} + if n := len(c.freeHandle); n > 0 { + id = c.freeHandle[n-1] + c.freeHandle = c.freeHandle[:n-1] + c.handle[id] = shandle + } else { + id = HandleID(len(c.handle)) + c.handle = append(c.handle, shandle) + } + + // Update mapping from node ID -> set of open Handle IDs. + for len(c.nodeHandles) <= int(nodeID) { + c.nodeHandles = append(c.nodeHandles, nil) + } + if c.nodeHandles[nodeID] == nil { + c.nodeHandles[nodeID] = make(map[HandleID]bool) + } + c.nodeHandles[nodeID][id] = true + + c.meta.Unlock() + return +} + +func (c *Conn) dropNode(id NodeID) { + c.meta.Lock() + c.node[id] = nil + if len(c.nodeHandles) > int(id) { + c.nodeHandles[id] = nil + } + c.freeNode = append(c.freeNode, id) + c.meta.Unlock() +} + +func (c *Conn) dropHandle(id HandleID) { + c.meta.Lock() + h := c.handle[id] + delete(c.nodeHandles[h.nodeID], id) + c.handle[id] = nil + c.freeHandle = append(c.freeHandle, id) + c.meta.Unlock() +} + +func (c *Conn) serve(fs FS, r Request) { + intr := make(Intr) + req := &serveRequest{Request: r, Intr: intr} + + Debugf("<- %s", req) + var node Node + var handle Handle + var snode *serveNode + var shandle *serveHandle + c.meta.Lock() + hdr := r.Hdr() + if id := hdr.Node; id != 0 { + if id < NodeID(len(c.node)) { + snode = c.node[uint(id)] + } + if snode == nil { + c.meta.Unlock() + println("missing node", id, len(c.node), snode) + Debugf("-> %#x %v", hdr.ID, ESTALE) + r.RespondError(ESTALE) + return + } + node = snode.node + } + if id := r.handle(); id != 0 { + if id < HandleID(len(c.handle)) { + shandle = c.handle[uint(id)] + } + if shandle == nil { + println("missing handle", id, len(c.handle), shandle) + c.meta.Unlock() + Debugf("-> %#x %v", hdr.ID, ESTALE) + r.RespondError(ESTALE) + return + } + handle = shandle.handle + } + intr = make(chan struct{}) + if c.req[hdr.ID] != nil { + // This happens with OSXFUSE. Assume it's okay and + // that we'll never see an interrupt for this one. + // Otherwise everything wedges. TODO: Report to OSXFUSE? + intr = nil + } else { + c.req[hdr.ID] = req + } + c.meta.Unlock() + + // Call this before responding. + // After responding is too late: we might get another request + // with the same ID and be very confused. + done := func(resp interface{}) { + Debugf("-> %#x %v", hdr.ID, resp) + c.meta.Lock() + c.req[hdr.ID] = nil + c.meta.Unlock() + } + + switch r := r.(type) { + default: + // Note: To FUSE, ENOSYS means "this server never implements this request." + // It would be inappropriate to return ENOSYS for other operations in this + // switch that might only be unavailable in some contexts, not all. + done(ENOSYS) + r.RespondError(ENOSYS) + + // FS operations. + case *InitRequest: + s := &InitResponse{ + MaxWrite: 4096, + } + if fs, ok := fs.(interface { + Init(*InitRequest, *InitResponse, Intr) Error + }); ok { + if err := fs.Init(r, s, intr); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(s) + r.Respond(s) + + case *StatfsRequest: + s := &StatfsResponse{} + if fs, ok := fs.(interface { + Statfs(*StatfsRequest, *StatfsResponse, Intr) Error + }); ok { + if err := fs.Statfs(r, s, intr); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(s) + r.Respond(s) + + // Node operations. + case *GetattrRequest: + s := &GetattrResponse{} + if n, ok := node.(interface { + Getattr(*GetattrRequest, *GetattrResponse, Intr) Error + }); ok { + if err := n.Getattr(r, s, intr); err != nil { + done(err) + r.RespondError(err) + break + } + } else { + s.AttrValid = 1 * time.Minute + s.Attr = snode.attr() + } + done(s) + r.Respond(s) + + case *SetattrRequest: + s := &SetattrResponse{} + + // Special-case truncation, if no other bits are set + // and the open Handles all have a WriteAll method. + if r.Valid&SetattrSize != 0 && r.Size == 0 { + type writeAll interface { + WriteAll([]byte, Intr) Error + } + switch r.Valid { + case SetattrLockOwner | SetattrSize, SetattrSize: + // Seen on Linux. Handle isn't set. + c.meta.Lock() + for hid := range c.nodeHandles[hdr.Node] { + shandle := c.handle[hid] + if _, ok := shandle.handle.(writeAll); ok { + shandle.trunc = true + } + } + c.meta.Unlock() + case SetattrHandle | SetattrSize: + // Seen on OS X; the Handle is provided. + if _, ok := handle.(writeAll); ok { + shandle.trunc = true + } + } + } + + log.Printf("setattr %v", r) + if n, ok := node.(interface { + Setattr(*SetattrRequest, *SetattrResponse, Intr) Error + }); ok { + if err := n.Setattr(r, s, intr); err != nil { + done(err) + r.RespondError(err) + break + } + done(s) + r.Respond(s) + break + } + + if s.AttrValid == 0 { + s.AttrValid = 1 * time.Minute + } + s.Attr = snode.attr() + done(s) + r.Respond(s) + + case *SymlinkRequest: + s := &SymlinkResponse{} + n, ok := node.(interface { + Symlink(*SymlinkRequest, Intr) (Node, Error) + }) + if !ok { + done(EIO) // XXX or EPERM like Mkdir? + r.RespondError(EIO) + break + } + n2, err := n.Symlink(r, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + c.saveLookup(&s.LookupResponse, snode, r.NewName, n2) + done(s) + r.Respond(s) + + case *ReadlinkRequest: + n, ok := node.(interface { + Readlink(*ReadlinkRequest, Intr) (string, Error) + }) + if !ok { + done(EIO) /// XXX or EPERM? + r.RespondError(EIO) + break + } + target, err := n.Readlink(r, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(target) + r.Respond(target) + + case *LinkRequest: + n, ok := node.(interface { + Link(r *LinkRequest, old Node, intr Intr) (Node, Error) + }) + if !ok { + log.Printf("Node %T doesn't implement fuse Link", node) + done(EIO) /// XXX or EPERM? + r.RespondError(EIO) + break + } + c.meta.Lock() + var oldNode *serveNode + if int(r.OldNode) < len(c.node) { + oldNode = c.node[r.OldNode] + } + c.meta.Unlock() + if oldNode == nil { + log.Printf("In LinkRequest, node %d not found", r.OldNode) + done(EIO) + r.RespondError(EIO) + break + } + n2, err := n.Link(r, oldNode.node, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + s := &LookupResponse{} + c.saveLookup(s, snode, r.NewName, n2) + done(s) + r.Respond(s) + + case *RemoveRequest: + n, ok := node.(interface { + Remove(*RemoveRequest, Intr) Error + }) + if !ok { + done(EIO) /// XXX or EPERM? + r.RespondError(EIO) + break + } + err := n.Remove(r, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(nil) + r.Respond() + + case *AccessRequest: + if n, ok := node.(interface { + Access(*AccessRequest, Intr) Error + }); ok { + if err := n.Access(r, intr); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(r) + r.Respond() + + case *LookupRequest: + var n2 Node + var err Error + s := &LookupResponse{} + if n, ok := node.(interface { + Lookup(string, Intr) (Node, Error) + }); ok { + n2, err = n.Lookup(r.Name, intr) + } else if n, ok := node.(interface { + Lookup(*LookupRequest, *LookupResponse, Intr) (Node, Error) + }); ok { + n2, err = n.Lookup(r, s, intr) + } else { + done(ENOENT) + r.RespondError(ENOENT) + break + } + if err != nil { + done(err) + r.RespondError(err) + break + } + c.saveLookup(s, snode, r.Name, n2) + done(s) + r.Respond(s) + + case *MkdirRequest: + s := &MkdirResponse{} + n, ok := node.(interface { + Mkdir(*MkdirRequest, Intr) (Node, Error) + }) + if !ok { + done(EPERM) + r.RespondError(EPERM) + break + } + n2, err := n.Mkdir(r, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + c.saveLookup(&s.LookupResponse, snode, r.Name, n2) + done(s) + r.Respond(s) + + case *OpenRequest: + s := &OpenResponse{Flags: OpenDirectIO} + var h2 Handle + if n, ok := node.(interface { + Open(*OpenRequest, *OpenResponse, Intr) (Handle, Error) + }); ok { + hh, err := n.Open(r, s, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + h2 = hh + } else { + h2 = node + } + s.Handle, _ = c.saveHandle(h2, hdr.Node) + done(s) + r.Respond(s) + + case *CreateRequest: + n, ok := node.(interface { + Create(*CreateRequest, *CreateResponse, Intr) (Node, Handle, Error) + }) + if !ok { + // If we send back ENOSYS, FUSE will try mknod+open. + done(EPERM) + r.RespondError(EPERM) + break + } + s := &CreateResponse{OpenResponse: OpenResponse{Flags: OpenDirectIO}} + n2, h2, err := n.Create(r, s, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + c.saveLookup(&s.LookupResponse, snode, r.Name, n2) + h, shandle := c.saveHandle(h2, hdr.Node) + s.Handle = h + shandle.trunc = true + done(s) + r.Respond(s) + + case *GetxattrRequest, *SetxattrRequest, *ListxattrRequest, *RemovexattrRequest: + // TODO: Use n. + done(ENOSYS) + r.RespondError(ENOSYS) + + case *ForgetRequest: + n, ok := node.(interface { + Forget() + }) + if ok { + n.Forget() + } + c.dropNode(hdr.Node) + done(r) + r.Respond() + + // Handle operations. + case *ReadRequest: + s := &ReadResponse{Data: make([]byte, 0, r.Size)} + if snode.isDir { + if h, ok := handle.(interface { + ReadDir(Intr) ([]Dirent, Error) + }); ok { + if shandle.readData == nil { + attr := snode.attr() + dirs, err := h.ReadDir(intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + var data []byte + data = AppendDirent(data, Dirent{Inode: attr.Inode, Name: "."}) + data = AppendDirent(data, Dirent{Inode: attr.Inode, Name: ".."}) + for _, dir := range dirs { + if dir.Inode == 0 { + dir.Inode = hash(path.Join(snode.name, dir.Name)) + } + data = AppendDirent(data, dir) + } + shandle.readData = data + } + HandleRead(r, s, shandle.readData) + done(s) + r.Respond(s) + break + } + } else { + if h, ok := handle.(interface { + ReadAll(Intr) ([]byte, Error) + }); ok { + if shandle.readData == nil { + data, err := h.ReadAll(intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + if data == nil { + data = []byte{} + } + shandle.readData = data + } + HandleRead(r, s, shandle.readData) + done(s) + r.Respond(s) + break + } + } + h, ok := handle.(interface { + Read(*ReadRequest, *ReadResponse, Intr) Error + }) + if !ok { + fmt.Printf("NO READ FOR %T\n", handle) + done(EIO) + r.RespondError(EIO) + break + } + if err := h.Read(r, s, intr); err != nil { + done(err) + r.RespondError(err) + break + } + done(s) + r.Respond(s) + + case *WriteRequest: + s := &WriteResponse{} + if shandle.trunc && r.Offset == int64(len(shandle.writeData)) { + shandle.writeData = append(shandle.writeData, r.Data...) + s.Size = len(r.Data) + done(s) + r.Respond(s) + break + } + if h, ok := handle.(interface { + Write(*WriteRequest, *WriteResponse, Intr) Error + }); ok { + if err := h.Write(r, s, intr); err != nil { + done(err) + r.RespondError(err) + break + } + done(s) + r.Respond(s) + break + } + println("NO WRITE") + done(EIO) + r.RespondError(EIO) + + case *FlushRequest: + if shandle.trunc { + h := handle.(interface { + WriteAll([]byte, Intr) Error + }) + if err := h.WriteAll(shandle.writeData, intr); err != nil { + done(err) + r.RespondError(err) + break + } + shandle.writeData = nil + shandle.trunc = false + } + if h, ok := handle.(interface { + Flush(*FlushRequest, Intr) Error + }); ok { + if err := h.Flush(r, intr); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(nil) + r.Respond() + + case *ReleaseRequest: + // No matter what, release the handle. + c.dropHandle(r.handle()) + if h, ok := handle.(interface { + Release(*ReleaseRequest, Intr) Error + }); ok { + if err := h.Release(r, intr); err != nil { + done(err) + r.RespondError(err) + break + } + } + done(nil) + r.Respond() + + case *DestroyRequest: + fs, ok := fs.(interface { + Destroy() + }) + if ok { + fs.Destroy() + } + done(nil) + r.Respond() + + case *RenameRequest: + c.meta.Lock() + var newDirNode *serveNode + if int(r.NewDir) < len(c.node) { + newDirNode = c.node[r.NewDir] + } + c.meta.Unlock() + if newDirNode == nil { + println("RENAME NEW DIR NODE NOT FOUND") + done(EIO) + r.RespondError(EIO) + break + } + n, ok := node.(interface { + Rename(r *RenameRequest, newDir Node, intr Intr) Error + }) + if !ok { + log.Printf("Node %T missing Rename method", node) + done(EIO) // XXX or EPERM like Mkdir? + r.RespondError(EIO) + break + } + err := n.Rename(r, newDirNode.node, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(nil) + r.Respond() + + case *MknodRequest: + n, ok := node.(interface { + Mknod(r *MknodRequest, intr Intr) (Node, Error) + }) + if !ok { + log.Printf("Node %T missing Mknod method", node) + done(EIO) + r.RespondError(EIO) + break + } + n2, err := n.Mknod(r, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + s := &LookupResponse{} + c.saveLookup(s, snode, r.Name, n2) + done(s) + r.Respond(s) + + case *FsyncRequest: + n, ok := node.(interface { + Fsync(r *FsyncRequest, intr Intr) Error + }) + if !ok { + log.Printf("Node %T missing Fsync method", node) + done(EIO) + r.RespondError(EIO) + break + } + err := n.Fsync(r, intr) + if err != nil { + done(err) + r.RespondError(err) + break + } + done(nil) + r.Respond() + + /* case *FsyncdirRequest: + done(ENOSYS) + r.RespondError(ENOSYS) + + case *GetlkRequest, *SetlkRequest, *SetlkwRequest: + done(ENOSYS) + r.RespondError(ENOSYS) + + // One of a kind. + case *InterruptRequest: + c.meta.Lock() + ireq := c.req[r.OldID] + if ireq != nil && ireq.Intr != nil { + close(ireq.Intr) + ireq.Intr = nil + } + c.meta.Unlock() + done(nil) + r.Respond() + + case *BmapRequest: + done(ENOSYS) + r.RespondError(ENOSYS) + + case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest: + done(ENOSYS) + r.RespondError(ENOSYS) + */ + } +} + +func (c *Conn) saveLookup(s *LookupResponse, snode *serveNode, elem string, n2 Node) { + name := path.Join(snode.name, elem) + var sn *serveNode + s.Node, s.Generation, sn = c.saveNode(name, n2) + if s.EntryValid == 0 { + s.EntryValid = 1 * time.Minute + } + if s.AttrValid == 0 { + s.AttrValid = 1 * time.Minute + } + s.Attr = sn.attr() +} + +// HandleRead handles a read request assuming that data is the entire file content. +// It adjusts the amount returned in resp according to req.Offset and req.Size. +func HandleRead(req *ReadRequest, resp *ReadResponse, data []byte) { + if req.Offset >= int64(len(data)) { + data = nil + } else { + data = data[req.Offset:] + } + if len(data) > req.Size { + data = data[:req.Size] + } + n := copy(resp.Data[:req.Size], data) + resp.Data = resp.Data[:n] +} + +// DataHandle returns a read-only Handle that satisfies reads +// using the given data. +func DataHandle(data []byte) Handle { + return &dataHandle{data} +} + +type dataHandle struct { + data []byte +} + +func (d *dataHandle) Read(intr Intr) ([]byte, Error) { + return d.data, nil +} diff --git a/vendor/github.com/mattermost/rsc/fuse/tree.go b/vendor/github.com/mattermost/rsc/fuse/tree.go new file mode 100644 index 000000000..fec0a748f --- /dev/null +++ b/vendor/github.com/mattermost/rsc/fuse/tree.go @@ -0,0 +1,93 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// FUSE directory tree, for servers that wish to use it with the service loop. + +package fuse + +import ( + "os" + pathpkg "path" + "strings" +) + +// A Tree implements a basic directory tree for FUSE. +type Tree struct { + tree +} + +func (t *Tree) Root() (Node, Error) { + return &t.tree, nil +} + +// Add adds the path to the tree, resolving to the given node. +// If path or a prefix of path has already been added to the tree, +// Add panics. +func (t *Tree) Add(path string, node Node) { + path = pathpkg.Clean("/" + path)[1:] + elems := strings.Split(path, "/") + dir := Node(&t.tree) + for i, elem := range elems { + dt, ok := dir.(*tree) + if !ok { + panic("fuse: Tree.Add for " + strings.Join(elems[:i], "/") + " and " + path) + } + n := dt.lookup(elem) + if n != nil { + if i+1 == len(elems) { + panic("fuse: Tree.Add for " + path + " conflicts with " + elem) + } + dir = n + } else { + if i+1 == len(elems) { + dt.add(elem, node) + } else { + dir = &tree{} + dt.add(elem, dir) + } + } + } +} + +type treeDir struct { + name string + node Node +} + +type tree struct { + dir []treeDir +} + +func (t *tree) lookup(name string) Node { + for _, d := range t.dir { + if d.name == name { + return d.node + } + } + return nil +} + +func (t *tree) add(name string, n Node) { + t.dir = append(t.dir, treeDir{name, n}) +} + +func (t *tree) Attr() Attr { + return Attr{Mode: os.ModeDir | 0555} +} + +func (t *tree) Lookup(name string, intr Intr) (Node, Error) { + n := t.lookup(name) + if n != nil { + return n, nil + } + return nil, ENOENT +} + +func (t *tree) ReadDir(intr Intr) ([]Dirent, Error) { + var out []Dirent + for _, d := range t.dir { + out = append(out, Dirent{Name: d.name}) + } + return out, nil +} -- cgit v1.2.3-1-g7c22