chrislu
3 years ago
2 changed files with 262 additions and 0 deletions
@ -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 |
||||
|
|
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue