mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2024-11-25 11:39:12 +08:00
supports renaming
This commit is contained in:
parent
417b59b893
commit
3d0e9e5197
@ -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
|
||||
|
237
weed/mount/weedfs_rename.go
Normal file
237
weed/mount/weedfs_rename.go
Normal file
@ -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
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user