Eliminated the dependency on bazilfuse.

geesefs-0-30-9
Aaron Jacobs 2015-07-27 12:58:19 +10:00
commit cd7e6b6dd9
26 changed files with 2795 additions and 586 deletions

98
LICENSE
View File

@ -200,3 +200,101 @@ Apache License
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. 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 <miklos@szeredi.hu>
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.
*/

View File

@ -1,31 +1,30 @@
[![GoDoc](https://godoc.org/github.com/jacobsa/ogletest?status.svg)](https://godoc.org/github.com/jacobsa/fuse) [![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 This package allows for writing and mounting user-space file systems from Go.
is a wrapper around [bazil.org/fuse][bazil], which does the heavy lifting. It Install it as follows:
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.
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 Afterward, see the documentation for the following three packages:
implementation for every method in the interface. Embed a
`fuseutil.NotImplementedFileSystem` struct to have default implementations
that return `ENOSYS`.
* Every method, struct, and field is thoroughly documented. This may help you * Package [fuse][] provides support for mounting a new file system and
get your bearings in the world of FUSE, the Linux VFS, traditional file reading requests from the kernel.
system implementations, etc., all of which tend to be very poorly
documented.
* Support for arbitrary offsets in directory entries returned by `ReadDir`. * Package [fuseops][] enumerates the supported requests from the kernel, and
(The bazil.org package assumes that offsets must be counts of bytes.) provides documentation on their semantics.
The very large disadvantage over using the bazil.org packages is that many * Package [fuseutil][], in particular the `FileSystem` interface, provides a
features have not yet been exposed. 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 This package owes its inspiration and most of its kernel-related code to
[bazil-fs]: http://godoc.org/bazil.org/fuse/fs [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 [samples]: http://godoc.org/github.com/jacobsa/fuse/samples
[bazil]: http://godoc.org/bazil.org/fuse

View File

@ -16,22 +16,49 @@ package fuse
import ( import (
"fmt" "fmt"
"io"
"log" "log"
"os"
"path" "path"
"runtime" "runtime"
"sync" "sync"
"syscall"
"golang.org/x/net/context" "golang.org/x/net/context"
"github.com/jacobsa/bazilfuse"
"github.com/jacobsa/fuse/fuseops" "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. // A connection to the fuse kernel process.
type Connection struct { type Connection struct {
debugLogger *log.Logger debugLogger *log.Logger
errorLogger *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. // The context from which all op contexts inherit.
parentCtx context.Context parentCtx context.Context
@ -41,30 +68,85 @@ type Connection struct {
mu sync.Mutex mu sync.Mutex
// A map from bazilfuse request ID (*not* the op ID for logging used above) // A map from fuse "unique" request ID (*not* the op ID for logging used
// to a function that cancel's its associated context. // above) to a function that cancel's its associated context.
// //
// GUARDED_BY(mu) // GUARDED_BY(mu)
cancelFuncs map[bazilfuse.RequestID]func() cancelFuncs map[uint64]func()
} }
// Responsibility for closing the wrapped connection is transferred to the // Create a connection wrapping the supplied file descriptor connected to the
// result. You must call c.close() eventually. // kernel. You must eventually call c.close().
// //
// The loggers may be nil. // The loggers may be nil.
func newConnection( func newConnection(
parentCtx context.Context, parentCtx context.Context,
debugLogger *log.Logger, debugLogger *log.Logger,
errorLogger *log.Logger, errorLogger *log.Logger,
wrapped *bazilfuse.Conn) (c *Connection, err error) { dev *os.File) (c *Connection, err error) {
c = &Connection{ c = &Connection{
debugLogger: debugLogger, debugLogger: debugLogger,
errorLogger: errorLogger, errorLogger: errorLogger,
wrapped: wrapped, dev: dev,
parentCtx: parentCtx, 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 return
} }
@ -104,28 +186,27 @@ func (c *Connection) debugLog(
// LOCKS_EXCLUDED(c.mu) // LOCKS_EXCLUDED(c.mu)
func (c *Connection) recordCancelFunc( func (c *Connection) recordCancelFunc(
reqID bazilfuse.RequestID, fuseID uint64,
f func()) { f func()) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if _, ok := c.cancelFuncs[reqID]; ok { if _, ok := c.cancelFuncs[fuseID]; ok {
panic(fmt.Sprintf("Already have cancel func for request %v", reqID)) 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 // 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. // Return a context that should be used for the op.
// //
// LOCKS_EXCLUDED(c.mu) // LOCKS_EXCLUDED(c.mu)
func (c *Connection) beginOp( func (c *Connection) beginOp(
bfReq bazilfuse.Request) (ctx context.Context) { opCode uint32,
reqID := bfReq.Hdr().ID fuseID uint64) (ctx context.Context) {
// Start with the parent context. // Start with the parent context.
ctx = c.parentCtx ctx = c.parentCtx
@ -137,46 +218,46 @@ func (c *Connection) beginOp(
// should not record any state keyed on their ID. // should not record any state keyed on their ID.
// //
// Cf. https://github.com/osxfuse/osxfuse/issues/208 // Cf. https://github.com/osxfuse/osxfuse/issues/208
if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok { if opCode != fusekernel.OpForget {
var cancel func() var cancel func()
ctx, cancel = context.WithCancel(ctx) ctx, cancel = context.WithCancel(ctx)
c.recordCancelFunc(reqID, cancel) c.recordCancelFunc(fuseID, cancel)
} }
return return
} }
// Clean up all state associated with an op to which the user has responded, // 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 // given its underlying fuse opcode and request ID. This must be called before
// response is sent to the kernel, to avoid a race where the request's ID might // a response is sent to the kernel, to avoid a race where the request's ID
// be reused by osxfuse. // might be reused by osxfuse.
// //
// LOCKS_EXCLUDED(c.mu) // LOCKS_EXCLUDED(c.mu)
func (c *Connection) finishOp(bfReq bazilfuse.Request) { func (c *Connection) finishOp(
opCode uint32,
fuseID uint64) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
reqID := bfReq.Hdr().ID
// Even though the op is finished, context.WithCancel requires us to arrange // 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 // for the cancellation function to be invoked. We also must remove it from
// our map. // our map.
// //
// Special case: we don't do this for Forget requests. See the note in // Special case: we don't do this for Forget requests. See the note in
// beginOp above. // beginOp above.
if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok { if opCode != fusekernel.OpForget {
cancel, ok := c.cancelFuncs[reqID] cancel, ok := c.cancelFuncs[fuseID]
if !ok { if !ok {
panic(fmt.Sprintf("Unknown request ID in finishOp: %v", reqID)) panic(fmt.Sprintf("Unknown request ID in finishOp: %v", fuseID))
} }
cancel() cancel()
delete(c.cancelFuncs, reqID) delete(c.cancelFuncs, fuseID)
} }
} }
// LOCKS_EXCLUDED(c.mu) // LOCKS_EXCLUDED(c.mu)
func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) { func (c *Connection) handleInterrupt(fuseID uint64) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -194,7 +275,7 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
// //
// Cf. https://github.com/osxfuse/osxfuse/issues/208 // Cf. https://github.com/osxfuse/osxfuse/issues/208
// Cf. http://comments.gmane.org/gmane.comp.file-systems.fuse.devel/14675 // 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 { if !ok {
return return
} }
@ -202,6 +283,71 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
cancel() 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 // Read the next op from the kernel process. Return io.EOF if the kernel has
// closed the connection. // closed the connection.
// //
@ -212,38 +358,19 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
func (c *Connection) ReadOp() (op fuseops.Op, err error) { func (c *Connection) ReadOp() (op fuseops.Op, err error) {
// Keep going until we find a request we know how to convert. // Keep going until we find a request we know how to convert.
for { for {
// Read a bazilfuse request. // Read the next message from the kernel.
var bfReq bazilfuse.Request var m *buffer.InMessage
bfReq, err = c.wrapped.ReadRequest() m, err = c.readMessage()
if err != nil { if err != nil {
return return
} }
// Choose an ID for this operation. // Choose an ID for this operation for the purposes of logging.
opID := c.nextOpID opID := c.nextOpID
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. // Set up op dependencies.
opCtx := c.beginOp(bfReq) opCtx := c.beginOp(m.Header().Opcode, m.Header().Unique)
var debugLogForOp func(int, string, ...interface{}) var debugLogForOp func(int, string, ...interface{})
if c.debugLogger != nil { 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, opCtx,
bfReq, m,
c.protocol,
debugLogForOp, debugLogForOp,
c.errorLogger, 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 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 // Close the connection. Must not be called until operations that were read
// from the connection have been responded to. // from the connection have been responded to.
func (c *Connection) close() (err error) { 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 return
} }

View File

@ -14,20 +14,16 @@
package fuse package fuse
import ( import "syscall"
"syscall"
"github.com/jacobsa/bazilfuse"
)
const ( const (
// Errors corresponding to kernel error numbers. These may be treated // Errors corresponding to kernel error numbers. These may be treated
// specially by fuseops.Op.Respond methods. // specially by fuseops.Op.Respond methods.
EEXIST = bazilfuse.EEXIST EEXIST = syscall.EEXIST
EINVAL = bazilfuse.Errno(syscall.EINVAL) EINVAL = syscall.EINVAL
EIO = bazilfuse.EIO EIO = syscall.EIO
ENOENT = bazilfuse.ENOENT ENOENT = syscall.ENOENT
ENOSYS = bazilfuse.ENOSYS ENOSYS = syscall.ENOSYS
ENOTDIR = bazilfuse.Errno(syscall.ENOTDIR) ENOTDIR = syscall.ENOTDIR
ENOTEMPTY = bazilfuse.Errno(syscall.ENOTEMPTY) ENOTEMPTY = syscall.ENOTEMPTY
) )

View File

@ -19,8 +19,9 @@ import (
"log" "log"
"reflect" "reflect"
"strings" "strings"
"syscall"
"github.com/jacobsa/bazilfuse" "github.com/jacobsa/fuse/internal/buffer"
"github.com/jacobsa/reqtrace" "github.com/jacobsa/reqtrace"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -30,10 +31,19 @@ import (
type internalOp interface { type internalOp interface {
Op Op
// Respond to the underlying bazilfuse request, successfully. // Create a response message for the kernel, leaving the leading
respond() // 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. // A helper for embedding common behavior.
type commonOp struct { type commonOp struct {
// The context exposed to the user. // The context exposed to the user.
@ -42,8 +52,11 @@ type commonOp struct {
// The op in which this struct is embedded. // The op in which this struct is embedded.
op internalOp op internalOp
// The underlying bazilfuse request for this op. // The fuse unique ID of this request, as assigned by the kernel.
bazilReq bazilfuse.Request 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 // A function that can be used to log debug information about the op. The
// first argument is a call depth. // first argument is a call depth.
@ -55,14 +68,11 @@ type commonOp struct {
// //
// May be nil. // May be nil.
errorLogger *log.Logger 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) { 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 // Attempt to better handle the usual case: a string that looks like
// "*fuseops.GetInodeAttributesOp". // "*fuseops.GetInodeAttributesOp".
@ -72,36 +82,46 @@ func (o *commonOp) ShortDesc() (desc string) {
opName = opName[len(prefix) : len(opName)-len(suffix)] opName = opName[len(prefix) : len(opName)-len(suffix)]
} }
// Include the inode number to which the op applies. desc = opName
desc = fmt.Sprintf("%s(inode=%v)", opName, o.bazilReq.Hdr().Node)
// Include the inode number to which the op applies, if possible.
if f := v.Elem().FieldByName("Inode"); f.IsValid() {
desc = fmt.Sprintf("%s(inode=%v)", desc, f.Interface())
}
return return
} }
func (o *commonOp) DebugString() string {
// By default, defer to ShortDesc.
return o.op.ShortDesc()
}
func (o *commonOp) init( func (o *commonOp) init(
ctx context.Context, ctx context.Context,
op internalOp, op internalOp,
bazilReq bazilfuse.Request, fuseID uint64,
sendReply replyFunc,
debugLog func(int, string, ...interface{}), debugLog func(int, string, ...interface{}),
errorLogger *log.Logger, errorLogger *log.Logger) {
finished func(error)) {
// Initialize basic fields. // Initialize basic fields.
o.ctx = ctx o.ctx = ctx
o.op = op o.op = op
o.bazilReq = bazilReq o.fuseID = fuseID
o.sendReply = sendReply
o.debugLog = debugLog o.debugLog = debugLog
o.errorLogger = errorLogger o.errorLogger = errorLogger
o.finished = finished
// Set up a trace span for this op. // Set up a trace span for this op.
var reportForTrace reqtrace.ReportFunc var reportForTrace reqtrace.ReportFunc
o.ctx, reportForTrace = reqtrace.StartSpan(o.ctx, o.op.ShortDesc()) o.ctx, reportForTrace = reqtrace.StartSpan(o.ctx, o.op.ShortDesc())
// When the op is finished, report to both reqtrace and the connection. // When the op is finished, report to both reqtrace and the connection.
prevFinish := o.finished prevSendReply := o.sendReply
o.finished = func(err error) { o.sendReply = func(op Op, fuseID uint64, msg []byte, opErr error) (err error) {
reportForTrace(err) reportForTrace(opErr)
prevFinish(err) 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) { func (o *commonOp) Respond(err error) {
// Report that the user is responding. // If successful, we ask the op for an appopriate response to the kernel, and
o.finished(err) // it is responsible for leaving room for the fusekernel.OutHeader struct.
// Otherwise, create our own.
// If successful, we should respond to bazilfuse with the appropriate struct. var b buffer.OutMessage
if err == nil { if err == nil {
o.op.respond() b = o.op.kernelResponse()
return } else {
b = buffer.NewOutMessage(0)
} }
// Log the error. // Fill in the header if a reply is needed.
if o.debugLog != nil { msg := b.Bytes()
o.Logf( if msg != nil {
"-> (%s) error: %v", h := b.OutHeader()
o.op.ShortDesc(), h.Unique = o.fuseID
err) 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 { // Reply.
o.errorLogger.Printf( replyErr := o.sendReply(o.op, o.fuseID, msg, err)
"(%s) error: %v", if replyErr != nil && o.errorLogger != nil {
o.op.ShortDesc(), o.errorLogger.Printf("Error from sendReply: %v", replyErr)
err)
} }
// Send a response to the kernel.
o.bazilReq.RespondError(err)
} }

View File

@ -15,252 +15,430 @@
package fuseops package fuseops
import ( import (
"bytes"
"errors"
"log" "log"
"os"
"syscall"
"time" "time"
"unsafe"
"github.com/jacobsa/fuse/internal/buffer"
"github.com/jacobsa/fuse/internal/fusekernel"
"golang.org/x/net/context" "golang.org/x/net/context"
"github.com/jacobsa/bazilfuse"
) )
// This function is an implementation detail of the fuse package, and must not // This function is an implementation detail of the fuse package, and must not
// be called by anyone else. // be called by anyone else.
// //
// Convert the supplied bazilfuse request struct to an Op. finished will be // Convert the supplied fuse kernel message to an Op. sendReply will be used to
// called with the error supplied to o.Respond when the user invokes that // send the reply back to the kernel once the user calls o.Respond. If the op
// method, before a response is sent to the kernel. // is unknown, a special unexported type will be used.
// //
// It is guaranteed that o != nil. If the op is unknown, a special unexported // The debug logging function and error logger may be nil. The caller is
// type will be used. // responsible for arranging for the message to be destroyed.
//
// The debug logging function and error logger may be nil.
func Convert( func Convert(
opCtx context.Context, opCtx context.Context,
r bazilfuse.Request, m *buffer.InMessage,
protocol fusekernel.Protocol,
debugLogForOp func(int, string, ...interface{}), debugLogForOp func(int, string, ...interface{}),
errorLogger *log.Logger, errorLogger *log.Logger,
finished func(error)) (o Op) { sendReply replyFunc) (o Op, err error) {
var co *commonOp var co *commonOp
var io internalOp var io internalOp
switch typed := r.(type) { switch m.Header().Opcode {
case *bazilfuse.LookupRequest: 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{ to := &LookUpInodeOp{
bfReq: typed, protocol: protocol,
Parent: InodeID(typed.Header.Node), Parent: InodeID(m.Header().Nodeid),
Name: typed.Name, Name: string(buf[:n-1]),
} }
io = to io = to
co = &to.commonOp co = &to.commonOp
case *bazilfuse.GetattrRequest: case fusekernel.OpGetattr:
to := &GetInodeAttributesOp{ to := &GetInodeAttributesOp{
bfReq: typed, protocol: protocol,
Inode: InodeID(typed.Header.Node), Inode: InodeID(m.Header().Nodeid),
} }
io = to io = to
co = &to.commonOp 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{ to := &SetInodeAttributesOp{
bfReq: typed, protocol: protocol,
Inode: InodeID(typed.Header.Node), Inode: InodeID(m.Header().Nodeid),
} }
if typed.Valid&bazilfuse.SetattrSize != 0 { valid := fusekernel.SetattrValid(in.Valid)
to.Size = &typed.Size if valid&fusekernel.SetattrSize != 0 {
to.Size = &in.Size
} }
if typed.Valid&bazilfuse.SetattrMode != 0 { if valid&fusekernel.SetattrMode != 0 {
to.Mode = &typed.Mode mode := convertFileMode(in.Mode)
to.Mode = &mode
} }
if typed.Valid&bazilfuse.SetattrAtime != 0 { if valid&fusekernel.SetattrAtime != 0 {
to.Atime = &typed.Atime t := time.Unix(int64(in.Atime), int64(in.AtimeNsec))
to.Atime = &t
} }
if typed.Valid&bazilfuse.SetattrMtime != 0 { if valid&fusekernel.SetattrMtime != 0 {
to.Mtime = &typed.Mtime t := time.Unix(int64(in.Mtime), int64(in.MtimeNsec))
to.Mtime = &t
} }
io = to io = to
co = &to.commonOp 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{ to := &ForgetInodeOp{
bfReq: typed, Inode: InodeID(m.Header().Nodeid),
Inode: InodeID(typed.Header.Node), N: in.Nlookup,
N: typed.N,
} }
io = to io = to
co = &to.commonOp 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{ to := &MkDirOp{
bfReq: typed, protocol: protocol,
Parent: InodeID(typed.Header.Node), Parent: InodeID(m.Header().Nodeid),
Name: typed.Name, Name: string(name),
Mode: typed.Mode,
// On Linux, vfs_mkdir calls through to the inode with at most
// permissions and sticky bits set (cf. https://goo.gl/WxgQXk), and fuse
// passes that on directly (cf. https://goo.gl/f31aMo). In other words,
// the fact that this is a directory is implicit in the fact that the
// opcode is mkdir. But we want the correct mode to go through, so ensure
// that os.ModeDir is set.
Mode: convertFileMode(in.Mode) | os.ModeDir,
} }
io = to io = to
co = &to.commonOp 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{ to := &CreateFileOp{
bfReq: typed, protocol: protocol,
Parent: InodeID(typed.Header.Node), Parent: InodeID(m.Header().Nodeid),
Name: typed.Name, Name: string(name),
Mode: typed.Mode, Mode: convertFileMode(in.Mode),
Flags: typed.Flags,
} }
io = to io = to
co = &to.commonOp 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{ to := &CreateSymlinkOp{
bfReq: typed, protocol: protocol,
Parent: InodeID(typed.Header.Node), Parent: InodeID(m.Header().Nodeid),
Name: typed.NewName, Name: string(newName),
Target: typed.Target, Target: string(target),
} }
io = to io = to
co = &to.commonOp 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{ to := &RenameOp{
bfReq: typed, OldParent: InodeID(m.Header().Nodeid),
OldParent: InodeID(typed.Header.Node), OldName: string(oldName),
OldName: typed.OldName, NewParent: InodeID(in.Newdir),
NewParent: InodeID(typed.NewDir), NewName: string(newName),
NewName: typed.NewName,
} }
io = to io = to
co = &to.commonOp co = &to.commonOp
case *bazilfuse.RemoveRequest: case fusekernel.OpUnlink:
if typed.Dir { buf := m.ConsumeBytes(m.Len())
to := &RmDirOp{ n := len(buf)
bfReq: typed, if n == 0 || buf[n-1] != '\x00' {
Parent: InodeID(typed.Header.Node), err = errors.New("Corrupt OpUnlink")
Name: typed.Name, return
}
io = to
co = &to.commonOp
} else {
to := &UnlinkOp{
bfReq: typed,
Parent: InodeID(typed.Header.Node),
Name: typed.Name,
}
io = to
co = &to.commonOp
} }
case *bazilfuse.OpenRequest: to := &UnlinkOp{
if typed.Dir { Parent: InodeID(m.Header().Nodeid),
to := &OpenDirOp{ Name: string(buf[:n-1]),
bfReq: typed, }
Inode: InodeID(typed.Header.Node), io = to
Flags: typed.Flags, co = &to.commonOp
}
io = to case fusekernel.OpRmdir:
co = &to.commonOp buf := m.ConsumeBytes(m.Len())
} else { n := len(buf)
to := &OpenFileOp{ if n == 0 || buf[n-1] != '\x00' {
bfReq: typed, err = errors.New("Corrupt OpRmdir")
Inode: InodeID(typed.Header.Node), return
Flags: typed.Flags,
}
io = to
co = &to.commonOp
} }
case *bazilfuse.ReadRequest: to := &RmDirOp{
if typed.Dir { Parent: InodeID(m.Header().Nodeid),
to := &ReadDirOp{ Name: string(buf[:n-1]),
bfReq: typed, }
Inode: InodeID(typed.Header.Node), io = to
Handle: HandleID(typed.Handle), co = &to.commonOp
Offset: DirOffset(typed.Offset),
Size: typed.Size, case fusekernel.OpOpen:
} to := &OpenFileOp{
io = to Inode: InodeID(m.Header().Nodeid),
co = &to.commonOp }
} else { io = to
to := &ReadFileOp{ co = &to.commonOp
bfReq: typed,
Inode: InodeID(typed.Header.Node), case fusekernel.OpOpendir:
Handle: HandleID(typed.Handle), to := &OpenDirOp{
Offset: typed.Offset, Inode: InodeID(m.Header().Nodeid),
Size: typed.Size, }
} io = to
io = to co = &to.commonOp
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: to := &ReadFileOp{
if typed.Dir { Inode: InodeID(m.Header().Nodeid),
to := &ReleaseDirHandleOp{ Handle: HandleID(in.Fh),
bfReq: typed, Offset: int64(in.Offset),
Handle: HandleID(typed.Handle), Size: int(in.Size),
} }
io = to io = to
co = &to.commonOp co = &to.commonOp
} else {
to := &ReleaseFileHandleOp{ case fusekernel.OpReaddir:
bfReq: typed, in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol)))
Handle: HandleID(typed.Handle), if in == nil {
} err = errors.New("Corrupt OpReaddir")
io = to return
co = &to.commonOp }
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{ to := &WriteFileOp{
bfReq: typed, Inode: InodeID(m.Header().Nodeid),
Inode: InodeID(typed.Header.Node), Handle: HandleID(in.Fh),
Handle: HandleID(typed.Handle), Data: buf,
Data: typed.Data, Offset: int64(in.Offset),
Offset: typed.Offset,
} }
io = to io = to
co = &to.commonOp co = &to.commonOp
case *bazilfuse.FsyncRequest: case fusekernel.OpFsync:
// We don't currently support this for directories. type input fusekernel.FsyncIn
if typed.Dir { in := (*input)(m.Consume(unsafe.Sizeof(input{})))
to := &unknownOp{} if in == nil {
io = to err = errors.New("Corrupt OpFsync")
co = &to.commonOp return
} else { }
to := &SyncFileOp{
bfReq: typed, to := &SyncFileOp{
Inode: InodeID(typed.Header.Node), Inode: InodeID(m.Header().Nodeid),
Handle: HandleID(typed.Handle), Handle: HandleID(in.Fh),
} }
io = to io = to
co = &to.commonOp 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{ to := &FlushFileOp{
bfReq: typed, Inode: InodeID(m.Header().Nodeid),
Inode: InodeID(typed.Header.Node), Handle: HandleID(in.Fh),
Handle: HandleID(typed.Handle),
} }
io = to io = to
co = &to.commonOp co = &to.commonOp
case *bazilfuse.ReadlinkRequest: case fusekernel.OpReadlink:
to := &ReadSymlinkOp{ to := &ReadSymlinkOp{
bfReq: typed, Inode: InodeID(m.Header().Nodeid),
Inode: InodeID(typed.Header.Node), }
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 io = to
co = &to.commonOp co = &to.commonOp
default: default:
to := &unknownOp{} to := &unknownOp{
opCode: m.Header().Opcode,
inode: InodeID(m.Header().Nodeid),
}
io = to io = to
co = &to.commonOp co = &to.commonOp
} }
@ -268,48 +446,69 @@ func Convert(
co.init( co.init(
opCtx, opCtx,
io, io,
r, m.Header().Unique,
sendReply,
debugLogForOp, debugLogForOp,
errorLogger, errorLogger)
finished)
o = io o = io
return return
} }
func convertTime(t time.Time) (secs uint64, nsec uint32) {
totalNano := t.UnixNano()
secs = uint64(totalNano / 1e9)
nsec = uint32(totalNano % 1e9)
return
}
func convertAttributes( func convertAttributes(
inode InodeID, inodeID InodeID,
attr InodeAttributes, in *InodeAttributes,
expiration time.Time) bazilfuse.Attr { out *fusekernel.Attr) {
return bazilfuse.Attr{ out.Ino = uint64(inodeID)
Inode: uint64(inode), out.Size = in.Size
Size: attr.Size, out.Atime, out.AtimeNsec = convertTime(in.Atime)
Mode: attr.Mode, out.Mtime, out.MtimeNsec = convertTime(in.Mtime)
Nlink: uint32(attr.Nlink), out.Ctime, out.CtimeNsec = convertTime(in.Ctime)
Atime: attr.Atime, out.SetCrtime(convertTime(in.Crtime))
Mtime: attr.Mtime, out.Nlink = uint32(in.Nlink) // TODO(jacobsa): Make the public field uint32?
Ctime: attr.Ctime, out.Uid = in.Uid
Crtime: attr.Crtime, out.Gid = in.Gid
Uid: attr.Uid,
Gid: attr.Gid, // Set the mode.
Valid: convertExpirationTime(expiration), 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 // Convert an absolute cache expiration time to a relative time from now for
// consumption by fuse. // consumption by the fuse kernel module.
func convertExpirationTime(t time.Time) (d time.Duration) { func convertExpirationTime(t time.Time) (secs uint64, nsecs uint32) {
// Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit // 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 // counts of nanoseconds (cf. http://goo.gl/EJupJV). So negative durations
// package converts time.Duration values to this form in a straightforward // are right out. There is no need to cap the positive magnitude, because
// way (cf. http://goo.gl/FJhV8j). // 2^64 seconds is well longer than the 2^63 ns range of time.Duration.
// d := t.Sub(time.Now())
// So negative durations are right out. There is no need to cap the positive if d > 0 {
// magnitude, because 2^64 seconds is well longer than the 2^63 ns range of secs = uint64(d / time.Second)
// time.Duration. nsecs = uint32((d % time.Second) / time.Nanosecond)
d = t.Sub(time.Now())
if d < 0 {
d = 0
} }
return return
@ -317,9 +516,41 @@ func convertExpirationTime(t time.Time) (d time.Duration) {
func convertChildInodeEntry( func convertChildInodeEntry(
in *ChildInodeEntry, in *ChildInodeEntry,
out *bazilfuse.LookupResponse) { out *fusekernel.EntryOut) {
out.Node = bazilfuse.NodeID(in.Child) out.Nodeid = uint64(in.Child)
out.Generation = uint64(in.Generation) out.Generation = uint64(in.Generation)
out.Attr = convertAttributes(in.Child, in.Attributes, in.AttributesExpiration) out.EntryValid, out.EntryValidNsec = convertExpirationTime(in.EntryExpiration)
out.EntryValid = 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
} }

View File

@ -18,8 +18,10 @@ import (
"fmt" "fmt"
"os" "os"
"time" "time"
"unsafe"
"github.com/jacobsa/bazilfuse" "github.com/jacobsa/fuse/internal/buffer"
"github.com/jacobsa/fuse/internal/fusekernel"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -30,6 +32,9 @@ type Op interface {
// A short description of the op, to be used in logging. // A short description of the op, to be used in logging.
ShortDesc() string 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. // A context that can be used for long-running operations.
Context() context.Context Context() context.Context
@ -55,7 +60,7 @@ type Op interface {
// when resolving user paths to dentry structs, which are then cached. // when resolving user paths to dentry structs, which are then cached.
type LookUpInodeOp struct { type LookUpInodeOp struct {
commonOp commonOp
bfReq *bazilfuse.LookupRequest protocol fusekernel.Protocol
// The ID of the directory inode to which the child belongs. // The ID of the directory inode to which the child belongs.
Parent InodeID Parent InodeID
@ -83,11 +88,12 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) {
return return
} }
func (o *LookUpInodeOp) respond() { func (o *LookUpInodeOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.LookupResponse{} size := fusekernel.EntryOutSize(o.protocol)
convertChildInodeEntry(&o.Entry, &resp) b = buffer.NewOutMessage(size)
out := (*fusekernel.EntryOut)(b.Grow(size))
convertChildInodeEntry(&o.Entry, out)
o.bfReq.Respond(&resp)
return return
} }
@ -97,7 +103,7 @@ func (o *LookUpInodeOp) respond() {
// field of ChildInodeEntry, etc. // field of ChildInodeEntry, etc.
type GetInodeAttributesOp struct { type GetInodeAttributesOp struct {
commonOp commonOp
bfReq *bazilfuse.GetattrRequest protocol fusekernel.Protocol
// The inode of interest. // The inode of interest.
Inode InodeID Inode InodeID
@ -109,12 +115,21 @@ type GetInodeAttributesOp struct {
AttributesExpiration time.Time AttributesExpiration time.Time
} }
func (o *GetInodeAttributesOp) respond() { func (o *GetInodeAttributesOp) DebugString() string {
resp := bazilfuse.GetattrResponse{ return fmt.Sprintf(
Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), "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 return
} }
@ -124,7 +139,7 @@ func (o *GetInodeAttributesOp) respond() {
// cases like ftrunctate(2). // cases like ftrunctate(2).
type SetInodeAttributesOp struct { type SetInodeAttributesOp struct {
commonOp commonOp
bfReq *bazilfuse.SetattrRequest protocol fusekernel.Protocol
// The inode of interest. // The inode of interest.
Inode InodeID Inode InodeID
@ -142,12 +157,13 @@ type SetInodeAttributesOp struct {
AttributesExpiration time.Time AttributesExpiration time.Time
} }
func (o *SetInodeAttributesOp) respond() { func (o *SetInodeAttributesOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.SetattrResponse{ size := fusekernel.AttrOutSize(o.protocol)
Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration), 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 return
} }
@ -192,7 +208,6 @@ func (o *SetInodeAttributesOp) respond() {
// implicitly decrementing all lookup counts to zero. // implicitly decrementing all lookup counts to zero.
type ForgetInodeOp struct { type ForgetInodeOp struct {
commonOp commonOp
bfReq *bazilfuse.ForgetRequest
// The inode whose reference count should be decremented. // The inode whose reference count should be decremented.
Inode InodeID Inode InodeID
@ -201,8 +216,8 @@ type ForgetInodeOp struct {
N uint64 N uint64
} }
func (o *ForgetInodeOp) respond() { func (o *ForgetInodeOp) kernelResponse() (b buffer.OutMessage) {
o.bfReq.Respond() // No response.
return return
} }
@ -223,7 +238,7 @@ func (o *ForgetInodeOp) respond() {
// Therefore the file system should return EEXIST if the name already exists. // Therefore the file system should return EEXIST if the name already exists.
type MkDirOp struct { type MkDirOp struct {
commonOp commonOp
bfReq *bazilfuse.MkdirRequest protocol fusekernel.Protocol
// The ID of parent directory inode within which to create the child. // The ID of parent directory inode within which to create the child.
Parent InodeID Parent InodeID
@ -244,11 +259,12 @@ func (o *MkDirOp) ShortDesc() (desc string) {
return return
} }
func (o *MkDirOp) respond() { func (o *MkDirOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.MkdirResponse{} size := fusekernel.EntryOutSize(o.protocol)
convertChildInodeEntry(&o.Entry, &resp.LookupResponse) b = buffer.NewOutMessage(size)
out := (*fusekernel.EntryOut)(b.Grow(size))
convertChildInodeEntry(&o.Entry, out)
o.bfReq.Respond(&resp)
return return
} }
@ -264,7 +280,7 @@ func (o *MkDirOp) respond() {
// Therefore the file system should return EEXIST if the name already exists. // Therefore the file system should return EEXIST if the name already exists.
type CreateFileOp struct { type CreateFileOp struct {
commonOp commonOp
bfReq *bazilfuse.CreateRequest protocol fusekernel.Protocol
// The ID of parent directory inode within which to create the child file. // The ID of parent directory inode within which to create the child file.
Parent InodeID Parent InodeID
@ -273,9 +289,6 @@ type CreateFileOp struct {
Name string Name string
Mode os.FileMode Mode os.FileMode
// Flags for the open operation.
Flags bazilfuse.OpenFlags
// Set by the file system: information about the inode that was created. // Set by the file system: information about the inode that was created.
// //
// The lookup count for the inode is implicitly incremented. See notes on // The lookup count for the inode is implicitly incremented. See notes on
@ -298,16 +311,16 @@ func (o *CreateFileOp) ShortDesc() (desc string) {
return return
} }
func (o *CreateFileOp) respond() { func (o *CreateFileOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.CreateResponse{ eSize := fusekernel.EntryOutSize(o.protocol)
OpenResponse: bazilfuse.OpenResponse{ b = buffer.NewOutMessage(eSize + unsafe.Sizeof(fusekernel.OpenOut{}))
Handle: bazilfuse.HandleID(o.Handle),
},
}
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 return
} }
@ -315,7 +328,7 @@ func (o *CreateFileOp) respond() {
// return EEXIST (cf. the notes on CreateFileOp and MkDirOp). // return EEXIST (cf. the notes on CreateFileOp and MkDirOp).
type CreateSymlinkOp struct { type CreateSymlinkOp struct {
commonOp commonOp
bfReq *bazilfuse.SymlinkRequest protocol fusekernel.Protocol
// The ID of parent directory inode within which to create the child symlink. // The ID of parent directory inode within which to create the child symlink.
Parent InodeID Parent InodeID
@ -344,11 +357,12 @@ func (o *CreateSymlinkOp) ShortDesc() (desc string) {
return return
} }
func (o *CreateSymlinkOp) respond() { func (o *CreateSymlinkOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.SymlinkResponse{} size := fusekernel.EntryOutSize(o.protocol)
convertChildInodeEntry(&o.Entry, &resp.LookupResponse) b = buffer.NewOutMessage(size)
out := (*fusekernel.EntryOut)(b.Grow(size))
convertChildInodeEntry(&o.Entry, out)
o.bfReq.Respond(&resp)
return return
} }
@ -392,7 +406,6 @@ func (o *CreateSymlinkOp) respond() {
// //
type RenameOp struct { type RenameOp struct {
commonOp commonOp
bfReq *bazilfuse.RenameRequest
// The old parent directory, and the name of the entry within it to be // The old parent directory, and the name of the entry within it to be
// relocated. // relocated.
@ -405,8 +418,8 @@ type RenameOp struct {
NewName string NewName string
} }
func (o *RenameOp) respond() { func (o *RenameOp) kernelResponse() (b buffer.OutMessage) {
o.bfReq.Respond() b = buffer.NewOutMessage(0)
return return
} }
@ -419,7 +432,6 @@ func (o *RenameOp) respond() {
// Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf) // Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf)
type RmDirOp struct { type RmDirOp struct {
commonOp commonOp
bfReq *bazilfuse.RemoveRequest
// The ID of parent directory inode, and the name of the directory being // The ID of parent directory inode, and the name of the directory being
// removed within it. // removed within it.
@ -427,8 +439,8 @@ type RmDirOp struct {
Name string Name string
} }
func (o *RmDirOp) respond() { func (o *RmDirOp) kernelResponse() (b buffer.OutMessage) {
o.bfReq.Respond() b = buffer.NewOutMessage(0)
return return
} }
@ -440,7 +452,6 @@ func (o *RmDirOp) respond() {
// Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C) // Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C)
type UnlinkOp struct { type UnlinkOp struct {
commonOp commonOp
bfReq *bazilfuse.RemoveRequest
// The ID of parent directory inode, and the name of the entry being removed // The ID of parent directory inode, and the name of the entry being removed
// within it. // within it.
@ -448,8 +459,8 @@ type UnlinkOp struct {
Name string Name string
} }
func (o *UnlinkOp) respond() { func (o *UnlinkOp) kernelResponse() (b buffer.OutMessage) {
o.bfReq.Respond() b = buffer.NewOutMessage(0)
return return
} }
@ -465,14 +476,10 @@ func (o *UnlinkOp) respond() {
// https://github.com/osxfuse/osxfuse/issues/199). // https://github.com/osxfuse/osxfuse/issues/199).
type OpenDirOp struct { type OpenDirOp struct {
commonOp commonOp
bfReq *bazilfuse.OpenRequest
// The ID of the inode to be opened. // The ID of the inode to be opened.
Inode InodeID Inode InodeID
// Mode and options flags.
Flags bazilfuse.OpenFlags
// Set by the file system: an opaque ID that will be echoed in follow-up // 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 // calls for this directory using the same struct file in the kernel. In
// practice this usually means follow-up calls using the file descriptor // practice this usually means follow-up calls using the file descriptor
@ -484,19 +491,17 @@ type OpenDirOp struct {
Handle HandleID Handle HandleID
} }
func (o *OpenDirOp) respond() { func (o *OpenDirOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.OpenResponse{ b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
Handle: bazilfuse.HandleID(o.Handle), out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
} out.Fh = uint64(o.Handle)
o.bfReq.Respond(&resp)
return return
} }
// Read entries from a directory previously opened with OpenDir. // Read entries from a directory previously opened with OpenDir.
type ReadDirOp struct { type ReadDirOp struct {
commonOp commonOp
bfReq *bazilfuse.ReadRequest
// The directory inode that we are reading, and the handle previously // The directory inode that we are reading, and the handle previously
// returned by OpenDir when opening that inode. // returned by OpenDir when opening that inode.
@ -584,12 +589,9 @@ type ReadDirOp struct {
Data []byte Data []byte
} }
func (o *ReadDirOp) respond() { func (o *ReadDirOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.ReadResponse{ b = buffer.NewOutMessage(uintptr(len(o.Data)))
Data: o.Data, b.Append(o.Data)
}
o.bfReq.Respond(&resp)
return return
} }
@ -603,7 +605,6 @@ func (o *ReadDirOp) respond() {
// Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do). // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
type ReleaseDirHandleOp struct { type ReleaseDirHandleOp struct {
commonOp commonOp
bfReq *bazilfuse.ReleaseRequest
// The handle ID to be released. The kernel guarantees that this ID will not // 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 // be used in further calls to the file system (unless it is reissued by the
@ -611,8 +612,8 @@ type ReleaseDirHandleOp struct {
Handle HandleID Handle HandleID
} }
func (o *ReleaseDirHandleOp) respond() { func (o *ReleaseDirHandleOp) kernelResponse() (b buffer.OutMessage) {
o.bfReq.Respond() b = buffer.NewOutMessage(0)
return return
} }
@ -628,14 +629,10 @@ func (o *ReleaseDirHandleOp) respond() {
// (cf.https://github.com/osxfuse/osxfuse/issues/199). // (cf.https://github.com/osxfuse/osxfuse/issues/199).
type OpenFileOp struct { type OpenFileOp struct {
commonOp commonOp
bfReq *bazilfuse.OpenRequest
// The ID of the inode to be opened. // The ID of the inode to be opened.
Inode InodeID Inode InodeID
// Mode and options flags.
Flags bazilfuse.OpenFlags
// An opaque ID that will be echoed in follow-up calls for this file using // 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 // the same struct file in the kernel. In practice this usually means
// follow-up calls using the file descriptor returned by open(2). // follow-up calls using the file descriptor returned by open(2).
@ -646,12 +643,11 @@ type OpenFileOp struct {
Handle HandleID Handle HandleID
} }
func (o *OpenFileOp) respond() { func (o *OpenFileOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.OpenResponse{ b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
Handle: bazilfuse.HandleID(o.Handle), out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
} out.Fh = uint64(o.Handle)
o.bfReq.Respond(&resp)
return return
} }
@ -662,7 +658,6 @@ func (o *OpenFileOp) respond() {
// more. // more.
type ReadFileOp struct { type ReadFileOp struct {
commonOp commonOp
bfReq *bazilfuse.ReadRequest
// The file inode that we are reading, and the handle previously returned by // The file inode that we are reading, and the handle previously returned by
// CreateFile or OpenFile when opening that inode. // CreateFile or OpenFile when opening that inode.
@ -685,12 +680,9 @@ type ReadFileOp struct {
Data []byte Data []byte
} }
func (o *ReadFileOp) respond() { func (o *ReadFileOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.ReadResponse{ b = buffer.NewOutMessage(uintptr(len(o.Data)))
Data: o.Data, b.Append(o.Data)
}
o.bfReq.Respond(&resp)
return return
} }
@ -727,7 +719,6 @@ func (o *ReadFileOp) respond() {
// concurrent requests".) // concurrent requests".)
type WriteFileOp struct { type WriteFileOp struct {
commonOp commonOp
bfReq *bazilfuse.WriteRequest
// The file inode that we are modifying, and the handle previously returned // The file inode that we are modifying, and the handle previously returned
// by CreateFile or OpenFile when opening that inode. // by CreateFile or OpenFile when opening that inode.
@ -765,12 +756,11 @@ type WriteFileOp struct {
Data []byte Data []byte
} }
func (o *WriteFileOp) respond() { func (o *WriteFileOp) kernelResponse() (b buffer.OutMessage) {
resp := bazilfuse.WriteResponse{ b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.WriteOut{}))
Size: len(o.Data), out := (*fusekernel.WriteOut)(b.Grow(unsafe.Sizeof(fusekernel.WriteOut{})))
} out.Size = uint32(len(o.Data))
o.bfReq.Respond(&resp)
return return
} }
@ -792,15 +782,14 @@ func (o *WriteFileOp) respond() {
// file (but which is not used in "real" file systems). // file (but which is not used in "real" file systems).
type SyncFileOp struct { type SyncFileOp struct {
commonOp commonOp
bfReq *bazilfuse.FsyncRequest
// The file and handle being sync'd. // The file and handle being sync'd.
Inode InodeID Inode InodeID
Handle HandleID Handle HandleID
} }
func (o *SyncFileOp) respond() { func (o *SyncFileOp) kernelResponse() (b buffer.OutMessage) {
o.bfReq.Respond() b = buffer.NewOutMessage(0)
return return
} }
@ -853,15 +842,14 @@ func (o *SyncFileOp) respond() {
// return any errors that occur. // return any errors that occur.
type FlushFileOp struct { type FlushFileOp struct {
commonOp commonOp
bfReq *bazilfuse.FlushRequest
// The file and handle being flushed. // The file and handle being flushed.
Inode InodeID Inode InodeID
Handle HandleID Handle HandleID
} }
func (o *FlushFileOp) respond() { func (o *FlushFileOp) kernelResponse() (b buffer.OutMessage) {
o.bfReq.Respond() b = buffer.NewOutMessage(0)
return return
} }
@ -875,7 +863,6 @@ func (o *FlushFileOp) respond() {
// Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do). // Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
type ReleaseFileHandleOp struct { type ReleaseFileHandleOp struct {
commonOp commonOp
bfReq *bazilfuse.ReleaseRequest
// The handle ID to be released. The kernel guarantees that this ID will not // 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 // be used in further calls to the file system (unless it is reissued by the
@ -883,8 +870,8 @@ type ReleaseFileHandleOp struct {
Handle HandleID Handle HandleID
} }
func (o *ReleaseFileHandleOp) respond() { func (o *ReleaseFileHandleOp) kernelResponse() (b buffer.OutMessage) {
o.bfReq.Respond() b = buffer.NewOutMessage(0)
return return
} }
@ -892,14 +879,16 @@ func (o *ReleaseFileHandleOp) respond() {
// non-nil error. // non-nil error.
type unknownOp struct { type unknownOp struct {
commonOp commonOp
opCode uint32
inode InodeID
} }
func (o *unknownOp) ShortDesc() (desc string) { func (o *unknownOp) ShortDesc() (desc string) {
desc = fmt.Sprintf("%T(inode=%v)", o.bazilReq, o.bazilReq.Hdr().Node) desc = fmt.Sprintf("<opcode %d>(inode=%v)", o.opCode, o.inode)
return 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())) 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. // Read the target of a symlink inode.
type ReadSymlinkOp struct { type ReadSymlinkOp struct {
commonOp commonOp
bfReq *bazilfuse.ReadlinkRequest
// The symlink inode that we are reading. // The symlink inode that we are reading.
Inode InodeID Inode InodeID
@ -919,7 +907,68 @@ type ReadSymlinkOp struct {
Target string Target string
} }
func (o *ReadSymlinkOp) respond() { func (o *ReadSymlinkOp) kernelResponse() (b buffer.OutMessage) {
o.bfReq.Respond(o.Target) 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 return
} }

View File

@ -19,7 +19,7 @@ import (
"os" "os"
"time" "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 // A 64-bit number used to uniquely identify a file or directory in the file
@ -38,7 +38,7 @@ const RootInodeID = 1
func init() { func init() {
// Make sure the constant above is correct. We do this at runtime rather than // 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 // 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. // 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 // 2. The constant can be untyped and can therefore more easily be used as
// an array index. // an array index.
// //
if RootInodeID != bazilfuse.RootID { if RootInodeID != fusekernel.RootID {
panic( panic(
fmt.Sprintf( fmt.Sprintf(
"Oops, RootInodeID is wrong: %v vs. %v", "Oops, RootInodeID is wrong: %v vs. %v",
RootInodeID, RootInodeID,
bazilfuse.RootID)) fusekernel.RootID))
} }
} }
@ -97,6 +97,16 @@ type InodeAttributes struct {
Gid uint32 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 // 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 // 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. // become free, the generation number must change when an ID is reused.

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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 <miklos@szeredi.hu>
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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1 @@
package fusekernel

View File

@ -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()
}

153
mount_config.go Normal file
View File

@ -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, ",")
}

155
mount_darwin.go Normal file
View File

@ -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
}

158
mount_linux.go Normal file
View File

@ -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
}

View File

@ -16,10 +16,7 @@ package fuse
import ( import (
"fmt" "fmt"
"log"
"runtime"
"github.com/jacobsa/bazilfuse"
"golang.org/x/net/context" "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 // Attempt to mount a file system on the given directory, using the supplied
// Server to serve connection requests. This function blocks until the file // Server to serve connection requests. This function blocks until the file
// system is successfully mounted. // system is successfully mounted.
@ -198,10 +72,11 @@ func Mount(
joinStatusAvailable: make(chan struct{}), joinStatusAvailable: make(chan struct{}),
} }
// Open a bazilfuse connection. // Begin the mounting process, which will continue in the background.
bfConn, err := bazilfuse.Mount(mfs.dir, config.bazilfuseOptions()...) ready := make(chan error, 1)
dev, err := mount(dir, config, ready)
if err != nil { if err != nil {
err = fmt.Errorf("bazilfuse.Mount: %v", err) err = fmt.Errorf("mount: %v", err)
return return
} }
@ -211,15 +86,14 @@ func Mount(
opContext = context.Background() opContext = context.Background()
} }
// Create our own Connection object wrapping it. // Create a Connection object wrapping the device.
connection, err := newConnection( connection, err := newConnection(
opContext, opContext,
config.DebugLogger, config.DebugLogger,
config.ErrorLogger, config.ErrorLogger,
bfConn) dev)
if err != nil { if err != nil {
bfConn.Close()
err = fmt.Errorf("newConnection: %v", err) err = fmt.Errorf("newConnection: %v", err)
return return
} }
@ -231,9 +105,9 @@ func Mount(
close(mfs.joinStatusAvailable) close(mfs.joinStatusAvailable)
}() }()
// Wait for the connection to say it is ready. // Wait for the mount process to complete.
if err = connection.waitForReady(); err != nil { if err = <-ready; err != nil {
err = fmt.Errorf("WaitForReady: %v", err) err = fmt.Errorf("mount (background): %v", err)
return return
} }

View File

@ -30,7 +30,6 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/jacobsa/bazilfuse"
"github.com/jacobsa/fuse/fsutil" "github.com/jacobsa/fuse/fsutil"
"github.com/jacobsa/fuse/fusetesting" "github.com/jacobsa/fuse/fusetesting"
"github.com/jacobsa/fuse/samples" "github.com/jacobsa/fuse/samples"
@ -58,8 +57,8 @@ type flushFSTest struct {
func (t *flushFSTest) setUp( func (t *flushFSTest) setUp(
ti *TestInfo, ti *TestInfo,
flushErr bazilfuse.Errno, flushErr syscall.Errno,
fsyncErr bazilfuse.Errno, fsyncErr syscall.Errno,
readOnly bool) { readOnly bool) {
var err error var err error
@ -810,7 +809,7 @@ func init() { RegisterTestSuite(&FlushErrorTest{}) }
func (t *FlushErrorTest) SetUp(ti *TestInfo) { func (t *FlushErrorTest) SetUp(ti *TestInfo) {
const noErr = 0 const noErr = 0
t.flushFSTest.setUp(ti, bazilfuse.ENOENT, noErr, false) t.flushFSTest.setUp(ti, syscall.ENOENT, noErr, false)
} }
func (t *FlushErrorTest) Close() { func (t *FlushErrorTest) Close() {
@ -890,7 +889,7 @@ func init() { RegisterTestSuite(&FsyncErrorTest{}) }
func (t *FsyncErrorTest) SetUp(ti *TestInfo) { func (t *FsyncErrorTest) SetUp(ti *TestInfo) {
const noErr = 0 const noErr = 0
t.flushFSTest.setUp(ti, noErr, bazilfuse.ENOENT, false) t.flushFSTest.setUp(ti, noErr, syscall.ENOENT, false)
} }
func (t *FsyncErrorTest) Fsync() { func (t *FsyncErrorTest) Fsync() {

View File

@ -23,8 +23,8 @@ import (
"log" "log"
"os" "os"
"runtime" "runtime"
"syscall"
"github.com/jacobsa/bazilfuse"
"github.com/jacobsa/fuse" "github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/samples/flushfs" "github.com/jacobsa/fuse/samples/flushfs"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -58,11 +58,11 @@ func makeFlushFS() (server fuse.Server, err error) {
var fsyncErr error var fsyncErr error
if *fFlushError != 0 { if *fFlushError != 0 {
flushErr = bazilfuse.Errno(*fFlushError) flushErr = syscall.Errno(*fFlushError)
} }
if *fFsyncError != 0 { if *fFsyncError != 0 {
fsyncErr = bazilfuse.Errno(*fFsyncError) fsyncErr = syscall.Errno(*fFsyncError)
} }
// Report flushes and fsyncs by writing the contents followed by a newline. // Report flushes and fsyncs by writing the contents followed by a newline.

View File

@ -14,10 +14,8 @@
package fuse package fuse
import "github.com/jacobsa/bazilfuse"
// Attempt to unmount the file system whose mount point is the supplied // Attempt to unmount the file system whose mount point is the supplied
// directory. // directory.
func Unmount(dir string) error { func Unmount(dir string) error {
return bazilfuse.Unmount(dir) return unmount(dir)
} }

23
unmount_linux.go Normal file
View File

@ -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
}

18
unmount_std.go Normal file
View File

@ -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
}