Eliminated the dependency on bazilfuse.
commit
cd7e6b6dd9
98
LICENSE
98
LICENSE
|
@ -200,3 +200,101 @@ Apache License
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
==========================================================================
|
||||
Portions of this package were adopted from bazil.org/fuse, which contains the
|
||||
following license notice.
|
||||
|
||||
Copyright (c) 2013-2015 Tommi Virtanen.
|
||||
Copyright (c) 2009, 2011, 2012 The Go Authors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
|
||||
The following included software components have additional copyright
|
||||
notices and license terms that may differ from the above.
|
||||
|
||||
|
||||
File fuse.go:
|
||||
|
||||
// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
|
||||
// which carries this notice:
|
||||
//
|
||||
// The files in this directory are subject to the following license.
|
||||
//
|
||||
// The author of this software is Russ Cox.
|
||||
//
|
||||
// Copyright (c) 2006 Russ Cox
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose without fee is hereby granted, provided that this entire notice
|
||||
// is included in all copies of any software which is or includes a copy
|
||||
// or modification of this software and in all copies of the supporting
|
||||
// documentation for such software.
|
||||
//
|
||||
// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
|
||||
// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
|
||||
// FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
|
||||
|
||||
File fuse_kernel.go:
|
||||
|
||||
// Derived from FUSE's fuse_kernel.h
|
||||
/*
|
||||
This file defines the kernel interface of FUSE
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <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.
|
||||
*/
|
||||
|
|
41
README.md
41
README.md
|
@ -1,31 +1,30 @@
|
|||
[![GoDoc](https://godoc.org/github.com/jacobsa/ogletest?status.svg)](https://godoc.org/github.com/jacobsa/fuse)
|
||||
|
||||
This package allows for writing and mounting user-space file systems from Go. It
|
||||
is a wrapper around [bazil.org/fuse][bazil], which does the heavy lifting. It
|
||||
does not make use of the [bazil.org/fuse/fs][bazil-fs] sub-package, which allows
|
||||
for something like an object-orientend representation of files and directories,
|
||||
and contains a decent amount of canned behavior.
|
||||
This package allows for writing and mounting user-space file systems from Go.
|
||||
Install it as follows:
|
||||
|
||||
The chief improvements and/or differences from the bazil.org packages are:
|
||||
go get -u github.com/jacobsa/fuse
|
||||
|
||||
* No surprises in the form of magic/default behaviors. You must provide an
|
||||
implementation for every method in the interface. Embed a
|
||||
`fuseutil.NotImplementedFileSystem` struct to have default implementations
|
||||
that return `ENOSYS`.
|
||||
Afterward, see the documentation for the following three packages:
|
||||
|
||||
* Every method, struct, and field is thoroughly documented. This may help you
|
||||
get your bearings in the world of FUSE, the Linux VFS, traditional file
|
||||
system implementations, etc., all of which tend to be very poorly
|
||||
documented.
|
||||
* Package [fuse][] provides support for mounting a new file system and
|
||||
reading requests from the kernel.
|
||||
|
||||
* Support for arbitrary offsets in directory entries returned by `ReadDir`.
|
||||
(The bazil.org package assumes that offsets must be counts of bytes.)
|
||||
* Package [fuseops][] enumerates the supported requests from the kernel, and
|
||||
provides documentation on their semantics.
|
||||
|
||||
The very large disadvantage over using the bazil.org packages is that many
|
||||
features have not yet been exposed.
|
||||
* Package [fuseutil][], in particular the `FileSystem` interface, provides a
|
||||
convenient way to create a file system type and export it to the kernel via
|
||||
`fuse.Mount`.
|
||||
|
||||
Make sure to see the sub-packages of the [samples][] package.
|
||||
Make sure to also see the sub-packages of the [samples][] package for examples
|
||||
and tests.
|
||||
|
||||
[bazil]: http://godoc.org/bazil.org/fuse
|
||||
[bazil-fs]: http://godoc.org/bazil.org/fuse/fs
|
||||
This package owes its inspiration and most of its kernel-related code to
|
||||
[bazil.org/fuse][bazil].
|
||||
|
||||
[fuse]: http://godoc.org/github.com/jacobsa/fuse
|
||||
[fuseops]: http://godoc.org/github.com/jacobsa/fuse/fuseops
|
||||
[fuseutil]: http://godoc.org/github.com/jacobsa/fuse/fuseutil
|
||||
[samples]: http://godoc.org/github.com/jacobsa/fuse/samples
|
||||
[bazil]: http://godoc.org/bazil.org/fuse
|
||||
|
|
315
connection.go
315
connection.go
|
@ -16,22 +16,49 @@ package fuse
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
"github.com/jacobsa/fuse/fuseops"
|
||||
"github.com/jacobsa/fuse/internal/buffer"
|
||||
"github.com/jacobsa/fuse/internal/fusekernel"
|
||||
)
|
||||
|
||||
// Ask the Linux kernel for larger read requests.
|
||||
//
|
||||
// As of 2015-03-26, the behavior in the kernel is:
|
||||
//
|
||||
// * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable
|
||||
// ra_pages to be init_response->max_readahead divided by the page size.
|
||||
//
|
||||
// * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set
|
||||
// backing_dev_info::ra_pages to the min of that value and what was sent
|
||||
// in the request's max_readahead field.
|
||||
//
|
||||
// * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding
|
||||
// how much to read ahead.
|
||||
//
|
||||
// * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero.
|
||||
//
|
||||
// Reading a page at a time is a drag. Ask for a larger size.
|
||||
const maxReadahead = 1 << 20
|
||||
|
||||
// A connection to the fuse kernel process.
|
||||
type Connection struct {
|
||||
debugLogger *log.Logger
|
||||
errorLogger *log.Logger
|
||||
wrapped *bazilfuse.Conn
|
||||
|
||||
// The device through which we're talking to the kernel, and the protocol
|
||||
// version that we're using to talk to it.
|
||||
dev *os.File
|
||||
protocol fusekernel.Protocol
|
||||
|
||||
// The context from which all op contexts inherit.
|
||||
parentCtx context.Context
|
||||
|
@ -41,30 +68,85 @@ type Connection struct {
|
|||
|
||||
mu sync.Mutex
|
||||
|
||||
// A map from bazilfuse request ID (*not* the op ID for logging used above)
|
||||
// to a function that cancel's its associated context.
|
||||
// A map from fuse "unique" request ID (*not* the op ID for logging used
|
||||
// above) to a function that cancel's its associated context.
|
||||
//
|
||||
// GUARDED_BY(mu)
|
||||
cancelFuncs map[bazilfuse.RequestID]func()
|
||||
cancelFuncs map[uint64]func()
|
||||
}
|
||||
|
||||
// Responsibility for closing the wrapped connection is transferred to the
|
||||
// result. You must call c.close() eventually.
|
||||
// Create a connection wrapping the supplied file descriptor connected to the
|
||||
// kernel. You must eventually call c.close().
|
||||
//
|
||||
// The loggers may be nil.
|
||||
func newConnection(
|
||||
parentCtx context.Context,
|
||||
debugLogger *log.Logger,
|
||||
errorLogger *log.Logger,
|
||||
wrapped *bazilfuse.Conn) (c *Connection, err error) {
|
||||
dev *os.File) (c *Connection, err error) {
|
||||
c = &Connection{
|
||||
debugLogger: debugLogger,
|
||||
errorLogger: errorLogger,
|
||||
wrapped: wrapped,
|
||||
dev: dev,
|
||||
parentCtx: parentCtx,
|
||||
cancelFuncs: make(map[bazilfuse.RequestID]func()),
|
||||
cancelFuncs: make(map[uint64]func()),
|
||||
}
|
||||
|
||||
// Initialize.
|
||||
err = c.Init()
|
||||
if err != nil {
|
||||
c.close()
|
||||
err = fmt.Errorf("Init: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Do the work necessary to cause the mount process to complete.
|
||||
func (c *Connection) Init() (err error) {
|
||||
// Read the init op.
|
||||
op, err := c.ReadOp()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Reading init op: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
initOp, ok := op.(*fuseops.InternalInitOp)
|
||||
if !ok {
|
||||
err = fmt.Errorf("Expected *fuseops.InternalInitOp, got %T", op)
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure the protocol version spoken by the kernel is new enough.
|
||||
min := fusekernel.Protocol{
|
||||
fusekernel.ProtoVersionMinMajor,
|
||||
fusekernel.ProtoVersionMinMinor,
|
||||
}
|
||||
|
||||
if initOp.Kernel.LT(min) {
|
||||
initOp.Respond(syscall.EPROTO)
|
||||
err = fmt.Errorf("Version too old: %v", initOp.Kernel)
|
||||
return
|
||||
}
|
||||
|
||||
// Downgrade our protocol if necessary.
|
||||
c.protocol = fusekernel.Protocol{
|
||||
fusekernel.ProtoVersionMaxMajor,
|
||||
fusekernel.ProtoVersionMaxMinor,
|
||||
}
|
||||
|
||||
if initOp.Kernel.LT(c.protocol) {
|
||||
c.protocol = initOp.Kernel
|
||||
}
|
||||
|
||||
// Respond to the init op.
|
||||
initOp.Library = c.protocol
|
||||
initOp.MaxReadahead = maxReadahead
|
||||
initOp.MaxWrite = buffer.MaxWriteSize
|
||||
initOp.Flags = fusekernel.InitBigWrites
|
||||
initOp.Respond(nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -104,28 +186,27 @@ func (c *Connection) debugLog(
|
|||
|
||||
// LOCKS_EXCLUDED(c.mu)
|
||||
func (c *Connection) recordCancelFunc(
|
||||
reqID bazilfuse.RequestID,
|
||||
fuseID uint64,
|
||||
f func()) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if _, ok := c.cancelFuncs[reqID]; ok {
|
||||
panic(fmt.Sprintf("Already have cancel func for request %v", reqID))
|
||||
if _, ok := c.cancelFuncs[fuseID]; ok {
|
||||
panic(fmt.Sprintf("Already have cancel func for request %v", fuseID))
|
||||
}
|
||||
|
||||
c.cancelFuncs[reqID] = f
|
||||
c.cancelFuncs[fuseID] = f
|
||||
}
|
||||
|
||||
// Set up state for an op that is about to be returned to the user, given its
|
||||
// underlying bazilfuse request.
|
||||
// underlying fuse opcode and request ID.
|
||||
//
|
||||
// Return a context that should be used for the op.
|
||||
//
|
||||
// LOCKS_EXCLUDED(c.mu)
|
||||
func (c *Connection) beginOp(
|
||||
bfReq bazilfuse.Request) (ctx context.Context) {
|
||||
reqID := bfReq.Hdr().ID
|
||||
|
||||
opCode uint32,
|
||||
fuseID uint64) (ctx context.Context) {
|
||||
// Start with the parent context.
|
||||
ctx = c.parentCtx
|
||||
|
||||
|
@ -137,46 +218,46 @@ func (c *Connection) beginOp(
|
|||
// should not record any state keyed on their ID.
|
||||
//
|
||||
// Cf. https://github.com/osxfuse/osxfuse/issues/208
|
||||
if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok {
|
||||
if opCode != fusekernel.OpForget {
|
||||
var cancel func()
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
c.recordCancelFunc(reqID, cancel)
|
||||
c.recordCancelFunc(fuseID, cancel)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up all state associated with an op to which the user has responded,
|
||||
// given its underlying bazilfuse request. This must be called before a
|
||||
// response is sent to the kernel, to avoid a race where the request's ID might
|
||||
// be reused by osxfuse.
|
||||
// given its underlying fuse opcode and request ID. This must be called before
|
||||
// a response is sent to the kernel, to avoid a race where the request's ID
|
||||
// might be reused by osxfuse.
|
||||
//
|
||||
// LOCKS_EXCLUDED(c.mu)
|
||||
func (c *Connection) finishOp(bfReq bazilfuse.Request) {
|
||||
func (c *Connection) finishOp(
|
||||
opCode uint32,
|
||||
fuseID uint64) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
reqID := bfReq.Hdr().ID
|
||||
|
||||
// Even though the op is finished, context.WithCancel requires us to arrange
|
||||
// for the cancellation function to be invoked. We also must remove it from
|
||||
// our map.
|
||||
//
|
||||
// Special case: we don't do this for Forget requests. See the note in
|
||||
// beginOp above.
|
||||
if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok {
|
||||
cancel, ok := c.cancelFuncs[reqID]
|
||||
if opCode != fusekernel.OpForget {
|
||||
cancel, ok := c.cancelFuncs[fuseID]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Unknown request ID in finishOp: %v", reqID))
|
||||
panic(fmt.Sprintf("Unknown request ID in finishOp: %v", fuseID))
|
||||
}
|
||||
|
||||
cancel()
|
||||
delete(c.cancelFuncs, reqID)
|
||||
delete(c.cancelFuncs, fuseID)
|
||||
}
|
||||
}
|
||||
|
||||
// LOCKS_EXCLUDED(c.mu)
|
||||
func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
|
||||
func (c *Connection) handleInterrupt(fuseID uint64) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
|
@ -194,7 +275,7 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
|
|||
//
|
||||
// Cf. https://github.com/osxfuse/osxfuse/issues/208
|
||||
// Cf. http://comments.gmane.org/gmane.comp.file-systems.fuse.devel/14675
|
||||
cancel, ok := c.cancelFuncs[req.IntrID]
|
||||
cancel, ok := c.cancelFuncs[fuseID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -202,6 +283,71 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
|
|||
cancel()
|
||||
}
|
||||
|
||||
func (c *Connection) allocateInMessage() (m *buffer.InMessage) {
|
||||
// TODO(jacobsa): Use a freelist.
|
||||
m = new(buffer.InMessage)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Connection) destroyInMessage(m *buffer.InMessage) {
|
||||
// TODO(jacobsa): Use a freelist.
|
||||
}
|
||||
|
||||
// Read the next message from the kernel. The message must later be destroyed
|
||||
// using destroyInMessage.
|
||||
func (c *Connection) readMessage() (m *buffer.InMessage, err error) {
|
||||
// Allocate a message.
|
||||
m = c.allocateInMessage()
|
||||
|
||||
// Loop past transient errors.
|
||||
for {
|
||||
// Attempt a reaed.
|
||||
err = m.Init(c.dev)
|
||||
|
||||
// Special cases:
|
||||
//
|
||||
// * ENODEV means fuse has hung up.
|
||||
//
|
||||
// * EINTR means we should try again. (This seems to happen often on
|
||||
// OS X, cf. http://golang.org/issue/11180)
|
||||
//
|
||||
if pe, ok := err.(*os.PathError); ok {
|
||||
switch pe.Err {
|
||||
case syscall.ENODEV:
|
||||
err = io.EOF
|
||||
|
||||
case syscall.EINTR:
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.destroyInMessage(m)
|
||||
m = nil
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Write the supplied message to the kernel.
|
||||
func (c *Connection) writeMessage(msg []byte) (err error) {
|
||||
// Avoid the retry loop in os.File.Write.
|
||||
n, err := syscall.Write(int(c.dev.Fd()), msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n != len(msg) {
|
||||
err = fmt.Errorf("Wrote %d bytes; expected %d", n, len(msg))
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Read the next op from the kernel process. Return io.EOF if the kernel has
|
||||
// closed the connection.
|
||||
//
|
||||
|
@ -212,38 +358,19 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
|
|||
func (c *Connection) ReadOp() (op fuseops.Op, err error) {
|
||||
// Keep going until we find a request we know how to convert.
|
||||
for {
|
||||
// Read a bazilfuse request.
|
||||
var bfReq bazilfuse.Request
|
||||
bfReq, err = c.wrapped.ReadRequest()
|
||||
|
||||
// Read the next message from the kernel.
|
||||
var m *buffer.InMessage
|
||||
m, err = c.readMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Choose an ID for this operation.
|
||||
// Choose an ID for this operation for the purposes of logging.
|
||||
opID := c.nextOpID
|
||||
c.nextOpID++
|
||||
|
||||
// Log the receipt of the operation.
|
||||
c.debugLog(opID, 1, "<- %v", bfReq)
|
||||
|
||||
// Special case: responding to statfs is required to make mounting work on
|
||||
// OS X. We don't currently expose the capability for the file system to
|
||||
// intercept this.
|
||||
if statfsReq, ok := bfReq.(*bazilfuse.StatfsRequest); ok {
|
||||
c.debugLog(opID, 1, "-> (Statfs) OK")
|
||||
statfsReq.Respond(&bazilfuse.StatfsResponse{})
|
||||
continue
|
||||
}
|
||||
|
||||
// Special case: handle interrupt requests.
|
||||
if interruptReq, ok := bfReq.(*bazilfuse.InterruptRequest); ok {
|
||||
c.handleInterrupt(interruptReq)
|
||||
continue
|
||||
}
|
||||
|
||||
// Set up op dependencies.
|
||||
opCtx := c.beginOp(bfReq)
|
||||
opCtx := c.beginOp(m.Header().Opcode, m.Header().Unique)
|
||||
|
||||
var debugLogForOp func(int, string, ...interface{})
|
||||
if c.debugLogger != nil {
|
||||
|
@ -252,28 +379,82 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
finished := func(err error) { c.finishOp(bfReq) }
|
||||
sendReply := func(
|
||||
op fuseops.Op,
|
||||
fuseID uint64,
|
||||
replyMsg []byte,
|
||||
opErr error) (err error) {
|
||||
// Make sure we destroy the message, as required by readMessage.
|
||||
defer c.destroyInMessage(m)
|
||||
|
||||
op = fuseops.Convert(
|
||||
// Clean up state for this op.
|
||||
c.finishOp(m.Header().Opcode, m.Header().Unique)
|
||||
|
||||
// Debug logging
|
||||
if c.debugLogger != nil {
|
||||
if opErr == nil {
|
||||
op.Logf("-> OK: %s", op.DebugString())
|
||||
} else {
|
||||
op.Logf("-> error: %v", opErr)
|
||||
}
|
||||
}
|
||||
|
||||
// Error logging
|
||||
if opErr != nil && c.errorLogger != nil {
|
||||
c.errorLogger.Printf("(%s) error: %v", op.ShortDesc(), opErr)
|
||||
}
|
||||
|
||||
// Send the reply to the kernel.
|
||||
err = c.writeMessage(replyMsg)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("writeMessage: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Convert the message to an Op.
|
||||
op, err = fuseops.Convert(
|
||||
opCtx,
|
||||
bfReq,
|
||||
m,
|
||||
c.protocol,
|
||||
debugLogForOp,
|
||||
c.errorLogger,
|
||||
finished)
|
||||
sendReply)
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("fuseops.Convert: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Log the receipt of the operation.
|
||||
c.debugLog(opID, 1, "<- %v", op.ShortDesc())
|
||||
|
||||
// Special case: responding to statfs is required to make mounting work on
|
||||
// OS X. We don't currently expose the capability for the file system to
|
||||
// intercept this.
|
||||
if _, ok := op.(*fuseops.InternalStatFSOp); ok {
|
||||
op.Respond(nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// Special case: handle interrupt requests.
|
||||
if interruptOp, ok := op.(*fuseops.InternalInterruptOp); ok {
|
||||
c.handleInterrupt(interruptOp.FuseID)
|
||||
continue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Connection) waitForReady() (err error) {
|
||||
<-c.wrapped.Ready
|
||||
err = c.wrapped.MountError
|
||||
return
|
||||
}
|
||||
|
||||
// Close the connection. Must not be called until operations that were read
|
||||
// from the connection have been responded to.
|
||||
func (c *Connection) close() (err error) {
|
||||
err = c.wrapped.Close()
|
||||
// Posix doesn't say that close can be called concurrently with read or
|
||||
// write, but luckily we exclude the possibility of a race by requiring the
|
||||
// user to respond to all ops first.
|
||||
err = c.dev.Close()
|
||||
return
|
||||
}
|
||||
|
|
20
errors.go
20
errors.go
|
@ -14,20 +14,16 @@
|
|||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
)
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
// Errors corresponding to kernel error numbers. These may be treated
|
||||
// specially by fuseops.Op.Respond methods.
|
||||
EEXIST = bazilfuse.EEXIST
|
||||
EINVAL = bazilfuse.Errno(syscall.EINVAL)
|
||||
EIO = bazilfuse.EIO
|
||||
ENOENT = bazilfuse.ENOENT
|
||||
ENOSYS = bazilfuse.ENOSYS
|
||||
ENOTDIR = bazilfuse.Errno(syscall.ENOTDIR)
|
||||
ENOTEMPTY = bazilfuse.Errno(syscall.ENOTEMPTY)
|
||||
EEXIST = syscall.EEXIST
|
||||
EINVAL = syscall.EINVAL
|
||||
EIO = syscall.EIO
|
||||
ENOENT = syscall.ENOENT
|
||||
ENOSYS = syscall.ENOSYS
|
||||
ENOTDIR = syscall.ENOTDIR
|
||||
ENOTEMPTY = syscall.ENOTEMPTY
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
case fusekernel.OpUnlink:
|
||||
buf := m.ConsumeBytes(m.Len())
|
||||
n := len(buf)
|
||||
if n == 0 || buf[n-1] != '\x00' {
|
||||
err = errors.New("Corrupt OpUnlink")
|
||||
return
|
||||
}
|
||||
io = to
|
||||
co = &to.commonOp
|
||||
} else {
|
||||
|
||||
to := &UnlinkOp{
|
||||
bfReq: typed,
|
||||
Parent: InodeID(typed.Header.Node),
|
||||
Name: typed.Name,
|
||||
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.OpenRequest:
|
||||
if typed.Dir {
|
||||
to := &OpenDirOp{
|
||||
bfReq: typed,
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
Flags: typed.Flags,
|
||||
to := &RmDirOp{
|
||||
Parent: InodeID(m.Header().Nodeid),
|
||||
Name: string(buf[:n-1]),
|
||||
}
|
||||
io = to
|
||||
co = &to.commonOp
|
||||
} else {
|
||||
|
||||
case fusekernel.OpOpen:
|
||||
to := &OpenFileOp{
|
||||
bfReq: typed,
|
||||
Inode: InodeID(typed.Header.Node),
|
||||
Flags: typed.Flags,
|
||||
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.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,
|
||||
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
|
||||
}
|
||||
|
||||
case *bazilfuse.ReleaseRequest:
|
||||
if typed.Dir {
|
||||
to := &ReleaseDirHandleOp{
|
||||
bfReq: typed,
|
||||
Handle: HandleID(typed.Handle),
|
||||
to := &ReadDirOp{
|
||||
Inode: InodeID(m.Header().Nodeid),
|
||||
Handle: HandleID(in.Fh),
|
||||
Offset: DirOffset(in.Offset),
|
||||
Size: int(in.Size),
|
||||
}
|
||||
io = to
|
||||
co = &to.commonOp
|
||||
} else {
|
||||
|
||||
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{
|
||||
bfReq: typed,
|
||||
Handle: HandleID(typed.Handle),
|
||||
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 {
|
||||
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{
|
||||
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 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
|
||||
}
|
||||
|
|
261
fuseops/ops.go
261
fuseops/ops.go
|
@ -18,8 +18,10 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/jacobsa/bazilfuse"
|
||||
"github.com/jacobsa/fuse/internal/buffer"
|
||||
"github.com/jacobsa/fuse/internal/fusekernel"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
@ -30,6 +32,9 @@ type Op interface {
|
|||
// A short description of the op, to be used in logging.
|
||||
ShortDesc() string
|
||||
|
||||
// A long description of the op, to be used in debug logging.
|
||||
DebugString() string
|
||||
|
||||
// A context that can be used for long-running operations.
|
||||
Context() context.Context
|
||||
|
||||
|
@ -55,7 +60,7 @@ type Op interface {
|
|||
// when resolving user paths to dentry structs, which are then cached.
|
||||
type LookUpInodeOp struct {
|
||||
commonOp
|
||||
bfReq *bazilfuse.LookupRequest
|
||||
protocol fusekernel.Protocol
|
||||
|
||||
// The ID of the directory inode to which the child belongs.
|
||||
Parent InodeID
|
||||
|
@ -83,11 +88,12 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) {
|
|||
return
|
||||
}
|
||||
|
||||
func (o *LookUpInodeOp) respond() {
|
||||
resp := bazilfuse.LookupResponse{}
|
||||
convertChildInodeEntry(&o.Entry, &resp)
|
||||
func (o *LookUpInodeOp) kernelResponse() (b buffer.OutMessage) {
|
||||
size := fusekernel.EntryOutSize(o.protocol)
|
||||
b = buffer.NewOutMessage(size)
|
||||
out := (*fusekernel.EntryOut)(b.Grow(size))
|
||||
convertChildInodeEntry(&o.Entry, out)
|
||||
|
||||
o.bfReq.Respond(&resp)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -97,7 +103,7 @@ func (o *LookUpInodeOp) respond() {
|
|||
// field of ChildInodeEntry, etc.
|
||||
type GetInodeAttributesOp struct {
|
||||
commonOp
|
||||
bfReq *bazilfuse.GetattrRequest
|
||||
protocol fusekernel.Protocol
|
||||
|
||||
// The inode of interest.
|
||||
Inode InodeID
|
||||
|
@ -109,12 +115,21 @@ type GetInodeAttributesOp struct {
|
|||
AttributesExpiration time.Time
|
||||
}
|
||||
|
||||
func (o *GetInodeAttributesOp) respond() {
|
||||
resp := bazilfuse.GetattrResponse{
|
||||
Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration),
|
||||
func (o *GetInodeAttributesOp) DebugString() string {
|
||||
return fmt.Sprintf(
|
||||
"Inode: %d, Exp: %v, Attr: %s",
|
||||
o.Inode,
|
||||
o.AttributesExpiration,
|
||||
o.Attributes.DebugString())
|
||||
}
|
||||
|
||||
o.bfReq.Respond(&resp)
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package fusekernel
|
|
@ -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()
|
||||
}
|
|
@ -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, ",")
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue