Josh Sorefdocs: spelling: e.g.e4a87f76 years agoHistory
406 lines · 12.8 KB · Go
RawDownload
// +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 filetype Node struct {	fusefs.Inode	node vfs.Node	fsys *FS} // Node types must be InodeEmbeddersvar _ fusefs.InodeEmbedder = (*Node)(nil) // newNode creates a new fusefs.Node from a vfs Nodefunc 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 directoryfunc (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 errorsfunc (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))