2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-26 06:46:34 +00:00

Update bazil.org/fuse

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

4
vendor/manifest vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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