1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
//go:build linux
// +build linux
package syscallcompat
import (
"os"
"runtime"
"strings"
"syscall"
"testing"
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fuse"
)
var emulate = false
func TestGetdents(t *testing.T) {
t.Skip("may fail on filesystems without extended attributes, disabled")
t.Logf("testing native getdents")
testGetdents(t)
t.Logf("testing emulateGetdents")
emulate = true
testGetdents(t)
}
// skipOnGccGo skips the emulateGetdents test when we are
// running linux and were compiled with gccgo. The test is known to fail
// (https://github.com/rfjakob/gocryptfs/issues/201), but getdents emulation
// is not used on linux, so let's skip the test and ignore the failure.
func skipOnGccGo(t *testing.T) {
if !emulate || runtime.GOOS != "linux" {
return
}
// runtime.Version() output...
// on go: go1.9.2
// on gccgo: go1.8.1 gccgo (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2)
v := runtime.Version()
if strings.Contains(v, "gccgo") {
t.Skipf("test is known-broken on gccgo")
}
}
func testGetdents(t *testing.T) {
getdentsUnderTest := getdents
if emulate {
getdentsUnderTest = emulateGetdents
}
// Fill a directory with filenames of length 1 ... 255
testDir, err := os.MkdirTemp(tmpDir, "TestGetdents")
if err != nil {
t.Fatal(err)
}
for i := 1; i <= unix.NAME_MAX; i++ {
n := strings.Repeat("x", i)
err = os.WriteFile(testDir+"/"+n, nil, 0600)
if err != nil {
t.Fatal(err)
}
}
// "/", "/dev" and "/proc/self" are good test cases because they contain
// many different file types (block and char devices, symlinks,
// mountpoints).
dirs := []string{testDir, "/", "/dev", "/proc/self"}
for _, dir := range dirs {
// Read directory using stdlib Readdir()
fd, err := os.Open(dir)
if err != nil {
t.Fatal(err)
}
defer fd.Close()
readdirEntries, err := fd.Readdir(0)
if err != nil {
t.Fatal(err)
}
readdirMap := make(map[string]*syscall.Stat_t)
for _, v := range readdirEntries {
readdirMap[v.Name()] = fuse.ToStatT(v)
}
// Read using our Getdents() implementation
_, err = fd.Seek(0, 0) // Rewind directory
if err != nil {
t.Fatal(err)
}
getdentsEntries, special, err := getdentsUnderTest(int(fd.Fd()))
if err != nil {
t.Log(err)
skipOnGccGo(t)
t.FailNow()
}
getdentsMap := make(map[string]fuse.DirEntry)
for _, v := range getdentsEntries {
getdentsMap[v.Name] = v
}
// Compare results
if len(getdentsEntries) != len(readdirEntries) {
t.Fatalf("len(getdentsEntries)=%d, len(readdirEntries)=%d",
len(getdentsEntries), len(readdirEntries))
}
for name := range readdirMap {
g := getdentsMap[name]
r := readdirMap[name]
rTyp := r.Mode & syscall.S_IFMT
if g.Mode != rTyp {
t.Errorf("%q: g.Mode=%#o, r.Mode=%#o", name, g.Mode, rTyp)
}
if g.Ino != r.Ino {
// The inode number of a directory that is reported by stat
// and getdents is different when it is a mountpoint. Only
// throw an error when we are NOT looking at a directory.
if g.Mode != syscall.S_IFDIR {
t.Errorf("%s: g.Ino=%d, r.Ino=%d", name, g.Ino, r.Ino)
}
}
}
if len(special) != 2 {
t.Error(special)
}
if !(special[0].Name == "." && special[1].Name == ".." ||
special[1].Name == "." && special[0].Name == "..") {
t.Error(special)
}
for _, v := range special {
if v.Ino == 0 {
t.Error(v)
}
if v.Mode != syscall.S_IFDIR {
t.Error(v)
}
}
}
}
|