Update bazil.org/fuse

This commit is contained in:
Alexander Neumann 2016-09-15 22:26:23 +02:00
parent 24398d2b9d
commit cb80a70aca
28 changed files with 1831 additions and 471 deletions

4
vendor/manifest vendored
View file

@ -4,8 +4,8 @@
{ {
"importpath": "bazil.org/fuse", "importpath": "bazil.org/fuse",
"repository": "https://github.com/bazil/fuse", "repository": "https://github.com/bazil/fuse",
"revision": "18419ee53958df28fcfc9490fe6123bd59e237bb", "revision": "371fbbdaa8987b715bdd21d6adc4c9b20155f748",
"branch": "HEAD" "branch": "master"
}, },
{ {
"importpath": "github.com/elithrar/simple-scrypt", "importpath": "github.com/elithrar/simple-scrypt",

View file

@ -24,16 +24,7 @@ func usage() {
flag.PrintDefaults() flag.PrintDefaults()
} }
func main() { func run(mountpoint string) error {
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
usage()
os.Exit(2)
}
mountpoint := flag.Arg(0)
c, err := fuse.Mount( c, err := fuse.Mount(
mountpoint, mountpoint,
fuse.FSName("clock"), fuse.FSName("clock"),
@ -42,10 +33,14 @@ func main() {
fuse.VolumeName("Clock filesystem"), fuse.VolumeName("Clock filesystem"),
) )
if err != nil { if err != nil {
log.Fatal(err) return err
} }
defer c.Close() defer c.Close()
if p := c.Protocol(); !p.HasInvalidate() {
return fmt.Errorf("kernel FUSE support is too old to have invalidations: version %v", p)
}
srv := fs.New(c, nil) srv := fs.New(c, nil)
filesys := &FS{ filesys := &FS{
// We pre-create the clock node so that it's always the same // We pre-create the clock node so that it's always the same
@ -61,12 +56,28 @@ func main() {
// This goroutine never exits. That's fine for this example. // This goroutine never exits. That's fine for this example.
go filesys.clockFile.update() go filesys.clockFile.update()
if err := srv.Serve(filesys); err != nil { if err := srv.Serve(filesys); err != nil {
log.Fatal(err) return err
} }
// Check if the mount process has an error to report. // Check if the mount process has an error to report.
<-c.Ready <-c.Ready
if err := c.MountError; err != nil { if err := c.MountError; err != nil {
return err
}
return nil
}
func main() {
flag.Usage = usage
flag.Parse()
if flag.NArg() != 1 {
usage()
os.Exit(2)
}
mountpoint := flag.Arg(0)
if err := run(mountpoint); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View file

@ -13,18 +13,18 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
var Usage = func() { func usage() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s MOUNTPOINT\n", os.Args[0])
flag.PrintDefaults() flag.PrintDefaults()
} }
func main() { func main() {
flag.Usage = Usage flag.Usage = usage
flag.Parse() flag.Parse()
if flag.NArg() != 1 { if flag.NArg() != 1 {
Usage() usage()
os.Exit(2) os.Exit(2)
} }
mountpoint := flag.Arg(0) mountpoint := flag.Arg(0)

View file

@ -0,0 +1,54 @@
package bench_test
import (
"fmt"
"os"
"testing"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"bazil.org/fuse/fs/fstestutil"
"golang.org/x/net/context"
)
type dummyFile struct {
fstestutil.File
}
type benchCreateDir struct {
fstestutil.Dir
}
var _ fs.NodeCreater = (*benchCreateDir)(nil)
func (f *benchCreateDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
child := &dummyFile{}
return child, child, nil
}
func BenchmarkCreate(b *testing.B) {
f := &benchCreateDir{}
mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil)
if err != nil {
b.Fatal(err)
}
defer mnt.Close()
// prepare file names to decrease test overhead
names := make([]string, 0, b.N)
for i := 0; i < b.N; i++ {
// zero-padded so cost stays the same on every iteration
names = append(names, mnt.Dir+"/"+fmt.Sprintf("%08x", i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
f, err := os.Create(names[i])
if err != nil {
b.Fatalf("WriteFile: %v", err)
}
f.Close()
}
b.StopTimer()
}

View file

@ -0,0 +1,42 @@
package bench_test
import (
"os"
"testing"
"golang.org/x/net/context"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"bazil.org/fuse/fs/fstestutil"
)
type benchLookupDir struct {
fstestutil.Dir
}
var _ fs.NodeRequestLookuper = (*benchLookupDir)(nil)
func (f *benchLookupDir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) {
return nil, fuse.ENOENT
}
func BenchmarkLookup(b *testing.B) {
f := &benchLookupDir{}
mnt, err := fstestutil.MountedT(b, fstestutil.SimpleFS{f}, nil)
if err != nil {
b.Fatal(err)
}
defer mnt.Close()
name := mnt.Dir + "/does-not-exist"
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := os.Stat(name); !os.IsNotExist(err) {
b.Fatalf("Stat: wrong error: %v", err)
}
}
b.StopTimer()
}

View file

@ -0,0 +1,70 @@
package fstestutil
import (
"fmt"
"io/ioutil"
"os"
)
// FileInfoCheck is a function that validates an os.FileInfo according
// to some criteria.
type FileInfoCheck func(fi os.FileInfo) error
type checkDirError struct {
missing map[string]struct{}
extra map[string]os.FileMode
}
func (e *checkDirError) Error() string {
return fmt.Sprintf("wrong directory contents: missing %v, extra %v", e.missing, e.extra)
}
// CheckDir checks the contents of the directory at path, making sure
// every directory entry listed in want is present. If the check is
// not nil, it must also pass.
//
// If want contains the impossible filename "", unexpected files are
// checked with that. If the key is not in want, unexpected files are
// an error.
//
// Missing entries, that are listed in want but not seen, are an
// error.
func CheckDir(path string, want map[string]FileInfoCheck) error {
problems := &checkDirError{
missing: make(map[string]struct{}, len(want)),
extra: make(map[string]os.FileMode),
}
for k := range want {
if k == "" {
continue
}
problems.missing[k] = struct{}{}
}
fis, err := ioutil.ReadDir(path)
if err != nil {
return fmt.Errorf("cannot read directory: %v", err)
}
for _, fi := range fis {
check, ok := want[fi.Name()]
if !ok {
check, ok = want[""]
}
if !ok {
problems.extra[fi.Name()] = fi.Mode()
continue
}
delete(problems.missing, fi.Name())
if check != nil {
if err := check(fi); err != nil {
return fmt.Errorf("check failed: %v: %v", fi.Name(), err)
}
}
}
if len(problems.missing) > 0 || len(problems.extra) > 0 {
return problems
}
return nil
}

View file

@ -50,13 +50,15 @@ func (mnt *Mount) Close() {
os.Remove(mnt.Dir) os.Remove(mnt.Dir)
} }
// Mounted mounts the fuse.Server at a temporary directory. // MountedFunc mounts a filesystem at a temporary directory. The
// filesystem used is constructed by calling a function, to allow
// storing fuse.Conn and fs.Server in the FS.
// //
// It also waits until the filesystem is known to be visible (OS X // It also waits until the filesystem is known to be visible (OS X
// workaround). // workaround).
// //
// After successful return, caller must clean up by calling Close. // After successful return, caller must clean up by calling Close.
func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { func MountedFunc(fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
dir, err := ioutil.TempDir("", "fusetest") dir, err := ioutil.TempDir("", "fusetest")
if err != nil { if err != nil {
return nil, err return nil, err
@ -75,6 +77,7 @@ func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Moun
Error: serveErr, Error: serveErr,
done: done, done: done,
} }
filesys := fn(mnt)
go func() { go func() {
defer close(done) defer close(done)
serveErr <- server.Serve(filesys) serveErr <- server.Serve(filesys)
@ -95,14 +98,25 @@ func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Moun
} }
} }
// MountedT mounts the filesystem at a temporary directory, // Mounted mounts the fuse.Server at a temporary directory.
//
// It also waits until the filesystem is known to be visible (OS X
// workaround).
//
// After successful return, caller must clean up by calling Close.
func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
fn := func(*Mount) fs.FS { return filesys }
return MountedFunc(fn, conf, options...)
}
// MountedFuncT mounts a filesystem at a temporary directory,
// directing it's debug log to the testing logger. // directing it's debug log to the testing logger.
// //
// See Mounted for usage. // See MountedFunc for usage.
// //
// The debug log is not enabled by default. Use `-fuse.debug` or call // The debug log is not enabled by default. Use `-fuse.debug` or call
// DebugByDefault to enable. // DebugByDefault to enable.
func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { func MountedFuncT(t testing.TB, fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
if conf == nil { if conf == nil {
conf = &fs.Config{} conf = &fs.Config{}
} }
@ -111,5 +125,17 @@ func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.Moun
t.Logf("FUSE: %s", msg) t.Logf("FUSE: %s", msg)
} }
} }
return Mounted(filesys, conf, options...) return MountedFunc(fn, conf, options...)
}
// MountedT mounts the filesystem at a temporary directory,
// directing it's debug log to the testing logger.
//
// See Mounted for usage.
//
// The debug log is not enabled by default. Use `-fuse.debug` or call
// DebugByDefault to enable.
func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
fn := func(*Mount) fs.FS { return filesys }
return MountedFuncT(t, fn, conf, options...)
} }

View file

@ -382,3 +382,28 @@ func (r *Removexattrs) RecordedRemovexattr() fuse.RemovexattrRequest {
} }
return *(val.(*fuse.RemovexattrRequest)) return *(val.(*fuse.RemovexattrRequest))
} }
// Creates records a Create request and its fields.
type Creates struct {
rec RequestRecorder
}
var _ = fs.NodeCreater(&Creates{})
// Create records the request and returns an error. Most callers should
// wrap this call in a function that returns a more useful result.
func (r *Creates) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
tmp := *req
r.rec.RecordRequest(&tmp)
return nil, nil, fuse.EIO
}
// RecordedCreate returns information about the Create request.
// If no request was seen, returns a zero value.
func (r *Creates) RecordedCreate() fuse.CreateRequest {
val := r.rec.Recorded()
if val == nil {
return fuse.CreateRequest{}
}
return *(val.(*fuse.CreateRequest))
}

View file

