xfs: Add support to RENAME_EXCHANGE flag
authorCarlos Maiolino <cmaiolino@redhat.com>
Tue, 23 Dec 2014 21:51:42 +0000 (08:51 +1100)
committerDave Chinner <david@fromorbit.com>
Tue, 23 Dec 2014 21:51:42 +0000 (08:51 +1100)
Adds a new function named xfs_cross_rename(), responsible for
handling requests from sys_renameat2() using RENAME_EXCHANGE flag.

Signed-off-by: Carlos Maiolino <cmaiolino@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
fs/xfs/xfs_inode.c
fs/xfs/xfs_inode.h
fs/xfs/xfs_iops.c

index 41f804e740d7150379414d36c668242d538b03eb..9916aef6099770b7faed0b1296a64156d61f9e87 100644 (file)
@@ -2655,6 +2655,124 @@ xfs_sort_for_rename(
        }
 }
 
+/*
+ * xfs_cross_rename()
+ *
+ * responsible for handling RENAME_EXCHANGE flag in renameat2() sytemcall
+ */
+STATIC int
+xfs_cross_rename(
+       struct xfs_trans        *tp,
+       struct xfs_inode        *dp1,
+       struct xfs_name         *name1,
+       struct xfs_inode        *ip1,
+       struct xfs_inode        *dp2,
+       struct xfs_name         *name2,
+       struct xfs_inode        *ip2,
+       struct xfs_bmap_free    *free_list,
+       xfs_fsblock_t           *first_block,
+       int                     spaceres)
+{
+       int             error = 0;
+       int             ip1_flags = 0;
+       int             ip2_flags = 0;
+       int             dp2_flags = 0;
+
+       /* Swap inode number for dirent in first parent */
+       error = xfs_dir_replace(tp, dp1, name1,
+                               ip2->i_ino,
+                               first_block, free_list, spaceres);
+       if (error)
+               goto out;
+
+       /* Swap inode number for dirent in second parent */
+       error = xfs_dir_replace(tp, dp2, name2,
+                               ip1->i_ino,
+                               first_block, free_list, spaceres);
+       if (error)
+               goto out;
+
+       /*
+        * 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(ip2->i_d.di_mode)) {
+                       error = xfs_dir_replace(tp, ip2, &xfs_name_dotdot,
+                                               dp1->i_ino, first_block,
+                                               free_list, spaceres);
+                       if (error)
+                               goto out;
+
+                       /* transfer ip2 ".." reference to dp1 */
+                       if (!S_ISDIR(ip1->i_d.di_mode)) {
+                               error = xfs_droplink(tp, dp2);
+                               if (error)
+                                       goto out;
+                               error = xfs_bumplink(tp, dp1);
+                               if (error)
+                                       goto out;
+                       }
+
+                       /*
+                        * 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(ip1->i_d.di_mode)) {
+                       error = xfs_dir_replace(tp, ip1, &xfs_name_dotdot,
+                                               dp2->i_ino, first_block,
+                                               free_list, spaceres);
+                       if (error)
+                               goto out;
+
+                       /* transfer ip1 ".." reference to dp2 */
+                       if (!S_ISDIR(ip2->i_d.di_mode)) {
+                               error = xfs_droplink(tp, dp1);
+                               if (error)
+                                       goto out;
+                               error = xfs_bumplink(tp, dp2);
+                               if (error)
+                                       goto out;
+                       }
+
+                       /*
+                        * 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);
+out:
+       return error;
+}
+
 /*
  * xfs_rename
  */
@@ -2665,7 +2783,8 @@ xfs_rename(
        xfs_inode_t     *src_ip,
        xfs_inode_t     *target_dp,
        struct xfs_name *target_name,
-       xfs_inode_t     *target_ip)
+       xfs_inode_t     *target_ip,
+       unsigned int    flags)
 {
        xfs_trans_t     *tp = NULL;
        xfs_mount_t     *mp = src_dp->i_mount;
@@ -2742,6 +2861,18 @@ xfs_rename(
                goto error_return;
        }
 
+       /*
+        * Handle RENAME_EXCHANGE flags
+        */
+       if (flags & RENAME_EXCHANGE) {
+               error = xfs_cross_rename(tp, src_dp, src_name, src_ip,
+                                        target_dp, target_name, target_ip,
+                                        &free_list, &first_block, spaceres);
+               if (error)
+                       goto abort_return;
+               goto finish_rename;
+       }
+
        /*
         * Set up the target.
         */
@@ -2881,6 +3012,7 @@ xfs_rename(
        if (new_parent)
                xfs_trans_log_inode(tp, target_dp, XFS_ILOG_CORE);
 
+finish_rename:
        /*
         * If this is a synchronous mount, make sure that the
         * rename transaction goes to disk before returning to
index 4ed2ba9342dc408d459462b66dc9cac10ad8d55d..f7722960b69cea95c68856f5c920fe735e541b00 100644 (file)
@@ -338,7 +338,7 @@ int         xfs_link(struct xfs_inode *tdp, struct xfs_inode *sip,
 int            xfs_rename(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);
+                          struct xfs_inode *target_ip, unsigned int flags);
 
 void           xfs_ilock(xfs_inode_t *, uint);
 int            xfs_ilock_nowait(xfs_inode_t *, uint);
index abb838a565ee6eb1b97b4b7d02305a3770af461d..ce80eeb8faa472fd225571de4df196dd6d689693 100644 (file)
@@ -384,19 +384,23 @@ xfs_vn_rename(
        unsigned int    flags)
 {
        struct inode    *new_inode = ndentry->d_inode;
+       int             omode = 0;
        struct xfs_name oname;
        struct xfs_name nname;
 
-       /* XFS does not support RENAME_EXCHANGE yet */
-       if (flags & ~RENAME_NOREPLACE)
+       if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE))
                return -EINVAL;
 
-       xfs_dentry_to_name(&oname, odentry, 0);
+       /* if we are exchanging files, we need to set i_mode of both files */
+       if (flags & RENAME_EXCHANGE)
+               omode = ndentry->d_inode->i_mode;
+
+       xfs_dentry_to_name(&oname, odentry, omode);
        xfs_dentry_to_name(&nname, ndentry, odentry->d_inode->i_mode);
 
        return xfs_rename(XFS_I(odir), &oname, XFS_I(odentry->d_inode),
                          XFS_I(ndir), &nname,
-                         new_inode ? XFS_I(new_inode) : NULL);
+                         new_inode ? XFS_I(new_inode) : NULL, flags);
 }
 
 /*