]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/blobdiff - libxfs/xfs_rmap.c
xfs: fix transaction leak on remote attr set/remove failure
[thirdparty/xfsprogs-dev.git] / libxfs / xfs_rmap.c
index 82c2597c251909bc3c0c27002444c874d46aab81..50f6043e9236fcee014049663c6e7eced90c8199 100644 (file)
@@ -1,19 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
 /*
  * Copyright (c) 2014 Red Hat, Inc.
  * All Rights Reserved.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it would be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write the Free Software Foundation,
- * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 #include "libxfs_priv.h"
 #include "xfs_fs.h"
 #include "xfs_rmap_btree.h"
 #include "xfs_trans_space.h"
 #include "xfs_trace.h"
+#include "xfs_errortag.h"
 #include "xfs_bmap.h"
 #include "xfs_inode.h"
+#include "xfs_ialloc.h"
 
 /*
  * Lookup the first record less than or equal to [bno, len, owner, offset]
@@ -146,7 +136,39 @@ done:
        return error;
 }
 
-static int
+STATIC int
+xfs_rmap_delete(
+       struct xfs_btree_cur    *rcur,
+       xfs_agblock_t           agbno,
+       xfs_extlen_t            len,
+       uint64_t                owner,
+       uint64_t                offset,
+       unsigned int            flags)
+{
+       int                     i;
+       int                     error;
+
+       trace_xfs_rmap_delete(rcur->bc_mp, rcur->bc_private.a.agno, agbno,
+                       len, owner, offset, flags);
+
+       error = xfs_rmap_lookup_eq(rcur, agbno, len, owner, offset, flags, &i);
+       if (error)
+               goto done;
+       XFS_WANT_CORRUPTED_GOTO(rcur->bc_mp, i == 1, done);
+
+       error = xfs_btree_delete(rcur, &i);
+       if (error)
+               goto done;
+       XFS_WANT_CORRUPTED_GOTO(rcur->bc_mp, i == 1, done);
+done:
+       if (error)
+               trace_xfs_rmap_delete_error(rcur->bc_mp,
+                               rcur->bc_private.a.agno, error, _RET_IP_);
+       return error;
+}
+
+/* Convert an internal btree record to an rmap record. */
+int
 xfs_rmap_btrec_to_irec(
        union xfs_btree_rec     *rec,
        struct xfs_rmap_irec    *irec)
