|
@@ -2797,40 +2797,81 @@ out_trans_abort:
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * xfs_rename_alloc_whiteout()
|
|
|
+ *
|
|
|
+ * Return a referenced, unlinked, unlocked inode that that can be used as a
|
|
|
+ * whiteout in a rename transaction. We use a tmpfile inode here so that if we
|
|
|
+ * crash between allocating the inode and linking it into the rename transaction
|
|
|
+ * recovery will free the inode and we won't leak it.
|
|
|
+ */
|
|
|
+static int
|
|
|
+xfs_rename_alloc_whiteout(
|
|
|
+ struct xfs_inode *dp,
|
|
|
+ struct xfs_inode **wip)
|
|
|
+{
|
|
|
+ struct xfs_inode *tmpfile;
|
|
|
+ int error;
|
|
|
+
|
|
|
+ error = xfs_create_tmpfile(dp, NULL, S_IFCHR | WHITEOUT_MODE, &tmpfile);
|
|
|
+ if (error)
|
|
|
+ return error;
|
|
|
+
|
|
|
+ /* Satisfy xfs_bumplink that this is a real tmpfile */
|
|
|
+ xfs_finish_inode_setup(tmpfile);
|
|
|
+ VFS_I(tmpfile)->i_state |= I_LINKABLE;
|
|
|
+
|
|
|
+ *wip = tmpfile;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* xfs_rename
|
|
|
*/
|
|
|
int
|
|
|
xfs_rename(
|
|
|
- xfs_inode_t *src_dp,
|
|
|
- struct xfs_name *src_name,
|
|
|
- xfs_inode_t *src_ip,
|
|
|
- xfs_inode_t *target_dp,
|
|
|
- struct xfs_name *target_name,
|
|
|
- xfs_inode_t *target_ip,
|
|
|
- unsigned int flags)
|
|
|
+ struct xfs_inode *src_dp,
|
|
|
+ struct xfs_name *src_name,
|
|
|
+ struct xfs_inode *src_ip,
|
|
|
+ struct xfs_inode *target_dp,
|
|
|
+ struct xfs_name *target_name,
|
|
|
+ struct xfs_inode *target_ip,
|
|
|
+ unsigned int flags)
|
|
|
{
|
|
|
- xfs_trans_t *tp = NULL;
|
|
|
- xfs_mount_t *mp = src_dp->i_mount;
|
|
|
- int new_parent; /* moving to a new dir */
|
|
|
- int src_is_directory; /* src_name is a directory */
|
|
|
- int error;
|
|
|
- xfs_bmap_free_t free_list;
|
|
|
- xfs_fsblock_t first_block;
|
|
|
- int cancel_flags = 0;
|
|
|
- xfs_inode_t *inodes[__XFS_SORT_INODES];
|
|
|
- int num_inodes = __XFS_SORT_INODES;
|
|
|
- int spaceres;
|
|
|
+ struct xfs_mount *mp = src_dp->i_mount;
|
|
|
+ struct xfs_trans *tp;
|
|
|
+ struct xfs_bmap_free free_list;
|
|
|
+ xfs_fsblock_t first_block;
|
|
|
+ struct xfs_inode *wip = NULL; /* whiteout inode */
|
|
|
+ struct xfs_inode *inodes[__XFS_SORT_INODES];
|
|
|
+ int num_inodes = __XFS_SORT_INODES;
|
|
|
+ int new_parent = (src_dp != target_dp);
|
|
|
+ int src_is_directory = S_ISDIR(src_ip->i_d.di_mode);
|
|
|
+ int cancel_flags = 0;
|
|
|
+ int spaceres;
|
|
|
+ int error;
|
|
|
|
|
|
trace_xfs_rename(src_dp, target_dp, src_name, target_name);
|
|
|
|
|
|
if ((flags & RENAME_EXCHANGE) && !target_ip)
|
|
|
return -EINVAL;
|
|
|
|
|
|
- new_parent = (src_dp != target_dp);
|
|
|
- src_is_directory = S_ISDIR(src_ip->i_d.di_mode);
|
|
|
+ /*
|
|
|
+ * If we are doing a whiteout operation, allocate the whiteout inode
|
|
|
+ * we will be placing at the target and ensure the type is set
|
|
|
+ * appropriately.
|
|
|
+ */
|
|
|
+ if (flags & RENAME_WHITEOUT) {
|
|
|
+ ASSERT(!(flags & (RENAME_NOREPLACE | RENAME_EXCHANGE)));
|
|
|
+ error = xfs_rename_alloc_whiteout(target_dp, &wip);
|
|
|
+ if (error)
|
|
|
+ return error;
|
|
|
+
|
|
|
+ /* setup target dirent info as whiteout */
|
|
|
+ src_name->type = XFS_DIR3_FT_CHRDEV;
|
|
|
+ }
|
|
|
|
|
|
- xfs_sort_for_rename(src_dp, target_dp, src_ip, target_ip, NULL,
|
|
|
+ xfs_sort_for_rename(src_dp, target_dp, src_ip, target_ip, wip,
|
|
|
inodes, &num_inodes);
|
|
|
|
|
|
tp = xfs_trans_alloc(mp, XFS_TRANS_RENAME);
|
|
@@ -2870,6 +2911,8 @@ xfs_rename(
|
|
|
xfs_trans_ijoin(tp, src_ip, XFS_ILOCK_EXCL);
|
|
|
if (target_ip)
|
|
|
xfs_trans_ijoin(tp, target_ip, XFS_ILOCK_EXCL);
|
|
|
+ if (wip)
|
|
|
+ xfs_trans_ijoin(tp, wip, XFS_ILOCK_EXCL);
|
|
|
|
|
|
/*
|
|
|
* If we are using project inheritance, we only allow renames
|
|
@@ -3019,17 +3062,55 @@ xfs_rename(
|
|
|
goto out_trans_abort;
|
|
|
}
|
|
|
|
|
|
- error = xfs_dir_removename(tp, src_dp, src_name, src_ip->i_ino,
|
|
|
+ /*
|
|
|
+ * For whiteouts, we only need to update the source dirent with the
|
|
|
+ * inode number of the whiteout inode rather than removing it
|
|
|
+ * altogether.
|
|
|
+ */
|
|
|
+ if (wip) {
|
|
|
+ error = xfs_dir_replace(tp, src_dp, src_name, wip->i_ino,
|
|
|
&first_block, &free_list, spaceres);
|
|
|
+ } else
|
|
|
+ error = xfs_dir_removename(tp, src_dp, src_name, src_ip->i_ino,
|
|
|
+ &first_block, &free_list, spaceres);
|
|
|
if (error)
|
|
|
goto out_trans_abort;
|
|
|
|
|
|
+ /*
|
|
|
+ * For whiteouts, we need to bump the link count on the whiteout inode.
|
|
|
+ * This means that failures all the way up to this point leave the inode
|
|
|
+ * on the unlinked list and so cleanup is a simple matter of dropping
|
|
|
+ * the remaining reference to it. If we fail here after bumping the link
|
|
|
+ * count, we're shutting down the filesystem so we'll never see the
|
|
|
+ * intermediate state on disk.
|
|
|
+ */
|
|
|
+ if (wip) {
|
|
|
+ ASSERT(wip->i_d.di_nlink == 0);
|
|
|
+ error = xfs_bumplink(tp, wip);
|
|
|
+ if (error)
|
|
|
+ goto out_trans_abort;
|
|
|
+ error = xfs_iunlink_remove(tp, wip);
|
|
|
+ if (error)
|
|
|
+ goto out_trans_abort;
|
|
|
+ xfs_trans_log_inode(tp, wip, XFS_ILOG_CORE);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Now we have a real link, clear the "I'm a tmpfile" state
|
|
|
+ * flag from the inode so it doesn't accidentally get misused in
|
|
|
+ * future.
|
|
|
+ */
|
|
|
+ VFS_I(wip)->i_state &= ~I_LINKABLE;
|
|
|
+ }
|
|
|
+
|
|
|
xfs_trans_ichgtime(tp, src_dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
|
|
|
xfs_trans_log_inode(tp, src_dp, XFS_ILOG_CORE);
|
|
|
if (new_parent)
|
|
|
xfs_trans_log_inode(tp, target_dp, XFS_ILOG_CORE);
|
|
|
|
|
|
- return xfs_finish_rename(tp, &free_list);
|
|
|
+ error = xfs_finish_rename(tp, &free_list);
|
|
|
+ if (wip)
|
|
|
+ IRELE(wip);
|
|
|
+ return error;
|
|
|
|
|
|
out_trans_abort:
|
|
|
cancel_flags |= XFS_TRANS_ABORT;
|
|
@@ -3037,6 +3118,8 @@ out_bmap_cancel:
|
|
|
xfs_bmap_cancel(&free_list);
|
|
|
out_trans_cancel:
|
|
|
xfs_trans_cancel(tp, cancel_flags);
|
|
|
+ if (wip)
|
|
|
+ IRELE(wip);
|
|
|
return error;
|
|
|
}
|
|
|
|