rclone/cmd/mount/dir.go
Filipe Azevedo 19c6081de2 cmount,mount,mount2: Introduce symlink support
We enable symlink support using the --links command line switch.
When symlink support is enabled, the mount backends will translate
the name of the vfs symlinks files (truncating their rclonelink suffix).
Also, operations like rename, symlink etc does not needs the rclonelink
suffix, it is handled internally to pass it to the underlying low level
VFS.
When symlink support is disabled, Symlink and Readlink functions will
transparently manage ".rclonelink" files as regular files.

Fixes #2975
2023-01-04 21:56:15 +01:00

358 lines
9.5 KiB
Go

//go:build linux || freebsd
// +build linux freebsd
package mount
import (
"context"
"fmt"
"io"
"os"
"path"
"strings"
"syscall"
"time"
"bazil.org/fuse"
fusefs "bazil.org/fuse/fs"
"github.com/rclone/rclone/cmd/mountlib"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/log"
"github.com/rclone/rclone/vfs"
)
// Dir represents a directory entry
type Dir struct {
*vfs.Dir
fsys *FS
}
// Check interface satisfied
var _ fusefs.Node = (*Dir)(nil)
func fallbackStat(dir *vfs.Dir, leaf string) (node vfs.Node, err error) {
node, err = dir.Stat(leaf)
if err == vfs.ENOENT && dir.VFS().Opt.Links {
node, err = dir.Stat(leaf + fs.LinkSuffix)
}
return node, err
}
// Attr updates the attributes of a directory
func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) (err error) {
defer log.Trace(d, "")("attr=%+v, err=%v", a, &err)
a.Valid = d.fsys.opt.AttrTimeout
a.Gid = d.VFS().Opt.GID
a.Uid = d.VFS().Opt.UID
a.Mode = d.Mode()
modTime := d.ModTime()
a.Atime = modTime
a.Mtime = modTime
a.Ctime = modTime
// FIXME include Valid so get some caching?
// FIXME fs.Debugf(d.path, "Dir.Attr %+v", a)
return nil
}
// Check interface satisfied
var _ fusefs.NodeSetattrer = (*Dir)(nil)
// Setattr handles attribute changes from FUSE. Currently supports ModTime only.
func (d *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) (err error) {
defer log.Trace(d, "stat=%+v", req)("err=%v", &err)
if d.VFS().Opt.NoModTime {
return nil
}
if req.Valid.MtimeNow() {
err = d.SetModTime(time.Now())
} else if req.Valid.Mtime() {
err = d.SetModTime(req.Mtime)
}
return translateError(err)
}
// Check interface satisfied
var _ fusefs.NodeRequestLookuper = (*Dir)(nil)
// Lookup looks up a specific entry in the receiver.
//
// Lookup should return a Node corresponding to the entry. If the
// name does not exist in the directory, Lookup should return ENOENT.
//
// Lookup need not to handle the names "." and "..".
func (d *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (node fusefs.Node, err error) {
defer log.Trace(d, "name=%q", req.Name)("node=%+v, err=%v", &node, &err)
mnode, err := fallbackStat(d.Dir, req.Name)
if err != nil {
return nil, translateError(err)
}
resp.EntryValid = d.fsys.opt.AttrTimeout
// Check the mnode to see if it has a fuse Node cached
// We must return the same fuse nodes for vfs Nodes
node, ok := mnode.Sys().(fusefs.Node)
if ok {
return node, nil
}
switch x := mnode.(type) {
case *vfs.File:
node = &File{x, d.fsys}
case *vfs.Dir:
node = &Dir{x, d.fsys}
default:
panic("bad type")
}
// Cache the node for later
mnode.SetSys(node)
return node, nil
}
// Check interface satisfied
var _ fusefs.HandleReadDirAller = (*Dir)(nil)
// ReadDirAll reads the contents of the directory
func (d *Dir) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
itemsRead := -1
defer log.Trace(d, "")("item=%d, err=%v", &itemsRead, &err)
items, err := d.Dir.ReadDirAll()
if err != nil {
return nil, translateError(err)
}
dirents = append(dirents, fuse.Dirent{
Type: fuse.DT_Dir,
Name: ".",
}, fuse.Dirent{
Type: fuse.DT_Dir,
Name: "..",
})
for _, node := range items {
name, isLink := d.VFS().TrimSymlink(node.Name())
if len(name) > mountlib.MaxLeafSize {
fs.Errorf(d, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name)
continue
}
var dirent = fuse.Dirent{
// Inode FIXME ???
Type: fuse.DT_File,
Name: name,
}
if isLink {
dirent.Type = fuse.DT_Link
} else if node.IsDir() {
dirent.Type = fuse.DT_Dir
}
dirents = append(dirents, dirent)
}
itemsRead = len(dirents)
return dirents, nil
}
var _ fusefs.NodeCreater = (*Dir)(nil)
// Create makes a new file
func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (node fusefs.Node, handle fusefs.Handle, err error) {
defer log.Trace(d, "name=%q", req.Name)("node=%v, handle=%v, err=%v", &node, &handle, &err)
// translate the fuse flags to os flags
osFlags := int(req.Flags) | os.O_CREATE
file, err := d.Dir.Create(req.Name, osFlags)
if err != nil {
return nil, nil, translateError(err)
}
fh, err := file.Open(osFlags)
if err != nil {
return nil, nil, translateError(err)
}
node = &File{file, d.fsys}
file.SetSys(node) // cache the FUSE node for later
return node, &FileHandle{fh}, err
}
var _ fusefs.NodeMkdirer = (*Dir)(nil)
// Mkdir creates a new directory
func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (node fusefs.Node, err error) {
defer log.Trace(d, "name=%q", req.Name)("node=%+v, err=%v", &node, &err)
dir, err := d.Dir.Mkdir(req.Name)
if err != nil {
return nil, translateError(err)
}
node = &Dir{dir, d.fsys}
dir.SetSys(node) // cache the FUSE node for later
return node, nil
}
var _ fusefs.NodeRemover = (*Dir)(nil)
// Remove removes the entry with the given name from
// the receiver, which must be a directory. The entry to be removed
// may correspond to a file (unlink) or to a directory (rmdir).
func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) (err error) {
defer log.Trace(d, "name=%q", req.Name)("err=%v", &err)
name := req.Name
if d.VFS().Opt.Links {
node, err := fallbackStat(d.Dir, name)
if err == nil {
name = node.Name()
}
}
err = d.Dir.RemoveName(name)
if err != nil {
return translateError(err)
}
return nil
}
// Invalidate a leaf in a directory
func (d *Dir) invalidateEntry(dirNode fusefs.Node, leaf string) {
fs.Debugf(dirNode, "Invalidating %q", leaf)
err := d.fsys.server.InvalidateEntry(dirNode, leaf)
if err != nil {
fs.Debugf(dirNode, "Failed to invalidate %q: %v", leaf, err)
}
}
// Check interface satisfied
var _ fusefs.NodeRenamer = (*Dir)(nil)
// Rename the file
func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fusefs.Node) (err error) {
defer log.Trace(d, "oldName=%q, newName=%q, newDir=%+v", req.OldName, req.NewName, newDir)("err=%v", &err)
destDir, ok := newDir.(*Dir)
if !ok {
return fmt.Errorf("unknown Dir type %T", newDir)
}
oldName := req.OldName
newName := req.NewName
if d.VFS().Opt.Links {
node, err := fallbackStat(d.Dir, oldName)
if err == nil {
oldName = node.Name()
if strings.HasSuffix(oldName, fs.LinkSuffix) {
newName += fs.LinkSuffix
}
}
}
err = d.Dir.Rename(oldName, newName, destDir.Dir)
if err != nil {
return translateError(err)
}
// Invalidate the new directory entry so it gets re-read (in
// the background otherwise we cause a deadlock)
//
// See https://github.com/rclone/rclone/issues/4977 for why
go d.invalidateEntry(newDir, req.NewName)
//go d.invalidateEntry(d, req.OldName)
return nil
}
// Check interface satisfied
var _ fusefs.NodeFsyncer = (*Dir)(nil)
// Fsync the directory
func (d *Dir) Fsync(ctx context.Context, req *fuse.FsyncRequest) (err error) {
defer log.Trace(d, "")("err=%v", &err)
err = d.Dir.Sync()
if err != nil {
return translateError(err)
}
return nil
}
// Check interface satisfied
var _ fusefs.NodeLinker = (*Dir)(nil)
// Link creates a new directory entry in the receiver based on an
// existing Node. Receiver must be a directory.
func (d *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fusefs.Node) (newNode fusefs.Node, err error) {
defer log.Trace(d, "req=%v, old=%v", req, old)("new=%v, err=%v", &newNode, &err)
return nil, syscall.ENOSYS
}
var _ fusefs.NodeSymlinker = (*Dir)(nil)
// Symlink create a symbolic link.
func (d *Dir) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (node fusefs.Node, err error) {
defer log.Trace(d, "Requested to symlink newname=%v, target=%v", req.NewName, req.Target)("node=%v, err=%v", &node, &err)
newName := path.Join(d.Path(), req.NewName)
target := req.Target
if d.VFS().Opt.Links {
// The user must NOT provide .rclonelink suffix
if strings.HasSuffix(newName, fs.LinkSuffix) {
fs.Errorf(nil, "Invalid name suffix provided: %v", newName)
return nil, vfs.EINVAL
}
newName += fs.LinkSuffix
} else {
// The user must provide .rclonelink suffix
if !strings.HasSuffix(newName, fs.LinkSuffix) {
fs.Errorf(nil, "Invalid name suffix provided: %v", newName)
return nil, vfs.EINVAL
}
}
// Add target suffix when linking to a link
if !strings.HasSuffix(target, fs.LinkSuffix) {
vnode, err := fallbackStat(d.Dir, target)
if err == nil && strings.HasSuffix(vnode.Name(), fs.LinkSuffix) {
target += fs.LinkSuffix
}
}
err = d.VFS().Symlink(target, newName)
if err != nil {
return nil, err
}
n, err := d.Stat(path.Base(newName))
if err != nil {
return nil, err
}
node = &File{n.(*vfs.File), d.fsys}
return node, nil
}
// Check interface satisfied
var _ fusefs.NodeMknoder = (*Dir)(nil)
// Mknod is called to create a file. Since we define create this will
// be called in preference, however NFS likes to call it for some
// reason. We don't actually create a file here just the Node.
func (d *Dir) Mknod(ctx context.Context, req *fuse.MknodRequest) (node fusefs.Node, err error) {
defer log.Trace(d, "name=%v, mode=%d, rdev=%d", req.Name, req.Mode, req.Rdev)("node=%v, err=%v", &node, &err)
if req.Rdev != 0 {
fs.Errorf(d, "Can't create device node %q", req.Name)
return nil, fuse.EIO
}
var cReq = fuse.CreateRequest{
Name: req.Name,
Flags: fuse.OpenFlags(os.O_CREATE | os.O_WRONLY),
Mode: req.Mode,
Umask: req.Umask,
}
var cResp fuse.CreateResponse
node, handle, err := d.Create(ctx, &cReq, &cResp)
if err != nil {
return nil, err
}
err = handle.(io.Closer).Close()
if err != nil {
return nil, err
}
return node, nil
}