diff options
Diffstat (limited to 'internal/syscallcompat')
| -rw-r--r-- | internal/syscallcompat/getdents_linux.go | 29 | ||||
| -rw-r--r-- | internal/syscallcompat/getdents_other.go | 50 | ||||
| -rw-r--r-- | internal/syscallcompat/getdents_test.go | 22 | ||||
| -rw-r--r-- | internal/syscallcompat/sys_linux.go | 7 | 
4 files changed, 76 insertions, 32 deletions
| 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) +} | 
