xfs: create libxfs helper to remove an existing inode/name from a directory

Create a new libxfs function to remove a (name, inode) entry from a
directory.  The upcoming metadata directory feature will need this to
create a metadata directory tree.

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:45 -07:00
parent 1964435d19
commit 90636e4531
3 changed files with 92 additions and 65 deletions

View File

@ -874,3 +874,84 @@ xfs_dir_add_child(
return 0;
}
/*
* Given a directory @dp, a child @ip, and a @name, remove the (@name, @ip)
* entry from the directory. Both inodes must have the ILOCK held.
*/
int
xfs_dir_remove_child(
struct xfs_trans *tp,
unsigned int resblks,
struct xfs_dir_update *du)
{
struct xfs_inode *dp = du->dp;
const struct xfs_name *name = du->name;
struct xfs_inode *ip = du->ip;
int error;
xfs_assert_ilocked(ip, XFS_ILOCK_EXCL);
xfs_assert_ilocked(dp, XFS_ILOCK_EXCL);
/*
* If we're removing a directory perform some additional validation.
*/
if (S_ISDIR(VFS_I(ip)->i_mode)) {
ASSERT(VFS_I(ip)->i_nlink >= 2);
if (VFS_I(ip)->i_nlink != 2)
return -ENOTEMPTY;
if (!xfs_dir_isempty(ip))
return -ENOTEMPTY;
/* Drop the link from ip's "..". */
error = xfs_droplink(tp, dp);
if (error)
return error;
/* Drop the "." link from ip to self. */
error = xfs_droplink(tp, ip);
if (error)
return error;
/*
* Point the unlinked child directory's ".." entry to the root
* directory to eliminate back-references to inodes that may
* get freed before the child directory is closed. If the fs
* gets shrunk, this can lead to dirent inode validation errors.
*/
if (dp->i_ino != tp->t_mountp->m_sb.sb_rootino) {
error = xfs_dir_replace(tp, ip, &xfs_name_dotdot,
tp->t_mountp->m_sb.sb_rootino, 0);
if (error)
return error;
}
} else {
/*
* When removing a non-directory we need to log the parent
* inode here. For a directory this is done implicitly
* by the xfs_droplink call for the ".." entry.
*/
xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
}
xfs_trans_ichgtime(tp, dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
/* Drop the link from dp to ip. */
error = xfs_droplink(tp, ip);
if (error)
return error;
error = xfs_dir_removename(tp, dp, name, ip->i_ino, resblks);
if (error) {
ASSERT(error != -ENOENT);
return error;
}
/* Remove parent pointer. */
if (du->ppargs) {
error = xfs_parent_removename(tp, du->ppargs, dp, name, ip);
if (error)
return error;
}
return 0;
}

View File

@ -322,5 +322,7 @@ int xfs_dir_create_child(struct xfs_trans *tp, unsigned int resblks,
struct xfs_dir_update *du);
int xfs_dir_add_child(struct xfs_trans *tp, unsigned int resblks,
struct xfs_dir_update *du);
int xfs_dir_remove_child(struct xfs_trans *tp, unsigned int resblks,
struct xfs_dir_update *du);
#endif /* __XFS_DIR2_H__ */

View File

@ -2040,13 +2040,17 @@ xfs_remove(
struct xfs_name *name,
struct xfs_inode *ip)
{
struct xfs_dir_update du = {
.dp = dp,
.name = name,
.ip = ip,
};
struct xfs_mount *mp = dp->i_mount;
struct xfs_trans *tp = NULL;
int is_dir = S_ISDIR(VFS_I(ip)->i_mode);
int dontcare;
int error = 0;
uint resblks;
struct xfs_parent_args *ppargs;
trace_xfs_remove(dp, name);
@ -2063,7 +2067,7 @@ xfs_remove(
if (error)
goto std_return;
error = xfs_parent_start(mp, &ppargs);
error = xfs_parent_start(mp, &du.ppargs);
if (error)
goto std_return;
@ -2086,70 +2090,10 @@ xfs_remove(
goto out_parent;
}
/*
* If we're removing a directory perform some additional validation.
*/
if (is_dir) {
ASSERT(VFS_I(ip)->i_nlink >= 2);
if (VFS_I(ip)->i_nlink != 2) {
error = -ENOTEMPTY;
goto out_trans_cancel;
}
if (!xfs_dir_isempty(ip)) {
error = -ENOTEMPTY;
goto out_trans_cancel;
}
/* Drop the link from ip's "..". */
error = xfs_droplink(tp, dp);
if (error)
goto out_trans_cancel;
/* Drop the "." link from ip to self. */
error = xfs_droplink(tp, ip);
if (error)
goto out_trans_cancel;
/*
* Point the unlinked child directory's ".." entry to the root
* directory to eliminate back-references to inodes that may
* get freed before the child directory is closed. If the fs
* gets shrunk, this can lead to dirent inode validation errors.
*/
if (dp->i_ino != tp->t_mountp->m_sb.sb_rootino) {
error = xfs_dir_replace(tp, ip, &xfs_name_dotdot,
tp->t_mountp->m_sb.sb_rootino, 0);
if (error)
goto out_trans_cancel;
}
} else {
/*
* When removing a non-directory we need to log the parent
* inode here. For a directory this is done implicitly
* by the xfs_droplink call for the ".." entry.
*/
xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
}
xfs_trans_ichgtime(tp, dp, XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
/* Drop the link from dp to ip. */
error = xfs_droplink(tp, ip);
error = xfs_dir_remove_child(tp, resblks, &du);
if (error)
goto out_trans_cancel;
error = xfs_dir_removename(tp, dp, name, ip->i_ino, resblks);
if (error) {
ASSERT(error != -ENOENT);
goto out_trans_cancel;
}
/* Remove parent pointer. */
if (ppargs) {
error = xfs_parent_removename(tp, ppargs, dp, name, ip);
if (error)
goto out_trans_cancel;
}
/*
* Drop the link from dp to ip, and if ip was a directory, remove the
* '.' and '..' references since we freed the directory.
@ -2173,7 +2117,7 @@ xfs_remove(
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_iunlock(dp, XFS_ILOCK_EXCL);
xfs_parent_finish(mp, ppargs);
xfs_parent_finish(mp, du.ppargs);
return 0;
out_trans_cancel:
@ -2182,7 +2126,7 @@ xfs_remove(
xfs_iunlock(ip, XFS_ILOCK_EXCL);
xfs_iunlock(dp, XFS_ILOCK_EXCL);
out_parent:
xfs_parent_finish(mp, ppargs);
xfs_parent_finish(mp, du.ppargs);
std_return:
return error;
}