From e50a6a57e57bc3cc925ba9a6e7f4dc1da4da3c84 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 13 Aug 2017 21:13:44 +0200 Subject: syscallcompat: implement Getdents() The Readdir function provided by os is inherently slow because it calls Lstat on all files. Getdents gives us all the information we need, but does not have a proper wrapper in the stdlib. Implement the "Getdents()" wrapper function that calls syscall.Getdents() and parses the returned byte blob to a fuse.DirEntry slice. --- internal/syscallcompat/getdents_test.go | 77 +++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 internal/syscallcompat/getdents_test.go (limited to 'internal/syscallcompat/getdents_test.go') diff --git a/internal/syscallcompat/getdents_test.go b/internal/syscallcompat/getdents_test.go new file mode 100644 index 0000000..f0b1e60 --- /dev/null +++ b/internal/syscallcompat/getdents_test.go @@ -0,0 +1,77 @@ +// +build linux + +package syscallcompat + +import ( + "io/ioutil" + "os" + "strings" + "syscall" + "testing" + + "github.com/hanwen/go-fuse/fuse" +) + +func TestGetdents(t *testing.T) { + // Fill a directory with filenames of length 1 ... 255 + testDir, err := ioutil.TempDir("", "TestGetdents") + if err != nil { + t.Fatal(err) + } + for i := 1; i <= syscall.NAME_MAX; i++ { + n := strings.Repeat("x", i) + err = ioutil.WriteFile(testDir+"/"+n, nil, 0600) + if err != nil { + t.Fatal(err) + } + } + // "/", "/dev" and "/proc" are good test cases because they contain many + // different file types (block and char devices, symlinks, mountpoints) + dirs := []string{testDir, "/", "/dev", "/proc"} + for _, dir := range dirs { + // Read directory using stdlib Readdir() + fd, err := os.Open(dir) + if err != nil { + t.Fatal(err) + } + readdirEntries, err := fd.Readdir(0) + if err != nil { + t.Fatal(err) + } + fd.Close() + readdirMap := make(map[string]*syscall.Stat_t) + for _, v := range readdirEntries { + readdirMap[v.Name()] = fuse.ToStatT(v) + } + // Read using our Getdents() + getdentsEntries, err := Getdents(dir) + if err != nil { + t.Fatal(err) + } + 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) + } + } + } + } +} -- cgit v1.2.3