mirror of
https://github.com/hanzoai/go-fuse.git
synced 2026-10-30 14:46:18 +00:00
399 lines
9.3 KiB
Go
399 lines
9.3 KiB
Go
// 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 (
|
|
"bytes"
|
|
"os"
|
|
"sync"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/hanzoai/go-fuse/v2/fuse"
|
|
"github.com/hanzoai/go-fuse/v2/internal/testutil"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func TestRenameNoOverwrite(t *testing.T) {
|
|
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
|
|
|
|
if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil {
|
|
t.Fatalf("Mkdir: %v", err)
|
|
}
|
|
tc.writeOrig("file", "hello", 0644)
|
|
tc.writeOrig("dir/file", "x", 0644)
|
|
|
|
f1, err := syscall.Open(tc.mntDir+"/", syscall.O_DIRECTORY, 0)
|
|
if err != nil {
|
|
t.Fatalf("open 1: %v", err)
|
|
}
|
|
defer syscall.Close(f1)
|
|
f2, err := syscall.Open(tc.mntDir+"/dir", syscall.O_DIRECTORY, 0)
|
|
if err != nil {
|
|
t.Fatalf("open 2: %v", err)
|
|
}
|
|
defer syscall.Close(f2)
|
|
|
|
if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_NOREPLACE); err == nil {
|
|
t.Errorf("rename NOREPLACE succeeded")
|
|
} else if err != syscall.EEXIST {
|
|
t.Errorf("got %v (%T) want EEXIST", err, err)
|
|
}
|
|
}
|
|
|
|
func TestRenameWhiteOut(t *testing.T) {
|
|
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
|
|
|
|
if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil {
|
|
t.Fatalf("Mkdir: %v", err)
|
|
}
|
|
tc.writeOrig("file", "hello", 0644)
|
|
|
|
f1, err := syscall.Open(tc.mntDir+"/", syscall.O_DIRECTORY, 0)
|
|
if err != nil {
|
|
t.Fatalf("open 1: %v", err)
|
|
}
|
|
defer syscall.Close(f1)
|
|
f2, err := syscall.Open(tc.mntDir+"/dir", syscall.O_DIRECTORY, 0)
|
|
if err != nil {
|
|
t.Fatalf("open 2: %v", err)
|
|
}
|
|
defer syscall.Close(f2)
|
|
|
|
if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_WHITEOUT); err != nil {
|
|
t.Errorf("rename WHITEOUT: %v", err)
|
|
}
|
|
|
|
fi, err := os.Stat(tc.mntDir + "/file")
|
|
if err != nil {
|
|
t.Fatalf("Stat: %v", err)
|
|
}
|
|
st := fi.Sys().(*syscall.Stat_t)
|
|
if st.Mode&syscall.S_IFMT != unix.S_IFCHR {
|
|
t.Errorf("old file is not a whiteout")
|
|
}
|
|
if st.Rdev != 0 {
|
|
t.Errorf("old file is not a whiteout")
|
|
}
|
|
|
|
fi, err = os.Stat(tc.mntDir + "/dir/file")
|
|
if err != nil {
|
|
t.Fatalf("Stat: %v", err)
|
|
}
|
|
st = fi.Sys().(*syscall.Stat_t)
|
|
if st.Mode&syscall.S_IFMT != unix.S_IFREG {
|
|
t.Errorf("new file is not a regular file")
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
|
|
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})
|
|
|
|
if !tc.server.KernelSettings().SupportsVersion(7, 28) {
|
|
t.Skip("need v7.28 for CopyFileRange")
|
|
}
|
|
|
|
tc.writeOrig("src", "01234567890123456789", 0644)
|
|
tc.writeOrig("dst", "abcdefghijabcdefghij", 0644)
|
|
|
|
f1, err := syscall.Open(tc.mntDir+"/src", syscall.O_RDONLY, 0)
|
|
if err != nil {
|
|
t.Fatalf("Open src: %v", err)
|
|
}
|
|
defer func() {
|
|
// syscall.Close() is treacherous; because fds are
|
|
// reused, a double close can cause serious havoc
|
|
if f1 > 0 {
|
|
syscall.Close(f1)
|
|
}
|
|
}()
|
|
|
|
f2, err := syscall.Open(tc.mntDir+"/dst", syscall.O_RDWR, 0)
|
|
if err != nil {
|
|
t.Fatalf("Open dst: %v", err)
|
|
}
|
|
defer func() {
|
|
if f2 > 0 {
|
|
defer syscall.Close(f2)
|
|
}
|
|
}()
|
|
|
|
srcOff := int64(5)
|
|
dstOff := int64(7)
|
|
if sz, err := unix.CopyFileRange(f1, &srcOff, f2, &dstOff, 3, 0); err != nil || sz != 3 {
|
|
t.Fatalf("CopyFileRange: %d,%v", sz, err)
|
|
}
|
|
|
|
err = syscall.Close(f1)
|
|
f1 = 0
|
|
if err != nil {
|
|
t.Fatalf("Close src: %v", err)
|
|
}
|
|
|
|
err = syscall.Close(f2)
|
|
f2 = 0
|
|
if err != nil {
|
|
t.Fatalf("Close dst: %v", err)
|
|
}
|
|
c, err := os.ReadFile(tc.mntDir + "/dst")
|
|
if err != nil {
|
|
t.Fatalf("ReadFile: %v", err)
|
|
}
|
|
|
|
want := "abcdefg567abcdefghij"
|
|
got := string(c)
|
|
if got != want {
|
|
t.Errorf("got %q want %q", got, want)
|
|
}
|
|
|
|
}
|
|
|
|
// Wait for a change in /proc/self/mounts. Efficient through the use of
|
|
// unix.Poll().
|
|
func waitProcMountsChange() error {
|
|
fd, err := syscall.Open("/proc/self/mounts", syscall.O_RDONLY, 0)
|
|
defer syscall.Close(fd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pollFds := []unix.PollFd{
|
|
{
|
|
Fd: int32(fd),
|
|
Events: unix.POLLPRI,
|
|
},
|
|
}
|
|
_, err = unix.Poll(pollFds, 1000)
|
|
return err
|
|
}
|
|
|
|
// Wait until mountpoint "mnt" shows up /proc/self/mounts
|
|
func waitMount(mnt string) error {
|
|
for {
|
|
err := waitProcMountsChange()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
content, err := os.ReadFile("/proc/self/mounts")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bytes.Contains(content, []byte(mnt)) {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// There is a hang that appears when enabling CAP_PARALLEL_DIROPS on Linux
|
|
// 4.15.0: https://github.com/hanwen/go-fuse/issues/281
|
|
// The hang was originally triggered by gvfs-udisks2-volume-monitor. This
|
|
// test emulates what gvfs-udisks2-volume-monitor does.
|
|
func TestParallelDiropsHang(t *testing.T) {
|
|
// We do NOT want to use newTestCase() here because we need to know the
|
|
// mnt path before the filesystem is mounted
|
|
dir := t.TempDir()
|
|
orig := dir + "/orig"
|
|
mnt := dir + "/mnt"
|
|
if err := os.Mkdir(orig, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Mkdir(mnt, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Unblock the goroutines onces the mount shows up in /proc/self/mounts
|
|
wait := make(chan struct{})
|
|
go func() {
|
|
err := waitMount(mnt)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
// Unblock the goroutines regardless of an error. We don't want to hang
|
|
// the test.
|
|
close(wait)
|
|
}()
|
|
|
|
// gvfs-udisks2-volume-monitor hits the mount with three threads - we try to
|
|
// emulate exactly what it does acc. to an strace log.
|
|
var wg sync.WaitGroup
|
|
wg.Add(3)
|
|
// [pid 2117] lstat(".../mnt/autorun.inf", <unfinished ...>
|
|
go func() {
|
|
defer wg.Done()
|
|
<-wait
|
|
var st unix.Stat_t
|
|
unix.Lstat(mnt+"/autorun.inf", &st)
|
|
}()
|
|
// [pid 2116] open(".../mnt/.xdg-volume-info", O_RDONLY <unfinished ...>
|
|
go func() {
|
|
defer wg.Done()
|
|
<-wait
|
|
syscall.Open(mnt+"/.xdg-volume-info", syscall.O_RDONLY, 0)
|
|
}()
|
|
// 25 times this:
|
|
// [pid 1874] open(".../mnt", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC <unfinished ...>
|
|
// [pid 1874] fstat(11, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
|
|
// [pid 1874] getdents(11, /* 2 entries */, 32768) = 48
|
|
// [pid 1874] close(11) = 0
|
|
go func() {
|
|
defer wg.Done()
|
|
<-wait
|
|
for i := 1; i <= 25; i++ {
|
|
f, err := os.Open(mnt)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
_, err = f.Stat()
|
|
if err != nil {
|
|
t.Error(err)
|
|
f.Close()
|
|
return
|
|
}
|
|
_, err = f.Readdirnames(-1)
|
|
if err != nil {
|
|
t.Errorf("iteration %d: fd %d: %v", i, f.Fd(), err)
|
|
return
|
|
}
|
|
f.Close()
|
|
}
|
|
}()
|
|
|
|
loopbackRoot, err := NewLoopbackRoot(orig)
|
|
if err != nil {
|
|
t.Fatalf("NewLoopbackRoot(%s): %v\n", orig, err)
|
|
}
|
|
sec := time.Second
|
|
opts := &Options{
|
|
AttrTimeout: &sec,
|
|
EntryTimeout: &sec,
|
|
}
|
|
opts.Debug = testutil.VerboseTest()
|
|
|
|
rawFS := NewNodeFS(loopbackRoot, opts)
|
|
server, err := fuse.NewServer(rawFS, mnt, &opts.MountOptions)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
go server.Serve()
|
|
|
|
wg.Wait()
|
|
server.Unmount()
|
|
}
|
|
|
|
func TestRoMount(t *testing.T) {
|
|
newTestCase(t, &testOptions{ro: true})
|
|
}
|
|
|
|
func TestDirectMount(t *testing.T) {
|
|
opts := &testOptions{
|
|
directMount: true,
|
|
}
|
|
if os.Geteuid() == 0 {
|
|
t.Log("running as root, setting DirectMountStrict")
|
|
opts.directMountStrict = true
|
|
}
|
|
newTestCase(t, opts)
|
|
}
|
|
|
|
const FS_NOATIME_FL = 0x00000080
|
|
const FS_IOC_GETFLAGS = 0x80086601
|
|
const FS_IOC_SETFLAGS = 0x40086602
|
|
|
|
func flipNoAtime(fd uintptr) (bool, error) {
|
|
var flags uint32
|
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, FS_IOC_GETFLAGS, uintptr(unsafe.Pointer(&flags)))
|
|
if errno != 0 {
|
|
return false, errno
|
|
}
|
|
|
|
before := (flags & FS_NOATIME_FL) != 0
|
|
flags ^= FS_NOATIME_FL
|
|
_, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), FS_IOC_SETFLAGS, uintptr(unsafe.Pointer(&flags)))
|
|
if errno != 0 {
|
|
return false, errno
|
|
}
|
|
|
|
return before, nil
|
|
}
|
|
|
|
func TestIoctlLoopbackFile(t *testing.T) {
|
|
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
|
|
|
|
f, err := os.Create(tc.origDir + "/file")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before, err := flipNoAtime(f.Fd())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
f.Close()
|
|
|
|
f, err = os.Open(tc.mntDir + "/file")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
after, err := flipNoAtime(f.Fd())
|
|
|
|
if after != !before {
|
|
t.Fatalf("didn't work: after %v, before %v", after, before)
|
|
}
|
|
}
|
|
|
|
func TestIoctlLoopbackDir(t *testing.T) {
|
|
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
|
|
|
|
err := os.Mkdir(tc.origDir+"/dir", 0777)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
d, err := os.Open(tc.origDir + "/dir")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before, err := flipNoAtime(d.Fd())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d.Close()
|
|
|
|
d, err = os.Open(tc.mntDir + "/dir")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer d.Close()
|
|
after, err := flipNoAtime(d.Fd())
|
|
|
|
if after != !before {
|
|
t.Fatalf("didn't work: after %v, before %v", after, before)
|
|
}
|
|
}
|