@ -18,6 +18,8 @@ import (
) )
import ( import (
"bytes"
"bazil.org/fuse" "bazil.org/fuse"
"bazil.org/fuse/fuseutil" "bazil.org/fuse/fuseutil"
) )
@ -89,6 +91,13 @@ type FSInodeGenerator interface {
// simple, read-only filesystem. // simple, read-only filesystem.
type Node interface { type Node interface {
// Attr fills attr with the standard metadata for the node. // Attr fills attr with the standard metadata for the node.
//
// Fields with reasonable defaults are prepopulated. For example,
// all times are set to a fixed moment when the program started.
//
// If Inode is left as 0, a dynamic inode number is chosen.
//
// The result may be cached for the duration set in Valid.
Attr(ctx context.Context, attr *fuse.Attr) error Attr(ctx context.Context, attr *fuse.Attr) error
} }
@ -105,9 +114,7 @@ type NodeSetattrer interface {
// Setattr sets the standard metadata for the receiver. // Setattr sets the standard metadata for the receiver.
// //
// Note, this is also used to communicate changes in the size of // Note, this is also used to communicate changes in the size of
// the file. Not implementing Setattr causes writes to be unable // the file, outside of Writes.
// to grow the file (except with OpenDirectIO, which bypasses that
// mechanism).
// //
// req.Valid is a bitmask of what fields are actually being set. // req.Valid is a bitmask of what fields are actually being set.
// For example, the method should not change the mode of the file // For example, the method should not change the mode of the file
@ -297,16 +304,17 @@ type HandleReader interface {
} }
type HandleWriter interface { type HandleWriter interface {
// Write requests to write data into the handle. // Write requests to write data into the handle at the given offset.
// Store the amount of data written in resp.Size.
// //
// There is a writeback page cache in the kernel that normally submits // There is a writeback page cache in the kernel that normally submits
// only page-aligned writes spanning one or more pages. However, // only page-aligned writes spanning one or more pages. However,
// you should not rely on this. To see individual requests as // you should not rely on this. To see individual requests as
// submitted by the file system clients, set OpenDirectIO. // submitted by the file system clients, set OpenDirectIO.
// //
// Note that file size changes are communicated through Setattr. // Writes that grow the file are expected to update the file size
// Writes beyond the size of the file as reported by Attr are not // (as seen through Attr). Note that file size changes are
// even attempted (except in OpenDirectIO mode). // communicated also through Setattr.
Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error
} }
@ -322,11 +330,13 @@ type Config struct {
// See fuse.Debug for the rules that log functions must follow. // See fuse.Debug for the rules that log functions must follow.
Debug func(msg interface{}) Debug func(msg interface{})
// Function to create new contexts. If nil, use // Function to put things into context for processing the request.
// context.Background. // The returned context must have ctx as its parent.
// //
// Note that changing this may not affect existing calls to Serve. // Note that changing this may not affect existing calls to Serve.
GetContext func() context.Context //
// Must not retain req.
WithContext func(ctx context.Context, req fuse.Request) context.Context
} }
// New returns a new FUSE server ready to serve this kernel FUSE // New returns a new FUSE server ready to serve this kernel FUSE
@ -342,14 +352,11 @@ func New(conn *fuse.Conn, config *Config) *Server {
} }
if config != nil { if config != nil {
s.debug = config.Debug s.debug = config.Debug
s.context = config.GetContext s.context = config.WithContext
} }
if s.debug == nil { if s.debug == nil {
s.debug = fuse.Debug s.debug = fuse.Debug
} }
if s.context == nil {
s.context = context.Background
}
return s return s
} }
@ -357,7 +364,7 @@ type Server struct {
// set in New // set in New
conn *fuse.Conn conn *fuse.Conn
debug func(msg interface{}) debug func(msg interface{})
context func() context.Context context func(ctx context.Context, req fuse.Request) context.Context
// set once at Serve time // set once at Serve time
fs FS fs FS
@ -494,7 +501,7 @@ func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64)
} }
sn.generation = c.nodeGen sn.generation = c.nodeGen
c.nodeRef[node] = id c.nodeRef[node] = id
return return id, sn.generation
} }
func (c *Server) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) { func (c *Server) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) {
@ -601,7 +608,7 @@ type logResponseHeader struct {
} }
func (m logResponseHeader) String() string { func (m logResponseHeader) String() string {
return fmt.Sprintf("ID=%#x", m.ID) return fmt.Sprintf("ID=%v", m.ID)
} }
type response struct { type response struct {
@ -626,21 +633,21 @@ func (r response) errstr() string {
func (r response) String() string { func (r response) String() string {
switch { switch {
case r.Errno != "" && r.Out != nil: case r.Errno != "" && r.Out != nil:
return fmt.Sprintf("-> %s error=%s %s", r.Request, r.errstr(), r.Out) return fmt.Sprintf("-> [%v] %v error=%s", r.Request, r.Out, r.errstr())
case r.Errno != "": case r.Errno != "":
return fmt.Sprintf("-> %s error=%s", r.Request, r.errstr()) return fmt.Sprintf("-> [%v] %s error=%s", r.Request, r.Op, r.errstr())
case r.Out != nil: case r.Out != nil:
// make sure (seemingly) empty values are readable // make sure (seemingly) empty values are readable
switch r.Out.(type) { switch r.Out.(type) {
case string: case string:
return fmt.Sprintf("-> %s %q", r.Request, r.Out) return fmt.Sprintf("-> [%v] %s %q", r.Request, r.Op, r.Out)
case []byte: case []byte:
return fmt.Sprintf("-> %s [% x]", r.Request, r.Out) return fmt.Sprintf("-> [%v] %s [% x]", r.Request, r.Op, r.Out)
default: default:
return fmt.Sprintf("-> %s %s", r.Request, r.Out) return fmt.Sprintf("-> [%v] %v", r.Request, r.Out)
} }
default: default:
return fmt.Sprintf("-> %s", r.Request) return fmt.Sprintf("-> [%v] %s", r.Request, r.Op)
} }
} }
@ -652,20 +659,23 @@ type notification struct {
} }
func (n notification) String() string { func (n notification) String() string {
switch { var buf bytes.Buffer
case n.Out != nil: fmt.Fprintf(&buf, "=> %s %v", n.Op, n.Node)
if n.Out != nil {
// make sure (seemingly) empty values are readable // make sure (seemingly) empty values are readable
switch n.Out.(type) { switch n.Out.(type) {
case string: case string:
return fmt.Sprintf("=> %s %d %q Err:%v", n.Op, n.Node, n.Out, n.Err) fmt.Fprintf(&buf, " %q", n.Out)
case []byte: case []byte:
return fmt.Sprintf("=> %s %d [% x] Err:%v", n.Op, n.Node, n.Out, n.Err) fmt.Fprintf(&buf, " [% x]", n.Out)
default: default:
return fmt.Sprintf("=> %s %d %s Err:%v", n.Op, n.Node, n.Out, n.Err) fmt.Fprintf(&buf, " %s", n.Out)
} }
default:
return fmt.Sprintf("=> %s %d Err:%v", n.Op, n.Node, n.Err)
} }
if n.Err != "" {
fmt.Fprintf(&buf, " Err:%v", n.Err)
}
return buf.String()
} }
type logMissingNode struct { type logMissingNode struct {
@ -685,7 +695,7 @@ type logLinkRequestOldNodeNotFound struct {
} }
func (m *logLinkRequestOldNodeNotFound) String() string { func (m *logLinkRequestOldNodeNotFound) String() string {
return fmt.Sprintf("In LinkRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.OldNode) return fmt.Sprintf("In LinkRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.OldNode)
} }
type renameNewDirNodeNotFound struct { type renameNewDirNodeNotFound struct {
@ -694,7 +704,7 @@ type renameNewDirNodeNotFound struct {
} }
func (m *renameNewDirNodeNotFound) String() string { func (m *renameNewDirNodeNotFound) String() string {
return fmt.Sprintf("In RenameRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.NewDir) return fmt.Sprintf("In RenameRequest (request %v), node %d not found", m.Request.Hdr().ID, m.In.NewDir)
} }
type handlerPanickedError struct { type handlerPanickedError struct {
@ -717,13 +727,52 @@ func (h handlerPanickedError) Errno() fuse.Errno {
return fuse.DefaultErrno return fuse.DefaultErrno
} }
// handlerTerminatedError happens when a handler terminates itself
// with runtime.Goexit. This is most commonly because of incorrect use
// of testing.TB.FailNow, typically via t.Fatal.
type handlerTerminatedError struct {
Request interface{}
}
var _ error = handlerTerminatedError{}
func (h handlerTerminatedError) Error() string {
return fmt.Sprintf("handler terminated (called runtime.Goexit)")
}
var _ fuse.ErrorNumber = handlerTerminatedError{}
func (h handlerTerminatedError) Errno() fuse.Errno {
return fuse.DefaultErrno
}
type handleNotReaderError struct {
handle Handle
}
var _ error = handleNotReaderError{}
func (e handleNotReaderError) Error() string {
return fmt.Sprintf("handle has no Read: %T", e.handle)
}
var _ fuse.ErrorNumber = handleNotReaderError{}
func (e handleNotReaderError) Errno() fuse.Errno {
return fuse.ENOTSUP
}
func initLookupResponse(s *fuse.LookupResponse) { func initLookupResponse(s *fuse.LookupResponse) {
s.EntryValid = entryValidTime s.EntryValid = entryValidTime
} }
func (c *Server) serve(r fuse.Request) { func (c *Server) serve(r fuse.Request) {
ctx, cancel := context.WithCancel(c.context()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
parentCtx := ctx
if c.context != nil {
ctx = c.context(ctx, r)
}
req := &serveRequest{Request: r, cancel: cancel} req := &serveRequest{Request: r, cancel: cancel}
@ -800,6 +849,7 @@ func (c *Server) serve(r fuse.Request) {
c.meta.Unlock() c.meta.Unlock()
} }
var responded bool
defer func() { defer func() {
if rec := recover(); rec != nil { if rec := recover(); rec != nil {
const size = 1 << 16 const size = 1 << 16
@ -813,114 +863,132 @@ func (c *Server) serve(r fuse.Request) {
} }
done(err) done(err)
r.RespondError(err) r.RespondError(err)
return
}
if !responded {
err := handlerTerminatedError{
Request: r,
}
done(err)
r.RespondError(err)
} }
}() }()
if err := c.handleRequest(ctx, node, snode, r, done); err != nil {
if err == context.Canceled {
select {
case <-parentCtx.Done():
// We canceled the parent context because of an
// incoming interrupt request, so return EINTR
// to trigger the right behavior in the client app.
//
// Only do this when it's the parent context that was
// canceled, not a context controlled by the program
// using this library, so we don't return EINTR too
// eagerly -- it might cause busy loops.
//
// Decent write-up on role of EINTR:
// http://250bpm.com/blog:12
err = fuse.EINTR
default:
// nothing
}
}
done(err)
r.RespondError(err)
}
// disarm runtime.Goexit protection
responded = true
}
// handleRequest will either a) call done(s) and r.Respond(s) OR b) return an error.
func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode, r fuse.Request, done func(resp interface{})) error {
switch r := r.(type) { switch r := r.(type) {
default: default:
// Note: To FUSE, ENOSYS means "this server never implements this request." // Note: To FUSE, ENOSYS means "this server never implements this request."
// It would be inappropriate to return ENOSYS for other operations in this // It would be inappropriate to return ENOSYS for other operations in this
// switch that might only be unavailable in some contexts, not all. // switch that might only be unavailable in some contexts, not all.
done(fuse.ENOSYS) return fuse.ENOSYS
r.RespondError(fuse.ENOSYS)
case *fuse.StatfsRequest: case *fuse.StatfsRequest:
s := &fuse.StatfsResponse{} s := &fuse.StatfsResponse{}
if fs, ok := c.fs.(FSStatfser); ok { if fs, ok := c.fs.(FSStatfser); ok {
if err := fs.Statfs(ctx, r, s); err != nil { if err := fs.Statfs(ctx, r, s); err != nil {
done(err) return err
r.RespondError(err)
break
} }
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
// Node operations. // Node operations.
case *fuse.GetattrRequest: case *fuse.GetattrRequest:
s := &fuse.GetattrResponse{} s := &fuse.GetattrResponse{}
if n, ok := node.(NodeGetattrer); ok { if n, ok := node.(NodeGetattrer); ok {
if err := n.Getattr(ctx, r, s); err != nil { if err := n.Getattr(ctx, r, s); err != nil {
done(err) return err
r.RespondError(err)
break
} }
} else { } else {
if err := snode.attr(ctx, &s.Attr); err != nil { if err := snode.attr(ctx, &s.Attr); err != nil {
done(err) return err
r.RespondError(err)
break
} }
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.SetattrRequest: case *fuse.SetattrRequest:
s := &fuse.SetattrResponse{} s := &fuse.SetattrResponse{}
if n, ok := node.(NodeSetattrer); ok { if n, ok := node.(NodeSetattrer); ok {
if err := n.Setattr(ctx, r, s); err != nil { if err := n.Setattr(ctx, r, s); err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(s)
r.Respond(s)
break
} }
if err := snode.attr(ctx, &s.Attr); err != nil { if err := snode.attr(ctx, &s.Attr); err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.SymlinkRequest: case *fuse.SymlinkRequest:
s := &fuse.SymlinkResponse{} s := &fuse.SymlinkResponse{}
initLookupResponse(&s.LookupResponse) initLookupResponse(&s.LookupResponse)
n, ok := node.(NodeSymlinker) n, ok := node.(NodeSymlinker)
if !ok { if !ok {
done(fuse.EIO) // XXX or EPERM like Mkdir? return fuse.EIO // XXX or EPERM like Mkdir?
r.RespondError(fuse.EIO)
break
} }
n2, err := n.Symlink(ctx, r) n2, err := n.Symlink(ctx, r)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil { if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.ReadlinkRequest: case *fuse.ReadlinkRequest:
n, ok := node.(NodeReadlinker) n, ok := node.(NodeReadlinker)
if !ok { if !ok {
done(fuse.EIO) /// XXX or EPERM? return fuse.EIO /// XXX or EPERM?
r.RespondError(fuse.EIO)
break
} }
target, err := n.Readlink(ctx, r) target, err := n.Readlink(ctx, r)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(target) done(target)
r.Respond(target) r.Respond(target)
return nil
case *fuse.LinkRequest: case *fuse.LinkRequest:
n, ok := node.(NodeLinker) n, ok := node.(NodeLinker)
if !ok { if !ok {
done(fuse.EIO) /// XXX or EPERM? return fuse.EIO /// XXX or EPERM?
r.RespondError(fuse.EIO)
break
} }
c.meta.Lock() c.meta.Lock()
var oldNode *serveNode var oldNode *serveNode
@ -933,52 +1001,43 @@ func (c *Server) serve(r fuse.Request) {
Request: r.Hdr(), Request: r.Hdr(),
In: r, In: r,
}) })
done(fuse.EIO) return fuse.EIO
r.RespondError(fuse.EIO)
break
} }
n2, err := n.Link(ctx, r, oldNode.node) n2, err := n.Link(ctx, r, oldNode.node)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
s := &fuse.LookupResponse{} s := &fuse.LookupResponse{}
initLookupResponse(s) initLookupResponse(s)
if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil { if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.RemoveRequest: case *fuse.RemoveRequest:
n, ok := node.(NodeRemover) n, ok := node.(NodeRemover)
if !ok { if !ok {
done(fuse.EIO) /// XXX or EPERM? return fuse.EIO /// XXX or EPERM?
r.RespondError(fuse.EIO)
break
} }
err := n.Remove(ctx, r) err := n.Remove(ctx, r)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
case *fuse.AccessRequest: case *fuse.AccessRequest:
if n, ok := node.(NodeAccesser); ok { if n, ok := node.(NodeAccesser); ok {
if err := n.Access(ctx, r); err != nil { if err := n.Access(ctx, r); err != nil {
done(err) return err
r.RespondError(err)
break
} }
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
case *fuse.LookupRequest: case *fuse.LookupRequest:
var n2 Node var n2 Node
@ -990,45 +1049,35 @@ func (c *Server) serve(r fuse.Request) {
} else if n, ok := node.(NodeRequestLookuper); ok { } else if n, ok := node.(NodeRequestLookuper); ok {
n2, err = n.Lookup(ctx, r, s) n2, err = n.Lookup(ctx, r, s)
} else { } else {
done(fuse.ENOENT) return fuse.ENOENT
r.RespondError(fuse.ENOENT)
break
} }
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.MkdirRequest: case *fuse.MkdirRequest:
s := &fuse.MkdirResponse{} s := &fuse.MkdirResponse{}
initLookupResponse(&s.LookupResponse) initLookupResponse(&s.LookupResponse)
n, ok := node.(NodeMkdirer) n, ok := node.(NodeMkdirer)
if !ok { if !ok {
done(fuse.EPERM) return fuse.EPERM
r.RespondError(fuse.EPERM)
break
} }
n2, err := n.Mkdir(ctx, r) n2, err := n.Mkdir(ctx, r)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.OpenRequest: case *fuse.OpenRequest:
s := &fuse.OpenResponse{} s := &fuse.OpenResponse{}
@ -1036,121 +1085,99 @@ func (c *Server) serve(r fuse.Request) {
if n, ok := node.(NodeOpener); ok { if n, ok := node.(NodeOpener); ok {
hh, err := n.Open(ctx, r, s) hh, err := n.Open(ctx, r, s)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
h2 = hh h2 = hh
} else { } else {
h2 = node h2 = node
} }
s.Handle = c.saveHandle(h2, hdr.Node) s.Handle = c.saveHandle(h2, r.Hdr().Node)
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.CreateRequest: case *fuse.CreateRequest:
n, ok := node.(NodeCreater) n, ok := node.(NodeCreater)
if !ok { if !ok {
// If we send back ENOSYS, FUSE will try mknod+open. // If we send back ENOSYS, FUSE will try mknod+open.
done(fuse.EPERM) return fuse.EPERM
r.RespondError(fuse.EPERM)
break
} }
s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}} s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}}
initLookupResponse(&s.LookupResponse) initLookupResponse(&s.LookupResponse)
n2, h2, err := n.Create(ctx, r, s) n2, h2, err := n.Create(ctx, r, s)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil {
done(err) return err
r.RespondError(err)
break
} }
s.Handle = c.saveHandle(h2, hdr.Node) s.Handle = c.saveHandle(h2, r.Hdr().Node)
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.GetxattrRequest: case *fuse.GetxattrRequest:
n, ok := node.(NodeGetxattrer) n, ok := node.(NodeGetxattrer)
if !ok { if !ok {
done(fuse.ENOTSUP) return fuse.ENOTSUP
r.RespondError(fuse.ENOTSUP)
break
} }
s := &fuse.GetxattrResponse{} s := &fuse.GetxattrResponse{}
err := n.Getxattr(ctx, r, s) err := n.Getxattr(ctx, r, s)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
done(fuse.ERANGE) return fuse.ERANGE
r.RespondError(fuse.ERANGE)
break
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.ListxattrRequest: case *fuse.ListxattrRequest:
n, ok := node.(NodeListxattrer) n, ok := node.(NodeListxattrer)
if !ok { if !ok {
done(fuse.ENOTSUP) return fuse.ENOTSUP
r.RespondError(fuse.ENOTSUP)
break
} }
s := &fuse.ListxattrResponse{} s := &fuse.ListxattrResponse{}
err := n.Listxattr(ctx, r, s) err := n.Listxattr(ctx, r, s)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) {
done(fuse.ERANGE) return fuse.ERANGE
r.RespondError(fuse.ERANGE)
break
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.SetxattrRequest: case *fuse.SetxattrRequest:
n, ok := node.(NodeSetxattrer) n, ok := node.(NodeSetxattrer)
if !ok { if !ok {
done(fuse.ENOTSUP) return fuse.ENOTSUP
r.RespondError(fuse.ENOTSUP)
break
} }
err := n.Setxattr(ctx, r) err := n.Setxattr(ctx, r)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
case *fuse.RemovexattrRequest: case *fuse.RemovexattrRequest:
n, ok := node.(NodeRemovexattrer) n, ok := node.(NodeRemovexattrer)
if !ok { if !ok {
done(fuse.ENOTSUP) return fuse.ENOTSUP
r.RespondError(fuse.ENOTSUP)
break
} }
err := n.Removexattr(ctx, r) err := n.Removexattr(ctx, r)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
case *fuse.ForgetRequest: case *fuse.ForgetRequest:
forget := c.dropNode(hdr.Node, r.N) forget := c.dropNode(r.Hdr().Node, r.N)
if forget { if forget {
n, ok := node.(NodeForgetter) n, ok := node.(NodeForgetter)
if ok { if ok {
@ -1159,26 +1186,29 @@ func (c *Server) serve(r fuse.Request) {
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
// Handle operations. // Handle operations.
case *fuse.ReadRequest: case *fuse.ReadRequest:
shandle := c.getHandle(r.Handle) shandle := c.getHandle(r.Handle)
if shandle == nil { if shandle == nil {
done(fuse.ESTALE) return fuse.ESTALE
r.RespondError(fuse.ESTALE)
return
} }
handle := shandle.handle handle := shandle.handle
s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)} s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)}
if r.Dir { if r.Dir {
if h, ok := handle.(HandleReadDirAller); ok { if h, ok := handle.(HandleReadDirAller); ok {
// detect rewinddir(3) or similar seek and refresh
// contents
if r.Offset == 0 {
shandle.readData = nil
}
if shandle.readData == nil { if shandle.readData == nil {
dirs, err := h.ReadDirAll(ctx) dirs, err := h.ReadDirAll(ctx)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
var data []byte var data []byte
for _, dir := range dirs { for _, dir := range dirs {
@ -1192,16 +1222,14 @@ func (c *Server) serve(r fuse.Request) {
fuseutil.HandleRead(r, s, shandle.readData) fuseutil.HandleRead(r, s, shandle.readData)
done(s) done(s)
r.Respond(s) r.Respond(s)
break return nil
} }
} else { } else {
if h, ok := handle.(HandleReadAller); ok { if h, ok := handle.(HandleReadAller); ok {
if shandle.readData == nil { if shandle.readData == nil {
data, err := h.ReadAll(ctx) data, err := h.ReadAll(ctx)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
if data == nil { if data == nil {
data = []byte{} data = []byte{}
@ -1211,71 +1239,58 @@ func (c *Server) serve(r fuse.Request) {
fuseutil.HandleRead(r, s, shandle.readData) fuseutil.HandleRead(r, s, shandle.readData)
done(s) done(s)
r.Respond(s) r.Respond(s)
break return nil
} }
h, ok := handle.(HandleReader) h, ok := handle.(HandleReader)
if !ok { if !ok {
fmt.Printf("NO READ FOR %T\n", handle) err := handleNotReaderError{handle: handle}
done(fuse.EIO) return err
r.RespondError(fuse.EIO)
break
} }
if err := h.Read(ctx, r, s); err != nil { if err := h.Read(ctx, r, s); err != nil {
done(err) return err
r.RespondError(err)
break
} }
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.WriteRequest: case *fuse.WriteRequest:
shandle := c.getHandle(r.Handle) shandle := c.getHandle(r.Handle)
if shandle == nil { if shandle == nil {
done(fuse.ESTALE) return fuse.ESTALE
r.RespondError(fuse.ESTALE)
return
} }
s := &fuse.WriteResponse{} s := &fuse.WriteResponse{}
if h, ok := shandle.handle.(HandleWriter); ok { if h, ok := shandle.handle.(HandleWriter); ok {
if err := h.Write(ctx, r, s); err != nil { if err := h.Write(ctx, r, s); err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
break return nil
} }
done(fuse.EIO) return fuse.EIO
r.RespondError(fuse.EIO)
case *fuse.FlushRequest: case *fuse.FlushRequest:
shandle := c.getHandle(r.Handle) shandle := c.getHandle(r.Handle)
if shandle == nil { if shandle == nil {
done(fuse.ESTALE) return fuse.ESTALE
r.RespondError(fuse.ESTALE)
return
} }
handle := shandle.handle handle := shandle.handle
if h, ok := handle.(HandleFlusher); ok { if h, ok := handle.(HandleFlusher); ok {
if err := h.Flush(ctx, r); err != nil { if err := h.Flush(ctx, r); err != nil {
done(err) return err
r.RespondError(err)
break
} }
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
case *fuse.ReleaseRequest: case *fuse.ReleaseRequest:
shandle := c.getHandle(r.Handle) shandle := c.getHandle(r.Handle)
if shandle == nil { if shandle == nil {
done(fuse.ESTALE) return fuse.ESTALE
r.RespondError(fuse.ESTALE)
return
} }
handle := shandle.handle handle := shandle.handle
@ -1284,13 +1299,12 @@ func (c *Server) serve(r fuse.Request) {
if h, ok := handle.(HandleReleaser); ok { if h, ok := handle.(HandleReleaser); ok {
if err := h.Release(ctx, r); err != nil { if err := h.Release(ctx, r); err != nil {
done(err) return err
r.RespondError(err)
break
} }
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
case *fuse.DestroyRequest: case *fuse.DestroyRequest:
if fs, ok := c.fs.(FSDestroyer); ok { if fs, ok := c.fs.(FSDestroyer); ok {
@ -1298,6 +1312,7 @@ func (c *Server) serve(r fuse.Request) {
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
case *fuse.RenameRequest: case *fuse.RenameRequest:
c.meta.Lock() c.meta.Lock()
@ -1311,63 +1326,50 @@ func (c *Server) serve(r fuse.Request) {
Request: r.Hdr(), Request: r.Hdr(),
In: r, In: r,
}) })
done(fuse.EIO) return fuse.EIO
r.RespondError(fuse.EIO)
break
} }
n, ok := node.(NodeRenamer) n, ok := node.(NodeRenamer)
if !ok { if !ok {
done(fuse.EIO) // XXX or EPERM like Mkdir? return fuse.EIO // XXX or EPERM like Mkdir?
r.RespondError(fuse.EIO)
break
} }
err := n.Rename(ctx, r, newDirNode.node) err := n.Rename(ctx, r, newDirNode.node)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
case *fuse.MknodRequest: case *fuse.MknodRequest:
n, ok := node.(NodeMknoder) n, ok := node.(NodeMknoder)
if !ok { if !ok {
done(fuse.EIO) return fuse.EIO
r.RespondError(fuse.EIO)
break
} }
n2, err := n.Mknod(ctx, r) n2, err := n.Mknod(ctx, r)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
s := &fuse.LookupResponse{} s := &fuse.LookupResponse{}
initLookupResponse(s) initLookupResponse(s)
if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
return nil
case *fuse.FsyncRequest: case *fuse.FsyncRequest:
n, ok := node.(NodeFsyncer) n, ok := node.(NodeFsyncer)
if !ok { if !ok {
done(fuse.EIO) return fuse.EIO
r.RespondError(fuse.EIO)
break
} }
err := n.Fsync(ctx, r) err := n.Fsync(ctx, r)
if err != nil { if err != nil {
done(err) return err
r.RespondError(err)
break
} }
done(nil) done(nil)
r.Respond() r.Respond()
return nil
case *fuse.InterruptRequest: case *fuse.InterruptRequest:
c.meta.Lock() c.meta.Lock()
@ -1379,24 +1381,23 @@ func (c *Server) serve(r fuse.Request) {
c.meta.Unlock() c.meta.Unlock()
done(nil) done(nil)
r.Respond() r.Respond()
return nil
/* case *FsyncdirRequest: /* case *FsyncdirRequest:
done(ENOSYS) return ENOSYS
r.RespondError(ENOSYS)
case *GetlkRequest, *SetlkRequest, *SetlkwRequest: case *GetlkRequest, *SetlkRequest, *SetlkwRequest:
done(ENOSYS) return ENOSYS
r.RespondError(ENOSYS)
case *BmapRequest: case *BmapRequest:
done(ENOSYS) return ENOSYS
r.RespondError(ENOSYS)
case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest: case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest:
done(ENOSYS) return ENOSYS
r.RespondError(ENOSYS)
*/ */
} }
panic("not reached")
} }
func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error { func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error {

View file

@ -0,0 +1,30 @@
package fs_test
import (
"testing"
"bazil.org/fuse/fs/fstestutil"
"golang.org/x/sys/unix"
)
type exchangeData struct {
fstestutil.File
// this struct cannot be zero size or multiple instances may look identical
_ int
}
func TestExchangeDataNotSupported(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{
"one": &exchangeData{},
"two": &exchangeData{},
}}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
if err := unix.Exchangedata(mnt.Dir+"/one", mnt.Dir+"/two", 0); err != unix.ENOTSUP {
t.Fatalf("expected ENOTSUP from exchangedata: %v", err)
}
}

View file

@ -7,10 +7,11 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"os/exec" "path"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"syscall" "syscall"
"testing" "testing"
"time" "time"
@ -56,6 +57,25 @@ func (f fifo) Attr(ctx context.Context, a *fuse.Attr) error {
return nil return nil
} }
func TestMountpointDoesNotExist(t *testing.T) {
t.Parallel()
tmp, err := ioutil.TempDir("", "fusetest")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmp)
mountpoint := path.Join(tmp, "does-not-exist")
conn, err := fuse.Mount(mountpoint)
if err == nil {
conn.Close()
t.Fatalf("expected error with non-existent mountpoint")
}
if _, ok := err.(*fuse.MountpointDoesNotExistError); !ok {
t.Fatalf("wrong error from mount: %T: %v", err, err)
}
}
type badRootFS struct{} type badRootFS struct{}
func (badRootFS) Root() (fs.Node, error) { func (badRootFS) Root() (fs.Node, error) {
@ -277,12 +297,18 @@ func (readAll) ReadAll(ctx context.Context) ([]byte, error) {
} }
func testReadAll(t *testing.T, path string) { func testReadAll(t *testing.T, path string) {
data, err := ioutil.ReadFile(path) f, err := os.Open(path)
if err != nil { if err != nil {
t.Fatalf("readAll: %v", err) t.Fatal(err)
} }
if string(data) != hi { defer f.Close()
t.Errorf("readAll = %q, want %q", data, hi) data := make([]byte, 4096)
n, err := f.Read(data)
if err != nil {
t.Fatal(err)
}
if g, e := string(data[:n]), hi; g != e {
t.Errorf("readAll = %q, want %q", g, e)
} }
} }
@ -366,6 +392,15 @@ func TestReadFileFlags(t *testing.T) {
_ = f.Close() _ = f.Close()
want := fuse.OpenReadWrite | fuse.OpenAppend want := fuse.OpenReadWrite | fuse.OpenAppend
if runtime.GOOS == "darwin" {
// OSXFUSE shares one read and one write handle for all
// clients, so it uses a OpenReadOnly handle for performing
// our read.
//
// If this test starts failing in the future, that probably
// means they added the feature, and we want to notice that!
want = fuse.OpenReadOnly
}
if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), want; g != e { if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), want; g != e {
t.Errorf("read saw file flags %+v, want %+v", g, e) t.Errorf("read saw file flags %+v, want %+v", g, e)
} }
@ -382,6 +417,13 @@ func (r *writeFlags) Attr(ctx context.Context, a *fuse.Attr) error {
return nil return nil
} }
func (r *writeFlags) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
// OSXFUSE 3.0.4 does a read-modify-write cycle even when the
// write was for 4096 bytes.
fuseutil.HandleRead(req, resp, []byte(hi))
return nil
}
func (r *writeFlags) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { func (r *writeFlags) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
r.fileFlags.Record(req.FileFlags) r.fileFlags.Record(req.FileFlags)
resp.Size = len(req.Data) resp.Size = len(req.Data)
@ -412,6 +454,15 @@ func TestWriteFileFlags(t *testing.T) {
_ = f.Close() _ = f.Close()
want := fuse.OpenReadWrite | fuse.OpenAppend want := fuse.OpenReadWrite | fuse.OpenAppend
if runtime.GOOS == "darwin" {
// OSXFUSE shares one read and one write handle for all
// clients, so it uses a OpenWriteOnly handle for performing
// our read.
//
// If this test starts failing in the future, that probably
// means they added the feature, and we want to notice that!
want = fuse.OpenWriteOnly
}
if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), want; g != e { if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), want; g != e {
t.Errorf("write saw file flags %+v, want %+v", g, e) t.Errorf("write saw file flags %+v, want %+v", g, e)
} }
@ -582,7 +633,6 @@ func (f *mkdir1) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, er
} }
func TestMkdir(t *testing.T) { func TestMkdir(t *testing.T) {
t.Parallel()
f := &mkdir1{} f := &mkdir1{}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
if err != nil { if err != nil {
@ -601,6 +651,10 @@ func TestMkdir(t *testing.T) {
if mnt.Conn.Protocol().HasUmask() { if mnt.Conn.Protocol().HasUmask() {
want.Umask = 0022 want.Umask = 0022
} }
if runtime.GOOS == "darwin" {
// https://github.com/osxfuse/osxfuse/issues/225
want.Umask = 0
}
if g, e := f.RecordedMkdir(), want; g != e { if g, e := f.RecordedMkdir(), want; g != e {
t.Errorf("mkdir saw %+v, want %+v", g, e) t.Errorf("mkdir saw %+v, want %+v", g, e)
} }
@ -610,6 +664,7 @@ func TestMkdir(t *testing.T) {
type create1file struct { type create1file struct {
fstestutil.File fstestutil.File
record.Creates
record.Fsyncs record.Fsyncs
} }
@ -623,31 +678,12 @@ func (f *create1) Create(ctx context.Context, req *fuse.CreateRequest, resp *fus
log.Printf("ERROR create1.Create unexpected name: %q\n", req.Name) log.Printf("ERROR create1.Create unexpected name: %q\n", req.Name)
return nil, nil, fuse.EPERM return nil, nil, fuse.EPERM
} }
flags := req.Flags
// OS X does not pass O_TRUNC here, Linux does; as this is a _, _, _ = f.f.Creates.Create(ctx, req, resp)
// Create, that's acceptable
flags &^= fuse.OpenTruncate
if runtime.GOOS == "linux" {
// Linux <3.7 accidentally leaks O_CLOEXEC through to FUSE;
// avoid spurious test failures
flags &^= fuse.OpenFlags(syscall.O_CLOEXEC)
}
if g, e := flags, fuse.OpenReadWrite|fuse.OpenCreate; g != e {
log.Printf("ERROR create1.Create unexpected flags: %v != %v\n", g, e)
return nil, nil, fuse.EPERM
}
if g, e := req.Mode, os.FileMode(0644); g != e {
log.Printf("ERROR create1.Create unexpected mode: %v != %v\n", g, e)
return nil, nil, fuse.EPERM
}
return &f.f, &f.f, nil return &f.f, &f.f, nil
} }
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
t.Parallel()
f := &create1{} f := &create1{}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
if err != nil { if err != nil {
@ -658,12 +694,38 @@ func TestCreate(t *testing.T) {
// uniform umask needed to make os.Create's 0666 into something // uniform umask needed to make os.Create's 0666 into something
// reproducible // reproducible
defer syscall.Umask(syscall.Umask(0022)) defer syscall.Umask(syscall.Umask(0022))
ff, err := os.Create(mnt.Dir + "/foo") ff, err := os.OpenFile(mnt.Dir+"/foo", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0640)
if err != nil { if err != nil {
t.Fatalf("create1 WriteFile: %v", err) t.Fatalf("create1 WriteFile: %v", err)
} }
defer ff.Close() defer ff.Close()
want := fuse.CreateRequest{
Name: "foo",
Flags: fuse.OpenReadWrite | fuse.OpenCreate | fuse.OpenTruncate,
Mode: 0640,
}
if mnt.Conn.Protocol().HasUmask() {
want.Umask = 0022
}
if runtime.GOOS == "darwin" {
// OS X does not pass O_TRUNC here, Linux does; as this is a
// Create, that's acceptable
want.Flags &^= fuse.OpenTruncate
// https://github.com/osxfuse/osxfuse/issues/225
want.Umask = 0
}
got := f.f.RecordedCreate()
if runtime.GOOS == "linux" {
// Linux <3.7 accidentally leaks O_CLOEXEC through to FUSE;
// avoid spurious test failures
got.Flags &^= fuse.OpenFlags(syscall.O_CLOEXEC)
}
if g, e := got, want; g != e {
t.Fatalf("create saw %+v, want %+v", g, e)
}
err = syscall.Fsync(int(ff.Fd())) err = syscall.Fsync(int(ff.Fd()))
if err != nil { if err != nil {
t.Fatalf("Fsync = %v", err) t.Fatalf("Fsync = %v", err)
@ -895,7 +957,6 @@ func (f *mknod1) Mknod(ctx context.Context, r *fuse.MknodRequest) (fs.Node, erro
} }
func TestMknod(t *testing.T) { func TestMknod(t *testing.T) {
t.Parallel()
if os.Getuid() != 0 { if os.Getuid() != 0 {
t.Skip("skipping unless root") t.Skip("skipping unless root")
} }
@ -907,15 +968,15 @@ func TestMknod(t *testing.T) {
} }
defer mnt.Close() defer mnt.Close()
defer syscall.Umask(syscall.Umask(0)) defer syscall.Umask(syscall.Umask(0022))
err = syscall.Mknod(mnt.Dir+"/node", syscall.S_IFIFO|0666, 123) err = syscall.Mknod(mnt.Dir+"/node", syscall.S_IFIFO|0660, 123)
if err != nil { if err != nil {
t.Fatalf("Mknod: %v", err) t.Fatalf("mknod: %v", err)
} }
want := fuse.MknodRequest{ want := fuse.MknodRequest{
Name: "node", Name: "node",
Mode: os.FileMode(os.ModeNamedPipe | 0666), Mode: os.FileMode(os.ModeNamedPipe | 0640),
Rdev: uint32(123), Rdev: uint32(123),
} }
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
@ -924,6 +985,13 @@ func TestMknod(t *testing.T) {
// bit is portable.) // bit is portable.)
want.Rdev = 0 want.Rdev = 0
} }
if mnt.Conn.Protocol().HasUmask() {
want.Umask = 0022
}
if runtime.GOOS == "darwin" {
// https://github.com/osxfuse/osxfuse/issues/225
want.Umask = 0
}
if g, e := f.RecordedMknod(), want; g != e { if g, e := f.RecordedMknod(), want; g != e {
t.Fatalf("mknod saw %+v, want %+v", g, e) t.Fatalf("mknod saw %+v, want %+v", g, e)
} }
@ -985,7 +1053,38 @@ func (it *interrupt) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse
default: default:
} }
<-ctx.Done() <-ctx.Done()
return fuse.EINTR return ctx.Err()
}
func helperInterrupt() {
log.SetPrefix("interrupt child: ")
log.SetFlags(0)
log.Printf("starting...")
f, err := os.Open("child")
if err != nil {
log.Fatalf("cannot open file: %v", err)
}
defer f.Close()
log.Printf("reading...")
buf := make([]byte, 4096)
n, err := syscall.Read(int(f.Fd()), buf)
switch err {
case nil:
log.Fatalf("read: expected error, got data: %q", buf[:n])
case syscall.EINTR:
log.Printf("read: saw EINTR, all good")
default:
log.Fatalf("read: wrong error: %v", err)
}
log.Printf("exiting...")
}
func init() {
childHelpers["interrupt"] = helperInterrupt
} }
func TestInterrupt(t *testing.T) { func TestInterrupt(t *testing.T) {
@ -999,46 +1098,71 @@ func TestInterrupt(t *testing.T) {
defer mnt.Close() defer mnt.Close()
// start a subprocess that can hang until signaled // start a subprocess that can hang until signaled
cmd := exec.Command("cat", mnt.Dir+"/child") child, err := childCmd("interrupt")
err = cmd.Start()
if err != nil { if err != nil {
t.Errorf("interrupt: cannot start cat: %v", err) t.Fatal(err)
}
child.Dir = mnt.Dir
if err := child.Start(); err != nil {
t.Errorf("cannot start child: %v", err)
return return
} }
// try to clean up if child is still alive when returning // try to clean up if child is still alive when returning
defer cmd.Process.Kill() defer child.Process.Kill()
// wait till we're sure it's hanging in read // wait till we're sure it's hanging in read
<-f.hanging <-f.hanging
err = cmd.Process.Signal(os.Interrupt) // err = child.Process.Signal(os.Interrupt)
var sig os.Signal = syscall.SIGIO
if runtime.GOOS == "darwin" {
// I can't get OSXFUSE 3.2.0 to trigger EINTR return from
// read(2), at least in a Go application. Works on Linux. So,
// on OS X, we just check that the signal at least kills the
// child, aborting the read, so operations on hanging FUSE
// filesystems can be aborted.
sig = os.Interrupt
}
err = child.Process.Signal(sig)
if err != nil { if err != nil {
t.Errorf("interrupt: cannot interrupt cat: %v", err) t.Errorf("cannot interrupt child: %v", err)
return return
} }
p, err := cmd.Process.Wait() p, err := child.Process.Wait()
if err != nil { if err != nil {
t.Errorf("interrupt: cat bork: %v", err) t.Errorf("child failed: %v", err)
return return
} }
switch ws := p.Sys().(type) { switch ws := p.Sys().(type) {
case syscall.WaitStatus: case syscall.WaitStatus:
if ws.CoreDump() { if ws.CoreDump() {
t.Errorf("interrupt: didn't expect cat to dump core: %v", ws) t.Fatalf("interrupt: didn't expect child to dump core: %v", ws)
} }
switch runtime.GOOS {
case "darwin":
// see comment above about EINTR on OS X
if ws.Exited() { if ws.Exited() {
t.Errorf("interrupt: didn't expect cat to exit normally: %v", ws) t.Fatalf("interrupt: expected child to die from signal, got exit status: %v", ws.ExitStatus())
} }
if !ws.Signaled() { if !ws.Signaled() {
t.Errorf("interrupt: expected cat to get a signal: %v", ws) t.Fatalf("interrupt: expected child to die from signal: %v", ws)
} else { }
if ws.Signal() != os.Interrupt { if got := ws.Signal(); got != sig {
t.Errorf("interrupt: cat got wrong signal: %v", ws) t.Errorf("interrupt: child failed: signal %d", got)
}
default:
if ws.Signaled() {
t.Fatalf("interrupt: didn't expect child to exit with a signal: %v", ws)
}
if !ws.Exited() {
t.Fatalf("interrupt: expected child to exit normally: %v", ws)
}
if status := ws.ExitStatus(); status != 0 {
t.Errorf("interrupt: child failed: exit status %d", status)
} }
} }
default: default:
@ -1046,6 +1170,51 @@ func TestInterrupt(t *testing.T) {
} }
} }
// Test deadline
type deadline struct {
fstestutil.File
}
var _ fs.NodeOpener = (*deadline)(nil)
func (it *deadline) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
<-ctx.Done()
return nil, ctx.Err()
}
func TestDeadline(t *testing.T) {
t.Parallel()
child := &deadline{}
config := &fs.Config{
WithContext: func(ctx context.Context, req fuse.Request) context.Context {
// return a context that has already deadlined
// Server.serve will cancel the parent context, which will
// cancel this one, so discarding cancel here should be
// safe.
ctx, _ = context.WithDeadline(ctx, time.Unix(0, 0))
return ctx
},
}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, config)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
f, err := os.Open(mnt.Dir + "/child")
if err == nil {
f.Close()
}
// not caused by signal -> should not get EINTR;
// context.DeadlineExceeded will be translated into EIO
if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.EIO {
t.Fatalf("wrong error from deadline open: %T: %v", err, err)
}
}
// Test truncate // Test truncate
type truncate struct { type truncate struct {
@ -1224,6 +1393,58 @@ func TestReadDirAll(t *testing.T) {
} }
} }
type readDirAllBad struct {
fstestutil.Dir
}
func (d *readDirAllBad) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
r := []fuse.Dirent{
{Name: "one", Inode: 11, Type: fuse.DT_Dir},
{Name: "three", Inode: 13},
{Name: "two", Inode: 12, Type: fuse.DT_File},
}
// pick a really distinct error, to identify it later
return r, fuse.Errno(syscall.ENAMETOOLONG)
}
func TestReadDirAllBad(t *testing.T) {
t.Parallel()
f := &readDirAllBad{}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
fil, err := os.Open(mnt.Dir)
if err != nil {
t.Error(err)
return
}
defer fil.Close()
var names []string
for {
n, err := fil.Readdirnames(1)
if err != nil {
if nerr, ok := err.(*os.SyscallError); !ok || nerr.Err != syscall.ENAMETOOLONG {
t.Fatalf("wrong error: %v", err)
}
break
}
names = append(names, n...)
}
t.Logf("Got readdir: %q", names)
// TODO could serve partial results from ReadDirAll but the
// shandle.readData mechanism makes that awkward.
if len(names) != 0 {
t.Errorf(`expected 0 entries, got: %q`, names)
return
}
}
// Test readdir without any ReadDir methods implemented. // Test readdir without any ReadDir methods implemented.
type readDirNotImplemented struct { type readDirNotImplemented struct {
@ -1255,6 +1476,73 @@ func TestReadDirNotImplemented(t *testing.T) {
} }
} }
type readDirAllRewind struct {
fstestutil.Dir
entries atomic.Value
}
func (d *readDirAllRewind) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
entries := d.entries.Load().([]fuse.Dirent)
return entries, nil
}
func TestReadDirAllRewind(t *testing.T) {
t.Parallel()
f := &readDirAllRewind{}
f.entries.Store([]fuse.Dirent{
{Name: "one", Inode: 11, Type: fuse.DT_Dir},
})
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
fil, err := os.Open(mnt.Dir)
if err != nil {
t.Error(err)
return
}
defer fil.Close()
{
names, err := fil.Readdirnames(100)
if err != nil {
t.Error(err)
return
}
t.Logf("Got readdir: %q", names)
if len(names) != 1 ||
names[0] != "one" {
t.Errorf(`expected entry of "one", got: %q`, names)
return
}
}
f.entries.Store([]fuse.Dirent{
{Name: "two", Inode: 12, Type: fuse.DT_File},
{Name: "one", Inode: 11, Type: fuse.DT_Dir},
})
if _, err := fil.Seek(0, os.SEEK_SET); err != nil {
t.Fatal(err)
}
{
names, err := fil.Readdirnames(100)
if err != nil {
t.Error(err)
return
}
t.Logf("Got readdir: %q", names)
if len(names) != 2 ||
names[0] != "two" ||
names[1] != "one" {
t.Errorf(`expected 2 entries of "two", "one", got: %q`, names)
return
}
}
}
// Test Chmod. // Test Chmod.
type chmod struct { type chmod struct {
@ -1364,6 +1652,10 @@ func (f *openNonSeekable) Open(ctx context.Context, req *fuse.OpenRequest, resp
} }
func TestOpenNonSeekable(t *testing.T) { func TestOpenNonSeekable(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skip("OSXFUSE shares one read and one write handle for all clients, does not support open modes")
}
t.Parallel() t.Parallel()
f := &openNonSeekable{} f := &openNonSeekable{}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
@ -2109,8 +2401,20 @@ func TestInvalidateNodeAttr(t *testing.T) {
t.Fatalf("stat error: %v", err) t.Fatalf("stat error: %v", err)
} }
} }
if g, e := a.attr.Count(), uint32(1); g != e { // With OSXFUSE 3.0.4, we seem to see typically two Attr calls by
t.Errorf("wrong Attr call count: %d != %d", g, e) // this point; something not populating the in-kernel cache
// properly? Cope with it; we care more about seeing a new Attr
// call after the invalidation.
//
// We still enforce a max number here so that we know that the
// invalidate actually did something, and it's not just that every
// Stat results in an Attr.
before := a.attr.Count()
if before == 0 {
t.Error("no Attr call seen")
}
if g, e := before, uint32(3); g > e {
t.Errorf("too many Attr calls seen: %d > %d", g, e)
} }
t.Logf("invalidating...") t.Logf("invalidating...")
@ -2123,7 +2427,7 @@ func TestInvalidateNodeAttr(t *testing.T) {
t.Fatalf("stat error: %v", err) t.Fatalf("stat error: %v", err)
} }
} }
if g, e := a.attr.Count(), uint32(2); g != e { if g, e := a.attr.Count(), before+1; g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e) t.Errorf("wrong Attr call count: %d != %d", g, e)
} }
} }
@ -2133,9 +2437,13 @@ type invalidateData struct {
t testing.TB t testing.TB
attr record.Counter attr record.Counter
read record.Counter read record.Counter
data atomic.Value
} }
const invalidateDataContent = "hello, world\n" const (
invalidateDataContent1 = "hello, world\n"
invalidateDataContent2 = "so long!\n"
)
var _ fs.Node = (*invalidateData)(nil) var _ fs.Node = (*invalidateData)(nil)
@ -2143,7 +2451,7 @@ func (i *invalidateData) Attr(ctx context.Context, a *fuse.Attr) error {
i.attr.Inc() i.attr.Inc()
i.t.Logf("Attr called, #%d", i.attr.Count()) i.t.Logf("Attr called, #%d", i.attr.Count())
a.Mode = 0600 a.Mode = 0600
a.Size = uint64(len(invalidateDataContent)) a.Size = uint64(len(i.data.Load().(string)))
return nil return nil
} }
@ -2152,17 +2460,18 @@ var _ fs.HandleReader = (*invalidateData)(nil)
func (i *invalidateData) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { func (i *invalidateData) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
i.read.Inc() i.read.Inc()
i.t.Logf("Read called, #%d", i.read.Count()) i.t.Logf("Read called, #%d", i.read.Count())
fuseutil.HandleRead(req, resp, []byte(invalidateDataContent)) fuseutil.HandleRead(req, resp, []byte(i.data.Load().(string)))
return nil return nil
} }
func TestInvalidateNodeData(t *testing.T) { func TestInvalidateNodeDataInvalidatesAttr(t *testing.T) {
// This test may see false positive failures when run under // This test may see false positive failures when run under
// extreme memory pressure. // extreme memory pressure.
t.Parallel() t.Parallel()
a := &invalidateData{ a := &invalidateData{
t: t, t: t,
} }
a.data.Store(invalidateDataContent1)
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -2179,31 +2488,93 @@ func TestInvalidateNodeData(t *testing.T) {
} }
defer f.Close() defer f.Close()
buf := make([]byte, 4) attrBefore := a.attr.Count()
if g, min := attrBefore, uint32(1); g < min {
t.Errorf("wrong Attr call count: %d < %d", g, min)
}
t.Logf("invalidating...")
a.data.Store(invalidateDataContent2)
if err := mnt.Server.InvalidateNodeData(a); err != nil {
t.Fatalf("invalidate error: %v", err)
}
// on OSXFUSE 3.0.6, the Attr has already triggered here, so don't
// check the count at this point
if _, err := f.Stat(); err != nil {
t.Errorf("stat error: %v", err)
}
if g, prev := a.attr.Count(), attrBefore; g <= prev {
t.Errorf("did not see Attr call after invalidate: %d <= %d", g, prev)
}
}
func TestInvalidateNodeDataInvalidatesData(t *testing.T) {
// This test may see false positive failures when run under
// extreme memory pressure.
t.Parallel()
a := &invalidateData{
t: t,
}
a.data.Store(invalidateDataContent1)
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
if !mnt.Conn.Protocol().HasInvalidate() {
t.Skip("Old FUSE protocol")
}
f, err := os.Open(mnt.Dir + "/child")
if err != nil {
t.Fatal(err)
}
defer f.Close()
{
buf := make([]byte, 100)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, 0); err != nil { n, err := f.ReadAt(buf, 0)
if err != nil && err != io.EOF {
t.Fatalf("readat error: %v", err) t.Fatalf("readat error: %v", err)
} }
if g, e := string(buf[:n]), invalidateDataContent1; g != e {
t.Errorf("wrong content: %q != %q", g, e)
}
} }
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
} }
if g, e := a.read.Count(), uint32(1); g != e { if g, e := a.read.Count(), uint32(1); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e) t.Errorf("wrong Read call count: %d != %d", g, e)
} }
t.Logf("invalidating...") t.Logf("invalidating...")
a.data.Store(invalidateDataContent2)
if err := mnt.Server.InvalidateNodeData(a); err != nil { if err := mnt.Server.InvalidateNodeData(a); err != nil {
t.Fatalf("invalidate error: %v", err) t.Fatalf("invalidate error: %v", err)
} }
if g, e := a.read.Count(), uint32(1); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
}
{
// explicitly don't cross the EOF, to trigger more edge cases
// (Linux will always do Getattr if you cross what it believes
// the EOF to be)
const bufSize = len(invalidateDataContent2) - 3
buf := make([]byte, bufSize)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, 0); err != nil { n, err := f.ReadAt(buf, 0)
if err != nil && err != io.EOF {
t.Fatalf("readat error: %v", err) t.Fatalf("readat error: %v", err)
} }
if g, e := string(buf[:n]), invalidateDataContent2[:bufSize]; g != e {
t.Errorf("wrong content: %q != %q", g, e)
}
} }
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
} }
if g, e := a.read.Count(), uint32(2); g != e { if g, e := a.read.Count(), uint32(2); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e) t.Errorf("wrong Read call count: %d != %d", g, e)
@ -2238,7 +2609,7 @@ func (i *invalidateDataPartial) Read(ctx context.Context, req *fuse.ReadRequest,
return nil return nil
} }
func TestInvalidateNodeDataRange(t *testing.T) { func TestInvalidateNodeDataRangeMiss(t *testing.T) {
// This test may see false positive failures when run under // This test may see false positive failures when run under
// extreme memory pressure. // extreme memory pressure.
t.Parallel() t.Parallel()
@ -2267,14 +2638,11 @@ func TestInvalidateNodeDataRange(t *testing.T) {
t.Fatalf("readat error: %v", err) t.Fatalf("readat error: %v", err)
} }
} }
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
}
if g, e := a.read.Count(), uint32(1); g != e { if g, e := a.read.Count(), uint32(1); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e) t.Errorf("wrong Read call count: %d != %d", g, e)
} }
t.Logf("invalidating...") t.Logf("invalidating an uninteresting block...")
if err := mnt.Server.InvalidateNodeDataRange(a, 4096, 4096); err != nil { if err := mnt.Server.InvalidateNodeDataRange(a, 4096, 4096); err != nil {
t.Fatalf("invalidate error: %v", err) t.Fatalf("invalidate error: %v", err)
} }
@ -2284,9 +2652,6 @@ func TestInvalidateNodeDataRange(t *testing.T) {
t.Fatalf("readat error: %v", err) t.Fatalf("readat error: %v", err)
} }
} }
if g, e := a.attr.Count(), uint32(1); g != e {
t.Errorf("wrong Attr call count: %d != %d", g, e)
}
// The page invalidated is not the page we're reading, so it // The page invalidated is not the page we're reading, so it
// should stay in cache. // should stay in cache.
if g, e := a.read.Count(), uint32(1); g != e { if g, e := a.read.Count(), uint32(1); g != e {
@ -2294,6 +2659,56 @@ func TestInvalidateNodeDataRange(t *testing.T) {
} }
} }
func TestInvalidateNodeDataRangeHit(t *testing.T) {
// This test may see false positive failures when run under
// extreme memory pressure.
t.Parallel()
a := &invalidateDataPartial{
t: t,
}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
if !mnt.Conn.Protocol().HasInvalidate() {
t.Skip("Old FUSE protocol")
}
f, err := os.Open(mnt.Dir + "/child")
if err != nil {
t.Fatal(err)
}
defer f.Close()
const offset = 4096
buf := make([]byte, 4)
for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, offset); err != nil {
t.Fatalf("readat error: %v", err)
}
}
if g, e := a.read.Count(), uint32(1); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
}
t.Logf("invalidating where the reads are...")
if err := mnt.Server.InvalidateNodeDataRange(a, offset, 4096); err != nil {
t.Fatalf("invalidate error: %v", err)
}
for i := 0; i < 10; i++ {
if _, err := f.ReadAt(buf, offset); err != nil {
t.Fatalf("readat error: %v", err)
}
}
// One new read
if g, e := a.read.Count(), uint32(2); g != e {
t.Errorf("wrong Read call count: %d != %d", g, e)
}
}
type invalidateEntryRoot struct { type invalidateEntryRoot struct {
fs.NodeRef fs.NodeRef
t testing.TB t testing.TB
@ -2380,13 +2795,13 @@ func (contextFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.O
func TestContext(t *testing.T) { func TestContext(t *testing.T) {
t.Parallel() t.Parallel()
ctx := context.Background()
const input = "kilroy was here" const input = "kilroy was here"
ctx = context.WithValue(ctx, &contextFileSentinel, input)
mnt, err := fstestutil.MountedT(t, mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{&fstestutil.ChildMap{"child": contextFile{}}}, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": contextFile{}}},
&fs.Config{ &fs.Config{
GetContext: func() context.Context { return ctx }, WithContext: func(ctx context.Context, req fuse.Request) context.Context {
return context.WithValue(ctx, &contextFileSentinel, input)
},
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -2401,3 +2816,28 @@ func TestContext(t *testing.T) {
t.Errorf("read wrong data: %q != %q", g, e) t.Errorf("read wrong data: %q != %q", g, e)
} }
} }
type goexitFile struct {
fstestutil.File
}
func (goexitFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
log.Println("calling runtime.Goexit...")
runtime.Goexit()
panic("not reached")
}
func TestGoexit(t *testing.T) {
t.Parallel()
mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{&fstestutil.ChildMap{"child": goexitFile{}}}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
_, err = ioutil.ReadFile(mnt.Dir + "/child")
if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.EIO {
t.Fatalf("wrong error from exiting handler: %T: %v", err, err)
}
}

View file

@ -132,6 +132,18 @@ type Conn struct {
proto Protocol proto Protocol
} }
// MountpointDoesNotExistError is an error returned when the
// mountpoint does not exist.
type MountpointDoesNotExistError struct {
Path string
}
var _ error = (*MountpointDoesNotExistError)(nil)
func (e *MountpointDoesNotExistError) Error() string {
return fmt.Sprintf("mountpoint does not exist: %v", e.Path)
}
// Mount mounts a new FUSE connection on the named directory // Mount mounts a new FUSE connection on the named directory
// and returns a connection for reading and writing FUSE messages. // and returns a connection for reading and writing FUSE messages.
// //
@ -164,6 +176,13 @@ func Mount(dir string, options ...MountOption) (*Conn, error) {
if err := initMount(c, &conf); err != nil { if err := initMount(c, &conf); err != nil {
c.Close() c.Close()
if err == ErrClosedWithoutInit {
// see if we can provide a better error
<-c.Ready
if err := c.MountError; err != nil {
return nil, err
}
}
return nil, err return nil, err
} }
@ -179,11 +198,15 @@ func (e *OldVersionError) Error() string {
return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin) return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin)
} }
var (
ErrClosedWithoutInit = errors.New("fuse connection closed without init")
)
func initMount(c *Conn, conf *mountConfig) error { func initMount(c *Conn, conf *mountConfig) error {
req, err := c.ReadRequest() req, err := c.ReadRequest()
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
return fmt.Errorf("missing init, got EOF") return ErrClosedWithoutInit
} }
return err return err
} }
@ -212,7 +235,7 @@ func initMount(c *Conn, conf *mountConfig) error {
s := &InitResponse{ s := &InitResponse{
Library: proto, Library: proto,
MaxReadahead: conf.maxReadahead, MaxReadahead: conf.maxReadahead,
MaxWrite: 128 * 1024, MaxWrite: maxWrite,
Flags: InitBigWrites | conf.initFlags, Flags: InitBigWrites | conf.initFlags,
} }
r.Respond(s) r.Respond(s)
@ -235,15 +258,27 @@ type Request interface {
// A RequestID identifies an active FUSE request. // A RequestID identifies an active FUSE request.
type RequestID uint64 type RequestID uint64
func (r RequestID) String() string {
return fmt.Sprintf("%#x", uint64(r))
}
// A NodeID is a number identifying a directory or file. // A NodeID is a number identifying a directory or file.
// It must be unique among IDs returned in LookupResponses // It must be unique among IDs returned in LookupResponses
// that have not yet been forgotten by ForgetRequests. // that have not yet been forgotten by ForgetRequests.
type NodeID uint64 type NodeID uint64
func (n NodeID) String() string {
return fmt.Sprintf("%#x", uint64(n))
}
// A HandleID is a number identifying an open directory or file. // A HandleID is a number identifying an open directory or file.
// It only needs to be unique while the directory or file is open. // It only needs to be unique while the directory or file is open.
type HandleID uint64 type HandleID uint64
func (h HandleID) String() string {
return fmt.Sprintf("%#x", uint64(h))
}
// The RootID identifies the root directory of a FUSE file system. // The RootID identifies the root directory of a FUSE file system.
const RootID NodeID = rootID const RootID NodeID = rootID
@ -261,7 +296,7 @@ type Header struct {
} }
func (h *Header) String() string { func (h *Header) String() string {
return fmt.Sprintf("ID=%#x Node=%#x Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid) return fmt.Sprintf("ID=%v Node=%v Uid=%d Gid=%d Pid=%d", h.ID, h.Node, h.Uid, h.Gid, h.Pid)
} }
func (h *Header) Hdr() *Header { func (h *Header) Hdr() *Header {
@ -368,9 +403,6 @@ func (h *Header) RespondError(err error) {
h.respond(buf) h.respond(buf)
} }
// Maximum file write size we are prepared to receive from the kernel.
const maxWrite = 16 * 1024 * 1024
// All requests read from the kernel, without data, are shorter than // All requests read from the kernel, without data, are shorter than
// this. // this.
var maxRequestSize = syscall.Getpagesize() var maxRequestSize = syscall.Getpagesize()
@ -985,7 +1017,33 @@ loop:
case opGetxtimes: case opGetxtimes:
panic("opGetxtimes") panic("opGetxtimes")
case opExchange: case opExchange:
panic("opExchange") in := (*exchangeIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
oldDirNodeID := NodeID(in.Olddir)
newDirNodeID := NodeID(in.Newdir)
oldNew := m.bytes()[unsafe.Sizeof(*in):]
// oldNew should be "oldname\x00newname\x00"
if len(oldNew) < 4 {
goto corrupt
}
if oldNew[len(oldNew)-1] != '\x00' {
goto corrupt
}
i := bytes.IndexByte(oldNew, '\x00')
if i < 0 {
goto corrupt
}
oldName, newName := string(oldNew[:i]), string(oldNew[i+1:len(oldNew)-1])
req = &ExchangeDataRequest{
Header: m.Header(),
OldDir: oldDirNodeID,
NewDir: newDirNodeID,
OldName: oldName,
NewName: newName,
// TODO options
}
} }
return req, nil return req, nil
@ -1147,7 +1205,7 @@ type InitRequest struct {
var _ = Request(&InitRequest{}) var _ = Request(&InitRequest{})
func (r *InitRequest) String() string { func (r *InitRequest) String() string {
return fmt.Sprintf("Init [%s] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags) return fmt.Sprintf("Init [%v] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags)
} }
// An InitResponse is the response to an InitRequest. // An InitResponse is the response to an InitRequest.
@ -1163,7 +1221,7 @@ type InitResponse struct {
} }
func (r *InitResponse) String() string { func (r *InitResponse) String() string {
return fmt.Sprintf("Init %+v", *r) return fmt.Sprintf("Init %v ra=%d fl=%v w=%d", r.Library, r.MaxReadahead, r.Flags, r.MaxWrite)
} }
// Respond replies to the request with the given response. // Respond replies to the request with the given response.
@ -1224,7 +1282,13 @@ type StatfsResponse struct {
} }
func (r *StatfsResponse) String() string { func (r *StatfsResponse) String() string {
return fmt.Sprintf("Statfs %+v", *r) return fmt.Sprintf("Statfs blocks=%d/%d/%d files=%d/%d bsize=%d frsize=%d namelen=%d",
r.Bavail, r.Bfree, r.Blocks,
r.Ffree, r.Files,
r.Bsize,
r.Frsize,
r.Namelen,
)
} }
// An AccessRequest asks whether the file can be accessed // An AccessRequest asks whether the file can be accessed
@ -1259,7 +1323,7 @@ type Attr struct {
Ctime time.Time // time of last inode change Ctime time.Time // time of last inode change
Crtime time.Time // time of creation (OS X only) Crtime time.Time // time of creation (OS X only)
Mode os.FileMode // file mode Mode os.FileMode // file mode
Nlink uint32 // number of links Nlink uint32 // number of links (usually 1)
Uid uint32 // owner uid Uid uint32 // owner uid
Gid uint32 // group gid Gid uint32 // group gid
Rdev uint32 // device numbers Rdev uint32 // device numbers
@ -1267,6 +1331,10 @@ type Attr struct {
BlockSize uint32 // preferred blocksize for filesystem I/O BlockSize uint32 // preferred blocksize for filesystem I/O
} }
func (a Attr) String() string {
return fmt.Sprintf("valid=%v ino=%v size=%d mode=%v", a.Valid, a.Inode, a.Size, a.Mode)
}
func unix(t time.Time) (sec uint64, nsec uint32) { func unix(t time.Time) (sec uint64, nsec uint32) {
nano := t.UnixNano() nano := t.UnixNano()
sec = uint64(nano / 1e9) sec = uint64(nano / 1e9)
@ -1329,7 +1397,7 @@ type GetattrRequest struct {
var _ = Request(&GetattrRequest{}) var _ = Request(&GetattrRequest{})
func (r *GetattrRequest) String() string { func (r *GetattrRequest) String() string {
return fmt.Sprintf("Getattr [%s] %#x fl=%v", &r.Header, r.Handle, r.Flags) return fmt.Sprintf("Getattr [%s] %v fl=%v", &r.Header, r.Handle, r.Flags)
} }
// Respond replies to the request with the given response. // Respond replies to the request with the given response.
@ -1349,7 +1417,7 @@ type GetattrResponse struct {
} }
func (r *GetattrResponse) String() string { func (r *GetattrResponse) String() string {
return fmt.Sprintf("Getattr %+v", *r) return fmt.Sprintf("Getattr %v", r.Attr)
} }
// A GetxattrRequest asks for the extended attributes associated with r.Node. // A GetxattrRequest asks for the extended attributes associated with r.Node.
@ -1540,8 +1608,12 @@ type LookupResponse struct {
Attr Attr Attr Attr
} }
func (r *LookupResponse) string() string {
return fmt.Sprintf("%v gen=%d valid=%v attr={%v}", r.Node, r.Generation, r.EntryValid, r.Attr)
}
func (r *LookupResponse) String() string { func (r *LookupResponse) String() string {
return fmt.Sprintf("Lookup %+v", *r) return fmt.Sprintf("Lookup %s", r.string())
} }
// An OpenRequest asks to open a file or directory // An OpenRequest asks to open a file or directory
@ -1572,8 +1644,12 @@ type OpenResponse struct {
Flags OpenResponseFlags Flags OpenResponseFlags
} }
func (r *OpenResponse) string() string {
return fmt.Sprintf("%v fl=%v", r.Handle, r.Flags)
}
func (r *OpenResponse) String() string { func (r *OpenResponse) String() string {
return fmt.Sprintf("Open %+v", *r) return fmt.Sprintf("Open %s", r.string())
} }
// A CreateRequest asks to create and open a file (not a directory). // A CreateRequest asks to create and open a file (not a directory).
@ -1582,6 +1658,7 @@ type CreateRequest struct {
Name string Name string
Flags OpenFlags Flags OpenFlags
Mode os.FileMode Mode os.FileMode
// Umask of the request. Not supported on OS X.
Umask os.FileMode Umask os.FileMode
} }
@ -1620,7 +1697,7 @@ type CreateResponse struct {
} }
func (r *CreateResponse) String() string { func (r *CreateResponse) String() string {
return fmt.Sprintf("Create %+v", *r) return fmt.Sprintf("Create {%s} {%s}", r.LookupResponse.string(), r.OpenResponse.string())
} }
// A MkdirRequest asks to create (but not open) a directory. // A MkdirRequest asks to create (but not open) a directory.
@ -1628,6 +1705,7 @@ type MkdirRequest struct {
Header `json:"-"` Header `json:"-"`
Name string Name string
Mode os.FileMode Mode os.FileMode
// Umask of the request. Not supported on OS X.
Umask os.FileMode Umask os.FileMode
} }
@ -1658,7 +1736,7 @@ type MkdirResponse struct {
} }
func (r *MkdirResponse) String() string { func (r *MkdirResponse) String() string {
return fmt.Sprintf("Mkdir %+v", *r) return fmt.Sprintf("Mkdir %v", r.LookupResponse.string())
} }
// A ReadRequest asks to read from an open file. // A ReadRequest asks to read from an open file.
@ -1676,7 +1754,7 @@ type ReadRequest struct {
var _ = Request(&ReadRequest{}) var _ = Request(&ReadRequest{})
func (r *ReadRequest) String() string { func (r *ReadRequest) String() string {
return fmt.Sprintf("Read [%s] %#x %d @%#x dir=%v fl=%v lock=%d ffl=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir, r.Flags, r.LockOwner, r.FileFlags) return fmt.Sprintf("Read [%s] %v %d @%#x dir=%v fl=%v lock=%d ffl=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir, r.Flags, r.LockOwner, r.FileFlags)
} }
// Respond replies to the request with the given response. // Respond replies to the request with the given response.
@ -1719,7 +1797,7 @@ type ReleaseRequest struct {
var _ = Request(&ReleaseRequest{}) var _ = Request(&ReleaseRequest{})
func (r *ReleaseRequest) String() string { func (r *ReleaseRequest) String() string {
return fmt.Sprintf("Release [%s] %#x fl=%v rfl=%v owner=%#x", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner) return fmt.Sprintf("Release [%s] %v fl=%v rfl=%v owner=%#x", &r.Header, r.Handle, r.Flags, r.ReleaseFlags, r.LockOwner)
} }
// Respond replies to the request, indicating that the handle has been released. // Respond replies to the request, indicating that the handle has been released.
@ -1861,7 +1939,7 @@ type WriteRequest struct {
var _ = Request(&WriteRequest{}) var _ = Request(&WriteRequest{})
func (r *WriteRequest) String() string { func (r *WriteRequest) String() string {
return fmt.Sprintf("Write [%s] %#x %d @%d fl=%v lock=%d ffl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags, r.LockOwner, r.FileFlags) return fmt.Sprintf("Write [%s] %v %d @%d fl=%v lock=%d ffl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags, r.LockOwner, r.FileFlags)
} }
type jsonWriteRequest struct { type jsonWriteRequest struct {
@ -1895,7 +1973,7 @@ type WriteResponse struct {
} }
func (r *WriteResponse) String() string { func (r *WriteResponse) String() string {
return fmt.Sprintf("Write %+v", *r) return fmt.Sprintf("Write %d", r.Size)
} }
// A SetattrRequest asks to change one or more attributes associated with a file, // A SetattrRequest asks to change one or more attributes associated with a file,
@ -1948,9 +2026,9 @@ func (r *SetattrRequest) String() string {
fmt.Fprintf(&buf, " mtime=now") fmt.Fprintf(&buf, " mtime=now")
} }
if r.Valid.Handle() { if r.Valid.Handle() {
fmt.Fprintf(&buf, " handle=%#x", r.Handle) fmt.Fprintf(&buf, " handle=%v", r.Handle)
} else { } else {
fmt.Fprintf(&buf, " handle=INVALID-%#x", r.Handle) fmt.Fprintf(&buf, " handle=INVALID-%v", r.Handle)
} }
if r.Valid.LockOwner() { if r.Valid.LockOwner() {
fmt.Fprintf(&buf, " lockowner") fmt.Fprintf(&buf, " lockowner")
@ -1965,7 +2043,7 @@ func (r *SetattrRequest) String() string {
fmt.Fprintf(&buf, " bkuptime=%v", r.Bkuptime) fmt.Fprintf(&buf, " bkuptime=%v", r.Bkuptime)
} }
if r.Valid.Flags() { if r.Valid.Flags() {
fmt.Fprintf(&buf, " flags=%#x", r.Flags) fmt.Fprintf(&buf, " flags=%v", r.Flags)
} }
return buf.String() return buf.String()
} }
@ -1988,7 +2066,7 @@ type SetattrResponse struct {
} }
func (r *SetattrResponse) String() string { func (r *SetattrResponse) String() string {
return fmt.Sprintf("Setattr %+v", *r) return fmt.Sprintf("Setattr %v", r.Attr)
} }
// A FlushRequest asks for the current state of an open file to be flushed // A FlushRequest asks for the current state of an open file to be flushed
@ -2004,7 +2082,7 @@ type FlushRequest struct {
var _ = Request(&FlushRequest{}) var _ = Request(&FlushRequest{})
func (r *FlushRequest) String() string { func (r *FlushRequest) String() string {
return fmt.Sprintf("Flush [%s] %#x fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner) return fmt.Sprintf("Flush [%s] %v fl=%#x lk=%#x", &r.Header, r.Handle, r.Flags, r.LockOwner)
} }
// Respond replies to the request, indicating that the flush succeeded. // Respond replies to the request, indicating that the flush succeeded.
@ -2065,6 +2143,10 @@ type SymlinkResponse struct {
LookupResponse LookupResponse
} }
func (r *SymlinkResponse) String() string {
return fmt.Sprintf("Symlink %v", r.LookupResponse.string())
}
// A ReadlinkRequest is a request to read a symlink's target. // A ReadlinkRequest is a request to read a symlink's target.
type ReadlinkRequest struct { type ReadlinkRequest struct {
Header `json:"-"` Header `json:"-"`
@ -2119,7 +2201,7 @@ type RenameRequest struct {
var _ = Request(&RenameRequest{}) var _ = Request(&RenameRequest{})
func (r *RenameRequest) String() string { func (r *RenameRequest) String() string {
return fmt.Sprintf("Rename [%s] from %q to dirnode %d %q", &r.Header, r.OldName, r.NewDir, r.NewName) return fmt.Sprintf("Rename [%s] from %q to dirnode %v %q", &r.Header, r.OldName, r.NewDir, r.NewName)
} }
func (r *RenameRequest) Respond() { func (r *RenameRequest) Respond() {
@ -2132,6 +2214,7 @@ type MknodRequest struct {
Name string Name string
Mode os.FileMode Mode os.FileMode
Rdev uint32 Rdev uint32
// Umask of the request. Not supported on OS X.
Umask os.FileMode Umask os.FileMode
} }
@ -2191,3 +2274,30 @@ func (r *InterruptRequest) Respond() {
func (r *InterruptRequest) String() string { func (r *InterruptRequest) String() string {
return fmt.Sprintf("Interrupt [%s] ID %v", &r.Header, r.IntrID) return fmt.Sprintf("Interrupt [%s] ID %v", &r.Header, r.IntrID)
} }
// An ExchangeDataRequest is a request to exchange the contents of two
// files, while leaving most metadata untouched.
//
// This request comes from OS X exchangedata(2) and represents its
// specific semantics. Crucially, it is very different from Linux
// renameat(2) RENAME_EXCHANGE.
//
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html
type ExchangeDataRequest struct {
Header `json:"-"`
OldDir, NewDir NodeID
OldName, NewName string
// TODO options
}
var _ = Request(&ExchangeDataRequest{})
func (r *ExchangeDataRequest) String() string {
// TODO options
return fmt.Sprintf("ExchangeData [%s] %v %q and %v %q", &r.Header, r.OldDir, r.OldName, r.NewDir, r.NewName)
}
func (r *ExchangeDataRequest) Respond() {
buf := newBuffer(0)
r.respond(buf)
}

View file

@ -0,0 +1,9 @@
package fuse
// Maximum file write size we are prepared to receive from the kernel.
//
// This value has to be >=16MB or OSXFUSE (3.4.0 observed) will
// forcibly close the /dev/fuse file descriptor on a Setxattr with a
// 16MB value. See TestSetxattr16MB and
// https://github.com/bazil/fuse/issues/42
const maxWrite = 16 * 1024 * 1024

View file

@ -0,0 +1,6 @@
package fuse
// Maximum file write size we are prepared to receive from the kernel.
//
// This number is just a guess.
const maxWrite = 128 * 1024

View file

@ -62,7 +62,7 @@ type kstatfs struct {
Bsize uint32 Bsize uint32
Namelen uint32 Namelen uint32
Frsize uint32 Frsize uint32
Padding uint32 _ uint32
Spare [6]uint32 Spare [6]uint32
} }
@ -159,9 +159,13 @@ const (
OpenWriteOnly OpenFlags = syscall.O_WRONLY OpenWriteOnly OpenFlags = syscall.O_WRONLY
OpenReadWrite OpenFlags = syscall.O_RDWR OpenReadWrite OpenFlags = syscall.O_RDWR
// File was opened in append-only mode, all writes will go to end
// of file. OS X does not provide this information.
OpenAppend OpenFlags = syscall.O_APPEND OpenAppend OpenFlags = syscall.O_APPEND
OpenCreate OpenFlags = syscall.O_CREAT OpenCreate OpenFlags = syscall.O_CREAT
OpenDirectory OpenFlags = syscall.O_DIRECTORY
OpenExclusive OpenFlags = syscall.O_EXCL OpenExclusive OpenFlags = syscall.O_EXCL
OpenNonblock OpenFlags = syscall.O_NONBLOCK
OpenSync OpenFlags = syscall.O_SYNC OpenSync OpenFlags = syscall.O_SYNC
OpenTruncate OpenFlags = syscall.O_TRUNC OpenTruncate OpenFlags = syscall.O_TRUNC
) )
@ -213,11 +217,13 @@ func accModeName(flags OpenFlags) string {
} }
var openFlagNames = []flagName{ var openFlagNames = []flagName{
{uint32(OpenCreate), "OpenCreate"},
{uint32(OpenExclusive), "OpenExclusive"},
{uint32(OpenTruncate), "OpenTruncate"},
{uint32(OpenAppend), "OpenAppend"}, {uint32(OpenAppend), "OpenAppend"},
{uint32(OpenCreate), "OpenCreate"},
{uint32(OpenDirectory), "OpenDirectory"},
{uint32(OpenExclusive), "OpenExclusive"},
{uint32(OpenNonblock), "OpenNonblock"},
{uint32(OpenSync), "OpenSync"}, {uint32(OpenSync), "OpenSync"},
{uint32(OpenTruncate), "OpenTruncate"},
} }
// The OpenResponseFlags are returned in the OpenResponse. // The OpenResponseFlags are returned in the OpenResponse.
@ -254,6 +260,7 @@ const (
InitAtomicTrunc InitFlags = 1 << 3 InitAtomicTrunc InitFlags = 1 << 3
InitExportSupport InitFlags = 1 << 4 InitExportSupport InitFlags = 1 << 4
InitBigWrites InitFlags = 1 << 5 InitBigWrites InitFlags = 1 << 5
// Do not mask file access modes with umask. Not supported on OS X.
InitDontMask InitFlags = 1 << 6 InitDontMask InitFlags = 1 << 6
InitSpliceWrite InitFlags = 1 << 7 InitSpliceWrite InitFlags = 1 << 7
InitSpliceMove InitFlags = 1 << 8 InitSpliceMove InitFlags = 1 << 8
@ -412,14 +419,14 @@ type forgetIn struct {
type getattrIn struct { type getattrIn struct {
GetattrFlags uint32 GetattrFlags uint32
dummy uint32 _ uint32
Fh uint64 Fh uint64
} }
type attrOut struct { type attrOut struct {
AttrValid uint64 // Cache timeout for the attributes AttrValid uint64 // Cache timeout for the attributes
AttrValidNsec uint32 AttrValidNsec uint32
Dummy uint32 _ uint32
Attr attr Attr attr
} }
@ -444,7 +451,7 @@ type mknodIn struct {
Mode uint32 Mode uint32
Rdev uint32 Rdev uint32
Umask uint32 Umask uint32
padding uint32 _ uint32
// "filename\x00" follows. // "filename\x00" follows.
} }
@ -482,6 +489,7 @@ type exchangeIn struct {
Olddir uint64 Olddir uint64
Newdir uint64 Newdir uint64
Options uint64 Options uint64
// "oldname\x00newname\x00" follows
} }
type linkIn struct { type linkIn struct {
@ -490,7 +498,7 @@ type linkIn struct {
type setattrInCommon struct { type setattrInCommon struct {
Valid uint32 Valid uint32
Padding uint32 _ uint32
Fh uint64 Fh uint64
Size uint64 Size uint64
LockOwner uint64 // unused on OS X? LockOwner uint64 // unused on OS X?
@ -515,14 +523,14 @@ type openIn struct {
type openOut struct { type openOut struct {
Fh uint64 Fh uint64
OpenFlags uint32 OpenFlags uint32
Padding uint32 _ uint32
} }
type createIn struct { type createIn struct {
Flags uint32 Flags uint32
Mode uint32 Mode uint32
Umask uint32 Umask uint32
padding uint32 _ uint32
} }
func createInSize(p Protocol) uintptr { func createInSize(p Protocol) uintptr {
@ -544,7 +552,7 @@ type releaseIn struct {
type flushIn struct { type flushIn struct {
Fh uint64 Fh uint64
FlushFlags uint32 FlushFlags uint32
Padding uint32 _ uint32
LockOwner uint64 LockOwner uint64
} }
@ -555,7 +563,7 @@ type readIn struct {
ReadFlags uint32 ReadFlags uint32
LockOwner uint64 LockOwner uint64
Flags uint32 Flags uint32
padding uint32 _ uint32
} }
func readInSize(p Protocol) uintptr { func readInSize(p Protocol) uintptr {
@ -590,7 +598,7 @@ type writeIn struct {
WriteFlags uint32 WriteFlags uint32
LockOwner uint64 LockOwner uint64
Flags uint32 Flags uint32
padding uint32 _ uint32
} }
func writeInSize(p Protocol) uintptr { func writeInSize(p Protocol) uintptr {
@ -604,7 +612,7 @@ func writeInSize(p Protocol) uintptr {
type writeOut struct { type writeOut struct {
Size uint32 Size uint32
Padding uint32 _ uint32
} }
// The WriteFlags are passed in WriteRequest. // The WriteFlags are passed in WriteRequest.
@ -634,7 +642,7 @@ type statfsOut struct {
type fsyncIn struct { type fsyncIn struct {
Fh uint64 Fh uint64
FsyncFlags uint32 FsyncFlags uint32
Padding uint32 _ uint32
} }
type setxattrInCommon struct { type setxattrInCommon struct {
@ -648,7 +656,7 @@ func (setxattrInCommon) position() uint32 {
type getxattrInCommon struct { type getxattrInCommon struct {
Size uint32 Size uint32
Padding uint32 _ uint32
} }
func (getxattrInCommon) position() uint32 { func (getxattrInCommon) position() uint32 {
@ -657,7 +665,7 @@ func (getxattrInCommon) position() uint32 {
type getxattrOut struct { type getxattrOut struct {
Size uint32 Size uint32
Padding uint32 _ uint32
} }
type lkIn struct { type lkIn struct {
@ -665,7 +673,7 @@ type lkIn struct {
Owner uint64 Owner uint64
Lk fileLock Lk fileLock
LkFlags uint32 LkFlags uint32
padding uint32 _ uint32
} }
func lkInSize(p Protocol) uintptr { func lkInSize(p Protocol) uintptr {
@ -683,7 +691,7 @@ type lkOut struct {
type accessIn struct { type accessIn struct {
Mask uint32 Mask uint32
Padding uint32 _ uint32
} }
type initIn struct { type initIn struct {
@ -711,7 +719,7 @@ type interruptIn struct {
type bmapIn struct { type bmapIn struct {
Block uint64 Block uint64
BlockSize uint32 BlockSize uint32
Padding uint32 _ uint32
} }
type bmapOut struct { type bmapOut struct {
@ -726,7 +734,7 @@ type inHeader struct {
Uid uint32 Uid uint32
Gid uint32 Gid uint32
Pid uint32 Pid uint32
Padding uint32 _ uint32
} }
const inHeaderSize = int(unsafe.Sizeof(inHeader{})) const inHeaderSize = int(unsafe.Sizeof(inHeader{}))
@ -762,5 +770,5 @@ type notifyInvalInodeOut struct {
type notifyInvalEntryOut struct { type notifyInvalEntryOut struct {
Parent uint64 Parent uint64
Namelen uint32 Namelen uint32
padding uint32 _ uint32
} }

View file

@ -7,7 +7,7 @@ import (
"bazil.org/fuse" "bazil.org/fuse"
) )
func TestOpenFlagsAccmodeMask(t *testing.T) { func TestOpenFlagsAccmodeMaskReadWrite(t *testing.T) {
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC) var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC)
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e { if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; g != e {
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e) t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
@ -23,6 +23,38 @@ func TestOpenFlagsAccmodeMask(t *testing.T) {
} }
} }
func TestOpenFlagsAccmodeMaskReadOnly(t *testing.T) {
var f = fuse.OpenFlags(os.O_RDONLY | os.O_SYNC)
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadOnly; g != e {
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
}
if !f.IsReadOnly() {
t.Fatalf("IsReadOnly is wrong: %v", f)
}
if f.IsWriteOnly() {
t.Fatalf("IsWriteOnly is wrong: %v", f)
}
if f.IsReadWrite() {
t.Fatalf("IsReadWrite is wrong: %v", f)
}
}
func TestOpenFlagsAccmodeMaskWriteOnly(t *testing.T) {
var f = fuse.OpenFlags(os.O_WRONLY | os.O_SYNC)
if g, e := f&fuse.OpenAccessModeMask, fuse.OpenWriteOnly; g != e {
t.Fatalf("OpenAccessModeMask behaves wrong: %v: %o != %o", f, g, e)
}
if f.IsReadOnly() {
t.Fatalf("IsReadOnly is wrong: %v", f)
}
if !f.IsWriteOnly() {
t.Fatalf("IsWriteOnly is wrong: %v", f)
}
if f.IsReadWrite() {
t.Fatalf("IsReadWrite is wrong: %v", f)
}
}
func TestOpenFlagsString(t *testing.T) { func TestOpenFlagsString(t *testing.T) {
var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND) var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND)
if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e { if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e {

View file

@ -0,0 +1,7 @@
package fuse
// Maximum file write size we are prepared to receive from the kernel.
//
// Linux 4.2.0 has been observed to cap this value at 128kB
// (FUSE_MAX_PAGES_PER_REQ=32, 4kB pages).
const maxWrite = 128 * 1024

38
vendor/src/bazil.org/fuse/mount.go vendored Normal file
View file

@ -0,0 +1,38 @@
package fuse
import (
"bufio"
"errors"
"io"
"log"
"sync"
)
var (
// ErrOSXFUSENotFound is returned from Mount when the OSXFUSE
// installation is not detected.
//
// Only happens on OS X. Make sure OSXFUSE is installed, or see
// OSXFUSELocations for customization.
ErrOSXFUSENotFound = errors.New("cannot locate OSXFUSE")
)
func neverIgnoreLine(line string) bool {
return false
}
func lineLogger(wg *sync.WaitGroup, prefix string, ignore func(line string) bool, r io.ReadCloser) {
defer wg.Done()
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
if ignore(line) {
continue
}
log.Printf("%s: %s", prefix, line)
}
if err := scanner.Err(); err != nil {
log.Printf("%s, error reading: %v", prefix, err)
}
}

View file

@ -1,22 +1,25 @@
package fuse package fuse
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path"
"strconv" "strconv"
"strings" "strings"
"sync"
"syscall" "syscall"
) )
var errNoAvail = errors.New("no available fuse devices") var (
errNoAvail = errors.New("no available fuse devices")
errNotLoaded = errors.New("osxfuse is not loaded")
)
var errNotLoaded = errors.New("osxfusefs is not loaded") func loadOSXFUSE(bin string) error {
cmd := exec.Command(bin)
func loadOSXFUSE() error {
cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs")
cmd.Dir = "/" cmd.Dir = "/"
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@ -24,11 +27,11 @@ func loadOSXFUSE() error {
return err return err
} }
func openOSXFUSEDev() (*os.File, error) { func openOSXFUSEDev(devPrefix string) (*os.File, error) {
var f *os.File var f *os.File
var err error var err error
for i := uint64(0); ; i++ { for i := uint64(0); ; i++ {
path := "/dev/osxfuse" + strconv.FormatUint(i, 10) path := devPrefix + strconv.FormatUint(i, 10)
f, err = os.OpenFile(path, os.O_RDWR, 0000) f, err = os.OpenFile(path, os.O_RDWR, 0000)
if os.IsNotExist(err) { if os.IsNotExist(err) {
if i == 0 { if i == 0 {
@ -52,9 +55,42 @@ func openOSXFUSEDev() (*os.File, error) {
} }
} }
func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error { func handleMountOSXFUSE(helperName string, errCh chan<- error) func(line string) (ignore bool) {
bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" var noMountpointPrefix = helperName + `: `
const noMountpointSuffix = `: No such file or directory`
return func(line string) (ignore bool) {
if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
// re-extract it from the error message in case some layer
// changed the path
mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
err := &MountpointDoesNotExistError{
Path: mountpoint,
}
select {
case errCh <- err:
return true
default:
// not the first error; fall back to logging it
return false
}
}
return false
}
}
// isBoringMountOSXFUSEError returns whether the Wait error is
// uninteresting; exit status 64 is.
func isBoringMountOSXFUSEError(err error) bool {
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 64 {
return true
}
}
return false
}
func callMount(bin string, daemonVar string, dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error {
for k, v := range conf.options { for k, v := range conf.options {
if strings.Contains(k, ",") || strings.Contains(v, ",") { if strings.Contains(k, ",") || strings.Contains(v, ",") {
// Silly limitation but the mount helper does not // Silly limitation but the mount helper does not
@ -77,50 +113,96 @@ func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{},
) )
cmd.ExtraFiles = []*os.File{f} cmd.ExtraFiles = []*os.File{f}
cmd.Env = os.Environ() cmd.Env = os.Environ()
// OSXFUSE <3.3.0
cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=") cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
// TODO this is used for fs typenames etc, let app influence it // OSXFUSE >=3.3.0
cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin) cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=")
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
err := cmd.Start() daemon := os.Args[0]
if err != nil { if daemonVar != "" {
return err cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
} }
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("setting up mount_osxfusefs stderr: %v", err)
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("mount_osxfusefs: %v", err)
}
helperErrCh := make(chan error, 1)
go func() { go func() {
err := cmd.Wait() var wg sync.WaitGroup
if err != nil { wg.Add(2)
if buf.Len() > 0 { go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
output := buf.Bytes() helperName := path.Base(bin)
output = bytes.TrimRight(output, "\n") go lineLogger(&wg, "mount helper error", handleMountOSXFUSE(helperName, helperErrCh), stderr)
msg := err.Error() + ": " + string(output) wg.Wait()
err = errors.New(msg) if err := cmd.Wait(); err != nil {
// see if we have a better error to report
select {
case helperErr := <-helperErrCh:
// log the Wait error if it's not what we expected
if !isBoringMountOSXFUSEError(err) {
log.Printf("mount helper failed: %v", err)
} }
// and now return what we grabbed from stderr as the real
// error
*errp = helperErr
close(ready)
return
default:
// nope, fall back to generic message
} }
*errp = err
*errp = fmt.Errorf("mount_osxfusefs: %v", err)
close(ready)
return
}
*errp = nil
close(ready) close(ready)
}() }()
return err return nil
} }
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
f, err := openOSXFUSEDev() locations := conf.osxfuseLocations
if locations == nil {
locations = []OSXFUSEPaths{
OSXFUSELocationV3,
OSXFUSELocationV2,
}
}
for _, loc := range locations {
if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
// try the other locations
continue
}
f, err := openOSXFUSEDev(loc.DevicePrefix)
if err == errNotLoaded { if err == errNotLoaded {
err = loadOSXFUSE() err = loadOSXFUSE(loc.Load)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// try again // try again
f, err = openOSXFUSEDev() f, err = openOSXFUSEDev(loc.DevicePrefix)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = callMount(dir, conf, f, ready, errp) err = callMount(loc.Mount, loc.DaemonVar, dir, conf, f, ready, errp)
if err != nil { if err != nil {
f.Close() f.Close()
return nil, err return nil, err
} }
return f, nil return f, nil
} }
return nil, ErrOSXFUSENotFound
}

View file

@ -2,11 +2,51 @@ package fuse
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"sync"
"syscall"
) )
func handleMountFusefsStderr(errCh chan<- error) func(line string) (ignore bool) {
return func(line string) (ignore bool) {
const (
noMountpointPrefix = `mount_fusefs: `
noMountpointSuffix = `: No such file or directory`
)
if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
// re-extract it from the error message in case some layer
// changed the path
mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
err := &MountpointDoesNotExistError{
Path: mountpoint,
}
select {
case errCh <- err:
return true
default:
// not the first error; fall back to logging it
return false
}
}
return false
}
}
// isBoringMountFusefsError returns whether the Wait error is
// uninteresting; exit status 1 is.
func isBoringMountFusefsError(err error) bool {
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 {
return true
}
}
return false
}
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) {
for k, v := range conf.options { for k, v := range conf.options {
if strings.Contains(k, ",") || strings.Contains(v, ",") { if strings.Contains(k, ",") || strings.Contains(v, ",") {
@ -31,9 +71,39 @@ func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*
) )
cmd.ExtraFiles = []*os.File{f} cmd.ExtraFiles = []*os.File{f}
out, err := cmd.CombinedOutput() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
return nil, fmt.Errorf("mount_fusefs: %q, %v", out, err) return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, fmt.Errorf("setting up mount_fusefs stderr: %v", err)
}
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("mount_fusefs: %v", err)
}
helperErrCh := make(chan error, 1)
var wg sync.WaitGroup
wg.Add(2)
go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
go lineLogger(&wg, "mount helper error", handleMountFusefsStderr(helperErrCh), stderr)
wg.Wait()
if err := cmd.Wait(); err != nil {
// see if we have a better error to report
select {
case helperErr := <-helperErrCh:
// log the Wait error if it's not what we expected
if !isBoringMountFusefsError(err) {
log.Printf("mount helper failed: %v", err)
}
// and now return what we grabbed from stderr as the real
// error
return nil, helperErr
default:
// nope, fall back to generic message
}
return nil, fmt.Errorf("mount_fusefs: %v", err)
} }
close(ready) close(ready)

View file

@ -1,37 +1,60 @@
package fuse package fuse
import ( import (
"bufio"
"fmt" "fmt"
"io"
"log" "log"
"net" "net"
"os" "os"
"os/exec" "os/exec"
"strings"
"sync" "sync"
"syscall" "syscall"
) )
func lineLogger(wg *sync.WaitGroup, prefix string, r io.ReadCloser) { func handleFusermountStderr(errCh chan<- error) func(line string) (ignore bool) {
defer wg.Done() return func(line string) (ignore bool) {
if line == `fusermount: failed to open /etc/fuse.conf: Permission denied` {
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 // Silence this particular message, it occurs way too
// commonly and isn't very relevant to whether the mount // commonly and isn't very relevant to whether the mount
// succeeds or not. // succeeds or not.
continue return true
}
const (
noMountpointPrefix = `fusermount: failed to access mountpoint `
noMountpointSuffix = `: No such file or directory`
)
if strings.HasPrefix(line, noMountpointPrefix) && strings.HasSuffix(line, noMountpointSuffix) {
// re-extract it from the error message in case some layer
// changed the path
mountpoint := line[len(noMountpointPrefix) : len(line)-len(noMountpointSuffix)]
err := &MountpointDoesNotExistError{
Path: mountpoint,
}
select {
case errCh <- err:
return true
default: default:
log.Printf("%s: %s", prefix, line) // not the first error; fall back to logging it
return false
} }
} }
if err := scanner.Err(); err != nil {
log.Printf("%s, error reading: %v", prefix, err) return false
} }
} }
// isBoringFusermountError returns whether the Wait error is
// uninteresting; exit status 1 is.
func isBoringFusermountError(err error) bool {
if err, ok := err.(*exec.ExitError); ok && err.Exited() {
if status, ok := err.Sys().(syscall.WaitStatus); ok && status.ExitStatus() == 1 {
return true
}
}
return false
}
func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) {
// linux mount is never delayed // linux mount is never delayed
close(ready) close(ready)
@ -70,11 +93,26 @@ func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (f
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("fusermount: %v", err) return nil, fmt.Errorf("fusermount: %v", err)
} }
helperErrCh := make(chan error, 1)
wg.Add(2) wg.Add(2)
go lineLogger(&wg, "mount helper output", stdout) go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout)
go lineLogger(&wg, "mount helper error", stderr) go lineLogger(&wg, "mount helper error", handleFusermountStderr(helperErrCh), stderr)
wg.Wait() wg.Wait()
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
// see if we have a better error to report
select {
case helperErr := <-helperErrCh:
// log the Wait error if it's not what we expected
if !isBoringFusermountError(err) {
log.Printf("mount helper failed: %v", err)
}
// and now return what we grabbed from stderr as the real
// error
return nil, helperErr
default:
// nope, fall back to generic message
}
return nil, fmt.Errorf("fusermount: %v", err) return nil, fmt.Errorf("fusermount: %v", err)
} }

View file

@ -15,6 +15,7 @@ type mountConfig struct {
options map[string]string options map[string]string
maxReadahead uint32 maxReadahead uint32
initFlags InitFlags initFlags InitFlags
osxfuseLocations []OSXFUSEPaths
} }
func escapeComma(s string) string { func escapeComma(s string) string {
@ -82,6 +83,63 @@ func VolumeName(name string) MountOption {
return volumeName(name) return volumeName(name)
} }
// NoAppleDouble makes OSXFUSE disallow files with names used by OS X
// to store extended attributes on file systems that do not support
// them natively.
//
// Such file names are:
//
// ._*
// .DS_Store
//
// OS X only. Others ignore this option.
func NoAppleDouble() MountOption {
return noAppleDouble
}
// NoAppleXattr makes OSXFUSE disallow extended attributes with the
// prefix "com.apple.". This disables persistent Finder state and
// other such information.
//
// OS X only. Others ignore this option.
func NoAppleXattr() MountOption {
return noAppleXattr
}
// ExclCreate causes O_EXCL flag to be set for only "truly" exclusive creates,
// i.e. create calls for which the initiator explicitly set the O_EXCL flag.
//
// OSXFUSE expects all create calls to return EEXIST in case the file
// already exists, regardless of whether O_EXCL was specified or not.
// To ensure this behavior, it normally sets OpenExclusive for all
// Create calls, regardless of whether the original call had it set.
// For distributed filesystems, that may force every file create to be
// a distributed consensus action, causing undesirable delays.
//
// This option makes the FUSE filesystem see the original flag value,
// and better decide when to ensure global consensus.
//
// Note that returning EEXIST on existing file create is still
// expected with OSXFUSE, regardless of the presence of the
// OpenExclusive flag.
//
// For more information, see
// https://github.com/osxfuse/osxfuse/issues/209
//
// OS X only. Others ignore this options.
// Requires OSXFUSE 3.4.1 or newer.
func ExclCreate() MountOption {
return exclCreate
}
// DaemonTimeout sets the time in seconds between a request and a reply before
// the FUSE mount is declared dead.
//
// OS X and FreeBSD only. Others ignore this option.
func DaemonTimeout(name string) MountOption {
return daemonTimeout(name)
}
var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot") var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOther and AllowRoot")
// AllowOther allows other users to access the file system. // AllowOther allows other users to access the file system.
@ -112,6 +170,24 @@ func AllowRoot() MountOption {
} }
} }
// AllowDev enables interpreting character or block special devices on the
// filesystem.
func AllowDev() MountOption {
return func(conf *mountConfig) error {
conf.options["dev"] = ""
return nil
}
}
// AllowSUID allows set-user-identifier or set-group-identifier bits to take
// effect.
func AllowSUID() MountOption {
return func(conf *mountConfig) error {
conf.options["suid"] = ""
return nil
}
}
// DefaultPermissions makes the kernel enforce access control based on // DefaultPermissions makes the kernel enforce access control based on
// the file mode (as in chmod). // the file mode (as in chmod).
// //
@ -168,3 +244,67 @@ func WritebackCache() MountOption {
return nil return nil
} }
} }
// OSXFUSEPaths describes the paths used by an installed OSXFUSE
// version. See OSXFUSELocationV3 for typical values.
type OSXFUSEPaths struct {
// Prefix for the device file. At mount time, an incrementing
// number is suffixed until a free FUSE device is found.
DevicePrefix string
// Path of the load helper, used to load the kernel extension if
// no device files are found.
Load string
// Path of the mount helper, used for the actual mount operation.
Mount string
// Environment variable used to pass the path to the executable
// calling the mount helper.
DaemonVar string
}
// Default paths for OSXFUSE. See OSXFUSELocations.
var (
OSXFUSELocationV3 = OSXFUSEPaths{
DevicePrefix: "/dev/osxfuse",
Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse",
Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH",
}
OSXFUSELocationV2 = OSXFUSEPaths{
DevicePrefix: "/dev/osxfuse",
Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs",
Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs",
DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH",
}
)
// OSXFUSELocations sets where to look for OSXFUSE files. The
// arguments are all the possible locations. The previous locations
// are replaced.
//
// Without this option, OSXFUSELocationV3 and OSXFUSELocationV2 are
// used.
//
// OS X only. Others ignore this option.
func OSXFUSELocations(paths ...OSXFUSEPaths) MountOption {
return func(conf *mountConfig) error {
if len(paths) == 0 {
return errors.New("must specify at least one location for OSXFUSELocations")
}
// replace previous values, but make a copy so there's no
// worries about caller mutating their slice
conf.osxfuseLocations = append(conf.osxfuseLocations[:0], paths...)
return nil
}
}
// AllowNonEmptyMount allows the mounting over a non-empty directory.
//
// The files in it will be shadowed by the freshly created mount. By
// default these mounts are rejected to prevent accidental covering up
// of data, which could for example prevent automatic backup.
func AllowNonEmptyMount() MountOption {
return func(conf *mountConfig) error {
conf.options["nonempty"] = ""
return nil
}
}

View file

@ -0,0 +1,64 @@
// Test for adjustable timeout between a FUSE request and the daemon's response.
//
// +build darwin freebsd
package fuse_test
import (
"os"
"runtime"
"syscall"
"testing"
"time"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"bazil.org/fuse/fs/fstestutil"
"golang.org/x/net/context"
)
type slowCreaterDir struct {
fstestutil.Dir
}
var _ fs.NodeCreater = slowCreaterDir{}
func (c slowCreaterDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
time.Sleep(10 * time.Second)
// pick a really distinct error, to identify it later
return nil, nil, fuse.Errno(syscall.ENAMETOOLONG)
}
func TestMountOptionDaemonTimeout(t *testing.T) {
if runtime.GOOS != "darwin" && runtime.GOOS != "freebsd" {
return
}
if testing.Short() {
t.Skip("skipping time-based test in short mode")
}
t.Parallel()
mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{slowCreaterDir{}},
nil,
fuse.DaemonTimeout("2"),
)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()
// This should fail by the kernel timing out the request.
f, err := os.Create(mnt.Dir + "/child")
if err == nil {
f.Close()
t.Fatal("expected an error")
}
perr, ok := err.(*os.PathError)
if !ok {
t.Fatalf("expected PathError, got %T: %v", err, err)
}
if perr.Err == syscall.ENAMETOOLONG {
t.Fatalf("expected other than ENAMETOOLONG, got %T: %v", err, err)
}
}

View file

@ -11,3 +11,25 @@ func volumeName(name string) MountOption {
return nil return nil
} }
} }
func daemonTimeout(name string) MountOption {
return func(conf *mountConfig) error {
conf.options["daemon_timeout"] = name
return nil
}
}
func noAppleXattr(conf *mountConfig) error {
conf.options["noapplexattr"] = ""
return nil
}
func noAppleDouble(conf *mountConfig) error {
conf.options["noappledouble"] = ""
return nil
}
func exclCreate(conf *mountConfig) error {
conf.options["excl_create"] = ""
return nil
}

View file

@ -7,3 +7,22 @@ func localVolume(conf *mountConfig) error {
func volumeName(name string) MountOption { func volumeName(name string) MountOption {
return dummyOption return dummyOption
} }
func daemonTimeout(name string) MountOption {
return func(conf *mountConfig) error {
conf.options["timeout"] = name
return nil
}
}
func noAppleXattr(conf *mountConfig) error {
return nil
}
func noAppleDouble(conf *mountConfig) error {
return nil
}
func exclCreate(conf *mountConfig) error {
return nil
}

View file

@ -7,3 +7,19 @@ func localVolume(conf *mountConfig) error {
func volumeName(name string) MountOption { func volumeName(name string) MountOption {
return dummyOption return dummyOption
} }
func daemonTimeout(name string) MountOption {
return dummyOption
}
func noAppleXattr(conf *mountConfig) error {
return nil
}
func noAppleDouble(conf *mountConfig) error {
return nil
}
func exclCreate(conf *mountConfig) error {
return nil
}

View file

@ -165,6 +165,7 @@ func TestMountOptionDefaultPermissions(t *testing.T) {
t.Skip("FreeBSD does not support DefaultPermissions") t.Skip("FreeBSD does not support DefaultPermissions")
} }
t.Parallel() t.Parallel()
mnt, err := fstestutil.MountedT(t, mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{ fstestutil.SimpleFS{
&fstestutil.ChildMap{"child": unwritableFile{}}, &fstestutil.ChildMap{"child": unwritableFile{}},
@ -172,7 +173,6 @@ func TestMountOptionDefaultPermissions(t *testing.T) {
nil, nil,
fuse.DefaultPermissions(), fuse.DefaultPermissions(),
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -203,12 +203,12 @@ func (createrDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fus
func TestMountOptionReadOnly(t *testing.T) { func TestMountOptionReadOnly(t *testing.T) {
t.Parallel() t.Parallel()
mnt, err := fstestutil.MountedT(t, mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{createrDir{}}, fstestutil.SimpleFS{createrDir{}},
nil, nil,
fuse.ReadOnly(), fuse.ReadOnly(),
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }