mirror of
https://github.com/rclone/rclone.git
synced 2024-12-22 07:01:46 +01:00
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
This commit is contained in:
parent
1891b6848b
commit
19c6081de2
132
cmd/cmount/fs.go
132
cmd/cmount/fs.go
@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -95,8 +96,11 @@ func (fsys *FS) closeHandle(fh uint64) (errc int) {
|
||||
}
|
||||
|
||||
// lookup a Node given a path
|
||||
func (fsys *FS) lookupNode(path string) (node vfs.Node, errc int) {
|
||||
func (fsys *FS) lookupNode(path string) (vfs.Node, int) {
|
||||
node, err := fsys.VFS.Stat(path)
|
||||
if err == vfs.ENOENT && fsys.VFS.Opt.Links {
|
||||
node, err = fsys.VFS.Stat(path + fs.LinkSuffix)
|
||||
}
|
||||
return node, translateError(err)
|
||||
}
|
||||
|
||||
@ -117,6 +121,13 @@ func (fsys *FS) lookupDir(path string) (dir *vfs.Dir, errc int) {
|
||||
func (fsys *FS) lookupParentDir(filePath string) (leaf string, dir *vfs.Dir, errc int) {
|
||||
parentDir, leaf := path.Split(filePath)
|
||||
dir, errc = fsys.lookupDir(parentDir)
|
||||
// Try to get real leaf for symlinks
|
||||
if fsys.VFS.Opt.Links {
|
||||
node, e := fsys.lookupNode(filePath)
|
||||
if e == 0 {
|
||||
leaf = node.Name()
|
||||
}
|
||||
}
|
||||
return leaf, dir, errc
|
||||
}
|
||||
|
||||
@ -153,15 +164,9 @@ func (fsys *FS) stat(node vfs.Node, stat *fuse.Stat_t) (errc int) {
|
||||
Size := uint64(node.Size())
|
||||
Blocks := (Size + 511) / 512
|
||||
modTime := node.ModTime()
|
||||
Mode := node.Mode().Perm()
|
||||
if node.IsDir() {
|
||||
Mode |= fuse.S_IFDIR
|
||||
} else {
|
||||
Mode |= fuse.S_IFREG
|
||||
}
|
||||
//stat.Dev = 1
|
||||
stat.Ino = node.Inode() // FIXME do we need to set the inode number?
|
||||
stat.Mode = uint32(Mode)
|
||||
stat.Mode = getMode(node)
|
||||
stat.Nlink = 1
|
||||
stat.Uid = fsys.VFS.Opt.UID
|
||||
stat.Gid = fsys.VFS.Opt.GID
|
||||
@ -252,7 +257,7 @@ func (fsys *FS) Readdir(dirPath string,
|
||||
fill(".", nil, 0)
|
||||
fill("..", nil, 0)
|
||||
for _, node := range nodes {
|
||||
name := node.Name()
|
||||
name, _ := fsys.VFS.TrimSymlink(node.Name())
|
||||
if len(name) > mountlib.MaxLeafSize {
|
||||
fs.Errorf(dirPath, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name)
|
||||
continue
|
||||
@ -330,13 +335,15 @@ func (fsys *FS) CreateEx(filePath string, mode uint32, fi *fuse.FileInfo_t) (err
|
||||
if errc != 0 {
|
||||
return errc
|
||||
}
|
||||
file, err := parentDir.Create(leaf, fi.Flags)
|
||||
// translate the fuse flags to os flags
|
||||
osFlags := translateOpenFlags(fi.Flags) | os.O_CREATE
|
||||
// translate the fuse mode to os mode
|
||||
//osMode := getFileMode(mode)
|
||||
file, err := parentDir.Create(leaf, osFlags)
|
||||
if err != nil {
|
||||
return translateError(err)
|
||||
}
|
||||
// translate the fuse flags to os flags
|
||||
flags := translateOpenFlags(fi.Flags) | os.O_CREATE
|
||||
handle, err := file.Open(flags)
|
||||
handle, err := file.Open(osFlags)
|
||||
if err != nil {
|
||||
return translateError(err)
|
||||
}
|
||||
@ -456,6 +463,18 @@ func (fsys *FS) Rmdir(dirPath string) (errc int) {
|
||||
// Rename renames a file.
|
||||
func (fsys *FS) Rename(oldPath string, newPath string) (errc int) {
|
||||
defer log.Trace(oldPath, "newPath=%q", newPath)("errc=%d", &errc)
|
||||
|
||||
if fsys.VFS.Opt.Links {
|
||||
node, e := fsys.lookupNode(oldPath)
|
||||
|
||||
if e == 0 {
|
||||
if strings.HasSuffix(node.Name(), fs.LinkSuffix) {
|
||||
oldPath += fs.LinkSuffix
|
||||
newPath += fs.LinkSuffix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return translateError(fsys.VFS.Rename(oldPath, newPath))
|
||||
}
|
||||
|
||||
@ -505,14 +524,58 @@ func (fsys *FS) Link(oldpath string, newpath string) (errc int) {
|
||||
|
||||
// Symlink creates a symbolic link.
|
||||
func (fsys *FS) Symlink(target string, newpath string) (errc int) {
|
||||
defer log.Trace(target, "newpath=%q", newpath)("errc=%d", &errc)
|
||||
return -fuse.ENOSYS
|
||||
defer log.Trace(fsys, "Requested to symlink newpath=%q, target=%q", newpath, target)("errc=%d", &errc)
|
||||
|
||||
if fsys.VFS.Opt.Links {
|
||||
// The user must NOT provide .rclonelink suffix
|
||||
if strings.HasSuffix(newpath, fs.LinkSuffix) {
|
||||
fs.Errorf(nil, "Invalid name suffix provided: %v", newpath)
|
||||
return translateError(vfs.EINVAL)
|
||||
}
|
||||
|
||||
newpath += fs.LinkSuffix
|
||||
} else {
|
||||
// The user must provide .rclonelink suffix
|
||||
if !strings.HasSuffix(newpath, fs.LinkSuffix) {
|
||||
fs.Errorf(nil, "Invalid name suffix provided: %v", newpath)
|
||||
return translateError(vfs.EINVAL)
|
||||
}
|
||||
}
|
||||
|
||||
// Add target suffix when linking to a link
|
||||
if !strings.HasSuffix(target, fs.LinkSuffix) {
|
||||
vnode, err := fsys.lookupNode(target)
|
||||
if err == 0 && strings.HasSuffix(vnode.Name(), fs.LinkSuffix) {
|
||||
target += fs.LinkSuffix
|
||||
}
|
||||
}
|
||||
|
||||
return translateError(fsys.VFS.Symlink(target, newpath))
|
||||
}
|
||||
|
||||
// Readlink reads the target of a symbolic link.
|
||||
func (fsys *FS) Readlink(path string) (errc int, linkPath string) {
|
||||
defer log.Trace(path, "")("linkPath=%q, errc=%d", &linkPath, &errc)
|
||||
return -fuse.ENOSYS, ""
|
||||
defer log.Trace(fsys, "Requested to read link")("errc=%v, linkPath=%q", &errc, linkPath)
|
||||
|
||||
if fsys.VFS.Opt.Links {
|
||||
// The user must NOT provide .rclonelink suffix
|
||||
if strings.HasSuffix(path, fs.LinkSuffix) {
|
||||
fs.Errorf(nil, "Invalid name suffix provided: %v", path)
|
||||
return translateError(vfs.EINVAL), ""
|
||||
}
|
||||
|
||||
path += fs.LinkSuffix
|
||||
} else {
|
||||
// The user must provide .rclonelink suffix
|
||||
if !strings.HasSuffix(path, fs.LinkSuffix) {
|
||||
fs.Errorf(nil, "Invalid name suffix provided: %v", path)
|
||||
return translateError(vfs.EINVAL), ""
|
||||
}
|
||||
}
|
||||
|
||||
linkPath, err := fsys.VFS.Readlink(path)
|
||||
linkPath, _ = fsys.VFS.TrimSymlink(linkPath)
|
||||
return translateError(err), linkPath
|
||||
}
|
||||
|
||||
// Chmod changes the permission bits of a file.
|
||||
@ -627,6 +690,41 @@ func translateOpenFlags(inFlags int) (outFlags int) {
|
||||
return outFlags
|
||||
}
|
||||
|
||||
// get the Mode from a vfs Node
|
||||
func getMode(node os.FileInfo) uint32 {
|
||||
vfsMode := node.Mode()
|
||||
Mode := vfsMode.Perm()
|
||||
if vfsMode&os.ModeDir != 0 {
|
||||
Mode |= fuse.S_IFDIR
|
||||
} else if vfsMode&os.ModeSymlink != 0 {
|
||||
Mode |= fuse.S_IFLNK
|
||||
} else if vfsMode&os.ModeNamedPipe != 0 {
|
||||
Mode |= fuse.S_IFIFO
|
||||
} else {
|
||||
Mode |= fuse.S_IFREG
|
||||
}
|
||||
return uint32(Mode)
|
||||
}
|
||||
|
||||
// convert fuse mode to os.FileMode
|
||||
// func getFileMode(mode uint32) os.FileMode {
|
||||
// osMode := os.FileMode(0)
|
||||
// if mode&fuse.S_IFDIR != 0 {
|
||||
// mode ^= fuse.S_IFDIR
|
||||
// osMode |= os.ModeDir
|
||||
// } else if mode&fuse.S_IFREG != 0 {
|
||||
// mode ^= fuse.S_IFREG
|
||||
// } else if mode&fuse.S_IFLNK != 0 {
|
||||
// mode ^= fuse.S_IFLNK
|
||||
// osMode |= os.ModeSymlink
|
||||
// } else if mode&fuse.S_IFIFO != 0 {
|
||||
// mode ^= fuse.S_IFIFO
|
||||
// osMode |= os.ModeNamedPipe
|
||||
// }
|
||||
// osMode |= os.FileMode(mode)
|
||||
// return osMode
|
||||
// }
|
||||
|
||||
// Make sure interfaces are satisfied
|
||||
var (
|
||||
_ fuse.FileSystemInterface = (*FS)(nil)
|
||||
|
103
cmd/mount/dir.go
103
cmd/mount/dir.go
@ -8,6 +8,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@ -28,13 +30,21 @@ type Dir struct {
|
||||
// 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 = os.ModeDir | d.VFS().Opt.DirPerms
|
||||
a.Mode = d.Mode()
|
||||
modTime := d.ModTime()
|
||||
a.Atime = modTime
|
||||
a.Mtime = modTime
|
||||
@ -74,7 +84,7 @@ var _ fusefs.NodeRequestLookuper = (*Dir)(nil)
|
||||
// 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 := d.Dir.Stat(req.Name)
|
||||
mnode, err := fallbackStat(d.Dir, req.Name)
|
||||
if err != nil {
|
||||
return nil, translateError(err)
|
||||
}
|
||||
@ -117,7 +127,7 @@ func (d *Dir) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error)
|
||||
Name: "..",
|
||||
})
|
||||
for _, node := range items {
|
||||
name := node.Name()
|
||||
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
|
||||
@ -127,7 +137,9 @@ func (d *Dir) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error)
|
||||
Type: fuse.DT_File,
|
||||
Name: name,
|
||||
}
|
||||
if node.IsDir() {
|
||||
if isLink {
|
||||
dirent.Type = fuse.DT_Link
|
||||
} else if node.IsDir() {
|
||||
dirent.Type = fuse.DT_Dir
|
||||
}
|
||||
dirents = append(dirents, dirent)
|
||||
@ -141,11 +153,13 @@ 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)
|
||||
file, err := d.Dir.Create(req.Name, int(req.Flags))
|
||||
// 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(int(req.Flags) | os.O_CREATE)
|
||||
fh, err := file.Open(osFlags)
|
||||
if err != nil {
|
||||
return nil, nil, translateError(err)
|
||||
}
|
||||
@ -175,7 +189,18 @@ 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.RemoveName(req.Name)
|
||||
|
||||
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)
|
||||
}
|
||||
@ -202,7 +227,22 @@ func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fusefs
|
||||
return fmt.Errorf("unknown Dir type %T", newDir)
|
||||
}
|
||||
|
||||
err = d.Dir.Rename(req.OldName, req.NewName, destDir.Dir)
|
||||
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)
|
||||
}
|
||||
@ -240,6 +280,53 @@ func (d *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fusefs.Node)
|
||||
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)
|
||||
|
||||
|
@ -5,11 +5,14 @@ package mount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"bazil.org/fuse"
|
||||
fusefs "bazil.org/fuse/fs"
|
||||
"github.com/rclone/rclone/fs"
|
||||
"github.com/rclone/rclone/fs/log"
|
||||
"github.com/rclone/rclone/vfs"
|
||||
)
|
||||
@ -32,7 +35,7 @@ func (f *File) Attr(ctx context.Context, a *fuse.Attr) (err error) {
|
||||
Blocks := (Size + 511) / 512
|
||||
a.Gid = f.VFS().Opt.GID
|
||||
a.Uid = f.VFS().Opt.UID
|
||||
a.Mode = f.VFS().Opt.FilePerms
|
||||
a.Mode = f.File.Mode() &^ os.ModeAppend
|
||||
a.Size = Size
|
||||
a.Atime = modTime
|
||||
a.Mtime = modTime
|
||||
@ -126,3 +129,32 @@ func (f *File) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) er
|
||||
}
|
||||
|
||||
var _ fusefs.NodeRemovexattrer = (*File)(nil)
|
||||
|
||||
var _ fusefs.NodeReadlinker = (*File)(nil)
|
||||
|
||||
// Readlink 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)
|
||||
|
||||
path := f.Path()
|
||||
|
||||
if f.VFS().Opt.Links {
|
||||
// The user must NOT provide .rclonelink suffix
|
||||
// if strings.HasSuffix(path, fs.LinkSuffix) {
|
||||
// fs.Errorf(nil, "Invalid name suffix provided: %v", path)
|
||||
// return "", vfs.EINVAL
|
||||
// }
|
||||
|
||||
// path += fs.LinkSuffix
|
||||
} else {
|
||||
// The user must provide .rclonelink suffix
|
||||
if !strings.HasSuffix(path, fs.LinkSuffix) {
|
||||
fs.Errorf(nil, "Invalid name suffix provided: %v", path)
|
||||
return "", vfs.EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
ret, err = f.VFS().Readlink(path)
|
||||
ret, _ = f.VFS().TrimSymlink(ret)
|
||||
return ret, err
|
||||
}
|
||||
|
@ -51,15 +51,39 @@ func (f *FS) SetDebug(debug bool) {
|
||||
|
||||
// get the Mode from a vfs Node
|
||||
func getMode(node os.FileInfo) uint32 {
|
||||
Mode := node.Mode().Perm()
|
||||
if node.IsDir() {
|
||||
vfsMode := node.Mode()
|
||||
Mode := vfsMode.Perm()
|
||||
if vfsMode&os.ModeDir != 0 {
|
||||
Mode |= fuse.S_IFDIR
|
||||
} else if vfsMode&os.ModeSymlink != 0 {
|
||||
Mode |= fuse.S_IFLNK
|
||||
} else if vfsMode&os.ModeNamedPipe != 0 {
|
||||
Mode |= fuse.S_IFIFO
|
||||
} else {
|
||||
Mode |= fuse.S_IFREG
|
||||
}
|
||||
return uint32(Mode)
|
||||
}
|
||||
|
||||
// convert fuse mode to os.FileMode
|
||||
// func getFileMode(mode uint32) os.FileMode {
|
||||
// osMode := os.FileMode(0)
|
||||
// if mode&fuse.S_IFDIR != 0 {
|
||||
// mode ^= fuse.S_IFDIR
|
||||
// osMode |= os.ModeDir
|
||||
// } else if mode&fuse.S_IFREG != 0 {
|
||||
// mode ^= fuse.S_IFREG
|
||||
// } else if mode&fuse.S_IFLNK != 0 {
|
||||
// mode ^= fuse.S_IFLNK
|
||||
// osMode |= os.ModeSymlink
|
||||
// } else if mode&fuse.S_IFIFO != 0 {
|
||||
// mode ^= fuse.S_IFIFO
|
||||
// osMode |= os.ModeNamedPipe
|
||||
// }
|
||||
// osMode |= os.FileMode(mode)
|
||||
// return osMode
|
||||
// }
|
||||
|
||||
// fill in attr from node
|
||||
func setAttr(node vfs.Node, attr *fuse.Attr) {
|
||||
Size := uint64(node.Size())
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
fusefs "github.com/hanwen/go-fuse/v2/fs"
|
||||
@ -56,6 +57,9 @@ func (n *Node) lookupVfsNodeInDir(leaf string) (vfsNode vfs.Node, errno syscall.
|
||||
return nil, syscall.ENOTDIR
|
||||
}
|
||||
vfsNode, err := dir.Stat(leaf)
|
||||
if err == vfs.ENOENT && dir.VFS().Opt.Links {
|
||||
vfsNode, err = dir.Stat(leaf + fs.LinkSuffix)
|
||||
}
|
||||
return vfsNode, translateError(err)
|
||||
}
|
||||
|
||||
@ -219,6 +223,7 @@ func (n *Node) Opendir(ctx context.Context) syscall.Errno {
|
||||
var _ = (fusefs.NodeOpendirer)((*Node)(nil))
|
||||
|
||||
type dirStream struct {
|
||||
fsys *FS
|
||||
nodes []os.FileInfo
|
||||
i int
|
||||
}
|
||||
@ -250,13 +255,14 @@ func (ds *dirStream) Next() (de fuse.DirEntry, errno syscall.Errno) {
|
||||
}, 0
|
||||
}
|
||||
fi := ds.nodes[ds.i-2]
|
||||
name, _ := ds.fsys.VFS.TrimSymlink(path.Base(fi.Name()))
|
||||
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()),
|
||||
Name: name,
|
||||
|
||||
// Ino is the inode number.
|
||||
Ino: 0, // FIXME
|
||||
@ -304,6 +310,7 @@ func (n *Node) Readdir(ctx context.Context) (ds fusefs.DirStream, errno syscall.
|
||||
}
|
||||
return &dirStream{
|
||||
nodes: items,
|
||||
fsys: n.fsys,
|
||||
}, 0
|
||||
}
|
||||
|
||||
@ -341,6 +348,8 @@ func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint3
|
||||
}
|
||||
// translate the fuse flags to os flags
|
||||
osFlags := int(flags) | os.O_CREATE
|
||||
// translate the fuse mode to os mode
|
||||
//osMode := getFileMode(mode)
|
||||
file, err := dir.Create(name, osFlags)
|
||||
if err != nil {
|
||||
return nil, nil, 0, translateError(err)
|
||||
@ -416,7 +425,101 @@ func (n *Node) Rename(ctx context.Context, oldName string, newParent fusefs.Inod
|
||||
if !ok {
|
||||
return syscall.ENOTDIR
|
||||
}
|
||||
if oldDir.VFS().Opt.Links {
|
||||
node, err := n.lookupVfsNodeInDir(oldName)
|
||||
|
||||
if err == 0 {
|
||||
oldName = node.Name()
|
||||
|
||||
if strings.HasSuffix(oldName, fs.LinkSuffix) {
|
||||
newName += fs.LinkSuffix
|
||||
}
|
||||
}
|
||||
}
|
||||
return translateError(oldDir.Rename(oldName, newName, newDir))
|
||||
}
|
||||
|
||||
var _ = (fusefs.NodeRenamer)((*Node)(nil))
|
||||
|
||||
var _ fusefs.NodeReadlinker = (*Node)(nil)
|
||||
|
||||
// Readlink read symbolic link target.
|
||||
func (n *Node) Readlink(ctx context.Context) (ret []byte, err syscall.Errno) {
|
||||
defer log.Trace(n, "Requested to read link")("ret=%v, err=%v", &ret, &err)
|
||||
|
||||
path := n.node.Path()
|
||||
|
||||
if n.node.VFS().Opt.Links {
|
||||
// The user must NOT provide .rclonelink suffix
|
||||
// if strings.HasSuffix(path, fs.LinkSuffix) {
|
||||
// fs.Errorf(nil, "Invalid name suffix provided: %v", path)
|
||||
// return nil, translateError(vfs.EINVAL)
|
||||
// }
|
||||
|
||||
// path += fs.LinkSuffix
|
||||
} else {
|
||||
// The user must provide .rclonelink suffix
|
||||
if !strings.HasSuffix(path, fs.LinkSuffix) {
|
||||
fs.Errorf(nil, "Invalid name suffix provided: %v", path)
|
||||
return nil, translateError(vfs.EINVAL)
|
||||
}
|
||||
}
|
||||
|
||||
s, serr := n.node.VFS().Readlink(path)
|
||||
if serr != nil {
|
||||
return nil, translateError(serr)
|
||||
}
|
||||
s, _ = n.node.VFS().TrimSymlink(s)
|
||||
return []byte(s), 0
|
||||
}
|
||||
|
||||
var _ fusefs.NodeSymlinker = (*Node)(nil)
|
||||
|
||||
// Symlink create symbolic link.
|
||||
func (n *Node) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *fusefs.Inode, err syscall.Errno) {
|
||||
defer log.Trace(n, "Requested to symlink name=%v, target=%v", name, target)("node=%v, err=%v", &node, &err)
|
||||
|
||||
name = path.Join(n.node.Path(), name)
|
||||
|
||||
if n.node.VFS().Opt.Links {
|
||||
// The user must NOT provide .rclonelink suffix
|
||||
if strings.HasSuffix(name, fs.LinkSuffix) {
|
||||
fs.Errorf(nil, "Invalid name suffix provided: %v", name)
|
||||
return nil, translateError(vfs.EINVAL)
|
||||
}
|
||||
|
||||
name += fs.LinkSuffix
|
||||
} else {
|
||||
// The user must provide .rclonelink suffix
|
||||
if !strings.HasSuffix(name, fs.LinkSuffix) {
|
||||
fs.Errorf(nil, "Invalid name suffix provided: %v", name)
|
||||
return nil, translateError(vfs.EINVAL)
|
||||
}
|
||||
}
|
||||
|
||||
// Add target suffix when linking to a link
|
||||
if !strings.HasSuffix(target, fs.LinkSuffix) {
|
||||
vnode, err := n.lookupVfsNodeInDir(target)
|
||||
if err == 0 && strings.HasSuffix(vnode.Name(), fs.LinkSuffix) {
|
||||
target += fs.LinkSuffix
|
||||
}
|
||||
}
|
||||
|
||||
serr := n.node.VFS().Symlink(target, name)
|
||||
if serr != nil {
|
||||
return nil, translateError(serr)
|
||||
}
|
||||
|
||||
// Find the created node
|
||||
vfsNode, err := n.lookupVfsNodeInDir(path.Base(name))
|
||||
if err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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, 0
|
||||
}
|
||||
|
@ -101,10 +101,6 @@ func TestSymlinks(t *testing.T) {
|
||||
// fs.Logf(nil, "LINK_FILE: %v, %v <-> %v, %v", lfl.Mode(), lfl.IsDir(), lf.Mode(), lf.IsDir())
|
||||
}
|
||||
|
||||
if !run.useVFS {
|
||||
t.Skip("Requires useVFS")
|
||||
}
|
||||
|
||||
suffix := ""
|
||||
|
||||
if run.useVFS || !run.vfsOpt.Links {
|
||||
|
Loading…
Reference in New Issue
Block a user