// Unlink implements pathfs.Filesystem. func (fs *FS) Unlink(path string, context *fuse.Context) (code fuse.Status) { if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.getBackingPath(path) if err != nil { return fuse.ToStatus(err) } cName := filepath.Base(cPath) if nametransform.IsLongContent(cName) { var dirfd *os.File dirfd, err = os.Open(filepath.Dir(cPath)) if err != nil { return fuse.ToStatus(err) } defer dirfd.Close() // Delete content err = syscallcompat.Unlinkat(int(dirfd.Fd()), cName) if err != nil { return fuse.ToStatus(err) } // Delete ".name" err = nametransform.DeleteLongName(dirfd, cName) if err != nil { tlog.Warn.Printf("Unlink: could not delete .name file: %v", err) } return fuse.ToStatus(err) } err = syscall.Unlink(cPath) return fuse.ToStatus(err) }
// Symlink implements pathfs.Filesystem. func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (code fuse.Status) { tlog.Debug.Printf("Symlink(\"%s\", \"%s\")", target, linkName) if fs.isFiltered(linkName) { return fuse.EPERM } cPath, err := fs.getBackingPath(linkName) if err != nil { return fuse.ToStatus(err) } if fs.args.PlaintextNames { err = os.Symlink(target, cPath) return fuse.ToStatus(err) } // Symlinks are encrypted like file contents (GCM) and base64-encoded cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil) cTarget := base64.URLEncoding.EncodeToString(cBinTarget) // Handle long file name cName := filepath.Base(cPath) if nametransform.IsLongContent(cName) { var dirfd *os.File dirfd, err = os.Open(filepath.Dir(cPath)) if err != nil { return fuse.ToStatus(err) } defer dirfd.Close() // Create ".name" file err = fs.nameTransform.WriteLongName(dirfd, cName, linkName) if err != nil { return fuse.ToStatus(err) } // Create "gocryptfs.longfile." symlink // TODO use syscall.Symlinkat once it is available in Go err = syscall.Symlink(cTarget, cPath) if err != nil { nametransform.DeleteLongName(dirfd, cName) } } else { // Create symlink err = os.Symlink(cTarget, cPath) } if err != nil { return fuse.ToStatus(err) } // Set owner if fs.args.PreserveOwner { err = os.Lchown(cPath, int(context.Owner.Uid), int(context.Owner.Gid)) if err != nil { tlog.Warn.Printf("Mknod: Lchown failed: %v", err) } } return fuse.OK }
// Create implements pathfs.Filesystem. func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { if fs.isFiltered(path) { return nil, fuse.EPERM } iflags, writeOnly := fs.mangleOpenFlags(flags) cPath, err := fs.getBackingPath(path) if err != nil { return nil, fuse.ToStatus(err) } var fd *os.File cName := filepath.Base(cPath) // Handle long file name if nametransform.IsLongContent(cName) { var dirfd *os.File dirfd, err = os.Open(filepath.Dir(cPath)) if err != nil { return nil, fuse.ToStatus(err) } defer dirfd.Close() // Create ".name" err = fs.nameTransform.WriteLongName(dirfd, cName, path) if err != nil { return nil, fuse.ToStatus(err) } // Create content var fdRaw int fdRaw, err = syscallcompat.Openat(int(dirfd.Fd()), cName, iflags|os.O_CREATE, mode) if err != nil { nametransform.DeleteLongName(dirfd, cName) return nil, fuse.ToStatus(err) } fd = os.NewFile(uintptr(fdRaw), cName) } else { // Normal (short) file name fd, err = os.OpenFile(cPath, iflags|os.O_CREATE, os.FileMode(mode)) if err != nil { return nil, fuse.ToStatus(err) } } // Set owner if fs.args.PreserveOwner { err = fd.Chown(int(context.Owner.Uid), int(context.Owner.Gid)) if err != nil { tlog.Warn.Printf("Create: fd.Chown failed: %v", err) } } return NewFile(fd, writeOnly, fs) }
// Mknod implements pathfs.Filesystem. func (fs *FS) Mknod(path string, mode uint32, dev uint32, context *fuse.Context) (code fuse.Status) { if fs.isFiltered(path) { return fuse.EPERM } cPath, err := fs.getBackingPath(path) if err != nil { return fuse.ToStatus(err) } // Create ".name" file to store long file name cName := filepath.Base(cPath) if nametransform.IsLongContent(cName) { var dirfd *os.File dirfd, err = os.Open(filepath.Dir(cPath)) if err != nil { return fuse.ToStatus(err) } defer dirfd.Close() err = fs.nameTransform.WriteLongName(dirfd, cName, path) if err != nil { return fuse.ToStatus(err) } // Create "gocryptfs.longfile." device node err = syscallcompat.Mknodat(int(dirfd.Fd()), cName, mode, int(dev)) if err != nil { nametransform.DeleteLongName(dirfd, cName) } } else { // Create regular device node err = syscall.Mknod(cPath, mode, int(dev)) } if err != nil { return fuse.ToStatus(err) } // Set owner if fs.args.PreserveOwner { err = os.Lchown(cPath, int(context.Owner.Uid), int(context.Owner.Gid)) if err != nil { tlog.Warn.Printf("Mknod: Lchown failed: %v", err) } } return fuse.OK }
// Link implements pathfs.Filesystem. func (fs *FS) Link(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) } // Handle long file name cNewName := filepath.Base(cNewPath) if nametransform.IsLongContent(cNewName) { dirfd, err := os.Open(filepath.Dir(cNewPath)) if err != nil { return fuse.ToStatus(err) } defer dirfd.Close() err = fs.nameTransform.WriteLongName(dirfd, cNewName, newPath) if err != nil { return fuse.ToStatus(err) } // TODO Use syscall.Linkat once it is available in Go (it is not in Go // 1.6). err = syscall.Link(cOldPath, cNewPath) if err != nil { nametransform.DeleteLongName(dirfd, cNewName) return fuse.ToStatus(err) } } return fuse.ToStatus(os.Link(cOldPath, cNewPath)) }
// Mkdir implements pathfs.FileSystem func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fuse.Status) { if fs.isFiltered(newPath) { return fuse.EPERM } cPath, err := fs.getBackingPath(newPath) if err != nil { return fuse.ToStatus(err) } if fs.args.PlaintextNames { err = os.Mkdir(cPath, os.FileMode(mode)) // Set owner if fs.args.PreserveOwner { err = os.Lchown(cPath, int(context.Owner.Uid), int(context.Owner.Gid)) if err != nil { tlog.Warn.Printf("Mkdir: Lchown failed: %v", err) } } return fuse.ToStatus(err) } // We need write and execute permissions to create gocryptfs.diriv origMode := mode mode = mode | 0300 // Handle long file name cName := filepath.Base(cPath) if nametransform.IsLongContent(cName) { var dirfd *os.File dirfd, err = os.Open(filepath.Dir(cPath)) if err != nil { return fuse.ToStatus(err) } defer dirfd.Close() // Create ".name" err = fs.nameTransform.WriteLongName(dirfd, cName, newPath) if err != nil { return fuse.ToStatus(err) } // Create directory err = fs.mkdirWithIv(cPath, mode) if err != nil { nametransform.DeleteLongName(dirfd, cName) return fuse.ToStatus(err) } } else { err = fs.mkdirWithIv(cPath, mode) if err != nil { return fuse.ToStatus(err) } } // Set permissions back to what the user wanted if origMode != mode { err = os.Chmod(cPath, os.FileMode(origMode)) if err != nil { tlog.Warn.Printf("Mkdir: Chmod failed: %v", err) } } // Set owner if fs.args.PreserveOwner { err = os.Lchown(cPath, int(context.Owner.Uid), int(context.Owner.Gid)) if err != nil { tlog.Warn.Printf("Mkdir: Lchown 1 failed: %v", err) } err = os.Lchown(filepath.Join(cPath, nametransform.DirIVFilename), int(context.Owner.Uid), int(context.Owner.Gid)) if err != nil { tlog.Warn.Printf("Mkdir: Lchown 2 failed: %v", err) } } return fuse.OK }
// 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 }
// 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 }