Skip to content

Commit

Permalink
Add symlink support to mount command for rclone#2975
Browse files Browse the repository at this point in the history
  • Loading branch information
dinoboy197 committed Aug 25, 2024
1 parent 1465629 commit a1bfcbb
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 1 deletion.
70 changes: 69 additions & 1 deletion cmd/mount/dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os"
"strings"
"syscall"
"time"

Expand Down Expand Up @@ -75,7 +76,13 @@ func (d *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.Lo
defer log.Trace(d, "name=%q", req.Name)("node=%+v, err=%v", &node, &err)
mnode, err := d.Dir.Stat(req.Name)
if err != nil {
return nil, translateError(err)
// might be a symlink; so check to see if it is
symlink_mnode, err := d.Dir.Stat(req.Name + ".rclonelink")
if err != nil {
return nil, translateError(err)
}
// symlink found
mnode = symlink_mnode
}
resp.EntryValid = time.Duration(d.fsys.opt.AttrTimeout)
// Check the mnode to see if it has a fuse Node cached
Expand Down Expand Up @@ -128,6 +135,11 @@ func (d *Dir) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error)
}
if node.IsDir() {
dirent.Type = fuse.DT_Dir
} else if strings.HasSuffix(name, ".rclonelink") {
// node represents a symlink; mark it as such, and remove the suffix from the node name
dirent.Type = fuse.DT_Link
dirent.Name = name[:len(name)-len(".rclonelink")]
defer log.Trace(d, name + " is a symlink; new name is " + dirent.Name)(name + " is a symlink; new name is " + dirent.Name, &itemsRead, &err)
}
dirents = append(dirents, dirent)
}
Expand Down Expand Up @@ -174,6 +186,17 @@ var _ fusefs.NodeRemover = (*Dir)(nil)
// 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)

_, err = d.Dir.Stat(req.Name)
if err != nil && err == vfs.ENOENT {
// check for removing symlink
err = d.Dir.RemoveName(req.Name + ".rclonelink")
if err != nil {
return translateError(err)
}
return nil
}

err = d.Dir.RemoveName(req.Name)
if err != nil {
return translateError(err)
Expand Down Expand Up @@ -201,6 +224,17 @@ func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fusefs
return fmt.Errorf("unknown Dir type %T", newDir)
}

_, err = d.Dir.Stat(req.OldName)
// if the file cannot be found, it might be because it is a symbolic link
if err != nil && err == vfs.ENOENT {
defer log.Trace(d, "Could not find file " + req.OldName + "; checking for symbolic link")("err=%v", &err)
err = d.Dir.Rename(req.OldName + ".rclonelink", req.NewName + ".rclonelink", destDir.Dir)
if err != nil {
return translateError(err)
}
return nil
}

err = d.Dir.Rename(req.OldName, req.NewName, destDir.Dir)
if err != nil {
return translateError(err)
Expand Down Expand Up @@ -268,3 +302,37 @@ func (d *Dir) Mknod(ctx context.Context, req *fuse.MknodRequest) (node fusefs.No
}
return node, nil
}

var _ fusefs.NodeSymlinker = (*Dir)(nil)

// create symbolic link
func (d *Dir) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (node fusefs.Node, err error) {
defer log.Trace(d, "target=%q; new_name=%q", req.Target, req.NewName)("node=%v, err=%v", &node, &err)

symlinkName := req.NewName + ".rclonelink"
file, err := d.Dir.Create(symlinkName, os.O_RDWR)
if err != nil {
defer log.Trace(d, "failed to create symlink file " + symlinkName)
return nil, translateError(err)
}
fh, err := file.Open(os.O_RDWR | os.O_CREATE)
if err != nil {
defer log.Trace(d, "failed to open symlink file " + symlinkName)
return nil, translateError(err)
}
node = &File{file, d.fsys}

_, err2 := fh.WriteAt([]byte(req.Target), 0)
if err2 != nil {
defer log.Trace(d, "failed to write to symlink file " + symlinkName)
return nil, translateError(err2)
}

err = fh.Release()
if err != nil {
defer log.Trace(d, "failed to close symlink file " + symlinkName)
return nil, translateError(err)
}

return node, err
}
27 changes: 27 additions & 0 deletions cmd/mount/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ package mount

import (
"context"
"io"
"os"
"strings"
"syscall"
"time"

Expand Down Expand Up @@ -33,6 +35,10 @@ func (f *File) Attr(ctx context.Context, a *fuse.Attr) (err error) {
a.Gid = f.VFS().Opt.GID
a.Uid = f.VFS().Opt.UID
a.Mode = os.FileMode(f.VFS().Opt.FilePerms)
if strings.HasSuffix(f.File.Name(), ".rclonelink") {
defer log.Trace(f, f.File.Name()+" is a symlink")("a=%+v, err=%v", a, &err)
a.Mode = 0777 | os.ModeSymlink
}
a.Size = Size
a.Atime = modTime
a.Mtime = modTime
Expand Down Expand Up @@ -129,3 +135,24 @@ func (f *File) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) er
}

var _ fusefs.NodeRemovexattrer = (*File)(nil)

var _ fusefs.NodeReadlinker = (*File)(nil)

// read symbolic link target
func (f *File) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (ret string, err error) {
defer log.Trace(f, "Requested to read link")("ret=%v, err=%v", &ret, &err)

handle, err := f.File.Open(syscall.O_RDONLY)
if err != nil {
return "", translateError(err)
}
data := make([]byte, f.File.Size())
_, err = handle.Read(data)
if err == io.EOF {
err = nil
} else if err != nil {
return "", translateError(err)
}

return string(data), nil
}

0 comments on commit a1bfcbb

Please sign in to comment.