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
|
// +build linux
package syscallcompat
import (
"io/ioutil"
"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.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 := ioutil.TempDir(tmpDir, "TestGetdents")
if err != nil {
t.Fatal(err)
}
for i := 1; i <= unix.NAME_MAX; i++ {
n := strings.Repeat("x", i)
err = ioutil.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)
}
}
}
}
|