Example #1
0
// 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
}
Example #2
0
// Rename implements pathfs.Filesystem.
func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) {
	if fs.isFiltered(newPath) {
		return fuse.EPERM
	}
	cOldPath, err := fs.getBackingPath(oldPath)
	if err != nil {
		return fuse.ToStatus(err)
	}
	cNewPath, err := fs.getBackingPath(newPath)
	if err != nil {
		return fuse.ToStatus(err)
	}
	// The Rename may cause a directory to take the place of another directory.
	// That directory may still be in the DirIV cache, clear it.
	fs.nameTransform.DirIVCache.Clear()

	// Handle long source file name
	var oldDirFd *os.File
	var finalOldDirFd int
	var finalOldPath = cOldPath
	cOldName := filepath.Base(cOldPath)
	if nametransform.IsLongContent(cOldName) {
		oldDirFd, err = os.Open(filepath.Dir(cOldPath))
		if err != nil {
			return fuse.ToStatus(err)
		}
		defer oldDirFd.Close()
		finalOldDirFd = int(oldDirFd.Fd())
		// Use relative path
		finalOldPath = cOldName
	}
	// Handle long destination file name
	var newDirFd *os.File
	var finalNewDirFd int
	var finalNewPath = cNewPath
	cNewName := filepath.Base(cNewPath)
	if nametransform.IsLongContent(cNewName) {
		newDirFd, err = os.Open(filepath.Dir(cNewPath))
		if err != nil {
			return fuse.ToStatus(err)
		}
		defer newDirFd.Close()
		finalNewDirFd = int(newDirFd.Fd())
		// Use relative path
		finalNewPath = cNewName
		// Create destination .name file
		err = fs.nameTransform.WriteLongName(newDirFd, cNewName, newPath)
		if err != nil {
			return fuse.ToStatus(err)
		}
	}
	// Actual rename
	tlog.Debug.Printf("Renameat oldfd=%d oldpath=%s newfd=%d newpath=%s\n", finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath)
	err = syscallcompat.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath)
	if err == syscall.ENOTEMPTY || err == syscall.EEXIST {
		// If an empty directory is overwritten we will always get an error as
		// the "empty" directory will still contain gocryptfs.diriv.
		// Interestingly, ext4 returns ENOTEMPTY while xfs returns EEXIST.
		// We handle that by trying to fs.Rmdir() the target directory and trying
		// again.
		tlog.Debug.Printf("Rename: Handling ENOTEMPTY")
		if fs.Rmdir(newPath, context) == fuse.OK {
			err = syscallcompat.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath)
		}
	}
	if err != nil {
		if newDirFd != nil {
			// Roll back .name creation
			nametransform.DeleteLongName(newDirFd, cNewName)
		}
		return fuse.ToStatus(err)
	}
	if oldDirFd != nil {
		nametransform.DeleteLongName(oldDirFd, cOldName)
	}
	return fuse.OK
}