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