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 != "" {
fmt.Printf("Writing cpu profile to %s\n", *cpuprofile) if !*quiet {
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,13 +68,17 @@ func main() { ...@@ -65,13 +68,17 @@ func main() {
defer pprof.StopCPUProfile() defer pprof.StopCPUProfile()
} }
if *memprofile != "" { if *memprofile != "" {
log.Printf("send SIGUSR1 to %d to dump memory profile", os.Getpid()) if !*quiet {
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 != "" {
fmt.Printf("Note: You must unmount gracefully, otherwise the profile file(s) will stay empty!\n") if !*quiet {
fmt.Printf("Note: You must unmount gracefully, otherwise the profile file(s) will stay empty!\n")
}
} }
orig := flag.Arg(1) orig := flag.Arg(1)
...@@ -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.
old := b.nodes[id.Ino] var t time.Duration
if old != nil { t0 := time.Now()
return old for i := 1; true; i++ {
} old := b.nodes[id.Ino]
if old == nil {
break
}
if old.stableAttr == id {
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,13 +515,11 @@ func (b *rawBridge) Rename(cancel <-chan struct{}, input *fuse.RenameIn, oldName ...@@ -455,13 +515,11 @@ 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,10 +91,16 @@ type Inode struct { ...@@ -90,10 +91,16 @@ 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 map[parentData]struct{}
// 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{}
} }
func (n *Inode) IsDir() bool { func (n *Inode) IsDir() bool {
...@@ -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{
defer tc.Clean() "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()
fn(t, tc.mntDir)
})
}
}
posixtest.TruncateNoFile(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
) )
...@@ -36,9 +36,10 @@ func (d DirEntry) String() string { ...@@ -36,9 +36,10 @@ func (d DirEntry) String() string {
// DirEntryList holds the return value for READDIR and READDIRPLUS // DirEntryList holds the return value for READDIR and READDIRPLUS
// 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() {
req.flatData = req.flatData[:n] // 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]
}
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)
} }
for _, k := range tests {
f := posixtest.All[k]
if f == nil {
t.Fatalf("test %s missing", k)
}
t.Run(k, func(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
if fi.Mode()&os.ModeSymlink == 0 { f(t, tc.mnt)
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 {
t.Errorf("destination %q does not exist: %v", subFile, err)
}
}
func TestRenameNonExistent(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
err := os.Rename(tc.mnt+"/doesnotexist", tc.mnt+"/doesnotmatter")
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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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