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
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)
This package allows for writing and mounting user-space file systems from Go. It
is a wrapper around [bazil.org/fuse][bazil], which does the heavy lifting. It
does not make use of the [bazil.org/fuse/fs][bazil-fs] sub-package, which allows
for something like an object-orientend representation of files and directories,
and contains a decent amount of canned behavior.
This package allows for writing and mounting user-space file systems from Go.
Install it as follows:
The chief improvements and/or differences from the bazil.org packages are:
go get -u github.com/jacobsa/fuse
* No surprises in the form of magic/default behaviors. You must provide an
implementation for every method in the interface. Embed a
`fuseutil.NotImplementedFileSystem` struct to have default implementations
that return `ENOSYS`.
Afterward, see the documentation for the following three packages:
* Every method, struct, and field is thoroughly documented. This may help you
get your bearings in the world of FUSE, the Linux VFS, traditional file
system implementations, etc., all of which tend to be very poorly
documented.
* Package [fuse][] provides support for mounting a new file system and
reading requests from the kernel.
* Support for arbitrary offsets in directory entries returned by `ReadDir`.
(The bazil.org package assumes that offsets must be counts of bytes.)
* Package [fuseops][] enumerates the supported requests from the kernel, and
provides documentation on their semantics.
The very large disadvantage over using the bazil.org packages is that many
features have not yet been exposed.
* Package [fuseutil][], in particular the `FileSystem` interface, provides a
convenient way to create a file system type and export it to the kernel via
`fuse.Mount`.
Make sure to see the sub-packages of the [samples][] package.
Make sure to also see the sub-packages of the [samples][] package for examples
and tests.
[bazil]: http://godoc.org/bazil.org/fuse
[bazil-fs]: http://godoc.org/bazil.org/fuse/fs
This package owes its inspiration and most of its kernel-related code to
[bazil.org/fuse][bazil].
[fuse]: http://godoc.org/github.com/jacobsa/fuse
[fuseops]: http://godoc.org/github.com/jacobsa/fuse/fuseops
[fuseutil]: http://godoc.org/github.com/jacobsa/fuse/fuseutil
[samples]: http://godoc.org/github.com/jacobsa/fuse/samples
[bazil]: http://godoc.org/bazil.org/fuse

View File

