You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							271 lines
						
					
					
						
							8.8 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							271 lines
						
					
					
						
							8.8 KiB
						
					
					
				| package mount | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"fmt" | |
| 	"io" | |
| 	"strings" | |
| 	"syscall" | |
| 
 | |
| 	"github.com/hanwen/go-fuse/v2/fs" | |
| 	"github.com/hanwen/go-fuse/v2/fuse" | |
| 	"github.com/seaweedfs/seaweedfs/weed/filer" | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/util" | |
| ) | |
| 
 | |
| /** 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 wfs.IsOverQuota { | |
| 		return fuse.Status(syscall.ENOSPC) | |
| 	} | |
| 
 | |
| 	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, code := wfs.inodeToPath.GetPath(in.NodeId) | |
| 	if code != fuse.OK { | |
| 		return | |
| 	} | |
| 	oldPath := oldDir.Child(oldName) | |
| 	newDir, code := wfs.inodeToPath.GetPath(in.Newdir) | |
| 	if code != fuse.OK { | |
| 		return | |
| 	} | |
| 	newPath := newDir.Child(newName) | |
| 
 | |
| 	oldEntry, status := wfs.maybeLoadEntry(oldPath) | |
| 	if status != fuse.OK { | |
| 		return status | |
| 	} | |
| 
 | |
| 	if wormEnforced, _ := wfs.wormEnforcedForEntry(oldPath, oldEntry); wormEnforced { | |
| 		return fuse.EPERM | |
| 	} | |
| 
 | |
| 	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 | |
|  | |
| 	glog.V(4).Infof("dir Rename %+v", resp.EventNotification) | |
| 
 | |
| 	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) | |
| 
 | |
| 		sourceInode, targetInode := wfs.inodeToPath.MovePath(oldPath, newPath) | |
| 		if sourceInode != 0 { | |
| 			fh, foundFh := wfs.fhMap.FindFileHandle(sourceInode) | |
| 			if foundFh { | |
| 				if entry := fh.GetEntry(); entry != nil { | |
| 					entry.Name = newName | |
| 				} | |
| 			} | |
| 			// invalidate attr and data | |
| 			// wfs.fuseServer.InodeNotify(sourceInode, 0, -1) | |
| 		} | |
| 		if targetInode != 0 { | |
| 			// invalidate attr and data | |
| 			// wfs.fuseServer.InodeNotify(targetInode, 0, -1) | |
| 		} | |
| 
 | |
| 	} 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 | |
| 
 | |
| }
 |