@@ -168,6 +190,8 @@ xfs_rmap_get_rec(
        struct xfs_rmap_irec    *irec,
        int                     *stat)
 {
+       struct xfs_mount        *mp = cur->bc_mp;
+       xfs_agnumber_t          agno = cur->bc_private.a.agno;
        union xfs_btree_rec     *rec;
        int                     error;
 
@@ -175,7 +199,241 @@ xfs_rmap_get_rec(
        if (error || !*stat)
                return error;
 
-       return xfs_rmap_btrec_to_irec(rec, irec);
+       if (xfs_rmap_btrec_to_irec(rec, irec))
+               goto out_bad_rec;
+
+       if (irec->rm_blockcount == 0)
+               goto out_bad_rec;
+       if (irec->rm_startblock <= XFS_AGFL_BLOCK(mp)) {
+               if (irec->rm_owner != XFS_RMAP_OWN_FS)
+                       goto out_bad_rec;
+               if (irec->rm_blockcount != XFS_AGFL_BLOCK(mp) + 1)
+                       goto out_bad_rec;
+       } else {
+               /* check for valid extent range, including overflow */
+               if (!xfs_verify_agbno(mp, agno, irec->rm_startblock))
+                       goto out_bad_rec;
+               if (irec->rm_startblock >
+                               irec->rm_startblock + irec->rm_blockcount)
+                       goto out_bad_rec;
+               if (!xfs_verify_agbno(mp, agno,
+                               irec->rm_startblock + irec->rm_blockcount - 1))
+                       goto out_bad_rec;
+       }
+
+       if (!(xfs_verify_ino(mp, irec->rm_owner) ||
+             (irec->rm_owner <= XFS_RMAP_OWN_FS &&
+              irec->rm_owner >= XFS_RMAP_OWN_MIN)))
+               goto out_bad_rec;
+
+       return 0;
+out_bad_rec:
+       xfs_warn(mp,
+               "Reverse Mapping BTree record corruption in AG %d detected!",
+               agno);
+       xfs_warn(mp,
+               "Owner 0x%llx, flags 0x%x, start block 0x%x block count 0x%x",
+               irec->rm_owner, irec->rm_flags, irec->rm_startblock,
+               irec->rm_blockcount);
+       return -EFSCORRUPTED;
+}
+
+struct xfs_find_left_neighbor_info {
+       struct xfs_rmap_irec    high;
+       struct xfs_rmap_irec    *irec;
+       int                     *stat;
+};
+
+/* For each rmap given, figure out if it matches the key we want. */
+STATIC int
+xfs_rmap_find_left_neighbor_helper(
+       struct xfs_btree_cur    *cur,
+       struct xfs_rmap_irec    *rec,
+       void                    *priv)
+{
+       struct xfs_find_left_neighbor_info      *info = priv;
+
+       trace_xfs_rmap_find_left_neighbor_candidate(cur->bc_mp,
+                       cur->bc_private.a.agno, rec->rm_startblock,
+                       rec->rm_blockcount, rec->rm_owner, rec->rm_offset,
+                       rec->rm_flags);
+
+       if (rec->rm_owner != info->high.rm_owner)
+               return XFS_BTREE_QUERY_RANGE_CONTINUE;
+       if (!XFS_RMAP_NON_INODE_OWNER(rec->rm_owner) &&
+           !(rec->rm_flags & XFS_RMAP_BMBT_BLOCK) &&
+           rec->rm_offset + rec->rm_blockcount - 1 != info->high.rm_offset)
+               return XFS_BTREE_QUERY_RANGE_CONTINUE;
+
+       *info->irec = *rec;
+       *info->stat = 1;
+       return XFS_BTREE_QUERY_RANGE_ABORT;
+}
+
+/*
+ * Find the record to the left of the given extent, being careful only to
+ * return a match with the same owner and adjacent physical and logical
+ * block ranges.
+ */
+int
+xfs_rmap_find_left_neighbor(
+       struct xfs_btree_cur    *cur,
+       xfs_agblock_t           bno,
+       uint64_t                owner,
+       uint64_t                offset,
+       unsigned int            flags,
+       struct xfs_rmap_irec    *irec,
+       int                     *stat)
+{
+       struct xfs_find_left_neighbor_info      info;
+       int                     error;
+
+       *stat = 0;
+       if (bno == 0)
+               return 0;
+       info.high.rm_startblock = bno - 1;
+       info.high.rm_owner = owner;
+       if (!XFS_RMAP_NON_INODE_OWNER(owner) &&
+           !(flags & XFS_RMAP_BMBT_BLOCK)) {
+               if (offset == 0)
+                       return 0;
+               info.high.rm_offset = offset - 1;
+       } else
+               info.high.rm_offset = 0;
+       info.high.rm_flags = flags;
+       info.high.rm_blockcount = 0;
+       info.irec = irec;
+       info.stat = stat;
+
+       trace_xfs_rmap_find_left_neighbor_query(cur->bc_mp,
+                       cur->bc_private.a.agno, bno, 0, owner, offset, flags);
+
+       error = xfs_rmap_query_range(cur, &info.high, &info.high,
+                       xfs_rmap_find_left_neighbor_helper, &info);
+       if (error == XFS_BTREE_QUERY_RANGE_ABORT)
+               error = 0;
+       if (*stat)
+               trace_xfs_rmap_find_left_neighbor_result(cur->bc_mp,
+                               cur->bc_private.a.agno, irec->rm_startblock,
+                               irec->rm_blockcount, irec->rm_owner,
+                               irec->rm_offset, irec->rm_flags);
+       return error;
+}
+
+/* For each rmap given, figure out if it matches the key we want. */
+STATIC int
+xfs_rmap_lookup_le_range_helper(
+       struct xfs_btree_cur    *cur,
+       struct xfs_rmap_irec    *rec,
+       void                    *priv)
+{
+       struct xfs_find_left_neighbor_info      *info = priv;
+
+       trace_xfs_rmap_lookup_le_range_candidate(cur->bc_mp,
+                       cur->bc_private.a.agno, rec->rm_startblock,
+                       rec->rm_blockcount, rec->rm_owner, rec->rm_offset,
+                       rec->rm_flags);
+
+       if (rec->rm_owner != info->high.rm_owner)
+               return XFS_BTREE_QUERY_RANGE_CONTINUE;
+       if (!XFS_RMAP_NON_INODE_OWNER(rec->rm_owner) &&
+           !(rec->rm_flags & XFS_RMAP_BMBT_BLOCK) &&
+           (rec->rm_offset > info->high.rm_offset ||
+            rec->rm_offset + rec->rm_blockcount <= info->high.rm_offset))
+               return XFS_BTREE_QUERY_RANGE_CONTINUE;
+
+       *info->irec = *rec;
+       *info->stat = 1;
+       return XFS_BTREE_QUERY_RANGE_ABORT;
+}
+
+/*
+ * Find the record to the left of the given extent, being careful only to
+ * return a match with the same owner and overlapping physical and logical
+ * block ranges.  This is the overlapping-interval version of
+ * xfs_rmap_lookup_le.
+ */
+int
+xfs_rmap_lookup_le_range(
+       struct xfs_btree_cur    *cur,
+       xfs_agblock_t           bno,
+       uint64_t                owner,
+       uint64_t                offset,
+       unsigned int            flags,
+       struct xfs_rmap_irec    *irec,
+       int                     *stat)
+{
+       struct xfs_find_left_neighbor_info      info;
+       int                     error;
+
+       info.high.rm_startblock = bno;
+       info.high.rm_owner = owner;
+       if (!XFS_RMAP_NON_INODE_OWNER(owner) && !(flags & XFS_RMAP_BMBT_BLOCK))
+               info.high.rm_offset = offset;
+       else
+               info.high.rm_offset = 0;
+       info.high.rm_flags = flags;
+       info.high.rm_blockcount = 0;
+       *stat = 0;
+       info.irec = irec;
+       info.stat = stat;
+
+       trace_xfs_rmap_lookup_le_range(cur->bc_mp,
+                       cur->bc_private.a.agno, bno, 0, owner, offset, flags);
+       error = xfs_rmap_query_range(cur, &info.high, &info.high,
+                       xfs_rmap_lookup_le_range_helper, &info);
+       if (error == XFS_BTREE_QUERY_RANGE_ABORT)
+               error = 0;
+       if (*stat)
+               trace_xfs_rmap_lookup_le_range_result(cur->bc_mp,
+                               cur->bc_private.a.agno, irec->rm_startblock,
+                               irec->rm_blockcount, irec->rm_owner,
+                               irec->rm_offset, irec->rm_flags);
+       return error;
+}
+
+/*
+ * Perform all the relevant owner checks for a removal op.  If we're doing an
+ * unknown-owner removal then we have no owner information to check.
+ */
+static int
+xfs_rmap_free_check_owner(
+       struct xfs_mount        *mp,
+       uint64_t                ltoff,
+       struct xfs_rmap_irec    *rec,
+       xfs_filblks_t           len,
+       uint64_t                owner,
+       uint64_t                offset,
+       unsigned int            flags)
+{
+       int                     error = 0;
+
+       if (owner == XFS_RMAP_OWN_UNKNOWN)
+               return 0;
+
+       /* Make sure the unwritten flag matches. */
+       XFS_WANT_CORRUPTED_GOTO(mp, (flags & XFS_RMAP_UNWRITTEN) ==
+                       (rec->rm_flags & XFS_RMAP_UNWRITTEN), out);
+
+       /* Make sure the owner matches what we expect to find in the tree. */
+       XFS_WANT_CORRUPTED_GOTO(mp, owner == rec->rm_owner, out);
+
+       /* Check the offset, if necessary. */
+       if (XFS_RMAP_NON_INODE_OWNER(owner))
+               goto out;
+
+       if (flags & XFS_RMAP_BMBT_BLOCK) {
+               XFS_WANT_CORRUPTED_GOTO(mp, rec->rm_flags & XFS_RMAP_BMBT_BLOCK,
+                               out);
+       } else {
+               XFS_WANT_CORRUPTED_GOTO(mp, rec->rm_offset <= offset, out);
+               XFS_WANT_CORRUPTED_GOTO(mp,
+                               ltoff + rec->rm_blockcount >= offset + len,
+                               out);
+       }
+
+out:
+       return error;
 }
 
 /*
@@ -255,33 +513,40 @@ xfs_rmap_unmap(
                goto out_done;
        }
 
-       /* Make sure the unwritten flag matches. */
-       XFS_WANT_CORRUPTED_GOTO(mp, (flags & XFS_RMAP_UNWRITTEN) ==
-                       (ltrec.rm_flags & XFS_RMAP_UNWRITTEN), out_error);
+       /*
+        * If we're doing an unknown-owner removal for EFI recovery, we expect
+        * to find the full range in the rmapbt or nothing at all.  If we
+        * don't find any rmaps overlapping either end of the range, we're
+        * done.  Hopefully this means that the EFI creator already queued
+        * (and finished) a RUI to remove the rmap.
+        */
+       if (owner == XFS_RMAP_OWN_UNKNOWN &&
+           ltrec.rm_startblock + ltrec.rm_blockcount <= bno) {
+               struct xfs_rmap_irec    rtrec;
+
+               error = xfs_btree_increment(cur, 0, &i);
+               if (error)
+                       goto out_error;
+               if (i == 0)
+                       goto out_done;
+               error = xfs_rmap_get_rec(cur, &rtrec, &i);
+               if (error)
+                       goto out_error;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, out_error);
+               if (rtrec.rm_startblock >= bno + len)
+                       goto out_done;
+       }
 
        /* Make sure the extent we found covers the entire freeing range. */
        XFS_WANT_CORRUPTED_GOTO(mp, ltrec.rm_startblock <= bno &&
-               ltrec.rm_startblock + ltrec.rm_blockcount >=
-               bno + len, out_error);
+                       ltrec.rm_startblock + ltrec.rm_blockcount >=
+                       bno + len, out_error);
 