@ -16,22 +16,49 @@ package fuse
import (
"fmt"
"io"
"log"
"os"
"path"
"runtime"
"sync"
"syscall"
"golang.org/x/net/context"
"github.com/jacobsa/bazilfuse"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/internal/buffer"
"github.com/jacobsa/fuse/internal/fusekernel"
)
// Ask the Linux kernel for larger read requests.
//
// As of 2015-03-26, the behavior in the kernel is:
//
// * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable
// ra_pages to be init_response->max_readahead divided by the page size.
//
// * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set
// backing_dev_info::ra_pages to the min of that value and what was sent
// in the request's max_readahead field.
//
// * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding
// how much to read ahead.
//
// * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero.
//
// Reading a page at a time is a drag. Ask for a larger size.
const maxReadahead = 1 << 20
// A connection to the fuse kernel process.
type Connection struct {
debugLogger *log.Logger
errorLogger *log.Logger
wrapped *bazilfuse.Conn
// The device through which we're talking to the kernel, and the protocol
// version that we're using to talk to it.
dev *os.File
protocol fusekernel.Protocol
// The context from which all op contexts inherit.
parentCtx context.Context
@ -41,30 +68,85 @@ type Connection struct {
mu sync.Mutex
// A map from bazilfuse request ID (*not* the op ID for logging used above)
// to a function that cancel's its associated context.
// A map from fuse "unique" request ID (*not* the op ID for logging used
// above) to a function that cancel's its associated context.
//
// GUARDED_BY(mu)
cancelFuncs map[bazilfuse.RequestID]func()
cancelFuncs map[uint64]func()
}
// Responsibility for closing the wrapped connection is transferred to the
// result. You must call c.close() eventually.
// Create a connection wrapping the supplied file descriptor connected to the
// kernel. You must eventually call c.close().
//
// The loggers may be nil.
func newConnection(
parentCtx context.Context,
debugLogger *log.Logger,
errorLogger *log.Logger,
wrapped *bazilfuse.Conn) (c *Connection, err error) {
dev *os.File) (c *Connection, err error) {
c = &Connection{
debugLogger: debugLogger,
errorLogger: errorLogger,
wrapped: wrapped,
dev: dev,
parentCtx: parentCtx,
cancelFuncs: make(map[bazilfuse.RequestID]func()),
cancelFuncs: make(map[uint64]func()),
}
// Initialize.
err = c.Init()
if err != nil {
c.close()
err = fmt.Errorf("Init: %v", err)
return
}
return
}
// Do the work necessary to cause the mount process to complete.
func (c *Connection) Init() (err error) {
// Read the init op.
op, err := c.ReadOp()
if err != nil {
err = fmt.Errorf("Reading init op: %v", err)
return
}
initOp, ok := op.(*fuseops.InternalInitOp)
if !ok {
err = fmt.Errorf("Expected *fuseops.InternalInitOp, got %T", op)
return
}
// Make sure the protocol version spoken by the kernel is new enough.
min := fusekernel.Protocol{
fusekernel.ProtoVersionMinMajor,
fusekernel.ProtoVersionMinMinor,
}
if initOp.Kernel.LT(min) {
initOp.Respond(syscall.EPROTO)
err = fmt.Errorf("Version too old: %v", initOp.Kernel)
return
}
// Downgrade our protocol if necessary.
c.protocol = fusekernel.Protocol{
fusekernel.ProtoVersionMaxMajor,
fusekernel.ProtoVersionMaxMinor,
}
if initOp.Kernel.LT(c.protocol) {
c.protocol = initOp.Kernel
}
// Respond to the init op.
initOp.Library = c.protocol
initOp.MaxReadahead = maxReadahead
initOp.MaxWrite = buffer.MaxWriteSize
initOp.Flags = fusekernel.InitBigWrites
initOp.Respond(nil)
return
}
@ -104,28 +186,27 @@ func (c *Connection) debugLog(
// LOCKS_EXCLUDED(c.mu)
func (c *Connection) recordCancelFunc(
reqID bazilfuse.RequestID,
fuseID uint64,
f func()) {
c.mu.Lock()
defer c.mu.Unlock()
if _, ok := c.cancelFuncs[reqID]; ok {
panic(fmt.Sprintf("Already have cancel func for request %v", reqID))
if _, ok := c.cancelFuncs[fuseID]; ok {
panic(fmt.Sprintf("Already have cancel func for request %v", fuseID))
}
c.cancelFuncs[reqID] = f
c.cancelFuncs[fuseID] = f
}
// Set up state for an op that is about to be returned to the user, given its
// underlying bazilfuse request.
// underlying fuse opcode and request ID.
//
// Return a context that should be used for the op.
//
// LOCKS_EXCLUDED(c.mu)
func (c *Connection) beginOp(
bfReq bazilfuse.Request) (ctx context.Context) {
reqID := bfReq.Hdr().ID
opCode uint32,
fuseID uint64) (ctx context.Context) {
// Start with the parent context.
ctx = c.parentCtx
@ -137,46 +218,46 @@ func (c *Connection) beginOp(
// should not record any state keyed on their ID.
//
// Cf. https://github.com/osxfuse/osxfuse/issues/208
if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok {
if opCode != fusekernel.OpForget {
var cancel func()
ctx, cancel = context.WithCancel(ctx)
c.recordCancelFunc(reqID, cancel)
c.recordCancelFunc(fuseID, cancel)
}
return
}
// Clean up all state associated with an op to which the user has responded,
// given its underlying bazilfuse request. This must be called before a
// response is sent to the kernel, to avoid a race where the request's ID might
// be reused by osxfuse.
// given its underlying fuse opcode and request ID. This must be called before
// a response is sent to the kernel, to avoid a race where the request's ID
// might be reused by osxfuse.
//
// LOCKS_EXCLUDED(c.mu)
func (c *Connection) finishOp(bfReq bazilfuse.Request) {
func (c *Connection) finishOp(
opCode uint32,
fuseID uint64) {
c.mu.Lock()
defer c.mu.Unlock()
reqID := bfReq.Hdr().ID
// Even though the op is finished, context.WithCancel requires us to arrange
// for the cancellation function to be invoked. We also must remove it from
// our map.
//
// Special case: we don't do this for Forget requests. See the note in
// beginOp above.
if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok {
cancel, ok := c.cancelFuncs[reqID]
if opCode != fusekernel.OpForget {
cancel, ok := c.cancelFuncs[fuseID]
if !ok {
panic(fmt.Sprintf("Unknown request ID in finishOp: %v", reqID))
panic(fmt.Sprintf("Unknown request ID in finishOp: %v", fuseID))
}
cancel()
delete(c.cancelFuncs, reqID)
delete(c.cancelFuncs, fuseID)
}
}
// LOCKS_EXCLUDED(c.mu)
func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
func (c *Connection) handleInterrupt(fuseID uint64) {
c.mu.Lock()
defer c.mu.Unlock()
@ -194,7 +275,7 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
//
// Cf. https://github.com/osxfuse/osxfuse/issues/208
// Cf. http://comments.gmane.org/gmane.comp.file-systems.fuse.devel/14675
cancel, ok := c.cancelFuncs[req.IntrID]
cancel, ok := c.cancelFuncs[fuseID]
if !ok {
return
}
@ -202,6 +283,71 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
cancel()
}
func (c *Connection) allocateInMessage() (m *buffer.InMessage) {
// TODO(jacobsa): Use a freelist.
m = new(buffer.InMessage)
return
}
func (c *Connection) destroyInMessage(m *buffer.InMessage) {
// TODO(jacobsa): Use a freelist.
}
// Read the next message from the kernel. The message must later be destroyed
// using destroyInMessage.
func (c *Connection) readMessage() (m *buffer.InMessage, err error) {
// Allocate a message.
m = c.allocateInMessage()
// Loop past transient errors.
for {
// Attempt a reaed.
err = m.Init(c.dev)
// Special cases:
//
// * ENODEV means fuse has hung up.
//
// * EINTR means we should try again. (This seems to happen often on
// OS X, cf. http://golang.org/issue/11180)
//
if pe, ok := err.(*os.PathError); ok {
switch pe.Err {
case syscall.ENODEV:
err = io.EOF
case syscall.EINTR:
err = nil
continue
}
}
if err != nil {
c.destroyInMessage(m)
m = nil
return
}
return
}
}
// Write the supplied message to the kernel.
func (c *Connection) writeMessage(msg []byte) (err error) {
// Avoid the retry loop in os.File.Write.
n, err := syscall.Write(int(c.dev.Fd()), msg)
if err != nil {
return
}
if n != len(msg) {
err = fmt.Errorf("Wrote %d bytes; expected %d", n, len(msg))
return
}
return
}
// Read the next op from the kernel process. Return io.EOF if the kernel has
// closed the connection.
//
@ -212,38 +358,19 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
func (c *Connection) ReadOp() (op fuseops.Op, err error) {
// Keep going until we find a request we know how to convert.
for {
// Read a bazilfuse request.
var bfReq bazilfuse.Request
bfReq, err = c.wrapped.ReadRequest()
// Read the next message from the kernel.
var m *buffer.InMessage
m, err = c.readMessage()
if err != nil {
return
}
// Choose an ID for this operation.
// Choose an ID for this operation for the purposes of logging.
opID := c.nextOpID
c.nextOpID++
// Log the receipt of the operation.
c.debugLog(opID, 1, "<- %v", bfReq)
// Special case: responding to statfs is required to make mounting work on
// OS X. We don't currently expose the capability for the file system to
// intercept this.
if statfsReq, ok := bfReq.(*bazilfuse.StatfsRequest); ok {
c.debugLog(opID, 1, "-> (Statfs) OK")
statfsReq.Respond(&bazilfuse.StatfsResponse{})
continue
}
// Special case: handle interrupt requests.
if interruptReq, ok := bfReq.(*bazilfuse.InterruptRequest); ok {
c.handleInterrupt(interruptReq)
continue
}
// Set up op dependencies.
opCtx := c.beginOp(bfReq)
opCtx := c.beginOp(m.Header().Opcode, m.Header().Unique)
var debugLogForOp func(int, string, ...interface{})
if c.debugLogger != nil {
@ -252,28 +379,82 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) {
}
}
finished := func(err error) { c.finishOp(bfReq) }
sendReply := func(
op fuseops.Op,
fuseID uint64,
replyMsg []byte,
opErr error) (err error) {
// Make sure we destroy the message, as required by readMessage.
defer c.destroyInMessage(m)
op = fuseops.Convert(
// Clean up state for this op.
c.finishOp(m.Header().Opcode, m.Header().Unique)
// Debug logging
if c.debugLogger != nil {
if opErr == nil {
op.Logf("-> OK: %s", op.DebugString())
} else {
op.Logf("-> error: %v", opErr)
}
}
// Error logging
if opErr != nil && c.errorLogger != nil {
c.errorLogger.Printf("(%s) error: %v", op.ShortDesc(), opErr)
}
// Send the reply to the kernel.
err = c.writeMessage(replyMsg)
if err != nil {
err = fmt.Errorf("writeMessage: %v", err)
return
}
return
}
// Convert the message to an Op.
op, err = fuseops.Convert(
opCtx,
bfReq,
m,
c.protocol,
debugLogForOp,
c.errorLogger,
finished)
sendReply)
if err != nil {
err = fmt.Errorf("fuseops.Convert: %v", err)
return
}
// Log the receipt of the operation.
c.debugLog(opID, 1, "<- %v", op.ShortDesc())
// Special case: responding to statfs is required to make mounting work on
// OS X. We don't currently expose the capability for the file system to
// intercept this.
if _, ok := op.(*fuseops.InternalStatFSOp); ok {
op.Respond(nil)
continue
}
// Special case: handle interrupt requests.
if interruptOp, ok := op.(*fuseops.InternalInterruptOp); ok {
c.handleInterrupt(interruptOp.FuseID)
continue
}
return
}
}
func (c *Connection) waitForReady() (err error) {
<-c.wrapped.Ready
err = c.wrapped.MountError
return
}
// Close the connection. Must not be called until operations that were read
// from the connection have been responded to.
func (c *Connection) close() (err error) {
err = c.wrapped.Close()
// Posix doesn't say that close can be called concurrently with read or
// write, but luckily we exclude the possibility of a race by requiring the
// user to respond to all ops first.
err = c.dev.Close()
return
}

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ import (
"os"
"time"
"github.com/jacobsa/bazilfuse"
"github.com/jacobsa/fuse/internal/fusekernel"
)
// A 64-bit number used to uniquely identify a file or directory in the file
@ -38,7 +38,7 @@ const RootInodeID = 1
func init() {
// Make sure the constant above is correct. We do this at runtime rather than
// defining the constant in terms of bazilfuse.RootID for two reasons:
// defining the constant in terms of fusekernel.RootID for two reasons:
//
// 1. Users can more clearly see that the root ID is low and can therefore
// be used as e.g. an array index, with space reserved up to the root.
@ -46,12 +46,12 @@ func init() {
// 2. The constant can be untyped and can therefore more easily be used as
// an array index.
//
if RootInodeID != bazilfuse.RootID {
if RootInodeID != fusekernel.RootID {
panic(
fmt.Sprintf(
"Oops, RootInodeID is wrong: %v vs. %v",
RootInodeID,
bazilfuse.RootID))
fusekernel.RootID))
}
}
@ -97,6 +97,16 @@ type InodeAttributes struct {
Gid uint32
}
func (a *InodeAttributes) DebugString() string {
return fmt.Sprintf(
"%d %d %v %d %d",
a.Size,
a.Nlink,
a.Mode,
a.Uid,
a.Gid)
}
// A generation number for an inode. Irrelevant for file systems that won't be
// exported over NFS. For those that will and that reuse inode IDs when they
// become free, the generation number must change when an ID is reused.

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 (
"fmt"
"log"
"runtime"
"github.com/jacobsa/bazilfuse"
"golang.org/x/net/context"
)
@ -62,129 +59,6 @@ func (mfs *MountedFileSystem) Join(ctx context.Context) error {
}
}
// Optional configuration accepted by Mount.
type MountConfig struct {
// The context from which every op read from the connetion by the sever
// should inherit. If nil, context.Background() will be used.
OpContext context.Context
// If non-empty, the name of the file system as displayed by e.g. `mount`.
// This is important because the `umount` command requires root privileges if
// it doesn't agree with /etc/fstab.
FSName string
// Mount the file system in read-only mode. File modes will appear as normal,
// but opening a file for writing and metadata operations like chmod,
// chtimes, etc. will fail.
ReadOnly bool
// A logger to use for logging errors. All errors are logged, with the
// exception of a few blacklisted errors that are expected. If nil, no error
// logging is performed.
ErrorLogger *log.Logger
// A logger to use for logging debug information. If nil, no debug logging is
// performed.
DebugLogger *log.Logger
// OS X only.
//
// Normally on OS X we mount with the novncache option
// (cf. http://goo.gl/1pTjuk), which disables entry caching in the kernel.
// This is because osxfuse does not honor the entry expiration values we
// return to it, instead caching potentially forever (cf.
// http://goo.gl/8yR0Ie), and it is probably better to fail to cache than to
// cache for too long, since the latter is more likely to hide consistency
// bugs that are difficult to detect and diagnose.
//
// This field disables the use of novncache, restoring entry caching. Beware:
// the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and
// entries will be cached for an arbitrarily long time.
EnableVnodeCaching bool
// Additional key=value options to pass unadulterated to the underlying mount
// command. See `man 8 mount`, the fuse documentation, etc. for
// system-specific information.
//
// For expert use only! May invalidate other guarantees made in the
// documentation for this package.
Options map[string]string
}
// Convert to mount options to be passed to package bazilfuse.
func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) {
isDarwin := runtime.GOOS == "darwin"
// Enable permissions checking in the kernel. See the comments on
// InodeAttributes.Mode.
opts = append(opts, bazilfuse.SetOption("default_permissions", ""))
// HACK(jacobsa): Work around what appears to be a bug in systemd v219, as
// shipped in Ubuntu 15.04, where it automatically unmounts any file system
// that doesn't set an explicit name.
//
// When Ubuntu contains systemd v220, this workaround should be removed and
// the systemd bug reopened if the problem persists.
//
// Cf. https://github.com/bazil/fuse/issues/89
// Cf. https://bugs.freedesktop.org/show_bug.cgi?id=90907
fsname := c.FSName
if runtime.GOOS == "linux" && fsname == "" {
fsname = "some_fuse_file_system"
}
// Special file system name?
if fsname != "" {
opts = append(opts, bazilfuse.FSName(fsname))
}
// Read only?
if c.ReadOnly {
opts = append(opts, bazilfuse.ReadOnly())
}
// OS X: set novncache when appropriate.
if isDarwin && !c.EnableVnodeCaching {
opts = append(opts, bazilfuse.SetOption("novncache", ""))
}
// OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which
// just add noise to debug output and can have significant cost on
// network-based file systems.
//
// Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options
if isDarwin {
opts = append(opts, bazilfuse.SetOption("noappledouble", ""))
}
// Ask the Linux kernel for larger read requests.
//
// As of 2015-03-26, the behavior in the kernel is:
//
// * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable
// ra_pages to be init_response->max_readahead divided by the page size.
//
// * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set
// backing_dev_info::ra_pages to the min of that value and what was sent
// in the request's max_readahead field.
//
// * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding
// how much to read ahead.
//
// * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero.
//
// Reading a page at a time is a drag. Ask for a larger size.
const maxReadahead = 1 << 20
opts = append(opts, bazilfuse.MaxReadahead(maxReadahead))
// Last but not least: other user-supplied options.
for k, v := range c.Options {
opts = append(opts, bazilfuse.SetOption(k, v))
}
return
}
// Attempt to mount a file system on the given directory, using the supplied
// Server to serve connection requests. This function blocks until the file
// system is successfully mounted.
@ -198,10 +72,11 @@ func Mount(
joinStatusAvailable: make(chan struct{}),
}
// Open a bazilfuse connection.
bfConn, err := bazilfuse.Mount(mfs.dir, config.bazilfuseOptions()...)
// Begin the mounting process, which will continue in the background.
ready := make(chan error, 1)
dev, err := mount(dir, config, ready)
if err != nil {
err = fmt.Errorf("bazilfuse.Mount: %v", err)
err = fmt.Errorf("mount: %v", err)
return
}
@ -211,15 +86,14 @@ func Mount(
opContext = context.Background()
}
// Create our own Connection object wrapping it.
// Create a Connection object wrapping the device.
connection, err := newConnection(
opContext,
config.DebugLogger,
config.ErrorLogger,
bfConn)
dev)
if err != nil {
bfConn.Close()
err = fmt.Errorf("newConnection: %v", err)
return
}
@ -231,9 +105,9 @@ func Mount(
close(mfs.joinStatusAvailable)
}()
// Wait for the connection to say it is ready.
if err = connection.waitForReady(); err != nil {
err = fmt.Errorf("WaitForReady: %v", err)
// Wait for the mount process to complete.
if err = <-ready; err != nil {
err = fmt.Errorf("mount (background): %v", err)
return
}

View File

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

View File

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

View File

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

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
}