1
0
mirror of https://github.com/rclone/rclone.git synced 2025-12-06 00:03:32 +00:00

vfs: add symlink support to VFS

This is somewhat limited in that it only resolves symlinks when files
are opened. This will work fine for the intended use in rclone mount,
but is inadequate for the other servers probably.
This commit is contained in:
Nick Craig-Wood
2024-10-17 16:18:37 +01:00
parent c0339327be
commit a5abe4b8b3
5 changed files with 268 additions and 16 deletions

View File

@@ -791,6 +791,9 @@ func (vfs *VFS) WriteFile(name string, data []byte, perm os.FileMode) (err error
}
// AddVirtual adds the object (file or dir) to the directory cache
//
// This is used by the vfs cache to insert objects that are uploading
// into the directory tree.
func (vfs *VFS) AddVirtual(remote string, size int64, isDir bool) (err error) {
remote = strings.TrimRight(remote, "/")
var dir *Dir
@@ -808,3 +811,85 @@ func (vfs *VFS) AddVirtual(remote string, size int64, isDir bool) (err error) {
dir.AddVirtual(leaf, size, false)
return nil
}
// Readlink returns the destination of the named symbolic link.
// If there is an error, it will be of type *PathError.
func (vfs *VFS) Readlink(name string) (s string, err error) {
if !vfs.Opt.Links {
fs.Errorf(nil, "symlinks not supported without the --links flag: %v", name)
return "", ENOSYS
}
node, err := vfs.Stat(name)
if err != nil {
return "", err
}
file, ok := node.(*File)
if !ok || !file.IsSymlink() {
return "", EINVAL // not a symlink
}
fd, err := file.Open(os.O_RDONLY | o_SYMLINK)
if err != nil {
return "", err
}
defer fs.CheckClose(fd, &err)
b, err := io.ReadAll(fd)
if err != nil {
return "", err
}
return string(b), nil
}
// CreateSymlink creates newname as a symbolic link to oldname.
// On Windows, a symlink to a non-existent oldname creates a file symlink;
// if oldname is later created as a directory the symlink will not work.
// It returns the node created
func (vfs *VFS) CreateSymlink(oldname, newname string) (Node, error) {
if !vfs.Opt.Links {
fs.Errorf(newname, "symlinks not supported without the --links flag")
return nil, ENOSYS
}
// Destination can't exist
_, err := vfs.Stat(newname)
if err == nil {
return nil, EEXIST
} else if err != ENOENT {
return nil, err
}
// Find the parent
dir, leaf, err := vfs.StatParent(newname)
if err != nil {
return nil, err
}
// Create the file node
flags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC | o_SYMLINK
file, err := dir.Create(leaf, flags)
if err != nil {
return nil, err
}
// Force the file to be a link
file.setSymlink()
// Open the file
fh, err := file.Open(flags)
if err != nil {
return nil, err
}
defer fs.CheckClose(fh, &err)
// Write the symlink data
_, err = fh.Write([]byte(strings.ReplaceAll(oldname, "\\", "/")))
return file, nil
}
// Symlink creates newname as a symbolic link to oldname.
// On Windows, a symlink to a non-existent oldname creates a file symlink;
// if oldname is later created as a directory the symlink will not work.
func (vfs *VFS) Symlink(oldname, newname string) error {
_, err := vfs.CreateSymlink(oldname, newname)
return err
}