]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
xfs: create libxfs helper to rename two directory entries
authorDarrick J. Wong <djwong@kernel.org>
Tue, 2 Jul 2024 18:22:46 +0000 (11:22 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 2 Jul 2024 18:36:59 +0000 (11:36 -0700)
Create a new libxfs function to rename 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>
fs/xfs/libxfs/xfs_dir2.c
fs/xfs/libxfs/xfs_dir2.h
fs/xfs/xfs_inode.c

index d650cfa023fdb582f221077f0991866fd4bf3686..34b63ba2e4f71e642a51fc176bb48711d42ae424 100644 (file)
@@ -23,6 +23,7 @@
 #include "xfs_trans_space.h"
 #include "xfs_parent.h"
 #include "xfs_ag.h"
+#include "xfs_ialloc.h"
 
 const struct xfs_name xfs_name_dotdot = {
        .name   = (const unsigned char *)"..",
@@ -1080,3 +1081,229 @@ xfs_dir_exchange_children(
 
        return 0;
 }
+
+/*
+ * Given an entry (@src_name, @src_ip) in directory @src_dp, make the entry
+ * @target_name in directory @target_dp point to @src_ip and remove the
+ * original entry, cleaning up everything left behind.
+ *
+ * Cleanup involves dropping a link count on @target_ip, and either removing
+ * the (@src_name, @src_ip) entry from @src_dp or simply replacing the entry
+ * with (@src_name, @wip) if a whiteout inode @wip is supplied.
+ *
+ * All inodes must have the ILOCK held.  We assume that if @src_ip is a
+ * directory then its '..' doesn't already point to @target_dp, and that @wip
+ * is a freshly allocated whiteout.
+ */
+int
+xfs_dir_rename_children(
+       struct xfs_trans        *tp,
+       struct xfs_dir_update   *du_src,
+       struct xfs_dir_update   *du_tgt,
+       unsigned int            spaceres,
+       struct xfs_dir_update   *du_wip)
+{
+       struct xfs_mount        *mp = tp->t_mountp;
+       struct xfs_inode        *src_dp = du_src->dp;
+       const struct xfs_name   *src_name = du_src->name;
+       struct xfs_inode        *src_ip = du_src->ip;
+       struct xfs_inode        *target_dp = du_tgt->dp;
+       const struct xfs_name   *target_name = du_tgt->name;
+       struct xfs_inode        *target_ip = du_tgt->ip;
+       bool                    new_parent = (src_dp != target_dp);
+       bool                    src_is_directory;
+       int                     error;
+
+       src_is_directory = S_ISDIR(VFS_I(src_ip)->i_mode);
+
+       /*
+        * Check for expected errors before we dirty the transaction
+        * so we can return an error without a transaction abort.
+        */
+       if (target_ip == NULL) {
+               /*
+                * If there's no space reservation, check the entry will
+                * fit before actually inserting it.
+                */
+               if (!spaceres) {
+                       error = xfs_dir_canenter(tp, target_dp, target_name);
+                       if (error)
+                               return error;
+               }
+       } else {
+               /*
+                * If target exists and it's a directory, check that whether
+                * it can be destroyed.
+                */
+               if (S_ISDIR(VFS_I(target_ip)->i_mode) &&
+                   (!xfs_dir_isempty(target_ip) ||
+                    (VFS_I(target_ip)->i_nlink > 2)))
+                       return -EEXIST;
+       }
+
+       /*
+        * Directory entry creation below may acquire the AGF. Remove
+        * the whiteout from the unlinked list first to preserve correct
+        * AGI/AGF locking order. This dirties the transaction so failures
+        * after this point will abort and log recovery will clean up the
+        * mess.
+        *
+        * For whiteouts, we need to bump the link count on the whiteout
+        * inode. After this point, we have a real link, clear the tmpfile
+        * state flag from the inode so it doesn't accidentally get misused
+        * in future.
+        */
+       if (du_wip->ip) {
+               struct xfs_perag        *pag;
+
+               ASSERT(VFS_I(du_wip->ip)->i_nlink == 0);
+
+               pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, du_wip->ip->i_ino));
+               error = xfs_iunlink_remove(tp, pag, du_wip->ip);
+               xfs_perag_put(pag);
+               if (error)
+                       return error;
+
+               xfs_bumplink(tp, du_wip->ip);
+       }
+
+       /*
+        * Set up the target.
+        */
+       if (target_ip == NULL) {
+               /*
+                * If target does not exist and the rename crosses
+                * directories, adjust the target directory link count
+                * to account for the ".." reference from the new entry.
+                */
+               error = xfs_dir_createname(tp, target_dp, target_name,
+                                          src_ip->i_ino, spaceres);
+               if (error)
+                       return error;
+
+               xfs_trans_ichgtime(tp, target_dp,
+                                       XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
+
+               if (new_parent && src_is_directory) {
+                       xfs_bumplink(tp, target_dp);
+               }
+       } else { /* target_ip != NULL */
+               /*
+                * Link the source inode under the target name.
+                * If the source inode is a directory and we are moving
+                * it across directories, its ".." entry will be
+                * inconsistent until we replace that down below.
+                *
+                * In case there is already an entry with the same
+                * name at the destination directory, remove it first.
+                */
+               error = xfs_dir_replace(tp, target_dp, target_name,
+                                       src_ip->i_ino, spaceres);
+               if (error)
+                       return error;
+
+               xfs_trans_ichgtime(tp, target_dp,
+                                       XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
+
+               /*
+                * Decrement the link count on the target since the target
+                * dir no longer points to it.
+                */
+               error = xfs_droplink(tp, target_ip);
+               if (error)
+                       return error;
+
+               if (src_is_directory) {
+                       /*
+                        * Drop the link from the old "." entry.
+                        */
+                       error = xfs_droplink(tp, target_ip);
+                       if (error)
+                               return error;
+               }
+       } /* target_ip != NULL */
+
+       /*
+        * Remove the source.
+        */
+       if (new_parent && src_is_directory) {
+               /*
+                * Rewrite the ".." entry to point to the new
+                * directory.
+                */
+               error = xfs_dir_replace(tp, src_ip, &xfs_name_dotdot,
+                                       target_dp->i_ino, spaceres);
+               ASSERT(error != -EEXIST);
+               if (error)
+                       return error;
+       }
+
+       /*
+        * We always want to hit the ctime on the source inode.
+        *
+        * This isn't strictly required by the standards since the source
+        * inode isn't really being changed, but old unix file systems did
+        * it and some incremental backup programs won't work without it.
+        */
+       xfs_trans_ichgtime(tp, src_ip, XFS_ICHGTIME_CHG);
+       xfs_trans_log_inode(tp, src_ip, XFS_ILOG_CORE);
+
+       /*
+        * Adjust the link count on src_dp.  This is necessary when
+        * renaming a directory, either within one parent when
+        * the target existed, or across two parent directories.
+        */
+       if (src_is_directory && (new_parent || target_ip != NULL)) {
+
+               /*
+                * Decrement link count on src_directory since the
+                * entry that's moved no longer points to it.
+                */
+               error = xfs_droplink(tp, src_dp);
+               if (error)
+                       return error;
+       }
+
+       /*
+        * For whiteouts, we only need to update the source dirent with the
+        * inode number of the whiteout inode rather than removing it
+        * altogether.
+        */
+       if (du_wip->ip)
+               error = xfs_dir_replace(tp, src_dp, src_name, du_wip->ip->i_ino,
+                                       spaceres);
+       else
+               error = xfs_dir_removename(tp, src_dp, src_name, src_ip->i_ino,
+                                          spaceres);
+       if (error)
+               return error;
+
+       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);
+
+       /* Schedule parent pointer updates. */
+       if (du_wip->ppargs) {
+               error = xfs_parent_addname(tp, du_wip->ppargs, src_dp,
+                               src_name, du_wip->ip);
+               if (error)
+                       return error;
+       }
+
+       if (du_src->ppargs) {
+               error = xfs_parent_replacename(tp, du_src->ppargs, src_dp,
+                               src_name, target_dp, target_name, src_ip);
+               if (error)
+                       return error;
+       }
+
+       if (du_tgt->ppargs) {
+               error = xfs_parent_removename(tp, du_tgt->ppargs, target_dp,
+                               target_name, target_ip);
+               if (error)
+                       return error;
+       }
+
+       return 0;
+}
index 8b1e192bd7a83dc3ecd2ba4f137fe8e6fc4b6b4d..df6d4bbe3d6f960c3f0721752847f73a5cdf7df7 100644 (file)
@@ -327,5 +327,8 @@ int xfs_dir_remove_child(struct xfs_trans *tp, unsigned int resblks,
 
 int xfs_dir_exchange_children(struct xfs_trans *tp, struct xfs_dir_update *du1,
                struct xfs_dir_update *du2, unsigned int spaceres);
+int xfs_dir_rename_children(struct xfs_trans *tp, struct xfs_dir_update *du_src,
+               struct xfs_dir_update *du_tgt, unsigned int spaceres,
+               struct xfs_dir_update *du_wip);
 
 #endif /* __XFS_DIR2_H__ */
index 363e98ee974c715031ba9396df7e5a11fa5e3e1e..3e4ec5337a3b74f1e40f4c25da3f179dbe2c7cff 100644 (file)
@@ -2341,13 +2341,20 @@ xfs_rename(
        struct xfs_inode        *target_ip,
        unsigned int            flags)
 {
+       struct xfs_dir_update   du_src = {
+               .dp             = src_dp,
+               .name           = src_name,
+               .ip             = src_ip,
+       };
+       struct xfs_dir_update   du_tgt = {
+               .dp             = target_dp,
+               .name           = target_name,
+               .ip             = target_ip,
+       };
+       struct xfs_dir_update   du_wip = { };
        struct xfs_mount        *mp = src_dp->i_mount;
        struct xfs_trans        *tp;
-       struct xfs_inode        *wip = NULL;            /* whiteout inode */
        struct xfs_inode        *inodes[__XFS_SORT_INODES];
-       struct xfs_parent_args  *src_ppargs = NULL;
-       struct xfs_parent_args  *tgt_ppargs = NULL;
-       struct xfs_parent_args  *wip_ppargs = NULL;
        int                     i;
        int                     num_inodes = __XFS_SORT_INODES;
        bool                    new_parent = (src_dp != target_dp);
@@ -2367,8 +2374,8 @@ xfs_rename(
         * appropriately.
         */
        if (flags & RENAME_WHITEOUT) {
-               error = xfs_rename_alloc_whiteout(idmap, src_name,
-                                                 target_dp, &wip);
+               error = xfs_rename_alloc_whiteout(idmap, src_name, target_dp,
+                               &du_wip.ip);
                if (error)
                        return error;
 
@@ -2376,21 +2383,21 @@ xfs_rename(
                src_name->type = XFS_DIR3_FT_CHRDEV;
        }
 
-       xfs_sort_for_rename(src_dp, target_dp, src_ip, target_ip, wip,
-                               inodes, &num_inodes);
+       xfs_sort_for_rename(src_dp, target_dp, src_ip, target_ip, du_wip.ip,
+                       inodes, &num_inodes);
 
-       error = xfs_parent_start(mp, &src_ppargs);
+       error = xfs_parent_start(mp, &du_src.ppargs);
        if (error)
                goto out_release_wip;
 
-       if (wip) {
-               error = xfs_parent_start(mp, &wip_ppargs);
+       if (du_wip.ip) {
+               error = xfs_parent_start(mp, &du_wip.ppargs);
                if (error)
                        goto out_src_ppargs;
        }
 
        if (target_ip) {
-               error = xfs_parent_start(mp, &tgt_ppargs);
+               error = xfs_parent_start(mp, &du_tgt.ppargs);
                if (error)
                        goto out_wip_ppargs;
        }
@@ -2398,7 +2405,7 @@ xfs_rename(
 retry:
        nospace_error = 0;
        spaceres = xfs_rename_space_res(mp, src_name->len, target_ip != NULL,
-                       target_name->len, wip != NULL);
+                       target_name->len, du_wip.ip != NULL);
        error = xfs_trans_alloc(mp, &M_RES(mp)->tr_rename, spaceres, 0, 0, &tp);
        if (error == -ENOSPC) {
                nospace_error = error;
@@ -2413,7 +2420,7 @@ retry:
         * We don't allow reservationless renaming when parent pointers are
         * enabled because we can't back out if the xattrs must grow.
         */
-       if (src_ppargs && nospace_error) {
+       if (du_src.ppargs && nospace_error) {
                error = nospace_error;
                xfs_trans_cancel(tp);
                goto out_tgt_ppargs;
@@ -2445,8 +2452,8 @@ retry:
        xfs_trans_ijoin(tp, src_ip, 0);
        if (target_ip)
                xfs_trans_ijoin(tp, target_ip, 0);
-       if (wip)
-               xfs_trans_ijoin(tp, wip, 0);
+       if (du_wip.ip)
+               xfs_trans_ijoin(tp, du_wip.ip, 0);
 
        /*
         * If we are using project inheritance, we only allow renames
@@ -2462,8 +2469,8 @@ retry:
        /* RENAME_EXCHANGE is unique from here on. */
        if (flags & RENAME_EXCHANGE) {
                error = xfs_cross_rename(tp, src_dp, src_name, src_ip,
-                               src_ppargs, target_dp, target_name, target_ip,
-                               tgt_ppargs, spaceres);
+                               du_src.ppargs, target_dp, target_name,
+                               target_ip, du_tgt.ppargs, spaceres);
                nospace_error = 0;
                goto out_unlock;
        }
@@ -2498,38 +2505,11 @@ retry:
         * We don't allow quotaless renaming when parent pointers are enabled
         * because we can't back out if the xattrs must grow.
         */
-       if (src_ppargs && nospace_error) {
+       if (du_src.ppargs && nospace_error) {
                error = nospace_error;
                goto out_trans_cancel;
        }
 
-       /*
-        * Check for expected errors before we dirty the transaction
-        * so we can return an error without a transaction abort.
-        */
-       if (target_ip == NULL) {
-               /*
-                * If there's no space reservation, check the entry will
-                * fit before actually inserting it.
-                */
-               if (!spaceres) {
-                       error = xfs_dir_canenter(tp, target_dp, target_name);
-                       if (error)
-                               goto out_trans_cancel;
-               }
-       } else {
-               /*
-                * If target exists and it's a directory, check that whether
-                * it can be destroyed.
-                */
-               if (S_ISDIR(VFS_I(target_ip)->i_mode) &&
-                   (!xfs_dir_isempty(target_ip) ||
-                    (VFS_I(target_ip)->i_nlink > 2))) {
-                       error = -EEXIST;
-                       goto out_trans_cancel;
-               }
-       }
-
        /*
         * Lock the AGI buffers we need to handle bumping the nlink of the
         * whiteout inode off the unlinked list and to handle dropping the
@@ -2541,7 +2521,7 @@ retry:
         * target_ip is either null or an empty directory.
         */
        for (i = 0; i < num_inodes && inodes[i] != NULL; i++) {
-               if (inodes[i] == wip ||
+               if (inodes[i] == du_wip.ip ||
                    (inodes[i] == target_ip &&
                     (VFS_I(target_ip)->i_nlink == 1 || src_is_directory))) {
                        struct xfs_perag        *pag;
@@ -2556,172 +2536,20 @@ retry:
                }
        }
 
-       /*
-        * Directory entry creation below may acquire the AGF. Remove
-        * the whiteout from the unlinked list first to preserve correct
-        * AGI/AGF locking order. This dirties the transaction so failures
-        * after this point will abort and log recovery will clean up the
-        * mess.
-        *
-        * For whiteouts, we need to bump the link count on the whiteout
-        * inode. After this point, we have a real link, clear the tmpfile
-        * state flag from the inode so it doesn't accidentally get misused
-        * in future.
-        */
-       if (wip) {
-               struct xfs_perag        *pag;
-
-               ASSERT(VFS_I(wip)->i_nlink == 0);
-
-               pag = xfs_perag_get(mp, XFS_INO_TO_AGNO(mp, wip->i_ino));
-               error = xfs_iunlink_remove(tp, pag, wip);
-               xfs_perag_put(pag);
-               if (error)
-                       goto out_trans_cancel;
-
-               xfs_bumplink(tp, wip);
-               VFS_I(wip)->i_state &= ~I_LINKABLE;
-       }
-
-       /*
-        * Set up the target.
-        */
-       if (target_ip == NULL) {
-               /*
-                * If target does not exist and the rename crosses
-                * directories, adjust the target directory link count
-                * to account for the ".." reference from the new entry.
-                */
-               error = xfs_dir_createname(tp, target_dp, target_name,
-                                          src_ip->i_ino, spaceres);
-               if (error)
-                       goto out_trans_cancel;
-
-               xfs_trans_ichgtime(tp, target_dp,
-                                       XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
-
-               if (new_parent && src_is_directory) {
-                       xfs_bumplink(tp, target_dp);
-               }
-       } else { /* target_ip != NULL */
-               /*
-                * Link the source inode under the target name.
-                * If the source inode is a directory and we are moving
-                * it across directories, its ".." entry will be
-                * inconsistent until we replace that down below.
-                *
-                * In case there is already an entry with the same
-                * name at the destination directory, remove it first.
-                */
-               error = xfs_dir_replace(tp, target_dp, target_name,
-                                       src_ip->i_ino, spaceres);
-               if (error)
-                       goto out_trans_cancel;
-
-               xfs_trans_ichgtime(tp, target_dp,
-                                       XFS_ICHGTIME_MOD | XFS_ICHGTIME_CHG);
-
-               /*
-                * Decrement the link count on the target since the target
-                * dir no longer points to it.
-                */
-               error = xfs_droplink(tp, target_ip);
-               if (error)
-                       goto out_trans_cancel;
-
-               if (src_is_directory) {
-                       /*
-                        * Drop the link from the old "." entry.
-                        */
-                       error = xfs_droplink(tp, target_ip);
-                       if (error)
-                               goto out_trans_cancel;
-               }
-       } /* target_ip != NULL */
-
-       /*
-        * Remove the source.
-        */
-       if (new_parent && src_is_directory) {
-               /*
-                * Rewrite the ".." entry to point to the new
-                * directory.
-                */
-               error = xfs_dir_replace(tp, src_ip, &xfs_name_dotdot,
-                                       target_dp->i_ino, spaceres);
-               ASSERT(error != -EEXIST);
-               if (error)
-                       goto out_trans_cancel;
-       }
-
-       /*
-        * We always want to hit the ctime on the source inode.
-        *
-        * This isn't strictly required by the standards since the source
-        * inode isn't really being changed, but old unix file systems did
-        * it and some incremental backup programs won't work without it.
-        */
-       xfs_trans_ichgtime(tp, src_ip, XFS_ICHGTIME_CHG);
-       xfs_trans_log_inode(tp, src_ip, XFS_ILOG_CORE);
-
-       /*
-        * Adjust the link count on src_dp.  This is necessary when
-        * renaming a directory, either within one parent when
-        * the target existed, or across two parent directories.
-        */
-       if (src_is_directory && (new_parent || target_ip != NULL)) {
-
-               /*
-                * Decrement link count on src_directory since the
-                * entry that's moved no longer points to it.
-                */
-               error = xfs_droplink(tp, src_dp);
-               if (error)
-                       goto out_trans_cancel;
-       }
-
-       /*
-        * 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,
-                                       spaceres);
-       else
-               error = xfs_dir_removename(tp, src_dp, src_name, src_ip->i_ino,
-                                          spaceres);
-
+       error = xfs_dir_rename_children(tp, &du_src, &du_tgt, spaceres,
+                       &du_wip);
        if (error)
                goto out_trans_cancel;
 
-       /* Schedule parent pointer updates. */
-       if (wip_ppargs) {
-               error = xfs_parent_addname(tp, wip_ppargs, src_dp, src_name,
-                               wip);
-               if (error)
-                       goto out_trans_cancel;
-       }
-
-       if (src_ppargs) {
-               error = xfs_parent_replacename(tp, src_ppargs, src_dp,
-                               src_name, target_dp, target_name, src_ip);
-               if (error)
-                       goto out_trans_cancel;
-       }
-
-       if (tgt_ppargs) {
-               error = xfs_parent_removename(tp, tgt_ppargs, target_dp,
-                               target_name, target_ip);
-               if (error)
-                       goto out_trans_cancel;
+       if (du_wip.ip) {
+               /*
+                * 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(du_wip.ip)->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);
-
        /*
         * Inform our hook clients that we've finished a rename operation as
         * follows: removed the source and target files from their directories;
@@ -2734,8 +2562,8 @@ retry:
                xfs_dir_update_hook(target_dp, target_ip, -1, target_name);
        xfs_dir_update_hook(src_dp, src_ip, -1, src_name);
        xfs_dir_update_hook(target_dp, src_ip, 1, target_name);
-       if (wip)
-               xfs_dir_update_hook(src_dp, wip, 1, src_name);
+       if (du_wip.ip)
+               xfs_dir_update_hook(src_dp, du_wip.ip, 1, src_name);
 
        error = xfs_finish_rename(tp);
        nospace_error = 0;
@@ -2746,14 +2574,14 @@ out_trans_cancel:
 out_unlock:
        xfs_iunlock_rename(inodes, num_inodes);
 out_tgt_ppargs:
-       xfs_parent_finish(mp, tgt_ppargs);
+       xfs_parent_finish(mp, du_tgt.ppargs);
 out_wip_ppargs:
-       xfs_parent_finish(mp, wip_ppargs);
+       xfs_parent_finish(mp, du_wip.ppargs);
 out_src_ppargs:
-       xfs_parent_finish(mp, src_ppargs);
+       xfs_parent_finish(mp, du_src.ppargs);
 out_release_wip:
-       if (wip)
-               xfs_irele(wip);
+       if (du_wip.ip)
+               xfs_irele(du_wip.ip);
        if (error == -ENOSPC && nospace_error)
                error = nospace_error;
        return error;