diff options
| author | Jakob Unterwurzacher | 2025-03-26 22:45:43 +0100 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2025-04-26 11:03:55 +0200 | 
| commit | ae3c859c1179498a4882b4bd69c2243aa6912332 (patch) | |
| tree | 5b40f6ecc0071afc8301cb68a4a52e9cd3847d86 | |
| parent | 7309412215551052dc4dbb6c137c568ccbd02430 (diff) | |
fusefrontend: switch to new go-fuse dir api
go-fuse 2.6.0, specifically,
https://github.com/hanwen/go-fuse/commit/e885cea8d4d40a5a9bb92bc3cef7193f2a316f59
introduced a new, file-based directory API while
deprecating the old one.
Switch to the new API.
xfstests generic/035 now passes.
Fixes https://github.com/hanwen/go-fuse/issues/55
| -rw-r--r-- | internal/fusefrontend/file.go | 2 | ||||
| -rw-r--r-- | internal/fusefrontend/file_dir_ops.go | 177 | ||||
| -rw-r--r-- | internal/fusefrontend/node_api_check.go | 1 | ||||
| -rw-r--r-- | internal/fusefrontend/node_dir_ops.go | 86 | 
4 files changed, 179 insertions, 87 deletions
| diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index 0e25de3..103c217 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -50,6 +50,8 @@ type File struct {  	lastOpCount uint64  	// Parent filesystem  	rootNode *RootNode +	// If this open file is a directory, dirHandle will be set, otherwise it's nil. +	dirHandle *DirHandle  }  // NewFile returns a new go-fuse File instance based on an already-open file diff --git a/internal/fusefrontend/file_dir_ops.go b/internal/fusefrontend/file_dir_ops.go new file mode 100644 index 0000000..b69e7bc --- /dev/null +++ b/internal/fusefrontend/file_dir_ops.go @@ -0,0 +1,177 @@ +package fusefrontend + +import ( +	"context" +	"syscall" + +	"github.com/hanwen/go-fuse/v2/fs" +	"github.com/hanwen/go-fuse/v2/fuse" +	"github.com/rfjakob/gocryptfs/v2/internal/configfile" +	"github.com/rfjakob/gocryptfs/v2/internal/nametransform" +	"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat" +	"github.com/rfjakob/gocryptfs/v2/internal/tlog" +) + +func (n *Node) OpendirHandle(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { +	var fd int = -1 +	var fdDup int = -1 +	var file *File +	var dirIV []byte +	var ds fs.DirStream +	rn := n.rootNode() + +	dirfd, cName, errno := n.prepareAtSyscallMyself() +	if errno != 0 { +		return +	} +	defer syscall.Close(dirfd) + +	// Open backing directory +	fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) +	if err != nil { +		errno = fs.ToErrno(err) +		return +	} + +	// NewLoopbackDirStreamFd gets its own fd to untangle Release vs Releasedir +	fdDup, err = syscall.Dup(fd) + +	if err != nil { +		errno = fs.ToErrno(err) +		goto err_out +	} + +	ds, errno = fs.NewLoopbackDirStreamFd(fdDup) +	if errno != 0 { +		goto err_out +	} + +	if !rn.args.PlaintextNames { +		// Read the DirIV from disk +		dirIV, err = rn.nameTransform.ReadDirIVAt(fd) +		if err != nil { +			tlog.Warn.Printf("OpendirHandle: could not read %s: %v", nametransform.DirIVFilename, err) +			errno = syscall.EIO +			goto err_out +		} +	} + +	file, _, errno = NewFile(fd, cName, rn) +	if errno != 0 { +		goto err_out +	} + +	file.dirHandle = &DirHandle{ +		ds:        ds, +		dirIV:     dirIV, +		isRootDir: n.IsRoot(), +	} + +	return file, fuseFlags, errno + +err_out: +	if fd >= 0 { +		syscall.Close(fd) +	} +	if fdDup >= 0 { +		syscall.Close(fdDup) +	} +	if errno == 0 { +		tlog.Warn.Printf("BUG: OpendirHandle: err_out called with errno == 0") +		errno = syscall.EIO +	} +	return nil, 0, errno +} + +type DirHandle struct { +	// Content of gocryptfs.diriv. nil if plaintextnames is used. +	dirIV []byte + +	isRootDir bool + +	// fs.loopbackDirStream with a private dup of the file descriptor +	ds fs.FileHandle +} + +var _ = (fs.FileReleasedirer)((*File)(nil)) + +func (f *File) Releasedir(ctx context.Context, flags uint32) { +	// Does its own locking +	f.dirHandle.ds.(fs.FileReleasedirer).Releasedir(ctx, flags) +	// Does its own locking +	f.Release(ctx) +} + +var _ = (fs.FileSeekdirer)((*File)(nil)) + +func (f *File) Seekdir(ctx context.Context, off uint64) syscall.Errno { +	return f.dirHandle.ds.(fs.FileSeekdirer).Seekdir(ctx, off) +} + +var _ = (fs.FileFsyncdirer)((*File)(nil)) + +func (f *File) Fsyncdir(ctx context.Context, flags uint32) syscall.Errno { +	return f.dirHandle.ds.(fs.FileFsyncdirer).Fsyncdir(ctx, flags) +} + +var _ = (fs.FileReaddirenter)((*File)(nil)) + +// This function is symlink-safe through use of openBackingDir() and +// ReadDirIVAt(). +func (f *File) Readdirent(ctx context.Context) (entry *fuse.DirEntry, errno syscall.Errno) { +	f.fdLock.RLock() +	defer f.fdLock.RUnlock() + +	for { +		entry, errno = f.dirHandle.ds.(fs.FileReaddirenter).Readdirent(ctx) +		if errno != 0 || entry == nil { +			return +		} + +		cName := entry.Name +		if cName == "." || cName == ".." { +			// We want these as-is +			return +		} +		if f.dirHandle.isRootDir && cName == configfile.ConfDefaultName { +			// silently ignore "gocryptfs.conf" in the top level dir +			continue +		} +		if f.rootNode.args.PlaintextNames { +			return +		} +		if !f.rootNode.args.DeterministicNames && cName == nametransform.DirIVFilename { +			// silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled +			continue +		} +		// Handle long file name +		isLong := nametransform.LongNameNone +		if f.rootNode.args.LongNames { +			isLong = nametransform.NameType(cName) +		} +		if isLong == nametransform.LongNameContent { +			cNameLong, err := nametransform.ReadLongNameAt(f.intFd(), cName) +			if err != nil { +				tlog.Warn.Printf("Readdirent: incomplete entry %q: Could not read .name: %v", +					cName, err) +				f.rootNode.reportMitigatedCorruption(cName) +				continue +			} +			cName = cNameLong +		} else if isLong == nametransform.LongNameFilename { +			// ignore "gocryptfs.longname.*.name" +			continue +		} +		name, err := f.rootNode.nameTransform.DecryptName(cName, f.dirHandle.dirIV) +		if err != nil { +			tlog.Warn.Printf("Readdirent: could not decrypt entry %q: %v", +				cName, err) +			f.rootNode.reportMitigatedCorruption(cName) +			continue +		} +		// Override the ciphertext name with the plaintext name but reuse the rest +		// of the structure +		entry.Name = name +		return +	} +} diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go index 0f60c74..37d4293 100644 --- a/internal/fusefrontend/node_api_check.go +++ b/internal/fusefrontend/node_api_check.go @@ -7,7 +7,6 @@ import (  // Check that we have implemented the fs.Node* interfaces  var _ = (fs.NodeGetattrer)((*Node)(nil))  var _ = (fs.NodeLookuper)((*Node)(nil)) -var _ = (fs.NodeReaddirer)((*Node)(nil))  var _ = (fs.NodeCreater)((*Node)(nil))  var _ = (fs.NodeMkdirer)((*Node)(nil))  var _ = (fs.NodeRmdirer)((*Node)(nil)) diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go index 97e4caa..11ff83d 100644 --- a/internal/fusefrontend/node_dir_ops.go +++ b/internal/fusefrontend/node_dir_ops.go @@ -12,7 +12,6 @@ import (  	"github.com/hanwen/go-fuse/v2/fs"  	"github.com/hanwen/go-fuse/v2/fuse" -	"github.com/rfjakob/gocryptfs/v2/internal/configfile"  	"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"  	"github.com/rfjakob/gocryptfs/v2/internal/nametransform"  	"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat" @@ -159,91 +158,6 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En  	return ch, 0  } -// Readdir - FUSE call. -// -// This function is symlink-safe through use of openBackingDir() and -// ReadDirIVAt(). -func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) { -	parentDirFd, cDirName, errno := n.prepareAtSyscallMyself() -	if errno != 0 { -		return nil, errno -	} -	defer syscall.Close(parentDirFd) - -	// Read ciphertext directory -	fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) -	if err != nil { -		return nil, fs.ToErrno(err) -	} -	defer syscall.Close(fd) -	cipherEntries, specialEntries, err := syscallcompat.GetdentsSpecial(fd) -	if err != nil { -		return nil, fs.ToErrno(err) -	} -	// Get DirIV (stays nil if PlaintextNames is used) -	var cachedIV []byte -	rn := n.rootNode() -	if !rn.args.PlaintextNames { -		// Read the DirIV from disk -		cachedIV, err = rn.nameTransform.ReadDirIVAt(fd) -		if err != nil { -			tlog.Warn.Printf("OpenDir %q: could not read %s: %v", cDirName, nametransform.DirIVFilename, err) -			return nil, syscall.EIO -		} -	} -	// Decrypted directory entries -	var plain []fuse.DirEntry -	// Add "." and ".." -	plain = append(plain, specialEntries...) -	// Filter and decrypt filenames -	for i := range cipherEntries { -		cName := cipherEntries[i].Name -		if n.IsRoot() && cName == configfile.ConfDefaultName { -			// silently ignore "gocryptfs.conf" in the top level dir -			continue -		} -		if rn.args.PlaintextNames { -			plain = append(plain, cipherEntries[i]) -			continue -		} -		if !rn.args.DeterministicNames && cName == nametransform.DirIVFilename { -			// silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled -			continue -		} -		// Handle long file name -		isLong := nametransform.LongNameNone -		if rn.args.LongNames { -			isLong = nametransform.NameType(cName) -		} -		if isLong == nametransform.LongNameContent { -			cNameLong, err := nametransform.ReadLongNameAt(fd, cName) -			if err != nil { -				tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v", -					cDirName, cName, err) -				rn.reportMitigatedCorruption(cName) -				continue -			} -			cName = cNameLong -		} else if isLong == nametransform.LongNameFilename { -			// ignore "gocryptfs.longname.*.name" -			continue -		} -		name, err := rn.nameTransform.DecryptName(cName, cachedIV) -		if err != nil { -			tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v", -				cDirName, cName, err) -			rn.reportMitigatedCorruption(cName) -			continue -		} -		// Override the ciphertext name with the plaintext name but reuse the rest -		// of the structure -		cipherEntries[i].Name = name -		plain = append(plain, cipherEntries[i]) -	} - -	return fs.NewListDirStream(plain), 0 -} -  // Rmdir - FUSE call.  //  // Symlink-safe through Unlinkat() + AT_REMOVEDIR. | 
