summaryrefslogtreecommitdiff
path: root/internal/fusefrontend/fs_dir.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/fusefrontend/fs_dir.go')
-rw-r--r--internal/fusefrontend/fs_dir.go343
1 files changed, 0 insertions, 343 deletions
diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go
deleted file mode 100644
index 0d8adae..0000000
--- a/internal/fusefrontend/fs_dir.go
+++ /dev/null
@@ -1,343 +0,0 @@
-package fusefrontend
-
-// Mkdir and Rmdir
-
-import (
- "fmt"
- "io"
- "runtime"
- "syscall"
-
- "golang.org/x/sys/unix"
-
- "github.com/hanwen/go-fuse/v2/fuse"
-
- "github.com/rfjakob/gocryptfs/internal/configfile"
- "github.com/rfjakob/gocryptfs/internal/cryptocore"
- "github.com/rfjakob/gocryptfs/internal/nametransform"
- "github.com/rfjakob/gocryptfs/internal/syscallcompat"
- "github.com/rfjakob/gocryptfs/internal/tlog"
-)
-
-const dsStoreName = ".DS_Store"
-
-// mkdirWithIv - create a new directory and corresponding diriv file. dirfd
-// should be a handle to the parent directory, cName is the name of the new
-// directory and mode specifies the access permissions to use.
-func (fs *FS) mkdirWithIv(dirfd int, cName string, mode uint32, context *fuse.Context) error {
- // Between the creation of the directory and the creation of gocryptfs.diriv
- // the directory is inconsistent. Take the lock to prevent other readers
- // from seeing it.
- fs.dirIVLock.Lock()
- defer fs.dirIVLock.Unlock()
- err := syscallcompat.MkdiratUser(dirfd, cName, mode, &context.Caller)
- if err != nil {
- return err
- }
- dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscallcompat.O_PATH, 0)
- if err == nil {
- // Create gocryptfs.diriv
- err = nametransform.WriteDirIVAt(dirfd2)
- syscall.Close(dirfd2)
- }
- if err != nil {
- // Delete inconsistent directory (missing gocryptfs.diriv!)
- err2 := syscallcompat.Unlinkat(dirfd, cName, unix.AT_REMOVEDIR)
- if err2 != nil {
- tlog.Warn.Printf("mkdirWithIv: rollback failed: %v", err2)
- }
- }
- return err
-}
-
-// Mkdir - FUSE call. Create a directory at "newPath" with permissions "mode".
-//
-// Symlink-safe through use of Mkdirat().
-func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fuse.Status) {
- if fs.isFiltered(newPath) {
- return fuse.EPERM
- }
- dirfd, cName, err := fs.openBackingDir(newPath)
- if err != nil {
- return fuse.ToStatus(err)
- }
- defer syscall.Close(dirfd)
- // Make sure context is nil if we don't want to preserve the owner
- if !fs.args.PreserveOwner {
- context = nil
- }
- if fs.args.PlaintextNames {
- err = syscallcompat.MkdiratUser(dirfd, cName, mode, &context.Caller)
- return fuse.ToStatus(err)
- }
-
- // We need write and execute permissions to create gocryptfs.diriv.
- // Also, we need read permissions to open the directory (to avoid
- // race-conditions between getting and setting the mode).
- origMode := mode
- mode = mode | 0700
-
- // Handle long file name
- if nametransform.IsLongContent(cName) {
- // Create ".name"
- err = fs.nameTransform.WriteLongNameAt(dirfd, cName, newPath)
- if err != nil {
- return fuse.ToStatus(err)
- }
-
- // Create directory
- err = fs.mkdirWithIv(dirfd, cName, mode, context)
- if err != nil {
- nametransform.DeleteLongNameAt(dirfd, cName)
- return fuse.ToStatus(err)
- }
- } else {
- err = fs.mkdirWithIv(dirfd, cName, mode, context)
- if err != nil {
- return fuse.ToStatus(err)
- }
- }
- // Set mode
- if origMode != mode {
- dirfd2, err := syscallcompat.Openat(dirfd, cName,
- syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
- if err != nil {
- tlog.Warn.Printf("Mkdir %q: Openat failed: %v", cName, err)
- return fuse.ToStatus(err)
- }
- defer syscall.Close(dirfd2)
-
- var st syscall.Stat_t
- err = syscall.Fstat(dirfd2, &st)
- if err != nil {
- tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err)
- return fuse.ToStatus(err)
- }
-
- // Preserve SGID bit if it was set due to inheritance.
- origMode = uint32(st.Mode&^0777) | origMode
- err = syscall.Fchmod(dirfd2, origMode)
- if err != nil {
- tlog.Warn.Printf("Mkdir %q: Fchmod %#o -> %#o failed: %v", cName, mode, origMode, err)
- return fuse.ToStatus(err)
- }
- }
- return fuse.OK
-}
-
-// haveDsstore return true if one of the entries in "names" is ".DS_Store".
-func haveDsstore(entries []fuse.DirEntry) bool {
- for _, e := range entries {
- if e.Name == dsStoreName {
- return true
- }
- }
- return false
-}
-
-// Rmdir - FUSE call.
-//
-// Symlink-safe through Unlinkat() + AT_REMOVEDIR.
-func (fs *FS) Rmdir(relPath string, context *fuse.Context) (code fuse.Status) {
- defer fs.dirCache.Clear()
- parentDirFd, cName, err := fs.openBackingDir(relPath)
- if err != nil {
- return fuse.ToStatus(err)
- }
- defer syscall.Close(parentDirFd)
- if fs.args.PlaintextNames {
- // Unlinkat with AT_REMOVEDIR is equivalent to Rmdir
- err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
- return fuse.ToStatus(err)
- }
- // Unless we are running as root, we need read, write and execute permissions
- // to handle gocryptfs.diriv.
- permWorkaround := false
- var origMode uint32
- if !fs.args.PreserveOwner {
- var st unix.Stat_t
- err = syscallcompat.Fstatat(parentDirFd, cName, &st, unix.AT_SYMLINK_NOFOLLOW)
- if err != nil {
- return fuse.ToStatus(err)
- }
- if st.Mode&0700 != 0700 {
- tlog.Debug.Printf("Rmdir: permWorkaround")
- permWorkaround = true
- // This cast is needed on Darwin, where st.Mode is uint16.
- origMode = uint32(st.Mode)
- err = syscallcompat.FchmodatNofollow(parentDirFd, cName, origMode|0700)
- if err != nil {
- tlog.Debug.Printf("Rmdir: permWorkaround: chmod failed: %v", err)
- return fuse.ToStatus(err)
- }
- }
- }
- dirfd, err := syscallcompat.Openat(parentDirFd, cName,
- syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
- if err != nil {
- tlog.Debug.Printf("Rmdir: Open: %v", err)
- return fuse.ToStatus(err)
- }
- defer syscall.Close(dirfd)
- // Undo the chmod if removing the directory failed. This must run before
- // closing dirfd, so defer it after (defer is LIFO).
- if permWorkaround {
- defer func() {
- if code != fuse.OK {
- err = unix.Fchmod(dirfd, origMode)
- if err != nil {
- tlog.Warn.Printf("Rmdir: permWorkaround: rollback failed: %v", err)
- }
- }
- }()
- }
-retry:
- // Check directory contents
- children, err := syscallcompat.Getdents(dirfd)
- if err == io.EOF {
- // The directory is empty
- tlog.Warn.Printf("Rmdir: %q: %s is missing", cName, nametransform.DirIVFilename)
- err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
- return fuse.ToStatus(err)
- }
- if err != nil {
- tlog.Warn.Printf("Rmdir: Readdirnames: %v", err)
- return fuse.ToStatus(err)
- }
- // MacOS sprinkles .DS_Store files everywhere. This is hard to avoid for
- // users, so handle it transparently here.
- if runtime.GOOS == "darwin" && len(children) <= 2 && haveDsstore(children) {
- err = unix.Unlinkat(dirfd, dsStoreName, 0)
- if err != nil {
- tlog.Warn.Printf("Rmdir: failed to delete blocking file %q: %v", dsStoreName, err)
- return fuse.ToStatus(err)
- }
- tlog.Warn.Printf("Rmdir: had to delete blocking file %q", dsStoreName)
- goto retry
- }
- // If the directory is not empty besides gocryptfs.diriv, do not even
- // attempt the dance around gocryptfs.diriv.
- if len(children) > 1 {
- return fuse.ToStatus(syscall.ENOTEMPTY)
- }
- // Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ"
- tmpName := fmt.Sprintf("%s.rmdir.%d", nametransform.DirIVFilename, cryptocore.RandUint64())
- tlog.Debug.Printf("Rmdir: Renaming %s to %s", nametransform.DirIVFilename, tmpName)
- // The directory is in an inconsistent state between rename and rmdir.
- // Protect against concurrent readers.
- fs.dirIVLock.Lock()
- defer fs.dirIVLock.Unlock()
- err = syscallcompat.Renameat(dirfd, nametransform.DirIVFilename,
- parentDirFd, tmpName)
- if err != nil {
- tlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v",
- nametransform.DirIVFilename, tmpName, err)
- return fuse.ToStatus(err)
- }
- // Actual Rmdir
- err = syscallcompat.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
- if err != nil {
- // This can happen if another file in the directory was created in the
- // meantime, undo the rename
- err2 := syscallcompat.Renameat(parentDirFd, tmpName,
- dirfd, nametransform.DirIVFilename)
- if err2 != nil {
- tlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2)
- }
- return fuse.ToStatus(err)
- }
- // Delete "gocryptfs.diriv.rmdir.XYZ"
- err = syscallcompat.Unlinkat(parentDirFd, tmpName, 0)
- if err != nil {
- tlog.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err)
- }
- // Delete .name file
- if nametransform.IsLongContent(cName) {
- nametransform.DeleteLongNameAt(parentDirFd, cName)
- }
- return fuse.OK
-}
-
-// OpenDir - FUSE call
-//
-// This function is symlink-safe through use of openBackingDir() and
-// ReadDirIVAt().
-func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
- tlog.Debug.Printf("OpenDir(%s)", dirName)
- parentDirFd, cDirName, err := fs.openBackingDir(dirName)
- if err != nil {
- return nil, fuse.ToStatus(err)
- }
- defer syscall.Close(parentDirFd)
- // Read ciphertext directory
- var cipherEntries []fuse.DirEntry
- var status fuse.Status
- fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|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
- if !fs.args.PlaintextNames {
- // Read the DirIV from disk
- cachedIV, err = nametransform.ReadDirIVAt(fd)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: could not read %s: %v", cDirName, nametransform.DirIVFilename, err)
- return nil, fuse.EIO
- }
- }
- // Decrypted directory entries
- var plain []fuse.DirEntry
- // Filter and decrypt filenames
- for i := range cipherEntries {
- cName := cipherEntries[i].Name
- if dirName == "" && cName == configfile.ConfDefaultName {
- // silently ignore "gocryptfs.conf" in the top level dir
- continue
- }
- if fs.args.PlaintextNames {
- plain = append(plain, cipherEntries[i])
- continue
- }
- if cName == nametransform.DirIVFilename {
- // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
- continue
- }
- // Handle long file name
- isLong := nametransform.LongNameNone
- if fs.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)
- fs.reportMitigatedCorruption(cName)
- continue
- }
- cName = cNameLong
- } else if isLong == nametransform.LongNameFilename {
- // ignore "gocryptfs.longname.*.name"
- continue
- }
- name, err := fs.nameTransform.DecryptName(cName, cachedIV)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v",
- cDirName, cName, err)
- fs.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 plain, status
-}