mirror of
https://github.com/rclone/rclone.git
synced 2025-01-03 12:59:32 +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"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -95,8 +96,11 @@ func (fsys *FS) closeHandle(fh uint64) (errc int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lookup a Node given a path
|
// 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)
|
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)
|
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) {
|
func (fsys *FS) lookupParentDir(filePath string) (leaf string, dir *vfs.Dir, errc int) {
|
||||||
parentDir, leaf := path.Split(filePath)
|
parentDir, leaf := path.Split(filePath)
|
||||||
dir, errc = fsys.lookupDir(parentDir)
|
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
|
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())
|
Size := uint64(node.Size())
|
||||||
Blocks := (Size + 511) / 512
|
Blocks := (Size + 511) / 512
|
||||||
modTime := node.ModTime()
|
modTime := node.ModTime()
|
||||||
Mode := node.Mode().Perm()
|
|
||||||
if node.IsDir() {
|
|
||||||
Mode |= fuse.S_IFDIR
|
|
||||||
} else {
|
|
||||||
Mode |= fuse.S_IFREG
|
|
||||||
}
|
|
||||||
//stat.Dev = 1
|
//stat.Dev = 1
|
||||||
stat.Ino = node.Inode() // FIXME do we need to set the inode number?
|
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.Nlink = 1
|
||||||
stat.Uid = fsys.VFS.Opt.UID
|
stat.Uid = fsys.VFS.Opt.UID
|
||||||
stat.Gid = fsys.VFS.Opt.GID
|
stat.Gid = fsys.VFS.Opt.GID
|
||||||
@ -252,7 +257,7 @@ func (fsys *FS) Readdir(dirPath string,
|
|||||||
fill(".", nil, 0)
|
fill(".", nil, 0)
|
||||||
fill("..", nil, 0)
|
fill("..", nil, 0)
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
name := node.Name()
|
name, _ := fsys.VFS.TrimSymlink(node.Name())
|
||||||
if len(name) > mountlib.MaxLeafSize {
|
if len(name) > mountlib.MaxLeafSize {
|
||||||
fs.Errorf(dirPath, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name)
|
fs.Errorf(dirPath, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name)
|
||||||
continue
|
continue
|
||||||
@ -330,13 +335,15 @@ func (fsys *FS) CreateEx(filePath string, mode uint32, fi *fuse.FileInfo_t) (err
|
|||||||
if errc != 0 {
|
if errc != 0 {
|
||||||
return errc
|
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 {
|
if err != nil {
|
||||||
return translateError(err)
|
return translateError(err)
|
||||||
}
|
}
|
||||||
// translate the fuse flags to os flags
|
handle, err := file.Open(osFlags)
|
||||||
flags := translateOpenFlags(fi.Flags) | os.O_CREATE
|
|
||||||
handle, err := file.Open(flags)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return translateError(err)
|
return translateError(err)
|
||||||
}
|
}
|
||||||
@ -456,6 +463,18 @@ func (fsys *FS) Rmdir(dirPath string) (errc int) {
|
|||||||
// Rename renames a file.
|
// Rename renames a file.
|
||||||
func (fsys *FS) Rename(oldPath string, newPath string) (errc int) {
|
func (fsys *FS) Rename(oldPath string, newPath string) (errc int) {
|
||||||
defer log.Trace(oldPath, "newPath=%q", newPath)("errc=%d", &errc)
|
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))
|
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.
|
// Symlink creates a symbolic link.
|
||||||
func (fsys *FS) Symlink(target string, newpath string) (errc int) {
|
func (fsys *FS) Symlink(target string, newpath string) (errc int) {
|
||||||
defer log.Trace(target, "newpath=%q", newpath)("errc=%d", &errc)
|
defer log.Trace(fsys, "Requested to symlink newpath=%q, target=%q", newpath, target)("errc=%d", &errc)
|
||||||
return -fuse.ENOSYS
|
|
||||||
|
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.
|
// Readlink reads the target of a symbolic link.
|
||||||
func (fsys *FS) Readlink(path string) (errc int, linkPath string) {
|
func (fsys *FS) Readlink(path string) (errc int, linkPath string) {
|
||||||
defer log.Trace(path, "")("linkPath=%q, errc=%d", &linkPath, &errc)
|
defer log.Trace(fsys, "Requested to read link")("errc=%v, linkPath=%q", &errc, linkPath)
|
||||||
return -fuse.ENOSYS, ""
|
|
||||||
|
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.
|
// Chmod changes the permission bits of a file.
|
||||||
@ -627,6 +690,41 @@ func translateOpenFlags(inFlags int) (outFlags int) {
|
|||||||
return outFlags
|
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
|
// Make sure interfaces are satisfied
|
||||||
var (
|
var (
|
||||||
_ fuse.FileSystemInterface = (*FS)(nil)
|
_ fuse.FileSystemInterface = (*FS)(nil)
|
||||||
|
103
cmd/mount/dir.go
103
cmd/mount/dir.go
@ -8,6 +8,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -28,13 +30,21 @@ type Dir struct {
|
|||||||
// Check interface satisfied
|
// Check interface satisfied
|
||||||
var _ fusefs.Node = (*Dir)(nil)
|
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
|
// Attr updates the attributes of a directory
|
||||||
func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) (err error) {
|
func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) (err error) {
|
||||||
defer log.Trace(d, "")("attr=%+v, err=%v", a, &err)
|
defer log.Trace(d, "")("attr=%+v, err=%v", a, &err)
|
||||||
a.Valid = d.fsys.opt.AttrTimeout
|
a.Valid = d.fsys.opt.AttrTimeout
|
||||||
a.Gid = d.VFS().Opt.GID
|
a.Gid = d.VFS().Opt.GID
|
||||||
a.Uid = d.VFS().Opt.UID
|
a.Uid = d.VFS().Opt.UID
|
||||||
a.Mode = os.ModeDir | d.VFS().Opt.DirPerms
|
a.Mode = d.Mode()
|
||||||
modTime := d.ModTime()
|
modTime := d.ModTime()
|
||||||
a.Atime = modTime
|
a.Atime = modTime
|
||||||
a.Mtime = modTime
|
a.Mtime = modTime
|
||||||
@ -74,7 +84,7 @@ var _ fusefs.NodeRequestLookuper = (*Dir)(nil)
|
|||||||
// Lookup need not to handle the names "." and "..".
|
// 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) {
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, translateError(err)
|
return nil, translateError(err)
|
||||||
}
|
}
|
||||||
@ -117,7 +127,7 @@ func (d *Dir) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error)
|
|||||||
Name: "..",
|
Name: "..",
|
||||||
})
|
})
|
||||||
for _, node := range items {
|
for _, node := range items {
|
||||||
name := node.Name()
|
name, isLink := d.VFS().TrimSymlink(node.Name())
|
||||||
if len(name) > mountlib.MaxLeafSize {
|
if len(name) > mountlib.MaxLeafSize {
|
||||||
fs.Errorf(d, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name)
|
fs.Errorf(d, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name)
|
||||||
continue
|
continue
|
||||||
@ -127,7 +137,9 @@ func (d *Dir) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error)
|
|||||||
Type: fuse.DT_File,
|
Type: fuse.DT_File,
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
if node.IsDir() {
|
if isLink {
|
||||||
|
dirent.Type = fuse.DT_Link
|
||||||
|
} else if node.IsDir() {
|
||||||
dirent.Type = fuse.DT_Dir
|
dirent.Type = fuse.DT_Dir
|
||||||
}
|
}
|
||||||
dirents = append(dirents, dirent)
|
dirents = append(dirents, dirent)
|
||||||
@ -141,11 +153,13 @@ var _ fusefs.NodeCreater = (*Dir)(nil)
|
|||||||
// Create makes a new file
|
// 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) {
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, nil, translateError(err)
|
return nil, nil, translateError(err)
|
||||||
}
|
}
|
||||||
fh, err := file.Open(int(req.Flags) | os.O_CREATE)
|
fh, err := file.Open(osFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, translateError(err)
|
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).
|
// may correspond to a file (unlink) or to a directory (rmdir).
|
||||||
func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) (err error) {
|
func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) (err error) {
|
||||||
defer log.Trace(d, "name=%q", req.Name)("err=%v", &err)
|
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 {
|
if err != nil {
|
||||||
return translateError(err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return translateError(err)
|
return translateError(err)
|
||||||
}
|
}
|
||||||
@ -240,6 +280,53 @@ func (d *Dir) Link(ctx context.Context, req *fuse.LinkRequest, old fusefs.Node)
|
|||||||
return nil, syscall.ENOSYS
|
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
|
// Check interface satisfied
|
||||||
var _ fusefs.NodeMknoder = (*Dir)(nil)
|
var _ fusefs.NodeMknoder = (*Dir)(nil)
|
||||||
|
|
||||||
|
@ -5,11 +5,14 @@ package mount
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"bazil.org/fuse"
|
"bazil.org/fuse"
|
||||||
fusefs "bazil.org/fuse/fs"
|
fusefs "bazil.org/fuse/fs"
|
||||||
|
"github.com/rclone/rclone/fs"
|
||||||
"github.com/rclone/rclone/fs/log"
|
"github.com/rclone/rclone/fs/log"
|
||||||
"github.com/rclone/rclone/vfs"
|
"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
|
Blocks := (Size + 511) / 512
|
||||||
a.Gid = f.VFS().Opt.GID
|
a.Gid = f.VFS().Opt.GID
|
||||||
a.Uid = f.VFS().Opt.UID
|
a.Uid = f.VFS().Opt.UID
|
||||||
a.Mode = f.VFS().Opt.FilePerms
|
a.Mode = f.File.Mode() &^ os.ModeAppend
|
||||||
a.Size = Size
|
a.Size = Size
|
||||||
a.Atime = modTime
|
a.Atime = modTime
|
||||||
a.Mtime = 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.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
|
// get the Mode from a vfs Node
|
||||||
func getMode(node os.FileInfo) uint32 {
|
func getMode(node os.FileInfo) uint32 {
|
||||||
Mode := node.Mode().Perm()
|
vfsMode := node.Mode()
|
||||||
if node.IsDir() {
|
Mode := vfsMode.Perm()
|
||||||
|
if vfsMode&os.ModeDir != 0 {
|
||||||
Mode |= fuse.S_IFDIR
|
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 {
|
} else {
|
||||||
Mode |= fuse.S_IFREG
|
Mode |= fuse.S_IFREG
|
||||||
}
|
}
|
||||||
return uint32(Mode)
|
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
|
// fill in attr from node
|
||||||
func setAttr(node vfs.Node, attr *fuse.Attr) {
|
func setAttr(node vfs.Node, attr *fuse.Attr) {
|
||||||
Size := uint64(node.Size())
|
Size := uint64(node.Size())
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
fusefs "github.com/hanwen/go-fuse/v2/fs"
|
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
|
return nil, syscall.ENOTDIR
|
||||||
}
|
}
|
||||||
vfsNode, err := dir.Stat(leaf)
|
vfsNode, err := dir.Stat(leaf)
|
||||||
|
if err == vfs.ENOENT && dir.VFS().Opt.Links {
|
||||||
|
vfsNode, err = dir.Stat(leaf + fs.LinkSuffix)
|
||||||
|
}
|
||||||
return vfsNode, translateError(err)
|
return vfsNode, translateError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +223,7 @@ func (n *Node) Opendir(ctx context.Context) syscall.Errno {
|
|||||||
var _ = (fusefs.NodeOpendirer)((*Node)(nil))
|
var _ = (fusefs.NodeOpendirer)((*Node)(nil))
|
||||||
|
|
||||||
type dirStream struct {
|
type dirStream struct {
|
||||||
|
fsys *FS
|
||||||
nodes []os.FileInfo
|
nodes []os.FileInfo
|
||||||
i int
|
i int
|
||||||
}
|
}
|
||||||
@ -250,13 +255,14 @@ func (ds *dirStream) Next() (de fuse.DirEntry, errno syscall.Errno) {
|
|||||||
}, 0
|
}, 0
|
||||||
}
|
}
|
||||||
fi := ds.nodes[ds.i-2]
|
fi := ds.nodes[ds.i-2]
|
||||||
|
name, _ := ds.fsys.VFS.TrimSymlink(path.Base(fi.Name()))
|
||||||
de = fuse.DirEntry{
|
de = fuse.DirEntry{
|
||||||
// Mode is the file's mode. Only the high bits (e.g. S_IFDIR)
|
// Mode is the file's mode. Only the high bits (e.g. S_IFDIR)
|
||||||
// are considered.
|
// are considered.
|
||||||
Mode: getMode(fi),
|
Mode: getMode(fi),
|
||||||
|
|
||||||
// Name is the basename of the file in the directory.
|
// Name is the basename of the file in the directory.
|
||||||
Name: path.Base(fi.Name()),
|
Name: name,
|
||||||
|
|
||||||
// Ino is the inode number.
|
// Ino is the inode number.
|
||||||
Ino: 0, // FIXME
|
Ino: 0, // FIXME
|
||||||
@ -304,6 +310,7 @@ func (n *Node) Readdir(ctx context.Context) (ds fusefs.DirStream, errno syscall.
|
|||||||
}
|
}
|
||||||
return &dirStream{
|
return &dirStream{
|
||||||
nodes: items,
|
nodes: items,
|
||||||
|
fsys: n.fsys,
|
||||||
}, 0
|
}, 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
|
// translate the fuse flags to os flags
|
||||||
osFlags := int(flags) | os.O_CREATE
|
osFlags := int(flags) | os.O_CREATE
|
||||||
|
// translate the fuse mode to os mode
|
||||||
|
//osMode := getFileMode(mode)
|
||||||
file, err := dir.Create(name, osFlags)
|
file, err := dir.Create(name, osFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, translateError(err)
|
return nil, nil, 0, translateError(err)
|
||||||
@ -416,7 +425,101 @@ func (n *Node) Rename(ctx context.Context, oldName string, newParent fusefs.Inod
|
|||||||
if !ok {
|
if !ok {
|
||||||
return syscall.ENOTDIR
|
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))
|
return translateError(oldDir.Rename(oldName, newName, newDir))
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = (fusefs.NodeRenamer)((*Node)(nil))
|
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())
|
// 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 := ""
|
suffix := ""
|
||||||
|
|
||||||
if run.useVFS || !run.vfsOpt.Links {
|
if run.useVFS || !run.vfsOpt.Links {
|
||||||
|
Loading…
Reference in New Issue
Block a user