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
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
==========================================================================
|
||||||
|
Portions of this package were adopted from bazil.org/fuse, which contains the
|
||||||
|
following license notice.
|
||||||
|
|
||||||
|
Copyright (c) 2013-2015 Tommi Virtanen.
|
||||||
|
Copyright (c) 2009, 2011, 2012 The Go Authors.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The following included software components have additional copyright
|
||||||
|
notices and license terms that may differ from the above.
|
||||||
|
|
||||||
|
|
||||||
|
File fuse.go:
|
||||||
|
|
||||||
|
// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
|
||||||
|
// which carries this notice:
|
||||||
|
//
|
||||||
|
// The files in this directory are subject to the following license.
|
||||||
|
//
|
||||||
|
// The author of this software is Russ Cox.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2006 Russ Cox
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose without fee is hereby granted, provided that this entire notice
|
||||||
|
// is included in all copies of any software which is or includes a copy
|
||||||
|
// or modification of this software and in all copies of the supporting
|
||||||
|
// documentation for such software.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||||
|
// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
|
||||||
|
// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
|
||||||
|
// FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
|
||||||
|
File fuse_kernel.go:
|
||||||
|
|
||||||
|
// Derived from FUSE's fuse_kernel.h
|
||||||
|
/*
|
||||||
|
This file defines the kernel interface of FUSE
|
||||||
|
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||||
|
|
||||||
|
|
||||||
|
This -- and only this -- header file may also be distributed under
|
||||||
|
the terms of the BSD Licence as follows:
|
||||||
|
|
||||||
|
Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
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)
|
[![GoDoc](https://godoc.org/github.com/jacobsa/ogletest?status.svg)](https://godoc.org/github.com/jacobsa/fuse)
|
||||||
|
|
||||||
This package allows for writing and mounting user-space file systems from Go. It
|
This package allows for writing and mounting user-space file systems from Go.
|
||||||
is a wrapper around [bazil.org/fuse][bazil], which does the heavy lifting. It
|
Install it as follows:
|
||||||
does not make use of the [bazil.org/fuse/fs][bazil-fs] sub-package, which allows
|
|
||||||
for something like an object-orientend representation of files and directories,
|
|
||||||
and contains a decent amount of canned behavior.
|
|
||||||
|
|
||||||
The chief improvements and/or differences from the bazil.org packages are:
|
go get -u github.com/jacobsa/fuse
|
||||||
|
|
||||||
* No surprises in the form of magic/default behaviors. You must provide an
|
Afterward, see the documentation for the following three packages:
|
||||||
implementation for every method in the interface. Embed a
|
|
||||||
`fuseutil.NotImplementedFileSystem` struct to have default implementations
|
|
||||||
that return `ENOSYS`.
|
|
||||||
|
|
||||||
* Every method, struct, and field is thoroughly documented. This may help you
|
* Package [fuse][] provides support for mounting a new file system and
|
||||||
get your bearings in the world of FUSE, the Linux VFS, traditional file
|
reading requests from the kernel.
|
||||||
system implementations, etc., all of which tend to be very poorly
|
|
||||||
documented.
|
|
||||||
|
|
||||||
* Support for arbitrary offsets in directory entries returned by `ReadDir`.
|
* Package [fuseops][] enumerates the supported requests from the kernel, and
|
||||||
(The bazil.org package assumes that offsets must be counts of bytes.)
|
provides documentation on their semantics.
|
||||||
|
|
||||||
The very large disadvantage over using the bazil.org packages is that many
|
* Package [fuseutil][], in particular the `FileSystem` interface, provides a
|
||||||
features have not yet been exposed.
|
convenient way to create a file system type and export it to the kernel via
|
||||||
|
`fuse.Mount`.
|
||||||
|
|
||||||
Make sure to see the sub-packages of the [samples][] package.
|
Make sure to also see the sub-packages of the [samples][] package for examples
|
||||||
|
and tests.
|
||||||
|
|
||||||
[bazil]: http://godoc.org/bazil.org/fuse
|
This package owes its inspiration and most of its kernel-related code to
|
||||||
[bazil-fs]: http://godoc.org/bazil.org/fuse/fs
|
[bazil.org/fuse][bazil].
|
||||||
|
|
||||||
|
[fuse]: http://godoc.org/github.com/jacobsa/fuse
|
||||||
|
[fuseops]: http://godoc.org/github.com/jacobsa/fuse/fuseops
|
||||||
|
[fuseutil]: http://godoc.org/github.com/jacobsa/fuse/fuseutil
|
||||||
[samples]: http://godoc.org/github.com/jacobsa/fuse/samples
|
[samples]: http://godoc.org/github.com/jacobsa/fuse/samples
|
||||||
|
[bazil]: http://godoc.org/bazil.org/fuse
|
||||||
|
|
315
connection.go
315
connection.go
|
@ -16,22 +16,49 @@ package fuse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
|
||||||
"github.com/jacobsa/fuse/fuseops"
|
"github.com/jacobsa/fuse/fuseops"
|
||||||
|
"github.com/jacobsa/fuse/internal/buffer"
|
||||||
|
"github.com/jacobsa/fuse/internal/fusekernel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Ask the Linux kernel for larger read requests.
|
||||||
|
//
|
||||||
|
// As of 2015-03-26, the behavior in the kernel is:
|
||||||
|
//
|
||||||
|
// * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable
|
||||||
|
// ra_pages to be init_response->max_readahead divided by the page size.
|
||||||
|
//
|
||||||
|
// * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set
|
||||||
|
// backing_dev_info::ra_pages to the min of that value and what was sent
|
||||||
|
// in the request's max_readahead field.
|
||||||
|
//
|
||||||
|
// * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding
|
||||||
|
// how much to read ahead.
|
||||||
|
//
|
||||||
|
// * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero.
|
||||||
|
//
|
||||||
|
// Reading a page at a time is a drag. Ask for a larger size.
|
||||||
|
const maxReadahead = 1 << 20
|
||||||
|
|
||||||
// A connection to the fuse kernel process.
|
// A connection to the fuse kernel process.
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
debugLogger *log.Logger
|
debugLogger *log.Logger
|
||||||
errorLogger *log.Logger
|
errorLogger *log.Logger
|
||||||
wrapped *bazilfuse.Conn
|
|
||||||
|
// The device through which we're talking to the kernel, and the protocol
|
||||||
|
// version that we're using to talk to it.
|
||||||
|
dev *os.File
|
||||||
|
protocol fusekernel.Protocol
|
||||||
|
|
||||||
// The context from which all op contexts inherit.
|
// The context from which all op contexts inherit.
|
||||||
parentCtx context.Context
|
parentCtx context.Context
|
||||||
|
@ -41,30 +68,85 @@ type Connection struct {
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
// A map from bazilfuse request ID (*not* the op ID for logging used above)
|
// A map from fuse "unique" request ID (*not* the op ID for logging used
|
||||||
// to a function that cancel's its associated context.
|
// above) to a function that cancel's its associated context.
|
||||||
//
|
//
|
||||||
// GUARDED_BY(mu)
|
// GUARDED_BY(mu)
|
||||||
cancelFuncs map[bazilfuse.RequestID]func()
|
cancelFuncs map[uint64]func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Responsibility for closing the wrapped connection is transferred to the
|
// Create a connection wrapping the supplied file descriptor connected to the
|
||||||
// result. You must call c.close() eventually.
|
// kernel. You must eventually call c.close().
|
||||||
//
|
//
|
||||||
// The loggers may be nil.
|
// The loggers may be nil.
|
||||||
func newConnection(
|
func newConnection(
|
||||||
parentCtx context.Context,
|
parentCtx context.Context,
|
||||||
debugLogger *log.Logger,
|
debugLogger *log.Logger,
|
||||||
errorLogger *log.Logger,
|
errorLogger *log.Logger,
|
||||||
wrapped *bazilfuse.Conn) (c *Connection, err error) {
|
dev *os.File) (c *Connection, err error) {
|
||||||
c = &Connection{
|
c = &Connection{
|
||||||
debugLogger: debugLogger,
|
debugLogger: debugLogger,
|
||||||
errorLogger: errorLogger,
|
errorLogger: errorLogger,
|
||||||
wrapped: wrapped,
|
dev: dev,
|
||||||
parentCtx: parentCtx,
|
parentCtx: parentCtx,
|
||||||
cancelFuncs: make(map[bazilfuse.RequestID]func()),
|
cancelFuncs: make(map[uint64]func()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize.
|
||||||
|
err = c.Init()
|
||||||
|
if err != nil {
|
||||||
|
c.close()
|
||||||
|
err = fmt.Errorf("Init: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the work necessary to cause the mount process to complete.
|
||||||
|
func (c *Connection) Init() (err error) {
|
||||||
|
// Read the init op.
|
||||||
|
op, err := c.ReadOp()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Reading init op: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
initOp, ok := op.(*fuseops.InternalInitOp)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("Expected *fuseops.InternalInitOp, got %T", op)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the protocol version spoken by the kernel is new enough.
|
||||||
|
min := fusekernel.Protocol{
|
||||||
|
fusekernel.ProtoVersionMinMajor,
|
||||||
|
fusekernel.ProtoVersionMinMinor,
|
||||||
|
}
|
||||||
|
|
||||||
|
if initOp.Kernel.LT(min) {
|
||||||
|
initOp.Respond(syscall.EPROTO)
|
||||||
|
err = fmt.Errorf("Version too old: %v", initOp.Kernel)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Downgrade our protocol if necessary.
|
||||||
|
c.protocol = fusekernel.Protocol{
|
||||||
|
fusekernel.ProtoVersionMaxMajor,
|
||||||
|
fusekernel.ProtoVersionMaxMinor,
|
||||||
|
}
|
||||||
|
|
||||||
|
if initOp.Kernel.LT(c.protocol) {
|
||||||
|
c.protocol = initOp.Kernel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond to the init op.
|
||||||
|
initOp.Library = c.protocol
|
||||||
|
initOp.MaxReadahead = maxReadahead
|
||||||
|
initOp.MaxWrite = buffer.MaxWriteSize
|
||||||
|
initOp.Flags = fusekernel.InitBigWrites
|
||||||
|
initOp.Respond(nil)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,28 +186,27 @@ func (c *Connection) debugLog(
|
||||||
|
|
||||||
// LOCKS_EXCLUDED(c.mu)
|
// LOCKS_EXCLUDED(c.mu)
|
||||||
func (c *Connection) recordCancelFunc(
|
func (c *Connection) recordCancelFunc(
|
||||||
reqID bazilfuse.RequestID,
|
fuseID uint64,
|
||||||
f func()) {
|
f func()) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if _, ok := c.cancelFuncs[reqID]; ok {
|
if _, ok := c.cancelFuncs[fuseID]; ok {
|
||||||
panic(fmt.Sprintf("Already have cancel func for request %v", reqID))
|
panic(fmt.Sprintf("Already have cancel func for request %v", fuseID))
|
||||||
}
|
}
|
||||||
|
|
||||||
c.cancelFuncs[reqID] = f
|
c.cancelFuncs[fuseID] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up state for an op that is about to be returned to the user, given its
|
// Set up state for an op that is about to be returned to the user, given its
|
||||||
// underlying bazilfuse request.
|
// underlying fuse opcode and request ID.
|
||||||
//
|
//
|
||||||
// Return a context that should be used for the op.
|
// Return a context that should be used for the op.
|
||||||
//
|
//
|
||||||
// LOCKS_EXCLUDED(c.mu)
|
// LOCKS_EXCLUDED(c.mu)
|
||||||
func (c *Connection) beginOp(
|
func (c *Connection) beginOp(
|
||||||
bfReq bazilfuse.Request) (ctx context.Context) {
|
opCode uint32,
|
||||||
reqID := bfReq.Hdr().ID
|
fuseID uint64) (ctx context.Context) {
|
||||||
|
|
||||||
// Start with the parent context.
|
// Start with the parent context.
|
||||||
ctx = c.parentCtx
|
ctx = c.parentCtx
|
||||||
|
|
||||||
|
@ -137,46 +218,46 @@ func (c *Connection) beginOp(
|
||||||
// should not record any state keyed on their ID.
|
// should not record any state keyed on their ID.
|
||||||
//
|
//
|
||||||
// Cf. https://github.com/osxfuse/osxfuse/issues/208
|
// Cf. https://github.com/osxfuse/osxfuse/issues/208
|
||||||
if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok {
|
if opCode != fusekernel.OpForget {
|
||||||
var cancel func()
|
var cancel func()
|
||||||
ctx, cancel = context.WithCancel(ctx)
|
ctx, cancel = context.WithCancel(ctx)
|
||||||
c.recordCancelFunc(reqID, cancel)
|
c.recordCancelFunc(fuseID, cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up all state associated with an op to which the user has responded,
|
// Clean up all state associated with an op to which the user has responded,
|
||||||
// given its underlying bazilfuse request. This must be called before a
|
// given its underlying fuse opcode and request ID. This must be called before
|
||||||
// response is sent to the kernel, to avoid a race where the request's ID might
|
// a response is sent to the kernel, to avoid a race where the request's ID
|
||||||
// be reused by osxfuse.
|
// might be reused by osxfuse.
|
||||||
//
|
//
|
||||||
// LOCKS_EXCLUDED(c.mu)
|
// LOCKS_EXCLUDED(c.mu)
|
||||||
func (c *Connection) finishOp(bfReq bazilfuse.Request) {
|
func (c *Connection) finishOp(
|
||||||
|
opCode uint32,
|
||||||
|
fuseID uint64) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
reqID := bfReq.Hdr().ID
|
|
||||||
|
|
||||||
// Even though the op is finished, context.WithCancel requires us to arrange
|
// Even though the op is finished, context.WithCancel requires us to arrange
|
||||||
// for the cancellation function to be invoked. We also must remove it from
|
// for the cancellation function to be invoked. We also must remove it from
|
||||||
// our map.
|
// our map.
|
||||||
//
|
//
|
||||||
// Special case: we don't do this for Forget requests. See the note in
|
// Special case: we don't do this for Forget requests. See the note in
|
||||||
// beginOp above.
|
// beginOp above.
|
||||||
if _, ok := bfReq.(*bazilfuse.ForgetRequest); !ok {
|
if opCode != fusekernel.OpForget {
|
||||||
cancel, ok := c.cancelFuncs[reqID]
|
cancel, ok := c.cancelFuncs[fuseID]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(fmt.Sprintf("Unknown request ID in finishOp: %v", reqID))
|
panic(fmt.Sprintf("Unknown request ID in finishOp: %v", fuseID))
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
delete(c.cancelFuncs, reqID)
|
delete(c.cancelFuncs, fuseID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LOCKS_EXCLUDED(c.mu)
|
// LOCKS_EXCLUDED(c.mu)
|
||||||
func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
|
func (c *Connection) handleInterrupt(fuseID uint64) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
@ -194,7 +275,7 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
|
||||||
//
|
//
|
||||||
// Cf. https://github.com/osxfuse/osxfuse/issues/208
|
// Cf. https://github.com/osxfuse/osxfuse/issues/208
|
||||||
// Cf. http://comments.gmane.org/gmane.comp.file-systems.fuse.devel/14675
|
// Cf. http://comments.gmane.org/gmane.comp.file-systems.fuse.devel/14675
|
||||||
cancel, ok := c.cancelFuncs[req.IntrID]
|
cancel, ok := c.cancelFuncs[fuseID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -202,6 +283,71 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Connection) allocateInMessage() (m *buffer.InMessage) {
|
||||||
|
// TODO(jacobsa): Use a freelist.
|
||||||
|
m = new(buffer.InMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) destroyInMessage(m *buffer.InMessage) {
|
||||||
|
// TODO(jacobsa): Use a freelist.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the next message from the kernel. The message must later be destroyed
|
||||||
|
// using destroyInMessage.
|
||||||
|
func (c *Connection) readMessage() (m *buffer.InMessage, err error) {
|
||||||
|
// Allocate a message.
|
||||||
|
m = c.allocateInMessage()
|
||||||
|
|
||||||
|
// Loop past transient errors.
|
||||||
|
for {
|
||||||
|
// Attempt a reaed.
|
||||||
|
err = m.Init(c.dev)
|
||||||
|
|
||||||
|
// Special cases:
|
||||||
|
//
|
||||||
|
// * ENODEV means fuse has hung up.
|
||||||
|
//
|
||||||
|
// * EINTR means we should try again. (This seems to happen often on
|
||||||
|
// OS X, cf. http://golang.org/issue/11180)
|
||||||
|
//
|
||||||
|
if pe, ok := err.(*os.PathError); ok {
|
||||||
|
switch pe.Err {
|
||||||
|
case syscall.ENODEV:
|
||||||
|
err = io.EOF
|
||||||
|
|
||||||
|
case syscall.EINTR:
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.destroyInMessage(m)
|
||||||
|
m = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the supplied message to the kernel.
|
||||||
|
func (c *Connection) writeMessage(msg []byte) (err error) {
|
||||||
|
// Avoid the retry loop in os.File.Write.
|
||||||
|
n, err := syscall.Write(int(c.dev.Fd()), msg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != len(msg) {
|
||||||
|
err = fmt.Errorf("Wrote %d bytes; expected %d", n, len(msg))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Read the next op from the kernel process. Return io.EOF if the kernel has
|
// Read the next op from the kernel process. Return io.EOF if the kernel has
|
||||||
// closed the connection.
|
// closed the connection.
|
||||||
//
|
//
|
||||||
|
@ -212,38 +358,19 @@ func (c *Connection) handleInterrupt(req *bazilfuse.InterruptRequest) {
|
||||||
func (c *Connection) ReadOp() (op fuseops.Op, err error) {
|
func (c *Connection) ReadOp() (op fuseops.Op, err error) {
|
||||||
// Keep going until we find a request we know how to convert.
|
// Keep going until we find a request we know how to convert.
|
||||||
for {
|
for {
|
||||||
// Read a bazilfuse request.
|
// Read the next message from the kernel.
|
||||||
var bfReq bazilfuse.Request
|
var m *buffer.InMessage
|
||||||
bfReq, err = c.wrapped.ReadRequest()
|
m, err = c.readMessage()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Choose an ID for this operation.
|
// Choose an ID for this operation for the purposes of logging.
|
||||||
opID := c.nextOpID
|
opID := c.nextOpID
|
||||||
c.nextOpID++
|
c.nextOpID++
|
||||||
|
|
||||||
// Log the receipt of the operation.
|
|
||||||
c.debugLog(opID, 1, "<- %v", bfReq)
|
|
||||||
|
|
||||||
// Special case: responding to statfs is required to make mounting work on
|
|
||||||
// OS X. We don't currently expose the capability for the file system to
|
|
||||||
// intercept this.
|
|
||||||
if statfsReq, ok := bfReq.(*bazilfuse.StatfsRequest); ok {
|
|
||||||
c.debugLog(opID, 1, "-> (Statfs) OK")
|
|
||||||
statfsReq.Respond(&bazilfuse.StatfsResponse{})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case: handle interrupt requests.
|
|
||||||
if interruptReq, ok := bfReq.(*bazilfuse.InterruptRequest); ok {
|
|
||||||
c.handleInterrupt(interruptReq)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up op dependencies.
|
// Set up op dependencies.
|
||||||
opCtx := c.beginOp(bfReq)
|
opCtx := c.beginOp(m.Header().Opcode, m.Header().Unique)
|
||||||
|
|
||||||
var debugLogForOp func(int, string, ...interface{})
|
var debugLogForOp func(int, string, ...interface{})
|
||||||
if c.debugLogger != nil {
|
if c.debugLogger != nil {
|
||||||
|
@ -252,28 +379,82 @@ func (c *Connection) ReadOp() (op fuseops.Op, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finished := func(err error) { c.finishOp(bfReq) }
|
sendReply := func(
|
||||||
|
op fuseops.Op,
|
||||||
|
fuseID uint64,
|
||||||
|
replyMsg []byte,
|
||||||
|
opErr error) (err error) {
|
||||||
|
// Make sure we destroy the message, as required by readMessage.
|
||||||
|
defer c.destroyInMessage(m)
|
||||||
|
|
||||||
op = fuseops.Convert(
|
// Clean up state for this op.
|
||||||
|
c.finishOp(m.Header().Opcode, m.Header().Unique)
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
if c.debugLogger != nil {
|
||||||
|
if opErr == nil {
|
||||||
|
op.Logf("-> OK: %s", op.DebugString())
|
||||||
|
} else {
|
||||||
|
op.Logf("-> error: %v", opErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logging
|
||||||
|
if opErr != nil && c.errorLogger != nil {
|
||||||
|
c.errorLogger.Printf("(%s) error: %v", op.ShortDesc(), opErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the reply to the kernel.
|
||||||
|
err = c.writeMessage(replyMsg)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("writeMessage: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the message to an Op.
|
||||||
|
op, err = fuseops.Convert(
|
||||||
opCtx,
|
opCtx,
|
||||||
bfReq,
|
m,
|
||||||
|
c.protocol,
|
||||||
debugLogForOp,
|
debugLogForOp,
|
||||||
c.errorLogger,
|
c.errorLogger,
|
||||||
finished)
|
sendReply)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("fuseops.Convert: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the receipt of the operation.
|
||||||
|
c.debugLog(opID, 1, "<- %v", op.ShortDesc())
|
||||||
|
|
||||||
|
// Special case: responding to statfs is required to make mounting work on
|
||||||
|
// OS X. We don't currently expose the capability for the file system to
|
||||||
|
// intercept this.
|
||||||
|
if _, ok := op.(*fuseops.InternalStatFSOp); ok {
|
||||||
|
op.Respond(nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: handle interrupt requests.
|
||||||
|
if interruptOp, ok := op.(*fuseops.InternalInterruptOp); ok {
|
||||||
|
c.handleInterrupt(interruptOp.FuseID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Connection) waitForReady() (err error) {
|
|
||||||
<-c.wrapped.Ready
|
|
||||||
err = c.wrapped.MountError
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the connection. Must not be called until operations that were read
|
// Close the connection. Must not be called until operations that were read
|
||||||
// from the connection have been responded to.
|
// from the connection have been responded to.
|
||||||
func (c *Connection) close() (err error) {
|
func (c *Connection) close() (err error) {
|
||||||
err = c.wrapped.Close()
|
// Posix doesn't say that close can be called concurrently with read or
|
||||||
|
// write, but luckily we exclude the possibility of a race by requiring the
|
||||||
|
// user to respond to all ops first.
|
||||||
|
err = c.dev.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
20
errors.go
20
errors.go
|
@ -14,20 +14,16 @@
|
||||||
|
|
||||||
package fuse
|
package fuse
|
||||||
|
|
||||||
import (
|
import "syscall"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Errors corresponding to kernel error numbers. These may be treated
|
// Errors corresponding to kernel error numbers. These may be treated
|
||||||
// specially by fuseops.Op.Respond methods.
|
// specially by fuseops.Op.Respond methods.
|
||||||
EEXIST = bazilfuse.EEXIST
|
EEXIST = syscall.EEXIST
|
||||||
EINVAL = bazilfuse.Errno(syscall.EINVAL)
|
EINVAL = syscall.EINVAL
|
||||||
EIO = bazilfuse.EIO
|
EIO = syscall.EIO
|
||||||
ENOENT = bazilfuse.ENOENT
|
ENOENT = syscall.ENOENT
|
||||||
ENOSYS = bazilfuse.ENOSYS
|
ENOSYS = syscall.ENOSYS
|
||||||
ENOTDIR = bazilfuse.Errno(syscall.ENOTDIR)
|
ENOTDIR = syscall.ENOTDIR
|
||||||
ENOTEMPTY = bazilfuse.Errno(syscall.ENOTEMPTY)
|
ENOTEMPTY = syscall.ENOTEMPTY
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,8 +19,9 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
"github.com/jacobsa/fuse/internal/buffer"
|
||||||
"github.com/jacobsa/reqtrace"
|
"github.com/jacobsa/reqtrace"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -30,10 +31,19 @@ import (
|
||||||
type internalOp interface {
|
type internalOp interface {
|
||||||
Op
|
Op
|
||||||
|
|
||||||
// Respond to the underlying bazilfuse request, successfully.
|
// Create a response message for the kernel, leaving the leading
|
||||||
respond()
|
// fusekernel.OutHeader untouched.
|
||||||
|
//
|
||||||
|
// Special case: a zero return value means that the kernel is not expecting a
|
||||||
|
// response.
|
||||||
|
kernelResponse() (b buffer.OutMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A function that sends a reply message back to the kernel for the request
|
||||||
|
// with the given fuse unique ID. The error argument is for informational
|
||||||
|
// purposes only; the error to hand to the kernel is encoded in the message.
|
||||||
|
type replyFunc func(Op, uint64, []byte, error) error
|
||||||
|
|
||||||
// A helper for embedding common behavior.
|
// A helper for embedding common behavior.
|
||||||
type commonOp struct {
|
type commonOp struct {
|
||||||
// The context exposed to the user.
|
// The context exposed to the user.
|
||||||
|
@ -42,8 +52,11 @@ type commonOp struct {
|
||||||
// The op in which this struct is embedded.
|
// The op in which this struct is embedded.
|
||||||
op internalOp
|
op internalOp
|
||||||
|
|
||||||
// The underlying bazilfuse request for this op.
|
// The fuse unique ID of this request, as assigned by the kernel.
|
||||||
bazilReq bazilfuse.Request
|
fuseID uint64
|
||||||
|
|
||||||
|
// A function that can be used to send a reply to the kernel.
|
||||||
|
sendReply replyFunc
|
||||||
|
|
||||||
// A function that can be used to log debug information about the op. The
|
// A function that can be used to log debug information about the op. The
|
||||||
// first argument is a call depth.
|
// first argument is a call depth.
|
||||||
|
@ -55,14 +68,11 @@ type commonOp struct {
|
||||||
//
|
//
|
||||||
// May be nil.
|
// May be nil.
|
||||||
errorLogger *log.Logger
|
errorLogger *log.Logger
|
||||||
|
|
||||||
// A function that is invoked with the error given to Respond, for use in
|
|
||||||
// closing off traces and reporting back to the connection.
|
|
||||||
finished func(error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *commonOp) ShortDesc() (desc string) {
|
func (o *commonOp) ShortDesc() (desc string) {
|
||||||
opName := reflect.TypeOf(o.op).String()
|
v := reflect.ValueOf(o.op)
|
||||||
|
opName := v.Type().String()
|
||||||
|
|
||||||
// Attempt to better handle the usual case: a string that looks like
|
// Attempt to better handle the usual case: a string that looks like
|
||||||
// "*fuseops.GetInodeAttributesOp".
|
// "*fuseops.GetInodeAttributesOp".
|
||||||
|
@ -72,36 +82,46 @@ func (o *commonOp) ShortDesc() (desc string) {
|
||||||
opName = opName[len(prefix) : len(opName)-len(suffix)]
|
opName = opName[len(prefix) : len(opName)-len(suffix)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include the inode number to which the op applies.
|
desc = opName
|
||||||
desc = fmt.Sprintf("%s(inode=%v)", opName, o.bazilReq.Hdr().Node)
|
|
||||||
|
// Include the inode number to which the op applies, if possible.
|
||||||
|
if f := v.Elem().FieldByName("Inode"); f.IsValid() {
|
||||||
|
desc = fmt.Sprintf("%s(inode=%v)", desc, f.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *commonOp) DebugString() string {
|
||||||
|
// By default, defer to ShortDesc.
|
||||||
|
return o.op.ShortDesc()
|
||||||
|
}
|
||||||
|
|
||||||
func (o *commonOp) init(
|
func (o *commonOp) init(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op internalOp,
|
op internalOp,
|
||||||
bazilReq bazilfuse.Request,
|
fuseID uint64,
|
||||||
|
sendReply replyFunc,
|
||||||
debugLog func(int, string, ...interface{}),
|
debugLog func(int, string, ...interface{}),
|
||||||
errorLogger *log.Logger,
|
errorLogger *log.Logger) {
|
||||||
finished func(error)) {
|
|
||||||
// Initialize basic fields.
|
// Initialize basic fields.
|
||||||
o.ctx = ctx
|
o.ctx = ctx
|
||||||
o.op = op
|
o.op = op
|
||||||
o.bazilReq = bazilReq
|
o.fuseID = fuseID
|
||||||
|
o.sendReply = sendReply
|
||||||
o.debugLog = debugLog
|
o.debugLog = debugLog
|
||||||
o.errorLogger = errorLogger
|
o.errorLogger = errorLogger
|
||||||
o.finished = finished
|
|
||||||
|
|
||||||
// Set up a trace span for this op.
|
// Set up a trace span for this op.
|
||||||
var reportForTrace reqtrace.ReportFunc
|
var reportForTrace reqtrace.ReportFunc
|
||||||
o.ctx, reportForTrace = reqtrace.StartSpan(o.ctx, o.op.ShortDesc())
|
o.ctx, reportForTrace = reqtrace.StartSpan(o.ctx, o.op.ShortDesc())
|
||||||
|
|
||||||
// When the op is finished, report to both reqtrace and the connection.
|
// When the op is finished, report to both reqtrace and the connection.
|
||||||
prevFinish := o.finished
|
prevSendReply := o.sendReply
|
||||||
o.finished = func(err error) {
|
o.sendReply = func(op Op, fuseID uint64, msg []byte, opErr error) (err error) {
|
||||||
reportForTrace(err)
|
reportForTrace(opErr)
|
||||||
prevFinish(err)
|
err = prevSendReply(op, fuseID, msg, opErr)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,30 +139,36 @@ func (o *commonOp) Logf(format string, v ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *commonOp) Respond(err error) {
|
func (o *commonOp) Respond(err error) {
|
||||||
// Report that the user is responding.
|
// If successful, we ask the op for an appopriate response to the kernel, and
|
||||||
o.finished(err)
|
// it is responsible for leaving room for the fusekernel.OutHeader struct.
|
||||||
|
// Otherwise, create our own.
|
||||||
// If successful, we should respond to bazilfuse with the appropriate struct.
|
var b buffer.OutMessage
|
||||||
if err == nil {
|
if err == nil {
|
||||||
o.op.respond()
|
b = o.op.kernelResponse()
|
||||||
return
|
} else {
|
||||||
|
b = buffer.NewOutMessage(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the error.
|
// Fill in the header if a reply is needed.
|
||||||
if o.debugLog != nil {
|
msg := b.Bytes()
|
||||||
o.Logf(
|
if msg != nil {
|
||||||
"-> (%s) error: %v",
|
h := b.OutHeader()
|
||||||
o.op.ShortDesc(),
|
h.Unique = o.fuseID
|
||||||
err)
|
h.Len = uint32(len(msg))
|
||||||
|
if err != nil {
|
||||||
|
// If the user gave us a syscall.Errno, use that value in the reply.
|
||||||
|
// Otherwise use the generic EIO.
|
||||||
|
if errno, ok := err.(syscall.Errno); ok {
|
||||||
|
h.Error = -int32(errno)
|
||||||
|
} else {
|
||||||
|
h.Error = -int32(syscall.EIO)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.errorLogger != nil {
|
// Reply.
|
||||||
o.errorLogger.Printf(
|
replyErr := o.sendReply(o.op, o.fuseID, msg, err)
|
||||||
"(%s) error: %v",
|
if replyErr != nil && o.errorLogger != nil {
|
||||||
o.op.ShortDesc(),
|
o.errorLogger.Printf("Error from sendReply: %v", replyErr)
|
||||||
err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a response to the kernel.
|
|
||||||
o.bazilReq.RespondError(err)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,252 +15,430 @@
|
||||||
package fuseops
|
package fuseops
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/jacobsa/fuse/internal/buffer"
|
||||||
|
"github.com/jacobsa/fuse/internal/fusekernel"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This function is an implementation detail of the fuse package, and must not
|
// This function is an implementation detail of the fuse package, and must not
|
||||||
// be called by anyone else.
|
// be called by anyone else.
|
||||||
//
|
//
|
||||||
// Convert the supplied bazilfuse request struct to an Op. finished will be
|
// Convert the supplied fuse kernel message to an Op. sendReply will be used to
|
||||||
// called with the error supplied to o.Respond when the user invokes that
|
// send the reply back to the kernel once the user calls o.Respond. If the op
|
||||||
// method, before a response is sent to the kernel.
|
// is unknown, a special unexported type will be used.
|
||||||
//
|
//
|
||||||
// It is guaranteed that o != nil. If the op is unknown, a special unexported
|
// The debug logging function and error logger may be nil. The caller is
|
||||||
// type will be used.
|
// responsible for arranging for the message to be destroyed.
|
||||||
//
|
|
||||||
// The debug logging function and error logger may be nil.
|
|
||||||
func Convert(
|
func Convert(
|
||||||
opCtx context.Context,
|
opCtx context.Context,
|
||||||
r bazilfuse.Request,
|
m *buffer.InMessage,
|
||||||
|
protocol fusekernel.Protocol,
|
||||||
debugLogForOp func(int, string, ...interface{}),
|
debugLogForOp func(int, string, ...interface{}),
|
||||||
errorLogger *log.Logger,
|
errorLogger *log.Logger,
|
||||||
finished func(error)) (o Op) {
|
sendReply replyFunc) (o Op, err error) {
|
||||||
var co *commonOp
|
var co *commonOp
|
||||||
|
|
||||||
var io internalOp
|
var io internalOp
|
||||||
switch typed := r.(type) {
|
switch m.Header().Opcode {
|
||||||
case *bazilfuse.LookupRequest:
|
case fusekernel.OpLookup:
|
||||||
|
buf := m.ConsumeBytes(m.Len())
|
||||||
|
n := len(buf)
|
||||||
|
if n == 0 || buf[n-1] != '\x00' {
|
||||||
|
err = errors.New("Corrupt OpLookup")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
to := &LookUpInodeOp{
|
to := &LookUpInodeOp{
|
||||||
bfReq: typed,
|
protocol: protocol,
|
||||||
Parent: InodeID(typed.Header.Node),
|
Parent: InodeID(m.Header().Nodeid),
|
||||||
Name: typed.Name,
|
Name: string(buf[:n-1]),
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.GetattrRequest:
|
case fusekernel.OpGetattr:
|
||||||
to := &GetInodeAttributesOp{
|
to := &GetInodeAttributesOp{
|
||||||
bfReq: typed,
|
protocol: protocol,
|
||||||
Inode: InodeID(typed.Header.Node),
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.SetattrRequest:
|
case fusekernel.OpSetattr:
|
||||||
|
type input fusekernel.SetattrIn
|
||||||
|
in := (*input)(m.Consume(unsafe.Sizeof(input{})))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpSetattr")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
to := &SetInodeAttributesOp{
|
to := &SetInodeAttributesOp{
|
||||||
bfReq: typed,
|
protocol: protocol,
|
||||||
Inode: InodeID(typed.Header.Node),
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
}
|
}
|
||||||
|
|
||||||
if typed.Valid&bazilfuse.SetattrSize != 0 {
|
valid := fusekernel.SetattrValid(in.Valid)
|
||||||
to.Size = &typed.Size
|
if valid&fusekernel.SetattrSize != 0 {
|
||||||
|
to.Size = &in.Size
|
||||||
}
|
}
|
||||||
|
|
||||||
if typed.Valid&bazilfuse.SetattrMode != 0 {
|
if valid&fusekernel.SetattrMode != 0 {
|
||||||
to.Mode = &typed.Mode
|
mode := convertFileMode(in.Mode)
|
||||||
|
to.Mode = &mode
|
||||||
}
|
}
|
||||||
|
|
||||||
if typed.Valid&bazilfuse.SetattrAtime != 0 {
|
if valid&fusekernel.SetattrAtime != 0 {
|
||||||
to.Atime = &typed.Atime
|
t := time.Unix(int64(in.Atime), int64(in.AtimeNsec))
|
||||||
|
to.Atime = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
if typed.Valid&bazilfuse.SetattrMtime != 0 {
|
if valid&fusekernel.SetattrMtime != 0 {
|
||||||
to.Mtime = &typed.Mtime
|
t := time.Unix(int64(in.Mtime), int64(in.MtimeNsec))
|
||||||
|
to.Mtime = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.ForgetRequest:
|
case fusekernel.OpForget:
|
||||||
|
type input fusekernel.ForgetIn
|
||||||
|
in := (*input)(m.Consume(unsafe.Sizeof(input{})))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpForget")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
to := &ForgetInodeOp{
|
to := &ForgetInodeOp{
|
||||||
bfReq: typed,
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
Inode: InodeID(typed.Header.Node),
|
N: in.Nlookup,
|
||||||
N: typed.N,
|
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.MkdirRequest:
|
case fusekernel.OpMkdir:
|
||||||
|
in := (*fusekernel.MkdirIn)(m.Consume(fusekernel.MkdirInSize(protocol)))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpMkdir")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := m.ConsumeBytes(m.Len())
|
||||||
|
i := bytes.IndexByte(name, '\x00')
|
||||||
|
if i < 0 {
|
||||||
|
err = errors.New("Corrupt OpMkdir")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name = name[:i]
|
||||||
|
|
||||||
to := &MkDirOp{
|
to := &MkDirOp{
|
||||||
bfReq: typed,
|
protocol: protocol,
|
||||||
Parent: InodeID(typed.Header.Node),
|
Parent: InodeID(m.Header().Nodeid),
|
||||||
Name: typed.Name,
|
Name: string(name),
|
||||||
Mode: typed.Mode,
|
|
||||||
|
// On Linux, vfs_mkdir calls through to the inode with at most
|
||||||
|
// permissions and sticky bits set (cf. https://goo.gl/WxgQXk), and fuse
|
||||||
|
// passes that on directly (cf. https://goo.gl/f31aMo). In other words,
|
||||||
|
// the fact that this is a directory is implicit in the fact that the
|
||||||
|
// opcode is mkdir. But we want the correct mode to go through, so ensure
|
||||||
|
// that os.ModeDir is set.
|
||||||
|
Mode: convertFileMode(in.Mode) | os.ModeDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.CreateRequest:
|
case fusekernel.OpCreate:
|
||||||
|
in := (*fusekernel.CreateIn)(m.Consume(fusekernel.CreateInSize(protocol)))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpCreate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := m.ConsumeBytes(m.Len())
|
||||||
|
i := bytes.IndexByte(name, '\x00')
|
||||||
|
if i < 0 {
|
||||||
|
err = errors.New("Corrupt OpCreate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name = name[:i]
|
||||||
|
|
||||||
to := &CreateFileOp{
|
to := &CreateFileOp{
|
||||||
bfReq: typed,
|
protocol: protocol,
|
||||||
Parent: InodeID(typed.Header.Node),
|
Parent: InodeID(m.Header().Nodeid),
|
||||||
Name: typed.Name,
|
Name: string(name),
|
||||||
Mode: typed.Mode,
|
Mode: convertFileMode(in.Mode),
|
||||||
Flags: typed.Flags,
|
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.SymlinkRequest:
|
case fusekernel.OpSymlink:
|
||||||
|
// The message is "newName\0target\0".
|
||||||
|
names := m.ConsumeBytes(m.Len())
|
||||||
|
if len(names) == 0 || names[len(names)-1] != 0 {
|
||||||
|
err = errors.New("Corrupt OpSymlink")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := bytes.IndexByte(names, '\x00')
|
||||||
|
if i < 0 {
|
||||||
|
err = errors.New("Corrupt OpSymlink")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newName, target := names[0:i], names[i+1:len(names)-1]
|
||||||
|
|
||||||
to := &CreateSymlinkOp{
|
to := &CreateSymlinkOp{
|
||||||
bfReq: typed,
|
protocol: protocol,
|
||||||
Parent: InodeID(typed.Header.Node),
|
Parent: InodeID(m.Header().Nodeid),
|
||||||
Name: typed.NewName,
|
Name: string(newName),
|
||||||
Target: typed.Target,
|
Target: string(target),
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.RenameRequest:
|
case fusekernel.OpRename:
|
||||||
|
type input fusekernel.RenameIn
|
||||||
|
in := (*input)(m.Consume(unsafe.Sizeof(input{})))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpRename")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
names := m.ConsumeBytes(m.Len())
|
||||||
|
// names should be "old\x00new\x00"
|
||||||
|
if len(names) < 4 {
|
||||||
|
err = errors.New("Corrupt OpRename")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if names[len(names)-1] != '\x00' {
|
||||||
|
err = errors.New("Corrupt OpRename")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := bytes.IndexByte(names, '\x00')
|
||||||
|
if i < 0 {
|
||||||
|
err = errors.New("Corrupt OpRename")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oldName, newName := names[:i], names[i+1:len(names)-1]
|
||||||
|
|
||||||
to := &RenameOp{
|
to := &RenameOp{
|
||||||
bfReq: typed,
|
OldParent: InodeID(m.Header().Nodeid),
|
||||||
OldParent: InodeID(typed.Header.Node),
|
OldName: string(oldName),
|
||||||
OldName: typed.OldName,
|
NewParent: InodeID(in.Newdir),
|
||||||
NewParent: InodeID(typed.NewDir),
|
NewName: string(newName),
|
||||||
NewName: typed.NewName,
|
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.RemoveRequest:
|
case fusekernel.OpUnlink:
|
||||||
if typed.Dir {
|
buf := m.ConsumeBytes(m.Len())
|
||||||
to := &RmDirOp{
|
n := len(buf)
|
||||||
bfReq: typed,
|
if n == 0 || buf[n-1] != '\x00' {
|
||||||
Parent: InodeID(typed.Header.Node),
|
err = errors.New("Corrupt OpUnlink")
|
||||||
Name: typed.Name,
|
return
|
||||||
}
|
|
||||||
io = to
|
|
||||||
co = &to.commonOp
|
|
||||||
} else {
|
|
||||||
to := &UnlinkOp{
|
|
||||||
bfReq: typed,
|
|
||||||
Parent: InodeID(typed.Header.Node),
|
|
||||||
Name: typed.Name,
|
|
||||||
}
|
|
||||||
io = to
|
|
||||||
co = &to.commonOp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case *bazilfuse.OpenRequest:
|
to := &UnlinkOp{
|
||||||
if typed.Dir {
|
Parent: InodeID(m.Header().Nodeid),
|
||||||
to := &OpenDirOp{
|
Name: string(buf[:n-1]),
|
||||||
bfReq: typed,
|
}
|
||||||
Inode: InodeID(typed.Header.Node),
|
io = to
|
||||||
Flags: typed.Flags,
|
co = &to.commonOp
|
||||||
}
|
|
||||||
io = to
|
case fusekernel.OpRmdir:
|
||||||
co = &to.commonOp
|
buf := m.ConsumeBytes(m.Len())
|
||||||
} else {
|
n := len(buf)
|
||||||
to := &OpenFileOp{
|
if n == 0 || buf[n-1] != '\x00' {
|
||||||
bfReq: typed,
|
err = errors.New("Corrupt OpRmdir")
|
||||||
Inode: InodeID(typed.Header.Node),
|
return
|
||||||
Flags: typed.Flags,
|
|
||||||
}
|
|
||||||
io = to
|
|
||||||
co = &to.commonOp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case *bazilfuse.ReadRequest:
|
to := &RmDirOp{
|
||||||
if typed.Dir {
|
Parent: InodeID(m.Header().Nodeid),
|
||||||
to := &ReadDirOp{
|
Name: string(buf[:n-1]),
|
||||||
bfReq: typed,
|
}
|
||||||
Inode: InodeID(typed.Header.Node),
|
io = to
|
||||||
Handle: HandleID(typed.Handle),
|
co = &to.commonOp
|
||||||
Offset: DirOffset(typed.Offset),
|
|
||||||
Size: typed.Size,
|
case fusekernel.OpOpen:
|
||||||
}
|
to := &OpenFileOp{
|
||||||
io = to
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
co = &to.commonOp
|
}
|
||||||
} else {
|
io = to
|
||||||
to := &ReadFileOp{
|
co = &to.commonOp
|
||||||
bfReq: typed,
|
|
||||||
Inode: InodeID(typed.Header.Node),
|
case fusekernel.OpOpendir:
|
||||||
Handle: HandleID(typed.Handle),
|
to := &OpenDirOp{
|
||||||
Offset: typed.Offset,
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
Size: typed.Size,
|
}
|
||||||
}
|
io = to
|
||||||
io = to
|
co = &to.commonOp
|
||||||
co = &to.commonOp
|
|
||||||
|
case fusekernel.OpRead:
|
||||||
|
in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol)))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpRead")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case *bazilfuse.ReleaseRequest:
|
to := &ReadFileOp{
|
||||||
if typed.Dir {
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
to := &ReleaseDirHandleOp{
|
Handle: HandleID(in.Fh),
|
||||||
bfReq: typed,
|
Offset: int64(in.Offset),
|
||||||
Handle: HandleID(typed.Handle),
|
Size: int(in.Size),
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
} else {
|
|
||||||
to := &ReleaseFileHandleOp{
|
case fusekernel.OpReaddir:
|
||||||
bfReq: typed,
|
in := (*fusekernel.ReadIn)(m.Consume(fusekernel.ReadInSize(protocol)))
|
||||||
Handle: HandleID(typed.Handle),
|
if in == nil {
|
||||||
}
|
err = errors.New("Corrupt OpReaddir")
|
||||||
io = to
|
return
|
||||||
co = &to.commonOp
|
}
|
||||||
|
|
||||||
|
to := &ReadDirOp{
|
||||||
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
|
Handle: HandleID(in.Fh),
|
||||||
|
Offset: DirOffset(in.Offset),
|
||||||
|
Size: int(in.Size),
|
||||||
|
}
|
||||||
|
io = to
|
||||||
|
co = &to.commonOp
|
||||||
|
|
||||||
|
case fusekernel.OpRelease:
|
||||||
|
type input fusekernel.ReleaseIn
|
||||||
|
in := (*input)(m.Consume(unsafe.Sizeof(input{})))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpRelease")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
to := &ReleaseFileHandleOp{
|
||||||
|
Handle: HandleID(in.Fh),
|
||||||
|
}
|
||||||
|
io = to
|
||||||
|
co = &to.commonOp
|
||||||
|
|
||||||
|
case fusekernel.OpReleasedir:
|
||||||
|
type input fusekernel.ReleaseIn
|
||||||
|
in := (*input)(m.Consume(unsafe.Sizeof(input{})))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpReleasedir")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
to := &ReleaseDirHandleOp{
|
||||||
|
Handle: HandleID(in.Fh),
|
||||||
|
}
|
||||||
|
io = to
|
||||||
|
co = &to.commonOp
|
||||||
|
|
||||||
|
case fusekernel.OpWrite:
|
||||||
|
in := (*fusekernel.WriteIn)(m.Consume(fusekernel.WriteInSize(protocol)))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpWrite")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := m.ConsumeBytes(m.Len())
|
||||||
|
if len(buf) < int(in.Size) {
|
||||||
|
err = errors.New("Corrupt OpWrite")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case *bazilfuse.WriteRequest:
|
|
||||||
to := &WriteFileOp{
|
to := &WriteFileOp{
|
||||||
bfReq: typed,
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
Inode: InodeID(typed.Header.Node),
|
Handle: HandleID(in.Fh),
|
||||||
Handle: HandleID(typed.Handle),
|
Data: buf,
|
||||||
Data: typed.Data,
|
Offset: int64(in.Offset),
|
||||||
Offset: typed.Offset,
|
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.FsyncRequest:
|
case fusekernel.OpFsync:
|
||||||
// We don't currently support this for directories.
|
type input fusekernel.FsyncIn
|
||||||
if typed.Dir {
|
in := (*input)(m.Consume(unsafe.Sizeof(input{})))
|
||||||
to := &unknownOp{}
|
if in == nil {
|
||||||
io = to
|
err = errors.New("Corrupt OpFsync")
|
||||||
co = &to.commonOp
|
return
|
||||||
} else {
|
}
|
||||||
to := &SyncFileOp{
|
|
||||||
bfReq: typed,
|
to := &SyncFileOp{
|
||||||
Inode: InodeID(typed.Header.Node),
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
Handle: HandleID(typed.Handle),
|
Handle: HandleID(in.Fh),
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
|
case fusekernel.OpFlush:
|
||||||
|
type input fusekernel.FlushIn
|
||||||
|
in := (*input)(m.Consume(unsafe.Sizeof(input{})))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpFlush")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
case *bazilfuse.FlushRequest:
|
|
||||||
to := &FlushFileOp{
|
to := &FlushFileOp{
|
||||||
bfReq: typed,
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
Inode: InodeID(typed.Header.Node),
|
Handle: HandleID(in.Fh),
|
||||||
Handle: HandleID(typed.Handle),
|
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
case *bazilfuse.ReadlinkRequest:
|
case fusekernel.OpReadlink:
|
||||||
to := &ReadSymlinkOp{
|
to := &ReadSymlinkOp{
|
||||||
bfReq: typed,
|
Inode: InodeID(m.Header().Nodeid),
|
||||||
Inode: InodeID(typed.Header.Node),
|
}
|
||||||
|
io = to
|
||||||
|
co = &to.commonOp
|
||||||
|
|
||||||
|
case fusekernel.OpStatfs:
|
||||||
|
to := &InternalStatFSOp{}
|
||||||
|
io = to
|
||||||
|
co = &to.commonOp
|
||||||
|
|
||||||
|
case fusekernel.OpInterrupt:
|
||||||
|
type input fusekernel.InterruptIn
|
||||||
|
in := (*input)(m.Consume(unsafe.Sizeof(input{})))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpInterrupt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
to := &InternalInterruptOp{
|
||||||
|
FuseID: in.Unique,
|
||||||
|
}
|
||||||
|
io = to
|
||||||
|
co = &to.commonOp
|
||||||
|
|
||||||
|
case fusekernel.OpInit:
|
||||||
|
type input fusekernel.InitIn
|
||||||
|
in := (*input)(m.Consume(unsafe.Sizeof(input{})))
|
||||||
|
if in == nil {
|
||||||
|
err = errors.New("Corrupt OpInit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
to := &InternalInitOp{
|
||||||
|
Kernel: fusekernel.Protocol{in.Major, in.Minor},
|
||||||
|
MaxReadahead: in.MaxReadahead,
|
||||||
|
Flags: fusekernel.InitFlags(in.Flags),
|
||||||
}
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
|
|
||||||
default:
|
default:
|
||||||
to := &unknownOp{}
|
to := &unknownOp{
|
||||||
|
opCode: m.Header().Opcode,
|
||||||
|
inode: InodeID(m.Header().Nodeid),
|
||||||
|
}
|
||||||
io = to
|
io = to
|
||||||
co = &to.commonOp
|
co = &to.commonOp
|
||||||
}
|
}
|
||||||
|
@ -268,48 +446,69 @@ func Convert(
|
||||||
co.init(
|
co.init(
|
||||||
opCtx,
|
opCtx,
|
||||||
io,
|
io,
|
||||||
r,
|
m.Header().Unique,
|
||||||
|
sendReply,
|
||||||
debugLogForOp,
|
debugLogForOp,
|
||||||
errorLogger,
|
errorLogger)
|
||||||
finished)
|
|
||||||
|
|
||||||
o = io
|
o = io
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertTime(t time.Time) (secs uint64, nsec uint32) {
|
||||||
|
totalNano := t.UnixNano()
|
||||||
|
secs = uint64(totalNano / 1e9)
|
||||||
|
nsec = uint32(totalNano % 1e9)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func convertAttributes(
|
func convertAttributes(
|
||||||
inode InodeID,
|
inodeID InodeID,
|
||||||
attr InodeAttributes,
|
in *InodeAttributes,
|
||||||
expiration time.Time) bazilfuse.Attr {
|
out *fusekernel.Attr) {
|
||||||
return bazilfuse.Attr{
|
out.Ino = uint64(inodeID)
|
||||||
Inode: uint64(inode),
|
out.Size = in.Size
|
||||||
Size: attr.Size,
|
out.Atime, out.AtimeNsec = convertTime(in.Atime)
|
||||||
Mode: attr.Mode,
|
out.Mtime, out.MtimeNsec = convertTime(in.Mtime)
|
||||||
Nlink: uint32(attr.Nlink),
|
out.Ctime, out.CtimeNsec = convertTime(in.Ctime)
|
||||||
Atime: attr.Atime,
|
out.SetCrtime(convertTime(in.Crtime))
|
||||||
Mtime: attr.Mtime,
|
out.Nlink = uint32(in.Nlink) // TODO(jacobsa): Make the public field uint32?
|
||||||
Ctime: attr.Ctime,
|
out.Uid = in.Uid
|
||||||
Crtime: attr.Crtime,
|
out.Gid = in.Gid
|
||||||
Uid: attr.Uid,
|
|
||||||
Gid: attr.Gid,
|
// Set the mode.
|
||||||
Valid: convertExpirationTime(expiration),
|
out.Mode = uint32(in.Mode) & 0777
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
out.Mode |= syscall.S_IFREG
|
||||||
|
case in.Mode&os.ModeDir != 0:
|
||||||
|
out.Mode |= syscall.S_IFDIR
|
||||||
|
case in.Mode&os.ModeDevice != 0:
|
||||||
|
if in.Mode&os.ModeCharDevice != 0 {
|
||||||
|
out.Mode |= syscall.S_IFCHR
|
||||||
|
} else {
|
||||||
|
out.Mode |= syscall.S_IFBLK
|
||||||
|
}
|
||||||
|
case in.Mode&os.ModeNamedPipe != 0:
|
||||||
|
out.Mode |= syscall.S_IFIFO
|
||||||
|
case in.Mode&os.ModeSymlink != 0:
|
||||||
|
out.Mode |= syscall.S_IFLNK
|
||||||
|
case in.Mode&os.ModeSocket != 0:
|
||||||
|
out.Mode |= syscall.S_IFSOCK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert an absolute cache expiration time to a relative time from now for
|
// Convert an absolute cache expiration time to a relative time from now for
|
||||||
// consumption by fuse.
|
// consumption by the fuse kernel module.
|
||||||
func convertExpirationTime(t time.Time) (d time.Duration) {
|
func convertExpirationTime(t time.Time) (secs uint64, nsecs uint32) {
|
||||||
// Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit
|
// Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit
|
||||||
// counts of nanoseconds (cf. http://goo.gl/EJupJV). The bazil.org/fuse
|
// counts of nanoseconds (cf. http://goo.gl/EJupJV). So negative durations
|
||||||
// package converts time.Duration values to this form in a straightforward
|
// are right out. There is no need to cap the positive magnitude, because
|
||||||
// way (cf. http://goo.gl/FJhV8j).
|
// 2^64 seconds is well longer than the 2^63 ns range of time.Duration.
|
||||||
//
|
d := t.Sub(time.Now())
|
||||||
// So negative durations are right out. There is no need to cap the positive
|
if d > 0 {
|
||||||
// magnitude, because 2^64 seconds is well longer than the 2^63 ns range of
|
secs = uint64(d / time.Second)
|
||||||
// time.Duration.
|
nsecs = uint32((d % time.Second) / time.Nanosecond)
|
||||||
d = t.Sub(time.Now())
|
|
||||||
if d < 0 {
|
|
||||||
d = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -317,9 +516,41 @@ func convertExpirationTime(t time.Time) (d time.Duration) {
|
||||||
|
|
||||||
func convertChildInodeEntry(
|
func convertChildInodeEntry(
|
||||||
in *ChildInodeEntry,
|
in *ChildInodeEntry,
|
||||||
out *bazilfuse.LookupResponse) {
|
out *fusekernel.EntryOut) {
|
||||||
out.Node = bazilfuse.NodeID(in.Child)
|
out.Nodeid = uint64(in.Child)
|
||||||
out.Generation = uint64(in.Generation)
|
out.Generation = uint64(in.Generation)
|
||||||
out.Attr = convertAttributes(in.Child, in.Attributes, in.AttributesExpiration)
|
out.EntryValid, out.EntryValidNsec = convertExpirationTime(in.EntryExpiration)
|
||||||
out.EntryValid = convertExpirationTime(in.EntryExpiration)
|
out.AttrValid, out.AttrValidNsec = convertExpirationTime(in.AttributesExpiration)
|
||||||
|
|
||||||
|
convertAttributes(in.Child, &in.Attributes, &out.Attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertFileMode(unixMode uint32) os.FileMode {
|
||||||
|
mode := os.FileMode(unixMode & 0777)
|
||||||
|
switch unixMode & syscall.S_IFMT {
|
||||||
|
case syscall.S_IFREG:
|
||||||
|
// nothing
|
||||||
|
case syscall.S_IFDIR:
|
||||||
|
mode |= os.ModeDir
|
||||||
|
case syscall.S_IFCHR:
|
||||||
|
mode |= os.ModeCharDevice | os.ModeDevice
|
||||||
|
case syscall.S_IFBLK:
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
case syscall.S_IFIFO:
|
||||||
|
mode |= os.ModeNamedPipe
|
||||||
|
case syscall.S_IFLNK:
|
||||||
|
mode |= os.ModeSymlink
|
||||||
|
case syscall.S_IFSOCK:
|
||||||
|
mode |= os.ModeSocket
|
||||||
|
default:
|
||||||
|
// no idea
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
}
|
||||||
|
if unixMode&syscall.S_ISUID != 0 {
|
||||||
|
mode |= os.ModeSetuid
|
||||||
|
}
|
||||||
|
if unixMode&syscall.S_ISGID != 0 {
|
||||||
|
mode |= os.ModeSetgid
|
||||||
|
}
|
||||||
|
return mode
|
||||||
}
|
}
|
||||||
|
|
263
fuseops/ops.go
263
fuseops/ops.go
|
@ -18,8 +18,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
"github.com/jacobsa/fuse/internal/buffer"
|
||||||
|
"github.com/jacobsa/fuse/internal/fusekernel"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,6 +32,9 @@ type Op interface {
|
||||||
// A short description of the op, to be used in logging.
|
// A short description of the op, to be used in logging.
|
||||||
ShortDesc() string
|
ShortDesc() string
|
||||||
|
|
||||||
|
// A long description of the op, to be used in debug logging.
|
||||||
|
DebugString() string
|
||||||
|
|
||||||
// A context that can be used for long-running operations.
|
// A context that can be used for long-running operations.
|
||||||
Context() context.Context
|
Context() context.Context
|
||||||
|
|
||||||
|
@ -55,7 +60,7 @@ type Op interface {
|
||||||
// when resolving user paths to dentry structs, which are then cached.
|
// when resolving user paths to dentry structs, which are then cached.
|
||||||
type LookUpInodeOp struct {
|
type LookUpInodeOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.LookupRequest
|
protocol fusekernel.Protocol
|
||||||
|
|
||||||
// The ID of the directory inode to which the child belongs.
|
// The ID of the directory inode to which the child belongs.
|
||||||
Parent InodeID
|
Parent InodeID
|
||||||
|
@ -83,11 +88,12 @@ func (o *LookUpInodeOp) ShortDesc() (desc string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LookUpInodeOp) respond() {
|
func (o *LookUpInodeOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.LookupResponse{}
|
size := fusekernel.EntryOutSize(o.protocol)
|
||||||
convertChildInodeEntry(&o.Entry, &resp)
|
b = buffer.NewOutMessage(size)
|
||||||
|
out := (*fusekernel.EntryOut)(b.Grow(size))
|
||||||
|
convertChildInodeEntry(&o.Entry, out)
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +103,7 @@ func (o *LookUpInodeOp) respond() {
|
||||||
// field of ChildInodeEntry, etc.
|
// field of ChildInodeEntry, etc.
|
||||||
type GetInodeAttributesOp struct {
|
type GetInodeAttributesOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.GetattrRequest
|
protocol fusekernel.Protocol
|
||||||
|
|
||||||
// The inode of interest.
|
// The inode of interest.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
|
@ -109,12 +115,21 @@ type GetInodeAttributesOp struct {
|
||||||
AttributesExpiration time.Time
|
AttributesExpiration time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *GetInodeAttributesOp) respond() {
|
func (o *GetInodeAttributesOp) DebugString() string {
|
||||||
resp := bazilfuse.GetattrResponse{
|
return fmt.Sprintf(
|
||||||
Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration),
|
"Inode: %d, Exp: %v, Attr: %s",
|
||||||
}
|
o.Inode,
|
||||||
|
o.AttributesExpiration,
|
||||||
|
o.Attributes.DebugString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *GetInodeAttributesOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
|
size := fusekernel.AttrOutSize(o.protocol)
|
||||||
|
b = buffer.NewOutMessage(size)
|
||||||
|
out := (*fusekernel.AttrOut)(b.Grow(size))
|
||||||
|
out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration)
|
||||||
|
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +139,7 @@ func (o *GetInodeAttributesOp) respond() {
|
||||||
// cases like ftrunctate(2).
|
// cases like ftrunctate(2).
|
||||||
type SetInodeAttributesOp struct {
|
type SetInodeAttributesOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.SetattrRequest
|
protocol fusekernel.Protocol
|
||||||
|
|
||||||
// The inode of interest.
|
// The inode of interest.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
|
@ -142,12 +157,13 @@ type SetInodeAttributesOp struct {
|
||||||
AttributesExpiration time.Time
|
AttributesExpiration time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *SetInodeAttributesOp) respond() {
|
func (o *SetInodeAttributesOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.SetattrResponse{
|
size := fusekernel.AttrOutSize(o.protocol)
|
||||||
Attr: convertAttributes(o.Inode, o.Attributes, o.AttributesExpiration),
|
b = buffer.NewOutMessage(size)
|
||||||
}
|
out := (*fusekernel.AttrOut)(b.Grow(size))
|
||||||
|
out.AttrValid, out.AttrValidNsec = convertExpirationTime(o.AttributesExpiration)
|
||||||
|
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +208,6 @@ func (o *SetInodeAttributesOp) respond() {
|
||||||
// implicitly decrementing all lookup counts to zero.
|
// implicitly decrementing all lookup counts to zero.
|
||||||
type ForgetInodeOp struct {
|
type ForgetInodeOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.ForgetRequest
|
|
||||||
|
|
||||||
// The inode whose reference count should be decremented.
|
// The inode whose reference count should be decremented.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
|
@ -201,8 +216,8 @@ type ForgetInodeOp struct {
|
||||||
N uint64
|
N uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ForgetInodeOp) respond() {
|
func (o *ForgetInodeOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
o.bfReq.Respond()
|
// No response.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +238,7 @@ func (o *ForgetInodeOp) respond() {
|
||||||
// Therefore the file system should return EEXIST if the name already exists.
|
// Therefore the file system should return EEXIST if the name already exists.
|
||||||
type MkDirOp struct {
|
type MkDirOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.MkdirRequest
|
protocol fusekernel.Protocol
|
||||||
|
|
||||||
// The ID of parent directory inode within which to create the child.
|
// The ID of parent directory inode within which to create the child.
|
||||||
Parent InodeID
|
Parent InodeID
|
||||||
|
@ -244,11 +259,12 @@ func (o *MkDirOp) ShortDesc() (desc string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *MkDirOp) respond() {
|
func (o *MkDirOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.MkdirResponse{}
|
size := fusekernel.EntryOutSize(o.protocol)
|
||||||
convertChildInodeEntry(&o.Entry, &resp.LookupResponse)
|
b = buffer.NewOutMessage(size)
|
||||||
|
out := (*fusekernel.EntryOut)(b.Grow(size))
|
||||||
|
convertChildInodeEntry(&o.Entry, out)
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +280,7 @@ func (o *MkDirOp) respond() {
|
||||||
// Therefore the file system should return EEXIST if the name already exists.
|
// Therefore the file system should return EEXIST if the name already exists.
|
||||||
type CreateFileOp struct {
|
type CreateFileOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.CreateRequest
|
protocol fusekernel.Protocol
|
||||||
|
|
||||||
// The ID of parent directory inode within which to create the child file.
|
// The ID of parent directory inode within which to create the child file.
|
||||||
Parent InodeID
|
Parent InodeID
|
||||||
|
@ -273,9 +289,6 @@ type CreateFileOp struct {
|
||||||
Name string
|
Name string
|
||||||
Mode os.FileMode
|
Mode os.FileMode
|
||||||
|
|
||||||
// Flags for the open operation.
|
|
||||||
Flags bazilfuse.OpenFlags
|
|
||||||
|
|
||||||
// Set by the file system: information about the inode that was created.
|
// Set by the file system: information about the inode that was created.
|
||||||
//
|
//
|
||||||
// The lookup count for the inode is implicitly incremented. See notes on
|
// The lookup count for the inode is implicitly incremented. See notes on
|
||||||
|
@ -298,16 +311,16 @@ func (o *CreateFileOp) ShortDesc() (desc string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CreateFileOp) respond() {
|
func (o *CreateFileOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.CreateResponse{
|
eSize := fusekernel.EntryOutSize(o.protocol)
|
||||||
OpenResponse: bazilfuse.OpenResponse{
|
b = buffer.NewOutMessage(eSize + unsafe.Sizeof(fusekernel.OpenOut{}))
|
||||||
Handle: bazilfuse.HandleID(o.Handle),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
convertChildInodeEntry(&o.Entry, &resp.LookupResponse)
|
e := (*fusekernel.EntryOut)(b.Grow(eSize))
|
||||||
|
convertChildInodeEntry(&o.Entry, e)
|
||||||
|
|
||||||
|
oo := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
|
||||||
|
oo.Fh = uint64(o.Handle)
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +328,7 @@ func (o *CreateFileOp) respond() {
|
||||||
// return EEXIST (cf. the notes on CreateFileOp and MkDirOp).
|
// return EEXIST (cf. the notes on CreateFileOp and MkDirOp).
|
||||||
type CreateSymlinkOp struct {
|
type CreateSymlinkOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.SymlinkRequest
|
protocol fusekernel.Protocol
|
||||||
|
|
||||||
// The ID of parent directory inode within which to create the child symlink.
|
// The ID of parent directory inode within which to create the child symlink.
|
||||||
Parent InodeID
|
Parent InodeID
|
||||||
|
@ -344,11 +357,12 @@ func (o *CreateSymlinkOp) ShortDesc() (desc string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CreateSymlinkOp) respond() {
|
func (o *CreateSymlinkOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.SymlinkResponse{}
|
size := fusekernel.EntryOutSize(o.protocol)
|
||||||
convertChildInodeEntry(&o.Entry, &resp.LookupResponse)
|
b = buffer.NewOutMessage(size)
|
||||||
|
out := (*fusekernel.EntryOut)(b.Grow(size))
|
||||||
|
convertChildInodeEntry(&o.Entry, out)
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,7 +406,6 @@ func (o *CreateSymlinkOp) respond() {
|
||||||
//
|
//
|
||||||
type RenameOp struct {
|
type RenameOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.RenameRequest
|
|
||||||
|
|
||||||
// The old parent directory, and the name of the entry within it to be
|
// The old parent directory, and the name of the entry within it to be
|
||||||
// relocated.
|
// relocated.
|
||||||
|
@ -405,8 +418,8 @@ type RenameOp struct {
|
||||||
NewName string
|
NewName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *RenameOp) respond() {
|
func (o *RenameOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
o.bfReq.Respond()
|
b = buffer.NewOutMessage(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,7 +432,6 @@ func (o *RenameOp) respond() {
|
||||||
// Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf)
|
// Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf)
|
||||||
type RmDirOp struct {
|
type RmDirOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.RemoveRequest
|
|
||||||
|
|
||||||
// The ID of parent directory inode, and the name of the directory being
|
// The ID of parent directory inode, and the name of the directory being
|
||||||
// removed within it.
|
// removed within it.
|
||||||
|
@ -427,8 +439,8 @@ type RmDirOp struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *RmDirOp) respond() {
|
func (o *RmDirOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
o.bfReq.Respond()
|
b = buffer.NewOutMessage(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,7 +452,6 @@ func (o *RmDirOp) respond() {
|
||||||
// Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C)
|
// Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C)
|
||||||
type UnlinkOp struct {
|
type UnlinkOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.RemoveRequest
|
|
||||||
|
|
||||||
// The ID of parent directory inode, and the name of the entry being removed
|
// The ID of parent directory inode, and the name of the entry being removed
|
||||||
// within it.
|
// within it.
|
||||||
|
@ -448,8 +459,8 @@ type UnlinkOp struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *UnlinkOp) respond() {
|
func (o *UnlinkOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
o.bfReq.Respond()
|
b = buffer.NewOutMessage(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,14 +476,10 @@ func (o *UnlinkOp) respond() {
|
||||||
// https://github.com/osxfuse/osxfuse/issues/199).
|
// https://github.com/osxfuse/osxfuse/issues/199).
|
||||||
type OpenDirOp struct {
|
type OpenDirOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.OpenRequest
|
|
||||||
|
|
||||||
// The ID of the inode to be opened.
|
// The ID of the inode to be opened.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
|
|
||||||
// Mode and options flags.
|
|
||||||
Flags bazilfuse.OpenFlags
|
|
||||||
|
|
||||||
// Set by the file system: an opaque ID that will be echoed in follow-up
|
// Set by the file system: an opaque ID that will be echoed in follow-up
|
||||||
// calls for this directory using the same struct file in the kernel. In
|
// calls for this directory using the same struct file in the kernel. In
|
||||||
// practice this usually means follow-up calls using the file descriptor
|
// practice this usually means follow-up calls using the file descriptor
|
||||||
|
@ -484,19 +491,17 @@ type OpenDirOp struct {
|
||||||
Handle HandleID
|
Handle HandleID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenDirOp) respond() {
|
func (o *OpenDirOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.OpenResponse{
|
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
|
||||||
Handle: bazilfuse.HandleID(o.Handle),
|
out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
|
||||||
}
|
out.Fh = uint64(o.Handle)
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read entries from a directory previously opened with OpenDir.
|
// Read entries from a directory previously opened with OpenDir.
|
||||||
type ReadDirOp struct {
|
type ReadDirOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.ReadRequest
|
|
||||||
|
|
||||||
// The directory inode that we are reading, and the handle previously
|
// The directory inode that we are reading, and the handle previously
|
||||||
// returned by OpenDir when opening that inode.
|
// returned by OpenDir when opening that inode.
|
||||||
|
@ -584,12 +589,9 @@ type ReadDirOp struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ReadDirOp) respond() {
|
func (o *ReadDirOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.ReadResponse{
|
b = buffer.NewOutMessage(uintptr(len(o.Data)))
|
||||||
Data: o.Data,
|
b.Append(o.Data)
|
||||||
}
|
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,7 +605,6 @@ func (o *ReadDirOp) respond() {
|
||||||
// Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
|
// Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
|
||||||
type ReleaseDirHandleOp struct {
|
type ReleaseDirHandleOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.ReleaseRequest
|
|
||||||
|
|
||||||
// The handle ID to be released. The kernel guarantees that this ID will not
|
// The handle ID to be released. The kernel guarantees that this ID will not
|
||||||
// be used in further calls to the file system (unless it is reissued by the
|
// be used in further calls to the file system (unless it is reissued by the
|
||||||
|
@ -611,8 +612,8 @@ type ReleaseDirHandleOp struct {
|
||||||
Handle HandleID
|
Handle HandleID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ReleaseDirHandleOp) respond() {
|
func (o *ReleaseDirHandleOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
o.bfReq.Respond()
|
b = buffer.NewOutMessage(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,14 +629,10 @@ func (o *ReleaseDirHandleOp) respond() {
|
||||||
// (cf.https://github.com/osxfuse/osxfuse/issues/199).
|
// (cf.https://github.com/osxfuse/osxfuse/issues/199).
|
||||||
type OpenFileOp struct {
|
type OpenFileOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.OpenRequest
|
|
||||||
|
|
||||||
// The ID of the inode to be opened.
|
// The ID of the inode to be opened.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
|
|
||||||
// Mode and options flags.
|
|
||||||
Flags bazilfuse.OpenFlags
|
|
||||||
|
|
||||||
// An opaque ID that will be echoed in follow-up calls for this file using
|
// An opaque ID that will be echoed in follow-up calls for this file using
|
||||||
// the same struct file in the kernel. In practice this usually means
|
// the same struct file in the kernel. In practice this usually means
|
||||||
// follow-up calls using the file descriptor returned by open(2).
|
// follow-up calls using the file descriptor returned by open(2).
|
||||||
|
@ -646,12 +643,11 @@ type OpenFileOp struct {
|
||||||
Handle HandleID
|
Handle HandleID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenFileOp) respond() {
|
func (o *OpenFileOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.OpenResponse{
|
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.OpenOut{}))
|
||||||
Handle: bazilfuse.HandleID(o.Handle),
|
out := (*fusekernel.OpenOut)(b.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
|
||||||
}
|
out.Fh = uint64(o.Handle)
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,7 +658,6 @@ func (o *OpenFileOp) respond() {
|
||||||
// more.
|
// more.
|
||||||
type ReadFileOp struct {
|
type ReadFileOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.ReadRequest
|
|
||||||
|
|
||||||
// The file inode that we are reading, and the handle previously returned by
|
// The file inode that we are reading, and the handle previously returned by
|
||||||
// CreateFile or OpenFile when opening that inode.
|
// CreateFile or OpenFile when opening that inode.
|
||||||
|
@ -685,12 +680,9 @@ type ReadFileOp struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ReadFileOp) respond() {
|
func (o *ReadFileOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.ReadResponse{
|
b = buffer.NewOutMessage(uintptr(len(o.Data)))
|
||||||
Data: o.Data,
|
b.Append(o.Data)
|
||||||
}
|
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -727,7 +719,6 @@ func (o *ReadFileOp) respond() {
|
||||||
// concurrent requests".)
|
// concurrent requests".)
|
||||||
type WriteFileOp struct {
|
type WriteFileOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.WriteRequest
|
|
||||||
|
|
||||||
// The file inode that we are modifying, and the handle previously returned
|
// The file inode that we are modifying, and the handle previously returned
|
||||||
// by CreateFile or OpenFile when opening that inode.
|
// by CreateFile or OpenFile when opening that inode.
|
||||||
|
@ -765,12 +756,11 @@ type WriteFileOp struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *WriteFileOp) respond() {
|
func (o *WriteFileOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
resp := bazilfuse.WriteResponse{
|
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.WriteOut{}))
|
||||||
Size: len(o.Data),
|
out := (*fusekernel.WriteOut)(b.Grow(unsafe.Sizeof(fusekernel.WriteOut{})))
|
||||||
}
|
out.Size = uint32(len(o.Data))
|
||||||
|
|
||||||
o.bfReq.Respond(&resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,15 +782,14 @@ func (o *WriteFileOp) respond() {
|
||||||
// file (but which is not used in "real" file systems).
|
// file (but which is not used in "real" file systems).
|
||||||
type SyncFileOp struct {
|
type SyncFileOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.FsyncRequest
|
|
||||||
|
|
||||||
// The file and handle being sync'd.
|
// The file and handle being sync'd.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
Handle HandleID
|
Handle HandleID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *SyncFileOp) respond() {
|
func (o *SyncFileOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
o.bfReq.Respond()
|
b = buffer.NewOutMessage(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -853,15 +842,14 @@ func (o *SyncFileOp) respond() {
|
||||||
// return any errors that occur.
|
// return any errors that occur.
|
||||||
type FlushFileOp struct {
|
type FlushFileOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.FlushRequest
|
|
||||||
|
|
||||||
// The file and handle being flushed.
|
// The file and handle being flushed.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
Handle HandleID
|
Handle HandleID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *FlushFileOp) respond() {
|
func (o *FlushFileOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
o.bfReq.Respond()
|
b = buffer.NewOutMessage(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -875,7 +863,6 @@ func (o *FlushFileOp) respond() {
|
||||||
// Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
|
// Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
|
||||||
type ReleaseFileHandleOp struct {
|
type ReleaseFileHandleOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.ReleaseRequest
|
|
||||||
|
|
||||||
// The handle ID to be released. The kernel guarantees that this ID will not
|
// The handle ID to be released. The kernel guarantees that this ID will not
|
||||||
// be used in further calls to the file system (unless it is reissued by the
|
// be used in further calls to the file system (unless it is reissued by the
|
||||||
|
@ -883,8 +870,8 @@ type ReleaseFileHandleOp struct {
|
||||||
Handle HandleID
|
Handle HandleID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ReleaseFileHandleOp) respond() {
|
func (o *ReleaseFileHandleOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
o.bfReq.Respond()
|
b = buffer.NewOutMessage(0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -892,14 +879,16 @@ func (o *ReleaseFileHandleOp) respond() {
|
||||||
// non-nil error.
|
// non-nil error.
|
||||||
type unknownOp struct {
|
type unknownOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
|
opCode uint32
|
||||||
|
inode InodeID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *unknownOp) ShortDesc() (desc string) {
|
func (o *unknownOp) ShortDesc() (desc string) {
|
||||||
desc = fmt.Sprintf("%T(inode=%v)", o.bazilReq, o.bazilReq.Hdr().Node)
|
desc = fmt.Sprintf("<opcode %d>(inode=%v)", o.opCode, o.inode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *unknownOp) respond() {
|
func (o *unknownOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
panic(fmt.Sprintf("Should never get here for unknown op: %s", o.ShortDesc()))
|
panic(fmt.Sprintf("Should never get here for unknown op: %s", o.ShortDesc()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -910,7 +899,6 @@ func (o *unknownOp) respond() {
|
||||||
// Read the target of a symlink inode.
|
// Read the target of a symlink inode.
|
||||||
type ReadSymlinkOp struct {
|
type ReadSymlinkOp struct {
|
||||||
commonOp
|
commonOp
|
||||||
bfReq *bazilfuse.ReadlinkRequest
|
|
||||||
|
|
||||||
// The symlink inode that we are reading.
|
// The symlink inode that we are reading.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
|
@ -919,7 +907,68 @@ type ReadSymlinkOp struct {
|
||||||
Target string
|
Target string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ReadSymlinkOp) respond() {
|
func (o *ReadSymlinkOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
o.bfReq.Respond(o.Target)
|
b = buffer.NewOutMessage(uintptr(len(o.Target)))
|
||||||
|
b.AppendString(o.Target)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Internal
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// TODO(jacobsa): Untangle the way ops work and move these to an internal
|
||||||
|
// package, along with Convert. I think all of the behavior wants to be on
|
||||||
|
// Connection. Ops have only String methods. Connection.ReadOp returns an
|
||||||
|
// interace{} and a context. If we must restore debug logging later, we can
|
||||||
|
// stuff an op ID in that context and add a Connection.Logf method. Connection
|
||||||
|
// has a Reply method that takes a descendent context and an error.
|
||||||
|
|
||||||
|
// Do not use this struct directly. See the TODO in fuseops/ops.go.
|
||||||
|
type InternalStatFSOp struct {
|
||||||
|
commonOp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *InternalStatFSOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
|
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.StatfsOut{}))
|
||||||
|
b.Grow(unsafe.Sizeof(fusekernel.StatfsOut{}))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not use this struct directly. See the TODO in fuseops/ops.go.
|
||||||
|
type InternalInterruptOp struct {
|
||||||
|
commonOp
|
||||||
|
FuseID uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *InternalInterruptOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
|
panic("Shouldn't get here.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not use this struct directly. See the TODO in fuseops/ops.go.
|
||||||
|
type InternalInitOp struct {
|
||||||
|
commonOp
|
||||||
|
|
||||||
|
// In
|
||||||
|
Kernel fusekernel.Protocol
|
||||||
|
|
||||||
|
// Out
|
||||||
|
Library fusekernel.Protocol
|
||||||
|
MaxReadahead uint32
|
||||||
|
Flags fusekernel.InitFlags
|
||||||
|
MaxWrite uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *InternalInitOp) kernelResponse() (b buffer.OutMessage) {
|
||||||
|
b = buffer.NewOutMessage(unsafe.Sizeof(fusekernel.InitOut{}))
|
||||||
|
out := (*fusekernel.InitOut)(b.Grow(unsafe.Sizeof(fusekernel.InitOut{})))
|
||||||
|
|
||||||
|
out.Major = o.Library.Major
|
||||||
|
out.Minor = o.Library.Minor
|
||||||
|
out.MaxReadahead = o.MaxReadahead
|
||||||
|
out.Flags = uint32(o.Flags)
|
||||||
|
out.MaxWrite = o.MaxWrite
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
"github.com/jacobsa/fuse/internal/fusekernel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A 64-bit number used to uniquely identify a file or directory in the file
|
// A 64-bit number used to uniquely identify a file or directory in the file
|
||||||
|
@ -38,7 +38,7 @@ const RootInodeID = 1
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Make sure the constant above is correct. We do this at runtime rather than
|
// Make sure the constant above is correct. We do this at runtime rather than
|
||||||
// defining the constant in terms of bazilfuse.RootID for two reasons:
|
// defining the constant in terms of fusekernel.RootID for two reasons:
|
||||||
//
|
//
|
||||||
// 1. Users can more clearly see that the root ID is low and can therefore
|
// 1. Users can more clearly see that the root ID is low and can therefore
|
||||||
// be used as e.g. an array index, with space reserved up to the root.
|
// be used as e.g. an array index, with space reserved up to the root.
|
||||||
|
@ -46,12 +46,12 @@ func init() {
|
||||||
// 2. The constant can be untyped and can therefore more easily be used as
|
// 2. The constant can be untyped and can therefore more easily be used as
|
||||||
// an array index.
|
// an array index.
|
||||||
//
|
//
|
||||||
if RootInodeID != bazilfuse.RootID {
|
if RootInodeID != fusekernel.RootID {
|
||||||
panic(
|
panic(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"Oops, RootInodeID is wrong: %v vs. %v",
|
"Oops, RootInodeID is wrong: %v vs. %v",
|
||||||
RootInodeID,
|
RootInodeID,
|
||||||
bazilfuse.RootID))
|
fusekernel.RootID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +97,16 @@ type InodeAttributes struct {
|
||||||
Gid uint32
|
Gid uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InodeAttributes) DebugString() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%d %d %v %d %d",
|
||||||
|
a.Size,
|
||||||
|
a.Nlink,
|
||||||
|
a.Mode,
|
||||||
|
a.Uid,
|
||||||
|
a.Gid)
|
||||||
|
}
|
||||||
|
|
||||||
// A generation number for an inode. Irrelevant for file systems that won't be
|
// A generation number for an inode. Irrelevant for file systems that won't be
|
||||||
// exported over NFS. For those that will and that reuse inode IDs when they
|
// exported over NFS. For those that will and that reuse inode IDs when they
|
||||||
// become free, the generation number must change when an ID is reused.
|
// become free, the generation number must change when an ID is reused.
|
||||||
|
|
|
@ -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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,129 +59,6 @@ func (mfs *MountedFileSystem) Join(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional configuration accepted by Mount.
|
|
||||||
type MountConfig struct {
|
|
||||||
// The context from which every op read from the connetion by the sever
|
|
||||||
// should inherit. If nil, context.Background() will be used.
|
|
||||||
OpContext context.Context
|
|
||||||
|
|
||||||
// If non-empty, the name of the file system as displayed by e.g. `mount`.
|
|
||||||
// This is important because the `umount` command requires root privileges if
|
|
||||||
// it doesn't agree with /etc/fstab.
|
|
||||||
FSName string
|
|
||||||
|
|
||||||
// Mount the file system in read-only mode. File modes will appear as normal,
|
|
||||||
// but opening a file for writing and metadata operations like chmod,
|
|
||||||
// chtimes, etc. will fail.
|
|
||||||
ReadOnly bool
|
|
||||||
|
|
||||||
// A logger to use for logging errors. All errors are logged, with the
|
|
||||||
// exception of a few blacklisted errors that are expected. If nil, no error
|
|
||||||
// logging is performed.
|
|
||||||
ErrorLogger *log.Logger
|
|
||||||
|
|
||||||
// A logger to use for logging debug information. If nil, no debug logging is
|
|
||||||
// performed.
|
|
||||||
DebugLogger *log.Logger
|
|
||||||
|
|
||||||
// OS X only.
|
|
||||||
//
|
|
||||||
// Normally on OS X we mount with the novncache option
|
|
||||||
// (cf. http://goo.gl/1pTjuk), which disables entry caching in the kernel.
|
|
||||||
// This is because osxfuse does not honor the entry expiration values we
|
|
||||||
// return to it, instead caching potentially forever (cf.
|
|
||||||
// http://goo.gl/8yR0Ie), and it is probably better to fail to cache than to
|
|
||||||
// cache for too long, since the latter is more likely to hide consistency
|
|
||||||
// bugs that are difficult to detect and diagnose.
|
|
||||||
//
|
|
||||||
// This field disables the use of novncache, restoring entry caching. Beware:
|
|
||||||
// the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and
|
|
||||||
// entries will be cached for an arbitrarily long time.
|
|
||||||
EnableVnodeCaching bool
|
|
||||||
|
|
||||||
// Additional key=value options to pass unadulterated to the underlying mount
|
|
||||||
// command. See `man 8 mount`, the fuse documentation, etc. for
|
|
||||||
// system-specific information.
|
|
||||||
//
|
|
||||||
// For expert use only! May invalidate other guarantees made in the
|
|
||||||
// documentation for this package.
|
|
||||||
Options map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to mount options to be passed to package bazilfuse.
|
|
||||||
func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) {
|
|
||||||
isDarwin := runtime.GOOS == "darwin"
|
|
||||||
|
|
||||||
// Enable permissions checking in the kernel. See the comments on
|
|
||||||
// InodeAttributes.Mode.
|
|
||||||
opts = append(opts, bazilfuse.SetOption("default_permissions", ""))
|
|
||||||
|
|
||||||
// HACK(jacobsa): Work around what appears to be a bug in systemd v219, as
|
|
||||||
// shipped in Ubuntu 15.04, where it automatically unmounts any file system
|
|
||||||
// that doesn't set an explicit name.
|
|
||||||
//
|
|
||||||
// When Ubuntu contains systemd v220, this workaround should be removed and
|
|
||||||
// the systemd bug reopened if the problem persists.
|
|
||||||
//
|
|
||||||
// Cf. https://github.com/bazil/fuse/issues/89
|
|
||||||
// Cf. https://bugs.freedesktop.org/show_bug.cgi?id=90907
|
|
||||||
fsname := c.FSName
|
|
||||||
if runtime.GOOS == "linux" && fsname == "" {
|
|
||||||
fsname = "some_fuse_file_system"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special file system name?
|
|
||||||
if fsname != "" {
|
|
||||||
opts = append(opts, bazilfuse.FSName(fsname))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read only?
|
|
||||||
if c.ReadOnly {
|
|
||||||
opts = append(opts, bazilfuse.ReadOnly())
|
|
||||||
}
|
|
||||||
|
|
||||||
// OS X: set novncache when appropriate.
|
|
||||||
if isDarwin && !c.EnableVnodeCaching {
|
|
||||||
opts = append(opts, bazilfuse.SetOption("novncache", ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
// OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which
|
|
||||||
// just add noise to debug output and can have significant cost on
|
|
||||||
// network-based file systems.
|
|
||||||
//
|
|
||||||
// Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
|
||||||
if isDarwin {
|
|
||||||
opts = append(opts, bazilfuse.SetOption("noappledouble", ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask the Linux kernel for larger read requests.
|
|
||||||
//
|
|
||||||
// As of 2015-03-26, the behavior in the kernel is:
|
|
||||||
//
|
|
||||||
// * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable
|
|
||||||
// ra_pages to be init_response->max_readahead divided by the page size.
|
|
||||||
//
|
|
||||||
// * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set
|
|
||||||
// backing_dev_info::ra_pages to the min of that value and what was sent
|
|
||||||
// in the request's max_readahead field.
|
|
||||||
//
|
|
||||||
// * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding
|
|
||||||
// how much to read ahead.
|
|
||||||
//
|
|
||||||
// * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero.
|
|
||||||
//
|
|
||||||
// Reading a page at a time is a drag. Ask for a larger size.
|
|
||||||
const maxReadahead = 1 << 20
|
|
||||||
opts = append(opts, bazilfuse.MaxReadahead(maxReadahead))
|
|
||||||
|
|
||||||
// Last but not least: other user-supplied options.
|
|
||||||
for k, v := range c.Options {
|
|
||||||
opts = append(opts, bazilfuse.SetOption(k, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to mount a file system on the given directory, using the supplied
|
// Attempt to mount a file system on the given directory, using the supplied
|
||||||
// Server to serve connection requests. This function blocks until the file
|
// Server to serve connection requests. This function blocks until the file
|
||||||
// system is successfully mounted.
|
// system is successfully mounted.
|
||||||
|
@ -198,10 +72,11 @@ func Mount(
|
||||||
joinStatusAvailable: make(chan struct{}),
|
joinStatusAvailable: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a bazilfuse connection.
|
// Begin the mounting process, which will continue in the background.
|
||||||
bfConn, err := bazilfuse.Mount(mfs.dir, config.bazilfuseOptions()...)
|
ready := make(chan error, 1)
|
||||||
|
dev, err := mount(dir, config, ready)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("bazilfuse.Mount: %v", err)
|
err = fmt.Errorf("mount: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,15 +86,14 @@ func Mount(
|
||||||
opContext = context.Background()
|
opContext = context.Background()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create our own Connection object wrapping it.
|
// Create a Connection object wrapping the device.
|
||||||
connection, err := newConnection(
|
connection, err := newConnection(
|
||||||
opContext,
|
opContext,
|
||||||
config.DebugLogger,
|
config.DebugLogger,
|
||||||
config.ErrorLogger,
|
config.ErrorLogger,
|
||||||
bfConn)
|
dev)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bfConn.Close()
|
|
||||||
err = fmt.Errorf("newConnection: %v", err)
|
err = fmt.Errorf("newConnection: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -231,9 +105,9 @@ func Mount(
|
||||||
close(mfs.joinStatusAvailable)
|
close(mfs.joinStatusAvailable)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for the connection to say it is ready.
|
// Wait for the mount process to complete.
|
||||||
if err = connection.waitForReady(); err != nil {
|
if err = <-ready; err != nil {
|
||||||
err = fmt.Errorf("WaitForReady: %v", err)
|
err = fmt.Errorf("mount (background): %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ import (
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
|
||||||
"github.com/jacobsa/fuse/fsutil"
|
"github.com/jacobsa/fuse/fsutil"
|
||||||
"github.com/jacobsa/fuse/fusetesting"
|
"github.com/jacobsa/fuse/fusetesting"
|
||||||
"github.com/jacobsa/fuse/samples"
|
"github.com/jacobsa/fuse/samples"
|
||||||
|
@ -58,8 +57,8 @@ type flushFSTest struct {
|
||||||
|
|
||||||
func (t *flushFSTest) setUp(
|
func (t *flushFSTest) setUp(
|
||||||
ti *TestInfo,
|
ti *TestInfo,
|
||||||
flushErr bazilfuse.Errno,
|
flushErr syscall.Errno,
|
||||||
fsyncErr bazilfuse.Errno,
|
fsyncErr syscall.Errno,
|
||||||
readOnly bool) {
|
readOnly bool) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -810,7 +809,7 @@ func init() { RegisterTestSuite(&FlushErrorTest{}) }
|
||||||
|
|
||||||
func (t *FlushErrorTest) SetUp(ti *TestInfo) {
|
func (t *FlushErrorTest) SetUp(ti *TestInfo) {
|
||||||
const noErr = 0
|
const noErr = 0
|
||||||
t.flushFSTest.setUp(ti, bazilfuse.ENOENT, noErr, false)
|
t.flushFSTest.setUp(ti, syscall.ENOENT, noErr, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FlushErrorTest) Close() {
|
func (t *FlushErrorTest) Close() {
|
||||||
|
@ -890,7 +889,7 @@ func init() { RegisterTestSuite(&FsyncErrorTest{}) }
|
||||||
|
|
||||||
func (t *FsyncErrorTest) SetUp(ti *TestInfo) {
|
func (t *FsyncErrorTest) SetUp(ti *TestInfo) {
|
||||||
const noErr = 0
|
const noErr = 0
|
||||||
t.flushFSTest.setUp(ti, noErr, bazilfuse.ENOENT, false)
|
t.flushFSTest.setUp(ti, noErr, syscall.ENOENT, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *FsyncErrorTest) Fsync() {
|
func (t *FsyncErrorTest) Fsync() {
|
||||||
|
|
|
@ -23,8 +23,8 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
|
||||||
"github.com/jacobsa/fuse"
|
"github.com/jacobsa/fuse"
|
||||||
"github.com/jacobsa/fuse/samples/flushfs"
|
"github.com/jacobsa/fuse/samples/flushfs"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -58,11 +58,11 @@ func makeFlushFS() (server fuse.Server, err error) {
|
||||||
var fsyncErr error
|
var fsyncErr error
|
||||||
|
|
||||||
if *fFlushError != 0 {
|
if *fFlushError != 0 {
|
||||||
flushErr = bazilfuse.Errno(*fFlushError)
|
flushErr = syscall.Errno(*fFlushError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *fFsyncError != 0 {
|
if *fFsyncError != 0 {
|
||||||
fsyncErr = bazilfuse.Errno(*fFsyncError)
|
fsyncErr = syscall.Errno(*fFsyncError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report flushes and fsyncs by writing the contents followed by a newline.
|
// Report flushes and fsyncs by writing the contents followed by a newline.
|
||||||
|
|
|
@ -14,10 +14,8 @@
|
||||||
|
|
||||||
package fuse
|
package fuse
|
||||||
|
|
||||||
import "github.com/jacobsa/bazilfuse"
|
|
||||||
|
|
||||||
// Attempt to unmount the file system whose mount point is the supplied
|
// Attempt to unmount the file system whose mount point is the supplied
|
||||||
// directory.
|
// directory.
|
||||||
func Unmount(dir string) error {
|
func Unmount(dir string) error {
|
||||||
return bazilfuse.Unmount(dir)
|
return unmount(dir)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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