Update bazil.org/fuse version

This commit is contained in:
Matthieu Rakotojaona 2015-07-18 19:07:59 +02:00
parent 32a321e51e
commit 3a82612244
30 changed files with 2062 additions and 597 deletions

2
Godeps/Godeps.json generated
View file

@ -7,7 +7,7 @@
"Deps": [ "Deps": [
{ {
"ImportPath": "bazil.org/fuse", "ImportPath": "bazil.org/fuse",
"Rev": "6312e7c7c12b9337021a37aff2b0f655f4709688" "Rev": "18419ee53958df28fcfc9490fe6123bd59e237bb"
}, },
{ {
"ImportPath": "github.com/jessevdk/go-flags", "ImportPath": "github.com/jessevdk/go-flags",

View file

@ -6,3 +6,6 @@
.*.swp .*.swp
*.test *.test
/clockfs
/hellofs

View file

@ -15,7 +15,7 @@ Heres how to get going:
Website: http://bazil.org/fuse/ 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 API docs: http://godoc.org/bazil.org/fuse

35
Godeps/_workspace/src/bazil.org/fuse/buffer.go generated vendored Normal file
View file

@ -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
}

View file

@ -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()
}
}

View file

@ -63,9 +63,10 @@ func (FS) Root() (fs.Node, error) {
// Dir implements both Node and Handle for the root directory. // Dir implements both Node and Handle for the root directory.
type Dir struct{} type Dir struct{}
func (Dir) Attr(a *fuse.Attr) { func (Dir) Attr(ctx context.Context, a *fuse.Attr) error {
a.Inode = 1 a.Inode = 1
a.Mode = os.ModeDir | 0555 a.Mode = os.ModeDir | 0555
return nil
} }
func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { func (Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
@ -88,10 +89,11 @@ type File struct{}
const greeting = "hello, world\n" 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.Inode = 2
a.Mode = 0444 a.Mode = 0444
a.Size = uint64(len(greeting)) a.Size = uint64(len(greeting))
return nil
} }
func (File) ReadAll(ctx context.Context) ([]byte, error) { func (File) ReadAll(ctx context.Context) ([]byte, error) {

View file

@ -22,13 +22,6 @@ type benchFS struct {
} }
var _ = fs.FS(benchFS{}) 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) { func (f benchFS) Root() (fs.Node, error) {
return benchDir{conf: f.conf}, nil return benchDir{conf: f.conf}, nil
@ -43,9 +36,10 @@ var _ = fs.NodeStringLookuper(benchDir{})
var _ = fs.Handle(benchDir{}) var _ = fs.Handle(benchDir{})
var _ = fs.HandleReadDirAller(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.Inode = 1
a.Mode = os.ModeDir | 0555 a.Mode = os.ModeDir | 0555
return nil
} }
func (d benchDir) Lookup(ctx context.Context, name string) (fs.Node, error) { 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.HandleReader(benchFile{})
var _ = fs.HandleWriter(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.Inode = 2
a.Mode = 0644 a.Mode = 0644
a.Size = 9999999999999999 a.Size = 9999999999999999
return nil
} }
func (f benchFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) { 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) { func benchmark(b *testing.B, fn func(b *testing.B, mnt string), conf *benchConfig) {
srv := &fs.Server{ filesys := benchFS{
FS: benchFS{ conf: conf,
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 { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

View file

@ -17,7 +17,8 @@ type Mount struct {
// Dir is the temporary directory where the filesystem is mounted. // Dir is the temporary directory where the filesystem is mounted.
Dir string Dir string
Conn *fuse.Conn Conn *fuse.Conn
Server *fs.Server
// Error will receive the return value of Serve. // Error will receive the return value of Serve.
Error <-chan error Error <-chan error
@ -55,7 +56,7 @@ func (mnt *Mount) Close() {
// workaround). // workaround).
// //
// After successful return, caller must clean up by calling Close. // 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") dir, err := ioutil.TempDir("", "fusetest")
if err != nil { if err != nil {
return nil, err return nil, err
@ -64,26 +65,27 @@ func Mounted(srv *fs.Server, options ...fuse.MountOption) (*Mount, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
server := fs.New(c, conf)
done := make(chan struct{}) done := make(chan struct{})
serveErr := make(chan error, 1) serveErr := make(chan error, 1)
mnt := &Mount{ mnt := &Mount{
Dir: dir, Dir: dir,
Conn: c, Conn: c,
Error: serveErr, Server: server,
done: done, Error: serveErr,
done: done,
} }
go func() { go func() {
defer close(done) defer close(done)
serveErr <- srv.Serve(c) serveErr <- server.Serve(filesys)
}() }()
select { select {
case <-mnt.Conn.Ready: case <-mnt.Conn.Ready:
if mnt.Conn.MountError != nil { if err := mnt.Conn.MountError; err != nil {
return nil, err return nil, err
} }
return mnt, err return mnt, nil
case err = <-mnt.Error: case err = <-mnt.Error:
// Serve quit early // Serve quit early
if err != nil { 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 // The debug log is not enabled by default. Use `-fuse.debug` or call
// DebugByDefault to enable. // DebugByDefault to enable.
func MountedT(t testing.TB, filesys fs.FS, options ...fuse.MountOption) (*Mount, error) { func MountedT(t testing.TB, filesys fs.FS, conf *fs.Config, options ...fuse.MountOption) (*Mount, error) {
srv := &fs.Server{ if conf == nil {
FS: filesys, conf = &fs.Config{}
} }
if debug { if debug && conf.Debug == nil {
srv.Debug = func(msg interface{}) { conf.Debug = func(msg interface{}) {
t.Logf("FUSE: %s", msg) t.Logf("FUSE: %s", msg)
} }
} }
return Mounted(srv, options...) return Mounted(filesys, conf, options...)
} }

View file

@ -35,7 +35,7 @@ type Counter struct {
} }
func (r *Counter) Inc() { func (r *Counter) Inc() {
atomic.StoreUint32(&r.count, 1) atomic.AddUint32(&r.count, 1)
} }
func (r *Counter) Count() uint32 { 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. // wrap this call in a function that returns a more useful result.
func (r *Setxattrs) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error { func (r *Setxattrs) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
tmp := *req 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) r.rec.RecordRequest(&tmp)
return nil return nil
} }

View file

@ -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. // File can be embedded in a struct to make it look like a file.
type File struct{} type File struct{}
func (f File) Attr(a *fuse.Attr) { func (f File) Attr(ctx context.Context, a *fuse.Attr) error {
a.Mode = 0666 a.Mode = 0666
return nil
} }
// Dir can be embedded in a struct to make it look like a directory. // Dir can be embedded in a struct to make it look like a directory.
type Dir struct{} 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 a.Mode = os.ModeDir | 0777
return nil
} }
// ChildMap is a directory with child nodes looked up from a map. // ChildMap is a directory with child nodes looked up from a map.
type ChildMap map[string]fs.Node type ChildMap map[string]fs.Node
var _ = fs.Node(ChildMap{}) var _ = fs.Node(&ChildMap{})
var _ = fs.NodeStringLookuper(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 a.Mode = os.ModeDir | 0777
return nil
} }
func (f ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) { func (f *ChildMap) Lookup(ctx context.Context, name string) (fs.Node, error) {
child, ok := f[name] child, ok := (*f)[name]
if !ok { if !ok {
return nil, fuse.ENOENT return nil, fuse.ENOENT
} }

View file

@ -7,7 +7,9 @@ import (
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"io" "io"
"log"
"reflect" "reflect"
"runtime"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -30,19 +32,12 @@ const (
// An FS is the interface required of a file system. // An FS is the interface required of a file system.
// //
// Other FUSE requests can be handled by implementing methods from the // Other FUSE requests can be handled by implementing methods from the
// FS* interfaces, for example FSIniter. // FS* interfaces, for example FSStatfser.
type FS interface { type FS interface {
// Root is called to obtain the Node for the file system root. // Root is called to obtain the Node for the file system root.
Root() (Node, error) 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 { type FSStatfser interface {
// Statfs is called to obtain file system metadata. // Statfs is called to obtain file system metadata.
// It should write that data to resp. // It should write that data to resp.
@ -81,10 +76,20 @@ type FSInodeGenerator interface {
// See the documentation for type FS for general information // See the documentation for type FS for general information
// pertaining to all methods. // 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 // Other FUSE requests can be handled by implementing methods from the
// Node* interfaces, for example NodeOpener. // 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 { 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 { type NodeGetattrer interface {
@ -152,7 +157,7 @@ type NodeStringLookuper interface {
// Lookup looks up a specific entry in the receiver, // Lookup looks up a specific entry in the receiver,
// which must be a directory. Lookup should return a Node // which must be a directory. Lookup should return a Node
// corresponding to the entry. If the name does not exist in // 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 need not to handle the names "." and "..".
Lookup(ctx context.Context, name string) (Node, error) Lookup(ctx context.Context, name string) (Node, error)
@ -238,14 +243,17 @@ type NodeRemovexattrer interface {
var startTime = time.Now() 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.Nlink = 1
attr.Atime = startTime attr.Atime = startTime
attr.Mtime = startTime attr.Mtime = startTime
attr.Ctime = startTime attr.Ctime = startTime
attr.Crtime = startTime attr.Crtime = startTime
n.Attr(&attr) if err := n.Attr(ctx, attr); err != nil {
return return err
}
return nil
} }
// A Handle is the interface required of an opened file or directory. // 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 Release(ctx context.Context, req *fuse.ReleaseRequest) error
} }
type Server struct { type Config struct {
FS FS
// Function to send debug log messages to. If nil, use fuse.Debug. // Function to send debug log messages to. If nil, use fuse.Debug.
// Note that changing this or fuse.Debug may not affect existing // Note that changing this or fuse.Debug may not affect existing
// calls to Serve. // calls to Serve.
// //
// See fuse.Debug for the rules that log functions must follow. // See fuse.Debug for the rules that log functions must follow.
Debug func(msg interface{}) Debug func(msg interface{})
// Function to create new contexts. If nil, use
// 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 // Serve serves the FUSE connection by making calls to the methods
// of fs and the Nodes and Handles it makes available. It returns only // of fs and the Nodes and Handles it makes available. It returns only
// when the connection has been closed or an unexpected error occurs. // when the connection has been closed or an unexpected error occurs.
func (s *Server) Serve(c *fuse.Conn) error { func (s *Server) Serve(fs FS) error {
sc := serveConn{ defer s.wg.Wait() // Wait for worker goroutines to complete before return
fs: s.FS,
debug: s.Debug, s.fs = fs
req: map[fuse.RequestID]*serveRequest{}, if dyn, ok := fs.(FSInodeGenerator); ok {
dynamicInode: GenerateDynamicInode, s.dynamicInode = dyn.GenerateInode
}
if sc.debug == nil {
sc.debug = fuse.Debug
}
if dyn, ok := sc.fs.(FSInodeGenerator); ok {
sc.dynamicInode = dyn.GenerateInode
} }
root, err := sc.fs.Root() root, err := fs.Root()
if err != nil { if err != nil {
return fmt.Errorf("cannot obtain root node: %v", err) return fmt.Errorf("cannot obtain root node: %v", err)
} }
sc.node = append(sc.node, nil, &serveNode{inode: 1, node: root, refs: 1}) // Recognize the root node if it's ever returned from Lookup,
sc.handle = append(sc.handle, nil) // 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 { for {
req, err := c.ReadRequest() req, err := s.conn.ReadRequest()
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
break break
@ -350,7 +412,11 @@ func (s *Server) Serve(c *fuse.Conn) error {
return err return err
} }
go sc.serve(req) s.wg.Add(1)
go func() {
defer s.wg.Done()
s.serve(req)
}()
} }
return nil return nil
} }
@ -358,44 +424,40 @@ func (s *Server) Serve(c *fuse.Conn) error {
// Serve serves a FUSE connection with the default settings. See // Serve serves a FUSE connection with the default settings. See
// Server.Serve. // Server.Serve.
func Serve(c *fuse.Conn, fs FS) error { func Serve(c *fuse.Conn, fs FS) error {
server := Server{ server := New(c, nil)
FS: fs, return server.Serve(fs)
}
return server.Serve(c)
} }
type nothing struct{} 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 { type serveRequest struct {
Request fuse.Request Request fuse.Request
cancel func() cancel func()
} }
type serveNode struct { type serveNode struct {
inode uint64 inode uint64
node Node generation uint64
refs 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) { func (sn *serveNode) attr(ctx context.Context, attr *fuse.Attr) error {
attr = nodeAttr(sn.node) err := nodeAttr(ctx, sn.node, attr)
if attr.Inode == 0 { if attr.Inode == 0 {
attr.Inode = sn.inode attr.Inode = sn.inode
} }
return return err
} }
type serveHandle struct { type serveHandle struct {
@ -404,42 +466,20 @@ type serveHandle struct {
nodeID fuse.NodeID nodeID fuse.NodeID
} }
// NodeRef can be embedded in a Node to recognize the same Node being // NodeRef is deprecated. It remains here to decrease code churn on
// returned from multiple Lookup, Create etc calls. // FUSE library users. You may remove it from your program now;
// // returning the same Node values are now recognized automatically,
// Without this, each Node will get a new NodeID, causing spurious // without needing NodeRef.
// cache invalidations, extra lookups and aliasing anomalies. This may type NodeRef struct{}
// not matter for a simple, read-only filesystem.
type NodeRef struct {
id fuse.NodeID
generation uint64
}
// nodeRef is only ever accessed while holding serveConn.meta func (c *Server) saveNode(inode uint64, node Node) (id fuse.NodeID, gen uint64) {
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) {
c.meta.Lock() c.meta.Lock()
defer c.meta.Unlock() defer c.meta.Unlock()
var ref *NodeRef if id, ok := c.nodeRef[node]; ok {
if nodeRef, ok := node.(nodeRef); ok { sn := c.node[id]
ref = nodeRef.nodeRef() sn.refs++
return id, sn.generation
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
}
} }
sn := &serveNode{inode: inode, node: node, refs: 1} 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)) id = fuse.NodeID(len(c.node))
c.node = append(c.node, sn) c.node = append(c.node, sn)
} }
gen = c.nodeGen sn.generation = c.nodeGen
if ref != nil { c.nodeRef[node] = id
ref.id = id
ref.generation = gen
}
return 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() c.meta.Lock()
shandle := &serveHandle{handle: handle, nodeID: nodeID} shandle := &serveHandle{handle: handle, nodeID: nodeID}
if n := len(c.freeHandle); n > 0 { 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) 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() c.meta.Lock()
defer c.meta.Unlock() defer c.meta.Unlock()
snode := c.node[id] snode := c.node[id]
@ -508,18 +545,16 @@ func (c *serveConn) dropNode(id fuse.NodeID, n uint64) (forget bool) {
snode.refs -= n snode.refs -= n
if snode.refs == 0 { if snode.refs == 0 {
snode.wg.Wait()
c.node[id] = nil c.node[id] = nil
if nodeRef, ok := snode.node.(nodeRef); ok { delete(c.nodeRef, snode.node)
ref := nodeRef.nodeRef()
*ref = NodeRef{}
}
c.freeNode = append(c.freeNode, id) c.freeNode = append(c.freeNode, id)
return true return true
} }
return false return false
} }
func (c *serveConn) dropHandle(id fuse.HandleID) { func (c *Server) dropHandle(id fuse.HandleID) {
c.meta.Lock() c.meta.Lock()
c.handle[id] = nil c.handle[id] = nil
c.freeHandle = append(c.freeHandle, id) c.freeHandle = append(c.freeHandle, id)
@ -532,11 +567,11 @@ type missingHandle struct {
} }
func (m missingHandle) String() string { 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. // 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() c.meta.Lock()
defer c.meta.Unlock() defer c.meta.Unlock()
if id < fuse.HandleID(len(c.handle)) { 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 { type logMissingNode struct {
MaxNode fuse.NodeID 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) 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) { type handlerPanickedError struct {
ctx, cancel := context.WithCancel(context.Background()) 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() defer cancel()
req := &serveRequest{Request: r, cancel: cancel} req := &serveRequest{Request: r, cancel: cancel}
@ -717,6 +800,22 @@ func (c *serveConn) serve(r fuse.Request) {
c.meta.Unlock() 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) { switch r := r.(type) {
default: default:
// Note: To FUSE, ENOSYS means "this server never implements this request." // Note: To FUSE, ENOSYS means "this server never implements this request."
@ -725,22 +824,6 @@ func (c *serveConn) serve(r fuse.Request) {
done(fuse.ENOSYS) done(fuse.ENOSYS)
r.RespondError(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: case *fuse.StatfsRequest:
s := &fuse.StatfsResponse{} s := &fuse.StatfsResponse{}
if fs, ok := c.fs.(FSStatfser); ok { if fs, ok := c.fs.(FSStatfser); ok {
@ -763,8 +846,11 @@ func (c *serveConn) serve(r fuse.Request) {
break break
} }
} else { } else {
s.AttrValid = attrValidTime if err := snode.attr(ctx, &s.Attr); err != nil {
s.Attr = snode.attr() done(err)
r.RespondError(err)
break
}
} }
done(s) done(s)
r.Respond(s) r.Respond(s)
@ -782,15 +868,17 @@ func (c *serveConn) serve(r fuse.Request) {
break break
} }
if s.AttrValid == 0 { if err := snode.attr(ctx, &s.Attr); err != nil {
s.AttrValid = attrValidTime done(err)
r.RespondError(err)
break
} }
s.Attr = snode.attr()
done(s) done(s)
r.Respond(s) r.Respond(s)
case *fuse.SymlinkRequest: case *fuse.SymlinkRequest:
s := &fuse.SymlinkResponse{} s := &fuse.SymlinkResponse{}
initLookupResponse(&s.LookupResponse)
n, ok := node.(NodeSymlinker) n, ok := node.(NodeSymlinker)
if !ok { if !ok {
done(fuse.EIO) // XXX or EPERM like Mkdir? done(fuse.EIO) // XXX or EPERM like Mkdir?
@ -803,7 +891,11 @@ func (c *serveConn) serve(r fuse.Request) {
r.RespondError(err) r.RespondError(err)
break 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) done(s)
r.Respond(s) r.Respond(s)
@ -852,7 +944,12 @@ func (c *serveConn) serve(r fuse.Request) {
break break
} }
s := &fuse.LookupResponse{} 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) done(s)
r.Respond(s) r.Respond(s)
@ -887,6 +984,7 @@ func (c *serveConn) serve(r fuse.Request) {
var n2 Node var n2 Node
var err error var err error
s := &fuse.LookupResponse{} s := &fuse.LookupResponse{}
initLookupResponse(s)
if n, ok := node.(NodeStringLookuper); ok { if n, ok := node.(NodeStringLookuper); ok {
n2, err = n.Lookup(ctx, r.Name) n2, err = n.Lookup(ctx, r.Name)
} else if n, ok := node.(NodeRequestLookuper); ok { } else if n, ok := node.(NodeRequestLookuper); ok {
@ -901,12 +999,17 @@ func (c *serveConn) serve(r fuse.Request) {
r.RespondError(err) r.RespondError(err)
break 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) done(s)
r.Respond(s) r.Respond(s)
case *fuse.MkdirRequest: case *fuse.MkdirRequest:
s := &fuse.MkdirResponse{} s := &fuse.MkdirResponse{}
initLookupResponse(&s.LookupResponse)
n, ok := node.(NodeMkdirer) n, ok := node.(NodeMkdirer)
if !ok { if !ok {
done(fuse.EPERM) done(fuse.EPERM)
@ -919,7 +1022,11 @@ func (c *serveConn) serve(r fuse.Request) {
r.RespondError(err) r.RespondError(err)
break 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) done(s)
r.Respond(s) r.Respond(s)
@ -950,13 +1057,18 @@ func (c *serveConn) serve(r fuse.Request) {
break break
} }
s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}} s := &fuse.CreateResponse{OpenResponse: fuse.OpenResponse{}}
initLookupResponse(&s.LookupResponse)
n2, h2, err := n.Create(ctx, r, s) n2, h2, err := n.Create(ctx, r, s)
if err != nil { if err != nil {
done(err) done(err)
r.RespondError(err) r.RespondError(err)
break 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) s.Handle = c.saveHandle(h2, hdr.Node)
done(s) done(s)
r.Respond(s) r.Respond(s)
@ -1232,7 +1344,12 @@ func (c *serveConn) serve(r fuse.Request) {
break break
} }
s := &fuse.LookupResponse{} 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) done(s)
r.Respond(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) { func (c *Server) saveLookup(ctx context.Context, s *fuse.LookupResponse, snode *serveNode, elem string, n2 Node) error {
s.Attr = nodeAttr(n2) if err := nodeAttr(ctx, n2, &s.Attr); err != nil {
return err
}
if s.Attr.Inode == 0 { if s.Attr.Inode == 0 {
s.Attr.Inode = c.dynamicInode(snode.inode, elem) s.Attr.Inode = c.dynamicInode(snode.inode, elem)
} }
s.Node, s.Generation = c.saveNode(s.Attr.Inode, n2) s.Node, s.Generation = c.saveNode(s.Attr.Inode, n2)
if s.EntryValid == 0 { return nil
s.EntryValid = entryValidTime }
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 { return err.Error()
s.AttrValid = attrValidTime }
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 // DataHandle returns a read-only Handle that satisfies reads

File diff suppressed because it is too large Load diff

View file

@ -77,8 +77,9 @@ func (t *tree) add(name string, n Node) {
t.dir = append(t.dir, treeDir{name, n}) 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 a.Mode = os.ModeDir | 0555
return nil
} }
func (t *tree) Lookup(ctx context.Context, name string) (Node, error) { func (t *tree) Lookup(ctx context.Context, name string) (Node, error) {

File diff suppressed because it is too large Load diff

View file

@ -41,13 +41,16 @@ import (
"unsafe" "unsafe"
) )
// Version is the FUSE version implemented by the package. // The FUSE version implemented by the package.
const Version = "7.8" const (
protoVersionMinMajor = 7
protoVersionMinMinor = 8
protoVersionMaxMajor = 7
protoVersionMaxMinor = 12
)
const ( const (
kernelVersion = 7 rootID = 1
kernelMinorVersion = 8
rootID = 1
) )
type kstatfs struct { type kstatfs struct {
@ -70,6 +73,22 @@ type fileLock struct {
Pid uint32 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 // The SetattrValid are bit flags describing which fields in the SetattrRequest
// are included in the change. // are included in the change.
type SetattrValid uint32 type SetattrValid uint32
@ -207,7 +226,7 @@ type OpenResponseFlags uint32
const ( const (
OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file
OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open 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 OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X
OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X
@ -220,6 +239,7 @@ func (fl OpenResponseFlags) String() string {
var openResponseFlagNames = []flagName{ var openResponseFlagNames = []flagName{
{uint32(OpenDirectIO), "OpenDirectIO"}, {uint32(OpenDirectIO), "OpenDirectIO"},
{uint32(OpenKeepCache), "OpenKeepCache"}, {uint32(OpenKeepCache), "OpenKeepCache"},
{uint32(OpenNonSeekable), "OpenNonSeekable"},
{uint32(OpenPurgeAttr), "OpenPurgeAttr"}, {uint32(OpenPurgeAttr), "OpenPurgeAttr"},
{uint32(OpenPurgeUBC), "OpenPurgeUBC"}, {uint32(OpenPurgeUBC), "OpenPurgeUBC"},
} }
@ -368,7 +388,6 @@ const (
) )
type entryOut struct { type entryOut struct {
outHeader
Nodeid uint64 // Inode ID Nodeid uint64 // Inode ID
Generation uint64 // Inode generation Generation uint64 // Inode generation
EntryValid uint64 // Cache timeout for the name EntryValid uint64 // Cache timeout for the name
@ -378,21 +397,43 @@ type entryOut struct {
Attr attr 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 { type forgetIn struct {
Nlookup uint64 Nlookup uint64
} }
type getattrIn struct {
GetattrFlags uint32
dummy uint32
Fh uint64
}
type attrOut struct { type attrOut struct {
outHeader
AttrValid uint64 // Cache timeout for the attributes AttrValid uint64 // Cache timeout for the attributes
AttrValidNsec uint32 AttrValidNsec uint32
Dummy uint32 Dummy uint32
Attr attr 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 // OS X
type getxtimesOut struct { type getxtimesOut struct {
outHeader
Bkuptime uint64 Bkuptime uint64
Crtime uint64 Crtime uint64
BkuptimeNsec uint32 BkuptimeNsec uint32
@ -400,17 +441,37 @@ type getxtimesOut struct {
} }
type mknodIn struct { type mknodIn struct {
Mode uint32 Mode uint32
Rdev uint32 Rdev uint32
Umask uint32
padding uint32
// "filename\x00" follows. // "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 { type mkdirIn struct {
Mode uint32 Mode uint32
Padding uint32 Umask uint32
// filename follows // 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 { type renameIn struct {
Newdir uint64 Newdir uint64
// "oldname\x00newname\x00" follows // "oldname\x00newname\x00" follows
@ -452,31 +513,25 @@ type openIn struct {
} }
type openOut struct { type openOut struct {
outHeader
Fh uint64 Fh uint64
OpenFlags uint32 OpenFlags uint32
Padding uint32 Padding uint32
} }
type createIn struct { type createIn struct {
Flags uint32 Flags uint32
Mode uint32 Mode uint32
Umask uint32
padding uint32
} }
type createOut struct { func createInSize(p Protocol) uintptr {
outHeader switch {
case p.LT(Protocol{7, 12}):
Nodeid uint64 // Inode ID return unsafe.Offsetof(createIn{}.Umask)
Generation uint64 // Inode generation default:
EntryValid uint64 // Cache timeout for the name return unsafe.Sizeof(createIn{})
AttrValid uint64 // Cache timeout for the attributes }
EntryValidNsec uint32
AttrValidNsec uint32
Attr attr
Fh uint64
OpenFlags uint32
Padding uint32
} }
type releaseIn struct { type releaseIn struct {
@ -494,10 +549,38 @@ type flushIn struct {
} }
type readIn struct { type readIn struct {
Fh uint64 Fh uint64
Offset uint64 Offset uint64
Size uint32 Size uint32
Padding 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 { type writeIn struct {
@ -505,10 +588,21 @@ type writeIn struct {
Offset uint64 Offset uint64
Size uint32 Size uint32
WriteFlags 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 { type writeOut struct {
outHeader
Size uint32 Size uint32
Padding uint32 Padding uint32
} }
@ -516,16 +610,24 @@ type writeOut struct {
// The WriteFlags are passed in WriteRequest. // The WriteFlags are passed in WriteRequest.
type WriteFlags uint32 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 { func (fl WriteFlags) String() string {
return flagString(uint32(fl), writeFlagNames) return flagString(uint32(fl), writeFlagNames)
} }
var writeFlagNames = []flagName{}
const compatStatfsSize = 48 const compatStatfsSize = 48
type statfsOut struct { type statfsOut struct {
outHeader
St kstatfs St kstatfs
} }
@ -554,19 +656,28 @@ func (getxattrInCommon) position() uint32 {
} }
type getxattrOut struct { type getxattrOut struct {
outHeader
Size uint32 Size uint32
Padding uint32 Padding uint32
} }
type lkIn struct { type lkIn struct {
Fh uint64 Fh uint64
Owner uint64 Owner uint64
Lk fileLock 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 { type lkOut struct {
outHeader
Lk fileLock Lk fileLock
} }
@ -585,7 +696,6 @@ type initIn struct {
const initInSize = int(unsafe.Sizeof(initIn{})) const initInSize = int(unsafe.Sizeof(initIn{}))
type initOut struct { type initOut struct {
outHeader
Major uint32 Major uint32
Minor uint32 Minor uint32
MaxReadahead uint32 MaxReadahead uint32
@ -605,7 +715,6 @@ type bmapIn struct {
} }
type bmapOut struct { type bmapOut struct {
outHeader
Block uint64 Block uint64
} }
@ -637,3 +746,21 @@ type dirent struct {
} }
const direntSize = 8 + 8 + 4 + 4 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
}

View file

@ -22,6 +22,8 @@ type attr struct {
Gid uint32 Gid uint32
Rdev uint32 Rdev uint32
Flags_ uint32 // OS X only; see chflags(2) Flags_ uint32 // OS X only; see chflags(2)
Blksize uint32
padding uint32
} }
func (a *attr) SetCrtime(s uint64, ns uint32) { func (a *attr) SetCrtime(s uint64, ns uint32) {

View file

@ -17,6 +17,8 @@ type attr struct {
Uid uint32 Uid uint32
Gid uint32 Gid uint32
Rdev uint32 Rdev uint32
Blksize uint32
padding uint32
} }
func (a *attr) Crtime() time.Time { func (a *attr) Crtime() time.Time {

View file

@ -17,8 +17,8 @@ type attr struct {
Uid uint32 Uid uint32
Gid uint32 Gid uint32
Rdev uint32 Rdev uint32
// Blksize uint32 // Only in protocol 7.9 Blksize uint32
// padding_ uint32 // Only in protocol 7.9 padding uint32
} }
func (a *attr) Crtime() time.Time { func (a *attr) Crtime() time.Time {

View file

@ -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" bin := "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs"
for k, v := range conf.options { for k, v := range conf.options {
@ -104,7 +104,7 @@ func callMount(dir string, conf *MountConfig, f *os.File, ready chan<- struct{},
return err 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() f, err := openOSXFUSEDev()
if err == errNotLoaded { if err == errNotLoaded {
err = loadOSXFUSE() err = loadOSXFUSE()

View file

@ -7,7 +7,7 @@ import (
"strings" "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 { for k, v := range conf.options {
if strings.Contains(k, ",") || strings.Contains(v, ",") { if strings.Contains(k, ",") || strings.Contains(v, ",") {
// Silly limitation but the mount helper does not // Silly limitation but the mount helper does not

View file

@ -1,14 +1,38 @@
package fuse package fuse
import ( import (
"bufio"
"fmt" "fmt"
"io"
"log"
"net" "net"
"os" "os"
"os/exec" "os/exec"
"sync"
"syscall" "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 // linux mount is never delayed
close(ready) close(ready)
@ -16,8 +40,12 @@ func mount(dir string, conf *MountConfig, ready chan<- struct{}, errp *error) (f
if err != nil { if err != nil {
return nil, fmt.Errorf("socketpair error: %v", err) 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( cmd := exec.Command(
"fusermount", "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") 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} cmd.ExtraFiles = []*os.File{writeFile}
out, err := cmd.CombinedOutput() var wg sync.WaitGroup
if len(out) > 0 || err != nil { stdout, err := cmd.StdoutPipe()
return nil, fmt.Errorf("fusermount: %q, %v", out, err) 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) c, err := net.FileConn(readFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("FileConn from fusermount socket: %v", err) return nil, fmt.Errorf("FileConn from fusermount socket: %v", err)

View file

@ -5,14 +5,16 @@ import (
"strings" "strings"
) )
func dummyOption(conf *MountConfig) error { func dummyOption(conf *mountConfig) error {
return nil 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. // Use it by passing MountOption values to Mount.
type MountConfig struct { type mountConfig struct {
options map[string]string options map[string]string
maxReadahead uint32
initFlags InitFlags
} }
func escapeComma(s string) string { 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 // getOptions makes a string of options suitable for passing to FUSE
// mount flag `-o`. Returns an empty string if no options were set. // mount flag `-o`. Returns an empty string if no options were set.
// Any platform specific adjustments should happen before the call. // Any platform specific adjustments should happen before the call.
func (m *MountConfig) getOptions() string { func (m *mountConfig) getOptions() string {
var opts []string var opts []string
for k, v := range m.options { for k, v := range m.options {
k = escapeComma(k) k = escapeComma(k)
@ -36,15 +38,17 @@ func (m *MountConfig) getOptions() string {
return strings.Join(opts, ",") return strings.Join(opts, ",")
} }
type mountOption func(*mountConfig) error
// MountOption is passed to Mount to change the behavior of the mount. // 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 // FSName sets the file system name (also called source) that is
// visible in the list of mounted file systems. // visible in the list of mounted file systems.
// //
// FreeBSD ignores this option. // FreeBSD ignores this option.
func FSName(name string) MountOption { func FSName(name string) MountOption {
return func(conf *MountConfig) error { return func(conf *mountConfig) error {
conf.options["fsname"] = name conf.options["fsname"] = name
return nil return nil
} }
@ -57,7 +61,7 @@ func FSName(name string) MountOption {
// OS X ignores this option. // OS X ignores this option.
// FreeBSD ignores this option. // FreeBSD ignores this option.
func Subtype(fstype string) MountOption { func Subtype(fstype string) MountOption {
return func(conf *MountConfig) error { return func(conf *mountConfig) error {
conf.options["subtype"] = fstype conf.options["subtype"] = fstype
return nil return nil
} }
@ -84,7 +88,7 @@ var ErrCannotCombineAllowOtherAndAllowRoot = errors.New("cannot combine AllowOth
// //
// Only one of AllowOther or AllowRoot can be used. // Only one of AllowOther or AllowRoot can be used.
func AllowOther() MountOption { func AllowOther() MountOption {
return func(conf *MountConfig) error { return func(conf *mountConfig) error {
if _, ok := conf.options["allow_root"]; ok { if _, ok := conf.options["allow_root"]; ok {
return ErrCannotCombineAllowOtherAndAllowRoot return ErrCannotCombineAllowOtherAndAllowRoot
} }
@ -99,7 +103,7 @@ func AllowOther() MountOption {
// //
// FreeBSD ignores this option. // FreeBSD ignores this option.
func AllowRoot() MountOption { func AllowRoot() MountOption {
return func(conf *MountConfig) error { return func(conf *mountConfig) error {
if _, ok := conf.options["allow_other"]; ok { if _, ok := conf.options["allow_other"]; ok {
return ErrCannotCombineAllowOtherAndAllowRoot return ErrCannotCombineAllowOtherAndAllowRoot
} }
@ -117,7 +121,7 @@ func AllowRoot() MountOption {
// //
// FreeBSD ignores this option. // FreeBSD ignores this option.
func DefaultPermissions() MountOption { func DefaultPermissions() MountOption {
return func(conf *MountConfig) error { return func(conf *mountConfig) error {
conf.options["default_permissions"] = "" conf.options["default_permissions"] = ""
return nil return nil
} }
@ -125,8 +129,42 @@ func DefaultPermissions() MountOption {
// ReadOnly makes the mount read-only. // ReadOnly makes the mount read-only.
func ReadOnly() MountOption { func ReadOnly() MountOption {
return func(conf *MountConfig) error { return func(conf *mountConfig) error {
conf.options["ro"] = "" conf.options["ro"] = ""
return nil 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
}
}

View file

@ -1,12 +1,12 @@
package fuse package fuse
func localVolume(conf *MountConfig) error { func localVolume(conf *mountConfig) error {
conf.options["local"] = "" conf.options["local"] = ""
return nil return nil
} }
func volumeName(name string) MountOption { func volumeName(name string) MountOption {
return func(conf *MountConfig) error { return func(conf *mountConfig) error {
conf.options["volname"] = name conf.options["volname"] = name
return nil return nil
} }

View file

@ -1,6 +1,6 @@
package fuse package fuse
func localVolume(conf *MountConfig) error { func localVolume(conf *mountConfig) error {
return nil return nil
} }

View file

@ -1,6 +1,10 @@
package fuse package fuse
// for TestMountOptionCommaError // for TestMountOptionCommaError
func ForTestSetMountOption(conf *MountConfig, k, v string) { func ForTestSetMountOption(k, v string) MountOption {
conf.options[k] = v fn := func(conf *mountConfig) error {
conf.options[k] = v
return nil
}
return fn
} }

View file

@ -1,6 +1,6 @@
package fuse package fuse
func localVolume(conf *MountConfig) error { func localVolume(conf *mountConfig) error {
return nil return nil
} }

View file

@ -18,11 +18,8 @@ func TestMountOptionCommaError(t *testing.T) {
// this test is not tied to any specific option, it just needs // this test is not tied to any specific option, it just needs
// some string content // some string content
var evil = "FuseTest,Marker" var evil = "FuseTest,Marker"
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
func(conf *fuse.MountConfig) error { fuse.ForTestSetMountOption("fusetest", evil),
fuse.ForTestSetMountOption(conf, "fusetest", evil)
return nil
},
) )
if err == nil { if err == nil {
mnt.Close() mnt.Close()

View file

@ -22,7 +22,7 @@ func TestMountOptionFSName(t *testing.T) {
} }
t.Parallel() t.Parallel()
const name = "FuseTestMarker" 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), fuse.FSName(name),
) )
if err != nil { if err != nil {
@ -45,7 +45,7 @@ func testMountOptionFSNameEvil(t *testing.T, evil string) {
} }
t.Parallel() t.Parallel()
var name = "FuseTest" + evil + "Marker" 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), fuse.FSName(name),
) )
if err != nil { if err != nil {
@ -102,7 +102,7 @@ func TestMountOptionSubtype(t *testing.T) {
} }
t.Parallel() t.Parallel()
const name = "FuseTestMarker" 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), fuse.Subtype(name),
) )
if err != nil { if err != nil {
@ -125,7 +125,7 @@ func TestMountOptionSubtype(t *testing.T) {
func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) { func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) {
t.Parallel() t.Parallel()
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
fuse.AllowOther(), fuse.AllowOther(),
fuse.AllowRoot(), fuse.AllowRoot(),
) )
@ -141,7 +141,7 @@ func TestMountOptionAllowOtherThenAllowRoot(t *testing.T) {
func TestMountOptionAllowRootThenAllowOther(t *testing.T) { func TestMountOptionAllowRootThenAllowOther(t *testing.T) {
t.Parallel() t.Parallel()
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{fstestutil.Dir{}}, nil,
fuse.AllowRoot(), fuse.AllowRoot(),
fuse.AllowOther(), fuse.AllowOther(),
) )
@ -155,8 +155,9 @@ func TestMountOptionAllowRootThenAllowOther(t *testing.T) {
type unwritableFile struct{} type unwritableFile struct{}
func (f unwritableFile) Attr(a *fuse.Attr) { func (f unwritableFile) Attr(ctx context.Context, a *fuse.Attr) error {
a.Mode = 0000 a.Mode = 0000
return nil
} }
func TestMountOptionDefaultPermissions(t *testing.T) { func TestMountOptionDefaultPermissions(t *testing.T) {
@ -166,8 +167,9 @@ func TestMountOptionDefaultPermissions(t *testing.T) {
t.Parallel() t.Parallel()
mnt, err := fstestutil.MountedT(t, mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{ fstestutil.SimpleFS{
fstestutil.ChildMap{"child": unwritableFile{}}, &fstestutil.ChildMap{"child": unwritableFile{}},
}, },
nil,
fuse.DefaultPermissions(), fuse.DefaultPermissions(),
) )
@ -203,6 +205,7 @@ func TestMountOptionReadOnly(t *testing.T) {
t.Parallel() t.Parallel()
mnt, err := fstestutil.MountedT(t, mnt, err := fstestutil.MountedT(t,
fstestutil.SimpleFS{createrDir{}}, fstestutil.SimpleFS{createrDir{}},
nil,
fuse.ReadOnly(), fuse.ReadOnly(),
) )

75
Godeps/_workspace/src/bazil.org/fuse/protocol.go generated vendored Normal file
View file

@ -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()
}

View file

@ -91,9 +91,10 @@ type snapshots struct {
repo *repository.Repository 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.Inode = 0
a.Mode = os.ModeDir | 0555 a.Mode = os.ModeDir | 0555
return nil
} }
func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
@ -144,9 +145,10 @@ type dir struct {
inode uint64 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.Inode = d.inode
a.Mode = os.ModeDir | 0555 a.Mode = os.ModeDir | 0555
return nil
} }
func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { 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 }, 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.Inode = f.node.Inode
a.Mode = f.node.Mode a.Mode = f.node.Mode
a.Size = f.node.Size a.Size = f.node.Size
return nil
} }
func (f *file) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { func (f *file) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {