xfs: create libxfs helper to exchange two directory entries

Create a new libxfs function to exchange two directory entries.
The upcoming metadata directory feature will need this to replace a
metadata inode directory entry.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
Darrick J. Wong 2024-07-02 11:22:46 -07:00
parent 90636e4531
commit a55712b35c
3 changed files with 142 additions and 98 deletions

View File

@ -955,3 +955,128 @@ xfs_dir_remove_child(
return 0;
}
/*
* Exchange the entry (@name1, @ip1) in directory @dp1 with the entry (@name2,
* @ip2) in directory @dp2, and update '..' @ip1 and @ip2's entries as needed.
* @ip1 and @ip2 need not be of the same type.
*
* All inodes must have the ILOCK held, and both entries must already exist.
*/
int
xfs_dir_exchange_children(
struct xfs_trans *tp,
struct xfs_dir_update *du1,
struct xfs_dir_update *du2,
unsigned int spaceres)
{
struct xfs_inode *dp1 = du1->dp;
const struct xfs_name *name1 = du1->name;
struct xfs_inode *ip1 = du1->ip;
struct xfs_inode *dp2 = du2->dp;
const struct xfs_name *name2 = du2->name;
struct xfs_inode *ip2 = du2->ip;
int ip1_flags = 0;
int ip2_flags = 0;
int dp2_flags = 0;
int error;
/* Swap inode number for dirent in first parent */
error = xfs_dir_replace(tp, dp1, name1, ip2->i_ino, spaceres);
if (error)
return error;
/* Swap inode number for dirent in second parent */
error = xfs_dir_replace(tp, dp2, name2, ip1->i_ino, spaceres);
if (error)
return error;
/*
* If we're renaming one or more directories across different parents,
* update the respective ".." entries (and link counts) to match the new
* parents.
*/
if (dp1 != dp2) {
dp2_flags = XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
if (S_ISDIR(VFS_I(ip2)->i_mode)) {
error = xfs_dir_replace(tp, ip2, &xfs_name_dotdot,
dp1->i_ino, spaceres);
if (error)
return error;
/* transfer ip2 ".." reference to dp1 */
if (!S_ISDIR(VFS_I(ip1)->i_mode)) {
error = xfs_droplink(tp, dp2);
if (error)
return error;
xfs_bumplink(tp, dp1);
}
/*
* Although ip1 isn't changed here, userspace needs
* to be warned about the change, so that applications
* relying on it (like backup ones), will properly
* notify the change
*/
ip1_flags |= XFS_ICHGTIME_CHG;
ip2_flags |= XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
}
if (S_ISDIR(VFS_I(ip1)->i_mode)) {
error = xfs_dir_replace(tp, ip1, &xfs_name_dotdot,
dp2->i_ino, spaceres);
if (error)
return error;
/* transfer ip1 ".." reference to dp2 */
if (!S_ISDIR(VFS_I(ip2)->i_mode)) {
error = xfs_droplink(tp, dp1);
if (error)
return error;
xfs_bumplink(tp, dp2);
}
/*
* Although ip2 isn't changed here, userspace needs
* to be warned about the change, so that applications
* relying on it (like backup ones), will properly
* notify the change
*/
ip1_flags |= XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
ip2_flags |= XFS_ICHGTIME_CHG;
}
}
if (ip1_flags) {
xfs_trans_ichgtime(tp, ip1, ip1_flags);
xfs_trans_log_inode(tp, ip1, XFS_ILOG_CORE);
}
if (ip2_flags) {
xfs_trans_ichgtime(tp, ip2, ip2_flags);
xfs_trans_log_inode(tp, ip2, XFS_ILOG_CORE);
}
if (dp2_flags) {
xfs_trans_ichgtime(tp, dp2, dp2_flags);
xfs_trans_log_inode(tp, dp2, XFS_ILOG_CORE);
}
xfs_trans_ichgtime(tp, dp1, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
xfs_trans_log_inode(tp, dp1, XFS_ILOG_CORE);
/* Schedule parent pointer replacements */
if (du1->ppargs) {
error = xfs_parent_replacename(tp, du1->ppargs, dp1, name1,
dp2, name2, ip1);
if (error)
return error;
}
if (du2->ppargs) {
error = xfs_parent_replacename(tp, du2->ppargs, dp2, name2,
dp1, name1, ip2);
if (error)
return error;
}
return 0;
}

View File

@ -325,4 +325,7 @@ int xfs_dir_add_child(struct xfs_trans *tp, unsigned int resblks,
int xfs_dir_remove_child(struct xfs_trans *tp, unsigned int resblks,
struct xfs_dir_update *du);
int xfs_dir_exchange_children(struct xfs_trans *tp, struct xfs_dir_update *du1,
struct xfs_dir_update *du2, unsigned int spaceres);
#endif /* __XFS_DIR2_H__ */

View File

@ -2238,108 +2238,24 @@ xfs_cross_rename(
struct xfs_parent_args *ip2_ppargs,
int spaceres)
{
int error = 0;
int ip1_flags = 0;
int ip2_flags = 0;
int dp2_flags = 0;
struct xfs_dir_update du1 = {
.dp = dp1,
.name = name1,
.ip = ip1,
.ppargs = ip1_ppargs,
};
struct xfs_dir_update du2 = {
.dp = dp2,
.name = name2,
.ip = ip2,
.ppargs = ip2_ppargs,
};
int error;
/* Swap inode number for dirent in first parent */
error = xfs_dir_replace(tp, dp1, name1, ip2->i_ino, spaceres);
error = xfs_dir_exchange_children(tp, &du1, &du2, spaceres);
if (error)
goto out_trans_abort;
/* Swap inode number for dirent in second parent */
error = xfs_dir_replace(tp, dp2, name2, ip1->i_ino, spaceres);
if (error)
goto out_trans_abort;
/*
* If we're renaming one or more directories across different parents,
* update the respective ".." entries (and link counts) to match the new
* parents.
*/
if (dp1 != dp2) {
dp2_flags = XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
if (S_ISDIR(VFS_I(ip2)->i_mode)) {
error = xfs_dir_replace(tp, ip2, &xfs_name_dotdot,
dp1->i_ino, spaceres);
if (error)
goto out_trans_abort;
/* transfer ip2 ".." reference to dp1 */
if (!S_ISDIR(VFS_I(ip1)->i_mode)) {
error = xfs_droplink(tp, dp2);
if (error)
goto out_trans_abort;
xfs_bumplink(tp, dp1);
}
/*
* Although ip1 isn't changed here, userspace needs
* to be warned about the change, so that applications
* relying on it (like backup ones), will properly
* notify the change
*/
ip1_flags |= XFS_ICHGTIME_CHG;
ip2_flags |= XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
}
if (S_ISDIR(VFS_I(ip1)->i_mode)) {
error = xfs_dir_replace(tp, ip1, &xfs_name_dotdot,
dp2->i_ino, spaceres);
if (error)
goto out_trans_abort;
/* transfer ip1 ".." reference to dp2 */
if (!S_ISDIR(VFS_I(ip2)->i_mode)) {
error = xfs_droplink(tp, dp1);
if (error)
goto out_trans_abort;
xfs_bumplink(tp, dp2);
}
/*
* Although ip2 isn't changed here, userspace needs
* to be warned about the change, so that applications
* relying on it (like backup ones), will properly
* notify the change
*/
ip1_flags |= XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG;
ip2_flags |= XFS_ICHGTIME_CHG;
}
}
/* Schedule parent pointer replacements */
if (ip1_ppargs) {
error = xfs_parent_replacename(tp, ip1_ppargs, dp1, name1, dp2,
name2, ip1);
if (error)
goto out_trans_abort;
}
if (ip2_ppargs) {
error = xfs_parent_replacename(tp, ip2_ppargs, dp2, name2, dp1,
name1, ip2);
if (error)
goto out_trans_abort;
}
if (ip1_flags) {
xfs_trans_ichgtime(tp, ip1, ip1_flags);
xfs_trans_log_inode(tp, ip1, XFS_ILOG_CORE);
}
if (ip2_flags) {
xfs_trans_ichgtime(tp, ip2, ip2_flags);
xfs_trans_log_inode(tp, ip2, XFS_ILOG_CORE);
}
if (dp2_flags) {
xfs_trans_ichgtime(tp, dp2, dp2_flags);
xfs_trans_log_inode(tp, dp2, XFS_ILOG_CORE);
}
xfs_trans_ichgtime(tp, dp1, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
xfs_trans_log_inode(tp, dp1, XFS_ILOG_CORE);
/*
* Inform our hook clients that we've finished an exchange operation as
* follows: removed the source and target files from their directories;