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. +*/ 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 diff --git a/connection.go b/connection.go index 5ee1922..1b6e141 100644 --- a/connection.go +++ b/connection.go @@ -16,22 +16,49 @@ package fuse import ( "fmt" + "io" "log" + "os" "path" "runtime" "sync" + "syscall" "golang.org/x/net/context" - "github.com/jacobsa/bazilfuse" "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/fuse/internal/buffer" + "github.com/jacobsa/fuse/internal/fusekernel" ) +// 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 errorLogger *log.Logger - wrapped *bazilfuse.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 @@ -41,30 +68,85 @@ 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 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[bazilfuse.RequestID]func() + 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 *bazilfuse.Conn) (c *Connection, err error) { + dev *os.File) (c *Connection, err error) { c = &Connection{ debugLogger: debugLogger, errorLogger: errorLogger, - wrapped: wrapped, + dev: dev, parentCtx: parentCtx, - cancelFuncs: make(map[bazilfuse.RequestID]func()), + 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.InternalInitOp) + if !ok { + err = fmt.Errorf("Expected *fuseops.InternalInitOp, 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 = initOp.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 } @@ -104,28 +186,27 @@ func (c *Connection) debugLog( // LOCKS_EXCLUDED(c.mu) func (c *Connection) recordCancelFunc( - reqID bazilfuse.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 bazilfuse 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 bazilfuse.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 +218,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.(*bazilfuse.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 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 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 bazilfuse.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.(*bazilfuse.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 *bazilfuse.InterruptRequest) { +func (c *Connection) handleInterrupt(fuseID uint64) { c.mu.Lock() defer c.mu.Unlock() @@ -194,7 +275,7 @@ func (c *Connection) handleInterrupt(req *bazilfuse.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 } @@ -202,6 +283,71 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) { cancel() } +func (c *Connection) allocateInMessage() (m *buffer.InMessage) { + // TODO(jacobsa): Use a freelist. + m = new(buffer.InMessage) + return +} + +func (c *Connection) destroyInMessage(m *buffer.InMessage) { + // TODO(jacobsa): Use a freelist. +} + +// Read the next message from the kernel. The message must later be destroyed +// using destroyInMessage. +func (c *Connection) readMessage() (m *buffer.InMessage, err error) { + // Allocate a message. + m = c.allocateInMessage() + + // Loop past transient errors. + for { + // Attempt a reaed. + err = m.Init(c.dev) + + // 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 + return + } + + return + } +} + +// 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.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. // @@ -212,38 +358,19 @@ 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 - bfReq, err = c.wrapped.ReadRequest() - + // Read the next message from the kernel. + var m *buffer.InMessage + m, err = c.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++ - // Log the receipt of the operation. - c.debugLog(opID, 1, "<- %v", bfReq) - - // 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 { - c.debugLog(opID, 1, "-> (Statfs) OK") - statfsReq.Respond(&bazilfuse.StatfsResponse{}) - continue - } - - // Special case: handle interrupt requests. - if interruptReq, ok := bfReq.(*bazilfuse.InterruptRequest); ok { - c.handleInterrupt(interruptReq) - continue - } - // Set up op dependencies. - opCtx := c.beginOp(bfReq) + opCtx := c.beginOp(m.Header().Opcode, m.Header().Unique) var debugLogForOp func(int, string, ...interface{}) if c.debugLogger != nil { @@ -252,28 +379,82 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) { } } - finished := func(err error) { c.finishOp(bfReq) } + sendReply := func( + op fuseops.Op, + fuseID uint64, + replyMsg []byte, + opErr error) (err error) { + // Make sure we destroy the message, as required by readMessage. + defer c.destroyInMessage(m) - op = fuseops.Convert( + // Clean up state for this op. + c.finishOp(m.Header().Opcode, m.Header().Unique) + + // Debug logging + if c.debugLogger != nil { + if opErr == nil { + op.Logf("-> OK: %s", op.DebugString()) + } else { + op.Logf("-> error: %v", 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.writeMessage(replyMsg) + if err != nil { + err = fmt.Errorf("writeMessage: %v", err) + return + } + + return + } + + // Convert the message to an Op. + op, err = fuseops.Convert( opCtx, - bfReq, + m, + c.protocol, debugLogForOp, c.errorLogger, - finished) + sendReply) + + if err != nil { + err = fmt.Errorf("fuseops.Convert: %v", err) + return + } + + // Log the receipt of the operation. + 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 + // intercept this. + if _, ok := op.(*fuseops.InternalStatFSOp); ok { + op.Respond(nil) + continue + } + + // Special case: handle interrupt requests. + if interruptOp, ok := op.(*fuseops.InternalInterruptOp); ok { + c.handleInterrupt(interruptOp.FuseID) + continue + } return } } -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) { - 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 } diff --git a/errors.go b/errors.go index 48b15aa..bc9f902 100644 --- a/errors.go +++ b/errors.go @@ -14,20 +14,16 @@ package fuse -import ( - "syscall" - - "github.com/jacobsa/bazilfuse" -) +import "syscall" 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 = 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 457afc5..fb3a224 100644 --- a/fuseops/common_op.go +++ b/fuseops/common_op.go @@ -19,8 +19,9 @@ import ( "log" "reflect" "strings" + "syscall" - "github.com/jacobsa/bazilfuse" + "github.com/jacobsa/fuse/internal/buffer" "github.com/jacobsa/reqtrace" "golang.org/x/net/context" ) @@ -30,10 +31,19 @@ import ( type internalOp interface { Op - // Respond to the underlying bazilfuse request, successfully. - respond() + // Create a response message for the kernel, leaving the leading + // fusekernel.OutHeader untouched. + // + // Special case: a zero return value means that the kernel is not expecting a + // response. + kernelResponse() (b buffer.OutMessage) } +// 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(Op, uint64, []byte, error) error + // A helper for embedding common behavior. type commonOp struct { // The context exposed to the user. @@ -42,8 +52,11 @@ type commonOp struct { // The op in which this struct is embedded. op internalOp - // The underlying bazilfuse request for this op. - bazilReq bazilfuse.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 replyFunc // A function that can be used to log debug information about the op. The // first argument is a call depth. @@ -55,14 +68,11 @@ 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) { - 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". @@ -72,36 +82,46 @@ 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) + 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)", desc, f.Interface()) + } return } +func (o *commonOp) DebugString() string { + // By default, defer to ShortDesc. + return o.op.ShortDesc() +} + func (o *commonOp) init( ctx context.Context, op internalOp, - bazilReq bazilfuse.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(op Op, fuseID uint64, msg []byte, opErr error) (err error) { + reportForTrace(opErr) + err = prevSendReply(op, fuseID, msg, opErr) + return } } @@ -119,30 +139,36 @@ 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 bazilfuse 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 b buffer.OutMessage if err == nil { - o.op.respond() - return + b = o.op.kernelResponse() + } else { + b = buffer.NewOutMessage(0) } - // Log the error. - if o.debugLog != nil { - o.Logf( - "-> (%s) error: %v", - o.op.ShortDesc(), - err) + // Fill in the header if a reply is needed. + msg := b.Bytes() + if msg != nil { + h := b.OutHeader() + h.Unique = o.fuseID + h.Len = uint32(len(msg)) + if err != nil { + // 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) + } + } } - if o.errorLogger != nil { - o.errorLogger.Printf( - "(%s) error: %v", - o.op.ShortDesc(), - err) + // Reply. + replyErr := o.sendReply(o.op, o.fuseID, msg, err) + if replyErr != nil && o.errorLogger != nil { + o.errorLogger.Printf("Error from sendReply: %v", replyErr) } - - // Send a response to the kernel. - o.bazilReq.RespondError(err) } diff --git a/fuseops/convert.go b/fuseops/convert.go index 938a8c0..c9f3db5 100644 --- a/fuseops/convert.go +++ b/fuseops/convert.go @@ -15,252 +15,430 @@ package fuseops import ( + "bytes" + "errors" "log" + "os" + "syscall" "time" + "unsafe" + "github.com/jacobsa/fuse/internal/buffer" + "github.com/jacobsa/fuse/internal/fusekernel" "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 -// 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. sendReply will be used to +// 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, - r bazilfuse.Request, + m *buffer.InMessage, + protocol fusekernel.Protocol, debugLogForOp func(int, string, ...interface{}), errorLogger *log.Logger, - finished func(error)) (o Op) { + sendReply replyFunc) (o Op, err error) { var co *commonOp var io internalOp - switch typed := r.(type) { - case *bazilfuse.LookupRequest: + switch m.Header().Opcode { + case fusekernel.OpLookup: + buf := m.ConsumeBytes(m.Len()) + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + err = errors.New("Corrupt OpLookup") + return + } + to := &LookUpInodeOp{ - bfReq: typed, - Parent: InodeID(typed.Header.Node), - Name: typed.Name, + protocol: protocol, + Parent: InodeID(m.Header().Nodeid), + Name: string(buf[:n-1]), } io = to co = &to.commonOp - case *bazilfuse.GetattrRequest: + case fusekernel.OpGetattr: to := &GetInodeAttributesOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), + protocol: protocol, + Inode: InodeID(m.Header().Nodeid), } io = to co = &to.commonOp - case *bazilfuse.SetattrRequest: + case fusekernel.OpSetattr: + type input fusekernel.SetattrIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpSetattr") + return + } + to := &SetInodeAttributesOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), + protocol: protocol, + Inode: InodeID(m.Header().Nodeid), } - if typed.Valid&bazilfuse.SetattrSize != 0 { - to.Size = &typed.Size + valid := fusekernel.SetattrValid(in.Valid) + if valid&fusekernel.SetattrSize != 0 { + to.Size = &in.Size } - if typed.Valid&bazilfuse.SetattrMode != 0 { - to.Mode = &typed.Mode + if valid&fusekernel.SetattrMode != 0 { + mode := convertFileMode(in.Mode) + to.Mode = &mode } - if typed.Valid&bazilfuse.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&bazilfuse.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 co = &to.commonOp - case *bazilfuse.ForgetRequest: + case fusekernel.OpForget: + type input fusekernel.ForgetIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpForget") + return + } + to := &ForgetInodeOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - N: typed.N, + Inode: InodeID(m.Header().Nodeid), + N: in.Nlookup, } io = to co = &to.commonOp - case *bazilfuse.MkdirRequest: + case fusekernel.OpMkdir: + in := (*fusekernel.MkdirIn)(m.Consume(fusekernel.MkdirInSize(protocol))) + if in == nil { + err = errors.New("Corrupt OpMkdir") + return + } + + name := m.ConsumeBytes(m.Len()) + i := bytes.IndexByte(name, '\x00') + if i < 0 { + err = errors.New("Corrupt OpMkdir") + return + } + name = name[:i] + to := &MkDirOp{ - bfReq: typed, - Parent: InodeID(typed.Header.Node), - Name: typed.Name, - Mode: typed.Mode, + protocol: protocol, + Parent: InodeID(m.Header().Nodeid), + Name: string(name), + + // 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: convertFileMode(in.Mode) | os.ModeDir, } + io = to co = &to.commonOp - case *bazilfuse.CreateRequest: + case fusekernel.OpCreate: + in := (*fusekernel.CreateIn)(m.Consume(fusekernel.CreateInSize(protocol))) + if in == nil { + err = errors.New("Corrupt OpCreate") + return + } + + name := m.ConsumeBytes(m.Len()) + i := bytes.IndexByte(name, '\x00') + if i < 0 { + err = errors.New("Corrupt OpCreate") + return + } + name = name[:i] + to := &CreateFileOp{ - bfReq: typed, - Parent: InodeID(typed.Header.Node), - Name: typed.Name, - Mode: typed.Mode, - Flags: typed.Flags, + protocol: protocol, + Parent: InodeID(m.Header().Nodeid), + Name: string(name), + Mode: convertFileMode(in.Mode), } io = to co = &to.commonOp - case *bazilfuse.SymlinkRequest: + case fusekernel.OpSymlink: + // 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 + } + i := bytes.IndexByte(names, '\x00') + if i < 0 { + err = errors.New("Corrupt OpSymlink") + return + } + 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, + protocol: protocol, + Parent: InodeID(m.Header().Nodeid), + Name: string(newName), + Target: string(target), } io = to co = &to.commonOp - case *bazilfuse.RenameRequest: + case fusekernel.OpRename: + type input fusekernel.RenameIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpRename") + return + } + + names := m.ConsumeBytes(m.Len()) + // names should be "old\x00new\x00" + if len(names) < 4 { + err = errors.New("Corrupt OpRename") + return + } + if names[len(names)-1] != '\x00' { + err = errors.New("Corrupt OpRename") + return + } + i := bytes.IndexByte(names, '\x00') + if i < 0 { + err = errors.New("Corrupt OpRename") + return + } + 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().Nodeid), + OldName: string(oldName), + NewParent: InodeID(in.Newdir), + NewName: string(newName), } io = to co = &to.commonOp - case *bazilfuse.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.OpUnlink: + buf := m.ConsumeBytes(m.Len()) + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + err = errors.New("Corrupt OpUnlink") + return } - case *bazilfuse.OpenRequest: - if typed.Dir { - to := &OpenDirOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - Flags: typed.Flags, - } - io = to - co = &to.commonOp - } else { - to := &OpenFileOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - Flags: typed.Flags, - } - io = to - co = &to.commonOp + to := &UnlinkOp{ + Parent: InodeID(m.Header().Nodeid), + Name: string(buf[:n-1]), + } + io = to + co = &to.commonOp + + case fusekernel.OpRmdir: + buf := m.ConsumeBytes(m.Len()) + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + err = errors.New("Corrupt OpRmdir") + return } - case *bazilfuse.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 + to := &RmDirOp{ + Parent: InodeID(m.Header().Nodeid), + Name: string(buf[:n-1]), + } + io = to + co = &to.commonOp + + case fusekernel.OpOpen: + to := &OpenFileOp{ + Inode: InodeID(m.Header().Nodeid), + } + io = to + co = &to.commonOp + + case fusekernel.OpOpendir: + to := &OpenDirOp{ + Inode: InodeID(m.Header().Nodeid), + } + io = to + co = &to.commonOp + + case fusekernel.OpRead: + in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol))) + if in == nil { + err = errors.New("Corrupt OpRead") + return } - case *bazilfuse.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 + to := &ReadFileOp{ + Inode: InodeID(m.Header().Nodeid), + Handle: HandleID(in.Fh), + Offset: int64(in.Offset), + Size: int(in.Size), + } + io = to + co = &to.commonOp + + case fusekernel.OpReaddir: + in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol))) + if in == nil { + err = errors.New("Corrupt OpReaddir") + return + } + + to := &ReadDirOp{ + Inode: InodeID(m.Header().Nodeid), + Handle: HandleID(in.Fh), + Offset: DirOffset(in.Offset), + Size: int(in.Size), + } + io = to + co = &to.commonOp + + case fusekernel.OpRelease: + type input fusekernel.ReleaseIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpRelease") + return + } + + to := &ReleaseFileHandleOp{ + Handle: HandleID(in.Fh), + } + io = to + co = &to.commonOp + + case fusekernel.OpReleasedir: + type input fusekernel.ReleaseIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpReleasedir") + return + } + + to := &ReleaseDirHandleOp{ + Handle: HandleID(in.Fh), + } + io = to + co = &to.commonOp + + case fusekernel.OpWrite: + in := (*fusekernel.WriteIn)(m.Consume(fusekernel.WriteInSize(protocol))) + if in == nil { + err = errors.New("Corrupt OpWrite") + return + } + + buf := m.ConsumeBytes(m.Len()) + if len(buf) < int(in.Size) { + err = errors.New("Corrupt OpWrite") + return } - case *bazilfuse.WriteRequest: to := &WriteFileOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - Handle: HandleID(typed.Handle), - Data: typed.Data, - Offset: typed.Offset, + Inode: InodeID(m.Header().Nodeid), + Handle: HandleID(in.Fh), + Data: buf, + Offset: int64(in.Offset), } io = to co = &to.commonOp - case *bazilfuse.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: + type input fusekernel.FsyncIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpFsync") + return + } + + to := &SyncFileOp{ + Inode: InodeID(m.Header().Nodeid), + Handle: HandleID(in.Fh), + } + io = to + co = &to.commonOp + + case fusekernel.OpFlush: + type input fusekernel.FlushIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpFlush") + return } - case *bazilfuse.FlushRequest: to := &FlushFileOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), - Handle: HandleID(typed.Handle), + Inode: InodeID(m.Header().Nodeid), + Handle: HandleID(in.Fh), } io = to co = &to.commonOp - case *bazilfuse.ReadlinkRequest: + case fusekernel.OpReadlink: to := &ReadSymlinkOp{ - bfReq: typed, - Inode: InodeID(typed.Header.Node), + Inode: InodeID(m.Header().Nodeid), + } + io = to + co = &to.commonOp + + case fusekernel.OpStatfs: + to := &InternalStatFSOp{} + io = to + co = &to.commonOp + + case fusekernel.OpInterrupt: + type input fusekernel.InterruptIn + in := (*input)(m.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpInterrupt") + return + } + + to := &InternalInterruptOp{ + FuseID: in.Unique, + } + 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{} + to := &unknownOp{ + opCode: m.Header().Opcode, + inode: InodeID(m.Header().Nodeid), + } io = to co = &to.commonOp } @@ -268,48 +446,69 @@ func Convert( co.init( opCtx, io, - r, + m.Header().Unique, + sendReply, debugLogForOp, - errorLogger, - finished) + errorLogger) o = io return } +func convertTime(t time.Time) (secs uint64, nsec uint32) { + totalNano := t.UnixNano() + secs = uint64(totalNano / 1e9) + nsec = uint32(totalNano % 1e9) + return +} + func convertAttributes( - inode InodeID, - attr InodeAttributes, - expiration time.Time) bazilfuse.Attr { - return bazilfuse.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) & 0777 + 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 @@ -317,9 +516,41 @@ func convertExpirationTime(t time.Time) (d time.Duration) { func convertChildInodeEntry( in *ChildInodeEntry, - out *bazilfuse.LookupResponse) { - out.Node = bazilfuse.NodeID(in.Child) + out *fusekernel.EntryOut) { + out.Nodeid = uint64(in.Child) out.Generation = uint64(in.Generation) - out.Attr = convertAttributes(in.Child, in.Attributes, in.AttributesExpiration) - out.EntryValid = convertExpirationTime(in.EntryExpiration) + out.EntryValid, out.EntryValidNsec = convertExpirationTime(in.EntryExpiration) + out.AttrValid, out.AttrValidNsec = convertExpirationTime(in.AttributesExpiration) + + 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 } diff --git a/fuseops/ops.go b/fuseops/ops.go index 0e464cb..0eb15b8 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -18,8 +18,10 @@ import ( "fmt" "os" "time" + "unsafe" - "github.com/jacobsa/bazilfuse" + "github.com/jacobsa/fuse/internal/buffer" + "github.com/jacobsa/fuse/internal/fusekernel" "golang.org/x/net/context" ) @@ -30,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 @@ -55,7 +60,7 @@ type Op interface { // when resolving user paths to dentry structs, which are then cached. type LookUpInodeOp struct { commonOp - bfReq *bazilfuse.LookupRequest + protocol fusekernel.Protocol // The ID of the directory inode to which the child belongs. Parent InodeID @@ -83,11 +88,12 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) { return } -func (o *LookUpInodeOp) respond() { - resp := bazilfuse.LookupResponse{} - convertChildInodeEntry(&o.Entry, &resp) +func (o *LookUpInodeOp) kernelResponse() (b buffer.OutMessage) { + size := fusekernel.EntryOutSize(o.protocol) + b = buffer.NewOutMessage(size) + out := (*fusekernel.EntryOut)(b.Grow(size)) + convertChildInodeEntry(&o.Entry, out) - o.bfReq.Respond(&resp) return } @@ -97,7 +103,7 @@ func (o *LookUpInodeOp) respond() { // field of ChildInodeEntry, etc. type GetInodeAttributesOp struct { commonOp - bfReq *bazilfuse.GetattrRequest + protocol fusekernel.Protocol // The inode of interest. Inode InodeID @@ -109,12 +115,21 @@ type GetInodeAttributesOp struct { AttributesExpiration time.Time } -func (o *GetInodeAttributesOp) respond() { - resp := bazilfuse.GetattrResponse{ - Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), - } +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() (b buffer.OutMessage) { + size := fusekernel.AttrOutSize(o.protocol) + b = buffer.NewOutMessage(size) + out := (*fusekernel.AttrOut)(b.Grow(size)) + out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) + convertAttributes(o.Inode, &o.Attributes, &out.Attr) - o.bfReq.Respond(&resp) return } @@ -124,7 +139,7 @@ func (o *GetInodeAttributesOp) respond() { // cases like ftrunctate(2). type SetInodeAttributesOp struct { commonOp - bfReq *bazilfuse.SetattrRequest + protocol fusekernel.Protocol // The inode of interest. Inode InodeID @@ -142,12 +157,13 @@ type SetInodeAttributesOp struct { AttributesExpiration time.Time } -func (o *SetInodeAttributesOp) respond() { - resp := bazilfuse.SetattrResponse{ - Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), - } +func (o *SetInodeAttributesOp) kernelResponse() (b buffer.OutMessage) { + size := fusekernel.AttrOutSize(o.protocol) + b = buffer.NewOutMessage(size) + out := (*fusekernel.AttrOut)(b.Grow(size)) + out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration) + convertAttributes(o.Inode, &o.Attributes, &out.Attr) - o.bfReq.Respond(&resp) return } @@ -192,7 +208,6 @@ func (o *SetInodeAttributesOp) respond() { // implicitly decrementing all lookup counts to zero. type ForgetInodeOp struct { commonOp - bfReq *bazilfuse.ForgetRequest // The inode whose reference count should be decremented. Inode InodeID @@ -201,8 +216,8 @@ type ForgetInodeOp struct { N uint64 } -func (o *ForgetInodeOp) respond() { - o.bfReq.Respond() +func (o *ForgetInodeOp) kernelResponse() (b buffer.OutMessage) { + // No response. return } @@ -223,7 +238,7 @@ func (o *ForgetInodeOp) respond() { // Therefore the file system should return EEXIST if the name already exists. type MkDirOp struct { commonOp - bfReq *bazilfuse.MkdirRequest + protocol fusekernel.Protocol // The ID of parent directory inode within which to create the child. Parent InodeID @@ -244,11 +259,12 @@ func (o *MkDirOp) ShortDesc() (desc string) { return } -func (o *MkDirOp) respond() { - resp := bazilfuse.MkdirResponse{} - convertChildInodeEntry(&o.Entry, &resp.LookupResponse) +func (o *MkDirOp) kernelResponse() (b buffer.OutMessage) { + size := fusekernel.EntryOutSize(o.protocol) + b = buffer.NewOutMessage(size) + out := (*fusekernel.EntryOut)(b.Grow(size)) + convertChildInodeEntry(&o.Entry, out) - o.bfReq.Respond(&resp) return } @@ -264,7 +280,7 @@ func (o *MkDirOp) respond() { // Therefore the file system should return EEXIST if the name already exists. type CreateFileOp struct { commonOp - bfReq *bazilfuse.CreateRequest + protocol fusekernel.Protocol // The ID of parent directory inode within which to create the child file. Parent InodeID @@ -273,9 +289,6 @@ type CreateFileOp struct { Name string Mode os.FileMode - // Flags for the open operation. - Flags bazilfuse.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 @@ -298,16 +311,16 @@ func (o *CreateFileOp) ShortDesc() (desc string) { return } -func (o *CreateFileOp) respond() { - resp := bazilfuse.CreateResponse{ - OpenResponse: bazilfuse.OpenResponse{ - Handle: bazilfuse.HandleID(o.Handle), - }, - } +func (o *CreateFileOp) kernelResponse() (b buffer.OutMessage) { + eSize := fusekernel.EntryOutSize(o.protocol) + b = buffer.NewOutMessage(eSize + unsafe.Sizeof(fusekernel.OpenOut{})) - convertChildInodeEntry(&o.Entry, &resp.LookupResponse) + e := (*fusekernel.EntryOut)(b.Grow(eSize)) + convertChildInodeEntry(&o.Entry, e) + + oo := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{}))) + oo.Fh = uint64(o.Handle) - o.bfReq.Respond(&resp) return } @@ -315,7 +328,7 @@ func (o *CreateFileOp) respond() { // return EEXIST (cf. the notes on CreateFileOp and MkDirOp). type CreateSymlinkOp struct { commonOp - bfReq *bazilfuse.SymlinkRequest + protocol fusekernel.Protocol // The ID of parent directory inode within which to create the child symlink. Parent InodeID @@ -344,11 +357,12 @@ func (o *CreateSymlinkOp) ShortDesc() (desc string) { return } -func (o *CreateSymlinkOp) respond() { - resp := bazilfuse.SymlinkResponse{} - convertChildInodeEntry(&o.Entry, &resp.LookupResponse) +func (o *CreateSymlinkOp) kernelResponse() (b buffer.OutMessage) { + size := fusekernel.EntryOutSize(o.protocol) + b = buffer.NewOutMessage(size) + out := (*fusekernel.EntryOut)(b.Grow(size)) + convertChildInodeEntry(&o.Entry, out) - o.bfReq.Respond(&resp) return } @@ -392,7 +406,6 @@ func (o *CreateSymlinkOp) respond() { // type RenameOp struct { commonOp - bfReq *bazilfuse.RenameRequest // The old parent directory, and the name of the entry within it to be // relocated. @@ -405,8 +418,8 @@ type RenameOp struct { NewName string } -func (o *RenameOp) respond() { - o.bfReq.Respond() +func (o *RenameOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -419,7 +432,6 @@ func (o *RenameOp) respond() { // Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf) type RmDirOp struct { commonOp - bfReq *bazilfuse.RemoveRequest // The ID of parent directory inode, and the name of the directory being // removed within it. @@ -427,8 +439,8 @@ type RmDirOp struct { Name string } -func (o *RmDirOp) respond() { - o.bfReq.Respond() +func (o *RmDirOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -440,7 +452,6 @@ func (o *RmDirOp) respond() { // Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C) type UnlinkOp struct { commonOp - bfReq *bazilfuse.RemoveRequest // The ID of parent directory inode, and the name of the entry being removed // within it. @@ -448,8 +459,8 @@ type UnlinkOp struct { Name string } -func (o *UnlinkOp) respond() { - o.bfReq.Respond() +func (o *UnlinkOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -465,14 +476,10 @@ func (o *UnlinkOp) respond() { // https://github.com/osxfuse/osxfuse/issues/199). type OpenDirOp struct { commonOp - bfReq *bazilfuse.OpenRequest // The ID of the inode to be opened. Inode InodeID - // Mode and options flags. - Flags bazilfuse.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 @@ -484,19 +491,17 @@ type OpenDirOp struct { Handle HandleID } -func (o *OpenDirOp) respond() { - resp := bazilfuse.OpenResponse{ - Handle: bazilfuse.HandleID(o.Handle), - } +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) - o.bfReq.Respond(&resp) return } // Read entries from a directory previously opened with OpenDir. type ReadDirOp struct { commonOp - bfReq *bazilfuse.ReadRequest // The directory inode that we are reading, and the handle previously // returned by OpenDir when opening that inode. @@ -584,12 +589,9 @@ type ReadDirOp struct { Data []byte } -func (o *ReadDirOp) respond() { - resp := bazilfuse.ReadResponse{ - Data: o.Data, - } - - o.bfReq.Respond(&resp) +func (o *ReadDirOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(uintptr(len(o.Data))) + b.Append(o.Data) return } @@ -603,7 +605,6 @@ 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 // 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 @@ -611,8 +612,8 @@ type ReleaseDirHandleOp struct { Handle HandleID } -func (o *ReleaseDirHandleOp) respond() { - o.bfReq.Respond() +func (o *ReleaseDirHandleOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -628,14 +629,10 @@ func (o *ReleaseDirHandleOp) respond() { // (cf.https://github.com/osxfuse/osxfuse/issues/199). type OpenFileOp struct { commonOp - bfReq *bazilfuse.OpenRequest // The ID of the inode to be opened. Inode InodeID - // Mode and options flags. - Flags bazilfuse.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). @@ -646,12 +643,11 @@ type OpenFileOp struct { Handle HandleID } -func (o *OpenFileOp) respond() { - resp := bazilfuse.OpenResponse{ - Handle: bazilfuse.HandleID(o.Handle), - } +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) - o.bfReq.Respond(&resp) return } @@ -662,7 +658,6 @@ func (o *OpenFileOp) respond() { // more. type ReadFileOp struct { commonOp - bfReq *bazilfuse.ReadRequest // The file inode that we are reading, and the handle previously returned by // CreateFile or OpenFile when opening that inode. @@ -685,12 +680,9 @@ type ReadFileOp struct { Data []byte } -func (o *ReadFileOp) respond() { - resp := bazilfuse.ReadResponse{ - Data: o.Data, - } - - o.bfReq.Respond(&resp) +func (o *ReadFileOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(uintptr(len(o.Data))) + b.Append(o.Data) return } @@ -727,7 +719,6 @@ func (o *ReadFileOp) respond() { // concurrent requests".) type WriteFileOp struct { commonOp - bfReq *bazilfuse.WriteRequest // The file inode that we are modifying, and the handle previously returned // by CreateFile or OpenFile when opening that inode. @@ -765,12 +756,11 @@ type WriteFileOp struct { Data []byte } -func (o *WriteFileOp) respond() { - resp := bazilfuse.WriteResponse{ - Size: len(o.Data), - } +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)) - o.bfReq.Respond(&resp) return } @@ -792,15 +782,14 @@ func (o *WriteFileOp) respond() { // file (but which is not used in "real" file systems). type SyncFileOp struct { commonOp - bfReq *bazilfuse.FsyncRequest // The file and handle being sync'd. Inode InodeID Handle HandleID } -func (o *SyncFileOp) respond() { - o.bfReq.Respond() +func (o *SyncFileOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -853,15 +842,14 @@ func (o *SyncFileOp) respond() { // return any errors that occur. type FlushFileOp struct { commonOp - bfReq *bazilfuse.FlushRequest // The file and handle being flushed. Inode InodeID Handle HandleID } -func (o *FlushFileOp) respond() { - o.bfReq.Respond() +func (o *FlushFileOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -875,7 +863,6 @@ 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 // 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 @@ -883,8 +870,8 @@ type ReleaseFileHandleOp struct { Handle HandleID } -func (o *ReleaseFileHandleOp) respond() { - o.bfReq.Respond() +func (o *ReleaseFileHandleOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(0) return } @@ -892,14 +879,16 @@ func (o *ReleaseFileHandleOp) respond() { // 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 } -func (o *unknownOp) respond() { +func (o *unknownOp) kernelResponse() (b buffer.OutMessage) { panic(fmt.Sprintf("Should never get here for unknown op: %s", o.ShortDesc())) } @@ -910,7 +899,6 @@ func (o *unknownOp) respond() { // Read the target of a symlink inode. type ReadSymlinkOp struct { commonOp - bfReq *bazilfuse.ReadlinkRequest // The symlink inode that we are reading. Inode InodeID @@ -919,7 +907,68 @@ type ReadSymlinkOp struct { Target string } -func (o *ReadSymlinkOp) respond() { - o.bfReq.Respond(o.Target) +func (o *ReadSymlinkOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(uintptr(len(o.Target))) + b.AppendString(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.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. 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 { + commonOp +} + +func (o *InternalStatFSOp) kernelResponse() (b buffer.OutMessage) { + b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.StatfsOut{})) + b.Grow(unsafe.Sizeof(fusekernel.StatfsOut{})) + + return +} + +// Do not use this struct directly. See the TODO in fuseops/ops.go. +type InternalInterruptOp struct { + commonOp + FuseID uint64 +} + +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 } diff --git a/fuseops/simple_types.go b/fuseops/simple_types.go index 1e9b7f1..c41d872 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/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 bazilfuse.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 != bazilfuse.RootID { + if RootInodeID != fusekernel.RootID { panic( fmt.Sprintf( "Oops, RootInodeID is wrong: %v vs. %v", RootInodeID, - bazilfuse.RootID)) + fusekernel.RootID)) } } @@ -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. diff --git a/internal/buffer/in_message.go b/internal/buffer/in_message.go new file mode 100644 index 0000000..f87f3b0 --- /dev/null +++ b/internal/buffer/in_message.go @@ -0,0 +1,115 @@ +// 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 ( + "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 { + remaining []byte + storage [bufSize]byte +} + +// 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) { + n, err := r.Read(m.storage[:]) + if err != nil { + return + } + + // 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( + "Header says %d bytes, but we read %d", + m.Header().Len, + n) + + return + } + + return +} + +// Return a reference to the header read in the most recent call to Init. +func (m *InMessage) Header() (h *fusekernel.InHeader) { + h = (*fusekernel.InHeader)(unsafe.Pointer(&m.storage[0])) + return +} + +// Return the number of bytes left to consume. +func (m *InMessage) Len() uintptr { + 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) { + 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 would fail. +func (m *InMessage) ConsumeBytes(n uintptr) (b []byte) { + if n > m.Len() { + return + } + + b = m.remaining[:n] + m.remaining = m.remaining[n:] + + return +} diff --git a/internal/buffer/in_message_darwin.go b/internal/buffer/in_message_darwin.go new file mode 100644 index 0000000..af37a02 --- /dev/null +++ b/internal/buffer/in_message_darwin.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, 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 diff --git a/internal/buffer/out_message.go b/internal/buffer/out_message.go new file mode 100644 index 0000000..0fe8ef4 --- /dev/null +++ b/internal/buffer/out_message.go @@ -0,0 +1,84 @@ +// 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" +) + +// 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 NewOutMessage. Exception: the zero value has +// Bytes() == nil. +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 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 *OutMessage) 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 *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)] + return +} + +// Equivalent to growing by the length of p, then copying p into the new segment. +func (b *OutMessage) 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 *OutMessage) 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 *OutMessage) Bytes() []byte { + return b.slice +} diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go new file mode 100644 index 0000000..778f5fe --- /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 fusekernel + +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) GetPosition() uint32 { + return 0 +} + +type getxattrInCommon struct { + Size uint32 + Padding uint32 +} + +func (getxattrInCommon) GetPosition() 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..fc02267 --- /dev/null +++ b/internal/fusekernel/fuse_kernel_darwin.go @@ -0,0 +1,88 @@ +package fusekernel + +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) GetPosition() uint32 { + return g.Position +} + +type SetxattrIn struct { + setxattrInCommon + + // OS X only + Position uint32 + Padding uint32 +} + +func (s *SetxattrIn) GetPosition() uint32 { + return s.Position +} diff --git a/internal/fusekernel/fuse_kernel_linux.go b/internal/fusekernel/fuse_kernel_linux.go new file mode 100644 index 0000000..0472e1d --- /dev/null +++ b/internal/fusekernel/fuse_kernel_linux.go @@ -0,0 +1,70 @@ +package fusekernel + +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..704fb00 --- /dev/null +++ b/internal/fusekernel/fuse_kernel_std.go @@ -0,0 +1 @@ +package fusekernel diff --git a/internal/fusekernel/protocol.go b/internal/fusekernel/protocol.go new file mode 100644 index 0000000..9bc17e2 --- /dev/null +++ b/internal/fusekernel/protocol.go @@ -0,0 +1,75 @@ +package fusekernel + +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() +} diff --git a/mount_config.go b/mount_config.go new file mode 100644 index 0000000..dd76561 --- /dev/null +++ b/mount_config.go @@ -0,0 +1,153 @@ +// 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 ( + "fmt" + "log" + "runtime" + "strings" + + "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) { + isDarwin := runtime.GOOS == "darwin" + opts = make(map[string]string) + + // Enable permissions checking in the kernel. See the comments on + // InodeAttributes.Mode. + 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 + // 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["fsname"] = fsname + } + + // Read only? + if c.ReadOnly { + opts["ro"] = "" + } + + // OS X: set novncache when appropriate. + if isDarwin && !c.EnableVnodeCaching { + opts["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["noappledouble"] = "" + } + + // Last but not least: other user-supplied options. + for k, v := range c.Options { + opts[k] = v + } + + 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 { + 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, ",") +} diff --git a/mount_darwin.go b/mount_darwin.go new file mode 100644 index 0000000..8ecf640 --- /dev/null +++ b/mount_darwin.go @@ -0,0 +1,155 @@ +package fuse + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "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 { + 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() (dev *os.File, err error) { + // Try each device name. + for i := uint64(0); ; i++ { + 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 must not be loaded. + err = errNotLoaded + return + } + + // Otherwise we've run out of kernel-provided devices + err = errNoAvail + return + } + + if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY { + // This device is in use; try the next one. + continue + } + + return + } +} + +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 cfg.toMap() { + if strings.Contains(k, ",") || strings.Contains(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", cfg.toOptionsString(), + // 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(buffer.MaxWriteSize, 10), + // refers to fd passed in cmd.ExtraFiles + "3", + dir, + ) + cmd.ExtraFiles = []*os.File{dev} + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=") + 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 + } + + // 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") + err = fmt.Errorf("%v: %s", err, output) + } + } + + ready <- err + }() + + return +} + +// 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) { + // 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 { + err = fmt.Errorf("loadOSXFUSE: %v", err) + return + } + + dev, err = openOSXFUSEDev() + } + + // Propagate errors. + if err != nil { + err = fmt.Errorf("openOSXFUSEDev: %v", err) + return + } + + // Call the mount binary with the device. + err = callMount(dir, cfg, dev, ready) + if err != nil { + dev.Close() + err = fmt.Errorf("callMount: %v", err) + return + } + + return +} diff --git a/mount_linux.go b/mount_linux.go new file mode 100644 index 0000000..94858bd --- /dev/null +++ b/mount_linux.go @@ -0,0 +1,158 @@ +package fuse + +import ( + "bufio" + "fmt" + "io" + "log" + "net" + "os" + "os/exec" + "sync" + "syscall" +) + +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) + } +} + +// 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 { + 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", cfg.toOptionsString(), + "--", + dir, + ) + + cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") + cmd.ExtraFiles = []*os.File{writeFile} + + stdout, err := cmd.StdoutPipe() + if err != nil { + err = fmt.Errorf("StdoutPipe: %v", err) + return + } + + 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() + + // 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 { + 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 { + 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 { + err = fmt.Errorf("ParseSocketControlMessage: %v", err) + return + } + + // We expect one message. + if len(scms) != 1 { + 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 { + err = fmt.Errorf("syscall.ParseUnixRights: %v", err) + return + } + + if len(gotFds) != 1 { + err = fmt.Errorf("wanted 1 fd; got %#v", gotFds) + return + } + + // Turn the FD into an os.File. + dev = os.NewFile(uintptr(gotFds[0]), "/dev/fuse") + + return +} diff --git a/mounted_file_system.go b/mounted_file_system.go index 580ea6b..54532bb 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -16,10 +16,7 @@ package fuse import ( "fmt" - "log" - "runtime" - "github.com/jacobsa/bazilfuse" "golang.org/x/net/context" ) @@ -62,129 +59,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 -} - -// Convert to mount options to be passed to package bazilfuse. -func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) { - isDarwin := runtime.GOOS == "darwin" - - // Enable permissions checking in the kernel. See the comments on - // InodeAttributes.Mode. - opts = append(opts, bazilfuse.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, bazilfuse.FSName(fsname)) - } - - // Read only? - if c.ReadOnly { - opts = append(opts, bazilfuse.ReadOnly()) - } - - // OS X: set novncache when appropriate. - if isDarwin && !c.EnableVnodeCaching { - opts = append(opts, bazilfuse.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, bazilfuse.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, bazilfuse.MaxReadahead(maxReadahead)) - - // Last but not least: other user-supplied options. - for k, v := range c.Options { - opts = append(opts, bazilfuse.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. @@ -198,10 +72,11 @@ func Mount( joinStatusAvailable: make(chan struct{}), } - // Open a bazilfuse connection. - bfConn, err := bazilfuse.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("bazilfuse.Mount: %v", err) + err = fmt.Errorf("mount: %v", err) return } @@ -211,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 } @@ -231,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 } diff --git a/samples/flushfs/flush_fs_test.go b/samples/flushfs/flush_fs_test.go index 2f8f0fb..278f0ba 100644 --- a/samples/flushfs/flush_fs_test.go +++ b/samples/flushfs/flush_fs_test.go @@ -30,7 +30,6 @@ import ( "golang.org/x/sys/unix" - "github.com/jacobsa/bazilfuse" "github.com/jacobsa/fuse/fsutil" "github.com/jacobsa/fuse/fusetesting" "github.com/jacobsa/fuse/samples" @@ -58,8 +57,8 @@ type flushFSTest struct { func (t *flushFSTest) setUp( ti *TestInfo, - flushErr bazilfuse.Errno, - fsyncErr bazilfuse.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, bazilfuse.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, bazilfuse.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 c13fe3b..5a25e12 100644 --- a/samples/mount_sample/mount.go +++ b/samples/mount_sample/mount.go @@ -23,8 +23,8 @@ import ( "log" "os" "runtime" + "syscall" - "github.com/jacobsa/bazilfuse" "github.com/jacobsa/fuse" "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 = syscall.Errno(*fFlushError) } if *fFsyncError != 0 { - fsyncErr = bazilfuse.Errno(*fFsyncError) + fsyncErr = syscall.Errno(*fFsyncError) } // Report flushes and fsyncs by writing the contents followed by a newline. diff --git a/unmount.go b/unmount.go index 2c50925..a9d78e0 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 unmount(dir) } diff --git a/unmount_linux.go b/unmount_linux.go new file mode 100644 index 0000000..c9ed745 --- /dev/null +++ b/unmount_linux.go @@ -0,0 +1,23 @@ +package fuse + +import ( + "bytes" + "fmt" + "os/exec" +) + +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") + err = fmt.Errorf("%v: %s", err, output) + } + + return + } + + return +} diff --git a/unmount_std.go b/unmount_std.go new file mode 100644 index 0000000..3324d6c --- /dev/null +++ b/unmount_std.go @@ -0,0 +1,18 @@ +// +build !linux + +package fuse + +import ( + "os" + "syscall" +) + +func unmount(dir string) (err error) { + err = syscall.Unmount(dir, 0) + if err != nil { + err = &os.PathError{Op: "unmount", Path: dir, Err: err} + return + } + + return +}