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
go_import_path: github.com/hanwen/go-fuse
go:
- 1.9.x
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
- master
matrix:
......@@ -25,6 +25,14 @@ install:
- go get -t ./...
- 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:
- go test -v ./...
- go test -race ./...
- set -e # fail fast
- 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
Older, deprecated APIs are available at
[github.com/hanwen/go-fuse/fuse/pathfs](https://godoc.org/github.com/hanwen/go-fuse/fuse/pathfs)
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
......
......@@ -17,9 +17,9 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
func setupFs(node fs.InodeEmbedder, N int) (string, func()) {
......
......@@ -10,8 +10,8 @@ import (
"strings"
"syscall"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
type StatFS struct {
......
......@@ -10,10 +10,10 @@ import (
"os"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/unionfs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/unionfs"
)
func main() {
......
......@@ -12,8 +12,8 @@ import (
"log"
"syscall"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
type HelloRoot struct {
......
......@@ -18,7 +18,7 @@ import (
"syscall"
"time"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/v2/fs"
)
func writeMemProfile(fn string, sigs <-chan os.Signal) {
......@@ -45,6 +45,7 @@ func main() {
// Scans the arg list and sets up flags
debug := flag.Bool("debug", false, "print debugging messages.")
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")
memprofile := flag.String("memprofile", "", "write memory profile to this file")
flag.Parse()
......@@ -55,7 +56,9 @@ func main() {
os.Exit(2)
}
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)
if err != nil {
fmt.Println(err)
......@@ -65,13 +68,17 @@ func main() {
defer pprof.StopCPUProfile()
}
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)
signal.Notify(profSig, syscall.SIGUSR1)
go writeMemProfile(*memprofile, profSig)
}
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)
......@@ -89,11 +96,26 @@ func main() {
}
opts.Debug = *debug
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)
if err != nil {
log.Fatalf("Mount fail: %v\n", err)
}
fmt.Println("Mounted!")
if !*quiet {
fmt.Println("Mounted!")
}
server.Wait()
}
......@@ -11,8 +11,8 @@ import (
"fmt"
"os"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
)
func main() {
......
......@@ -15,8 +15,8 @@ import (
"path/filepath"
"time"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/zipfs"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/zipfs"
)
func main() {
......
......@@ -20,9 +20,9 @@ import (
"syscall"
"time"
"github.com/hanwen/go-fuse/benchmark"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/benchmark"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
func main() {
......
......@@ -11,9 +11,9 @@ import (
"os"
"time"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/unionfs"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/unionfs"
)
func main() {
......
......@@ -18,8 +18,8 @@ import (
"strings"
"time"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/zipfs"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/zipfs"
)
func main() {
......
......@@ -168,10 +168,11 @@ package fs
import (
"context"
"log"
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// InodeEmbedder is an interface for structs that embed Inode.
......@@ -222,7 +223,8 @@ type NodeAccesser interface {
// FOPEN_DIRECTIO, Size should be set so it can be read correctly. If
// returning zeroed permissions, the default behavior is to change the
// 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 {
Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
}
......@@ -598,4 +600,14 @@ type Options struct {
// If nonzero, replace default (zero) GID with the given GID
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
import (
"context"
"log"
"math/rand"
"sync"
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal"
)
func errnoToStatus(errno syscall.Errno) fuse.Status {
......@@ -36,10 +37,21 @@ type fileEntry struct {
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 {
options Options
root *Inode
server *fuse.Server
server ServerCallbacks
// mu protects the following data. Locks for inodes must be
// taken before rawBridge.mu
......@@ -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.
//
// root
......@@ -87,14 +105,23 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persisten
// dir1.Lookup("file") and dir2.Lookup("file") are executed
// simultaneously. The matching StableAttrs ensure that we return the
// same node.
old := b.nodes[id.Ino]
if old != nil {
return old
}
var t time.Duration
t0 := time.Now()
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
if id.Mode == 0 {
id.Mode = fuse.S_IFREG
t = expSleep(t)
if i%5000 == 0 {
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()
......@@ -102,6 +129,27 @@ func (b *rawBridge) newInodeUnlocked(ops InodeEmbedder, id StableAttr, persisten
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 {
ch := b.newInodeUnlocked(ops, id, persistent)
if ch != ops.embed() {
......@@ -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.
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)
parent.setEntry(name, child)
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.changeCounter++
var fh uint32
if file != nil {
......@@ -159,6 +216,7 @@ func (b *rawBridge) setAttr(out *fuse.Attr) {
if b.options.GID != 0 && out.Gid == 0 {
out.Gid = b.options.GID
}
setBlocks(out)
}
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 {
bridge := &rawBridge{
automaticIno: opts.FirstAutomaticIno,
server: opts.ServerCallbacks,
}
if bridge.automaticIno == 1 {
bridge.automaticIno++
......@@ -244,7 +303,6 @@ func (b *rawBridge) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name s
child.setEntryOut(out)
b.addNewChild(parent, name, child, nil, 0, out)
b.setEntryOutTimeout(out)
return fuse.OK
}
......@@ -417,6 +475,9 @@ func (b *rawBridge) getattr(ctx context.Context, n *Inode, f FileHandle, out *fu
}
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.Mode = (out.Attr.Mode & 07777) | n.stableAttr.Mode
b.setAttr(&out.Attr)
......@@ -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 {
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
if in.Valid&fuse.FATTR_FH == 0 {
f = nil
}
var errno = syscall.ENOTSUP
if fops, ok := n.ops.(NodeSetattrer); ok {
......@@ -455,13 +515,11 @@ func (b *rawBridge) Rename(cancel <-chan struct{}, input *fuse.RenameIn, oldName
if input.Flags&RENAME_EXCHANGE != 0 {
p1.ExchangeChild(oldName, p2, newName)
} else {
if ok := p1.MvChild(oldName, p2, newName, true); !ok {
log.Println("MvChild failed")
}
// MvChild cannot fail with overwrite=true.
_ = p1.MvChild(oldName, p2, newName, true)
}
return errnoToStatus(errno)
}
return errnoToStatus(errno)
}
return fuse.ENOTSUP
}
......@@ -762,7 +820,7 @@ func (b *rawBridge) Fallocate(cancel <-chan struct{}, input *fuse.FallocateIn) f
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))
}
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 fuse.ENOTSUP
......@@ -858,7 +916,7 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
}
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
for f.dirStream.HasNext() {
for f.dirStream.HasNext() || f.hasOverflow {
var e fuse.DirEntry
var errno syscall.Errno
......@@ -880,6 +938,15 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
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)
if errno != 0 {
if b.options.NegativeTimeout != nil {
......@@ -889,10 +956,9 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
b.addNewChild(n, e.Name, child, nil, 0, entryOut)
child.setEntryOut(entryOut)
b.setEntryOutTimeout(entryOut)
if (e.Mode &^ 07777) != (child.stableAttr.Mode &^ 07777) {
// should go back and change the
// already serialized entry
log.Panicf("mode mismatch between readdir %o and lookup %o", e.Mode, child.stableAttr.Mode)
if e.Mode&syscall.S_IFMT != child.stableAttr.Mode&syscall.S_IFMT {
// The file type has changed behind our back. Use the new value.
out.FixMode(child.stableAttr.Mode)
}
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 (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type keepCacheFile struct {
......
......@@ -7,7 +7,7 @@ package fs
import (
"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.
......
......@@ -11,8 +11,8 @@ import (
"syscall"
"time"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
// bytesFileHandle is a file handle that carries separate content for
......
......@@ -12,7 +12,7 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type dioRoot struct {
......@@ -52,7 +52,8 @@ func (f *dioFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFl
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{}
mntDir, server, clean := testMount(t, root, nil)
defer clean()
......
......@@ -7,7 +7,7 @@ package fs
import (
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type dirArray struct {
......
......@@ -9,7 +9,7 @@ import (
"os"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
func NewLoopbackDirStream(nm string) (DirStream, syscall.Errno) {
......
......@@ -9,7 +9,7 @@ import (
"syscall"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type loopbackDirStream struct {
......
......@@ -11,8 +11,8 @@ import (
"strconv"
"syscall"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
// numberNode is a filesystem node representing an integer. Prime
......
......@@ -10,8 +10,8 @@ import (
"log"
"os"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
// ExampleMount shows how to create a loopback file system, and
......
......@@ -12,7 +12,7 @@ import (
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
"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 (
"syscall"
"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 {
......@@ -30,3 +30,13 @@ func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
err := futimens(int(f.fd), &ts)
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 (
"strings"
"syscall"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
// files contains the files we will expose as a file system
......
......@@ -8,13 +8,14 @@ import (
"context"
"fmt"
"log"
"math/rand"
"sort"
"strings"
"sync"
"syscall"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type parentData struct {
......@@ -64,9 +65,8 @@ type Inode struct {
// Following data is mutable.
// protected by bridge.mu
// file handles.
// protected by bridge.mu
openFiles []uint32
// mu protects the following mutable fields. When locking
......@@ -78,6 +78,7 @@ type Inode struct {
// from the tree, even if there are no live references. This
// must be set on creation, and can only be changed to false
// by calling removeRef.
// When you change this, you MUST increment changeCounter.
persistent bool
// changeCounter increments every time the mutable state
......@@ -90,10 +91,16 @@ type Inode struct {
changeCounter uint32
// Number of kernel refs to this node.
// When you change this, you MUST increment changeCounter.
lookupCount uint64
// Children of this Inode.
// When you change this, you MUST increment changeCounter.
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 {
......@@ -261,7 +268,11 @@ func (n *Inode) Operations() InodeEmbedder {
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 {
var segments []string
p := n
......@@ -270,11 +281,18 @@ func (n *Inode) Path(root *Inode) string {
// We don't try to take all locks at the same time, because
// the caller won't use the "path" string under lock anyway.
found := false
p.mu.Lock()
// Select an arbitrary parent
for pd = range p.parents {
found = true
break
}
p.mu.Unlock()
if found == false {
p = nil
break
}
if pd.parent == nil {
break
}
......@@ -283,9 +301,12 @@ func (n *Inode) Path(root *Inode) string {
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?
segments = append(segments, ".deleted")
segments = append(segments, deletedPlaceholder)
}
i := 0
......@@ -545,6 +566,8 @@ retry:
for _, nm := range names {
ch := n.children[nm]
delete(n.children, nm)
delete(ch.parents, parentData{nm, n})
ch.changeCounter++
}
n.changeCounter++
......@@ -565,7 +588,8 @@ retry:
}
// 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 {
if len(newName) == 0 {
log.Panicf("empty newName for MvChild")
......
......@@ -11,7 +11,7 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type interruptRoot struct {
......
......@@ -10,7 +10,7 @@ import (
"path/filepath"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type loopbackRoot struct {
......@@ -55,10 +55,9 @@ func (n *loopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.
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{}
err := syscall.Stat(n.rootPath, &st)
err := syscall.Stat(r.rootPath, &st)
if err != nil {
return ToErrno(err)
}
......@@ -71,7 +70,7 @@ func (n *loopbackNode) root() *loopbackRoot {
}
func (n *loopbackNode) path() string {
path := n.Path(nil)
path := n.Path(n.Root())
return filepath.Join(n.root().rootPath, path)
}
......@@ -90,12 +89,26 @@ func (n *loopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryO
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) {
p := filepath.Join(n.path(), name)
err := syscall.Mknod(p, mode, int(rdev))
if err != nil {
return nil, ToErrno(err)
}
n.preserveOwner(ctx, p)
st := syscall.Stat_t{}
if err := syscall.Lstat(p, &st); err != nil {
syscall.Rmdir(p)
......@@ -116,6 +129,7 @@ func (n *loopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out
if err != nil {
return nil, ToErrno(err)
}
n.preserveOwner(ctx, p)
st := syscall.Stat_t{}
if err := syscall.Lstat(p, &st); err != nil {
syscall.Rmdir(p)
......@@ -156,9 +170,9 @@ func (n *loopbackNode) Rename(ctx context.Context, name string, newParent InodeE
}
p1 := filepath.Join(n.path(), name)
p2 := filepath.Join(newParentLoopback.path(), newName)
err := os.Rename(p1, p2)
err := syscall.Rename(p1, p2)
return ToErrno(err)
}
......@@ -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) {
p := filepath.Join(n.path(), name)
flags = flags &^ syscall.O_APPEND
fd, err := syscall.Open(p, int(flags)|os.O_CREATE, mode)
if err != nil {
return nil, nil, 0, ToErrno(err)
}
n.preserveOwner(ctx, p)
st := syscall.Stat_t{}
if err := syscall.Fstat(fd, &st); err != nil {
syscall.Close(fd)
......@@ -211,8 +225,9 @@ func (n *loopbackNode) Symlink(ctx context.Context, target, name string, out *fu
if err != nil {
return nil, ToErrno(err)
}
n.preserveOwner(ctx, p)
st := syscall.Stat_t{}
if syscall.Lstat(p, &st); err != nil {
if err := syscall.Lstat(p, &st); err != nil {
syscall.Unlink(p)
return nil, ToErrno(err)
}
......@@ -232,7 +247,7 @@ func (n *loopbackNode) Link(ctx context.Context, target InodeEmbedder, name stri
return nil, ToErrno(err)
}
st := syscall.Stat_t{}
if syscall.Lstat(p, &st); err != nil {
if err := syscall.Lstat(p, &st); err != nil {
syscall.Unlink(p)
return nil, ToErrno(err)
}
......@@ -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) {
flags = flags &^ syscall.O_APPEND
p := n.path()
f, err := syscall.Open(p, int(flags), 0)
if err != nil {
......@@ -288,7 +304,7 @@ func (n *loopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.Attr
}
p := n.path()
var err error = nil
var err error
st := syscall.Stat_t{}
err = syscall.Lstat(p, &st)
if err != nil {
......@@ -371,7 +387,7 @@ func (n *loopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt
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
// operations available.
func NewLoopbackRoot(root string) (InodeEmbedder, error) {
......
......@@ -12,8 +12,8 @@ import (
"time"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/utimens"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/utimens"
)
func (n *loopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
......
......@@ -14,22 +14,22 @@ import (
)
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)
}
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)
}
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)
}
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)
}
......
......@@ -14,8 +14,8 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/kylelemons/godebug/pretty"
"golang.org/x/sys/unix"
)
......@@ -124,7 +124,30 @@ func TestXAttr(t *testing.T) {
if err := syscall.Setxattr(tc.mntDir+"/file", attr, value, 0); err != nil {
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 {
t.Fatalf("Getxattr: %v", err)
}
......@@ -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) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
......
......@@ -6,44 +6,75 @@ package fs
import (
"context"
"sync"
"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
// slice in memory.
type MemRegularFile struct {
Inode
mu sync.Mutex
Data []byte
Attr fuse.Attr
}
var _ = (NodeOpener)((*MemRegularFile)(nil))
var _ = (NodeReader)((*MemRegularFile)(nil))
var _ = (NodeWriter)((*MemRegularFile)(nil))
var _ = (NodeSetattrer)((*MemRegularFile)(nil))
var _ = (NodeFlusher)((*MemRegularFile)(nil))
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, 0, syscall.EPERM
return nil, fuse.FOPEN_KEEP_CACHE, OK
}
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))
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.Size = uint64(len(f.Data))
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 {
return 0
}
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)
if end > len(f.Data) {
end = len(f.Data)
......
......@@ -13,8 +13,8 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
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
t.Fatal(err)
}
return mntDir, server, func() {
server.Unmount()
os.Remove(mntDir)
if err := server.Unmount(); err != nil {
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) {
} else if st.Uid != 42 || st.Gid != 43 {
t.Fatalf("Got Lstat %d, %d want 42,43", st.Uid, st.Gid)
}
}
func TestDataFile(t *testing.T) {
......@@ -97,6 +100,10 @@ func TestDataFile(t *testing.T) {
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)
if err != nil {
t.Fatalf("Open: %v", err)
......@@ -116,6 +123,17 @@ func TestDataFile(t *testing.T) {
if 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) {
......
......@@ -7,7 +7,7 @@ package fs
import (
"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
......
// 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 (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
func TestReadonlyCreate(t *testing.T) {
......
......@@ -12,8 +12,8 @@ import (
"syscall"
"time"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
// bytesNode is a file that can be read and written
......
......@@ -15,9 +15,9 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/posixtest"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/hanwen/go-fuse/v2/posixtest"
)
type testCase struct {
......@@ -32,6 +32,7 @@ type testCase struct {
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) {
if err := ioutil.WriteFile(filepath.Join(tc.origDir, path), []byte(content), mode); err != nil {
tc.Fatal(err)
......@@ -51,14 +52,20 @@ type testOptions struct {
entryCache bool
attrCache 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 {
if opts == nil {
opts = &testOptions{}
}
if opts.testDir == "" {
opts.testDir = testutil.TempDir()
}
tc := &testCase{
dir: testutil.TempDir(),
dir: opts.testDir,
T: t,
}
tc.origDir = tc.dir + "/orig"
......@@ -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) {
tc := newTestCase(t, &testOptions{
suppressDebug: true,
......@@ -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) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
defer tc.Clean()
......@@ -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) {
tc := newTestCase(t, &testOptions{suppressDebug: true, attrCache: true, entryCache: true})
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
stress := func(gr int) {
......@@ -407,11 +345,47 @@ func TestMknod(t *testing.T) {
}
}
func TestTruncate(t *testing.T) {
tc := newTestCase(t, &testOptions{})
defer tc.Clean()
func TestPosix(t *testing.T) {
noisy := map[string]bool{
"ParallelFileOpen": true,
"ReadDir": true,
}
for nm, fn := range posixtest.All {
t.Run(nm, func(t *testing.T) {
tc := newTestCase(t, &testOptions{
suppressDebug: noisy[nm],
attrCache: true, entryCache: true})
defer tc.Clean()
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() {
......
......@@ -14,7 +14,7 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/v2/fs"
)
var testData = map[string]string{
......
......@@ -17,8 +17,8 @@ import (
"sync"
"syscall"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
)
// zipFile is a file read from a zip archive.
......
......@@ -158,6 +158,15 @@ type MountOptions struct {
// If set, ask kernel not to do automatic data cache invalidation.
// The filesystem is fully responsible for invalidating data cache.
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.
......
......@@ -32,4 +32,6 @@ const (
CUSE_INIT = 4096
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 {
// DirEntryList holds the return value for READDIR and READDIRPLUS
// opcodes.
type DirEntryList struct {
buf []byte
size int
offset uint64
buf []byte
size int // capacity of the underlying buffer
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
......@@ -77,7 +78,7 @@ func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) b
dirent.Off = l.offset + 1
dirent.Ino = inode
dirent.NameLen = uint32(len(name))
dirent.Typ = (mode & 0170000) >> 12
dirent.Typ = modeToType(mode)
oldLen += direntSize
copy(l.buf[oldLen:], name)
oldLen += len(name)
......@@ -90,19 +91,42 @@ func (l *DirEntryList) Add(prefix int, name string, inode uint64, mode uint32) b
return true
}
// AddDirLookupEntry is used for ReadDirPlus. It serializes a DirEntry
// and returns the space for entry. If no space is left, returns a nil
// pointer.
// AddDirLookupEntry is used for ReadDirPlus. If reserves and zeroizes space
// for an EntryOut struct and serializes a DirEntry.
// 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 {
lastStart := len(l.buf)
ok := l.Add(int(unsafe.Sizeof(EntryOut{})), e.Name,
e.Ino, e.Mode)
const entryOutSize = int(unsafe.Sizeof(EntryOut{}))
oldLen := len(l.buf)
ok := l.Add(entryOutSize, e.Name, e.Ino, e.Mode)
if !ok {
return nil
}
result := (*EntryOut)(unsafe.Pointer(&l.buf[lastStart]))
*result = EntryOut{}
return result
l.lastDirent = (*_Dirent)(unsafe.Pointer(&l.buf[oldLen+entryOutSize]))
entryOut := (*EntryOut)(unsafe.Pointer(&l.buf[oldLen]))
*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 {
......
......@@ -92,6 +92,6 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
return syscall.Dup(int(f.Fd()))
}
func unmount(dir string) error {
func unmount(dir string, opts *MountOptions) error {
return syscall.Unmount(dir, 0)
}
......@@ -7,6 +7,7 @@ package fuse
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path"
......@@ -26,9 +27,59 @@ func unixgramSocketpair() (l, r *os.File, err error) {
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
// mount point is always absolute.
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()
if err != nil {
return
......@@ -79,7 +130,15 @@ func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, e
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()
if err != nil {
return err
......
......@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
// 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
// kernel's idea of what an FS looks like. File systems can have
......@@ -15,7 +15,7 @@ package nodefs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// The Node interface implements the user-defined file system
......
......@@ -7,7 +7,7 @@ package nodefs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type defaultFile struct{}
......
......@@ -7,7 +7,7 @@ package nodefs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// NewDefaultNode returns an implementation of Node that returns
......
......@@ -8,7 +8,7 @@ import (
"log"
"sync"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type connectorDir struct {
......
......@@ -8,8 +8,8 @@ import (
"io/ioutil"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type nodeReadNode struct {
......
......@@ -10,7 +10,7 @@ import (
"sync"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// DataFile is for implementing read-only filesystems. This
......
......@@ -9,8 +9,8 @@ import (
"time"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/utimens"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/utimens"
)
func (f *loopbackFile) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
......
......@@ -8,7 +8,7 @@ import (
"syscall"
"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 {
......
......@@ -10,8 +10,8 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
// Check that loopbackFile.Utimens() works as expected
......
......@@ -16,7 +16,7 @@ import (
"time"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// Tests should set to true.
......
......@@ -9,7 +9,7 @@ import (
"sync"
"unsafe"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// openedFile stores either an open dir or an open file.
......
......@@ -13,7 +13,7 @@ import (
"strings"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// Returns the RawFileSystem so it can be mounted.
......
......@@ -5,7 +5,7 @@
package nodefs
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.
......
......@@ -9,7 +9,7 @@ import (
"log"
"sync"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type parentData struct {
......
......@@ -9,7 +9,7 @@ import (
"sync"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
type lockingFile struct {
......
......@@ -11,7 +11,7 @@ import (
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// NewMemNodeFSRoot creates an in-memory node-based filesystem. Files
......
......@@ -10,8 +10,8 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
const testTtl = 100 * time.Millisecond
......
......@@ -100,7 +100,6 @@ func doInit(server *Server, req *request) {
server.kernelSettings.Flags |= CAP_FLOCK_LOCKS | CAP_POSIX_LOCKS
}
dataCacheMode := input.Flags & CAP_AUTO_INVAL_DATA
if server.opts.ExplicitDataCacheControl {
// 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) {
req.status = OK
out.Size = n
} 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
} else {
req.flatData = req.flatData[:0]
......@@ -525,6 +528,8 @@ func getHandler(o uint32) *operationHandler {
return operationHandlers[o]
}
var maxInputSize uintptr
func init() {
operationHandlers = make([]*operationHandler, _OPCODE_COUNT)
for i := range operationHandlers {
......@@ -536,6 +541,7 @@ func init() {
operationHandlers[op].FileNameOut = true
}
maxInputSize = 0
for op, sz := range map[uint32]uintptr{
_OP_FORGET: unsafe.Sizeof(ForgetIn{}),
_OP_BATCH_FORGET: unsafe.Sizeof(_BatchForgetIn{}),
......@@ -576,6 +582,9 @@ func init() {
_OP_COPY_FILE_RANGE: unsafe.Sizeof(CopyFileRangeIn{}),
} {
operationHandlers[op].InputSize = sz
if sz > maxInputSize {
maxInputSize = sz
}
}
for op, sz := range map[uint32]uintptr{
......
......@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
// 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
......@@ -11,8 +11,8 @@ package pathfs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
)
// A filesystem API that uses paths rather than inodes. A minimal
......
......@@ -7,7 +7,7 @@ package pathfs
import (
"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 {
......
......@@ -9,7 +9,7 @@ import (
"os"
"testing"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
func TestCopyFile(t *testing.T) {
......
......@@ -7,8 +7,8 @@ package pathfs
import (
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
)
// NewDefaultFileSystem creates a filesystem that responds ENOSYS for
......
......@@ -8,8 +8,8 @@ import (
"sync"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
)
type lockingFileSystem struct {
......
......@@ -11,9 +11,9 @@ import (
"path/filepath"
"syscall"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal"
)
type loopbackFileSystem struct {
......@@ -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) {
// 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)
if err != nil {
return nil, fuse.ToStatus(err)
......@@ -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) {
flags = flags &^ syscall.O_APPEND
f, err := os.OpenFile(fs.GetPath(path), int(flags)|os.O_CREATE, os.FileMode(mode))
return nodefs.NewLoopbackFile(f), fuse.ToStatus(err)
}
......@@ -8,8 +8,8 @@ import (
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/utimens"
"github.com/hanwen/go-fuse/v2/fuse"
"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 {
......
......@@ -9,7 +9,7 @@ import (
"syscall"
"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) {
......
......@@ -12,8 +12,8 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
// Check that loopbackFileSystem.Utimens() works as expected
......
......@@ -9,9 +9,9 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type ownerFs struct {
......
......@@ -12,8 +12,8 @@ import (
"sync"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
)
// refCountedInode is used in clientInodeMap. The reference count is used to decide
......
......@@ -9,8 +9,8 @@ import (
"path/filepath"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
)
// PrefixFileSystem adds a path prefix to incoming calls.
......
......@@ -8,8 +8,8 @@ import (
"fmt"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
)
// NewReadonlyFileSystem returns a wrapper that only exposes read-only
......
......@@ -13,9 +13,9 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
var xattrGolden = map[string][]byte{
......
......@@ -9,16 +9,17 @@ const pollHackName = ".go-fuse-epoll-hack"
const pollHackInode = ^uint64(0)
func doPollHackLookup(ms *Server, req *request) {
attr := Attr{
Ino: pollHackInode,
Mode: S_IFREG | 0644,
Nlink: 1,
}
switch req.inHeader.Opcode {
case _OP_CREATE:
out := (*CreateOut)(req.outData())
out.EntryOut = EntryOut{
NodeId: pollHackInode,
Attr: Attr{
Ino: pollHackInode,
Mode: S_IFREG | 0644,
Nlink: 1,
},
Attr: attr,
}
out.OpenOut = OpenOut{
Fh: pollHackInode,
......@@ -28,7 +29,15 @@ func doPollHackLookup(ms *Server, req *request) {
out := (*EntryOut)(req.outData())
*out = EntryOut{}
req.status = ENOENT
case _OP_GETATTR:
out := (*AttrOut)(req.outData())
out.Attr = attr
req.status = OK
case _OP_POLL:
req.status = ENOSYS
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 (
"sync"
"syscall"
"time"
"unsafe"
)
const (
......@@ -107,7 +108,7 @@ func (ms *Server) Unmount() (err error) {
}
delay := time.Duration(0)
for try := 0; try < 5; try++ {
err = unmount(ms.mountPoint)
err = unmount(ms.mountPoint, ms.opts)
if err == nil {
break
}
......@@ -175,8 +176,11 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
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)
if !filepath.IsAbs(mountPoint) {
cwd, err := os.Getwd()
......@@ -198,6 +202,10 @@ func NewServer(fs RawFileSystem, mountPoint string, opts *MountOptions) (*Server
// TODO - unmount as well?
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
}
......@@ -256,13 +264,14 @@ func handleEINTR(fn func() error) (err error) {
// Returns a new request, or error. In case exitIdle is given, returns
// nil, OK if we have too many readers already.
func (ms *Server) readRequest(exitIdle bool) (req *request, code Status) {
req = ms.reqPool.Get().(*request)
dest := ms.readPool.Get().([]byte)
ms.reqMu.Lock()
if ms.reqReaders > _MAX_READERS {
ms.reqMu.Unlock()
return nil, OK
}
req = ms.reqPool.Get().(*request)
dest := ms.readPool.Get().([]byte)
ms.reqReaders++
ms.reqMu.Unlock()
......@@ -356,7 +365,6 @@ func (ms *Server) recordStats(req *request) {
//
// Each filesystem operation executes in a separate goroutine.
func (ms *Server) Serve() {
ms.loops.Add(1)
ms.loop(false)
ms.loops.Wait()
......@@ -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() {
ms.loops.Wait()
}
......@@ -454,14 +463,8 @@ func (ms *Server) handleRequest(req *request) Status {
log.Println(req.InputDebug())
}
if req.inHeader.NodeId == pollHackInode {
// We want to avoid switching off features through our
// 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 {
if req.inHeader.NodeId == pollHackInode ||
req.inHeader.NodeId == FUSE_ROOT_ID && len(req.filenames) > 0 && req.filenames[0] == pollHackName {
doPollHackLookup(ms, req)
} else if req.status.Ok() && req.handler.Func == nil {
log.Printf("Unimplemented opcode %v", operationName(req.inHeader.Opcode))
......@@ -479,6 +482,15 @@ func (ms *Server) handleRequest(req *request) Status {
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 {
if cap(req.bufferPoolOutputBuf) >= int(size) {
req.bufferPoolOutputBuf = req.bufferPoolOutputBuf[:size]
......@@ -486,7 +498,10 @@ func (ms *Server) allocOut(req *request, size uint32) []byte {
}
if req.bufferPoolOutputBuf != nil {
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)
return req.bufferPoolOutputBuf
}
......
......@@ -8,7 +8,7 @@ import (
"fmt"
"os"
"github.com/hanwen/go-fuse/splice"
"github.com/hanwen/go-fuse/v2/splice"
)
func (s *Server) setSplice() {
......
......@@ -14,10 +14,10 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type cacheFs struct {
......@@ -54,7 +54,7 @@ func setupCacheTest(t *testing.T) (string, *pathfs.PathNodeFs, func()) {
}
opts := nodefs.NewOptions()
opts.AttrTimeout = 10*time.Millisecond
opts.AttrTimeout = 10 * time.Millisecond
opts.Debug = testutil.VerboseTest()
state, _, err := nodefs.Mount(dir+"/mnt", pfs.Root(), mntOpts, opts)
if err != nil {
......@@ -121,7 +121,7 @@ func TestFopenKeepCache(t *testing.T) {
}
// 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)
mtimeAfter := xstat(wd + "/orig/file.txt").ModTime()
......@@ -134,7 +134,7 @@ func TestFopenKeepCache(t *testing.T) {
//
// this way we make sure the kernel knows updated size/mtime before we
// try to read the file next time.
time.Sleep(100*time.Millisecond)
time.Sleep(100 * time.Millisecond)
_ = xstat(wd + "/mnt/file.txt")
c = xreadFile(wd + "/mnt/file.txt")
......
......@@ -15,9 +15,9 @@ import (
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
// DataNode is a nodefs.Node that Reads static data.
......
......@@ -10,9 +10,9 @@ import (
"path"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
func TestDefaultNodeGetAttr(t *testing.T) {
......
......@@ -9,10 +9,10 @@ import (
"os"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type DefaultReadFS struct {
......
......@@ -13,9 +13,9 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type flipNode struct {
......
......@@ -15,9 +15,9 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
func TestFlockExclusive(t *testing.T) {
......
......@@ -10,10 +10,10 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type MutableDataFile struct {
......
......@@ -13,7 +13,7 @@ import (
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
func TestTouch(t *testing.T) {
......
......@@ -18,10 +18,11 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
"github.com/hanwen/go-fuse/v2/posixtest"
)
type testCase struct {
......@@ -236,26 +237,6 @@ func TestWriteThrough(t *testing.T) {
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) {
tc := NewTestCase(t)
defer tc.Cleanup()
......@@ -399,71 +380,27 @@ func TestLinkForget(t *testing.T) {
}
}
func TestSymlink(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
contents := []byte{1, 2, 3}
tc.WriteFile(tc.origFile, []byte(contents), 0700)
linkFile := "symlink-file"
orig := "hello.txt"
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)
func TestPosix(t *testing.T) {
tests := []string{
"SymlinkReadlink",
"MkdirRmdir",
"RenameOverwriteDestNoExist",
"RenameOverwriteDestExist",
"ReadDir",
"ReadDirPicksUpCreate",
"AppendWrite",
}
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 {
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)
f(t, tc.mnt)
})
}
}
......@@ -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) {
if os.Geteuid() == 0 {
t.Log("Skipping TestAccess() as root.")
......@@ -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.
// This checks that the fix for https://github.com/hanwen/go-fuse/issues/252
// does not break this case.
......@@ -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) {
r0, _, e1 := syscall.Syscall(
syscall.SYS_IOCTL, uintptr(fd), uintptr(cmd), uintptr(arg))
......@@ -1001,5 +865,4 @@ func TestLookupKnownChildrenAttrCopied(t *testing.T) {
} else if fi.Mode() != mode {
t.Fatalf("got mode %o, want %o", fi.Mode(), mode)
}
}
......@@ -12,10 +12,10 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
func TestMountOnExisting(t *testing.T) {
......
......@@ -8,9 +8,9 @@ import (
"os"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type truncatableFile struct {
......
......@@ -9,9 +9,9 @@ import (
"sync"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type rootNode struct {
......
......@@ -10,9 +10,9 @@ import (
"sync/atomic"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
// exercise functionality when open returns 0 file handle.
......
......@@ -9,10 +9,10 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type NotifyFs struct {
......
......@@ -10,10 +10,10 @@ import (
"os/exec"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
type umaskFS struct {
......
......@@ -12,9 +12,9 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/nodefs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
// this file is linux-only, since it uses syscall.Getxattr.
......
......@@ -159,6 +159,7 @@ type SetAttrInCommon struct {
Unused5 uint32
}
// GetFh returns the file handle if available, or 0 if undefined.
func (s *SetAttrInCommon) GetFh() (uint64, bool) {
if s.Valid&FATTR_FH != 0 {
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