-       /* Make sure the owner matches what we expect to find in the tree. */
-       XFS_WANT_CORRUPTED_GOTO(mp, owner == ltrec.rm_owner ||
-                                   XFS_RMAP_NON_INODE_OWNER(owner), out_error);
-
-       /* Check the offset, if necessary. */
-       if (!XFS_RMAP_NON_INODE_OWNER(owner)) {
-               if (flags & XFS_RMAP_BMBT_BLOCK) {
-                       XFS_WANT_CORRUPTED_GOTO(mp,
-                                       ltrec.rm_flags & XFS_RMAP_BMBT_BLOCK,
-                                       out_error);
-               } else {
-                       XFS_WANT_CORRUPTED_GOTO(mp,
-                                       ltrec.rm_offset <= offset, out_error);
-                       XFS_WANT_CORRUPTED_GOTO(mp,
-                                       ltoff + ltrec.rm_blockcount >= offset + len,
-                                       out_error);
-               }
-       }
+       /* Check owner information. */
+       error = xfs_rmap_free_check_owner(mp, ltoff, &ltrec, len, owner,
+                       offset, flags);
+       if (error)
+               goto out_error;
 
        if (ltrec.rm_startblock == bno && ltrec.rm_blockcount == len) {
                /* exact match, simply remove the record from rmap tree */
@@ -403,14 +668,8 @@ xfs_rmap_free(
        cur = xfs_rmapbt_init_cursor(mp, tp, agbp, agno);
 
        error = xfs_rmap_unmap(cur, bno, len, false, oinfo);
-       if (error)
-               goto out_error;
-
-       xfs_btree_del_cursor(cur, XFS_BTREE_NOERROR);
-       return 0;
 
-out_error:
-       xfs_btree_del_cursor(cur, XFS_BTREE_ERROR);
+       xfs_btree_del_cursor(cur, error);
        return error;
 }
 
@@ -475,6 +734,7 @@ xfs_rmap_map(
                flags |= XFS_RMAP_UNWRITTEN;
        trace_xfs_rmap_map(mp, cur->bc_private.a.agno, bno, len,
                        unwritten, oinfo);
+       ASSERT(!xfs_rmap_should_skip_owner_update(oinfo));
 
        /*
         * For the initial lookup, look for an exact match or the left-adjacent
@@ -485,19 +745,19 @@ xfs_rmap_map(
                        &have_lt);
        if (error)
                goto out_error;
-       XFS_WANT_CORRUPTED_GOTO(mp, have_lt == 1, out_error);
-
-       error = xfs_rmap_get_rec(cur, &ltrec, &have_lt);
-       if (error)
-               goto out_error;
-       XFS_WANT_CORRUPTED_GOTO(mp, have_lt == 1, out_error);
-       trace_xfs_rmap_lookup_le_range_result(cur->bc_mp,
-                       cur->bc_private.a.agno, ltrec.rm_startblock,
-                       ltrec.rm_blockcount, ltrec.rm_owner,
-                       ltrec.rm_offset, ltrec.rm_flags);
-
-       if (!xfs_rmap_is_mergeable(&ltrec, owner, flags))
-               have_lt = 0;
+       if (have_lt) {
+               error = xfs_rmap_get_rec(cur, &ltrec, &have_lt);
+               if (error)
+                       goto out_error;
+               XFS_WANT_CORRUPTED_GOTO(mp, have_lt == 1, out_error);
+               trace_xfs_rmap_lookup_le_range_result(cur->bc_mp,
+                               cur->bc_private.a.agno, ltrec.rm_startblock,
+                               ltrec.rm_blockcount, ltrec.rm_owner,
+                               ltrec.rm_offset, ltrec.rm_flags);
+
+               if (!xfs_rmap_is_mergeable(&ltrec, owner, flags))
+                       have_lt = 0;
+       }
 
        XFS_WANT_CORRUPTED_GOTO(mp,
                have_lt == 0 ||
@@ -644,14 +904,8 @@ xfs_rmap_alloc(
 
        cur = xfs_rmapbt_init_cursor(mp, tp, agbp, agno);
        error = xfs_rmap_map(cur, bno, len, false, oinfo);
-       if (error)
-               goto out_error;
 
-       xfs_btree_del_cursor(cur, XFS_BTREE_NOERROR);
-       return 0;
-
-out_error:
-       xfs_btree_del_cursor(cur, XFS_BTREE_ERROR);
+       xfs_btree_del_cursor(cur, error);
        return error;
 }
 
@@ -1091,73 +1345,810 @@ done:
        return error;
 }
 
-#undef NEW
-#undef LEFT
-#undef RIGHT
-#undef PREV
-
-struct xfs_rmap_query_range_info {
-       xfs_rmap_query_range_fn fn;
-       void                            *priv;
-};
-
-/* Format btree record and pass to our callback. */
+/*
+ * Convert an unwritten extent to a real extent or vice versa.  If there is no
+ * possibility of overlapping extents, delegate to the simpler convert
+ * function.
+ */
 STATIC int
-xfs_rmap_query_range_helper(
+xfs_rmap_convert_shared(
        struct xfs_btree_cur    *cur,
-       union xfs_btree_rec     *rec,
-       void                    *priv)
+       xfs_agblock_t           bno,
+       xfs_extlen_t            len,
+       bool                    unwritten,
+       struct xfs_owner_info   *oinfo)
 {
-       struct xfs_rmap_query_range_info        *query = priv;
-       struct xfs_rmap_irec                    irec;
-       int                                     error;
+       struct xfs_mount        *mp = cur->bc_mp;
+       struct xfs_rmap_irec    r[4];   /* neighbor extent entries */
+                                       /* left is 0, right is 1, prev is 2 */
+                                       /* new is 3 */
+       uint64_t                owner;
+       uint64_t                offset;
+       uint64_t                new_endoff;
+       unsigned int            oldext;
+       unsigned int            newext;
+       unsigned int            flags = 0;
+       int                     i;
+       int                     state = 0;
+       int                     error;
 
-       error = xfs_rmap_btrec_to_irec(rec, &irec);
-       if (error)
-               return error;
-       return query->fn(cur, &irec, query->priv);
-}
+       xfs_owner_info_unpack(oinfo, &owner, &offset, &flags);
+       ASSERT(!(XFS_RMAP_NON_INODE_OWNER(owner) ||
+                       (flags & (XFS_RMAP_ATTR_FORK | XFS_RMAP_BMBT_BLOCK))));
+       oldext = unwritten ? XFS_RMAP_UNWRITTEN : 0;
+       new_endoff = offset + len;
+       trace_xfs_rmap_convert(mp, cur->bc_private.a.agno, bno, len,
+                       unwritten, oinfo);
 
-/* Find all rmaps between two keys. */
-int
-xfs_rmap_query_range(
-       struct xfs_btree_cur            *cur,
-       struct xfs_rmap_irec            *low_rec,
-       struct xfs_rmap_irec            *high_rec,
-       xfs_rmap_query_range_fn fn,
-       void                            *priv)
-{
-       union xfs_btree_irec            low_brec;
-       union xfs_btree_irec            high_brec;
-       struct xfs_rmap_query_range_info        query;
+       /*
+        * For the initial lookup, look for and exact match or the left-adjacent
+        * record for our insertion point. This will also give us the record for
+        * start block contiguity tests.
+        */
+       error = xfs_rmap_lookup_le_range(cur, bno, owner, offset, flags,
+                       &PREV, &i);
+       if (error)
+               goto done;
+       XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
 
-       low_brec.r = *low_rec;
-       high_brec.r = *high_rec;
-       query.priv = priv;
-       query.fn = fn;
-       return xfs_btree_query_range(cur, &low_brec, &high_brec,
-                       xfs_rmap_query_range_helper, &query);
-}
+       ASSERT(PREV.rm_offset <= offset);
+       ASSERT(PREV.rm_offset + PREV.rm_blockcount >= new_endoff);
+       ASSERT((PREV.rm_flags & XFS_RMAP_UNWRITTEN) == oldext);
+       newext = ~oldext & XFS_RMAP_UNWRITTEN;
 
-/* Clean up after calling xfs_rmap_finish_one. */
-void
-xfs_rmap_finish_one_cleanup(
-       struct xfs_trans        *tp,
-       struct xfs_btree_cur    *rcur,
-       int                     error)
-{
-       struct xfs_buf          *agbp;
+       /*
+        * Set flags determining what part of the previous oldext allocation
+        * extent is being replaced by a newext allocation.
+        */
+       if (PREV.rm_offset == offset)
+               state |= RMAP_LEFT_FILLING;
+       if (PREV.rm_offset + PREV.rm_blockcount == new_endoff)
+               state |= RMAP_RIGHT_FILLING;
 
-       if (rcur == NULL)
-               return;
-       agbp = rcur->bc_private.a.agbp;
-       xfs_btree_del_cursor(rcur, error ? XFS_BTREE_ERROR : XFS_BTREE_NOERROR);
+       /* Is there a left record that abuts our range? */
+       error = xfs_rmap_find_left_neighbor(cur, bno, owner, offset, newext,
+                       &LEFT, &i);
        if (error)
-               xfs_trans_brelse(tp, agbp);
-}
-
-/*
- * Process one of the deferred rmap operations.  We pass back the
+               goto done;
+       if (i) {
+               state |= RMAP_LEFT_VALID;
+               XFS_WANT_CORRUPTED_GOTO(mp,
+                               LEFT.rm_startblock + LEFT.rm_blockcount <= bno,
+                               done);
+               if (xfs_rmap_is_mergeable(&LEFT, owner, newext))
+                       state |= RMAP_LEFT_CONTIG;
+       }
+
+       /* Is there a right record that abuts our range? */
+       error = xfs_rmap_lookup_eq(cur, bno + len, len, owner, offset + len,
+                       newext, &i);
+       if (error)
+               goto done;
+       if (i) {
+               state |= RMAP_RIGHT_VALID;
+               error = xfs_rmap_get_rec(cur, &RIGHT, &i);
+               if (error)
+                       goto done;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
+               XFS_WANT_CORRUPTED_GOTO(mp, bno + len <= RIGHT.rm_startblock,
+                               done);
+               trace_xfs_rmap_find_right_neighbor_result(cur->bc_mp,
+                               cur->bc_private.a.agno, RIGHT.rm_startblock,
+                               RIGHT.rm_blockcount, RIGHT.rm_owner,
+                               RIGHT.rm_offset, RIGHT.rm_flags);
+               if (xfs_rmap_is_mergeable(&RIGHT, owner, newext))
+                       state |= RMAP_RIGHT_CONTIG;
+       }
+
+       /* check that left + prev + right is not too long */
+       if ((state & (RMAP_LEFT_FILLING | RMAP_LEFT_CONTIG |
+                        RMAP_RIGHT_FILLING | RMAP_RIGHT_CONTIG)) ==
+           (RMAP_LEFT_FILLING | RMAP_LEFT_CONTIG |
+            RMAP_RIGHT_FILLING | RMAP_RIGHT_CONTIG) &&
+           (unsigned long)LEFT.rm_blockcount + len +
+            RIGHT.rm_blockcount > XFS_RMAP_LEN_MAX)
+               state &= ~RMAP_RIGHT_CONTIG;
+
+       trace_xfs_rmap_convert_state(mp, cur->bc_private.a.agno, state,
+                       _RET_IP_);
+       /*
+        * Switch out based on the FILLING and CONTIG state bits.
+        */
+       switch (state & (RMAP_LEFT_FILLING | RMAP_LEFT_CONTIG |
+                        RMAP_RIGHT_FILLING | RMAP_RIGHT_CONTIG)) {
+       case RMAP_LEFT_FILLING | RMAP_LEFT_CONTIG |
+            RMAP_RIGHT_FILLING | RMAP_RIGHT_CONTIG:
+               /*
+                * Setting all of a previous oldext extent to newext.
+                * The left and right neighbors are both contiguous with new.
+                */
+               error = xfs_rmap_delete(cur, RIGHT.rm_startblock,
+                               RIGHT.rm_blockcount, RIGHT.rm_owner,
+                               RIGHT.rm_offset, RIGHT.rm_flags);
+               if (error)
+                       goto done;
+               error = xfs_rmap_delete(cur, PREV.rm_startblock,
+                               PREV.rm_blockcount, PREV.rm_owner,
+                               PREV.rm_offset, PREV.rm_flags);
+               if (error)
+                       goto done;
+               NEW = LEFT;
+               error = xfs_rmap_lookup_eq(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags, &i);
+               if (error)
+                       goto done;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
+               NEW.rm_blockcount += PREV.rm_blockcount + RIGHT.rm_blockcount;
+               error = xfs_rmap_update(cur, &NEW);
+               if (error)
+                       goto done;
+               break;
+
+       case RMAP_LEFT_FILLING | RMAP_RIGHT_FILLING | RMAP_LEFT_CONTIG:
+               /*
+                * Setting all of a previous oldext extent to newext.
+                * The left neighbor is contiguous, the right is not.
+                */
+               error = xfs_rmap_delete(cur, PREV.rm_startblock,
+                               PREV.rm_blockcount, PREV.rm_owner,
+                               PREV.rm_offset, PREV.rm_flags);
+               if (error)
+                       goto done;
+               NEW = LEFT;
+               error = xfs_rmap_lookup_eq(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags, &i);
+               if (error)
+                       goto done;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
+               NEW.rm_blockcount += PREV.rm_blockcount;
+               error = xfs_rmap_update(cur, &NEW);
+               if (error)
+                       goto done;
+               break;
+
+       case RMAP_LEFT_FILLING | RMAP_RIGHT_FILLING | RMAP_RIGHT_CONTIG:
+               /*
+                * Setting all of a previous oldext extent to newext.
+                * The right neighbor is contiguous, the left is not.
+                */
+               error = xfs_rmap_delete(cur, RIGHT.rm_startblock,
+                               RIGHT.rm_blockcount, RIGHT.rm_owner,
+                               RIGHT.rm_offset, RIGHT.rm_flags);
+               if (error)
+                       goto done;
+               NEW = PREV;
+               error = xfs_rmap_lookup_eq(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags, &i);
+               if (error)
+                       goto done;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
+               NEW.rm_blockcount += RIGHT.rm_blockcount;
+               NEW.rm_flags = RIGHT.rm_flags;
+               error = xfs_rmap_update(cur, &NEW);
+               if (error)
+                       goto done;
+               break;
+
+       case RMAP_LEFT_FILLING | RMAP_RIGHT_FILLING:
+               /*
+                * Setting all of a previous oldext extent to newext.
+                * Neither the left nor right neighbors are contiguous with
+                * the new one.
+                */
+               NEW = PREV;
+               error = xfs_rmap_lookup_eq(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags, &i);
+               if (error)
+                       goto done;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
+               NEW.rm_flags = newext;
+               error = xfs_rmap_update(cur, &NEW);
+               if (error)
+                       goto done;
+               break;
+
+       case RMAP_LEFT_FILLING | RMAP_LEFT_CONTIG:
+               /*
+                * Setting the first part of a previous oldext extent to newext.
+                * The left neighbor is contiguous.
+                */
+               NEW = PREV;
+               error = xfs_rmap_delete(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags);
+               if (error)
+                       goto done;
+               NEW.rm_offset += len;
+               NEW.rm_startblock += len;
+               NEW.rm_blockcount -= len;
+               error = xfs_rmap_insert(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags);
+               if (error)
+                       goto done;
+               NEW = LEFT;
+               error = xfs_rmap_lookup_eq(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags, &i);
+               if (error)
+                       goto done;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
+               NEW.rm_blockcount += len;
+               error = xfs_rmap_update(cur, &NEW);
+               if (error)
+                       goto done;
+               break;
+
+       case RMAP_LEFT_FILLING:
+               /*
+                * Setting the first part of a previous oldext extent to newext.
+                * The left neighbor is not contiguous.
+                */
+               NEW = PREV;
+               error = xfs_rmap_delete(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags);
+               if (error)
+                       goto done;
+               NEW.rm_offset += len;
+               NEW.rm_startblock += len;
+               NEW.rm_blockcount -= len;
+               error = xfs_rmap_insert(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags);
+               if (error)
+                       goto done;
+               error = xfs_rmap_insert(cur, bno, len, owner, offset, newext);
+               if (error)
+                       goto done;
+               break;
+
+       case RMAP_RIGHT_FILLING | RMAP_RIGHT_CONTIG:
+               /*
+                * Setting the last part of a previous oldext extent to newext.
+                * The right neighbor is contiguous with the new allocation.
+                */
+               NEW = PREV;
+               error = xfs_rmap_lookup_eq(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags, &i);
+               if (error)
+                       goto done;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
+               NEW.rm_blockcount = offset - NEW.rm_offset;
+               error = xfs_rmap_update(cur, &NEW);
+               if (error)
+                       goto done;
+               NEW = RIGHT;
+               error = xfs_rmap_delete(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags);
+               if (error)
+                       goto done;
+               NEW.rm_offset = offset;
+               NEW.rm_startblock = bno;
+               NEW.rm_blockcount += len;
+               error = xfs_rmap_insert(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags);
+               if (error)
+                       goto done;
+               break;
+
+       case RMAP_RIGHT_FILLING:
+               /*
+                * Setting the last part of a previous oldext extent to newext.
+                * The right neighbor is not contiguous.
+                */
+               NEW = PREV;
+               error = xfs_rmap_lookup_eq(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags, &i);
+               if (error)
+                       goto done;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
+               NEW.rm_blockcount -= len;
+               error = xfs_rmap_update(cur, &NEW);
+               if (error)
+                       goto done;
+               error = xfs_rmap_insert(cur, bno, len, owner, offset, newext);
+               if (error)
+                       goto done;
+               break;
+
+       case 0:
+               /*
+                * Setting the middle part of a previous oldext extent to
+                * newext.  Contiguity is impossible here.
+                * One extent becomes three extents.
+                */
+               /* new right extent - oldext */
+               NEW.rm_startblock = bno + len;
+               NEW.rm_owner = owner;
+               NEW.rm_offset = new_endoff;
+               NEW.rm_blockcount = PREV.rm_offset + PREV.rm_blockcount -
+                               new_endoff;
+               NEW.rm_flags = PREV.rm_flags;
+               error = xfs_rmap_insert(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner, NEW.rm_offset,
+                               NEW.rm_flags);
+               if (error)
+                       goto done;
+               /* new left extent - oldext */
+               NEW = PREV;
+               error = xfs_rmap_lookup_eq(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner,
+                               NEW.rm_offset, NEW.rm_flags, &i);
+               if (error)
+                       goto done;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, done);
+               NEW.rm_blockcount = offset - NEW.rm_offset;
+               error = xfs_rmap_update(cur, &NEW);
+               if (error)
+                       goto done;
+               /* new middle extent - newext */
+               NEW.rm_startblock = bno;
+               NEW.rm_blockcount = len;
+               NEW.rm_owner = owner;
+               NEW.rm_offset = offset;
+               NEW.rm_flags = newext;
+               error = xfs_rmap_insert(cur, NEW.rm_startblock,
+                               NEW.rm_blockcount, NEW.rm_owner, NEW.rm_offset,
+                               NEW.rm_flags);
+               if (error)
+                       goto done;
+               break;
+
+       case RMAP_LEFT_FILLING | RMAP_LEFT_CONTIG | RMAP_RIGHT_CONTIG:
+       case RMAP_RIGHT_FILLING | RMAP_LEFT_CONTIG | RMAP_RIGHT_CONTIG:
+       case RMAP_LEFT_FILLING | RMAP_RIGHT_CONTIG:
+       case RMAP_RIGHT_FILLING | RMAP_LEFT_CONTIG:
+       case RMAP_LEFT_CONTIG | RMAP_RIGHT_CONTIG:
+       case RMAP_LEFT_CONTIG:
+       case RMAP_RIGHT_CONTIG:
+               /*
+                * These cases are all impossible.
+                */
+               ASSERT(0);
+       }
+
+       trace_xfs_rmap_convert_done(mp, cur->bc_private.a.agno, bno, len,
+                       unwritten, oinfo);
+done:
+       if (error)
+               trace_xfs_rmap_convert_error(cur->bc_mp,
+                               cur->bc_private.a.agno, error, _RET_IP_);
+       return error;
+}
+
+#undef NEW
+#undef LEFT
+#undef RIGHT
+#undef PREV
+
+/*
+ * Find an extent in the rmap btree and unmap it.  For rmap extent types that
+ * can overlap (data fork rmaps on reflink filesystems) we must be careful
+ * that the prev/next records in the btree might belong to another owner.
+ * Therefore we must use delete+insert to alter any of the key fields.
+ *
+ * For every other situation there can only be one owner for a given extent,
+ * so we can call the regular _free function.
+ */
+STATIC int
+xfs_rmap_unmap_shared(
+       struct xfs_btree_cur    *cur,
+       xfs_agblock_t           bno,
+       xfs_extlen_t            len,
+       bool                    unwritten,
+       struct xfs_owner_info   *oinfo)
+{
+       struct xfs_mount        *mp = cur->bc_mp;
+       struct xfs_rmap_irec    ltrec;
+       uint64_t                ltoff;
+       int                     error = 0;
+       int                     i;
+       uint64_t                owner;
+       uint64_t                offset;
+       unsigned int            flags;
+
+       xfs_owner_info_unpack(oinfo, &owner, &offset, &flags);
+       if (unwritten)
+               flags |= XFS_RMAP_UNWRITTEN;
+       trace_xfs_rmap_unmap(mp, cur->bc_private.a.agno, bno, len,
+                       unwritten, oinfo);
+
+       /*
+        * We should always have a left record because there's a static record
+        * for the AG headers at rm_startblock == 0 created by mkfs/growfs that
+        * will not ever be removed from the tree.
+        */
+       error = xfs_rmap_lookup_le_range(cur, bno, owner, offset, flags,
+                       &ltrec, &i);
+       if (error)
+               goto out_error;
+       XFS_WANT_CORRUPTED_GOTO(mp, i == 1, out_error);
+       ltoff = ltrec.rm_offset;
+
+       /* Make sure the extent we found covers the entire freeing range. */
+       XFS_WANT_CORRUPTED_GOTO(mp, ltrec.rm_startblock <= bno &&
+               ltrec.rm_startblock + ltrec.rm_blockcount >=
+               bno + len, out_error);
+
+       /* Make sure the owner matches what we expect to find in the tree. */
+       XFS_WANT_CORRUPTED_GOTO(mp, owner == ltrec.rm_owner, out_error);
+
+       /* Make sure the unwritten flag matches. */
+       XFS_WANT_CORRUPTED_GOTO(mp, (flags & XFS_RMAP_UNWRITTEN) ==
+                       (ltrec.rm_flags & XFS_RMAP_UNWRITTEN), out_error);
+
+       /* Check the offset. */
+       XFS_WANT_CORRUPTED_GOTO(mp, ltrec.rm_offset <= offset, out_error);
+       XFS_WANT_CORRUPTED_GOTO(mp, offset <= ltoff + ltrec.rm_blockcount,
+                       out_error);
+
+       if (ltrec.rm_startblock == bno && ltrec.rm_blockcount == len) {
+               /* Exact match, simply remove the record from rmap tree. */
+               error = xfs_rmap_delete(cur, ltrec.rm_startblock,
+                               ltrec.rm_blockcount, ltrec.rm_owner,
+                               ltrec.rm_offset, ltrec.rm_flags);
+               if (error)
+                       goto out_error;
+       } else if (ltrec.rm_startblock == bno) {
+               /*
+                * Overlap left hand side of extent: move the start, trim the
+                * length and update the current record.
+                *
+                *       ltbno                ltlen
+                * Orig:    |oooooooooooooooooooo|
+                * Freeing: |fffffffff|
+                * Result:            |rrrrrrrrrr|
+                *         bno       len
+                */
+
+               /* Delete prev rmap. */
+               error = xfs_rmap_delete(cur, ltrec.rm_startblock,
+                               ltrec.rm_blockcount, ltrec.rm_owner,
+                               ltrec.rm_offset, ltrec.rm_flags);
+               if (error)
+                       goto out_error;
+
+               /* Add an rmap at the new offset. */
+               ltrec.rm_startblock += len;
+               ltrec.rm_blockcount -= len;
+               ltrec.rm_offset += len;
+               error = xfs_rmap_insert(cur, ltrec.rm_startblock,
+                               ltrec.rm_blockcount, ltrec.rm_owner,
+                               ltrec.rm_offset, ltrec.rm_flags);
+               if (error)
+                       goto out_error;
+       } else if (ltrec.rm_startblock + ltrec.rm_blockcount == bno + len) {
+               /*
+                * Overlap right hand side of extent: trim the length and
+                * update the current record.
+                *
+                *       ltbno                ltlen
+                * Orig:    |oooooooooooooooooooo|
+                * Freeing:            |fffffffff|
+                * Result:  |rrrrrrrrrr|
+                *                    bno       len
+                */
+               error = xfs_rmap_lookup_eq(cur, ltrec.rm_startblock,
+                               ltrec.rm_blockcount, ltrec.rm_owner,
+                               ltrec.rm_offset, ltrec.rm_flags, &i);
+               if (error)
+                       goto out_error;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, out_error);
+               ltrec.rm_blockcount -= len;
+               error = xfs_rmap_update(cur, &ltrec);
+               if (error)
+                       goto out_error;
+       } else {
+               /*
+                * Overlap middle of extent: trim the length of the existing
+                * record to the length of the new left-extent size, increment
+                * the insertion position so we can insert a new record
+                * containing the remaining right-extent space.
+                *
+                *       ltbno                ltlen
+                * Orig:    |oooooooooooooooooooo|
+                * Freeing:       |fffffffff|
+                * Result:  |rrrrr|         |rrrr|
+                *               bno       len
+                */
+               xfs_extlen_t    orig_len = ltrec.rm_blockcount;
+
+               /* Shrink the left side of the rmap */
+               error = xfs_rmap_lookup_eq(cur, ltrec.rm_startblock,
+                               ltrec.rm_blockcount, ltrec.rm_owner,
+                               ltrec.rm_offset, ltrec.rm_flags, &i);
+               if (error)
+                       goto out_error;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, out_error);
+               ltrec.rm_blockcount = bno - ltrec.rm_startblock;
+               error = xfs_rmap_update(cur, &ltrec);
+               if (error)
+                       goto out_error;
+
+               /* Add an rmap at the new offset */
+               error = xfs_rmap_insert(cur, bno + len,
+                               orig_len - len - ltrec.rm_blockcount,
+                               ltrec.rm_owner, offset + len,
+                               ltrec.rm_flags);
+               if (error)
+                       goto out_error;
+       }
+
+       trace_xfs_rmap_unmap_done(mp, cur->bc_private.a.agno, bno, len,
+                       unwritten, oinfo);
+out_error:
+       if (error)
+               trace_xfs_rmap_unmap_error(cur->bc_mp,
+                               cur->bc_private.a.agno, error, _RET_IP_);
+       return error;
+}
+
+/*
+ * Find an extent in the rmap btree and map it.  For rmap extent types that
+ * can overlap (data fork rmaps on reflink filesystems) we must be careful
+ * that the prev/next records in the btree might belong to another owner.
+ * Therefore we must use delete+insert to alter any of the key fields.
+ *
+ * For every other situation there can only be one owner for a given extent,
+ * so we can call the regular _alloc function.
+ */
+STATIC int
+xfs_rmap_map_shared(
+       struct xfs_btree_cur    *cur,
+       xfs_agblock_t           bno,
+       xfs_extlen_t            len,
+       bool                    unwritten,
+       struct xfs_owner_info   *oinfo)
+{
+       struct xfs_mount        *mp = cur->bc_mp;
+       struct xfs_rmap_irec    ltrec;
+       struct xfs_rmap_irec    gtrec;
+       int                     have_gt;
+       int                     have_lt;
+       int                     error = 0;
+       int                     i;
+       uint64_t                owner;
+       uint64_t                offset;
+       unsigned int            flags = 0;
+
+       xfs_owner_info_unpack(oinfo, &owner, &offset, &flags);
+       if (unwritten)
+               flags |= XFS_RMAP_UNWRITTEN;
+       trace_xfs_rmap_map(mp, cur->bc_private.a.agno, bno, len,
+                       unwritten, oinfo);
+
+       /* Is there a left record that abuts our range? */
+       error = xfs_rmap_find_left_neighbor(cur, bno, owner, offset, flags,
+                       &ltrec, &have_lt);
+       if (error)
+               goto out_error;
+       if (have_lt &&
+           !xfs_rmap_is_mergeable(&ltrec, owner, flags))
+               have_lt = 0;
+
+       /* Is there a right record that abuts our range? */
+       error = xfs_rmap_lookup_eq(cur, bno + len, len, owner, offset + len,
+                       flags, &have_gt);
+       if (error)
+               goto out_error;
+       if (have_gt) {
+               error = xfs_rmap_get_rec(cur, &gtrec, &have_gt);
+               if (error)
+                       goto out_error;
+               XFS_WANT_CORRUPTED_GOTO(mp, have_gt == 1, out_error);
+               trace_xfs_rmap_find_right_neighbor_result(cur->bc_mp,
+                       cur->bc_private.a.agno, gtrec.rm_startblock,
+                       gtrec.rm_blockcount, gtrec.rm_owner,
+                       gtrec.rm_offset, gtrec.rm_flags);
+
+               if (!xfs_rmap_is_mergeable(&gtrec, owner, flags))
+                       have_gt = 0;
+       }
+
+       if (have_lt &&
+           ltrec.rm_startblock + ltrec.rm_blockcount == bno &&
+           ltrec.rm_offset + ltrec.rm_blockcount == offset) {
+               /*
+                * Left edge contiguous, merge into left record.
+                *
+                *       ltbno     ltlen
+                * orig:   |ooooooooo|
+                * adding:           |aaaaaaaaa|
+                * result: |rrrrrrrrrrrrrrrrrrr|
+                *                  bno       len
+                */
+               ltrec.rm_blockcount += len;
+               if (have_gt &&
+                   bno + len == gtrec.rm_startblock &&
+                   offset + len == gtrec.rm_offset) {
+                       /*
+                        * Right edge also contiguous, delete right record
+                        * and merge into left record.
+                        *
+                        *       ltbno     ltlen    gtbno     gtlen
+                        * orig:   |ooooooooo|         |ooooooooo|
+                        * adding:           |aaaaaaaaa|
+                        * result: |rrrrrrrrrrrrrrrrrrrrrrrrrrrrr|
+                        */
+                       ltrec.rm_blockcount += gtrec.rm_blockcount;
+                       error = xfs_rmap_delete(cur, gtrec.rm_startblock,
+                                       gtrec.rm_blockcount, gtrec.rm_owner,
+                                       gtrec.rm_offset, gtrec.rm_flags);
+                       if (error)
+                               goto out_error;
+               }
+
+               /* Point the cursor back to the left record and update. */
+               error = xfs_rmap_lookup_eq(cur, ltrec.rm_startblock,
+                               ltrec.rm_blockcount, ltrec.rm_owner,
+                               ltrec.rm_offset, ltrec.rm_flags, &i);
+               if (error)
+                       goto out_error;
+               XFS_WANT_CORRUPTED_GOTO(mp, i == 1, out_error);
+
+               error = xfs_rmap_update(cur, &ltrec);
+               if (error)
+                       goto out_error;
+       } else if (have_gt &&
+                  bno + len == gtrec.rm_startblock &&
+                  offset + len == gtrec.rm_offset) {
+               /*
+                * Right edge contiguous, merge into right record.
+                *
+                *                 gtbno     gtlen
+                * Orig:             |ooooooooo|
+                * adding: |aaaaaaaaa|
+                * Result: |rrrrrrrrrrrrrrrrrrr|
+                *        bno       len
+                */
+               /* Delete the old record. */
+               error = xfs_rmap_delete(cur, gtrec.rm_startblock,
+                               gtrec.rm_blockcount, gtrec.rm_owner,
+                               gtrec.rm_offset, gtrec.rm_flags);
+               if (error)
+                       goto out_error;
+
+               /* Move the start and re-add it. */
+               gtrec.rm_startblock = bno;
+               gtrec.rm_blockcount += len;
+               gtrec.rm_offset = offset;
+               error = xfs_rmap_insert(cur, gtrec.rm_startblock,
+                               gtrec.rm_blockcount, gtrec.rm_owner,
+                               gtrec.rm_offset, gtrec.rm_flags);
+               if (error)
+                       goto out_error;
+       } else {
+               /*
+                * No contiguous edge with identical owner, insert
+                * new record at current cursor position.
+                */
+               error = xfs_rmap_insert(cur, bno, len, owner, offset, flags);
+               if (error)
+                       goto out_error;
+       }
+
+       trace_xfs_rmap_map_done(mp, cur->bc_private.a.agno, bno, len,
+                       unwritten, oinfo);
+out_error:
+       if (error)
+               trace_xfs_rmap_map_error(cur->bc_mp,
+                               cur->bc_private.a.agno, error, _RET_IP_);
+       return error;
+}
+
+/* Insert a raw rmap into the rmapbt. */
+int
+xfs_rmap_map_raw(
+       struct xfs_btree_cur    *cur,
+       struct xfs_rmap_irec    *rmap)
+{
+       struct xfs_owner_info   oinfo;
+
+       oinfo.oi_owner = rmap->rm_owner;
+       oinfo.oi_offset = rmap->rm_offset;
+       oinfo.oi_flags = 0;
+       if (rmap->rm_flags & XFS_RMAP_ATTR_FORK)
+               oinfo.oi_flags |= XFS_OWNER_INFO_ATTR_FORK;
+       if (rmap->rm_flags & XFS_RMAP_BMBT_BLOCK)
+               oinfo.oi_flags |= XFS_OWNER_INFO_BMBT_BLOCK;
+
+       if (rmap->rm_flags || XFS_RMAP_NON_INODE_OWNER(rmap->rm_owner))
+               return xfs_rmap_map(cur, rmap->rm_startblock,
+                               rmap->rm_blockcount,
+                               rmap->rm_flags & XFS_RMAP_UNWRITTEN,
+                               &oinfo);
+
+       return xfs_rmap_map_shared(cur, rmap->rm_startblock,
+                       rmap->rm_blockcount,
+                       rmap->rm_flags & XFS_RMAP_UNWRITTEN,
+                       &oinfo);
+}
+
+struct xfs_rmap_query_range_info {
+       xfs_rmap_query_range_fn fn;
+       void                            *priv;
+};
+
+/* Format btree record and pass to our callback. */
+STATIC int
+xfs_rmap_query_range_helper(
+       struct xfs_btree_cur    *cur,
+       union xfs_btree_rec     *rec,
+       void                    *priv)
+{
+       struct xfs_rmap_query_range_info        *query = priv;
+       struct xfs_rmap_irec                    irec;
+       int                                     error;
+
+       error = xfs_rmap_btrec_to_irec(rec, &irec);
+       if (error)
+               return error;
+       return query->fn(cur, &irec, query->priv);
+}
+
+/* Find all rmaps between two keys. */
+int
+xfs_rmap_query_range(
+       struct xfs_btree_cur                    *cur,
+       struct xfs_rmap_irec                    *low_rec,
+       struct xfs_rmap_irec                    *high_rec,
+       xfs_rmap_query_range_fn                 fn,
+       void                                    *priv)
+{
+       union xfs_btree_irec                    low_brec;
+       union xfs_btree_irec                    high_brec;
+       struct xfs_rmap_query_range_info        query;
+
+       low_brec.r = *low_rec;
+       high_brec.r = *high_rec;
+       query.priv = priv;
+       query.fn = fn;
+       return xfs_btree_query_range(cur, &low_brec, &high_brec,
+                       xfs_rmap_query_range_helper, &query);
+}
+
+/* Find all rmaps. */
+int
+xfs_rmap_query_all(
+       struct xfs_btree_cur                    *cur,
+       xfs_rmap_query_range_fn                 fn,
+       void                                    *priv)
+{
+       struct xfs_rmap_query_range_info        query;
+
+       query.priv = priv;
+       query.fn = fn;
+       return xfs_btree_query_all(cur, xfs_rmap_query_range_helper, &query);
+}
+
+/* Clean up after calling xfs_rmap_finish_one. */
+void
+xfs_rmap_finish_one_cleanup(
+       struct xfs_trans        *tp,
+       struct xfs_btree_cur    *rcur,
+       int                     error)
+{
+       struct xfs_buf          *agbp;
+
+       if (rcur == NULL)
+               return;
+       agbp = rcur->bc_private.a.agbp;
+       xfs_btree_del_cursor(rcur, error);
+       if (error)
+               xfs_trans_brelse(tp, agbp);
+}
+
+/*
+ * Process one of the deferred rmap operations.  We pass back the
  * btree cursor to maintain our lock on the rmapbt between calls.
  * This saves time and eliminates a buffer deadlock between the
  * superblock and the AGF because we'll always grab them in the same
@@ -1167,7 +2158,7 @@ int
 xfs_rmap_finish_one(
        struct xfs_trans                *tp,
        enum xfs_rmap_intent_type       type,
-       __uint64_t                      owner,
+       uint64_t                        owner,
        int                             whichfork,
        xfs_fileoff_t                   startoff,
        xfs_fsblock_t                   startblock,
@@ -1192,8 +2183,7 @@ xfs_rmap_finish_one(
                        startoff, blockcount, state);
 
        if (XFS_TEST_ERROR(false, mp,
-                       XFS_ERRTAG_RMAP_FINISH_ONE,
-                       XFS_RANDOM_RMAP_FINISH_ONE))
+                       XFS_ERRTAG_RMAP_FINISH_ONE))
                return -EIO;
 
        /*
@@ -1235,15 +2225,27 @@ xfs_rmap_finish_one(
        case XFS_RMAP_MAP:
                error = xfs_rmap_map(rcur, bno, blockcount, unwritten, &oinfo);
                break;
+       case XFS_RMAP_MAP_SHARED:
+               error = xfs_rmap_map_shared(rcur, bno, blockcount, unwritten,
+                               &oinfo);
+               break;
        case XFS_RMAP_FREE:
        case XFS_RMAP_UNMAP:
                error = xfs_rmap_unmap(rcur, bno, blockcount, unwritten,
                                &oinfo);
                break;
+       case XFS_RMAP_UNMAP_SHARED:
+               error = xfs_rmap_unmap_shared(rcur, bno, blockcount, unwritten,
+                               &oinfo);
+               break;
        case XFS_RMAP_CONVERT:
                error = xfs_rmap_convert(rcur, bno, blockcount, !unwritten,
                                &oinfo);
                break;
+       case XFS_RMAP_CONVERT_SHARED:
+               error = xfs_rmap_convert_shared(rcur, bno, blockcount,
+                               !unwritten, &oinfo);
+               break;
        default:
                ASSERT(0);
                error = -EFSCORRUPTED;
@@ -1276,7 +2278,7 @@ __xfs_rmap_add(
        struct xfs_mount                *mp,
        struct xfs_defer_ops            *dfops,
        enum xfs_rmap_intent_type       type,
-       __uint64_t                      owner,
+       uint64_t                        owner,
        int                             whichfork,
        struct xfs_bmbt_irec            *bmap)
 {
@@ -1313,7 +2315,8 @@ xfs_rmap_map_extent(
        if (!xfs_rmap_update_is_needed(mp, whichfork))
                return 0;
 
-       return __xfs_rmap_add(mp, dfops, XFS_RMAP_MAP, ip->i_ino,
+       return __xfs_rmap_add(mp, dfops, xfs_is_reflink_inode(ip) ?
+                       XFS_RMAP_MAP_SHARED : XFS_RMAP_MAP, ip->i_ino,
                        whichfork, PREV);
 }
 
@@ -1329,7 +2332,8 @@ xfs_rmap_unmap_extent(
        if (!xfs_rmap_update_is_needed(mp, whichfork))
                return 0;
 
-       return __xfs_rmap_add(mp, dfops, XFS_RMAP_UNMAP, ip->i_ino,
+       return __xfs_rmap_add(mp, dfops, xfs_is_reflink_inode(ip) ?
+                       XFS_RMAP_UNMAP_SHARED : XFS_RMAP_UNMAP, ip->i_ino,
                        whichfork, PREV);
 }
 
@@ -1345,7 +2349,8 @@ xfs_rmap_convert_extent(
        if (!xfs_rmap_update_is_needed(mp, whichfork))
                return 0;
 
-       return __xfs_rmap_add(mp, dfops, XFS_RMAP_CONVERT, ip->i_ino,
+       return __xfs_rmap_add(mp, dfops, xfs_is_reflink_inode(ip) ?
+                       XFS_RMAP_CONVERT_SHARED : XFS_RMAP_CONVERT, ip->i_ino,
                        whichfork, PREV);
 }
 
@@ -1357,7 +2362,7 @@ xfs_rmap_alloc_extent(
        xfs_agnumber_t          agno,
        xfs_agblock_t           bno,
        xfs_extlen_t            len,
-       __uint64_t              owner)
+       uint64_t                owner)
 {
        struct xfs_bmbt_irec    bmap;
 
@@ -1381,7 +2386,7 @@ xfs_rmap_free_extent(
        xfs_agnumber_t          agno,
        xfs_agblock_t           bno,
        xfs_extlen_t            len,
-       __uint64_t              owner)
+       uint64_t                owner)
 {
        struct xfs_bmbt_irec    bmap;
 
@@ -1396,3 +2401,151 @@ xfs_rmap_free_extent(
        return __xfs_rmap_add(mp, dfops, XFS_RMAP_FREE, owner,
                        XFS_DATA_FORK, &bmap);
 }
+
+/* Compare rmap records.  Returns -1 if a < b, 1 if a > b, and 0 if equal. */
+int
+xfs_rmap_compare(
+       const struct xfs_rmap_irec      *a,
+       const struct xfs_rmap_irec      *b)
+{
+       __u64                           oa;
+       __u64                           ob;
+
+       oa = xfs_rmap_irec_offset_pack(a);
+       ob = xfs_rmap_irec_offset_pack(b);
+
+       if (a->rm_startblock < b->rm_startblock)
+               return -1;
+       else if (a->rm_startblock > b->rm_startblock)
+               return 1;
+       else if (a->rm_owner < b->rm_owner)
+               return -1;
+       else if (a->rm_owner > b->rm_owner)
+               return 1;
+       else if (oa < ob)
+               return -1;
+       else if (oa > ob)
+               return 1;
+       else
+               return 0;
+}
+
+/* Is there a record covering a given extent? */
+int
+xfs_rmap_has_record(
+       struct xfs_btree_cur    *cur,
+       xfs_agblock_t           bno,
+       xfs_extlen_t            len,
+       bool                    *exists)
+{
+       union xfs_btree_irec    low;
+       union xfs_btree_irec    high;
+
+       memset(&low, 0, sizeof(low));
+       low.r.rm_startblock = bno;
+       memset(&high, 0xFF, sizeof(high));
+       high.r.rm_startblock = bno + len - 1;
+
+       return xfs_btree_has_record(cur, &low, &high, exists);
+}
+
+/*
+ * Is there a record for this owner completely covering a given physical
+ * extent?  If so, *has_rmap will be set to true.  If there is no record
+ * or the record only covers part of the range, we set *has_rmap to false.
+ * This function doesn't perform range lookups or offset checks, so it is
+ * not suitable for checking data fork blocks.
+ */
+int
+xfs_rmap_record_exists(
+       struct xfs_btree_cur    *cur,
+       xfs_agblock_t           bno,
+       xfs_extlen_t            len,
+       struct xfs_owner_info   *oinfo,
+       bool                    *has_rmap)
+{
+       uint64_t                owner;
+       uint64_t                offset;
+       unsigned int            flags;
+       int                     has_record;
+       struct xfs_rmap_irec    irec;
+       int                     error;
+
+       xfs_owner_info_unpack(oinfo, &owner, &offset, &flags);
+       ASSERT(XFS_RMAP_NON_INODE_OWNER(owner) ||
+              (flags & XFS_RMAP_BMBT_BLOCK));
+
+       error = xfs_rmap_lookup_le(cur, bno, len, owner, offset, flags,
+                       &has_record);
+       if (error)
+               return error;
+       if (!has_record) {
+               *has_rmap = false;
+               return 0;
+       }
+
+       error = xfs_rmap_get_rec(cur, &irec, &has_record);
+       if (error)
+               return error;
+       if (!has_record) {
+               *has_rmap = false;
+               return 0;
+       }
+
+       *has_rmap = (irec.rm_owner == owner && irec.rm_startblock <= bno &&
+                    irec.rm_startblock + irec.rm_blockcount >= bno + len);
+       return 0;
+}
+
+struct xfs_rmap_key_state {
+       uint64_t                        owner;
+       uint64_t                        offset;
+       unsigned int                    flags;
+       bool                            has_rmap;
+};
+
+/* For each rmap given, figure out if it doesn't match the key we want. */
+STATIC int
+xfs_rmap_has_other_keys_helper(
+       struct xfs_btree_cur            *cur,
+       struct xfs_rmap_irec            *rec,
+       void                            *priv)
+{
+       struct xfs_rmap_key_state       *rks = priv;
+
+       if (rks->owner == rec->rm_owner && rks->offset == rec->rm_offset &&
+           ((rks->flags & rec->rm_flags) & XFS_RMAP_KEY_FLAGS) == rks->flags)
+               return 0;
+       rks->has_rmap = true;
+       return XFS_BTREE_QUERY_RANGE_ABORT;
+}
+
+/*
+ * Given an extent and some owner info, can we find records overlapping
+ * the extent whose owner info does not match the given owner?
+ */
+int
+xfs_rmap_has_other_keys(
+       struct xfs_btree_cur            *cur,
+       xfs_agblock_t                   bno,
+       xfs_extlen_t                    len,
+       struct xfs_owner_info           *oinfo,
+       bool                            *has_rmap)
+{
+       struct xfs_rmap_irec            low = {0};
+       struct xfs_rmap_irec            high;
+       struct xfs_rmap_key_state       rks;
+       int                             error;
+
+       xfs_owner_info_unpack(oinfo, &rks.owner, &rks.offset, &rks.flags);
+       rks.has_rmap = false;
+
+       low.rm_startblock = bno;
+       memset(&high, 0xFF, sizeof(high));
+       high.rm_startblock = bno + len - 1;
+
+       error = xfs_rmap_query_range(cur, &low, &high,
+                       xfs_rmap_has_other_keys_helper, &rks);
+       *has_rmap = rks.has_rmap;
+       return error;
+}