Files
go-fuse/fuse/test/loopback_linux_test.go

248 lines
6.1 KiB
Go

// Copyright 2016 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 test
import (
"flag"
"fmt"
"os"
"path/filepath"
"syscall"
"testing"
"time"
"golang.org/x/sys/unix"
"github.com/hanzoai/go-fuse/v2/fuse"
)
var enableOverlayfsTest bool
var enableOverlayfsTestFlag string = "test.overlayfs"
func init() {
// flag to enable test with overayfs. This would not work on kernel 5.15
// so don't enable on that kernel.
flag.BoolVar(&enableOverlayfsTest, enableOverlayfsTestFlag, false, "enable tests with overlayfs (would fail on kernel 5.15)")
}
func TestTouch(t *testing.T) {
ts := NewTestCase(t)
defer ts.Cleanup()
contents := []byte{1, 2, 3}
err := os.WriteFile(ts.origFile, []byte(contents), 0700)
if err != nil {
t.Fatalf("WriteFile failed: %v", err)
}
err = os.Chtimes(ts.mountFile, time.Unix(42, 0), time.Unix(43, 0))
if err != nil {
t.Fatalf("Chtimes failed: %v", err)
}
var stat syscall.Stat_t
err = syscall.Lstat(ts.mountFile, &stat)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if stat.Atim.Sec != 42 {
t.Errorf("Got atime.sec %d, want 42. Stat_t was %#v", stat.Atim.Sec, stat)
}
if stat.Mtim.Sec != 43 {
t.Errorf("Got mtime.sec %d, want 43. Stat_t was %#v", stat.Mtim.Sec, stat)
}
}
func TestNegativeTime(t *testing.T) {
ts := NewTestCase(t)
defer ts.Cleanup()
_, err := os.Create(ts.origFile)
if err != nil {
t.Fatalf("Create failed: %v", err)
}
var stat syscall.Stat_t
// set negative nanosecond will occur errors on UtimesNano as invalid argument
ut := time.Date(1960, time.January, 10, 23, 0, 0, 0, time.UTC)
tim := []syscall.Timespec{
syscall.NsecToTimespec(ut.UnixNano()),
syscall.NsecToTimespec(ut.UnixNano()),
}
err = syscall.UtimesNano(ts.mountFile, tim)
if err != nil {
t.Fatalf("UtimesNano failed: %v", err)
}
err = syscall.Lstat(ts.mountFile, &stat)
if err != nil {
t.Fatalf("Lstat failed: %v", err)
}
if stat.Atim.Sec >= 0 || stat.Mtim.Sec >= 0 {
t.Errorf("Got wrong timestamps %v", stat)
}
}
// Setting nanoseconds should work for dates after 1970
func TestUtimesNano(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
path := tc.mountFile
err := os.WriteFile(path, []byte("xyz"), 0600)
if err != nil {
t.Fatal(err)
}
ts := make([]syscall.Timespec, 2)
// atime
ts[0].Sec = 1
ts[0].Nsec = 2
// mtime
ts[1].Sec = 3
ts[1].Nsec = 4
err = syscall.UtimesNano(path, ts)
if err != nil {
t.Fatal(err)
}
var st syscall.Stat_t
err = syscall.Stat(path, &st)
if err != nil {
t.Fatal(err)
}
if st.Atim != ts[0] {
t.Errorf("Wrong atime: %v, want: %v", st.Atim, ts[0])
}
if st.Mtim != ts[1] {
t.Errorf("Wrong mtime: %v, want: %v", st.Mtim, ts[1])
}
}
func clearStatfs(s *syscall.Statfs_t) {
empty := syscall.Statfs_t{}
s.Type = 0
s.Fsid = empty.Fsid
s.Spare = empty.Spare
// TODO - figure out what this is for.
s.Flags = 0
}
// Check that fuse mount can serve as a overlayfs lowerdir.
func TestOverlayfs(t *testing.T) {
if !enableOverlayfsTest {
t.Skipf("this test must be enabled through the flag %q", enableOverlayfsTestFlag)
}
if os.Getuid() != 0 {
t.Skip("this test requires root")
}
tc := NewTestCase(t)
defer tc.Cleanup()
testfile := "test"
content := randomData(125)
tc.Mkdir(tc.origSubdir, 0777)
tc.WriteFile(filepath.Join(tc.origSubdir, testfile), content, 0700)
tmpMergedDir := t.TempDir()
tmpWorkDir := t.TempDir()
tmpUpperDir := t.TempDir()
defer os.RemoveAll(tmpUpperDir)
if err := unix.Mount("overlay", tmpMergedDir, "overlay", 0,
fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", tc.mnt, tmpUpperDir, tmpWorkDir)); err != nil {
t.Fatalf("failed to mount overlay: %v", err)
}
defer unix.Unmount(tmpMergedDir, 0)
err := os.Chtimes(filepath.Join(tmpMergedDir, "subdir", testfile), time.Unix(42, 0), time.Unix(43, 0))
if err != nil {
t.Fatalf("Chtimes failed: %v", err)
}
}
func TestFallocate(t *testing.T) {
ts := NewTestCase(t)
defer ts.Cleanup()
if ts.state.KernelSettings().Minor < 19 {
t.Log("FUSE does not support Fallocate.")
return
}
rwFile, err := os.OpenFile(ts.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("FUSE Fallocate failed: %v", err)
}
fi, err := os.Lstat(ts.orig + "/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())
}
}
// Check that "." and ".." exists. unix.Getdents is linux specific.
func TestSpecialEntries(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
d, err := os.Open(tc.mnt)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer d.Close()
buf := make([]byte, 100)
n, err := unix.Getdents(int(d.Fd()), buf)
if n == 0 {
t.Errorf("directory is empty, entries '.' and '..' are missing")
}
}
// Check that readdir(3) returns valid inode numbers in the directory entries
func TestReaddirInodes(t *testing.T) {
tc := NewTestCase(t)
defer tc.Cleanup()
// create "hello.txt"
filename := "hello.txt"
path := tc.orig + "/" + filename
err := os.WriteFile(path, []byte("xyz"), 0600)
if err != nil {
t.Fatal(err)
}
// open mountpoint dir
d, err := os.Open(tc.mnt)
if err != nil {
t.Fatalf("Open failed: %v", err)
}
defer d.Close()
buf := make([]byte, 100)
// readdir(3) use getdents64(2) internally which returns linux_dirent64
// structures. We don't have readdir(3) so we call getdents64(2) directly.
n, err := unix.Getdents(int(d.Fd()), buf)
if n == 0 {
t.Error("empty directory - we need at least one file")
}
buf = buf[:n]
entries := parseDirents(buf)
t.Logf("parseDirents returned %d entries", len(entries))
// Find "hello.txt" and check inode number.
for _, entry := range entries {
if entry.name != filename {
continue
}
if entry.ino != 0 && entry.ino != fuse.FUSE_UNKNOWN_INO {
// Inode number looks good, we are done.
return
}
t.Errorf("got invalid inode number: %d = 0x%x", entry.ino, entry.ino)
}
t.Errorf("%q not found in directory listing", filename)
}