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.

247 lines
8.3 KiB

  1. package mount
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/chrislusf/seaweedfs/weed/filer"
  6. "github.com/chrislusf/seaweedfs/weed/glog"
  7. "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
  8. "github.com/chrislusf/seaweedfs/weed/util"
  9. "github.com/hanwen/go-fuse/v2/fs"
  10. "github.com/hanwen/go-fuse/v2/fuse"
  11. "io"
  12. "strings"
  13. "syscall"
  14. )
  15. /** Rename a file
  16. *
  17. * If the target exists it should be atomically replaced. If
  18. * the target's inode's lookup count is non-zero, the file
  19. * system is expected to postpone any removal of the inode
  20. * until the lookup count reaches zero (see description of the
  21. * forget function).
  22. *
  23. * If this request is answered with an error code of ENOSYS, this is
  24. * treated as a permanent failure with error code EINVAL, i.e. all
  25. * future bmap requests will fail with EINVAL without being
  26. * send to the filesystem process.
  27. *
  28. * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If
  29. * RENAME_NOREPLACE is specified, the filesystem must not
  30. * overwrite *newname* if it exists and return an error
  31. * instead. If `RENAME_EXCHANGE` is specified, the filesystem
  32. * must atomically exchange the two files, i.e. both must
  33. * exist and neither may be deleted.
  34. *
  35. * Valid replies:
  36. * fuse_reply_err
  37. *
  38. * @param req request handle
  39. * @param parent inode number of the old parent directory
  40. * @param name old name
  41. * @param newparent inode number of the new parent directory
  42. * @param newname new name
  43. */
  44. /*
  45. renameat2()
  46. renameat2() has an additional flags argument. A renameat2() call
  47. with a zero flags argument is equivalent to renameat().
  48. The flags argument is a bit mask consisting of zero or more of
  49. the following flags:
  50. RENAME_EXCHANGE
  51. Atomically exchange oldpath and newpath. Both pathnames
  52. must exist but may be of different types (e.g., one could
  53. be a non-empty directory and the other a symbolic link).
  54. RENAME_NOREPLACE
  55. Don't overwrite newpath of the rename. Return an error if
  56. newpath already exists.
  57. RENAME_NOREPLACE can't be employed together with
  58. RENAME_EXCHANGE.
  59. RENAME_NOREPLACE requires support from the underlying
  60. filesystem. Support for various filesystems was added as
  61. follows:
  62. * ext4 (Linux 3.15);
  63. * btrfs, tmpfs, and cifs (Linux 3.17);
  64. * xfs (Linux 4.0);
  65. * Support for many other filesystems was added in Linux
  66. 4.9, including ext2, minix, reiserfs, jfs, vfat, and
  67. bpf.
  68. RENAME_WHITEOUT (since Linux 3.18)
  69. This operation makes sense only for overlay/union
  70. filesystem implementations.
  71. Specifying RENAME_WHITEOUT creates a "whiteout" object at
  72. the source of the rename at the same time as performing
  73. the rename. The whole operation is atomic, so that if the
  74. rename succeeds then the whiteout will also have been
  75. created.
  76. A "whiteout" is an object that has special meaning in
  77. union/overlay filesystem constructs. In these constructs,
  78. multiple layers exist and only the top one is ever
  79. modified. A whiteout on an upper layer will effectively
  80. hide a matching file in the lower layer, making it appear
  81. as if the file didn't exist.
  82. When a file that exists on the lower layer is renamed, the
  83. file is first copied up (if not already on the upper
  84. layer) and then renamed on the upper, read-write layer.
  85. At the same time, the source file needs to be "whiteouted"
  86. (so that the version of the source file in the lower layer
  87. is rendered invisible). The whole operation needs to be
  88. done atomically.
  89. When not part of a union/overlay, the whiteout appears as
  90. a character device with a {0,0} device number. (Note that
  91. other union/overlay implementations may employ different
  92. methods for storing whiteout entries; specifically, BSD
  93. union mount employs a separate inode type, DT_WHT, which,
  94. while supported by some filesystems available in Linux,
  95. such as CODA and XFS, is ignored by the kernel's whiteout
  96. support code, as of Linux 4.19, at least.)
  97. RENAME_WHITEOUT requires the same privileges as creating a
  98. device node (i.e., the CAP_MKNOD capability).
  99. RENAME_WHITEOUT can't be employed together with
  100. RENAME_EXCHANGE.
  101. RENAME_WHITEOUT requires support from the underlying
  102. filesystem. Among the filesystems that support it are
  103. tmpfs (since Linux 3.18), ext4 (since Linux 3.18), XFS
  104. (since Linux 4.1), f2fs (since Linux 4.2), btrfs (since
  105. Linux 4.7), and ubifs (since Linux 4.9).
  106. */
  107. const (
  108. RenameEmptyFlag = 0
  109. RenameNoReplace = 1
  110. RenameExchange = fs.RENAME_EXCHANGE
  111. RenameWhiteout = 3
  112. )
  113. func (wfs *WFS) Rename(cancel <-chan struct{}, in *fuse.RenameIn, oldName string, newName string) (code fuse.Status) {
  114. if s := checkName(newName); s != fuse.OK {
  115. return s
  116. }
  117. switch in.Flags {
  118. case RenameEmptyFlag:
  119. case RenameNoReplace:
  120. case RenameExchange:
  121. case RenameWhiteout:
  122. return fuse.ENOTSUP
  123. default:
  124. return fuse.EINVAL
  125. }
  126. oldDir, code := wfs.inodeToPath.GetPath(in.NodeId)
  127. if code != fuse.OK {
  128. return
  129. }
  130. oldPath := oldDir.Child(oldName)
  131. newDir, code := wfs.inodeToPath.GetPath(in.Newdir)
  132. if code != fuse.OK {
  133. return
  134. }
  135. newPath := newDir.Child(newName)
  136. glog.V(4).Infof("dir Rename %s => %s", oldPath, newPath)
  137. // update remote filer
  138. err := wfs.WithFilerClient(true, func(client filer_pb.SeaweedFilerClient) error {
  139. ctx, cancel := context.WithCancel(context.Background())
  140. defer cancel()
  141. request := &filer_pb.StreamRenameEntryRequest{
  142. OldDirectory: string(oldDir),
  143. OldName: oldName,
  144. NewDirectory: string(newDir),
  145. NewName: newName,
  146. Signatures: []int32{wfs.signature},
  147. }
  148. stream, err := client.StreamRenameEntry(ctx, request)
  149. if err != nil {
  150. code = fuse.EIO
  151. return fmt.Errorf("dir AtomicRenameEntry %s => %s : %v", oldPath, newPath, err)
  152. }
  153. for {
  154. resp, recvErr := stream.Recv()
  155. if recvErr != nil {
  156. if recvErr == io.EOF {
  157. break
  158. } else {
  159. if strings.Contains(recvErr.Error(), "not empty") {
  160. code = fuse.Status(syscall.ENOTEMPTY)
  161. } else if strings.Contains(recvErr.Error(), "not directory") {
  162. code = fuse.ENOTDIR
  163. }
  164. return fmt.Errorf("dir Rename %s => %s receive: %v", oldPath, newPath, recvErr)
  165. }
  166. }
  167. if err = wfs.handleRenameResponse(ctx, resp); err != nil {
  168. glog.V(0).Infof("dir Rename %s => %s : %v", oldPath, newPath, err)
  169. return err
  170. }
  171. }
  172. return nil
  173. })
  174. if err != nil {
  175. glog.V(0).Infof("Link: %v", err)
  176. return
  177. }
  178. return fuse.OK
  179. }
  180. func (wfs *WFS) handleRenameResponse(ctx context.Context, resp *filer_pb.StreamRenameEntryResponse) error {
  181. // comes from filer StreamRenameEntry, can only be create or delete entry
  182. glog.V(4).Infof("dir Rename %+v", resp.EventNotification)
  183. if resp.EventNotification.NewEntry != nil {
  184. // with new entry, the old entry name also exists. This is the first step to create new entry
  185. newEntry := filer.FromPbEntry(resp.EventNotification.NewParentPath, resp.EventNotification.NewEntry)
  186. if err := wfs.metaCache.AtomicUpdateEntryFromFiler(ctx, "", newEntry, false); err != nil {
  187. return err
  188. }
  189. oldParent, newParent := util.FullPath(resp.Directory), util.FullPath(resp.EventNotification.NewParentPath)
  190. oldName, newName := resp.EventNotification.OldEntry.Name, resp.EventNotification.NewEntry.Name
  191. oldPath := oldParent.Child(oldName)
  192. newPath := newParent.Child(newName)
  193. replacedInode := wfs.inodeToPath.MovePath(oldPath, newPath)
  194. // invalidate attr and data
  195. if replacedInode > 0 {
  196. wfs.fuseServer.InodeNotify(replacedInode, 0, -1)
  197. }
  198. } else if resp.EventNotification.OldEntry != nil {
  199. // without new entry, only old entry name exists. This is the second step to delete old entry
  200. if err := wfs.metaCache.AtomicUpdateEntryFromFiler(ctx, util.NewFullPath(resp.Directory, resp.EventNotification.OldEntry.Name), nil, resp.EventNotification.DeleteChunks); err != nil {
  201. return err
  202. }
  203. }
  204. return nil
  205. }