diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b5d73b44d..8e0f27b54 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -7,7 +7,7 @@ "Deps": [ { "ImportPath": "bazil.org/fuse", - "Rev": "6312e7c7c12b9337021a37aff2b0f655f4709688" + "Rev": "18419ee53958df28fcfc9490fe6123bd59e237bb" }, { "ImportPath": "github.com/jessevdk/go-flags", diff --git a/Godeps/_workspace/src/bazil.org/fuse/.gitignore b/Godeps/_workspace/src/bazil.org/fuse/.gitignore index 2b286ca94..53589948c 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/.gitignore +++ b/Godeps/_workspace/src/bazil.org/fuse/.gitignore @@ -6,3 +6,6 @@ .*.swp *.test + +/clockfs +/hellofs diff --git a/Godeps/_workspace/src/bazil.org/fuse/README.md b/Godeps/_workspace/src/bazil.org/fuse/README.md index 471b2b258..8c6d556ee 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/README.md +++ b/Godeps/_workspace/src/bazil.org/fuse/README.md @@ -15,7 +15,7 @@ Here’s how to get going: Website: http://bazil.org/fuse/ -Github repository: https://github.com/bazillion/fuse +Github repository: https://github.com/bazil/fuse API docs: http://godoc.org/bazil.org/fuse diff --git a/Godeps/_workspace/src/bazil.org/fuse/buffer.go b/Godeps/_workspace/src/bazil.org/fuse/buffer.go new file mode 100644 index 000000000..bb1d2b776 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/buffer.go @@ -0,0 +1,35 @@ +package fuse + +import "unsafe" + +// buffer provides a mechanism for constructing a message from +// multiple segments. +type buffer []byte + +// alloc allocates size bytes and returns a pointer to the new +// segment. +func (w *buffer) alloc(size uintptr) unsafe.Pointer { + s := int(size) + if len(*w)+s > cap(*w) { + old := *w + *w = make([]byte, len(*w), 2*cap(*w)+s) + copy(*w, old) + } + l := len(*w) + *w = (*w)[:l+s] + return unsafe.Pointer(&(*w)[l]) +} + +// reset clears out the contents of the buffer. +func (w *buffer) reset() { + for i := range (*w)[:cap(*w)] { + (*w)[i] = 0 + } + *w = (*w)[:0] +} + +func newBuffer(extra uintptr) buffer { + const hdrSize = unsafe.Sizeof(outHeader{}) + buf := make(buffer, hdrSize, hdrSize+extra) + return buf +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/examples/clockfs/clockfs.go b/Godeps/_workspace/src/bazil.org/fuse/examples/clockfs/clockfs.go new file mode 100644 index 000000000..178fda943 --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/examples/clockfs/clockfs.go @@ -0,0 +1,173 @@ +// Clockfs implements a file system with the current time in a file. +// It was written to demonstrate kernel cache invalidation. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "sync/atomic" + "syscall" + "time" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + _ "bazil.org/fuse/fs/fstestutil" + "bazil.org/fuse/fuseutil" + "golang.org/x/net/context" +) + +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.Parse() + + if flag.NArg() != 1 { + usage() + os.Exit(2) + } + mountpoint := flag.Arg(0) + + c, err := fuse.Mount( + mountpoint, + fuse.FSName("clock"), + fuse.Subtype("clockfsfs"), + fuse.LocalVolume(), + fuse.VolumeName("Clock filesystem"), + ) + if err != nil { + log.Fatal(err) + } + defer c.Close() + + srv := fs.New(c, nil) + filesys := &FS{ + // We pre-create the clock node so that it's always the same + // object returned from all the Lookups. You could carefully + // track its lifetime between Lookup&Forget, and have the + // ticking & invalidation happen only when active, but let's + // keep this example simple. + clockFile: &File{ + fuse: srv, + }, + } + filesys.clockFile.tick() + // This goroutine never exits. That's fine for this example. + go filesys.clockFile.update() + if err := srv.Serve(filesys); err != nil { + log.Fatal(err) + } + + // Check if the mount process has an error to report. + <-c.Ready + if err := c.MountError; err != nil { + log.Fatal(err) + } +} + +type FS struct { + clockFile *File +} + +var _ fs.FS = (*FS)(nil) + +func (f *FS) Root() (fs.Node, error) { + return &Dir{fs: f}, nil +} + +// Dir implements both Node and Handle for the root directory. +type Dir struct { + fs *FS +} + +var _ fs.Node = (*Dir)(nil) + +func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) error { + a.Inode = 1 + a.Mode = os.ModeDir | 0555 + return nil +} + +var _ fs.NodeStringLookuper = (*Dir)(nil) + +func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { + if name == "clock" { + return d.fs.clockFile, nil + } + return nil, fuse.ENOENT +} + +var dirDirs = []fuse.Dirent{ + {Inode: 2, Name: "clock", Type: fuse.DT_File}, +} + +var _ fs.HandleReadDirAller = (*Dir)(nil) + +func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { + return dirDirs, nil +} + +type File struct { + fs.NodeRef + fuse *fs.Server + content atomic.Value + count uint64 +} + +var _ fs.Node = (*File)(nil) + +func (f *File) Attr(ctx context.Context, a *fuse.Attr) error { + a.Inode = 2 + a.Mode = 0444 + t := f.content.Load().(string) + a.Size = uint64(len(t)) + return nil +} + +var _ fs.NodeOpener = (*File)(nil) + +func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { + if !req.Flags.IsReadOnly() { + return nil, fuse.Errno(syscall.EACCES) + } + resp.Flags |= fuse.OpenKeepCache + return f, nil +} + +var _ fs.Handle = (*File)(nil) + +var _ fs.HandleReader = (*File)(nil) + +func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { + t := f.content.Load().(string) + fuseutil.HandleRead(req, resp, []byte(t)) + return nil +} + +func (f *File) tick() { + // Intentionally a variable-length format, to demonstrate size changes. + f.count++ + s := fmt.Sprintf("%d\t%s\n", f.count, time.Now()) + f.content.Store(s) + + // For simplicity, this example tries to send invalidate + // notifications even when the kernel does not hold a reference to + // the node, so be extra sure to ignore ErrNotCached. + if err := f.fuse.InvalidateNodeData(f); err != nil && err != fuse.ErrNotCached { + log.Printf("invalidate error: %v", err) + } +} + +func (f *File) update() { + tick := time.NewTicker(1 * time.Second) + defer tick.Stop() + for range tick.C { + f.tick() + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go b/Godeps/_workspace/src/bazil.org/fuse/examples/hellofs/hello.go similarity index 92% rename from Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go rename to Godeps/_workspace/src/bazil.org/fuse/examples/hellofs/hello.go index 5d9febe75..5ec5ce8ee 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/hellofs/hello.go +++ b/Godeps/_workspace/src/bazil.org/fuse/examples/hellofs/hello.go @@ -63,9 +63,10 @@ func (FS) Root() (fs.Node, error) { // Dir implements both Node and Handle for the root directory. type Dir struct{} -func (Dir) Attr(a *fuse.Attr) { +func (Dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0555 + return nil } func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { @@ -88,10 +89,11 @@ type File struct{} const greeting = "hello, world\n" -func (File) Attr(a *fuse.Attr) { +func (File) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 2 a.Mode = 0444 a.Size = uint64(len(greeting)) + return nil } func (File) ReadAll(ctx context.Context) ([]byte, error) { diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/bench/bench_test.go b/Godeps/_workspace/src/bazil.org/fuse/fs/bench/bench_test.go index c1177671a..d26f82c17 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/bench/bench_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/bench/bench_test.go @@ -22,13 +22,6 @@ type benchFS struct { } var _ = fs.FS(benchFS{}) -var _ = fs.FSIniter(benchFS{}) - -func (benchFS) Init(ctx context.Context, req *fuse.InitRequest, resp *fuse.InitResponse) error { - resp.MaxReadahead = 64 * 1024 * 1024 - resp.Flags |= fuse.InitAsyncRead - return nil -} func (f benchFS) Root() (fs.Node, error) { return benchDir{conf: f.conf}, nil @@ -43,9 +36,10 @@ var _ = fs.NodeStringLookuper(benchDir{}) var _ = fs.Handle(benchDir{}) var _ = fs.HandleReadDirAller(benchDir{}) -func (benchDir) Attr(a *fuse.Attr) { +func (benchDir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0555 + return nil } func (d benchDir) Lookup(ctx context.Context, name string) (fs.Node, error) { @@ -73,10 +67,11 @@ var _ = fs.Handle(benchFile{}) var _ = fs.HandleReader(benchFile{}) var _ = fs.HandleWriter(benchFile{}) -func (benchFile) Attr(a *fuse.Attr) { +func (benchFile) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 2 a.Mode = 0644 a.Size = 9999999999999999 + return nil } func (f benchFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { @@ -103,12 +98,14 @@ func (benchFile) Fsync(ctx context.Context, req *fuse.FsyncRequest) error { } func benchmark(b *testing.B, fn func(b *testing.B, mnt string), conf *benchConfig) { - srv := &fs.Server{ - FS: benchFS{ - conf: conf, - }, + filesys := benchFS{ + conf: conf, } - mnt, err := fstestutil.Mounted(srv) + mnt, err := fstestutil.Mounted(filesys, nil, + fuse.MaxReadahead(64*1024*1024), + fuse.AsyncRead(), + fuse.WritebackCache(), + ) if err != nil { b.Fatal(err) } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go index 5c30011df..1209d2572 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/mounted.go @@ -17,7 +17,8 @@ type Mount struct { // Dir is the temporary directory where the filesystem is mounted. Dir string - Conn *fuse.Conn + Conn *fuse.Conn + Server *fs.Server // Error will receive the return value of Serve. Error <-chan error @@ -55,7 +56,7 @@ func (mnt *Mount) Close() { // workaround). // // After successful return, caller must clean up by calling Close. -func Mounted(srv *fs.Server, options ...fuse.MountOption) (*Mount, error) { +func Mounted(filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { dir, err := ioutil.TempDir("", "fusetest") if err != nil { return nil, err @@ -64,26 +65,27 @@ func Mounted(srv *fs.Server, options ...fuse.MountOption) (*Mount, error) { if err != nil { return nil, err } - + server := fs.New(c, conf) done := make(chan struct{}) serveErr := make(chan error, 1) mnt := &Mount{ - Dir: dir, - Conn: c, - Error: serveErr, - done: done, + Dir: dir, + Conn: c, + Server: server, + Error: serveErr, + done: done, } go func() { defer close(done) - serveErr <- srv.Serve(c) + serveErr <- server.Serve(filesys) }() select { case <-mnt.Conn.Ready: - if mnt.Conn.MountError != nil { + if err := mnt.Conn.MountError; err != nil { return nil, err } - return mnt, err + return mnt, nil case err = <-mnt.Error: // Serve quit early if err != nil { @@ -100,14 +102,14 @@ func Mounted(srv *fs.Server, options ...fuse.MountOption) (*Mount, error) { // // The debug log is not enabled by default. Use `-fuse.debug` or call // DebugByDefault to enable. -func MountedT(t testing.TB, filesys fs.FS, options ...fuse.MountOption) (*Mount, error) { - srv := &fs.Server{ - FS: filesys, +func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) { + if conf == nil { + conf = &fs.Config{} } - if debug { - srv.Debug = func(msg interface{}) { + if debug && conf.Debug == nil { + conf.Debug = func(msg interface{}) { t.Logf("FUSE: %s", msg) } } - return Mounted(srv, options...) + return Mounted(filesys, conf, options...) } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/record.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/record.go index fff6fdbd2..2bf2e9f8b 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/record.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/record/record.go @@ -35,7 +35,7 @@ type Counter struct { } func (r *Counter) Inc() { - atomic.StoreUint32(&r.count, 1) + atomic.AddUint32(&r.count, 1) } func (r *Counter) Count() uint32 { @@ -341,6 +341,9 @@ var _ = fs.NodeSetxattrer(&Setxattrs{}) // wrap this call in a function that returns a more useful result. func (r *Setxattrs) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error { tmp := *req + // The byte slice points to memory that will be reused, so make a + // deep copy. + tmp.Xattr = append([]byte(nil), req.Xattr...) r.rec.RecordRequest(&tmp) return nil } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go index 63be5b1ca..c1988bf70 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/fstestutil/testfs.go @@ -22,29 +22,32 @@ func (f SimpleFS) Root() (fs.Node, error) { // File can be embedded in a struct to make it look like a file. type File struct{} -func (f File) Attr(a *fuse.Attr) { +func (f File) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 + return nil } // Dir can be embedded in a struct to make it look like a directory. type Dir struct{} -func (f Dir) Attr(a *fuse.Attr) { +func (f Dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0777 + return nil } // ChildMap is a directory with child nodes looked up from a map. type ChildMap map[string]fs.Node -var _ = fs.Node(ChildMap{}) -var _ = fs.NodeStringLookuper(ChildMap{}) +var _ = fs.Node(&ChildMap{}) +var _ = fs.NodeStringLookuper(&ChildMap{}) -func (f ChildMap) Attr(a *fuse.Attr) { +func (f *ChildMap) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0777 + return nil } -func (f ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) { - child, ok := f[name] +func (f *ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) { + child, ok := (*f)[name] if !ok { return nil, fuse.ENOENT } diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go b/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go index bbc4f6140..e5c3de865 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/serve.go @@ -7,7 +7,9 @@ import ( "fmt" "hash/fnv" "io" + "log" "reflect" + "runtime" "strings" "sync" "time" @@ -30,19 +32,12 @@ const ( // An FS is the interface required of a file system. // // Other FUSE requests can be handled by implementing methods from the -// FS* interfaces, for example FSIniter. +// FS* interfaces, for example FSStatfser. type FS interface { // Root is called to obtain the Node for the file system root. Root() (Node, error) } -type FSIniter interface { - // Init is called to initialize the FUSE connection. - // It can inspect the request and adjust the response as desired. - // Init must return promptly. - Init(ctx context.Context, req *fuse.InitRequest, resp *fuse.InitResponse) error -} - type FSStatfser interface { // Statfs is called to obtain file system metadata. // It should write that data to resp. @@ -81,10 +76,20 @@ type FSInodeGenerator interface { // See the documentation for type FS for general information // pertaining to all methods. // +// A Node must be usable as a map key, that is, it cannot be a +// function, map or slice. +// // Other FUSE requests can be handled by implementing methods from the // Node* interfaces, for example NodeOpener. +// +// Methods returning Node should take care to return the same Node +// when the result is logically the same instance. Without this, each +// Node will get a new NodeID, causing spurious cache invalidations, +// extra lookups and aliasing anomalies. This may not matter for a +// simple, read-only filesystem. type Node interface { - Attr(*fuse.Attr) + // Attr fills attr with the standard metadata for the node. + Attr(ctx context.Context, attr *fuse.Attr) error } type NodeGetattrer interface { @@ -152,7 +157,7 @@ type NodeStringLookuper interface { // Lookup looks up a specific entry in the receiver, // which must be a directory. Lookup should return a Node // corresponding to the entry. If the name does not exist in - // the directory, Lookup should return nil, err. + // the directory, Lookup should return ENOENT. // // Lookup need not to handle the names "." and "..". Lookup(ctx context.Context, name string) (Node, error) @@ -238,14 +243,17 @@ type NodeRemovexattrer interface { var startTime = time.Now() -func nodeAttr(n Node) (attr fuse.Attr) { +func nodeAttr(ctx context.Context, n Node, attr *fuse.Attr) error { + attr.Valid = attrValidTime attr.Nlink = 1 attr.Atime = startTime attr.Mtime = startTime attr.Ctime = startTime attr.Crtime = startTime - n.Attr(&attr) - return + if err := n.Attr(ctx, attr); err != nil { + return err + } + return nil } // A Handle is the interface required of an opened file or directory. @@ -306,43 +314,97 @@ type HandleReleaser interface { Release(ctx context.Context, req *fuse.ReleaseRequest) error } -type Server struct { - FS FS - +type Config struct { // Function to send debug log messages to. If nil, use fuse.Debug. // Note that changing this or fuse.Debug may not affect existing // calls to Serve. // // 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. + // + // Note that changing this may not affect existing calls to Serve. + GetContext func() context.Context +} + +// New returns a new FUSE server ready to serve this kernel FUSE +// connection. +// +// Config may be nil. +func New(conn *fuse.Conn, config *Config) *Server { + s := &Server{ + conn: conn, + req: map[fuse.RequestID]*serveRequest{}, + nodeRef: map[Node]fuse.NodeID{}, + dynamicInode: GenerateDynamicInode, + } + if config != nil { + s.debug = config.Debug + s.context = config.GetContext + } + if s.debug == nil { + s.debug = fuse.Debug + } + if s.context == nil { + s.context = context.Background + } + return s +} + +type Server struct { + // set in New + conn *fuse.Conn + debug func(msg interface{}) + context func() context.Context + + // set once at Serve time + fs FS + dynamicInode func(parent uint64, name string) uint64 + + // state, protected by meta + meta sync.Mutex + req map[fuse.RequestID]*serveRequest + node []*serveNode + nodeRef map[Node]fuse.NodeID + handle []*serveHandle + freeNode []fuse.NodeID + freeHandle []fuse.HandleID + nodeGen uint64 + + // Used to ensure worker goroutines finish before Serve returns + wg sync.WaitGroup } // Serve serves the FUSE connection by making calls to the methods // of fs and the Nodes and Handles it makes available. It returns only // when the connection has been closed or an unexpected error occurs. -func (s *Server) Serve(c *fuse.Conn) error { - sc := serveConn{ - fs: s.FS, - debug: s.Debug, - req: map[fuse.RequestID]*serveRequest{}, - dynamicInode: GenerateDynamicInode, - } - if sc.debug == nil { - sc.debug = fuse.Debug - } - if dyn, ok := sc.fs.(FSInodeGenerator); ok { - sc.dynamicInode = dyn.GenerateInode +func (s *Server) Serve(fs FS) error { + defer s.wg.Wait() // Wait for worker goroutines to complete before return + + s.fs = fs + if dyn, ok := fs.(FSInodeGenerator); ok { + s.dynamicInode = dyn.GenerateInode } - root, err := sc.fs.Root() + root, err := fs.Root() if err != nil { return fmt.Errorf("cannot obtain root node: %v", err) } - sc.node = append(sc.node, nil, &serveNode{inode: 1, node: root, refs: 1}) - sc.handle = append(sc.handle, nil) + // Recognize the root node if it's ever returned from Lookup, + // passed to Invalidate, etc. + s.nodeRef[root] = 1 + s.node = append(s.node, nil, &serveNode{ + inode: 1, + generation: s.nodeGen, + node: root, + refs: 1, + }) + s.handle = append(s.handle, nil) for { - req, err := c.ReadRequest() + req, err := s.conn.ReadRequest() if err != nil { if err == io.EOF { break @@ -350,7 +412,11 @@ func (s *Server) Serve(c *fuse.Conn) error { return err } - go sc.serve(req) + s.wg.Add(1) + go func() { + defer s.wg.Done() + s.serve(req) + }() } return nil } @@ -358,44 +424,40 @@ func (s *Server) Serve(c *fuse.Conn) error { // Serve serves a FUSE connection with the default settings. See // Server.Serve. func Serve(c *fuse.Conn, fs FS) error { - server := Server{ - FS: fs, - } - return server.Serve(c) + server := New(c, nil) + return server.Serve(fs) } type nothing struct{} -type serveConn struct { - meta sync.Mutex - fs FS - req map[fuse.RequestID]*serveRequest - node []*serveNode - handle []*serveHandle - freeNode []fuse.NodeID - freeHandle []fuse.HandleID - nodeGen uint64 - debug func(msg interface{}) - dynamicInode func(parent uint64, name string) uint64 -} - type serveRequest struct { Request fuse.Request cancel func() } type serveNode struct { - inode uint64 - node Node - refs uint64 + inode uint64 + generation uint64 + node Node + refs uint64 + + // Delay freeing the NodeID until waitgroup is done. This allows + // using the NodeID for short periods of time without holding the + // Server.meta lock. + // + // Rules: + // + // - hold Server.meta while calling wg.Add, then unlock + // - do NOT try to reacquire Server.meta + wg sync.WaitGroup } -func (sn *serveNode) attr() (attr fuse.Attr) { - attr = nodeAttr(sn.node) +func (sn *serveNode) attr(ctx context.Context, attr *fuse.Attr) error { + err := nodeAttr(ctx, sn.node, attr) if attr.Inode == 0 { attr.Inode = sn.inode } - return + return err } type serveHandle struct { @@ -404,42 +466,20 @@ type serveHandle struct { nodeID fuse.NodeID } -// NodeRef can be embedded in a Node to recognize the same Node being -// returned from multiple Lookup, Create etc calls. -// -// Without this, each Node will get a new NodeID, causing spurious -// cache invalidations, extra lookups and aliasing anomalies. This may -// not matter for a simple, read-only filesystem. -type NodeRef struct { - id fuse.NodeID - generation uint64 -} +// NodeRef is deprecated. It remains here to decrease code churn on +// FUSE library users. You may remove it from your program now; +// returning the same Node values are now recognized automatically, +// without needing NodeRef. +type NodeRef struct{} -// nodeRef is only ever accessed while holding serveConn.meta -func (n *NodeRef) nodeRef() *NodeRef { - return n -} - -type nodeRef interface { - nodeRef() *NodeRef -} - -func (c *serveConn) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) { +func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) { c.meta.Lock() defer c.meta.Unlock() - var ref *NodeRef - if nodeRef, ok := node.(nodeRef); ok { - ref = nodeRef.nodeRef() - - if ref.id != 0 { - // dropNode guarantees that NodeRef is zeroed at the same - // time as the NodeID is removed from serveConn.node, as - // guarded by c.meta; this means sn cannot be nil here - sn := c.node[ref.id] - sn.refs++ - return ref.id, ref.generation - } + if id, ok := c.nodeRef[node]; ok { + sn := c.node[id] + sn.refs++ + return id, sn.generation } sn := &serveNode{inode: inode, node: node, refs: 1} @@ -452,15 +492,12 @@ func (c *serveConn) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint6 id = fuse.NodeID(len(c.node)) c.node = append(c.node, sn) } - gen = c.nodeGen - if ref != nil { - ref.id = id - ref.generation = gen - } + sn.generation = c.nodeGen + c.nodeRef[node] = id return } -func (c *serveConn) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) { +func (c *Server) saveHandle(handle Handle, nodeID fuse.NodeID) (id fuse.HandleID) { c.meta.Lock() shandle := &serveHandle{handle: handle, nodeID: nodeID} if n := len(c.freeHandle); n > 0 { @@ -485,7 +522,7 @@ func (n *nodeRefcountDropBug) String() string { return fmt.Sprintf("bug: trying to drop %d of %d references to %v", n.N, n.Refs, n.Node) } -func (c *serveConn) dropNode(id fuse.NodeID, n uint64) (forget bool) { +func (c *Server) dropNode(id fuse.NodeID, n uint64) (forget bool) { c.meta.Lock() defer c.meta.Unlock() snode := c.node[id] @@ -508,18 +545,16 @@ func (c *serveConn) dropNode(id fuse.NodeID, n uint64) (forget bool) { snode.refs -= n if snode.refs == 0 { + snode.wg.Wait() c.node[id] = nil - if nodeRef, ok := snode.node.(nodeRef); ok { - ref := nodeRef.nodeRef() - *ref = NodeRef{} - } + delete(c.nodeRef, snode.node) c.freeNode = append(c.freeNode, id) return true } return false } -func (c *serveConn) dropHandle(id fuse.HandleID) { +func (c *Server) dropHandle(id fuse.HandleID) { c.meta.Lock() c.handle[id] = nil c.freeHandle = append(c.freeHandle, id) @@ -532,11 +567,11 @@ type missingHandle struct { } func (m missingHandle) String() string { - return fmt.Sprint("missing handle", m.Handle, m.MaxHandle) + return fmt.Sprint("missing handle: ", m.Handle, m.MaxHandle) } // Returns nil for invalid handles. -func (c *serveConn) getHandle(id fuse.HandleID) (shandle *serveHandle) { +func (c *Server) getHandle(id fuse.HandleID) (shandle *serveHandle) { c.meta.Lock() defer c.meta.Unlock() if id < fuse.HandleID(len(c.handle)) { @@ -609,6 +644,30 @@ func (r response) String() string { } } +type notification struct { + Op string + Node fuse.NodeID + Out interface{} `json:",omitempty"` + Err string `json:",omitempty"` +} + +func (n notification) String() string { + switch { + case 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) + case []byte: + return fmt.Sprintf("=> %s %d [% x] Err:%v", n.Op, n.Node, n.Out, n.Err) + default: + return fmt.Sprintf("=> %s %d %s Err:%v", n.Op, n.Node, n.Out, n.Err) + } + default: + return fmt.Sprintf("=> %s %d Err:%v", n.Op, n.Node, n.Err) + } +} + type logMissingNode struct { MaxNode fuse.NodeID } @@ -638,8 +697,32 @@ func (m *renameNewDirNodeNotFound) String() string { return fmt.Sprintf("In RenameRequest (request %#x), node %d not found", m.Request.Hdr().ID, m.In.NewDir) } -func (c *serveConn) serve(r fuse.Request) { - ctx, cancel := context.WithCancel(context.Background()) +type handlerPanickedError struct { + Request interface{} + Err interface{} +} + +var _ error = handlerPanickedError{} + +func (h handlerPanickedError) Error() string { + return fmt.Sprintf("handler panicked: %v", h.Err) +} + +var _ fuse.ErrorNumber = handlerPanickedError{} + +func (h handlerPanickedError) Errno() fuse.Errno { + if err, ok := h.Err.(fuse.ErrorNumber); ok { + return err.Errno() + } + return fuse.DefaultErrno +} + +func initLookupResponse(s *fuse.LookupResponse) { + s.EntryValid = entryValidTime +} + +func (c *Server) serve(r fuse.Request) { + ctx, cancel := context.WithCancel(c.context()) defer cancel() req := &serveRequest{Request: r, cancel: cancel} @@ -717,6 +800,22 @@ func (c *serveConn) serve(r fuse.Request) { c.meta.Unlock() } + defer func() { + if rec := recover(); rec != nil { + const size = 1 << 16 + buf := make([]byte, size) + n := runtime.Stack(buf, false) + buf = buf[:n] + log.Printf("fuse: panic in handler for %v: %v\n%s", r, rec, buf) + err := handlerPanickedError{ + Request: r, + Err: rec, + } + done(err) + r.RespondError(err) + } + }() + switch r := r.(type) { default: // Note: To FUSE, ENOSYS means "this server never implements this request." @@ -725,22 +824,6 @@ func (c *serveConn) serve(r fuse.Request) { done(fuse.ENOSYS) r.RespondError(fuse.ENOSYS) - // FS operations. - case *fuse.InitRequest: - s := &fuse.InitResponse{ - MaxWrite: 128 * 1024, - Flags: fuse.InitBigWrites, - } - if fs, ok := c.fs.(FSIniter); ok { - if err := fs.Init(ctx, r, s); err != nil { - done(err) - r.RespondError(err) - break - } - } - done(s) - r.Respond(s) - case *fuse.StatfsRequest: s := &fuse.StatfsResponse{} if fs, ok := c.fs.(FSStatfser); ok { @@ -763,8 +846,11 @@ func (c *serveConn) serve(r fuse.Request) { break } } else { - s.AttrValid = attrValidTime - s.Attr = snode.attr() + if err := snode.attr(ctx, &s.Attr); err != nil { + done(err) + r.RespondError(err) + break + } } done(s) r.Respond(s) @@ -782,15 +868,17 @@ func (c *serveConn) serve(r fuse.Request) { break } - if s.AttrValid == 0 { - s.AttrValid = attrValidTime + if err := snode.attr(ctx, &s.Attr); err != nil { + done(err) + r.RespondError(err) + break } - s.Attr = snode.attr() done(s) r.Respond(s) case *fuse.SymlinkRequest: s := &fuse.SymlinkResponse{} + initLookupResponse(&s.LookupResponse) n, ok := node.(NodeSymlinker) if !ok { done(fuse.EIO) // XXX or EPERM like Mkdir? @@ -803,7 +891,11 @@ func (c *serveConn) serve(r fuse.Request) { r.RespondError(err) break } - c.saveLookup(&s.LookupResponse, snode, r.NewName, n2) + if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.NewName, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) @@ -852,7 +944,12 @@ func (c *serveConn) serve(r fuse.Request) { break } s := &fuse.LookupResponse{} - c.saveLookup(s, snode, r.NewName, n2) + initLookupResponse(s) + if err := c.saveLookup(ctx, s, snode, r.NewName, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) @@ -887,6 +984,7 @@ func (c *serveConn) serve(r fuse.Request) { var n2 Node var err error s := &fuse.LookupResponse{} + initLookupResponse(s) if n, ok := node.(NodeStringLookuper); ok { n2, err = n.Lookup(ctx, r.Name) } else if n, ok := node.(NodeRequestLookuper); ok { @@ -901,12 +999,17 @@ func (c *serveConn) serve(r fuse.Request) { r.RespondError(err) break } - c.saveLookup(s, snode, r.Name, n2) + if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) case *fuse.MkdirRequest: s := &fuse.MkdirResponse{} + initLookupResponse(&s.LookupResponse) n, ok := node.(NodeMkdirer) if !ok { done(fuse.EPERM) @@ -919,7 +1022,11 @@ func (c *serveConn) serve(r fuse.Request) { r.RespondError(err) break } - c.saveLookup(&s.LookupResponse, snode, r.Name, n2) + if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) @@ -950,13 +1057,18 @@ func (c *serveConn) serve(r fuse.Request) { break } 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 } - c.saveLookup(&s.LookupResponse, snode, r.Name, n2) + if err := c.saveLookup(ctx, &s.LookupResponse, snode, r.Name, n2); err != nil { + done(err) + r.RespondError(err) + break + } s.Handle = c.saveHandle(h2, hdr.Node) done(s) r.Respond(s) @@ -1232,7 +1344,12 @@ func (c *serveConn) serve(r fuse.Request) { break } s := &fuse.LookupResponse{} - c.saveLookup(s, snode, r.Name, n2) + initLookupResponse(s) + if err := c.saveLookup(ctx, s, snode, r.Name, n2); err != nil { + done(err) + r.RespondError(err) + break + } done(s) r.Respond(s) @@ -1282,19 +1399,133 @@ func (c *serveConn) serve(r fuse.Request) { } } -func (c *serveConn) saveLookup(s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) { - s.Attr = nodeAttr(n2) +func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error { + if err := nodeAttr(ctx, n2, &s.Attr); err != nil { + return err + } if s.Attr.Inode == 0 { s.Attr.Inode = c.dynamicInode(snode.inode, elem) } s.Node, s.Generation = c.saveNode(s.Attr.Inode, n2) - if s.EntryValid == 0 { - s.EntryValid = entryValidTime + return nil +} + +type invalidateNodeDetail struct { + Off int64 + Size int64 +} + +func (i invalidateNodeDetail) String() string { + return fmt.Sprintf("Off:%d Size:%d", i.Off, i.Size) +} + +func errstr(err error) string { + if err == nil { + return "" } - if s.AttrValid == 0 { - s.AttrValid = attrValidTime + return err.Error() +} + +func (s *Server) invalidateNode(node Node, off int64, size int64) error { + s.meta.Lock() + id, ok := s.nodeRef[node] + if ok { + snode := s.node[id] + snode.wg.Add(1) + defer snode.wg.Done() } + s.meta.Unlock() + if !ok { + // This is what the kernel would have said, if we had been + // able to send this message; it's not cached. + return fuse.ErrNotCached + } + // Delay logging until after we can record the error too. We + // consider a /dev/fuse write to be instantaneous enough to not + // need separate before and after messages. + err := s.conn.InvalidateNode(id, off, size) + s.debug(notification{ + Op: "InvalidateNode", + Node: id, + Out: invalidateNodeDetail{ + Off: off, + Size: size, + }, + Err: errstr(err), + }) + return err +} + +// InvalidateNodeAttr invalidates the kernel cache of the attributes +// of node. +// +// Returns fuse.ErrNotCached if the kernel is not currently caching +// the node. +func (s *Server) InvalidateNodeAttr(node Node) error { + return s.invalidateNode(node, 0, 0) +} + +// InvalidateNodeData invalidates the kernel cache of the attributes +// and data of node. +// +// Returns fuse.ErrNotCached if the kernel is not currently caching +// the node. +func (s *Server) InvalidateNodeData(node Node) error { + return s.invalidateNode(node, 0, -1) +} + +// InvalidateNodeDataRange invalidates the kernel cache of the +// attributes and a range of the data of node. +// +// Returns fuse.ErrNotCached if the kernel is not currently caching +// the node. +func (s *Server) InvalidateNodeDataRange(node Node, off int64, size int64) error { + return s.invalidateNode(node, off, size) +} + +type invalidateEntryDetail struct { + Name string +} + +func (i invalidateEntryDetail) String() string { + return fmt.Sprintf("%q", i.Name) +} + +// InvalidateEntry invalidates the kernel cache of the directory entry +// identified by parent node and entry basename. +// +// Kernel may or may not cache directory listings. To invalidate +// those, use InvalidateNode to invalidate all of the data for a +// directory. (As of 2015-06, Linux FUSE does not cache directory +// listings.) +// +// Returns ErrNotCached if the kernel is not currently caching the +// node. +func (s *Server) InvalidateEntry(parent Node, name string) error { + s.meta.Lock() + id, ok := s.nodeRef[parent] + if ok { + snode := s.node[id] + snode.wg.Add(1) + defer snode.wg.Done() + } + s.meta.Unlock() + if !ok { + // This is what the kernel would have said, if we had been + // able to send this message; it's not cached. + return fuse.ErrNotCached + } + err := s.conn.InvalidateEntry(id, name) + s.debug(notification{ + Op: "InvalidateEntry", + Node: id, + Out: invalidateEntryDetail{ + Name: name, + }, + Err: errstr(err), + }) + return err } // DataHandle returns a read-only Handle that satisfies reads diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go b/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go index 6d614ae78..762e0a963 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/serve_test.go @@ -43,15 +43,17 @@ type symlink struct { target string } -func (f symlink) Attr(a *fuse.Attr) { +func (f symlink) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeSymlink | 0666 + return nil } // fifo can be embedded in a struct to make it look like a named pipe. type fifo struct{} -func (f fifo) Attr(a *fuse.Attr) { +func (f fifo) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeNamedPipe | 0666 + return nil } type badRootFS struct{} @@ -63,7 +65,7 @@ func (badRootFS) Root() (fs.Node, error) { func TestRootErr(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, badRootFS{}) + mnt, err := fstestutil.MountedT(t, badRootFS{}, nil) if err == nil { // path for synchronous mounts (linux): started out fine, now // wait for Serve to cycle through @@ -84,15 +86,58 @@ func TestRootErr(t *testing.T) { } } +type testPanic struct{} + +type panicSentinel struct{} + +var _ error = panicSentinel{} + +func (panicSentinel) Error() string { return "just a test" } + +var _ fuse.ErrorNumber = panicSentinel{} + +func (panicSentinel) Errno() fuse.Errno { + return fuse.Errno(syscall.ENAMETOOLONG) +} + +func (f testPanic) Root() (fs.Node, error) { + return f, nil +} + +func (f testPanic) Attr(ctx context.Context, a *fuse.Attr) error { + a.Inode = 1 + a.Mode = os.ModeDir | 0777 + return nil +} + +func (f testPanic) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { + panic(panicSentinel{}) +} + +func TestPanic(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, testPanic{}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + err = os.Mkdir(mnt.Dir+"/trigger-a-panic", 0700) + if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.ENAMETOOLONG { + t.Fatalf("wrong error from panicking handler: %T: %v", err, err) + } +} + type testStatFS struct{} func (f testStatFS) Root() (fs.Node, error) { return f, nil } -func (f testStatFS) Attr(a *fuse.Attr) { +func (f testStatFS) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0777 + return nil } func (f testStatFS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error { @@ -103,12 +148,21 @@ func (f testStatFS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *f func TestStatfs(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, testStatFS{}) + mnt, err := fstestutil.MountedT(t, testStatFS{}, nil) if err != nil { t.Fatal(err) } defer mnt.Close() + // Perform an operation that forces the OS X mount to be ready, so + // we know the Statfs handler will really be called. OS X insists + // on volumes answering Statfs calls very early (before FUSE + // handshake), so OSXFUSE gives made-up answers for a few brief moments + // during the mount process. + if _, err := os.Stat(mnt.Dir + "/does-not-exist"); !os.IsNotExist(err) { + t.Fatal(err) + } + { var st syscall.Statfs_t err = syscall.Statfs(mnt.Dir, &st) @@ -143,7 +197,6 @@ func TestStatfs(t *testing.T) { t.Errorf("got Files = %d; want %d", g, e) } } - } // Test Stat of root. @@ -154,14 +207,17 @@ func (f root) Root() (fs.Node, error) { return f, nil } -func (root) Attr(a *fuse.Attr) { +func (root) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 1 a.Mode = os.ModeDir | 0555 + // This has to be a power of two, but try to pick something that's an unlikely default. + a.BlockSize = 65536 + return nil } func TestStatRoot(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, root{}) + mnt, err := fstestutil.MountedT(t, root{}, nil) if err != nil { t.Fatal(err) } @@ -192,6 +248,13 @@ func TestStatRoot(t *testing.T) { if stat.Gid != 0 { t.Errorf("root has wrong gid: %d", stat.Gid) } + if mnt.Conn.Protocol().HasAttrBlockSize() { + // convert stat.Blksize too because it's int64 on Linux but + // int32 on Darwin. + if g, e := int64(stat.Blksize), int64(65536); g != e { + t.Errorf("root has wrong blocksize: %d != %d", g, e) + } + } } } @@ -203,9 +266,10 @@ type readAll struct { const hi = "hello, world" -func (readAll) Attr(a *fuse.Attr) { +func (readAll) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 a.Size = uint64(len(hi)) + return nil } func (readAll) ReadAll(ctx context.Context) ([]byte, error) { @@ -224,7 +288,7 @@ func testReadAll(t *testing.T, path string) { func TestReadAll(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": readAll{}}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": readAll{}}}, nil) if err != nil { t.Fatal(err) } @@ -239,9 +303,10 @@ type readWithHandleRead struct { fstestutil.File } -func (readWithHandleRead) Attr(a *fuse.Attr) { +func (readWithHandleRead) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 a.Size = uint64(len(hi)) + return nil } func (readWithHandleRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { @@ -251,7 +316,7 @@ func (readWithHandleRead) Read(ctx context.Context, req *fuse.ReadRequest, resp func TestReadAllWithHandleRead(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": readWithHandleRead{}}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": readWithHandleRead{}}}, nil) if err != nil { t.Fatal(err) } @@ -260,6 +325,98 @@ func TestReadAllWithHandleRead(t *testing.T) { testReadAll(t, mnt.Dir+"/child") } +type readFlags struct { + fstestutil.File + fileFlags record.Recorder +} + +func (r *readFlags) Attr(ctx context.Context, a *fuse.Attr) error { + a.Mode = 0666 + a.Size = uint64(len(hi)) + return nil +} + +func (r *readFlags) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { + r.fileFlags.Record(req.FileFlags) + fuseutil.HandleRead(req, resp, []byte(hi)) + return nil +} + +func TestReadFileFlags(t *testing.T) { + t.Parallel() + r := &readFlags{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + if !mnt.Conn.Protocol().HasReadWriteFlags() { + t.Skip("Old FUSE protocol") + } + + f, err := os.OpenFile(mnt.Dir+"/child", os.O_RDWR|os.O_APPEND, 0666) + if err != nil { + t.Fatal(err) + } + defer f.Close() + if _, err := f.Read(make([]byte, 4096)); err != nil { + t.Fatal(err) + } + _ = f.Close() + + want := fuse.OpenReadWrite | fuse.OpenAppend + if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), want; g != e { + t.Errorf("read saw file flags %+v, want %+v", g, e) + } +} + +type writeFlags struct { + fstestutil.File + fileFlags record.Recorder +} + +func (r *writeFlags) Attr(ctx context.Context, a *fuse.Attr) error { + a.Mode = 0666 + a.Size = uint64(len(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) + return nil +} + +func TestWriteFileFlags(t *testing.T) { + t.Parallel() + r := &writeFlags{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + if !mnt.Conn.Protocol().HasReadWriteFlags() { + t.Skip("Old FUSE protocol") + } + + f, err := os.OpenFile(mnt.Dir+"/child", os.O_RDWR|os.O_APPEND, 0666) + if err != nil { + t.Fatal(err) + } + defer f.Close() + if _, err := f.Write(make([]byte, 4096)); err != nil { + t.Fatal(err) + } + _ = f.Close() + + want := fuse.OpenReadWrite | fuse.OpenAppend + if g, e := r.fileFlags.Recorded().(fuse.OpenFlags), want; g != e { + t.Errorf("write saw file flags %+v, want %+v", g, e) + } +} + // Test Release. type release struct { @@ -270,7 +427,7 @@ type release struct { func TestRelease(t *testing.T) { t.Parallel() r := &release{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": r}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil) if err != nil { t.Fatal(err) } @@ -297,7 +454,7 @@ type write struct { func TestWrite(t *testing.T) { t.Parallel() w := &write{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -344,7 +501,7 @@ type writeLarge struct { func TestWriteLarge(t *testing.T) { t.Parallel() w := &write{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -391,7 +548,7 @@ type writeTruncateFlush struct { func TestWriteTruncateFlush(t *testing.T) { t.Parallel() w := &writeTruncateFlush{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -427,7 +584,7 @@ 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}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -441,6 +598,9 @@ func TestMkdir(t *testing.T) { t.Fatalf("mkdir: %v", err) } want := fuse.MkdirRequest{Name: "foo", Mode: os.ModeDir | 0751} + if mnt.Conn.Protocol().HasUmask() { + want.Umask = 0022 + } if g, e := f.RecordedMkdir(), want; g != e { t.Errorf("mkdir saw %+v, want %+v", g, e) } @@ -489,7 +649,7 @@ func (f *create1) Create(ctx context.Context, req *fuse.CreateRequest, resp *fus func TestCreate(t *testing.T) { t.Parallel() f := &create1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -558,7 +718,7 @@ func (f *create3) Remove(ctx context.Context, r *fuse.RemoveRequest) error { func TestCreateWriteRemove(t *testing.T) { t.Parallel() f := &create3{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -607,7 +767,7 @@ func (f *symlink1) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.No func TestSymlink(t *testing.T) { t.Parallel() f := &symlink1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -656,7 +816,7 @@ func (f *link1) Link(ctx context.Context, r *fuse.LinkRequest, old fs.Node) (fs. func TestLink(t *testing.T) { t.Parallel() f := &link1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -703,7 +863,7 @@ func (f *rename1) Rename(ctx context.Context, r *fuse.RenameRequest, newDir fs.N func TestRename(t *testing.T) { t.Parallel() f := &rename1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -741,7 +901,7 @@ func TestMknod(t *testing.T) { } f := &mknod1{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -775,9 +935,10 @@ type dataHandleTest struct { fstestutil.File } -func (dataHandleTest) Attr(a *fuse.Attr) { +func (dataHandleTest) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 a.Size = uint64(len(hi)) + return nil } func (dataHandleTest) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { @@ -787,7 +948,7 @@ func (dataHandleTest) Open(ctx context.Context, req *fuse.OpenRequest, resp *fus func TestDataHandle(t *testing.T) { t.Parallel() f := &dataHandleTest{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -812,9 +973,10 @@ type interrupt struct { hanging chan struct{} } -func (interrupt) Attr(a *fuse.Attr) { +func (interrupt) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0666 a.Size = 1 + return nil } func (it *interrupt) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { @@ -830,7 +992,7 @@ func TestInterrupt(t *testing.T) { t.Parallel() f := &interrupt{} f.hanging = make(chan struct{}, 1) - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -894,7 +1056,7 @@ type truncate struct { func testTruncate(t *testing.T, toSize int64) { t.Parallel() f := &truncate{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -935,7 +1097,7 @@ type ftruncate struct { func testFtruncate(t *testing.T, toSize int64) { t.Parallel() f := &ftruncate{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -985,7 +1147,7 @@ type truncateWithOpen struct { func TestTruncateWithOpen(t *testing.T) { t.Parallel() f := &truncateWithOpen{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1029,7 +1191,7 @@ func (d *readDirAll) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { func TestReadDirAll(t *testing.T) { t.Parallel() f := &readDirAll{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -1071,7 +1233,7 @@ type readDirNotImplemented struct { func TestReadDirNotImplemented(t *testing.T) { t.Parallel() f := &readDirNotImplemented{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -1112,7 +1274,7 @@ func (f *chmod) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fus func TestChmod(t *testing.T) { t.Parallel() f := &chmod{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1140,13 +1302,12 @@ func (f *open) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenR f.Opens.Open(ctx, req, resp) // pick a really distinct error, to identify it later return nil, fuse.Errno(syscall.ENAMETOOLONG) - } func TestOpen(t *testing.T) { t.Parallel() f := &open{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1193,6 +1354,40 @@ func TestOpen(t *testing.T) { } } +type openNonSeekable struct { + fstestutil.File +} + +func (f *openNonSeekable) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { + resp.Flags |= fuse.OpenNonSeekable + return f, nil +} + +func TestOpenNonSeekable(t *testing.T) { + t.Parallel() + f := &openNonSeekable{} + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + if !mnt.Conn.Protocol().HasOpenNonSeekable() { + t.Skip("Old FUSE protocol") + } + + fil, err := os.Open(mnt.Dir + "/child") + if err != nil { + t.Fatal(err) + } + defer fil.Close() + + _, err = fil.Seek(0, os.SEEK_SET) + if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.ESPIPE { + t.Fatalf("wrong error: %v", err) + } +} + // Test Fsync on a dir type fsyncDir struct { @@ -1203,7 +1398,7 @@ type fsyncDir struct { func TestFsyncDir(t *testing.T) { t.Parallel() f := &fsyncDir{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil) if err != nil { t.Fatal(err) } @@ -1254,7 +1449,7 @@ func (f *getxattr) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp func TestGetxattr(t *testing.T) { t.Parallel() f := &getxattr{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1290,7 +1485,7 @@ func (f *getxattrTooSmall) Getxattr(ctx context.Context, req *fuse.GetxattrReque func TestGetxattrTooSmall(t *testing.T) { t.Parallel() f := &getxattrTooSmall{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1321,7 +1516,7 @@ func (f *getxattrSize) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, func TestGetxattrSize(t *testing.T) { t.Parallel() f := &getxattrSize{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1353,7 +1548,7 @@ func (f *listxattr) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, r func TestListxattr(t *testing.T) { t.Parallel() f := &listxattr{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1392,7 +1587,7 @@ func (f *listxattrTooSmall) Listxattr(ctx context.Context, req *fuse.ListxattrRe func TestListxattrTooSmall(t *testing.T) { t.Parallel() f := &listxattrTooSmall{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1423,7 +1618,7 @@ func (f *listxattrSize) Listxattr(ctx context.Context, req *fuse.ListxattrReques func TestListxattrSize(t *testing.T) { t.Parallel() f := &listxattrSize{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1454,7 +1649,7 @@ func testSetxattr(t *testing.T, size int) { t.Parallel() f := &setxattr{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1507,7 +1702,7 @@ type removexattr struct { func TestRemovexattr(t *testing.T) { t.Parallel() f := &removexattr{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": f}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil) if err != nil { t.Fatal(err) } @@ -1537,7 +1732,7 @@ func (f defaultErrno) Lookup(ctx context.Context, name string) (fs.Node, error) func TestDefaultErrno(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{defaultErrno{}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{defaultErrno{}}, nil) if err != nil { t.Fatal(err) } @@ -1583,7 +1778,7 @@ func (f customErrNode) Lookup(ctx context.Context, name string) (fs.Node, error) func TestCustomErrno(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{customErrNode{}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{customErrNode{}}, nil) if err != nil { t.Fatal(err) } @@ -1619,12 +1814,13 @@ func (f *inMemoryFile) bytes() []byte { return f.data } -func (f *inMemoryFile) Attr(a *fuse.Attr) { +func (f *inMemoryFile) Attr(ctx context.Context, a *fuse.Attr) error { f.mu.Lock() defer f.mu.Unlock() a.Mode = 0666 a.Size = uint64(len(f.data)) + return nil } func (f *inMemoryFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { @@ -1703,7 +1899,7 @@ func TestMmap(t *testing.T) { w := &mmap{} w.data = make([]byte, mmapSize) - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -1756,7 +1952,7 @@ func (directRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.Re func TestDirectRead(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": directRead{}}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": directRead{}}}, nil) if err != nil { t.Fatal(err) } @@ -1783,7 +1979,7 @@ func (f *directWrite) Open(ctx context.Context, req *fuse.OpenRequest, resp *fus func TestDirectWrite(t *testing.T) { t.Parallel() w := &directWrite{} - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": w}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil) if err != nil { t.Fatal(err) } @@ -1821,14 +2017,21 @@ type attrUnlinked struct { var _ fs.Node = attrUnlinked{} -func (f attrUnlinked) Attr(a *fuse.Attr) { - f.File.Attr(a) +func (f attrUnlinked) Attr(ctx context.Context, a *fuse.Attr) error { + if err := f.File.Attr(ctx, a); err != nil { + return err + } a.Nlink = 0 + return nil } func TestAttrUnlinked(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.ChildMap{"child": attrUnlinked{}}}) + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": attrUnlinked{}}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() fi, err := os.Stat(mnt.Dir + "/child") if err != nil { @@ -1841,3 +2044,360 @@ func TestAttrUnlinked(t *testing.T) { } } } + +// Test behavior when Attr method fails + +type attrBad struct { +} + +var _ fs.Node = attrBad{} + +func (attrBad) Attr(ctx context.Context, attr *fuse.Attr) error { + return fuse.Errno(syscall.ENAMETOOLONG) +} + +func TestAttrBad(t *testing.T) { + t.Parallel() + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": attrBad{}}}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + _, err = os.Stat(mnt.Dir + "/child") + if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.ENAMETOOLONG { + t.Fatalf("wrong error: %v", err) + } +} + +// Test kernel cache invalidation + +type invalidateAttr struct { + fs.NodeRef + t testing.TB + attr record.Counter +} + +var _ fs.Node = (*invalidateAttr)(nil) + +func (i *invalidateAttr) Attr(ctx context.Context, a *fuse.Attr) error { + i.attr.Inc() + i.t.Logf("Attr called, #%d", i.attr.Count()) + a.Mode = 0600 + return nil +} + +func TestInvalidateNodeAttr(t *testing.T) { + // This test may see false positive failures when run under + // extreme memory pressure. + t.Parallel() + a := &invalidateAttr{ + 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") + } + + for i := 0; i < 10; i++ { + if _, err := os.Stat(mnt.Dir + "/child"); err != nil { + 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) + } + + t.Logf("invalidating...") + if err := mnt.Server.InvalidateNodeAttr(a); err != nil { + t.Fatalf("invalidate error: %v", err) + } + + for i := 0; i < 10; i++ { + if _, err := os.Stat(mnt.Dir + "/child"); err != nil { + t.Fatalf("stat error: %v", err) + } + } + if g, e := a.attr.Count(), uint32(2); g != e { + t.Errorf("wrong Attr call count: %d != %d", g, e) + } +} + +type invalidateData struct { + fs.NodeRef + t testing.TB + attr record.Counter + read record.Counter +} + +const invalidateDataContent = "hello, world\n" + +var _ fs.Node = (*invalidateData)(nil) + +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)) + return nil +} + +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)) + return nil +} + +func TestInvalidateNodeData(t *testing.T) { + // This test may see false positive failures when run under + // extreme memory pressure. + t.Parallel() + a := &invalidateData{ + 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() + + buf := make([]byte, 4) + for i := 0; i < 10; i++ { + if _, err := f.ReadAt(buf, 0); err != nil { + 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...") + 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.attr.Count(), uint32(1); g != e { + t.Errorf("wrong Attr call count: %d != %d", g, e) + } + if g, e := a.read.Count(), uint32(2); g != e { + t.Errorf("wrong Read call count: %d != %d", g, e) + } +} + +type invalidateDataPartial struct { + fs.NodeRef + t testing.TB + attr record.Counter + read record.Counter +} + +var invalidateDataPartialContent = strings.Repeat("hello, world\n", 1000) + +var _ fs.Node = (*invalidateDataPartial)(nil) + +func (i *invalidateDataPartial) 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(invalidateDataPartialContent)) + return nil +} + +var _ fs.HandleReader = (*invalidateDataPartial)(nil) + +func (i *invalidateDataPartial) 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(invalidateDataPartialContent)) + return nil +} + +func TestInvalidateNodeDataRange(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() + + buf := make([]byte, 4) + for i := 0; i < 10; i++ { + if _, err := f.ReadAt(buf, 0); err != nil { + 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...") + if err := mnt.Server.InvalidateNodeDataRange(a, 4096, 4096); 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.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 { + t.Errorf("wrong Read call count: %d != %d", g, e) + } +} + +type invalidateEntryRoot struct { + fs.NodeRef + t testing.TB + lookup record.Counter +} + +var _ fs.Node = (*invalidateEntryRoot)(nil) + +func (i *invalidateEntryRoot) Attr(ctx context.Context, a *fuse.Attr) error { + a.Mode = 0600 | os.ModeDir + return nil +} + +var _ fs.NodeStringLookuper = (*invalidateEntryRoot)(nil) + +func (i *invalidateEntryRoot) Lookup(ctx context.Context, name string) (fs.Node, error) { + if name != "child" { + return nil, fuse.ENOENT + } + i.lookup.Inc() + i.t.Logf("Lookup called, #%d", i.lookup.Count()) + return fstestutil.File{}, nil +} + +func TestInvalidateEntry(t *testing.T) { + // This test may see false positive failures when run under + // extreme memory pressure. + t.Parallel() + a := &invalidateEntryRoot{ + t: t, + } + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{a}, nil) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + if !mnt.Conn.Protocol().HasInvalidate() { + t.Skip("Old FUSE protocol") + } + + for i := 0; i < 10; i++ { + if _, err := os.Stat(mnt.Dir + "/child"); err != nil { + t.Fatalf("stat error: %v", err) + } + } + if g, e := a.lookup.Count(), uint32(1); g != e { + t.Errorf("wrong Lookup call count: %d != %d", g, e) + } + + t.Logf("invalidating...") + if err := mnt.Server.InvalidateEntry(a, "child"); err != nil { + t.Fatalf("invalidate error: %v", err) + } + + for i := 0; i < 10; i++ { + if _, err := os.Stat(mnt.Dir + "/child"); err != nil { + t.Fatalf("stat error: %v", err) + } + } + if g, e := a.lookup.Count(), uint32(2); g != e { + t.Errorf("wrong Lookup call count: %d != %d", g, e) + } +} + +type contextFile struct { + fstestutil.File +} + +var contextFileSentinel int + +func (contextFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { + v := ctx.Value(&contextFileSentinel) + if v == nil { + return nil, fuse.ESTALE + } + data, ok := v.(string) + if !ok { + return nil, fuse.EIO + } + resp.Flags |= fuse.OpenDirectIO + return fs.DataHandle([]byte(data)), nil +} + +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 }, + }) + if err != nil { + t.Fatal(err) + } + defer mnt.Close() + + data, err := ioutil.ReadFile(mnt.Dir + "/child") + if err != nil { + t.Fatalf("cannot read context file: %v", err) + } + if g, e := string(data), input; g != e { + t.Errorf("read wrong data: %q != %q", g, e) + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go b/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go index 07135ce2f..7e078045a 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fs/tree.go @@ -77,8 +77,9 @@ func (t *tree) add(name string, n Node) { t.dir = append(t.dir, treeDir{name, n}) } -func (t *tree) Attr(a *fuse.Attr) { +func (t *tree) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = os.ModeDir | 0555 + return nil } func (t *tree) Lookup(ctx context.Context, name string) (Node, error) { diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse.go b/Godeps/_workspace/src/bazil.org/fuse/fuse.go index 8ef28cb60..c34d05981 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse.go @@ -46,7 +46,7 @@ // The required and optional methods for the FS, Node, and Handle interfaces // have the general form // -// Op(ctx context.Context, req *OpRequest, resp *OpResponse) Error +// Op(ctx context.Context, req *OpRequest, resp *OpResponse) error // // where Op is the name of a FUSE operation. Op reads request // parameters from req and writes results to resp. An operation whose @@ -68,7 +68,7 @@ // can implement ErrorNumber to control the errno returned. Without // ErrorNumber, a generic errno (EIO) is returned. // -// Errors messages will be visible in the debug log as part of the +// Error messages will be visible in the debug log as part of the // response. // // Interrupted Operations @@ -125,9 +125,11 @@ type Conn struct { // File handle for kernel communication. Only safe to access if // rio or wio is held. dev *os.File - buf []byte - wio sync.Mutex + wio sync.RWMutex rio sync.RWMutex + + // Protocol version negotiated with InitRequest/InitResponse. + proto Protocol } // Mount mounts a new FUSE connection on the named directory @@ -141,7 +143,7 @@ type Conn struct { // possible errors. Incoming requests on Conn must be served to make // progress. func Mount(dir string, options ...MountOption) (*Conn, error) { - conf := MountConfig{ + conf := mountConfig{ options: make(map[string]string), } for _, option := range options { @@ -159,9 +161,64 @@ func Mount(dir string, options ...MountOption) (*Conn, error) { return nil, err } c.dev = f + + if err := initMount(c, &conf); err != nil { + c.Close() + return nil, err + } + return c, nil } +type OldVersionError struct { + Kernel Protocol + LibraryMin Protocol +} + +func (e *OldVersionError) Error() string { + return fmt.Sprintf("kernel FUSE version is too old: %v < %v", e.Kernel, e.LibraryMin) +} + +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 err + } + r, ok := req.(*InitRequest) + if !ok { + return fmt.Errorf("missing init, got: %T", req) + } + + min := Protocol{protoVersionMinMajor, protoVersionMinMinor} + if r.Kernel.LT(min) { + req.RespondError(Errno(syscall.EPROTO)) + c.Close() + return &OldVersionError{ + Kernel: r.Kernel, + LibraryMin: min, + } + } + + proto := Protocol{protoVersionMaxMajor, protoVersionMaxMinor} + if r.Kernel.LT(proto) { + // Kernel doesn't support the latest version we have. + proto = r.Kernel + } + c.proto = proto + + s := &InitResponse{ + Library: proto, + MaxReadahead: conf.maxReadahead, + MaxWrite: 128 * 1024, + Flags: InitBigWrites | conf.initFlags, + } + r.Respond(s) + return nil +} + // A Request represents a single FUSE request received from the kernel. // Use a type switch to determine the specific kind. // A request of unrecognized type will have concrete type *Header. @@ -215,13 +272,10 @@ func (h *Header) noResponse() { putMessage(h.msg) } -func (h *Header) respond(out *outHeader, n uintptr) { - h.Conn.respond(out, n) - putMessage(h.msg) -} - -func (h *Header) respondData(out *outHeader, n uintptr, data []byte) { - h.Conn.respondData(out, n, data) +func (h *Header) respond(msg []byte) { + out := (*outHeader)(unsafe.Pointer(&msg[0])) + out.Unique = uint64(h.ID) + h.Conn.respond(msg) putMessage(h.msg) } @@ -308,8 +362,10 @@ func (h *Header) RespondError(err error) { } // FUSE uses negative errors! // TODO: File bug report against OSXFUSE: positive error causes kernel panic. - out := &outHeader{Error: -int32(errno), Unique: uint64(h.ID)} - h.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + hOut := (*outHeader)(unsafe.Pointer(&buf[0])) + hOut.Error = -int32(errno) + h.respond(buf) } // Maximum file write size we are prepared to receive from the kernel. @@ -449,6 +505,10 @@ func (c *Conn) fd() int { return int(c.dev.Fd()) } +func (c *Conn) Protocol() Protocol { + return c.proto +} + // ReadRequest returns the next FUSE request from the kernel. // // Caller must call either Request.Respond or Request.RespondError in @@ -529,8 +589,22 @@ loop: } case opGetattr: - req = &GetattrRequest{ - Header: m.Header(), + switch { + case c.proto.LT(Protocol{7, 9}): + req = &GetattrRequest{ + Header: m.Header(), + } + + default: + in := (*getattrIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + req = &GetattrRequest{ + Header: m.Header(), + Flags: GetattrFlags(in.GetattrFlags), + Handle: HandleID(in.Fh), + } } case opSetattr: @@ -595,33 +669,39 @@ loop: } case opMknod: - in := (*mknodIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + size := mknodInSize(c.proto) + if m.len() < size { goto corrupt } - name := m.bytes()[unsafe.Sizeof(*in):] + in := (*mknodIn)(m.data()) + name := m.bytes()[size:] if len(name) < 2 || name[len(name)-1] != '\x00' { goto corrupt } name = name[:len(name)-1] - req = &MknodRequest{ + r := &MknodRequest{ Header: m.Header(), Mode: fileMode(in.Mode), Rdev: in.Rdev, Name: string(name), } + if c.proto.GE(Protocol{7, 12}) { + r.Umask = fileMode(in.Umask) & os.ModePerm + } + req = r case opMkdir: - in := (*mkdirIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + size := mkdirInSize(c.proto) + if m.len() < size { goto corrupt } - name := m.bytes()[unsafe.Sizeof(*in):] + in := (*mkdirIn)(m.data()) + name := m.bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt } - req = &MkdirRequest{ + r := &MkdirRequest{ Header: m.Header(), Name: string(name[:i]), // observed on Linux: mkdirIn.Mode & syscall.S_IFMT == 0, @@ -629,6 +709,10 @@ loop: // code branch; enforce type to directory Mode: fileMode((in.Mode &^ syscall.S_IFMT) | syscall.S_IFDIR), } + if c.proto.GE(Protocol{7, 12}) { + r.Umask = fileMode(in.Umask) & os.ModePerm + } + req = r case opUnlink, opRmdir: buf := m.bytes() @@ -681,20 +765,26 @@ loop: case opRead, opReaddir: in := (*readIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + if m.len() < readInSize(c.proto) { goto corrupt } - req = &ReadRequest{ + r := &ReadRequest{ Header: m.Header(), Dir: m.hdr.Opcode == opReaddir, Handle: HandleID(in.Fh), Offset: int64(in.Offset), Size: int(in.Size), } + if c.proto.GE(Protocol{7, 9}) { + r.Flags = ReadFlags(in.ReadFlags) + r.LockOwner = in.LockOwner + r.FileFlags = openFlags(in.Flags) + } + req = r case opWrite: in := (*writeIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + if m.len() < writeInSize(c.proto) { goto corrupt } r := &WriteRequest{ @@ -703,7 +793,11 @@ loop: Offset: int64(in.Offset), Flags: WriteFlags(in.WriteFlags), } - buf := m.bytes()[unsafe.Sizeof(*in):] + if c.proto.GE(Protocol{7, 9}) { + r.LockOwner = in.LockOwner + r.FileFlags = openFlags(in.Flags) + } + buf := m.bytes()[writeInSize(c.proto):] if uint32(len(buf)) < in.Size { goto corrupt } @@ -823,8 +917,7 @@ loop: } req = &InitRequest{ Header: m.Header(), - Major: in.Major, - Minor: in.Minor, + Kernel: Protocol{in.Major, in.Minor}, MaxReadahead: in.MaxReadahead, Flags: InitFlags(in.Flags), } @@ -847,21 +940,26 @@ loop: } case opCreate: - in := (*createIn)(m.data()) - if m.len() < unsafe.Sizeof(*in) { + size := createInSize(c.proto) + if m.len() < size { goto corrupt } - name := m.bytes()[unsafe.Sizeof(*in):] + in := (*createIn)(m.data()) + name := m.bytes()[size:] i := bytes.IndexByte(name, '\x00') if i < 0 { goto corrupt } - req = &CreateRequest{ + r := &CreateRequest{ Header: m.Header(), Flags: openFlags(in.Flags), Mode: fileMode(in.Mode), Name: string(name[:i]), } + if c.proto.GE(Protocol{7, 12}) { + r.Umask = fileMode(in.Umask) & os.ModePerm + } + req = r case opInterrupt: in := (*interruptIn)(m.data()) @@ -915,6 +1013,15 @@ func (b bugShortKernelWrite) String() string { return fmt.Sprintf("short kernel write: written=%d/%d error=%q stack=\n%s", b.Written, b.Length, b.Error, b.Stack) } +type bugKernelWriteError struct { + Error string + Stack string +} + +func (b bugKernelWriteError) String() string { + return fmt.Sprintf("kernel write error: error=%q stack=\n%s", b.Error, b.Stack) +} + // safe to call even with nil error func errorString(err error) string { if err == nil { @@ -923,13 +1030,14 @@ func errorString(err error) string { return err.Error() } -func (c *Conn) respond(out *outHeader, n uintptr) { - c.wio.Lock() - defer c.wio.Unlock() - out.Len = uint32(n) - msg := (*[1 << 30]byte)(unsafe.Pointer(out))[:n] +func (c *Conn) writeToKernel(msg []byte) error { + out := (*outHeader)(unsafe.Pointer(&msg[0])) + out.Len = uint32(len(msg)) + + c.wio.RLock() + defer c.wio.RUnlock() nn, err := syscall.Write(c.fd(), msg) - if nn != len(msg) || err != nil { + if err == nil && nn != len(msg) { Debug(bugShortKernelWrite{ Written: int64(nn), Length: int64(len(msg)), @@ -937,24 +1045,100 @@ func (c *Conn) respond(out *outHeader, n uintptr) { Stack: stack(), }) } + return err } -func (c *Conn) respondData(out *outHeader, n uintptr, data []byte) { - c.wio.Lock() - defer c.wio.Unlock() - // TODO: use writev - out.Len = uint32(n + uintptr(len(data))) - msg := make([]byte, out.Len) - copy(msg, (*[1 << 30]byte)(unsafe.Pointer(out))[:n]) - copy(msg[n:], data) - syscall.Write(c.fd(), msg) +func (c *Conn) respond(msg []byte) { + if err := c.writeToKernel(msg); err != nil { + Debug(bugKernelWriteError{ + Error: errorString(err), + Stack: stack(), + }) + } +} + +type notCachedError struct{} + +func (notCachedError) Error() string { + return "node not cached" +} + +var _ ErrorNumber = notCachedError{} + +func (notCachedError) Errno() Errno { + // Behave just like if the original syscall.ENOENT had been passed + // straight through. + return ENOENT +} + +var ( + ErrNotCached = notCachedError{} +) + +// sendInvalidate sends an invalidate notification to kernel. +// +// A returned ENOENT is translated to a friendlier error. +func (c *Conn) sendInvalidate(msg []byte) error { + switch err := c.writeToKernel(msg); err { + case syscall.ENOENT: + return ErrNotCached + default: + return err + } +} + +// InvalidateNode invalidates the kernel cache of the attributes and a +// range of the data of a node. +// +// Giving offset 0 and size -1 means all data. To invalidate just the +// attributes, give offset 0 and size 0. +// +// Returns ErrNotCached if the kernel is not currently caching the +// node. +func (c *Conn) InvalidateNode(nodeID NodeID, off int64, size int64) error { + buf := newBuffer(unsafe.Sizeof(notifyInvalInodeOut{})) + h := (*outHeader)(unsafe.Pointer(&buf[0])) + // h.Unique is 0 + h.Error = notifyCodeInvalInode + out := (*notifyInvalInodeOut)(buf.alloc(unsafe.Sizeof(notifyInvalInodeOut{}))) + out.Ino = uint64(nodeID) + out.Off = off + out.Len = size + return c.sendInvalidate(buf) +} + +// InvalidateEntry invalidates the kernel cache of the directory entry +// identified by parent directory node ID and entry basename. +// +// Kernel may or may not cache directory listings. To invalidate +// those, use InvalidateNode to invalidate all of the data for a +// directory. (As of 2015-06, Linux FUSE does not cache directory +// listings.) +// +// Returns ErrNotCached if the kernel is not currently caching the +// node. +func (c *Conn) InvalidateEntry(parent NodeID, name string) error { + const maxUint32 = ^uint32(0) + if uint64(len(name)) > uint64(maxUint32) { + // very unlikely, but we don't want to silently truncate + return syscall.ENAMETOOLONG + } + buf := newBuffer(unsafe.Sizeof(notifyInvalEntryOut{}) + uintptr(len(name)) + 1) + h := (*outHeader)(unsafe.Pointer(&buf[0])) + // h.Unique is 0 + h.Error = notifyCodeInvalEntry + out := (*notifyInvalEntryOut)(buf.alloc(unsafe.Sizeof(notifyInvalEntryOut{}))) + out.Parent = uint64(parent) + out.Namelen = uint32(len(name)) + buf = append(buf, name...) + buf = append(buf, '\x00') + return c.sendInvalidate(buf) } // An InitRequest is the first request sent on a FUSE file system. type InitRequest struct { Header `json:"-"` - Major uint32 - Minor uint32 + Kernel Protocol // Maximum readahead in bytes that the kernel plans to use. MaxReadahead uint32 Flags InitFlags @@ -963,11 +1147,12 @@ type InitRequest struct { var _ = Request(&InitRequest{}) func (r *InitRequest) String() string { - return fmt.Sprintf("Init [%s] %d.%d ra=%d fl=%v", &r.Header, r.Major, r.Minor, r.MaxReadahead, r.Flags) + return fmt.Sprintf("Init [%s] %v ra=%d fl=%v", &r.Header, r.Kernel, r.MaxReadahead, r.Flags) } // An InitResponse is the response to an InitRequest. type InitResponse struct { + Library Protocol // Maximum readahead in bytes that the kernel can use. Ignored if // greater than InitRequest.MaxReadahead. MaxReadahead uint32 @@ -983,20 +1168,20 @@ func (r *InitResponse) String() string { // Respond replies to the request with the given response. func (r *InitRequest) Respond(resp *InitResponse) { - out := &initOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Major: kernelVersion, - Minor: kernelMinorVersion, - MaxReadahead: resp.MaxReadahead, - Flags: uint32(resp.Flags), - MaxWrite: resp.MaxWrite, - } + buf := newBuffer(unsafe.Sizeof(initOut{})) + out := (*initOut)(buf.alloc(unsafe.Sizeof(initOut{}))) + out.Major = resp.Library.Major + out.Minor = resp.Library.Minor + out.MaxReadahead = resp.MaxReadahead + out.Flags = uint32(resp.Flags) + out.MaxWrite = resp.MaxWrite + // MaxWrite larger than our receive buffer would just lead to // errors on large writes. if out.MaxWrite > maxWrite { out.MaxWrite = maxWrite } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + r.respond(buf) } // A StatfsRequest requests information about the mounted file system. @@ -1012,19 +1197,18 @@ func (r *StatfsRequest) String() string { // Respond replies to the request with the given response. func (r *StatfsRequest) Respond(resp *StatfsResponse) { - out := &statfsOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - St: kstatfs{ - Blocks: resp.Blocks, - Bfree: resp.Bfree, - Bavail: resp.Bavail, - Files: resp.Files, - Bsize: resp.Bsize, - Namelen: resp.Namelen, - Frsize: resp.Frsize, - }, + buf := newBuffer(unsafe.Sizeof(statfsOut{})) + out := (*statfsOut)(buf.alloc(unsafe.Sizeof(statfsOut{}))) + out.St = kstatfs{ + Blocks: resp.Blocks, + Bfree: resp.Bfree, + Bavail: resp.Bavail, + Files: resp.Files, + Bsize: resp.Bsize, + Namelen: resp.Namelen, + Frsize: resp.Frsize, } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + r.respond(buf) } // A StatfsResponse is the response to a StatfsRequest. @@ -1059,25 +1243,28 @@ func (r *AccessRequest) String() string { // Respond replies to the request indicating that access is allowed. // To deny access, use RespondError. func (r *AccessRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // An Attr is the metadata for a single file or directory. type Attr struct { - Inode uint64 // inode number - Size uint64 // size in bytes - Blocks uint64 // size in blocks - Atime time.Time // time of last access - Mtime time.Time // time of last modification - 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 - Uid uint32 // owner uid - Gid uint32 // group gid - Rdev uint32 // device numbers - Flags uint32 // chflags(2) flags (OS X only) + Valid time.Duration // how long Attr can be cached + + Inode uint64 // inode number + Size uint64 // size in bytes + Blocks uint64 // size in 512-byte units + Atime time.Time // time of last access + Mtime time.Time // time of last modification + 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 + Uid uint32 // owner uid + Gid uint32 // group gid + Rdev uint32 // device numbers + Flags uint32 // chflags(2) flags (OS X only) + BlockSize uint32 // preferred blocksize for filesystem I/O } func unix(t time.Time) (sec uint64, nsec uint32) { @@ -1087,7 +1274,7 @@ func unix(t time.Time) (sec uint64, nsec uint32) { return } -func (a *Attr) attr() (out attr) { +func (a *Attr) attr(out *attr, proto Protocol) { out.Ino = a.Inode out.Size = a.Size out.Blocks = a.Blocks @@ -1125,6 +1312,9 @@ func (a *Attr) attr() (out attr) { out.Gid = a.Gid out.Rdev = a.Rdev out.SetFlags(a.Flags) + if proto.GE(Protocol{7, 9}) { + out.Blksize = a.BlockSize + } return } @@ -1132,29 +1322,30 @@ func (a *Attr) attr() (out attr) { // A GetattrRequest asks for the metadata for the file denoted by r.Node. type GetattrRequest struct { Header `json:"-"` + Flags GetattrFlags + Handle HandleID } var _ = Request(&GetattrRequest{}) func (r *GetattrRequest) String() string { - return fmt.Sprintf("Getattr [%s]", &r.Header) + return fmt.Sprintf("Getattr [%s] %#x fl=%v", &r.Header, r.Handle, r.Flags) } // Respond replies to the request with the given response. func (r *GetattrRequest) Respond(resp *GetattrResponse) { - out := &attrOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + size := attrOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*attrOut)(buf.alloc(size)) + out.AttrValid = uint64(resp.Attr.Valid / time.Second) + out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) + resp.Attr.attr(&out.Attr, r.Header.Conn.proto) + r.respond(buf) } // A GetattrResponse is the response to a GetattrRequest. type GetattrResponse struct { - AttrValid time.Duration // how long Attr can be cached - Attr Attr // file attributes + Attr Attr // file attributes } func (r *GetattrResponse) String() string { @@ -1187,14 +1378,14 @@ func (r *GetxattrRequest) String() string { // Respond replies to the request with the given response. func (r *GetxattrRequest) Respond(resp *GetxattrResponse) { if r.Size == 0 { - out := &getxattrOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Size: uint32(len(resp.Xattr)), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + buf := newBuffer(unsafe.Sizeof(getxattrOut{})) + out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{}))) + out.Size = uint32(len(resp.Xattr)) + r.respond(buf) } else { - out := &outHeader{Unique: uint64(r.ID)} - r.respondData(out, unsafe.Sizeof(*out), resp.Xattr) + buf := newBuffer(uintptr(len(resp.Xattr))) + buf = append(buf, resp.Xattr...) + r.respond(buf) } } @@ -1223,14 +1414,14 @@ func (r *ListxattrRequest) String() string { // Respond replies to the request with the given response. func (r *ListxattrRequest) Respond(resp *ListxattrResponse) { if r.Size == 0 { - out := &getxattrOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Size: uint32(len(resp.Xattr)), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + buf := newBuffer(unsafe.Sizeof(getxattrOut{})) + out := (*getxattrOut)(buf.alloc(unsafe.Sizeof(getxattrOut{}))) + out.Size = uint32(len(resp.Xattr)) + r.respond(buf) } else { - out := &outHeader{Unique: uint64(r.ID)} - r.respondData(out, unsafe.Sizeof(*out), resp.Xattr) + buf := newBuffer(uintptr(len(resp.Xattr))) + buf = append(buf, resp.Xattr...) + r.respond(buf) } } @@ -1265,8 +1456,8 @@ func (r *RemovexattrRequest) String() string { // Respond replies to the request, indicating that the attribute was removed. func (r *RemovexattrRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A SetxattrRequest asks to set an extended attribute associated with a file. @@ -1310,8 +1501,8 @@ func (r *SetxattrRequest) String() string { // Respond replies to the request, indicating that the extended attribute was set. func (r *SetxattrRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A LookupRequest asks to look up the given name in the directory named by r.Node. @@ -1328,17 +1519,17 @@ func (r *LookupRequest) String() string { // Respond replies to the request with the given response. func (r *LookupRequest) Respond(resp *LookupResponse) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + size := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*entryOut)(buf.alloc(size)) + out.Nodeid = uint64(resp.Node) + out.Generation = resp.Generation + out.EntryValid = uint64(resp.EntryValid / time.Second) + out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) + out.AttrValid = uint64(resp.Attr.Valid / time.Second) + out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) + resp.Attr.attr(&out.Attr, r.Header.Conn.proto) + r.respond(buf) } // A LookupResponse is the response to a LookupRequest. @@ -1346,7 +1537,6 @@ type LookupResponse struct { Node NodeID Generation uint64 EntryValid time.Duration - AttrValid time.Duration Attr Attr } @@ -1369,12 +1559,11 @@ func (r *OpenRequest) String() string { // Respond replies to the request with the given response. func (r *OpenRequest) Respond(resp *OpenResponse) { - out := &openOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Fh: uint64(resp.Handle), - OpenFlags: uint32(resp.Flags), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + buf := newBuffer(unsafe.Sizeof(openOut{})) + out := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{}))) + out.Fh = uint64(resp.Handle) + out.OpenFlags = uint32(resp.Flags) + r.respond(buf) } // A OpenResponse is the response to a OpenRequest. @@ -1393,31 +1582,34 @@ type CreateRequest struct { Name string Flags OpenFlags Mode os.FileMode + Umask os.FileMode } var _ = Request(&CreateRequest{}) func (r *CreateRequest) String() string { - return fmt.Sprintf("Create [%s] %q fl=%v mode=%v", &r.Header, r.Name, r.Flags, r.Mode) + return fmt.Sprintf("Create [%s] %q fl=%v mode=%v umask=%v", &r.Header, r.Name, r.Flags, r.Mode, r.Umask) } // Respond replies to the request with the given response. func (r *CreateRequest) Respond(resp *CreateResponse) { - out := &createOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, + eSize := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(eSize + unsafe.Sizeof(openOut{})) - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), + e := (*entryOut)(buf.alloc(eSize)) + e.Nodeid = uint64(resp.Node) + e.Generation = resp.Generation + e.EntryValid = uint64(resp.EntryValid / time.Second) + e.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) + e.AttrValid = uint64(resp.Attr.Valid / time.Second) + e.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) + resp.Attr.attr(&e.Attr, r.Header.Conn.proto) - Fh: uint64(resp.Handle), - OpenFlags: uint32(resp.Flags), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + o := (*openOut)(buf.alloc(unsafe.Sizeof(openOut{}))) + o.Fh = uint64(resp.Handle) + o.OpenFlags = uint32(resp.Flags) + + r.respond(buf) } // A CreateResponse is the response to a CreateRequest. @@ -1436,27 +1628,28 @@ type MkdirRequest struct { Header `json:"-"` Name string Mode os.FileMode + Umask os.FileMode } var _ = Request(&MkdirRequest{}) func (r *MkdirRequest) String() string { - return fmt.Sprintf("Mkdir [%s] %q mode=%v", &r.Header, r.Name, r.Mode) + return fmt.Sprintf("Mkdir [%s] %q mode=%v umask=%v", &r.Header, r.Name, r.Mode, r.Umask) } // Respond replies to the request with the given response. func (r *MkdirRequest) Respond(resp *MkdirResponse) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + size := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*entryOut)(buf.alloc(size)) + out.Nodeid = uint64(resp.Node) + out.Generation = resp.Generation + out.EntryValid = uint64(resp.EntryValid / time.Second) + out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) + out.AttrValid = uint64(resp.Attr.Valid / time.Second) + out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) + resp.Attr.attr(&out.Attr, r.Header.Conn.proto) + r.respond(buf) } // A MkdirResponse is the response to a MkdirRequest. @@ -1470,23 +1663,27 @@ func (r *MkdirResponse) String() string { // A ReadRequest asks to read from an open file. type ReadRequest struct { - Header `json:"-"` - Dir bool // is this Readdir? - Handle HandleID - Offset int64 - Size int + Header `json:"-"` + Dir bool // is this Readdir? + Handle HandleID + Offset int64 + Size int + Flags ReadFlags + LockOwner uint64 + FileFlags OpenFlags } var _ = Request(&ReadRequest{}) func (r *ReadRequest) String() string { - return fmt.Sprintf("Read [%s] %#x %d @%#x dir=%v", &r.Header, r.Handle, r.Size, r.Offset, r.Dir) + 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) } // Respond replies to the request with the given response. func (r *ReadRequest) Respond(resp *ReadResponse) { - out := &outHeader{Unique: uint64(r.ID)} - r.respondData(out, unsafe.Sizeof(*out), resp.Data) + buf := newBuffer(uintptr(len(resp.Data))) + buf = append(buf, resp.Data...) + r.respond(buf) } // A ReadResponse is the response to a ReadRequest. @@ -1527,8 +1724,8 @@ func (r *ReleaseRequest) String() string { // Respond replies to the request, indicating that the handle has been released. func (r *ReleaseRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A DestroyRequest is sent by the kernel when unmounting the file system. @@ -1546,8 +1743,8 @@ func (r *DestroyRequest) String() string { // Respond replies to the request. func (r *DestroyRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A ForgetRequest is sent by the kernel when forgetting about r.Node @@ -1653,16 +1850,18 @@ func AppendDirent(data []byte, dir Dirent) []byte { // A WriteRequest asks to write to an open file. type WriteRequest struct { Header - Handle HandleID - Offset int64 - Data []byte - Flags WriteFlags + Handle HandleID + Offset int64 + Data []byte + Flags WriteFlags + LockOwner uint64 + FileFlags OpenFlags } var _ = Request(&WriteRequest{}) func (r *WriteRequest) String() string { - return fmt.Sprintf("Write [%s] %#x %d @%d fl=%v", &r.Header, r.Handle, len(r.Data), r.Offset, r.Flags) + 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) } type jsonWriteRequest struct { @@ -1684,11 +1883,10 @@ func (r *WriteRequest) MarshalJSON() ([]byte, error) { // Respond replies to the request with the given response. func (r *WriteRequest) Respond(resp *WriteResponse) { - out := &writeOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Size: uint32(resp.Size), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + buf := newBuffer(unsafe.Sizeof(writeOut{})) + out := (*writeOut)(buf.alloc(unsafe.Sizeof(writeOut{}))) + out.Size = uint32(resp.Size) + r.respond(buf) } // A WriteResponse replies to a write indicating how many bytes were written. @@ -1775,19 +1973,18 @@ func (r *SetattrRequest) String() string { // Respond replies to the request with the given response, // giving the updated attributes. func (r *SetattrRequest) Respond(resp *SetattrResponse) { - out := &attrOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + size := attrOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*attrOut)(buf.alloc(size)) + out.AttrValid = uint64(resp.Attr.Valid / time.Second) + out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) + resp.Attr.attr(&out.Attr, r.Header.Conn.proto) + r.respond(buf) } // A SetattrResponse is the response to a SetattrRequest. type SetattrResponse struct { - AttrValid time.Duration // how long Attr can be cached - Attr Attr // file attributes + Attr Attr // file attributes } func (r *SetattrResponse) String() string { @@ -1812,8 +2009,8 @@ func (r *FlushRequest) String() string { // Respond replies to the request, indicating that the flush succeeded. func (r *FlushRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A RemoveRequest asks to remove a file or directory from the @@ -1832,8 +2029,8 @@ func (r *RemoveRequest) String() string { // Respond replies to the request, indicating that the file was removed. func (r *RemoveRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // A SymlinkRequest is a request to create a symlink making NewName point to Target. @@ -1850,17 +2047,17 @@ func (r *SymlinkRequest) String() string { // Respond replies to the request, indicating that the symlink was created. func (r *SymlinkRequest) Respond(resp *SymlinkResponse) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + size := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*entryOut)(buf.alloc(size)) + out.Nodeid = uint64(resp.Node) + out.Generation = resp.Generation + out.EntryValid = uint64(resp.EntryValid / time.Second) + out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) + out.AttrValid = uint64(resp.Attr.Valid / time.Second) + out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) + resp.Attr.attr(&out.Attr, r.Header.Conn.proto) + r.respond(buf) } // A SymlinkResponse is the response to a SymlinkRequest. @@ -1880,8 +2077,9 @@ func (r *ReadlinkRequest) String() string { } func (r *ReadlinkRequest) Respond(target string) { - out := &outHeader{Unique: uint64(r.ID)} - r.respondData(out, unsafe.Sizeof(*out), []byte(target)) + buf := newBuffer(uintptr(len(target))) + buf = append(buf, target...) + r.respond(buf) } // A LinkRequest is a request to create a hard link. @@ -1898,17 +2096,17 @@ func (r *LinkRequest) String() string { } func (r *LinkRequest) Respond(resp *LookupResponse) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + size := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*entryOut)(buf.alloc(size)) + out.Nodeid = uint64(resp.Node) + out.Generation = resp.Generation + out.EntryValid = uint64(resp.EntryValid / time.Second) + out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) + out.AttrValid = uint64(resp.Attr.Valid / time.Second) + out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) + resp.Attr.attr(&out.Attr, r.Header.Conn.proto) + r.respond(buf) } // A RenameRequest is a request to rename a file. @@ -1925,8 +2123,8 @@ func (r *RenameRequest) String() string { } func (r *RenameRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } type MknodRequest struct { @@ -1934,26 +2132,27 @@ type MknodRequest struct { Name string Mode os.FileMode Rdev uint32 + Umask os.FileMode } var _ = Request(&MknodRequest{}) func (r *MknodRequest) String() string { - return fmt.Sprintf("Mknod [%s] Name %q mode %v rdev %d", &r.Header, r.Name, r.Mode, r.Rdev) + return fmt.Sprintf("Mknod [%s] Name %q mode=%v umask=%v rdev=%d", &r.Header, r.Name, r.Mode, r.Umask, r.Rdev) } func (r *MknodRequest) Respond(resp *LookupResponse) { - out := &entryOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - Nodeid: uint64(resp.Node), - Generation: resp.Generation, - EntryValid: uint64(resp.EntryValid / time.Second), - EntryValidNsec: uint32(resp.EntryValid % time.Second / time.Nanosecond), - AttrValid: uint64(resp.AttrValid / time.Second), - AttrValidNsec: uint32(resp.AttrValid % time.Second / time.Nanosecond), - Attr: resp.Attr.attr(), - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) + size := entryOutSize(r.Header.Conn.proto) + buf := newBuffer(size) + out := (*entryOut)(buf.alloc(size)) + out.Nodeid = uint64(resp.Node) + out.Generation = resp.Generation + out.EntryValid = uint64(resp.EntryValid / time.Second) + out.EntryValidNsec = uint32(resp.EntryValid % time.Second / time.Nanosecond) + out.AttrValid = uint64(resp.Attr.Valid / time.Second) + out.AttrValidNsec = uint32(resp.Attr.Valid % time.Second / time.Nanosecond) + resp.Attr.attr(&out.Attr, r.Header.Conn.proto) + r.respond(buf) } type FsyncRequest struct { @@ -1971,8 +2170,8 @@ func (r *FsyncRequest) String() string { } func (r *FsyncRequest) Respond() { - out := &outHeader{Unique: uint64(r.ID)} - r.respond(out, unsafe.Sizeof(*out)) + buf := newBuffer(0) + r.respond(buf) } // An InterruptRequest is a request to interrupt another pending request. The @@ -1992,38 +2191,3 @@ func (r *InterruptRequest) Respond() { func (r *InterruptRequest) String() string { return fmt.Sprintf("Interrupt [%s] ID %v", &r.Header, r.IntrID) } - -/*{ - -// A XXXRequest xxx. -type XXXRequest struct { - Header `json:"-"` - xxx -} - -var _ = Request(&XXXRequest{}) - -func (r *XXXRequest) String() string { - return fmt.Sprintf("XXX [%s] xxx", &r.Header) -} - -// Respond replies to the request with the given response. -func (r *XXXRequest) Respond(resp *XXXResponse) { - out := &xxxOut{ - outHeader: outHeader{Unique: uint64(r.ID)}, - xxx, - } - r.respond(&out.outHeader, unsafe.Sizeof(*out)) -} - -// A XXXResponse is the response to a XXXRequest. -type XXXResponse struct { - xxx -} - -func (r *XXXResponse) String() string { - return fmt.Sprintf("XXX %+v", *r) -} - - } -*/ diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go index b04c89a9e..c08798dfd 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel.go @@ -41,13 +41,16 @@ import ( "unsafe" ) -// Version is the FUSE version implemented by the package. -const Version = "7.8" +// The FUSE version implemented by the package. +const ( + protoVersionMinMajor = 7 + protoVersionMinMinor = 8 + protoVersionMaxMajor = 7 + protoVersionMaxMinor = 12 +) const ( - kernelVersion = 7 - kernelMinorVersion = 8 - rootID = 1 + rootID = 1 ) type kstatfs struct { @@ -70,6 +73,22 @@ type fileLock struct { Pid uint32 } +// GetattrFlags are bit flags that can be seen in GetattrRequest. +type GetattrFlags uint32 + +const ( + // Indicates the handle is valid. + GetattrFh GetattrFlags = 1 << 0 +) + +var getattrFlagsNames = []flagName{ + {uint32(GetattrFh), "GetattrFh"}, +} + +func (fl GetattrFlags) String() string { + return flagString(uint32(fl), getattrFlagsNames) +} + // The SetattrValid are bit flags describing which fields in the SetattrRequest // are included in the change. type SetattrValid uint32 @@ -207,7 +226,7 @@ type OpenResponseFlags uint32 const ( OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open - OpenNonSeekable OpenResponseFlags = 1 << 2 // (Linux?) + OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X) OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X @@ -220,6 +239,7 @@ func (fl OpenResponseFlags) String() string { var openResponseFlagNames = []flagName{ {uint32(OpenDirectIO), "OpenDirectIO"}, {uint32(OpenKeepCache), "OpenKeepCache"}, + {uint32(OpenNonSeekable), "OpenNonSeekable"}, {uint32(OpenPurgeAttr), "OpenPurgeAttr"}, {uint32(OpenPurgeUBC), "OpenPurgeUBC"}, } @@ -368,7 +388,6 @@ const ( ) type entryOut struct { - outHeader Nodeid uint64 // Inode ID Generation uint64 // Inode generation EntryValid uint64 // Cache timeout for the name @@ -378,21 +397,43 @@ type entryOut struct { Attr attr } +func entryOutSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(entryOut{}.Attr) + unsafe.Offsetof(entryOut{}.Attr.Blksize) + default: + return unsafe.Sizeof(entryOut{}) + } +} + type forgetIn struct { Nlookup uint64 } +type getattrIn struct { + GetattrFlags uint32 + dummy uint32 + Fh uint64 +} + type attrOut struct { - outHeader AttrValid uint64 // Cache timeout for the attributes AttrValidNsec uint32 Dummy uint32 Attr attr } +func attrOutSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(attrOut{}.Attr) + unsafe.Offsetof(attrOut{}.Attr.Blksize) + default: + return unsafe.Sizeof(attrOut{}) + } +} + // OS X type getxtimesOut struct { - outHeader Bkuptime uint64 Crtime uint64 BkuptimeNsec uint32 @@ -400,17 +441,37 @@ type getxtimesOut struct { } type mknodIn struct { - Mode uint32 - Rdev uint32 + Mode uint32 + Rdev uint32 + Umask uint32 + padding uint32 // "filename\x00" follows. } +func mknodInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 12}): + return unsafe.Offsetof(mknodIn{}.Umask) + default: + return unsafe.Sizeof(mknodIn{}) + } +} + type mkdirIn struct { - Mode uint32 - Padding uint32 + Mode uint32 + Umask uint32 // filename follows } +func mkdirInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 12}): + return unsafe.Offsetof(mkdirIn{}.Umask) + 4 + default: + return unsafe.Sizeof(mkdirIn{}) + } +} + type renameIn struct { Newdir uint64 // "oldname\x00newname\x00" follows @@ -452,31 +513,25 @@ type openIn struct { } type openOut struct { - outHeader Fh uint64 OpenFlags uint32 Padding uint32 } type createIn struct { - Flags uint32 - Mode uint32 + Flags uint32 + Mode uint32 + Umask uint32 + padding uint32 } -type createOut struct { - outHeader - - Nodeid uint64 // Inode ID - Generation uint64 // Inode generation - EntryValid uint64 // Cache timeout for the name - AttrValid uint64 // Cache timeout for the attributes - EntryValidNsec uint32 - AttrValidNsec uint32 - Attr attr - - Fh uint64 - OpenFlags uint32 - Padding uint32 +func createInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 12}): + return unsafe.Offsetof(createIn{}.Umask) + default: + return unsafe.Sizeof(createIn{}) + } } type releaseIn struct { @@ -494,10 +549,38 @@ type flushIn struct { } type readIn struct { - Fh uint64 - Offset uint64 - Size uint32 - Padding uint32 + Fh uint64 + Offset uint64 + Size uint32 + ReadFlags uint32 + LockOwner uint64 + Flags uint32 + padding uint32 +} + +func readInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(readIn{}.ReadFlags) + 4 + default: + return unsafe.Sizeof(readIn{}) + } +} + +// The ReadFlags are passed in ReadRequest. +type ReadFlags uint32 + +const ( + // LockOwner field is valid. + ReadLockOwner ReadFlags = 1 << 1 +) + +var readFlagNames = []flagName{ + {uint32(ReadLockOwner), "ReadLockOwner"}, +} + +func (fl ReadFlags) String() string { + return flagString(uint32(fl), readFlagNames) } type writeIn struct { @@ -505,10 +588,21 @@ type writeIn struct { Offset uint64 Size uint32 WriteFlags uint32 + LockOwner uint64 + Flags uint32 + padding uint32 +} + +func writeInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(writeIn{}.LockOwner) + default: + return unsafe.Sizeof(writeIn{}) + } } type writeOut struct { - outHeader Size uint32 Padding uint32 } @@ -516,16 +610,24 @@ type writeOut struct { // The WriteFlags are passed in WriteRequest. type WriteFlags uint32 +const ( + WriteCache WriteFlags = 1 << 0 + // LockOwner field is valid. + WriteLockOwner WriteFlags = 1 << 1 +) + +var writeFlagNames = []flagName{ + {uint32(WriteCache), "WriteCache"}, + {uint32(WriteLockOwner), "WriteLockOwner"}, +} + func (fl WriteFlags) String() string { return flagString(uint32(fl), writeFlagNames) } -var writeFlagNames = []flagName{} - const compatStatfsSize = 48 type statfsOut struct { - outHeader St kstatfs } @@ -554,19 +656,28 @@ func (getxattrInCommon) position() uint32 { } type getxattrOut struct { - outHeader Size uint32 Padding uint32 } type lkIn struct { - Fh uint64 - Owner uint64 - Lk fileLock + Fh uint64 + Owner uint64 + Lk fileLock + LkFlags uint32 + padding uint32 +} + +func lkInSize(p Protocol) uintptr { + switch { + case p.LT(Protocol{7, 9}): + return unsafe.Offsetof(lkIn{}.LkFlags) + default: + return unsafe.Sizeof(lkIn{}) + } } type lkOut struct { - outHeader Lk fileLock } @@ -585,7 +696,6 @@ type initIn struct { const initInSize = int(unsafe.Sizeof(initIn{})) type initOut struct { - outHeader Major uint32 Minor uint32 MaxReadahead uint32 @@ -605,7 +715,6 @@ type bmapIn struct { } type bmapOut struct { - outHeader Block uint64 } @@ -637,3 +746,21 @@ type dirent struct { } const direntSize = 8 + 8 + 4 + 4 + +const ( + notifyCodePoll int32 = 1 + notifyCodeInvalInode int32 = 2 + notifyCodeInvalEntry int32 = 3 +) + +type notifyInvalInodeOut struct { + Ino uint64 + Off int64 + Len int64 +} + +type notifyInvalEntryOut struct { + Parent uint64 + Namelen uint32 + padding uint32 +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go index 4f9347d03..b9873fdf3 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_darwin.go @@ -22,6 +22,8 @@ type attr struct { Gid uint32 Rdev uint32 Flags_ uint32 // OS X only; see chflags(2) + Blksize uint32 + padding uint32 } func (a *attr) SetCrtime(s uint64, ns uint32) { diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go index 7636878c3..b1141e41d 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_freebsd.go @@ -17,6 +17,8 @@ type attr struct { Uid uint32 Gid uint32 Rdev uint32 + Blksize uint32 + padding uint32 } func (a *attr) Crtime() time.Time { diff --git a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go index 6a752457a..d3ba86617 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go +++ b/Godeps/_workspace/src/bazil.org/fuse/fuse_kernel_linux.go @@ -17,8 +17,8 @@ type attr struct { Uid uint32 Gid uint32 Rdev uint32 - // Blksize uint32 // Only in protocol 7.9 - // padding_ uint32 // Only in protocol 7.9 + Blksize uint32 + padding uint32 } func (a *attr) Crtime() time.Time { diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go index 742b084c2..86e977ea2 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_darwin.go @@ -52,7 +52,7 @@ func openOSXFUSEDev() (*os.File, error) { } } -func callMount(dir string, conf *MountConfig, f *os.File, ready chan<- struct{}, errp *error) error { +func callMount(dir string, conf *mountConfig, f *os.File, ready chan<- struct{}, errp *error) error { bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs" for k, v := range conf.options { @@ -104,7 +104,7 @@ func callMount(dir string, conf *MountConfig, f *os.File, ready chan<- struct{}, return err } -func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { +func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { f, err := openOSXFUSEDev() if err == errNotLoaded { err = loadOSXFUSE() diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go b/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go index 951dcf10c..2914ce9e1 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_freebsd.go @@ -7,7 +7,7 @@ import ( "strings" ) -func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { +func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (*os.File, error) { for k, v := range conf.options { if strings.Contains(k, ",") || strings.Contains(v, ",") { // Silly limitation but the mount helper does not diff --git a/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go b/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go index 0748c0a5d..de7a06003 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go +++ b/Godeps/_workspace/src/bazil.org/fuse/mount_linux.go @@ -1,14 +1,38 @@ package fuse import ( + "bufio" "fmt" + "io" + "log" "net" "os" "os/exec" + "sync" "syscall" ) -func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { +func lineLogger(wg *sync.WaitGroup, prefix string, r io.ReadCloser) { + defer wg.Done() + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + switch line := scanner.Text(); line { + case `fusermount: failed to open /etc/fuse.conf: Permission denied`: + // Silence this particular message, it occurs way too + // commonly and isn't very relevant to whether the mount + // succeeds or not. + continue + default: + log.Printf("%s: %s", prefix, line) + } + } + if err := scanner.Err(); err != nil { + log.Printf("%s, error reading: %v", prefix, err) + } +} + +func mount(dir string, conf *mountConfig, ready chan<- struct{}, errp *error) (fusefd *os.File, err error) { // linux mount is never delayed close(ready) @@ -16,8 +40,12 @@ func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (f if err != nil { return nil, fmt.Errorf("socketpair error: %v", err) } - defer syscall.Close(fds[0]) - defer syscall.Close(fds[1]) + + writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") + defer writeFile.Close() + + readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") + defer readFile.Close() cmd := exec.Command( "fusermount", @@ -27,17 +55,29 @@ func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (f ) cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") - writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") - defer writeFile.Close() cmd.ExtraFiles = []*os.File{writeFile} - out, err := cmd.CombinedOutput() - if len(out) > 0 || err != nil { - return nil, fmt.Errorf("fusermount: %q, %v", out, err) + var wg sync.WaitGroup + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("setting up fusermount stderr: %v", err) + } + stderr, err := cmd.StderrPipe() + if err != nil { + return nil, fmt.Errorf("setting up fusermount stderr: %v", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("fusermount: %v", err) + } + wg.Add(2) + go lineLogger(&wg, "mount helper output", stdout) + go lineLogger(&wg, "mount helper error", stderr) + wg.Wait() + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("fusermount: %v", err) } - readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") - defer readFile.Close() c, err := net.FileConn(readFile) if err != nil { return nil, fmt.Errorf("FileConn from fusermount socket: %v", err) diff --git a/Godeps/_workspace/src/bazil.org/fuse/options.go b/Godeps/_workspace/src/bazil.org/fuse/options.go index 5a0b7f180..98c418f1d 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options.go @@ -5,14 +5,16 @@ import ( "strings" ) -func dummyOption(conf *MountConfig) error { +func dummyOption(conf *mountConfig) error { return nil } -// MountConfig holds the configuration for a mount operation. +// mountConfig holds the configuration for a mount operation. // Use it by passing MountOption values to Mount. -type MountConfig struct { - options map[string]string +type mountConfig struct { + options map[string]string + maxReadahead uint32 + initFlags InitFlags } func escapeComma(s string) string { @@ -24,7 +26,7 @@ func escapeComma(s string) string { // getOptions makes a string of options suitable for passing to FUSE // mount flag `-o`. Returns an empty string if no options were set. // Any platform specific adjustments should happen before the call. -func (m *MountConfig) getOptions() string { +func (m *mountConfig) getOptions() string { var opts []string for k, v := range m.options { k = escapeComma(k) @@ -36,15 +38,17 @@ func (m *MountConfig) getOptions() string { return strings.Join(opts, ",") } +type mountOption func(*mountConfig) error + // MountOption is passed to Mount to change the behavior of the mount. -type MountOption func(*MountConfig) error +type MountOption mountOption // FSName sets the file system name (also called source) that is // visible in the list of mounted file systems. // // FreeBSD ignores this option. func FSName(name string) MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { conf.options["fsname"] = name return nil } @@ -57,7 +61,7 @@ func FSName(name string) MountOption { // OS X ignores this option. // FreeBSD ignores this option. func Subtype(fstype string) MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { conf.options["subtype"] = fstype return nil } @@ -84,7 +88,7 @@ var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOth // // Only one of AllowOther or AllowRoot can be used. func AllowOther() MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { if _, ok := conf.options["allow_root"]; ok { return ErrCannotCombineAllowOtherAndAllowRoot } @@ -99,7 +103,7 @@ func AllowOther() MountOption { // // FreeBSD ignores this option. func AllowRoot() MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { if _, ok := conf.options["allow_other"]; ok { return ErrCannotCombineAllowOtherAndAllowRoot } @@ -117,7 +121,7 @@ func AllowRoot() MountOption { // // FreeBSD ignores this option. func DefaultPermissions() MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { conf.options["default_permissions"] = "" return nil } @@ -125,8 +129,42 @@ func DefaultPermissions() MountOption { // ReadOnly makes the mount read-only. func ReadOnly() MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { conf.options["ro"] = "" return nil } } + +// MaxReadahead sets the number of bytes that can be prefetched for +// sequential reads. The kernel can enforce a maximum value lower than +// this. +// +// This setting makes the kernel perform speculative reads that do not +// originate from any client process. This usually tremendously +// improves read performance. +func MaxReadahead(n uint32) MountOption { + return func(conf *mountConfig) error { + conf.maxReadahead = n + return nil + } +} + +// AsyncRead enables multiple outstanding read requests for the same +// handle. Without this, there is at most one request in flight at a +// time. +func AsyncRead() MountOption { + return func(conf *mountConfig) error { + conf.initFlags |= InitAsyncRead + return nil + } +} + +// WritebackCache enables the kernel to buffer writes before sending +// them to the FUSE server. Without this, writethrough caching is +// used. +func WritebackCache() MountOption { + return func(conf *mountConfig) error { + conf.initFlags |= InitWritebackCache + return nil + } +} diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go b/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go index 15aedbcfc..f71fa97eb 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_darwin.go @@ -1,12 +1,12 @@ package fuse -func localVolume(conf *MountConfig) error { +func localVolume(conf *mountConfig) error { conf.options["local"] = "" return nil } func volumeName(name string) MountOption { - return func(conf *MountConfig) error { + return func(conf *mountConfig) error { conf.options["volname"] = name return nil } diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go b/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go index 8eb07e009..5a0b84806 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_freebsd.go @@ -1,6 +1,6 @@ package fuse -func localVolume(conf *MountConfig) error { +func localVolume(conf *mountConfig) error { return nil } diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go index f9c90e8bf..76b13e2ac 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_helper_test.go @@ -1,6 +1,10 @@ package fuse // for TestMountOptionCommaError -func ForTestSetMountOption(conf *MountConfig, k, v string) { - conf.options[k] = v +func ForTestSetMountOption(k, v string) MountOption { + fn := func(conf *mountConfig) error { + conf.options[k] = v + return nil + } + return fn } diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_linux.go b/Godeps/_workspace/src/bazil.org/fuse/options_linux.go index 8eb07e009..5a0b84806 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_linux.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_linux.go @@ -1,6 +1,6 @@ package fuse -func localVolume(conf *MountConfig) error { +func localVolume(conf *mountConfig) error { return nil } diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go index 53f3c6fa3..968628abf 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_nocomma_test.go @@ -18,11 +18,8 @@ func TestMountOptionCommaError(t *testing.T) { // this test is not tied to any specific option, it just needs // some string content var evil = "FuseTest,Marker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, - func(conf *fuse.MountConfig) error { - fuse.ForTestSetMountOption(conf, "fusetest", evil) - return nil - }, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, + fuse.ForTestSetMountOption("fusetest", evil), ) if err == nil { mnt.Close() diff --git a/Godeps/_workspace/src/bazil.org/fuse/options_test.go b/Godeps/_workspace/src/bazil.org/fuse/options_test.go index 8d337aaf6..2578e1f47 100644 --- a/Godeps/_workspace/src/bazil.org/fuse/options_test.go +++ b/Godeps/_workspace/src/bazil.org/fuse/options_test.go @@ -22,7 +22,7 @@ func TestMountOptionFSName(t *testing.T) { } t.Parallel() const name = "FuseTestMarker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.FSName(name), ) if err != nil { @@ -45,7 +45,7 @@ func testMountOptionFSNameEvil(t *testing.T, evil string) { } t.Parallel() var name = "FuseTest" + evil + "Marker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.FSName(name), ) if err != nil { @@ -102,7 +102,7 @@ func TestMountOptionSubtype(t *testing.T) { } t.Parallel() const name = "FuseTestMarker" - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.Subtype(name), ) if err != nil { @@ -125,7 +125,7 @@ func TestMountOptionSubtype(t *testing.T) { func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.AllowOther(), fuse.AllowRoot(), ) @@ -141,7 +141,7 @@ func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) { func TestMountOptionAllowRootThenAllowOther(t *testing.T) { t.Parallel() - mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, + mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil, fuse.AllowRoot(), fuse.AllowOther(), ) @@ -155,8 +155,9 @@ func TestMountOptionAllowRootThenAllowOther(t *testing.T) { type unwritableFile struct{} -func (f unwritableFile) Attr(a *fuse.Attr) { +func (f unwritableFile) Attr(ctx context.Context, a *fuse.Attr) error { a.Mode = 0000 + return nil } func TestMountOptionDefaultPermissions(t *testing.T) { @@ -166,8 +167,9 @@ func TestMountOptionDefaultPermissions(t *testing.T) { t.Parallel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{ - fstestutil.ChildMap{"child": unwritableFile{}}, + &fstestutil.ChildMap{"child": unwritableFile{}}, }, + nil, fuse.DefaultPermissions(), ) @@ -203,6 +205,7 @@ func TestMountOptionReadOnly(t *testing.T) { t.Parallel() mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{createrDir{}}, + nil, fuse.ReadOnly(), ) diff --git a/Godeps/_workspace/src/bazil.org/fuse/protocol.go b/Godeps/_workspace/src/bazil.org/fuse/protocol.go new file mode 100644 index 000000000..a77bbf72f --- /dev/null +++ b/Godeps/_workspace/src/bazil.org/fuse/protocol.go @@ -0,0 +1,75 @@ +package fuse + +import ( + "fmt" +) + +// Protocol is a FUSE protocol version number. +type Protocol struct { + Major uint32 + Minor uint32 +} + +func (p Protocol) String() string { + return fmt.Sprintf("%d.%d", p.Major, p.Minor) +} + +// LT returns whether a is less than b. +func (a Protocol) LT(b Protocol) bool { + return a.Major < b.Major || + (a.Major == b.Major && a.Minor < b.Minor) +} + +// GE returns whether a is greater than or equal to b. +func (a Protocol) GE(b Protocol) bool { + return a.Major > b.Major || + (a.Major == b.Major && a.Minor >= b.Minor) +} + +func (a Protocol) is79() bool { + return a.GE(Protocol{7, 9}) +} + +// HasAttrBlockSize returns whether Attr.BlockSize is respected by the +// kernel. +func (a Protocol) HasAttrBlockSize() bool { + return a.is79() +} + +// HasReadWriteFlags returns whether ReadRequest/WriteRequest +// fields Flags and FileFlags are valid. +func (a Protocol) HasReadWriteFlags() bool { + return a.is79() +} + +// HasGetattrFlags returns whether GetattrRequest field Flags is +// valid. +func (a Protocol) HasGetattrFlags() bool { + return a.is79() +} + +func (a Protocol) is710() bool { + return a.GE(Protocol{7, 10}) +} + +// HasOpenNonSeekable returns whether OpenResponse field Flags flag +// OpenNonSeekable is supported. +func (a Protocol) HasOpenNonSeekable() bool { + return a.is710() +} + +func (a Protocol) is712() bool { + return a.GE(Protocol{7, 12}) +} + +// HasUmask returns whether CreateRequest/MkdirRequest/MknodRequest +// field Umask is valid. +func (a Protocol) HasUmask() bool { + return a.is712() +} + +// HasInvalidate returns whether InvalidateNode/InvalidateEntry are +// supported. +func (a Protocol) HasInvalidate() bool { + return a.is712() +} diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 67e9f4d73..d5fd79d68 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -91,9 +91,10 @@ type snapshots struct { repo *repository.Repository } -func (sn *snapshots) Attr(a *fuse.Attr) { +func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = 0 a.Mode = os.ModeDir | 0555 + return nil } func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { @@ -144,9 +145,10 @@ type dir struct { inode uint64 } -func (d *dir) Attr(a *fuse.Attr) { +func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = d.inode a.Mode = os.ModeDir | 0555 + return nil } func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { @@ -224,10 +226,11 @@ func makeFile(repo *repository.Repository, node *restic.Node) (*file, error) { }, nil } -func (f *file) Attr(a *fuse.Attr) { +func (f *file) Attr(ctx context.Context, a *fuse.Attr) error { a.Inode = f.node.Inode a.Mode = f.node.Mode a.Size = f.node.Size + return nil } func (f *file) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {