aboutsummaryrefslogtreecommitdiff
path: root/internal/syscallcompat/getdents_test.go
blob: eb670d625c03db0f73e7d7a3232ae8a9eadf0742 (plain)
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 (
	"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)
			}
		}
	}
}