From 216fede41e378de5b466997701571956780936f0 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 15:47:23 +1000 Subject: [PATCH 001/157] Copied from jacobsa/bazilfuse@b378951. --- internal/fusekernel/fuse_kernel.go | 766 +++++++++++++++++++++ internal/fusekernel/fuse_kernel_darwin.go | 88 +++ internal/fusekernel/fuse_kernel_freebsd.go | 62 ++ internal/fusekernel/fuse_kernel_linux.go | 70 ++ internal/fusekernel/fuse_kernel_std.go | 1 + internal/fusekernel/fuse_kernel_test.go | 31 + 6 files changed, 1018 insertions(+) create mode 100644 internal/fusekernel/fuse_kernel.go create mode 100644 internal/fusekernel/fuse_kernel_darwin.go create mode 100644 internal/fusekernel/fuse_kernel_freebsd.go create mode 100644 internal/fusekernel/fuse_kernel_linux.go create mode 100644 internal/fusekernel/fuse_kernel_std.go create mode 100644 internal/fusekernel/fuse_kernel_test.go diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go new file mode 100644 index 0000000..37404ab --- /dev/null +++ b/internal/fusekernel/fuse_kernel.go @@ -0,0 +1,766 @@ +// See the file LICENSE for copyright and licensing information. + +// Derived from FUSE's fuse_kernel.h, which carries this notice: +/* + 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 bazilfuse + +import ( + "fmt" + "syscall" + "unsafe" +) + +// The FUSE version implemented by the package. +const ( + protoVersionMinMajor = 7 + protoVersionMinMinor = 8 + protoVersionMaxMajor = 7 + protoVersionMaxMinor = 12 +) + +const ( + 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 +} + +// GetattrFlags are bit flags that can be seen in GetattrRequest. +type GetattrFlags uint32 + +const ( + // Indicates the handle is valid. + GetattrFh GetattrFlags = 1 << 0 +) + +var getattrFlagsNames = []flagName{ + {uint32(GetattrFh), "GetattrFh"}, +} + +func (fl GetattrFlags) String() string { + return flagString(uint32(fl), getattrFlagsNames) +} + +// 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 + + // 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) AtimeNow() bool { return fl&SetattrAtimeNow != 0 } +func (fl SetattrValid) MtimeNow() bool { return fl&SetattrMtimeNow != 0 } +func (fl SetattrValid) LockOwner() bool { return fl&SetattrLockOwner != 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(SetattrAtimeNow), "SetattrAtimeNow"}, + {uint32(SetattrMtimeNow), "SetattrMtimeNow"}, + {uint32(SetattrLockOwner), "SetattrLockOwner"}, + {uint32(SetattrCrtime), "SetattrCrtime"}, + {uint32(SetattrChgtime), "SetattrChgtime"}, + {uint32(SetattrBkuptime), "SetattrBkuptime"}, + {uint32(SetattrFlags), "SetattrFlags"}, +} + +// Flags that can be seen in OpenRequest.Flags. +const ( + // Access modes. These are not 1-bit flags, but alternatives where + // only one can be chosen. See the IsReadOnly etc convenience + // methods. + OpenReadOnly OpenFlags = syscall.O_RDONLY + OpenWriteOnly OpenFlags = syscall.O_WRONLY + OpenReadWrite OpenFlags = syscall.O_RDWR + + OpenAppend OpenFlags = syscall.O_APPEND + OpenCreate OpenFlags = syscall.O_CREAT + OpenExclusive OpenFlags = syscall.O_EXCL + OpenSync OpenFlags = syscall.O_SYNC + OpenTruncate OpenFlags = syscall.O_TRUNC +) + +// OpenAccessModeMask is a bitmask that separates the access mode +// from the other flags in OpenFlags. +const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE + +// OpenFlags are the O_FOO flags passed to open/create/etc calls. For +// example, os.O_WRONLY | os.O_APPEND. +type OpenFlags uint32 + +func (fl OpenFlags) String() string { + // O_RDONLY, O_RWONLY, O_RDWR are not flags + s := accModeName(fl & OpenAccessModeMask) + flags := uint32(fl &^ OpenAccessModeMask) + if flags != 0 { + s = s + "+" + flagString(flags, openFlagNames) + } + return s +} + +// Return true if OpenReadOnly is set. +func (fl OpenFlags) IsReadOnly() bool { + return fl&OpenAccessModeMask == OpenReadOnly +} + +// Return true if OpenWriteOnly is set. +func (fl OpenFlags) IsWriteOnly() bool { + return fl&OpenAccessModeMask == OpenWriteOnly +} + +// Return true if OpenReadWrite is set. +func (fl OpenFlags) IsReadWrite() bool { + return fl&OpenAccessModeMask == OpenReadWrite +} + +func accModeName(flags OpenFlags) string { + switch flags { + case OpenReadOnly: + return "OpenReadOnly" + case OpenWriteOnly: + return "OpenWriteOnly" + case OpenReadWrite: + return "OpenReadWrite" + default: + return "" + } +} + +var openFlagNames = []flagName{ + {uint32(OpenCreate), "OpenCreate"}, + {uint32(OpenExclusive), "OpenExclusive"}, + {uint32(OpenTruncate), "OpenTruncate"}, + {uint32(OpenAppend), "OpenAppend"}, + {uint32(OpenSync), "OpenSync"}, +} + +// The OpenResponseFlags are returned in the OpenResponse. +type OpenResponseFlags uint32 + +const ( + OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file + OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open + OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X) + + OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X + OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X +) + +func (fl OpenResponseFlags) String() string { + return flagString(uint32(fl), openResponseFlagNames) +} + +var openResponseFlagNames = []flagName{ + {uint32(OpenDirectIO), "OpenDirectIO"}, + {uint32(OpenKeepCache), "OpenKeepCache"}, + {uint32(OpenNonSeekable), "OpenNonSeekable"}, + {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 + InitFileOps InitFlags = 1 << 2 + InitAtomicTrunc InitFlags = 1 << 3 + InitExportSupport InitFlags = 1 << 4 + InitBigWrites InitFlags = 1 << 5 + InitDontMask InitFlags = 1 << 6 + InitSpliceWrite InitFlags = 1 << 7 + InitSpliceMove InitFlags = 1 << 8 + InitSpliceRead InitFlags = 1 << 9 + InitFlockLocks InitFlags = 1 << 10 + InitHasIoctlDir InitFlags = 1 << 11 + InitAutoInvalData InitFlags = 1 << 12 + InitDoReaddirplus InitFlags = 1 << 13 + InitReaddirplusAuto InitFlags = 1 << 14 + InitAsyncDIO InitFlags = 1 << 15 + InitWritebackCache InitFlags = 1 << 16 + InitNoOpenSupport InitFlags = 1 << 17 + + 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(InitFileOps), "InitFileOps"}, + {uint32(InitAtomicTrunc), "InitAtomicTrunc"}, + {uint32(InitExportSupport), "InitExportSupport"}, + {uint32(InitBigWrites), "InitBigWrites"}, + {uint32(InitDontMask), "InitDontMask"}, + {uint32(InitSpliceWrite), "InitSpliceWrite"}, + {uint32(InitSpliceMove), "InitSpliceMove"}, + {uint32(InitSpliceRead), "InitSpliceRead"}, + {uint32(InitFlockLocks), "InitFlockLocks"}, + {uint32(InitHasIoctlDir), "InitHasIoctlDir"}, + {uint32(InitAutoInvalData), "InitAutoInvalData"}, + {uint32(InitDoReaddirplus), "InitDoReaddirplus"}, + {uint32(InitReaddirplusAuto), "InitReaddirplusAuto"}, + {uint32(InitAsyncDIO), "InitAsyncDIO"}, + {uint32(InitWritebackCache), "InitWritebackCache"}, + {uint32(InitNoOpenSupport), "InitNoOpenSupport"}, + + {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 +) + +type entryOut struct { + 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 +} + +func entryOutSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(entryOut{}.Attr) + unsafe.Offsetof(entryOut{}.Attr.Blksize) + default: + return unsafe.Sizeof(entryOut{}) + } +} + +type forgetIn struct { + Nlookup uint64 +} + +type getattrIn struct { + GetattrFlags uint32 + dummy uint32 + Fh uint64 +} + +type attrOut struct { + AttrValid uint64 // Cache timeout for the attributes + AttrValidNsec uint32 + Dummy uint32 + Attr attr +} + +func attrOutSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(attrOut{}.Attr) + unsafe.Offsetof(attrOut{}.Attr.Blksize) + default: + return unsafe.Sizeof(attrOut{}) + } +} + +// OS X +type getxtimesOut struct { + Bkuptime uint64 + Crtime uint64 + BkuptimeNsec uint32 + CrtimeNsec uint32 +} + +type mknodIn struct { + Mode uint32 + Rdev uint32 + Umask uint32 + padding uint32 + // "filename\x00" follows. +} + +func mknodInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 12}): + return unsafe.Offsetof(mknodIn{}.Umask) + default: + return unsafe.Sizeof(mknodIn{}) + } +} + +type mkdirIn struct { + Mode uint32 + Umask uint32 + // filename follows +} + +func mkdirInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 12}): + return unsafe.Offsetof(mkdirIn{}.Umask) + 4 + default: + return unsafe.Sizeof(mkdirIn{}) + } +} + +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 + Unused uint32 +} + +type openOut struct { + Fh uint64 + OpenFlags uint32 + Padding uint32 +} + +type createIn struct { + Flags uint32 + Mode uint32 + Umask uint32 + padding uint32 +} + +func createInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 12}): + return unsafe.Offsetof(createIn{}.Umask) + default: + return unsafe.Sizeof(createIn{}) + } +} + +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 + ReadFlags uint32 + LockOwner uint64 + Flags uint32 + padding uint32 +} + +func readInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(readIn{}.ReadFlags) + 4 + default: + return unsafe.Sizeof(readIn{}) + } +} + +// The ReadFlags are passed in ReadRequest. +type ReadFlags uint32 + +const ( + // LockOwner field is valid. + ReadLockOwner ReadFlags = 1 << 1 +) + +var readFlagNames = []flagName{ + {uint32(ReadLockOwner), "ReadLockOwner"}, +} + +func (fl ReadFlags) String() string { + return flagString(uint32(fl), readFlagNames) +} + +type writeIn struct { + Fh uint64 + Offset uint64 + Size uint32 + WriteFlags uint32 + LockOwner uint64 + Flags uint32 + padding uint32 +} + +func writeInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(writeIn{}.LockOwner) + default: + return unsafe.Sizeof(writeIn{}) + } +} + +type writeOut struct { + Size uint32 + Padding uint32 +} + +// The WriteFlags are passed in WriteRequest. +type WriteFlags uint32 + +const ( + WriteCache WriteFlags = 1 << 0 + // LockOwner field is valid. + WriteLockOwner WriteFlags = 1 << 1 +) + +var writeFlagNames = []flagName{ + {uint32(WriteCache), "WriteCache"}, + {uint32(WriteLockOwner), "WriteLockOwner"}, +} + +func (fl WriteFlags) String() string { + return flagString(uint32(fl), writeFlagNames) +} + +const compatStatfsSize = 48 + +type statfsOut struct { + St kstatfs +} + +type fsyncIn struct { + Fh uint64 + FsyncFlags uint32 + Padding uint32 +} + +type setxattrInCommon struct { + Size uint32 + Flags uint32 +} + +func (setxattrInCommon) position() uint32 { + return 0 +} + +type getxattrInCommon struct { + Size uint32 + Padding uint32 +} + +func (getxattrInCommon) position() uint32 { + return 0 +} + +type getxattrOut struct { + Size uint32 + Padding uint32 +} + +type lkIn struct { + Fh uint64 + Owner uint64 + Lk fileLock + LkFlags uint32 + padding uint32 +} + +func lkInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(lkIn{}.LkFlags) + default: + return unsafe.Sizeof(lkIn{}) + } +} + +type lkOut struct { + 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 { + 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 { + 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 + +const ( + notifyCodePoll int32 = 1 + notifyCodeInvalInode int32 = 2 + notifyCodeInvalEntry int32 = 3 +) + +type notifyInvalInodeOut struct { + Ino uint64 + Off int64 + Len int64 +} + +type notifyInvalEntryOut struct { + Parent uint64 + Namelen uint32 + padding uint32 +} diff --git a/internal/fusekernel/fuse_kernel_darwin.go b/internal/fusekernel/fuse_kernel_darwin.go new file mode 100644 index 0000000..a5200e6 --- /dev/null +++ b/internal/fusekernel/fuse_kernel_darwin.go @@ -0,0 +1,88 @@ +package bazilfuse + +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) + Blksize uint32 + padding uint32 +} + +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_ +} + +func openFlags(flags uint32) OpenFlags { + return OpenFlags(flags) +} + +type getxattrIn struct { + getxattrInCommon + + // OS X only + Position uint32 + Padding uint32 +} + +func (g *getxattrIn) position() uint32 { + return g.Position +} + +type setxattrIn struct { + setxattrInCommon + + // OS X only + Position uint32 + Padding uint32 +} + +func (s *setxattrIn) position() uint32 { + return s.Position +} diff --git a/internal/fusekernel/fuse_kernel_freebsd.go b/internal/fusekernel/fuse_kernel_freebsd.go new file mode 100644 index 0000000..53b846a --- /dev/null +++ b/internal/fusekernel/fuse_kernel_freebsd.go @@ -0,0 +1,62 @@ +package bazilfuse + +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 + padding uint32 +} + +func (a *attr) Crtime() time.Time { + return time.Time{} +} + +func (a *attr) SetCrtime(s uint64, ns uint32) { + // ignored on freebsd +} + +func (a *attr) SetFlags(f uint32) { + // ignored on freebsd +} + +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 +} + +func openFlags(flags uint32) OpenFlags { + return OpenFlags(flags) +} + +type getxattrIn struct { + getxattrInCommon +} + +type setxattrIn struct { + setxattrInCommon +} diff --git a/internal/fusekernel/fuse_kernel_linux.go b/internal/fusekernel/fuse_kernel_linux.go new file mode 100644 index 0000000..fc0509b --- /dev/null +++ b/internal/fusekernel/fuse_kernel_linux.go @@ -0,0 +1,70 @@ +package bazilfuse + +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 + padding uint32 +} + +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 +} + +func openFlags(flags uint32) OpenFlags { + // on amd64, the 32-bit O_LARGEFILE flag is always seen; + // on i386, the flag probably depends on the app + // requesting, but in any case should be utterly + // uninteresting to us here; our kernel protocol messages + // are not directly related to the client app's kernel + // API/ABI + flags &^= 0x8000 + + return OpenFlags(flags) +} + +type getxattrIn struct { + getxattrInCommon +} + +type setxattrIn struct { + setxattrInCommon +} diff --git a/internal/fusekernel/fuse_kernel_std.go b/internal/fusekernel/fuse_kernel_std.go new file mode 100644 index 0000000..98b9541 --- /dev/null +++ b/internal/fusekernel/fuse_kernel_std.go @@ -0,0 +1 @@ +package bazilfuse diff --git a/internal/fusekernel/fuse_kernel_test.go b/internal/fusekernel/fuse_kernel_test.go new file mode 100644 index 0000000..821bccb --- /dev/null +++ b/internal/fusekernel/fuse_kernel_test.go @@ -0,0 +1,31 @@ +package bazilfuse_test + +import ( + "os" + "testing" + + fuse "github.com/jacobsa/bazilfuse" +) + +func TestOpenFlagsAccmodeMask(t *testing.T) { + var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC) + if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e { + t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e) + } + if f.IsReadOnly() { + t.Fatalf("IsReadOnly is wrong: %v", f) + } + if f.IsWriteOnly() { + t.Fatalf("IsWriteOnly is wrong: %v", f) + } + if !f.IsReadWrite() { + t.Fatalf("IsReadWrite is wrong: %v", f) + } +} + +func TestOpenFlagsString(t *testing.T) { + var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND) + if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e { + t.Fatalf("OpenFlags.String: %q != %q", g, e) + } +} From d87d15e89317aba8ea15faeaf1d7478a57cbbe93 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 15:52:04 +1000 Subject: [PATCH 002/157] Copied from jacobsa/bazilfuse@b378951. --- internal/fuseshim/fuse.go | 2206 +++++++++++++++++++++ internal/fuseshim/mount_darwin.go | 131 ++ internal/fuseshim/mount_freebsd.go | 44 + internal/fuseshim/mount_linux.go | 116 ++ internal/fuseshim/options.go | 180 ++ internal/fuseshim/options_darwin.go | 13 + internal/fuseshim/options_freebsd.go | 9 + internal/fuseshim/options_helper_test.go | 10 + internal/fuseshim/options_linux.go | 9 + internal/fuseshim/options_nocomma_test.go | 31 + internal/fuseshim/options_test.go | 231 +++ internal/fuseshim/protocol.go | 75 + 12 files changed, 3055 insertions(+) create mode 100644 internal/fuseshim/fuse.go create mode 100644 internal/fuseshim/mount_darwin.go create mode 100644 internal/fuseshim/mount_freebsd.go create mode 100644 internal/fuseshim/mount_linux.go create mode 100644 internal/fuseshim/options.go create mode 100644 internal/fuseshim/options_darwin.go create mode 100644 internal/fuseshim/options_freebsd.go create mode 100644 internal/fuseshim/options_helper_test.go create mode 100644 internal/fuseshim/options_linux.go create mode 100644 internal/fuseshim/options_nocomma_test.go create mode 100644 internal/fuseshim/options_test.go create mode 100644 internal/fuseshim/protocol.go diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go new file mode 100644 index 0000000..c09095b --- /dev/null +++ b/internal/fuseshim/fuse.go @@ -0,0 +1,2206 @@ +// 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 bazilfuse // import "github.com/jacobsa/bazilfuse" + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "sync" + "syscall" + "time" + "unsafe" +) + +// 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 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); err != nil { + c.Close() + return nil, err + } + + return c, nil +} + +type OldVersionError struct { + Kernel Protocol + LibraryMin 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, conf *mountConfig) 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 := Protocol{protoVersionMinMajor, protoVersionMinMinor} + if r.Kernel.LT(min) { + req.RespondError(Errno(syscall.EPROTO)) + c.Close() + return &OldVersionError{ + Kernel: r.Kernel, + LibraryMin: min, + } + } + + proto := Protocol{protoVersionMaxMajor, 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: conf.maxReadahead, + MaxWrite: maxWrite, + Flags: InitBigWrites | conf.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 = 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 := (*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 := (*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 = (*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 *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() 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, + + msg: 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() Protocol { + return c.proto +} + +// 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) { + 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 { + putMessage(m) + return nil, err + } + if n <= 0 { + putMessage(m) + return nil, io.EOF + } + m.buf = m.buf[:n] + + if n < inHeaderSize { + putMessage(m) + 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) { + // 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) + putMessage(m) + return nil, err + } + + 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: + Debug(noOpcode{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: + switch { + case c.proto.LT(Protocol{7, 9}): + req = &GetattrRequest{ + Header: m.Header(), + } + + default: + in := (*getattrIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &GetattrRequest{ + Header: m.Header(), + Flags: GetattrFlags(in.GetattrFlags), + Handle: HandleID(in.Fh), + } + } + + 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: + size := mknodInSize(c.proto) + if m.len() < size { + goto corrupt + } + in := (*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(Protocol{7, 12}) { + r.Umask = fileMode(in.Umask) & os.ModePerm + } + req = r + + case opMkdir: + size := mkdirInSize(c.proto) + if m.len() < size { + goto corrupt + } + in := (*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(Protocol{7, 12}) { + r.Umask = fileMode(in.Umask) & os.ModePerm + } + req = r + + 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]) + 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: openFlags(in.Flags), + } + + case opRead, opReaddir: + in := (*readIn)(m.data()) + if m.len() < readInSize(c.proto) { + goto corrupt + } + r := &ReadRequest{ + Header: m.Header(), + Dir: m.hdr.Opcode == opReaddir, + Handle: HandleID(in.Fh), + Offset: int64(in.Offset), + Size: int(in.Size), + } + if c.proto.GE(Protocol{7, 9}) { + r.Flags = ReadFlags(in.ReadFlags) + r.LockOwner = in.LockOwner + r.FileFlags = openFlags(in.Flags) + } + req = r + + case opWrite: + in := (*writeIn)(m.data()) + if m.len() < writeInSize(c.proto) { + goto corrupt + } + r := &WriteRequest{ + Header: m.Header(), + Handle: HandleID(in.Fh), + Offset: int64(in.Offset), + Flags: WriteFlags(in.WriteFlags), + } + if c.proto.GE(Protocol{7, 9}) { + r.LockOwner = in.LockOwner + r.FileFlags = openFlags(in.Flags) + } + buf := m.bytes()[writeInSize(c.proto):] + 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: openFlags(in.Flags), + ReleaseFlags: ReleaseFlags(in.ReleaseFlags), + LockOwner: in.LockOwner, + } + + case opFsync, opFsyncdir: + in := (*fsyncIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &FsyncRequest{ + Dir: m.hdr.Opcode == opFsyncdir, + Header: m.Header(), + Handle: HandleID(in.Fh), + Flags: in.FsyncFlags, + } + + case opSetxattr: + in := (*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.position(), + Name: string(name[:i]), + Xattr: xattr, + } + + case opGetxattr: + in := (*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.position(), + } + + case opListxattr: + in := (*getxattrIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &ListxattrRequest{ + Header: m.Header(), + Size: in.Size, + Position: in.position(), + } + + 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(), + Kernel: Protocol{in.Major, in.Minor}, + MaxReadahead: in.MaxReadahead, + Flags: InitFlags(in.Flags), + } + + 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: + size := createInSize(c.proto) + if m.len() < size { + goto corrupt + } + in := (*createIn)(m.data()) + name := m.bytes()[size:] + i := bytes.IndexByte(name, '\x00') + if i < 0 { + goto corrupt + } + r := &CreateRequest{ + Header: m.Header(), + Flags: openFlags(in.Flags), + Mode: fileMode(in.Mode), + Name: string(name[:i]), + } + if c.proto.GE(Protocol{7, 12}) { + r.Umask = fileMode(in.Umask) & os.ModePerm + } + req = r + + case opInterrupt: + in := (*interruptIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &InterruptRequest{ + Header: m.Header(), + IntrID: RequestID(in.Unique), + } + + 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: + Debug(malformedMessage{}) + putMessage(m) + 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 := (*outHeader)(unsafe.Pointer(&msg[0])) + out.Len = uint32(len(msg)) + + c.wio.RLock() + defer c.wio.RUnlock() + nn, err := syscall.Write(c.fd(), msg) + if err == nil && nn != len(msg) { + Debug(bugShortKernelWrite{ + Written: int64(nn), + Length: int64(len(msg)), + Error: errorString(err), + Stack: stack(), + }) + } + return err +} + +func (c *Conn) respond(msg []byte) { + if err := c.writeToKernel(msg); err != nil { + Debug(bugKernelWriteError{ + Error: errorString(err), + Stack: stack(), + }) + } +} + +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(notifyInvalInodeOut{})) + h := (*outHeader)(unsafe.Pointer(&buf[0])) + // h.Unique is 0 + h.Error = notifyCodeInvalInode + out := (*notifyInvalInodeOut)(buf.alloc(unsafe.Sizeof(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(notifyInvalEntryOut{}) + uintptr(len(name)) + 1) + h := (*outHeader)(unsafe.Pointer(&buf[0])) + // h.Unique is 0 + h.Error = notifyCodeInvalEntry + out := (*notifyInvalEntryOut)(buf.alloc(unsafe.Sizeof(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 Protocol + // Maximum readahead in bytes that the kernel plans to use. + MaxReadahead uint32 + Flags 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 Protocol + // Maximum readahead in bytes that the kernel can use. Ignored if + // greater than InitRequest.MaxReadahead. + MaxReadahead uint32 + Flags 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(initOut{})) + out := (*initOut)(buf.alloc(unsafe.Sizeof(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(statfsOut{})) + out := (*statfsOut)(buf.alloc(unsafe.Sizeof(statfsOut{}))) + out.St = 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 *attr, proto 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(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 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 := attrOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*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(getxattrOut{})) + out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(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(getxattrOut{})) + out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(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 := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*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 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(openOut{})) + out := (*openOut)(buf.alloc(unsafe.Sizeof(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 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 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 := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(eSize + unsafe.Sizeof(openOut{})) + + e := (*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 := (*openOut)(buf.alloc(unsafe.Sizeof(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 := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*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 ReadFlags + LockOwner uint64 + FileFlags 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 OpenFlags // flags from OpenRequest + ReleaseFlags 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 := dirent{ + Ino: dir.Inode, + Namelen: uint32(len(dir.Name)), + Type: uint32(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 + LockOwner uint64 + FileFlags 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 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(writeOut{})) + out := (*writeOut)(buf.alloc(unsafe.Sizeof(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 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 := attrOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*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 := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*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 := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*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 := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*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 new file mode 100644 index 0000000..dc9b88d --- /dev/null +++ b/internal/fuseshim/mount_darwin.go @@ -0,0 +1,131 @@ +package bazilfuse + +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 new file mode 100644 index 0000000..3a02fa2 --- /dev/null +++ b/internal/fuseshim/mount_freebsd.go @@ -0,0 +1,44 @@ +package bazilfuse + +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 new file mode 100644 index 0000000..7f18132 --- /dev/null +++ b/internal/fuseshim/mount_linux.go @@ -0,0 +1,116 @@ +package bazilfuse + +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 new file mode 100644 index 0000000..2c1b8f7 --- /dev/null +++ b/internal/fuseshim/options.go @@ -0,0 +1,180 @@ +package bazilfuse + +import ( + "errors" + "strings" +) + +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 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 |= 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 |= InitWritebackCache + return nil + } +} diff --git a/internal/fuseshim/options_darwin.go b/internal/fuseshim/options_darwin.go new file mode 100644 index 0000000..6394c1c --- /dev/null +++ b/internal/fuseshim/options_darwin.go @@ -0,0 +1,13 @@ +package bazilfuse + +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 new file mode 100644 index 0000000..66bf0c3 --- /dev/null +++ b/internal/fuseshim/options_freebsd.go @@ -0,0 +1,9 @@ +package bazilfuse + +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 new file mode 100644 index 0000000..dad7706 --- /dev/null +++ b/internal/fuseshim/options_helper_test.go @@ -0,0 +1,10 @@ +package bazilfuse + +// 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 new file mode 100644 index 0000000..66bf0c3 --- /dev/null +++ b/internal/fuseshim/options_linux.go @@ -0,0 +1,9 @@ +package bazilfuse + +func localVolume(conf *mountConfig) error { + return nil +} + +func volumeName(name string) MountOption { + return dummyOption +} diff --git a/internal/fuseshim/options_nocomma_test.go b/internal/fuseshim/options_nocomma_test.go new file mode 100644 index 0000000..c05bc10 --- /dev/null +++ b/internal/fuseshim/options_nocomma_test.go @@ -0,0 +1,31 @@ +// This file contains tests for platforms that have no escape +// mechanism for including commas in mount options. +// +// +build darwin + +package bazilfuse_test + +import ( + "runtime" + "testing" + + fuse "github.com/jacobsa/bazilfuse" + "github.com/jacobsa/bazilfuse/fs/fstestutil" +) + +func TestMountOptionCommaError(t *testing.T) { + t.Parallel() + // this test is not tied to any specific option, it just needs + // some string content + var evil = "FuseTest,Marker" + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, + fuse.ForTestSetMountOption("fusetest", evil), + ) + if err == nil { + mnt.Close() + t.Fatal("expected an error about commas") + } + if g, e := err.Error(), `mount options cannot contain commas on `+runtime.GOOS+`: "fusetest"="FuseTest,Marker"`; g != e { + t.Fatalf("wrong error: %q != %q", g, e) + } +} diff --git a/internal/fuseshim/options_test.go b/internal/fuseshim/options_test.go new file mode 100644 index 0000000..63c97e1 --- /dev/null +++ b/internal/fuseshim/options_test.go @@ -0,0 +1,231 @@ +package bazilfuse_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/protocol.go b/internal/fuseshim/protocol.go new file mode 100644 index 0000000..a13fc12 --- /dev/null +++ b/internal/fuseshim/protocol.go @@ -0,0 +1,75 @@ +package bazilfuse + +import ( + "fmt" +) + +// Protocol is a FUSE protocol version number. +type Protocol struct { + Major uint32 + Minor uint32 +} + +func (p Protocol) String() string { + return fmt.Sprintf("%d.%d", p.Major, p.Minor) +} + +// LT returns whether a is less than b. +func (a Protocol) LT(b Protocol) bool { + return a.Major < b.Major || + (a.Major == b.Major && a.Minor < b.Minor) +} + +// GE returns whether a is greater than or equal to b. +func (a Protocol) GE(b Protocol) bool { + return a.Major > b.Major || + (a.Major == b.Major && a.Minor >= b.Minor) +} + +func (a Protocol) is79() bool { + return a.GE(Protocol{7, 9}) +} + +// HasAttrBlockSize returns whether Attr.BlockSize is respected by the +// kernel. +func (a Protocol) HasAttrBlockSize() bool { + return a.is79() +} + +// HasReadWriteFlags returns whether ReadRequest/WriteRequest +// fields Flags and FileFlags are valid. +func (a Protocol) HasReadWriteFlags() bool { + return a.is79() +} + +// HasGetattrFlags returns whether GetattrRequest field Flags is +// valid. +func (a Protocol) HasGetattrFlags() bool { + return a.is79() +} + +func (a Protocol) is710() bool { + return a.GE(Protocol{7, 10}) +} + +// HasOpenNonSeekable returns whether OpenResponse field Flags flag +// OpenNonSeekable is supported. +func (a Protocol) HasOpenNonSeekable() bool { + return a.is710() +} + +func (a Protocol) is712() bool { + return a.GE(Protocol{7, 12}) +} + +// HasUmask returns whether CreateRequest/MkdirRequest/MknodRequest +// field Umask is valid. +func (a Protocol) HasUmask() bool { + return a.is712() +} + +// HasInvalidate returns whether InvalidateNode/InvalidateEntry are +// supported. +func (a Protocol) HasInvalidate() bool { + return a.is712() +} From 7b2b33d52633d074afbd5152697ef7e21b59e2cf Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 15:53:23 +1000 Subject: [PATCH 003/157] Fixed some build errors. --- internal/fuseshim/fuse.go | 2 +- internal/fuseshim/mount_darwin.go | 2 +- internal/fuseshim/options.go | 2 +- internal/fuseshim/options_darwin.go | 2 +- internal/fuseshim/options_helper_test.go | 2 +- internal/fuseshim/options_nocomma_test.go | 2 +- internal/fuseshim/options_test.go | 2 +- internal/fuseshim/protocol.go | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index c09095b..6a11f5e 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -98,7 +98,7 @@ // Behavior and metadata of the mounted file system can be changed by // passing MountOption values to Mount. // -package bazilfuse // import "github.com/jacobsa/bazilfuse" +package fuseshim import ( "bytes" diff --git a/internal/fuseshim/mount_darwin.go b/internal/fuseshim/mount_darwin.go index dc9b88d..cf8646f 100644 --- a/internal/fuseshim/mount_darwin.go +++ b/internal/fuseshim/mount_darwin.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim import ( "bytes" diff --git a/internal/fuseshim/options.go b/internal/fuseshim/options.go index 2c1b8f7..6365c06 100644 --- a/internal/fuseshim/options.go +++ b/internal/fuseshim/options.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim import ( "errors" diff --git a/internal/fuseshim/options_darwin.go b/internal/fuseshim/options_darwin.go index 6394c1c..20d85ea 100644 --- a/internal/fuseshim/options_darwin.go +++ b/internal/fuseshim/options_darwin.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim func localVolume(conf *mountConfig) error { conf.options["local"] = "" diff --git a/internal/fuseshim/options_helper_test.go b/internal/fuseshim/options_helper_test.go index dad7706..57b0dca 100644 --- a/internal/fuseshim/options_helper_test.go +++ b/internal/fuseshim/options_helper_test.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim // for TestMountOptionCommaError func ForTestSetMountOption(k, v string) MountOption { diff --git a/internal/fuseshim/options_nocomma_test.go b/internal/fuseshim/options_nocomma_test.go index c05bc10..6353e03 100644 --- a/internal/fuseshim/options_nocomma_test.go +++ b/internal/fuseshim/options_nocomma_test.go @@ -3,7 +3,7 @@ // // +build darwin -package bazilfuse_test +package fuseshim_test import ( "runtime" diff --git a/internal/fuseshim/options_test.go b/internal/fuseshim/options_test.go index 63c97e1..a5048ac 100644 --- a/internal/fuseshim/options_test.go +++ b/internal/fuseshim/options_test.go @@ -1,4 +1,4 @@ -package bazilfuse_test +package fuseshim_test import ( "os" diff --git a/internal/fuseshim/protocol.go b/internal/fuseshim/protocol.go index a13fc12..daf0466 100644 --- a/internal/fuseshim/protocol.go +++ b/internal/fuseshim/protocol.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim import ( "fmt" From e8ddcd361c0e226836a9f4f2e6e0b608405567d6 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 15:54:32 +1000 Subject: [PATCH 004/157] Fixed kernel package name. --- internal/fusekernel/fuse_kernel.go | 2 +- internal/fusekernel/fuse_kernel_darwin.go | 2 +- internal/fusekernel/fuse_kernel_std.go | 2 +- internal/fusekernel/fuse_kernel_test.go | 2 +- internal/fuseshim/options.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go index 37404ab..de70c77 100644 --- a/internal/fusekernel/fuse_kernel.go +++ b/internal/fusekernel/fuse_kernel.go @@ -33,7 +33,7 @@ SUCH DAMAGE. */ -package bazilfuse +package fusekernel import ( "fmt" diff --git a/internal/fusekernel/fuse_kernel_darwin.go b/internal/fusekernel/fuse_kernel_darwin.go index a5200e6..750e960 100644 --- a/internal/fusekernel/fuse_kernel_darwin.go +++ b/internal/fusekernel/fuse_kernel_darwin.go @@ -1,4 +1,4 @@ -package bazilfuse +package fusekernel import ( "time" diff --git a/internal/fusekernel/fuse_kernel_std.go b/internal/fusekernel/fuse_kernel_std.go index 98b9541..704fb00 100644 --- a/internal/fusekernel/fuse_kernel_std.go +++ b/internal/fusekernel/fuse_kernel_std.go @@ -1 +1 @@ -package bazilfuse +package fusekernel diff --git a/internal/fusekernel/fuse_kernel_test.go b/internal/fusekernel/fuse_kernel_test.go index 821bccb..bec3cfd 100644 --- a/internal/fusekernel/fuse_kernel_test.go +++ b/internal/fusekernel/fuse_kernel_test.go @@ -1,4 +1,4 @@ -package bazilfuse_test +package fusekernel_test import ( "os" diff --git a/internal/fuseshim/options.go b/internal/fuseshim/options.go index 6365c06..285790f 100644 --- a/internal/fuseshim/options.go +++ b/internal/fuseshim/options.go @@ -14,7 +14,7 @@ func dummyOption(conf *mountConfig) error { type mountConfig struct { options map[string]string maxReadahead uint32 - initFlags InitFlags + initFlags fusekernel.InitFlags } func escapeComma(s string) string { From 4bc4c23872efe6a16a81dd0e64b5b07665b9cb94 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 15:54:57 +1000 Subject: [PATCH 005/157] Fixed Protocol layering. --- internal/{fuseshim => fusekernel}/protocol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename internal/{fuseshim => fusekernel}/protocol.go (98%) diff --git a/internal/fuseshim/protocol.go b/internal/fusekernel/protocol.go similarity index 98% rename from internal/fuseshim/protocol.go rename to internal/fusekernel/protocol.go index daf0466..9bc17e2 100644 --- a/internal/fuseshim/protocol.go +++ b/internal/fusekernel/protocol.go @@ -1,4 +1,4 @@ -package fuseshim +package fusekernel import ( "fmt" From f0fb8be9b42aa9ebffdedbd9fe54a90b46f136bc Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 15:55:54 +1000 Subject: [PATCH 006/157] Fixed some fuseshim build errors. --- internal/fuseshim/fuse.go | 36 +++++++++++++++++++----------------- internal/fuseshim/options.go | 2 ++ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 6a11f5e..9eb55cf 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -111,6 +111,8 @@ import ( "syscall" "time" "unsafe" + + "github.com/jacobsa/fuse/internal/fusekernel" ) // A Conn represents a connection to a mounted FUSE file system. @@ -129,7 +131,7 @@ type Conn struct { rio sync.RWMutex // Protocol version negotiated with InitRequest/InitResponse. - proto Protocol + proto fusekernel.Protocol } // Mount mounts a new FUSE connection on the named directory @@ -171,8 +173,8 @@ func Mount(dir string, options ...MountOption) (*Conn, error) { } type OldVersionError struct { - Kernel Protocol - LibraryMin Protocol + Kernel fusekernel.Protocol + LibraryMin fusekernel.Protocol } func (e *OldVersionError) Error() string { @@ -192,7 +194,7 @@ func initMount(c *Conn, conf *mountConfig) error { return fmt.Errorf("missing init, got: %T", req) } - min := Protocol{protoVersionMinMajor, protoVersionMinMinor} + min := fusekernel.Protocol{protoVersionMinMajor, protoVersionMinMinor} if r.Kernel.LT(min) { req.RespondError(Errno(syscall.EPROTO)) c.Close() @@ -202,7 +204,7 @@ func initMount(c *Conn, conf *mountConfig) error { } } - proto := Protocol{protoVersionMaxMajor, protoVersionMaxMinor} + proto := fusekernel.Protocol{protoVersionMaxMajor, protoVersionMaxMinor} if r.Kernel.LT(proto) { // Kernel doesn't support the latest version we have. proto = r.Kernel @@ -518,7 +520,7 @@ func (c *Conn) fd() int { return int(c.dev.Fd()) } -func (c *Conn) Protocol() Protocol { +func (c *Conn) Protocol() fusekernel.Protocol { return c.proto } @@ -603,7 +605,7 @@ loop: case opGetattr: switch { - case c.proto.LT(Protocol{7, 9}): + case c.proto.LT(fusekernel.Protocol{7, 9}): req = &GetattrRequest{ Header: m.Header(), } @@ -698,7 +700,7 @@ loop: Rdev: in.Rdev, Name: string(name), } - if c.proto.GE(Protocol{7, 12}) { + if c.proto.GE(fusekernel.Protocol{7, 12}) { r.Umask = fileMode(in.Umask) & os.ModePerm } req = r @@ -722,7 +724,7 @@ loop: // code branch; enforce type to directory Mode: fileMode((in.Mode &^ syscall.S_IFMT) | syscall.S_IFDIR), } - if c.proto.GE(Protocol{7, 12}) { + if c.proto.GE(fusekernel.Protocol{7, 12}) { r.Umask = fileMode(in.Umask) & os.ModePerm } req = r @@ -788,7 +790,7 @@ loop: Offset: int64(in.Offset), Size: int(in.Size), } - if c.proto.GE(Protocol{7, 9}) { + if c.proto.GE(fusekernel.Protocol{7, 9}) { r.Flags = ReadFlags(in.ReadFlags) r.LockOwner = in.LockOwner r.FileFlags = openFlags(in.Flags) @@ -806,7 +808,7 @@ loop: Offset: int64(in.Offset), Flags: WriteFlags(in.WriteFlags), } - if c.proto.GE(Protocol{7, 9}) { + if c.proto.GE(fusekernel.Protocol{7, 9}) { r.LockOwner = in.LockOwner r.FileFlags = openFlags(in.Flags) } @@ -930,7 +932,7 @@ loop: } req = &InitRequest{ Header: m.Header(), - Kernel: Protocol{in.Major, in.Minor}, + Kernel: fusekernel.Protocol{in.Major, in.Minor}, MaxReadahead: in.MaxReadahead, Flags: InitFlags(in.Flags), } @@ -969,7 +971,7 @@ loop: Mode: fileMode(in.Mode), Name: string(name[:i]), } - if c.proto.GE(Protocol{7, 12}) { + if c.proto.GE(fusekernel.Protocol{7, 12}) { r.Umask = fileMode(in.Umask) & os.ModePerm } req = r @@ -1151,7 +1153,7 @@ func (c *Conn) InvalidateEntry(parent NodeID, name string) error { // An InitRequest is the first request sent on a FUSE file system. type InitRequest struct { Header `json:"-"` - Kernel Protocol + Kernel fusekernel.Protocol // Maximum readahead in bytes that the kernel plans to use. MaxReadahead uint32 Flags InitFlags @@ -1165,7 +1167,7 @@ func (r *InitRequest) String() string { // An InitResponse is the response to an InitRequest. type InitResponse struct { - Library Protocol + Library fusekernel.Protocol // Maximum readahead in bytes that the kernel can use. Ignored if // greater than InitRequest.MaxReadahead. MaxReadahead uint32 @@ -1287,7 +1289,7 @@ func unix(t time.Time) (sec uint64, nsec uint32) { return } -func (a *Attr) attr(out *attr, proto Protocol) { +func (a *Attr) attr(out *attr, proto fusekernel.Protocol) { out.Ino = a.Inode out.Size = a.Size out.Blocks = a.Blocks @@ -1325,7 +1327,7 @@ func (a *Attr) attr(out *attr, proto Protocol) { out.Gid = a.Gid out.Rdev = a.Rdev out.SetFlags(a.Flags) - if proto.GE(Protocol{7, 9}) { + if proto.GE(fusekernel.Protocol{7, 9}) { out.Blksize = a.BlockSize } diff --git a/internal/fuseshim/options.go b/internal/fuseshim/options.go index 285790f..7e89207 100644 --- a/internal/fuseshim/options.go +++ b/internal/fuseshim/options.go @@ -3,6 +3,8 @@ package fuseshim import ( "errors" "strings" + + "github.com/jacobsa/fuse/internal/fusekernel" ) func dummyOption(conf *mountConfig) error { From d6a582c6a5f5b58610f6c28fba3c21abbdd2036c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 15:58:40 +1000 Subject: [PATCH 007/157] Fixed more shim errors. --- internal/fusekernel/fuse_kernel.go | 10 +++--- internal/fusekernel/fuse_kernel_darwin.go | 6 ++-- internal/fuseshim/fuse.go | 44 +++++++++++------------ 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go index de70c77..b7a5721 100644 --- a/internal/fusekernel/fuse_kernel.go +++ b/internal/fusekernel/fuse_kernel.go @@ -50,7 +50,7 @@ const ( ) const ( - rootID = 1 + RootID = 1 ) type kstatfs struct { @@ -394,7 +394,7 @@ type entryOut struct { AttrValid uint64 // Cache timeout for the attributes EntryValidNsec uint32 AttrValidNsec uint32 - Attr attr + Attr Attr } func entryOutSize(p Protocol) uintptr { @@ -420,7 +420,7 @@ type attrOut struct { AttrValid uint64 // Cache timeout for the attributes AttrValidNsec uint32 Dummy uint32 - Attr attr + Attr Attr } func attrOutSize(p Protocol) uintptr { @@ -718,7 +718,7 @@ type bmapOut struct { Block uint64 } -type inHeader struct { +type InHeader struct { Len uint32 Opcode uint32 Unique uint64 @@ -729,7 +729,7 @@ type inHeader struct { Padding uint32 } -const inHeaderSize = int(unsafe.Sizeof(inHeader{})) +const inHeaderSize = int(unsafe.Sizeof(InHeader{})) type outHeader struct { Len uint32 diff --git a/internal/fusekernel/fuse_kernel_darwin.go b/internal/fusekernel/fuse_kernel_darwin.go index 750e960..eae4262 100644 --- a/internal/fusekernel/fuse_kernel_darwin.go +++ b/internal/fusekernel/fuse_kernel_darwin.go @@ -4,7 +4,7 @@ import ( "time" ) -type attr struct { +type Attr struct { Ino uint64 Size uint64 Blocks uint64 @@ -26,11 +26,11 @@ type attr struct { padding uint32 } -func (a *attr) SetCrtime(s uint64, ns uint32) { +func (a *Attr) SetCrtime(s uint64, ns uint32) { a.Crtime_, a.CrtimeNsec = s, ns } -func (a *attr) SetFlags(f uint32) { +func (a *Attr) SetFlags(f uint32) { a.Flags_ = f } diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 9eb55cf..69ce0a8 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -247,7 +247,7 @@ type NodeID uint64 type HandleID uint64 // The RootID identifies the root directory of a FUSE file system. -const RootID NodeID = rootID +const RootID NodeID = fusekernel.RootID // A Header describes the basic information sent in every request. type Header struct { @@ -390,7 +390,7 @@ var reqPool struct { func allocMessage() *message { m := &message{buf: make([]byte, bufSize)} - m.hdr = (*inHeader)(unsafe.Pointer(&m.buf[0])) + m.hdr = (*fusekernel.InHeader)(unsafe.Pointer(&m.buf[0])) return m } @@ -425,9 +425,9 @@ func putMessage(m *message) { // 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 + buf []byte // all bytes + hdr *fusekernel.InHeader // header + off int // offset for reading additional fields } func (m *message) len() uintptr { @@ -617,7 +617,7 @@ loop: } req = &GetattrRequest{ Header: m.Header(), - Flags: GetattrFlags(in.GetattrFlags), + Flags: fusekernel.GetattrFlags(in.GetattrFlags), Handle: HandleID(in.Fh), } } @@ -934,7 +934,7 @@ loop: Header: m.Header(), Kernel: fusekernel.Protocol{in.Major, in.Minor}, MaxReadahead: in.MaxReadahead, - Flags: InitFlags(in.Flags), + Flags: fusekernel.InitFlags(in.Flags), } case opGetlk: @@ -1156,7 +1156,7 @@ type InitRequest struct { Kernel fusekernel.Protocol // Maximum readahead in bytes that the kernel plans to use. MaxReadahead uint32 - Flags InitFlags + Flags fusekernel.InitFlags } var _ = Request(&InitRequest{}) @@ -1171,7 +1171,7 @@ type InitResponse struct { // Maximum readahead in bytes that the kernel can use. Ignored if // greater than InitRequest.MaxReadahead. MaxReadahead uint32 - Flags InitFlags + Flags fusekernel.InitFlags // Maximum size of a single write operation. // Linux enforces a minimum of 4 KiB. MaxWrite uint32 @@ -1289,7 +1289,7 @@ func unix(t time.Time) (sec uint64, nsec uint32) { return } -func (a *Attr) attr(out *attr, proto fusekernel.Protocol) { +func (a *Attr) attr(out *fusekernel.Attr, proto fusekernel.Protocol) { out.Ino = a.Inode out.Size = a.Size out.Blocks = a.Blocks @@ -1337,7 +1337,7 @@ func (a *Attr) attr(out *attr, proto fusekernel.Protocol) { // A GetattrRequest asks for the metadata for the file denoted by r.Node. type GetattrRequest struct { Header `json:"-"` - Flags GetattrFlags + Flags fusekernel.GetattrFlags Handle HandleID } @@ -1563,7 +1563,7 @@ func (r *LookupResponse) String() string { type OpenRequest struct { Header `json:"-"` Dir bool // is this Opendir? - Flags OpenFlags + Flags fusekernel.OpenFlags } var _ = Request(&OpenRequest{}) @@ -1584,7 +1584,7 @@ func (r *OpenRequest) Respond(resp *OpenResponse) { // A OpenResponse is the response to a OpenRequest. type OpenResponse struct { Handle HandleID - Flags OpenResponseFlags + Flags fusekernel.OpenResponseFlags } func (r *OpenResponse) String() string { @@ -1595,7 +1595,7 @@ func (r *OpenResponse) String() string { type CreateRequest struct { Header `json:"-"` Name string - Flags OpenFlags + Flags fusekernel.OpenFlags Mode os.FileMode Umask os.FileMode } @@ -1683,9 +1683,9 @@ type ReadRequest struct { Handle HandleID Offset int64 Size int - Flags ReadFlags + Flags fusekernel.ReadFlags LockOwner uint64 - FileFlags OpenFlags + FileFlags fusekernel.OpenFlags } var _ = Request(&ReadRequest{}) @@ -1726,8 +1726,8 @@ type ReleaseRequest struct { Header `json:"-"` Dir bool // is this Releasedir? Handle HandleID - Flags OpenFlags // flags from OpenRequest - ReleaseFlags ReleaseFlags + Flags fusekernel.OpenFlags // flags from OpenRequest + ReleaseFlags fusekernel.ReleaseFlags LockOwner uint32 } @@ -1868,9 +1868,9 @@ type WriteRequest struct { Handle HandleID Offset int64 Data []byte - Flags WriteFlags + Flags fusekernel.WriteFlags LockOwner uint64 - FileFlags OpenFlags + FileFlags fusekernel.OpenFlags } var _ = Request(&WriteRequest{}) @@ -1883,7 +1883,7 @@ type jsonWriteRequest struct { Handle HandleID Offset int64 Len uint64 - Flags WriteFlags + Flags fusekernel.WriteFlags } func (r *WriteRequest) MarshalJSON() ([]byte, error) { @@ -1917,7 +1917,7 @@ func (r *WriteResponse) String() string { // as indicated by Valid. type SetattrRequest struct { Header `json:"-"` - Valid SetattrValid + Valid fusekernel.SetattrValid Handle HandleID Size uint64 Atime time.Time From ca5f985182ebdac5bb5f21437c507f7028ef38b9 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 15:59:29 +1000 Subject: [PATCH 008/157] Fixed more errors. --- internal/fusekernel/fuse_kernel.go | 10 +++++----- internal/fuseshim/fuse.go | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go index b7a5721..921f4fd 100644 --- a/internal/fusekernel/fuse_kernel.go +++ b/internal/fusekernel/fuse_kernel.go @@ -43,10 +43,10 @@ import ( // The FUSE version implemented by the package. const ( - protoVersionMinMajor = 7 - protoVersionMinMinor = 8 - protoVersionMaxMajor = 7 - protoVersionMaxMinor = 12 + ProtoVersionMinMajor = 7 + ProtoVersionMinMinor = 8 + ProtoVersionMaxMajor = 7 + ProtoVersionMaxMinor = 12 ) const ( @@ -731,7 +731,7 @@ type InHeader struct { const inHeaderSize = int(unsafe.Sizeof(InHeader{})) -type outHeader struct { +type OutHeader struct { Len uint32 Error int32 Unique uint64 diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 69ce0a8..1e927cc 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -194,7 +194,7 @@ func initMount(c *Conn, conf *mountConfig) error { return fmt.Errorf("missing init, got: %T", req) } - min := fusekernel.Protocol{protoVersionMinMajor, protoVersionMinMinor} + min := fusekernel.Protocol{fusekernel.ProtoVersionMinMajor, fusekernel.ProtoVersionMinMinor} if r.Kernel.LT(min) { req.RespondError(Errno(syscall.EPROTO)) c.Close() @@ -204,7 +204,7 @@ func initMount(c *Conn, conf *mountConfig) error { } } - proto := fusekernel.Protocol{protoVersionMaxMajor, protoVersionMaxMinor} + proto := fusekernel.Protocol{fusekernel.ProtoVersionMaxMajor, fusekernel.ProtoVersionMaxMinor} if r.Kernel.LT(proto) { // Kernel doesn't support the latest version we have. proto = r.Kernel @@ -215,7 +215,7 @@ func initMount(c *Conn, conf *mountConfig) error { Library: proto, MaxReadahead: conf.maxReadahead, MaxWrite: maxWrite, - Flags: InitBigWrites | conf.initFlags, + Flags: fusekernel.InitBigWrites | conf.initFlags, } r.Respond(s) return nil @@ -275,7 +275,7 @@ func (h *Header) noResponse() { } func (h *Header) respond(msg []byte) { - out := (*outHeader)(unsafe.Pointer(&msg[0])) + out := (*fusekernel.OutHeader)(unsafe.Pointer(&msg[0])) out.Unique = uint64(h.ID) h.Conn.respond(msg) putMessage(h.msg) @@ -365,7 +365,7 @@ func (h *Header) RespondError(err error) { // FUSE uses negative errors! // TODO: File bug report against OSXFUSE: positive error causes kernel panic. buf := newBuffer(0) - hOut := (*outHeader)(unsafe.Pointer(&buf[0])) + hOut := (*fusekernel.OutHeader)(unsafe.Pointer(&buf[0])) hOut.Error = -int32(errno) h.respond(buf) } @@ -1046,7 +1046,7 @@ func errorString(err error) string { } func (c *Conn) writeToKernel(msg []byte) error { - out := (*outHeader)(unsafe.Pointer(&msg[0])) + out := (*fusekernel.OutHeader)(unsafe.Pointer(&msg[0])) out.Len = uint32(len(msg)) c.wio.RLock() @@ -1112,7 +1112,7 @@ func (c *Conn) sendInvalidate(msg []byte) error { // node. func (c *Conn) InvalidateNode(nodeID NodeID, off int64, size int64) error { buf := newBuffer(unsafe.Sizeof(notifyInvalInodeOut{})) - h := (*outHeader)(unsafe.Pointer(&buf[0])) + h := (*fusekernel.OutHeader)(unsafe.Pointer(&buf[0])) // h.Unique is 0 h.Error = notifyCodeInvalInode out := (*notifyInvalInodeOut)(buf.alloc(unsafe.Sizeof(notifyInvalInodeOut{}))) @@ -1139,7 +1139,7 @@ func (c *Conn) InvalidateEntry(parent NodeID, name string) error { return syscall.ENAMETOOLONG } buf := newBuffer(unsafe.Sizeof(notifyInvalEntryOut{}) + uintptr(len(name)) + 1) - h := (*outHeader)(unsafe.Pointer(&buf[0])) + h := (*fusekernel.OutHeader)(unsafe.Pointer(&buf[0])) // h.Unique is 0 h.Error = notifyCodeInvalEntry out := (*notifyInvalEntryOut)(buf.alloc(unsafe.Sizeof(notifyInvalEntryOut{}))) From 4dd9732c5b4932ba34ab31ec0d40fe1d7c9178a9 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:00:10 +1000 Subject: [PATCH 009/157] Copied a missing file from from jacobsa/bazilfuse@b378951. --- internal/fuseshim/buffer.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 internal/fuseshim/buffer.go diff --git a/internal/fuseshim/buffer.go b/internal/fuseshim/buffer.go new file mode 100644 index 0000000..fa482a2 --- /dev/null +++ b/internal/fuseshim/buffer.go @@ -0,0 +1,35 @@ +package bazilfuse + +import "unsafe" + +// 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) buffer { + const hdrSize = unsafe.Sizeof(outHeader{}) + buf := make(buffer, hdrSize, hdrSize+extra) + return buf +} From bd41a4e788ec1595ec0ffd1896316ad5a25fcd29 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:01:45 +1000 Subject: [PATCH 010/157] Fixed more build errors. --- internal/fusekernel/fuse_kernel.go | 86 ++++++++++++------------- internal/fuseshim/buffer.go | 10 ++- internal/fuseshim/fuse.go | 100 ++++++++++++++--------------- 3 files changed, 100 insertions(+), 96 deletions(-) diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go index 921f4fd..1db15e0 100644 --- a/internal/fusekernel/fuse_kernel.go +++ b/internal/fusekernel/fuse_kernel.go @@ -342,49 +342,49 @@ var releaseFlagNames = []flagName{ // 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? + 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 + OpSetvolname = 61 + OpGetxtimes = 62 + OpExchange = 63 ) type entryOut struct { @@ -693,7 +693,7 @@ type initIn struct { Flags uint32 } -const initInSize = int(unsafe.Sizeof(initIn{})) +const InitInSize = int(unsafe.Sizeof(initIn{})) type initOut struct { Major uint32 @@ -729,7 +729,7 @@ type InHeader struct { Padding uint32 } -const inHeaderSize = int(unsafe.Sizeof(InHeader{})) +const InHeaderSize = int(unsafe.Sizeof(InHeader{})) type OutHeader struct { Len uint32 diff --git a/internal/fuseshim/buffer.go b/internal/fuseshim/buffer.go index fa482a2..d57bb44 100644 --- a/internal/fuseshim/buffer.go +++ b/internal/fuseshim/buffer.go @@ -1,6 +1,10 @@ -package bazilfuse +package fuseshim -import "unsafe" +import ( + "unsafe" + + "github.com/jacobsa/fuse/internal/fusekernel" +) // buffer provides a mechanism for constructing a message from // multiple segments. @@ -29,7 +33,7 @@ func (w *buffer) reset() { } func newBuffer(extra uintptr) buffer { - const hdrSize = unsafe.Sizeof(outHeader{}) + const hdrSize = unsafe.Sizeof(fusekernel.OutHeader{}) buf := make(buffer, hdrSize, hdrSize+extra) return buf } diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 1e927cc..bde402e 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -549,19 +549,19 @@ loop: } m.buf = m.buf[:n] - if n < inHeaderSize { + if n < fusekernel.InHeaderSize { putMessage(m) 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) { + 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(writeIn{})) && m.hdr.Opcode == opWrite { + if m.hdr.Len < uint32(n) && m.hdr.Len >= uint32(unsafe.Sizeof(writeIn{})) && m.hdr.Opcode == fusekernel.OpWrite { m.hdr.Len = uint32(n) } @@ -572,7 +572,7 @@ loop: return nil, err } - m.off = inHeaderSize + m.off = fusekernel.InHeaderSize // Convert to data structures. // Do not trust kernel to hand us well-formed data. @@ -582,7 +582,7 @@ loop: Debug(noOpcode{Opcode: m.hdr.Opcode}) goto unrecognized - case opLookup: + case fusekernel.OpLookup: buf := m.bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { @@ -593,7 +593,7 @@ loop: Name: string(buf[:n-1]), } - case opForget: + case fusekernel.OpForget: in := (*forgetIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -603,7 +603,7 @@ loop: N: in.Nlookup, } - case opGetattr: + case fusekernel.OpGetattr: switch { case c.proto.LT(fusekernel.Protocol{7, 9}): req = &GetattrRequest{ @@ -622,7 +622,7 @@ loop: } } - case opSetattr: + case fusekernel.OpSetattr: in := (*setattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -642,7 +642,7 @@ loop: Flags: in.Flags(), } - case opReadlink: + case fusekernel.OpReadlink: if len(m.bytes()) > 0 { goto corrupt } @@ -650,7 +650,7 @@ loop: Header: m.Header(), } - case opSymlink: + case fusekernel.OpSymlink: // m.bytes() is "newName\0target\0" names := m.bytes() if len(names) == 0 || names[len(names)-1] != 0 { @@ -667,7 +667,7 @@ loop: Target: string(target), } - case opLink: + case fusekernel.OpLink: in := (*linkIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -683,7 +683,7 @@ loop: NewName: string(newName), } - case opMknod: + case fusekernel.OpMknod: size := mknodInSize(c.proto) if m.len() < size { goto corrupt @@ -705,7 +705,7 @@ loop: } req = r - case opMkdir: + case fusekernel.OpMkdir: size := mkdirInSize(c.proto) if m.len() < size { goto corrupt @@ -729,7 +729,7 @@ loop: } req = r - case opUnlink, opRmdir: + case fusekernel.OpUnlink, fusekernel.OpRmdir: buf := m.bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { @@ -738,10 +738,10 @@ loop: req = &RemoveRequest{ Header: m.Header(), Name: string(buf[:n-1]), - Dir: m.hdr.Opcode == opRmdir, + Dir: m.hdr.Opcode == fusekernel.OpRmdir, } - case opRename: + case fusekernel.OpRename: in := (*renameIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -767,25 +767,25 @@ loop: NewName: newName, } - case opOpendir, opOpen: + case fusekernel.OpOpendir, fusekernel.OpOpen: in := (*openIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &OpenRequest{ Header: m.Header(), - Dir: m.hdr.Opcode == opOpendir, + Dir: m.hdr.Opcode == fusekernel.OpOpendir, Flags: openFlags(in.Flags), } - case opRead, opReaddir: + case fusekernel.OpRead, fusekernel.OpReaddir: in := (*readIn)(m.data()) if m.len() < readInSize(c.proto) { goto corrupt } r := &ReadRequest{ Header: m.Header(), - Dir: m.hdr.Opcode == opReaddir, + Dir: m.hdr.Opcode == fusekernel.OpReaddir, Handle: HandleID(in.Fh), Offset: int64(in.Offset), Size: int(in.Size), @@ -797,7 +797,7 @@ loop: } req = r - case opWrite: + case fusekernel.OpWrite: in := (*writeIn)(m.data()) if m.len() < writeInSize(c.proto) { goto corrupt @@ -819,38 +819,38 @@ loop: r.Data = buf req = r - case opStatfs: + case fusekernel.OpStatfs: req = &StatfsRequest{ Header: m.Header(), } - case opRelease, opReleasedir: + case fusekernel.OpRelease, fusekernel.OpReleasedir: in := (*releaseIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &ReleaseRequest{ Header: m.Header(), - Dir: m.hdr.Opcode == opReleasedir, + Dir: m.hdr.Opcode == fusekernel.OpReleasedir, Handle: HandleID(in.Fh), Flags: openFlags(in.Flags), ReleaseFlags: ReleaseFlags(in.ReleaseFlags), LockOwner: in.LockOwner, } - case opFsync, opFsyncdir: + case fusekernel.OpFsync, fusekernel.OpFsyncdir: in := (*fsyncIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &FsyncRequest{ - Dir: m.hdr.Opcode == opFsyncdir, + Dir: m.hdr.Opcode == fusekernel.OpFsyncdir, Header: m.Header(), Handle: HandleID(in.Fh), Flags: in.FsyncFlags, } - case opSetxattr: + case fusekernel.OpSetxattr: in := (*setxattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -874,7 +874,7 @@ loop: Xattr: xattr, } - case opGetxattr: + case fusekernel.OpGetxattr: in := (*getxattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -891,7 +891,7 @@ loop: Position: in.position(), } - case opListxattr: + case fusekernel.OpListxattr: in := (*getxattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -902,7 +902,7 @@ loop: Position: in.position(), } - case opRemovexattr: + case fusekernel.OpRemovexattr: buf := m.bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { @@ -913,7 +913,7 @@ loop: Name: string(buf[:n-1]), } - case opFlush: + case fusekernel.OpFlush: in := (*flushIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -925,7 +925,7 @@ loop: LockOwner: in.LockOwner, } - case opInit: + case fusekernel.OpInit: in := (*initIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -937,14 +937,14 @@ loop: Flags: fusekernel.InitFlags(in.Flags), } - case opGetlk: - panic("opGetlk") - case opSetlk: - panic("opSetlk") - case opSetlkw: - panic("opSetlkw") + case fusekernel.OpGetlk: + panic("fusekernel.OpGetlk") + case fusekernel.OpSetlk: + panic("fusekernel.OpSetlk") + case fusekernel.OpSetlkw: + panic("fusekernel.OpSetlkw") - case opAccess: + case fusekernel.OpAccess: in := (*accessIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -954,7 +954,7 @@ loop: Mask: in.Mask, } - case opCreate: + case fusekernel.OpCreate: size := createInSize(c.proto) if m.len() < size { goto corrupt @@ -976,7 +976,7 @@ loop: } req = r - case opInterrupt: + case fusekernel.OpInterrupt: in := (*interruptIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt @@ -986,21 +986,21 @@ loop: IntrID: RequestID(in.Unique), } - case opBmap: - panic("opBmap") + case fusekernel.OpBmap: + panic("fusekernel.OpBmap") - case opDestroy: + case fusekernel.OpDestroy: req = &DestroyRequest{ Header: m.Header(), } // OS X - case opSetvolname: - panic("opSetvolname") - case opGetxtimes: - panic("opGetxtimes") - case opExchange: - panic("opExchange") + case fusekernel.OpSetvolname: + panic("fusekernel.OpSetvolname") + case fusekernel.OpGetxtimes: + panic("fusekernel.OpGetxtimes") + case fusekernel.OpExchange: + panic("fusekernel.OpExchange") } return req, nil From b14b41f3f5ac7a3c06bd88e9d8e457443d554f83 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:11:50 +1000 Subject: [PATCH 011/157] Fixed more build errors. --- internal/fusekernel/fuse_kernel.go | 84 ++++++++-------- internal/fusekernel/fuse_kernel_darwin.go | 20 ++-- internal/fuseshim/fuse.go | 111 ++++++++++------------ 3 files changed, 96 insertions(+), 119 deletions(-) diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go index 1db15e0..fe25ee4 100644 --- a/internal/fusekernel/fuse_kernel.go +++ b/internal/fusekernel/fuse_kernel.go @@ -397,7 +397,7 @@ type entryOut struct { Attr Attr } -func entryOutSize(p Protocol) uintptr { +func EntryOutSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): return unsafe.Offsetof(entryOut{}.Attr) + unsafe.Offsetof(entryOut{}.Attr.Blksize) @@ -406,11 +406,11 @@ func entryOutSize(p Protocol) uintptr { } } -type forgetIn struct { +type ForgetIn struct { Nlookup uint64 } -type getattrIn struct { +type GetattrIn struct { GetattrFlags uint32 dummy uint32 Fh uint64 @@ -423,7 +423,7 @@ type attrOut struct { Attr Attr } -func attrOutSize(p Protocol) uintptr { +func AttrOutSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): return unsafe.Offsetof(attrOut{}.Attr) + unsafe.Offsetof(attrOut{}.Attr.Blksize) @@ -440,7 +440,7 @@ type getxtimesOut struct { CrtimeNsec uint32 } -type mknodIn struct { +type MknodIn struct { Mode uint32 Rdev uint32 Umask uint32 @@ -448,43 +448,43 @@ type mknodIn struct { // "filename\x00" follows. } -func mknodInSize(p Protocol) uintptr { +func MknodInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 12}): - return unsafe.Offsetof(mknodIn{}.Umask) + return unsafe.Offsetof(MknodIn{}.Umask) default: - return unsafe.Sizeof(mknodIn{}) + return unsafe.Sizeof(MknodIn{}) } } -type mkdirIn struct { +type MkdirIn struct { Mode uint32 Umask uint32 // filename follows } -func mkdirInSize(p Protocol) uintptr { +func MkdirInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 12}): - return unsafe.Offsetof(mkdirIn{}.Umask) + 4 + return unsafe.Offsetof(MkdirIn{}.Umask) + 4 default: - return unsafe.Sizeof(mkdirIn{}) + return unsafe.Sizeof(MkdirIn{}) } } -type renameIn struct { +type RenameIn struct { Newdir uint64 // "oldname\x00newname\x00" follows } // OS X -type exchangeIn struct { +type ExchangeIn struct { Olddir uint64 Newdir uint64 Options uint64 } -type linkIn struct { +type LinkIn struct { Oldnodeid uint64 } @@ -507,7 +507,7 @@ type setattrInCommon struct { Unused5 uint32 } -type openIn struct { +type OpenIn struct { Flags uint32 Unused uint32 } @@ -518,37 +518,37 @@ type openOut struct { Padding uint32 } -type createIn struct { +type CreateIn struct { Flags uint32 Mode uint32 Umask uint32 padding uint32 } -func createInSize(p Protocol) uintptr { +func CreateInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 12}): - return unsafe.Offsetof(createIn{}.Umask) + return unsafe.Offsetof(CreateIn{}.Umask) default: - return unsafe.Sizeof(createIn{}) + return unsafe.Sizeof(CreateIn{}) } } -type releaseIn struct { +type ReleaseIn struct { Fh uint64 Flags uint32 ReleaseFlags uint32 LockOwner uint32 } -type flushIn struct { +type FlushIn struct { Fh uint64 FlushFlags uint32 Padding uint32 LockOwner uint64 } -type readIn struct { +type ReadIn struct { Fh uint64 Offset uint64 Size uint32 @@ -558,12 +558,12 @@ type readIn struct { padding uint32 } -func readInSize(p Protocol) uintptr { +func ReadInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): - return unsafe.Offsetof(readIn{}.ReadFlags) + 4 + return unsafe.Offsetof(ReadIn{}.ReadFlags) + 4 default: - return unsafe.Sizeof(readIn{}) + return unsafe.Sizeof(ReadIn{}) } } @@ -583,7 +583,7 @@ func (fl ReadFlags) String() string { return flagString(uint32(fl), readFlagNames) } -type writeIn struct { +type WriteIn struct { Fh uint64 Offset uint64 Size uint32 @@ -593,12 +593,12 @@ type writeIn struct { padding uint32 } -func writeInSize(p Protocol) uintptr { +func WriteInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): - return unsafe.Offsetof(writeIn{}.LockOwner) + return unsafe.Offsetof(WriteIn{}.LockOwner) default: - return unsafe.Sizeof(writeIn{}) + return unsafe.Sizeof(WriteIn{}) } } @@ -631,7 +631,7 @@ type statfsOut struct { St kstatfs } -type fsyncIn struct { +type FsyncIn struct { Fh uint64 FsyncFlags uint32 Padding uint32 @@ -660,7 +660,7 @@ type getxattrOut struct { Padding uint32 } -type lkIn struct { +type LkIn struct { Fh uint64 Owner uint64 Lk fileLock @@ -668,12 +668,12 @@ type lkIn struct { padding uint32 } -func lkInSize(p Protocol) uintptr { +func LkInSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): - return unsafe.Offsetof(lkIn{}.LkFlags) + return unsafe.Offsetof(LkIn{}.LkFlags) default: - return unsafe.Sizeof(lkIn{}) + return unsafe.Sizeof(LkIn{}) } } @@ -681,19 +681,19 @@ type lkOut struct { Lk fileLock } -type accessIn struct { +type AccessIn struct { Mask uint32 Padding uint32 } -type initIn struct { +type InitIn struct { Major uint32 Minor uint32 MaxReadahead uint32 Flags uint32 } -const InitInSize = int(unsafe.Sizeof(initIn{})) +const InitInSize = int(unsafe.Sizeof(InitIn{})) type initOut struct { Major uint32 @@ -704,11 +704,11 @@ type initOut struct { MaxWrite uint32 } -type interruptIn struct { +type InterruptIn struct { Unique uint64 } -type bmapIn struct { +type BmapIn struct { Block uint64 BlockSize uint32 Padding uint32 @@ -753,13 +753,13 @@ const ( notifyCodeInvalEntry int32 = 3 ) -type notifyInvalInodeOut struct { +type NotifyInvalInodeOut struct { Ino uint64 Off int64 Len int64 } -type notifyInvalEntryOut struct { +type NotifyInvalEntryOut struct { Parent uint64 Namelen uint32 padding uint32 diff --git a/internal/fusekernel/fuse_kernel_darwin.go b/internal/fusekernel/fuse_kernel_darwin.go index eae4262..2eb842d 100644 --- a/internal/fusekernel/fuse_kernel_darwin.go +++ b/internal/fusekernel/fuse_kernel_darwin.go @@ -34,7 +34,7 @@ func (a *Attr) SetFlags(f uint32) { a.Flags_ = f } -type setattrIn struct { +type SetattrIn struct { setattrInCommon // OS X only @@ -47,15 +47,15 @@ type setattrIn struct { Flags_ uint32 // see chflags(2) } -func (in *setattrIn) BkupTime() time.Time { +func (in *SetattrIn) BkupTime() time.Time { return time.Unix(int64(in.Bkuptime_), int64(in.BkuptimeNsec)) } -func (in *setattrIn) Chgtime() time.Time { +func (in *SetattrIn) Chgtime() time.Time { return time.Unix(int64(in.Chgtime_), int64(in.ChgtimeNsec)) } -func (in *setattrIn) Flags() uint32 { +func (in *SetattrIn) Flags() uint32 { return in.Flags_ } @@ -63,7 +63,7 @@ func openFlags(flags uint32) OpenFlags { return OpenFlags(flags) } -type getxattrIn struct { +type GetxattrIn struct { getxattrInCommon // OS X only @@ -71,18 +71,10 @@ type getxattrIn struct { Padding uint32 } -func (g *getxattrIn) position() uint32 { - return g.Position -} - -type setxattrIn struct { +type SetxattrIn struct { setxattrInCommon // OS X only Position uint32 Padding uint32 } - -func (s *setxattrIn) position() uint32 { - return s.Position -} diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index bde402e..e1e98e3 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -561,7 +561,7 @@ loop: } // 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 == fusekernel.OpWrite { + if m.hdr.Len < uint32(n) && m.hdr.Len >= uint32(unsafe.Sizeof(fusekernel.WriteIn{})) && m.hdr.Opcode == fusekernel.OpWrite { m.hdr.Len = uint32(n) } @@ -579,7 +579,6 @@ loop: var req Request switch m.hdr.Opcode { default: - Debug(noOpcode{Opcode: m.hdr.Opcode}) goto unrecognized case fusekernel.OpLookup: @@ -594,7 +593,7 @@ loop: } case fusekernel.OpForget: - in := (*forgetIn)(m.data()) + in := (*fusekernel.ForgetIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -611,7 +610,7 @@ loop: } default: - in := (*getattrIn)(m.data()) + in := (*fusekernel.GetattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -623,13 +622,13 @@ loop: } case fusekernel.OpSetattr: - in := (*setattrIn)(m.data()) + in := (*fusekernel.SetattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &SetattrRequest{ Header: m.Header(), - Valid: SetattrValid(in.Valid), + Valid: fusekernel.SetattrValid(in.Valid), Handle: HandleID(in.Fh), Size: in.Size, Atime: time.Unix(int64(in.Atime), int64(in.AtimeNsec)), @@ -668,7 +667,7 @@ loop: } case fusekernel.OpLink: - in := (*linkIn)(m.data()) + in := (*fusekernel.LinkIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -684,11 +683,11 @@ loop: } case fusekernel.OpMknod: - size := mknodInSize(c.proto) + size := fusekernel.MknodInSize(c.proto) if m.len() < size { goto corrupt } - in := (*mknodIn)(m.data()) + in := (*fusekernel.MknodIn)(m.data()) name := m.bytes()[size:] if len(name) < 2 || name[len(name)-1] != '\x00' { goto corrupt @@ -706,11 +705,11 @@ loop: req = r case fusekernel.OpMkdir: - size := mkdirInSize(c.proto) + size := fusekernel.MkdirInSize(c.proto) if m.len() < size { goto corrupt } - in := (*mkdirIn)(m.data()) + in := (*fusekernel.MkdirIn)(m.data()) name := m.bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { @@ -742,7 +741,7 @@ loop: } case fusekernel.OpRename: - in := (*renameIn)(m.data()) + in := (*fusekernel.RenameIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -768,19 +767,19 @@ loop: } case fusekernel.OpOpendir, fusekernel.OpOpen: - in := (*openIn)(m.data()) + 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: openFlags(in.Flags), + Flags: fusekernel.OpenFlags(in.Flags), } case fusekernel.OpRead, fusekernel.OpReaddir: - in := (*readIn)(m.data()) - if m.len() < readInSize(c.proto) { + in := (*fusekernel.ReadIn)(m.data()) + if m.len() < fusekernel.ReadInSize(c.proto) { goto corrupt } r := &ReadRequest{ @@ -791,28 +790,28 @@ loop: Size: int(in.Size), } if c.proto.GE(fusekernel.Protocol{7, 9}) { - r.Flags = ReadFlags(in.ReadFlags) + r.Flags = fusekernel.ReadFlags(in.ReadFlags) r.LockOwner = in.LockOwner - r.FileFlags = openFlags(in.Flags) + r.FileFlags = fusekernel.OpenFlags(in.Flags) } req = r case fusekernel.OpWrite: - in := (*writeIn)(m.data()) - if m.len() < writeInSize(c.proto) { + 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: WriteFlags(in.WriteFlags), + Flags: fusekernel.WriteFlags(in.WriteFlags), } if c.proto.GE(fusekernel.Protocol{7, 9}) { r.LockOwner = in.LockOwner - r.FileFlags = openFlags(in.Flags) + r.FileFlags = fusekernel.OpenFlags(in.Flags) } - buf := m.bytes()[writeInSize(c.proto):] + buf := m.bytes()[fusekernel.WriteInSize(c.proto):] if uint32(len(buf)) < in.Size { goto corrupt } @@ -825,7 +824,7 @@ loop: } case fusekernel.OpRelease, fusekernel.OpReleasedir: - in := (*releaseIn)(m.data()) + in := (*fusekernel.ReleaseIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -833,13 +832,13 @@ loop: Header: m.Header(), Dir: m.hdr.Opcode == fusekernel.OpReleasedir, Handle: HandleID(in.Fh), - Flags: openFlags(in.Flags), - ReleaseFlags: ReleaseFlags(in.ReleaseFlags), + Flags: fusekernel.OpenFlags(in.Flags), + ReleaseFlags: fusekernel.ReleaseFlags(in.ReleaseFlags), LockOwner: in.LockOwner, } case fusekernel.OpFsync, fusekernel.OpFsyncdir: - in := (*fsyncIn)(m.data()) + in := (*fusekernel.FsyncIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -851,7 +850,7 @@ loop: } case fusekernel.OpSetxattr: - in := (*setxattrIn)(m.data()) + in := (*fusekernel.SetxattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -869,13 +868,13 @@ loop: req = &SetxattrRequest{ Header: m.Header(), Flags: in.Flags, - Position: in.position(), + Position: in.Position(), Name: string(name[:i]), Xattr: xattr, } case fusekernel.OpGetxattr: - in := (*getxattrIn)(m.data()) + in := (*fusekernel.GetxattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -888,18 +887,18 @@ loop: Header: m.Header(), Name: string(name[:i]), Size: in.Size, - Position: in.position(), + Position: in.Position, } case fusekernel.OpListxattr: - in := (*getxattrIn)(m.data()) + in := (*fusekernel.GetxattrIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } req = &ListxattrRequest{ Header: m.Header(), Size: in.Size, - Position: in.position(), + Position: in.Position, } case fusekernel.OpRemovexattr: @@ -914,7 +913,7 @@ loop: } case fusekernel.OpFlush: - in := (*flushIn)(m.data()) + in := (*fusekernel.FlushIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -926,7 +925,7 @@ loop: } case fusekernel.OpInit: - in := (*initIn)(m.data()) + in := (*fusekernel.InitIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -945,7 +944,7 @@ loop: panic("fusekernel.OpSetlkw") case fusekernel.OpAccess: - in := (*accessIn)(m.data()) + in := (*fusekernel.AccessIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -955,11 +954,11 @@ loop: } case fusekernel.OpCreate: - size := createInSize(c.proto) + size := fusekernel.CreateInSize(c.proto) if m.len() < size { goto corrupt } - in := (*createIn)(m.data()) + in := (*fusekernel.CreateIn)(m.data()) name := m.bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { @@ -967,7 +966,7 @@ loop: } r := &CreateRequest{ Header: m.Header(), - Flags: openFlags(in.Flags), + Flags: fusekernel.OpenFlags(in.Flags), Mode: fileMode(in.Mode), Name: string(name[:i]), } @@ -977,7 +976,7 @@ loop: req = r case fusekernel.OpInterrupt: - in := (*interruptIn)(m.data()) + in := (*fusekernel.InterruptIn)(m.data()) if m.len() < unsafe.Sizeof(*in) { goto corrupt } @@ -1006,7 +1005,6 @@ loop: return req, nil corrupt: - Debug(malformedMessage{}) putMessage(m) return nil, fmt.Errorf("fuse: malformed message") @@ -1052,24 +1050,11 @@ func (c *Conn) writeToKernel(msg []byte) error { c.wio.RLock() defer c.wio.RUnlock() nn, err := syscall.Write(c.fd(), msg) - if err == nil && nn != len(msg) { - Debug(bugShortKernelWrite{ - Written: int64(nn), - Length: int64(len(msg)), - Error: errorString(err), - Stack: stack(), - }) - } return err } func (c *Conn) respond(msg []byte) { - if err := c.writeToKernel(msg); err != nil { - Debug(bugKernelWriteError{ - Error: errorString(err), - Stack: stack(), - }) - } + c.writeToKernel(msg) } type notCachedError struct{} @@ -1349,7 +1334,7 @@ func (r *GetattrRequest) String() string { // Respond replies to the request with the given response. func (r *GetattrRequest) Respond(resp *GetattrResponse) { - size := attrOutSize(r.Header.Conn.proto) + size := fusekernel.AttrOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*attrOut)(buf.alloc(size)) out.AttrValid = uint64(resp.Attr.Valid / time.Second) @@ -1534,7 +1519,7 @@ func (r *LookupRequest) String() string { // Respond replies to the request with the given response. func (r *LookupRequest) Respond(resp *LookupResponse) { - size := entryOutSize(r.Header.Conn.proto) + size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) @@ -1608,7 +1593,7 @@ func (r *CreateRequest) String() string { // Respond replies to the request with the given response. func (r *CreateRequest) Respond(resp *CreateResponse) { - eSize := entryOutSize(r.Header.Conn.proto) + eSize := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(eSize + unsafe.Sizeof(openOut{})) e := (*entryOut)(buf.alloc(eSize)) @@ -1654,7 +1639,7 @@ func (r *MkdirRequest) String() string { // Respond replies to the request with the given response. func (r *MkdirRequest) Respond(resp *MkdirResponse) { - size := entryOutSize(r.Header.Conn.proto) + size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) @@ -1988,7 +1973,7 @@ func (r *SetattrRequest) String() string { // Respond replies to the request with the given response, // giving the updated attributes. func (r *SetattrRequest) Respond(resp *SetattrResponse) { - size := attrOutSize(r.Header.Conn.proto) + size := fusekernel.AttrOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*attrOut)(buf.alloc(size)) out.AttrValid = uint64(resp.Attr.Valid / time.Second) @@ -2062,7 +2047,7 @@ func (r *SymlinkRequest) String() string { // Respond replies to the request, indicating that the symlink was created. func (r *SymlinkRequest) Respond(resp *SymlinkResponse) { - size := entryOutSize(r.Header.Conn.proto) + size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) @@ -2111,7 +2096,7 @@ func (r *LinkRequest) String() string { } func (r *LinkRequest) Respond(resp *LookupResponse) { - size := entryOutSize(r.Header.Conn.proto) + size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) @@ -2157,7 +2142,7 @@ func (r *MknodRequest) String() string { } func (r *MknodRequest) Respond(resp *LookupResponse) { - size := entryOutSize(r.Header.Conn.proto) + size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) out := (*entryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) From 8c94df5ce913877b3ba7f417aae0414bf78f7ddc Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:16:42 +1000 Subject: [PATCH 012/157] Made package fuseshim build. --- internal/fusekernel/fuse_kernel.go | 46 +++++++------- internal/fusekernel/fuse_kernel_darwin.go | 8 +++ internal/fuseshim/fuse.go | 74 +++++++++++------------ internal/fuseshim/options.go | 4 +- 4 files changed, 70 insertions(+), 62 deletions(-) diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go index fe25ee4..778f5fe 100644 --- a/internal/fusekernel/fuse_kernel.go +++ b/internal/fusekernel/fuse_kernel.go @@ -53,7 +53,7 @@ const ( RootID = 1 ) -type kstatfs struct { +type Kstatfs struct { Blocks uint64 Bfree uint64 Bavail uint64 @@ -387,7 +387,7 @@ const ( OpExchange = 63 ) -type entryOut struct { +type EntryOut struct { Nodeid uint64 // Inode ID Generation uint64 // Inode generation EntryValid uint64 // Cache timeout for the name @@ -400,9 +400,9 @@ type entryOut struct { func EntryOutSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): - return unsafe.Offsetof(entryOut{}.Attr) + unsafe.Offsetof(entryOut{}.Attr.Blksize) + return unsafe.Offsetof(EntryOut{}.Attr) + unsafe.Offsetof(EntryOut{}.Attr.Blksize) default: - return unsafe.Sizeof(entryOut{}) + return unsafe.Sizeof(EntryOut{}) } } @@ -416,7 +416,7 @@ type GetattrIn struct { Fh uint64 } -type attrOut struct { +type AttrOut struct { AttrValid uint64 // Cache timeout for the attributes AttrValidNsec uint32 Dummy uint32 @@ -426,14 +426,14 @@ type attrOut struct { func AttrOutSize(p Protocol) uintptr { switch { case p.LT(Protocol{7, 9}): - return unsafe.Offsetof(attrOut{}.Attr) + unsafe.Offsetof(attrOut{}.Attr.Blksize) + return unsafe.Offsetof(AttrOut{}.Attr) + unsafe.Offsetof(AttrOut{}.Attr.Blksize) default: - return unsafe.Sizeof(attrOut{}) + return unsafe.Sizeof(AttrOut{}) } } // OS X -type getxtimesOut struct { +type GetxtimesOut struct { Bkuptime uint64 Crtime uint64 BkuptimeNsec uint32 @@ -512,7 +512,7 @@ type OpenIn struct { Unused uint32 } -type openOut struct { +type OpenOut struct { Fh uint64 OpenFlags uint32 Padding uint32 @@ -602,7 +602,7 @@ func WriteInSize(p Protocol) uintptr { } } -type writeOut struct { +type WriteOut struct { Size uint32 Padding uint32 } @@ -627,8 +627,8 @@ func (fl WriteFlags) String() string { const compatStatfsSize = 48 -type statfsOut struct { - St kstatfs +type StatfsOut struct { + St Kstatfs } type FsyncIn struct { @@ -642,7 +642,7 @@ type setxattrInCommon struct { Flags uint32 } -func (setxattrInCommon) position() uint32 { +func (setxattrInCommon) GetPosition() uint32 { return 0 } @@ -651,11 +651,11 @@ type getxattrInCommon struct { Padding uint32 } -func (getxattrInCommon) position() uint32 { +func (getxattrInCommon) GetPosition() uint32 { return 0 } -type getxattrOut struct { +type GetxattrOut struct { Size uint32 Padding uint32 } @@ -677,7 +677,7 @@ func LkInSize(p Protocol) uintptr { } } -type lkOut struct { +type LkOut struct { Lk fileLock } @@ -695,7 +695,7 @@ type InitIn struct { const InitInSize = int(unsafe.Sizeof(InitIn{})) -type initOut struct { +type InitOut struct { Major uint32 Minor uint32 MaxReadahead uint32 @@ -714,7 +714,7 @@ type BmapIn struct { Padding uint32 } -type bmapOut struct { +type BmapOut struct { Block uint64 } @@ -737,7 +737,7 @@ type OutHeader struct { Unique uint64 } -type dirent struct { +type Dirent struct { Ino uint64 Off uint64 Namelen uint32 @@ -745,12 +745,12 @@ type dirent struct { Name [0]byte } -const direntSize = 8 + 8 + 4 + 4 +const DirentSize = 8 + 8 + 4 + 4 const ( - notifyCodePoll int32 = 1 - notifyCodeInvalInode int32 = 2 - notifyCodeInvalEntry int32 = 3 + NotifyCodePoll int32 = 1 + NotifyCodeInvalInode int32 = 2 + NotifyCodeInvalEntry int32 = 3 ) type NotifyInvalInodeOut struct { diff --git a/internal/fusekernel/fuse_kernel_darwin.go b/internal/fusekernel/fuse_kernel_darwin.go index 2eb842d..fc02267 100644 --- a/internal/fusekernel/fuse_kernel_darwin.go +++ b/internal/fusekernel/fuse_kernel_darwin.go @@ -71,6 +71,10 @@ type GetxattrIn struct { Padding uint32 } +func (g *GetxattrIn) GetPosition() uint32 { + return g.Position +} + type SetxattrIn struct { setxattrInCommon @@ -78,3 +82,7 @@ type SetxattrIn struct { Position uint32 Padding uint32 } + +func (s *SetxattrIn) GetPosition() uint32 { + return s.Position +} diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index e1e98e3..56d1faf 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -868,7 +868,7 @@ loop: req = &SetxattrRequest{ Header: m.Header(), Flags: in.Flags, - Position: in.Position(), + Position: in.GetPosition(), Name: string(name[:i]), Xattr: xattr, } @@ -887,7 +887,7 @@ loop: Header: m.Header(), Name: string(name[:i]), Size: in.Size, - Position: in.Position, + Position: in.GetPosition(), } case fusekernel.OpListxattr: @@ -898,7 +898,7 @@ loop: req = &ListxattrRequest{ Header: m.Header(), Size: in.Size, - Position: in.Position, + Position: in.GetPosition(), } case fusekernel.OpRemovexattr: @@ -1049,7 +1049,7 @@ func (c *Conn) writeToKernel(msg []byte) error { c.wio.RLock() defer c.wio.RUnlock() - nn, err := syscall.Write(c.fd(), msg) + _, err := syscall.Write(c.fd(), msg) return err } @@ -1096,11 +1096,11 @@ func (c *Conn) sendInvalidate(msg []byte) error { // 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(notifyInvalInodeOut{})) + buf := newBuffer(unsafe.Sizeof(fusekernel.NotifyInvalInodeOut{})) h := (*fusekernel.OutHeader)(unsafe.Pointer(&buf[0])) // h.Unique is 0 - h.Error = notifyCodeInvalInode - out := (*notifyInvalInodeOut)(buf.alloc(unsafe.Sizeof(notifyInvalInodeOut{}))) + h.Error = fusekernel.NotifyCodeInvalInode + out := (*fusekernel.NotifyInvalInodeOut)(buf.alloc(unsafe.Sizeof(fusekernel.NotifyInvalInodeOut{}))) out.Ino = uint64(nodeID) out.Off = off out.Len = size @@ -1123,11 +1123,11 @@ func (c *Conn) InvalidateEntry(parent NodeID, name string) error { // very unlikely, but we don't want to silently truncate return syscall.ENAMETOOLONG } - buf := newBuffer(unsafe.Sizeof(notifyInvalEntryOut{}) + uintptr(len(name)) + 1) + buf := newBuffer(unsafe.Sizeof(fusekernel.NotifyInvalEntryOut{}) + uintptr(len(name)) + 1) h := (*fusekernel.OutHeader)(unsafe.Pointer(&buf[0])) // h.Unique is 0 - h.Error = notifyCodeInvalEntry - out := (*notifyInvalEntryOut)(buf.alloc(unsafe.Sizeof(notifyInvalEntryOut{}))) + 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...) @@ -1168,8 +1168,8 @@ func (r *InitResponse) String() string { // Respond replies to the request with the given response. func (r *InitRequest) Respond(resp *InitResponse) { - buf := newBuffer(unsafe.Sizeof(initOut{})) - out := (*initOut)(buf.alloc(unsafe.Sizeof(initOut{}))) + 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 @@ -1197,9 +1197,9 @@ func (r *StatfsRequest) String() string { // Respond replies to the request with the given response. func (r *StatfsRequest) Respond(resp *StatfsResponse) { - buf := newBuffer(unsafe.Sizeof(statfsOut{})) - out := (*statfsOut)(buf.alloc(unsafe.Sizeof(statfsOut{}))) - out.St = kstatfs{ + 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, @@ -1336,7 +1336,7 @@ func (r *GetattrRequest) String() string { func (r *GetattrRequest) Respond(resp *GetattrResponse) { size := fusekernel.AttrOutSize(r.Header.Conn.proto) buf := newBuffer(size) - out := (*attrOut)(buf.alloc(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) @@ -1378,8 +1378,8 @@ func (r *GetxattrRequest) String() string { // Respond replies to the request with the given response. func (r *GetxattrRequest) Respond(resp *GetxattrResponse) { if r.Size == 0 { - buf := newBuffer(unsafe.Sizeof(getxattrOut{})) - out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{}))) + 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 { @@ -1414,8 +1414,8 @@ func (r *ListxattrRequest) String() string { // Respond replies to the request with the given response. func (r *ListxattrRequest) Respond(resp *ListxattrResponse) { if r.Size == 0 { - buf := newBuffer(unsafe.Sizeof(getxattrOut{})) - out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{}))) + 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 { @@ -1521,7 +1521,7 @@ func (r *LookupRequest) String() string { func (r *LookupRequest) Respond(resp *LookupResponse) { size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) - out := (*entryOut)(buf.alloc(size)) + out := (*fusekernel.EntryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) @@ -1559,8 +1559,8 @@ func (r *OpenRequest) String() string { // Respond replies to the request with the given response. func (r *OpenRequest) Respond(resp *OpenResponse) { - buf := newBuffer(unsafe.Sizeof(openOut{})) - out := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{}))) + 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) @@ -1594,9 +1594,9 @@ func (r *CreateRequest) String() string { // 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(openOut{})) + buf := newBuffer(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) - e := (*entryOut)(buf.alloc(eSize)) + e := (*fusekernel.EntryOut)(buf.alloc(eSize)) e.Nodeid = uint64(resp.Node) e.Generation = resp.Generation e.EntryValid = uint64(resp.EntryValid / time.Second) @@ -1605,7 +1605,7 @@ func (r *CreateRequest) Respond(resp *CreateResponse) { e.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) resp.Attr.attr(&e.Attr, r.Header.Conn.proto) - o := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{}))) + o := (*fusekernel.OpenOut)(buf.alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) o.Fh = uint64(resp.Handle) o.OpenFlags = uint32(resp.Flags) @@ -1641,7 +1641,7 @@ func (r *MkdirRequest) String() string { func (r *MkdirRequest) Respond(resp *MkdirResponse) { size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) - out := (*entryOut)(buf.alloc(size)) + out := (*fusekernel.EntryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) @@ -1831,15 +1831,15 @@ func (t DirentType) String() string { // 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{ + de := fusekernel.Dirent{ Ino: dir.Inode, Namelen: uint32(len(dir.Name)), Type: uint32(dir.Type), } - de.Off = uint64(len(data) + direntSize + (len(dir.Name)+7)&^7) - data = append(data, (*[direntSize]byte)(unsafe.Pointer(&de))[:]...) + 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 := direntSize + uintptr(len(dir.Name)) + n := fusekernel.DirentSize + uintptr(len(dir.Name)) if n%8 != 0 { var pad [8]byte data = append(data, pad[:8-n%8]...) @@ -1883,8 +1883,8 @@ func (r *WriteRequest) MarshalJSON() ([]byte, error) { // Respond replies to the request with the given response. func (r *WriteRequest) Respond(resp *WriteResponse) { - buf := newBuffer(unsafe.Sizeof(writeOut{})) - out := (*writeOut)(buf.alloc(unsafe.Sizeof(writeOut{}))) + buf := newBuffer(unsafe.Sizeof(fusekernel.WriteOut{})) + out := (*fusekernel.WriteOut)(buf.alloc(unsafe.Sizeof(fusekernel.WriteOut{}))) out.Size = uint32(resp.Size) r.respond(buf) } @@ -1975,7 +1975,7 @@ func (r *SetattrRequest) String() string { func (r *SetattrRequest) Respond(resp *SetattrResponse) { size := fusekernel.AttrOutSize(r.Header.Conn.proto) buf := newBuffer(size) - out := (*attrOut)(buf.alloc(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) @@ -2049,7 +2049,7 @@ func (r *SymlinkRequest) String() string { func (r *SymlinkRequest) Respond(resp *SymlinkResponse) { size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) - out := (*entryOut)(buf.alloc(size)) + out := (*fusekernel.EntryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) @@ -2098,7 +2098,7 @@ func (r *LinkRequest) String() string { func (r *LinkRequest) Respond(resp *LookupResponse) { size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) - out := (*entryOut)(buf.alloc(size)) + out := (*fusekernel.EntryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) @@ -2144,7 +2144,7 @@ func (r *MknodRequest) String() string { func (r *MknodRequest) Respond(resp *LookupResponse) { size := fusekernel.EntryOutSize(r.Header.Conn.proto) buf := newBuffer(size) - out := (*entryOut)(buf.alloc(size)) + out := (*fusekernel.EntryOut)(buf.alloc(size)) out.Nodeid = uint64(resp.Node) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) diff --git a/internal/fuseshim/options.go b/internal/fuseshim/options.go index 7e89207..2630ad0 100644 --- a/internal/fuseshim/options.go +++ b/internal/fuseshim/options.go @@ -166,7 +166,7 @@ func MaxReadahead(n uint32) MountOption { // time. func AsyncRead() MountOption { return func(conf *mountConfig) error { - conf.initFlags |= InitAsyncRead + conf.initFlags |= fusekernel.InitAsyncRead return nil } } @@ -176,7 +176,7 @@ func AsyncRead() MountOption { // used. func WritebackCache() MountOption { return func(conf *mountConfig) error { - conf.initFlags |= InitWritebackCache + conf.initFlags |= fusekernel.InitWritebackCache return nil } } From 81dee67b515401a054c3765d5d920497cbdf35a9 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:18:43 +1000 Subject: [PATCH 013/157] Use package fuseshim in package fuse. --- connection.go | 44 +++++++++++++++++++++--------------------- errors.go | 16 +++++++-------- mounted_file_system.go | 27 +++++++++++++------------- unmount.go | 4 +--- 4 files changed, 45 insertions(+), 46 deletions(-) diff --git a/connection.go b/connection.go index 5ee1922..6c2f16f 100644 --- a/connection.go +++ b/connection.go @@ -23,15 +23,15 @@ import ( "golang.org/x/net/context" - "github.com/jacobsa/bazilfuse" "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/fuse/internal/fuseshim" ) // A connection to the fuse kernel process. type Connection struct { debugLogger *log.Logger errorLogger *log.Logger - wrapped *bazilfuse.Conn + wrapped *fuseshim.Conn // The context from which all op contexts inherit. parentCtx context.Context @@ -41,11 +41,11 @@ type Connection struct { mu sync.Mutex - // A map from bazilfuse request ID (*not* the op ID for logging used above) - // to a function that cancel's its associated context. + // A map from fuseshim request ID (*not* the op ID for logging used above) to + // a function that cancel's its associated context. // // GUARDED_BY(mu) - cancelFuncs map[bazilfuse.RequestID]func() + cancelFuncs map[fuseshim.RequestID]func() } // Responsibility for closing the wrapped connection is transferred to the @@ -56,13 +56,13 @@ func newConnection( parentCtx context.Context, debugLogger *log.Logger, errorLogger *log.Logger, - wrapped *bazilfuse.Conn) (c *Connection, err error) { + wrapped *fuseshim.Conn) (c *Connection, err error) { c = &Connection{ debugLogger: debugLogger, errorLogger: errorLogger, wrapped: wrapped, parentCtx: parentCtx, - cancelFuncs: make(map[bazilfuse.RequestID]func()), + cancelFuncs: make(map[fuseshim.RequestID]func()), } return @@ -104,7 +104,7 @@ func (c *Connection) debugLog( // LOCKS_EXCLUDED(c.mu) func (c *Connection) recordCancelFunc( - reqID bazilfuse.RequestID, + reqID fuseshim.RequestID, f func()) { c.mu.Lock() defer c.mu.Unlock() @@ -117,13 +117,13 @@ func (c *Connection) recordCancelFunc( } // Set up state for an op that is about to be returned to the user, given its -// underlying bazilfuse request. +// underlying fuseshim request. // // Return a context that should be used for the op. // // LOCKS_EXCLUDED(c.mu) func (c *Connection) beginOp( - bfReq bazilfuse.Request) (ctx context.Context) { + bfReq fuseshim.Request) (ctx context.Context) { reqID := bfReq.Hdr().ID // Start with the parent context. @@ -137,7 +137,7 @@ func (c *Connection) beginOp( // should not record any state keyed on their ID. // // Cf. https://github.com/osxfuse/osxfuse/issues/208 - if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok { + if _, ok := bfReq.(*fuseshim.ForgetRequest); !ok { var cancel func() ctx, cancel = context.WithCancel(ctx) c.recordCancelFunc(reqID, cancel) @@ -147,12 +147,12 @@ func (c *Connection) beginOp( } // Clean up all state associated with an op to which the user has responded, -// given its underlying bazilfuse request. This must be called before a -// response is sent to the kernel, to avoid a race where the request's ID might -// be reused by osxfuse. +// given its underlying fuseshim request. This must be called before a response +// is sent to the kernel, to avoid a race where the request's ID might be +// reused by osxfuse. // // LOCKS_EXCLUDED(c.mu) -func (c *Connection) finishOp(bfReq bazilfuse.Request) { +func (c *Connection) finishOp(bfReq fuseshim.Request) { c.mu.Lock() defer c.mu.Unlock() @@ -164,7 +164,7 @@ func (c *Connection) finishOp(bfReq bazilfuse.Request) { // // Special case: we don't do this for Forget requests. See the note in // beginOp above. - if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok { + if _, ok := bfReq.(*fuseshim.ForgetRequest); !ok { cancel, ok := c.cancelFuncs[reqID] if !ok { panic(fmt.Sprintf("Unknown request ID in finishOp: %v", reqID)) @@ -176,7 +176,7 @@ func (c *Connection) finishOp(bfReq bazilfuse.Request) { } // LOCKS_EXCLUDED(c.mu) -func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) { +func (c *Connection) handleInterrupt(req *fuseshim.InterruptRequest) { c.mu.Lock() defer c.mu.Unlock() @@ -212,8 +212,8 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) { func (c *Connection) ReadOp() (op fuseops.Op, err error) { // Keep going until we find a request we know how to convert. for { - // Read a bazilfuse request. - var bfReq bazilfuse.Request + // Read a fuseshim request. + var bfReq fuseshim.Request bfReq, err = c.wrapped.ReadRequest() if err != nil { @@ -230,14 +230,14 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { // Special case: responding to statfs is required to make mounting work on // OS X. We don't currently expose the capability for the file system to // intercept this. - if statfsReq, ok := bfReq.(*bazilfuse.StatfsRequest); ok { + if statfsReq, ok := bfReq.(*fuseshim.StatfsRequest); ok { c.debugLog(opID, 1, "-> (Statfs) OK") - statfsReq.Respond(&bazilfuse.StatfsResponse{}) + statfsReq.Respond(&fuseshim.StatfsResponse{}) continue } // Special case: handle interrupt requests. - if interruptReq, ok := bfReq.(*bazilfuse.InterruptRequest); ok { + if interruptReq, ok := bfReq.(*fuseshim.InterruptRequest); ok { c.handleInterrupt(interruptReq) continue } diff --git a/errors.go b/errors.go index 48b15aa..d3dea3e 100644 --- a/errors.go +++ b/errors.go @@ -17,17 +17,17 @@ package fuse import ( "syscall" - "github.com/jacobsa/bazilfuse" + "github.com/jacobsa/fuse/internal/fuseshim" ) const ( // Errors corresponding to kernel error numbers. These may be treated // specially by fuseops.Op.Respond methods. - EEXIST = bazilfuse.EEXIST - EINVAL = bazilfuse.Errno(syscall.EINVAL) - EIO = bazilfuse.EIO - ENOENT = bazilfuse.ENOENT - ENOSYS = bazilfuse.ENOSYS - ENOTDIR = bazilfuse.Errno(syscall.ENOTDIR) - ENOTEMPTY = bazilfuse.Errno(syscall.ENOTEMPTY) + EEXIST = fuseshim.EEXIST + EINVAL = fuseshim.Errno(syscall.EINVAL) + EIO = fuseshim.EIO + ENOENT = fuseshim.ENOENT + ENOSYS = fuseshim.ENOSYS + ENOTDIR = fuseshim.Errno(syscall.ENOTDIR) + ENOTEMPTY = fuseshim.Errno(syscall.ENOTEMPTY) ) diff --git a/mounted_file_system.go b/mounted_file_system.go index 580ea6b..4a57ec6 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -19,7 +19,8 @@ import ( "log" "runtime" - "github.com/jacobsa/bazilfuse" + "github.com/jacobsa/fuse/internal/fuseshim" + "golang.org/x/net/context" ) @@ -111,13 +112,13 @@ type MountConfig struct { Options map[string]string } -// Convert to mount options to be passed to package bazilfuse. -func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) { +// Convert to mount options to be passed to package fuseshim. +func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { isDarwin := runtime.GOOS == "darwin" // Enable permissions checking in the kernel. See the comments on // InodeAttributes.Mode. - opts = append(opts, bazilfuse.SetOption("default_permissions", "")) + opts = append(opts, fuseshim.SetOption("default_permissions", "")) // HACK(jacobsa): Work around what appears to be a bug in systemd v219, as // shipped in Ubuntu 15.04, where it automatically unmounts any file system @@ -135,17 +136,17 @@ func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) { // Special file system name? if fsname != "" { - opts = append(opts, bazilfuse.FSName(fsname)) + opts = append(opts, fuseshim.FSName(fsname)) } // Read only? if c.ReadOnly { - opts = append(opts, bazilfuse.ReadOnly()) + opts = append(opts, fuseshim.ReadOnly()) } // OS X: set novncache when appropriate. if isDarwin && !c.EnableVnodeCaching { - opts = append(opts, bazilfuse.SetOption("novncache", "")) + opts = append(opts, fuseshim.SetOption("novncache", "")) } // OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which @@ -154,7 +155,7 @@ func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) { // // Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options if isDarwin { - opts = append(opts, bazilfuse.SetOption("noappledouble", "")) + opts = append(opts, fuseshim.SetOption("noappledouble", "")) } // Ask the Linux kernel for larger read requests. @@ -175,11 +176,11 @@ func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) { // // Reading a page at a time is a drag. Ask for a larger size. const maxReadahead = 1 << 20 - opts = append(opts, bazilfuse.MaxReadahead(maxReadahead)) + opts = append(opts, fuseshim.MaxReadahead(maxReadahead)) // Last but not least: other user-supplied options. for k, v := range c.Options { - opts = append(opts, bazilfuse.SetOption(k, v)) + opts = append(opts, fuseshim.SetOption(k, v)) } return @@ -198,10 +199,10 @@ func Mount( joinStatusAvailable: make(chan struct{}), } - // Open a bazilfuse connection. - bfConn, err := bazilfuse.Mount(mfs.dir, config.bazilfuseOptions()...) + // Open a fuseshim connection. + bfConn, err := fuseshim.Mount(mfs.dir, config.bazilfuseOptions()...) if err != nil { - err = fmt.Errorf("bazilfuse.Mount: %v", err) + err = fmt.Errorf("fuseshim.Mount: %v", err) return } diff --git a/unmount.go b/unmount.go index 2c50925..e261333 100644 --- a/unmount.go +++ b/unmount.go @@ -14,10 +14,8 @@ package fuse -import "github.com/jacobsa/bazilfuse" - // Attempt to unmount the file system whose mount point is the supplied // directory. func Unmount(dir string) error { - return bazilfuse.Unmount(dir) + return fuseshim.Unmount(dir) } From 4dc22ba2e7edaa58fafec0a640480d5b1abe6324 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:21:14 +1000 Subject: [PATCH 014/157] Use package fuseshim in package fuseops. --- fuseops/common_op.go | 12 +++---- fuseops/convert.go | 54 ++++++++++++++-------------- fuseops/ops.go | 78 ++++++++++++++++++++--------------------- fuseops/simple_types.go | 8 ++--- 4 files changed, 75 insertions(+), 77 deletions(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index 457afc5..e6491d7 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -20,7 +20,7 @@ import ( "reflect" "strings" - "github.com/jacobsa/bazilfuse" + "github.com/jacobsa/fuse/internal/fuseshim" "github.com/jacobsa/reqtrace" "golang.org/x/net/context" ) @@ -30,7 +30,7 @@ import ( type internalOp interface { Op - // Respond to the underlying bazilfuse request, successfully. + // Respond to the underlying fuseshim request, successfully. respond() } @@ -42,8 +42,8 @@ type commonOp struct { // The op in which this struct is embedded. op internalOp - // The underlying bazilfuse request for this op. - bazilReq bazilfuse.Request + // The underlying fuseshim request for this op. + bazilReq fuseshim.Request // A function that can be used to log debug information about the op. The // first argument is a call depth. @@ -81,7 +81,7 @@ func (o *commonOp) ShortDesc() (desc string) { func (o *commonOp) init( ctx context.Context, op internalOp, - bazilReq bazilfuse.Request, + bazilReq fuseshim.Request, debugLog func(int, string, ...interface{}), errorLogger *log.Logger, finished func(error)) { @@ -122,7 +122,7 @@ func (o *commonOp) Respond(err error) { // Report that the user is responding. o.finished(err) - // If successful, we should respond to bazilfuse with the appropriate struct. + // If successful, we should respond to fuseshim with the appropriate struct. if err == nil { o.op.respond() return diff --git a/fuseops/convert.go b/fuseops/convert.go index 938a8c0..706c33c 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -19,14 +19,12 @@ import ( "time" "golang.org/x/net/context" - - "github.com/jacobsa/bazilfuse" ) // This function is an implementation detail of the fuse package, and must not // be called by anyone else. // -// Convert the supplied bazilfuse request struct to an Op. finished will be +// Convert the supplied fuseshim request struct to an Op. finished will be // called with the error supplied to o.Respond when the user invokes that // method, before a response is sent to the kernel. // @@ -36,7 +34,7 @@ import ( // The debug logging function and error logger may be nil. func Convert( opCtx context.Context, - r bazilfuse.Request, + r fuseshim.Request, debugLogForOp func(int, string, ...interface{}), errorLogger *log.Logger, finished func(error)) (o Op) { @@ -44,7 +42,7 @@ func Convert( var io internalOp switch typed := r.(type) { - case *bazilfuse.LookupRequest: + case *fuseshim.LookupRequest: to := &LookUpInodeOp{ bfReq: typed, Parent: InodeID(typed.Header.Node), @@ -53,7 +51,7 @@ func Convert( io = to co = &to.commonOp - case *bazilfuse.GetattrRequest: + case *fuseshim.GetattrRequest: to := &GetInodeAttributesOp{ bfReq: typed, Inode: InodeID(typed.Header.Node), @@ -61,32 +59,32 @@ func Convert( io = to co = &to.commonOp - case *bazilfuse.SetattrRequest: + case *fuseshim.SetattrRequest: to := &SetInodeAttributesOp{ bfReq: typed, Inode: InodeID(typed.Header.Node), } - if typed.Valid&bazilfuse.SetattrSize != 0 { + if typed.Valid&fuseshim.SetattrSize != 0 { to.Size = &typed.Size } - if typed.Valid&bazilfuse.SetattrMode != 0 { + if typed.Valid&fuseshim.SetattrMode != 0 { to.Mode = &typed.Mode } - if typed.Valid&bazilfuse.SetattrAtime != 0 { + if typed.Valid&fuseshim.SetattrAtime != 0 { to.Atime = &typed.Atime } - if typed.Valid&bazilfuse.SetattrMtime != 0 { + if typed.Valid&fuseshim.SetattrMtime != 0 { to.Mtime = &typed.Mtime } io = to co = &to.commonOp - case *bazilfuse.ForgetRequest: + case *fuseshim.ForgetRequest: to := &ForgetInodeOp{ bfReq: typed, Inode: InodeID(typed.Header.Node), @@ -95,7 +93,7 @@ func Convert( io = to co = &to.commonOp - case *bazilfuse.MkdirRequest: + case *fuseshim.MkdirRequest: to := &MkDirOp{ bfReq: typed, Parent: InodeID(typed.Header.Node), @@ -105,7 +103,7 @@ func Convert( io = to co = &to.commonOp - case *bazilfuse.CreateRequest: + case *fuseshim.CreateRequest: to := &CreateFileOp{ bfReq: typed, Parent: InodeID(typed.Header.Node), @@ -116,7 +114,7 @@ func Convert( io = to co = &to.commonOp - case *bazilfuse.SymlinkRequest: + case *fuseshim.SymlinkRequest: to := &CreateSymlinkOp{ bfReq: typed, Parent: InodeID(typed.Header.Node), @@ -126,7 +124,7 @@ func Convert( io = to co = &to.commonOp - case *bazilfuse.RenameRequest: + case *fuseshim.RenameRequest: to := &RenameOp{ bfReq: typed, OldParent: InodeID(typed.Header.Node), @@ -137,7 +135,7 @@ func Convert( io = to co = &to.commonOp - case *bazilfuse.RemoveRequest: + case *fuseshim.RemoveRequest: if typed.Dir { to := &RmDirOp{ bfReq: typed, @@ -156,7 +154,7 @@ func Convert( co = &to.commonOp } - case *bazilfuse.OpenRequest: + case *fuseshim.OpenRequest: if typed.Dir { to := &OpenDirOp{ bfReq: typed, @@ -175,7 +173,7 @@ func Convert( co = &to.commonOp } - case *bazilfuse.ReadRequest: + case *fuseshim.ReadRequest: if typed.Dir { to := &ReadDirOp{ bfReq: typed, @@ -198,7 +196,7 @@ func Convert( co = &to.commonOp } - case *bazilfuse.ReleaseRequest: + case *fuseshim.ReleaseRequest: if typed.Dir { to := &ReleaseDirHandleOp{ bfReq: typed, @@ -215,7 +213,7 @@ func Convert( co = &to.commonOp } - case *bazilfuse.WriteRequest: + case *fuseshim.WriteRequest: to := &WriteFileOp{ bfReq: typed, Inode: InodeID(typed.Header.Node), @@ -226,7 +224,7 @@ func Convert( io = to co = &to.commonOp - case *bazilfuse.FsyncRequest: + case *fuseshim.FsyncRequest: // We don't currently support this for directories. if typed.Dir { to := &unknownOp{} @@ -242,7 +240,7 @@ func Convert( co = &to.commonOp } - case *bazilfuse.FlushRequest: + case *fuseshim.FlushRequest: to := &FlushFileOp{ bfReq: typed, Inode: InodeID(typed.Header.Node), @@ -251,7 +249,7 @@ func Convert( io = to co = &to.commonOp - case *bazilfuse.ReadlinkRequest: + case *fuseshim.ReadlinkRequest: to := &ReadSymlinkOp{ bfReq: typed, Inode: InodeID(typed.Header.Node), @@ -280,8 +278,8 @@ func Convert( func convertAttributes( inode InodeID, attr InodeAttributes, - expiration time.Time) bazilfuse.Attr { - return bazilfuse.Attr{ + expiration time.Time) fuseshim.Attr { + return fuseshim.Attr{ Inode: uint64(inode), Size: attr.Size, Mode: attr.Mode, @@ -317,8 +315,8 @@ func convertExpirationTime(t time.Time) (d time.Duration) { func convertChildInodeEntry( in *ChildInodeEntry, - out *bazilfuse.LookupResponse) { - out.Node = bazilfuse.NodeID(in.Child) + out *fuseshim.LookupResponse) { + out.Node = fuseshim.NodeID(in.Child) out.Generation = uint64(in.Generation) out.Attr = convertAttributes(in.Child, in.Attributes, in.AttributesExpiration) out.EntryValid = convertExpirationTime(in.EntryExpiration) diff --git a/fuseops/ops.go b/fuseops/ops.go index 0e464cb..180b487 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -19,7 +19,7 @@ import ( "os" "time" - "github.com/jacobsa/bazilfuse" + "github.com/jacobsa/fuse/internal/fuseshim" "golang.org/x/net/context" ) @@ -55,7 +55,7 @@ type Op interface { // when resolving user paths to dentry structs, which are then cached. type LookUpInodeOp struct { commonOp - bfReq *bazilfuse.LookupRequest + bfReq *fuseshim.LookupRequest // The ID of the directory inode to which the child belongs. Parent InodeID @@ -84,7 +84,7 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) { } func (o *LookUpInodeOp) respond() { - resp := bazilfuse.LookupResponse{} + resp := fuseshim.LookupResponse{} convertChildInodeEntry(&o.Entry, &resp) o.bfReq.Respond(&resp) @@ -97,7 +97,7 @@ func (o *LookUpInodeOp) respond() { // field of ChildInodeEntry, etc. type GetInodeAttributesOp struct { commonOp - bfReq *bazilfuse.GetattrRequest + bfReq *fuseshim.GetattrRequest // The inode of interest. Inode InodeID @@ -110,7 +110,7 @@ type GetInodeAttributesOp struct { } func (o *GetInodeAttributesOp) respond() { - resp := bazilfuse.GetattrResponse{ + resp := fuseshim.GetattrResponse{ Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), } @@ -124,7 +124,7 @@ func (o *GetInodeAttributesOp) respond() { // cases like ftrunctate(2). type SetInodeAttributesOp struct { commonOp - bfReq *bazilfuse.SetattrRequest + bfReq *fuseshim.SetattrRequest // The inode of interest. Inode InodeID @@ -143,7 +143,7 @@ type SetInodeAttributesOp struct { } func (o *SetInodeAttributesOp) respond() { - resp := bazilfuse.SetattrResponse{ + resp := fuseshim.SetattrResponse{ Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), } @@ -192,7 +192,7 @@ func (o *SetInodeAttributesOp) respond() { // implicitly decrementing all lookup counts to zero. type ForgetInodeOp struct { commonOp - bfReq *bazilfuse.ForgetRequest + bfReq *fuseshim.ForgetRequest // The inode whose reference count should be decremented. Inode InodeID @@ -223,7 +223,7 @@ func (o *ForgetInodeOp) respond() { // Therefore the file system should return EEXIST if the name already exists. type MkDirOp struct { commonOp - bfReq *bazilfuse.MkdirRequest + bfReq *fuseshim.MkdirRequest // The ID of parent directory inode within which to create the child. Parent InodeID @@ -245,7 +245,7 @@ func (o *MkDirOp) ShortDesc() (desc string) { } func (o *MkDirOp) respond() { - resp := bazilfuse.MkdirResponse{} + resp := fuseshim.MkdirResponse{} convertChildInodeEntry(&o.Entry, &resp.LookupResponse) o.bfReq.Respond(&resp) @@ -264,7 +264,7 @@ func (o *MkDirOp) respond() { // Therefore the file system should return EEXIST if the name already exists. type CreateFileOp struct { commonOp - bfReq *bazilfuse.CreateRequest + bfReq *fuseshim.CreateRequest // The ID of parent directory inode within which to create the child file. Parent InodeID @@ -274,7 +274,7 @@ type CreateFileOp struct { Mode os.FileMode // Flags for the open operation. - Flags bazilfuse.OpenFlags + Flags fuseshim.OpenFlags // Set by the file system: information about the inode that was created. // @@ -299,9 +299,9 @@ func (o *CreateFileOp) ShortDesc() (desc string) { } func (o *CreateFileOp) respond() { - resp := bazilfuse.CreateResponse{ - OpenResponse: bazilfuse.OpenResponse{ - Handle: bazilfuse.HandleID(o.Handle), + resp := fuseshim.CreateResponse{ + OpenResponse: fuseshim.OpenResponse{ + Handle: fuseshim.HandleID(o.Handle), }, } @@ -315,7 +315,7 @@ func (o *CreateFileOp) respond() { // return EEXIST (cf. the notes on CreateFileOp and MkDirOp). type CreateSymlinkOp struct { commonOp - bfReq *bazilfuse.SymlinkRequest + bfReq *fuseshim.SymlinkRequest // The ID of parent directory inode within which to create the child symlink. Parent InodeID @@ -345,7 +345,7 @@ func (o *CreateSymlinkOp) ShortDesc() (desc string) { } func (o *CreateSymlinkOp) respond() { - resp := bazilfuse.SymlinkResponse{} + resp := fuseshim.SymlinkResponse{} convertChildInodeEntry(&o.Entry, &resp.LookupResponse) o.bfReq.Respond(&resp) @@ -392,7 +392,7 @@ func (o *CreateSymlinkOp) respond() { // type RenameOp struct { commonOp - bfReq *bazilfuse.RenameRequest + bfReq *fuseshim.RenameRequest // The old parent directory, and the name of the entry within it to be // relocated. @@ -419,7 +419,7 @@ func (o *RenameOp) respond() { // Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf) type RmDirOp struct { commonOp - bfReq *bazilfuse.RemoveRequest + bfReq *fuseshim.RemoveRequest // The ID of parent directory inode, and the name of the directory being // removed within it. @@ -440,7 +440,7 @@ func (o *RmDirOp) respond() { // Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C) type UnlinkOp struct { commonOp - bfReq *bazilfuse.RemoveRequest + bfReq *fuseshim.RemoveRequest // The ID of parent directory inode, and the name of the entry being removed // within it. @@ -465,13 +465,13 @@ func (o *UnlinkOp) respond() { // https://github.com/osxfuse/osxfuse/issues/199). type OpenDirOp struct { commonOp - bfReq *bazilfuse.OpenRequest + bfReq *fuseshim.OpenRequest // The ID of the inode to be opened. Inode InodeID // Mode and options flags. - Flags bazilfuse.OpenFlags + Flags fuseshim.OpenFlags // Set by the file system: an opaque ID that will be echoed in follow-up // calls for this directory using the same struct file in the kernel. In @@ -485,8 +485,8 @@ type OpenDirOp struct { } func (o *OpenDirOp) respond() { - resp := bazilfuse.OpenResponse{ - Handle: bazilfuse.HandleID(o.Handle), + resp := fuseshim.OpenResponse{ + Handle: fuseshim.HandleID(o.Handle), } o.bfReq.Respond(&resp) @@ -496,7 +496,7 @@ func (o *OpenDirOp) respond() { // Read entries from a directory previously opened with OpenDir. type ReadDirOp struct { commonOp - bfReq *bazilfuse.ReadRequest + bfReq *fuseshim.ReadRequest // The directory inode that we are reading, and the handle previously // returned by OpenDir when opening that inode. @@ -585,7 +585,7 @@ type ReadDirOp struct { } func (o *ReadDirOp) respond() { - resp := bazilfuse.ReadResponse{ + resp := fuseshim.ReadResponse{ Data: o.Data, } @@ -603,7 +603,7 @@ func (o *ReadDirOp) respond() { // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do). type ReleaseDirHandleOp struct { commonOp - bfReq *bazilfuse.ReleaseRequest + bfReq *fuseshim.ReleaseRequest // The handle ID to be released. The kernel guarantees that this ID will not // be used in further calls to the file system (unless it is reissued by the @@ -628,13 +628,13 @@ func (o *ReleaseDirHandleOp) respond() { // (cf.https://github.com/osxfuse/osxfuse/issues/199). type OpenFileOp struct { commonOp - bfReq *bazilfuse.OpenRequest + bfReq *fuseshim.OpenRequest // The ID of the inode to be opened. Inode InodeID // Mode and options flags. - Flags bazilfuse.OpenFlags + Flags fuseshim.OpenFlags // An opaque ID that will be echoed in follow-up calls for this file using // the same struct file in the kernel. In practice this usually means @@ -647,8 +647,8 @@ type OpenFileOp struct { } func (o *OpenFileOp) respond() { - resp := bazilfuse.OpenResponse{ - Handle: bazilfuse.HandleID(o.Handle), + resp := fuseshim.OpenResponse{ + Handle: fuseshim.HandleID(o.Handle), } o.bfReq.Respond(&resp) @@ -662,7 +662,7 @@ func (o *OpenFileOp) respond() { // more. type ReadFileOp struct { commonOp - bfReq *bazilfuse.ReadRequest + bfReq *fuseshim.ReadRequest // The file inode that we are reading, and the handle previously returned by // CreateFile or OpenFile when opening that inode. @@ -686,7 +686,7 @@ type ReadFileOp struct { } func (o *ReadFileOp) respond() { - resp := bazilfuse.ReadResponse{ + resp := fuseshim.ReadResponse{ Data: o.Data, } @@ -727,7 +727,7 @@ func (o *ReadFileOp) respond() { // concurrent requests".) type WriteFileOp struct { commonOp - bfReq *bazilfuse.WriteRequest + bfReq *fuseshim.WriteRequest // The file inode that we are modifying, and the handle previously returned // by CreateFile or OpenFile when opening that inode. @@ -766,7 +766,7 @@ type WriteFileOp struct { } func (o *WriteFileOp) respond() { - resp := bazilfuse.WriteResponse{ + resp := fuseshim.WriteResponse{ Size: len(o.Data), } @@ -792,7 +792,7 @@ func (o *WriteFileOp) respond() { // file (but which is not used in "real" file systems). type SyncFileOp struct { commonOp - bfReq *bazilfuse.FsyncRequest + bfReq *fuseshim.FsyncRequest // The file and handle being sync'd. Inode InodeID @@ -853,7 +853,7 @@ func (o *SyncFileOp) respond() { // return any errors that occur. type FlushFileOp struct { commonOp - bfReq *bazilfuse.FlushRequest + bfReq *fuseshim.FlushRequest // The file and handle being flushed. Inode InodeID @@ -875,7 +875,7 @@ func (o *FlushFileOp) respond() { // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do). type ReleaseFileHandleOp struct { commonOp - bfReq *bazilfuse.ReleaseRequest + bfReq *fuseshim.ReleaseRequest // The handle ID to be released. The kernel guarantees that this ID will not // be used in further calls to the file system (unless it is reissued by the @@ -910,7 +910,7 @@ func (o *unknownOp) respond() { // Read the target of a symlink inode. type ReadSymlinkOp struct { commonOp - bfReq *bazilfuse.ReadlinkRequest + bfReq *fuseshim.ReadlinkRequest // The symlink inode that we are reading. Inode InodeID diff --git a/fuseops/simple_types.go b/fuseops/simple_types.go index 1e9b7f1..2ef7f54 100644 --- a/fuseops/simple_types.go +++ b/fuseops/simple_types.go @@ -19,7 +19,7 @@ import ( "os" "time" - "github.com/jacobsa/bazilfuse" + "github.com/jacobsa/fuse/internal/fuseshim" ) // A 64-bit number used to uniquely identify a file or directory in the file @@ -38,7 +38,7 @@ const RootInodeID = 1 func init() { // Make sure the constant above is correct. We do this at runtime rather than - // defining the constant in terms of bazilfuse.RootID for two reasons: + // defining the constant in terms of fuseshim.RootID for two reasons: // // 1. Users can more clearly see that the root ID is low and can therefore // be used as e.g. an array index, with space reserved up to the root. @@ -46,12 +46,12 @@ func init() { // 2. The constant can be untyped and can therefore more easily be used as // an array index. // - if RootInodeID != bazilfuse.RootID { + if RootInodeID != fuseshim.RootID { panic( fmt.Sprintf( "Oops, RootInodeID is wrong: %v vs. %v", RootInodeID, - bazilfuse.RootID)) + fuseshim.RootID)) } } From 8e0cba22ddbe9a509350e06d45fb4eff723e830d Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:22:05 +1000 Subject: [PATCH 015/157] Unexported unused Flags fields. --- fuseops/convert.go | 3 --- fuseops/ops.go | 9 --------- 2 files changed, 12 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 706c33c..1ef9a06 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -109,7 +109,6 @@ func Convert( Parent: InodeID(typed.Header.Node), Name: typed.Name, Mode: typed.Mode, - Flags: typed.Flags, } io = to co = &to.commonOp @@ -159,7 +158,6 @@ func Convert( to := &OpenDirOp{ bfReq: typed, Inode: InodeID(typed.Header.Node), - Flags: typed.Flags, } io = to co = &to.commonOp @@ -167,7 +165,6 @@ func Convert( to := &OpenFileOp{ bfReq: typed, Inode: InodeID(typed.Header.Node), - Flags: typed.Flags, } io = to co = &to.commonOp diff --git a/fuseops/ops.go b/fuseops/ops.go index 180b487..e2a2684 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -273,9 +273,6 @@ type CreateFileOp struct { Name string Mode os.FileMode - // Flags for the open operation. - Flags fuseshim.OpenFlags - // Set by the file system: information about the inode that was created. // // The lookup count for the inode is implicitly incremented. See notes on @@ -470,9 +467,6 @@ type OpenDirOp struct { // The ID of the inode to be opened. Inode InodeID - // Mode and options flags. - Flags fuseshim.OpenFlags - // Set by the file system: an opaque ID that will be echoed in follow-up // calls for this directory using the same struct file in the kernel. In // practice this usually means follow-up calls using the file descriptor @@ -633,9 +627,6 @@ type OpenFileOp struct { // The ID of the inode to be opened. Inode InodeID - // Mode and options flags. - Flags fuseshim.OpenFlags - // An opaque ID that will be echoed in follow-up calls for this file using // the same struct file in the kernel. In practice this usually means // follow-up calls using the file descriptor returned by open(2). From b8bf3cacb43b48a8e610218f3b2a7b20e3626ff2 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:22:32 +1000 Subject: [PATCH 016/157] Fixed some build errors. --- fuseops/convert.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 1ef9a06..d21ad70 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -18,6 +18,8 @@ import ( "log" "time" + "github.com/jacobsa/fuse/internal/fusekernel" + "github.com/jacobsa/fuse/internal/fuseshim" "golang.org/x/net/context" ) @@ -65,19 +67,19 @@ func Convert( Inode: InodeID(typed.Header.Node), } - if typed.Valid&fuseshim.SetattrSize != 0 { + if typed.Valid&fusekernel.SetattrSize != 0 { to.Size = &typed.Size } - if typed.Valid&fuseshim.SetattrMode != 0 { + if typed.Valid&fusekernel.SetattrMode != 0 { to.Mode = &typed.Mode } - if typed.Valid&fuseshim.SetattrAtime != 0 { + if typed.Valid&fusekernel.SetattrAtime != 0 { to.Atime = &typed.Atime } - if typed.Valid&fuseshim.SetattrMtime != 0 { + if typed.Valid&fusekernel.SetattrMtime != 0 { to.Mtime = &typed.Mtime } From 38685c9ea2e5fd8ea3554d1da774786398fc4b3d Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:22:49 +1000 Subject: [PATCH 017/157] Copied some missing files from from jacobsa/bazilfuse@b378951. --- internal/fuseshim/unmount.go | 6 ++++++ internal/fuseshim/unmount_linux.go | 21 +++++++++++++++++++++ internal/fuseshim/unmount_std.go | 17 +++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 internal/fuseshim/unmount.go create mode 100644 internal/fuseshim/unmount_linux.go create mode 100644 internal/fuseshim/unmount_std.go diff --git a/internal/fuseshim/unmount.go b/internal/fuseshim/unmount.go new file mode 100644 index 0000000..9cf90ad --- /dev/null +++ b/internal/fuseshim/unmount.go @@ -0,0 +1,6 @@ +package bazilfuse + +// 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 new file mode 100644 index 0000000..e4c18d6 --- /dev/null +++ b/internal/fuseshim/unmount_linux.go @@ -0,0 +1,21 @@ +package bazilfuse + +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 new file mode 100644 index 0000000..87eea1b --- /dev/null +++ b/internal/fuseshim/unmount_std.go @@ -0,0 +1,17 @@ +// +build !linux + +package bazilfuse + +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 +} From 61d71f86ba043a2d0357d9ac378e3a5a28e6a670 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:23:26 +1000 Subject: [PATCH 018/157] Fixed some package names. --- internal/fuseshim/unmount.go | 2 +- internal/fuseshim/unmount_std.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/fuseshim/unmount.go b/internal/fuseshim/unmount.go index 9cf90ad..0cc989d 100644 --- a/internal/fuseshim/unmount.go +++ b/internal/fuseshim/unmount.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim // Unmount tries to unmount the filesystem mounted at dir. func Unmount(dir string) error { diff --git a/internal/fuseshim/unmount_std.go b/internal/fuseshim/unmount_std.go index 87eea1b..3c38dd2 100644 --- a/internal/fuseshim/unmount_std.go +++ b/internal/fuseshim/unmount_std.go @@ -1,6 +1,6 @@ // +build !linux -package bazilfuse +package fuseshim import ( "os" From dd005502f0069cad963b31a0161956a09733a54a Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:23:45 +1000 Subject: [PATCH 019/157] Fixed a build error. --- unmount.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unmount.go b/unmount.go index e261333..7acec46 100644 --- a/unmount.go +++ b/unmount.go @@ -14,6 +14,8 @@ package fuse +import "github.com/jacobsa/fuse/internal/fuseshim" + // Attempt to unmount the file system whose mount point is the supplied // directory. func Unmount(dir string) error { From aa32dd928b9a4a2a4ebd84927725a99301493a71 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:26:32 +1000 Subject: [PATCH 020/157] Fixed some more references to bazilfuse. --- internal/fusekernel/fuse_kernel_freebsd.go | 2 +- internal/fusekernel/fuse_kernel_linux.go | 2 +- internal/fuseshim/mount_freebsd.go | 2 +- internal/fuseshim/mount_linux.go | 2 +- internal/fuseshim/options_freebsd.go | 2 +- internal/fuseshim/options_linux.go | 2 +- samples/flushfs/flush_fs_test.go | 10 +++++----- samples/mount_sample/mount.go | 6 +++--- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/fusekernel/fuse_kernel_freebsd.go b/internal/fusekernel/fuse_kernel_freebsd.go index 53b846a..f04590f 100644 --- a/internal/fusekernel/fuse_kernel_freebsd.go +++ b/internal/fusekernel/fuse_kernel_freebsd.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim import "time" diff --git a/internal/fusekernel/fuse_kernel_linux.go b/internal/fusekernel/fuse_kernel_linux.go index fc0509b..a464e50 100644 --- a/internal/fusekernel/fuse_kernel_linux.go +++ b/internal/fusekernel/fuse_kernel_linux.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim import "time" diff --git a/internal/fuseshim/mount_freebsd.go b/internal/fuseshim/mount_freebsd.go index 3a02fa2..36b9aac 100644 --- a/internal/fuseshim/mount_freebsd.go +++ b/internal/fuseshim/mount_freebsd.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim import ( "fmt" diff --git a/internal/fuseshim/mount_linux.go b/internal/fuseshim/mount_linux.go index 7f18132..dfe70b5 100644 --- a/internal/fuseshim/mount_linux.go +++ b/internal/fuseshim/mount_linux.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim import ( "bufio" diff --git a/internal/fuseshim/options_freebsd.go b/internal/fuseshim/options_freebsd.go index 66bf0c3..b9ceae2 100644 --- a/internal/fuseshim/options_freebsd.go +++ b/internal/fuseshim/options_freebsd.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim func localVolume(conf *mountConfig) error { return nil diff --git a/internal/fuseshim/options_linux.go b/internal/fuseshim/options_linux.go index 66bf0c3..b9ceae2 100644 --- a/internal/fuseshim/options_linux.go +++ b/internal/fuseshim/options_linux.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim func localVolume(conf *mountConfig) error { return nil diff --git a/samples/flushfs/flush_fs_test.go b/samples/flushfs/flush_fs_test.go index 2f8f0fb..60d8d11 100644 --- a/samples/flushfs/flush_fs_test.go +++ b/samples/flushfs/flush_fs_test.go @@ -30,9 +30,9 @@ import ( "golang.org/x/sys/unix" - "github.com/jacobsa/bazilfuse" "github.com/jacobsa/fuse/fsutil" "github.com/jacobsa/fuse/fusetesting" + "github.com/jacobsa/fuse/internal/fuseshim" "github.com/jacobsa/fuse/samples" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" @@ -58,8 +58,8 @@ type flushFSTest struct { func (t *flushFSTest) setUp( ti *TestInfo, - flushErr bazilfuse.Errno, - fsyncErr bazilfuse.Errno, + flushErr fuseshim.Errno, + fsyncErr fuseshim.Errno, readOnly bool) { var err error @@ -810,7 +810,7 @@ func init() { RegisterTestSuite(&FlushErrorTest{}) } func (t *FlushErrorTest) SetUp(ti *TestInfo) { const noErr = 0 - t.flushFSTest.setUp(ti, bazilfuse.ENOENT, noErr, false) + t.flushFSTest.setUp(ti, fuseshim.ENOENT, noErr, false) } func (t *FlushErrorTest) Close() { @@ -890,7 +890,7 @@ func init() { RegisterTestSuite(&FsyncErrorTest{}) } func (t *FsyncErrorTest) SetUp(ti *TestInfo) { const noErr = 0 - t.flushFSTest.setUp(ti, noErr, bazilfuse.ENOENT, false) + t.flushFSTest.setUp(ti, noErr, fuseshim.ENOENT, false) } func (t *FsyncErrorTest) Fsync() { diff --git a/samples/mount_sample/mount.go b/samples/mount_sample/mount.go index c13fe3b..71707bf 100644 --- a/samples/mount_sample/mount.go +++ b/samples/mount_sample/mount.go @@ -24,8 +24,8 @@ import ( "os" "runtime" - "github.com/jacobsa/bazilfuse" "github.com/jacobsa/fuse" + "github.com/jacobsa/fuse/internal/fuseshim" "github.com/jacobsa/fuse/samples/flushfs" "golang.org/x/net/context" ) @@ -58,11 +58,11 @@ func makeFlushFS() (server fuse.Server, err error) { var fsyncErr error if *fFlushError != 0 { - flushErr = bazilfuse.Errno(*fFlushError) + flushErr = fuseshim.Errno(*fFlushError) } if *fFsyncError != 0 { - fsyncErr = bazilfuse.Errno(*fFsyncError) + fsyncErr = fuseshim.Errno(*fFsyncError) } // Report flushes and fsyncs by writing the contents followed by a newline. From e01202779d02aecac80030d6b592e4fc4286329e Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 12:51:12 +1000 Subject: [PATCH 021/157] Deleted a broken test. --- internal/fuseshim/options_nocomma_test.go | 31 ----------------------- 1 file changed, 31 deletions(-) delete mode 100644 internal/fuseshim/options_nocomma_test.go diff --git a/internal/fuseshim/options_nocomma_test.go b/internal/fuseshim/options_nocomma_test.go deleted file mode 100644 index 6353e03..0000000 --- a/internal/fuseshim/options_nocomma_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// This file contains tests for platforms that have no escape -// mechanism for including commas in mount options. -// -// +build darwin - -package fuseshim_test - -import ( - "runtime" - "testing" - - fuse "github.com/jacobsa/bazilfuse" - "github.com/jacobsa/bazilfuse/fs/fstestutil" -) - -func TestMountOptionCommaError(t *testing.T) { - t.Parallel() - // this test is not tied to any specific option, it just needs - // some string content - var evil = "FuseTest,Marker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, - fuse.ForTestSetMountOption("fusetest", evil), - ) - if err == nil { - mnt.Close() - t.Fatal("expected an error about commas") - } - if g, e := err.Error(), `mount options cannot contain commas on `+runtime.GOOS+`: "fusetest"="FuseTest,Marker"`; g != e { - t.Fatalf("wrong error: %q != %q", g, e) - } -} From deded00e91ba33dce1f3c1068e7bda2ead0239f1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:31:06 +1000 Subject: [PATCH 022/157] Export Message. --- internal/fuseshim/fuse.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 56d1faf..be79630 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -259,7 +259,7 @@ type Header struct { Pid uint32 // process ID of process making request // for returning to reqPool - msg *message + msg *Message } func (h *Header) String() string { @@ -385,16 +385,16 @@ var bufSize = maxRequestSize + maxWrite // buf allocated and len==bufSize, and hdr set. var reqPool struct { Mu sync.Mutex - Freelist []*message + Freelist []*Message } -func allocMessage() *message { - m := &message{buf: make([]byte, bufSize)} +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) { +func getMessage(c *Conn) (m *Message) { reqPool.Mu.Lock() l := len(reqPool.Freelist) if l != 0 { @@ -412,7 +412,7 @@ func getMessage(c *Conn) (m *message) { return m } -func putMessage(m *message) { +func putMessage(m *Message) { m.buf = m.buf[:bufSize] m.conn = nil m.off = 0 @@ -423,18 +423,18 @@ func putMessage(m *message) { } // a message represents the bytes of a single FUSE message -type message struct { +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 { +func (m *Message) len() uintptr { return uintptr(len(m.buf) - m.off) } -func (m *message) data() unsafe.Pointer { +func (m *Message) data() unsafe.Pointer { var p unsafe.Pointer if m.off < len(m.buf) { p = unsafe.Pointer(&m.buf[m.off]) @@ -442,11 +442,11 @@ func (m *message) data() unsafe.Pointer { return p } -func (m *message) bytes() []byte { +func (m *Message) bytes() []byte { return m.buf[m.off:] } -func (m *message) Header() Header { +func (m *Message) Header() Header { h := m.hdr return Header{ Conn: m.conn, From c18d5a42e6287c3df1ed0e869ccd511fc02633bd Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:33:59 +1000 Subject: [PATCH 023/157] Split out message reading in preparation for reusing it. --- internal/fuseshim/fuse.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index be79630..2b1bb38 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -524,12 +524,12 @@ func (c *Conn) Protocol() fusekernel.Protocol { return c.proto } -// ReadRequest returns the next FUSE request from the kernel. +// 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. // -// 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) { - m := getMessage(c) +// The message must later be returend with putMessage. +func (c *Conn) ReadMessage() (m *Message, err error) { + m = getMessage(c) loop: c.rio.RLock() n, err := syscall.Read(c.fd(), m.buf) @@ -573,6 +573,19 @@ loop: } 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. From de61f1bdca59ec02dd81f466927fa19444f29785 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:35:19 +1000 Subject: [PATCH 024/157] Export a Destroy method. --- internal/fuseshim/fuse.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 2b1bb38..d7e1db0 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -460,6 +460,12 @@ func (m *Message) Header() Header { } } +// 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) @@ -527,7 +533,7 @@ func (c *Conn) Protocol() fusekernel.Protocol { // 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 returend with putMessage. +// The message must later be returned by calling m.Destroy. func (c *Conn) ReadMessage() (m *Message, err error) { m = getMessage(c) loop: @@ -540,17 +546,17 @@ loop: goto loop } if err != nil && err != syscall.ENODEV { - putMessage(m) + m.Destroy() return nil, err } if n <= 0 { - putMessage(m) + m.Destroy() return nil, io.EOF } m.buf = m.buf[:n] if n < fusekernel.InHeaderSize { - putMessage(m) + m.Destroy() return nil, errors.New("fuse: message too short") } @@ -568,7 +574,7 @@ loop: 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) - putMessage(m) + m.Destroy() return nil, err } @@ -1018,7 +1024,7 @@ func (c *Conn) ReadRequest() (Request, error) { return req, nil corrupt: - putMessage(m) + m.Destroy() return nil, fmt.Errorf("fuse: malformed message") unrecognized: From 8a92167cc8b21283c855b66a503a1d56e4d44652 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:36:31 +1000 Subject: [PATCH 025/157] Updated the signature of fuseops.Convert. --- fuseops/convert.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index d21ad70..52936f3 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -26,9 +26,9 @@ import ( // This function is an implementation detail of the fuse package, and must not // be called by anyone else. // -// Convert the supplied fuseshim request struct to an Op. finished will be -// called with the error supplied to o.Respond when the user invokes that -// method, before a response is sent to the kernel. +// Convert the supplied fuse kernel message to an Op. finished will be called +// with the error supplied to o.Respond when the user invokes that method, +// before a response is sent to the kernel. o.Respond will destroy the message. // // It is guaranteed that o != nil. If the op is unknown, a special unexported // type will be used. @@ -36,7 +36,7 @@ import ( // The debug logging function and error logger may be nil. func Convert( opCtx context.Context, - r fuseshim.Request, + m *fuseshim.Message, debugLogForOp func(int, string, ...interface{}), errorLogger *log.Logger, finished func(error)) (o Op) { From 7fb5d10b11ef4e72d6ecf9c4fe0a9e452e4f245c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:37:58 +1000 Subject: [PATCH 026/157] Begin on Convert. --- fuseops/convert.go | 2 +- internal/fuseshim/fuse.go | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 52936f3..17f1fd1 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -43,7 +43,7 @@ func Convert( var co *commonOp var io internalOp - switch typed := r.(type) { + switch m.Hdr.Opcode { case *fuseshim.LookupRequest: to := &LookUpInodeOp{ bfReq: typed, diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index d7e1db0..5c73103 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -382,7 +382,7 @@ var bufSize = maxRequestSize + maxWrite // 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. +// buf allocated and len==bufSize, and Hdr set. var reqPool struct { Mu sync.Mutex Freelist []*Message @@ -390,7 +390,7 @@ var reqPool struct { func allocMessage() *Message { m := &Message{buf: make([]byte, bufSize)} - m.hdr = (*fusekernel.InHeader)(unsafe.Pointer(&m.buf[0])) + m.Hdr = (*fusekernel.InHeader)(unsafe.Pointer(&m.buf[0])) return m } @@ -426,7 +426,7 @@ func putMessage(m *Message) { type Message struct { conn *Conn buf []byte // all bytes - hdr *fusekernel.InHeader // header + Hdr *fusekernel.InHeader // header off int // offset for reading additional fields } @@ -447,7 +447,7 @@ func (m *Message) bytes() []byte { } func (m *Message) Header() Header { - h := m.hdr + h := m.Hdr return Header{ Conn: m.conn, ID: RequestID(h.Unique), @@ -562,18 +562,18 @@ loop: // 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) + 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) + // 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) { + 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) + err := fmt.Errorf("fuse: read %d opcode %d but expected %d", n, m.Hdr.Opcode, m.Hdr.Len) m.Destroy() return nil, err } @@ -596,7 +596,7 @@ func (c *Conn) ReadRequest() (Request, error) { // Convert to data structures. // Do not trust kernel to hand us well-formed data. var req Request - switch m.hdr.Opcode { + switch m.Hdr.Opcode { default: goto unrecognized @@ -756,7 +756,7 @@ func (c *Conn) ReadRequest() (Request, error) { req = &RemoveRequest{ Header: m.Header(), Name: string(buf[:n-1]), - Dir: m.hdr.Opcode == fusekernel.OpRmdir, + Dir: m.Hdr.Opcode == fusekernel.OpRmdir, } case fusekernel.OpRename: @@ -792,7 +792,7 @@ func (c *Conn) ReadRequest() (Request, error) { } req = &OpenRequest{ Header: m.Header(), - Dir: m.hdr.Opcode == fusekernel.OpOpendir, + Dir: m.Hdr.Opcode == fusekernel.OpOpendir, Flags: fusekernel.OpenFlags(in.Flags), } @@ -803,7 +803,7 @@ func (c *Conn) ReadRequest() (Request, error) { } r := &ReadRequest{ Header: m.Header(), - Dir: m.hdr.Opcode == fusekernel.OpReaddir, + Dir: m.Hdr.Opcode == fusekernel.OpReaddir, Handle: HandleID(in.Fh), Offset: int64(in.Offset), Size: int(in.Size), @@ -849,7 +849,7 @@ func (c *Conn) ReadRequest() (Request, error) { } req = &ReleaseRequest{ Header: m.Header(), - Dir: m.hdr.Opcode == fusekernel.OpReleasedir, + Dir: m.Hdr.Opcode == fusekernel.OpReleasedir, Handle: HandleID(in.Fh), Flags: fusekernel.OpenFlags(in.Flags), ReleaseFlags: fusekernel.ReleaseFlags(in.ReleaseFlags), @@ -862,7 +862,7 @@ func (c *Conn) ReadRequest() (Request, error) { goto corrupt } req = &FsyncRequest{ - Dir: m.hdr.Opcode == fusekernel.OpFsyncdir, + Dir: m.Hdr.Opcode == fusekernel.OpFsyncdir, Header: m.Header(), Handle: HandleID(in.Fh), Flags: in.FsyncFlags, From f70c00c1edecbf4d9881e431650b08a5f23d5cd0 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:40:27 +1000 Subject: [PATCH 027/157] LookUpInodeOp --- fuseops/convert.go | 13 +++++++++---- internal/fuseshim/fuse.go | 30 +++++++++++++++--------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 17f1fd1..d5eb007 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -44,11 +44,16 @@ func Convert( var io internalOp switch m.Hdr.Opcode { - case *fuseshim.LookupRequest: + case fusekernel.OpLookup: + buf := m.Bytes() + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + goto corrupt + } + to := &LookUpInodeOp{ - bfReq: typed, - Parent: InodeID(typed.Header.Node), - Name: typed.Name, + Parent: InodeID(m.Header().Node), + Name: string(buf[:n-1]), } io = to co = &to.commonOp diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 5c73103..a41c23f 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -442,7 +442,7 @@ func (m *Message) data() unsafe.Pointer { return p } -func (m *Message) bytes() []byte { +func (m *Message) Bytes() []byte { return m.buf[m.off:] } @@ -601,7 +601,7 @@ func (c *Conn) ReadRequest() (Request, error) { goto unrecognized case fusekernel.OpLookup: - buf := m.bytes() + buf := m.Bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { goto corrupt @@ -661,7 +661,7 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpReadlink: - if len(m.bytes()) > 0 { + if len(m.Bytes()) > 0 { goto corrupt } req = &ReadlinkRequest{ @@ -669,8 +669,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpSymlink: - // m.bytes() is "newName\0target\0" - names := m.bytes() + // m.Bytes() is "newName\0target\0" + names := m.Bytes() if len(names) == 0 || names[len(names)-1] != 0 { goto corrupt } @@ -690,7 +690,7 @@ func (c *Conn) ReadRequest() (Request, error) { if m.len() < unsafe.Sizeof(*in) { goto corrupt } - newName := m.bytes()[unsafe.Sizeof(*in):] + newName := m.Bytes()[unsafe.Sizeof(*in):] if len(newName) < 2 || newName[len(newName)-1] != 0 { goto corrupt } @@ -707,7 +707,7 @@ func (c *Conn) ReadRequest() (Request, error) { goto corrupt } in := (*fusekernel.MknodIn)(m.data()) - name := m.bytes()[size:] + name := m.Bytes()[size:] if len(name) < 2 || name[len(name)-1] != '\x00' { goto corrupt } @@ -729,7 +729,7 @@ func (c *Conn) ReadRequest() (Request, error) { goto corrupt } in := (*fusekernel.MkdirIn)(m.data()) - name := m.bytes()[size:] + name := m.Bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt @@ -748,7 +748,7 @@ func (c *Conn) ReadRequest() (Request, error) { req = r case fusekernel.OpUnlink, fusekernel.OpRmdir: - buf := m.bytes() + buf := m.Bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { goto corrupt @@ -765,7 +765,7 @@ func (c *Conn) ReadRequest() (Request, error) { goto corrupt } newDirNodeID := NodeID(in.Newdir) - oldNew := m.bytes()[unsafe.Sizeof(*in):] + oldNew := m.Bytes()[unsafe.Sizeof(*in):] // oldNew should be "old\x00new\x00" if len(oldNew) < 4 { goto corrupt @@ -830,7 +830,7 @@ func (c *Conn) ReadRequest() (Request, error) { r.LockOwner = in.LockOwner r.FileFlags = fusekernel.OpenFlags(in.Flags) } - buf := m.bytes()[fusekernel.WriteInSize(c.proto):] + buf := m.Bytes()[fusekernel.WriteInSize(c.proto):] if uint32(len(buf)) < in.Size { goto corrupt } @@ -874,7 +874,7 @@ func (c *Conn) ReadRequest() (Request, error) { goto corrupt } m.off += int(unsafe.Sizeof(*in)) - name := m.bytes() + name := m.Bytes() i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt @@ -897,7 +897,7 @@ func (c *Conn) ReadRequest() (Request, error) { if m.len() < unsafe.Sizeof(*in) { goto corrupt } - name := m.bytes()[unsafe.Sizeof(*in):] + name := m.Bytes()[unsafe.Sizeof(*in):] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt @@ -921,7 +921,7 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpRemovexattr: - buf := m.bytes() + buf := m.Bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { goto corrupt @@ -978,7 +978,7 @@ func (c *Conn) ReadRequest() (Request, error) { goto corrupt } in := (*fusekernel.CreateIn)(m.data()) - name := m.bytes()[size:] + name := m.Bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt From 93a670bc8cb834b55b9cc16268c3c96e4cf1eecd Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 23 Jul 2015 16:41:34 +1000 Subject: [PATCH 028/157] GetInodeAttributesOp --- fuseops/convert.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index d5eb007..5213f72 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -58,10 +58,9 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.GetattrRequest: + case fusekernel.OpGetattr: to := &GetInodeAttributesOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), + Inode: InodeID(m.Header().Node), } io = to co = &to.commonOp From a30301af462a2ea3f3af3d7b8333a676480a4ffe Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 07:53:16 +1000 Subject: [PATCH 029/157] SetInodeAttributesOp --- fuseops/convert.go | 31 +++++++---- internal/fuseshim/fuse.go | 105 +++++++++++++++++++------------------- 2 files changed, 72 insertions(+), 64 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 5213f72..ca4348d 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -17,6 +17,7 @@ package fuseops import ( "log" "time" + "unsafe" "github.com/jacobsa/fuse/internal/fusekernel" "github.com/jacobsa/fuse/internal/fuseshim" @@ -65,26 +66,34 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.SetattrRequest: + case fusekernel.OpSetattr: + in := (*fusekernel.SetattrIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { + goto corrupt + } + to := &SetInodeAttributesOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), + Inode: InodeID(m.Header().Node), } - if typed.Valid&fusekernel.SetattrSize != 0 { - to.Size = &typed.Size + valid := fusekernel.SetattrValid(in.Valid) + if valid&fusekernel.SetattrSize != 0 { + to.Size = &in.Size } - if typed.Valid&fusekernel.SetattrMode != 0 { - to.Mode = &typed.Mode + if valid&fusekernel.SetattrMode != 0 { + mode := fuseshim.FileMode(in.Mode) + to.Mode = &mode } - if typed.Valid&fusekernel.SetattrAtime != 0 { - to.Atime = &typed.Atime + if valid&fusekernel.SetattrAtime != 0 { + t := time.Unix(int64(in.Atime), int64(in.AtimeNsec)) + to.Atime = &t } - if typed.Valid&fusekernel.SetattrMtime != 0 { - to.Mtime = &typed.Mtime + if valid&fusekernel.SetattrMtime != 0 { + t := time.Unix(int64(in.Mtime), int64(in.MtimeNsec)) + to.Mtime = &t } io = to diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index a41c23f..69c12ac 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -430,12 +430,11 @@ type Message struct { off int // offset for reading additional fields } -func (m *Message) len() uintptr { +func (m *Message) Len() uintptr { return uintptr(len(m.buf) - m.off) } -func (m *Message) data() unsafe.Pointer { - var p unsafe.Pointer +func (m *Message) Data() (p unsafe.Pointer) { if m.off < len(m.buf) { p = unsafe.Pointer(&m.buf[m.off]) } @@ -466,8 +465,8 @@ func (m *Message) Destroy() { putMessage(m) } -// fileMode returns a Go os.FileMode from a Unix mode. -func fileMode(unixMode uint32) os.FileMode { +// 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: @@ -612,8 +611,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpForget: - in := (*fusekernel.ForgetIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.ForgetIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &ForgetRequest{ @@ -629,8 +628,8 @@ func (c *Conn) ReadRequest() (Request, error) { } default: - in := (*fusekernel.GetattrIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.GetattrIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &GetattrRequest{ @@ -641,8 +640,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpSetattr: - in := (*fusekernel.SetattrIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.SetattrIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &SetattrRequest{ @@ -652,7 +651,7 @@ func (c *Conn) ReadRequest() (Request, error) { 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), + Mode: FileMode(in.Mode), Uid: in.Uid, Gid: in.Gid, Bkuptime: in.BkupTime(), @@ -686,8 +685,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpLink: - in := (*fusekernel.LinkIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.LinkIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } newName := m.Bytes()[unsafe.Sizeof(*in):] @@ -703,10 +702,10 @@ func (c *Conn) ReadRequest() (Request, error) { case fusekernel.OpMknod: size := fusekernel.MknodInSize(c.proto) - if m.len() < size { + if m.Len() < size { goto corrupt } - in := (*fusekernel.MknodIn)(m.data()) + in := (*fusekernel.MknodIn)(m.Data()) name := m.Bytes()[size:] if len(name) < 2 || name[len(name)-1] != '\x00' { goto corrupt @@ -714,21 +713,21 @@ func (c *Conn) ReadRequest() (Request, error) { name = name[:len(name)-1] r := &MknodRequest{ Header: m.Header(), - Mode: fileMode(in.Mode), + 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 + r.Umask = FileMode(in.Umask) & os.ModePerm } req = r case fusekernel.OpMkdir: size := fusekernel.MkdirInSize(c.proto) - if m.len() < size { + if m.Len() < size { goto corrupt } - in := (*fusekernel.MkdirIn)(m.data()) + in := (*fusekernel.MkdirIn)(m.Data()) name := m.Bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { @@ -738,12 +737,12 @@ func (c *Conn) ReadRequest() (Request, error) { 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" + // 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), + 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 + r.Umask = FileMode(in.Umask) & os.ModePerm } req = r @@ -760,8 +759,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpRename: - in := (*fusekernel.RenameIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.RenameIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } newDirNodeID := NodeID(in.Newdir) @@ -786,8 +785,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpOpendir, fusekernel.OpOpen: - in := (*fusekernel.OpenIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.OpenIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &OpenRequest{ @@ -797,8 +796,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpRead, fusekernel.OpReaddir: - in := (*fusekernel.ReadIn)(m.data()) - if m.len() < fusekernel.ReadInSize(c.proto) { + in := (*fusekernel.ReadIn)(m.Data()) + if m.Len() < fusekernel.ReadInSize(c.proto) { goto corrupt } r := &ReadRequest{ @@ -816,8 +815,8 @@ func (c *Conn) ReadRequest() (Request, error) { req = r case fusekernel.OpWrite: - in := (*fusekernel.WriteIn)(m.data()) - if m.len() < fusekernel.WriteInSize(c.proto) { + in := (*fusekernel.WriteIn)(m.Data()) + if m.Len() < fusekernel.WriteInSize(c.proto) { goto corrupt } r := &WriteRequest{ @@ -843,8 +842,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpRelease, fusekernel.OpReleasedir: - in := (*fusekernel.ReleaseIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.ReleaseIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &ReleaseRequest{ @@ -857,8 +856,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpFsync, fusekernel.OpFsyncdir: - in := (*fusekernel.FsyncIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.FsyncIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &FsyncRequest{ @@ -869,8 +868,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpSetxattr: - in := (*fusekernel.SetxattrIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.SetxattrIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } m.off += int(unsafe.Sizeof(*in)) @@ -893,8 +892,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpGetxattr: - in := (*fusekernel.GetxattrIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.GetxattrIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } name := m.Bytes()[unsafe.Sizeof(*in):] @@ -910,8 +909,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpListxattr: - in := (*fusekernel.GetxattrIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.GetxattrIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &ListxattrRequest{ @@ -932,8 +931,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpFlush: - in := (*fusekernel.FlushIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.FlushIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &FlushRequest{ @@ -944,8 +943,8 @@ func (c *Conn) ReadRequest() (Request, error) { } case fusekernel.OpInit: - in := (*fusekernel.InitIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.InitIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &InitRequest{ @@ -963,8 +962,8 @@ func (c *Conn) ReadRequest() (Request, error) { panic("fusekernel.OpSetlkw") case fusekernel.OpAccess: - in := (*fusekernel.AccessIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.AccessIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &AccessRequest{ @@ -974,10 +973,10 @@ func (c *Conn) ReadRequest() (Request, error) { case fusekernel.OpCreate: size := fusekernel.CreateInSize(c.proto) - if m.len() < size { + if m.Len() < size { goto corrupt } - in := (*fusekernel.CreateIn)(m.data()) + in := (*fusekernel.CreateIn)(m.Data()) name := m.Bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { @@ -986,17 +985,17 @@ func (c *Conn) ReadRequest() (Request, error) { r := &CreateRequest{ Header: m.Header(), Flags: fusekernel.OpenFlags(in.Flags), - Mode: fileMode(in.Mode), + Mode: FileMode(in.Mode), Name: string(name[:i]), } if c.proto.GE(fusekernel.Protocol{7, 12}) { - r.Umask = fileMode(in.Umask) & os.ModePerm + r.Umask = FileMode(in.Umask) & os.ModePerm } req = r case fusekernel.OpInterrupt: - in := (*fusekernel.InterruptIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + in := (*fusekernel.InterruptIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { goto corrupt } req = &InterruptRequest{ From 091d0e967c32eb5ab9c9d22daca8fd7696446aca Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 07:57:31 +1000 Subject: [PATCH 030/157] ForgetInodeOp --- fuseops/convert.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index ca4348d..40419e1 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -99,11 +99,15 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.ForgetRequest: + case fusekernel.OpForget: + in := (*fusekernel.ForgetIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { + goto corrupt + } + to := &ForgetInodeOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - N: typed.N, + Inode: InodeID(m.Header().Node), + N: in.Nlookup, } io = to co = &to.commonOp From 39b29365926ad5c2713f6cd53181960606e52b53 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:02:49 +1000 Subject: [PATCH 031/157] MkDirOp --- fuseops/convert.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 40419e1..1abff3c 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -15,6 +15,7 @@ package fuseops import ( + "bytes" "log" "time" "unsafe" @@ -38,6 +39,7 @@ import ( func Convert( opCtx context.Context, m *fuseshim.Message, + protocol fusekernel.Protocol, debugLogForOp func(int, string, ...interface{}), errorLogger *log.Logger, finished func(error)) (o Op) { @@ -112,12 +114,23 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.MkdirRequest: + case fusekernel.OpMkdir: + size := fusekernel.MkdirInSize(protocol) + 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 + } + name = name[:i] + to := &MkDirOp{ - bfReq: typed, - Parent: InodeID(typed.Header.Node), - Name: typed.Name, - Mode: typed.Mode, + Parent: InodeID(m.Header().Node), + Name: string(name), + Mode: fuseshim.FileMode(in.Mode), } io = to co = &to.commonOp From bf11280dcdbe1595e0ec0bdf79b04065a45254e1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:04:03 +1000 Subject: [PATCH 032/157] CreateFileOp --- fuseops/convert.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 1abff3c..c26b358 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -135,12 +135,23 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.CreateRequest: + case fusekernel.OpCreate: + size := fusekernel.CreateInSize(protocol) + 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 + } + name = name[:i] + to := &CreateFileOp{ - bfReq: typed, - Parent: InodeID(typed.Header.Node), - Name: typed.Name, - Mode: typed.Mode, + Parent: InodeID(m.Header().Node), + Name: string(name), + Mode: fuseshim.FileMode(in.Mode), } io = to co = &to.commonOp From 88036b9232d5f8ec5471c93005245eb4e301b4ed Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:04:59 +1000 Subject: [PATCH 033/157] CreateSymlinkOp --- fuseops/convert.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index c26b358..1bd15a1 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -156,12 +156,22 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.SymlinkRequest: + 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] + to := &CreateSymlinkOp{ - bfReq: typed, - Parent: InodeID(typed.Header.Node), - Name: typed.NewName, - Target: typed.Target, + Parent: InodeID(m.Header().Node), + Name: string(newName), + Target: string(target), } io = to co = &to.commonOp From f667900499cf0995c023813ac72007be6c1bf3be Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:07:22 +1000 Subject: [PATCH 034/157] RenameOp --- fuseops/convert.go | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 1bd15a1..73ba423 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -176,13 +176,30 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.RenameRequest: + case fusekernel.OpRename: + in := (*fusekernel.RenameIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { + goto corrupt + } + names := m.Bytes()[unsafe.Sizeof(*in):] + // names should be "old\x00new\x00" + if len(names) < 4 { + goto corrupt + } + if names[len(names)-1] != '\x00' { + goto corrupt + } + i := bytes.IndexByte(names, '\x00') + if i < 0 { + goto corrupt + } + oldName, newName := names[:i], names[i+1:len(names)-1] + to := &RenameOp{ - bfReq: typed, - OldParent: InodeID(typed.Header.Node), - OldName: typed.OldName, - NewParent: InodeID(typed.NewDir), - NewName: typed.NewName, + OldParent: InodeID(m.Header().Node), + OldName: string(oldName), + NewParent: InodeID(in.Newdir), + NewName: string(newName), } io = to co = &to.commonOp From d8e3e30f99c91a92cf4c74a374c74b0d43456ed1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:08:41 +1000 Subject: [PATCH 035/157] UnlinkOp --- fuseops/convert.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fuseops/convert.go b/fuseops/convert.go index 73ba423..a53cbc2 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -204,6 +204,20 @@ func Convert( io = to co = &to.commonOp + case fusekernel.OpUnlink: + buf := m.Bytes() + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + goto corrupt + } + + to := &UnlinkOp{ + Parent: InodeID(m.Header().Node), + Name: string(buf[:n-1]), + } + io = to + co = &to.commonOp + case *fuseshim.RemoveRequest: if typed.Dir { to := &RmDirOp{ From 7133c835aad56df47b1a11c6978e7e72fe09c825 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:09:07 +1000 Subject: [PATCH 036/157] RmDirOp --- fuseops/convert.go | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index a53cbc2..10c803e 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -218,25 +218,20 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.RemoveRequest: - if typed.Dir { - to := &RmDirOp{ - bfReq: typed, - Parent: InodeID(typed.Header.Node), - Name: typed.Name, - } - io = to - co = &to.commonOp - } else { - to := &UnlinkOp{ - bfReq: typed, - Parent: InodeID(typed.Header.Node), - Name: typed.Name, - } - io = to - co = &to.commonOp + case fusekernel.OpRmdir: + buf := m.Bytes() + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + goto corrupt } + to := &RmDirOp{ + Parent: InodeID(m.Header().Node), + Name: string(buf[:n-1]), + } + io = to + co = &to.commonOp + case *fuseshim.OpenRequest: if typed.Dir { to := &OpenDirOp{ From fc142cb274e29166ae3a9904856ae8b2f43ac1b8 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:10:45 +1000 Subject: [PATCH 037/157] ReadFileOp --- fuseops/convert.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/fuseops/convert.go b/fuseops/convert.go index 10c803e..66a2b1e 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -249,6 +249,21 @@ func Convert( co = &to.commonOp } + case fusekernel.OpRead: + in := (*fusekernel.ReadIn)(m.Data()) + if m.Len() < fusekernel.ReadInSize(protocol) { + goto corrupt + } + + to := &ReadFileOp{ + Inode: InodeID(m.Header().Node), + Handle: HandleID(in.Fh), + Offset: int64(in.Offset), + Size: int(in.Size), + } + io = to + co = &to.commonOp + case *fuseshim.ReadRequest: if typed.Dir { to := &ReadDirOp{ From 90aee4644b233a00eb7a5f5797e14202c7fe6f1f Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:11:24 +1000 Subject: [PATCH 038/157] ReadDirOp --- fuseops/convert.go | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 66a2b1e..7d06f84 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -264,29 +264,21 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.ReadRequest: - if typed.Dir { - to := &ReadDirOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - Handle: HandleID(typed.Handle), - Offset: DirOffset(typed.Offset), - Size: typed.Size, - } - io = to - co = &to.commonOp - } else { - to := &ReadFileOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - Handle: HandleID(typed.Handle), - Offset: typed.Offset, - Size: typed.Size, - } - io = to - co = &to.commonOp + case fusekernel.OpReaddir: + in := (*fusekernel.ReadIn)(m.Data()) + if m.Len() < fusekernel.ReadInSize(protocol) { + goto corrupt } + to := &ReadDirOp{ + Inode: InodeID(m.Header().Node), + Handle: HandleID(in.Fh), + Offset: int64(in.Offset), + Size: int(in.Size), + } + io = to + co = &to.commonOp + case *fuseshim.ReleaseRequest: if typed.Dir { to := &ReleaseDirHandleOp{ From ecdc71b13658274ad11a739c52ec9a686c76af1f Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:12:07 +1000 Subject: [PATCH 039/157] OpenFileOp --- fuseops/convert.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fuseops/convert.go b/fuseops/convert.go index 7d06f84..7dbcb0b 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -232,6 +232,13 @@ func Convert( io = to co = &to.commonOp + case fusekernel.OpOpen: + to := &OpenFileOp{ + Inode: InodeID(m.Header().Node), + } + io = to + co = &to.commonOp + case *fuseshim.OpenRequest: if typed.Dir { to := &OpenDirOp{ From 86c921d9eb857727dea0ac5663df1ed70ba6db5a Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:12:29 +1000 Subject: [PATCH 040/157] OpenDirOp --- fuseops/convert.go | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 7dbcb0b..b9c5b27 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -239,22 +239,12 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.OpenRequest: - if typed.Dir { - to := &OpenDirOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - } - io = to - co = &to.commonOp - } else { - to := &OpenFileOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - } - io = to - co = &to.commonOp + case fusekernel.OpOpendir: + to := &OpenDirOp{ + Inode: InodeID(m.Header().Node), } + io = to + co = &to.commonOp case fusekernel.OpRead: in := (*fusekernel.ReadIn)(m.Data()) From d1c11558615db957b6abd54d8cbcb630c9a33d39 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:13:36 +1000 Subject: [PATCH 041/157] ReleaseFileHandleOp --- fuseops/convert.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index b9c5b27..6a1025e 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -270,12 +270,24 @@ func Convert( to := &ReadDirOp{ Inode: InodeID(m.Header().Node), Handle: HandleID(in.Fh), - Offset: int64(in.Offset), + Offset: DirOffset(in.Offset), Size: int(in.Size), } io = to co = &to.commonOp + case fusekernel.OpRelease: + in := (*fusekernel.ReleaseIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { + goto corrupt + } + + to := &ReleaseFileHandleOp{ + Handle: HandleID(in.Fh), + } + io = to + co = &to.commonOp + case *fuseshim.ReleaseRequest: if typed.Dir { to := &ReleaseDirHandleOp{ From 8c9daf83da30459d79447f18539a9221f0225b1e Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:14:02 +1000 Subject: [PATCH 042/157] ReleaseDirHandleOp --- fuseops/convert.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 6a1025e..9dec336 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -288,23 +288,18 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.ReleaseRequest: - if typed.Dir { - to := &ReleaseDirHandleOp{ - bfReq: typed, - Handle: HandleID(typed.Handle), - } - io = to - co = &to.commonOp - } else { - to := &ReleaseFileHandleOp{ - bfReq: typed, - Handle: HandleID(typed.Handle), - } - io = to - co = &to.commonOp + case fusekernel.OpReleasedir: + in := (*fusekernel.ReleaseIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { + goto corrupt } + to := &ReleaseDirHandleOp{ + Handle: HandleID(in.Fh), + } + io = to + co = &to.commonOp + case *fuseshim.WriteRequest: to := &WriteFileOp{ bfReq: typed, From 5e97b58fcc439b6fc4b84651a8628e88ce17ff30 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:15:52 +1000 Subject: [PATCH 043/157] WriteFileOp --- fuseops/convert.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 9dec336..6d985bc 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -300,13 +300,23 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.WriteRequest: + case fusekernel.OpWrite: + in := (*fusekernel.WriteIn)(m.Data()) + size := fusekernel.WriteInSize(protocol) + if m.Len() < size { + goto corrupt + } + + buf := m.Bytes()[size:] + if len(buf) < int(in.Size) { + goto corrupt + } + to := &WriteFileOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - Handle: HandleID(typed.Handle), - Data: typed.Data, - Offset: typed.Offset, + Inode: InodeID(m.Header().Node), + Handle: HandleID(in.Fh), + Data: buf, + Offset: int64(in.Offset), } io = to co = &to.commonOp From d29e0a6f93c24611fb5fd21ee6ec0b9ca8842836 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:16:46 +1000 Subject: [PATCH 044/157] SyncFileOp --- fuseops/convert.go | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 6d985bc..92becec 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -321,22 +321,19 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.FsyncRequest: - // We don't currently support this for directories. - if typed.Dir { - to := &unknownOp{} - io = to - co = &to.commonOp - } else { - to := &SyncFileOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - Handle: HandleID(typed.Handle), - } - io = to - co = &to.commonOp + case fusekernel.OpFsync: + in := (*fusekernel.FsyncIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { + goto corrupt } + to := &SyncFileOp{ + Inode: InodeID(m.Header().Node), + Handle: HandleID(in.Fh), + } + io = to + co = &to.commonOp + case *fuseshim.FlushRequest: to := &FlushFileOp{ bfReq: typed, From b7a46f5fc65e5d47c8a878589df7805b5d906f6e Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:19:04 +1000 Subject: [PATCH 045/157] FlushFileOp --- fuseops/convert.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 92becec..6117fe3 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -334,11 +334,15 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.FlushRequest: + case fusekernel.OpFlush: + in := (*fusekernel.FlushIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { + goto corrupt + } + to := &FlushFileOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - Handle: HandleID(typed.Handle), + Inode: InodeID(m.Header().Node), + Handle: HandleID(in.Fh), } io = to co = &to.commonOp From 3d9ad48171f71684ef2928f7cef794cbfe10a23a Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:19:32 +1000 Subject: [PATCH 046/157] ReadSymlinkOp --- fuseops/convert.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 6117fe3..9b0888d 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -347,10 +347,9 @@ func Convert( io = to co = &to.commonOp - case *fuseshim.ReadlinkRequest: + case fusekernel.OpReadlink: to := &ReadSymlinkOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), + Inode: InodeID(m.Header().Node), } io = to co = &to.commonOp From 5cc86afafb074a954fee3e82dfec0425f6ee3aad Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:20:44 +1000 Subject: [PATCH 047/157] Fixed an error. --- fuseops/convert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 9b0888d..913164e 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -363,7 +363,7 @@ func Convert( co.init( opCtx, io, - r, + m, debugLogForOp, errorLogger, finished) From 75e419f39142d539c6a25d8863eb143edcd14a8d Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:30:43 +1000 Subject: [PATCH 048/157] OpenFileOp.kernelResponse --- fuseops/ops.go | 13 ++--- internal/fuseshim/buffer.go | 16 +++---- internal/fuseshim/fuse.go | 94 ++++++++++++++++++------------------- 3 files changed, 62 insertions(+), 61 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index e2a2684..6fd3501 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -18,7 +18,9 @@ import ( "fmt" "os" "time" + "unsafe" + "github.com/jacobsa/fuse/internal/fusekernel" "github.com/jacobsa/fuse/internal/fuseshim" "golang.org/x/net/context" ) @@ -622,7 +624,6 @@ func (o *ReleaseDirHandleOp) respond() { // (cf.https://github.com/osxfuse/osxfuse/issues/199). type OpenFileOp struct { commonOp - bfReq *fuseshim.OpenRequest // The ID of the inode to be opened. Inode InodeID @@ -637,12 +638,12 @@ type OpenFileOp struct { Handle HandleID } -func (o *OpenFileOp) respond() { - resp := fuseshim.OpenResponse{ - Handle: fuseshim.HandleID(o.Handle), - } +func (o *OpenFileOp) kernelResponse() (msg []byte) { + buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.OpenOut{})) + out := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) + out.Fh = uint64(o.Handle) - o.bfReq.Respond(&resp) + msg = buf return } diff --git a/internal/fuseshim/buffer.go b/internal/fuseshim/buffer.go index d57bb44..d2999bc 100644 --- a/internal/fuseshim/buffer.go +++ b/internal/fuseshim/buffer.go @@ -6,13 +6,13 @@ import ( "github.com/jacobsa/fuse/internal/fusekernel" ) -// buffer provides a mechanism for constructing a message from -// multiple segments. -type buffer []byte +// 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 { +func (w *Buffer) Alloc(size uintptr) unsafe.Pointer { s := int(size) if len(*w)+s > cap(*w) { old := *w @@ -25,15 +25,15 @@ func (w *buffer) alloc(size uintptr) unsafe.Pointer { } // reset clears out the contents of the buffer. -func (w *buffer) reset() { +func (w *Buffer) reset() { for i := range (*w)[:cap(*w)] { (*w)[i] = 0 } *w = (*w)[:0] } -func newBuffer(extra uintptr) buffer { +func NewBuffer(extra uintptr) (buf Buffer) { const hdrSize = unsafe.Sizeof(fusekernel.OutHeader{}) - buf := make(buffer, hdrSize, hdrSize+extra) - return buf + buf = make(Buffer, hdrSize, hdrSize+extra) + return } diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 69c12ac..900fcab 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -364,7 +364,7 @@ func (h *Header) RespondError(err error) { } // FUSE uses negative errors! // TODO: File bug report against OSXFUSE: positive error causes kernel panic. - buf := newBuffer(0) + buf := NewBuffer(0) hOut := (*fusekernel.OutHeader)(unsafe.Pointer(&buf[0])) hOut.Error = -int32(errno) h.respond(buf) @@ -1114,11 +1114,11 @@ func (c *Conn) sendInvalidate(msg []byte) error { // 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{})) + 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 := (*fusekernel.NotifyInvalInodeOut)(buf.Alloc(unsafe.Sizeof(fusekernel.NotifyInvalInodeOut{}))) out.Ino = uint64(nodeID) out.Off = off out.Len = size @@ -1141,11 +1141,11 @@ func (c *Conn) InvalidateEntry(parent NodeID, name string) error { // very unlikely, but we don't want to silently truncate return syscall.ENAMETOOLONG } - buf := newBuffer(unsafe.Sizeof(fusekernel.NotifyInvalEntryOut{}) + uintptr(len(name)) + 1) + 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 := (*fusekernel.NotifyInvalEntryOut)(buf.Alloc(unsafe.Sizeof(fusekernel.NotifyInvalEntryOut{}))) out.Parent = uint64(parent) out.Namelen = uint32(len(name)) buf = append(buf, name...) @@ -1186,8 +1186,8 @@ func (r *InitResponse) String() string { // 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{}))) + 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 @@ -1215,8 +1215,8 @@ func (r *StatfsRequest) String() string { // 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{}))) + 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, @@ -1261,7 +1261,7 @@ func (r *AccessRequest) String() string { // Respond replies to the request indicating that access is allowed. // To deny access, use RespondError. func (r *AccessRequest) Respond() { - buf := newBuffer(0) + buf := NewBuffer(0) r.respond(buf) } @@ -1353,8 +1353,8 @@ func (r *GetattrRequest) String() string { // 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)) + 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) @@ -1396,12 +1396,12 @@ func (r *GetxattrRequest) String() string { // 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{}))) + 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 := NewBuffer(uintptr(len(resp.Xattr))) buf = append(buf, resp.Xattr...) r.respond(buf) } @@ -1432,12 +1432,12 @@ func (r *ListxattrRequest) String() string { // 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{}))) + 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 := NewBuffer(uintptr(len(resp.Xattr))) buf = append(buf, resp.Xattr...) r.respond(buf) } @@ -1474,7 +1474,7 @@ func (r *RemovexattrRequest) String() string { // Respond replies to the request, indicating that the attribute was removed. func (r *RemovexattrRequest) Respond() { - buf := newBuffer(0) + buf := NewBuffer(0) r.respond(buf) } @@ -1519,7 +1519,7 @@ func (r *SetxattrRequest) String() string { // Respond replies to the request, indicating that the extended attribute was set. func (r *SetxattrRequest) Respond() { - buf := newBuffer(0) + buf := NewBuffer(0) r.respond(buf) } @@ -1538,8 +1538,8 @@ func (r *LookupRequest) String() string { // 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)) + 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) @@ -1577,8 +1577,8 @@ func (r *OpenRequest) String() string { // 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{}))) + 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) @@ -1612,9 +1612,9 @@ func (r *CreateRequest) String() string { // 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{})) + buf := NewBuffer(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) - e := (*fusekernel.EntryOut)(buf.alloc(eSize)) + e := (*fusekernel.EntryOut)(buf.Alloc(eSize)) e.Nodeid = uint64(resp.Node) e.Generation = resp.Generation e.EntryValid = uint64(resp.EntryValid / time.Second) @@ -1623,7 +1623,7 @@ func (r *CreateRequest) Respond(resp *CreateResponse) { 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 := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) o.Fh = uint64(resp.Handle) o.OpenFlags = uint32(resp.Flags) @@ -1658,8 +1658,8 @@ func (r *MkdirRequest) String() string { // 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)) + 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) @@ -1699,7 +1699,7 @@ func (r *ReadRequest) String() string { // Respond replies to the request with the given response. func (r *ReadRequest) Respond(resp *ReadResponse) { - buf := newBuffer(uintptr(len(resp.Data))) + buf := NewBuffer(uintptr(len(resp.Data))) buf = append(buf, resp.Data...) r.respond(buf) } @@ -1742,7 +1742,7 @@ func (r *ReleaseRequest) String() string { // Respond replies to the request, indicating that the handle has been released. func (r *ReleaseRequest) Respond() { - buf := newBuffer(0) + buf := NewBuffer(0) r.respond(buf) } @@ -1761,7 +1761,7 @@ func (r *DestroyRequest) String() string { // Respond replies to the request. func (r *DestroyRequest) Respond() { - buf := newBuffer(0) + buf := NewBuffer(0) r.respond(buf) } @@ -1901,8 +1901,8 @@ func (r *WriteRequest) MarshalJSON() ([]byte, error) { // 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{}))) + buf := NewBuffer(unsafe.Sizeof(fusekernel.WriteOut{})) + out := (*fusekernel.WriteOut)(buf.Alloc(unsafe.Sizeof(fusekernel.WriteOut{}))) out.Size = uint32(resp.Size) r.respond(buf) } @@ -1992,8 +1992,8 @@ func (r *SetattrRequest) String() string { // 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)) + 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) @@ -2027,7 +2027,7 @@ func (r *FlushRequest) String() string { // Respond replies to the request, indicating that the flush succeeded. func (r *FlushRequest) Respond() { - buf := newBuffer(0) + buf := NewBuffer(0) r.respond(buf) } @@ -2047,7 +2047,7 @@ func (r *RemoveRequest) String() string { // Respond replies to the request, indicating that the file was removed. func (r *RemoveRequest) Respond() { - buf := newBuffer(0) + buf := NewBuffer(0) r.respond(buf) } @@ -2066,8 +2066,8 @@ func (r *SymlinkRequest) String() string { // 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)) + 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) @@ -2095,7 +2095,7 @@ func (r *ReadlinkRequest) String() string { } func (r *ReadlinkRequest) Respond(target string) { - buf := newBuffer(uintptr(len(target))) + buf := NewBuffer(uintptr(len(target))) buf = append(buf, target...) r.respond(buf) } @@ -2115,8 +2115,8 @@ func (r *LinkRequest) String() string { func (r *LinkRequest) Respond(resp *LookupResponse) { size := fusekernel.EntryOutSize(r.Header.Conn.proto) - buf := newBuffer(size) - out := (*fusekernel.EntryOut)(buf.alloc(size)) + 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) @@ -2141,7 +2141,7 @@ func (r *RenameRequest) String() string { } func (r *RenameRequest) Respond() { - buf := newBuffer(0) + buf := NewBuffer(0) r.respond(buf) } @@ -2161,8 +2161,8 @@ func (r *MknodRequest) String() string { func (r *MknodRequest) Respond(resp *LookupResponse) { size := fusekernel.EntryOutSize(r.Header.Conn.proto) - buf := newBuffer(size) - out := (*fusekernel.EntryOut)(buf.alloc(size)) + 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) @@ -2188,7 +2188,7 @@ func (r *FsyncRequest) String() string { } func (r *FsyncRequest) Respond() { - buf := newBuffer(0) + buf := NewBuffer(0) r.respond(buf) } From 6ded0a543e9929fa444cc2cbfa1dd6ccde4bed41 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:33:40 +1000 Subject: [PATCH 049/157] Updated commonOp fields. --- fuseops/common_op.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index e6491d7..cd7ae0e 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -30,8 +30,9 @@ import ( type internalOp interface { Op - // Respond to the underlying fuseshim request, successfully. - respond() + // Create a response message for the kernel, with leading pading for a + // fusekernel.OutHeader struct. + kernelResponse() []byte } // A helper for embedding common behavior. @@ -42,8 +43,11 @@ type commonOp struct { // The op in which this struct is embedded. op internalOp - // The underlying fuseshim request for this op. - bazilReq fuseshim.Request + // The fuse unique ID of this request, as assigned by the kernel. + fuseID uint64 + + // A function that can be used to send a reply to the kernel. + sendReply func([]byte) error // A function that can be used to log debug information about the op. The // first argument is a call depth. From a2c287f90a3ddbad790fa37f227132c256e9edee Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:37:20 +1000 Subject: [PATCH 050/157] Updated commonOp fields again. --- fuseops/common_op.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index cd7ae0e..844f6ac 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -35,6 +35,10 @@ type internalOp interface { kernelResponse() []byte } +// A function that sends a reply message back to the kernel for the request +// with the given fuse unique ID. +type replyFunc func(uint64, []byte) error + // A helper for embedding common behavior. type commonOp struct { // The context exposed to the user. @@ -47,7 +51,7 @@ type commonOp struct { fuseID uint64 // A function that can be used to send a reply to the kernel. - sendReply func([]byte) error + sendReply replyFunc // A function that can be used to log debug information about the op. The // first argument is a call depth. @@ -59,10 +63,6 @@ type commonOp struct { // // May be nil. errorLogger *log.Logger - - // A function that is invoked with the error given to Respond, for use in - // closing off traces and reporting back to the connection. - finished func(error) } func (o *commonOp) ShortDesc() (desc string) { From 36e4dd833189af42ad5d1d529097bf38b346b3d1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 08:41:16 +1000 Subject: [PATCH 051/157] commonOp.ShortDesc --- fuseops/common_op.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index 844f6ac..e39ab98 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -66,7 +66,8 @@ type commonOp struct { } func (o *commonOp) ShortDesc() (desc string) { - opName := reflect.TypeOf(o.op).String() + v := reflect.ValueOf(o.op) + opName := v.Type().String() // Attempt to better handle the usual case: a string that looks like // "*fuseops.GetInodeAttributesOp". @@ -76,8 +77,10 @@ func (o *commonOp) ShortDesc() (desc string) { opName = opName[len(prefix) : len(opName)-len(suffix)] } - // Include the inode number to which the op applies. - desc = fmt.Sprintf("%s(inode=%v)", opName, o.bazilReq.Hdr().Node) + // Include the inode number to which the op applies, if possible. + if f := v.FieldByName("Inode"); f.IsValid() { + desc = fmt.Sprintf("%s(inode=%v)", opName, f.Interface()) + } return } From fc9297d3d124348027b2a3af4a88b83c8a79e354 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 09:43:02 +1000 Subject: [PATCH 052/157] commonOp.init --- fuseops/common_op.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index e39ab98..e8292d3 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -20,7 +20,6 @@ import ( "reflect" "strings" - "github.com/jacobsa/fuse/internal/fuseshim" "github.com/jacobsa/reqtrace" "golang.org/x/net/context" ) @@ -36,8 +35,9 @@ type internalOp interface { } // A function that sends a reply message back to the kernel for the request -// with the given fuse unique ID. -type replyFunc func(uint64, []byte) error +// with the given fuse unique ID. The error argument is for informational +// purposes only; the error to hand to the kernel is encoded in the message. +type replyFunc func(uint64, []byte, error) error // A helper for embedding common behavior. type commonOp struct { @@ -88,27 +88,28 @@ func (o *commonOp) ShortDesc() (desc string) { func (o *commonOp) init( ctx context.Context, op internalOp, - bazilReq fuseshim.Request, + fuseID uint64, + sendReply replyFunc, debugLog func(int, string, ...interface{}), - errorLogger *log.Logger, - finished func(error)) { + errorLogger *log.Logger) { // Initialize basic fields. o.ctx = ctx o.op = op - o.bazilReq = bazilReq + o.fuseID = fuseID + o.sendReply = sendReply o.debugLog = debugLog o.errorLogger = errorLogger - o.finished = finished // Set up a trace span for this op. var reportForTrace reqtrace.ReportFunc o.ctx, reportForTrace = reqtrace.StartSpan(o.ctx, o.op.ShortDesc()) // When the op is finished, report to both reqtrace and the connection. - prevFinish := o.finished - o.finished = func(err error) { - reportForTrace(err) - prevFinish(err) + prevSendReply := o.sendReply + o.sendReply = func(fuseID uint64, msg []byte, opErr error) (err error) { + reportForTrace(opErr) + err = prevSendReply(fuseID, msg, opErr) + return } } From e2849d09f3229a03c6bcd2a498e81df5d31368d4 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 09:52:37 +1000 Subject: [PATCH 053/157] commonOp.Respond --- fuseops/common_op.go | 60 ++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index e8292d3..c796d87 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -19,7 +19,10 @@ import ( "log" "reflect" "strings" + "unsafe" + "github.com/jacobsa/fuse/internal/fusekernel" + "github.com/jacobsa/fuse/internal/fuseshim" "github.com/jacobsa/reqtrace" "golang.org/x/net/context" ) @@ -127,30 +130,49 @@ func (o *commonOp) Logf(format string, v ...interface{}) { } func (o *commonOp) Respond(err error) { - // Report that the user is responding. - o.finished(err) - - // If successful, we should respond to fuseshim with the appropriate struct. + // If successful, we ask the op for an appopriate response to the kernel, and + // it is responsible for leaving room for the fusekernel.OutHeader struct. + // Otherwise, create our own. + var msg []byte if err == nil { - o.op.respond() - return + msg = o.op.kernelResponse() + } else { + msg = fuseshim.NewBuffer(0) } - // Log the error. - if o.debugLog != nil { - o.Logf( - "-> (%s) error: %v", - o.op.ShortDesc(), - err) + // Fill in the header. + h := (*fusekernel.OutHeader)(unsafe.Pointer(&msg[0])) + h.Unique = o.fuseID + h.Len = uint32(len(msg)) + if err != nil { + errno := fuseshim.EIO + if ferr, ok := err.(fuseshim.ErrorNumber); ok { + errno = ferr.Errno() + } + + h.Error = -int32(errno) } - if o.errorLogger != nil { - o.errorLogger.Printf( - "(%s) error: %v", - o.op.ShortDesc(), - err) + // Log the error, if any. + if err != nil { + if o.debugLog != nil { + o.Logf( + "-> (%s) error: %v", + o.op.ShortDesc(), + err) + } + + if o.errorLogger != nil { + o.errorLogger.Printf( + "(%s) error: %v", + o.op.ShortDesc(), + err) + } } - // Send a response to the kernel. - o.bazilReq.RespondError(err) + // Reply. + replyErr := o.sendReply(o.fuseID, msg, err) + if replyErr != nil && o.errorLogger != nil { + o.errorLogger.Printf("Error from sendReply: %v", replyErr) + } } From af893b822a3ab1ba3d1dbffddddcd792065f2812 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 09:53:28 +1000 Subject: [PATCH 054/157] Updated respond signatures. --- fuseops/ops.go | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 6fd3501..5a9607b 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -85,7 +85,7 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) { return } -func (o *LookUpInodeOp) respond() { +func (o *LookUpInodeOp) kernelResponse() (msg []byte) { resp := fuseshim.LookupResponse{} convertChildInodeEntry(&o.Entry, &resp) @@ -111,7 +111,7 @@ type GetInodeAttributesOp struct { AttributesExpiration time.Time } -func (o *GetInodeAttributesOp) respond() { +func (o *GetInodeAttributesOp) kernelResponse() (msg []byte) { resp := fuseshim.GetattrResponse{ Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), } @@ -144,7 +144,7 @@ type SetInodeAttributesOp struct { AttributesExpiration time.Time } -func (o *SetInodeAttributesOp) respond() { +func (o *SetInodeAttributesOp) kernelResponse() (msg []byte) { resp := fuseshim.SetattrResponse{ Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), } @@ -203,7 +203,7 @@ type ForgetInodeOp struct { N uint64 } -func (o *ForgetInodeOp) respond() { +func (o *ForgetInodeOp) kernelResponse() (msg []byte) { o.bfReq.Respond() return } @@ -246,7 +246,7 @@ func (o *MkDirOp) ShortDesc() (desc string) { return } -func (o *MkDirOp) respond() { +func (o *MkDirOp) kernelResponse() (msg []byte) { resp := fuseshim.MkdirResponse{} convertChildInodeEntry(&o.Entry, &resp.LookupResponse) @@ -297,7 +297,7 @@ func (o *CreateFileOp) ShortDesc() (desc string) { return } -func (o *CreateFileOp) respond() { +func (o *CreateFileOp) kernelResponse() (msg []byte) { resp := fuseshim.CreateResponse{ OpenResponse: fuseshim.OpenResponse{ Handle: fuseshim.HandleID(o.Handle), @@ -343,7 +343,7 @@ func (o *CreateSymlinkOp) ShortDesc() (desc string) { return } -func (o *CreateSymlinkOp) respond() { +func (o *CreateSymlinkOp) kernelResponse() (msg []byte) { resp := fuseshim.SymlinkResponse{} convertChildInodeEntry(&o.Entry, &resp.LookupResponse) @@ -404,7 +404,7 @@ type RenameOp struct { NewName string } -func (o *RenameOp) respond() { +func (o *RenameOp) kernelResponse() (msg []byte) { o.bfReq.Respond() return } @@ -426,7 +426,7 @@ type RmDirOp struct { Name string } -func (o *RmDirOp) respond() { +func (o *RmDirOp) kernelResponse() (msg []byte) { o.bfReq.Respond() return } @@ -447,7 +447,7 @@ type UnlinkOp struct { Name string } -func (o *UnlinkOp) respond() { +func (o *UnlinkOp) kernelResponse() (msg []byte) { o.bfReq.Respond() return } @@ -480,7 +480,7 @@ type OpenDirOp struct { Handle HandleID } -func (o *OpenDirOp) respond() { +func (o *OpenDirOp) kernelResponse() (msg []byte) { resp := fuseshim.OpenResponse{ Handle: fuseshim.HandleID(o.Handle), } @@ -580,7 +580,7 @@ type ReadDirOp struct { Data []byte } -func (o *ReadDirOp) respond() { +func (o *ReadDirOp) kernelResponse() (msg []byte) { resp := fuseshim.ReadResponse{ Data: o.Data, } @@ -607,7 +607,7 @@ type ReleaseDirHandleOp struct { Handle HandleID } -func (o *ReleaseDirHandleOp) respond() { +func (o *ReleaseDirHandleOp) kernelResponse() (msg []byte) { o.bfReq.Respond() return } @@ -677,7 +677,7 @@ type ReadFileOp struct { Data []byte } -func (o *ReadFileOp) respond() { +func (o *ReadFileOp) kernelResponse() (msg []byte) { resp := fuseshim.ReadResponse{ Data: o.Data, } @@ -757,7 +757,7 @@ type WriteFileOp struct { Data []byte } -func (o *WriteFileOp) respond() { +func (o *WriteFileOp) kernelResponse() (msg []byte) { resp := fuseshim.WriteResponse{ Size: len(o.Data), } @@ -791,7 +791,7 @@ type SyncFileOp struct { Handle HandleID } -func (o *SyncFileOp) respond() { +func (o *SyncFileOp) kernelResponse() (msg []byte) { o.bfReq.Respond() return } @@ -852,7 +852,7 @@ type FlushFileOp struct { Handle HandleID } -func (o *FlushFileOp) respond() { +func (o *FlushFileOp) kernelResponse() (msg []byte) { o.bfReq.Respond() return } @@ -875,7 +875,7 @@ type ReleaseFileHandleOp struct { Handle HandleID } -func (o *ReleaseFileHandleOp) respond() { +func (o *ReleaseFileHandleOp) kernelResponse() (msg []byte) { o.bfReq.Respond() return } @@ -891,7 +891,7 @@ func (o *unknownOp) ShortDesc() (desc string) { return } -func (o *unknownOp) respond() { +func (o *unknownOp) kernelResponse() (msg []byte) { panic(fmt.Sprintf("Should never get here for unknown op: %s", o.ShortDesc())) } @@ -911,7 +911,7 @@ type ReadSymlinkOp struct { Target string } -func (o *ReadSymlinkOp) respond() { +func (o *ReadSymlinkOp) kernelResponse() (msg []byte) { o.bfReq.Respond(o.Target) return } From a5b7862c5c17f1bb8d000d9a447529b002c1fc31 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 09:55:24 +1000 Subject: [PATCH 055/157] Fixed the Convert interface. --- fuseops/convert.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 913164e..9580ef8 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -28,9 +28,9 @@ import ( // This function is an implementation detail of the fuse package, and must not // be called by anyone else. // -// Convert the supplied fuse kernel message to an Op. finished will be called -// with the error supplied to o.Respond when the user invokes that method, -// before a response is sent to the kernel. o.Respond will destroy the message. +// Convert the supplied fuse kernel message to an Op. sendReply will be used to +// send the reply back to the kernel once the user calls o.Respond. That +// function is responsible for destroying the message. // // It is guaranteed that o != nil. If the op is unknown, a special unexported // type will be used. @@ -42,7 +42,7 @@ func Convert( protocol fusekernel.Protocol, debugLogForOp func(int, string, ...interface{}), errorLogger *log.Logger, - finished func(error)) (o Op) { + sendReply replyFunc) (o Op) { var co *commonOp var io internalOp From f717d878682b1aeb338415d1d24e2a737448d03d Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 09:58:42 +1000 Subject: [PATCH 056/157] Fixed some build errors. --- fuseops/convert.go | 11 +++++++---- fuseops/ops.go | 4 +++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 9580ef8..3c4ee92 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -355,7 +355,10 @@ func Convert( co = &to.commonOp default: - to := &unknownOp{} + to := &unknownOp{ + opCode: m.Hdr.Opcode, + inode: InodeID(m.Header().Node), + } io = to co = &to.commonOp } @@ -363,10 +366,10 @@ func Convert( co.init( opCtx, io, - m, + m.Hdr.Unique, + sendReply, debugLogForOp, - errorLogger, - finished) + errorLogger) o = io return diff --git a/fuseops/ops.go b/fuseops/ops.go index 5a9607b..6a93313 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -884,10 +884,12 @@ func (o *ReleaseFileHandleOp) kernelResponse() (msg []byte) { // non-nil error. type unknownOp struct { commonOp + opCode uint32 + inode InodeID } func (o *unknownOp) ShortDesc() (desc string) { - desc = fmt.Sprintf("%T(inode=%v)", o.bazilReq, o.bazilReq.Hdr().Node) + desc = fmt.Sprintf("(inode=%v)", o.opCode, o.inode) return } From 9082660f6dd875e6575ee16372d5c96f9b217823 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 09:59:18 +1000 Subject: [PATCH 057/157] Eliminated some unnecessary function calls. --- fuseops/convert.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 3c4ee92..2682e71 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -55,7 +55,7 @@ func Convert( } to := &LookUpInodeOp{ - Parent: InodeID(m.Header().Node), + Parent: InodeID(m.Hdr.Nodeid), Name: string(buf[:n-1]), } io = to @@ -63,7 +63,7 @@ func Convert( case fusekernel.OpGetattr: to := &GetInodeAttributesOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), } io = to co = &to.commonOp @@ -75,7 +75,7 @@ func Convert( } to := &SetInodeAttributesOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), } valid := fusekernel.SetattrValid(in.Valid) @@ -108,7 +108,7 @@ func Convert( } to := &ForgetInodeOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), N: in.Nlookup, } io = to @@ -128,7 +128,7 @@ func Convert( name = name[:i] to := &MkDirOp{ - Parent: InodeID(m.Header().Node), + Parent: InodeID(m.Hdr.Nodeid), Name: string(name), Mode: fuseshim.FileMode(in.Mode), } @@ -149,7 +149,7 @@ func Convert( name = name[:i] to := &CreateFileOp{ - Parent: InodeID(m.Header().Node), + Parent: InodeID(m.Hdr.Nodeid), Name: string(name), Mode: fuseshim.FileMode(in.Mode), } @@ -169,7 +169,7 @@ func Convert( newName, target := names[0:i], names[i+1:len(names)-1] to := &CreateSymlinkOp{ - Parent: InodeID(m.Header().Node), + Parent: InodeID(m.Hdr.Nodeid), Name: string(newName), Target: string(target), } @@ -196,7 +196,7 @@ func Convert( oldName, newName := names[:i], names[i+1:len(names)-1] to := &RenameOp{ - OldParent: InodeID(m.Header().Node), + OldParent: InodeID(m.Hdr.Nodeid), OldName: string(oldName), NewParent: InodeID(in.Newdir), NewName: string(newName), @@ -212,7 +212,7 @@ func Convert( } to := &UnlinkOp{ - Parent: InodeID(m.Header().Node), + Parent: InodeID(m.Hdr.Nodeid), Name: string(buf[:n-1]), } io = to @@ -226,7 +226,7 @@ func Convert( } to := &RmDirOp{ - Parent: InodeID(m.Header().Node), + Parent: InodeID(m.Hdr.Nodeid), Name: string(buf[:n-1]), } io = to @@ -234,14 +234,14 @@ func Convert( case fusekernel.OpOpen: to := &OpenFileOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), } io = to co = &to.commonOp case fusekernel.OpOpendir: to := &OpenDirOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), } io = to co = &to.commonOp @@ -253,7 +253,7 @@ func Convert( } to := &ReadFileOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), Handle: HandleID(in.Fh), Offset: int64(in.Offset), Size: int(in.Size), @@ -268,7 +268,7 @@ func Convert( } to := &ReadDirOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), Handle: HandleID(in.Fh), Offset: DirOffset(in.Offset), Size: int(in.Size), @@ -313,7 +313,7 @@ func Convert( } to := &WriteFileOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), Handle: HandleID(in.Fh), Data: buf, Offset: int64(in.Offset), @@ -328,7 +328,7 @@ func Convert( } to := &SyncFileOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), Handle: HandleID(in.Fh), } io = to @@ -341,7 +341,7 @@ func Convert( } to := &FlushFileOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), Handle: HandleID(in.Fh), } io = to @@ -349,7 +349,7 @@ func Convert( case fusekernel.OpReadlink: to := &ReadSymlinkOp{ - Inode: InodeID(m.Header().Node), + Inode: InodeID(m.Hdr.Nodeid), } io = to co = &to.commonOp @@ -357,7 +357,7 @@ func Convert( default: to := &unknownOp{ opCode: m.Hdr.Opcode, - inode: InodeID(m.Header().Node), + inode: InodeID(m.Hdr.Nodeid), } io = to co = &to.commonOp From 290ac455fd4f8e507077cee53fef438727822f5e Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:04:12 +1000 Subject: [PATCH 058/157] Fixed some build errors. --- fuseops/convert.go | 82 +++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 2682e71..fb7b733 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -16,6 +16,7 @@ package fuseops import ( "bytes" + "errors" "log" "time" "unsafe" @@ -29,20 +30,18 @@ import ( // be called by anyone else. // // Convert the supplied fuse kernel message to an Op. sendReply will be used to -// send the reply back to the kernel once the user calls o.Respond. That -// function is responsible for destroying the message. +// send the reply back to the kernel once the user calls o.Respond. If the op +// is unknown, a special unexported type will be used. // -// It is guaranteed that o != nil. If the op is unknown, a special unexported -// type will be used. -// -// The debug logging function and error logger may be nil. +// The debug logging function and error logger may be nil. The caller is +// responsible for arranging for the message to be destroyed. func Convert( opCtx context.Context, m *fuseshim.Message, protocol fusekernel.Protocol, debugLogForOp func(int, string, ...interface{}), errorLogger *log.Logger, - sendReply replyFunc) (o Op) { + sendReply replyFunc) (o Op, err error) { var co *commonOp var io internalOp @@ -51,7 +50,8 @@ func Convert( buf := m.Bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { - goto corrupt + err = errors.New("Corrupted OpLookup") + return } to := &LookUpInodeOp{ @@ -71,7 +71,8 @@ func Convert( case fusekernel.OpSetattr: in := (*fusekernel.SetattrIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - goto corrupt + err = errors.New("Corrupted OpSetattr") + return } to := &SetInodeAttributesOp{ @@ -104,7 +105,8 @@ func Convert( case fusekernel.OpForget: in := (*fusekernel.ForgetIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - goto corrupt + err = errors.New("Corrupted OpForget") + return } to := &ForgetInodeOp{ @@ -117,13 +119,15 @@ func Convert( case fusekernel.OpMkdir: size := fusekernel.MkdirInSize(protocol) if m.Len() < size { - goto corrupt + err = errors.New("Corrupted OpMkdir") + return } in := (*fusekernel.MkdirIn)(m.Data()) name := m.Bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { - goto corrupt + err = errors.New("Corrupted OpMkdir") + return } name = name[:i] @@ -138,13 +142,15 @@ func Convert( case fusekernel.OpCreate: size := fusekernel.CreateInSize(protocol) if m.Len() < size { - goto corrupt + err = errors.New("Corrupted OpCreate") + return } in := (*fusekernel.CreateIn)(m.Data()) name := m.Bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { - goto corrupt + err = errors.New("Corrupted OpCreate") + return } name = name[:i] @@ -160,11 +166,13 @@ func Convert( // m.Bytes() is "newName\0target\0" names := m.Bytes() if len(names) == 0 || names[len(names)-1] != 0 { - goto corrupt + err = errors.New("Corrupted OpSymlink") + return } i := bytes.IndexByte(names, '\x00') if i < 0 { - goto corrupt + err = errors.New("Corrupted OpSymlink") + return } newName, target := names[0:i], names[i+1:len(names)-1] @@ -179,19 +187,23 @@ func Convert( case fusekernel.OpRename: in := (*fusekernel.RenameIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - goto corrupt + err = errors.New("Corrupted OpRename") + return } names := m.Bytes()[unsafe.Sizeof(*in):] // names should be "old\x00new\x00" if len(names) < 4 { - goto corrupt + err = errors.New("Corrupted OpRename") + return } if names[len(names)-1] != '\x00' { - goto corrupt + err = errors.New("Corrupted OpRename") + return } i := bytes.IndexByte(names, '\x00') if i < 0 { - goto corrupt + err = errors.New("Corrupted OpRename") + return } oldName, newName := names[:i], names[i+1:len(names)-1] @@ -208,7 +220,8 @@ func Convert( buf := m.Bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { - goto corrupt + err = errors.New("Corrupted OpUnlink") + return } to := &UnlinkOp{ @@ -222,7 +235,8 @@ func Convert( buf := m.Bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { - goto corrupt + err = errors.New("Corrupted OpRmdir") + return } to := &RmDirOp{ @@ -249,7 +263,8 @@ func Convert( case fusekernel.OpRead: in := (*fusekernel.ReadIn)(m.Data()) if m.Len() < fusekernel.ReadInSize(protocol) { - goto corrupt + err = errors.New("Corrupted OpRead") + return } to := &ReadFileOp{ @@ -264,7 +279,8 @@ func Convert( case fusekernel.OpReaddir: in := (*fusekernel.ReadIn)(m.Data()) if m.Len() < fusekernel.ReadInSize(protocol) { - goto corrupt + err = errors.New("Corrupted OpReaddir") + return } to := &ReadDirOp{ @@ -279,7 +295,8 @@ func Convert( case fusekernel.OpRelease: in := (*fusekernel.ReleaseIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - goto corrupt + err = errors.New("Corrupted OpRelease") + return } to := &ReleaseFileHandleOp{ @@ -291,7 +308,8 @@ func Convert( case fusekernel.OpReleasedir: in := (*fusekernel.ReleaseIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - goto corrupt + err = errors.New("Corrupted OpReleasedir") + return } to := &ReleaseDirHandleOp{ @@ -304,12 +322,14 @@ func Convert( in := (*fusekernel.WriteIn)(m.Data()) size := fusekernel.WriteInSize(protocol) if m.Len() < size { - goto corrupt + err = errors.New("Corrupted OpWrite") + return } buf := m.Bytes()[size:] if len(buf) < int(in.Size) { - goto corrupt + err = errors.New("Corrupted OpWrite") + return } to := &WriteFileOp{ @@ -324,7 +344,8 @@ func Convert( case fusekernel.OpFsync: in := (*fusekernel.FsyncIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - goto corrupt + err = errors.New("Corrupted OpFsync") + return } to := &SyncFileOp{ @@ -337,7 +358,8 @@ func Convert( case fusekernel.OpFlush: in := (*fusekernel.FlushIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - goto corrupt + err = errors.New("Corrupted OpFlush") + return } to := &FlushFileOp{ From 72906b755ab14ec70227017177c680ce4c39a36d Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:19:21 +1000 Subject: [PATCH 059/157] Fixed much of Connection.ReadOp. --- connection.go | 119 ++++++++++++++++++++++---------------- internal/fuseshim/fuse.go | 2 +- 2 files changed, 69 insertions(+), 52 deletions(-) diff --git a/connection.go b/connection.go index 6c2f16f..7210dbc 100644 --- a/connection.go +++ b/connection.go @@ -15,6 +15,7 @@ package fuse import ( + "errors" "fmt" "log" "path" @@ -24,6 +25,7 @@ import ( "golang.org/x/net/context" "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/fuse/internal/fusekernel" "github.com/jacobsa/fuse/internal/fuseshim" ) @@ -41,11 +43,11 @@ type Connection struct { mu sync.Mutex - // A map from fuseshim request ID (*not* the op ID for logging used above) to - // a function that cancel's its associated context. + // A map from fuse "unique" request ID (*not* the op ID for logging used + // above) to a function that cancel's its associated context. // // GUARDED_BY(mu) - cancelFuncs map[fuseshim.RequestID]func() + cancelFuncs map[uint64]func() } // Responsibility for closing the wrapped connection is transferred to the @@ -62,7 +64,7 @@ func newConnection( errorLogger: errorLogger, wrapped: wrapped, parentCtx: parentCtx, - cancelFuncs: make(map[fuseshim.RequestID]func()), + cancelFuncs: make(map[uint64]func()), } return @@ -104,28 +106,27 @@ func (c *Connection) debugLog( // LOCKS_EXCLUDED(c.mu) func (c *Connection) recordCancelFunc( - reqID fuseshim.RequestID, + fuseID uint64, f func()) { c.mu.Lock() defer c.mu.Unlock() - if _, ok := c.cancelFuncs[reqID]; ok { - panic(fmt.Sprintf("Already have cancel func for request %v", reqID)) + if _, ok := c.cancelFuncs[fuseID]; ok { + panic(fmt.Sprintf("Already have cancel func for request %v", fuseID)) } - c.cancelFuncs[reqID] = f + c.cancelFuncs[fuseID] = f } // Set up state for an op that is about to be returned to the user, given its -// underlying fuseshim request. +// underlying fuse opcode and request ID. // // Return a context that should be used for the op. // // LOCKS_EXCLUDED(c.mu) func (c *Connection) beginOp( - bfReq fuseshim.Request) (ctx context.Context) { - reqID := bfReq.Hdr().ID - + opCode uint32, + fuseID uint64) (ctx context.Context) { // Start with the parent context. ctx = c.parentCtx @@ -137,46 +138,46 @@ func (c *Connection) beginOp( // should not record any state keyed on their ID. // // Cf. https://github.com/osxfuse/osxfuse/issues/208 - if _, ok := bfReq.(*fuseshim.ForgetRequest); !ok { + if opCode != fusekernel.OpForget { var cancel func() ctx, cancel = context.WithCancel(ctx) - c.recordCancelFunc(reqID, cancel) + c.recordCancelFunc(fuseID, cancel) } return } // Clean up all state associated with an op to which the user has responded, -// given its underlying fuseshim request. This must be called before a response -// is sent to the kernel, to avoid a race where the request's ID might be -// reused by osxfuse. +// given its underlying fuse opcode and request ID. This must be called before +// a response is sent to the kernel, to avoid a race where the request's ID +// might be reused by osxfuse. // // LOCKS_EXCLUDED(c.mu) -func (c *Connection) finishOp(bfReq fuseshim.Request) { +func (c *Connection) finishOp( + opCode uint32, + fuseID uint64) { c.mu.Lock() defer c.mu.Unlock() - reqID := bfReq.Hdr().ID - // Even though the op is finished, context.WithCancel requires us to arrange // for the cancellation function to be invoked. We also must remove it from // our map. // // Special case: we don't do this for Forget requests. See the note in // beginOp above. - if _, ok := bfReq.(*fuseshim.ForgetRequest); !ok { - cancel, ok := c.cancelFuncs[reqID] + if opCode != fusekernel.OpForget { + cancel, ok := c.cancelFuncs[fuseID] if !ok { - panic(fmt.Sprintf("Unknown request ID in finishOp: %v", reqID)) + panic(fmt.Sprintf("Unknown request ID in finishOp: %v", fuseID)) } cancel() - delete(c.cancelFuncs, reqID) + delete(c.cancelFuncs, fuseID) } } // LOCKS_EXCLUDED(c.mu) -func (c *Connection) handleInterrupt(req *fuseshim.InterruptRequest) { +func (c *Connection) handleInterrupt(fuseID uint64) { c.mu.Lock() defer c.mu.Unlock() @@ -194,7 +195,7 @@ func (c *Connection) handleInterrupt(req *fuseshim.InterruptRequest) { // // Cf. https://github.com/osxfuse/osxfuse/issues/208 // Cf. http://comments.gmane.org/gmane.comp.file-systems.fuse.devel/14675 - cancel, ok := c.cancelFuncs[req.IntrID] + cancel, ok := c.cancelFuncs[fuseID] if !ok { return } @@ -212,20 +213,55 @@ func (c *Connection) handleInterrupt(req *fuseshim.InterruptRequest) { func (c *Connection) ReadOp() (op fuseops.Op, err error) { // Keep going until we find a request we know how to convert. for { - // Read a fuseshim request. - var bfReq fuseshim.Request - bfReq, err = c.wrapped.ReadRequest() - + // Read the next message from the fuseshim connection. + var m *fuseshim.Message + m, err = c.wrapped.ReadMessage() if err != nil { return } - // Choose an ID for this operation. + // Choose an ID for this operation for the purposes of logging. opID := c.nextOpID c.nextOpID++ + // Set up op dependencies. + opCtx := c.beginOp(m.Hdr.Opcode, m.Hdr.Unique) + + var debugLogForOp func(int, string, ...interface{}) + if c.debugLogger != nil { + debugLogForOp = func(calldepth int, format string, v ...interface{}) { + c.debugLog(opID, calldepth+1, format, v...) + } + } + + sendReply := func( + fuseID uint64, + msg []byte, + opErr error) (err error) { + // TODO(jacobsa): Turn this into a method and maybe kill the fuseID + // parameter. + // + // TODO(jacobsa): Don't forget to destroy the message. + err = errors.New("TODO") + return + } + + // Convert the message to an Op. + op, err = fuseops.Convert( + opCtx, + m, + c.wrapped.Protocol(), + debugLogForOp, + c.errorLogger, + sendReply) + + if err != nil { + err = fmt.Errorf("fuseops.Convert: %v", err) + return + } + // Log the receipt of the operation. - c.debugLog(opID, 1, "<- %v", bfReq) + c.debugLog(opID, 1, "<- %v", op) // Special case: responding to statfs is required to make mounting work on // OS X. We don't currently expose the capability for the file system to @@ -242,25 +278,6 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { continue } - // Set up op dependencies. - opCtx := c.beginOp(bfReq) - - var debugLogForOp func(int, string, ...interface{}) - if c.debugLogger != nil { - debugLogForOp = func(calldepth int, format string, v ...interface{}) { - c.debugLog(opID, calldepth+1, format, v...) - } - } - - finished := func(err error) { c.finishOp(bfReq) } - - op = fuseops.Convert( - opCtx, - bfReq, - debugLogForOp, - c.errorLogger, - finished) - return } } diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 900fcab..666615d 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -532,7 +532,7 @@ func (c *Conn) Protocol() fusekernel.Protocol { // 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 returned by calling m.Destroy. +// The message must later be disposed of by calling m.Destroy. func (c *Conn) ReadMessage() (m *Message, err error) { m = getMessage(c) loop: From 296d9cbc62e1b9f2ef4f0fb3655683e60a742b56 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:28:47 +1000 Subject: [PATCH 060/157] Declared internal ops. --- fuseops/ops.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/fuseops/ops.go b/fuseops/ops.go index 6a93313..075a391 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -917,3 +917,32 @@ func (o *ReadSymlinkOp) kernelResponse() (msg []byte) { o.bfReq.Respond(o.Target) return } + +//////////////////////////////////////////////////////////////////////// +// Internal +//////////////////////////////////////////////////////////////////////// + +// TODO(jacobsa): Untangle the way ops work and move these to an internal +// package, along with Convert. I think all of the behavior wants to be on +// Connection. Ops have only String methods. Connection.ReadRequest returns an +// interace{} and a context. If we must restore debug logging later, we can +// stuff an op ID in that context and add a Connection.Logf method. + +// Do not use this struct directly. See the TODO in fuseops/ops.go. +type InternalStatFSOp struct { + commonOp +} + +func (o *InternalStatFSOp) kernelResponse() (msg []byte) { + panic("TODO") +} + +// Do not use this struct directly. See the TODO in fuseops/ops.go. +type InternalInterruptOp struct { + commonOp + FuseID uint64 +} + +func (o *InternalInterruptOp) kernelResponse() (msg []byte) { + panic("Shouldn't get here.") +} From b5f954ccfb2dfb0cc011499f2dc903038d9ca325 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:30:46 +1000 Subject: [PATCH 061/157] Added Convert support for the internal ops. --- fuseops/convert.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fuseops/convert.go b/fuseops/convert.go index fb7b733..e9912d7 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -376,6 +376,24 @@ func Convert( io = to co = &to.commonOp + case fusekernel.OpStatfs: + to := &InternalStatFSOp{} + io = to + co = &to.commonOp + + case fusekernel.OpInterrupt: + in := (*fusekernel.InterruptIn)(m.Data()) + if m.Len() < unsafe.Sizeof(*in) { + err = errors.New("Corrupted OpInterrupt") + return + } + + to := &InternalInterruptOp{ + FuseID: in.Unique, + } + io = to + co = &to.commonOp + default: to := &unknownOp{ opCode: m.Hdr.Opcode, From 35021f0bfe66701134ac20850c5ebc865cee30d3 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:31:07 +1000 Subject: [PATCH 062/157] Improved error messages. --- fuseops/convert.go | 48 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index e9912d7..19f983e 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -50,7 +50,7 @@ func Convert( buf := m.Bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { - err = errors.New("Corrupted OpLookup") + err = errors.New("Corrupt OpLookup") return } @@ -71,7 +71,7 @@ func Convert( case fusekernel.OpSetattr: in := (*fusekernel.SetattrIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - err = errors.New("Corrupted OpSetattr") + err = errors.New("Corrupt OpSetattr") return } @@ -105,7 +105,7 @@ func Convert( case fusekernel.OpForget: in := (*fusekernel.ForgetIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - err = errors.New("Corrupted OpForget") + err = errors.New("Corrupt OpForget") return } @@ -119,14 +119,14 @@ func Convert( case fusekernel.OpMkdir: size := fusekernel.MkdirInSize(protocol) if m.Len() < size { - err = errors.New("Corrupted OpMkdir") + err = errors.New("Corrupt OpMkdir") return } in := (*fusekernel.MkdirIn)(m.Data()) name := m.Bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { - err = errors.New("Corrupted OpMkdir") + err = errors.New("Corrupt OpMkdir") return } name = name[:i] @@ -142,14 +142,14 @@ func Convert( case fusekernel.OpCreate: size := fusekernel.CreateInSize(protocol) if m.Len() < size { - err = errors.New("Corrupted OpCreate") + err = errors.New("Corrupt OpCreate") return } in := (*fusekernel.CreateIn)(m.Data()) name := m.Bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { - err = errors.New("Corrupted OpCreate") + err = errors.New("Corrupt OpCreate") return } name = name[:i] @@ -166,12 +166,12 @@ func Convert( // m.Bytes() is "newName\0target\0" names := m.Bytes() if len(names) == 0 || names[len(names)-1] != 0 { - err = errors.New("Corrupted OpSymlink") + err = errors.New("Corrupt OpSymlink") return } i := bytes.IndexByte(names, '\x00') if i < 0 { - err = errors.New("Corrupted OpSymlink") + err = errors.New("Corrupt OpSymlink") return } newName, target := names[0:i], names[i+1:len(names)-1] @@ -187,22 +187,22 @@ func Convert( case fusekernel.OpRename: in := (*fusekernel.RenameIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - err = errors.New("Corrupted OpRename") + err = errors.New("Corrupt OpRename") return } names := m.Bytes()[unsafe.Sizeof(*in):] // names should be "old\x00new\x00" if len(names) < 4 { - err = errors.New("Corrupted OpRename") + err = errors.New("Corrupt OpRename") return } if names[len(names)-1] != '\x00' { - err = errors.New("Corrupted OpRename") + err = errors.New("Corrupt OpRename") return } i := bytes.IndexByte(names, '\x00') if i < 0 { - err = errors.New("Corrupted OpRename") + err = errors.New("Corrupt OpRename") return } oldName, newName := names[:i], names[i+1:len(names)-1] @@ -220,7 +220,7 @@ func Convert( buf := m.Bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { - err = errors.New("Corrupted OpUnlink") + err = errors.New("Corrupt OpUnlink") return } @@ -235,7 +235,7 @@ func Convert( buf := m.Bytes() n := len(buf) if n == 0 || buf[n-1] != '\x00' { - err = errors.New("Corrupted OpRmdir") + err = errors.New("Corrupt OpRmdir") return } @@ -263,7 +263,7 @@ func Convert( case fusekernel.OpRead: in := (*fusekernel.ReadIn)(m.Data()) if m.Len() < fusekernel.ReadInSize(protocol) { - err = errors.New("Corrupted OpRead") + err = errors.New("Corrupt OpRead") return } @@ -279,7 +279,7 @@ func Convert( case fusekernel.OpReaddir: in := (*fusekernel.ReadIn)(m.Data()) if m.Len() < fusekernel.ReadInSize(protocol) { - err = errors.New("Corrupted OpReaddir") + err = errors.New("Corrupt OpReaddir") return } @@ -295,7 +295,7 @@ func Convert( case fusekernel.OpRelease: in := (*fusekernel.ReleaseIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - err = errors.New("Corrupted OpRelease") + err = errors.New("Corrupt OpRelease") return } @@ -308,7 +308,7 @@ func Convert( case fusekernel.OpReleasedir: in := (*fusekernel.ReleaseIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - err = errors.New("Corrupted OpReleasedir") + err = errors.New("Corrupt OpReleasedir") return } @@ -322,13 +322,13 @@ func Convert( in := (*fusekernel.WriteIn)(m.Data()) size := fusekernel.WriteInSize(protocol) if m.Len() < size { - err = errors.New("Corrupted OpWrite") + err = errors.New("Corrupt OpWrite") return } buf := m.Bytes()[size:] if len(buf) < int(in.Size) { - err = errors.New("Corrupted OpWrite") + err = errors.New("Corrupt OpWrite") return } @@ -344,7 +344,7 @@ func Convert( case fusekernel.OpFsync: in := (*fusekernel.FsyncIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - err = errors.New("Corrupted OpFsync") + err = errors.New("Corrupt OpFsync") return } @@ -358,7 +358,7 @@ func Convert( case fusekernel.OpFlush: in := (*fusekernel.FlushIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - err = errors.New("Corrupted OpFlush") + err = errors.New("Corrupt OpFlush") return } @@ -384,7 +384,7 @@ func Convert( case fusekernel.OpInterrupt: in := (*fusekernel.InterruptIn)(m.Data()) if m.Len() < unsafe.Sizeof(*in) { - err = errors.New("Corrupted OpInterrupt") + err = errors.New("Corrupt OpInterrupt") return } From b8d3dfd5acd87491e7e6dfb686555d47774f80dc Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:32:36 +1000 Subject: [PATCH 063/157] Fixed statfs and interrupt. --- connection.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/connection.go b/connection.go index 7210dbc..5bdc337 100644 --- a/connection.go +++ b/connection.go @@ -266,15 +266,14 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { // Special case: responding to statfs is required to make mounting work on // OS X. We don't currently expose the capability for the file system to // intercept this. - if statfsReq, ok := bfReq.(*fuseshim.StatfsRequest); ok { - c.debugLog(opID, 1, "-> (Statfs) OK") - statfsReq.Respond(&fuseshim.StatfsResponse{}) + if _, ok := op.(*fuseops.InternalStatFSOp); ok { + op.Respond(nil) continue } // Special case: handle interrupt requests. - if interruptReq, ok := bfReq.(*fuseshim.InterruptRequest); ok { - c.handleInterrupt(interruptReq) + if interruptOp, ok := op.(*fuseops.InternalInterruptOp); ok { + c.handleInterrupt(interruptOp.FuseID) continue } From b0acac98d54c9e958ccf614756118abef2be5889 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:33:39 +1000 Subject: [PATCH 064/157] Fixed a panic. --- fuseops/common_op.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index c796d87..1339b02 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -81,7 +81,7 @@ func (o *commonOp) ShortDesc() (desc string) { } // Include the inode number to which the op applies, if possible. - if f := v.FieldByName("Inode"); f.IsValid() { + if f := v.Elem().FieldByName("Inode"); f.IsValid() { desc = fmt.Sprintf("%s(inode=%v)", opName, f.Interface()) } From 707d5245b0b52f4436f2a783956b4824cfd88d18 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:34:45 +1000 Subject: [PATCH 065/157] Killed bfReq fields. --- fuseops/ops.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 075a391..b818295 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -57,7 +57,6 @@ type Op interface { // when resolving user paths to dentry structs, which are then cached. type LookUpInodeOp struct { commonOp - bfReq *fuseshim.LookupRequest // The ID of the directory inode to which the child belongs. Parent InodeID @@ -99,7 +98,6 @@ func (o *LookUpInodeOp) kernelResponse() (msg []byte) { // field of ChildInodeEntry, etc. type GetInodeAttributesOp struct { commonOp - bfReq *fuseshim.GetattrRequest // The inode of interest. Inode InodeID @@ -126,7 +124,6 @@ func (o *GetInodeAttributesOp) kernelResponse() (msg []byte) { // cases like ftrunctate(2). type SetInodeAttributesOp struct { commonOp - bfReq *fuseshim.SetattrRequest // The inode of interest. Inode InodeID @@ -194,7 +191,6 @@ func (o *SetInodeAttributesOp) kernelResponse() (msg []byte) { // implicitly decrementing all lookup counts to zero. type ForgetInodeOp struct { commonOp - bfReq *fuseshim.ForgetRequest // The inode whose reference count should be decremented. Inode InodeID @@ -225,7 +221,6 @@ func (o *ForgetInodeOp) kernelResponse() (msg []byte) { // Therefore the file system should return EEXIST if the name already exists. type MkDirOp struct { commonOp - bfReq *fuseshim.MkdirRequest // The ID of parent directory inode within which to create the child. Parent InodeID @@ -266,7 +261,6 @@ func (o *MkDirOp) kernelResponse() (msg []byte) { // Therefore the file system should return EEXIST if the name already exists. type CreateFileOp struct { commonOp - bfReq *fuseshim.CreateRequest // The ID of parent directory inode within which to create the child file. Parent InodeID @@ -314,7 +308,6 @@ func (o *CreateFileOp) kernelResponse() (msg []byte) { // return EEXIST (cf. the notes on CreateFileOp and MkDirOp). type CreateSymlinkOp struct { commonOp - bfReq *fuseshim.SymlinkRequest // The ID of parent directory inode within which to create the child symlink. Parent InodeID @@ -391,7 +384,6 @@ func (o *CreateSymlinkOp) kernelResponse() (msg []byte) { // type RenameOp struct { commonOp - bfReq *fuseshim.RenameRequest // The old parent directory, and the name of the entry within it to be // relocated. @@ -418,7 +410,6 @@ func (o *RenameOp) kernelResponse() (msg []byte) { // Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf) type RmDirOp struct { commonOp - bfReq *fuseshim.RemoveRequest // The ID of parent directory inode, and the name of the directory being // removed within it. @@ -439,7 +430,6 @@ func (o *RmDirOp) kernelResponse() (msg []byte) { // Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C) type UnlinkOp struct { commonOp - bfReq *fuseshim.RemoveRequest // The ID of parent directory inode, and the name of the entry being removed // within it. @@ -464,7 +454,6 @@ func (o *UnlinkOp) kernelResponse() (msg []byte) { // https://github.com/osxfuse/osxfuse/issues/199). type OpenDirOp struct { commonOp - bfReq *fuseshim.OpenRequest // The ID of the inode to be opened. Inode InodeID @@ -492,7 +481,6 @@ func (o *OpenDirOp) kernelResponse() (msg []byte) { // Read entries from a directory previously opened with OpenDir. type ReadDirOp struct { commonOp - bfReq *fuseshim.ReadRequest // The directory inode that we are reading, and the handle previously // returned by OpenDir when opening that inode. @@ -599,7 +587,6 @@ func (o *ReadDirOp) kernelResponse() (msg []byte) { // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do). type ReleaseDirHandleOp struct { commonOp - bfReq *fuseshim.ReleaseRequest // The handle ID to be released. The kernel guarantees that this ID will not // be used in further calls to the file system (unless it is reissued by the @@ -654,7 +641,6 @@ func (o *OpenFileOp) kernelResponse() (msg []byte) { // more. type ReadFileOp struct { commonOp - bfReq *fuseshim.ReadRequest // The file inode that we are reading, and the handle previously returned by // CreateFile or OpenFile when opening that inode. @@ -719,7 +705,6 @@ func (o *ReadFileOp) kernelResponse() (msg []byte) { // concurrent requests".) type WriteFileOp struct { commonOp - bfReq *fuseshim.WriteRequest // The file inode that we are modifying, and the handle previously returned // by CreateFile or OpenFile when opening that inode. @@ -784,7 +769,6 @@ func (o *WriteFileOp) kernelResponse() (msg []byte) { // file (but which is not used in "real" file systems). type SyncFileOp struct { commonOp - bfReq *fuseshim.FsyncRequest // The file and handle being sync'd. Inode InodeID @@ -845,7 +829,6 @@ func (o *SyncFileOp) kernelResponse() (msg []byte) { // return any errors that occur. type FlushFileOp struct { commonOp - bfReq *fuseshim.FlushRequest // The file and handle being flushed. Inode InodeID @@ -867,7 +850,6 @@ func (o *FlushFileOp) kernelResponse() (msg []byte) { // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do). type ReleaseFileHandleOp struct { commonOp - bfReq *fuseshim.ReleaseRequest // The handle ID to be released. The kernel guarantees that this ID will not // be used in further calls to the file system (unless it is reissued by the @@ -904,7 +886,6 @@ func (o *unknownOp) kernelResponse() (msg []byte) { // Read the target of a symlink inode. type ReadSymlinkOp struct { commonOp - bfReq *fuseshim.ReadlinkRequest // The symlink inode that we are reading. Inode InodeID From 738a1dad7d065a27b96c7eb1e1d9d6f45ba90e46 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:35:51 +1000 Subject: [PATCH 066/157] InternalStatFSOp.kernelResponse --- fuseops/ops.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index b818295..72133d3 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -915,7 +915,9 @@ type InternalStatFSOp struct { } func (o *InternalStatFSOp) kernelResponse() (msg []byte) { - panic("TODO") + buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.StatfsOut{})) + buf.Alloc(unsafe.Sizeof(fusekernel.StatfsOut{})) + return } // Do not use this struct directly. See the TODO in fuseops/ops.go. From 9206e9af944651922c4916be2f7ab1c7dc3831c5 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:36:50 +1000 Subject: [PATCH 067/157] Refactored OpenFileOp.kernelResponse. --- fuseops/ops.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 72133d3..5a7385e 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -626,9 +626,11 @@ type OpenFileOp struct { } func (o *OpenFileOp) kernelResponse() (msg []byte) { - buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.OpenOut{})) - out := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) - out.Fh = uint64(o.Handle) + type kernelOut fusekernel.OpenOut + + buf := fuseshim.NewBuffer(unsafe.Sizeof(kernelOut{})) + out := (*kernelOut)(buf.Alloc(unsafe.Sizeof(kernelOut{}))) + kernelOut.Fh = uint64(o.Handle) msg = buf return From 32dc0dc4daf5c3e68b57afd65741e209bf94c034 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:41:29 +1000 Subject: [PATCH 068/157] Started on LookUpInodeOp.kernelResponse. --- fuseops/convert.go | 7 ++++--- fuseops/ops.go | 16 +++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 19f983e..191b953 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -455,9 +455,10 @@ func convertExpirationTime(t time.Time) (d time.Duration) { func convertChildInodeEntry( in *ChildInodeEntry, - out *fuseshim.LookupResponse) { - out.Node = fuseshim.NodeID(in.Child) - out.Generation = uint64(in.Generation) + out *fusekernel.EntryOut) { + out.Nodeid = in.Child + out.Generation = in.Generation + convertAttributes(in.Attributes, &out.Attr) out.Attr = convertAttributes(in.Child, in.Attributes, in.AttributesExpiration) out.EntryValid = convertExpirationTime(in.EntryExpiration) } diff --git a/fuseops/ops.go b/fuseops/ops.go index 5a7385e..13feabc 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -85,11 +85,17 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) { } func (o *LookUpInodeOp) kernelResponse() (msg []byte) { - resp := fuseshim.LookupResponse{} - convertChildInodeEntry(&o.Entry, &resp) - - o.bfReq.Respond(&resp) - return + size := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) + 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) } // Refresh the attributes for an inode whose ID was previously returned in a From e226901fc684bebc208a49c59cec20a679276f1d Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:54:09 +1000 Subject: [PATCH 069/157] Fixed a bunch of build errors. --- fuseops/convert.go | 83 ++++++++++++++++++++++++++++------------------ fuseops/ops.go | 2 +- 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 191b953..70b43dc 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -18,6 +18,8 @@ import ( "bytes" "errors" "log" + "os" + "syscall" "time" "unsafe" @@ -415,39 +417,55 @@ func Convert( return } +func convertTime(t time.Time) (secs uint64, nsec uint32) + func convertAttributes( - inode InodeID, - attr InodeAttributes, - expiration time.Time) fuseshim.Attr { - return fuseshim.Attr{ - Inode: uint64(inode), - Size: attr.Size, - Mode: attr.Mode, - Nlink: uint32(attr.Nlink), - Atime: attr.Atime, - Mtime: attr.Mtime, - Ctime: attr.Ctime, - Crtime: attr.Crtime, - Uid: attr.Uid, - Gid: attr.Gid, - Valid: convertExpirationTime(expiration), + inodeID InodeID, + in *InodeAttributes, + out *fusekernel.Attr) { + out.Ino = uint64(inodeID) + out.Size = in.Size + out.Atime, out.AtimeNsec = convertTime(in.Atime) + out.Mtime, out.MtimeNsec = convertTime(in.Mtime) + out.Ctime, out.CtimeNsec = convertTime(in.Ctime) + out.SetCrtime(convertTime(in.Crtime)) + out.Nlink = uint32(in.Nlink) // TODO(jacobsa): Make the public field uint32? + out.Uid = in.Uid + out.Gid = in.Gid + + // Set the mode. + out.Mode = uint32(in.Mode) & 077 + switch { + default: + out.Mode |= syscall.S_IFREG + case in.Mode&os.ModeDir != 0: + out.Mode |= syscall.S_IFDIR + case in.Mode&os.ModeDevice != 0: + if in.Mode&os.ModeCharDevice != 0 { + out.Mode |= syscall.S_IFCHR + } else { + out.Mode |= syscall.S_IFBLK + } + case in.Mode&os.ModeNamedPipe != 0: + out.Mode |= syscall.S_IFIFO + case in.Mode&os.ModeSymlink != 0: + out.Mode |= syscall.S_IFLNK + case in.Mode&os.ModeSocket != 0: + out.Mode |= syscall.S_IFSOCK } } // Convert an absolute cache expiration time to a relative time from now for -// consumption by fuse. -func convertExpirationTime(t time.Time) (d time.Duration) { +// consumption by the fuse kernel module. +func convertExpirationTime(t time.Time) (secs uint64, nsecs uint32) { // Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit - // counts of nanoseconds (cf. http://goo.gl/EJupJV). The bazil.org/fuse - // package converts time.Duration values to this form in a straightforward - // way (cf. http://goo.gl/FJhV8j). - // - // So negative durations are right out. There is no need to cap the positive - // magnitude, because 2^64 seconds is well longer than the 2^63 ns range of - // time.Duration. - d = t.Sub(time.Now()) - if d < 0 { - d = 0 + // counts of nanoseconds (cf. http://goo.gl/EJupJV). So negative durations + // are right out. There is no need to cap the positive magnitude, because + // 2^64 seconds is well longer than the 2^63 ns range of time.Duration. + d := t.Sub(time.Now()) + if d > 0 { + secs = uint64(d / time.Second) + nsecs = uint32((d % time.Second) / time.Nanosecond) } return @@ -456,9 +474,10 @@ func convertExpirationTime(t time.Time) (d time.Duration) { func convertChildInodeEntry( in *ChildInodeEntry, out *fusekernel.EntryOut) { - out.Nodeid = in.Child - out.Generation = in.Generation - convertAttributes(in.Attributes, &out.Attr) - out.Attr = convertAttributes(in.Child, in.Attributes, in.AttributesExpiration) - out.EntryValid = convertExpirationTime(in.EntryExpiration) + out.Nodeid = uint64(in.Child) + out.Generation = uint64(in.Generation) + out.EntryValid, out.EntryValidNsec = convertExpirationTime(in.EntryExpiration) + out.AttrValid, out.AttrValidNsec = convertExpirationTime(in.AttributesExpiration) + + convertAttributes(in.Child, &in.Attributes, &out.Attr) } diff --git a/fuseops/ops.go b/fuseops/ops.go index 13feabc..e40e726 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -88,7 +88,7 @@ func (o *LookUpInodeOp) kernelResponse() (msg []byte) { size := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) buf := NewBuffer(size) out := (*fusekernel.EntryOut)(buf.Alloc(size)) - out.Nodeid = uint64(resp.Node) + out.Nodeid = uint64(o.Entry.InodeID) out.Generation = resp.Generation out.EntryValid = uint64(resp.EntryValid / time.Second) out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) From 99efba22006be914e5eadd8dcc003dd2a45d18f2 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:55:03 +1000 Subject: [PATCH 070/157] LookUpInodeOp.kernelResponse --- fuseops/ops.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index e40e726..9cb8f29 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -86,16 +86,12 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) { func (o *LookUpInodeOp) kernelResponse() (msg []byte) { size := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) - buf := NewBuffer(size) + buf := fuseshim.NewBuffer(size) out := (*fusekernel.EntryOut)(buf.Alloc(size)) - out.Nodeid = uint64(o.Entry.InodeID) - 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) + convertChildInodeEntry(&o.Entry, out) + + msg = buf + return } // Refresh the attributes for an inode whose ID was previously returned in a From c59b546a87f5e91b55cb838e41cadd869d5baf00 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:57:34 +1000 Subject: [PATCH 071/157] GetInodeAttributesOp.kernelResponse --- fuseops/ops.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 9cb8f29..8ac2e4c 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -112,11 +112,13 @@ type GetInodeAttributesOp struct { } func (o *GetInodeAttributesOp) kernelResponse() (msg []byte) { - resp := fuseshim.GetattrResponse{ - Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), - } + size := fusekernel.AttrOutSize(fusekernel.Protocol{0, 0}) + buf := fuseshim.NewBuffer(size) + out := (*fusekernel.AttrOut)(buf.Alloc(size)) + out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) + convertAttributes(o.Inode, &o.Attributes, &out.Attr) - o.bfReq.Respond(&resp) + msg = buf return } From 4ce589bcf1149d9a42fd3a78e038962a328b4e7b Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 10:58:17 +1000 Subject: [PATCH 072/157] SetInodeAttributesOp.kernelResponse --- fuseops/ops.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 8ac2e4c..01962b2 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -146,11 +146,13 @@ type SetInodeAttributesOp struct { } func (o *SetInodeAttributesOp) kernelResponse() (msg []byte) { - resp := fuseshim.SetattrResponse{ - Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), - } + size := fusekernel.AttrOutSize(fusekernel.Protocol{0, 0}) + buf := fuseshim.NewBuffer(size) + out := (*fusekernel.AttrOut)(buf.Alloc(size)) + out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) + convertAttributes(o.Inode, &o.Attributes, &out.Attr) - o.bfReq.Respond(&resp) + msg = buf return } From 350f75b8123ec3b8a90f307616b49f95552c58a8 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:01:36 +1000 Subject: [PATCH 073/157] CreateFileOp.kernelResponse --- fuseops/ops.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 01962b2..2a063be 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -206,8 +206,7 @@ type ForgetInodeOp struct { } func (o *ForgetInodeOp) kernelResponse() (msg []byte) { - o.bfReq.Respond() - return + panic("TODO: Signal that no response should happen here.") } //////////////////////////////////////////////////////////////////////// @@ -248,10 +247,12 @@ func (o *MkDirOp) ShortDesc() (desc string) { } func (o *MkDirOp) kernelResponse() (msg []byte) { - resp := fuseshim.MkdirResponse{} - convertChildInodeEntry(&o.Entry, &resp.LookupResponse) + size := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) + buf := fuseshim.NewBuffer(size) + out := (*fusekernel.EntryOut)(buf.Alloc(size)) + convertChildInodeEntry(&o.Entry, out) - o.bfReq.Respond(&resp) + msg = buf return } @@ -298,15 +299,16 @@ func (o *CreateFileOp) ShortDesc() (desc string) { } func (o *CreateFileOp) kernelResponse() (msg []byte) { - resp := fuseshim.CreateResponse{ - OpenResponse: fuseshim.OpenResponse{ - Handle: fuseshim.HandleID(o.Handle), - }, - } + eSize := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) + buf := fuseshim.NewBuffer(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) - convertChildInodeEntry(&o.Entry, &resp.LookupResponse) + e := (*fusekernel.EntryOut)(buf.Alloc(eSize)) + convertChildInodeEntry(&o.Entry, e) - o.bfReq.Respond(&resp) + oo := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) + oo.Fh = uint64(o.Handle) + + msg = buf return } From b7d2d61e21306326fe55b364cbdcbb4834ee026a Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:04:12 +1000 Subject: [PATCH 074/157] More kernelResponse methods. --- fuseops/ops.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 2a063be..ca5402a 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -345,10 +345,12 @@ func (o *CreateSymlinkOp) ShortDesc() (desc string) { } func (o *CreateSymlinkOp) kernelResponse() (msg []byte) { - resp := fuseshim.SymlinkResponse{} - convertChildInodeEntry(&o.Entry, &resp.LookupResponse) + size := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) + buf := fuseshim.NewBuffer(size) + out := (*fusekernel.EntryOut)(buf.Alloc(size)) + convertChildInodeEntry(&o.Entry, out) - o.bfReq.Respond(&resp) + msg = buf return } @@ -405,7 +407,7 @@ type RenameOp struct { } func (o *RenameOp) kernelResponse() (msg []byte) { - o.bfReq.Respond() + msg = fuseshim.NewBuffer(0) return } @@ -426,7 +428,7 @@ type RmDirOp struct { } func (o *RmDirOp) kernelResponse() (msg []byte) { - o.bfReq.Respond() + msg = fuseshim.NewBuffer(0) return } @@ -446,7 +448,7 @@ type UnlinkOp struct { } func (o *UnlinkOp) kernelResponse() (msg []byte) { - o.bfReq.Respond() + msg = fuseshim.NewBuffer(0) return } @@ -603,7 +605,7 @@ type ReleaseDirHandleOp struct { } func (o *ReleaseDirHandleOp) kernelResponse() (msg []byte) { - o.bfReq.Respond() + msg = fuseshim.NewBuffer(0) return } @@ -786,7 +788,7 @@ type SyncFileOp struct { } func (o *SyncFileOp) kernelResponse() (msg []byte) { - o.bfReq.Respond() + msg = fuseshim.NewBuffer(0) return } @@ -846,7 +848,7 @@ type FlushFileOp struct { } func (o *FlushFileOp) kernelResponse() (msg []byte) { - o.bfReq.Respond() + msg = fuseshim.NewBuffer(0) return } @@ -868,7 +870,7 @@ type ReleaseFileHandleOp struct { } func (o *ReleaseFileHandleOp) kernelResponse() (msg []byte) { - o.bfReq.Respond() + msg = fuseshim.NewBuffer(0) return } From 987b494dcad77e725f2da7363b3a191bb1246839 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:07:17 +1000 Subject: [PATCH 075/157] Finished with kernelResponse methods. --- fuseops/ops.go | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index ca5402a..23aed6e 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -480,11 +480,11 @@ type OpenDirOp struct { } func (o *OpenDirOp) kernelResponse() (msg []byte) { - resp := fuseshim.OpenResponse{ - Handle: fuseshim.HandleID(o.Handle), - } + buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.OpenOut{})) + out := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) + out.Fh = uint64(o.Handle) - o.bfReq.Respond(&resp) + msg = buf return } @@ -579,11 +579,8 @@ type ReadDirOp struct { } func (o *ReadDirOp) kernelResponse() (msg []byte) { - resp := fuseshim.ReadResponse{ - Data: o.Data, - } - - o.bfReq.Respond(&resp) + msg = fuseshim.NewBuffer(uintptr(len(o.Data))) + msg = append(msg, o.Data...) return } @@ -636,11 +633,9 @@ type OpenFileOp struct { } func (o *OpenFileOp) kernelResponse() (msg []byte) { - type kernelOut fusekernel.OpenOut - - buf := fuseshim.NewBuffer(unsafe.Sizeof(kernelOut{})) - out := (*kernelOut)(buf.Alloc(unsafe.Sizeof(kernelOut{}))) - kernelOut.Fh = uint64(o.Handle) + buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.OpenOut{})) + out := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) + out.Fh = uint64(o.Handle) msg = buf return @@ -676,11 +671,8 @@ type ReadFileOp struct { } func (o *ReadFileOp) kernelResponse() (msg []byte) { - resp := fuseshim.ReadResponse{ - Data: o.Data, - } - - o.bfReq.Respond(&resp) + msg = fuseshim.NewBuffer(uintptr(len(o.Data))) + msg = append(msg, o.Data...) return } @@ -755,11 +747,11 @@ type WriteFileOp struct { } func (o *WriteFileOp) kernelResponse() (msg []byte) { - resp := fuseshim.WriteResponse{ - Size: len(o.Data), - } + buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.WriteOut{})) + out := (*fusekernel.WriteOut)(buf.Alloc(unsafe.Sizeof(fusekernel.WriteOut{}))) + out.Size = uint32(len(o.Data)) - o.bfReq.Respond(&resp) + msg = buf return } @@ -907,7 +899,8 @@ type ReadSymlinkOp struct { } func (o *ReadSymlinkOp) kernelResponse() (msg []byte) { - o.bfReq.Respond(o.Target) + msg = fuseshim.NewBuffer(uintptr(len(o.Target))) + msg = append(msg, o.Target...) return } From 75e9eb21a29d0f972239d15e9345e63f035f6c77 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:08:24 +1000 Subject: [PATCH 076/157] convertTime --- fuseops/convert.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 70b43dc..3ce0c99 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -417,7 +417,12 @@ func Convert( return } -func convertTime(t time.Time) (secs uint64, nsec uint32) +func convertTime(t time.Time) (secs uint64, nsec uint32) { + totalNano := t.UnixNano() + secs = uint64(totalNano / 1e9) + nsec = uint32(totalNano % 1e9) + return +} func convertAttributes( inodeID InodeID, From 22c5b466f6e9152788b3c9458d266a7b1d9b95c3 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:10:14 +1000 Subject: [PATCH 077/157] Fixed a bug. --- fuseops/ops.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fuseops/ops.go b/fuseops/ops.go index 23aed6e..13e156b 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -922,6 +922,8 @@ type InternalStatFSOp struct { func (o *InternalStatFSOp) kernelResponse() (msg []byte) { buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.StatfsOut{})) buf.Alloc(unsafe.Sizeof(fusekernel.StatfsOut{})) + + msg = buf return } From 9b8d9280acfc4504b2c519feb3eae55d6c813920 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:13:17 +1000 Subject: [PATCH 078/157] Fixed a logging bug. --- connection.go | 2 +- fuseops/common_op.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/connection.go b/connection.go index 5bdc337..65796e7 100644 --- a/connection.go +++ b/connection.go @@ -261,7 +261,7 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { } // Log the receipt of the operation. - c.debugLog(opID, 1, "<- %v", op) + c.debugLog(opID, 1, "<- %v", op.ShortDesc()) // Special case: responding to statfs is required to make mounting work on // OS X. We don't currently expose the capability for the file system to diff --git a/fuseops/common_op.go b/fuseops/common_op.go index 1339b02..c083b1c 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -80,9 +80,11 @@ func (o *commonOp) ShortDesc() (desc string) { opName = opName[len(prefix) : len(opName)-len(suffix)] } + desc = opName + // Include the inode number to which the op applies, if possible. if f := v.Elem().FieldByName("Inode"); f.IsValid() { - desc = fmt.Sprintf("%s(inode=%v)", opName, f.Interface()) + desc = fmt.Sprintf("%s(inode=%v)", desc, f.Interface()) } return From 4a2eeda4cab3c8dc057c4dd96e76cbc4b17cd639 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:14:36 +1000 Subject: [PATCH 079/157] Log fuse errors in the test. --- samples/in_process.go | 1 + samples/mount_sample/mount.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/in_process.go b/samples/in_process.go index d7f92aa..552f11b 100644 --- a/samples/in_process.go +++ b/samples/in_process.go @@ -61,6 +61,7 @@ type SampleTest struct { // REQUIRES: t.Server has been set. func (t *SampleTest) SetUp(ti *ogletest.TestInfo) { cfg := t.MountConfig + cfg.ErrorLogger = log.New(os.Stderr, "fuse error: ", log.Flags()) if *fDebug { cfg.DebugLogger = log.New(os.Stderr, "fuse: ", 0) } diff --git a/samples/mount_sample/mount.go b/samples/mount_sample/mount.go index 71707bf..8f9b8db 100644 --- a/samples/mount_sample/mount.go +++ b/samples/mount_sample/mount.go @@ -138,7 +138,8 @@ func main() { } cfg := &fuse.MountConfig{ - ReadOnly: *fReadOnly, + ReadOnly: *fReadOnly, + ErrorLogger: log.New(os.Stderr, "fuse error: ", log.Flags()), } if *fDebug { From b6ae9475ba47b3e007cebba47c5ab2440cd85c06 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:16:49 +1000 Subject: [PATCH 080/157] Partially implemented sendReply. --- connection.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/connection.go b/connection.go index 65796e7..50bfcc0 100644 --- a/connection.go +++ b/connection.go @@ -15,7 +15,6 @@ package fuse import ( - "errors" "fmt" "log" "path" @@ -236,13 +235,17 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { sendReply := func( fuseID uint64, - msg []byte, + replyMsg []byte, opErr error) (err error) { - // TODO(jacobsa): Turn this into a method and maybe kill the fuseID - // parameter. - // - // TODO(jacobsa): Don't forget to destroy the message. - err = errors.New("TODO") + // Clean up state for this op. + c.finishOp(m.Hdr.Opcode, m.Hdr.Unique) + + // Send the reply to the kernel. + panic("TODO") + + // Destroy the message, as required by fuseshim.Connection.ReadMessage. + m.Destroy() + return } From 9c2f2652f7f9b6aa4a42880d221397abd622c1b9 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:27:03 +1000 Subject: [PATCH 081/157] Write replies to the kernel. --- connection.go | 13 +++++++++---- internal/fuseshim/fuse.go | 6 +++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/connection.go b/connection.go index 50bfcc0..6295dab 100644 --- a/connection.go +++ b/connection.go @@ -237,14 +237,19 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { fuseID uint64, replyMsg []byte, opErr error) (err error) { + // Make sure we destroy the message, as required by + // fuseshim.Connection.ReadMessage. + defer m.Destroy() + // Clean up state for this op. c.finishOp(m.Hdr.Opcode, m.Hdr.Unique) // Send the reply to the kernel. - panic("TODO") - - // Destroy the message, as required by fuseshim.Connection.ReadMessage. - m.Destroy() + err = c.wrapped.WriteToKernel(replyMsg) + if err != nil { + err = fmt.Errorf("WriteToKernel: %v", err) + return + } return } diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 666615d..299415f 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -1061,7 +1061,7 @@ func errorString(err error) string { return err.Error() } -func (c *Conn) writeToKernel(msg []byte) error { +func (c *Conn) WriteToKernel(msg []byte) error { out := (*fusekernel.OutHeader)(unsafe.Pointer(&msg[0])) out.Len = uint32(len(msg)) @@ -1072,7 +1072,7 @@ func (c *Conn) writeToKernel(msg []byte) error { } func (c *Conn) respond(msg []byte) { - c.writeToKernel(msg) + c.WriteToKernel(msg) } type notCachedError struct{} @@ -1097,7 +1097,7 @@ var ( // // A returned ENOENT is translated to a friendlier error. func (c *Conn) sendInvalidate(msg []byte) error { - switch err := c.writeToKernel(msg); err { + switch err := c.WriteToKernel(msg); err { case syscall.ENOENT: return ErrNotCached default: From 85cd1cb2999b9c64a9502296e3553a65a93fd15e Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:38:23 +1000 Subject: [PATCH 082/157] Fixed error and debug logging. --- connection.go | 15 +++++++++++++++ fuseops/common_op.go | 25 ++++--------------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/connection.go b/connection.go index 6295dab..6029285 100644 --- a/connection.go +++ b/connection.go @@ -234,6 +234,7 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { } sendReply := func( + op fuseops.Op, fuseID uint64, replyMsg []byte, opErr error) (err error) { @@ -244,6 +245,20 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { // Clean up state for this op. c.finishOp(m.Hdr.Opcode, m.Hdr.Unique) + // Debug logging + if c.debugLogger != nil { + if opErr == nil { + op.Logf("-> %s OK", op.ShortDesc()) + } else { + op.Logf("-> %s error: %v", op.ShortDesc(), opErr) + } + } + + // Error logging + if opErr != nil && c.errorLogger != nil { + c.errorLogger.Printf("(%s) error: %v", op.ShortDesc(), opErr) + } + // Send the reply to the kernel. err = c.wrapped.WriteToKernel(replyMsg) if err != nil { diff --git a/fuseops/common_op.go b/fuseops/common_op.go index c083b1c..fb261e9 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -40,7 +40,7 @@ type internalOp interface { // A function that sends a reply message back to the kernel for the request // with the given fuse unique ID. The error argument is for informational // purposes only; the error to hand to the kernel is encoded in the message. -type replyFunc func(uint64, []byte, error) error +type replyFunc func(Op, uint64, []byte, error) error // A helper for embedding common behavior. type commonOp struct { @@ -111,9 +111,9 @@ func (o *commonOp) init( // When the op is finished, report to both reqtrace and the connection. prevSendReply := o.sendReply - o.sendReply = func(fuseID uint64, msg []byte, opErr error) (err error) { + o.sendReply = func(op Op, fuseID uint64, msg []byte, opErr error) (err error) { reportForTrace(opErr) - err = prevSendReply(fuseID, msg, opErr) + err = prevSendReply(op, fuseID, msg, opErr) return } } @@ -155,25 +155,8 @@ func (o *commonOp) Respond(err error) { h.Error = -int32(errno) } - // Log the error, if any. - if err != nil { - if o.debugLog != nil { - o.Logf( - "-> (%s) error: %v", - o.op.ShortDesc(), - err) - } - - if o.errorLogger != nil { - o.errorLogger.Printf( - "(%s) error: %v", - o.op.ShortDesc(), - err) - } - } - // Reply. - replyErr := o.sendReply(o.fuseID, msg, err) + replyErr := o.sendReply(o.op, o.fuseID, msg, err) if replyErr != nil && o.errorLogger != nil { o.errorLogger.Printf("Error from sendReply: %v", replyErr) } From 497407fde2908f71607650e8fa415f22458c7fa1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:47:39 +1000 Subject: [PATCH 083/157] Better debug logging for getattr. --- connection.go | 4 ++-- fuseops/common_op.go | 5 +++++ fuseops/ops.go | 11 +++++++++++ fuseops/simple_types.go | 10 ++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/connection.go b/connection.go index 6029285..e1d03da 100644 --- a/connection.go +++ b/connection.go @@ -248,9 +248,9 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { // Debug logging if c.debugLogger != nil { if opErr == nil { - op.Logf("-> %s OK", op.ShortDesc()) + op.Logf("-> OK: %s", op.DebugString()) } else { - op.Logf("-> %s error: %v", op.ShortDesc(), opErr) + op.Logf("-> error: %v", opErr) } } diff --git a/fuseops/common_op.go b/fuseops/common_op.go index fb261e9..b49c110 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -90,6 +90,11 @@ func (o *commonOp) ShortDesc() (desc string) { return } +func (o *commonOp) DebugString() string { + // By default, defer to ShortDesc. + return o.op.ShortDesc() +} + func (o *commonOp) init( ctx context.Context, op internalOp, diff --git a/fuseops/ops.go b/fuseops/ops.go index 13e156b..74c2074 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -32,6 +32,9 @@ type Op interface { // A short description of the op, to be used in logging. ShortDesc() string + // A long description of the op, to be used in debug logging. + DebugString() string + // A context that can be used for long-running operations. Context() context.Context @@ -122,6 +125,14 @@ func (o *GetInodeAttributesOp) kernelResponse() (msg []byte) { return } +func (o *GetInodeAttributesOp) DebugString() string { + return fmt.Sprintf( + "Inode: %d, Exp: %v, Attr: %s", + o.Inode, + o.AttributesExpiration, + o.Attributes.DebugString()) +} + // Change attributes for an inode. // // The kernel sends this for obvious cases like chmod(2), and for less obvious diff --git a/fuseops/simple_types.go b/fuseops/simple_types.go index 2ef7f54..3191b36 100644 --- a/fuseops/simple_types.go +++ b/fuseops/simple_types.go @@ -97,6 +97,16 @@ type InodeAttributes struct { Gid uint32 } +func (a *InodeAttributes) DebugString() string { + return fmt.Sprintf( + "%d %d %v %d %d", + a.Size, + a.Nlink, + a.Mode, + a.Uid, + a.Gid) +} + // A generation number for an inode. Irrelevant for file systems that won't be // exported over NFS. For those that will and that reuse inode IDs when they // become free, the generation number must change when an ID is reused. From ff420379811470b1c21efa7a9dde15a65cf4ff50 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:48:24 +1000 Subject: [PATCH 084/157] Changed method order. --- fuseops/ops.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 74c2074..3583bb7 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -114,6 +114,14 @@ type GetInodeAttributesOp struct { AttributesExpiration time.Time } +func (o *GetInodeAttributesOp) DebugString() string { + return fmt.Sprintf( + "Inode: %d, Exp: %v, Attr: %s", + o.Inode, + o.AttributesExpiration, + o.Attributes.DebugString()) +} + func (o *GetInodeAttributesOp) kernelResponse() (msg []byte) { size := fusekernel.AttrOutSize(fusekernel.Protocol{0, 0}) buf := fuseshim.NewBuffer(size) @@ -125,14 +133,6 @@ func (o *GetInodeAttributesOp) kernelResponse() (msg []byte) { return } -func (o *GetInodeAttributesOp) DebugString() string { - return fmt.Sprintf( - "Inode: %d, Exp: %v, Attr: %s", - o.Inode, - o.AttributesExpiration, - o.Attributes.DebugString()) -} - // Change attributes for an inode. // // The kernel sends this for obvious cases like chmod(2), and for less obvious From 503825fd5f9caf68a4ee03d0f3343e506243e066 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:49:37 +1000 Subject: [PATCH 085/157] Fixed a conversion bug. --- fuseops/convert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 3ce0c99..87baa23 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -439,7 +439,7 @@ func convertAttributes( out.Gid = in.Gid // Set the mode. - out.Mode = uint32(in.Mode) & 077 + out.Mode = uint32(in.Mode) & 0777 switch { default: out.Mode |= syscall.S_IFREG From 0f1a471c0680d26ee67f614a32b6f95c97bc9573 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:50:11 +1000 Subject: [PATCH 086/157] Revert "Log fuse errors in the test." There are lots of benign errors that add noise. This reverts commit 9a148bfd0246c3603ab580c4adfe548f5bd3e3b9. --- samples/in_process.go | 1 - samples/mount_sample/mount.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/samples/in_process.go b/samples/in_process.go index 552f11b..d7f92aa 100644 --- a/samples/in_process.go +++ b/samples/in_process.go @@ -61,7 +61,6 @@ type SampleTest struct { // REQUIRES: t.Server has been set. func (t *SampleTest) SetUp(ti *ogletest.TestInfo) { cfg := t.MountConfig - cfg.ErrorLogger = log.New(os.Stderr, "fuse error: ", log.Flags()) if *fDebug { cfg.DebugLogger = log.New(os.Stderr, "fuse: ", 0) } diff --git a/samples/mount_sample/mount.go b/samples/mount_sample/mount.go index 8f9b8db..71707bf 100644 --- a/samples/mount_sample/mount.go +++ b/samples/mount_sample/mount.go @@ -138,8 +138,7 @@ func main() { } cfg := &fuse.MountConfig{ - ReadOnly: *fReadOnly, - ErrorLogger: log.New(os.Stderr, "fuse error: ", log.Flags()), + ReadOnly: *fReadOnly, } if *fDebug { From d9c8d95df7abe569ca6e6e07a3ea1148c1c05076 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:54:56 +1000 Subject: [PATCH 087/157] Fixed a panic. --- fuseops/common_op.go | 25 +++++++++++++++---------- fuseops/ops.go | 3 ++- internal/fuseshim/fuse.go | 5 ++++- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index b49c110..316ba24 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -34,6 +34,9 @@ type internalOp interface { // Create a response message for the kernel, with leading pading for a // fusekernel.OutHeader struct. + // + // Special case: a return value of nil means that the kernel is not expecting + // a response. kernelResponse() []byte } @@ -147,17 +150,19 @@ func (o *commonOp) Respond(err error) { msg = fuseshim.NewBuffer(0) } - // Fill in the header. - h := (*fusekernel.OutHeader)(unsafe.Pointer(&msg[0])) - h.Unique = o.fuseID - h.Len = uint32(len(msg)) - if err != nil { - errno := fuseshim.EIO - if ferr, ok := err.(fuseshim.ErrorNumber); ok { - errno = ferr.Errno() - } + // Fill in the header if a reply is needed. + if msg != nil { + h := (*fusekernel.OutHeader)(unsafe.Pointer(&msg[0])) + h.Unique = o.fuseID + h.Len = uint32(len(msg)) + if err != nil { + errno := fuseshim.EIO + if ferr, ok := err.(fuseshim.ErrorNumber); ok { + errno = ferr.Errno() + } - h.Error = -int32(errno) + h.Error = -int32(errno) + } } // Reply. diff --git a/fuseops/ops.go b/fuseops/ops.go index 3583bb7..c3f7c68 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -217,7 +217,8 @@ type ForgetInodeOp struct { } func (o *ForgetInodeOp) kernelResponse() (msg []byte) { - panic("TODO: Signal that no response should happen here.") + // No response. + return } //////////////////////////////////////////////////////////////////////// diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 299415f..03f3a65 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -1061,10 +1061,13 @@ func errorString(err error) string { return err.Error() } -func (c *Conn) WriteToKernel(msg []byte) 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) From 050bcb1c021c6a8275b54a9f47f2f6d50bd048c3 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 11:57:53 +1000 Subject: [PATCH 088/157] Fixed a bug. --- internal/fuseshim/fuse.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 03f3a65..4748db2 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -1075,7 +1075,7 @@ func (c *Conn) WriteToKernel(msg []byte) error { } func (c *Conn) respond(msg []byte) { - c.WriteToKernel(msg) + c.writeToKernel(msg) } type notCachedError struct{} @@ -1100,7 +1100,7 @@ var ( // // A returned ENOENT is translated to a friendlier error. func (c *Conn) sendInvalidate(msg []byte) error { - switch err := c.WriteToKernel(msg); err { + switch err := c.writeToKernel(msg); err { case syscall.ENOENT: return ErrNotCached default: From f3956ca1097ee20f2a76e1cd691c93e3fd1d788e Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 12:57:29 +1000 Subject: [PATCH 089/157] Fixed some Linux build errors. --- internal/fusekernel/fuse_kernel_linux.go | 22 +++++++++++----------- internal/fuseshim/unmount_linux.go | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/fusekernel/fuse_kernel_linux.go b/internal/fusekernel/fuse_kernel_linux.go index a464e50..0472e1d 100644 --- a/internal/fusekernel/fuse_kernel_linux.go +++ b/internal/fusekernel/fuse_kernel_linux.go @@ -1,8 +1,8 @@ -package fuseshim +package fusekernel import "time" -type attr struct { +type Attr struct { Ino uint64 Size uint64 Blocks uint64 @@ -21,31 +21,31 @@ type attr struct { padding uint32 } -func (a *attr) Crtime() time.Time { +func (a *Attr) Crtime() time.Time { return time.Time{} } -func (a *attr) SetCrtime(s uint64, ns uint32) { +func (a *Attr) SetCrtime(s uint64, ns uint32) { // Ignored on Linux. } -func (a *attr) SetFlags(f uint32) { +func (a *Attr) SetFlags(f uint32) { // Ignored on Linux. } -type setattrIn struct { +type SetattrIn struct { setattrInCommon } -func (in *setattrIn) BkupTime() time.Time { +func (in *SetattrIn) BkupTime() time.Time { return time.Time{} } -func (in *setattrIn) Chgtime() time.Time { +func (in *SetattrIn) Chgtime() time.Time { return time.Time{} } -func (in *setattrIn) Flags() uint32 { +func (in *SetattrIn) Flags() uint32 { return 0 } @@ -61,10 +61,10 @@ func openFlags(flags uint32) OpenFlags { return OpenFlags(flags) } -type getxattrIn struct { +type GetxattrIn struct { getxattrInCommon } -type setxattrIn struct { +type SetxattrIn struct { setxattrInCommon } diff --git a/internal/fuseshim/unmount_linux.go b/internal/fuseshim/unmount_linux.go index e4c18d6..6a165cc 100644 --- a/internal/fuseshim/unmount_linux.go +++ b/internal/fuseshim/unmount_linux.go @@ -1,4 +1,4 @@ -package bazilfuse +package fuseshim import ( "bytes" From ab8c5728eb860cba896865ec4f5cc6b0a94ed899 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 13:08:26 +1000 Subject: [PATCH 090/157] Fixed some size-related bugs on Linux. --- fuseops/convert.go | 32 +++++++++++++++++++------------- fuseops/ops.go | 18 ++++++++++++------ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 87baa23..9e1c6fc 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -57,15 +57,17 @@ func Convert( } to := &LookUpInodeOp{ - Parent: InodeID(m.Hdr.Nodeid), - Name: string(buf[:n-1]), + protocol: protocol, + Parent: InodeID(m.Hdr.Nodeid), + Name: string(buf[:n-1]), } io = to co = &to.commonOp case fusekernel.OpGetattr: to := &GetInodeAttributesOp{ - Inode: InodeID(m.Hdr.Nodeid), + protocol: protocol, + Inode: InodeID(m.Hdr.Nodeid), } io = to co = &to.commonOp @@ -78,7 +80,8 @@ func Convert( } to := &SetInodeAttributesOp{ - Inode: InodeID(m.Hdr.Nodeid), + protocol: protocol, + Inode: InodeID(m.Hdr.Nodeid), } valid := fusekernel.SetattrValid(in.Valid) @@ -134,9 +137,10 @@ func Convert( name = name[:i] to := &MkDirOp{ - Parent: InodeID(m.Hdr.Nodeid), - Name: string(name), - Mode: fuseshim.FileMode(in.Mode), + protocol: protocol, + Parent: InodeID(m.Hdr.Nodeid), + Name: string(name), + Mode: fuseshim.FileMode(in.Mode), } io = to co = &to.commonOp @@ -157,9 +161,10 @@ func Convert( name = name[:i] to := &CreateFileOp{ - Parent: InodeID(m.Hdr.Nodeid), - Name: string(name), - Mode: fuseshim.FileMode(in.Mode), + protocol: protocol, + Parent: InodeID(m.Hdr.Nodeid), + Name: string(name), + Mode: fuseshim.FileMode(in.Mode), } io = to co = &to.commonOp @@ -179,9 +184,10 @@ func Convert( newName, target := names[0:i], names[i+1:len(names)-1] to := &CreateSymlinkOp{ - Parent: InodeID(m.Hdr.Nodeid), - Name: string(newName), - Target: string(target), + protocol: protocol, + Parent: InodeID(m.Hdr.Nodeid), + Name: string(newName), + Target: string(target), } io = to co = &to.commonOp diff --git a/fuseops/ops.go b/fuseops/ops.go index c3f7c68..cd528f1 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -60,6 +60,7 @@ type Op interface { // when resolving user paths to dentry structs, which are then cached. type LookUpInodeOp struct { commonOp + protocol fusekernel.Protocol // The ID of the directory inode to which the child belongs. Parent InodeID @@ -88,7 +89,7 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) { } func (o *LookUpInodeOp) kernelResponse() (msg []byte) { - size := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) + size := fusekernel.EntryOutSize(o.protocol) buf := fuseshim.NewBuffer(size) out := (*fusekernel.EntryOut)(buf.Alloc(size)) convertChildInodeEntry(&o.Entry, out) @@ -103,6 +104,7 @@ func (o *LookUpInodeOp) kernelResponse() (msg []byte) { // field of ChildInodeEntry, etc. type GetInodeAttributesOp struct { commonOp + protocol fusekernel.Protocol // The inode of interest. Inode InodeID @@ -123,7 +125,7 @@ func (o *GetInodeAttributesOp) DebugString() string { } func (o *GetInodeAttributesOp) kernelResponse() (msg []byte) { - size := fusekernel.AttrOutSize(fusekernel.Protocol{0, 0}) + size := fusekernel.AttrOutSize(o.protocol) buf := fuseshim.NewBuffer(size) out := (*fusekernel.AttrOut)(buf.Alloc(size)) out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) @@ -139,6 +141,7 @@ func (o *GetInodeAttributesOp) kernelResponse() (msg []byte) { // cases like ftrunctate(2). type SetInodeAttributesOp struct { commonOp + protocol fusekernel.Protocol // The inode of interest. Inode InodeID @@ -157,7 +160,7 @@ type SetInodeAttributesOp struct { } func (o *SetInodeAttributesOp) kernelResponse() (msg []byte) { - size := fusekernel.AttrOutSize(fusekernel.Protocol{0, 0}) + size := fusekernel.AttrOutSize(o.protocol) buf := fuseshim.NewBuffer(size) out := (*fusekernel.AttrOut)(buf.Alloc(size)) out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) @@ -238,6 +241,7 @@ func (o *ForgetInodeOp) kernelResponse() (msg []byte) { // Therefore the file system should return EEXIST if the name already exists. type MkDirOp struct { commonOp + protocol fusekernel.Protocol // The ID of parent directory inode within which to create the child. Parent InodeID @@ -259,7 +263,7 @@ func (o *MkDirOp) ShortDesc() (desc string) { } func (o *MkDirOp) kernelResponse() (msg []byte) { - size := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) + size := fusekernel.EntryOutSize(o.protocol) buf := fuseshim.NewBuffer(size) out := (*fusekernel.EntryOut)(buf.Alloc(size)) convertChildInodeEntry(&o.Entry, out) @@ -280,6 +284,7 @@ func (o *MkDirOp) kernelResponse() (msg []byte) { // Therefore the file system should return EEXIST if the name already exists. type CreateFileOp struct { commonOp + protocol fusekernel.Protocol // The ID of parent directory inode within which to create the child file. Parent InodeID @@ -311,7 +316,7 @@ func (o *CreateFileOp) ShortDesc() (desc string) { } func (o *CreateFileOp) kernelResponse() (msg []byte) { - eSize := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) + eSize := fusekernel.EntryOutSize(o.protocol) buf := fuseshim.NewBuffer(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) e := (*fusekernel.EntryOut)(buf.Alloc(eSize)) @@ -328,6 +333,7 @@ func (o *CreateFileOp) kernelResponse() (msg []byte) { // return EEXIST (cf. the notes on CreateFileOp and MkDirOp). type CreateSymlinkOp struct { commonOp + protocol fusekernel.Protocol // The ID of parent directory inode within which to create the child symlink. Parent InodeID @@ -357,7 +363,7 @@ func (o *CreateSymlinkOp) ShortDesc() (desc string) { } func (o *CreateSymlinkOp) kernelResponse() (msg []byte) { - size := fusekernel.EntryOutSize(fusekernel.Protocol{0, 0}) + size := fusekernel.EntryOutSize(o.protocol) buf := fuseshim.NewBuffer(size) out := (*fusekernel.EntryOut)(buf.Alloc(size)) convertChildInodeEntry(&o.Entry, out) From f8a67d283f89b5c735a95a5cc480cda517c8faef Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 13:23:39 +1000 Subject: [PATCH 091/157] Fixed a mkdir bug on Linux. --- fuseops/convert.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 9e1c6fc..fb30d8e 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -140,8 +140,16 @@ func Convert( protocol: protocol, Parent: InodeID(m.Hdr.Nodeid), Name: string(name), - Mode: fuseshim.FileMode(in.Mode), + + // On Linux, vfs_mkdir calls through to the inode with at most + // permissions and sticky bits set (cf. https://goo.gl/WxgQXk), and fuse + // passes that on directly (cf. https://goo.gl/f31aMo). In other words, + // the fact that this is a directory is implicit in the fact that the + // opcode is mkdir. But we want the correct mode to go through, so ensure + // that os.ModeDir is set. + Mode: fuseshim.FileMode(in.Mode) | os.ModeDir, } + io = to co = &to.commonOp From 3fe63db6fb6b8288631459284bc630d322709d67 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 13:26:25 +1000 Subject: [PATCH 092/157] Pasted from package fuseshim. --- internal/buffer/buffer.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 internal/buffer/buffer.go diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go new file mode 100644 index 0000000..d2999bc --- /dev/null +++ b/internal/buffer/buffer.go @@ -0,0 +1,39 @@ +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 +} From 9c1134ca3f9742085f1b9c380f384d97d6f5463c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 13:36:53 +1000 Subject: [PATCH 093/157] Added a Buffer type. --- internal/buffer/buffer.go | 78 +++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index d2999bc..e6d7077 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -1,39 +1,61 @@ -package fuseshim +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buffer import ( + "reflect" "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]) +// Buffer provides a mechanism for constructing a single contiguous fuse +// message from multiple segments, where the first segment is always a +// fusekernel.OutHeader message. +// +// Must be created with New. +type Buffer struct { + slice []byte } -// 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) +// Create a new buffer whose initial contents are a zeroed fusekernel.OutHeader +// message, and with room enough to grow by extra bytes. +func New(extra uintptr) (b Buffer) { + const headerSize = unsafe.Sizeof(fusekernel.OutHeader{}) + b.slice = make([]byte, headerSize, headerSize+extra) return } + +// Return a pointer to the header at the start of the buffer. +func (b *Buffer) OutHeader() (h *fusekernel.OutHeader) { + sh := (*reflect.SliceHeader)(unsafe.Pointer(&b.slice)) + h = (*fusekernel.OutHeader)(unsafe.Pointer(sh.Data)) + return +} + +// Grow the buffer by the supplied number of bytes, returning a pointer to the +// start of the new segment. The sum of the arguments given to Grow must not +// exceed the argument given to New when creating the buffer. +func (b *Buffer) Grow(size uintptr) (p unsafe.Pointer) { + sh := (*reflect.SliceHeader)(unsafe.Pointer(&b.slice)) + p = unsafe.Pointer(sh.Data + uintptr(sh.Len)) + b.slice = b.slice[:len(b.slice)+int(size)] + return +} + +// Return a reference to the current contents of the buffer. +func (b *Buffer) Bytes() []byte { + return b.slice +} From 7784641da18d05e352f7a40bbdcf14a002bfe65f Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 13:41:05 +1000 Subject: [PATCH 094/157] Use buffer in commonOp.Respond. --- fuseops/common_op.go | 22 +++++++++++----------- internal/buffer/buffer.go | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index 316ba24..939d16e 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -19,9 +19,8 @@ import ( "log" "reflect" "strings" - "unsafe" - "github.com/jacobsa/fuse/internal/fusekernel" + "github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/fuse/internal/fuseshim" "github.com/jacobsa/reqtrace" "golang.org/x/net/context" @@ -32,12 +31,12 @@ import ( type internalOp interface { Op - // Create a response message for the kernel, with leading pading for a - // fusekernel.OutHeader struct. + // Create a response message for the kernel, leaving the leading + // fusekernel.OutHeader untouched. // - // Special case: a return value of nil means that the kernel is not expecting - // a response. - kernelResponse() []byte + // Special case: a zero return value means that the kernel is not expecting a + // response. + kernelResponse() (b buffer.Buffer) } // A function that sends a reply message back to the kernel for the request @@ -143,16 +142,17 @@ func (o *commonOp) Respond(err error) { // If successful, we ask the op for an appopriate response to the kernel, and // it is responsible for leaving room for the fusekernel.OutHeader struct. // Otherwise, create our own. - var msg []byte + var b buffer.Buffer if err == nil { - msg = o.op.kernelResponse() + b = o.op.kernelResponse() } else { - msg = fuseshim.NewBuffer(0) + b = buffer.New(0) } // Fill in the header if a reply is needed. + msg := b.Bytes() if msg != nil { - h := (*fusekernel.OutHeader)(unsafe.Pointer(&msg[0])) + h := b.OutHeader() h.Unique = o.fuseID h.Len = uint32(len(msg)) if err != nil { diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index e6d7077..45b482f 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -25,7 +25,7 @@ import ( // message from multiple segments, where the first segment is always a // fusekernel.OutHeader message. // -// Must be created with New. +// Must be created with New. Exception: the zero value has Bytes() == nil. type Buffer struct { slice []byte } From 2c346ca77fdcc47b8d79a27579447f516e008192 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 13:46:18 +1000 Subject: [PATCH 095/157] Fixed kernelResponse methods. --- fuseops/ops.go | 126 ++++++++++++++++++-------------------- internal/buffer/buffer.go | 22 +++++++ 2 files changed, 80 insertions(+), 68 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index cd528f1..1c594b4 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -20,8 +20,8 @@ import ( "time" "unsafe" + "github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/fuse/internal/fusekernel" - "github.com/jacobsa/fuse/internal/fuseshim" "golang.org/x/net/context" ) @@ -88,13 +88,12 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) { return } -func (o *LookUpInodeOp) kernelResponse() (msg []byte) { +func (o *LookUpInodeOp) kernelResponse() (b buffer.Buffer) { size := fusekernel.EntryOutSize(o.protocol) - buf := fuseshim.NewBuffer(size) - out := (*fusekernel.EntryOut)(buf.Alloc(size)) + b = buffer.New(size) + out := (*fusekernel.EntryOut)(b.Grow(size)) convertChildInodeEntry(&o.Entry, out) - msg = buf return } @@ -124,14 +123,13 @@ func (o *GetInodeAttributesOp) DebugString() string { o.Attributes.DebugString()) } -func (o *GetInodeAttributesOp) kernelResponse() (msg []byte) { +func (o *GetInodeAttributesOp) kernelResponse() (b buffer.Buffer) { size := fusekernel.AttrOutSize(o.protocol) - buf := fuseshim.NewBuffer(size) - out := (*fusekernel.AttrOut)(buf.Alloc(size)) + b = buffer.New(size) + out := (*fusekernel.AttrOut)(b.Grow(size)) out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) convertAttributes(o.Inode, &o.Attributes, &out.Attr) - msg = buf return } @@ -159,14 +157,13 @@ type SetInodeAttributesOp struct { AttributesExpiration time.Time } -func (o *SetInodeAttributesOp) kernelResponse() (msg []byte) { +func (o *SetInodeAttributesOp) kernelResponse() (b buffer.Buffer) { size := fusekernel.AttrOutSize(o.protocol) - buf := fuseshim.NewBuffer(size) - out := (*fusekernel.AttrOut)(buf.Alloc(size)) + b = buffer.New(size) + out := (*fusekernel.AttrOut)(b.Grow(size)) out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) convertAttributes(o.Inode, &o.Attributes, &out.Attr) - msg = buf return } @@ -219,7 +216,7 @@ type ForgetInodeOp struct { N uint64 } -func (o *ForgetInodeOp) kernelResponse() (msg []byte) { +func (o *ForgetInodeOp) kernelResponse() (b buffer.Buffer) { // No response. return } @@ -262,13 +259,12 @@ func (o *MkDirOp) ShortDesc() (desc string) { return } -func (o *MkDirOp) kernelResponse() (msg []byte) { +func (o *MkDirOp) kernelResponse() (b buffer.Buffer) { size := fusekernel.EntryOutSize(o.protocol) - buf := fuseshim.NewBuffer(size) - out := (*fusekernel.EntryOut)(buf.Alloc(size)) + b = buffer.New(size) + out := (*fusekernel.EntryOut)(b.Grow(size)) convertChildInodeEntry(&o.Entry, out) - msg = buf return } @@ -315,17 +311,16 @@ func (o *CreateFileOp) ShortDesc() (desc string) { return } -func (o *CreateFileOp) kernelResponse() (msg []byte) { +func (o *CreateFileOp) kernelResponse() (b buffer.Buffer) { eSize := fusekernel.EntryOutSize(o.protocol) - buf := fuseshim.NewBuffer(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) + b = buffer.New(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) - e := (*fusekernel.EntryOut)(buf.Alloc(eSize)) + e := (*fusekernel.EntryOut)(b.Grow(eSize)) convertChildInodeEntry(&o.Entry, e) - oo := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) + oo := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{}))) oo.Fh = uint64(o.Handle) - msg = buf return } @@ -362,13 +357,12 @@ func (o *CreateSymlinkOp) ShortDesc() (desc string) { return } -func (o *CreateSymlinkOp) kernelResponse() (msg []byte) { +func (o *CreateSymlinkOp) kernelResponse() (b buffer.Buffer) { size := fusekernel.EntryOutSize(o.protocol) - buf := fuseshim.NewBuffer(size) - out := (*fusekernel.EntryOut)(buf.Alloc(size)) + b = buffer.New(size) + out := (*fusekernel.EntryOut)(b.Grow(size)) convertChildInodeEntry(&o.Entry, out) - msg = buf return } @@ -424,8 +418,8 @@ type RenameOp struct { NewName string } -func (o *RenameOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(0) +func (o *RenameOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(0) return } @@ -445,8 +439,8 @@ type RmDirOp struct { Name string } -func (o *RmDirOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(0) +func (o *RmDirOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(0) return } @@ -465,8 +459,8 @@ type UnlinkOp struct { Name string } -func (o *UnlinkOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(0) +func (o *UnlinkOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(0) return } @@ -497,12 +491,11 @@ type OpenDirOp struct { Handle HandleID } -func (o *OpenDirOp) kernelResponse() (msg []byte) { - buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.OpenOut{})) - out := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) +func (o *OpenDirOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(unsafe.Sizeof(fusekernel.OpenOut{})) + out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{}))) out.Fh = uint64(o.Handle) - msg = buf return } @@ -596,9 +589,9 @@ type ReadDirOp struct { Data []byte } -func (o *ReadDirOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(uintptr(len(o.Data))) - msg = append(msg, o.Data...) +func (o *ReadDirOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(uintptr(len(o.Data))) + b.Append(o.Data) return } @@ -619,8 +612,8 @@ type ReleaseDirHandleOp struct { Handle HandleID } -func (o *ReleaseDirHandleOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(0) +func (o *ReleaseDirHandleOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(0) return } @@ -650,12 +643,11 @@ type OpenFileOp struct { Handle HandleID } -func (o *OpenFileOp) kernelResponse() (msg []byte) { - buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.OpenOut{})) - out := (*fusekernel.OpenOut)(buf.Alloc(unsafe.Sizeof(fusekernel.OpenOut{}))) +func (o *OpenFileOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(unsafe.Sizeof(fusekernel.OpenOut{})) + out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{}))) out.Fh = uint64(o.Handle) - msg = buf return } @@ -688,9 +680,9 @@ type ReadFileOp struct { Data []byte } -func (o *ReadFileOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(uintptr(len(o.Data))) - msg = append(msg, o.Data...) +func (o *ReadFileOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(uintptr(len(o.Data))) + b.Append(o.Data) return } @@ -764,12 +756,11 @@ type WriteFileOp struct { Data []byte } -func (o *WriteFileOp) kernelResponse() (msg []byte) { - buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.WriteOut{})) - out := (*fusekernel.WriteOut)(buf.Alloc(unsafe.Sizeof(fusekernel.WriteOut{}))) +func (o *WriteFileOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(unsafe.Sizeof(fusekernel.WriteOut{})) + out := (*fusekernel.WriteOut)(b.Grow(unsafe.Sizeof(fusekernel.WriteOut{}))) out.Size = uint32(len(o.Data)) - msg = buf return } @@ -797,8 +788,8 @@ type SyncFileOp struct { Handle HandleID } -func (o *SyncFileOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(0) +func (o *SyncFileOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(0) return } @@ -857,8 +848,8 @@ type FlushFileOp struct { Handle HandleID } -func (o *FlushFileOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(0) +func (o *FlushFileOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(0) return } @@ -879,8 +870,8 @@ type ReleaseFileHandleOp struct { Handle HandleID } -func (o *ReleaseFileHandleOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(0) +func (o *ReleaseFileHandleOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(0) return } @@ -897,7 +888,7 @@ func (o *unknownOp) ShortDesc() (desc string) { return } -func (o *unknownOp) kernelResponse() (msg []byte) { +func (o *unknownOp) kernelResponse() (b buffer.Buffer) { panic(fmt.Sprintf("Should never get here for unknown op: %s", o.ShortDesc())) } @@ -916,9 +907,9 @@ type ReadSymlinkOp struct { Target string } -func (o *ReadSymlinkOp) kernelResponse() (msg []byte) { - msg = fuseshim.NewBuffer(uintptr(len(o.Target))) - msg = append(msg, o.Target...) +func (o *ReadSymlinkOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(uintptr(len(o.Target))) + b.AppendString(o.Target) return } @@ -937,11 +928,10 @@ type InternalStatFSOp struct { commonOp } -func (o *InternalStatFSOp) kernelResponse() (msg []byte) { - buf := fuseshim.NewBuffer(unsafe.Sizeof(fusekernel.StatfsOut{})) - buf.Alloc(unsafe.Sizeof(fusekernel.StatfsOut{})) +func (o *InternalStatFSOp) kernelResponse() (b buffer.Buffer) { + b = buffer.New(unsafe.Sizeof(fusekernel.StatfsOut{})) + b.Grow(unsafe.Sizeof(fusekernel.StatfsOut{})) - msg = buf return } @@ -951,6 +941,6 @@ type InternalInterruptOp struct { FuseID uint64 } -func (o *InternalInterruptOp) kernelResponse() (msg []byte) { +func (o *InternalInterruptOp) kernelResponse() (b buffer.Buffer) { panic("Shouldn't get here.") } diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 45b482f..fe69a79 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -55,6 +55,28 @@ func (b *Buffer) Grow(size uintptr) (p unsafe.Pointer) { return } +// Equivalent to growing by the length of p, then copying p into the new segment. +func (b *Buffer) Append(p []byte) { + sh := reflect.SliceHeader{ + Data: uintptr(b.Grow(uintptr(len(p)))), + Len: len(p), + Cap: len(p), + } + + copy(*(*[]byte)(unsafe.Pointer(&sh)), p) +} + +// Equivalent to growing by the length of s, then copying s into the new segment. +func (b *Buffer) AppendString(s string) { + sh := reflect.SliceHeader{ + Data: uintptr(b.Grow(uintptr(len(s)))), + Len: len(s), + Cap: len(s), + } + + copy(*(*[]byte)(unsafe.Pointer(&sh)), s) +} + // Return a reference to the current contents of the buffer. func (b *Buffer) Bytes() []byte { return b.slice From 3720ab29636b70741bc5635d8e73f1d75db16f01 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 13:48:10 +1000 Subject: [PATCH 096/157] Updated a TODO. --- fuseops/ops.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 1c594b4..97cf058 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -919,9 +919,10 @@ func (o *ReadSymlinkOp) kernelResponse() (b buffer.Buffer) { // TODO(jacobsa): Untangle the way ops work and move these to an internal // package, along with Convert. I think all of the behavior wants to be on -// Connection. Ops have only String methods. Connection.ReadRequest returns an +// Connection. Ops have only String methods. Connection.ReadOp returns an // interace{} and a context. If we must restore debug logging later, we can -// stuff an op ID in that context and add a Connection.Logf method. +// stuff an op ID in that context and add a Connection.Logf method. Connection +// has a Reply method that takes a descendent context and an error. // Do not use this struct directly. See the TODO in fuseops/ops.go. type InternalStatFSOp struct { From 964ae1b4e511327cdb38c109336407b65e6f7655 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:02:55 +1000 Subject: [PATCH 097/157] Don't rely on fuseshim for errors. --- errors.go | 20 ++++++++------------ fuseops/common_op.go | 13 +++++++------ samples/flushfs/flush_fs_test.go | 9 ++++----- samples/mount_sample/mount.go | 6 +++--- 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/errors.go b/errors.go index d3dea3e..bc9f902 100644 --- a/errors.go +++ b/errors.go @@ -14,20 +14,16 @@ package fuse -import ( - "syscall" - - "github.com/jacobsa/fuse/internal/fuseshim" -) +import "syscall" const ( // Errors corresponding to kernel error numbers. These may be treated // specially by fuseops.Op.Respond methods. - EEXIST = fuseshim.EEXIST - EINVAL = fuseshim.Errno(syscall.EINVAL) - EIO = fuseshim.EIO - ENOENT = fuseshim.ENOENT - ENOSYS = fuseshim.ENOSYS - ENOTDIR = fuseshim.Errno(syscall.ENOTDIR) - ENOTEMPTY = fuseshim.Errno(syscall.ENOTEMPTY) + EEXIST = syscall.EEXIST + EINVAL = syscall.EINVAL + EIO = syscall.EIO + ENOENT = syscall.ENOENT + ENOSYS = syscall.ENOSYS + ENOTDIR = syscall.ENOTDIR + ENOTEMPTY = syscall.ENOTEMPTY ) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index 939d16e..203793c 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -19,9 +19,9 @@ import ( "log" "reflect" "strings" + "syscall" "github.com/jacobsa/fuse/internal/buffer" - "github.com/jacobsa/fuse/internal/fuseshim" "github.com/jacobsa/reqtrace" "golang.org/x/net/context" ) @@ -156,12 +156,13 @@ func (o *commonOp) Respond(err error) { h.Unique = o.fuseID h.Len = uint32(len(msg)) if err != nil { - errno := fuseshim.EIO - if ferr, ok := err.(fuseshim.ErrorNumber); ok { - errno = ferr.Errno() + // If the user gave us a syscall.Errno, use that value in the reply. + // Otherwise use the generic EIO. + if errno, ok := err.(syscall.Errno); ok { + h.Error = -int32(errno) + } else { + h.Error = -int32(syscall.EIO) } - - h.Error = -int32(errno) } } diff --git a/samples/flushfs/flush_fs_test.go b/samples/flushfs/flush_fs_test.go index 60d8d11..278f0ba 100644 --- a/samples/flushfs/flush_fs_test.go +++ b/samples/flushfs/flush_fs_test.go @@ -32,7 +32,6 @@ import ( "github.com/jacobsa/fuse/fsutil" "github.com/jacobsa/fuse/fusetesting" - "github.com/jacobsa/fuse/internal/fuseshim" "github.com/jacobsa/fuse/samples" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" @@ -58,8 +57,8 @@ type flushFSTest struct { func (t *flushFSTest) setUp( ti *TestInfo, - flushErr fuseshim.Errno, - fsyncErr fuseshim.Errno, + flushErr syscall.Errno, + fsyncErr syscall.Errno, readOnly bool) { var err error @@ -810,7 +809,7 @@ func init() { RegisterTestSuite(&FlushErrorTest{}) } func (t *FlushErrorTest) SetUp(ti *TestInfo) { const noErr = 0 - t.flushFSTest.setUp(ti, fuseshim.ENOENT, noErr, false) + t.flushFSTest.setUp(ti, syscall.ENOENT, noErr, false) } func (t *FlushErrorTest) Close() { @@ -890,7 +889,7 @@ func init() { RegisterTestSuite(&FsyncErrorTest{}) } func (t *FsyncErrorTest) SetUp(ti *TestInfo) { const noErr = 0 - t.flushFSTest.setUp(ti, noErr, fuseshim.ENOENT, false) + t.flushFSTest.setUp(ti, noErr, syscall.ENOENT, false) } func (t *FsyncErrorTest) Fsync() { diff --git a/samples/mount_sample/mount.go b/samples/mount_sample/mount.go index 71707bf..5a25e12 100644 --- a/samples/mount_sample/mount.go +++ b/samples/mount_sample/mount.go @@ -23,9 +23,9 @@ import ( "log" "os" "runtime" + "syscall" "github.com/jacobsa/fuse" - "github.com/jacobsa/fuse/internal/fuseshim" "github.com/jacobsa/fuse/samples/flushfs" "golang.org/x/net/context" ) @@ -58,11 +58,11 @@ func makeFlushFS() (server fuse.Server, err error) { var fsyncErr error if *fFlushError != 0 { - flushErr = fuseshim.Errno(*fFlushError) + flushErr = syscall.Errno(*fFlushError) } if *fFsyncError != 0 { - fsyncErr = fuseshim.Errno(*fFsyncError) + fsyncErr = syscall.Errno(*fFsyncError) } // Report flushes and fsyncs by writing the contents followed by a newline. From 6dd4bf3b90466924721b78abbc6db23c21bf1946 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:05:13 +1000 Subject: [PATCH 098/157] Stop referring to fuseshim.RootID. --- fuseops/simple_types.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fuseops/simple_types.go b/fuseops/simple_types.go index 3191b36..c41d872 100644 --- a/fuseops/simple_types.go +++ b/fuseops/simple_types.go @@ -19,7 +19,7 @@ import ( "os" "time" - "github.com/jacobsa/fuse/internal/fuseshim" + "github.com/jacobsa/fuse/internal/fusekernel" ) // A 64-bit number used to uniquely identify a file or directory in the file @@ -38,7 +38,7 @@ const RootInodeID = 1 func init() { // Make sure the constant above is correct. We do this at runtime rather than - // defining the constant in terms of fuseshim.RootID for two reasons: + // defining the constant in terms of fusekernel.RootID for two reasons: // // 1. Users can more clearly see that the root ID is low and can therefore // be used as e.g. an array index, with space reserved up to the root. @@ -46,12 +46,12 @@ func init() { // 2. The constant can be untyped and can therefore more easily be used as // an array index. // - if RootInodeID != fuseshim.RootID { + if RootInodeID != fusekernel.RootID { panic( fmt.Sprintf( "Oops, RootInodeID is wrong: %v vs. %v", RootInodeID, - fuseshim.RootID)) + fusekernel.RootID)) } } From 5166631830e78797b0546c9bec664574ccbf3879 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:09:11 +1000 Subject: [PATCH 099/157] Renamed to out_message.go. --- internal/buffer/{buffer.go => out_message.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename internal/buffer/{buffer.go => out_message.go} (100%) diff --git a/internal/buffer/buffer.go b/internal/buffer/out_message.go similarity index 100% rename from internal/buffer/buffer.go rename to internal/buffer/out_message.go From b3d13508fbb05cc7b7330e0f23d35ac7e2a73244 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:11:03 +1000 Subject: [PATCH 100/157] Renamed to OutMessage. --- internal/buffer/out_message.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/buffer/out_message.go b/internal/buffer/out_message.go index fe69a79..123a141e 100644 --- a/internal/buffer/out_message.go +++ b/internal/buffer/out_message.go @@ -21,25 +21,25 @@ import ( "github.com/jacobsa/fuse/internal/fusekernel" ) -// Buffer provides a mechanism for constructing a single contiguous fuse +// OutMessage provides a mechanism for constructing a single contiguous fuse // message from multiple segments, where the first segment is always a // fusekernel.OutHeader message. // // Must be created with New. Exception: the zero value has Bytes() == nil. -type Buffer struct { +type OutMessage struct { slice []byte } // Create a new buffer whose initial contents are a zeroed fusekernel.OutHeader // message, and with room enough to grow by extra bytes. -func New(extra uintptr) (b Buffer) { +func NewOutMessage(extra uintptr) (b OutMessage) { const headerSize = unsafe.Sizeof(fusekernel.OutHeader{}) b.slice = make([]byte, headerSize, headerSize+extra) return } // Return a pointer to the header at the start of the buffer. -func (b *Buffer) OutHeader() (h *fusekernel.OutHeader) { +func (b *OutMessage) OutHeader() (h *fusekernel.OutHeader) { sh := (*reflect.SliceHeader)(unsafe.Pointer(&b.slice)) h = (*fusekernel.OutHeader)(unsafe.Pointer(sh.Data)) return @@ -48,7 +48,7 @@ func (b *Buffer) OutHeader() (h *fusekernel.OutHeader) { // Grow the buffer by the supplied number of bytes, returning a pointer to the // start of the new segment. The sum of the arguments given to Grow must not // exceed the argument given to New when creating the buffer. -func (b *Buffer) Grow(size uintptr) (p unsafe.Pointer) { +func (b *OutMessage) Grow(size uintptr) (p unsafe.Pointer) { sh := (*reflect.SliceHeader)(unsafe.Pointer(&b.slice)) p = unsafe.Pointer(sh.Data + uintptr(sh.Len)) b.slice = b.slice[:len(b.slice)+int(size)] @@ -56,7 +56,7 @@ func (b *Buffer) Grow(size uintptr) (p unsafe.Pointer) { } // Equivalent to growing by the length of p, then copying p into the new segment. -func (b *Buffer) Append(p []byte) { +func (b *OutMessage) Append(p []byte) { sh := reflect.SliceHeader{ Data: uintptr(b.Grow(uintptr(len(p)))), Len: len(p), @@ -67,7 +67,7 @@ func (b *Buffer) Append(p []byte) { } // Equivalent to growing by the length of s, then copying s into the new segment. -func (b *Buffer) AppendString(s string) { +func (b *OutMessage) AppendString(s string) { sh := reflect.SliceHeader{ Data: uintptr(b.Grow(uintptr(len(s)))), Len: len(s), @@ -78,6 +78,6 @@ func (b *Buffer) AppendString(s string) { } // Return a reference to the current contents of the buffer. -func (b *Buffer) Bytes() []byte { +func (b *OutMessage) Bytes() []byte { return b.slice } From 34bba99216ee90a864f81a907018b1e88821a6dc Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:12:12 +1000 Subject: [PATCH 101/157] Fixed a bunch of build errors. --- fuseops/common_op.go | 6 ++-- fuseops/ops.go | 86 ++++++++++++++++++++++---------------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/fuseops/common_op.go b/fuseops/common_op.go index 203793c..fb3a224 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -36,7 +36,7 @@ type internalOp interface { // // Special case: a zero return value means that the kernel is not expecting a // response. - kernelResponse() (b buffer.Buffer) + kernelResponse() (b buffer.OutMessage) } // A function that sends a reply message back to the kernel for the request @@ -142,11 +142,11 @@ func (o *commonOp) Respond(err error) { // If successful, we ask the op for an appopriate response to the kernel, and // it is responsible for leaving room for the fusekernel.OutHeader struct. // Otherwise, create our own. - var b buffer.Buffer + var b buffer.OutMessage if err == nil { b = o.op.kernelResponse() } else { - b = buffer.New(0) + b = buffer.NewOutMessage(0) } // Fill in the header if a reply is needed. diff --git a/fuseops/ops.go b/fuseops/ops.go index 97cf058..65f951b 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -88,9 +88,9 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) { return } -func (o *LookUpInodeOp) kernelResponse() (b buffer.Buffer) { +func (o *LookUpInodeOp) kernelResponse() (b buffer.OutMessage) { size := fusekernel.EntryOutSize(o.protocol) - b = buffer.New(size) + b = buffer.NewOutMessage(size) out := (*fusekernel.EntryOut)(b.Grow(size)) convertChildInodeEntry(&o.Entry, out) @@ -123,9 +123,9 @@ func (o *GetInodeAttributesOp) DebugString() string { o.Attributes.DebugString()) } -func (o *GetInodeAttributesOp) kernelResponse() (b buffer.Buffer) { +func (o *GetInodeAttributesOp) kernelResponse() (b buffer.OutMessage) { size := fusekernel.AttrOutSize(o.protocol) - b = buffer.New(size) + b = buffer.NewOutMessage(size) out := (*fusekernel.AttrOut)(b.Grow(size)) out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) convertAttributes(o.Inode, &o.Attributes, &out.Attr) @@ -157,9 +157,9 @@ type SetInodeAttributesOp struct { AttributesExpiration time.Time } -func (o *SetInodeAttributesOp) kernelResponse() (b buffer.Buffer) { +func (o *SetInodeAttributesOp) kernelResponse() (b buffer.OutMessage) { size := fusekernel.AttrOutSize(o.protocol) - b = buffer.New(size) + b = buffer.NewOutMessage(size) out := (*fusekernel.AttrOut)(b.Grow(size)) out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) convertAttributes(o.Inode, &o.Attributes, &out.Attr) @@ -216,7 +216,7 @@ type ForgetInodeOp struct { N uint64 } -func (o *ForgetInodeOp) kernelResponse() (b buffer.Buffer) { +func (o *ForgetInodeOp) kernelResponse() (b buffer.OutMessage) { // No response. return } @@ -259,9 +259,9 @@ func (o *MkDirOp) ShortDesc() (desc string) { return } -func (o *MkDirOp) kernelResponse() (b buffer.Buffer) { +func (o *MkDirOp) kernelResponse() (b buffer.OutMessage) { size := fusekernel.EntryOutSize(o.protocol) - b = buffer.New(size) + b = buffer.NewOutMessage(size) out := (*fusekernel.EntryOut)(b.Grow(size)) convertChildInodeEntry(&o.Entry, out) @@ -311,9 +311,9 @@ func (o *CreateFileOp) ShortDesc() (desc string) { return } -func (o *CreateFileOp) kernelResponse() (b buffer.Buffer) { +func (o *CreateFileOp) kernelResponse() (b buffer.OutMessage) { eSize := fusekernel.EntryOutSize(o.protocol) - b = buffer.New(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) + b = buffer.NewOutMessage(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) e := (*fusekernel.EntryOut)(b.Grow(eSize)) convertChildInodeEntry(&o.Entry, e) @@ -357,9 +357,9 @@ func (o *CreateSymlinkOp) ShortDesc() (desc string) { return } -func (o *CreateSymlinkOp) kernelResponse() (b buffer.Buffer) { +func (o *CreateSymlinkOp) kernelResponse() (b buffer.OutMessage) { size := fusekernel.EntryOutSize(o.protocol) - b = buffer.New(size) + b = buffer.NewOutMessage(size) out := (*fusekernel.EntryOut)(b.Grow(size)) convertChildInodeEntry(&o.Entry, out) @@ -418,8 +418,8 @@ type RenameOp struct { NewName string } -func (o *RenameOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(0) +func (o *RenameOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -439,8 +439,8 @@ type RmDirOp struct { Name string } -func (o *RmDirOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(0) +func (o *RmDirOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -459,8 +459,8 @@ type UnlinkOp struct { Name string } -func (o *UnlinkOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(0) +func (o *UnlinkOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -491,8 +491,8 @@ type OpenDirOp struct { Handle HandleID } -func (o *OpenDirOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(unsafe.Sizeof(fusekernel.OpenOut{})) +func (o *OpenDirOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{})) out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{}))) out.Fh = uint64(o.Handle) @@ -589,8 +589,8 @@ type ReadDirOp struct { Data []byte } -func (o *ReadDirOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(uintptr(len(o.Data))) +func (o *ReadDirOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(uintptr(len(o.Data))) b.Append(o.Data) return } @@ -612,8 +612,8 @@ type ReleaseDirHandleOp struct { Handle HandleID } -func (o *ReleaseDirHandleOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(0) +func (o *ReleaseDirHandleOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -643,8 +643,8 @@ type OpenFileOp struct { Handle HandleID } -func (o *OpenFileOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(unsafe.Sizeof(fusekernel.OpenOut{})) +func (o *OpenFileOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{})) out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{}))) out.Fh = uint64(o.Handle) @@ -680,8 +680,8 @@ type ReadFileOp struct { Data []byte } -func (o *ReadFileOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(uintptr(len(o.Data))) +func (o *ReadFileOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(uintptr(len(o.Data))) b.Append(o.Data) return } @@ -756,8 +756,8 @@ type WriteFileOp struct { Data []byte } -func (o *WriteFileOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(unsafe.Sizeof(fusekernel.WriteOut{})) +func (o *WriteFileOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.WriteOut{})) out := (*fusekernel.WriteOut)(b.Grow(unsafe.Sizeof(fusekernel.WriteOut{}))) out.Size = uint32(len(o.Data)) @@ -788,8 +788,8 @@ type SyncFileOp struct { Handle HandleID } -func (o *SyncFileOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(0) +func (o *SyncFileOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -848,8 +848,8 @@ type FlushFileOp struct { Handle HandleID } -func (o *FlushFileOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(0) +func (o *FlushFileOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -870,8 +870,8 @@ type ReleaseFileHandleOp struct { Handle HandleID } -func (o *ReleaseFileHandleOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(0) +func (o *ReleaseFileHandleOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -888,7 +888,7 @@ func (o *unknownOp) ShortDesc() (desc string) { return } -func (o *unknownOp) kernelResponse() (b buffer.Buffer) { +func (o *unknownOp) kernelResponse() (b buffer.OutMessage) { panic(fmt.Sprintf("Should never get here for unknown op: %s", o.ShortDesc())) } @@ -907,8 +907,8 @@ type ReadSymlinkOp struct { Target string } -func (o *ReadSymlinkOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(uintptr(len(o.Target))) +func (o *ReadSymlinkOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(uintptr(len(o.Target))) b.AppendString(o.Target) return } @@ -929,8 +929,8 @@ type InternalStatFSOp struct { commonOp } -func (o *InternalStatFSOp) kernelResponse() (b buffer.Buffer) { - b = buffer.New(unsafe.Sizeof(fusekernel.StatfsOut{})) +func (o *InternalStatFSOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.StatfsOut{})) b.Grow(unsafe.Sizeof(fusekernel.StatfsOut{})) return @@ -942,6 +942,6 @@ type InternalInterruptOp struct { FuseID uint64 } -func (o *InternalInterruptOp) kernelResponse() (b buffer.Buffer) { +func (o *InternalInterruptOp) kernelResponse() (b buffer.OutMessage) { panic("Shouldn't get here.") } From 2e8b1546be201032c6a6bf631467961d77118546 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:27:14 +1000 Subject: [PATCH 102/157] Declared InMessage. --- internal/buffer/in_message.go | 54 ++++++++++++++++++++++++++++++++++ internal/buffer/out_message.go | 3 +- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 internal/buffer/in_message.go diff --git a/internal/buffer/in_message.go b/internal/buffer/in_message.go new file mode 100644 index 0000000..26386d6 --- /dev/null +++ b/internal/buffer/in_message.go @@ -0,0 +1,54 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buffer + +import ( + "errors" + "io" + "unsafe" + + "github.com/jacobsa/fuse/internal/fusekernel" +) + +// An incoming message from the kernel, including leading fusekernel.InHeader +// struct. Provides storage for messages and convenient access to their +// contents. +type InMessage struct { +} + +// Initialize with the data read by a single call to r.Read. The first call to +// Consume will consume the bytes directly after the fusekernel.InHeader +// struct. +func (m *InMessage) Init(r io.Reader) (err error) { + err = errors.New("TODO") + return +} + +// Return a reference to the header read in the most recent call to Init. +func (m *InMessage) Header() (h *fusekernel.InHeader) { + panic("TODO") +} + +// Consume the next n bytes from the message, returning a nil pointer if there +// are fewer than n bytes available. +func (m *InMessage) Consume(n uintptr) (p unsafe.Pointer) { + panic("TODO") +} + +// Equivalent to Consume, except returns a slice of bytes. The result will be +// nil if Consume fails. +func (m *InMessage) ConsumeBytes(n uintptr) (b []byte) { + panic("TODO") +} diff --git a/internal/buffer/out_message.go b/internal/buffer/out_message.go index 123a141e..0fe8ef4 100644 --- a/internal/buffer/out_message.go +++ b/internal/buffer/out_message.go @@ -25,7 +25,8 @@ import ( // message from multiple segments, where the first segment is always a // fusekernel.OutHeader message. // -// Must be created with New. Exception: the zero value has Bytes() == nil. +// Must be created with NewOutMessage. Exception: the zero value has +// Bytes() == nil. type OutMessage struct { slice []byte } From 0370159a6813487c50ed52773058ac3622f1c1a6 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:29:31 +1000 Subject: [PATCH 103/157] Export some fields of fuseshim.Conn. --- internal/fuseshim/fuse.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 4748db2..22fde82 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -125,10 +125,10 @@ type Conn struct { 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 + // Rio or Wio is held. + Dev *os.File + Wio sync.RWMutex + Rio sync.RWMutex // Protocol version negotiated with InitRequest/InitResponse. proto fusekernel.Protocol @@ -162,7 +162,7 @@ func Mount(dir string, options ...MountOption) (*Conn, error) { if err != nil { return nil, err } - c.dev = f + c.Dev = f if err := initMount(c, &conf); err != nil { c.Close() @@ -513,16 +513,16 @@ func (malformedMessage) String() string { // 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() + c.Wio.Lock() + defer c.Wio.Unlock() + c.Rio.Lock() + defer c.Rio.Unlock() + return c.Dev.Close() } -// caller must hold wio or rio +// caller must hold Wio or Rio func (c *Conn) fd() int { - return int(c.dev.Fd()) + return int(c.Dev.Fd()) } func (c *Conn) Protocol() fusekernel.Protocol { @@ -536,9 +536,9 @@ func (c *Conn) Protocol() fusekernel.Protocol { func (c *Conn) ReadMessage() (m *Message, err error) { m = getMessage(c) loop: - c.rio.RLock() + c.Rio.RLock() n, err := syscall.Read(c.fd(), m.buf) - c.rio.RUnlock() + c.Rio.RUnlock() if err == syscall.EINTR { // OSXFUSE sends EINTR to userspace when a request interrupt // completed before it got sent to userspace? @@ -1068,8 +1068,8 @@ func (c *Conn) writeToKernel(msg []byte) error { } func (c *Conn) WriteToKernel(msg []byte) error { - c.wio.RLock() - defer c.wio.RUnlock() + c.Wio.RLock() + defer c.Wio.RUnlock() _, err := syscall.Write(c.fd(), msg) return err } From 3b9092aca5719b743494482a69a4d815307370fc Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:33:27 +1000 Subject: [PATCH 104/157] Use InMessage in several places. --- connection.go | 32 +++++++++++++++++++++++-------- fuseops/convert.go | 47 +++++++++++++++++++++++----------------------- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/connection.go b/connection.go index e1d03da..d81fff8 100644 --- a/connection.go +++ b/connection.go @@ -15,6 +15,7 @@ package fuse import ( + "errors" "fmt" "log" "path" @@ -24,6 +25,7 @@ import ( "golang.org/x/net/context" "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/fuse/internal/fusekernel" "github.com/jacobsa/fuse/internal/fuseshim" ) @@ -202,6 +204,21 @@ func (c *Connection) handleInterrupt(fuseID uint64) { cancel() } +func (c *Connection) allocateInMessage() (m *buffer.InMessage) { + panic("TODO") +} + +func (c *Connection) destroyInMessage(m *buffer.InMessage) { + panic("TODO") +} + +// Read the next message from the kernel. The message must later be destroyed +// using destroyInMessage. +func (c *Connection) readMessage() (m *buffer.InMessage, err error) { + err = errors.New("TODO") + return +} + // Read the next op from the kernel process. Return io.EOF if the kernel has // closed the connection. // @@ -212,9 +229,9 @@ func (c *Connection) handleInterrupt(fuseID uint64) { func (c *Connection) ReadOp() (op fuseops.Op, err error) { // Keep going until we find a request we know how to convert. for { - // Read the next message from the fuseshim connection. - var m *fuseshim.Message - m, err = c.wrapped.ReadMessage() + // Read the next message from the kernel. + var m *buffer.InMessage + m, err = c.readMessage() if err != nil { return } @@ -224,7 +241,7 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { c.nextOpID++ // Set up op dependencies. - opCtx := c.beginOp(m.Hdr.Opcode, m.Hdr.Unique) + opCtx := c.beginOp(m.Header().Opcode, m.Header().Unique) var debugLogForOp func(int, string, ...interface{}) if c.debugLogger != nil { @@ -238,12 +255,11 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { fuseID uint64, replyMsg []byte, opErr error) (err error) { - // Make sure we destroy the message, as required by - // fuseshim.Connection.ReadMessage. - defer m.Destroy() + // Make sure we destroy the message, as required by readMessage. + defer c.destroyInMessage(m) // Clean up state for this op. - c.finishOp(m.Hdr.Opcode, m.Hdr.Unique) + c.finishOp(m.Header().Opcode, m.Header().Unique) // Debug logging if c.debugLogger != nil { diff --git a/fuseops/convert.go b/fuseops/convert.go index fb30d8e..220a91b 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -23,6 +23,7 @@ import ( "time" "unsafe" + "github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/fuse/internal/fusekernel" "github.com/jacobsa/fuse/internal/fuseshim" "golang.org/x/net/context" @@ -39,7 +40,7 @@ import ( // responsible for arranging for the message to be destroyed. func Convert( opCtx context.Context, - m *fuseshim.Message, + m *buffer.InMessage, protocol fusekernel.Protocol, debugLogForOp func(int, string, ...interface{}), errorLogger *log.Logger, @@ -47,7 +48,7 @@ func Convert( var co *commonOp var io internalOp - switch m.Hdr.Opcode { + switch m.Header().Opcode { case fusekernel.OpLookup: buf := m.Bytes() n := len(buf) @@ -58,7 +59,7 @@ func Convert( to := &LookUpInodeOp{ protocol: protocol, - Parent: InodeID(m.Hdr.Nodeid), + Parent: InodeID(m.Header().Nodeid), Name: string(buf[:n-1]), } io = to @@ -67,7 +68,7 @@ func Convert( case fusekernel.OpGetattr: to := &GetInodeAttributesOp{ protocol: protocol, - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), } io = to co = &to.commonOp @@ -81,7 +82,7 @@ func Convert( to := &SetInodeAttributesOp{ protocol: protocol, - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), } valid := fusekernel.SetattrValid(in.Valid) @@ -115,7 +116,7 @@ func Convert( } to := &ForgetInodeOp{ - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), N: in.Nlookup, } io = to @@ -138,7 +139,7 @@ func Convert( to := &MkDirOp{ protocol: protocol, - Parent: InodeID(m.Hdr.Nodeid), + Parent: InodeID(m.Header().Nodeid), Name: string(name), // On Linux, vfs_mkdir calls through to the inode with at most @@ -170,7 +171,7 @@ func Convert( to := &CreateFileOp{ protocol: protocol, - Parent: InodeID(m.Hdr.Nodeid), + Parent: InodeID(m.Header().Nodeid), Name: string(name), Mode: fuseshim.FileMode(in.Mode), } @@ -193,7 +194,7 @@ func Convert( to := &CreateSymlinkOp{ protocol: protocol, - Parent: InodeID(m.Hdr.Nodeid), + Parent: InodeID(m.Header().Nodeid), Name: string(newName), Target: string(target), } @@ -224,7 +225,7 @@ func Convert( oldName, newName := names[:i], names[i+1:len(names)-1] to := &RenameOp{ - OldParent: InodeID(m.Hdr.Nodeid), + OldParent: InodeID(m.Header().Nodeid), OldName: string(oldName), NewParent: InodeID(in.Newdir), NewName: string(newName), @@ -241,7 +242,7 @@ func Convert( } to := &UnlinkOp{ - Parent: InodeID(m.Hdr.Nodeid), + Parent: InodeID(m.Header().Nodeid), Name: string(buf[:n-1]), } io = to @@ -256,7 +257,7 @@ func Convert( } to := &RmDirOp{ - Parent: InodeID(m.Hdr.Nodeid), + Parent: InodeID(m.Header().Nodeid), Name: string(buf[:n-1]), } io = to @@ -264,14 +265,14 @@ func Convert( case fusekernel.OpOpen: to := &OpenFileOp{ - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), } io = to co = &to.commonOp case fusekernel.OpOpendir: to := &OpenDirOp{ - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), } io = to co = &to.commonOp @@ -284,7 +285,7 @@ func Convert( } to := &ReadFileOp{ - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), Handle: HandleID(in.Fh), Offset: int64(in.Offset), Size: int(in.Size), @@ -300,7 +301,7 @@ func Convert( } to := &ReadDirOp{ - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), Handle: HandleID(in.Fh), Offset: DirOffset(in.Offset), Size: int(in.Size), @@ -349,7 +350,7 @@ func Convert( } to := &WriteFileOp{ - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), Handle: HandleID(in.Fh), Data: buf, Offset: int64(in.Offset), @@ -365,7 +366,7 @@ func Convert( } to := &SyncFileOp{ - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), Handle: HandleID(in.Fh), } io = to @@ -379,7 +380,7 @@ func Convert( } to := &FlushFileOp{ - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), Handle: HandleID(in.Fh), } io = to @@ -387,7 +388,7 @@ func Convert( case fusekernel.OpReadlink: to := &ReadSymlinkOp{ - Inode: InodeID(m.Hdr.Nodeid), + Inode: InodeID(m.Header().Nodeid), } io = to co = &to.commonOp @@ -412,8 +413,8 @@ func Convert( default: to := &unknownOp{ - opCode: m.Hdr.Opcode, - inode: InodeID(m.Hdr.Nodeid), + opCode: m.Header().Opcode, + inode: InodeID(m.Header().Nodeid), } io = to co = &to.commonOp @@ -422,7 +423,7 @@ func Convert( co.init( opCtx, io, - m.Hdr.Unique, + m.Header().Unique, sendReply, debugLogForOp, errorLogger) From 64f4827424f1924669628e1ee0b63982b7df6b93 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:36:59 +1000 Subject: [PATCH 105/157] Fixed some build errors. --- fuseops/convert.go | 12 +++++++----- internal/buffer/in_message.go | 5 +++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 220a91b..d0ad973 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -50,7 +50,7 @@ func Convert( var io internalOp switch m.Header().Opcode { case fusekernel.OpLookup: - buf := m.Bytes() + buf := m.ConsumeBytes(m.Len()) n := len(buf) if n == 0 || buf[n-1] != '\x00' { err = errors.New("Corrupt OpLookup") @@ -74,8 +74,9 @@ func Convert( co = &to.commonOp case fusekernel.OpSetattr: - in := (*fusekernel.SetattrIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { + type input fusekernel.SetattrIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpSetattr") return } @@ -109,8 +110,9 @@ func Convert( co = &to.commonOp case fusekernel.OpForget: - in := (*fusekernel.ForgetIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { + type input fusekernel.ForgetIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpForget") return } diff --git a/internal/buffer/in_message.go b/internal/buffer/in_message.go index 26386d6..c991b9a 100644 --- a/internal/buffer/in_message.go +++ b/internal/buffer/in_message.go @@ -41,6 +41,11 @@ func (m *InMessage) Header() (h *fusekernel.InHeader) { panic("TODO") } +// Return the number of bytes left to consume. +func (m *InMessage) Len() uintptr { + panic("TODO") +} + // Consume the next n bytes from the message, returning a nil pointer if there // are fewer than n bytes available. func (m *InMessage) Consume(n uintptr) (p unsafe.Pointer) { From eb177d5cf4361b79e3247fa09a78ee77c607c066 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:40:43 +1000 Subject: [PATCH 106/157] Fixed a bunch more. --- fuseops/convert.go | 54 +++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index d0ad973..9f71fb1 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -125,13 +125,13 @@ func Convert( co = &to.commonOp case fusekernel.OpMkdir: - size := fusekernel.MkdirInSize(protocol) - if m.Len() < size { + in := (*fusekernel.MkdirIn)(m.Consume(fusekernel.MkdirInSize(protocol))) + if in == nil { err = errors.New("Corrupt OpMkdir") return } - in := (*fusekernel.MkdirIn)(m.Data()) - name := m.Bytes()[size:] + + name := m.ConsumeBytes(m.Len()) i := bytes.IndexByte(name, '\x00') if i < 0 { err = errors.New("Corrupt OpMkdir") @@ -157,13 +157,13 @@ func Convert( co = &to.commonOp case fusekernel.OpCreate: - size := fusekernel.CreateInSize(protocol) - if m.Len() < size { - err = errors.New("Corrupt OpCreate") + in := (*fusekernel.MkdirIn)(m.Consume(fusekernel.CreateInSize(protocol))) + if in == nil { + err = errors.New("Corrupt OpMkdir") return } - in := (*fusekernel.CreateIn)(m.Data()) - name := m.Bytes()[size:] + + name := m.ConsumeBytes(m.Len()) i := bytes.IndexByte(name, '\x00') if i < 0 { err = errors.New("Corrupt OpCreate") @@ -181,8 +181,8 @@ func Convert( co = &to.commonOp case fusekernel.OpSymlink: - // m.Bytes() is "newName\0target\0" - names := m.Bytes() + // The message is "newName\0target\0". + names := m.ConsumeBytes(m.Len()) if len(names) == 0 || names[len(names)-1] != 0 { err = errors.New("Corrupt OpSymlink") return @@ -204,12 +204,14 @@ func Convert( co = &to.commonOp case fusekernel.OpRename: - in := (*fusekernel.RenameIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { + type input fusekernel.RenameIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpRename") return } - names := m.Bytes()[unsafe.Sizeof(*in):] + + names := m.ConsumeBytes(m.Len()) // names should be "old\x00new\x00" if len(names) < 4 { err = errors.New("Corrupt OpRename") @@ -236,7 +238,7 @@ func Convert( co = &to.commonOp case fusekernel.OpUnlink: - buf := m.Bytes() + buf := m.ConsumeBytes(m.Len()) n := len(buf) if n == 0 || buf[n-1] != '\x00' { err = errors.New("Corrupt OpUnlink") @@ -251,7 +253,7 @@ func Convert( co = &to.commonOp case fusekernel.OpRmdir: - buf := m.Bytes() + buf := m.ConsumeBytes(m.Len()) n := len(buf) if n == 0 || buf[n-1] != '\x00' { err = errors.New("Corrupt OpRmdir") @@ -280,8 +282,9 @@ func Convert( co = &to.commonOp case fusekernel.OpRead: - in := (*fusekernel.ReadIn)(m.Data()) - if m.Len() < fusekernel.ReadInSize(protocol) { + type input fusekernel.ReadIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpRead") return } @@ -296,8 +299,9 @@ func Convert( co = &to.commonOp case fusekernel.OpReaddir: - in := (*fusekernel.ReadIn)(m.Data()) - if m.Len() < fusekernel.ReadInSize(protocol) { + type input fusekernel.ReadIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpReaddir") return } @@ -312,8 +316,9 @@ func Convert( co = &to.commonOp case fusekernel.OpRelease: - in := (*fusekernel.ReleaseIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { + type input fusekernel.ReleaseIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpRelease") return } @@ -325,8 +330,9 @@ func Convert( co = &to.commonOp case fusekernel.OpReleasedir: - in := (*fusekernel.ReleaseIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { + type input fusekernel.ReleaseIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpReleasedir") return } From ca4f55538f366b3222adf277cf087a9f7a9c564c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:41:59 +1000 Subject: [PATCH 107/157] Fixed remaining build errors. --- fuseops/convert.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 9f71fb1..ae72784 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -344,14 +344,13 @@ func Convert( co = &to.commonOp case fusekernel.OpWrite: - in := (*fusekernel.WriteIn)(m.Data()) - size := fusekernel.WriteInSize(protocol) - if m.Len() < size { + in := (*fusekernel.WriteIn)(m.Consume(fusekernel.WriteInSize(protocol))) + if in == nil { err = errors.New("Corrupt OpWrite") return } - buf := m.Bytes()[size:] + buf := m.ConsumeBytes(m.Len()) if len(buf) < int(in.Size) { err = errors.New("Corrupt OpWrite") return @@ -367,8 +366,9 @@ func Convert( co = &to.commonOp case fusekernel.OpFsync: - in := (*fusekernel.FsyncIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { + type input fusekernel.FsyncIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpFsync") return } @@ -381,8 +381,9 @@ func Convert( co = &to.commonOp case fusekernel.OpFlush: - in := (*fusekernel.FlushIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { + type input fusekernel.FlushIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpFlush") return } @@ -407,8 +408,9 @@ func Convert( co = &to.commonOp case fusekernel.OpInterrupt: - in := (*fusekernel.InterruptIn)(m.Data()) - if m.Len() < unsafe.Sizeof(*in) { + type input fusekernel.InterruptIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { err = errors.New("Corrupt OpInterrupt") return } From b8110bf8a7a593420ea4339def600fc1810c18a1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:45:47 +1000 Subject: [PATCH 108/157] Added a buffer field. --- internal/buffer/in_message.go | 18 ++++++++++++++++++ internal/buffer/in_message_darwin.go | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 internal/buffer/in_message_darwin.go diff --git a/internal/buffer/in_message.go b/internal/buffer/in_message.go index c991b9a..2611f9d 100644 --- a/internal/buffer/in_message.go +++ b/internal/buffer/in_message.go @@ -16,16 +16,34 @@ package buffer import ( "errors" + "fmt" "io" + "syscall" "unsafe" "github.com/jacobsa/fuse/internal/fusekernel" ) +// All requests read from the kernel, without data, are shorter than +// this. +const pageSize = 4096 + +func init() { + // Confirm the page size. + if syscall.Getpagesize() != pageSize { + panic(fmt.Sprintf("Page size is unexpectedly %d", syscall.Getpagesize())) + } +} + +// We size the buffer to have enough room for a fuse request plus data +// associated with a write request. +const bufSize = pageSize + MaxWriteSize + // An incoming message from the kernel, including leading fusekernel.InHeader // struct. Provides storage for messages and convenient access to their // contents. type InMessage struct { + buf [bufSize]byte } // Initialize with the data read by a single call to r.Read. The first call to diff --git a/internal/buffer/in_message_darwin.go b/internal/buffer/in_message_darwin.go new file mode 100644 index 0000000..d1f6b9f --- /dev/null +++ b/internal/buffer/in_message_darwin.go @@ -0,0 +1,20 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buffer + +// The maximum fuse write request size that InMessage can acommodate. +// +// Experimentally, OS X appears to cap the size of writes to 1 MiB. +const MaxWriteSize = 1 << 20 From d93a81b046e4eb1b48559bec350b25d763847ccd Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:46:45 +1000 Subject: [PATCH 109/157] Defined the constant for Linux, too. --- internal/buffer/in_message_darwin.go | 3 ++- internal/buffer/in_message_linux.go | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 internal/buffer/in_message_linux.go diff --git a/internal/buffer/in_message_darwin.go b/internal/buffer/in_message_darwin.go index d1f6b9f..af37a02 100644 --- a/internal/buffer/in_message_darwin.go +++ b/internal/buffer/in_message_darwin.go @@ -16,5 +16,6 @@ package buffer // The maximum fuse write request size that InMessage can acommodate. // -// Experimentally, OS X appears to cap the size of writes to 1 MiB. +// Experimentally, OS X appears to cap the size of writes to 1 MiB, regardless +// of whether a larger size is specified in the mount options. const MaxWriteSize = 1 << 20 diff --git a/internal/buffer/in_message_linux.go b/internal/buffer/in_message_linux.go new file mode 100644 index 0000000..964c7da --- /dev/null +++ b/internal/buffer/in_message_linux.go @@ -0,0 +1,21 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buffer + +// The maximum fuse write request size that InMessage can acommodate. +// +// Experimentally, Linux appears to refuse to honor a MaxWrite setting in an +// INIT response of more than 128 KiB. +const MaxWriteSize = 1 << 17 From e0d989cba30a931ba1f7efb304ae267541ea2c48 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:52:54 +1000 Subject: [PATCH 110/157] InMessage.Header --- internal/buffer/in_message.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/buffer/in_message.go b/internal/buffer/in_message.go index 2611f9d..c4a5107 100644 --- a/internal/buffer/in_message.go +++ b/internal/buffer/in_message.go @@ -43,7 +43,8 @@ const bufSize = pageSize + MaxWriteSize // struct. Provides storage for messages and convenient access to their // contents. type InMessage struct { - buf [bufSize]byte + remaining []byte + storage [bufSize]byte } // Initialize with the data read by a single call to r.Read. The first call to @@ -56,7 +57,8 @@ func (m *InMessage) Init(r io.Reader) (err error) { // Return a reference to the header read in the most recent call to Init. func (m *InMessage) Header() (h *fusekernel.InHeader) { - panic("TODO") + h = (*fusekernel.InHeader)(unsafe.Pointer(&m.storage[0])) + return } // Return the number of bytes left to consume. From b550b092fe6f9cee38d79fc0db407d921f6fddd1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:55:35 +1000 Subject: [PATCH 111/157] Implemented some other InMessage methods. --- internal/buffer/in_message.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/internal/buffer/in_message.go b/internal/buffer/in_message.go index c4a5107..eb4ab7a 100644 --- a/internal/buffer/in_message.go +++ b/internal/buffer/in_message.go @@ -63,17 +63,31 @@ func (m *InMessage) Header() (h *fusekernel.InHeader) { // Return the number of bytes left to consume. func (m *InMessage) Len() uintptr { - panic("TODO") + return uintptr(len(m.remaining)) } // Consume the next n bytes from the message, returning a nil pointer if there // are fewer than n bytes available. func (m *InMessage) Consume(n uintptr) (p unsafe.Pointer) { - panic("TODO") + if m.Len() == 0 || n > m.Len() { + return + } + + p = unsafe.Pointer(&m.remaining[0]) + m.remaining = m.remaining[n:] + + return } // Equivalent to Consume, except returns a slice of bytes. The result will be -// nil if Consume fails. +// nil if Consume would fail. func (m *InMessage) ConsumeBytes(n uintptr) (b []byte) { - panic("TODO") + if n > m.Len() { + return + } + + b = m.remaining[:n] + m.remaining = m.remaining[n:] + + return } From 9ea4360f19cdaedeb1bb66ae59b6e8d11886084a Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 14:57:18 +1000 Subject: [PATCH 112/157] InMessage.Init --- internal/buffer/in_message.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/buffer/in_message.go b/internal/buffer/in_message.go index eb4ab7a..ee836fc 100644 --- a/internal/buffer/in_message.go +++ b/internal/buffer/in_message.go @@ -15,7 +15,6 @@ package buffer import ( - "errors" "fmt" "io" "syscall" @@ -51,7 +50,17 @@ type InMessage struct { // Consume will consume the bytes directly after the fusekernel.InHeader // struct. func (m *InMessage) Init(r io.Reader) (err error) { - err = errors.New("TODO") + n, err := r.Read(m.storage[:]) + if err != nil { + return + } + + if uintptr(n) < unsafe.Sizeof(fusekernel.InHeader{}) { + err = fmt.Errorf("Unexpectedly read only %d bytes.", n) + return + } + + m.remaining = m.storage[:n] return } From f2e8a5d8068dce478b0287ca54d401af96e268f0 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:04:28 +1000 Subject: [PATCH 113/157] Connection.readMessage --- connection.go | 24 +++++++++++++++++++++--- internal/buffer/in_message.go | 11 +++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/connection.go b/connection.go index d81fff8..c48e79f 100644 --- a/connection.go +++ b/connection.go @@ -15,7 +15,6 @@ package fuse import ( - "errors" "fmt" "log" "path" @@ -215,8 +214,27 @@ func (c *Connection) destroyInMessage(m *buffer.InMessage) { // Read the next message from the kernel. The message must later be destroyed // using destroyInMessage. func (c *Connection) readMessage() (m *buffer.InMessage, err error) { - err = errors.New("TODO") - return + // Allocate a message. + m = c.allocateInMessage() + + // Loop past transient errors. + for { + // Lock and read. + // + // TODO(jacobsa): Ensure that we document concurrency constraints that make + // it safe, then kill the lock here. + c.wrapped.Rio.RLock() + err = m.Init(c.wrapped.Dev) + c.wrapped.Rio.RUnlock() + + if err != nil { + c.destroyInMessage(m) + m = nil + return + } + + return + } } // Read the next op from the kernel process. Return io.EOF if the kernel has diff --git a/internal/buffer/in_message.go b/internal/buffer/in_message.go index ee836fc..72ea5c3 100644 --- a/internal/buffer/in_message.go +++ b/internal/buffer/in_message.go @@ -55,11 +55,22 @@ func (m *InMessage) Init(r io.Reader) (err error) { return } + // Make sure the message is long enough that calling Header is safe. if uintptr(n) < unsafe.Sizeof(fusekernel.InHeader{}) { err = fmt.Errorf("Unexpectedly read only %d bytes.", n) return } + // Check the header's length. + if int(m.Header().Len) != n { + err = fmt.Errorf( + "Header says %d bytes, but we read %d", + m.Header().Len, + n) + + return + } + m.remaining = m.storage[:n] return } From 7a5761a67cca894f076bcc034c4d2d59b0e1a68e Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:05:27 +1000 Subject: [PATCH 114/157] Fixed some TODOs. --- connection.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/connection.go b/connection.go index c48e79f..e93a996 100644 --- a/connection.go +++ b/connection.go @@ -204,11 +204,13 @@ func (c *Connection) handleInterrupt(fuseID uint64) { } func (c *Connection) allocateInMessage() (m *buffer.InMessage) { - panic("TODO") + // TODO(jacobsa): Use a freelist. + m = new(buffer.InMessage) + return } func (c *Connection) destroyInMessage(m *buffer.InMessage) { - panic("TODO") + // TODO(jacobsa): Use a freelist. } // Read the next message from the kernel. The message must later be destroyed From a1dadab66e6b17e16853cd303f322f1dc01a713a Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:07:55 +1000 Subject: [PATCH 115/157] Fixed a bug. --- connection.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/connection.go b/connection.go index e93a996..b9c027d 100644 --- a/connection.go +++ b/connection.go @@ -16,10 +16,13 @@ package fuse import ( "fmt" + "io" "log" + "os" "path" "runtime" "sync" + "syscall" "golang.org/x/net/context" @@ -232,6 +235,12 @@ func (c *Connection) readMessage() (m *buffer.InMessage, err error) { if err != nil { c.destroyInMessage(m) m = nil + + // Special case: ENODEV means fuse has hung up. + if pe, ok := err.(*os.PathError); ok && pe.Err == syscall.ENODEV { + err = io.EOF + } + return } From 59962d69e6891711354c479b6b835741509f161e Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:10:28 +1000 Subject: [PATCH 116/157] Fixed a bug. --- internal/buffer/in_message.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/buffer/in_message.go b/internal/buffer/in_message.go index 72ea5c3..f87f3b0 100644 --- a/internal/buffer/in_message.go +++ b/internal/buffer/in_message.go @@ -55,12 +55,15 @@ func (m *InMessage) Init(r io.Reader) (err error) { return } - // Make sure the message is long enough that calling Header is safe. - if uintptr(n) < unsafe.Sizeof(fusekernel.InHeader{}) { + // Make sure the message is long enough. + const headerSize = unsafe.Sizeof(fusekernel.InHeader{}) + if uintptr(n) < headerSize { err = fmt.Errorf("Unexpectedly read only %d bytes.", n) return } + m.remaining = m.storage[headerSize:n] + // Check the header's length. if int(m.Header().Len) != n { err = fmt.Errorf( @@ -71,7 +74,6 @@ func (m *InMessage) Init(r io.Reader) (err error) { return } - m.remaining = m.storage[:n] return } From 36b7b6ab0e766a120dec385b2fb24660b423fbfe Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:11:31 +1000 Subject: [PATCH 117/157] Fixed a bug. --- fuseops/convert.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index ae72784..11fd2cc 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -282,8 +282,7 @@ func Convert( co = &to.commonOp case fusekernel.OpRead: - type input fusekernel.ReadIn - in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol))) if in == nil { err = errors.New("Corrupt OpRead") return @@ -299,8 +298,7 @@ func Convert( co = &to.commonOp case fusekernel.OpReaddir: - type input fusekernel.ReadIn - in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol))) if in == nil { err = errors.New("Corrupt OpReaddir") return From 816f77d732847948ca25bd801255e93ef5f1c7fd Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:13:26 +1000 Subject: [PATCH 118/157] Fixed a bug. --- fuseops/convert.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 11fd2cc..0f265cd 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -157,9 +157,9 @@ func Convert( co = &to.commonOp case fusekernel.OpCreate: - in := (*fusekernel.MkdirIn)(m.Consume(fusekernel.CreateInSize(protocol))) + in := (*fusekernel.CreateIn)(m.Consume(fusekernel.CreateInSize(protocol))) if in == nil { - err = errors.New("Corrupt OpMkdir") + err = errors.New("Corrupt OpCreate") return } From 4de334e034a014cf4c5c2cfe4c6578397fd6153a Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:20:43 +1000 Subject: [PATCH 119/157] Fixed a bug related to EINTR. --- connection.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/connection.go b/connection.go index b9c027d..2d5d9ee 100644 --- a/connection.go +++ b/connection.go @@ -232,15 +232,27 @@ func (c *Connection) readMessage() (m *buffer.InMessage, err error) { err = m.Init(c.wrapped.Dev) c.wrapped.Rio.RUnlock() + // Special cases: + // + // * ENODEV means fuse has hung up. + // + // * EINTR means we should try again. (This seems to happen often on + // OS X, cf. http://golang.org/issue/11180) + // + if pe, ok := err.(*os.PathError); ok { + switch pe.Err { + case syscall.ENODEV: + err = io.EOF + + case syscall.EINTR: + err = nil + continue + } + } + if err != nil { c.destroyInMessage(m) m = nil - - // Special case: ENODEV means fuse has hung up. - if pe, ok := err.(*os.PathError); ok && pe.Err == syscall.ENODEV { - err = io.EOF - } - return } From 876746fcd20d23ffa85c1210713eaa196275df99 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:23:11 +1000 Subject: [PATCH 120/157] Don't depend on fuseshim.FileMode. --- fuseops/convert.go | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/fuseops/convert.go b/fuseops/convert.go index 0f265cd..b8bee74 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -25,7 +25,6 @@ import ( "github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/fuse/internal/fusekernel" - "github.com/jacobsa/fuse/internal/fuseshim" "golang.org/x/net/context" ) @@ -92,7 +91,7 @@ func Convert( } if valid&fusekernel.SetattrMode != 0 { - mode := fuseshim.FileMode(in.Mode) + mode := convertFileMode(in.Mode) to.Mode = &mode } @@ -150,7 +149,7 @@ func Convert( // the fact that this is a directory is implicit in the fact that the // opcode is mkdir. But we want the correct mode to go through, so ensure // that os.ModeDir is set. - Mode: fuseshim.FileMode(in.Mode) | os.ModeDir, + Mode: convertFileMode(in.Mode) | os.ModeDir, } io = to @@ -175,7 +174,7 @@ func Convert( protocol: protocol, Parent: InodeID(m.Header().Nodeid), Name: string(name), - Mode: fuseshim.FileMode(in.Mode), + Mode: convertFileMode(in.Mode), } io = to co = &to.commonOp @@ -509,3 +508,33 @@ func convertChildInodeEntry( convertAttributes(in.Child, &in.Attributes, &out.Attr) } + +func convertFileMode(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 +} From 32418239aedb3367984845facdd6721c22fff18b Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:26:07 +1000 Subject: [PATCH 121/157] Removed read locks around the device. We require no conrrent calls to ReadOp, and that ServeOps doesn't return until all ops have been responded to, so I believe this should be safe. In particular, fuseshim uses the locks only to exclude reads and writes during closing, not for anything else. --- connection.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/connection.go b/connection.go index 2d5d9ee..776de3e 100644 --- a/connection.go +++ b/connection.go @@ -224,13 +224,8 @@ func (c *Connection) readMessage() (m *buffer.InMessage, err error) { // Loop past transient errors. for { - // Lock and read. - // - // TODO(jacobsa): Ensure that we document concurrency constraints that make - // it safe, then kill the lock here. - c.wrapped.Rio.RLock() + // Attempt a reaed. err = m.Init(c.wrapped.Dev) - c.wrapped.Rio.RUnlock() // Special cases: // From ee2b961839c49f61c0d565653a29c916713b70af Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:31:16 +1000 Subject: [PATCH 122/157] Don't depend on fuseshim.Conn for sending messages. --- connection.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/connection.go b/connection.go index 776de3e..7380d78 100644 --- a/connection.go +++ b/connection.go @@ -255,6 +255,22 @@ func (c *Connection) readMessage() (m *buffer.InMessage, err error) { } } +// Write the supplied message to the kernel. +func (c *Connection) writeMessage(msg []byte) (err error) { + // Avoid the retry loop in os.File.Write. + n, err := syscall.Write(int(c.wrapped.Dev.Fd()), msg) + if err != nil { + return + } + + if n != len(msg) { + err = fmt.Errorf("Wrote %d bytes; expected %d", n, len(msg)) + return + } + + return +} + // Read the next op from the kernel process. Return io.EOF if the kernel has // closed the connection. // @@ -312,9 +328,9 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { } // Send the reply to the kernel. - err = c.wrapped.WriteToKernel(replyMsg) + err = c.writeMessage(replyMsg) if err != nil { - err = fmt.Errorf("WriteToKernel: %v", err) + err = fmt.Errorf("writeMessage: %v", err) return } From cd47dbb4b8069385e038805bc187f998d8ba3fd9 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:32:50 +1000 Subject: [PATCH 123/157] Refactored fields a bit. --- connection.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/connection.go b/connection.go index 7380d78..964c48d 100644 --- a/connection.go +++ b/connection.go @@ -38,6 +38,11 @@ type Connection struct { errorLogger *log.Logger wrapped *fuseshim.Conn + // The device through which we're talking to the kernel, and the protocol + // version that we're using to talk to it. + dev *os.File + protocol fusekernel.Protocol + // The context from which all op contexts inherit. parentCtx context.Context @@ -66,6 +71,8 @@ func newConnection( debugLogger: debugLogger, errorLogger: errorLogger, wrapped: wrapped, + dev: wrapped.Dev, + protocol: wrapped.Protocol(), parentCtx: parentCtx, cancelFuncs: make(map[uint64]func()), } @@ -225,7 +232,7 @@ func (c *Connection) readMessage() (m *buffer.InMessage, err error) { // Loop past transient errors. for { // Attempt a reaed. - err = m.Init(c.wrapped.Dev) + err = m.Init(c.dev) // Special cases: // @@ -258,7 +265,7 @@ func (c *Connection) readMessage() (m *buffer.InMessage, err error) { // Write the supplied message to the kernel. func (c *Connection) writeMessage(msg []byte) (err error) { // Avoid the retry loop in os.File.Write. - n, err := syscall.Write(int(c.wrapped.Dev.Fd()), msg) + n, err := syscall.Write(int(c.dev.Fd()), msg) if err != nil { return } @@ -341,7 +348,7 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { op, err = fuseops.Convert( opCtx, m, - c.wrapped.Protocol(), + c.protocol, debugLogForOp, c.errorLogger, sendReply) From a721a505bf747b3b6505a5f135c04d39197fba86 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:35:31 +1000 Subject: [PATCH 124/157] Document the reason for a restriction. --- connection.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/connection.go b/connection.go index 964c48d..4e83b8b 100644 --- a/connection.go +++ b/connection.go @@ -388,6 +388,9 @@ func (c *Connection) waitForReady() (err error) { // Close the connection. Must not be called until operations that were read // from the connection have been responded to. func (c *Connection) close() (err error) { - err = c.wrapped.Close() + // Posix doesn't say that close can be called concurrently with read or + // write, but luckily we exclude the possibility of a race by requiring the + // user to respond to all ops first. + err = c.dev.Close() return } From 89c74b0dc8a031c55de857840b2fba53e40febd5 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:40:31 +1000 Subject: [PATCH 125/157] Upated the license file. --- LICENSE | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/LICENSE b/LICENSE index e06d208..ffe99f1 100644 --- a/LICENSE +++ b/LICENSE @@ -200,3 +200,101 @@ Apache License See the License for the specific language governing permissions and limitations under the License. + +========================================================================== +Portions of this package were adopted from bazil.org/fuse, which contains the +following license notice. + +Copyright (c) 2013-2015 Tommi Virtanen. +Copyright (c) 2009, 2011, 2012 The Go Authors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * 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. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE COPYRIGHT +OWNER 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. + + + +The following included software components have additional copyright +notices and license terms that may differ from the above. + + +File fuse.go: + +// 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. + + +File fuse_kernel.go: + +// 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. +*/ From 695b70b1e63da3b0ad1f6f1afe0d23d5fe5cce29 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:39:04 +1000 Subject: [PATCH 126/157] Pasted the implementation from bazilfuse. --- mount_darwin.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 mount_darwin.go diff --git a/mount_darwin.go b/mount_darwin.go new file mode 100644 index 0000000..cf8646f --- /dev/null +++ b/mount_darwin.go @@ -0,0 +1,131 @@ +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 +} From 56758cb302096f1173274d39c434117622e66de1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:45:16 +1000 Subject: [PATCH 127/157] Gave mount a makeover. --- mount_darwin.go | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index cf8646f..1f9bc55 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -1,4 +1,4 @@ -package fuseshim +package fuse import ( "bytes" @@ -109,23 +109,42 @@ func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, return err } -func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { - f, err := openOSXFUSEDev() +// Begin the process of mounting at the given directory, returning a connection +// to the kernel. Mounting continues in the background, and is complete when an +// error is written to the supplied channel. The file system may need to +// service the connection in order for mounting to complete. +func mount( + dir string, + conf *mountConfig, + ready chan<- error) (dev *os.File, err error) { + // Open the device. + dev, err = openOSXFUSEDev() + + // Special case: we may need to explicitly load osxfuse. Load it, then try + // again. if err == errNotLoaded { err = loadOSXFUSE() if err != nil { - return nil, err + err = fmt.Errorf("loadOSXFUSE: %v", err) + return } - // try again - f, err = openOSXFUSEDev() + + dev, err = openOSXFUSEDev() } + + // Propagate errors. if err != nil { - return nil, err + err = fmt.Errorf("openOSXFUSEDev: %v", err) + return } - err = callMount(dir, conf, f, ready, errp) + + // Call the mount binary with the device. + err = callMount(dir, conf, dev, ready) if err != nil { - f.Close() - return nil, err + dev.Close() + err = fmt.Errorf("callMount: %v", err) + return } - return f, nil + + return } From 1bb0fd472497dcc36d3e586e6450878ec2d51420 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:45:34 +1000 Subject: [PATCH 128/157] Trimmed a bit. --- mount_darwin.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index 1f9bc55..4980396 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -11,11 +11,6 @@ import ( "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") From eda994c2e66a9dc298c24b8ebdb7fad2b08f7485 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:48:17 +1000 Subject: [PATCH 129/157] Gave openOSXFUSEDev a makeover. --- mount_darwin.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index 4980396..c540228 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -24,31 +24,29 @@ func loadOSXFUSE() error { return err } -func openOSXFUSEDev() (*os.File, error) { - var f *os.File - var err error +func openOSXFUSEDev() (dev *os.File, err error) { + // Try each device name. for i := uint64(0); ; i++ { - path := "/dev/osxfuse" + strconv.FormatUint(i, 10) - f, err = os.OpenFile(path, os.O_RDWR, 0000) + path := fmt.Sprintf("/dev/osxfuse%d", i) + dev, 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 + // Not even the first device was found. Fuse must not be loaded. + err = errNotLoaded + return } - // we've run out of kernel-provided devices - return nil, errNoAvail + // Otherwise we've run out of kernel-provided devices + err = errNoAvail + return } if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY { - // try the next one + // This device is in use; try the next one. continue } - if err != nil { - return nil, err - } - return f, nil + return } } From a9693474e8b68e2bb96fc564f90f07fa2a767182 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:48:23 +1000 Subject: [PATCH 130/157] Fixed a build error. --- mount_darwin.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index c540228..ababb03 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -9,10 +9,11 @@ import ( "strconv" "strings" "syscall" + + "github.com/jacobsa/fuse/internal/buffer" ) var errNoAvail = errors.New("no available fuse devices") - var errNotLoaded = errors.New("osxfusefs is not loaded") func loadOSXFUSE() error { @@ -68,7 +69,7 @@ func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, // // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses // this instead. - "-o", "iosize="+strconv.FormatUint(maxWrite, 10), + "-o", "iosize="+strconv.FormatUint(buffer.MaxWriteSize, 10), // refers to fd passed in cmd.ExtraFiles "3", dir, From 19934c68be62c2b1d2a1addbfe3a69d2d148c67c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:51:18 +1000 Subject: [PATCH 131/157] Touched up callMount. --- mount_darwin.go | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index ababb03..41137cd 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -51,16 +51,25 @@ func openOSXFUSEDev() (dev *os.File, err error) { } } -func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error { - bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" +func callMount( + dir string, + cfg *mountConfig, + dev *os.File, + ready chan<- error) (err error) { + const bin = "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" + // The mount helper doesn't understand any escaping. 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) + return fmt.Errorf( + "mount options cannot contain commas on darwin: %q=%q", + k, + v) } } + + // Call the mount helper, passing in the device file and saving output into a + // buffer. cmd := exec.Command( bin, "-o", conf.getOptions(), @@ -74,33 +83,36 @@ func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, "3", dir, ) - cmd.ExtraFiles = []*os.File{f} + cmd.ExtraFiles = []*os.File{dev} 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() + err = cmd.Start() if err != nil { - return err + return } + + // In the background, wait for the command to complete. 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) + err = fmt.Errorf("%v: %s", err, output) } } - *errp = err - close(ready) + + ready <- err }() - return err + + return } // Begin the process of mounting at the given directory, returning a connection From cd6c68c83f573d2478b6291b7df546021df971f3 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:54:02 +1000 Subject: [PATCH 132/157] Fixed some build errors. --- mount_darwin.go | 10 +++++----- mounted_file_system.go | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/mount_darwin.go b/mount_darwin.go index 41137cd..428b73a 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -53,13 +53,13 @@ func openOSXFUSEDev() (dev *os.File, err error) { func callMount( dir string, - cfg *mountConfig, + cfg *MountConfig, dev *os.File, ready chan<- error) (err error) { const bin = "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" // The mount helper doesn't understand any escaping. - for k, v := range conf.options { + for k, v := range cfg.toMap() { if strings.Contains(k, ",") || strings.Contains(v, ",") { return fmt.Errorf( "mount options cannot contain commas on darwin: %q=%q", @@ -72,7 +72,7 @@ func callMount( // buffer. cmd := exec.Command( bin, - "-o", conf.getOptions(), + "-o", cfg.toOptionsString(), // Tell osxfuse-kext how large our buffer is. It must split // writes larger than this into multiple writes. // @@ -121,7 +121,7 @@ func callMount( // service the connection in order for mounting to complete. func mount( dir string, - conf *mountConfig, + cfg *MountConfig, ready chan<- error) (dev *os.File, err error) { // Open the device. dev, err = openOSXFUSEDev() @@ -145,7 +145,7 @@ func mount( } // Call the mount binary with the device. - err = callMount(dir, conf, dev, ready) + err = callMount(dir, cfg, dev, ready) if err != nil { dev.Close() err = fmt.Errorf("callMount: %v", err) diff --git a/mounted_file_system.go b/mounted_file_system.go index 4a57ec6..2d4ab4e 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -112,6 +112,17 @@ type MountConfig struct { Options map[string]string } +// Create a map containing all of the key=value mount options to be given to +// the mount helper. +func (c *MountConfig) toMap() (opts map[string]string) { + panic("TODO") +} + +// Create an options string suitable for passing to the mount helper. +func (c *MountConfig) toOptionsString() string { + panic("TODO") +} + // Convert to mount options to be passed to package fuseshim. func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { isDarwin := runtime.GOOS == "darwin" From d2b9accc31805aed67ca858db591e4d71a3bc7a6 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:55:03 +1000 Subject: [PATCH 133/157] Moved MountConfig into a separate file. --- mount_config.go | 158 +++++++++++++++++++++++++++++++++++++++++ mounted_file_system.go | 136 ----------------------------------- 2 files changed, 158 insertions(+), 136 deletions(-) create mode 100644 mount_config.go diff --git a/mount_config.go b/mount_config.go new file mode 100644 index 0000000..8081591 --- /dev/null +++ b/mount_config.go @@ -0,0 +1,158 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fuse + +import ( + "log" + "runtime" + + "github.com/jacobsa/fuse/internal/fuseshim" + + "golang.org/x/net/context" +) + +// Optional configuration accepted by Mount. +type MountConfig struct { + // The context from which every op read from the connetion by the sever + // should inherit. If nil, context.Background() will be used. + OpContext context.Context + + // If non-empty, the name of the file system as displayed by e.g. `mount`. + // This is important because the `umount` command requires root privileges if + // it doesn't agree with /etc/fstab. + FSName string + + // Mount the file system in read-only mode. File modes will appear as normal, + // but opening a file for writing and metadata operations like chmod, + // chtimes, etc. will fail. + ReadOnly bool + + // A logger to use for logging errors. All errors are logged, with the + // exception of a few blacklisted errors that are expected. If nil, no error + // logging is performed. + ErrorLogger *log.Logger + + // A logger to use for logging debug information. If nil, no debug logging is + // performed. + DebugLogger *log.Logger + + // OS X only. + // + // Normally on OS X we mount with the novncache option + // (cf. http://goo.gl/1pTjuk), which disables entry caching in the kernel. + // This is because osxfuse does not honor the entry expiration values we + // return to it, instead caching potentially forever (cf. + // http://goo.gl/8yR0Ie), and it is probably better to fail to cache than to + // cache for too long, since the latter is more likely to hide consistency + // bugs that are difficult to detect and diagnose. + // + // This field disables the use of novncache, restoring entry caching. Beware: + // the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and + // entries will be cached for an arbitrarily long time. + EnableVnodeCaching bool + + // Additional key=value options to pass unadulterated to the underlying mount + // command. See `man 8 mount`, the fuse documentation, etc. for + // system-specific information. + // + // For expert use only! May invalidate other guarantees made in the + // documentation for this package. + Options map[string]string +} + +// Create a map containing all of the key=value mount options to be given to +// the mount helper. +func (c *MountConfig) toMap() (opts map[string]string) { + panic("TODO") +} + +// Create an options string suitable for passing to the mount helper. +func (c *MountConfig) toOptionsString() string { + panic("TODO") +} + +// Convert to mount options to be passed to package fuseshim. +func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { + isDarwin := runtime.GOOS == "darwin" + + // Enable permissions checking in the kernel. See the comments on + // InodeAttributes.Mode. + opts = append(opts, fuseshim.SetOption("default_permissions", "")) + + // HACK(jacobsa): Work around what appears to be a bug in systemd v219, as + // shipped in Ubuntu 15.04, where it automatically unmounts any file system + // that doesn't set an explicit name. + // + // When Ubuntu contains systemd v220, this workaround should be removed and + // the systemd bug reopened if the problem persists. + // + // Cf. https://github.com/bazil/fuse/issues/89 + // Cf. https://bugs.freedesktop.org/show_bug.cgi?id=90907 + fsname := c.FSName + if runtime.GOOS == "linux" && fsname == "" { + fsname = "some_fuse_file_system" + } + + // Special file system name? + if fsname != "" { + opts = append(opts, fuseshim.FSName(fsname)) + } + + // Read only? + if c.ReadOnly { + opts = append(opts, fuseshim.ReadOnly()) + } + + // OS X: set novncache when appropriate. + if isDarwin && !c.EnableVnodeCaching { + opts = append(opts, fuseshim.SetOption("novncache", "")) + } + + // OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which + // just add noise to debug output and can have significant cost on + // network-based file systems. + // + // Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options + if isDarwin { + opts = append(opts, fuseshim.SetOption("noappledouble", "")) + } + + // Ask the Linux kernel for larger read requests. + // + // As of 2015-03-26, the behavior in the kernel is: + // + // * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable + // ra_pages to be init_response->max_readahead divided by the page size. + // + // * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set + // backing_dev_info::ra_pages to the min of that value and what was sent + // in the request's max_readahead field. + // + // * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding + // how much to read ahead. + // + // * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero. + // + // Reading a page at a time is a drag. Ask for a larger size. + const maxReadahead = 1 << 20 + opts = append(opts, fuseshim.MaxReadahead(maxReadahead)) + + // Last but not least: other user-supplied options. + for k, v := range c.Options { + opts = append(opts, fuseshim.SetOption(k, v)) + } + + return +} diff --git a/mounted_file_system.go b/mounted_file_system.go index 2d4ab4e..d070353 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -16,8 +16,6 @@ package fuse import ( "fmt" - "log" - "runtime" "github.com/jacobsa/fuse/internal/fuseshim" @@ -63,140 +61,6 @@ func (mfs *MountedFileSystem) Join(ctx context.Context) error { } } -// Optional configuration accepted by Mount. -type MountConfig struct { - // The context from which every op read from the connetion by the sever - // should inherit. If nil, context.Background() will be used. - OpContext context.Context - - // If non-empty, the name of the file system as displayed by e.g. `mount`. - // This is important because the `umount` command requires root privileges if - // it doesn't agree with /etc/fstab. - FSName string - - // Mount the file system in read-only mode. File modes will appear as normal, - // but opening a file for writing and metadata operations like chmod, - // chtimes, etc. will fail. - ReadOnly bool - - // A logger to use for logging errors. All errors are logged, with the - // exception of a few blacklisted errors that are expected. If nil, no error - // logging is performed. - ErrorLogger *log.Logger - - // A logger to use for logging debug information. If nil, no debug logging is - // performed. - DebugLogger *log.Logger - - // OS X only. - // - // Normally on OS X we mount with the novncache option - // (cf. http://goo.gl/1pTjuk), which disables entry caching in the kernel. - // This is because osxfuse does not honor the entry expiration values we - // return to it, instead caching potentially forever (cf. - // http://goo.gl/8yR0Ie), and it is probably better to fail to cache than to - // cache for too long, since the latter is more likely to hide consistency - // bugs that are difficult to detect and diagnose. - // - // This field disables the use of novncache, restoring entry caching. Beware: - // the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and - // entries will be cached for an arbitrarily long time. - EnableVnodeCaching bool - - // Additional key=value options to pass unadulterated to the underlying mount - // command. See `man 8 mount`, the fuse documentation, etc. for - // system-specific information. - // - // For expert use only! May invalidate other guarantees made in the - // documentation for this package. - Options map[string]string -} - -// Create a map containing all of the key=value mount options to be given to -// the mount helper. -func (c *MountConfig) toMap() (opts map[string]string) { - panic("TODO") -} - -// Create an options string suitable for passing to the mount helper. -func (c *MountConfig) toOptionsString() string { - panic("TODO") -} - -// Convert to mount options to be passed to package fuseshim. -func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { - isDarwin := runtime.GOOS == "darwin" - - // Enable permissions checking in the kernel. See the comments on - // InodeAttributes.Mode. - opts = append(opts, fuseshim.SetOption("default_permissions", "")) - - // HACK(jacobsa): Work around what appears to be a bug in systemd v219, as - // shipped in Ubuntu 15.04, where it automatically unmounts any file system - // that doesn't set an explicit name. - // - // When Ubuntu contains systemd v220, this workaround should be removed and - // the systemd bug reopened if the problem persists. - // - // Cf. https://github.com/bazil/fuse/issues/89 - // Cf. https://bugs.freedesktop.org/show_bug.cgi?id=90907 - fsname := c.FSName - if runtime.GOOS == "linux" && fsname == "" { - fsname = "some_fuse_file_system" - } - - // Special file system name? - if fsname != "" { - opts = append(opts, fuseshim.FSName(fsname)) - } - - // Read only? - if c.ReadOnly { - opts = append(opts, fuseshim.ReadOnly()) - } - - // OS X: set novncache when appropriate. - if isDarwin && !c.EnableVnodeCaching { - opts = append(opts, fuseshim.SetOption("novncache", "")) - } - - // OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which - // just add noise to debug output and can have significant cost on - // network-based file systems. - // - // Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options - if isDarwin { - opts = append(opts, fuseshim.SetOption("noappledouble", "")) - } - - // Ask the Linux kernel for larger read requests. - // - // As of 2015-03-26, the behavior in the kernel is: - // - // * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable - // ra_pages to be init_response->max_readahead divided by the page size. - // - // * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set - // backing_dev_info::ra_pages to the min of that value and what was sent - // in the request's max_readahead field. - // - // * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding - // how much to read ahead. - // - // * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero. - // - // Reading a page at a time is a drag. Ask for a larger size. - const maxReadahead = 1 << 20 - opts = append(opts, fuseshim.MaxReadahead(maxReadahead)) - - // Last but not least: other user-supplied options. - for k, v := range c.Options { - opts = append(opts, fuseshim.SetOption(k, v)) - } - - return -} - // Attempt to mount a file system on the given directory, using the supplied // Server to serve connection requests. This function blocks until the file // system is successfully mounted. From baa7c29fad8bee992d9c1bdd518e55b077523b2c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 15:56:54 +1000 Subject: [PATCH 134/157] Use the mount helper in Mount. --- mounted_file_system.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/mounted_file_system.go b/mounted_file_system.go index d070353..54532bb 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -17,8 +17,6 @@ package fuse import ( "fmt" - "github.com/jacobsa/fuse/internal/fuseshim" - "golang.org/x/net/context" ) @@ -74,10 +72,11 @@ func Mount( joinStatusAvailable: make(chan struct{}), } - // Open a fuseshim connection. - bfConn, err := fuseshim.Mount(mfs.dir, config.bazilfuseOptions()...) + // Begin the mounting process, which will continue in the background. + ready := make(chan error, 1) + dev, err := mount(dir, config, ready) if err != nil { - err = fmt.Errorf("fuseshim.Mount: %v", err) + err = fmt.Errorf("mount: %v", err) return } @@ -87,15 +86,14 @@ func Mount( opContext = context.Background() } - // Create our own Connection object wrapping it. + // Create a Connection object wrapping the device. connection, err := newConnection( opContext, config.DebugLogger, config.ErrorLogger, - bfConn) + dev) if err != nil { - bfConn.Close() err = fmt.Errorf("newConnection: %v", err) return } @@ -107,9 +105,9 @@ func Mount( close(mfs.joinStatusAvailable) }() - // Wait for the connection to say it is ready. - if err = connection.waitForReady(); err != nil { - err = fmt.Errorf("WaitForReady: %v", err) + // Wait for the mount process to complete. + if err = <-ready; err != nil { + err = fmt.Errorf("mount (background): %v", err) return } From 28605268713e6c49c9d7a0377f38307277e409bd Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:01:45 +1000 Subject: [PATCH 135/157] Updated newConnection. --- connection.go | 18 +++++++++++++++--- internal/fuseshim/fuse.go | 11 +++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/connection.go b/connection.go index 4e83b8b..878f302 100644 --- a/connection.go +++ b/connection.go @@ -58,15 +58,27 @@ type Connection struct { cancelFuncs map[uint64]func() } -// Responsibility for closing the wrapped connection is transferred to the -// result. You must call c.close() eventually. +// Create a connection wrapping the supplied file descriptor connected to the +// kernel. You must eventually call c.close(). // // The loggers may be nil. func newConnection( parentCtx context.Context, debugLogger *log.Logger, errorLogger *log.Logger, - wrapped *fuseshim.Conn) (c *Connection, err error) { + dev *os.File) (c *Connection, err error) { + // Create an initialized a wrapped fuseshim connection. + wrapped := &fuseshim.Conn{ + Dev: dev, + } + + err = fuseshim.InitMount(wrapped, TODO, TODO) + if err != nil { + err = fmt.Errorf("fuseshim.InitMount: %v", err) + return + } + + // Create an object wrapping it. c = &Connection{ debugLogger: debugLogger, errorLogger: errorLogger, diff --git a/internal/fuseshim/fuse.go b/internal/fuseshim/fuse.go index 22fde82..8b2d3a1 100644 --- a/internal/fuseshim/fuse.go +++ b/internal/fuseshim/fuse.go @@ -164,7 +164,7 @@ func Mount(dir string, options ...MountOption) (*Conn, error) { } c.Dev = f - if err := initMount(c, &conf); err != nil { + if err := InitMount(c, conf.maxReadahead, conf.initFlags); err != nil { c.Close() return nil, err } @@ -181,7 +181,10 @@ func (e *OldVersionError) Error() string { return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin) } -func initMount(c *Conn, conf *mountConfig) error { +func InitMount( + c *Conn, + maxReadahead uint32, + initFlags fusekernel.InitFlags) error { req, err := c.ReadRequest() if err != nil { if err == io.EOF { @@ -213,9 +216,9 @@ func initMount(c *Conn, conf *mountConfig) error { s := &InitResponse{ Library: proto, - MaxReadahead: conf.maxReadahead, + MaxReadahead: maxReadahead, MaxWrite: maxWrite, - Flags: fusekernel.InitBigWrites | conf.initFlags, + Flags: fusekernel.InitBigWrites | initFlags, } r.Respond(s) return nil From 91664cfc31f23af5d5e1310cc74cbd67a3c2002c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:03:24 +1000 Subject: [PATCH 136/157] Fixed maxReadahead. --- connection.go | 21 ++++++++++++++++++++- mount_config.go | 20 -------------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/connection.go b/connection.go index 878f302..ab465bd 100644 --- a/connection.go +++ b/connection.go @@ -32,6 +32,25 @@ import ( "github.com/jacobsa/fuse/internal/fuseshim" ) +// Ask the Linux kernel for larger read requests. +// +// As of 2015-03-26, the behavior in the kernel is: +// +// * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable +// ra_pages to be init_response->max_readahead divided by the page size. +// +// * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set +// backing_dev_info::ra_pages to the min of that value and what was sent +// in the request's max_readahead field. +// +// * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding +// how much to read ahead. +// +// * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero. +// +// Reading a page at a time is a drag. Ask for a larger size. +const maxReadahead = 1 << 20 + // A connection to the fuse kernel process. type Connection struct { debugLogger *log.Logger @@ -72,7 +91,7 @@ func newConnection( Dev: dev, } - err = fuseshim.InitMount(wrapped, TODO, TODO) + err = fuseshim.InitMount(wrapped, maxReadahead, TODO) if err != nil { err = fmt.Errorf("fuseshim.InitMount: %v", err) return diff --git a/mount_config.go b/mount_config.go index 8081591..cf1fff8 100644 --- a/mount_config.go +++ b/mount_config.go @@ -129,26 +129,6 @@ func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { opts = append(opts, fuseshim.SetOption("noappledouble", "")) } - // Ask the Linux kernel for larger read requests. - // - // As of 2015-03-26, the behavior in the kernel is: - // - // * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable - // ra_pages to be init_response->max_readahead divided by the page size. - // - // * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set - // backing_dev_info::ra_pages to the min of that value and what was sent - // in the request's max_readahead field. - // - // * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding - // how much to read ahead. - // - // * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero. - // - // Reading a page at a time is a drag. Ask for a larger size. - const maxReadahead = 1 << 20 - opts = append(opts, fuseshim.MaxReadahead(maxReadahead)) - // Last but not least: other user-supplied options. for k, v := range c.Options { opts = append(opts, fuseshim.SetOption(k, v)) From ba66e02af11d9d55a0c8fdf84fa88aeea1d93fd1 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:05:01 +1000 Subject: [PATCH 137/157] Fixed initFlags. --- connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connection.go b/connection.go index ab465bd..b136e06 100644 --- a/connection.go +++ b/connection.go @@ -91,7 +91,7 @@ func newConnection( Dev: dev, } - err = fuseshim.InitMount(wrapped, maxReadahead, TODO) + err = fuseshim.InitMount(wrapped, maxReadahead, 0) if err != nil { err = fmt.Errorf("fuseshim.InitMount: %v", err) return From 440c9ee7f7441bb1c7c27535c99f7ad0b680384c Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:06:53 +1000 Subject: [PATCH 138/157] MountConfig.toMap --- mount_config.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/mount_config.go b/mount_config.go index cf1fff8..e20c915 100644 --- a/mount_config.go +++ b/mount_config.go @@ -18,8 +18,6 @@ import ( "log" "runtime" - "github.com/jacobsa/fuse/internal/fuseshim" - "golang.org/x/net/context" ) @@ -75,21 +73,12 @@ type MountConfig struct { // Create a map containing all of the key=value mount options to be given to // the mount helper. func (c *MountConfig) toMap() (opts map[string]string) { - panic("TODO") -} - -// Create an options string suitable for passing to the mount helper. -func (c *MountConfig) toOptionsString() string { - panic("TODO") -} - -// Convert to mount options to be passed to package fuseshim. -func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { isDarwin := runtime.GOOS == "darwin" + opts = make(map[string]string) // Enable permissions checking in the kernel. See the comments on // InodeAttributes.Mode. - opts = append(opts, fuseshim.SetOption("default_permissions", "")) + opts["default_permissions"] = "" // HACK(jacobsa): Work around what appears to be a bug in systemd v219, as // shipped in Ubuntu 15.04, where it automatically unmounts any file system @@ -107,17 +96,17 @@ func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { // Special file system name? if fsname != "" { - opts = append(opts, fuseshim.FSName(fsname)) + opts["fsname"] = fsname } // Read only? if c.ReadOnly { - opts = append(opts, fuseshim.ReadOnly()) + opts["ro"] = "" } // OS X: set novncache when appropriate. if isDarwin && !c.EnableVnodeCaching { - opts = append(opts, fuseshim.SetOption("novncache", "")) + opts["novncache"] = "" } // OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which @@ -126,13 +115,18 @@ func (c *MountConfig) bazilfuseOptions() (opts []fuseshim.MountOption) { // // Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options if isDarwin { - opts = append(opts, fuseshim.SetOption("noappledouble", "")) + opts["noappledouble"] = "" } // Last but not least: other user-supplied options. for k, v := range c.Options { - opts = append(opts, fuseshim.SetOption(k, v)) + opts[k] = v } return } + +// Create an options string suitable for passing to the mount helper. +func (c *MountConfig) toOptionsString() string { + panic("TODO") +} From f4d8f9816579c857d0fb97295c476e391dde8560 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:09:15 +1000 Subject: [PATCH 139/157] MountConfig.toOptionsString --- mount_config.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/mount_config.go b/mount_config.go index e20c915..dd76561 100644 --- a/mount_config.go +++ b/mount_config.go @@ -15,8 +15,10 @@ package fuse import ( + "fmt" "log" "runtime" + "strings" "golang.org/x/net/context" ) @@ -126,7 +128,26 @@ func (c *MountConfig) toMap() (opts map[string]string) { return } +func escapeOptionsKey(s string) (res string) { + res = s + res = strings.Replace(res, `\`, `\\`, -1) + res = strings.Replace(res, `,`, `\,`, -1) + return +} + // Create an options string suitable for passing to the mount helper. func (c *MountConfig) toOptionsString() string { - panic("TODO") + var components []string + for k, v := range c.toMap() { + k = escapeOptionsKey(k) + + component := k + if v != "" { + component = fmt.Sprintf("%s=%s", k, v) + } + + components = append(components, component) + } + + return strings.Join(components, ",") } From 6a1e2f5502482ab2dab6d1519f8f8996a0d9593e Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:10:41 +1000 Subject: [PATCH 140/157] Copied mount_linux.go. --- mount_linux.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 mount_linux.go diff --git a/mount_linux.go b/mount_linux.go new file mode 100644 index 0000000..dfe70b5 --- /dev/null +++ b/mount_linux.go @@ -0,0 +1,116 @@ +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 +} From bd9024064aa1296e90374f1106cd13019132f6be Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:10:49 +1000 Subject: [PATCH 141/157] Fixed the package name. --- mount_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mount_linux.go b/mount_linux.go index dfe70b5..df87ec7 100644 --- a/mount_linux.go +++ b/mount_linux.go @@ -1,4 +1,4 @@ -package fuseshim +package fuse import ( "bufio" From ee57176a0bee565ae241c0c93e33e25623638170 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:10:54 +1000 Subject: [PATCH 142/157] Trimmed a bit. --- mount_linux.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mount_linux.go b/mount_linux.go index df87ec7..de7a060 100644 --- a/mount_linux.go +++ b/mount_linux.go @@ -12,10 +12,6 @@ import ( "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() From e9529e8d361480fd064b92c58b64752fc228e3d8 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:18:07 +1000 Subject: [PATCH 143/157] Gave mount a makeover. --- mount_linux.go | 94 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 24 deletions(-) diff --git a/mount_linux.go b/mount_linux.go index de7a060..94858bd 100644 --- a/mount_linux.go +++ b/mount_linux.go @@ -32,81 +32,127 @@ func lineLogger(wg *sync.WaitGroup, prefix string, r io.ReadCloser) { } } -func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { - // linux mount is never delayed - close(ready) +// Begin the process of mounting at the given directory, returning a connection +// to the kernel. Mounting continues in the background, and is complete when an +// error is written to the supplied channel. The file system may need to +// service the connection in order for mounting to complete. +func mount( + dir string, + cfg *MountConfig, + ready chan<- error) (dev *os.File, err error) { + // On linux, mounting is never delayed. + ready <- nil + // Create a socket pair. fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0) if err != nil { - return nil, fmt.Errorf("socketpair error: %v", err) + err = fmt.Errorf("Socketpair: %v", err) + return } + // Wrap the sockets into os.File objects that we will pass off to fusermount. writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") defer writeFile.Close() readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") defer readFile.Close() + // Start fusermount, passing it pipes for stdout and stderr. cmd := exec.Command( "fusermount", - "-o", conf.getOptions(), + "-o", cfg.toOptionsString(), "--", dir, ) - cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") + 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) + err = fmt.Errorf("StdoutPipe: %v", err) + return } - if err := cmd.Start(); err != nil { - return nil, fmt.Errorf("fusermount: %v", err) + stderr, err := cmd.StderrPipe() + if err != nil { + err = fmt.Errorf("StderrPipe: %v", err) + return } + + err = cmd.Start() + if err != nil { + err = fmt.Errorf("Starting fusermount: %v", err) + return + } + + // Log fusermount output until it closes stdout and stderr. + var wg sync.WaitGroup 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) + + // Wait for the command. + err = cmd.Wait() + if err != nil { + err = fmt.Errorf("fusermount: %v", err) + return } + // Wrap the socket file in a connection. c, err := net.FileConn(readFile) if err != nil { - return nil, fmt.Errorf("FileConn from fusermount socket: %v", err) + err = fmt.Errorf("FileConn: %v", err) + return } defer c.Close() + // We expect to have a Unix domain socket. uc, ok := c.(*net.UnixConn) if !ok { - return nil, fmt.Errorf("unexpected FileConn type; expected UnixConn, got %T", c) + err = fmt.Errorf("Expected UnixConn, got %T", c) + return } + // Read a message. buf := make([]byte, 32) // expect 1 byte oob := make([]byte, 32) // expect 24 bytes _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob) + if err != nil { + err = fmt.Errorf("ReadMsgUnix: %v", err) + return + } + + // Parse the message. scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) if err != nil { - return nil, fmt.Errorf("ParseSocketControlMessage: %v", err) + err = fmt.Errorf("ParseSocketControlMessage: %v", err) + return } + + // We expect one message. if len(scms) != 1 { - return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms) + err = fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms) + return } + scm := scms[0] + + // Pull out the FD returned by fusermount gotFds, err := syscall.ParseUnixRights(&scm) if err != nil { - return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err) + err = fmt.Errorf("syscall.ParseUnixRights: %v", err) + return } + if len(gotFds) != 1 { - return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds) + err = fmt.Errorf("wanted 1 fd; got %#v", gotFds) + return } - f := os.NewFile(uintptr(gotFds[0]), "/dev/fuse") - return f, nil + + // Turn the FD into an os.File. + dev = os.NewFile(uintptr(gotFds[0]), "/dev/fuse") + + return } From c48e561e20619440243b9675a7a2076474bcfca8 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:20:11 +1000 Subject: [PATCH 144/157] Copied unmount files from fuseshim. --- unmount.go | 23 +++-------------------- unmount_linux.go | 21 +++++++++++++++++++++ unmount_std.go | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 20 deletions(-) create mode 100644 unmount_linux.go create mode 100644 unmount_std.go diff --git a/unmount.go b/unmount.go index 7acec46..0cc989d 100644 --- a/unmount.go +++ b/unmount.go @@ -1,23 +1,6 @@ -// Copyright 2015 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +package fuseshim -package fuse - -import "github.com/jacobsa/fuse/internal/fuseshim" - -// Attempt to unmount the file system whose mount point is the supplied -// directory. +// Unmount tries to unmount the filesystem mounted at dir. func Unmount(dir string) error { - return fuseshim.Unmount(dir) + return unmount(dir) } diff --git a/unmount_linux.go b/unmount_linux.go new file mode 100644 index 0000000..6a165cc --- /dev/null +++ b/unmount_linux.go @@ -0,0 +1,21 @@ +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/unmount_std.go b/unmount_std.go new file mode 100644 index 0000000..3c38dd2 --- /dev/null +++ b/unmount_std.go @@ -0,0 +1,17 @@ +// +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 +} From 31b79aae73347b58b3179b5e706af8c42e324872 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:20:44 +1000 Subject: [PATCH 145/157] Fixed unmount.go. --- unmount.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/unmount.go b/unmount.go index 0cc989d..a9d78e0 100644 --- a/unmount.go +++ b/unmount.go @@ -1,6 +1,21 @@ -package fuseshim +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -// Unmount tries to unmount the filesystem mounted at dir. +package fuse + +// Attempt to unmount the file system whose mount point is the supplied +// directory. func Unmount(dir string) error { return unmount(dir) } From c7757fe840013b93ede07f4e82d483c0292418be Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:21:18 +1000 Subject: [PATCH 146/157] Fixed unmount_std.go. --- unmount_std.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/unmount_std.go b/unmount_std.go index 3c38dd2..3324d6c 100644 --- a/unmount_std.go +++ b/unmount_std.go @@ -1,17 +1,18 @@ // +build !linux -package fuseshim +package fuse import ( "os" "syscall" ) -func unmount(dir string) error { - err := syscall.Unmount(dir, 0) +func unmount(dir string) (err error) { + err = syscall.Unmount(dir, 0) if err != nil { err = &os.PathError{Op: "unmount", Path: dir, Err: err} - return err + return } - return nil + + return } From c7738d6ded51bbbea7e8e52f75a5d80913d094c7 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:21:58 +1000 Subject: [PATCH 147/157] Fixed unmount_linux.go. --- unmount_linux.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/unmount_linux.go b/unmount_linux.go index 6a165cc..86abc8d 100644 --- a/unmount_linux.go +++ b/unmount_linux.go @@ -2,20 +2,22 @@ package fuseshim import ( "bytes" - "errors" + "fmt" "os/exec" ) -func unmount(dir string) error { +func unmount(dir string) (err error) { + // Call fusermount. 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) + err = fmt.Errorf("%v: %s", err, output) } - return err + + return } - return nil + + return } From 3e99a8c67a97a23effc4f49c4ff465af1024b5e6 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:22:33 +1000 Subject: [PATCH 148/157] Fixed a build error on Linux. --- unmount_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unmount_linux.go b/unmount_linux.go index 86abc8d..c9ed745 100644 --- a/unmount_linux.go +++ b/unmount_linux.go @@ -1,4 +1,4 @@ -package fuseshim +package fuse import ( "bytes" From 74e87c22b8b92b58ee4aa3df0fef80c8a494e4f2 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:23:13 +1000 Subject: [PATCH 149/157] Killed some dead code. --- connection.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/connection.go b/connection.go index b136e06..1ed1797 100644 --- a/connection.go +++ b/connection.go @@ -410,12 +410,6 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { } } -func (c *Connection) waitForReady() (err error) { - <-c.wrapped.Ready - err = c.wrapped.MountError - return -} - // Close the connection. Must not be called until operations that were read // from the connection have been responded to. func (c *Connection) close() (err error) { From 7775584ecaf725a8075a431cff995fcaaf034a65 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:23:37 +1000 Subject: [PATCH 150/157] Killed an unnecessary field. --- connection.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/connection.go b/connection.go index 1ed1797..cd659c7 100644 --- a/connection.go +++ b/connection.go @@ -55,7 +55,6 @@ const maxReadahead = 1 << 20 type Connection struct { debugLogger *log.Logger errorLogger *log.Logger - wrapped *fuseshim.Conn // The device through which we're talking to the kernel, and the protocol // version that we're using to talk to it. @@ -101,7 +100,6 @@ func newConnection( c = &Connection{ debugLogger: debugLogger, errorLogger: errorLogger, - wrapped: wrapped, dev: wrapped.Dev, protocol: wrapped.Protocol(), parentCtx: parentCtx, From 6a25a1071c7198f9b0c1261c4493c83e99704af9 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:29:59 +1000 Subject: [PATCH 151/157] Connection.Init --- connection.go | 71 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/connection.go b/connection.go index cd659c7..ed984bf 100644 --- a/connection.go +++ b/connection.go @@ -29,7 +29,6 @@ import ( "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/fuse/internal/fusekernel" - "github.com/jacobsa/fuse/internal/fuseshim" ) // Ask the Linux kernel for larger read requests. @@ -85,27 +84,69 @@ func newConnection( debugLogger *log.Logger, errorLogger *log.Logger, dev *os.File) (c *Connection, err error) { - // Create an initialized a wrapped fuseshim connection. - wrapped := &fuseshim.Conn{ - Dev: dev, - } - - err = fuseshim.InitMount(wrapped, maxReadahead, 0) - if err != nil { - err = fmt.Errorf("fuseshim.InitMount: %v", err) - return - } - - // Create an object wrapping it. c = &Connection{ debugLogger: debugLogger, errorLogger: errorLogger, - dev: wrapped.Dev, - protocol: wrapped.Protocol(), + dev: dev, parentCtx: parentCtx, cancelFuncs: make(map[uint64]func()), } + // Initialize. + err = c.Init() + if err != nil { + c.close() + err = fmt.Errorf("Init: %v", err) + return + } + + return +} + +// Do the work necessary to cause the mount process to complete. +func (c *Connection) Init() (err error) { + // Read the init op. + op, err := c.ReadOp() + if err != nil { + err = fmt.Errorf("Reading init op: %v", err) + return + } + + initOp, ok := op.(*fuseops.InitOp) + if !ok { + err = fmt.Errorf("Expected *fuseops.InitOp, got %T", op) + return + } + + // Make sure the protocol version spoken by the kernel is new enough. + min := fusekernel.Protocol{ + fusekernel.ProtoVersionMinMajor, + fusekernel.ProtoVersionMinMinor, + } + + if initOp.Kernel.LT(min) { + initOp.Respond(syscall.EPROTO) + err = fmt.Errorf("Version too old: %v", initOp.Kernel) + return + } + + // Downgrade our protocol if necessary. + c.protocol = fusekernel.Protocol{ + fusekernel.ProtoVersionMaxMajor, + fusekernel.ProtoVersionMaxMinor, + } + + if initOp.Kernel.LT(c.protocol) { + c.protocol = r.Kernel + } + + // Respond to the init op. + initOp.Library = c.protocol + initOp.MaxReadahead = maxReadahead + initOp.MaxWrite = buffer.MaxWriteSize + initOp.Flags = fusekernel.InitBigWrites + initOp.Respond(nil) + return } From a07a396c6a3c6e8d525799ca758990a8a00c3660 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:35:14 +1000 Subject: [PATCH 152/157] Added support for init ops. --- connection.go | 6 +++--- fuseops/convert.go | 16 ++++++++++++++++ fuseops/ops.go | 27 +++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/connection.go b/connection.go index ed984bf..1b6e141 100644 --- a/connection.go +++ b/connection.go @@ -112,9 +112,9 @@ func (c *Connection) Init() (err error) { return } - initOp, ok := op.(*fuseops.InitOp) + initOp, ok := op.(*fuseops.InternalInitOp) if !ok { - err = fmt.Errorf("Expected *fuseops.InitOp, got %T", op) + err = fmt.Errorf("Expected *fuseops.InternalInitOp, got %T", op) return } @@ -137,7 +137,7 @@ func (c *Connection) Init() (err error) { } if initOp.Kernel.LT(c.protocol) { - c.protocol = r.Kernel + c.protocol = initOp.Kernel } // Respond to the init op. diff --git a/fuseops/convert.go b/fuseops/convert.go index b8bee74..c9f3db5 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -418,6 +418,22 @@ func Convert( io = to co = &to.commonOp + case fusekernel.OpInit: + type input fusekernel.InitIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpInit") + return + } + + to := &InternalInitOp{ + Kernel: fusekernel.Protocol{in.Major, in.Minor}, + MaxReadahead: in.MaxReadahead, + Flags: fusekernel.InitFlags(in.Flags), + } + io = to + co = &to.commonOp + default: to := &unknownOp{ opCode: m.Header().Opcode, diff --git a/fuseops/ops.go b/fuseops/ops.go index 65f951b..0eb15b8 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -945,3 +945,30 @@ type InternalInterruptOp struct { func (o *InternalInterruptOp) kernelResponse() (b buffer.OutMessage) { panic("Shouldn't get here.") } + +// Do not use this struct directly. See the TODO in fuseops/ops.go. +type InternalInitOp struct { + commonOp + + // In + Kernel fusekernel.Protocol + + // Out + Library fusekernel.Protocol + MaxReadahead uint32 + Flags fusekernel.InitFlags + MaxWrite uint32 +} + +func (o *InternalInitOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.InitOut{})) + out := (*fusekernel.InitOut)(b.Grow(unsafe.Sizeof(fusekernel.InitOut{}))) + + out.Major = o.Library.Major + out.Minor = o.Library.Minor + out.MaxReadahead = o.MaxReadahead + out.Flags = uint32(o.Flags) + out.MaxWrite = o.MaxWrite + + return +} From ed2ae82923cfdcc8e5039f259755bf61f0826710 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:35:54 +1000 Subject: [PATCH 153/157] Deleted fuseshim. --- internal/fuseshim/buffer.go | 39 - internal/fuseshim/fuse.go | 2217 ---------------------- internal/fuseshim/mount_darwin.go | 131 -- internal/fuseshim/mount_freebsd.go | 44 - internal/fuseshim/mount_linux.go | 116 -- internal/fuseshim/options.go | 182 -- internal/fuseshim/options_darwin.go | 13 - internal/fuseshim/options_freebsd.go | 9 - internal/fuseshim/options_helper_test.go | 10 - internal/fuseshim/options_linux.go | 9 - internal/fuseshim/options_test.go | 231 --- internal/fuseshim/unmount.go | 6 - internal/fuseshim/unmount_linux.go | 21 - internal/fuseshim/unmount_std.go | 17 - 14 files changed, 3045 deletions(-) delete mode 100644 internal/fuseshim/buffer.go delete mode 100644 internal/fuseshim/fuse.go delete mode 100644 internal/fuseshim/mount_darwin.go delete mode 100644 internal/fuseshim/mount_freebsd.go delete mode 100644 internal/fuseshim/mount_linux.go delete mode 100644 internal/fuseshim/options.go delete mode 100644 internal/fuseshim/options_darwin.go delete mode 100644 internal/fuseshim/options_freebsd.go delete mode 100644 internal/fuseshim/options_helper_test.go delete mode 100644 internal/fuseshim/options_linux.go delete mode 100644 internal/fuseshim/options_test.go delete mode 100644 internal/fuseshim/unmount.go delete mode 100644 internal/fuseshim/unmount_linux.go delete mode 100644 internal/fuseshim/unmount_std.go 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 -} From 1213e41ef87c948477bb1292c9a1a421820097a0 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:36:40 +1000 Subject: [PATCH 154/157] Removed a TODO that doesn't belong to me. --- mount_darwin.go | 1 - 1 file changed, 1 deletion(-) diff --git a/mount_darwin.go b/mount_darwin.go index 428b73a..8ecf640 100644 --- a/mount_darwin.go +++ b/mount_darwin.go @@ -86,7 +86,6 @@ func callMount( cmd.ExtraFiles = []*os.File{dev} 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 From 7b00963ee7703179215a5fff738608cf761fc69f Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Fri, 24 Jul 2015 16:38:48 +1000 Subject: [PATCH 155/157] Deleted a test that references the old package. --- internal/fusekernel/fuse_kernel_test.go | 31 ------------------------- 1 file changed, 31 deletions(-) delete mode 100644 internal/fusekernel/fuse_kernel_test.go diff --git a/internal/fusekernel/fuse_kernel_test.go b/internal/fusekernel/fuse_kernel_test.go deleted file mode 100644 index bec3cfd..0000000 --- a/internal/fusekernel/fuse_kernel_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package fusekernel_test - -import ( - "os" - "testing" - - fuse "github.com/jacobsa/bazilfuse" -) - -func TestOpenFlagsAccmodeMask(t *testing.T) { - var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC) - if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e { - t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e) - } - if f.IsReadOnly() { - t.Fatalf("IsReadOnly is wrong: %v", f) - } - if f.IsWriteOnly() { - t.Fatalf("IsWriteOnly is wrong: %v", f) - } - if !f.IsReadWrite() { - t.Fatalf("IsReadWrite is wrong: %v", f) - } -} - -func TestOpenFlagsString(t *testing.T) { - var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND) - if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e { - t.Fatalf("OpenFlags.String: %q != %q", g, e) - } -} From 563f47c299318ac0d97688d3683ab0467ee5db61 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 27 Jul 2015 11:49:13 +1000 Subject: [PATCH 156/157] Deleted a broken FreeBSD file. --- internal/fusekernel/fuse_kernel_freebsd.go | 62 ---------------------- 1 file changed, 62 deletions(-) delete mode 100644 internal/fusekernel/fuse_kernel_freebsd.go diff --git a/internal/fusekernel/fuse_kernel_freebsd.go b/internal/fusekernel/fuse_kernel_freebsd.go deleted file mode 100644 index f04590f..0000000 --- a/internal/fusekernel/fuse_kernel_freebsd.go +++ /dev/null @@ -1,62 +0,0 @@ -package fuseshim - -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 - padding uint32 -} - -func (a *attr) Crtime() time.Time { - return time.Time{} -} - -func (a *attr) SetCrtime(s uint64, ns uint32) { - // ignored on freebsd -} - -func (a *attr) SetFlags(f uint32) { - // ignored on freebsd -} - -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 -} - -func openFlags(flags uint32) OpenFlags { - return OpenFlags(flags) -} - -type getxattrIn struct { - getxattrInCommon -} - -type setxattrIn struct { - setxattrInCommon -} From 95647107067fb77dfe3462ec9f331d40979937c2 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Mon, 27 Jul 2015 11:53:29 +1000 Subject: [PATCH 157/157] Reworked the readme. --- README.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 79c5fe0..6b59acc 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,30 @@ [![GoDoc](https://godoc.org/github.com/jacobsa/ogletest?status.svg)](https://godoc.org/github.com/jacobsa/fuse) -This package allows for writing and mounting user-space file systems from Go. It -is a wrapper around [bazil.org/fuse][bazil], which does the heavy lifting. It -does not make use of the [bazil.org/fuse/fs][bazil-fs] sub-package, which allows -for something like an object-orientend representation of files and directories, -and contains a decent amount of canned behavior. +This package allows for writing and mounting user-space file systems from Go. +Install it as follows: -The chief improvements and/or differences from the bazil.org packages are: + go get -u github.com/jacobsa/fuse - * No surprises in the form of magic/default behaviors. You must provide an - implementation for every method in the interface. Embed a - `fuseutil.NotImplementedFileSystem` struct to have default implementations - that return `ENOSYS`. +Afterward, see the documentation for the following three packages: - * Every method, struct, and field is thoroughly documented. This may help you - get your bearings in the world of FUSE, the Linux VFS, traditional file - system implementations, etc., all of which tend to be very poorly - documented. + * Package [fuse][] provides support for mounting a new file system and + reading requests from the kernel. - * Support for arbitrary offsets in directory entries returned by `ReadDir`. - (The bazil.org package assumes that offsets must be counts of bytes.) + * Package [fuseops][] enumerates the supported requests from the kernel, and + provides documentation on their semantics. -The very large disadvantage over using the bazil.org packages is that many -features have not yet been exposed. + * Package [fuseutil][], in particular the `FileSystem` interface, provides a + convenient way to create a file system type and export it to the kernel via + `fuse.Mount`. -Make sure to see the sub-packages of the [samples][] package. +Make sure to also see the sub-packages of the [samples][] package for examples +and tests. -[bazil]: http://godoc.org/bazil.org/fuse -[bazil-fs]: http://godoc.org/bazil.org/fuse/fs +This package owes its inspiration and most of its kernel-related code to +[bazil.org/fuse][bazil]. + +[fuse]: http://godoc.org/github.com/jacobsa/fuse +[fuseops]: http://godoc.org/github.com/jacobsa/fuse/fuseops +[fuseutil]: http://godoc.org/github.com/jacobsa/fuse/fuseutil [samples]: http://godoc.org/github.com/jacobsa/fuse/samples +[bazil]: http://godoc.org/bazil.org/fuse