diff --git a/internal/fuseshim/buffer.go b/internal/fuseshim/buffer.go deleted file mode 100644 index d2999bc..0000000 --- a/internal/fuseshim/buffer.go +++ /dev/null @@ -1,39 +0,0 @@ -package fuseshim - -import ( - "unsafe" - - "github.com/jacobsa/fuse/internal/fusekernel" -) - -// Buffer provides a mechanism for constructing a message from multiple -// segments. -type Buffer []byte - -// alloc allocates size bytes and returns a pointer to the new -// segment. -func (w *Buffer) Alloc(size uintptr) unsafe.Pointer { - s := int(size) - if len(*w)+s > cap(*w) { - old := *w - *w = make([]byte, len(*w), 2*cap(*w)+s) - copy(*w, old) - } - l := len(*w) - *w = (*w)[:l+s] - return unsafe.Pointer(&(*w)[l]) -} - -// reset clears out the contents of the buffer. -func (w *Buffer) reset() { - for i := range (*w)[:cap(*w)] { - (*w)[i] = 0 - } - *w = (*w)[:0] -} - -func NewBuffer(extra uintptr) (buf Buffer) { - const hdrSize = unsafe.Sizeof(fusekernel.OutHeader{}) - buf = make(Buffer, hdrSize, hdrSize+extra) - return -} diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go deleted file mode 100644 index 8b2d3a1..0000000 --- a/internal/fuseshim/fuse.go +++ /dev/null @@ -1,2217 +0,0 @@ -// See the file LICENSE for copyright and licensing information. - -// 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 Linux, OS X, and FreeBSD. -// -// 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 fs.Serve 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 fs.Serve approach. -// -// Service Methods -// -// The required and optional methods for the FS, Node, and Handle interfaces -// have the general form -// -// Op(ctx context.Context, req *OpRequest, resp *OpResponse) 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. -// -// The operation must not hold on to the request or response, -// including any []byte fields such as WriteRequest.Data or -// SetxattrRequest.Xattr. -// -// Errors -// -// Operations can return errors. The FUSE interface can only -// communicate POSIX errno error numbers to file system clients, the -// message is not visible to file system clients. The returned error -// can implement ErrorNumber to control the errno returned. Without -// ErrorNumber, a generic errno (EIO) is returned. -// -// Error messages will be visible in the debug log as part of the -// response. -// -// 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 context will be cancelled. -// Blocking operations should select on a receive from ctx.Done() 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, supporting -// cancellation is not necessary. -// -// 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. The kernel FUSE layer normally prevents other -// users from accessing the FUSE file system (to change this, see -// AllowOther, AllowRoot), but does not enforce access modes (to -// change this, see DefaultPermissions). -// -// Mount Options -// -// Behavior and metadata of the mounted file system can be changed by -// passing MountOption values to Mount. -// -package fuseshim - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "sync" - "syscall" - "time" - "unsafe" - - "github.com/jacobsa/fuse/internal/fusekernel" -) - -// A Conn represents a connection to a mounted FUSE file system. -type Conn struct { - // Ready is closed when the mount is complete or has failed. - Ready <-chan struct{} - - // MountError stores any error from the mount process. Only valid - // after Ready is closed. - MountError error - - // File handle for kernel communication. Only safe to access if - // Rio or Wio is held. - Dev *os.File - Wio sync.RWMutex - Rio sync.RWMutex - - // Protocol version negotiated with InitRequest/InitResponse. - proto fusekernel.Protocol -} - -// Mount mounts a new FUSE connection on the named directory -// and returns a connection for reading and writing FUSE messages. -// -// After a successful return, caller must call Close to free -// resources. -// -// Even on successful return, the new mount is not guaranteed to be -// visible until after Conn.Ready is closed. See Conn.MountError for -// possible errors. Incoming requests on Conn must be served to make -// progress. -func Mount(dir string, options ...MountOption) (*Conn, error) { - conf := mountConfig{ - options: make(map[string]string), - } - for _, option := range options { - if err := option(&conf); err != nil { - return nil, err - } - } - - ready := make(chan struct{}, 1) - c := &Conn{ - Ready: ready, - } - f, err := mount(dir, &conf, ready, &c.MountError) - if err != nil { - return nil, err - } - c.Dev = f - - if err := InitMount(c, conf.maxReadahead, conf.initFlags); err != nil { - c.Close() - return nil, err - } - - return c, nil -} - -type OldVersionError struct { - Kernel fusekernel.Protocol - LibraryMin fusekernel.Protocol -} - -func (e *OldVersionError) Error() string { - return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin) -} - -func InitMount( - c *Conn, - maxReadahead uint32, - initFlags fusekernel.InitFlags) error { - req, err := c.ReadRequest() - if err != nil { - if err == io.EOF { - return fmt.Errorf("missing init, got EOF") - } - return err - } - r, ok := req.(*InitRequest) - if !ok { - return fmt.Errorf("missing init, got: %T", req) - } - - min := fusekernel.Protocol{fusekernel.ProtoVersionMinMajor, fusekernel.ProtoVersionMinMinor} - if r.Kernel.LT(min) { - req.RespondError(Errno(syscall.EPROTO)) - c.Close() - return &OldVersionError{ - Kernel: r.Kernel, - LibraryMin: min, - } - } - - proto := fusekernel.Protocol{fusekernel.ProtoVersionMaxMajor, fusekernel.ProtoVersionMaxMinor} - if r.Kernel.LT(proto) { - // Kernel doesn't support the latest version we have. - proto = r.Kernel - } - c.proto = proto - - s := &InitResponse{ - Library: proto, - MaxReadahead: maxReadahead, - MaxWrite: maxWrite, - Flags: fusekernel.InitBigWrites | initFlags, - } - r.Respond(s) - return 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 -} - -// 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 = fusekernel.RootID - -// A Header describes the basic information sent in every request. -type Header struct { - Conn *Conn `json:"-"` // 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 - - // for returning to reqPool - msg *Message -} - -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) noResponse() { - putMessage(h.msg) -} - -func (h *Header) respond(msg []byte) { - out := (*fusekernel.OutHeader)(unsafe.Pointer(&msg[0])) - out.Unique = uint64(h.ID) - h.Conn.respond(msg) - putMessage(h.msg) -} - -// An ErrorNumber is an error with a specific error number. -// -// Operations may return an error value that implements ErrorNumber to -// control what specific error number (errno) to return. -type ErrorNumber interface { - // Errno returns the the error number (errno) for this error. - Errno() Errno -} - -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) - - // EINTR indicates request was interrupted by an InterruptRequest. - // See also fs.Intr. - EINTR = Errno(syscall.EINTR) - - ERANGE = Errno(syscall.ERANGE) - ENOTSUP = Errno(syscall.ENOTSUP) - EEXIST = Errno(syscall.EEXIST) -) - -// DefaultErrno is the errno used when error returned does not -// implement ErrorNumber. -const DefaultErrno = EIO - -var errnoNames = map[Errno]string{ - ENOSYS: "ENOSYS", - ESTALE: "ESTALE", - ENOENT: "ENOENT", - EIO: "EIO", - EPERM: "EPERM", - EINTR: "EINTR", - EEXIST: "EEXIST", -} - -// Errno implements Error and ErrorNumber using a syscall.Errno. -type Errno syscall.Errno - -var _ = ErrorNumber(Errno(0)) -var _ = error(Errno(0)) - -func (e Errno) Errno() Errno { - return e -} - -func (e Errno) String() string { - return syscall.Errno(e).Error() -} - -func (e Errno) Error() string { - return syscall.Errno(e).Error() -} - -// ErrnoName returns the short non-numeric identifier for this errno. -// For example, "EIO". -func (e Errno) ErrnoName() string { - s := errnoNames[e] - if s == "" { - s = fmt.Sprint(e.Errno()) - } - return s -} - -func (e Errno) MarshalText() ([]byte, error) { - s := e.ErrnoName() - return []byte(s), nil -} - -func (h *Header) RespondError(err error) { - errno := DefaultErrno - if ferr, ok := err.(ErrorNumber); ok { - errno = ferr.Errno() - } - // FUSE uses negative errors! - // TODO: File bug report against OSXFUSE: positive error causes kernel panic. - buf := NewBuffer(0) - hOut := (*fusekernel.OutHeader)(unsafe.Pointer(&buf[0])) - hOut.Error = -int32(errno) - h.respond(buf) -} - -// All requests read from the kernel, without data, are shorter than -// this. -var maxRequestSize = syscall.Getpagesize() -var bufSize = maxRequestSize + maxWrite - -// reqPool is a pool of messages. -// -// Lifetime of a logical message is from getMessage to putMessage. -// getMessage is called by ReadRequest. putMessage is called by -// Conn.ReadRequest, Request.Respond, or Request.RespondError. -// -// Messages in the pool are guaranteed to have conn and off zeroed, -// buf allocated and len==bufSize, and Hdr set. -var reqPool struct { - Mu sync.Mutex - Freelist []*Message -} - -func allocMessage() *Message { - m := &Message{buf: make([]byte, bufSize)} - m.Hdr = (*fusekernel.InHeader)(unsafe.Pointer(&m.buf[0])) - return m -} - -func getMessage(c *Conn) (m *Message) { - reqPool.Mu.Lock() - l := len(reqPool.Freelist) - if l != 0 { - m = reqPool.Freelist[l-1] - reqPool.Freelist = reqPool.Freelist[:l-1] - } - - reqPool.Mu.Unlock() - - if m == nil { - m = allocMessage() - } - - m.conn = c - return m -} - -func putMessage(m *Message) { - m.buf = m.buf[:bufSize] - m.conn = nil - m.off = 0 - - reqPool.Mu.Lock() - reqPool.Freelist = append(reqPool.Freelist, m) - reqPool.Mu.Unlock() -} - -// a message represents the bytes of a single FUSE message -type Message struct { - conn *Conn - buf []byte // all bytes - Hdr *fusekernel.InHeader // header - off int // offset for reading additional fields -} - -func (m *Message) Len() uintptr { - return uintptr(len(m.buf) - m.off) -} - -func (m *Message) Data() (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, - - msg: m, - } -} - -// Destroy the message, releasing its resources. The message must not be used -// further. -func (m *Message) Destroy() { - putMessage(m) -} - -// 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 -} - -type noOpcode struct { - Opcode uint32 -} - -func (m noOpcode) String() string { - return fmt.Sprintf("No opcode %v", m.Opcode) -} - -type malformedMessage struct { -} - -func (malformedMessage) String() string { - return "malformed message" -} - -// Close closes the FUSE connection. -func (c *Conn) Close() error { - c.Wio.Lock() - defer c.Wio.Unlock() - c.Rio.Lock() - defer c.Rio.Unlock() - return c.Dev.Close() -} - -// caller must hold Wio or Rio -func (c *Conn) fd() int { - return int(c.Dev.Fd()) -} - -func (c *Conn) Protocol() fusekernel.Protocol { - return c.proto -} - -// Read and sanity check a message from the kernel. Return io.EOF when the -// kernel has hung up. The offset will point to the limit of the header. -// -// The message must later be disposed of by calling m.Destroy. -func (c *Conn) ReadMessage() (m *Message, err error) { - m = getMessage(c) -loop: - c.Rio.RLock() - n, err := syscall.Read(c.fd(), m.buf) - c.Rio.RUnlock() - if err == syscall.EINTR { - // OSXFUSE sends EINTR to userspace when a request interrupt - // completed before it got sent to userspace? - goto loop - } - if err != nil && err != syscall.ENODEV { - m.Destroy() - return nil, err - } - if n <= 0 { - m.Destroy() - return nil, io.EOF - } - m.buf = m.buf[:n] - - if n < fusekernel.InHeaderSize { - m.Destroy() - 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 == fusekernel.InHeaderSize+fusekernel.InitInSize && m.Hdr.Opcode == fusekernel.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(fusekernel.WriteIn{})) && m.Hdr.Opcode == fusekernel.OpWrite { - m.Hdr.Len = uint32(n) - } - - if m.Hdr.Len != uint32(n) { - // prepare error message before returning m to pool - err := fmt.Errorf("fuse: read %d opcode %d but expected %d", n, m.Hdr.Opcode, m.Hdr.Len) - m.Destroy() - return nil, err - } - - m.off = fusekernel.InHeaderSize - return -} - -// ReadRequest returns the next FUSE request from the kernel. -// -// Caller must call either Request.Respond or Request.RespondError in -// a reasonable time. Caller must not retain Request after that call. -func (c *Conn) ReadRequest() (Request, error) { - // Read a message. - m, err := c.ReadMessage() - if err != nil { - return nil, err - } - - // Convert to data structures. - // Do not trust kernel to hand us well-formed data. - var req Request - switch m.Hdr.Opcode { - default: - goto unrecognized - - case fusekernel.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 fusekernel.OpForget: - in := (*fusekernel.ForgetIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &ForgetRequest{ - Header: m.Header(), - N: in.Nlookup, - } - - case fusekernel.OpGetattr: - switch { - case c.proto.LT(fusekernel.Protocol{7, 9}): - req = &GetattrRequest{ - Header: m.Header(), - } - - default: - in := (*fusekernel.GetattrIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &GetattrRequest{ - Header: m.Header(), - Flags: fusekernel.GetattrFlags(in.GetattrFlags), - Handle: HandleID(in.Fh), - } - } - - case fusekernel.OpSetattr: - in := (*fusekernel.SetattrIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &SetattrRequest{ - Header: m.Header(), - Valid: fusekernel.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 fusekernel.OpReadlink: - if len(m.Bytes()) > 0 { - goto corrupt - } - req = &ReadlinkRequest{ - Header: m.Header(), - } - - case fusekernel.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 fusekernel.OpLink: - in := (*fusekernel.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 fusekernel.OpMknod: - size := fusekernel.MknodInSize(c.proto) - if m.Len() < size { - goto corrupt - } - in := (*fusekernel.MknodIn)(m.Data()) - name := m.Bytes()[size:] - if len(name) < 2 || name[len(name)-1] != '\x00' { - goto corrupt - } - name = name[:len(name)-1] - r := &MknodRequest{ - Header: m.Header(), - Mode: FileMode(in.Mode), - Rdev: in.Rdev, - Name: string(name), - } - if c.proto.GE(fusekernel.Protocol{7, 12}) { - r.Umask = FileMode(in.Umask) & os.ModePerm - } - req = r - - case fusekernel.OpMkdir: - size := fusekernel.MkdirInSize(c.proto) - if m.Len() < size { - goto corrupt - } - in := (*fusekernel.MkdirIn)(m.Data()) - name := m.Bytes()[size:] - i := bytes.IndexByte(name, '\x00') - if i < 0 { - goto corrupt - } - r := &MkdirRequest{ - Header: m.Header(), - Name: string(name[:i]), - // observed on Linux: mkdirIn.Mode & syscall.S_IFMT == 0, - // and this causes FileMode to go into it's "no idea" - // code branch; enforce type to directory - Mode: FileMode((in.Mode &^ syscall.S_IFMT) | syscall.S_IFDIR), - } - if c.proto.GE(fusekernel.Protocol{7, 12}) { - r.Umask = FileMode(in.Umask) & os.ModePerm - } - req = r - - case fusekernel.OpUnlink, fusekernel.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 == fusekernel.OpRmdir, - } - - case fusekernel.OpRename: - in := (*fusekernel.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]) - req = &RenameRequest{ - Header: m.Header(), - NewDir: newDirNodeID, - OldName: oldName, - NewName: newName, - } - - case fusekernel.OpOpendir, fusekernel.OpOpen: - in := (*fusekernel.OpenIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &OpenRequest{ - Header: m.Header(), - Dir: m.Hdr.Opcode == fusekernel.OpOpendir, - Flags: fusekernel.OpenFlags(in.Flags), - } - - case fusekernel.OpRead, fusekernel.OpReaddir: - in := (*fusekernel.ReadIn)(m.Data()) - if m.Len() < fusekernel.ReadInSize(c.proto) { - goto corrupt - } - r := &ReadRequest{ - Header: m.Header(), - Dir: m.Hdr.Opcode == fusekernel.OpReaddir, - Handle: HandleID(in.Fh), - Offset: int64(in.Offset), - Size: int(in.Size), - } - if c.proto.GE(fusekernel.Protocol{7, 9}) { - r.Flags = fusekernel.ReadFlags(in.ReadFlags) - r.LockOwner = in.LockOwner - r.FileFlags = fusekernel.OpenFlags(in.Flags) - } - req = r - - case fusekernel.OpWrite: - in := (*fusekernel.WriteIn)(m.Data()) - if m.Len() < fusekernel.WriteInSize(c.proto) { - goto corrupt - } - r := &WriteRequest{ - Header: m.Header(), - Handle: HandleID(in.Fh), - Offset: int64(in.Offset), - Flags: fusekernel.WriteFlags(in.WriteFlags), - } - if c.proto.GE(fusekernel.Protocol{7, 9}) { - r.LockOwner = in.LockOwner - r.FileFlags = fusekernel.OpenFlags(in.Flags) - } - buf := m.Bytes()[fusekernel.WriteInSize(c.proto):] - if uint32(len(buf)) < in.Size { - goto corrupt - } - r.Data = buf - req = r - - case fusekernel.OpStatfs: - req = &StatfsRequest{ - Header: m.Header(), - } - - case fusekernel.OpRelease, fusekernel.OpReleasedir: - in := (*fusekernel.ReleaseIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &ReleaseRequest{ - Header: m.Header(), - Dir: m.Hdr.Opcode == fusekernel.OpReleasedir, - Handle: HandleID(in.Fh), - Flags: fusekernel.OpenFlags(in.Flags), - ReleaseFlags: fusekernel.ReleaseFlags(in.ReleaseFlags), - LockOwner: in.LockOwner, - } - - case fusekernel.OpFsync, fusekernel.OpFsyncdir: - in := (*fusekernel.FsyncIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &FsyncRequest{ - Dir: m.Hdr.Opcode == fusekernel.OpFsyncdir, - Header: m.Header(), - Handle: HandleID(in.Fh), - Flags: in.FsyncFlags, - } - - case fusekernel.OpSetxattr: - in := (*fusekernel.SetxattrIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - m.off += int(unsafe.Sizeof(*in)) - name := m.Bytes() - i := bytes.IndexByte(name, '\x00') - if i < 0 { - goto corrupt - } - xattr := name[i+1:] - if uint32(len(xattr)) < in.Size { - goto corrupt - } - xattr = xattr[:in.Size] - req = &SetxattrRequest{ - Header: m.Header(), - Flags: in.Flags, - Position: in.GetPosition(), - Name: string(name[:i]), - Xattr: xattr, - } - - case fusekernel.OpGetxattr: - in := (*fusekernel.GetxattrIn)(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 = &GetxattrRequest{ - Header: m.Header(), - Name: string(name[:i]), - Size: in.Size, - Position: in.GetPosition(), - } - - case fusekernel.OpListxattr: - in := (*fusekernel.GetxattrIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &ListxattrRequest{ - Header: m.Header(), - Size: in.Size, - Position: in.GetPosition(), - } - - case fusekernel.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 fusekernel.OpFlush: - in := (*fusekernel.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 fusekernel.OpInit: - in := (*fusekernel.InitIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &InitRequest{ - Header: m.Header(), - Kernel: fusekernel.Protocol{in.Major, in.Minor}, - MaxReadahead: in.MaxReadahead, - Flags: fusekernel.InitFlags(in.Flags), - } - - case fusekernel.OpGetlk: - panic("fusekernel.OpGetlk") - case fusekernel.OpSetlk: - panic("fusekernel.OpSetlk") - case fusekernel.OpSetlkw: - panic("fusekernel.OpSetlkw") - - case fusekernel.OpAccess: - in := (*fusekernel.AccessIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &AccessRequest{ - Header: m.Header(), - Mask: in.Mask, - } - - case fusekernel.OpCreate: - size := fusekernel.CreateInSize(c.proto) - if m.Len() < size { - goto corrupt - } - in := (*fusekernel.CreateIn)(m.Data()) - name := m.Bytes()[size:] - i := bytes.IndexByte(name, '\x00') - if i < 0 { - goto corrupt - } - r := &CreateRequest{ - Header: m.Header(), - Flags: fusekernel.OpenFlags(in.Flags), - Mode: FileMode(in.Mode), - Name: string(name[:i]), - } - if c.proto.GE(fusekernel.Protocol{7, 12}) { - r.Umask = FileMode(in.Umask) & os.ModePerm - } - req = r - - case fusekernel.OpInterrupt: - in := (*fusekernel.InterruptIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { - goto corrupt - } - req = &InterruptRequest{ - Header: m.Header(), - IntrID: RequestID(in.Unique), - } - - case fusekernel.OpBmap: - panic("fusekernel.OpBmap") - - case fusekernel.OpDestroy: - req = &DestroyRequest{ - Header: m.Header(), - } - - // OS X - case fusekernel.OpSetvolname: - panic("fusekernel.OpSetvolname") - case fusekernel.OpGetxtimes: - panic("fusekernel.OpGetxtimes") - case fusekernel.OpExchange: - panic("fusekernel.OpExchange") - } - - return req, nil - -corrupt: - m.Destroy() - 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 -} - -type bugShortKernelWrite struct { - Written int64 - Length int64 - Error string - Stack string -} - -func (b bugShortKernelWrite) String() string { - return fmt.Sprintf("short kernel write: written=%d/%d error=%q stack=\n%s", b.Written, b.Length, b.Error, b.Stack) -} - -type bugKernelWriteError struct { - Error string - Stack string -} - -func (b bugKernelWriteError) String() string { - return fmt.Sprintf("kernel write error: error=%q stack=\n%s", b.Error, b.Stack) -} - -// safe to call even with nil error -func errorString(err error) string { - if err == nil { - return "" - } - return err.Error() -} - -func (c *Conn) writeToKernel(msg []byte) error { - out := (*fusekernel.OutHeader)(unsafe.Pointer(&msg[0])) - out.Len = uint32(len(msg)) - return c.WriteToKernel(msg) -} - -func (c *Conn) WriteToKernel(msg []byte) error { - c.Wio.RLock() - defer c.Wio.RUnlock() - _, err := syscall.Write(c.fd(), msg) - return err -} - -func (c *Conn) respond(msg []byte) { - c.writeToKernel(msg) -} - -type notCachedError struct{} - -func (notCachedError) Error() string { - return "node not cached" -} - -var _ ErrorNumber = notCachedError{} - -func (notCachedError) Errno() Errno { - // Behave just like if the original syscall.ENOENT had been passed - // straight through. - return ENOENT -} - -var ( - ErrNotCached = notCachedError{} -) - -// sendInvalidate sends an invalidate notification to kernel. -// -// A returned ENOENT is translated to a friendlier error. -func (c *Conn) sendInvalidate(msg []byte) error { - switch err := c.writeToKernel(msg); err { - case syscall.ENOENT: - return ErrNotCached - default: - return err - } -} - -// InvalidateNode invalidates the kernel cache of the attributes and a -// range of the data of a node. -// -// Giving offset 0 and size -1 means all data. To invalidate just the -// attributes, give offset 0 and size 0. -// -// Returns ErrNotCached if the kernel is not currently caching the -// node. -func (c *Conn) InvalidateNode(nodeID NodeID, off int64, size int64) error { - buf := NewBuffer(unsafe.Sizeof(fusekernel.NotifyInvalInodeOut{})) - h := (*fusekernel.OutHeader)(unsafe.Pointer(&buf[0])) - // h.Unique is 0 - h.Error = fusekernel.NotifyCodeInvalInode - out := (*fusekernel.NotifyInvalInodeOut)(buf.Alloc(unsafe.Sizeof(fusekernel.NotifyInvalInodeOut{}))) - out.Ino = uint64(nodeID) - out.Off = off - out.Len = size - return c.sendInvalidate(buf) -} - -// InvalidateEntry invalidates the kernel cache of the directory entry -// identified by parent directory node ID and entry basename. -// -// Kernel may or may not cache directory listings. To invalidate -// those, use InvalidateNode to invalidate all of the data for a -// directory. (As of 2015-06, Linux FUSE does not cache directory -// listings.) -// -// Returns ErrNotCached if the kernel is not currently caching the -// node. -func (c *Conn) InvalidateEntry(parent NodeID, name string) error { - const maxUint32 = ^uint32(0) - if uint64(len(name)) > uint64(maxUint32) { - // very unlikely, but we don't want to silently truncate - return syscall.ENAMETOOLONG - } - buf := NewBuffer(unsafe.Sizeof(fusekernel.NotifyInvalEntryOut{}) + uintptr(len(name)) + 1) - h := (*fusekernel.OutHeader)(unsafe.Pointer(&buf[0])) - // h.Unique is 0 - h.Error = fusekernel.NotifyCodeInvalEntry - out := (*fusekernel.NotifyInvalEntryOut)(buf.Alloc(unsafe.Sizeof(fusekernel.NotifyInvalEntryOut{}))) - out.Parent = uint64(parent) - out.Namelen = uint32(len(name)) - buf = append(buf, name...) - buf = append(buf, '\x00') - return c.sendInvalidate(buf) -} - -// An InitRequest is the first request sent on a FUSE file system. -type InitRequest struct { - Header `json:"-"` - Kernel fusekernel.Protocol - // Maximum readahead in bytes that the kernel plans to use. - MaxReadahead uint32 - Flags fusekernel.InitFlags -} - -var _ = Request(&InitRequest{}) - -func (r *InitRequest) String() string { - return fmt.Sprintf("Init [%s] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags) -} - -// An InitResponse is the response to an InitRequest. -type InitResponse struct { - Library fusekernel.Protocol - // Maximum readahead in bytes that the kernel can use. Ignored if - // greater than InitRequest.MaxReadahead. - MaxReadahead uint32 - Flags fusekernel.InitFlags - // Maximum size of a single write operation. - // Linux enforces a minimum of 4 KiB. - 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) { - buf := NewBuffer(unsafe.Sizeof(fusekernel.InitOut{})) - out := (*fusekernel.InitOut)(buf.Alloc(unsafe.Sizeof(fusekernel.InitOut{}))) - out.Major = resp.Library.Major - out.Minor = resp.Library.Minor - out.MaxReadahead = resp.MaxReadahead - out.Flags = uint32(resp.Flags) - out.MaxWrite = resp.MaxWrite - - // MaxWrite larger than our receive buffer would just lead to - // errors on large writes. - if out.MaxWrite > maxWrite { - out.MaxWrite = maxWrite - } - r.respond(buf) -} - -// A StatfsRequest requests information about the mounted file system. -type StatfsRequest struct { - Header `json:"-"` -} - -var _ = Request(&StatfsRequest{}) - -func (r *StatfsRequest) String() string { - return fmt.Sprintf("Statfs [%s]", &r.Header) -} - -// Respond replies to the request with the given response. -func (r *StatfsRequest) Respond(resp *StatfsResponse) { - buf := NewBuffer(unsafe.Sizeof(fusekernel.StatfsOut{})) - out := (*fusekernel.StatfsOut)(buf.Alloc(unsafe.Sizeof(fusekernel.StatfsOut{}))) - out.St = fusekernel.Kstatfs{ - Blocks: resp.Blocks, - Bfree: resp.Bfree, - Bavail: resp.Bavail, - Files: resp.Files, - Bsize: resp.Bsize, - Namelen: resp.Namelen, - Frsize: resp.Frsize, - } - r.respond(buf) -} - -// 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 // Fragment size, smallest addressable data size in the file system. -} - -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 `json:"-"` - Mask uint32 -} - -var _ = Request(&AccessRequest{}) - -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() { - buf := NewBuffer(0) - r.respond(buf) -} - -// An Attr is the metadata for a single file or directory. -type Attr struct { - Valid time.Duration // how long Attr can be cached - - Inode uint64 // inode number - Size uint64 // size in bytes - Blocks uint64 // size in 512-byte units - 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) - BlockSize uint32 // preferred blocksize for filesystem I/O -} - -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 *fusekernel.Attr, proto fusekernel.Protocol) { - 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 - out.Uid = a.Uid - out.Gid = a.Gid - out.Rdev = a.Rdev - out.SetFlags(a.Flags) - if proto.GE(fusekernel.Protocol{7, 9}) { - out.Blksize = a.BlockSize - } - - return -} - -// A GetattrRequest asks for the metadata for the file denoted by r.Node. -type GetattrRequest struct { - Header `json:"-"` - Flags fusekernel.GetattrFlags - Handle HandleID -} - -var _ = Request(&GetattrRequest{}) - -func (r *GetattrRequest) String() string { - return fmt.Sprintf("Getattr [%s] %#x fl=%v", &r.Header, r.Handle, r.Flags) -} - -// Respond replies to the request with the given response. -func (r *GetattrRequest) Respond(resp *GetattrResponse) { - size := fusekernel.AttrOutSize(r.Header.Conn.proto) - buf := NewBuffer(size) - out := (*fusekernel.AttrOut)(buf.Alloc(size)) - out.AttrValid = uint64(resp.Attr.Valid / time.Second) - out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) - resp.Attr.attr(&out.Attr, r.Header.Conn.proto) - r.respond(buf) -} - -// A GetattrResponse is the response to a GetattrRequest. -type GetattrResponse struct { - 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 `json:"-"` - - // Maximum size to return. - Size uint32 - - // Name of the attribute requested. - Name string - - // Offset within extended attributes. - // - // Only valid for OS X, and then only with the resource fork - // attribute. - Position uint32 -} - -var _ = Request(&GetxattrRequest{}) - -func (r *GetxattrRequest) String() string { - return fmt.Sprintf("Getxattr [%s] %q %d @%d", &r.Header, r.Name, r.Size, r.Position) -} - -// Respond replies to the request with the given response. -func (r *GetxattrRequest) Respond(resp *GetxattrResponse) { - if r.Size == 0 { - buf := NewBuffer(unsafe.Sizeof(fusekernel.GetxattrOut{})) - out := (*fusekernel.GetxattrOut)(buf.Alloc(unsafe.Sizeof(fusekernel.GetxattrOut{}))) - out.Size = uint32(len(resp.Xattr)) - r.respond(buf) - } else { - buf := NewBuffer(uintptr(len(resp.Xattr))) - buf = append(buf, resp.Xattr...) - r.respond(buf) - } -} - -// 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 `json:"-"` - Size uint32 // maximum size to return - Position uint32 // offset within attribute list -} - -var _ = Request(&ListxattrRequest{}) - -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) { - if r.Size == 0 { - buf := NewBuffer(unsafe.Sizeof(fusekernel.GetxattrOut{})) - out := (*fusekernel.GetxattrOut)(buf.Alloc(unsafe.Sizeof(fusekernel.GetxattrOut{}))) - out.Size = uint32(len(resp.Xattr)) - r.respond(buf) - } else { - buf := NewBuffer(uintptr(len(resp.Xattr))) - buf = append(buf, resp.Xattr...) - r.respond(buf) - } -} - -// 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) -} - -// Append adds an extended attribute name to the response. -func (r *ListxattrResponse) Append(names ...string) { - for _, name := range names { - r.Xattr = append(r.Xattr, name...) - r.Xattr = append(r.Xattr, '\x00') - } -} - -// A RemovexattrRequest asks to remove an extended attribute associated with r.Node. -type RemovexattrRequest struct { - Header `json:"-"` - Name string // name of extended attribute -} - -var _ = Request(&RemovexattrRequest{}) - -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() { - buf := NewBuffer(0) - r.respond(buf) -} - -// A SetxattrRequest asks to set an extended attribute associated with a file. -type SetxattrRequest struct { - Header `json:"-"` - - // Flags can make the request fail if attribute does/not already - // exist. Unfortunately, the constants are platform-specific and - // not exposed by Go1.2. Look for XATTR_CREATE, XATTR_REPLACE. - // - // TODO improve this later - // - // TODO XATTR_CREATE and exist -> EEXIST - // - // TODO XATTR_REPLACE and not exist -> ENODATA - Flags uint32 - - // Offset within extended attributes. - // - // Only valid for OS X, and then only with the resource fork - // attribute. - Position uint32 - - Name string - Xattr []byte -} - -var _ = Request(&SetxattrRequest{}) - -func trunc(b []byte, max int) ([]byte, string) { - if len(b) > max { - return b[:max], "..." - } - return b, "" -} - -func (r *SetxattrRequest) String() string { - xattr, tail := trunc(r.Xattr, 16) - return fmt.Sprintf("Setxattr [%s] %q %x%s fl=%v @%#x", &r.Header, r.Name, xattr, tail, r.Flags, r.Position) -} - -// Respond replies to the request, indicating that the extended attribute was set. -func (r *SetxattrRequest) Respond() { - buf := NewBuffer(0) - r.respond(buf) -} - -// A LookupRequest asks to look up the given name in the directory named by r.Node. -type LookupRequest struct { - Header `json:"-"` - Name string -} - -var _ = Request(&LookupRequest{}) - -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) { - size := fusekernel.EntryOutSize(r.Header.Conn.proto) - buf := NewBuffer(size) - out := (*fusekernel.EntryOut)(buf.Alloc(size)) - out.Nodeid = uint64(resp.Node) - out.Generation = resp.Generation - out.EntryValid = uint64(resp.EntryValid / time.Second) - out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) - out.AttrValid = uint64(resp.Attr.Valid / time.Second) - out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) - resp.Attr.attr(&out.Attr, r.Header.Conn.proto) - r.respond(buf) -} - -// A LookupResponse is the response to a LookupRequest. -type LookupResponse struct { - Node NodeID - Generation uint64 - EntryValid 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 `json:"-"` - Dir bool // is this Opendir? - Flags fusekernel.OpenFlags -} - -var _ = Request(&OpenRequest{}) - -func (r *OpenRequest) String() string { - return fmt.Sprintf("Open [%s] dir=%v fl=%v", &r.Header, r.Dir, r.Flags) -} - -// Respond replies to the request with the given response. -func (r *OpenRequest) Respond(resp *OpenResponse) { - buf := NewBuffer(unsafe.Sizeof(fusekernel.OpenOut{})) - out := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) - out.Fh = uint64(resp.Handle) - out.OpenFlags = uint32(resp.Flags) - r.respond(buf) -} - -// A OpenResponse is the response to a OpenRequest. -type OpenResponse struct { - Handle HandleID - Flags fusekernel.OpenResponseFlags -} - -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 `json:"-"` - Name string - Flags fusekernel.OpenFlags - Mode os.FileMode - Umask os.FileMode -} - -var _ = Request(&CreateRequest{}) - -func (r *CreateRequest) String() string { - return fmt.Sprintf("Create [%s] %q fl=%v mode=%v umask=%v", &r.Header, r.Name, r.Flags, r.Mode, r.Umask) -} - -// Respond replies to the request with the given response. -func (r *CreateRequest) Respond(resp *CreateResponse) { - eSize := fusekernel.EntryOutSize(r.Header.Conn.proto) - buf := NewBuffer(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) - - e := (*fusekernel.EntryOut)(buf.Alloc(eSize)) - e.Nodeid = uint64(resp.Node) - e.Generation = resp.Generation - e.EntryValid = uint64(resp.EntryValid / time.Second) - e.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) - e.AttrValid = uint64(resp.Attr.Valid / time.Second) - e.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) - resp.Attr.attr(&e.Attr, r.Header.Conn.proto) - - o := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) - o.Fh = uint64(resp.Handle) - o.OpenFlags = uint32(resp.Flags) - - r.respond(buf) -} - -// 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 `json:"-"` - Name string - Mode os.FileMode - Umask os.FileMode -} - -var _ = Request(&MkdirRequest{}) - -func (r *MkdirRequest) String() string { - return fmt.Sprintf("Mkdir [%s] %q mode=%v umask=%v", &r.Header, r.Name, r.Mode, r.Umask) -} - -// Respond replies to the request with the given response. -func (r *MkdirRequest) Respond(resp *MkdirResponse) { - size := fusekernel.EntryOutSize(r.Header.Conn.proto) - buf := NewBuffer(size) - out := (*fusekernel.EntryOut)(buf.Alloc(size)) - out.Nodeid = uint64(resp.Node) - out.Generation = resp.Generation - out.EntryValid = uint64(resp.EntryValid / time.Second) - out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) - out.AttrValid = uint64(resp.Attr.Valid / time.Second) - out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) - resp.Attr.attr(&out.Attr, r.Header.Conn.proto) - r.respond(buf) -} - -// 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 `json:"-"` - Dir bool // is this Readdir? - Handle HandleID - Offset int64 - Size int - Flags fusekernel.ReadFlags - LockOwner uint64 - FileFlags fusekernel.OpenFlags -} - -var _ = Request(&ReadRequest{}) - -func (r *ReadRequest) String() string { - return fmt.Sprintf("Read [%s] %#x %d @%#x dir=%v fl=%v lock=%d ffl=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir, r.Flags, r.LockOwner, r.FileFlags) -} - -// Respond replies to the request with the given response. -func (r *ReadRequest) Respond(resp *ReadResponse) { - buf := NewBuffer(uintptr(len(resp.Data))) - buf = append(buf, resp.Data...) - r.respond(buf) -} - -// A ReadResponse is the response to a ReadRequest. -type ReadResponse struct { - Data []byte -} - -func (r *ReadResponse) String() string { - return fmt.Sprintf("Read %d", len(r.Data)) -} - -type jsonReadResponse struct { - Len uint64 -} - -func (r *ReadResponse) MarshalJSON() ([]byte, error) { - j := jsonReadResponse{ - Len: uint64(len(r.Data)), - } - return json.Marshal(j) -} - -// A ReleaseRequest asks to release (close) an open file handle. -type ReleaseRequest struct { - Header `json:"-"` - Dir bool // is this Releasedir? - Handle HandleID - Flags fusekernel.OpenFlags // flags from OpenRequest - ReleaseFlags fusekernel.ReleaseFlags - LockOwner uint32 -} - -var _ = Request(&ReleaseRequest{}) - -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() { - buf := NewBuffer(0) - r.respond(buf) -} - -// 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 `json:"-"` -} - -var _ = Request(&DestroyRequest{}) - -func (r *DestroyRequest) String() string { - return fmt.Sprintf("Destroy [%s]", &r.Header) -} - -// Respond replies to the request. -func (r *DestroyRequest) Respond() { - buf := NewBuffer(0) - r.respond(buf) -} - -// A ForgetRequest is sent by the kernel when forgetting about r.Node -// as returned by r.N lookup requests. -type ForgetRequest struct { - Header `json:"-"` - N uint64 -} - -var _ = Request(&ForgetRequest{}) - -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. - r.noResponse() -} - -// A Dirent represents a single directory entry. -type Dirent struct { - // Inode this entry names. - Inode uint64 - - // Type of the entry, for example DT_File. - // - // Setting this is optional. The zero value (DT_Unknown) means - // callers will just need to do a Getattr when the type is - // needed. Providing a type can speed up operations - // significantly. - Type DirentType - - // Name of the entry - Name string -} - -// Type of an entry in a directory listing. -type DirentType uint32 - -const ( - // These don't quite match os.FileMode; especially there's an - // explicit unknown, instead of zero value meaning file. They - // are also not quite syscall.DT_*; nothing says the FUSE - // protocol follows those, and even if they were, we don't - // want each fs to fiddle with syscall. - - // The shift by 12 is hardcoded in the FUSE userspace - // low-level C library, so it's safe here. - - DT_Unknown DirentType = 0 - DT_Socket DirentType = syscall.S_IFSOCK >> 12 - DT_Link DirentType = syscall.S_IFLNK >> 12 - DT_File DirentType = syscall.S_IFREG >> 12 - DT_Block DirentType = syscall.S_IFBLK >> 12 - DT_Dir DirentType = syscall.S_IFDIR >> 12 - DT_Char DirentType = syscall.S_IFCHR >> 12 - DT_FIFO DirentType = syscall.S_IFIFO >> 12 -) - -func (t DirentType) String() string { - switch t { - case DT_Unknown: - return "unknown" - case DT_Socket: - return "socket" - case DT_Link: - return "link" - case DT_File: - return "file" - case DT_Block: - return "block" - case DT_Dir: - return "dir" - case DT_Char: - return "char" - case DT_FIFO: - return "fifo" - } - return "invalid" -} - -// AppendDirent appends the encoded form of a directory entry to data -// and returns the resulting slice. -func AppendDirent(data []byte, dir Dirent) []byte { - de := fusekernel.Dirent{ - Ino: dir.Inode, - Namelen: uint32(len(dir.Name)), - Type: uint32(dir.Type), - } - de.Off = uint64(len(data) + fusekernel.DirentSize + (len(dir.Name)+7)&^7) - data = append(data, (*[fusekernel.DirentSize]byte)(unsafe.Pointer(&de))[:]...) - data = append(data, dir.Name...) - n := fusekernel.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 fusekernel.WriteFlags - LockOwner uint64 - FileFlags fusekernel.OpenFlags -} - -var _ = Request(&WriteRequest{}) - -func (r *WriteRequest) String() string { - return fmt.Sprintf("Write [%s] %#x %d @%d fl=%v lock=%d ffl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags, r.LockOwner, r.FileFlags) -} - -type jsonWriteRequest struct { - Handle HandleID - Offset int64 - Len uint64 - Flags fusekernel.WriteFlags -} - -func (r *WriteRequest) MarshalJSON() ([]byte, error) { - j := jsonWriteRequest{ - Handle: r.Handle, - Offset: r.Offset, - Len: uint64(len(r.Data)), - Flags: r.Flags, - } - return json.Marshal(j) -} - -// Respond replies to the request with the given response. -func (r *WriteRequest) Respond(resp *WriteResponse) { - buf := NewBuffer(unsafe.Sizeof(fusekernel.WriteOut{})) - out := (*fusekernel.WriteOut)(buf.Alloc(unsafe.Sizeof(fusekernel.WriteOut{}))) - out.Size = uint32(resp.Size) - r.respond(buf) -} - -// 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 `json:"-"` - Valid fusekernel.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) -} - -var _ = Request(&SetattrRequest{}) - -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.AtimeNow() { - fmt.Fprintf(&buf, " atime=now") - } - if r.Valid.Mtime() { - fmt.Fprintf(&buf, " mtime=%v", r.Mtime) - } - if r.Valid.MtimeNow() { - fmt.Fprintf(&buf, " mtime=now") - } - if r.Valid.Handle() { - fmt.Fprintf(&buf, " handle=%#x", r.Handle) - } else { - fmt.Fprintf(&buf, " handle=INVALID-%#x", r.Handle) - } - if r.Valid.LockOwner() { - fmt.Fprintf(&buf, " lockowner") - } - 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() -} - -// Respond replies to the request with the given response, -// giving the updated attributes. -func (r *SetattrRequest) Respond(resp *SetattrResponse) { - size := fusekernel.AttrOutSize(r.Header.Conn.proto) - buf := NewBuffer(size) - out := (*fusekernel.AttrOut)(buf.Alloc(size)) - out.AttrValid = uint64(resp.Attr.Valid / time.Second) - out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) - resp.Attr.attr(&out.Attr, r.Header.Conn.proto) - r.respond(buf) -} - -// A SetattrResponse is the response to a SetattrRequest. -type SetattrResponse struct { - 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 `json:"-"` - Handle HandleID - Flags uint32 - LockOwner uint64 -} - -var _ = Request(&FlushRequest{}) - -func (r *FlushRequest) String() string { - return fmt.Sprintf("Flush [%s] %#x fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner) -} - -// Respond replies to the request, indicating that the flush succeeded. -func (r *FlushRequest) Respond() { - buf := NewBuffer(0) - r.respond(buf) -} - -// A RemoveRequest asks to remove a file or directory from the -// directory r.Node. -type RemoveRequest struct { - Header `json:"-"` - Name string // name of the entry to remove - Dir bool // is this rmdir? -} - -var _ = Request(&RemoveRequest{}) - -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() { - buf := NewBuffer(0) - r.respond(buf) -} - -// A SymlinkRequest is a request to create a symlink making NewName point to Target. -type SymlinkRequest struct { - Header `json:"-"` - NewName, Target string -} - -var _ = Request(&SymlinkRequest{}) - -func (r *SymlinkRequest) String() string { - return fmt.Sprintf("Symlink [%s] from %q to target %q", &r.Header, r.NewName, r.Target) -} - -// Respond replies to the request, indicating that the symlink was created. -func (r *SymlinkRequest) Respond(resp *SymlinkResponse) { - size := fusekernel.EntryOutSize(r.Header.Conn.proto) - buf := NewBuffer(size) - out := (*fusekernel.EntryOut)(buf.Alloc(size)) - out.Nodeid = uint64(resp.Node) - out.Generation = resp.Generation - out.EntryValid = uint64(resp.EntryValid / time.Second) - out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) - out.AttrValid = uint64(resp.Attr.Valid / time.Second) - out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) - resp.Attr.attr(&out.Attr, r.Header.Conn.proto) - r.respond(buf) -} - -// 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 `json:"-"` -} - -var _ = Request(&ReadlinkRequest{}) - -func (r *ReadlinkRequest) String() string { - return fmt.Sprintf("Readlink [%s]", &r.Header) -} - -func (r *ReadlinkRequest) Respond(target string) { - buf := NewBuffer(uintptr(len(target))) - buf = append(buf, target...) - r.respond(buf) -} - -// A LinkRequest is a request to create a hard link. -type LinkRequest struct { - Header `json:"-"` - OldNode NodeID - NewName string -} - -var _ = Request(&LinkRequest{}) - -func (r *LinkRequest) String() string { - return fmt.Sprintf("Link [%s] node %d to %q", &r.Header, r.OldNode, r.NewName) -} - -func (r *LinkRequest) Respond(resp *LookupResponse) { - size := fusekernel.EntryOutSize(r.Header.Conn.proto) - buf := NewBuffer(size) - out := (*fusekernel.EntryOut)(buf.Alloc(size)) - out.Nodeid = uint64(resp.Node) - out.Generation = resp.Generation - out.EntryValid = uint64(resp.EntryValid / time.Second) - out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) - out.AttrValid = uint64(resp.Attr.Valid / time.Second) - out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) - resp.Attr.attr(&out.Attr, r.Header.Conn.proto) - r.respond(buf) -} - -// A RenameRequest is a request to rename a file. -type RenameRequest struct { - Header `json:"-"` - NewDir NodeID - OldName, NewName string -} - -var _ = Request(&RenameRequest{}) - -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() { - buf := NewBuffer(0) - r.respond(buf) -} - -type MknodRequest struct { - Header `json:"-"` - Name string - Mode os.FileMode - Rdev uint32 - Umask os.FileMode -} - -var _ = Request(&MknodRequest{}) - -func (r *MknodRequest) String() string { - return fmt.Sprintf("Mknod [%s] Name %q mode=%v umask=%v rdev=%d", &r.Header, r.Name, r.Mode, r.Umask, r.Rdev) -} - -func (r *MknodRequest) Respond(resp *LookupResponse) { - size := fusekernel.EntryOutSize(r.Header.Conn.proto) - buf := NewBuffer(size) - out := (*fusekernel.EntryOut)(buf.Alloc(size)) - out.Nodeid = uint64(resp.Node) - out.Generation = resp.Generation - out.EntryValid = uint64(resp.EntryValid / time.Second) - out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) - out.AttrValid = uint64(resp.Attr.Valid / time.Second) - out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) - resp.Attr.attr(&out.Attr, r.Header.Conn.proto) - r.respond(buf) -} - -type FsyncRequest struct { - Header `json:"-"` - Handle HandleID - // TODO bit 1 is datasync, not well documented upstream - Flags uint32 - Dir bool -} - -var _ = Request(&FsyncRequest{}) - -func (r *FsyncRequest) String() string { - return fmt.Sprintf("Fsync [%s] Handle %v Flags %v", &r.Header, r.Handle, r.Flags) -} - -func (r *FsyncRequest) Respond() { - buf := NewBuffer(0) - r.respond(buf) -} - -// An InterruptRequest is a request to interrupt another pending request. The -// response to that request should return an error status of EINTR. -type InterruptRequest struct { - Header `json:"-"` - IntrID RequestID // ID of the request to be interrupt. -} - -var _ = Request(&InterruptRequest{}) - -func (r *InterruptRequest) Respond() { - // nothing to do here - r.noResponse() -} - -func (r *InterruptRequest) String() string { - return fmt.Sprintf("Interrupt [%s] ID %v", &r.Header, r.IntrID) -} diff --git a/internal/fuseshim/mount_darwin.go b/internal/fuseshim/mount_darwin.go deleted file mode 100644 index cf8646f..0000000 --- a/internal/fuseshim/mount_darwin.go +++ /dev/null @@ -1,131 +0,0 @@ -package fuseshim - -import ( - "bytes" - "errors" - "fmt" - "os" - "os/exec" - "strconv" - "strings" - "syscall" -) - -// OS X appears to cap the size of writes to 1 MiB. This constant is also used -// for sizing receive buffers, so make it as small as it can be without -// limiting write sizes. -const maxWrite = 1 << 20 - -var errNoAvail = errors.New("no available fuse devices") - -var errNotLoaded = errors.New("osxfusefs is not loaded") - -func loadOSXFUSE() error { - cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs") - cmd.Dir = "/" - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - return err -} - -func openOSXFUSEDev() (*os.File, error) { - var f *os.File - var err error - for i := uint64(0); ; i++ { - path := "/dev/osxfuse" + strconv.FormatUint(i, 10) - f, err = os.OpenFile(path, os.O_RDWR, 0000) - if os.IsNotExist(err) { - if i == 0 { - // not even the first device was found -> fuse is not loaded - return nil, errNotLoaded - } - - // we've run out of kernel-provided devices - return nil, errNoAvail - } - - if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY { - // try the next one - continue - } - - if err != nil { - return nil, err - } - return f, nil - } -} - -func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error { - bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" - - for k, v := range conf.options { - if strings.Contains(k, ",") || strings.Contains(v, ",") { - // Silly limitation but the mount helper does not - // understand any escaping. See TestMountOptionCommaError. - return fmt.Errorf("mount options cannot contain commas on darwin: %q=%q", k, v) - } - } - cmd := exec.Command( - bin, - "-o", conf.getOptions(), - // Tell osxfuse-kext how large our buffer is. It must split - // writes larger than this into multiple writes. - // - // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses - // this instead. - "-o", "iosize="+strconv.FormatUint(maxWrite, 10), - // refers to fd passed in cmd.ExtraFiles - "3", - dir, - ) - cmd.ExtraFiles = []*os.File{f} - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=") - // TODO this is used for fs typenames etc, let app influence it - cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin) - var buf bytes.Buffer - cmd.Stdout = &buf - cmd.Stderr = &buf - - err := cmd.Start() - if err != nil { - return err - } - go func() { - err := cmd.Wait() - if err != nil { - if buf.Len() > 0 { - output := buf.Bytes() - output = bytes.TrimRight(output, "\n") - msg := err.Error() + ": " + string(output) - err = errors.New(msg) - } - } - *errp = err - close(ready) - }() - return err -} - -func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { - f, err := openOSXFUSEDev() - if err == errNotLoaded { - err = loadOSXFUSE() - if err != nil { - return nil, err - } - // try again - f, err = openOSXFUSEDev() - } - if err != nil { - return nil, err - } - err = callMount(dir, conf, f, ready, errp) - if err != nil { - f.Close() - return nil, err - } - return f, nil -} diff --git a/internal/fuseshim/mount_freebsd.go b/internal/fuseshim/mount_freebsd.go deleted file mode 100644 index 36b9aac..0000000 --- a/internal/fuseshim/mount_freebsd.go +++ /dev/null @@ -1,44 +0,0 @@ -package fuseshim - -import ( - "fmt" - "os" - "os/exec" - "strings" -) - -// Maximum file write size we are prepared to receive from the kernel. -const maxWrite = 128 * 1024 - -func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { - for k, v := range conf.options { - if strings.Contains(k, ",") || strings.Contains(v, ",") { - // Silly limitation but the mount helper does not - // understand any escaping. See TestMountOptionCommaError. - return nil, fmt.Errorf("mount options cannot contain commas on FreeBSD: %q=%q", k, v) - } - } - - f, err := os.OpenFile("/dev/fuse", os.O_RDWR, 0000) - if err != nil { - *errp = err - return nil, err - } - - cmd := exec.Command( - "/sbin/mount_fusefs", - "--safe", - "-o", conf.getOptions(), - "3", - dir, - ) - cmd.ExtraFiles = []*os.File{f} - - out, err := cmd.CombinedOutput() - if err != nil { - return nil, fmt.Errorf("mount_fusefs: %q, %v", out, err) - } - - close(ready) - return f, nil -} diff --git a/internal/fuseshim/mount_linux.go b/internal/fuseshim/mount_linux.go deleted file mode 100644 index dfe70b5..0000000 --- a/internal/fuseshim/mount_linux.go +++ /dev/null @@ -1,116 +0,0 @@ -package fuseshim - -import ( - "bufio" - "fmt" - "io" - "log" - "net" - "os" - "os/exec" - "sync" - "syscall" -) - -// Maximum file write size we are prepared to receive from the kernel. Linux -// appears to limit writes to 128 KiB. -const maxWrite = 128 * 1024 - -func lineLogger(wg *sync.WaitGroup, prefix string, r io.ReadCloser) { - defer wg.Done() - - scanner := bufio.NewScanner(r) - for scanner.Scan() { - switch line := scanner.Text(); line { - case `fusermount: failed to open /etc/fuse.conf: Permission denied`: - // Silence this particular message, it occurs way too - // commonly and isn't very relevant to whether the mount - // succeeds or not. - continue - default: - log.Printf("%s: %s", prefix, line) - } - } - if err := scanner.Err(); err != nil { - log.Printf("%s, error reading: %v", prefix, err) - } -} - -func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { - // linux mount is never delayed - close(ready) - - fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0) - if err != nil { - return nil, fmt.Errorf("socketpair error: %v", err) - } - - writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") - defer writeFile.Close() - - readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") - defer readFile.Close() - - cmd := exec.Command( - "fusermount", - "-o", conf.getOptions(), - "--", - dir, - ) - cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") - - cmd.ExtraFiles = []*os.File{writeFile} - - var wg sync.WaitGroup - stdout, err := cmd.StdoutPipe() - if err != nil { - return nil, fmt.Errorf("setting up fusermount stderr: %v", err) - } - stderr, err := cmd.StderrPipe() - if err != nil { - return nil, fmt.Errorf("setting up fusermount stderr: %v", err) - } - - if err := cmd.Start(); err != nil { - return nil, fmt.Errorf("fusermount: %v", err) - } - wg.Add(2) - go lineLogger(&wg, "mount helper output", stdout) - go lineLogger(&wg, "mount helper error", stderr) - wg.Wait() - if err := cmd.Wait(); err != nil { - return nil, fmt.Errorf("fusermount: %v", err) - } - - c, err := net.FileConn(readFile) - if err != nil { - return nil, fmt.Errorf("FileConn from fusermount socket: %v", err) - } - defer c.Close() - - uc, ok := c.(*net.UnixConn) - if !ok { - return nil, fmt.Errorf("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 nil, fmt.Errorf("ParseSocketControlMessage: %v", err) - } - if len(scms) != 1 { - return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms) - } - scm := scms[0] - gotFds, err := syscall.ParseUnixRights(&scm) - if err != nil { - return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err) - } - if len(gotFds) != 1 { - return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds) - } - f := os.NewFile(uintptr(gotFds[0]), "/dev/fuse") - return f, nil -} diff --git a/internal/fuseshim/options.go b/internal/fuseshim/options.go deleted file mode 100644 index 2630ad0..0000000 --- a/internal/fuseshim/options.go +++ /dev/null @@ -1,182 +0,0 @@ -package fuseshim - -import ( - "errors" - "strings" - - "github.com/jacobsa/fuse/internal/fusekernel" -) - -func dummyOption(conf *mountConfig) error { - return nil -} - -// mountConfig holds the configuration for a mount operation. -// Use it by passing MountOption values to Mount. -type mountConfig struct { - options map[string]string - maxReadahead uint32 - initFlags fusekernel.InitFlags -} - -func escapeComma(s string) string { - s = strings.Replace(s, `\`, `\\`, -1) - s = strings.Replace(s, `,`, `\,`, -1) - return s -} - -// getOptions makes a string of options suitable for passing to FUSE -// mount flag `-o`. Returns an empty string if no options were set. -// Any platform specific adjustments should happen before the call. -func (m *mountConfig) getOptions() string { - var opts []string - for k, v := range m.options { - k = escapeComma(k) - if v != "" { - k += "=" + escapeComma(v) - } - opts = append(opts, k) - } - return strings.Join(opts, ",") -} - -type mountOption func(*mountConfig) error - -// MountOption is passed to Mount to change the behavior of the mount. -type MountOption mountOption - -// FSName sets the file system name (also called source) that is -// visible in the list of mounted file systems. -// -// FreeBSD ignores this option. -func FSName(name string) MountOption { - return func(conf *mountConfig) error { - conf.options["fsname"] = name - return nil - } -} - -// Subtype sets the subtype of the mount. The main type is always -// `fuse`. The type in a list of mounted file systems will look like -// `fuse.foo`. -// -// OS X ignores this option. -// FreeBSD ignores this option. -func Subtype(fstype string) MountOption { - return func(conf *mountConfig) error { - conf.options["subtype"] = fstype - return nil - } -} - -// LocalVolume sets the volume to be local (instead of network), -// changing the behavior of Finder, Spotlight, and such. -// -// OS X only. Others ignore this option. -func LocalVolume() MountOption { - return localVolume -} - -// VolumeName sets the volume name shown in Finder. -// -// OS X only. Others ignore this option. -func VolumeName(name string) MountOption { - return volumeName(name) -} - -var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot") - -// AllowOther allows other users to access the file system. -// -// Only one of AllowOther or AllowRoot can be used. -func AllowOther() MountOption { - return func(conf *mountConfig) error { - if _, ok := conf.options["allow_root"]; ok { - return ErrCannotCombineAllowOtherAndAllowRoot - } - conf.options["allow_other"] = "" - return nil - } -} - -// AllowRoot allows other users to access the file system. -// -// Only one of AllowOther or AllowRoot can be used. -// -// FreeBSD ignores this option. -func AllowRoot() MountOption { - return func(conf *mountConfig) error { - if _, ok := conf.options["allow_other"]; ok { - return ErrCannotCombineAllowOtherAndAllowRoot - } - conf.options["allow_root"] = "" - return nil - } -} - -// DefaultPermissions makes the kernel enforce access control based on -// the file mode (as in chmod). -// -// Without this option, the Node itself decides what is and is not -// allowed. This is normally ok because FUSE file systems cannot be -// accessed by other users without AllowOther/AllowRoot. -// -// FreeBSD ignores this option. -func DefaultPermissions() MountOption { - return func(conf *mountConfig) error { - conf.options["default_permissions"] = "" - return nil - } -} - -// Set the supplied arbitrary (key, value) pair in the "-o" argument to the -// fuse mount binary, overriding any previous setting for the key. If value is -// empty, the '=' will be omitted from the argument. -func SetOption(key, value string) MountOption { - return func(conf *mountConfig) error { - conf.options[key] = value - return nil - } -} - -// ReadOnly makes the mount read-only. -func ReadOnly() MountOption { - return func(conf *mountConfig) error { - conf.options["ro"] = "" - return nil - } -} - -// MaxReadahead sets the number of bytes that can be prefetched for -// sequential reads. The kernel can enforce a maximum value lower than -// this. -// -// This setting makes the kernel perform speculative reads that do not -// originate from any client process. This usually tremendously -// improves read performance. -func MaxReadahead(n uint32) MountOption { - return func(conf *mountConfig) error { - conf.maxReadahead = n - return nil - } -} - -// AsyncRead enables multiple outstanding read requests for the same -// handle. Without this, there is at most one request in flight at a -// time. -func AsyncRead() MountOption { - return func(conf *mountConfig) error { - conf.initFlags |= fusekernel.InitAsyncRead - return nil - } -} - -// WritebackCache enables the kernel to buffer writes before sending -// them to the FUSE server. Without this, writethrough caching is -// used. -func WritebackCache() MountOption { - return func(conf *mountConfig) error { - conf.initFlags |= fusekernel.InitWritebackCache - return nil - } -} diff --git a/internal/fuseshim/options_darwin.go b/internal/fuseshim/options_darwin.go deleted file mode 100644 index 20d85ea..0000000 --- a/internal/fuseshim/options_darwin.go +++ /dev/null @@ -1,13 +0,0 @@ -package fuseshim - -func localVolume(conf *mountConfig) error { - conf.options["local"] = "" - return nil -} - -func volumeName(name string) MountOption { - return func(conf *mountConfig) error { - conf.options["volname"] = name - return nil - } -} diff --git a/internal/fuseshim/options_freebsd.go b/internal/fuseshim/options_freebsd.go deleted file mode 100644 index b9ceae2..0000000 --- a/internal/fuseshim/options_freebsd.go +++ /dev/null @@ -1,9 +0,0 @@ -package fuseshim - -func localVolume(conf *mountConfig) error { - return nil -} - -func volumeName(name string) MountOption { - return dummyOption -} diff --git a/internal/fuseshim/options_helper_test.go b/internal/fuseshim/options_helper_test.go deleted file mode 100644 index 57b0dca..0000000 --- a/internal/fuseshim/options_helper_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package fuseshim - -// for TestMountOptionCommaError -func ForTestSetMountOption(k, v string) MountOption { - fn := func(conf *mountConfig) error { - conf.options[k] = v - return nil - } - return fn -} diff --git a/internal/fuseshim/options_linux.go b/internal/fuseshim/options_linux.go deleted file mode 100644 index b9ceae2..0000000 --- a/internal/fuseshim/options_linux.go +++ /dev/null @@ -1,9 +0,0 @@ -package fuseshim - -func localVolume(conf *mountConfig) error { - return nil -} - -func volumeName(name string) MountOption { - return dummyOption -} diff --git a/internal/fuseshim/options_test.go b/internal/fuseshim/options_test.go deleted file mode 100644 index a5048ac..0000000 --- a/internal/fuseshim/options_test.go +++ /dev/null @@ -1,231 +0,0 @@ -package fuseshim_test - -import ( - "os" - "runtime" - "syscall" - "testing" - - fuse "github.com/jacobsa/bazilfuse" - "github.com/jacobsa/bazilfuse/fs" - "github.com/jacobsa/bazilfuse/fs/fstestutil" - "golang.org/x/net/context" -) - -func init() { - fstestutil.DebugByDefault() -} - -func TestMountOptionFSName(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.Skip("FreeBSD does not support FSName") - } - t.Parallel() - const name = "FuseTestMarker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, - fuse.FSName(name), - ) - if err != nil { - t.Fatal(err) - } - defer mnt.Close() - - info, err := fstestutil.GetMountInfo(mnt.Dir) - if err != nil { - t.Fatal(err) - } - if g, e := info.FSName, name; g != e { - t.Errorf("wrong FSName: %q != %q", g, e) - } -} - -func testMountOptionFSNameEvil(t *testing.T, evil string) { - if runtime.GOOS == "freebsd" { - t.Skip("FreeBSD does not support FSName") - } - t.Parallel() - var name = "FuseTest" + evil + "Marker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, - fuse.FSName(name), - ) - if err != nil { - t.Fatal(err) - } - defer mnt.Close() - - info, err := fstestutil.GetMountInfo(mnt.Dir) - if err != nil { - t.Fatal(err) - } - if g, e := info.FSName, name; g != e { - t.Errorf("wrong FSName: %q != %q", g, e) - } -} - -func TestMountOptionFSNameEvilComma(t *testing.T) { - if runtime.GOOS == "darwin" { - // see TestMountOptionCommaError for a test that enforces we - // at least give a nice error, instead of corrupting the mount - // options - t.Skip("TODO: OS X gets this wrong, commas in mount options cannot be escaped at all") - } - testMountOptionFSNameEvil(t, ",") -} - -func TestMountOptionFSNameEvilSpace(t *testing.T) { - testMountOptionFSNameEvil(t, " ") -} - -func TestMountOptionFSNameEvilTab(t *testing.T) { - testMountOptionFSNameEvil(t, "\t") -} - -func TestMountOptionFSNameEvilNewline(t *testing.T) { - testMountOptionFSNameEvil(t, "\n") -} - -func TestMountOptionFSNameEvilBackslash(t *testing.T) { - testMountOptionFSNameEvil(t, `\`) -} - -func TestMountOptionFSNameEvilBackslashDouble(t *testing.T) { - // catch double-unescaping, if it were to happen - testMountOptionFSNameEvil(t, `\\`) -} - -func TestMountOptionSubtype(t *testing.T) { - if runtime.GOOS == "darwin" { - t.Skip("OS X does not support Subtype") - } - if runtime.GOOS == "freebsd" { - t.Skip("FreeBSD does not support Subtype") - } - t.Parallel() - const name = "FuseTestMarker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, - fuse.Subtype(name), - ) - if err != nil { - t.Fatal(err) - } - defer mnt.Close() - - info, err := fstestutil.GetMountInfo(mnt.Dir) - if err != nil { - t.Fatal(err) - } - if g, e := info.Type, "fuse."+name; g != e { - t.Errorf("wrong Subtype: %q != %q", g, e) - } -} - -// TODO test LocalVolume - -// TODO test AllowOther; hard because needs system-level authorization - -func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) { - t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, - fuse.AllowOther(), - fuse.AllowRoot(), - ) - if err == nil { - mnt.Close() - } - if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e { - t.Fatalf("wrong error: %v != %v", g, e) - } -} - -// TODO test AllowRoot; hard because needs system-level authorization - -func TestMountOptionAllowRootThenAllowOther(t *testing.T) { - t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, - fuse.AllowRoot(), - fuse.AllowOther(), - ) - if err == nil { - mnt.Close() - } - if g, e := err, fuse.ErrCannotCombineAllowOtherAndAllowRoot; g != e { - t.Fatalf("wrong error: %v != %v", g, e) - } -} - -type unwritableFile struct{} - -func (f unwritableFile) Attr(ctx context.Context, a *fuse.Attr) error { - a.Mode = 0000 - return nil -} - -func TestMountOptionDefaultPermissions(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.Skip("FreeBSD does not support DefaultPermissions") - } - t.Parallel() - mnt, err := fstestutil.MountedT(t, - fstestutil.SimpleFS{ - &fstestutil.ChildMap{"child": unwritableFile{}}, - }, - nil, - fuse.DefaultPermissions(), - ) - - if err != nil { - t.Fatal(err) - } - defer mnt.Close() - - // This will be prevented by kernel-level access checking when - // DefaultPermissions is used. - f, err := os.OpenFile(mnt.Dir+"/child", os.O_WRONLY, 0000) - if err == nil { - f.Close() - t.Fatal("expected an error") - } - if !os.IsPermission(err) { - t.Fatalf("expected a permission error, got %T: %v", err, err) - } -} - -type createrDir struct { - fstestutil.Dir -} - -var _ fs.NodeCreater = createrDir{} - -func (createrDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { - // pick a really distinct error, to identify it later - return nil, nil, fuse.Errno(syscall.ENAMETOOLONG) -} - -func TestMountOptionReadOnly(t *testing.T) { - t.Parallel() - mnt, err := fstestutil.MountedT(t, - fstestutil.SimpleFS{createrDir{}}, - nil, - fuse.ReadOnly(), - ) - - if err != nil { - t.Fatal(err) - } - defer mnt.Close() - - // This will be prevented by kernel-level access checking when - // ReadOnly is used. - f, err := os.Create(mnt.Dir + "/child") - if err == nil { - f.Close() - t.Fatal("expected an error") - } - perr, ok := err.(*os.PathError) - if !ok { - t.Fatalf("expected PathError, got %T: %v", err, err) - } - if perr.Err != syscall.EROFS { - t.Fatalf("expected EROFS, got %T: %v", err, err) - } -} diff --git a/internal/fuseshim/unmount.go b/internal/fuseshim/unmount.go deleted file mode 100644 index 0cc989d..0000000 --- a/internal/fuseshim/unmount.go +++ /dev/null @@ -1,6 +0,0 @@ -package fuseshim - -// Unmount tries to unmount the filesystem mounted at dir. -func Unmount(dir string) error { - return unmount(dir) -} diff --git a/internal/fuseshim/unmount_linux.go b/internal/fuseshim/unmount_linux.go deleted file mode 100644 index 6a165cc..0000000 --- a/internal/fuseshim/unmount_linux.go +++ /dev/null @@ -1,21 +0,0 @@ -package fuseshim - -import ( - "bytes" - "errors" - "os/exec" -) - -func unmount(dir string) error { - cmd := exec.Command("fusermount", "-u", dir) - output, err := cmd.CombinedOutput() - if err != nil { - if len(output) > 0 { - output = bytes.TrimRight(output, "\n") - msg := err.Error() + ": " + string(output) - err = errors.New(msg) - } - return err - } - return nil -} diff --git a/internal/fuseshim/unmount_std.go b/internal/fuseshim/unmount_std.go deleted file mode 100644 index 3c38dd2..0000000 --- a/internal/fuseshim/unmount_std.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build !linux - -package fuseshim - -import ( - "os" - "syscall" -) - -func unmount(dir string) error { - err := syscall.Unmount(dir, 0) - if err != nil { - err = &os.PathError{Op: "unmount", Path: dir, Err: err} - return err - } - return nil -}