Example #1
0
// 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)
}
Example #2
0
// 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
}
Example #3
0
// 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)
}
Example #4
0
// 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
}
Example #5
0
// 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))
}
Example #6
0
// 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
}
Example #7
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 #8
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
}