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
......
......@@ -16,8 +16,10 @@ const (
)
type Attr struct {
Ino uint64
Size uint64
Ino uint64
Size uint64
// Blocks is the number of 512-byte blocks that the file occupies on disk.
Blocks uint64
Atime uint64
Mtime uint64
......@@ -28,7 +30,9 @@ type Attr struct {
Mode uint32
Nlink uint32
Owner
Rdev uint32
Rdev uint32
// Blksize is the preferred size for file system operations.
Blksize uint32
Padding uint32
}
......
module github.com/hanwen/go-fuse
module github.com/hanwen/go-fuse/v2
require (
github.com/hanwen/go-fuse v1.0.0
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522
)
github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE=
......
......@@ -53,18 +53,21 @@ func TestHasAccess(t *testing.T) {
_ = myOtherGid
_ = notMyGid
for i, tc := range []testcase{
cases := []testcase{
{myUid, myGid, myUid, myGid, 0100, 01, true},
{myUid, myGid, myUid + 1, notMyGid, 0001, 0001, true},
{myUid, myGid, myUid + 1, notMyGid, 0000, 0001, false},
{myUid, myGid, myUid + 1, notMyGid, 0007, 0000, true},
{myUid, myGid, myUid + 1, myOtherGid, 0020, 002, true},
{myUid, myGid, myUid + 1, notMyGid, 0020, 002, false},
{myUid, myGid, myUid, myGid, 0000, 01, false},
{myUid, myGid, myUid, myGid, 0200, 01, false},
{0, myGid, myUid + 1, notMyGid, 0700, 01, true},
} {
}
if myOtherGid != 0 {
cases = append(cases, testcase{myUid, myGid, myUid + 1, myOtherGid, 0020, 002, true})
}
for i, tc := range cases {
got := HasAccess(tc.uid, tc.gid, tc.fuid, tc.fgid, tc.perm, tc.mask)
if got != tc.want {
t.Errorf("%d: accessCheck(%v): got %v, want %v", i, tc, got, tc.want)
......
......@@ -9,7 +9,7 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// Check that loopback Utimens() works as expected.
......
......@@ -8,7 +8,7 @@ import (
"syscall"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// timeToTimeval converts time.Time to syscall.Timeval
......
......@@ -13,8 +13,8 @@ import (
"path/filepath"
"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"
)
func filePathHash(path string) string {
......
......@@ -13,10 +13,10 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fs"
"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/fs"
"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 {
......
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package posixtest
import (
"fmt"
"log"
"os"
"runtime"
"strings"
)
// listFds lists the open file descriptors for process "pid". Pass pid=0 for
// ourselves. Pass a prefix to ignore all paths that do not start with "prefix".
//
// Copied from https://github.com/rfjakob/gocryptfs/blob/master/tests/test_helpers/mount_unmount.go#L191
func listFds(pid int, prefix string) []string {
// We need /proc to get the list of fds for other processes. Only exists
// on Linux.
if runtime.GOOS != "linux" && pid > 0 {
return nil
}
// Both Linux and MacOS have /dev/fd
dir := "/dev/fd"
if pid > 0 {
dir = fmt.Sprintf("/proc/%d/fd", pid)
}
f, err := os.Open(dir)
if err != nil {
fmt.Printf("ListFds: %v\n", err)
return nil
}
defer f.Close()
// Note: Readdirnames filters "." and ".."
names, err := f.Readdirnames(0)
if err != nil {
log.Panic(err)
}
var out []string
var filtered []string
for _, n := range names {
fdPath := dir + "/" + n
fi, err := os.Lstat(fdPath)
if err != nil {
// fd was closed in the meantime
continue
}
if fi.Mode()&0400 > 0 {
n += "r"
}
if fi.Mode()&0200 > 0 {
n += "w"
}
target, err := os.Readlink(fdPath)
if err != nil {
// fd was closed in the meantime
continue
}
if strings.HasPrefix(target, "pipe:") || strings.HasPrefix(target, "anon_inode:[eventpoll]") {
// go-fuse creates pipes on demand for splice(), which
// creates spurious test failures. Ignore all pipes.
// Also get rid of the "eventpoll" fd that is always there and not
// interesting.
filtered = append(filtered, target)
continue
}
if prefix != "" && !strings.HasPrefix(target, prefix) {
filtered = append(filtered, target)
continue
}
out = append(out, n+"="+target)
}
out = append(out, fmt.Sprintf("(filtered: %s)", strings.Join(filtered, ", ")))
return out
}
......@@ -12,14 +12,15 @@ import (
"os"
"path/filepath"
"runtime"
"sync"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/v2/fuse"
)
// All holds a map of all test functions
var All = map[string]func(*testing.T, string){
"AppendWrite": AppendWrite,
"SymlinkReadlink": SymlinkReadlink,
"FileBasic": FileBasic,
"TruncateFile": TruncateFile,
......@@ -27,11 +28,57 @@ var All = map[string]func(*testing.T, string){
"FdLeak": FdLeak,
"MkdirRmdir": MkdirRmdir,
"NlinkZero": NlinkZero,
"FstatDeleted": FstatDeleted,
"ParallelFileOpen": ParallelFileOpen,
"Link": Link,
"LinkUnlinkRename": LinkUnlinkRename,
"RenameOverwriteDestNoExist": RenameOverwriteDestNoExist,
"RenameOverwriteDestExist": RenameOverwriteDestExist,
"RenameOpenDir": RenameOpenDir,
"ReadDir": ReadDir,
"ReadDirPicksUpCreate": ReadDirPicksUpCreate,
"DirectIO": DirectIO,
"OpenAt": OpenAt,
"Fallocate": Fallocate,
}
func DirectIO(t *testing.T, mnt string) {
fn := mnt + "/file.txt"
fd, err := syscall.Open(fn, syscall.O_TRUNC|syscall.O_CREAT|syscall.O_DIRECT|syscall.O_WRONLY, 0644)
if err == syscall.EINVAL {
t.Skip("FS does not support O_DIRECT")
}
if err != nil {
t.Fatalf("Open: %v", err)
}
defer func() {
if fd != 0 {
syscall.Close(fd)
}
}()
data := bytes.Repeat([]byte("bye"), 4096)
if n, err := syscall.Write(fd, data); err != nil || n != len(data) {
t.Fatalf("Write: %v (%d)", err, n)
}
err = syscall.Close(fd)
fd = 0
if err != nil {
t.Fatalf("Close: %v", err)
}
fd, err = syscall.Open(fn, syscall.O_DIRECT|syscall.O_RDONLY, 0644)
if err != nil {
t.Fatalf("Open 2: %v", err)
}
roundtrip := bytes.Repeat([]byte("xxx"), 4096)
if n, err := syscall.Read(fd, roundtrip); err != nil || n != len(data) {
t.Fatalf("ReadAt: %v (%d)", err, n)
}
if bytes.Compare(roundtrip, data) != 0 {
t.Errorf("roundtrip made changes: got %q.., want %q..", roundtrip[:10], data[:10])
}
}
// SymlinkReadlink tests basic symlink functionality
......@@ -149,13 +196,9 @@ func FdLeak(t *testing.T, mnt string) {
}
if runtime.GOOS == "linux" {
infos, err := ioutil.ReadDir("/proc/self/fd")
if err != nil {
t.Errorf("ReadDir %v", err)
}
infos := listFds(0, "")
if len(infos) > 15 {
t.Errorf("found %d open file descriptors for 100x ReadFile", len(infos))
t.Errorf("found %d open file descriptors for 100x ReadFile: %v", len(infos), infos)
}
}
}
......@@ -214,30 +257,92 @@ func NlinkZero(t *testing.T, mnt string) {
}
// FstatDeleted is similar to NlinkZero, but Fstat()s multiple deleted files
// in random order and checks that the results match an earlier Stat().
//
// Excercises the fd-finding logic in rawBridge.GetAttr.
func FstatDeleted(t *testing.T, mnt string) {
const iMax = 9
type file struct {
fd int
st syscall.Stat_t
}
files := make(map[int]file)
for i := 0; i <= iMax; i++ {
// Create files with different sizes
path := fmt.Sprintf("%s/%d", mnt, i)
content := make([]byte, i)
err := ioutil.WriteFile(path, content, 0644)
if err != nil {
t.Fatalf("WriteFile: %v", err)
}
var st syscall.Stat_t
err = syscall.Stat(path, &st)
if err != nil {
t.Fatal(err)
}
// Open
fd, err := syscall.Open(path, syscall.O_RDONLY, 0)
if err != nil {
t.Fatal(err)
}
files[i] = file{fd, st}
defer syscall.Close(fd)
// Delete
err = syscall.Unlink(path)
if err != nil {
t.Fatal(err)
}
}
// Fstat in random order
for _, v := range files {
var st syscall.Stat_t
err := syscall.Fstat(v.fd, &st)
if err != nil {
t.Fatal(err)
}
// Ignore ctime, changes on unlink
v.st.Ctim = syscall.Timespec{}
st.Ctim = syscall.Timespec{}
// Nlink value should have dropped to zero
v.st.Nlink = 0
// Rest should stay the same
if v.st != st {
t.Errorf("stat mismatch: want=%v\n have=%v", v.st, st)
}
}
}
func ParallelFileOpen(t *testing.T, mnt string) {
fn := mnt + "/file"
if err := ioutil.WriteFile(fn, []byte("content"), 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
var wg sync.WaitGroup
N := 10
errs := make(chan error, N)
one := func(b byte) {
f, err := os.OpenFile(fn, os.O_RDWR, 0644)
if err != nil {
t.Fatalf("OpenFile: %v", err)
errs <- err
return
}
var buf [10]byte
f.Read(buf[:])
buf[0] = b
f.WriteAt(buf[0:1], 2)
f.Close()
wg.Done()
errs <- nil
}
for i := 0; i < 10; i++ {
wg.Add(1)
for i := 0; i < N; i++ {
go one(byte(i))
}
wg.Wait()
for i := 0; i < N; i++ {
if e := <-errs; e != nil {
t.Error(e)
}
}
}
func Link(t *testing.T, mnt string) {
......@@ -265,6 +370,10 @@ func Link(t *testing.T, mnt string) {
if st.Ino != beforeIno {
t.Errorf("Lstat after: got %d, want %d", st.Ino, beforeIno)
}
if st.Nlink != 2 {
t.Errorf("Expect 2 links, got %d", st.Nlink)
}
}
func RenameOverwriteDestNoExist(t *testing.T, mnt string) {
......@@ -311,39 +420,208 @@ func RenameOverwrite(t *testing.T, mnt string, destExists bool) {
}
}
func ReadDir(t *testing.T, mnt string) {
f, err := os.Open(mnt)
func RenameOpenDir(t *testing.T, mnt string) {
if err := os.Mkdir(mnt+"/dir1", 0755); err != nil {
t.Fatalf("Mkdir: %v", err)
}
// Different permissions so directories are easier to tell apart
if err := os.Mkdir(mnt+"/dir2", 0700); err != nil {
t.Fatalf("Mkdir: %v", err)
}
var st1 syscall.Stat_t
if err := syscall.Stat(mnt+"/dir2", &st1); err != nil {
t.Fatalf("Stat: %v", err)
}
fd, err := syscall.Open(mnt+"/dir2", syscall.O_RDONLY, 0)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
defer syscall.Close(fd)
if err := syscall.Rename(mnt+"/dir1", mnt+"/dir2"); err != nil {
t.Fatalf("Rename: %v", err)
}
// add entries after opening the directory
var st2 syscall.Stat_t
if err := syscall.Fstat(fd, &st2); err != nil {
t.Skipf("Fstat failed: %v. Known limitation - see https://github.com/hanwen/go-fuse/issues/55", err)
}
if st2.Mode&syscall.S_IFMT != syscall.S_IFDIR {
t.Errorf("got mode %o, want %o", st2.Mode, syscall.S_IFDIR)
}
if st2.Ino != st1.Ino {
t.Errorf("got ino %d, want %d", st2.Ino, st1.Ino)
}
if st2.Mode&0777 != st1.Mode&0777 {
t.Skipf("got permissions %#o, want %#o. Known limitation - see https://github.com/hanwen/go-fuse/issues/55",
st2.Mode&0777, st1.Mode&0777)
}
}
// ReadDir creates 110 files one by one, checking that we get the expected
// entries after each file creation.
func ReadDir(t *testing.T, mnt string) {
want := map[string]bool{}
// 40 bytes of filename, so 110 entries overflows a
// 4096 page.
for i := 0; i < 110; i++ {
// 40 bytes of filename, so 110 entries overflows a
// 4096 page.
nm := fmt.Sprintf("file%036x", i)
want[nm] = true
if err := ioutil.WriteFile(filepath.Join(mnt, nm), []byte("hello"), 0644); err != nil {
t.Fatalf("WriteFile %q: %v", nm, err)
}
// Verify that we get the expected entries
f, err := os.Open(mnt)
if err != nil {
t.Fatalf("Open: %v", err)
}
names, err := f.Readdirnames(-1)
if err != nil {
t.Fatalf("ReadDir: %v", err)
}
f.Close()
got := map[string]bool{}
for _, e := range names {
got[e] = true
}
if len(got) != len(want) {
t.Errorf("got %d entries, want %d", len(got), len(want))
}
for k := range got {
if !want[k] {
t.Errorf("got unknown name %q", k)
}
}
}
}
// Readdir should pick file created after open, but before readdir.
func ReadDirPicksUpCreate(t *testing.T, mnt string) {
f, err := os.Open(mnt)
if err != nil {
t.Fatalf("Open: %v", err)
}
if err := ioutil.WriteFile(mnt+"/file", []byte{42}, 0644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
names, err := f.Readdirnames(-1)
if err != nil {
t.Fatalf("ReadDir: %v", err)
}
got := map[string]bool{}
for _, e := range names {
got[e] = true
f.Close()
if len(names) != 1 || names[0] != "file" {
t.Errorf("missing file created after opendir")
}
}
// LinkUnlinkRename implements rename with a link/unlink sequence
func LinkUnlinkRename(t *testing.T, mnt string) {
content := []byte("hello")
tmp := mnt + "/tmpfile"
if err := ioutil.WriteFile(tmp, content, 0644); err != nil {
t.Fatalf("WriteFile %q: %v", tmp, err)
}
if len(got) != len(want) {
t.Errorf("got %d entries, want %d", len(got), len(want))
dest := mnt + "/file"
if err := syscall.Link(tmp, dest); err != nil {
t.Fatalf("Link %q %q: %v", tmp, dest, err)
}
if err := syscall.Unlink(tmp); err != nil {
t.Fatalf("Unlink %q: %v", tmp, err)
}
if back, err := ioutil.ReadFile(dest); err != nil {
t.Fatalf("Read %q: %v", dest, err)
} else if bytes.Compare(back, content) != 0 {
t.Fatalf("Read got %q want %q", back, content)
}
for k := range got {
if !want[k] {
t.Errorf("got unknown name %q", k)
}
// test open with O_APPEND
func AppendWrite(t *testing.T, mnt string) {
fd, err := syscall.Open(mnt+"/file", syscall.O_WRONLY|syscall.O_APPEND|syscall.O_CREAT, 0644)
if err != nil {
t.Fatalf("Open: %v", err)
}
defer func() {
if fd != 0 {
syscall.Close(fd)
}
}()
if _, err := syscall.Write(fd, []byte("hello")); err != nil {
t.Fatalf("Write 1: %v", err)
}
if _, err := syscall.Write(fd, []byte("world")); err != nil {
t.Fatalf("Write 2: %v", err)
}
if err := syscall.Close(fd); err != nil {
t.Fatalf("Open: %v", err)
}
fd = 0
want := []byte("helloworld")
got, err := ioutil.ReadFile(mnt + "/file")
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if bytes.Compare(got, want) != 0 {
t.Errorf("got %q want %q", got, want)
}
}
// OpenAt tests syscall.Openat().
//
// Hint:
// $ go test ./fs -run TestPosix/OpenAt -v
func OpenAt(t *testing.T, mnt string) {
dir1 := mnt + "/dir1"
err := os.Mkdir(dir1, 0777)
if err != nil {
t.Fatal(err)
}
dirfd, err := syscall.Open(dir1, syscall.O_RDONLY, 0)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(dirfd)
dir2 := mnt + "/dir2"
err = os.Rename(dir1, dir2)
if err != nil {
t.Fatal(err)
}
fd, err := syscall.Openat(dirfd, "file1", syscall.O_CREAT, 0700)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
_, err = os.Stat(dir2 + "/file1")
if err != nil {
t.Error(err)
}
}
func Fallocate(t *testing.T, mnt string) {
rwFile, err := os.OpenFile(mnt+"/file", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
t.Fatalf("OpenFile failed: %v", err)
}
defer rwFile.Close()
err = syscall.Fallocate(int(rwFile.Fd()), 0, 1024, 4096)
if err != nil {
t.Fatalf("Fallocate failed: %v", err)
}
fi, err := os.Lstat(mnt + "/file")
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if fi.Size() < (1024 + 4096) {
t.Fatalf("fallocate should have changed file size. Got %d bytes",
fi.Size())
}
}
......@@ -15,9 +15,9 @@ import (
"syscall"
"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/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
)
type knownFs struct {
......
......@@ -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"
)
const entryTTL = 100 * time.Millisecond
......
......@@ -10,9 +10,9 @@ import (
"strings"
"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/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
)
const _XATTRSEP = "@XATTR@"
......
......@@ -9,9 +9,9 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"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/pathfs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
func modeMapEq(m1, m2 map[string]uint32) bool {
......
......@@ -7,7 +7,7 @@ package unionfs
import (
"os"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
)
func NewUnionFsFromRoots(roots []string, opts *UnionFsOptions, roCaching bool) (pathfs.FileSystem, error) {
......
......@@ -8,8 +8,8 @@ import (
"sync"
"time"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/fuse/pathfs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
)
// newDirnameMap reads the contents of the given directory. On error,
......
......@@ -16,9 +16,9 @@ import (
"syscall"
"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/v2/fuse"
"github.com/hanwen/go-fuse/v2/fuse/nodefs"
"github.com/hanwen/go-fuse/v2/fuse/pathfs"
)
func filePathHash(path string) string {
......
......@@ -18,11 +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/posixtest"
"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"
)
func TestFilePathHash(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 TestFS struct {
......
......@@ -19,8 +19,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"
)
// MultiZipFs is a filesystem that mounts zipfiles.
......
......@@ -10,9 +10,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"
)
const testTtl = 100 * time.Millisecond
......
......@@ -17,8 +17,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"
)
// TODO - handle symlinks.
......
......@@ -16,8 +16,8 @@ import (
"testing"
"time"
"github.com/hanwen/go-fuse/fs"
"github.com/hanwen/go-fuse/internal/testutil"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/internal/testutil"
)
var tarContents = map[string]string{
......
......@@ -15,8 +15,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"
)
type zipRoot struct {
......@@ -84,7 +84,9 @@ func (zf *zipFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrO
out.Atime = out.Mtime
out.Ctime = out.Mtime
out.Size = zf.file.UncompressedSize64
out.Blocks = (out.Size + 511) / 512
const bs = 512
out.Blksize = bs
out.Blocks = (out.Size + bs - 1) / bs
return 0
}
......
......@@ -13,9 +13,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 testZipFile() string {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment