From 1b3c3b1347ef711ec38bb4c5cb14661035faf2ce Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sun, 23 May 2021 12:00:09 +0200 Subject: [PATCH] syscallcompat: add GetdentsSpecial() GetdentsSpecial calls then Getdents syscall, with normal entries and "." / ".." split into two slices. --- internal/syscallcompat/getdents_linux.go | 23 ++++++----- internal/syscallcompat/getdents_other.go | 50 +++++++++++++++--------- internal/syscallcompat/getdents_test.go | 17 +++++++- internal/syscallcompat/sys_darwin.go | 5 +++ internal/syscallcompat/sys_linux.go | 9 ++++- 5 files changed, 74 insertions(+), 30 deletions(-) diff --git a/internal/syscallcompat/getdents_linux.go b/internal/syscallcompat/getdents_linux.go index e3a2c4a..852b3cd 100644 --- a/internal/syscallcompat/getdents_linux.go +++ b/internal/syscallcompat/getdents_linux.go @@ -27,7 +27,7 @@ const sizeofDirent = int(unsafe.Sizeof(unix.Dirent{})) const maxReclen = 280 // getdents wraps unix.Getdents and converts the result to []fuse.DirEntry. -func getdents(fd int) ([]fuse.DirEntry, error) { +func getdents(fd int) (entries []fuse.DirEntry, entriesSpecial []fuse.DirEntry, err error) { // Collect syscall result in smartBuf. // "bytes.Buffer" is smart about expanding the capacity and avoids the // exponential runtime of simple append(). @@ -44,9 +44,9 @@ func getdents(fd int) ([]fuse.DirEntry, error) { } else if err != nil { if smartBuf.Len() > 0 { tlog.Warn.Printf("warning: unix.Getdents returned errno %d in the middle of data ( https://github.com/rfjakob/gocryptfs/issues/483 )", err.(syscall.Errno)) - return nil, syscall.EIO + return nil, nil, syscall.EIO } - return nil, err + return nil, nil, err } if n == 0 { break @@ -66,12 +66,12 @@ func getdents(fd int) ([]fuse.DirEntry, error) { tlog.Warn.Printf("Getdents: corrupt entry #%d: Reclen=0 at offset=%d. Returning EBADR", numEntries, offset) // EBADR = Invalid request descriptor - return nil, syscall.EBADR + return nil, nil, syscall.EBADR } if int(s.Reclen) > maxReclen { tlog.Warn.Printf("Getdents: corrupt entry #%d: Reclen=%d > %d. Returning EBADR", numEntries, s.Reclen, maxReclen) - return nil, syscall.EBADR + return nil, nil, syscall.EBADR } offset += int(s.Reclen) numEntries++ @@ -80,17 +80,22 @@ func getdents(fd int) ([]fuse.DirEntry, error) { // Note: syscall.ParseDirent() only returns the names, // we want all the data, so we have to implement // it on our own. - entries := make([]fuse.DirEntry, 0, numEntries) + entries = make([]fuse.DirEntry, 0, numEntries) offset = 0 for offset < len(buf) { s := *(*unix.Dirent)(unsafe.Pointer(&buf[offset])) name, err := getdentsName(s) if err != nil { - return nil, err + return nil, nil, err } offset += int(s.Reclen) if name == "." || name == ".." { - // os.File.Readdir() drops "." and "..". Let's be compatible. + // These are always directories, no need to call convertDType. + entriesSpecial = append(entriesSpecial, fuse.DirEntry{ + Ino: s.Ino, + Mode: syscall.S_IFDIR, + Name: name, + }) continue } mode, err := convertDType(fd, name, s.Type) @@ -105,7 +110,7 @@ func getdents(fd int) ([]fuse.DirEntry, error) { Name: name, }) } - return entries, nil + return entries, entriesSpecial, nil } // getdentsName extracts the filename from a Dirent struct and returns it as diff --git a/internal/syscallcompat/getdents_other.go b/internal/syscallcompat/getdents_other.go index 82932db..1ed5ecf 100644 --- a/internal/syscallcompat/getdents_other.go +++ b/internal/syscallcompat/getdents_other.go @@ -9,27 +9,11 @@ import ( "github.com/hanwen/go-fuse/v2/fuse" ) -// emulateGetdents reads all directory entries from the open directory "fd" -// and returns them in a fuse.DirEntry slice. -func emulateGetdents(fd int) (out []fuse.DirEntry, err error) { - // os.File closes the fd in its finalizer. Duplicate the fd to not affect - // the original fd. - newFd, err := syscall.Dup(fd) - if err != nil { - return nil, err - } - f := os.NewFile(uintptr(newFd), "") - defer f.Close() - // Get all file names in the directory - names, err := f.Readdirnames(0) - if err != nil { - return nil, err - } - // Stat all of them and convert to fuse.DirEntry - out = make([]fuse.DirEntry, 0, len(names)) +func fillDirEntries(fd int, names []string) ([]fuse.DirEntry, error) { + out := make([]fuse.DirEntry, 0, len(names)) for _, name := range names { var st unix.Stat_t - err = Fstatat(fd, name, &st, unix.AT_SYMLINK_NOFOLLOW) + err := Fstatat(fd, name, &st, unix.AT_SYMLINK_NOFOLLOW) if err == syscall.ENOENT { // File disappeared between readdir and stat. Pretend we did not // see it. @@ -47,3 +31,31 @@ func emulateGetdents(fd int) (out []fuse.DirEntry, err error) { } return out, nil } + +// emulateGetdents reads all directory entries from the open directory "fd" +// and returns normal entries and "." / ".." split into two slices. +func emulateGetdents(fd int) (out []fuse.DirEntry, outSpecial []fuse.DirEntry, err error) { + // os.File closes the fd in its finalizer. Duplicate the fd to not affect + // the original fd. + newFd, err := syscall.Dup(fd) + if err != nil { + return nil, nil, err + } + f := os.NewFile(uintptr(newFd), "") + defer f.Close() + // Get all file names in the directory + names, err := f.Readdirnames(0) + if err != nil { + return nil, nil, err + } + // Stat all the names and convert to fuse.DirEntry + out, err = fillDirEntries(fd, names) + if err != nil { + return nil, nil, err + } + outSpecial, err = fillDirEntries(fd, []string{".", ".."}) + if err != nil { + return nil, nil, err + } + return out, outSpecial, nil +} diff --git a/internal/syscallcompat/getdents_test.go b/internal/syscallcompat/getdents_test.go index cb95b7c..a6f41ca 100644 --- a/internal/syscallcompat/getdents_test.go +++ b/internal/syscallcompat/getdents_test.go @@ -83,7 +83,7 @@ func testGetdents(t *testing.T) { if err != nil { t.Fatal(err) } - getdentsEntries, err := getdentsUnderTest(int(fd.Fd())) + getdentsEntries, special, err := getdentsUnderTest(int(fd.Fd())) if err != nil { t.Log(err) skipOnGccGo(t) @@ -114,5 +114,20 @@ func testGetdents(t *testing.T) { } } } + 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) + } + } } } diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go index a14a4ee..b5f7d49 100644 --- a/internal/syscallcompat/sys_darwin.go +++ b/internal/syscallcompat/sys_darwin.go @@ -216,6 +216,11 @@ func UtimesNanoAtNofollow(dirfd int, path string, a *time.Time, m *time.Time) (e } func Getdents(fd int) ([]fuse.DirEntry, error) { + entries, _, err := emulateGetdents(fd) + return entries, err +} + +func GetdentsSpecial(fd int) (entries []fuse.DirEntry, entriesSpecial []fuse.DirEntry, err error) { return emulateGetdents(fd) } diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go index 46d039c..36ce7d5 100644 --- a/internal/syscallcompat/sys_linux.go +++ b/internal/syscallcompat/sys_linux.go @@ -244,8 +244,15 @@ func UtimesNanoAtNofollow(dirfd int, path string, a *time.Time, m *time.Time) (e return err } -// Getdents syscall. +// Getdents syscall with "." and ".." filtered out. func Getdents(fd int) ([]fuse.DirEntry, error) { + entries, _, err := getdents(fd) + return entries, err +} + +// GetdentsSpecial calls the Getdents syscall, +// with normal entries and "." / ".." split into two slices. +func GetdentsSpecial(fd int) (entries []fuse.DirEntry, entriesSpecial []fuse.DirEntry, err error) { return getdents(fd) }