diff --git a/cmd/cmount/fs.go b/cmd/cmount/fs.go index c097611b7..f0dd6a5db 100644 --- a/cmd/cmount/fs.go +++ b/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) diff --git a/cmd/mount/dir.go b/cmd/mount/dir.go index b82b52179..883b0dd88 100644 --- a/cmd/mount/dir.go +++ b/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) diff --git a/cmd/mount/file.go b/cmd/mount/file.go index 1a0ad6c28..8e1a54aca 100644 --- a/cmd/mount/file.go +++ b/cmd/mount/file.go @@ -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 +} diff --git a/cmd/mount2/fs.go b/cmd/mount2/fs.go index 0ab28f05a..79edcbd7c 100644 --- a/cmd/mount2/fs.go +++ b/cmd/mount2/fs.go @@ -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()) diff --git a/cmd/mount2/node.go b/cmd/mount2/node.go index 4cbe8ea5c..71183d92d 100644 --- a/cmd/mount2/node.go +++ b/cmd/mount2/node.go @@ -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 +} diff --git a/vfs/vfstest/file.go b/vfs/vfstest/file.go index 7622be907..13c81b473 100644 --- a/vfs/vfstest/file.go +++ b/vfs/vfstest/file.go @@ -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 {