summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend/fs_dir.go22
-rw-r--r--internal/syscallcompat/getdents_linux.go29
-rw-r--r--internal/syscallcompat/getdents_other.go50
-rw-r--r--internal/syscallcompat/getdents_test.go22
-rw-r--r--internal/syscallcompat/sys_linux.go7
5 files changed, 84 insertions, 46 deletions
diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go
index ae52412..2102008 100644
--- a/internal/fusefrontend/fs_dir.go
+++ b/internal/fusefrontend/fs_dir.go
@@ -271,20 +271,14 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f
cDirAbsPath := filepath.Join(fs.args.Cipherdir, cDirName)
var cipherEntries []fuse.DirEntry
var status fuse.Status
- if syscallcompat.HaveGetdents {
- // Getdents avoids calling Lstat on each file.
- cipherEntries, err = syscallcompat.Getdents(cDirAbsPath)
- if err != nil {
- return nil, fuse.ToStatus(err)
- }
- } else {
- haveGetdentsWarnOnce.Do(func() {
- tlog.Warn.Printf("OpenDir: Getdents not available, falling back to OpenDir")
- })
- cipherEntries, status = fs.FileSystem.OpenDir(cDirName, context)
- if !status.Ok() {
- return nil, status
- }
+ fd, err := syscall.Open(cDirAbsPath, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
+ if err != nil {
+ return nil, fuse.ToStatus(err)
+ }
+ defer syscall.Close(fd)
+ cipherEntries, err = syscallcompat.Getdents(fd)
+ if err != nil {
+ return nil, fuse.ToStatus(err)
}
// Get DirIV (stays nil if PlaintextNames is used)
var cachedIV []byte
diff --git a/internal/syscallcompat/getdents_linux.go b/internal/syscallcompat/getdents_linux.go
index 55bffff..49f9233 100644
--- a/internal/syscallcompat/getdents_linux.go
+++ b/internal/syscallcompat/getdents_linux.go
@@ -12,26 +12,17 @@ import (
"syscall"
"unsafe"
+ "golang.org/x/sys/unix"
+
"github.com/hanwen/go-fuse/fuse"
"github.com/rfjakob/gocryptfs/internal/tlog"
)
-// HaveGetdents is true if we have a working implementation of Getdents
-const HaveGetdents = true
-
const sizeofDirent = int(unsafe.Sizeof(syscall.Dirent{}))
-// Getdents wraps syscall.Getdents and converts the result to []fuse.DirEntry.
-// The function takes a path instead of an fd because we need to be able to
-// call Lstat on files. Fstatat is not yet available in Go as of v1.9:
-// https://github.com/golang/go/issues/14216
-func Getdents(dir string) ([]fuse.DirEntry, error) {
- fd, err := syscall.Open(dir, syscall.O_RDONLY, 0)
- if err != nil {
- return nil, err
- }
- defer syscall.Close(fd)
+// getdents wraps syscall.Getdents and converts the result to []fuse.DirEntry.
+func getdents(fd int) ([]fuse.DirEntry, error) {
// Collect syscall result in smartBuf.
// "bytes.Buffer" is smart about expanding the capacity and avoids the
// exponential runtime of simple append().
@@ -84,7 +75,7 @@ func Getdents(dir string) ([]fuse.DirEntry, error) {
// os.File.Readdir() drops "." and "..". Let's be compatible.
continue
}
- mode, err := convertDType(s.Type, dir+"/"+name)
+ mode, err := convertDType(fd, name, s.Type)
if err != nil {
// The file may have been deleted in the meantime. Just skip it
// and go on.
@@ -125,17 +116,17 @@ func getdentsName(s syscall.Dirent) (string, error) {
var dtUnknownWarnOnce sync.Once
// convertDType converts a Dirent.Type to at Stat_t.Mode value.
-func convertDType(dtype uint8, file string) (uint32, error) {
+func convertDType(dirfd int, name string, dtype uint8) (uint32, error) {
if dtype != syscall.DT_UNKNOWN {
// Shift up by four octal digits = 12 bits
return uint32(dtype) << 12, nil
}
- // DT_UNKNOWN: we have to call Lstat()
+ // DT_UNKNOWN: we have to call stat()
dtUnknownWarnOnce.Do(func() {
- tlog.Warn.Printf("Getdents: convertDType: received DT_UNKNOWN, falling back to Lstat")
+ tlog.Warn.Printf("Getdents: convertDType: received DT_UNKNOWN, falling back to stat")
})
- var st syscall.Stat_t
- err := syscall.Lstat(file, &st)
+ var st unix.Stat_t
+ err := Fstatat(dirfd, name, &st, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
return 0, err
}
diff --git a/internal/syscallcompat/getdents_other.go b/internal/syscallcompat/getdents_other.go
index 4ef5b8f..6d08a9b 100644
--- a/internal/syscallcompat/getdents_other.go
+++ b/internal/syscallcompat/getdents_other.go
@@ -1,17 +1,49 @@
-// +build !linux
-
package syscallcompat
import (
- "log"
+ "os"
+ "syscall"
+
+ "golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/fuse"
)
-// HaveGetdents is true if we have a working implementation of Getdents
-const HaveGetdents = false
-
-func Getdents(dir string) ([]fuse.DirEntry, error) {
- log.Panic("only implemented on Linux")
- return nil, nil
+// 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))
+ for _, name := range names {
+ var st unix.Stat_t
+ 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.
+ continue
+ }
+ if err != nil {
+ return nil, err
+ }
+ newEntry := fuse.DirEntry{
+ Name: name,
+ Mode: uint32(st.Mode) & syscall.S_IFMT,
+ Ino: st.Ino,
+ }
+ out = append(out, newEntry)
+ }
+ return out, nil
}
diff --git a/internal/syscallcompat/getdents_test.go b/internal/syscallcompat/getdents_test.go
index 8f2bd09..131ffee 100644
--- a/internal/syscallcompat/getdents_test.go
+++ b/internal/syscallcompat/getdents_test.go
@@ -12,9 +12,19 @@ import (
"github.com/hanwen/go-fuse/fuse"
)
+var getdentsUnderTest = getdents
+
func TestGetdents(t *testing.T) {
+ t.Logf("testing native getdents")
+ testGetdents(t)
+ t.Logf("testing emulateGetdents")
+ getdentsUnderTest = emulateGetdents
+ testGetdents(t)
+}
+
+func testGetdents(t *testing.T) {
// Fill a directory with filenames of length 1 ... 255
- testDir, err := ioutil.TempDir("", "TestGetdents")
+ testDir, err := ioutil.TempDir(tmpDir, "TestGetdents")
if err != nil {
t.Fatal(err)
}
@@ -35,17 +45,21 @@ func TestGetdents(t *testing.T) {
if err != nil {
t.Fatal(err)
}
+ defer fd.Close()
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)
+ // Read using our Getdents() implementation
+ _, err = fd.Seek(0, 0) // Rewind directory
+ if err != nil {
+ t.Fatal(err)
+ }
+ getdentsEntries, err := getdentsUnderTest(int(fd.Fd()))
if err != nil {
t.Fatal(err)
}
diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go
index fb8d8b3..b5b949e 100644
--- a/internal/syscallcompat/sys_linux.go
+++ b/internal/syscallcompat/sys_linux.go
@@ -7,6 +7,8 @@ import (
"golang.org/x/sys/unix"
+ "github.com/hanwen/go-fuse/fuse"
+
"github.com/rfjakob/gocryptfs/internal/tlog"
)
@@ -115,3 +117,8 @@ func Fstatat(dirfd int, path string, stat *unix.Stat_t, flags int) (err error) {
}
return unix.Fstatat(dirfd, path, stat, flags)
}
+
+// Getdents syscall.
+func Getdents(fd int) ([]fuse.DirEntry, error) {
+ return getdents(fd)
+}