Imported without modification from github.com/jacobsa/gcsfuse/fuseutil.
At commit 90c8d87fe8701d2335671eb01cbc1d70f655c87f. I'm splitting this out because it's large and more generally useful.geesefs-0-30-9
parent
b6dc0c88f1
commit
915afb6308
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||||
|
|
||||||
|
package fuseutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fEnableDebug = flag.Bool(
|
||||||
|
"fuseutil.debug",
|
||||||
|
false,
|
||||||
|
"Write FUSE debugging messages to stderr.")
|
||||||
|
|
||||||
|
// Create a logger based on command-line flag settings.
|
||||||
|
func getLogger() *log.Logger {
|
||||||
|
var writer io.Writer = ioutil.Discard
|
||||||
|
if *fEnableDebug {
|
||||||
|
writer = os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
return log.New(writer, "fuseutil: ", log.LstdFlags)
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||||
|
|
||||||
|
package fuseutil
|
||||||
|
|
||||||
|
import "bazil.org/fuse"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Errors corresponding to kernel error numbers. These may be treated
|
||||||
|
// specially when returned by a FileSystem method.
|
||||||
|
ENOSYS = fuse.ENOSYS
|
||||||
|
)
|
|
@ -0,0 +1,199 @@
|
||||||
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||||
|
|
||||||
|
package fuseutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An interface that must be implemented by file systems to be mounted with
|
||||||
|
// FUSE. See also the comments on request and response structs.
|
||||||
|
//
|
||||||
|
// Not all methods need to have interesting implementations. Embed a field of
|
||||||
|
// type NotImplementedFileSystem to inherit defaults that return ENOSYS to the
|
||||||
|
// kernel.
|
||||||
|
//
|
||||||
|
// Must be safe for concurrent access via all methods.
|
||||||
|
type FileSystem interface {
|
||||||
|
// Open a file or directory identified by an inode ID. The kernel calls this
|
||||||
|
// method when setting up a struct file for a particular inode, usually in
|
||||||
|
// response to an open(2) call from a user-space process. This may have side
|
||||||
|
// effects, depending on the flags passed.
|
||||||
|
Open(
|
||||||
|
ctx context.Context,
|
||||||
|
req *OpenRequest) (*OpenResponse, error)
|
||||||
|
|
||||||
|
// Look up a child by name within a parent directory. The kernel calls this
|
||||||
|
// when resolving user paths to dentry structs, which are then cached.
|
||||||
|
Lookup(
|
||||||
|
ctx context.Context,
|
||||||
|
req *LookupRequest) (*LookupResponse, error)
|
||||||
|
|
||||||
|
// Forget an inode ID previously issued (e.g. by Lookup). The kernel calls
|
||||||
|
// this when removing an inode from its internal caches.
|
||||||
|
//
|
||||||
|
// The kernel guarantees that the node ID will not be used in further calls
|
||||||
|
// to the file system (unless it is reissued by the file system).
|
||||||
|
Forget(
|
||||||
|
ctx context.Context,
|
||||||
|
req *ForgetRequest) (*ForgetResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Simple types
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// A 64-bit number used to uniquely identify a file or directory in the file
|
||||||
|
// system. File systems may mint inode IDs with any value except for
|
||||||
|
// RootInodeID.
|
||||||
|
//
|
||||||
|
// This corresponds to struct inode::i_no in the VFS layer.
|
||||||
|
// (Cf. http://goo.gl/tvYyQt)
|
||||||
|
type InodeID uint64
|
||||||
|
|
||||||
|
// A distinguished inode ID that identifies the root of the file system, e.g.
|
||||||
|
// in a request to Open or Lookup. Unlike all other inode IDs, which are minted
|
||||||
|
// by the file system, the FUSE VFS layer may send a request for this ID
|
||||||
|
// without the file system ever having referenced it in a previous response.
|
||||||
|
const RootInodeID InodeID = InodeID(fuse.RootID)
|
||||||
|
|
||||||
|
// A generation number for an inode. Irrelevant for file systems that won't be
|
||||||
|
// exported over NFS. For those that will and that reuse inode IDs when they
|
||||||
|
// become free, the generation number must change when an ID is reused.
|
||||||
|
//
|
||||||
|
// This corresponds to struct inode::i_generation in the VFS layer.
|
||||||
|
// (Cf. http://goo.gl/tvYyQt)
|
||||||
|
//
|
||||||
|
// Some related reading:
|
||||||
|
//
|
||||||
|
// http://fuse.sourceforge.net/doxygen/structfuse__entry__param.html
|
||||||
|
// http://stackoverflow.com/q/11071996/1505451
|
||||||
|
// http://goo.gl/CqvwyX
|
||||||
|
// http://julipedia.meroh.net/2005/09/nfs-file-handles.html
|
||||||
|
// http://goo.gl/wvo3MB
|
||||||
|
//
|
||||||
|
type GenerationNumber uint64
|
||||||
|
|
||||||
|
// Attributes for a file or directory inode. Corresponds to struct inode (cf.
|
||||||
|
// http://goo.gl/tvYyQt).
|
||||||
|
type InodeAttributes struct {
|
||||||
|
// The size of the file in bytes.
|
||||||
|
Size uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Requests and responses
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type OpenRequest struct {
|
||||||
|
// The ID of the inode to be opened.
|
||||||
|
Inode InodeID
|
||||||
|
|
||||||
|
// Mode and options flags.
|
||||||
|
Flags fuse.OpenFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently nothing interesting here. The file system should perform any
|
||||||
|
// checking and side effects necessary as part of FileSystem.Open, and return
|
||||||
|
// an error if appropriate.
|
||||||
|
type OpenResponse struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type LookupRequest struct {
|
||||||
|
// The ID of the directory inode to which the child belongs.
|
||||||
|
Parent InodeID
|
||||||
|
|
||||||
|
// The name of the child of interest, relative to the parent. For example, in
|
||||||
|
// this directory structure:
|
||||||
|
//
|
||||||
|
// foo/
|
||||||
|
// bar/
|
||||||
|
// baz
|
||||||
|
//
|
||||||
|
// the file system may receive a request to look up the child named "bar" for
|
||||||
|
// the parent foo/.
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type LookupResponse struct {
|
||||||
|
// The ID of the child inode. The file system must ensure that the returned
|
||||||
|
// inode ID remains valid until a later call to Forget.
|
||||||
|
Child InodeID
|
||||||
|
|
||||||
|
// A generation number for this incarnation of the inode with the given ID.
|
||||||
|
// See comments on type GenerationNumber for more.
|
||||||
|
Generation GenerationNumber
|
||||||
|
|
||||||
|
// Current ttributes for the child inode.
|
||||||
|
Attributes InodeAttributes
|
||||||
|
|
||||||
|
// The FUSE VFS layer in the kernel maintains a cache of file attributes,
|
||||||
|
// used whenever up to date information about size, mode, etc. is needed.
|
||||||
|
//
|
||||||
|
// For example, this is the abridged call chain for fstat(2):
|
||||||
|
//
|
||||||
|
// * (http://goo.gl/tKBH1p) fstat calls vfs_fstat.
|
||||||
|
// * (http://goo.gl/3HeITq) vfs_fstat eventuall calls vfs_getattr_nosec.
|
||||||
|
// * (http://goo.gl/DccFQr) vfs_getattr_nosec calls i_op->getattr.
|
||||||
|
// * (http://goo.gl/dpKkst) fuse_getattr calls fuse_update_attributes.
|
||||||
|
// * (http://goo.gl/yNlqPw) fuse_update_attributes uses the values in the
|
||||||
|
// struct inode if allowed, otherwise calling out to the user-space code.
|
||||||
|
//
|
||||||
|
// In addition to obvious cases like fstat, this is also used in more subtle
|
||||||
|
// cases like updating size information before seeking (http://goo.gl/2nnMFa)
|
||||||
|
// or reading (http://goo.gl/FQSWs8).
|
||||||
|
//
|
||||||
|
// Most 'real' file systems do not set inode_operations::getattr, and
|
||||||
|
// therefore vfs_getattr_nosec calls generic_fillattr which simply grabs the
|
||||||
|
// information from the inode struct. This makes sense because these file
|
||||||
|
// systems cannot spontaneously change; all modifications go through the
|
||||||
|
// kernel which can update the inode struct as appropriate.
|
||||||
|
//
|
||||||
|
// In contrast, a FUSE file system may have spontaneous changes, so it calls
|
||||||
|
// out to user space to fetch attributes. However this is expensive, so the
|
||||||
|
// FUSE layer in the kernel caches the attributes if requested.
|
||||||
|
//
|
||||||
|
// This field controls when the attributes returned in this response and
|
||||||
|
// stashed in the struct inode should be re-queried. Leave at the zero value
|
||||||
|
// to disable caching.
|
||||||
|
//
|
||||||
|
// More reading:
|
||||||
|
// http://stackoverflow.com/q/21540315/1505451
|
||||||
|
AttributesExpiration time.Time
|
||||||
|
|
||||||
|
// The time until which the kernel may maintain an entry for this name to
|
||||||
|
// inode mapping in its dentry cache. After this time, it will revalidate the
|
||||||
|
// dentry.
|
||||||
|
//
|
||||||
|
// As in the discussion of attribute caching above, unlike real file systems,
|
||||||
|
// FUSE file systems may spontaneously change their name -> inode mapping.
|
||||||
|
// Therefore the FUSE VFS layer uses dentry_operations::d_revalidate
|
||||||
|
// (http://goo.gl/dVea0h) to intercept lookups and revalidate by calling the
|
||||||
|
// user-space Lookup method. However the latter may be slow, so it caches the
|
||||||
|
// entries until the time defined by this field.
|
||||||
|
//
|
||||||
|
// Example code walk:
|
||||||
|
//
|
||||||
|
// * (http://goo.gl/M2G3tO) lookup_dcache calls d_revalidate if enabled.
|
||||||
|
// * (http://goo.gl/ef0Elu) fuse_dentry_revalidate just uses the dentry's
|
||||||
|
// inode if fuse_dentry_time(entry) hasn't passed. Otherwise it sends a
|
||||||
|
// lookup request.
|
||||||
|
//
|
||||||
|
// Leave at the zero value to disable caching.
|
||||||
|
EntryExpiration time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForgetRequest struct {
|
||||||
|
// The inode to be forgotten. The kernel guarantees that the node ID will not
|
||||||
|
// be used in further calls to the file system (unless it is reissued by the
|
||||||
|
// file system).
|
||||||
|
ID InodeID
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForgetResponse struct {
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||||
|
|
||||||
|
package fuseutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A struct representing the status of a mount operation, with methods for
|
||||||
|
// waiting on the mount to complete, waiting for unmounting, and causing
|
||||||
|
// unmounting.
|
||||||
|
type MountedFileSystem struct {
|
||||||
|
dir string
|
||||||
|
|
||||||
|
// The result to return from WaitForReady. Not valid until the channel is
|
||||||
|
// closed.
|
||||||
|
readyStatus error
|
||||||
|
readyStatusAvailable chan struct{}
|
||||||
|
|
||||||
|
// The result to return from Join. Not valid until the channel is closed.
|
||||||
|
joinStatus error
|
||||||
|
joinStatusAvailable chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the directory on which the file system is mounted (or where we
|
||||||
|
// attempted to mount it.)
|
||||||
|
func (mfs *MountedFileSystem) Dir() string {
|
||||||
|
return mfs.dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the mount point is ready to be used. After a successful return
|
||||||
|
// from this function, the contents of the mounted file system should be
|
||||||
|
// visible in the directory supplied to NewMountPoint. May be called multiple
|
||||||
|
// times.
|
||||||
|
func (mfs *MountedFileSystem) WaitForReady(ctx context.Context) error {
|
||||||
|
select {
|
||||||
|
case <-mfs.readyStatusAvailable:
|
||||||
|
return mfs.readyStatus
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block until a mounted file system has been unmounted. The return value will
|
||||||
|
// be non-nil if anything unexpected happened while serving. May be called
|
||||||
|
// multiple times. Must not be called unless WaitForReady has returned nil.
|
||||||
|
func (mfs *MountedFileSystem) Join(ctx context.Context) error {
|
||||||
|
select {
|
||||||
|
case <-mfs.joinStatusAvailable:
|
||||||
|
return mfs.joinStatus
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to unmount the file system. Use Join to wait for it to actually be
|
||||||
|
// unmounted. You must first call WaitForReady to ensure there is no race with
|
||||||
|
// mounting.
|
||||||
|
func (mfs *MountedFileSystem) Unmount() error {
|
||||||
|
return fuse.Unmount(mfs.dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs in the background.
|
||||||
|
func (mfs *MountedFileSystem) mountAndServe(
|
||||||
|
server *server,
|
||||||
|
options []fuse.MountOption) {
|
||||||
|
logger := getLogger()
|
||||||
|
|
||||||
|
// Open a FUSE connection.
|
||||||
|
logger.Println("Opening a FUSE connection.")
|
||||||
|
c, err := fuse.Mount(mfs.dir, options...)
|
||||||
|
if err != nil {
|
||||||
|
mfs.readyStatus = errors.New("fuse.Mount: " + err.Error())
|
||||||
|
close(mfs.readyStatusAvailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
// Start a goroutine that will notify the MountedFileSystem object when the
|
||||||
|
// connection says it is ready (or it fails to become ready).
|
||||||
|
go func() {
|
||||||
|
logger.Println("Waiting for the FUSE connection to be ready.")
|
||||||
|
<-c.Ready
|
||||||
|
logger.Println("The FUSE connection is ready.")
|
||||||
|
|
||||||
|
mfs.readyStatus = c.MountError
|
||||||
|
close(mfs.readyStatusAvailable)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Serve the connection using the file system object.
|
||||||
|
logger.Println("Serving the FUSE connection.")
|
||||||
|
if err := server.Serve(c); err != nil {
|
||||||
|
mfs.joinStatus = errors.New("Serve: " + err.Error())
|
||||||
|
close(mfs.joinStatusAvailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal that everything is okay.
|
||||||
|
close(mfs.joinStatusAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to mount the supplied file system on the given directory.
|
||||||
|
// mfs.WaitForReady() must be called to find out whether the mount was
|
||||||
|
// successful.
|
||||||
|
func Mount(
|
||||||
|
dir string,
|
||||||
|
fs FileSystem,
|
||||||
|
options ...fuse.MountOption) (mfs *MountedFileSystem, err error) {
|
||||||
|
// Create a server object.
|
||||||
|
server, err := newServer(fs)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the struct.
|
||||||
|
mfs = &MountedFileSystem{
|
||||||
|
dir: dir,
|
||||||
|
readyStatusAvailable: make(chan struct{}),
|
||||||
|
joinStatusAvailable: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount in the background.
|
||||||
|
go mfs.mountAndServe(server, options)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||||
|
|
||||||
|
package fuseutil
|
||||||
|
|
||||||
|
import "golang.org/x/net/context"
|
||||||
|
|
||||||
|
// Embed this within your file system type to inherit default implementations
|
||||||
|
// of all methods that return ENOSYS.
|
||||||
|
type NotImplementedFileSystem struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ FileSystem = &NotImplementedFileSystem{}
|
||||||
|
|
||||||
|
func (fs *NotImplementedFileSystem) Open(
|
||||||
|
ctx context.Context,
|
||||||
|
req *OpenRequest) (*OpenResponse, error) {
|
||||||
|
return nil, ENOSYS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *NotImplementedFileSystem) Lookup(
|
||||||
|
ctx context.Context,
|
||||||
|
req *LookupRequest) (*LookupResponse, error) {
|
||||||
|
return nil, ENOSYS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *NotImplementedFileSystem) Forget(
|
||||||
|
ctx context.Context,
|
||||||
|
req *ForgetRequest) (*ForgetResponse, error) {
|
||||||
|
return nil, ENOSYS
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||||
|
|
||||||
|
package samples
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jacobsa/gcsfuse/fuseutil"
|
||||||
|
"github.com/jacobsa/gcsfuse/timeutil"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A file system with a fixed structure that looks like this:
|
||||||
|
//
|
||||||
|
// hello
|
||||||
|
// dir/
|
||||||
|
// world
|
||||||
|
//
|
||||||
|
// Each file contains the string "Hello, world!".
|
||||||
|
type HelloFS struct {
|
||||||
|
fuseutil.NotImplementedFileSystem
|
||||||
|
Clock timeutil.Clock
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fuseutil.FileSystem = &HelloFS{}
|
||||||
|
|
||||||
|
func (fs *HelloFS) Open(
|
||||||
|
ctx context.Context,
|
||||||
|
req *fuseutil.OpenRequest) (resp *fuseutil.OpenResponse, err error) {
|
||||||
|
// We always allow opening the root directory.
|
||||||
|
if req.Inode == fuseutil.RootInodeID {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jacobsa): Handle others.
|
||||||
|
err = fuseutil.ENOSYS
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||||
|
|
||||||
|
package samples_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jacobsa/gcsfuse/fuseutil"
|
||||||
|
"github.com/jacobsa/gcsfuse/fuseutil/samples"
|
||||||
|
"github.com/jacobsa/gcsfuse/timeutil"
|
||||||
|
. "github.com/jacobsa/ogletest"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHelloFS(t *testing.T) { RunTests(t) }
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Boilerplate
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type HelloFSTest struct {
|
||||||
|
clock timeutil.SimulatedClock
|
||||||
|
mfs *fuseutil.MountedFileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ SetUpInterface = &HelloFSTest{}
|
||||||
|
var _ TearDownInterface = &HelloFSTest{}
|
||||||
|
|
||||||
|
func init() { RegisterTestSuite(&HelloFSTest{}) }
|
||||||
|
|
||||||
|
func (t *HelloFSTest) SetUp(ti *TestInfo) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Set up a fixed, non-zero time.
|
||||||
|
t.clock.AdvanceTime(time.Now().Sub(t.clock.Now()))
|
||||||
|
|
||||||
|
// Set up a temporary directory for mounting.
|
||||||
|
mountPoint, err := ioutil.TempDir("", "hello_fs_test")
|
||||||
|
if err != nil {
|
||||||
|
panic("ioutil.TempDir: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount a file system.
|
||||||
|
fs := &samples.HelloFS{
|
||||||
|
Clock: &t.clock,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.mfs, err = fuseutil.Mount(mountPoint, fs); err != nil {
|
||||||
|
panic("Mount: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = t.mfs.WaitForReady(context.Background()); err != nil {
|
||||||
|
panic("MountedFileSystem.WaitForReady: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) TearDown() {
|
||||||
|
// Unmount the file system. Try again on "resource busy" errors.
|
||||||
|
delay := 10 * time.Millisecond
|
||||||
|
for {
|
||||||
|
err := t.mfs.Unmount()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(err.Error(), "resource busy") {
|
||||||
|
log.Println("Resource busy error while unmounting; trying again")
|
||||||
|
time.Sleep(delay)
|
||||||
|
delay = time.Duration(1.3 * float64(delay))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("MountedFileSystem.Unmount: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.mfs.Join(context.Background()); err != nil {
|
||||||
|
panic("MountedFileSystem.Join: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Test functions
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func (t *HelloFSTest) ReadDir_Root() {
|
||||||
|
entries, err := ioutil.ReadDir(t.mfs.Dir())
|
||||||
|
|
||||||
|
AssertEq(nil, err)
|
||||||
|
AssertEq(2, len(entries))
|
||||||
|
var fi os.FileInfo
|
||||||
|
|
||||||
|
// dir
|
||||||
|
fi = entries[0]
|
||||||
|
ExpectEq("dir", fi.Name())
|
||||||
|
ExpectEq(0, fi.Size())
|
||||||
|
ExpectEq(os.ModeDir|0500, fi.Mode())
|
||||||
|
ExpectEq(t.clock.Now(), fi.ModTime())
|
||||||
|
ExpectTrue(fi.IsDir())
|
||||||
|
|
||||||
|
// hello
|
||||||
|
fi = entries[1]
|
||||||
|
ExpectEq("hello", fi.Name())
|
||||||
|
ExpectEq(len("Hello, world!"), fi.Size())
|
||||||
|
ExpectEq(0400, fi.Mode())
|
||||||
|
ExpectEq(t.clock.Now(), fi.ModTime())
|
||||||
|
ExpectFalse(fi.IsDir())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) ReadDir_Dir() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) ReadDir_NonExistent() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) Stat_Hello() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) Stat_Dir() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) Stat_World() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) Stat_NonExistent() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) Read_Hello() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) Read_World() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HelloFSTest) Open_NonExistent() {
|
||||||
|
AssertTrue(false, "TODO")
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
|
// Author: jacobsa@google.com (Aaron Jacobs)
|
||||||
|
|
||||||
|
package fuseutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An object that terminates one end of the userspace <-> FUSE VFS connection.
|
||||||
|
type server struct {
|
||||||
|
logger *log.Logger
|
||||||
|
fs FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a server that relays requests to the supplied file system.
|
||||||
|
func newServer(fs FileSystem) (s *server, err error) {
|
||||||
|
s = &server{
|
||||||
|
logger: getLogger(),
|
||||||
|
fs: fs,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve the fuse connection by repeatedly reading requests from the supplied
|
||||||
|
// FUSE connection, responding as dictated by the file system. Return when the
|
||||||
|
// connection is closed or an unexpected error occurs.
|
||||||
|
func (s *server) Serve(c *fuse.Conn) (err error) {
|
||||||
|
// Read a message at a time, dispatching to goroutines doing the actual
|
||||||
|
// processing.
|
||||||
|
for {
|
||||||
|
var fuseReq fuse.Request
|
||||||
|
fuseReq, err = c.ReadRequest()
|
||||||
|
|
||||||
|
// ReadRequest returns EOF when the connection has been closed.
|
||||||
|
//
|
||||||
|
// TODO(jacobsa): Remove this and verify it's actually needed.
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, forward on errors.
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Conn.ReadRequest: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.handleFuseRequest(fuseReq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) handleFuseRequest(fuseReq fuse.Request) {
|
||||||
|
// Log the request.
|
||||||
|
s.logger.Println("Received:", fuseReq)
|
||||||
|
|
||||||
|
// TODO(jacobsa): Support cancellation when interrupted, if we can coax the
|
||||||
|
// system into reproducing such requests.
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Attempt to handle it.
|
||||||
|
switch typed := fuseReq.(type) {
|
||||||
|
case *fuse.InitRequest:
|
||||||
|
// Responding to this is required to make mounting work, at least on OS X.
|
||||||
|
// We don't currently expose the capability for the file system to
|
||||||
|
// intercept this.
|
||||||
|
fuseResp := &fuse.InitResponse{}
|
||||||
|
s.logger.Println("Responding:", fuseResp)
|
||||||
|
typed.Respond(fuseResp)
|
||||||
|
|
||||||
|
case *fuse.StatfsRequest:
|
||||||
|
// Responding to this is required to make mounting work, at least on OS X.
|
||||||
|
// We don't currently expose the capability for the file system to
|
||||||
|
// intercept this.
|
||||||
|
fuseResp := &fuse.StatfsResponse{}
|
||||||
|
s.logger.Println("Responding:", fuseResp)
|
||||||
|
typed.Respond(fuseResp)
|
||||||
|
|
||||||
|
case *fuse.OpenRequest:
|
||||||
|
// Convert the request.
|
||||||
|
req := &OpenRequest{
|
||||||
|
Inode: InodeID(typed.Header.Node),
|
||||||
|
Flags: typed.Flags,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the file system.
|
||||||
|
if _, err := s.fs.Open(ctx, req); err != nil {
|
||||||
|
s.logger.Print("Responding:", err)
|
||||||
|
typed.RespondError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is nothing interesting to convert in the response.
|
||||||
|
fuseResp := &fuse.OpenResponse{}
|
||||||
|
s.logger.Print("Responding:", fuseResp)
|
||||||
|
typed.Respond(fuseResp)
|
||||||
|
|
||||||
|
default:
|
||||||
|
s.logger.Println("Unhandled type. Returning ENOSYS.")
|
||||||
|
typed.RespondError(ENOSYS)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue