aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--weed/mount/inode_to_path.go25
-rw-r--r--weed/mount/weedfs_rename.go237
2 files changed, 262 insertions, 0 deletions
diff --git a/weed/mount/inode_to_path.go b/weed/mount/inode_to_path.go
index 84b952227..529ecadda 100644
--- a/weed/mount/inode_to_path.go
+++ b/weed/mount/inode_to_path.go
@@ -37,6 +37,7 @@ func (i *InodeToPath) Lookup(path util.FullPath) uint64 {
i.nextInodeId++
i.path2inode[path] = inode
i.inode2path[inode] = &InodeEntry{path, 1}
+ println("add", path, inode)
} else {
i.inode2path[inode].nlookup++
}
@@ -103,6 +104,30 @@ func (i *InodeToPath) RemovePath(path util.FullPath) {
}
}
+func (i *InodeToPath) MovePath(sourcePath, targetPath util.FullPath) {
+ if sourcePath == "/" || targetPath == "/" {
+ return
+ }
+ i.Lock()
+ defer i.Unlock()
+ sourceInode, sourceFound := i.path2inode[sourcePath]
+ targetInode, targetFound := i.path2inode[targetPath]
+ if sourceFound {
+ delete(i.path2inode, sourcePath)
+ i.path2inode[targetPath] = sourceInode
+ } else {
+ // it is possible some source folder items has not been visited before
+ // so no need to worry about their source inodes
+ return
+ }
+ i.inode2path[sourceInode].FullPath = targetPath
+ if targetFound {
+ delete(i.inode2path, targetInode)
+ } else {
+ i.inode2path[sourceInode].nlookup++
+ }
+}
+
func (i *InodeToPath) Forget(inode, nlookup uint64) {
if inode == 1 {
return
diff --git a/weed/mount/weedfs_rename.go b/weed/mount/weedfs_rename.go
new file mode 100644
index 000000000..a4054b64a
--- /dev/null
+++ b/weed/mount/weedfs_rename.go
@@ -0,0 +1,237 @@
+package mount
+
+import (
+ "context"
+ "fmt"
+ "github.com/chrislusf/seaweedfs/weed/filer"
+ "github.com/chrislusf/seaweedfs/weed/glog"
+ "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
+ "github.com/chrislusf/seaweedfs/weed/util"
+ "github.com/hanwen/go-fuse/v2/fs"
+ "github.com/hanwen/go-fuse/v2/fuse"
+ "io"
+ "strings"
+ "syscall"
+)
+
+/** Rename a file
+ *
+ * If the target exists it should be atomically replaced. If
+ * the target's inode's lookup count is non-zero, the file
+ * system is expected to postpone any removal of the inode
+ * until the lookup count reaches zero (see description of the
+ * forget function).
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent failure with error code EINVAL, i.e. all
+ * future bmap requests will fail with EINVAL without being
+ * send to the filesystem process.
+ *
+ * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If
+ * RENAME_NOREPLACE is specified, the filesystem must not
+ * overwrite *newname* if it exists and return an error
+ * instead. If `RENAME_EXCHANGE` is specified, the filesystem
+ * must atomically exchange the two files, i.e. both must
+ * exist and neither may be deleted.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param parent inode number of the old parent directory
+ * @param name old name
+ * @param newparent inode number of the new parent directory
+ * @param newname new name
+ */
+/*
+renameat2()
+ renameat2() has an additional flags argument. A renameat2() call
+ with a zero flags argument is equivalent to renameat().
+
+ The flags argument is a bit mask consisting of zero or more of
+ the following flags:
+
+ RENAME_EXCHANGE
+ Atomically exchange oldpath and newpath. Both pathnames
+ must exist but may be of different types (e.g., one could
+ be a non-empty directory and the other a symbolic link).
+
+ RENAME_NOREPLACE
+ Don't overwrite newpath of the rename. Return an error if
+ newpath already exists.
+
+ RENAME_NOREPLACE can't be employed together with
+ RENAME_EXCHANGE.
+
+ RENAME_NOREPLACE requires support from the underlying
+ filesystem. Support for various filesystems was added as
+ follows:
+
+ * ext4 (Linux 3.15);
+
+ * btrfs, tmpfs, and cifs (Linux 3.17);
+
+ * xfs (Linux 4.0);
+
+ * Support for many other filesystems was added in Linux
+ 4.9, including ext2, minix, reiserfs, jfs, vfat, and
+ bpf.
+
+ RENAME_WHITEOUT (since Linux 3.18)
+ This operation makes sense only for overlay/union
+ filesystem implementations.
+
+ Specifying RENAME_WHITEOUT creates a "whiteout" object at
+ the source of the rename at the same time as performing
+ the rename. The whole operation is atomic, so that if the
+ rename succeeds then the whiteout will also have been
+ created.
+
+ A "whiteout" is an object that has special meaning in
+ union/overlay filesystem constructs. In these constructs,
+ multiple layers exist and only the top one is ever
+ modified. A whiteout on an upper layer will effectively
+ hide a matching file in the lower layer, making it appear
+ as if the file didn't exist.
+
+ When a file that exists on the lower layer is renamed, the
+ file is first copied up (if not already on the upper
+ layer) and then renamed on the upper, read-write layer.
+ At the same time, the source file needs to be "whiteouted"
+ (so that the version of the source file in the lower layer
+ is rendered invisible). The whole operation needs to be
+ done atomically.
+
+ When not part of a union/overlay, the whiteout appears as
+ a character device with a {0,0} device number. (Note that
+ other union/overlay implementations may employ different
+ methods for storing whiteout entries; specifically, BSD
+ union mount employs a separate inode type, DT_WHT, which,
+ while supported by some filesystems available in Linux,
+ such as CODA and XFS, is ignored by the kernel's whiteout
+ support code, as of Linux 4.19, at least.)
+
+ RENAME_WHITEOUT requires the same privileges as creating a
+ device node (i.e., the CAP_MKNOD capability).
+
+ RENAME_WHITEOUT can't be employed together with
+ RENAME_EXCHANGE.
+
+ RENAME_WHITEOUT requires support from the underlying
+ filesystem. Among the filesystems that support it are
+ tmpfs (since Linux 3.18), ext4 (since Linux 3.18), XFS
+ (since Linux 4.1), f2fs (since Linux 4.2), btrfs (since
+ Linux 4.7), and ubifs (since Linux 4.9).
+*/
+const (
+ RenameEmptyFlag = 0
+ RenameNoReplace = 1
+ RenameExchange = fs.RENAME_EXCHANGE
+ RenameWhiteout = 3
+)
+
+func (wfs *WFS) Rename(cancel <-chan struct{}, in *fuse.RenameIn, oldName string, newName string) (code fuse.Status) {
+ if s := checkName(newName); s != fuse.OK {
+ return s
+ }
+
+ switch in.Flags {
+ case RenameEmptyFlag:
+ case RenameNoReplace:
+ case RenameExchange:
+ case RenameWhiteout:
+ return fuse.ENOTSUP
+ default:
+ return fuse.EINVAL
+ }
+
+ oldDir := wfs.inodeToPath.GetPath(in.NodeId)
+ oldPath := oldDir.Child(oldName)
+ newDir := wfs.inodeToPath.GetPath(in.Newdir)
+ newPath := newDir.Child(newName)
+
+ glog.V(4).Infof("dir Rename %s => %s", oldPath, newPath)
+
+ // update remote filer
+ err := wfs.WithFilerClient(true, func(client filer_pb.SeaweedFilerClient) error {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ request := &filer_pb.StreamRenameEntryRequest{
+ OldDirectory: string(oldDir),
+ OldName: oldName,
+ NewDirectory: string(newDir),
+ NewName: newName,
+ Signatures: []int32{wfs.signature},
+ }
+
+ stream, err := client.StreamRenameEntry(ctx, request)
+ if err != nil {
+ code = fuse.EIO
+ return fmt.Errorf("dir AtomicRenameEntry %s => %s : %v", oldPath, newPath, err)
+ }
+
+ for {
+ resp, recvErr := stream.Recv()
+ if recvErr != nil {
+ if recvErr == io.EOF {
+ break
+ } else {
+ if strings.Contains(recvErr.Error(), "not empty") {
+ code = fuse.Status(syscall.ENOTEMPTY)
+ } else if strings.Contains(recvErr.Error(), "not directory") {
+ code = fuse.ENOTDIR
+ }
+ return fmt.Errorf("dir Rename %s => %s receive: %v", oldPath, newPath, recvErr)
+ }
+ }
+
+ if err = wfs.handleRenameResponse(ctx, resp); err != nil {
+ glog.V(0).Infof("dir Rename %s => %s : %v", oldPath, newPath, err)
+ return err
+ }
+
+ }
+
+ return nil
+
+ })
+ if err != nil {
+ glog.V(0).Infof("Link: %v", err)
+ return
+ }
+
+ return fuse.OK
+
+}
+
+func (wfs *WFS) handleRenameResponse(ctx context.Context, resp *filer_pb.StreamRenameEntryResponse) error {
+ // comes from filer StreamRenameEntry, can only be create or delete entry
+
+ if resp.EventNotification.NewEntry != nil {
+ // with new entry, the old entry name also exists. This is the first step to create new entry
+ newEntry := filer.FromPbEntry(resp.EventNotification.NewParentPath, resp.EventNotification.NewEntry)
+ if err := wfs.metaCache.AtomicUpdateEntryFromFiler(ctx, "", newEntry); err != nil {
+ return err
+ }
+
+ oldParent, newParent := util.FullPath(resp.Directory), util.FullPath(resp.EventNotification.NewParentPath)
+ oldName, newName := resp.EventNotification.OldEntry.Name, resp.EventNotification.NewEntry.Name
+
+ oldPath := oldParent.Child(oldName)
+ newPath := newParent.Child(newName)
+
+ wfs.inodeToPath.MovePath(oldPath, newPath)
+
+ // TODO change file handle
+
+ } else if resp.EventNotification.OldEntry != nil {
+ // without new entry, only old entry name exists. This is the second step to delete old entry
+ if err := wfs.metaCache.AtomicUpdateEntryFromFiler(ctx, util.NewFullPath(resp.Directory, resp.EventNotification.OldEntry.Name), nil); err != nil {
+ return err
+ }
+ }
+
+ return nil
+
+}