]> 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 09f74a5f48a5769f9cf7eeb0492ddb4df83c134f..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"
@@ -37,6 +25,7 @@
 #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]
@@ -201,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;
 
@@ -208,7 +199,43 @@ 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 {
@@ -641,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;
 }
 
@@ -724,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 (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;
+               if (!xfs_rmap_is_mergeable(&ltrec, owner, flags))
+                       have_lt = 0;
+       }
 
        XFS_WANT_CORRUPTED_GOTO(mp,
                have_lt == 0 ||
@@ -883,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;
 }
 
@@ -1372,6 +1387,8 @@ xfs_rmap_convert_shared(
         */
        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);
 
        ASSERT(PREV.rm_offset <= offset);
@@ -2028,6 +2045,34 @@ out_error:
        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;
@@ -2097,7 +2142,7 @@ xfs_rmap_finish_one_cleanup(
        if (rcur == NULL)
                return;
        agbp = rcur->bc_private.a.agbp;
-       xfs_btree_del_cursor(rcur, error ? XFS_BTREE_ERROR : XFS_BTREE_NOERROR);
+       xfs_btree_del_cursor(rcur, error);
        if (error)
                xfs_trans_brelse(tp, agbp);
 }
@@ -2451,3 +2496,56 @@ xfs_rmap_record_exists(
                     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;
+}