mirror of
https://github.com/rclone/rclone.git
synced 2024-11-30 04:15:26 +01:00
e4a87f772f
Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>
407 lines
13 KiB
Go
407 lines
13 KiB
Go
// +build linux darwin,amd64
|
|
|
|
package mount2
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path"
|
|
"syscall"
|
|
|
|
fusefs "github.com/hanwen/go-fuse/v2/fs"
|
|
"github.com/hanwen/go-fuse/v2/fuse"
|
|
"github.com/rclone/rclone/cmd/mountlib"
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/log"
|
|
"github.com/rclone/rclone/vfs"
|
|
)
|
|
|
|
// Node represents a directory or file
|
|
type Node struct {
|
|
fusefs.Inode
|
|
node vfs.Node
|
|
fsys *FS
|
|
}
|
|
|
|
// Node types must be InodeEmbedders
|
|
var _ fusefs.InodeEmbedder = (*Node)(nil)
|
|
|
|
// newNode creates a new fusefs.Node from a vfs Node
|
|
func newNode(fsys *FS, vfsNode vfs.Node) (node *Node) {
|
|
// Check the vfsNode to see if it has a fuse Node cached
|
|
// We must return the same fuse nodes for vfs Nodes
|
|
node, ok := vfsNode.Sys().(*Node)
|
|
if ok {
|
|
return node
|
|
}
|
|
node = &Node{
|
|
node: vfsNode,
|
|
fsys: fsys,
|
|
}
|
|
// Cache the node for later
|
|
vfsNode.SetSys(node)
|
|
return node
|
|
}
|
|
|
|
// String used for pretty printing.
|
|
func (n *Node) String() string {
|
|
return n.node.Path()
|
|
}
|
|
|
|
// lookup a Node in a directory
|
|
func (n *Node) lookupVfsNodeInDir(leaf string) (vfsNode vfs.Node, errno syscall.Errno) {
|
|
dir, ok := n.node.(*vfs.Dir)
|
|
if !ok {
|
|
return nil, syscall.ENOTDIR
|
|
}
|
|
vfsNode, err := dir.Stat(leaf)
|
|
return vfsNode, translateError(err)
|
|
}
|
|
|
|
// // lookup a Dir given a path
|
|
// func (n *Node) lookupDir(path string) (dir *vfs.Dir, code fuse.Status) {
|
|
// node, code := fsys.lookupVfsNodeInDir(path)
|
|
// if !code.Ok() {
|
|
// return nil, code
|
|
// }
|
|
// dir, ok := n.(*vfs.Dir)
|
|
// if !ok {
|
|
// return nil, fuse.ENOTDIR
|
|
// }
|
|
// return dir, fuse.OK
|
|
// }
|
|
|
|
// // lookup a parent Dir given a path returning the dir and the leaf
|
|
// func (n *Node) lookupParentDir(filePath string) (leaf string, dir *vfs.Dir, code fuse.Status) {
|
|
// parentDir, leaf := path.Split(filePath)
|
|
// dir, code = fsys.lookupDir(parentDir)
|
|
// return leaf, dir, code
|
|
// }
|
|
|
|
// Statfs implements statistics for the filesystem that holds this
|
|
// Inode. If not defined, the `out` argument will zeroed with an OK
|
|
// result. This is because OSX filesystems must Statfs, or the mount
|
|
// will not work.
|
|
func (n *Node) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
|
|
defer log.Trace(n, "")("out=%+v", &out)
|
|
out = new(fuse.StatfsOut)
|
|
const blockSize = 4096
|
|
const fsBlocks = (1 << 50) / blockSize
|
|
out.Blocks = fsBlocks // Total data blocks in file system.
|
|
out.Bfree = fsBlocks // Free blocks in file system.
|
|
out.Bavail = fsBlocks // Free blocks in file system if you're not root.
|
|
out.Files = 1e9 // Total files in file system.
|
|
out.Ffree = 1e9 // Free files in file system.
|
|
out.Bsize = blockSize // Block size
|
|
out.NameLen = 255 // Maximum file name length?
|
|
out.Frsize = blockSize // Fragment size, smallest addressable data size in the file system.
|
|
mountlib.ClipBlocks(&out.Blocks)
|
|
mountlib.ClipBlocks(&out.Bfree)
|
|
mountlib.ClipBlocks(&out.Bavail)
|
|
return 0
|
|
}
|
|
|
|
var _ = (fusefs.NodeStatfser)((*Node)(nil))
|
|
|
|
// Getattr reads attributes for an Inode. The library will ensure that
|
|
// Mode and Ino are set correctly. For files that are not opened with
|
|
// FOPEN_DIRECTIO, Size should be set so it can be read correctly. If
|
|
// returning zeroed permissions, the default behavior is to change the
|
|
// mode of 0755 (directory) or 0644 (files). This can be switched off
|
|
// with the Options.NullPermissions setting. If blksize is unset, 4096
|
|
// is assumed, and the 'blocks' field is set accordingly.
|
|
func (n *Node) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.AttrOut) syscall.Errno {
|
|
n.fsys.setAttrOut(n.node, out)
|
|
return 0
|
|
}
|
|
|
|
var _ = (fusefs.NodeGetattrer)((*Node)(nil))
|
|
|
|
// Setattr sets attributes for an Inode.
|
|
func (n *Node) Setattr(ctx context.Context, f fusefs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) {
|
|
defer log.Trace(n, "in=%v", in)("out=%#v, errno=%v", &out, &errno)
|
|
var err error
|
|
n.fsys.setAttrOut(n.node, out)
|
|
size, ok := in.GetSize()
|
|
if ok {
|
|
err = n.node.Truncate(int64(size))
|
|
if err != nil {
|
|
return translateError(err)
|
|
}
|
|
out.Attr.Size = size
|
|
}
|
|
mtime, ok := in.GetMTime()
|
|
if ok {
|
|
err = n.node.SetModTime(mtime)
|
|
if err != nil {
|
|
return translateError(err)
|
|
}
|
|
out.Attr.Mtime = uint64(mtime.Unix())
|
|
out.Attr.Mtimensec = uint32(mtime.Nanosecond())
|
|
}
|
|
return 0
|
|
}
|
|
|
|
var _ = (fusefs.NodeSetattrer)((*Node)(nil))
|
|
|
|
// Open opens an Inode (of regular file type) for reading. It
|
|
// is optional but recommended to return a FileHandle.
|
|
func (n *Node) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
|
defer log.Trace(n, "flags=%#o", flags)("errno=%v", &errno)
|
|
// fuse flags are based off syscall flags as are os flags, so
|
|
// should be compatible
|
|
handle, err := n.node.Open(int(flags))
|
|
if err != nil {
|
|
return nil, 0, translateError(err)
|
|
}
|
|
// If size unknown then use direct io to read
|
|
if entry := n.node.DirEntry(); entry != nil && entry.Size() < 0 {
|
|
fuseFlags |= fuse.FOPEN_DIRECT_IO
|
|
}
|
|
return newFileHandle(handle, n.fsys), fuseFlags, 0
|
|
}
|
|
|
|
var _ = (fusefs.NodeOpener)((*Node)(nil))
|
|
|
|
// Lookup should find a direct child of a directory by the child's name. If
|
|
// the entry does not exist, it should return ENOENT and optionally
|
|
// set a NegativeTimeout in `out`. If it does exist, it should return
|
|
// attribute data in `out` and return the Inode for the child. A new
|
|
// inode can be created using `Inode.NewInode`. The new Inode will be
|
|
// added to the FS tree automatically if the return status is OK.
|
|
//
|
|
// If a directory does not implement NodeLookuper, the library looks
|
|
// for an existing child with the given name.
|
|
//
|
|
// The input to a Lookup is {parent directory, name string}.
|
|
//
|
|
// Lookup, if successful, must return an *Inode. Once the Inode is
|
|
// returned to the kernel, the kernel can issue further operations,
|
|
// such as Open or Getxattr on that node.
|
|
//
|
|
// A successful Lookup also returns an EntryOut. Among others, this
|
|
// contains file attributes (mode, size, mtime, etc.).
|
|
//
|
|
// FUSE supports other operations that modify the namespace. For
|
|
// example, the Symlink, Create, Mknod, Link methods all create new
|
|
// children in directories. Hence, they also return *Inode and must
|
|
// populate their fuse.EntryOut arguments.
|
|
func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) {
|
|
defer log.Trace(n, "name=%q", name)("inode=%v, attr=%v, errno=%v", &inode, &out, &errno)
|
|
vfsNode, errno := n.lookupVfsNodeInDir(name)
|
|
if errno != 0 {
|
|
return nil, errno
|
|
}
|
|
newNode := newNode(n.fsys, vfsNode)
|
|
|
|
// FIXME
|
|
// out.SetEntryTimeout(dt time.Duration)
|
|
// out.SetAttrTimeout(dt time.Duration)
|
|
n.fsys.setEntryOut(vfsNode, out)
|
|
|
|
return n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode}), 0
|
|
}
|
|
|
|
var _ = (fusefs.NodeLookuper)((*Node)(nil))
|
|
|
|
// Opendir opens a directory Inode for reading its
|
|
// contents. The actual reading is driven from Readdir, so
|
|
// this method is just for performing sanity/permission
|
|
// checks. The default is to return success.
|
|
func (n *Node) Opendir(ctx context.Context) syscall.Errno {
|
|
if !n.node.IsDir() {
|
|
return syscall.ENOTDIR
|
|
}
|
|
return 0
|
|
}
|
|
|
|
var _ = (fusefs.NodeOpendirer)((*Node)(nil))
|
|
|
|
type dirStream struct {
|
|
nodes []os.FileInfo
|
|
i int
|
|
}
|
|
|
|
// HasNext indicates if there are further entries. HasNext
|
|
// might be called on already closed streams.
|
|
func (ds *dirStream) HasNext() bool {
|
|
return ds.i < len(ds.nodes)
|
|
}
|
|
|
|
// Next retrieves the next entry. It is only called if HasNext
|
|
// has previously returned true. The Errno return may be used to
|
|
// indicate I/O errors
|
|
func (ds *dirStream) Next() (de fuse.DirEntry, errno syscall.Errno) {
|
|
// defer log.Trace(nil, "")("de=%+v, errno=%v", &de, &errno)
|
|
fi := ds.nodes[ds.i]
|
|
de = fuse.DirEntry{
|
|
// Mode is the file's mode. Only the high bits (e.g. S_IFDIR)
|
|
// are considered.
|
|
Mode: getMode(fi),
|
|
|
|
// Name is the basename of the file in the directory.
|
|
Name: path.Base(fi.Name()),
|
|
|
|
// Ino is the inode number.
|
|
Ino: 0, // FIXME
|
|
}
|
|
ds.i++
|
|
return de, 0
|
|
}
|
|
|
|
// Close releases resources related to this directory
|
|
// stream.
|
|
func (ds *dirStream) Close() {
|
|
}
|
|
|
|
var _ fusefs.DirStream = (*dirStream)(nil)
|
|
|
|
// Readdir opens a stream of directory entries.
|
|
//
|
|
// Readdir essentially returns a list of strings, and it is allowed
|
|
// for Readdir to return different results from Lookup. For example,
|
|
// you can return nothing for Readdir ("ls my-fuse-mount" is empty),
|
|
// while still implementing Lookup ("ls my-fuse-mount/a-specific-file"
|
|
// shows a single file).
|
|
//
|
|
// If a directory does not implement NodeReaddirer, a list of
|
|
// currently known children from the tree is returned. This means that
|
|
// static in-memory file systems need not implement NodeReaddirer.
|
|
func (n *Node) Readdir(ctx context.Context) (ds fusefs.DirStream, errno syscall.Errno) {
|
|
defer log.Trace(n, "")("ds=%v, errno=%v", &ds, &errno)
|
|
if !n.node.IsDir() {
|
|
return nil, syscall.ENOTDIR
|
|
}
|
|
fh, err := n.node.Open(os.O_RDONLY)
|
|
if err != nil {
|
|
return nil, translateError(err)
|
|
}
|
|
defer func() {
|
|
closeErr := fh.Close()
|
|
if errno == 0 && closeErr != nil {
|
|
errno = translateError(closeErr)
|
|
}
|
|
}()
|
|
items, err := fh.Readdir(-1)
|
|
if err != nil {
|
|
return nil, translateError(err)
|
|
}
|
|
return &dirStream{
|
|
nodes: items,
|
|
}, 0
|
|
}
|
|
|
|
var _ = (fusefs.NodeReaddirer)((*Node)(nil))
|
|
|
|
// Mkdir is similar to Lookup, but must create a directory entry and Inode.
|
|
// Default is to return EROFS.
|
|
func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (inode *fusefs.Inode, errno syscall.Errno) {
|
|
defer log.Trace(name, "mode=0%o", mode)("inode=%v, errno=%v", &inode, &errno)
|
|
dir, ok := n.node.(*vfs.Dir)
|
|
if !ok {
|
|
return nil, syscall.ENOTDIR
|
|
}
|
|
newDir, err := dir.Mkdir(name)
|
|
if err != nil {
|
|
return nil, translateError(err)
|
|
}
|
|
newNode := newNode(n.fsys, newDir)
|
|
n.fsys.setEntryOut(newNode.node, out)
|
|
newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode})
|
|
return newInode, 0
|
|
}
|
|
|
|
var _ = (fusefs.NodeMkdirer)((*Node)(nil))
|
|
|
|
// Create is similar to Lookup, but should create a new
|
|
// child. It typically also returns a FileHandle as a
|
|
// reference for future reads/writes.
|
|
// Default is to return EROFS.
|
|
func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fusefs.Inode, fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
|
defer log.Trace(n, "name=%q, flags=%#o, mode=%#o", name, flags, mode)("node=%v, fh=%v, flags=%#o, errno=%v", &node, &fh, &fuseFlags, &errno)
|
|
dir, ok := n.node.(*vfs.Dir)
|
|
if !ok {
|
|
return nil, nil, 0, syscall.ENOTDIR
|
|
}
|
|
// translate the fuse flags to os flags
|
|
osFlags := int(flags) | os.O_CREATE
|
|
file, err := dir.Create(name, osFlags)
|
|
if err != nil {
|
|
return nil, nil, 0, translateError(err)
|
|
}
|
|
handle, err := file.Open(osFlags)
|
|
if err != nil {
|
|
return nil, nil, 0, translateError(err)
|
|
}
|
|
fh = newFileHandle(handle, n.fsys)
|
|
// FIXME
|
|
// fh = &fusefs.WithFlags{
|
|
// File: fh,
|
|
// //FuseFlags: fuse.FOPEN_NONSEEKABLE,
|
|
// OpenFlags: flags,
|
|
// }
|
|
|
|
// Find the created node
|
|
vfsNode, errno := n.lookupVfsNodeInDir(name)
|
|
if errno != 0 {
|
|
return nil, nil, 0, errno
|
|
}
|
|
n.fsys.setEntryOut(vfsNode, out)
|
|
newNode := newNode(n.fsys, vfsNode)
|
|
fs.Debugf(nil, "attr=%#v", out.Attr)
|
|
newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode})
|
|
return newInode, fh, 0, 0
|
|
}
|
|
|
|
var _ = (fusefs.NodeCreater)((*Node)(nil))
|
|
|
|
// Unlink should remove a child from this directory. If the
|
|
// return status is OK, the Inode is removed as child in the
|
|
// FS tree automatically. Default is to return EROFS.
|
|
func (n *Node) Unlink(ctx context.Context, name string) (errno syscall.Errno) {
|
|
defer log.Trace(n, "name=%q", name)("errno=%v", &errno)
|
|
vfsNode, errno := n.lookupVfsNodeInDir(name)
|
|
if errno != 0 {
|
|
return errno
|
|
}
|
|
return translateError(vfsNode.Remove())
|
|
}
|
|
|
|
var _ = (fusefs.NodeUnlinker)((*Node)(nil))
|
|
|
|
// Rmdir is like Unlink but for directories.
|
|
// Default is to return EROFS.
|
|
func (n *Node) Rmdir(ctx context.Context, name string) (errno syscall.Errno) {
|
|
defer log.Trace(n, "name=%q", name)("errno=%v", &errno)
|
|
vfsNode, errno := n.lookupVfsNodeInDir(name)
|
|
if errno != 0 {
|
|
return errno
|
|
}
|
|
return translateError(vfsNode.Remove())
|
|
}
|
|
|
|
var _ = (fusefs.NodeRmdirer)((*Node)(nil))
|
|
|
|
// Rename should move a child from one directory to a different
|
|
// one. The change is effected in the FS tree if the return status is
|
|
// OK. Default is to return EROFS.
|
|
func (n *Node) Rename(ctx context.Context, oldName string, newParent fusefs.InodeEmbedder, newName string, flags uint32) (errno syscall.Errno) {
|
|
defer log.Trace(n, "oldName=%q, newParent=%v, newName=%q", oldName, newParent, newName)("errno=%v", &errno)
|
|
oldDir, ok := n.node.(*vfs.Dir)
|
|
if !ok {
|
|
return syscall.ENOTDIR
|
|
}
|
|
newParentNode, ok := newParent.(*Node)
|
|
if !ok {
|
|
fs.Errorf(n, "newParent was not a *Node")
|
|
return syscall.EIO
|
|
}
|
|
newDir, ok := newParentNode.node.(*vfs.Dir)
|
|
if !ok {
|
|
return syscall.ENOTDIR
|
|
}
|
|
return translateError(oldDir.Rename(oldName, newName, newDir))
|
|
}
|
|
|
|
var _ = (fusefs.NodeRenamer)((*Node)(nil))
|