Commit ee19aa4c authored by Kirill Smelkov's avatar Kirill Smelkov

Merge branch 'master' into t

* master: (60 commits)
  fuse: increment loops WaitGroup on Server creation
  loopback: enable diagnostics logging
  fs: detect Inode.Path() hitting an orphaned inode
  tests: make RenameOpenDir more sensitive
  fs: add Options.Logger, and use throughout
  fs: fix FileAllocater type assertion
  loopback: leave file permissions on "000" files as-is
  example/loopback: make -allow-other imply default_permissions
  fuse: implement GETATTR for pollHack
  loopback: preserve owner when running as root
  tests: posixtest: add FstatDeleted
  loopback: use Lgetxattr and friends
  fs: remove typeChangeDebug code
  fs: don't log error failing MvChild
  posixtest: fix some lint errors
  fs: fix lint errors
  fs: failure to unmount on cleanup is a fatal error
  fs: plug fd leak in bridge_test.go
  fs: don't enforce dirent ordering.
  internal: skip test with other group if user is only 1 group
  ...
parents c0f14a4a fe141f38
...@@ -4,10 +4,10 @@ language: go ...@@ -4,10 +4,10 @@ language: go
go_import_path: github.com/hanwen/go-fuse go_import_path: github.com/hanwen/go-fuse
go: go:
- 1.9.x
- 1.10.x - 1.10.x
- 1.11.x - 1.11.x
- 1.12.x - 1.12.x
- 1.13.x
- master - master
matrix: matrix:
...@@ -25,6 +25,14 @@ install: ...@@ -25,6 +25,14 @@ install:
- go get -t ./... - go get -t ./...
- go get -t -race ./... - go get -t -race ./...
# Travis CI has a no-output-timeout of 10 minutes.
# Set "go test -timeout" lower so we get proper backtraces
# on a hung test.
# The tests sometimes hang in a way that "go test -timeout"
# does not work anymore. Use the external "timeout" command
# as backup, triggering 1 minute later.
script: script:
- go test -v ./... - set -e # fail fast
- go test -race ./... - timeout -s QUIT -k 10s 90s go test -failfast -timeout 1m -v ./fs
- timeout -s QUIT -k 10s 6m go test -failfast -timeout 5m -v ./...
- set +e # restore
...@@ -14,7 +14,7 @@ systems ...@@ -14,7 +14,7 @@ systems
Older, deprecated APIs are available at Older, deprecated APIs are available at
[github.com/hanwen/go-fuse/fuse/pathfs](https://godoc.org/github.com/hanwen/go-fuse/fuse/pathfs) [github.com/hanwen/go-fuse/fuse/pathfs](https://godoc.org/github.com/hanwen/go-fuse/fuse/pathfs)
and and
[github.com/hanwen/go-fuse/fuse/pathfs](https://godoc.org/github.com/hanwen/go-fuse/fuse/nodefs). [github.com/hanwen/go-fuse/fuse/nodefs](https://godoc.org/github.com/hanwen/go-fuse/fuse/nodefs).
## Comparison with other FUSE libraries ## Comparison with other FUSE libraries
......
...@@ -17,9 +17,9 @@ import ( ...@@ -17,9 +17,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func setupFs(node fs.InodeEmbedder, N int) (string, func()) { func setupFs(node fs.InodeEmbedder, N int) (string, func()) {
......
...@@ -10,8 +10,8 @@ import ( ...@@ -10,8 +10,8 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type StatFS struct { type StatFS struct {
......
...@@ -10,10 +10,10 @@ import ( ...@@ -10,10 +10,10 @@ import (
"os" "os"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/unionfs" "github.com/hanwen/go-fuse/v2/unionfs"
) )
func main() { func main() {
......
...@@ -12,8 +12,8 @@ import ( ...@@ -12,8 +12,8 @@ import (
"log" "log"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type HelloRoot struct { type HelloRoot struct {
......
...@@ -18,7 +18,7 @@ import ( ...@@ -18,7 +18,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
) )
func writeMemProfile(fn string, sigs <-chan os.Signal) { func writeMemProfile(fn string, sigs <-chan os.Signal) {
...@@ -45,6 +45,7 @@ func main() { ...@@ -45,6 +45,7 @@ func main() {
// Scans the arg list and sets up flags // Scans the arg list and sets up flags
debug := flag.Bool("debug", false, "print debugging messages.") debug := flag.Bool("debug", false, "print debugging messages.")
other := flag.Bool("allow-other", false, "mount with -o allowother.") other := flag.Bool("allow-other", false, "mount with -o allowother.")
quiet := flag.Bool("q", false, "quiet")
cpuprofile := flag.String("cpuprofile", "", "write cpu profile to this file") cpuprofile := flag.String("cpuprofile", "", "write cpu profile to this file")
memprofile := flag.String("memprofile", "", "write memory profile to this file") memprofile := flag.String("memprofile", "", "write memory profile to this file")
flag.Parse() flag.Parse()
...@@ -55,7 +56,9 @@ func main() { ...@@ -55,7 +56,9 @@ func main() {
os.Exit(2) os.Exit(2)
} }
if *cpuprofile != "" { if *cpuprofile != "" {
if !*quiet {
fmt.Printf("Writing cpu profile to %s\n", *cpuprofile) fmt.Printf("Writing cpu profile to %s\n", *cpuprofile)
}
f, err := os.Create(*cpuprofile) f, err := os.Create(*cpuprofile)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
...@@ -65,14 +68,18 @@ func main() { ...@@ -65,14 +68,18 @@ func main() {
defer pprof.StopCPUProfile() defer pprof.StopCPUProfile()
} }
if *memprofile != "" { if *memprofile != "" {
if !*quiet {
log.Printf("send SIGUSR1 to %d to dump memory profile", os.Getpid()) log.Printf("send SIGUSR1 to %d to dump memory profile", os.Getpid())
}
profSig := make(chan os.Signal, 1) profSig := make(chan os.Signal, 1)
signal.Notify(profSig, syscall.SIGUSR1) signal.Notify(profSig, syscall.SIGUSR1)
go writeMemProfile(*memprofile, profSig) go writeMemProfile(*memprofile, profSig)
} }
if *cpuprofile != "" || *memprofile != "" { if *cpuprofile != "" || *memprofile != "" {
if !*quiet {
fmt.Printf("Note: You must unmount gracefully, otherwise the profile file(s) will stay empty!\n") fmt.Printf("Note: You must unmount gracefully, otherwise the profile file(s) will stay empty!\n")
} }
}
orig := flag.Arg(1) orig := flag.Arg(1)
loopbackRoot, err := fs.NewLoopbackRoot(orig) loopbackRoot, err := fs.NewLoopbackRoot(orig)
...@@ -89,11 +96,26 @@ func main() { ...@@ -89,11 +96,26 @@ func main() {
} }
opts.Debug = *debug opts.Debug = *debug
opts.AllowOther = *other opts.AllowOther = *other
if opts.AllowOther {
// Make the kernel check file permissions for us
opts.MountOptions.Options = append(opts.MountOptions.Options, "default_permissions")
}
// First column in "df -T": original dir
opts.MountOptions.Options = append(opts.MountOptions.Options, "fsname="+orig)
// Second column in "df -T" will be shown as "fuse." + Name
opts.MountOptions.Name = "loopback"
// Leave file permissions on "000" files as-is
opts.NullPermissions = true
// Enable diagnostics logging
if !*quiet {
opts.Logger = log.New(os.Stderr, "", 0)
}
server, err := fs.Mount(flag.Arg(0), loopbackRoot, opts) server, err := fs.Mount(flag.Arg(0), loopbackRoot, opts)
if err != nil { if err != nil {
log.Fatalf("Mount fail: %v\n", err) log.Fatalf("Mount fail: %v\n", err)
} }
if !*quiet {
fmt.Println("Mounted!") fmt.Println("Mounted!")
}
server.Wait() server.Wait()
} }
...@@ -11,8 +11,8 @@ import ( ...@@ -11,8 +11,8 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
) )
func main() { func main() {
......
...@@ -15,8 +15,8 @@ import ( ...@@ -15,8 +15,8 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/zipfs" "github.com/hanwen/go-fuse/v2/zipfs"
) )
func main() { func main() {
......
...@@ -20,9 +20,9 @@ import ( ...@@ -20,9 +20,9 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/benchmark" "github.com/hanwen/go-fuse/v2/benchmark"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
func main() { func main() {
......
...@@ -11,9 +11,9 @@ import ( ...@@ -11,9 +11,9 @@ import (
"os" "os"
"time" "time"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/unionfs" "github.com/hanwen/go-fuse/v2/unionfs"
) )
func main() { func main() {
......
...@@ -18,8 +18,8 @@ import ( ...@@ -18,8 +18,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/zipfs" "github.com/hanwen/go-fuse/v2/zipfs"
) )
func main() { func main() {
......
...@@ -168,10 +168,11 @@ package fs ...@@ -168,10 +168,11 @@ package fs
import ( import (
"context" "context"
"log"
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// InodeEmbedder is an interface for structs that embed Inode. // InodeEmbedder is an interface for structs that embed Inode.
...@@ -222,7 +223,8 @@ type NodeAccesser interface { ...@@ -222,7 +223,8 @@ type NodeAccesser interface {
// FOPEN_DIRECTIO, Size should be set so it can be read correctly. If // FOPEN_DIRECTIO, Size should be set so it can be read correctly. If
// returning zeroed permissions, the default behavior is to change the // returning zeroed permissions, the default behavior is to change the
// mode of 0755 (directory) or 0644 (files). This can be switched off // mode of 0755 (directory) or 0644 (files). This can be switched off
// with the Options.NullPermissions setting. // with the Options.NullPermissions setting. If blksize is unset, 4096
// is assumed, and the 'blocks' field is set accordingly.
type NodeGetattrer interface { type NodeGetattrer interface {
Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
} }
...@@ -598,4 +600,14 @@ type Options struct { ...@@ -598,4 +600,14 @@ type Options struct {
// If nonzero, replace default (zero) GID with the given GID // If nonzero, replace default (zero) GID with the given GID
GID uint32 GID uint32
// ServerCallbacks can be provided to stub out notification
// functions for testing a filesystem without mounting it.
ServerCallbacks ServerCallbacks
// Logger is a sink for diagnostic messages. Diagnostic
// messages are printed under conditions where we cannot
// return error, but want to signal something seems off
// anyway. If unset, no messages are printed.
Logger *log.Logger
} }
...@@ -7,12 +7,13 @@ package fs ...@@ -7,12 +7,13 @@ package fs
import ( import (
"context" "context"
"log" "log"
"math/rand"
"sync" "sync"
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal" "github.com/hanwen/go-fuse/v2/internal"
) )
func errnoToStatus(errno syscall.Errno) fuse.Status { func errnoToStatus(errno syscall.Errno) fuse.Status {
...@@ -36,10 +37,21 @@ type fileEntry struct { ...@@ -36,10 +37,21 @@ type fileEntry struct {
wg sync.WaitGroup wg sync.WaitGroup
} }
// ServerCallbacks are calls into the kernel to manipulate the inode,
// entry and page cache. They are stubbed so filesystems can be
// unittested without mounting them.
type ServerCallbacks interface {
DeleteNotify(parent uint64, child uint64, name string) fuse.Status
EntryNotify(parent uint64, name string) fuse.Status
InodeNotify(node uint64, off int64, length int64) fuse.Status
InodeRetrieveCache(node uint64, offset int64, dest []byte) (n int, st fuse.Status)
InodeNotifyStoreCache(node uint64, offset int64, data []byte) fuse.Status
}
type rawBridge struct { type rawBridge struct {
options Options options Options
root *Inode root *Inode
server *fuse.Server server ServerCallbacks
// mu protects the following data. Locks for inodes must be // mu protects the following data. Locks for inodes must be
// taken before rawBridge.mu // taken before rawBridge.mu
...@@ -76,6 +88,12 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persisten ...@@ -76,6 +88,12 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persisten
} }
} }
// Only the file type bits matter
id.Mode = id.Mode & syscall.S_IFMT
if id.Mode == 0 {
id.Mode = fuse.S_IFREG
}
// the same node can be looked up through 2 paths in parallel, eg. // the same node can be looked up through 2 paths in parallel, eg.
// //
// root // root
...@@ -87,14 +105,23 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persisten ...@@ -87,14 +105,23 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persisten
// dir1.Lookup("file") and dir2.Lookup("file") are executed // dir1.Lookup("file") and dir2.Lookup("file") are executed
// simultaneously. The matching StableAttrs ensure that we return the // simultaneously. The matching StableAttrs ensure that we return the
// same node. // same node.
var t time.Duration
t0 := time.Now()
for i := 1; true; i++ {
old := b.nodes[id.Ino] old := b.nodes[id.Ino]
if old != nil { if old == nil {
break
}
if old.stableAttr == id {
return old return old
} }
b.mu.Unlock()
id.Mode = id.Mode &^ 07777 t = expSleep(t)
if id.Mode == 0 { if i%5000 == 0 {
id.Mode = fuse.S_IFREG b.logf("blocked for %.0f seconds waiting for FORGET on i%d", time.Since(t0).Seconds(), id.Ino)
}
b.mu.Lock()
} }
b.nodes[id.Ino] = ops.embed() b.nodes[id.Ino] = ops.embed()
...@@ -102,6 +129,27 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persisten ...@@ -102,6 +129,27 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persisten
return ops.embed() return ops.embed()
} }
func (b *rawBridge) logf(format string, args ...interface{}) {
if b.options.Logger != nil {
b.options.Logger.Printf(format, args...)
}
}
// expSleep sleeps for time `t` and returns an exponentially increasing value
// for the next sleep time, capped at 1 ms.
func expSleep(t time.Duration) time.Duration {
if t == 0 {
return time.Microsecond
}
time.Sleep(t)
// Next sleep is between t and 2*t
t += time.Duration(rand.Int63n(int64(t)))
if t >= time.Millisecond {
return time.Millisecond
}
return t
}
func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode { func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAttr, persistent bool) *Inode {
ch := b.newInodeUnlocked(ops, id, persistent) ch := b.newInodeUnlocked(ops, id, persistent)
if ch != ops.embed() { if ch != ops.embed() {
...@@ -116,11 +164,20 @@ func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAt ...@@ -116,11 +164,20 @@ func (b *rawBridge) newInode(ctx context.Context, ops InodeEmbedder, id StableAt
// addNewChild inserts the child into the tree. Returns file handle if file != nil. // addNewChild inserts the child into the tree. Returns file handle if file != nil.
func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file FileHandle, fileFlags uint32, out *fuse.EntryOut) uint32 { func (b *rawBridge) addNewChild(parent *Inode, name string, child *Inode, file FileHandle, fileFlags uint32, out *fuse.EntryOut) uint32 {
if name == "." || name == ".." {
log.Panicf("BUG: tried to add virtual entry %q to the actual tree", name)
}
lockNodes(parent, child) lockNodes(parent, child)
parent.setEntry(name, child) parent.setEntry(name, child)
b.mu.Lock() b.mu.Lock()
// Due to concurrent FORGETs, lookupCount may have dropped to zero.
// This means it MAY have been deleted from nodes[] already. Add it back.
if child.lookupCount == 0 {
b.nodes[child.stableAttr.Ino] = child
}
child.lookupCount++ child.lookupCount++
child.changeCounter++
var fh uint32 var fh uint32
if file != nil { if file != nil {
...@@ -159,6 +216,7 @@ func (b *rawBridge) setAttr(out *fuse.Attr) { ...@@ -159,6 +216,7 @@ func (b *rawBridge) setAttr(out *fuse.Attr) {
if b.options.GID != 0 && out.Gid == 0 { if b.options.GID != 0 && out.Gid == 0 {
out.Gid = b.options.GID out.Gid = b.options.GID
} }
setBlocks(out)
} }
func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) { func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) {
...@@ -172,6 +230,7 @@ func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) { ...@@ -172,6 +230,7 @@ func (b *rawBridge) setAttrTimeout(out *fuse.AttrOut) {
func NewNodeFS(root InodeEmbedder, opts *Options) fuse.RawFileSystem { func NewNodeFS(root InodeEmbedder, opts *Options) fuse.RawFileSystem {
bridge := &rawBridge{ bridge := &rawBridge{
automaticIno: opts.FirstAutomaticIno, automaticIno: opts.FirstAutomaticIno,
server: opts.ServerCallbacks,
} }
if bridge.automaticIno == 1 { if bridge.automaticIno == 1 {
bridge.automaticIno++ bridge.automaticIno++
...@@ -244,7 +303,6 @@ func (b *rawBridge) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name s ...@@ -244,7 +303,6 @@ func (b *rawBridge) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name s
child.setEntryOut(out) child.setEntryOut(out)
b.addNewChild(parent, name, child, nil, 0, out) b.addNewChild(parent, name, child, nil, 0, out)
b.setEntryOutTimeout(out) b.setEntryOutTimeout(out)
return fuse.OK return fuse.OK
} }
...@@ -417,6 +475,9 @@ func (b *rawBridge) getattr(ctx context.Context, n *Inode, f FileHandle, out *fu ...@@ -417,6 +475,9 @@ func (b *rawBridge) getattr(ctx context.Context, n *Inode, f FileHandle, out *fu
} }
if errno == 0 { if errno == 0 {
if out.Ino != 0 && n.stableAttr.Ino > 1 && out.Ino != n.stableAttr.Ino {
b.logf("warning: rawBridge.getattr: overriding ino %d with %d", out.Ino, n.stableAttr.Ino)
}
out.Ino = n.stableAttr.Ino out.Ino = n.stableAttr.Ino
out.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode out.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode
b.setAttr(&out.Attr) b.setAttr(&out.Attr)
...@@ -428,11 +489,10 @@ func (b *rawBridge) getattr(ctx context.Context, n *Inode, f FileHandle, out *fu ...@@ -428,11 +489,10 @@ func (b *rawBridge) getattr(ctx context.Context, n *Inode, f FileHandle, out *fu
func (b *rawBridge) SetAttr(cancel <-chan struct{}, in *fuse.SetAttrIn, out *fuse.AttrOut) fuse.Status { func (b *rawBridge) SetAttr(cancel <-chan struct{}, in *fuse.SetAttrIn, out *fuse.AttrOut) fuse.Status {
ctx := &fuse.Context{Caller: in.Caller, Cancel: cancel} ctx := &fuse.Context{Caller: in.Caller, Cancel: cancel}
n, fEntry := b.inode(in.NodeId, in.Fh) fh, _ := in.GetFh()
n, fEntry := b.inode(in.NodeId, fh)
f := fEntry.file f := fEntry.file
if in.Valid&fuse.FATTR_FH == 0 {
f = nil
}
var errno = syscall.ENOTSUP var errno = syscall.ENOTSUP
if fops, ok := n.ops.(NodeSetattrer); ok { if fops, ok := n.ops.(NodeSetattrer); ok {
...@@ -455,14 +515,12 @@ func (b *rawBridge) Rename(cancel <-chan struct{}, input *fuse.RenameIn, oldName ...@@ -455,14 +515,12 @@ func (b *rawBridge) Rename(cancel <-chan struct{}, input *fuse.RenameIn, oldName
if input.Flags&RENAME_EXCHANGE != 0 { if input.Flags&RENAME_EXCHANGE != 0 {
p1.ExchangeChild(oldName, p2, newName) p1.ExchangeChild(oldName, p2, newName)
} else { } else {
if ok := p1.MvChild(oldName, p2, newName, true); !ok { // MvChild cannot fail with overwrite=true.
log.Println("MvChild failed") _ = p1.MvChild(oldName, p2, newName, true)
} }
} }
return errnoToStatus(errno) return errnoToStatus(errno)
} }
}
return fuse.ENOTSUP return fuse.ENOTSUP
} }
...@@ -762,7 +820,7 @@ func (b *rawBridge) Fallocate(cancel <-chan struct{}, input *fuse.FallocateIn) f ...@@ -762,7 +820,7 @@ func (b *rawBridge) Fallocate(cancel <-chan struct{}, input *fuse.FallocateIn) f
if a, ok := n.ops.(NodeAllocater); ok { if a, ok := n.ops.(NodeAllocater); ok {
return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Offset, input.Length, input.Mode)) return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, f.file, input.Offset, input.Length, input.Mode))
} }
if a, ok := n.ops.(FileAllocater); ok { if a, ok := f.file.(FileAllocater); ok {
return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Offset, input.Length, input.Mode)) return errnoToStatus(a.Allocate(&fuse.Context{Caller: input.Caller, Cancel: cancel}, input.Offset, input.Length, input.Mode))
} }
return fuse.ENOTSUP return fuse.ENOTSUP
...@@ -858,7 +916,7 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out ...@@ -858,7 +916,7 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
} }
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
for f.dirStream.HasNext() { for f.dirStream.HasNext() || f.hasOverflow {
var e fuse.DirEntry var e fuse.DirEntry
var errno syscall.Errno var errno syscall.Errno
...@@ -880,6 +938,15 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out ...@@ -880,6 +938,15 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
return fuse.OK return fuse.OK
} }
// Virtual entries "." and ".." should be part of the
// directory listing, but not part of the filesystem tree.
// The values in EntryOut are ignored by Linux
// (see fuse_direntplus_link() in linux/fs/fuse/readdir.c), so leave
// them at zero-value.
if e.Name == "." || e.Name == ".." {
continue
}
child, errno := b.lookup(ctx, n, e.Name, entryOut) child, errno := b.lookup(ctx, n, e.Name, entryOut)
if errno != 0 { if errno != 0 {
if b.options.NegativeTimeout != nil { if b.options.NegativeTimeout != nil {
...@@ -889,10 +956,9 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out ...@@ -889,10 +956,9 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
b.addNewChild(n, e.Name, child, nil, 0, entryOut) b.addNewChild(n, e.Name, child, nil, 0, entryOut)
child.setEntryOut(entryOut) child.setEntryOut(entryOut)
b.setEntryOutTimeout(entryOut) b.setEntryOutTimeout(entryOut)
if (e.Mode &^ 07777) != (child.stableAttr.Mode &^ 07777) { if e.Mode&syscall.S_IFMT != child.stableAttr.Mode&syscall.S_IFMT {
// should go back and change the // The file type has changed behind our back. Use the new value.
// already serialized entry out.FixMode(child.stableAttr.Mode)
log.Panicf("mode mismatch between readdir %o and lookup %o", e.Mode, child.stableAttr.Mode)
} }
entryOut.Mode = child.stableAttr.Mode | (entryOut.Mode & 07777) entryOut.Mode = child.stableAttr.Mode | (entryOut.Mode & 07777)
} }
......
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import (
"context"
"log"
"os"
"strings"
"syscall"
"testing"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
)
// TestBridgeReaddirPlusVirtualEntries looks at "." and ".." in the ReadDirPlus
// output. They should exist, but the NodeId should be zero.
func TestBridgeReaddirPlusVirtualEntries(t *testing.T) {
// Set suppressDebug as we do our own logging
tc := newTestCase(t, &testOptions{suppressDebug: true})
defer tc.Clean()
rb := tc.rawFS.(*rawBridge)
// We only populate what rawBridge.OpenDir() actually looks at.
openIn := fuse.OpenIn{}
openIn.NodeId = 1 // root node always has id 1 and always exists
openOut := fuse.OpenOut{}
status := rb.OpenDir(nil, &openIn, &openOut)
if !status.Ok() {
t.Fatal(status)
}
releaseIn := fuse.ReleaseIn{
Fh: openOut.Fh,
}
releaseIn.NodeId = 1
defer rb.ReleaseDir(&releaseIn)
// We only populate what rawBridge.ReadDirPlus() actually looks at.
readIn := fuse.ReadIn{}
readIn.NodeId = 1
readIn.Fh = openOut.Fh
buf := make([]byte, 400)
dirents := fuse.NewDirEntryList(buf, 0)
status = rb.ReadDirPlus(nil, &readIn, dirents)
if !status.Ok() {
t.Fatal(status)
}
// Parse the output buffer. Looks like this in memory:
// 1) fuse.EntryOut
// 2) fuse._Dirent
// 3) Name (null-terminated)
// 4) Padding to align to 8 bytes
// [repeat]
const entryOutSize = int(unsafe.Sizeof(fuse.EntryOut{}))
// = unsafe.Sizeof(fuse._Dirent{}), see fuse/types.go
const direntSize = 24
// Round up to 8.
const entry2off = (entryOutSize + direntSize + len(".\x00") + 7) / 8 * 8
names := map[string]*fuse.EntryOut{}
// 1st entry should be "."
entry1 := (*fuse.EntryOut)(unsafe.Pointer(&buf[0]))
name1 := string(buf[entryOutSize+direntSize : entryOutSize+direntSize+2])
names[name1] = entry1
// 2nd entry should be ".."
entry2 := (*fuse.EntryOut)(unsafe.Pointer(&buf[entry2off]))
name2 := string(buf[entry2off+entryOutSize+direntSize : entry2off+entryOutSize+direntSize+2])
names[name2] = entry2
if len(names) != 2 || names[".\000"] == nil || names[".."] == nil {
t.Fatalf(`got %v, want {".\\0", ".."}`, names)
}
for k, v := range names {
if v.NodeId != 0 {
t.Errorf("entry %q NodeId should be 0, but is %d", k, v.NodeId)
}
}
}
// TestTypeChange simulates inode number reuse that happens on real
// filesystems. For go-fuse, inode number reuse can look like a file changing
// to a directory or vice versa. Acutally, the old inode does not exist anymore,
// we just have not received the FORGET yet.
func TestTypeChange(t *testing.T) {
rootNode := testTypeChangeIno{}
mnt, _, clean := testMount(t, &rootNode, nil)
defer clean()
for i := 0; i < 100; i++ {
fi, _ := os.Stat(mnt + "/file")
syscall.Unlink(mnt + "/file")
fi, _ = os.Stat(mnt + "/dir")
if !fi.IsDir() {
t.Fatal("should be a dir now")
}
syscall.Rmdir(mnt + "/dir")
fi, _ = os.Stat(mnt + "/file")
if fi.IsDir() {
t.Fatal("should be a file now")
}
}
}
type testTypeChangeIno struct {
Inode
}
// Lookup function for TestTypeChange:
// If name == "dir", returns a node of type dir,
// if name == "file" of type file,
// otherwise ENOENT.
func (fn *testTypeChangeIno) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
var mode uint32
switch name {
case "file":
mode = syscall.S_IFREG
case "dir":
mode = syscall.S_IFDIR
default:
return nil, syscall.ENOENT
}
stable := StableAttr{
Mode: mode,
Ino: 1234,
}
childFN := &testTypeChangeIno{}
child := fn.NewInode(ctx, childFN, stable)
return child, syscall.F_OK
}
// TestDeletedInodePath checks that Inode.Path returns ".deleted" if an Inode is
// disconnected from the hierarchy (=orphaned)
func TestDeletedInodePath(t *testing.T) {
rootNode := testDeletedIno{}
mnt, _, clean := testMount(t, &rootNode, &Options{Logger: log.New(os.Stderr, "", 0)})
defer clean()
// Open a file handle so the kernel cannot FORGET the inode
fd, err := os.Open(mnt + "/dir")
if err != nil {
t.Fatal(err)
}
defer fd.Close()
// Delete it so the inode does not have a path anymore
err = syscall.Rmdir(mnt + "/dir")
if err != nil {
t.Fatal(err)
}
rootNode.deleted = true
// Our Getattr implementation `testDeletedIno.Getattr` should return
// ENFILE when everything looks ok, EILSEQ otherwise.
var st syscall.Stat_t
err = syscall.Fstat(int(fd.Fd()), &st)
if err != syscall.ENFILE {
t.Error(err)
}
}
type testDeletedIno struct {
Inode
deleted bool
}
func (n *testDeletedIno) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
if n.Root().Operations().(*testDeletedIno).deleted {
return nil, syscall.ENOENT
}
if name != "dir" {
return nil, syscall.ENOENT
}
childNode := &testDeletedIno{}
stable := StableAttr{Mode: syscall.S_IFDIR, Ino: 999}
child := n.NewInode(ctx, childNode, stable)
return child, syscall.F_OK
}
func (n *testDeletedIno) Opendir(ctx context.Context) syscall.Errno {
return OK
}
func (n *testDeletedIno) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
prefix := ".go-fuse"
p := n.Path(n.Root())
if strings.HasPrefix(p, prefix) {
// Return ENFILE when things look ok
return syscall.ENFILE
}
// Otherwise EILSEQ
return syscall.EILSEQ
}
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type keepCacheFile struct { type keepCacheFile struct {
......
...@@ -7,7 +7,7 @@ package fs ...@@ -7,7 +7,7 @@ package fs
import ( import (
"syscall" "syscall"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// OK is the Errno return value to indicate absense of errors. // OK is the Errno return value to indicate absense of errors.
......
...@@ -11,8 +11,8 @@ import ( ...@@ -11,8 +11,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// bytesFileHandle is a file handle that carries separate content for // bytesFileHandle is a file handle that carries separate content for
......
...@@ -12,7 +12,7 @@ import ( ...@@ -12,7 +12,7 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type dioRoot struct { type dioRoot struct {
...@@ -52,7 +52,8 @@ func (f *dioFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFl ...@@ -52,7 +52,8 @@ func (f *dioFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFl
return &dioFH{}, fuse.FOPEN_DIRECT_IO, OK return &dioFH{}, fuse.FOPEN_DIRECT_IO, OK
} }
func TestDirectIO(t *testing.T) { // this tests FOPEN_DIRECT_IO (as opposed to O_DIRECTIO)
func TestFUSEDirectIO(t *testing.T) {
root := &dioRoot{} root := &dioRoot{}
mntDir, server, clean := testMount(t, root, nil) mntDir, server, clean := testMount(t, root, nil)
defer clean() defer clean()
......
...@@ -7,7 +7,7 @@ package fs ...@@ -7,7 +7,7 @@ package fs
import ( import (
"syscall" "syscall"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type dirArray struct { type dirArray struct {
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"os" "os"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
func NewLoopbackDirStream(nm string) (DirStream, syscall.Errno) { func NewLoopbackDirStream(nm string) (DirStream, syscall.Errno) {
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type loopbackDirStream struct { type loopbackDirStream struct {
......
...@@ -11,8 +11,8 @@ import ( ...@@ -11,8 +11,8 @@ import (
"strconv" "strconv"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// numberNode is a filesystem node representing an integer. Prime // numberNode is a filesystem node representing an integer. Prime
......
...@@ -10,8 +10,8 @@ import ( ...@@ -10,8 +10,8 @@ import (
"log" "log"
"os" "os"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// ExampleMount shows how to create a loopback file system, and // ExampleMount shows how to create a loopback file system, and
......
...@@ -12,7 +12,7 @@ import ( ...@@ -12,7 +12,7 @@ import (
"syscall" "syscall"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
......
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import "github.com/hanwen/go-fuse/v2/fuse"
func setBlocks(out *fuse.Attr) {
}
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno { func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
...@@ -30,3 +30,13 @@ func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno { ...@@ -30,3 +30,13 @@ func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
err := futimens(int(f.fd), &ts) err := futimens(int(f.fd), &ts)
return ToErrno(err) return ToErrno(err)
} }
func setBlocks(out *fuse.Attr) {
if out.Blksize > 0 {
return
}
out.Blksize = 4096
pages := (out.Size + 4095) / 4096
out.Blocks = pages * 8
}
...@@ -12,8 +12,8 @@ import ( ...@@ -12,8 +12,8 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// files contains the files we will expose as a file system // files contains the files we will expose as a file system
......
...@@ -8,13 +8,14 @@ import ( ...@@ -8,13 +8,14 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"math/rand"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type parentData struct { type parentData struct {
...@@ -64,9 +65,8 @@ type Inode struct { ...@@ -64,9 +65,8 @@ type Inode struct {
// Following data is mutable. // Following data is mutable.
// protected by bridge.mu
// file handles. // file handles.
// protected by bridge.mu
openFiles []uint32 openFiles []uint32
// mu protects the following mutable fields. When locking // mu protects the following mutable fields. When locking
...@@ -78,6 +78,7 @@ type Inode struct { ...@@ -78,6 +78,7 @@ type Inode struct {
// from the tree, even if there are no live references. This // from the tree, even if there are no live references. This
// must be set on creation, and can only be changed to false // must be set on creation, and can only be changed to false
// by calling removeRef. // by calling removeRef.
// When you change this, you MUST increment changeCounter.
persistent bool persistent bool
// changeCounter increments every time the mutable state // changeCounter increments every time the mutable state
...@@ -90,9 +91,15 @@ type Inode struct { ...@@ -90,9 +91,15 @@ type Inode struct {
changeCounter uint32 changeCounter uint32
// Number of kernel refs to this node. // Number of kernel refs to this node.
// When you change this, you MUST increment changeCounter.
lookupCount uint64 lookupCount uint64
// Children of this Inode.
// When you change this, you MUST increment changeCounter.
children map[string]*Inode children map[string]*Inode
// Parents of this Inode. Can be more than one due to hard links.
// When you change this, you MUST increment changeCounter.
parents map[parentData]struct{} parents map[parentData]struct{}
} }
...@@ -261,7 +268,11 @@ func (n *Inode) Operations() InodeEmbedder { ...@@ -261,7 +268,11 @@ func (n *Inode) Operations() InodeEmbedder {
return n.ops return n.ops
} }
// Path returns a path string to the inode relative to the root. // Path returns a path string to the inode relative to `root`.
// Pass nil to walk the hierarchy as far up as possible.
//
// If you set `root`, Path() warns if it finds an orphaned Inode, i.e.
// if it does not end up at `root` after walking the hierarchy.
func (n *Inode) Path(root *Inode) string { func (n *Inode) Path(root *Inode) string {
var segments []string var segments []string
p := n p := n
...@@ -270,11 +281,18 @@ func (n *Inode) Path(root *Inode) string { ...@@ -270,11 +281,18 @@ func (n *Inode) Path(root *Inode) string {
// We don't try to take all locks at the same time, because // We don't try to take all locks at the same time, because
// the caller won't use the "path" string under lock anyway. // the caller won't use the "path" string under lock anyway.
found := false
p.mu.Lock() p.mu.Lock()
// Select an arbitrary parent
for pd = range p.parents { for pd = range p.parents {
found = true
break break
} }
p.mu.Unlock() p.mu.Unlock()
if found == false {
p = nil
break
}
if pd.parent == nil { if pd.parent == nil {
break break
} }
...@@ -283,9 +301,12 @@ func (n *Inode) Path(root *Inode) string { ...@@ -283,9 +301,12 @@ func (n *Inode) Path(root *Inode) string {
p = pd.parent p = pd.parent
} }
if p == nil { if root != nil && root != p {
deletedPlaceholder := fmt.Sprintf(".go-fuse.%d/deleted", rand.Uint64())
n.bridge.logf("warning: Inode.Path: inode i%d is orphaned, replacing segment with %q",
n.stableAttr.Ino, deletedPlaceholder)
// NOSUBMIT - should replace rather than append? // NOSUBMIT - should replace rather than append?
segments = append(segments, ".deleted") segments = append(segments, deletedPlaceholder)
} }
i := 0 i := 0
...@@ -545,6 +566,8 @@ retry: ...@@ -545,6 +566,8 @@ retry:
for _, nm := range names { for _, nm := range names {
ch := n.children[nm] ch := n.children[nm]
delete(n.children, nm) delete(n.children, nm)
delete(ch.parents, parentData{nm, n})
ch.changeCounter++ ch.changeCounter++
} }
n.changeCounter++ n.changeCounter++
...@@ -565,7 +588,8 @@ retry: ...@@ -565,7 +588,8 @@ retry:
} }
// MvChild executes a rename. If overwrite is set, a child at the // MvChild executes a rename. If overwrite is set, a child at the
// destination will be overwritten, should it exist. // destination will be overwritten, should it exist. It returns false
// if 'overwrite' is false, and the destination exists.
func (n *Inode) MvChild(old string, newParent *Inode, newName string, overwrite bool) bool { func (n *Inode) MvChild(old string, newParent *Inode, newName string, overwrite bool) bool {
if len(newName) == 0 { if len(newName) == 0 {
log.Panicf("empty newName for MvChild") log.Panicf("empty newName for MvChild")
......
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type interruptRoot struct { type interruptRoot struct {
......
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
"path/filepath" "path/filepath"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type loopbackRoot struct { type loopbackRoot struct {
...@@ -55,10 +55,9 @@ func (n *loopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall. ...@@ -55,10 +55,9 @@ func (n *loopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.
return OK return OK
} }
func (n *loopbackRoot) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno { func (r *loopbackRoot) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno {
st := syscall.Stat_t{} st := syscall.Stat_t{}
err := syscall.Stat(n.rootPath, &st) err := syscall.Stat(r.rootPath, &st)
if err != nil { if err != nil {
return ToErrno(err) return ToErrno(err)
} }
...@@ -71,7 +70,7 @@ func (n *loopbackNode) root() *loopbackRoot { ...@@ -71,7 +70,7 @@ func (n *loopbackNode) root() *loopbackRoot {
} }
func (n *loopbackNode) path() string { func (n *loopbackNode) path() string {
path := n.Path(nil) path := n.Path(n.Root())
return filepath.Join(n.root().rootPath, path) return filepath.Join(n.root().rootPath, path)
} }
...@@ -90,12 +89,26 @@ func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryO ...@@ -90,12 +89,26 @@ func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryO
return ch, 0 return ch, 0
} }
// preserveOwner sets uid and gid of `path` according to the caller information
// in `ctx`.
func (n *loopbackNode) preserveOwner(ctx context.Context, path string) error {
if os.Getuid() != 0 {
return nil
}
caller, ok := fuse.FromContext(ctx)
if !ok {
return nil
}
return syscall.Lchown(path, int(caller.Uid), int(caller.Gid))
}
func (n *loopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) { func (n *loopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno) {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
err := syscall.Mknod(p, mode, int(rdev)) err := syscall.Mknod(p, mode, int(rdev))
if err != nil { if err != nil {
return nil, ToErrno(err) return nil, ToErrno(err)
} }
n.preserveOwner(ctx, p)
st := syscall.Stat_t{} st := syscall.Stat_t{}
if err := syscall.Lstat(p, &st); err != nil { if err := syscall.Lstat(p, &st); err != nil {
syscall.Rmdir(p) syscall.Rmdir(p)
...@@ -116,6 +129,7 @@ func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out ...@@ -116,6 +129,7 @@ func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out
if err != nil { if err != nil {
return nil, ToErrno(err) return nil, ToErrno(err)
} }
n.preserveOwner(ctx, p)
st := syscall.Stat_t{} st := syscall.Stat_t{}
if err := syscall.Lstat(p, &st); err != nil { if err := syscall.Lstat(p, &st); err != nil {
syscall.Rmdir(p) syscall.Rmdir(p)
...@@ -156,9 +170,9 @@ func (n *loopbackNode) Rename(ctx context.Context, name string, newParent InodeE ...@@ -156,9 +170,9 @@ func (n *loopbackNode) Rename(ctx context.Context, name string, newParent InodeE
} }
p1 := filepath.Join(n.path(), name) p1 := filepath.Join(n.path(), name)
p2 := filepath.Join(newParentLoopback.path(), newName) p2 := filepath.Join(newParentLoopback.path(), newName)
err := os.Rename(p1, p2)
err := syscall.Rename(p1, p2)
return ToErrno(err) return ToErrno(err)
} }
...@@ -185,12 +199,12 @@ var _ = (NodeCreater)((*loopbackNode)(nil)) ...@@ -185,12 +199,12 @@ var _ = (NodeCreater)((*loopbackNode)(nil))
func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) { func (n *loopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
p := filepath.Join(n.path(), name) p := filepath.Join(n.path(), name)
flags = flags &^ syscall.O_APPEND
fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode) fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode)
if err != nil { if err != nil {
return nil, nil, 0, ToErrno(err) return nil, nil, 0, ToErrno(err)
} }
n.preserveOwner(ctx, p)
st := syscall.Stat_t{} st := syscall.Stat_t{}
if err := syscall.Fstat(fd, &st); err != nil { if err := syscall.Fstat(fd, &st); err != nil {
syscall.Close(fd) syscall.Close(fd)
...@@ -211,8 +225,9 @@ func (n *loopbackNode) Symlink(ctx context.Context, target, name string, out *fu ...@@ -211,8 +225,9 @@ func (n *loopbackNode) Symlink(ctx context.Context, target, name string, out *fu
if err != nil { if err != nil {
return nil, ToErrno(err) return nil, ToErrno(err)
} }
n.preserveOwner(ctx, p)
st := syscall.Stat_t{} st := syscall.Stat_t{}
if syscall.Lstat(p, &st); err != nil { if err := syscall.Lstat(p, &st); err != nil {
syscall.Unlink(p) syscall.Unlink(p)
return nil, ToErrno(err) return nil, ToErrno(err)
} }
...@@ -232,7 +247,7 @@ func (n *loopbackNode) Link(ctx context.Context, target InodeEmbedder, name stri ...@@ -232,7 +247,7 @@ func (n *loopbackNode) Link(ctx context.Context, target InodeEmbedder, name stri
return nil, ToErrno(err) return nil, ToErrno(err)
} }
st := syscall.Stat_t{} st := syscall.Stat_t{}
if syscall.Lstat(p, &st); err != nil { if err := syscall.Lstat(p, &st); err != nil {
syscall.Unlink(p) syscall.Unlink(p)
return nil, ToErrno(err) return nil, ToErrno(err)
} }
...@@ -260,6 +275,7 @@ func (n *loopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) { ...@@ -260,6 +275,7 @@ func (n *loopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
} }
func (n *loopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { func (n *loopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
flags = flags &^ syscall.O_APPEND
p := n.path() p := n.path()
f, err := syscall.Open(p, int(flags), 0) f, err := syscall.Open(p, int(flags), 0)
if err != nil { if err != nil {
...@@ -288,7 +304,7 @@ func (n *loopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.Attr ...@@ -288,7 +304,7 @@ func (n *loopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.Attr
} }
p := n.path() p := n.path()
var err error = nil var err error
st := syscall.Stat_t{} st := syscall.Stat_t{}
err = syscall.Lstat(p, &st) err = syscall.Lstat(p, &st)
if err != nil { if err != nil {
...@@ -371,7 +387,7 @@ func (n *loopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt ...@@ -371,7 +387,7 @@ func (n *loopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt
return OK return OK
} }
// NewLoopback returns a root node for a loopback file system whose // NewLoopbackRoot returns a root node for a loopback file system whose
// root is at the given root. This node implements all NodeXxxxer // root is at the given root. This node implements all NodeXxxxer
// operations available. // operations available.
func NewLoopbackRoot(root string) (InodeEmbedder, error) { func NewLoopbackRoot(root string) (InodeEmbedder, error) {
......
...@@ -12,8 +12,8 @@ import ( ...@@ -12,8 +12,8 @@ import (
"time" "time"
"unsafe" "unsafe"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/utimens" "github.com/hanwen/go-fuse/v2/internal/utimens"
) )
func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
......
...@@ -14,22 +14,22 @@ import ( ...@@ -14,22 +14,22 @@ import (
) )
func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
sz, err := syscall.Getxattr(n.path(), attr, dest) sz, err := unix.Lgetxattr(n.path(), attr, dest)
return uint32(sz), ToErrno(err) return uint32(sz), ToErrno(err)
} }
func (n *loopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno { func (n *loopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
err := syscall.Setxattr(n.path(), attr, data, int(flags)) err := unix.Lsetxattr(n.path(), attr, data, int(flags))
return ToErrno(err) return ToErrno(err)
} }
func (n *loopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno { func (n *loopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
err := syscall.Removexattr(n.path(), attr) err := unix.Lremovexattr(n.path(), attr)
return ToErrno(err) return ToErrno(err)
} }
func (n *loopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { func (n *loopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
sz, err := syscall.Listxattr(n.path(), dest) sz, err := unix.Llistxattr(n.path(), dest)
return uint32(sz), ToErrno(err) return uint32(sz), ToErrno(err)
} }
......
...@@ -14,8 +14,8 @@ import ( ...@@ -14,8 +14,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/kylelemons/godebug/pretty" "github.com/kylelemons/godebug/pretty"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
...@@ -124,7 +124,30 @@ func TestXAttr(t *testing.T) { ...@@ -124,7 +124,30 @@ func TestXAttr(t *testing.T) {
if err := syscall.Setxattr(tc.mntDir+"/file", attr, value, 0); err != nil { if err := syscall.Setxattr(tc.mntDir+"/file", attr, value, 0); err != nil {
t.Fatalf("Setxattr: %v", err) t.Fatalf("Setxattr: %v", err)
} }
sz, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf)
sz, err := syscall.Listxattr(tc.mntDir+"/file", nil)
if err != nil {
t.Fatalf("Listxattr: %v", err)
}
buf = make([]byte, sz)
if _, err := syscall.Listxattr(tc.mntDir+"/file", buf); err != nil {
t.Fatalf("Listxattr: %v", err)
} else {
attributes := bytes.Split(buf[:sz], []byte{0})
found := false
for _, a := range attributes {
if string(a) == attr {
found = true
break
}
}
if !found {
t.Fatalf("Listxattr: %q (not found: %q", buf[:sz], attr)
}
}
sz, err = syscall.Getxattr(tc.mntDir+"/file", attr, buf)
if err != nil { if err != nil {
t.Fatalf("Getxattr: %v", err) t.Fatalf("Getxattr: %v", err)
} }
...@@ -140,6 +163,28 @@ func TestXAttr(t *testing.T) { ...@@ -140,6 +163,28 @@ func TestXAttr(t *testing.T) {
} }
} }
// TestXAttrSymlink verifies that we did not forget to use Lgetxattr instead
// of Getxattr. This test is Linux-specific because it depends on the behavoir
// of the `security` namespace.
//
// On Linux, symlinks can not have xattrs in the `user` namespace, so we
// try to read something from `security`. Writing would need root rights,
// so don't even bother. See `man 7 xattr` for more info.
func TestXAttrSymlink(t *testing.T) {
tc := newTestCase(t, nil)
defer tc.Clean()
path := tc.mntDir + "/symlink"
if err := syscall.Symlink("target/does/not/exist", path); err != nil {
t.Fatal(err)
}
buf := make([]byte, 10)
_, err := unix.Lgetxattr(path, "security.foo", buf)
if err != unix.ENODATA {
t.Errorf("want %d=ENODATA, got error %d=%q instead", unix.ENODATA, err, err)
}
}
func TestCopyFileRange(t *testing.T) { func TestCopyFileRange(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean() defer tc.Clean()
......
...@@ -6,44 +6,75 @@ package fs ...@@ -6,44 +6,75 @@ package fs
import ( import (
"context" "context"
"sync"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// MemRegularFile is a filesystem node that holds a read-only data // MemRegularFile is a filesystem node that holds a read-only data
// slice in memory. // slice in memory.
type MemRegularFile struct { type MemRegularFile struct {
Inode Inode
mu sync.Mutex
Data []byte Data []byte
Attr fuse.Attr Attr fuse.Attr
} }
var _ = (NodeOpener)((*MemRegularFile)(nil)) var _ = (NodeOpener)((*MemRegularFile)(nil))
var _ = (NodeReader)((*MemRegularFile)(nil)) var _ = (NodeReader)((*MemRegularFile)(nil))
var _ = (NodeWriter)((*MemRegularFile)(nil))
var _ = (NodeSetattrer)((*MemRegularFile)(nil))
var _ = (NodeFlusher)((*MemRegularFile)(nil)) var _ = (NodeFlusher)((*MemRegularFile)(nil))
func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) { func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno) {
if flags&(syscall.O_RDWR) != 0 || flags&syscall.O_WRONLY != 0 { return nil, fuse.FOPEN_KEEP_CACHE, OK
return nil, 0, syscall.EPERM }
func (f *MemRegularFile) Write(ctx context.Context, fh FileHandle, data []byte, off int64) (uint32, syscall.Errno) {
f.mu.Lock()
defer f.mu.Unlock()
end := int64(len(data)) + off
if int64(len(f.Data)) < end {
n := make([]byte, end)
copy(n, f.Data)
f.Data = n
} }
return nil, fuse.FOPEN_KEEP_CACHE, OK copy(f.Data[off:off+int64(len(data))], data)
return uint32(len(data)), 0
} }
var _ = (NodeGetattrer)((*MemRegularFile)(nil)) var _ = (NodeGetattrer)((*MemRegularFile)(nil))
func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno { func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
out.Attr = f.Attr out.Attr = f.Attr
out.Attr.Size = uint64(len(f.Data)) out.Attr.Size = uint64(len(f.Data))
return OK return OK
} }
func (f *MemRegularFile) Setattr(ctx context.Context, fh FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
if sz, ok := in.GetSize(); ok {
f.Data = f.Data[:sz]
}
out.Attr = f.Attr
out.Size = uint64(len(f.Data))
return OK
}
func (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno { func (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno {
return 0 return 0
} }
func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
f.mu.Lock()
defer f.mu.Unlock()
end := int(off) + len(dest) end := int(off) + len(dest)
if end > len(f.Data) { if end > len(f.Data) {
end = len(f.Data) end = len(f.Data)
......
...@@ -13,8 +13,8 @@ import ( ...@@ -13,8 +13,8 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.Server, func()) { func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.Server, func()) {
...@@ -33,8 +33,12 @@ func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.S ...@@ -33,8 +33,12 @@ func testMount(t *testing.T, root InodeEmbedder, opts *Options) (string, *fuse.S
t.Fatal(err) t.Fatal(err)
} }
return mntDir, server, func() { return mntDir, server, func() {
server.Unmount() if err := server.Unmount(); err != nil {
os.Remove(mntDir) t.Fatalf("testMount: Unmount failed: %v", err)
}
if err := syscall.Rmdir(mntDir); err != nil {
t.Errorf("testMount: Remove failed: %v", err)
}
} }
} }
...@@ -64,7 +68,6 @@ func TestDefaultOwner(t *testing.T) { ...@@ -64,7 +68,6 @@ func TestDefaultOwner(t *testing.T) {
} else if st.Uid != 42 || st.Gid != 43 { } else if st.Uid != 42 || st.Gid != 43 {
t.Fatalf("Got Lstat %d, %d want 42,43", st.Uid, st.Gid) t.Fatalf("Got Lstat %d, %d want 42,43", st.Uid, st.Gid)
} }
} }
func TestDataFile(t *testing.T) { func TestDataFile(t *testing.T) {
...@@ -97,6 +100,10 @@ func TestDataFile(t *testing.T) { ...@@ -97,6 +100,10 @@ func TestDataFile(t *testing.T) {
t.Errorf("got mode %o, want %o", st.Mode, want) t.Errorf("got mode %o, want %o", st.Mode, want)
} }
if st.Size != int64(len(want)) || st.Blocks != 8 || st.Blksize != 4096 {
t.Errorf("got %#v, want sz = %d, 8 blocks, 4096 blocksize", st, len(want))
}
fd, err := syscall.Open(mntDir+"/file", syscall.O_RDONLY, 0) fd, err := syscall.Open(mntDir+"/file", syscall.O_RDONLY, 0)
if err != nil { if err != nil {
t.Fatalf("Open: %v", err) t.Fatalf("Open: %v", err)
...@@ -116,6 +123,17 @@ func TestDataFile(t *testing.T) { ...@@ -116,6 +123,17 @@ func TestDataFile(t *testing.T) {
if got != want { if got != want {
t.Errorf("got %q want %q", got, want) t.Errorf("got %q want %q", got, want)
} }
replace := []byte("replaced!")
if err := ioutil.WriteFile(mntDir+"/file", replace, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
if gotBytes, err := ioutil.ReadFile(mntDir + "/file"); err != nil {
t.Fatalf("ReadFile: %v", err)
} else if bytes.Compare(replace, gotBytes) != 0 {
t.Fatalf("read: got %q want %q", gotBytes, replace)
}
} }
func TestDataFileLargeRead(t *testing.T) { func TestDataFileLargeRead(t *testing.T) {
......
...@@ -7,7 +7,7 @@ package fs ...@@ -7,7 +7,7 @@ package fs
import ( import (
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// Mount mounts the given NodeFS on the directory, and starts serving // Mount mounts the given NodeFS on the directory, and starts serving
......
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import (
"context"
"fmt"
"hash/crc32"
"os"
"syscall"
"testing"
"github.com/hanwen/go-fuse/v2/fuse"
)
type randomTypeTest struct {
Inode
}
var _ = (NodeLookuper)((*randomTypeTest)(nil))
var _ = (NodeReaddirer)((*randomTypeTest)(nil))
// Lookup finds a dir.
func (fn *randomTypeTest) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
stable := StableAttr{
Mode: fuse.S_IFDIR,
}
// Override the file type on a pseudo-random subset of entries
if crc32.ChecksumIEEE([]byte(name))%2 == 0 {
stable.Mode = fuse.S_IFREG
}
childFN := &randomTypeTest{}
child := fn.NewInode(ctx, childFN, stable)
return child, syscall.F_OK
}
// Readdir will always return one child dir.
func (fn *randomTypeTest) Readdir(ctx context.Context) (DirStream, syscall.Errno) {
var entries []fuse.DirEntry
for i := 0; i < 100; i++ {
entries = append(entries, fuse.DirEntry{
Name: fmt.Sprintf("%d", i),
Mode: fuse.S_IFDIR,
})
}
return NewListDirStream(entries), syscall.F_OK
}
// TestReaddirTypeFixup tests that DirEntryList.FixMode() works as expected.
func TestReaddirTypeFixup(t *testing.T) {
root := &randomTypeTest{}
mntDir, _, clean := testMount(t, root, nil)
defer clean()
f, err := os.Open(mntDir)
if err != nil {
t.Fatalf("open: %v", err)
}
defer f.Close()
// (Ab)use loopbackDirStream to call and parse getdents(2) on mntDir.
// This makes the kernel call READDIRPLUS, which ultimately calls
// randomTypeTest.Readdir() and randomTypeTest.Lookup() above.
ds, errno := NewLoopbackDirStream(mntDir)
if errno != 0 {
t.Fatalf("readdir: %v", err)
}
defer ds.Close()
for ds.HasNext() {
e, err := ds.Next()
if err != 0 {
t.Errorf("Next: %d", err)
}
t.Logf("%q: mode=0x%x", e.Name, e.Mode)
gotIsDir := (e.Mode & syscall.S_IFDIR) != 0
wantIsdir := (crc32.ChecksumIEEE([]byte(e.Name)) % 2) == 1
if gotIsDir != wantIsdir {
t.Errorf("%q: isdir %v, want %v", e.Name, gotIsDir, wantIsdir)
}
}
}
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
func TestReadonlyCreate(t *testing.T) { func TestReadonlyCreate(t *testing.T) {
......
...@@ -12,8 +12,8 @@ import ( ...@@ -12,8 +12,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// bytesNode is a file that can be read and written // bytesNode is a file that can be read and written
......
...@@ -15,9 +15,9 @@ import ( ...@@ -15,9 +15,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/hanwen/go-fuse/posixtest" "github.com/hanwen/go-fuse/v2/posixtest"
) )
type testCase struct { type testCase struct {
...@@ -32,6 +32,7 @@ type testCase struct { ...@@ -32,6 +32,7 @@ type testCase struct {
server *fuse.Server server *fuse.Server
} }
// writeOrig writes a file into the backing directory of the loopback mount
func (tc *testCase) writeOrig(path, content string, mode os.FileMode) { func (tc *testCase) writeOrig(path, content string, mode os.FileMode) {
if err := ioutil.WriteFile(filepath.Join(tc.origDir, path), []byte(content), mode); err != nil { if err := ioutil.WriteFile(filepath.Join(tc.origDir, path), []byte(content), mode); err != nil {
tc.Fatal(err) tc.Fatal(err)
...@@ -51,14 +52,20 @@ type testOptions struct { ...@@ -51,14 +52,20 @@ type testOptions struct {
entryCache bool entryCache bool
attrCache bool attrCache bool
suppressDebug bool suppressDebug bool
testDir string
} }
// newTestCase creates the directories `orig` and `mnt` inside a temporary
// directory and mounts a loopback filesystem, backed by `orig`, on `mnt`.
func newTestCase(t *testing.T, opts *testOptions) *testCase { func newTestCase(t *testing.T, opts *testOptions) *testCase {
if opts == nil { if opts == nil {
opts = &testOptions{} opts = &testOptions{}
} }
if opts.testDir == "" {
opts.testDir = testutil.TempDir()
}
tc := &testCase{ tc := &testCase{
dir: testutil.TempDir(), dir: opts.testDir,
T: t, T: t,
} }
tc.origDir = tc.dir + "/orig" tc.origDir = tc.dir + "/orig"
...@@ -137,20 +144,6 @@ func TestBasic(t *testing.T) { ...@@ -137,20 +144,6 @@ func TestBasic(t *testing.T) {
} }
} }
func TestFileBasic(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
posixtest.FileBasic(t, tc.mntDir)
}
func TestFileTruncate(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
posixtest.TruncateFile(t, tc.mntDir)
}
func TestFileFdLeak(t *testing.T) { func TestFileFdLeak(t *testing.T) {
tc := newTestCase(t, &testOptions{ tc := newTestCase(t, &testOptions{
suppressDebug: true, suppressDebug: true,
...@@ -174,56 +167,6 @@ func TestFileFdLeak(t *testing.T) { ...@@ -174,56 +167,6 @@ func TestFileFdLeak(t *testing.T) {
} }
} }
func TestMkdir(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
posixtest.MkdirRmdir(t, tc.mntDir)
}
func testRenameOverwrite(t *testing.T, destExists bool) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
posixtest.RenameOverwrite(t, tc.mntDir, destExists)
}
func TestRenameDestExist(t *testing.T) {
testRenameOverwrite(t, true)
}
func TestRenameDestNoExist(t *testing.T) {
testRenameOverwrite(t, false)
}
func TestNlinkZero(t *testing.T) {
// xfstest generic/035.
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
posixtest.NlinkZero(t, tc.mntDir)
}
func TestParallelFileOpen(t *testing.T) {
tc := newTestCase(t, &testOptions{suppressDebug: true, attrCache: true, entryCache: true})
defer tc.Clean()
posixtest.ParallelFileOpen(t, tc.mntDir)
}
func TestSymlink(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
posixtest.SymlinkReadlink(t, tc.mntDir)
}
func TestLink(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
posixtest.Link(t, tc.mntDir)
}
func TestNotifyEntry(t *testing.T) { func TestNotifyEntry(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean() defer tc.Clean()
...@@ -257,22 +200,17 @@ func TestNotifyEntry(t *testing.T) { ...@@ -257,22 +200,17 @@ func TestNotifyEntry(t *testing.T) {
} }
} }
func TestReadDir(t *testing.T) {
tc := newTestCase(t, &testOptions{
suppressDebug: true,
attrCache: true,
entryCache: true,
})
defer tc.Clean()
posixtest.ReadDir(t, tc.mntDir)
}
func TestReadDirStress(t *testing.T) { func TestReadDirStress(t *testing.T) {
tc := newTestCase(t, &testOptions{suppressDebug: true, attrCache: true, entryCache: true}) tc := newTestCase(t, &testOptions{suppressDebug: true, attrCache: true, entryCache: true})
defer tc.Clean() defer tc.Clean()
// (ab)use posixtest.ReadDir to create 110 test files
posixtest.ReadDir(t, tc.mntDir) // Create 110 entries
for i := 0; i < 110; i++ {
name := fmt.Sprintf("file%036x", i)
if err := ioutil.WriteFile(filepath.Join(tc.mntDir, name), []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile %q: %v", name, err)
}
}
var wg sync.WaitGroup var wg sync.WaitGroup
stress := func(gr int) { stress := func(gr int) {
...@@ -407,11 +345,47 @@ func TestMknod(t *testing.T) { ...@@ -407,11 +345,47 @@ func TestMknod(t *testing.T) {
} }
} }
func TestTruncate(t *testing.T) { func TestPosix(t *testing.T) {
tc := newTestCase(t, &testOptions{}) noisy := map[string]bool{
"ParallelFileOpen": true,
"ReadDir": true,
}
for nm, fn := range posixtest.All {
t.Run(nm, func(t *testing.T) {
tc := newTestCase(t, &testOptions{
suppressDebug: noisy[nm],
attrCache: true, entryCache: true})
defer tc.Clean() defer tc.Clean()
posixtest.TruncateNoFile(t, tc.mntDir) fn(t, tc.mntDir)
})
}
}
func TestOpenDirectIO(t *testing.T) {
// Apparently, tmpfs does not allow O_DIRECT, so try to create
// a test temp directory in the home directory.
ext4Dir := filepath.Join(os.Getenv("HOME"), ".go-fuse-test")
if err := os.MkdirAll(ext4Dir, 0755); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
defer os.RemoveAll(ext4Dir)
posixtest.DirectIO(t, ext4Dir)
if t.Failed() {
t.Skip("DirectIO failed on underlying FS")
}
opts := testOptions{
testDir: ext4Dir,
attrCache: true,
entryCache: true,
}
tc := newTestCase(t, &opts)
defer tc.Clean()
posixtest.DirectIO(t, tc.mntDir)
} }
func init() { func init() {
......
...@@ -14,7 +14,7 @@ import ( ...@@ -14,7 +14,7 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
) )
var testData = map[string]string{ var testData = map[string]string{
......
...@@ -17,8 +17,8 @@ import ( ...@@ -17,8 +17,8 @@ import (
"sync" "sync"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// zipFile is a file read from a zip archive. // zipFile is a file read from a zip archive.
......
...@@ -158,6 +158,15 @@ type MountOptions struct { ...@@ -158,6 +158,15 @@ type MountOptions struct {
// If set, ask kernel not to do automatic data cache invalidation. // If set, ask kernel not to do automatic data cache invalidation.
// The filesystem is fully responsible for invalidating data cache. // The filesystem is fully responsible for invalidating data cache.
ExplicitDataCacheControl bool ExplicitDataCacheControl bool
// If set, fuse will first attempt to use syscall.Mount instead of
// fusermount to mount the filesystem. This will not update /etc/mtab
// but might be needed if fusermount is not available.
DirectMount bool
// Options passed to syscall.Mount, the default value used by fusermount
// is syscall.MS_NOSUID|syscall.MS_NODEV
DirectMountFlags uintptr
} }
// RawFileSystem is an interface close to the FUSE wire protocol. // RawFileSystem is an interface close to the FUSE wire protocol.
......
...@@ -32,4 +32,6 @@ const ( ...@@ -32,4 +32,6 @@ const (
CUSE_INIT = 4096 CUSE_INIT = 4096
O_ANYWRITE = uint32(os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREATE | os.O_TRUNC) O_ANYWRITE = uint32(os.O_WRONLY | os.O_RDWR | os.O_APPEND | os.O_CREATE | os.O_TRUNC)
logicalBlockSize = 512
) )
...@@ -37,8 +37,9 @@ func (d DirEntry) String() string { ...@@ -37,8 +37,9 @@ func (d DirEntry) String() string {
// opcodes. // opcodes.
type DirEntryList struct { type DirEntryList struct {
buf []byte buf []byte
size int size int // capacity of the underlying buffer
offset uint64 offset uint64 // entry count (NOT a byte offset)
lastDirent *_Dirent // pointer to the last serialized _Dirent. Used by FixMode().
} }
// NewDirEntryList creates a DirEntryList with the given data buffer // NewDirEntryList creates a DirEntryList with the given data buffer
...@@ -77,7 +78,7 @@ func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) b ...@@ -77,7 +78,7 @@ func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) b
dirent.Off = l.offset + 1 dirent.Off = l.offset + 1
dirent.Ino = inode dirent.Ino = inode
dirent.NameLen = uint32(len(name)) dirent.NameLen = uint32(len(name))
dirent.Typ = (mode & 0170000) >> 12 dirent.Typ = modeToType(mode)
oldLen += direntSize oldLen += direntSize
copy(l.buf[oldLen:], name) copy(l.buf[oldLen:], name)
oldLen += len(name) oldLen += len(name)
...@@ -90,19 +91,42 @@ func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) b ...@@ -90,19 +91,42 @@ func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) b
return true return true
} }
// AddDirLookupEntry is used for ReadDirPlus. It serializes a DirEntry // AddDirLookupEntry is used for ReadDirPlus. If reserves and zeroizes space
// and returns the space for entry. If no space is left, returns a nil // for an EntryOut struct and serializes a DirEntry.
// pointer. // On success, it returns pointers to both structs.
// If not enough space was left, it returns two nil pointers.
//
// The resulting READDIRPLUS output buffer looks like this in memory:
// 1) EntryOut{}
// 2) _Dirent{}
// 3) Name (null-terminated)
// 4) Padding to align to 8 bytes
// [repeat]
func (l *DirEntryList) AddDirLookupEntry(e DirEntry) *EntryOut { func (l *DirEntryList) AddDirLookupEntry(e DirEntry) *EntryOut {
lastStart := len(l.buf) const entryOutSize = int(unsafe.Sizeof(EntryOut{}))
ok := l.Add(int(unsafe.Sizeof(EntryOut{})), e.Name, oldLen := len(l.buf)
e.Ino, e.Mode) ok := l.Add(entryOutSize, e.Name, e.Ino, e.Mode)
if !ok { if !ok {
return nil return nil
} }
result := (*EntryOut)(unsafe.Pointer(&l.buf[lastStart])) l.lastDirent = (*_Dirent)(unsafe.Pointer(&l.buf[oldLen+entryOutSize]))
*result = EntryOut{} entryOut := (*EntryOut)(unsafe.Pointer(&l.buf[oldLen]))
return result *entryOut = EntryOut{} // zeroize
return entryOut
}
// modeToType converts a file *mode* (as used in syscall.Stat_t.Mode)
// to a file *type* (as used in _Dirent.Typ).
// Equivalent to IFTODT() in libc (see man 5 dirent).
func modeToType(mode uint32) uint32 {
return (mode & 0170000) >> 12
}
// FixMode overrides the file mode of the last direntry that was added. This can
// be needed when a directory changes while READDIRPLUS is running.
// Only the file type bits of mode are considered, the rest is masked out.
func (l *DirEntryList) FixMode(mode uint32) {
l.lastDirent.Typ = modeToType(mode)
} }
func (l *DirEntryList) bytes() []byte { func (l *DirEntryList) bytes() []byte {
......
...@@ -92,6 +92,6 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e ...@@ -92,6 +92,6 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
return syscall.Dup(int(f.Fd())) return syscall.Dup(int(f.Fd()))
} }
func unmount(dir string) error { func unmount(dir string, opts *MountOptions) error {
return syscall.Unmount(dir, 0) return syscall.Unmount(dir, 0)
} }
...@@ -7,6 +7,7 @@ package fuse ...@@ -7,6 +7,7 @@ package fuse
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path" "path"
...@@ -26,9 +27,59 @@ func unixgramSocketpair() (l, r *os.File, err error) { ...@@ -26,9 +27,59 @@ func unixgramSocketpair() (l, r *os.File, err error) {
return return
} }
// Create a FUSE FS on the specified mount point without using
// fusermount.
func mountDirect(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
fd, err = syscall.Open("/dev/fuse", os.O_RDWR, 0) // use syscall.Open since we want an int fd
if err != nil {
return
}
// managed to open dev/fuse, attempt to mount
source := opts.FsName
if source == "" {
source = opts.Name
}
var flags uintptr
flags |= syscall.MS_NOSUID | syscall.MS_NODEV
// some values we need to pass to mount, but override possible since opts.Options comes after
var r = []string{
fmt.Sprintf("fd=%d", fd),
"rootmode=40000",
"user_id=0",
"group_id=0",
}
r = append(r, opts.Options...)
if opts.AllowOther {
r = append(r, "allow_other")
}
err = syscall.Mount(opts.FsName, mountPoint, "fuse."+opts.Name, opts.DirectMountFlags, strings.Join(r, ","))
if err != nil {
syscall.Close(fd)
return
}
// success
close(ready)
return
}
// Create a FUSE FS on the specified mount point. The returned // Create a FUSE FS on the specified mount point. The returned
// mount point is always absolute. // mount point is always absolute.
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) { func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
if opts.DirectMount {
fd, err := mountDirect(mountPoint, opts, ready)
if err == nil {
return fd, nil
} else if opts.Debug {
log.Printf("mount: failed to do direct mount: %s", err)
}
}
local, remote, err := unixgramSocketpair() local, remote, err := unixgramSocketpair()
if err != nil { if err != nil {
return return
...@@ -79,7 +130,15 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e ...@@ -79,7 +130,15 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
return fd, err return fd, err
} }
func unmount(mountPoint string) (err error) { func unmount(mountPoint string, opts *MountOptions) (err error) {
if opts.DirectMount {
// Attempt to directly unmount, if fails fallback to fusermount method
err := syscall.Unmount(mountPoint, 0)
if err == nil {
return nil
}
}
bin, err := fusermountBinary() bin, err := fusermountBinary()
if err != nil { if err != nil {
return err return err
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// This package is deprecated. New projects should use the package // This package is deprecated. New projects should use the package
// "github.com/hanwen/go-fuse/fs" instead. // "github.com/hanwen/go-fuse/v2/fs" instead.
// //
// The nodefs package offers a high level API that resembles the // The nodefs package offers a high level API that resembles the
// kernel's idea of what an FS looks like. File systems can have // kernel's idea of what an FS looks like. File systems can have
...@@ -15,7 +15,7 @@ package nodefs ...@@ -15,7 +15,7 @@ package nodefs
import ( import (
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// The Node interface implements the user-defined file system // The Node interface implements the user-defined file system
......
...@@ -7,7 +7,7 @@ package nodefs ...@@ -7,7 +7,7 @@ package nodefs
import ( import (
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type defaultFile struct{} type defaultFile struct{}
......
...@@ -7,7 +7,7 @@ package nodefs ...@@ -7,7 +7,7 @@ package nodefs
import ( import (
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// NewDefaultNode returns an implementation of Node that returns // NewDefaultNode returns an implementation of Node that returns
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"log" "log"
"sync" "sync"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type connectorDir struct { type connectorDir struct {
......
...@@ -8,8 +8,8 @@ import ( ...@@ -8,8 +8,8 @@ import (
"io/ioutil" "io/ioutil"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type nodeReadNode struct { type nodeReadNode struct {
......
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
"sync" "sync"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// DataFile is for implementing read-only filesystems. This // DataFile is for implementing read-only filesystems. This
......
...@@ -9,8 +9,8 @@ import ( ...@@ -9,8 +9,8 @@ import (
"time" "time"
"unsafe" "unsafe"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/utimens" "github.com/hanwen/go-fuse/v2/internal/utimens"
) )
func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status { func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
......
...@@ -10,8 +10,8 @@ import ( ...@@ -10,8 +10,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
// Check that loopbackFile.Utimens() works as expected // Check that loopbackFile.Utimens() works as expected
......
...@@ -16,7 +16,7 @@ import ( ...@@ -16,7 +16,7 @@ import (
"time" "time"
"unsafe" "unsafe"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// Tests should set to true. // Tests should set to true.
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"sync" "sync"
"unsafe" "unsafe"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// openedFile stores either an open dir or an open file. // openedFile stores either an open dir or an open file.
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// Returns the RawFileSystem so it can be mounted. // Returns the RawFileSystem so it can be mounted.
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
package nodefs package nodefs
import ( import (
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// Mount mounts a filesystem with the given root node on the given directory. // Mount mounts a filesystem with the given root node on the given directory.
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"log" "log"
"sync" "sync"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type parentData struct { type parentData struct {
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type lockingFile struct { type lockingFile struct {
......
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// NewMemNodeFSRoot creates an in-memory node-based filesystem. Files // NewMemNodeFSRoot creates an in-memory node-based filesystem. Files
......
...@@ -10,8 +10,8 @@ import ( ...@@ -10,8 +10,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
const testTtl = 100 * time.Millisecond const testTtl = 100 * time.Millisecond
......
...@@ -100,7 +100,6 @@ func doInit(server *Server, req *request) { ...@@ -100,7 +100,6 @@ func doInit(server *Server, req *request) {
server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
} }
dataCacheMode := input.Flags & CAP_AUTO_INVAL_DATA dataCacheMode := input.Flags & CAP_AUTO_INVAL_DATA
if server.opts.ExplicitDataCacheControl { if server.opts.ExplicitDataCacheControl {
// we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode // we don't want CAP_AUTO_INVAL_DATA even if we cannot go into fully explicit mode
...@@ -277,7 +276,11 @@ func doGetXAttr(server *Server, req *request) { ...@@ -277,7 +276,11 @@ func doGetXAttr(server *Server, req *request) {
req.status = OK req.status = OK
out.Size = n out.Size = n
} else if req.status.Ok() { } else if req.status.Ok() {
// ListXAttr called with an empty buffer returns the current size of
// the list but does not touch the buffer (see man 2 listxattr).
if len(req.flatData) > 0 {
req.flatData = req.flatData[:n] req.flatData = req.flatData[:n]
}
out.Size = n out.Size = n
} else { } else {
req.flatData = req.flatData[:0] req.flatData = req.flatData[:0]
...@@ -525,6 +528,8 @@ func getHandler(o uint32) *operationHandler { ...@@ -525,6 +528,8 @@ func getHandler(o uint32) *operationHandler {
return operationHandlers[o] return operationHandlers[o]
} }
var maxInputSize uintptr
func init() { func init() {
operationHandlers = make([]*operationHandler, _OPCODE_COUNT) operationHandlers = make([]*operationHandler, _OPCODE_COUNT)
for i := range operationHandlers { for i := range operationHandlers {
...@@ -536,6 +541,7 @@ func init() { ...@@ -536,6 +541,7 @@ func init() {
operationHandlers[op].FileNameOut = true operationHandlers[op].FileNameOut = true
} }
maxInputSize = 0
for op, sz := range map[uint32]uintptr{ for op, sz := range map[uint32]uintptr{
_OP_FORGET: unsafe.Sizeof(ForgetIn{}), _OP_FORGET: unsafe.Sizeof(ForgetIn{}),
_OP_BATCH_FORGET: unsafe.Sizeof(_BatchForgetIn{}), _OP_BATCH_FORGET: unsafe.Sizeof(_BatchForgetIn{}),
...@@ -576,6 +582,9 @@ func init() { ...@@ -576,6 +582,9 @@ func init() {
_OP_COPY_FILE_RANGE: unsafe.Sizeof(CopyFileRangeIn{}), _OP_COPY_FILE_RANGE: unsafe.Sizeof(CopyFileRangeIn{}),
} { } {
operationHandlers[op].InputSize = sz operationHandlers[op].InputSize = sz
if sz > maxInputSize {
maxInputSize = sz
}
} }
for op, sz := range map[uint32]uintptr{ for op, sz := range map[uint32]uintptr{
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// This package is deprecated. New projects should use the package // This package is deprecated. New projects should use the package
// "github.com/hanwen/go-fuse/fs" instead. // "github.com/hanwen/go-fuse/v2/fs" instead.
// //
// Package pathfs provides a file system API expressed in filenames. // Package pathfs provides a file system API expressed in filenames.
package pathfs package pathfs
...@@ -11,8 +11,8 @@ package pathfs ...@@ -11,8 +11,8 @@ package pathfs
import ( import (
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
) )
// A filesystem API that uses paths rather than inodes. A minimal // A filesystem API that uses paths rather than inodes. A minimal
......
...@@ -7,7 +7,7 @@ package pathfs ...@@ -7,7 +7,7 @@ package pathfs
import ( import (
"os" "os"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
func CopyFile(srcFs, destFs FileSystem, srcFile, destFile string, context *fuse.Context) fuse.Status { func CopyFile(srcFs, destFs FileSystem, srcFile, destFile string, context *fuse.Context) fuse.Status {
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func TestCopyFile(t *testing.T) { func TestCopyFile(t *testing.T) {
......
...@@ -7,8 +7,8 @@ package pathfs ...@@ -7,8 +7,8 @@ package pathfs
import ( import (
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
) )
// NewDefaultFileSystem creates a filesystem that responds ENOSYS for // NewDefaultFileSystem creates a filesystem that responds ENOSYS for
......
...@@ -8,8 +8,8 @@ import ( ...@@ -8,8 +8,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
) )
type lockingFileSystem struct { type lockingFileSystem struct {
......
...@@ -11,9 +11,9 @@ import ( ...@@ -11,9 +11,9 @@ import (
"path/filepath" "path/filepath"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal" "github.com/hanwen/go-fuse/v2/internal"
) )
type loopbackFileSystem struct { type loopbackFileSystem struct {
...@@ -119,6 +119,9 @@ func (fs *loopbackFileSystem) OpenDir(name string, context *fuse.Context) (strea ...@@ -119,6 +119,9 @@ func (fs *loopbackFileSystem) OpenDir(name string, context *fuse.Context) (strea
} }
func (fs *loopbackFileSystem) Open(name string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { func (fs *loopbackFileSystem) Open(name string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) {
// filter out append. The kernel layer will translate the
// offsets for us appropriately.
flags = flags &^ syscall.O_APPEND
f, err := os.OpenFile(fs.GetPath(name), int(flags), 0) f, err := os.OpenFile(fs.GetPath(name), int(flags), 0)
if err != nil { if err != nil {
return nil, fuse.ToStatus(err) return nil, fuse.ToStatus(err)
...@@ -190,6 +193,7 @@ func (fs *loopbackFileSystem) Access(name string, mode uint32, context *fuse.Con ...@@ -190,6 +193,7 @@ func (fs *loopbackFileSystem) Access(name string, mode uint32, context *fuse.Con
} }
func (fs *loopbackFileSystem) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { func (fs *loopbackFileSystem) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) {
flags = flags &^ syscall.O_APPEND
f, err := os.OpenFile(fs.GetPath(path), int(flags)|os.O_CREATE, os.FileMode(mode)) f, err := os.OpenFile(fs.GetPath(path), int(flags)|os.O_CREATE, os.FileMode(mode))
return nodefs.NewLoopbackFile(f), fuse.ToStatus(err) return nodefs.NewLoopbackFile(f), fuse.ToStatus(err)
} }
...@@ -8,8 +8,8 @@ import ( ...@@ -8,8 +8,8 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/utimens" "github.com/hanwen/go-fuse/v2/internal/utimens"
) )
func (fs *loopbackFileSystem) Utimens(path string, a *time.Time, m *time.Time, context *fuse.Context) fuse.Status { func (fs *loopbackFileSystem) Utimens(path string, a *time.Time, m *time.Time, context *fuse.Context) fuse.Status {
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
func (fs *loopbackFileSystem) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) { func (fs *loopbackFileSystem) ListXAttr(name string, context *fuse.Context) ([]string, fuse.Status) {
......
...@@ -12,8 +12,8 @@ import ( ...@@ -12,8 +12,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
// Check that loopbackFileSystem.Utimens() works as expected // Check that loopbackFileSystem.Utimens() works as expected
......
...@@ -9,9 +9,9 @@ import ( ...@@ -9,9 +9,9 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type ownerFs struct { type ownerFs struct {
......
...@@ -12,8 +12,8 @@ import ( ...@@ -12,8 +12,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
) )
// refCountedInode is used in clientInodeMap. The reference count is used to decide // refCountedInode is used in clientInodeMap. The reference count is used to decide
......
...@@ -9,8 +9,8 @@ import ( ...@@ -9,8 +9,8 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
) )
// PrefixFileSystem adds a path prefix to incoming calls. // PrefixFileSystem adds a path prefix to incoming calls.
......
...@@ -8,8 +8,8 @@ import ( ...@@ -8,8 +8,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
) )
// NewReadonlyFileSystem returns a wrapper that only exposes read-only // NewReadonlyFileSystem returns a wrapper that only exposes read-only
......
...@@ -13,9 +13,9 @@ import ( ...@@ -13,9 +13,9 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
var xattrGolden = map[string][]byte{ var xattrGolden = map[string][]byte{
......
...@@ -9,16 +9,17 @@ const pollHackName = ".go-fuse-epoll-hack" ...@@ -9,16 +9,17 @@ const pollHackName = ".go-fuse-epoll-hack"
const pollHackInode = ^uint64(0) const pollHackInode = ^uint64(0)
func doPollHackLookup(ms *Server, req *request) { func doPollHackLookup(ms *Server, req *request) {
attr := Attr{
Ino: pollHackInode,
Mode: S_IFREG | 0644,
Nlink: 1,
}
switch req.inHeader.Opcode { switch req.inHeader.Opcode {
case _OP_CREATE: case _OP_CREATE:
out := (*CreateOut)(req.outData()) out := (*CreateOut)(req.outData())
out.EntryOut = EntryOut{ out.EntryOut = EntryOut{
NodeId: pollHackInode, NodeId: pollHackInode,
Attr: Attr{ Attr: attr,
Ino: pollHackInode,
Mode: S_IFREG | 0644,
Nlink: 1,
},
} }
out.OpenOut = OpenOut{ out.OpenOut = OpenOut{
Fh: pollHackInode, Fh: pollHackInode,
...@@ -28,7 +29,15 @@ func doPollHackLookup(ms *Server, req *request) { ...@@ -28,7 +29,15 @@ func doPollHackLookup(ms *Server, req *request) {
out := (*EntryOut)(req.outData()) out := (*EntryOut)(req.outData())
*out = EntryOut{} *out = EntryOut{}
req.status = ENOENT req.status = ENOENT
case _OP_GETATTR:
out := (*AttrOut)(req.outData())
out.Attr = attr
req.status = OK
case _OP_POLL:
req.status = ENOSYS
default: default:
req.status = EIO // We want to avoid switching off features through our
// poll hack, so don't use ENOSYS
req.status = ERANGE
} }
} }
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"sync" "sync"
"syscall" "syscall"
"time" "time"
"unsafe"
) )
const ( const (
...@@ -107,7 +108,7 @@ func (ms *Server) Unmount() (err error) { ...@@ -107,7 +108,7 @@ func (ms *Server) Unmount() (err error) {
} }
delay := time.Duration(0) delay := time.Duration(0)
for try := 0; try < 5; try++ { for try := 0; try < 5; try++ {
err = unmount(ms.mountPoint) err = unmount(ms.mountPoint, ms.opts)
if err == nil { if err == nil {
break break
} }
...@@ -175,8 +176,11 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server ...@@ -175,8 +176,11 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
cancel: make(chan struct{}), cancel: make(chan struct{}),
} }
} }
ms.readPool.New = func() interface{} { return make([]byte, o.MaxWrite+pageSize) } ms.readPool.New = func() interface{} {
buf := make([]byte, o.MaxWrite+int(maxInputSize)+logicalBlockSize)
buf = alignSlice(buf, unsafe.Sizeof(WriteIn{}), logicalBlockSize, uintptr(o.MaxWrite)+maxInputSize)
return buf
}
mountPoint = filepath.Clean(mountPoint) mountPoint = filepath.Clean(mountPoint)
if !filepath.IsAbs(mountPoint) { if !filepath.IsAbs(mountPoint) {
cwd, err := os.Getwd() cwd, err := os.Getwd()
...@@ -198,6 +202,10 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server ...@@ -198,6 +202,10 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
// TODO - unmount as well? // TODO - unmount as well?
return nil, fmt.Errorf("init: %s", code) return nil, fmt.Errorf("init: %s", code)
} }
// This prepares for Serve being called somewhere, either
// synchronously or asynchronously.
ms.loops.Add(1)
return ms, nil return ms, nil
} }
...@@ -256,13 +264,14 @@ func handleEINTR(fn func() error) (err error) { ...@@ -256,13 +264,14 @@ func handleEINTR(fn func() error) (err error) {
// Returns a new request, or error. In case exitIdle is given, returns // Returns a new request, or error. In case exitIdle is given, returns
// nil, OK if we have too many readers already. // nil, OK if we have too many readers already.
func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) { func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
req = ms.reqPool.Get().(*request)
dest := ms.readPool.Get().([]byte)
ms.reqMu.Lock() ms.reqMu.Lock()
if ms.reqReaders > _MAX_READERS { if ms.reqReaders > _MAX_READERS {
ms.reqMu.Unlock() ms.reqMu.Unlock()
return nil, OK return nil, OK
} }
req = ms.reqPool.Get().(*request)
dest := ms.readPool.Get().([]byte)
ms.reqReaders++ ms.reqReaders++
ms.reqMu.Unlock() ms.reqMu.Unlock()
...@@ -356,7 +365,6 @@ func (ms *Server) recordStats(req *request) { ...@@ -356,7 +365,6 @@ func (ms *Server) recordStats(req *request) {
// //
// Each filesystem operation executes in a separate goroutine. // Each filesystem operation executes in a separate goroutine.
func (ms *Server) Serve() { func (ms *Server) Serve() {
ms.loops.Add(1)
ms.loop(false) ms.loop(false)
ms.loops.Wait() ms.loops.Wait()
...@@ -382,7 +390,8 @@ func (ms *Server) Serve() { ...@@ -382,7 +390,8 @@ func (ms *Server) Serve() {
} }
} }
// Wait waits for the serve loop to exit // Wait waits for the serve loop to exit. This should only be called
// after Serve has been called, or it will hang indefinitely.
func (ms *Server) Wait() { func (ms *Server) Wait() {
ms.loops.Wait() ms.loops.Wait()
} }
...@@ -454,14 +463,8 @@ func (ms *Server) handleRequest(req *request) Status { ...@@ -454,14 +463,8 @@ func (ms *Server) handleRequest(req *request) Status {
log.Println(req.InputDebug()) log.Println(req.InputDebug())
} }
if req.inHeader.NodeId == pollHackInode { if req.inHeader.NodeId == pollHackInode ||
// We want to avoid switching off features through our req.inHeader.NodeId == FUSE_ROOT_ID && len(req.filenames) > 0 && req.filenames[0] == pollHackName {
// poll hack, so don't use ENOSYS
req.status = EIO
if req.inHeader.Opcode == _OP_POLL {
req.status = ENOSYS
}
} else if req.inHeader.NodeId == FUSE_ROOT_ID && len(req.filenames) > 0 && req.filenames[0] == pollHackName {
doPollHackLookup(ms, req) doPollHackLookup(ms, req)
} else if req.status.Ok() && req.handler.Func == nil { } else if req.status.Ok() && req.handler.Func == nil {
log.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode)) log.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode))
...@@ -479,6 +482,15 @@ func (ms *Server) handleRequest(req *request) Status { ...@@ -479,6 +482,15 @@ func (ms *Server) handleRequest(req *request) Status {
return Status(errNo) return Status(errNo)
} }
// alignSlice ensures that the byte at alignedByte is aligned with the
// given logical block size. The input slice should be at least (size
// + blockSize)
func alignSlice(buf []byte, alignedByte, blockSize, size uintptr) []byte {
misaligned := uintptr(unsafe.Pointer(&buf[alignedByte])) & (blockSize - 1)
buf = buf[blockSize-misaligned:]
return buf[:size]
}
func (ms *Server) allocOut(req *request, size uint32) []byte { func (ms *Server) allocOut(req *request, size uint32) []byte {
if cap(req.bufferPoolOutputBuf) >= int(size) { if cap(req.bufferPoolOutputBuf) >= int(size) {
req.bufferPoolOutputBuf = req.bufferPoolOutputBuf[:size] req.bufferPoolOutputBuf = req.bufferPoolOutputBuf[:size]
...@@ -486,7 +498,10 @@ func (ms *Server) allocOut(req *request, size uint32) []byte { ...@@ -486,7 +498,10 @@ func (ms *Server) allocOut(req *request, size uint32) []byte {
} }
if req.bufferPoolOutputBuf != nil { if req.bufferPoolOutputBuf != nil {
ms.buffers.FreeBuffer(req.bufferPoolOutputBuf) ms.buffers.FreeBuffer(req.bufferPoolOutputBuf)
req.bufferPoolOutputBuf = nil
} }
// As this allocated a multiple of the page size, very likely
// this is aligned to logicalBlockSize too, which is smaller.
req.bufferPoolOutputBuf = ms.buffers.AllocBuffer(size) req.bufferPoolOutputBuf = ms.buffers.AllocBuffer(size)
return req.bufferPoolOutputBuf return req.bufferPoolOutputBuf
} }
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/hanwen/go-fuse/splice" "github.com/hanwen/go-fuse/v2/splice"
) )
func (s *Server) setSplice() { func (s *Server) setSplice() {
......
...@@ -14,10 +14,10 @@ import ( ...@@ -14,10 +14,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type cacheFs struct { type cacheFs struct {
...@@ -54,7 +54,7 @@ func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) { ...@@ -54,7 +54,7 @@ func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) {
} }
opts := nodefs.NewOptions() opts := nodefs.NewOptions()
opts.AttrTimeout = 10*time.Millisecond opts.AttrTimeout = 10 * time.Millisecond
opts.Debug = testutil.VerboseTest() opts.Debug = testutil.VerboseTest()
state, _, err := nodefs.Mount(dir+"/mnt", pfs.Root(), mntOpts, opts) state, _, err := nodefs.Mount(dir+"/mnt", pfs.Root(), mntOpts, opts)
if err != nil { if err != nil {
...@@ -121,7 +121,7 @@ func TestFopenKeepCache(t *testing.T) { ...@@ -121,7 +121,7 @@ func TestFopenKeepCache(t *testing.T) {
} }
// sleep a bit to make sure mtime of file for before and after are different // sleep a bit to make sure mtime of file for before and after are different
time.Sleep(20*time.Millisecond) time.Sleep(20 * time.Millisecond)
xwriteFile(wd+"/orig/file.txt", after) xwriteFile(wd+"/orig/file.txt", after)
mtimeAfter := xstat(wd + "/orig/file.txt").ModTime() mtimeAfter := xstat(wd + "/orig/file.txt").ModTime()
...@@ -134,7 +134,7 @@ func TestFopenKeepCache(t *testing.T) { ...@@ -134,7 +134,7 @@ func TestFopenKeepCache(t *testing.T) {
// //
// this way we make sure the kernel knows updated size/mtime before we // this way we make sure the kernel knows updated size/mtime before we
// try to read the file next time. // try to read the file next time.
time.Sleep(100*time.Millisecond) time.Sleep(100 * time.Millisecond)
_ = xstat(wd + "/mnt/file.txt") _ = xstat(wd + "/mnt/file.txt")
c = xreadFile(wd + "/mnt/file.txt") c = xreadFile(wd + "/mnt/file.txt")
......
...@@ -15,9 +15,9 @@ import ( ...@@ -15,9 +15,9 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
// DataNode is a nodefs.Node that Reads static data. // DataNode is a nodefs.Node that Reads static data.
......
...@@ -10,9 +10,9 @@ import ( ...@@ -10,9 +10,9 @@ import (
"path" "path"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func TestDefaultNodeGetAttr(t *testing.T) { func TestDefaultNodeGetAttr(t *testing.T) {
......
...@@ -9,10 +9,10 @@ import ( ...@@ -9,10 +9,10 @@ import (
"os" "os"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type DefaultReadFS struct { type DefaultReadFS struct {
......
...@@ -13,9 +13,9 @@ import ( ...@@ -13,9 +13,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type flipNode struct { type flipNode struct {
......
...@@ -15,9 +15,9 @@ import ( ...@@ -15,9 +15,9 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func TestFlockExclusive(t *testing.T) { func TestFlockExclusive(t *testing.T) {
......
...@@ -10,10 +10,10 @@ import ( ...@@ -10,10 +10,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type MutableDataFile struct { type MutableDataFile struct {
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
func TestTouch(t *testing.T) { func TestTouch(t *testing.T) {
......
...@@ -18,10 +18,11 @@ import ( ...@@ -18,10 +18,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/hanwen/go-fuse/v2/posixtest"
) )
type testCase struct { type testCase struct {
...@@ -236,26 +237,6 @@ func TestWriteThrough(t *testing.T) { ...@@ -236,26 +237,6 @@ func TestWriteThrough(t *testing.T) {
CompareSlices(t, slice[:n], content) CompareSlices(t, slice[:n], content)
} }
func TestMkdirRmdir(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
// Mkdir/Rmdir.
if err := os.Mkdir(tc.mountSubdir, 0777); err != nil {
t.Fatalf("Mkdir failed: %v", err)
}
if fi, err := os.Lstat(tc.origSubdir); err != nil {
t.Fatalf("Lstat(%q): %v", tc.origSubdir, err)
} else if !fi.IsDir() {
t.Errorf("Not a directory: %v", fi)
}
if err := os.Remove(tc.mountSubdir); err != nil {
t.Fatalf("Remove failed: %v", err)
}
}
func TestLinkCreate(t *testing.T) { func TestLinkCreate(t *testing.T) {
tc := NewTestCase(t) tc := NewTestCase(t)
defer tc.Cleanup() defer tc.Cleanup()
...@@ -399,71 +380,27 @@ func TestLinkForget(t *testing.T) { ...@@ -399,71 +380,27 @@ func TestLinkForget(t *testing.T) {
} }
} }
func TestSymlink(t *testing.T) { func TestPosix(t *testing.T) {
tc := NewTestCase(t) tests := []string{
defer tc.Cleanup() "SymlinkReadlink",
"MkdirRmdir",
contents := []byte{1, 2, 3} "RenameOverwriteDestNoExist",
tc.WriteFile(tc.origFile, []byte(contents), 0700) "RenameOverwriteDestExist",
"ReadDir",
linkFile := "symlink-file" "ReadDirPicksUpCreate",
orig := "hello.txt" "AppendWrite",
err := os.Symlink(orig, filepath.Join(tc.mnt, linkFile))
if err != nil {
t.Fatalf("Symlink failed: %v", err)
}
origLink := filepath.Join(tc.orig, linkFile)
fi, err := os.Lstat(origLink)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if fi.Mode()&os.ModeSymlink == 0 {
t.Errorf("not a symlink: %v", fi)
return
}
read, err := os.Readlink(filepath.Join(tc.mnt, linkFile))
if err != nil {
t.Fatalf("Readlink failed: %v", err)
}
if read != orig {
t.Errorf("unexpected symlink value '%v'", read)
}
}
func TestRename(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
contents := []byte{1, 2, 3}
tc.WriteFile(tc.origFile, []byte(contents), 0700)
sd := tc.mnt + "/testRename"
tc.Mkdir(sd, 0777)
subFile := sd + "/subfile"
if err := os.Rename(tc.mountFile, subFile); err != nil {
t.Fatalf("Rename failed: %v", err)
}
f, _ := os.Lstat(tc.origFile)
if f != nil {
t.Errorf("original %v still exists.", tc.origFile)
} }
if _, err := os.Lstat(subFile); err != nil { for _, k := range tests {
t.Errorf("destination %q does not exist: %v", subFile, err) f := posixtest.All[k]
if f == nil {
t.Fatalf("test %s missing", k)
} }
} t.Run(k, func(t *testing.T) {
func TestRenameNonExistent(t *testing.T) {
tc := NewTestCase(t) tc := NewTestCase(t)
defer tc.Cleanup() defer tc.Cleanup()
err := os.Rename(tc.mnt+"/doesnotexist", tc.mnt+"/doesnotmatter") f(t, tc.mnt)
if !os.IsNotExist(err) { })
t.Errorf("got err %v, want ENOENT", err)
} }
} }
...@@ -495,24 +432,6 @@ func TestDelRename(t *testing.T) { ...@@ -495,24 +432,6 @@ func TestDelRename(t *testing.T) {
} }
} }
func TestOverwriteRename(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
sd := tc.mnt + "/testOverwriteRename"
tc.Mkdir(sd, 0755)
d := sd + "/dest"
tc.WriteFile(d, []byte("blabla"), 0644)
s := sd + "/src"
tc.WriteFile(s, []byte("blabla"), 0644)
if err := os.Rename(s, d); err != nil {
t.Fatalf("Rename failed: %v", err)
}
}
func TestAccess(t *testing.T) { func TestAccess(t *testing.T) {
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
t.Log("Skipping TestAccess() as root.") t.Log("Skipping TestAccess() as root.")
...@@ -558,44 +477,6 @@ func TestMknod(t *testing.T) { ...@@ -558,44 +477,6 @@ func TestMknod(t *testing.T) {
} }
} }
func TestReaddir(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
contents := []byte{1, 2, 3}
tc.Mkdir(tc.origSubdir, 0777)
dir, err := os.Open(tc.mnt)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer dir.Close()
// READDIR should show "hello.txt" even if it is created after the OPENDIR.
// https://github.com/hanwen/go-fuse/issues/252
tc.WriteFile(tc.origFile, []byte(contents), 0700)
infos, err := dir.Readdir(10)
if err != nil {
t.Fatalf("Readdir failed: %v", err)
}
wanted := map[string]bool{
"hello.txt": true,
"subdir": true,
}
if len(wanted) != len(infos) {
t.Errorf("Wrong number of directory entries: want=%d have=%d", len(wanted), len(infos))
} else {
for _, v := range infos {
_, ok := wanted[v.Name()]
if !ok {
t.Errorf("Unexpected name %v", v.Name())
}
}
}
}
// Test that READDIR works even if the directory is renamed after the OPENDIR. // Test that READDIR works even if the directory is renamed after the OPENDIR.
// This checks that the fix for https://github.com/hanwen/go-fuse/issues/252 // This checks that the fix for https://github.com/hanwen/go-fuse/issues/252
// does not break this case. // does not break this case.
...@@ -813,23 +694,6 @@ func TestLargeDirRead(t *testing.T) { ...@@ -813,23 +694,6 @@ func TestLargeDirRead(t *testing.T) {
} }
} }
func TestRootDir(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
d, err := os.Open(tc.mnt)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
if _, err := d.Readdirnames(-1); err != nil {
t.Fatalf("Readdirnames failed: %v", err)
}
if err := d.Close(); err != nil {
t.Fatalf("Close failed: %v", err)
}
}
func ioctl(fd int, cmd int, arg uintptr) (int, int) { func ioctl(fd int, cmd int, arg uintptr) (int, int) {
r0, _, e1 := syscall.Syscall( r0, _, e1 := syscall.Syscall(
syscall.SYS_IOCTL, uintptr(fd), uintptr(cmd), uintptr(arg)) syscall.SYS_IOCTL, uintptr(fd), uintptr(cmd), uintptr(arg))
...@@ -1001,5 +865,4 @@ func TestLookupKnownChildrenAttrCopied(t *testing.T) { ...@@ -1001,5 +865,4 @@ func TestLookupKnownChildrenAttrCopied(t *testing.T) {
} else if fi.Mode() != mode { } else if fi.Mode() != mode {
t.Fatalf("got mode %o, want %o", fi.Mode(), mode) t.Fatalf("got mode %o, want %o", fi.Mode(), mode)
} }
} }
...@@ -12,10 +12,10 @@ import ( ...@@ -12,10 +12,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func TestMountOnExisting(t *testing.T) { func TestMountOnExisting(t *testing.T) {
......
...@@ -8,9 +8,9 @@ import ( ...@@ -8,9 +8,9 @@ import (
"os" "os"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type truncatableFile struct { type truncatableFile struct {
......
...@@ -9,9 +9,9 @@ import ( ...@@ -9,9 +9,9 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type rootNode struct { type rootNode struct {
......
...@@ -10,9 +10,9 @@ import ( ...@@ -10,9 +10,9 @@ import (
"sync/atomic" "sync/atomic"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
// exercise functionality when open returns 0 file handle. // exercise functionality when open returns 0 file handle.
......
...@@ -9,10 +9,10 @@ import ( ...@@ -9,10 +9,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type NotifyFs struct { type NotifyFs struct {
......
...@@ -10,10 +10,10 @@ import ( ...@@ -10,10 +10,10 @@ import (
"os/exec" "os/exec"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type umaskFS struct { type umaskFS struct {
......
...@@ -12,9 +12,9 @@ import ( ...@@ -12,9 +12,9 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
// this file is linux-only, since it uses syscall.Getxattr. // this file is linux-only, since it uses syscall.Getxattr.
......
...@@ -159,6 +159,7 @@ type SetAttrInCommon struct { ...@@ -159,6 +159,7 @@ type SetAttrInCommon struct {
Unused5 uint32 Unused5 uint32
} }
// GetFh returns the file handle if available, or 0 if undefined.
func (s *SetAttrInCommon) GetFh() (uint64, bool) { func (s *SetAttrInCommon) GetFh() (uint64, bool) {
if s.Valid&FATTR_FH != 0 { if s.Valid&FATTR_FH != 0 {
return s.Fh, true return s.Fh, true
......
...@@ -18,6 +18,8 @@ const ( ...@@ -18,6 +18,8 @@ const (
type Attr struct { type Attr struct {
Ino uint64 Ino uint64
Size uint64 Size uint64
// Blocks is the number of 512-byte blocks that the file occupies on disk.
Blocks uint64 Blocks uint64
Atime uint64 Atime uint64
Mtime uint64 Mtime uint64
...@@ -29,6 +31,8 @@ type Attr struct { ...@@ -29,6 +31,8 @@ type Attr struct {
Nlink uint32 Nlink uint32
Owner Owner
Rdev uint32 Rdev uint32
// Blksize is the preferred size for file system operations.
Blksize uint32 Blksize uint32
Padding uint32 Padding uint32
} }
......
module github.com/hanwen/go-fuse module github.com/hanwen/go-fuse/v2
require ( require (
github.com/hanwen/go-fuse v1.0.0
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522
) )
github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE=
......
...@@ -53,18 +53,21 @@ func TestHasAccess(t *testing.T) { ...@@ -53,18 +53,21 @@ func TestHasAccess(t *testing.T) {
_ = myOtherGid _ = myOtherGid
_ = notMyGid _ = notMyGid
for i, tc := range []testcase{ cases := []testcase{
{myUid, myGid, myUid, myGid, 0100, 01, true}, {myUid, myGid, myUid, myGid, 0100, 01, true},
{myUid, myGid, myUid + 1, notMyGid, 0001, 0001, true}, {myUid, myGid, myUid + 1, notMyGid, 0001, 0001, true},
{myUid, myGid, myUid + 1, notMyGid, 0000, 0001, false}, {myUid, myGid, myUid + 1, notMyGid, 0000, 0001, false},
{myUid, myGid, myUid + 1, notMyGid, 0007, 0000, true}, {myUid, myGid, myUid + 1, notMyGid, 0007, 0000, true},
{myUid, myGid, myUid + 1, myOtherGid, 0020, 002, true},
{myUid, myGid, myUid + 1, notMyGid, 0020, 002, false}, {myUid, myGid, myUid + 1, notMyGid, 0020, 002, false},
{myUid, myGid, myUid, myGid, 0000, 01, false}, {myUid, myGid, myUid, myGid, 0000, 01, false},
{myUid, myGid, myUid, myGid, 0200, 01, false}, {myUid, myGid, myUid, myGid, 0200, 01, false},
{0, myGid, myUid + 1, notMyGid, 0700, 01, true}, {0, myGid, myUid + 1, notMyGid, 0700, 01, true},
} { }
if myOtherGid != 0 {
cases = append(cases, testcase{myUid, myGid, myUid + 1, myOtherGid, 0020, 002, true})
}
for i, tc := range cases {
got := HasAccess(tc.uid, tc.gid, tc.fuid, tc.fgid, tc.perm, tc.mask) got := HasAccess(tc.uid, tc.gid, tc.fuid, tc.fgid, tc.perm, tc.mask)
if got != tc.want { if got != tc.want {
t.Errorf("%d: accessCheck(%v): got %v, want %v", i, tc, got, tc.want) t.Errorf("%d: accessCheck(%v): got %v, want %v", i, tc, got, tc.want)
......
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// Check that loopback Utimens() works as expected. // Check that loopback Utimens() works as expected.
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// timeToTimeval converts time.Time to syscall.Timeval // timeToTimeval converts time.Time to syscall.Timeval
......
...@@ -13,8 +13,8 @@ import ( ...@@ -13,8 +13,8 @@ import (
"path/filepath" "path/filepath"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
func filePathHash(path string) string { func filePathHash(path string) string {
......
...@@ -13,10 +13,10 @@ import ( ...@@ -13,10 +13,10 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/hanwen/go-fuse/posixtest" "github.com/hanwen/go-fuse/v2/posixtest"
) )
type testCase struct { type testCase struct {
......
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package posixtest
import (
"fmt"
"log"
"os"
"runtime"
"strings"
)
// listFds lists the open file descriptors for process "pid". Pass pid=0 for
// ourselves. Pass a prefix to ignore all paths that do not start with "prefix".
//
// Copied from https://github.com/rfjakob/gocryptfs/blob/master/tests/test_helpers/mount_unmount.go#L191
func listFds(pid int, prefix string) []string {
// We need /proc to get the list of fds for other processes. Only exists
// on Linux.
if runtime.GOOS != "linux" && pid > 0 {
return nil
}
// Both Linux and MacOS have /dev/fd
dir := "/dev/fd"
if pid > 0 {
dir = fmt.Sprintf("/proc/%d/fd", pid)
}
f, err := os.Open(dir)
if err != nil {
fmt.Printf("ListFds: %v\n", err)
return nil
}
defer f.Close()
// Note: Readdirnames filters "." and ".."
names, err := f.Readdirnames(0)
if err != nil {
log.Panic(err)
}
var out []string
var filtered []string
for _, n := range names {
fdPath := dir + "/" + n
fi, err := os.Lstat(fdPath)
if err != nil {
// fd was closed in the meantime
continue
}
if fi.Mode()&0400 > 0 {
n += "r"
}
if fi.Mode()&0200 > 0 {
n += "w"
}
target, err := os.Readlink(fdPath)
if err != nil {
// fd was closed in the meantime
continue
}
if strings.HasPrefix(target, "pipe:") || strings.HasPrefix(target, "anon_inode:[eventpoll]") {
// go-fuse creates pipes on demand for splice(), which
// creates spurious test failures. Ignore all pipes.
// Also get rid of the "eventpoll" fd that is always there and not
// interesting.
filtered = append(filtered, target)
continue
}
if prefix != "" && !strings.HasPrefix(target, prefix) {
filtered = append(filtered, target)
continue
}
out = append(out, n+"="+target)
}
out = append(out, fmt.Sprintf("(filtered: %s)", strings.Join(filtered, ", ")))
return out
}
...@@ -12,14 +12,15 @@ import ( ...@@ -12,14 +12,15 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync"
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// All holds a map of all test functions
var All = map[string]func(*testing.T, string){ var All = map[string]func(*testing.T, string){
"AppendWrite": AppendWrite,
"SymlinkReadlink": SymlinkReadlink, "SymlinkReadlink": SymlinkReadlink,
"FileBasic": FileBasic, "FileBasic": FileBasic,
"TruncateFile": TruncateFile, "TruncateFile": TruncateFile,
...@@ -27,11 +28,57 @@ var All = map[string]func(*testing.T, string){ ...@@ -27,11 +28,57 @@ var All = map[string]func(*testing.T, string){
"FdLeak": FdLeak, "FdLeak": FdLeak,
"MkdirRmdir": MkdirRmdir, "MkdirRmdir": MkdirRmdir,
"NlinkZero": NlinkZero, "NlinkZero": NlinkZero,
"FstatDeleted": FstatDeleted,
"ParallelFileOpen": ParallelFileOpen, "ParallelFileOpen": ParallelFileOpen,
"Link": Link, "Link": Link,
"LinkUnlinkRename": LinkUnlinkRename,
"RenameOverwriteDestNoExist": RenameOverwriteDestNoExist, "RenameOverwriteDestNoExist": RenameOverwriteDestNoExist,
"RenameOverwriteDestExist": RenameOverwriteDestExist, "RenameOverwriteDestExist": RenameOverwriteDestExist,
"RenameOpenDir": RenameOpenDir,
"ReadDir": ReadDir, "ReadDir": ReadDir,
"ReadDirPicksUpCreate": ReadDirPicksUpCreate,
"DirectIO": DirectIO,
"OpenAt": OpenAt,
"Fallocate": Fallocate,
}
func DirectIO(t *testing.T, mnt string) {
fn := mnt + "/file.txt"
fd, err := syscall.Open(fn, syscall.O_TRUNC|syscall.O_CREAT|syscall.O_DIRECT|syscall.O_WRONLY, 0644)
if err == syscall.EINVAL {
t.Skip("FS does not support O_DIRECT")
}
if err != nil {
t.Fatalf("Open: %v", err)
}
defer func() {
if fd != 0 {
syscall.Close(fd)
}
}()
data := bytes.Repeat([]byte("bye"), 4096)
if n, err := syscall.Write(fd, data); err != nil || n != len(data) {
t.Fatalf("Write: %v (%d)", err, n)
}
err = syscall.Close(fd)
fd = 0
if err != nil {
t.Fatalf("Close: %v", err)
}
fd, err = syscall.Open(fn, syscall.O_DIRECT|syscall.O_RDONLY, 0644)
if err != nil {
t.Fatalf("Open 2: %v", err)
}
roundtrip := bytes.Repeat([]byte("xxx"), 4096)
if n, err := syscall.Read(fd, roundtrip); err != nil || n != len(data) {
t.Fatalf("ReadAt: %v (%d)", err, n)
}
if bytes.Compare(roundtrip, data) != 0 {
t.Errorf("roundtrip made changes: got %q.., want %q..", roundtrip[:10], data[:10])
}
} }
// SymlinkReadlink tests basic symlink functionality // SymlinkReadlink tests basic symlink functionality
...@@ -149,13 +196,9 @@ func FdLeak(t *testing.T, mnt string) { ...@@ -149,13 +196,9 @@ func FdLeak(t *testing.T, mnt string) {
} }
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
infos, err := ioutil.ReadDir("/proc/self/fd") infos := listFds(0, "")
if err != nil {
t.Errorf("ReadDir %v", err)
}
if len(infos) > 15 { if len(infos) > 15 {
t.Errorf("found %d open file descriptors for 100x ReadFile", len(infos)) t.Errorf("found %d open file descriptors for 100x ReadFile: %v", len(infos), infos)
} }
} }
} }
...@@ -214,30 +257,92 @@ func NlinkZero(t *testing.T, mnt string) { ...@@ -214,30 +257,92 @@ func NlinkZero(t *testing.T, mnt string) {
} }
// FstatDeleted is similar to NlinkZero, but Fstat()s multiple deleted files
// in random order and checks that the results match an earlier Stat().
//
// Excercises the fd-finding logic in rawBridge.GetAttr.
func FstatDeleted(t *testing.T, mnt string) {
const iMax = 9
type file struct {
fd int
st syscall.Stat_t
}
files := make(map[int]file)
for i := 0; i <= iMax; i++ {
// Create files with different sizes
path := fmt.Sprintf("%s/%d", mnt, i)
content := make([]byte, i)
err := ioutil.WriteFile(path, content, 0644)
if err != nil {
t.Fatalf("WriteFile: %v", err)
}
var st syscall.Stat_t
err = syscall.Stat(path, &st)
if err != nil {
t.Fatal(err)
}
// Open
fd, err := syscall.Open(path, syscall.O_RDONLY, 0)
if err != nil {
t.Fatal(err)
}
files[i] = file{fd, st}
defer syscall.Close(fd)
// Delete
err = syscall.Unlink(path)
if err != nil {
t.Fatal(err)
}
}
// Fstat in random order
for _, v := range files {
var st syscall.Stat_t
err := syscall.Fstat(v.fd, &st)
if err != nil {
t.Fatal(err)
}
// Ignore ctime, changes on unlink
v.st.Ctim = syscall.Timespec{}
st.Ctim = syscall.Timespec{}
// Nlink value should have dropped to zero
v.st.Nlink = 0
// Rest should stay the same
if v.st != st {
t.Errorf("stat mismatch: want=%v\n have=%v", v.st, st)
}
}
}
func ParallelFileOpen(t *testing.T, mnt string) { func ParallelFileOpen(t *testing.T, mnt string) {
fn := mnt + "/file" fn := mnt + "/file"
if err := ioutil.WriteFile(fn, []byte("content"), 0644); err != nil { if err := ioutil.WriteFile(fn, []byte("content"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err) t.Fatalf("WriteFile: %v", err)
} }
var wg sync.WaitGroup N := 10
errs := make(chan error, N)
one := func(b byte) { one := func(b byte) {
f, err := os.OpenFile(fn, os.O_RDWR, 0644) f, err := os.OpenFile(fn, os.O_RDWR, 0644)
if err != nil { if err != nil {
t.Fatalf("OpenFile: %v", err) errs <- err
return
} }
var buf [10]byte var buf [10]byte
f.Read(buf[:]) f.Read(buf[:])
buf[0] = b buf[0] = b
f.WriteAt(buf[0:1], 2) f.WriteAt(buf[0:1], 2)
f.Close() f.Close()
wg.Done() errs <- nil
} }
for i := 0; i < 10; i++ { for i := 0; i < N; i++ {
wg.Add(1)
go one(byte(i)) go one(byte(i))
} }
wg.Wait()
for i := 0; i < N; i++ {
if e := <-errs; e != nil {
t.Error(e)
}
}
} }
func Link(t *testing.T, mnt string) { func Link(t *testing.T, mnt string) {
...@@ -265,6 +370,10 @@ func Link(t *testing.T, mnt string) { ...@@ -265,6 +370,10 @@ func Link(t *testing.T, mnt string) {
if st.Ino != beforeIno { if st.Ino != beforeIno {
t.Errorf("Lstat after: got %d, want %d", st.Ino, beforeIno) t.Errorf("Lstat after: got %d, want %d", st.Ino, beforeIno)
} }
if st.Nlink != 2 {
t.Errorf("Expect 2 links, got %d", st.Nlink)
}
} }
func RenameOverwriteDestNoExist(t *testing.T, mnt string) { func RenameOverwriteDestNoExist(t *testing.T, mnt string) {
...@@ -311,29 +420,67 @@ func RenameOverwrite(t *testing.T, mnt string, destExists bool) { ...@@ -311,29 +420,67 @@ func RenameOverwrite(t *testing.T, mnt string, destExists bool) {
} }
} }
func ReadDir(t *testing.T, mnt string) { func RenameOpenDir(t *testing.T, mnt string) {
f, err := os.Open(mnt) if err := os.Mkdir(mnt+"/dir1", 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
// Different permissions so directories are easier to tell apart
if err := os.Mkdir(mnt+"/dir2", 0700); err != nil {
t.Fatalf("Mkdir: %v", err)
}
var st1 syscall.Stat_t
if err := syscall.Stat(mnt+"/dir2", &st1); err != nil {
t.Fatalf("Stat: %v", err)
}
fd, err := syscall.Open(mnt+"/dir2", syscall.O_RDONLY, 0)
if err != nil { if err != nil {
t.Fatalf("Open: %v", err) t.Fatalf("Open: %v", err)
} }
defer f.Close() defer syscall.Close(fd)
if err := syscall.Rename(mnt+"/dir1", mnt+"/dir2"); err != nil {
t.Fatalf("Rename: %v", err)
}
var st2 syscall.Stat_t
if err := syscall.Fstat(fd, &st2); err != nil {
t.Skipf("Fstat failed: %v. Known limitation - see https://github.com/hanwen/go-fuse/issues/55", err)
}
if st2.Mode&syscall.S_IFMT != syscall.S_IFDIR {
t.Errorf("got mode %o, want %o", st2.Mode, syscall.S_IFDIR)
}
if st2.Ino != st1.Ino {
t.Errorf("got ino %d, want %d", st2.Ino, st1.Ino)
}
if st2.Mode&0777 != st1.Mode&0777 {
t.Skipf("got permissions %#o, want %#o. Known limitation - see https://github.com/hanwen/go-fuse/issues/55",
st2.Mode&0777, st1.Mode&0777)
}
}
// add entries after opening the directory // ReadDir creates 110 files one by one, checking that we get the expected
// entries after each file creation.
func ReadDir(t *testing.T, mnt string) {
want := map[string]bool{} want := map[string]bool{}
for i := 0; i < 110; i++ {
// 40 bytes of filename, so 110 entries overflows a // 40 bytes of filename, so 110 entries overflows a
// 4096 page. // 4096 page.
for i := 0; i < 110; i++ {
nm := fmt.Sprintf("file%036x", i) nm := fmt.Sprintf("file%036x", i)
want[nm] = true want[nm] = true
if err := ioutil.WriteFile(filepath.Join(mnt, nm), []byte("hello"), 0644); err != nil { if err := ioutil.WriteFile(filepath.Join(mnt, nm), []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile %q: %v", nm, err) t.Fatalf("WriteFile %q: %v", nm, err)
} }
// Verify that we get the expected entries
f, err := os.Open(mnt)
if err != nil {
t.Fatalf("Open: %v", err)
} }
names, err := f.Readdirnames(-1) names, err := f.Readdirnames(-1)
if err != nil { if err != nil {
t.Fatalf("ReadDir: %v", err) t.Fatalf("ReadDir: %v", err)
} }
f.Close()
got := map[string]bool{} got := map[string]bool{}
for _, e := range names { for _, e := range names {
got[e] = true got[e] = true
...@@ -346,4 +493,135 @@ func ReadDir(t *testing.T, mnt string) { ...@@ -346,4 +493,135 @@ func ReadDir(t *testing.T, mnt string) {
t.Errorf("got unknown name %q", k) t.Errorf("got unknown name %q", k)
} }
} }
}
}
// Readdir should pick file created after open, but before readdir.
func ReadDirPicksUpCreate(t *testing.T, mnt string) {
f, err := os.Open(mnt)
if err != nil {
t.Fatalf("Open: %v", err)
}
if err := ioutil.WriteFile(mnt+"/file", []byte{42}, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
names, err := f.Readdirnames(-1)
if err != nil {
t.Fatalf("ReadDir: %v", err)
}
f.Close()
if len(names) != 1 || names[0] != "file" {
t.Errorf("missing file created after opendir")
}
}
// LinkUnlinkRename implements rename with a link/unlink sequence
func LinkUnlinkRename(t *testing.T, mnt string) {
content := []byte("hello")
tmp := mnt + "/tmpfile"
if err := ioutil.WriteFile(tmp, content, 0644); err != nil {
t.Fatalf("WriteFile %q: %v", tmp, err)
}
dest := mnt + "/file"
if err := syscall.Link(tmp, dest); err != nil {
t.Fatalf("Link %q %q: %v", tmp, dest, err)
}
if err := syscall.Unlink(tmp); err != nil {
t.Fatalf("Unlink %q: %v", tmp, err)
}
if back, err := ioutil.ReadFile(dest); err != nil {
t.Fatalf("Read %q: %v", dest, err)
} else if bytes.Compare(back, content) != 0 {
t.Fatalf("Read got %q want %q", back, content)
}
}
// test open with O_APPEND
func AppendWrite(t *testing.T, mnt string) {
fd, err := syscall.Open(mnt+"/file", syscall.O_WRONLY|syscall.O_APPEND|syscall.O_CREAT, 0644)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer func() {
if fd != 0 {
syscall.Close(fd)
}
}()
if _, err := syscall.Write(fd, []byte("hello")); err != nil {
t.Fatalf("Write 1: %v", err)
}
if _, err := syscall.Write(fd, []byte("world")); err != nil {
t.Fatalf("Write 2: %v", err)
}
if err := syscall.Close(fd); err != nil {
t.Fatalf("Open: %v", err)
}
fd = 0
want := []byte("helloworld")
got, err := ioutil.ReadFile(mnt + "/file")
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if bytes.Compare(got, want) != 0 {
t.Errorf("got %q want %q", got, want)
}
}
// OpenAt tests syscall.Openat().
//
// Hint:
// $ go test ./fs -run TestPosix/OpenAt -v
func OpenAt(t *testing.T, mnt string) {
dir1 := mnt + "/dir1"
err := os.Mkdir(dir1, 0777)
if err != nil {
t.Fatal(err)
}
dirfd, err := syscall.Open(dir1, syscall.O_RDONLY, 0)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(dirfd)
dir2 := mnt + "/dir2"
err = os.Rename(dir1, dir2)
if err != nil {
t.Fatal(err)
}
fd, err := syscall.Openat(dirfd, "file1", syscall.O_CREAT, 0700)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
_, err = os.Stat(dir2 + "/file1")
if err != nil {
t.Error(err)
}
}
func Fallocate(t *testing.T, mnt string) {
rwFile, err := os.OpenFile(mnt+"/file", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer rwFile.Close()
err = syscall.Fallocate(int(rwFile.Fd()), 0, 1024, 4096)
if err != nil {
t.Fatalf("Fallocate failed: %v", err)
}
fi, err := os.Lstat(mnt + "/file")
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if fi.Size() < (1024 + 4096) {
t.Fatalf("fallocate should have changed file size. Got %d bytes",
fi.Size())
}
} }
...@@ -15,9 +15,9 @@ import ( ...@@ -15,9 +15,9 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
) )
type knownFs struct { type knownFs struct {
......
...@@ -10,10 +10,10 @@ import ( ...@@ -10,10 +10,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
const entryTTL = 100 * time.Millisecond const entryTTL = 100 * time.Millisecond
......
...@@ -10,9 +10,9 @@ import ( ...@@ -10,9 +10,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
) )
const _XATTRSEP = "@XATTR@" const _XATTRSEP = "@XATTR@"
......
...@@ -9,9 +9,9 @@ import ( ...@@ -9,9 +9,9 @@ import (
"syscall" "syscall"
"testing" "testing"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func modeMapEq(m1, m2 map[string]uint32) bool { func modeMapEq(m1, m2 map[string]uint32) bool {
......
...@@ -7,7 +7,7 @@ package unionfs ...@@ -7,7 +7,7 @@ package unionfs
import ( import (
"os" "os"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
) )
func NewUnionFsFromRoots(roots []string, opts *UnionFsOptions, roCaching bool) (pathfs.FileSystem, error) { func NewUnionFsFromRoots(roots []string, opts *UnionFsOptions, roCaching bool) (pathfs.FileSystem, error) {
......
...@@ -8,8 +8,8 @@ import ( ...@@ -8,8 +8,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
) )
// newDirnameMap reads the contents of the given directory. On error, // newDirnameMap reads the contents of the given directory. On error,
......
...@@ -16,9 +16,9 @@ import ( ...@@ -16,9 +16,9 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
) )
func filePathHash(path string) string { func filePathHash(path string) string {
......
...@@ -18,11 +18,11 @@ import ( ...@@ -18,11 +18,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/hanwen/go-fuse/posixtest" "github.com/hanwen/go-fuse/v2/posixtest"
) )
func TestFilePathHash(t *testing.T) { func TestFilePathHash(t *testing.T) {
......
...@@ -10,10 +10,10 @@ import ( ...@@ -10,10 +10,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs" "github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs" "github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
type TestFS struct { type TestFS struct {
......
...@@ -19,8 +19,8 @@ import ( ...@@ -19,8 +19,8 @@ import (
"log" "log"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// MultiZipFs is a filesystem that mounts zipfiles. // MultiZipFs is a filesystem that mounts zipfiles.
......
...@@ -10,9 +10,9 @@ import ( ...@@ -10,9 +10,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
const testTtl = 100 * time.Millisecond const testTtl = 100 * time.Millisecond
......
...@@ -17,8 +17,8 @@ import ( ...@@ -17,8 +17,8 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
// TODO - handle symlinks. // TODO - handle symlinks.
......
...@@ -16,8 +16,8 @@ import ( ...@@ -16,8 +16,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
var tarContents = map[string]string{ var tarContents = map[string]string{
......
...@@ -15,8 +15,8 @@ import ( ...@@ -15,8 +15,8 @@ import (
"sync" "sync"
"syscall" "syscall"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
) )
type zipRoot struct { type zipRoot struct {
...@@ -84,7 +84,9 @@ func (zf *zipFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrO ...@@ -84,7 +84,9 @@ func (zf *zipFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrO
out.Atime = out.Mtime out.Atime = out.Mtime
out.Ctime = out.Mtime out.Ctime = out.Mtime
out.Size = zf.file.UncompressedSize64 out.Size = zf.file.UncompressedSize64
out.Blocks = (out.Size + 511) / 512 const bs = 512
out.Blksize = bs
out.Blocks = (out.Size + bs - 1) / bs
return 0 return 0
} }
......
...@@ -13,9 +13,9 @@ import ( ...@@ -13,9 +13,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/hanwen/go-fuse/fs" "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/internal/testutil" "github.com/hanwen/go-fuse/v2/internal/testutil"
) )
func testZipFile() string { func testZipFile() string {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment