net/mlx4: Fix firmware command timeout during interrupt test
[cascardo/linux.git] / fs / xfs / xfs_bmap_util.c
index e827d65..552465e 100644 (file)
@@ -42,6 +42,9 @@
 #include "xfs_icache.h"
 #include "xfs_log.h"
 #include "xfs_rmap_btree.h"
+#include "xfs_iomap.h"
+#include "xfs_reflink.h"
+#include "xfs_refcount.h"
 
 /* Kernel only BMAP related definitions and functions */
 
@@ -389,11 +392,13 @@ xfs_bmap_count_blocks(
 STATIC int
 xfs_getbmapx_fix_eof_hole(
        xfs_inode_t             *ip,            /* xfs incore inode pointer */
+       int                     whichfork,
        struct getbmapx         *out,           /* output structure */
        int                     prealloced,     /* this is a file with
                                                 * preallocated data space */
        __int64_t               end,            /* last block requested */
-       xfs_fsblock_t           startblock)
+       xfs_fsblock_t           startblock,
+       bool                    moretocome)
 {
        __int64_t               fixlen;
        xfs_mount_t             *mp;            /* file system mount point */
@@ -418,8 +423,9 @@ xfs_getbmapx_fix_eof_hole(
                else
                        out->bmv_block = xfs_fsb_to_db(ip, startblock);
                fileblock = XFS_BB_TO_FSB(ip->i_mount, out->bmv_offset);
-               ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK);
-               if (xfs_iext_bno_to_ext(ifp, fileblock, &lastx) &&
+               ifp = XFS_IFORK_PTR(ip, whichfork);
+               if (!moretocome &&
+                   xfs_iext_bno_to_ext(ifp, fileblock, &lastx) &&
                   (lastx == (ifp->if_bytes / (uint)sizeof(xfs_bmbt_rec_t))-1))
                        out->bmv_oflags |= BMV_OF_LAST;
        }
@@ -427,6 +433,81 @@ xfs_getbmapx_fix_eof_hole(
        return 1;
 }
 
+/* Adjust the reported bmap around shared/unshared extent transitions. */
+STATIC int
+xfs_getbmap_adjust_shared(
+       struct xfs_inode                *ip,
+       int                             whichfork,
+       struct xfs_bmbt_irec            *map,
+       struct getbmapx                 *out,
+       struct xfs_bmbt_irec            *next_map)
+{
+       struct xfs_mount                *mp = ip->i_mount;
+       xfs_agnumber_t                  agno;
+       xfs_agblock_t                   agbno;
+       xfs_agblock_t                   ebno;
+       xfs_extlen_t                    elen;
+       xfs_extlen_t                    nlen;
+       int                             error;
+
+       next_map->br_startblock = NULLFSBLOCK;
+       next_map->br_startoff = NULLFILEOFF;
+       next_map->br_blockcount = 0;
+
+       /* Only written data blocks can be shared. */
+       if (!xfs_is_reflink_inode(ip) || whichfork != XFS_DATA_FORK ||
+           map->br_startblock == DELAYSTARTBLOCK ||
+           map->br_startblock == HOLESTARTBLOCK ||
+           ISUNWRITTEN(map))
+               return 0;
+
+       agno = XFS_FSB_TO_AGNO(mp, map->br_startblock);
+       agbno = XFS_FSB_TO_AGBNO(mp, map->br_startblock);
+       error = xfs_reflink_find_shared(mp, agno, agbno, map->br_blockcount,
+                       &ebno, &elen, true);
+       if (error)
+               return error;
+
+       if (ebno == NULLAGBLOCK) {
+               /* No shared blocks at all. */
+               return 0;
+       } else if (agbno == ebno) {
+               /*
+                * Shared extent at (agbno, elen).  Shrink the reported
+                * extent length and prepare to move the start of map[i]
+                * to agbno+elen, with the aim of (re)formatting the new
+                * map[i] the next time through the inner loop.
+                */
+               out->bmv_length = XFS_FSB_TO_BB(mp, elen);
+               out->bmv_oflags |= BMV_OF_SHARED;
+               if (elen != map->br_blockcount) {
+                       *next_map = *map;
+                       next_map->br_startblock += elen;
+                       next_map->br_startoff += elen;
+                       next_map->br_blockcount -= elen;
+               }
+               map->br_blockcount -= elen;
+       } else {
+               /*
+                * There's an unshared extent (agbno, ebno - agbno)
+                * followed by shared extent at (ebno, elen).  Shrink
+                * the reported extent length to cover only the unshared
+                * extent and prepare to move up the start of map[i] to
+                * ebno, with the aim of (re)formatting the new map[i]
+                * the next time through the inner loop.
+                */
+               *next_map = *map;
+               nlen = ebno - agbno;
+               out->bmv_length = XFS_FSB_TO_BB(mp, nlen);
+               next_map->br_startblock += nlen;
+               next_map->br_startoff += nlen;
+               next_map->br_blockcount -= nlen;
+               map->br_blockcount -= nlen;
+       }
+
+       return 0;
+}
+
 /*
  * Get inode's extents as described in bmv, and format for output.
  * Calls formatter to fill the user's buffer until all extents
@@ -459,12 +540,28 @@ xfs_getbmap(
        int                     iflags;         /* interface flags */
        int                     bmapi_flags;    /* flags for xfs_bmapi */
        int                     cur_ext = 0;
+       struct xfs_bmbt_irec    inject_map;
 
        mp = ip->i_mount;
        iflags = bmv->bmv_iflags;
-       whichfork = iflags & BMV_IF_ATTRFORK ? XFS_ATTR_FORK : XFS_DATA_FORK;
 
-       if (whichfork == XFS_ATTR_FORK) {
+#ifndef DEBUG
+       /* Only allow CoW fork queries if we're debugging. */
+       if (iflags & BMV_IF_COWFORK)
+               return -EINVAL;
+#endif
+       if ((iflags & BMV_IF_ATTRFORK) && (iflags & BMV_IF_COWFORK))
+               return -EINVAL;
+
+       if (iflags & BMV_IF_ATTRFORK)
+               whichfork = XFS_ATTR_FORK;
+       else if (iflags & BMV_IF_COWFORK)
+               whichfork = XFS_COW_FORK;
+       else
+               whichfork = XFS_DATA_FORK;
+
+       switch (whichfork) {
+       case XFS_ATTR_FORK:
                if (XFS_IFORK_Q(ip)) {
                        if (ip->i_d.di_aformat != XFS_DINODE_FMT_EXTENTS &&
                            ip->i_d.di_aformat != XFS_DINODE_FMT_BTREE &&
@@ -480,7 +577,20 @@ xfs_getbmap(
 
                prealloced = 0;
                fixlen = 1LL << 32;
-       } else {
+               break;
+       case XFS_COW_FORK:
+               if (ip->i_cformat != XFS_DINODE_FMT_EXTENTS)
+                       return -EINVAL;
+
+               if (xfs_get_cowextsz_hint(ip)) {
+                       prealloced = 1;
+                       fixlen = mp->m_super->s_maxbytes;
+               } else {
+                       prealloced = 0;
+                       fixlen = XFS_ISIZE(ip);
+               }
+               break;
+       default:
                if (ip->i_d.di_format != XFS_DINODE_FMT_EXTENTS &&
                    ip->i_d.di_format != XFS_DINODE_FMT_BTREE &&
                    ip->i_d.di_format != XFS_DINODE_FMT_LOCAL)
@@ -494,6 +604,7 @@ xfs_getbmap(
                        prealloced = 0;
                        fixlen = XFS_ISIZE(ip);
                }
+               break;
        }
 
        if (bmv->bmv_length == -1) {
@@ -520,7 +631,8 @@ xfs_getbmap(
                return -ENOMEM;
 
        xfs_ilock(ip, XFS_IOLOCK_SHARED);
-       if (whichfork == XFS_DATA_FORK) {
+       switch (whichfork) {
+       case XFS_DATA_FORK:
                if (!(iflags & BMV_IF_DELALLOC) &&
                    (ip->i_delayed_blks || XFS_ISIZE(ip) > ip->i_d.di_size)) {
                        error = filemap_write_and_wait(VFS_I(ip)->i_mapping);
@@ -538,8 +650,14 @@ xfs_getbmap(
                }
 
                lock = xfs_ilock_data_map_shared(ip);
-       } else {
+               break;
+       case XFS_COW_FORK:
+               lock = XFS_ILOCK_SHARED;
+               xfs_ilock(ip, lock);
+               break;
+       case XFS_ATTR_FORK:
                lock = xfs_ilock_attr_map_shared(ip);
+               break;
        }
 
        /*
@@ -581,7 +699,8 @@ xfs_getbmap(
                        goto out_free_map;
                ASSERT(nmap <= subnex);
 
-               for (i = 0; i < nmap && nexleft && bmv->bmv_length; i++) {
+               for (i = 0; i < nmap && nexleft && bmv->bmv_length &&
+                               cur_ext < bmv->bmv_count; i++) {
                        out[cur_ext].bmv_oflags = 0;
                        if (map[i].br_state == XFS_EXT_UNWRITTEN)
                                out[cur_ext].bmv_oflags |= BMV_OF_PREALLOC;
@@ -614,9 +733,16 @@ xfs_getbmap(
                                goto out_free_map;
                        }
 
-                       if (!xfs_getbmapx_fix_eof_hole(ip, &out[cur_ext],
-                                       prealloced, bmvend,
-                                       map[i].br_startblock))
+                       /* Is this a shared block? */
+                       error = xfs_getbmap_adjust_shared(ip, whichfork,
+                                       &map[i], &out[cur_ext], &inject_map);
+                       if (error)
+                               goto out_free_map;
+
+                       if (!xfs_getbmapx_fix_eof_hole(ip, whichfork,
+                                       &out[cur_ext], prealloced, bmvend,
+                                       map[i].br_startblock,
+                                       inject_map.br_startblock != NULLFSBLOCK))
                                goto out_free_map;
 
                        bmv->bmv_offset =
@@ -636,11 +762,16 @@ xfs_getbmap(
                                continue;
                        }
 
-                       nexleft--;
+                       if (inject_map.br_startblock != NULLFSBLOCK) {
+                               map[i] = inject_map;
+                               i--;
+                       } else
+                               nexleft--;
                        bmv->bmv_entries++;
                        cur_ext++;
                }
-       } while (nmap && nexleft && bmv->bmv_length);
+       } while (nmap && nexleft && bmv->bmv_length &&
+                cur_ext < bmv->bmv_count);
 
  out_free_map:
        kmem_free(map);
@@ -1433,8 +1564,8 @@ xfs_insert_file_space(
  */
 static int
 xfs_swap_extents_check_format(
-       xfs_inode_t     *ip,    /* target inode */
-       xfs_inode_t     *tip)   /* tmp inode */
+       struct xfs_inode        *ip,    /* target inode */
+       struct xfs_inode        *tip)   /* tmp inode */
 {
 
        /* Should never get a local format */
@@ -1449,6 +1580,13 @@ xfs_swap_extents_check_format(
        if (ip->i_d.di_nextents < tip->i_d.di_nextents)
                return -EINVAL;
 
+       /*
+        * If we have to use the (expensive) rmap swap method, we can
+        * handle any number of extents and any format.
+        */
+       if (xfs_sb_version_hasrmapbt(&ip->i_mount->m_sb))
+               return 0;
+
        /*
         * if the target inode is in extent form and the temp inode is in btree
         * form then we will end up with the target inode in the wrong format
@@ -1518,125 +1656,161 @@ xfs_swap_extent_flush(
        return 0;
 }
 
-int
-xfs_swap_extents(
-       xfs_inode_t     *ip,    /* target inode */
-       xfs_inode_t     *tip,   /* tmp inode */
-       xfs_swapext_t   *sxp)
+/*
+ * Move extents from one file to another, when rmap is enabled.
+ */
+STATIC int
+xfs_swap_extent_rmap(
+       struct xfs_trans                **tpp,
+       struct xfs_inode                *ip,
+       struct xfs_inode                *tip)
 {
-       xfs_mount_t     *mp = ip->i_mount;
-       xfs_trans_t     *tp;
-       xfs_bstat_t     *sbp = &sxp->sx_stat;
-       xfs_ifork_t     *tempifp, *ifp, *tifp;
-       int             src_log_flags, target_log_flags;
-       int             error = 0;
-       int             aforkblks = 0;
-       int             taforkblks = 0;
-       __uint64_t      tmp;
-       int             lock_flags;
-
-       /* XXX: we can't do this with rmap, will fix later */
-       if (xfs_sb_version_hasrmapbt(&mp->m_sb))
-               return -EOPNOTSUPP;
-
-       tempifp = kmem_alloc(sizeof(xfs_ifork_t), KM_MAYFAIL);
-       if (!tempifp) {
-               error = -ENOMEM;
-               goto out;
-       }
+       struct xfs_bmbt_irec            irec;
+       struct xfs_bmbt_irec            uirec;
+       struct xfs_bmbt_irec            tirec;
+       xfs_fileoff_t                   offset_fsb;
+       xfs_fileoff_t                   end_fsb;
+       xfs_filblks_t                   count_fsb;
+       xfs_fsblock_t                   firstfsb;
+       struct xfs_defer_ops            dfops;
+       int                             error;
+       xfs_filblks_t                   ilen;
+       xfs_filblks_t                   rlen;
+       int                             nimaps;
+       __uint64_t                      tip_flags2;
 
        /*
-        * Lock the inodes against other IO, page faults and truncate to
-        * begin with.  Then we can ensure the inodes are flushed and have no
-        * page cache safely. Once we have done this we can take the ilocks and
-        * do the rest of the checks.
+        * If the source file has shared blocks, we must flag the donor
+        * file as having shared blocks so that we get the shared-block
+        * rmap functions when we go to fix up the rmaps.  The flags
+        * will be switch for reals later.
         */
-       lock_flags = XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL;
-       xfs_lock_two_inodes(ip, tip, XFS_IOLOCK_EXCL);
-       xfs_lock_two_inodes(ip, tip, XFS_MMAPLOCK_EXCL);
-
-       /* Verify that both files have the same format */
-       if ((VFS_I(ip)->i_mode & S_IFMT) != (VFS_I(tip)->i_mode & S_IFMT)) {
-               error = -EINVAL;
-               goto out_unlock;
-       }
+       tip_flags2 = tip->i_d.di_flags2;
+       if (ip->i_d.di_flags2 & XFS_DIFLAG2_REFLINK)
+               tip->i_d.di_flags2 |= XFS_DIFLAG2_REFLINK;
+
+       offset_fsb = 0;
+       end_fsb = XFS_B_TO_FSB(ip->i_mount, i_size_read(VFS_I(ip)));
+       count_fsb = (xfs_filblks_t)(end_fsb - offset_fsb);
+
+       while (count_fsb) {
+               /* Read extent from the donor file */
+               nimaps = 1;
+               error = xfs_bmapi_read(tip, offset_fsb, count_fsb, &tirec,
+                               &nimaps, 0);
+               if (error)
+                       goto out;
+               ASSERT(nimaps == 1);
+               ASSERT(tirec.br_startblock != DELAYSTARTBLOCK);
+
+               trace_xfs_swap_extent_rmap_remap(tip, &tirec);
+               ilen = tirec.br_blockcount;
+
+               /* Unmap the old blocks in the source file. */
+               while (tirec.br_blockcount) {
+                       xfs_defer_init(&dfops, &firstfsb);
+                       trace_xfs_swap_extent_rmap_remap_piece(tip, &tirec);
+
+                       /* Read extent from the source file */
+                       nimaps = 1;
+                       error = xfs_bmapi_read(ip, tirec.br_startoff,
+                                       tirec.br_blockcount, &irec,
+                                       &nimaps, 0);
+                       if (error)
+                               goto out_defer;
+                       ASSERT(nimaps == 1);
+                       ASSERT(tirec.br_startoff == irec.br_startoff);
+                       trace_xfs_swap_extent_rmap_remap_piece(ip, &irec);
+
+                       /* Trim the extent. */
+                       uirec = tirec;
+                       uirec.br_blockcount = rlen = min_t(xfs_filblks_t,
+                                       tirec.br_blockcount,
+                                       irec.br_blockcount);
+                       trace_xfs_swap_extent_rmap_remap_piece(tip, &uirec);
+
+                       /* Remove the mapping from the donor file. */
+                       error = xfs_bmap_unmap_extent((*tpp)->t_mountp, &dfops,
+                                       tip, &uirec);
+                       if (error)
+                               goto out_defer;
 
-       /* Verify both files are either real-time or non-realtime */
-       if (XFS_IS_REALTIME_INODE(ip) != XFS_IS_REALTIME_INODE(tip)) {
-               error = -EINVAL;
-               goto out_unlock;
-       }
+                       /* Remove the mapping from the source file. */
+                       error = xfs_bmap_unmap_extent((*tpp)->t_mountp, &dfops,
+                                       ip, &irec);
+                       if (error)
+                               goto out_defer;
 
-       error = xfs_swap_extent_flush(ip);
-       if (error)
-               goto out_unlock;
-       error = xfs_swap_extent_flush(tip);
-       if (error)
-               goto out_unlock;
+                       /* Map the donor file's blocks into the source file. */
+                       error = xfs_bmap_map_extent((*tpp)->t_mountp, &dfops,
+                                       ip, &uirec);
+                       if (error)
+                               goto out_defer;
 
-       error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp);
-       if (error)
-               goto out_unlock;
+                       /* Map the source file's blocks into the donor file. */
+                       error = xfs_bmap_map_extent((*tpp)->t_mountp, &dfops,
+                                       tip, &irec);
+                       if (error)
+                               goto out_defer;
 
-       /*
-        * Lock and join the inodes to the tansaction so that transaction commit
-        * or cancel will unlock the inodes from this point onwards.
-        */
-       xfs_lock_two_inodes(ip, tip, XFS_ILOCK_EXCL);
-       lock_flags |= XFS_ILOCK_EXCL;
-       xfs_trans_ijoin(tp, ip, lock_flags);
-       xfs_trans_ijoin(tp, tip, lock_flags);
+                       error = xfs_defer_finish(tpp, &dfops, ip);
+                       if (error)
+                               goto out_defer;
 
+                       tirec.br_startoff += rlen;
+                       if (tirec.br_startblock != HOLESTARTBLOCK &&
+                           tirec.br_startblock != DELAYSTARTBLOCK)
+                               tirec.br_startblock += rlen;
+                       tirec.br_blockcount -= rlen;
+               }
 
-       /* Verify all data are being swapped */
-       if (sxp->sx_offset != 0 ||
-           sxp->sx_length != ip->i_d.di_size ||
-           sxp->sx_length != tip->i_d.di_size) {
-               error = -EFAULT;
-               goto out_trans_cancel;
+               /* Roll on... */
+               count_fsb -= ilen;
+               offset_fsb += ilen;
        }
 
-       trace_xfs_swap_extent_before(ip, 0);
-       trace_xfs_swap_extent_before(tip, 1);
+       tip->i_d.di_flags2 = tip_flags2;
+       return 0;
 
-       /* check inode formats now that data is flushed */
-       error = xfs_swap_extents_check_format(ip, tip);
-       if (error) {
-               xfs_notice(mp,
-                   "%s: inode 0x%llx format is incompatible for exchanging.",
-                               __func__, ip->i_ino);
-               goto out_trans_cancel;
-       }
+out_defer:
+       xfs_defer_cancel(&dfops);
+out:
+       trace_xfs_swap_extent_rmap_error(ip, error, _RET_IP_);
+       tip->i_d.di_flags2 = tip_flags2;
+       return error;
+}
+
+/* Swap the extents of two files by swapping data forks. */
+STATIC int
+xfs_swap_extent_forks(
+       struct xfs_trans        *tp,
+       struct xfs_inode        *ip,
+       struct xfs_inode        *tip,
+       int                     *src_log_flags,
+       int                     *target_log_flags)
+{
+       struct xfs_ifork        tempifp, *ifp, *tifp;
+       int                     aforkblks = 0;
+       int                     taforkblks = 0;
+       __uint64_t              tmp;
+       int                     error;
 
-       /*
-        * Compare the current change & modify times with that
-        * passed in.  If they differ, we abort this swap.
-        * This is the mechanism used to ensure the calling
-        * process that the file was not changed out from
-        * under it.
-        */
-       if ((sbp->bs_ctime.tv_sec != VFS_I(ip)->i_ctime.tv_sec) ||
-           (sbp->bs_ctime.tv_nsec != VFS_I(ip)->i_ctime.tv_nsec) ||
-           (sbp->bs_mtime.tv_sec != VFS_I(ip)->i_mtime.tv_sec) ||
-           (sbp->bs_mtime.tv_nsec != VFS_I(ip)->i_mtime.tv_nsec)) {
-               error = -EBUSY;
-               goto out_trans_cancel;
-       }
        /*
         * Count the number of extended attribute blocks
         */
        if ( ((XFS_IFORK_Q(ip) != 0) && (ip->i_d.di_anextents > 0)) &&
             (ip->i_d.di_aformat != XFS_DINODE_FMT_LOCAL)) {
-               error = xfs_bmap_count_blocks(tp, ip, XFS_ATTR_FORK, &aforkblks);
+               error = xfs_bmap_count_blocks(tp, ip, XFS_ATTR_FORK,
+                               &aforkblks);
                if (error)
-                       goto out_trans_cancel;
+                       return error;
        }
        if ( ((XFS_IFORK_Q(tip) != 0) && (tip->i_d.di_anextents > 0)) &&
             (tip->i_d.di_aformat != XFS_DINODE_FMT_LOCAL)) {
                error = xfs_bmap_count_blocks(tp, tip, XFS_ATTR_FORK,
-                       &taforkblks);
+                               &taforkblks);
                if (error)
-                       goto out_trans_cancel;
+                       return error;
        }
 
        /*
@@ -1645,31 +1819,23 @@ xfs_swap_extents(
         * buffers, and so the validation done on read will expect the owner
         * field to be correctly set. Once we change the owners, we can swap the
         * inode forks.
-        *
-        * Note the trickiness in setting the log flags - we set the owner log
-        * flag on the opposite inode (i.e. the inode we are setting the new
-        * owner to be) because once we swap the forks and log that, log
-        * recovery is going to see the fork as owned by the swapped inode,
-        * not the pre-swapped inodes.
         */
-       src_log_flags = XFS_ILOG_CORE;
-       target_log_flags = XFS_ILOG_CORE;
        if (ip->i_d.di_version == 3 &&
            ip->i_d.di_format == XFS_DINODE_FMT_BTREE) {
-               target_log_flags |= XFS_ILOG_DOWNER;
+               (*target_log_flags) |= XFS_ILOG_DOWNER;
                error = xfs_bmbt_change_owner(tp, ip, XFS_DATA_FORK,
                                              tip->i_ino, NULL);
                if (error)
-                       goto out_trans_cancel;
+                       return error;
        }
 
        if (tip->i_d.di_version == 3 &&
            tip->i_d.di_format == XFS_DINODE_FMT_BTREE) {
-               src_log_flags |= XFS_ILOG_DOWNER;
+               (*src_log_flags) |= XFS_ILOG_DOWNER;
                error = xfs_bmbt_change_owner(tp, tip, XFS_DATA_FORK,
                                              ip->i_ino, NULL);
                if (error)
-                       goto out_trans_cancel;
+                       return error;
        }
 
        /*
@@ -1677,9 +1843,9 @@ xfs_swap_extents(
         */
        ifp = &ip->i_df;
        tifp = &tip->i_df;
-       *tempifp = *ifp;        /* struct copy */
+       tempifp = *ifp;         /* struct copy */
        *ifp = *tifp;           /* struct copy */
-       *tifp = *tempifp;       /* struct copy */
+       *tifp = tempifp;        /* struct copy */
 
        /*
         * Fix the on-disk inode values
@@ -1719,12 +1885,12 @@ xfs_swap_extents(
                        ifp->if_u1.if_extents =
                                ifp->if_u2.if_inline_ext;
                }
-               src_log_flags |= XFS_ILOG_DEXT;
+               (*src_log_flags) |= XFS_ILOG_DEXT;
                break;
        case XFS_DINODE_FMT_BTREE:
                ASSERT(ip->i_d.di_version < 3 ||
-                      (src_log_flags & XFS_ILOG_DOWNER));
-               src_log_flags |= XFS_ILOG_DBROOT;
+                      (*src_log_flags & XFS_ILOG_DOWNER));
+               (*src_log_flags) |= XFS_ILOG_DBROOT;
                break;
        }
 
@@ -1738,15 +1904,166 @@ xfs_swap_extents(
                        tifp->if_u1.if_extents =
                                tifp->if_u2.if_inline_ext;
                }
-               target_log_flags |= XFS_ILOG_DEXT;
+               (*target_log_flags) |= XFS_ILOG_DEXT;
                break;
        case XFS_DINODE_FMT_BTREE:
-               target_log_flags |= XFS_ILOG_DBROOT;
+               (*target_log_flags) |= XFS_ILOG_DBROOT;
                ASSERT(tip->i_d.di_version < 3 ||
-                      (target_log_flags & XFS_ILOG_DOWNER));
+                      (*target_log_flags & XFS_ILOG_DOWNER));
                break;
        }
 
+       return 0;
+}
+
+int
+xfs_swap_extents(
+       struct xfs_inode        *ip,    /* target inode */
+       struct xfs_inode        *tip,   /* tmp inode */
+       struct xfs_swapext      *sxp)
+{
+       struct xfs_mount        *mp = ip->i_mount;
+       struct xfs_trans        *tp;
+       struct xfs_bstat        *sbp = &sxp->sx_stat;
+       int                     src_log_flags, target_log_flags;
+       int                     error = 0;
+       int                     lock_flags;
+       struct xfs_ifork        *cowfp;
+       __uint64_t              f;
+       int                     resblks;
+
+       /*
+        * Lock the inodes against other IO, page faults and truncate to
+        * begin with.  Then we can ensure the inodes are flushed and have no
+        * page cache safely. Once we have done this we can take the ilocks and
+        * do the rest of the checks.
+        */
+       lock_flags = XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL;
+       xfs_lock_two_inodes(ip, tip, XFS_IOLOCK_EXCL);
+       xfs_lock_two_inodes(ip, tip, XFS_MMAPLOCK_EXCL);
+
+       /* Verify that both files have the same format */
+       if ((VFS_I(ip)->i_mode & S_IFMT) != (VFS_I(tip)->i_mode & S_IFMT)) {
+               error = -EINVAL;
+               goto out_unlock;
+       }
+
+       /* Verify both files are either real-time or non-realtime */
+       if (XFS_IS_REALTIME_INODE(ip) != XFS_IS_REALTIME_INODE(tip)) {
+               error = -EINVAL;
+               goto out_unlock;
+       }
+
+       error = xfs_swap_extent_flush(ip);
+       if (error)
+               goto out_unlock;
+       error = xfs_swap_extent_flush(tip);
+       if (error)
+               goto out_unlock;
+
+       /*
+        * Extent "swapping" with rmap requires a permanent reservation and
+        * a block reservation because it's really just a remap operation
+        * performed with log redo items!
+        */
+       if (xfs_sb_version_hasrmapbt(&mp->m_sb)) {
+               /*
+                * Conceptually this shouldn't affect the shape of either
+                * bmbt, but since we atomically move extents one by one,
+                * we reserve enough space to rebuild both trees.
+                */
+               resblks = XFS_SWAP_RMAP_SPACE_RES(mp,
+                               XFS_IFORK_NEXTENTS(ip, XFS_DATA_FORK),
+                               XFS_DATA_FORK) +
+                         XFS_SWAP_RMAP_SPACE_RES(mp,
+                               XFS_IFORK_NEXTENTS(tip, XFS_DATA_FORK),
+                               XFS_DATA_FORK);
+               error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, resblks,
+                               0, 0, &tp);
+       } else
+               error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0,
+                               0, 0, &tp);
+       if (error)
+               goto out_unlock;
+
+       /*
+        * Lock and join the inodes to the tansaction so that transaction commit
+        * or cancel will unlock the inodes from this point onwards.
+        */
+       xfs_lock_two_inodes(ip, tip, XFS_ILOCK_EXCL);
+       lock_flags |= XFS_ILOCK_EXCL;
+       xfs_trans_ijoin(tp, ip, 0);
+       xfs_trans_ijoin(tp, tip, 0);
+
+
+       /* Verify all data are being swapped */
+       if (sxp->sx_offset != 0 ||
+           sxp->sx_length != ip->i_d.di_size ||
+           sxp->sx_length != tip->i_d.di_size) {
+               error = -EFAULT;
+               goto out_trans_cancel;
+       }
+
+       trace_xfs_swap_extent_before(ip, 0);
+       trace_xfs_swap_extent_before(tip, 1);
+
+       /* check inode formats now that data is flushed */
+       error = xfs_swap_extents_check_format(ip, tip);
+       if (error) {
+               xfs_notice(mp,
+                   "%s: inode 0x%llx format is incompatible for exchanging.",
+                               __func__, ip->i_ino);
+               goto out_trans_cancel;
+       }
+
+       /*
+        * Compare the current change & modify times with that
+        * passed in.  If they differ, we abort this swap.
+        * This is the mechanism used to ensure the calling
+        * process that the file was not changed out from
+        * under it.
+        */
+       if ((sbp->bs_ctime.tv_sec != VFS_I(ip)->i_ctime.tv_sec) ||
+           (sbp->bs_ctime.tv_nsec != VFS_I(ip)->i_ctime.tv_nsec) ||
+           (sbp->bs_mtime.tv_sec != VFS_I(ip)->i_mtime.tv_sec) ||
+           (sbp->bs_mtime.tv_nsec != VFS_I(ip)->i_mtime.tv_nsec)) {
+               error = -EBUSY;
+               goto out_trans_cancel;
+       }
+
+       /*
+        * Note the trickiness in setting the log flags - we set the owner log
+        * flag on the opposite inode (i.e. the inode we are setting the new
+        * owner to be) because once we swap the forks and log that, log
+        * recovery is going to see the fork as owned by the swapped inode,
+        * not the pre-swapped inodes.
+        */
+       src_log_flags = XFS_ILOG_CORE;
+       target_log_flags = XFS_ILOG_CORE;
+
+       if (xfs_sb_version_hasrmapbt(&mp->m_sb))
+               error = xfs_swap_extent_rmap(&tp, ip, tip);
+       else
+               error = xfs_swap_extent_forks(tp, ip, tip, &src_log_flags,
+                               &target_log_flags);
+       if (error)
+               goto out_trans_cancel;
+
+       /* Do we have to swap reflink flags? */
+       if ((ip->i_d.di_flags2 & XFS_DIFLAG2_REFLINK) ^
+           (tip->i_d.di_flags2 & XFS_DIFLAG2_REFLINK)) {
+               f = ip->i_d.di_flags2 & XFS_DIFLAG2_REFLINK;
+               ip->i_d.di_flags2 &= ~XFS_DIFLAG2_REFLINK;
+               ip->i_d.di_flags2 |= tip->i_d.di_flags2 & XFS_DIFLAG2_REFLINK;
+               tip->i_d.di_flags2 &= ~XFS_DIFLAG2_REFLINK;
+               tip->i_d.di_flags2 |= f & XFS_DIFLAG2_REFLINK;
+               cowfp = ip->i_cowfp;
+               ip->i_cowfp = tip->i_cowfp;
+               tip->i_cowfp = cowfp;
+               xfs_inode_set_cowblocks_tag(ip);
+               xfs_inode_set_cowblocks_tag(tip);
+       }
+
        xfs_trans_log_inode(tp, ip,  src_log_flags);
        xfs_trans_log_inode(tp, tip, target_log_flags);
 
@@ -1761,16 +2078,16 @@ xfs_swap_extents(
 
        trace_xfs_swap_extent_after(ip, 0);
        trace_xfs_swap_extent_after(tip, 1);
-out:
-       kmem_free(tempifp);
-       return error;
 
-out_unlock:
        xfs_iunlock(ip, lock_flags);
        xfs_iunlock(tip, lock_flags);
-       goto out;
+       return error;
 
 out_trans_cancel:
        xfs_trans_cancel(tp);
-       goto out;
+
+out_unlock:
+       xfs_iunlock(ip, lock_flags);
+       xfs_iunlock(tip, lock_flags);
+       return error;
 }