diff --git a/vendor/manifest b/vendor/manifest index eb13e43ab..e44436756 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -4,8 +4,8 @@ { "importpath": "bazil.org/fuse", "repository": "https://github.com/bazil/fuse", - "revision": "18419ee53958df28fcfc9490fe6123bd59e237bb", - "branch": "HEAD" + "revision": "371fbbdaa8987b715bdd21d6adc4c9b20155f748", + "branch": "master" }, { "importpath": "github.com/elithrar/simple-scrypt", diff --git a/vendor/src/bazil.org/fuse/examples/clockfs/clockfs.go b/vendor/src/bazil.org/fuse/examples/clockfs/clockfs.go index 178fda943..c053f0259 100644 --- a/vendor/src/bazil.org/fuse/examples/clockfs/clockfs.go +++ b/vendor/src/bazil.org/fuse/examples/clockfs/clockfs.go @@ -24,16 +24,7 @@ func usage() { flag.PrintDefaults() } -func main() { - flag.Usage = usage - flag.Parse() - - if flag.NArg() != 1 { - usage() - os.Exit(2) - } - mountpoint := flag.Arg(0) - +func run(mountpoint string) error { c, err := fuse.Mount( mountpoint, fuse.FSName("clock"), @@ -42,10 +33,14 @@ func main() { fuse.VolumeName("Clock filesystem"), ) if err != nil { - log.Fatal(err) + return err } 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) filesys := &FS{ // 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. go filesys.clockFile.update() if err := srv.Serve(filesys); err != nil { - log.Fatal(err) + return err } // Check if the mount process has an error to report. <-c.Ready 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) } } diff --git a/vendor/src/bazil.org/fuse/examples/hellofs/hello.go b/vendor/src/bazil.org/fuse/examples/hellofs/hello.go index 5ec5ce8ee..eb6a234fe 100644 --- a/vendor/src/bazil.org/fuse/examples/hellofs/hello.go +++ b/vendor/src/bazil.org/fuse/examples/hellofs/hello.go @@ -13,18 +13,18 @@ import ( "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, " %s MOUNTPOINT\n", os.Args[0]) flag.PrintDefaults() } func main() { - flag.Usage = Usage + flag.Usage = usage flag.Parse() if flag.NArg() != 1 { - Usage() + usage() os.Exit(2) } mountpoint := flag.Arg(0) diff --git a/vendor/src/bazil.org/fuse/fs/bench/bench_create_test.go b/vendor/src/bazil.org/fuse/fs/bench/bench_create_test.go new file mode 100644 index 000000000..1f78786b7 --- /dev/null +++ b/vendor/src/bazil.org/fuse/fs/bench/bench_create_test.go @@ -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() +} diff --git a/vendor/src/bazil.org/fuse/fs/bench/bench_lookup_test.go b/vendor/src/bazil.org/fuse/fs/bench/bench_lookup_test.go new file mode 100644 index 000000000..b22edc82e --- /dev/null +++ b/vendor/src/bazil.org/fuse/fs/bench/bench_lookup_test.go @@ -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() +} diff --git a/vendor/src/bazil.org/fuse/fs/bench/bench_test.go b/vendor/src/bazil.org/fuse/fs/bench/bench_readwrite_test.go similarity index 100% rename from vendor/src/bazil.org/fuse/fs/bench/bench_test.go rename to vendor/src/bazil.org/fuse/fs/bench/bench_readwrite_test.go diff --git a/vendor/src/bazil.org/fuse/fs/fstestutil/checkdir.go b/vendor/src/bazil.org/fuse/fs/fstestutil/checkdir.go new file mode 100644 index 000000000..74e5899e9 --- /dev/null +++ b/vendor/src/bazil.org/fuse/fs/fstestutil/checkdir.go @@ -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 +} diff --git a/vendor/src/bazil.org/fuse/fs/fstestutil/mounted.go b/vendor/src/bazil.org/fuse/fs/fstestutil/mounted.go index 1209d2572..2fae1588a 100644 --- a/vendor/src/bazil.org/fuse/fs/fstestutil/mounted.go +++ b/vendor/src/bazil.org/fuse/fs/fstestutil/mounted.go @@ -50,13 +50,15 @@ func (mnt *Mount) Close() { 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 // workaround). // // 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") if err != nil { return nil, err @@ -75,6 +77,7 @@ func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Moun Error: serveErr, done: done, } + filesys := fn(mnt) go func() { defer close(done) 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. // -// See Mounted for usage. +// See MountedFunc 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) { +func MountedFuncT(t testing.TB, fn func(*Mount) fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { if conf == nil { 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) } } - 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...) } diff --git a/vendor/src/bazil.org/fuse/fs/fstestutil/record/record.go b/vendor/src/bazil.org/fuse/fs/fstestutil/record/record.go index 1b07da5ca..13654dee4 100644 --- a/vendor/src/bazil.org/fuse/fs/fstestutil/record/record.go +++ b/vendor/src/bazil.org/fuse/fs/fstestutil/record/record.go @@ -382,3 +382,28 @@ func (r *Removexattrs) RecordedRemovexattr() 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)) +} diff --git a/vendor/src/bazil.org/fuse/fs/serve.go b/vendor/src/bazil.org/fuse/fs/serve.go index a73a33b07..e9fc56590 100644 --- a/vendor/src/bazil.org/fuse/fs/serve.go +++ b/vendor/src/bazil.org/fuse/fs/serve.go @@ -18,6 +18,8 @@ import ( ) import ( + "bytes" + "bazil.org/fuse" "bazil.org/fuse/fuseutil" ) @@ -89,6 +91,13 @@ type FSInodeGenerator interface { // simple, read-only filesystem. type Node interface { // 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 } @@ -105,9 +114,7 @@ type NodeSetattrer interface { // Setattr sets the standard metadata for the receiver. // // Note, this is also used to communicate changes in the size of - // the file. Not implementing Setattr causes writes to be unable - // to grow the file (except with OpenDirectIO, which bypasses that - // mechanism). + // the file, outside of Writes. // // req.Valid is a bitmask of what fields are actually being set. // For example, the method should not change the mode of the file @@ -297,16 +304,17 @@ type HandleReader 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 // only page-aligned writes spanning one or more pages. However, // you should not rely on this. To see individual requests as // submitted by the file system clients, set OpenDirectIO. // - // Note that file size changes are communicated through Setattr. - // Writes beyond the size of the file as reported by Attr are not - // even attempted (except in OpenDirectIO mode). + // Writes that grow the file are expected to update the file size + // (as seen through Attr). Note that file size changes are + // communicated also through Setattr. 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. Debug func(msg interface{}) - // Function to create new contexts. If nil, use - // context.Background. + // Function to put things into context for processing the request. + // The returned context must have ctx as its parent. // // 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 @@ -342,14 +352,11 @@ func New(conn *fuse.Conn, config *Config) *Server { } if config != nil { s.debug = config.Debug - s.context = config.GetContext + s.context = config.WithContext } if s.debug == nil { s.debug = fuse.Debug } - if s.context == nil { - s.context = context.Background - } return s } @@ -357,7 +364,7 @@ type Server struct { // set in New conn *fuse.Conn debug func(msg interface{}) - context func() context.Context + context func(ctx context.Context, req fuse.Request) context.Context // set once at Serve time fs FS @@ -494,7 +501,7 @@ func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) } sn.generation = c.nodeGen c.nodeRef[node] = id - return + return id, sn.generation } func (c *Server) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) { @@ -601,7 +608,7 @@ type logResponseHeader struct { } func (m logResponseHeader) String() string { - return fmt.Sprintf("ID=%#x", m.ID) + return fmt.Sprintf("ID=%v", m.ID) } type response struct { @@ -626,21 +633,21 @@ func (r response) errstr() string { func (r response) String() string { switch { 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 != "": - 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: // make sure (seemingly) empty values are readable switch r.Out.(type) { 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: - return fmt.Sprintf("-> %s [% x]", r.Request, r.Out) + return fmt.Sprintf("-> [%v] %s [% x]", r.Request, r.Op, r.Out) default: - return fmt.Sprintf("-> %s %s", r.Request, r.Out) + return fmt.Sprintf("-> [%v] %v", r.Request, r.Out) } 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 { - switch { - case n.Out != nil: + var buf bytes.Buffer + fmt.Fprintf(&buf, "=> %s %v", n.Op, n.Node) + if n.Out != nil { // make sure (seemingly) empty values are readable switch n.Out.(type) { 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: - return fmt.Sprintf("=> %s %d [% x] Err:%v", n.Op, n.Node, n.Out, n.Err) + fmt.Fprintf(&buf, " [% x]", n.Out) 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 { @@ -685,7 +695,7 @@ type logLinkRequestOldNodeNotFound struct { } 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 { @@ -694,7 +704,7 @@ type renameNewDirNodeNotFound struct { } 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 { @@ -717,13 +727,52 @@ func (h handlerPanickedError) Errno() fuse.Errno { 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) { s.EntryValid = entryValidTime } func (c *Server) serve(r fuse.Request) { - ctx, cancel := context.WithCancel(c.context()) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() + parentCtx := ctx + if c.context != nil { + ctx = c.context(ctx, r) + } req := &serveRequest{Request: r, cancel: cancel} @@ -800,6 +849,7 @@ func (c *Server) serve(r fuse.Request) { c.meta.Unlock() } + var responded bool defer func() { if rec := recover(); rec != nil { const size = 1 << 16 @@ -813,114 +863,132 @@ func (c *Server) serve(r fuse.Request) { } done(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) { default: // Note: To FUSE, ENOSYS means "this server never implements this request." // It would be inappropriate to return ENOSYS for other operations in this // switch that might only be unavailable in some contexts, not all. - done(fuse.ENOSYS) - r.RespondError(fuse.ENOSYS) + return fuse.ENOSYS case *fuse.StatfsRequest: s := &fuse.StatfsResponse{} if fs, ok := c.fs.(FSStatfser); ok { if err := fs.Statfs(ctx, r, s); err != nil { - done(err) - r.RespondError(err) - break + return err } } done(s) r.Respond(s) + return nil // Node operations. case *fuse.GetattrRequest: s := &fuse.GetattrResponse{} if n, ok := node.(NodeGetattrer); ok { if err := n.Getattr(ctx, r, s); err != nil { - done(err) - r.RespondError(err) - break + return err } } else { if err := snode.attr(ctx, &s.Attr); err != nil { - done(err) - r.RespondError(err) - break + return err } } done(s) r.Respond(s) + return nil case *fuse.SetattrRequest: s := &fuse.SetattrResponse{} if n, ok := node.(NodeSetattrer); ok { if err := n.Setattr(ctx, r, s); err != nil { - done(err) - r.RespondError(err) - break + return err } - done(s) - r.Respond(s) - break } if err := snode.attr(ctx, &s.Attr); err != nil { - done(err) - r.RespondError(err) - break + return err } done(s) r.Respond(s) + return nil case *fuse.SymlinkRequest: s := &fuse.SymlinkResponse{} initLookupResponse(&s.LookupResponse) n, ok := node.(NodeSymlinker) if !ok { - done(fuse.EIO) // XXX or EPERM like Mkdir? - r.RespondError(fuse.EIO) - break + return fuse.EIO // XXX or EPERM like Mkdir? } n2, err := n.Symlink(ctx, r) if err != nil { - done(err) - r.RespondError(err) - break + return err } if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil { - done(err) - r.RespondError(err) - break + return err } done(s) r.Respond(s) + return nil case *fuse.ReadlinkRequest: n, ok := node.(NodeReadlinker) if !ok { - done(fuse.EIO) /// XXX or EPERM? - r.RespondError(fuse.EIO) - break + return fuse.EIO /// XXX or EPERM? } target, err := n.Readlink(ctx, r) if err != nil { - done(err) - r.RespondError(err) - break + return err } done(target) r.Respond(target) + return nil case *fuse.LinkRequest: n, ok := node.(NodeLinker) if !ok { - done(fuse.EIO) /// XXX or EPERM? - r.RespondError(fuse.EIO) - break + return fuse.EIO /// XXX or EPERM? } c.meta.Lock() var oldNode *serveNode @@ -933,52 +1001,43 @@ func (c *Server) serve(r fuse.Request) { Request: r.Hdr(), In: r, }) - done(fuse.EIO) - r.RespondError(fuse.EIO) - break + return fuse.EIO } n2, err := n.Link(ctx, r, oldNode.node) if err != nil { - done(err) - r.RespondError(err) - break + return err } s := &fuse.LookupResponse{} initLookupResponse(s) if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil { - done(err) - r.RespondError(err) - break + return err } done(s) r.Respond(s) + return nil case *fuse.RemoveRequest: n, ok := node.(NodeRemover) if !ok { - done(fuse.EIO) /// XXX or EPERM? - r.RespondError(fuse.EIO) - break + return fuse.EIO /// XXX or EPERM? } err := n.Remove(ctx, r) if err != nil { - done(err) - r.RespondError(err) - break + return err } done(nil) r.Respond() + return nil case *fuse.AccessRequest: if n, ok := node.(NodeAccesser); ok { if err := n.Access(ctx, r); err != nil { - done(err) - r.RespondError(err) - break + return err } } done(nil) r.Respond() + return nil case *fuse.LookupRequest: var n2 Node @@ -990,45 +1049,35 @@ func (c *Server) serve(r fuse.Request) { } else if n, ok := node.(NodeRequestLookuper); ok { n2, err = n.Lookup(ctx, r, s) } else { - done(fuse.ENOENT) - r.RespondError(fuse.ENOENT) - break + return fuse.ENOENT } if err != nil { - done(err) - r.RespondError(err) - break + return err } if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { - done(err) - r.RespondError(err) - break + return err } done(s) r.Respond(s) + return nil case *fuse.MkdirRequest: s := &fuse.MkdirResponse{} initLookupResponse(&s.LookupResponse) n, ok := node.(NodeMkdirer) if !ok { - done(fuse.EPERM) - r.RespondError(fuse.EPERM) - break + return fuse.EPERM } n2, err := n.Mkdir(ctx, r) if err != nil { - done(err) - r.RespondError(err) - break + return err } if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { - done(err) - r.RespondError(err) - break + return err } done(s) r.Respond(s) + return nil case *fuse.OpenRequest: s := &fuse.OpenResponse{} @@ -1036,121 +1085,99 @@ func (c *Server) serve(r fuse.Request) { if n, ok := node.(NodeOpener); ok { hh, err := n.Open(ctx, r, s) if err != nil { - done(err) - r.RespondError(err) - break + return err } h2 = hh } else { h2 = node } - s.Handle = c.saveHandle(h2, hdr.Node) + s.Handle = c.saveHandle(h2, r.Hdr().Node) done(s) r.Respond(s) + return nil case *fuse.CreateRequest: n, ok := node.(NodeCreater) if !ok { // If we send back ENOSYS, FUSE will try mknod+open. - done(fuse.EPERM) - r.RespondError(fuse.EPERM) - break + return fuse.EPERM } s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}} initLookupResponse(&s.LookupResponse) n2, h2, err := n.Create(ctx, r, s) if err != nil { - done(err) - r.RespondError(err) - break + return err } if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { - done(err) - r.RespondError(err) - break + return err } - s.Handle = c.saveHandle(h2, hdr.Node) + s.Handle = c.saveHandle(h2, r.Hdr().Node) done(s) r.Respond(s) + return nil case *fuse.GetxattrRequest: n, ok := node.(NodeGetxattrer) if !ok { - done(fuse.ENOTSUP) - r.RespondError(fuse.ENOTSUP) - break + return fuse.ENOTSUP } s := &fuse.GetxattrResponse{} err := n.Getxattr(ctx, r, s) if err != nil { - done(err) - r.RespondError(err) - break + return err } if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { - done(fuse.ERANGE) - r.RespondError(fuse.ERANGE) - break + return fuse.ERANGE } done(s) r.Respond(s) + return nil case *fuse.ListxattrRequest: n, ok := node.(NodeListxattrer) if !ok { - done(fuse.ENOTSUP) - r.RespondError(fuse.ENOTSUP) - break + return fuse.ENOTSUP } s := &fuse.ListxattrResponse{} err := n.Listxattr(ctx, r, s) if err != nil { - done(err) - r.RespondError(err) - break + return err } if r.Size != 0 && uint64(len(s.Xattr)) > uint64(r.Size) { - done(fuse.ERANGE) - r.RespondError(fuse.ERANGE) - break + return fuse.ERANGE } done(s) r.Respond(s) + return nil case *fuse.SetxattrRequest: n, ok := node.(NodeSetxattrer) if !ok { - done(fuse.ENOTSUP) - r.RespondError(fuse.ENOTSUP) - break + return fuse.ENOTSUP } err := n.Setxattr(ctx, r) if err != nil { - done(err) - r.RespondError(err) - break + return err } done(nil) r.Respond() + return nil case *fuse.RemovexattrRequest: n, ok := node.(NodeRemovexattrer) if !ok { - done(fuse.ENOTSUP) - r.RespondError(fuse.ENOTSUP) - break + return fuse.ENOTSUP } err := n.Removexattr(ctx, r) if err != nil { - done(err) - r.RespondError(err) - break + return err } done(nil) r.Respond() + return nil case *fuse.ForgetRequest: - forget := c.dropNode(hdr.Node, r.N) + forget := c.dropNode(r.Hdr().Node, r.N) if forget { n, ok := node.(NodeForgetter) if ok { @@ -1159,26 +1186,29 @@ func (c *Server) serve(r fuse.Request) { } done(nil) r.Respond() + return nil // Handle operations. case *fuse.ReadRequest: shandle := c.getHandle(r.Handle) if shandle == nil { - done(fuse.ESTALE) - r.RespondError(fuse.ESTALE) - return + return fuse.ESTALE } handle := shandle.handle s := &fuse.ReadResponse{Data: make([]byte, 0, r.Size)} if r.Dir { 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 { dirs, err := h.ReadDirAll(ctx) if err != nil { - done(err) - r.RespondError(err) - break + return err } var data []byte for _, dir := range dirs { @@ -1192,16 +1222,14 @@ func (c *Server) serve(r fuse.Request) { fuseutil.HandleRead(r, s, shandle.readData) done(s) r.Respond(s) - break + return nil } } else { if h, ok := handle.(HandleReadAller); ok { if shandle.readData == nil { data, err := h.ReadAll(ctx) if err != nil { - done(err) - r.RespondError(err) - break + return err } if data == nil { data = []byte{} @@ -1211,71 +1239,58 @@ func (c *Server) serve(r fuse.Request) { fuseutil.HandleRead(r, s, shandle.readData) done(s) r.Respond(s) - break + return nil } h, ok := handle.(HandleReader) if !ok { - fmt.Printf("NO READ FOR %T\n", handle) - done(fuse.EIO) - r.RespondError(fuse.EIO) - break + err := handleNotReaderError{handle: handle} + return err } if err := h.Read(ctx, r, s); err != nil { - done(err) - r.RespondError(err) - break + return err } } done(s) r.Respond(s) + return nil case *fuse.WriteRequest: shandle := c.getHandle(r.Handle) if shandle == nil { - done(fuse.ESTALE) - r.RespondError(fuse.ESTALE) - return + return fuse.ESTALE } s := &fuse.WriteResponse{} if h, ok := shandle.handle.(HandleWriter); ok { if err := h.Write(ctx, r, s); err != nil { - done(err) - r.RespondError(err) - break + return err } done(s) r.Respond(s) - break + return nil } - done(fuse.EIO) - r.RespondError(fuse.EIO) + return fuse.EIO case *fuse.FlushRequest: shandle := c.getHandle(r.Handle) if shandle == nil { - done(fuse.ESTALE) - r.RespondError(fuse.ESTALE) - return + return fuse.ESTALE } handle := shandle.handle if h, ok := handle.(HandleFlusher); ok { if err := h.Flush(ctx, r); err != nil { - done(err) - r.RespondError(err) - break + return err } } done(nil) r.Respond() + return nil case *fuse.ReleaseRequest: shandle := c.getHandle(r.Handle) if shandle == nil { - done(fuse.ESTALE) - r.RespondError(fuse.ESTALE) - return + return fuse.ESTALE } handle := shandle.handle @@ -1284,13 +1299,12 @@ func (c *Server) serve(r fuse.Request) { if h, ok := handle.(HandleReleaser); ok { if err := h.Release(ctx, r); err != nil { - done(err) - r.RespondError(err) - break + return err } } done(nil) r.Respond() + return nil case *fuse.DestroyRequest: if fs, ok := c.fs.(FSDestroyer); ok { @@ -1298,6 +1312,7 @@ func (c *Server) serve(r fuse.Request) { } done(nil) r.Respond() + return nil case *fuse.RenameRequest: c.meta.Lock() @@ -1311,63 +1326,50 @@ func (c *Server) serve(r fuse.Request) { Request: r.Hdr(), In: r, }) - done(fuse.EIO) - r.RespondError(fuse.EIO) - break + return fuse.EIO } n, ok := node.(NodeRenamer) if !ok { - done(fuse.EIO) // XXX or EPERM like Mkdir? - r.RespondError(fuse.EIO) - break + return fuse.EIO // XXX or EPERM like Mkdir? } err := n.Rename(ctx, r, newDirNode.node) if err != nil { - done(err) - r.RespondError(err) - break + return err } done(nil) r.Respond() + return nil case *fuse.MknodRequest: n, ok := node.(NodeMknoder) if !ok { - done(fuse.EIO) - r.RespondError(fuse.EIO) - break + return fuse.EIO } n2, err := n.Mknod(ctx, r) if err != nil { - done(err) - r.RespondError(err) - break + return err } s := &fuse.LookupResponse{} initLookupResponse(s) if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { - done(err) - r.RespondError(err) - break + return err } done(s) r.Respond(s) + return nil case *fuse.FsyncRequest: n, ok := node.(NodeFsyncer) if !ok { - done(fuse.EIO) - r.RespondError(fuse.EIO) - break + return fuse.EIO } err := n.Fsync(ctx, r) if err != nil { - done(err) - r.RespondError(err) - break + return err } done(nil) r.Respond() + return nil case *fuse.InterruptRequest: c.meta.Lock() @@ -1379,24 +1381,23 @@ func (c *Server) serve(r fuse.Request) { c.meta.Unlock() done(nil) r.Respond() + return nil /* case *FsyncdirRequest: - done(ENOSYS) - r.RespondError(ENOSYS) + return ENOSYS case *GetlkRequest, *SetlkRequest, *SetlkwRequest: - done(ENOSYS) - r.RespondError(ENOSYS) + return ENOSYS case *BmapRequest: - done(ENOSYS) - r.RespondError(ENOSYS) + return ENOSYS case *SetvolnameRequest, *GetxtimesRequest, *ExchangeRequest: - done(ENOSYS) - r.RespondError(ENOSYS) + return ENOSYS */ } + + panic("not reached") } func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error { diff --git a/vendor/src/bazil.org/fuse/fs/serve_darwin_test.go b/vendor/src/bazil.org/fuse/fs/serve_darwin_test.go new file mode 100644 index 000000000..f716dc66b --- /dev/null +++ b/vendor/src/bazil.org/fuse/fs/serve_darwin_test.go @@ -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) + } +} diff --git a/vendor/src/bazil.org/fuse/fs/serve_test.go b/vendor/src/bazil.org/fuse/fs/serve_test.go index 762e0a963..e7f9cfb09 100644 --- a/vendor/src/bazil.org/fuse/fs/serve_test.go +++ b/vendor/src/bazil.org/fuse/fs/serve_test.go @@ -7,10 +7,11 @@ import ( "io/ioutil" "log" "os" - "os/exec" + "path" "runtime" "strings" "sync" + "sync/atomic" "syscall" "testing" "time" @@ -56,6 +57,25 @@ func (f fifo) Attr(ctx context.Context, a *fuse.Attr) error { 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{} 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) { - data, err := ioutil.ReadFile(path) + f, err := os.Open(path) if err != nil { - t.Fatalf("readAll: %v", err) + t.Fatal(err) } - if string(data) != hi { - t.Errorf("readAll = %q, want %q", data, hi) + defer f.Close() + 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() 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 { 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 } +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 { r.fileFlags.Record(req.FileFlags) resp.Size = len(req.Data) @@ -412,6 +454,15 @@ func TestWriteFileFlags(t *testing.T) { _ = f.Close() 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 { 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) { - t.Parallel() f := &mkdir1{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { @@ -601,6 +651,10 @@ func TestMkdir(t *testing.T) { 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.RecordedMkdir(), want; g != e { t.Errorf("mkdir saw %+v, want %+v", g, e) } @@ -610,6 +664,7 @@ func TestMkdir(t *testing.T) { type create1file struct { fstestutil.File + record.Creates 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) return nil, nil, fuse.EPERM } - flags := req.Flags - // OS X does not pass O_TRUNC here, Linux does; as this is a - // 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 - } + _, _, _ = f.f.Creates.Create(ctx, req, resp) return &f.f, &f.f, nil } func TestCreate(t *testing.T) { - t.Parallel() f := &create1{} mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { @@ -658,12 +694,38 @@ func TestCreate(t *testing.T) { // uniform umask needed to make os.Create's 0666 into something // reproducible 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 { t.Fatalf("create1 WriteFile: %v", err) } 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())) if err != nil { 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) { - t.Parallel() if os.Getuid() != 0 { t.Skip("skipping unless root") } @@ -907,15 +968,15 @@ func TestMknod(t *testing.T) { } defer mnt.Close() - defer syscall.Umask(syscall.Umask(0)) - err = syscall.Mknod(mnt.Dir+"/node", syscall.S_IFIFO|0666, 123) + defer syscall.Umask(syscall.Umask(0022)) + err = syscall.Mknod(mnt.Dir+"/node", syscall.S_IFIFO|0660, 123) if err != nil { - t.Fatalf("Mknod: %v", err) + t.Fatalf("mknod: %v", err) } want := fuse.MknodRequest{ Name: "node", - Mode: os.FileMode(os.ModeNamedPipe | 0666), + Mode: os.FileMode(os.ModeNamedPipe | 0640), Rdev: uint32(123), } if runtime.GOOS == "linux" { @@ -924,6 +985,13 @@ func TestMknod(t *testing.T) { // bit is portable.) 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 { 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: } <-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) { @@ -999,46 +1098,71 @@ func TestInterrupt(t *testing.T) { defer mnt.Close() // start a subprocess that can hang until signaled - cmd := exec.Command("cat", mnt.Dir+"/child") - - err = cmd.Start() + child, err := childCmd("interrupt") 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 } // 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 <-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 { - t.Errorf("interrupt: cannot interrupt cat: %v", err) + t.Errorf("cannot interrupt child: %v", err) return } - p, err := cmd.Process.Wait() + p, err := child.Process.Wait() if err != nil { - t.Errorf("interrupt: cat bork: %v", err) + t.Errorf("child failed: %v", err) return } switch ws := p.Sys().(type) { case syscall.WaitStatus: 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) } - - if ws.Exited() { - t.Errorf("interrupt: didn't expect cat to exit normally: %v", ws) - } - - if !ws.Signaled() { - t.Errorf("interrupt: expected cat to get a signal: %v", ws) - } else { - if ws.Signal() != os.Interrupt { - t.Errorf("interrupt: cat got wrong signal: %v", ws) + switch runtime.GOOS { + case "darwin": + // see comment above about EINTR on OS X + if ws.Exited() { + t.Fatalf("interrupt: expected child to die from signal, got exit status: %v", ws.ExitStatus()) + } + if !ws.Signaled() { + t.Fatalf("interrupt: expected child to die from signal: %v", ws) + } + if got := ws.Signal(); got != sig { + 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: @@ -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 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. 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. type chmod struct { @@ -1364,6 +1652,10 @@ func (f *openNonSeekable) Open(ctx context.Context, req *fuse.OpenRequest, resp } 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() f := &openNonSeekable{} 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) } } - if g, e := a.attr.Count(), uint32(1); g != e { - t.Errorf("wrong Attr call count: %d != %d", g, e) + // With OSXFUSE 3.0.4, we seem to see typically two Attr calls by + // 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...") @@ -2123,7 +2427,7 @@ func TestInvalidateNodeAttr(t *testing.T) { 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) } } @@ -2133,9 +2437,13 @@ type invalidateData struct { t testing.TB attr 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) @@ -2143,7 +2451,7 @@ func (i *invalidateData) Attr(ctx context.Context, a *fuse.Attr) error { i.attr.Inc() i.t.Logf("Attr called, #%d", i.attr.Count()) a.Mode = 0600 - a.Size = uint64(len(invalidateDataContent)) + a.Size = uint64(len(i.data.Load().(string))) 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 { i.read.Inc() 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 } -func TestInvalidateNodeData(t *testing.T) { +func TestInvalidateNodeDataInvalidatesAttr(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) @@ -2179,31 +2488,93 @@ func TestInvalidateNodeData(t *testing.T) { } defer f.Close() - buf := make([]byte, 4) - for i := 0; i < 10; i++ { - if _, err := f.ReadAt(buf, 0); err != nil { - t.Fatalf("readat error: %v", err) - } + attrBefore := a.attr.Count() + if g, min := attrBefore, uint32(1); g < min { + t.Errorf("wrong Attr call count: %d < %d", g, min) } - if g, e := a.attr.Count(), uint32(1); g != e { - t.Errorf("wrong Attr call count: %d != %d", g, e) + + 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++ { + n, err := f.ReadAt(buf, 0) + if err != nil && err != io.EOF { + 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.read.Count(), uint32(1); g != e { t.Errorf("wrong Read call count: %d != %d", g, e) } t.Logf("invalidating...") + a.data.Store(invalidateDataContent2) if err := mnt.Server.InvalidateNodeData(a); err != nil { t.Fatalf("invalidate error: %v", err) } - for i := 0; i < 10; i++ { - if _, err := f.ReadAt(buf, 0); 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) } - if g, e := a.attr.Count(), uint32(1); g != e { - t.Errorf("wrong Attr 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++ { + n, err := f.ReadAt(buf, 0) + if err != nil && err != io.EOF { + 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.read.Count(), uint32(2); 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 } -func TestInvalidateNodeDataRange(t *testing.T) { +func TestInvalidateNodeDataRangeMiss(t *testing.T) { // This test may see false positive failures when run under // extreme memory pressure. t.Parallel() @@ -2267,14 +2638,11 @@ func TestInvalidateNodeDataRange(t *testing.T) { 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 { 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 { t.Fatalf("invalidate error: %v", err) } @@ -2284,9 +2652,6 @@ func TestInvalidateNodeDataRange(t *testing.T) { 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 // should stay in cache. 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 { fs.NodeRef t testing.TB @@ -2380,13 +2795,13 @@ func (contextFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.O func TestContext(t *testing.T) { t.Parallel() - ctx := context.Background() const input = "kilroy was here" - ctx = context.WithValue(ctx, &contextFileSentinel, input) mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": contextFile{}}}, &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 { t.Fatal(err) @@ -2401,3 +2816,28 @@ func TestContext(t *testing.T) { 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) + } +} diff --git a/vendor/src/bazil.org/fuse/fuse.go b/vendor/src/bazil.org/fuse/fuse.go index 424b2dce4..6db0ef293 100644 --- a/vendor/src/bazil.org/fuse/fuse.go +++ b/vendor/src/bazil.org/fuse/fuse.go @@ -132,6 +132,18 @@ type Conn struct { 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 // 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 { 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 } @@ -179,11 +198,15 @@ func (e *OldVersionError) Error() string { 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 { req, err := c.ReadRequest() if err != nil { if err == io.EOF { - return fmt.Errorf("missing init, got EOF") + return ErrClosedWithoutInit } return err } @@ -212,7 +235,7 @@ func initMount(c *Conn, conf *mountConfig) error { s := &InitResponse{ Library: proto, MaxReadahead: conf.maxReadahead, - MaxWrite: 128 * 1024, + MaxWrite: maxWrite, Flags: InitBigWrites | conf.initFlags, } r.Respond(s) @@ -235,15 +258,27 @@ type Request interface { // A RequestID identifies an active FUSE request. type RequestID uint64 +func (r RequestID) String() string { + return fmt.Sprintf("%#x", uint64(r)) +} + // A NodeID is a number identifying a directory or file. // It must be unique among IDs returned in LookupResponses // that have not yet been forgotten by ForgetRequests. 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. // It only needs to be unique while the directory or file is open. 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. const RootID NodeID = rootID @@ -261,7 +296,7 @@ type Header struct { } 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 { @@ -368,9 +403,6 @@ func (h *Header) RespondError(err error) { 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 // this. var maxRequestSize = syscall.Getpagesize() @@ -985,7 +1017,33 @@ loop: case opGetxtimes: panic("opGetxtimes") 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 @@ -1147,7 +1205,7 @@ type InitRequest struct { var _ = Request(&InitRequest{}) 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. @@ -1163,7 +1221,7 @@ type InitResponse struct { } 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. @@ -1224,7 +1282,13 @@ type StatfsResponse struct { } 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 @@ -1259,7 +1323,7 @@ type Attr struct { Ctime time.Time // time of last inode change Crtime time.Time // time of creation (OS X only) Mode os.FileMode // file mode - Nlink uint32 // number of links + Nlink uint32 // number of links (usually 1) Uid uint32 // owner uid Gid uint32 // group gid Rdev uint32 // device numbers @@ -1267,6 +1331,10 @@ type Attr struct { 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) { nano := t.UnixNano() sec = uint64(nano / 1e9) @@ -1329,7 +1397,7 @@ type GetattrRequest struct { var _ = Request(&GetattrRequest{}) 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. @@ -1349,7 +1417,7 @@ type GetattrResponse struct { } 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. @@ -1540,8 +1608,12 @@ type LookupResponse struct { 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 { - return fmt.Sprintf("Lookup %+v", *r) + return fmt.Sprintf("Lookup %s", r.string()) } // An OpenRequest asks to open a file or directory @@ -1572,8 +1644,12 @@ type OpenResponse struct { Flags OpenResponseFlags } +func (r *OpenResponse) string() string { + return fmt.Sprintf("%v fl=%v", r.Handle, r.Flags) +} + 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). @@ -1582,7 +1658,8 @@ type CreateRequest struct { Name string Flags OpenFlags Mode os.FileMode - Umask os.FileMode + // Umask of the request. Not supported on OS X. + Umask os.FileMode } var _ = Request(&CreateRequest{}) @@ -1620,7 +1697,7 @@ type CreateResponse struct { } 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. @@ -1628,7 +1705,8 @@ type MkdirRequest struct { Header `json:"-"` Name string Mode os.FileMode - Umask os.FileMode + // Umask of the request. Not supported on OS X. + Umask os.FileMode } var _ = Request(&MkdirRequest{}) @@ -1658,7 +1736,7 @@ type MkdirResponse struct { } 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. @@ -1676,7 +1754,7 @@ type ReadRequest struct { var _ = Request(&ReadRequest{}) 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. @@ -1719,7 +1797,7 @@ type ReleaseRequest struct { var _ = Request(&ReleaseRequest{}) 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. @@ -1861,7 +1939,7 @@ type WriteRequest struct { var _ = Request(&WriteRequest{}) 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 { @@ -1895,7 +1973,7 @@ type WriteResponse struct { } 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, @@ -1948,9 +2026,9 @@ func (r *SetattrRequest) String() string { fmt.Fprintf(&buf, " mtime=now") } if r.Valid.Handle() { - fmt.Fprintf(&buf, " handle=%#x", r.Handle) + fmt.Fprintf(&buf, " handle=%v", r.Handle) } else { - fmt.Fprintf(&buf, " handle=INVALID-%#x", r.Handle) + fmt.Fprintf(&buf, " handle=INVALID-%v", r.Handle) } if r.Valid.LockOwner() { fmt.Fprintf(&buf, " lockowner") @@ -1965,7 +2043,7 @@ func (r *SetattrRequest) String() string { fmt.Fprintf(&buf, " bkuptime=%v", r.Bkuptime) } if r.Valid.Flags() { - fmt.Fprintf(&buf, " flags=%#x", r.Flags) + fmt.Fprintf(&buf, " flags=%v", r.Flags) } return buf.String() } @@ -1988,7 +2066,7 @@ type SetattrResponse struct { } 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 @@ -2004,7 +2082,7 @@ type FlushRequest struct { var _ = Request(&FlushRequest{}) 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. @@ -2065,6 +2143,10 @@ type SymlinkResponse struct { 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. type ReadlinkRequest struct { Header `json:"-"` @@ -2119,7 +2201,7 @@ type RenameRequest struct { var _ = Request(&RenameRequest{}) 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() { @@ -2132,7 +2214,8 @@ type MknodRequest struct { Name string Mode os.FileMode Rdev uint32 - Umask os.FileMode + // Umask of the request. Not supported on OS X. + Umask os.FileMode } var _ = Request(&MknodRequest{}) @@ -2191,3 +2274,30 @@ func (r *InterruptRequest) Respond() { func (r *InterruptRequest) String() string { 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) +} diff --git a/vendor/src/bazil.org/fuse/fuse_darwin.go b/vendor/src/bazil.org/fuse/fuse_darwin.go new file mode 100644 index 000000000..b58dca97d --- /dev/null +++ b/vendor/src/bazil.org/fuse/fuse_darwin.go @@ -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 diff --git a/vendor/src/bazil.org/fuse/fuse_freebsd.go b/vendor/src/bazil.org/fuse/fuse_freebsd.go new file mode 100644 index 000000000..4aa83a0d4 --- /dev/null +++ b/vendor/src/bazil.org/fuse/fuse_freebsd.go @@ -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 diff --git a/vendor/src/bazil.org/fuse/fuse_kernel.go b/vendor/src/bazil.org/fuse/fuse_kernel.go index c08798dfd..87c5ca1dc 100644 --- a/vendor/src/bazil.org/fuse/fuse_kernel.go +++ b/vendor/src/bazil.org/fuse/fuse_kernel.go @@ -62,7 +62,7 @@ type kstatfs struct { Bsize uint32 Namelen uint32 Frsize uint32 - Padding uint32 + _ uint32 Spare [6]uint32 } @@ -159,9 +159,13 @@ const ( OpenWriteOnly OpenFlags = syscall.O_WRONLY 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 OpenCreate OpenFlags = syscall.O_CREAT + OpenDirectory OpenFlags = syscall.O_DIRECTORY OpenExclusive OpenFlags = syscall.O_EXCL + OpenNonblock OpenFlags = syscall.O_NONBLOCK OpenSync OpenFlags = syscall.O_SYNC OpenTruncate OpenFlags = syscall.O_TRUNC ) @@ -213,11 +217,13 @@ func accModeName(flags OpenFlags) string { } var openFlagNames = []flagName{ - {uint32(OpenCreate), "OpenCreate"}, - {uint32(OpenExclusive), "OpenExclusive"}, - {uint32(OpenTruncate), "OpenTruncate"}, {uint32(OpenAppend), "OpenAppend"}, + {uint32(OpenCreate), "OpenCreate"}, + {uint32(OpenDirectory), "OpenDirectory"}, + {uint32(OpenExclusive), "OpenExclusive"}, + {uint32(OpenNonblock), "OpenNonblock"}, {uint32(OpenSync), "OpenSync"}, + {uint32(OpenTruncate), "OpenTruncate"}, } // The OpenResponseFlags are returned in the OpenResponse. @@ -248,12 +254,13 @@ var openResponseFlagNames = []flagName{ type InitFlags uint32 const ( - InitAsyncRead InitFlags = 1 << 0 - InitPosixLocks InitFlags = 1 << 1 - InitFileOps InitFlags = 1 << 2 - InitAtomicTrunc InitFlags = 1 << 3 - InitExportSupport InitFlags = 1 << 4 - InitBigWrites InitFlags = 1 << 5 + InitAsyncRead InitFlags = 1 << 0 + InitPosixLocks InitFlags = 1 << 1 + InitFileOps InitFlags = 1 << 2 + InitAtomicTrunc InitFlags = 1 << 3 + InitExportSupport InitFlags = 1 << 4 + InitBigWrites InitFlags = 1 << 5 + // Do not mask file access modes with umask. Not supported on OS X. InitDontMask InitFlags = 1 << 6 InitSpliceWrite InitFlags = 1 << 7 InitSpliceMove InitFlags = 1 << 8 @@ -412,14 +419,14 @@ type forgetIn struct { type getattrIn struct { GetattrFlags uint32 - dummy uint32 + _ uint32 Fh uint64 } type attrOut struct { AttrValid uint64 // Cache timeout for the attributes AttrValidNsec uint32 - Dummy uint32 + _ uint32 Attr attr } @@ -441,10 +448,10 @@ type getxtimesOut struct { } type mknodIn struct { - Mode uint32 - Rdev uint32 - Umask uint32 - padding uint32 + Mode uint32 + Rdev uint32 + Umask uint32 + _ uint32 // "filename\x00" follows. } @@ -482,6 +489,7 @@ type exchangeIn struct { Olddir uint64 Newdir uint64 Options uint64 + // "oldname\x00newname\x00" follows } type linkIn struct { @@ -490,7 +498,7 @@ type linkIn struct { type setattrInCommon struct { Valid uint32 - Padding uint32 + _ uint32 Fh uint64 Size uint64 LockOwner uint64 // unused on OS X? @@ -515,14 +523,14 @@ type openIn struct { type openOut struct { Fh uint64 OpenFlags uint32 - Padding uint32 + _ uint32 } type createIn struct { - Flags uint32 - Mode uint32 - Umask uint32 - padding uint32 + Flags uint32 + Mode uint32 + Umask uint32 + _ uint32 } func createInSize(p Protocol) uintptr { @@ -544,7 +552,7 @@ type releaseIn struct { type flushIn struct { Fh uint64 FlushFlags uint32 - Padding uint32 + _ uint32 LockOwner uint64 } @@ -555,7 +563,7 @@ type readIn struct { ReadFlags uint32 LockOwner uint64 Flags uint32 - padding uint32 + _ uint32 } func readInSize(p Protocol) uintptr { @@ -590,7 +598,7 @@ type writeIn struct { WriteFlags uint32 LockOwner uint64 Flags uint32 - padding uint32 + _ uint32 } func writeInSize(p Protocol) uintptr { @@ -603,8 +611,8 @@ func writeInSize(p Protocol) uintptr { } type writeOut struct { - Size uint32 - Padding uint32 + Size uint32 + _ uint32 } // The WriteFlags are passed in WriteRequest. @@ -634,7 +642,7 @@ type statfsOut struct { type fsyncIn struct { Fh uint64 FsyncFlags uint32 - Padding uint32 + _ uint32 } type setxattrInCommon struct { @@ -647,8 +655,8 @@ func (setxattrInCommon) position() uint32 { } type getxattrInCommon struct { - Size uint32 - Padding uint32 + Size uint32 + _ uint32 } func (getxattrInCommon) position() uint32 { @@ -656,8 +664,8 @@ func (getxattrInCommon) position() uint32 { } type getxattrOut struct { - Size uint32 - Padding uint32 + Size uint32 + _ uint32 } type lkIn struct { @@ -665,7 +673,7 @@ type lkIn struct { Owner uint64 Lk fileLock LkFlags uint32 - padding uint32 + _ uint32 } func lkInSize(p Protocol) uintptr { @@ -682,8 +690,8 @@ type lkOut struct { } type accessIn struct { - Mask uint32 - Padding uint32 + Mask uint32 + _ uint32 } type initIn struct { @@ -711,7 +719,7 @@ type interruptIn struct { type bmapIn struct { Block uint64 BlockSize uint32 - Padding uint32 + _ uint32 } type bmapOut struct { @@ -719,14 +727,14 @@ type bmapOut struct { } type inHeader struct { - Len uint32 - Opcode uint32 - Unique uint64 - Nodeid uint64 - Uid uint32 - Gid uint32 - Pid uint32 - Padding uint32 + Len uint32 + Opcode uint32 + Unique uint64 + Nodeid uint64 + Uid uint32 + Gid uint32 + Pid uint32 + _ uint32 } const inHeaderSize = int(unsafe.Sizeof(inHeader{})) @@ -762,5 +770,5 @@ type notifyInvalInodeOut struct { type notifyInvalEntryOut struct { Parent uint64 Namelen uint32 - padding uint32 + _ uint32 } diff --git a/vendor/src/bazil.org/fuse/fuse_kernel_test.go b/vendor/src/bazil.org/fuse/fuse_kernel_test.go index bee2e63c5..ffb7613ba 100644 --- a/vendor/src/bazil.org/fuse/fuse_kernel_test.go +++ b/vendor/src/bazil.org/fuse/fuse_kernel_test.go @@ -7,7 +7,7 @@ import ( "bazil.org/fuse" ) -func TestOpenFlagsAccmodeMask(t *testing.T) { +func TestOpenFlagsAccmodeMaskReadWrite(t *testing.T) { var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC) if g, e := f&fuse.OpenAccessModeMask, fuse.OpenReadWrite; 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) { var f = fuse.OpenFlags(os.O_RDWR | os.O_SYNC | os.O_APPEND) if g, e := f.String(), "OpenReadWrite+OpenAppend+OpenSync"; g != e { diff --git a/vendor/src/bazil.org/fuse/fuse_linux.go b/vendor/src/bazil.org/fuse/fuse_linux.go new file mode 100644 index 000000000..5fb96f9ae --- /dev/null +++ b/vendor/src/bazil.org/fuse/fuse_linux.go @@ -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 diff --git a/vendor/src/bazil.org/fuse/mount.go b/vendor/src/bazil.org/fuse/mount.go new file mode 100644 index 000000000..8054e9021 --- /dev/null +++ b/vendor/src/bazil.org/fuse/mount.go @@ -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) + } +} diff --git a/vendor/src/bazil.org/fuse/mount_darwin.go b/vendor/src/bazil.org/fuse/mount_darwin.go index 86e977ea2..c1c36e62b 100644 --- a/vendor/src/bazil.org/fuse/mount_darwin.go +++ b/vendor/src/bazil.org/fuse/mount_darwin.go @@ -1,22 +1,25 @@ package fuse import ( - "bytes" "errors" "fmt" + "log" "os" "os/exec" + "path" "strconv" "strings" + "sync" "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() error { - cmd := exec.Command("/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs") +func loadOSXFUSE(bin string) error { + cmd := exec.Command(bin) cmd.Dir = "/" cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -24,11 +27,11 @@ func loadOSXFUSE() error { return err } -func openOSXFUSEDev() (*os.File, error) { +func openOSXFUSEDev(devPrefix string) (*os.File, error) { var f *os.File var err error 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) if os.IsNotExist(err) { 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 { - bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" +func handleMountOSXFUSE(helperName string, errCh chan<- error) func(line string) (ignore bool) { + 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 { if strings.Contains(k, ",") || strings.Contains(v, ",") { // 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.Env = os.Environ() + // OSXFUSE <3.3.0 cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=") - // TODO this is used for fs typenames etc, let app influence it - cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_DAEMON_PATH="+bin) - var buf bytes.Buffer - cmd.Stdout = &buf - cmd.Stderr = &buf + // OSXFUSE >=3.3.0 + cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=") - err := cmd.Start() - if err != nil { - return err + daemon := os.Args[0] + if daemonVar != "" { + 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() { - err := cmd.Wait() - if err != nil { - if buf.Len() > 0 { - output := buf.Bytes() - output = bytes.TrimRight(output, "\n") - msg := err.Error() + ": " + string(output) - err = errors.New(msg) + var wg sync.WaitGroup + wg.Add(2) + go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout) + helperName := path.Base(bin) + go lineLogger(&wg, "mount helper error", handleMountOSXFUSE(helperName, 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 !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 = fmt.Errorf("mount_osxfusefs: %v", err) + close(ready) + return } - *errp = err + + *errp = nil close(ready) }() - return err + return nil } func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { - f, err := openOSXFUSEDev() - if err == errNotLoaded { - err = loadOSXFUSE() + 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 { + err = loadOSXFUSE(loc.Load) + if err != nil { + return nil, err + } + // try again + f, err = openOSXFUSEDev(loc.DevicePrefix) + } if err != nil { return nil, err } - // try again - f, err = openOSXFUSEDev() + err = callMount(loc.Mount, loc.DaemonVar, dir, conf, f, ready, errp) + if err != nil { + f.Close() + return nil, err + } + return f, nil } - if err != nil { - return nil, err - } - err = callMount(dir, conf, f, ready, errp) - if err != nil { - f.Close() - return nil, err - } - return f, nil + return nil, ErrOSXFUSENotFound } diff --git a/vendor/src/bazil.org/fuse/mount_freebsd.go b/vendor/src/bazil.org/fuse/mount_freebsd.go index 2914ce9e1..70bb41024 100644 --- a/vendor/src/bazil.org/fuse/mount_freebsd.go +++ b/vendor/src/bazil.org/fuse/mount_freebsd.go @@ -2,11 +2,51 @@ package fuse import ( "fmt" + "log" "os" "os/exec" "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) { for k, v := range conf.options { 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} - out, err := cmd.CombinedOutput() + stdout, err := cmd.StdoutPipe() 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) diff --git a/vendor/src/bazil.org/fuse/mount_linux.go b/vendor/src/bazil.org/fuse/mount_linux.go index de7a06003..197d1044e 100644 --- a/vendor/src/bazil.org/fuse/mount_linux.go +++ b/vendor/src/bazil.org/fuse/mount_linux.go @@ -1,35 +1,58 @@ package fuse import ( - "bufio" "fmt" - "io" "log" "net" "os" "os/exec" + "strings" "sync" "syscall" ) -func lineLogger(wg *sync.WaitGroup, prefix string, r io.ReadCloser) { - defer wg.Done() - - scanner := bufio.NewScanner(r) - for scanner.Scan() { - switch line := scanner.Text(); line { - case `fusermount: failed to open /etc/fuse.conf: Permission denied`: +func handleFusermountStderr(errCh chan<- error) func(line string) (ignore bool) { + return func(line string) (ignore bool) { + if line == `fusermount: failed to open /etc/fuse.conf: Permission denied` { // Silence this particular message, it occurs way too // commonly and isn't very relevant to whether the mount // succeeds or not. - continue - default: - log.Printf("%s: %s", prefix, line) + 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: + // not the first error; fall back to logging it + return false + } + } + + 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 } } - if err := scanner.Err(); err != nil { - log.Printf("%s, error reading: %v", prefix, err) - } + return false } func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { @@ -70,11 +93,26 @@ func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (f if err := cmd.Start(); err != nil { return nil, fmt.Errorf("fusermount: %v", err) } + helperErrCh := make(chan error, 1) wg.Add(2) - go lineLogger(&wg, "mount helper output", stdout) - go lineLogger(&wg, "mount helper error", stderr) + go lineLogger(&wg, "mount helper output", neverIgnoreLine, stdout) + go lineLogger(&wg, "mount helper error", handleFusermountStderr(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 !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) } diff --git a/vendor/src/bazil.org/fuse/options.go b/vendor/src/bazil.org/fuse/options.go index 98c418f1d..65ce8a541 100644 --- a/vendor/src/bazil.org/fuse/options.go +++ b/vendor/src/bazil.org/fuse/options.go @@ -12,9 +12,10 @@ func dummyOption(conf *mountConfig) error { // mountConfig holds the configuration for a mount operation. // Use it by passing MountOption values to Mount. type mountConfig struct { - options map[string]string - maxReadahead uint32 - initFlags InitFlags + options map[string]string + maxReadahead uint32 + initFlags InitFlags + osxfuseLocations []OSXFUSEPaths } func escapeComma(s string) string { @@ -82,6 +83,63 @@ func VolumeName(name string) MountOption { 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") // 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 // the file mode (as in chmod). // @@ -168,3 +244,67 @@ func WritebackCache() MountOption { 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 + } +} diff --git a/vendor/src/bazil.org/fuse/options_daemon_timeout_test.go b/vendor/src/bazil.org/fuse/options_daemon_timeout_test.go new file mode 100644 index 000000000..1cd3eccff --- /dev/null +++ b/vendor/src/bazil.org/fuse/options_daemon_timeout_test.go @@ -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) + } +} diff --git a/vendor/src/bazil.org/fuse/options_darwin.go b/vendor/src/bazil.org/fuse/options_darwin.go index f71fa97eb..faa9d78e7 100644 --- a/vendor/src/bazil.org/fuse/options_darwin.go +++ b/vendor/src/bazil.org/fuse/options_darwin.go @@ -11,3 +11,25 @@ func volumeName(name string) MountOption { 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 +} diff --git a/vendor/src/bazil.org/fuse/options_freebsd.go b/vendor/src/bazil.org/fuse/options_freebsd.go index 5a0b84806..7c164b136 100644 --- a/vendor/src/bazil.org/fuse/options_freebsd.go +++ b/vendor/src/bazil.org/fuse/options_freebsd.go @@ -7,3 +7,22 @@ func localVolume(conf *mountConfig) error { func volumeName(name string) MountOption { 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 +} diff --git a/vendor/src/bazil.org/fuse/options_linux.go b/vendor/src/bazil.org/fuse/options_linux.go index 5a0b84806..13f0896d5 100644 --- a/vendor/src/bazil.org/fuse/options_linux.go +++ b/vendor/src/bazil.org/fuse/options_linux.go @@ -7,3 +7,19 @@ func localVolume(conf *mountConfig) error { func volumeName(name string) MountOption { 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 +} diff --git a/vendor/src/bazil.org/fuse/options_test.go b/vendor/src/bazil.org/fuse/options_test.go index 2578e1f47..965c00c95 100644 --- a/vendor/src/bazil.org/fuse/options_test.go +++ b/vendor/src/bazil.org/fuse/options_test.go @@ -165,6 +165,7 @@ func TestMountOptionDefaultPermissions(t *testing.T) { t.Skip("FreeBSD does not support DefaultPermissions") } t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{ &fstestutil.ChildMap{"child": unwritableFile{}}, @@ -172,7 +173,6 @@ func TestMountOptionDefaultPermissions(t *testing.T) { nil, fuse.DefaultPermissions(), ) - if err != nil { t.Fatal(err) } @@ -203,12 +203,12 @@ func (createrDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fus func TestMountOptionReadOnly(t *testing.T) { t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{createrDir{}}, nil, fuse.ReadOnly(), ) - if err != nil { t.Fatal(err) }