func (rfs *ReverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEntry) ([]fuse.DirEntry, fuse.Status) { if relPath != "" || rfs.args.ConfigCustom { return entries, fuse.OK } // We are in the root dir and the default config file name // ".gocryptfs.reverse.conf" is used. We map it to "gocryptfs.conf". dupe := -1 status := fuse.OK for i := range entries { if entries[i].Name == configfile.ConfReverseName { entries[i].Name = configfile.ConfDefaultName } else if entries[i].Name == configfile.ConfDefaultName { dupe = i } } if dupe >= 0 { // Warn the user loudly: The gocryptfs.conf_NAME_COLLISION file will // throw ENOENT errors that are hard to miss. tlog.Warn.Printf("The file %s is mapped to %s and shadows another file. Please rename %s in %s .", configfile.ConfReverseName, configfile.ConfDefaultName, configfile.ConfDefaultName, rfs.args.Cipherdir) entries[dupe].Name = "gocryptfs.conf_NAME_COLLISION_" + fmt.Sprintf("%d", cryptocore.RandUint64()) } return entries, status }
// Rmdir implements pathfs.FileSystem func (fs *FS) Rmdir(path string, context *fuse.Context) (code fuse.Status) { cPath, err := fs.getBackingPath(path) if err != nil { return fuse.ToStatus(err) } if fs.args.PlaintextNames { err = syscall.Rmdir(cPath) return fuse.ToStatus(err) } parentDir := filepath.Dir(cPath) parentDirFd, err := os.Open(parentDir) if err != nil { return fuse.ToStatus(err) } defer parentDirFd.Close() cName := filepath.Base(cPath) dirfdRaw, err := syscallcompat.Openat(int(parentDirFd.Fd()), cName, syscall.O_RDONLY, 0) if err == syscall.EACCES { // We need permission to read and modify the directory tlog.Debug.Printf("Rmdir: handling EACCESS") // TODO use syscall.Fstatat once it is available in Go var fi os.FileInfo fi, err = os.Lstat(cPath) if err != nil { tlog.Debug.Printf("Rmdir: Stat: %v", err) return fuse.ToStatus(err) } origMode := fi.Mode() // TODO use syscall.Chmodat once it is available in Go err = os.Chmod(cPath, origMode|0700) if err != nil { tlog.Debug.Printf("Rmdir: Chmod failed: %v", err) return fuse.ToStatus(err) } // Retry open var st syscall.Stat_t syscall.Lstat(cPath, &st) dirfdRaw, err = syscallcompat.Openat(int(parentDirFd.Fd()), cName, syscall.O_RDONLY, 0) // Undo the chmod if removing the directory failed defer func() { if code != fuse.OK { err = os.Chmod(cPath, origMode) if err != nil { tlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err) } } }() } if err != nil { tlog.Debug.Printf("Rmdir: Open: %v", err) return fuse.ToStatus(err) } dirfd := os.NewFile(uintptr(dirfdRaw), cName) defer dirfd.Close() children, err := dirfd.Readdirnames(10) if err == nil { // 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("gocryptfs.diriv.rmdir.%d", 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(int(dirfd.Fd()), nametransform.DirIVFilename, int(parentDirFd.Fd()), tmpName) if err != nil { tlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v", nametransform.DirIVFilename, tmpName, err) return fuse.ToStatus(err) } // Actual Rmdir // TODO Use syscall.Unlinkat with the AT_REMOVEDIR flag once it is available // in Go err = syscall.Rmdir(cPath) if err != nil { // This can happen if another file in the directory was created in the // meantime, undo the rename err2 := syscallcompat.Renameat(int(parentDirFd.Fd()), tmpName, int(dirfd.Fd()), nametransform.DirIVFilename) if err != nil { tlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2) } return fuse.ToStatus(err) } // Delete "gocryptfs.diriv.rmdir.XYZ" err = syscallcompat.Unlinkat(int(parentDirFd.Fd()), tmpName) if err != nil { tlog.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err) } } else if err == io.EOF { // The directory is empty tlog.Warn.Printf("Rmdir: %q: gocryptfs.diriv is missing", cPath) err = syscall.Rmdir(cPath) if err != nil { return fuse.ToStatus(err) } } else { tlog.Warn.Printf("Rmdir: Readdirnames: %v", err) return fuse.ToStatus(err) } // Delete .name file if nametransform.IsLongContent(cName) { nametransform.DeleteLongName(parentDirFd, cName) } // The now-deleted directory may have been in the DirIV cache. Clear it. fs.nameTransform.DirIVCache.Clear() return fuse.OK }