]> git.sesse.net Git - bcachefs-tools-debian/blobdiff - libbcachefs/fsck.c
Update bcachefs sources to f9c612bbf82d bcachefs: Fixes for building in userspace
[bcachefs-tools-debian] / libbcachefs / fsck.c
index 37ba927cbd31e6c571dd3fd197ea1e8ece5067c1..206302b0f5ed8a6393b7a9f97fc6b1d0d89bb0d4 100644 (file)
@@ -11,7 +11,8 @@
 #include "fsck.h"
 #include "inode.h"
 #include "keylist.h"
-#include "subvolume.h"
+#include "recovery.h"
+#include "snapshot.h"
 #include "super.h"
 #include "xattr.h"
 
@@ -79,7 +80,7 @@ static int __snapshot_lookup_subvol(struct btree_trans *trans, u32 snapshot,
        if (!ret)
                *subvol = le32_to_cpu(s.subvol);
        else if (bch2_err_matches(ret, ENOENT))
-               bch_err(trans->c, "snapshot %u not fonud", snapshot);
+               bch_err(trans->c, "snapshot %u not found", snapshot);
        return ret;
 
 }
@@ -126,8 +127,7 @@ static int lookup_first_inode(struct btree_trans *trans, u64 inode_nr,
        ret = bch2_inode_unpack(k, inode);
 err:
        if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
-               bch_err(trans->c, "error fetching inode %llu: %s",
-                       inode_nr, bch2_err_str(ret));
+               bch_err_msg(trans->c, ret, "fetching inode %llu", inode_nr);
        bch2_trans_iter_exit(trans, &iter);
        return ret;
 }
@@ -153,8 +153,7 @@ static int __lookup_inode(struct btree_trans *trans, u64 inode_nr,
                *snapshot = iter.pos.snapshot;
 err:
        if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
-               bch_err(trans->c, "error fetching inode %llu:%u: %s",
-                       inode_nr, *snapshot, bch2_err_str(ret));
+               bch_err_msg(trans->c, ret, "fetching inode %llu:%u", inode_nr, *snapshot);
        bch2_trans_iter_exit(trans, &iter);
        return ret;
 }
@@ -205,83 +204,19 @@ static int __write_inode(struct btree_trans *trans,
                                BTREE_UPDATE_INTERNAL_SNAPSHOT_NODE);
 }
 
-static int write_inode(struct btree_trans *trans,
-                      struct bch_inode_unpacked *inode,
-                      u32 snapshot)
+static int fsck_write_inode(struct btree_trans *trans,
+                           struct bch_inode_unpacked *inode,
+                           u32 snapshot)
 {
        int ret = commit_do(trans, NULL, NULL,
                                  BTREE_INSERT_NOFAIL|
                                  BTREE_INSERT_LAZY_RW,
                                  __write_inode(trans, inode, snapshot));
        if (ret)
-               bch_err(trans->c, "error in fsck: error updating inode: %s",
-                       bch2_err_str(ret));
+               bch_err_fn(trans->c, ret);
        return ret;
 }
 
-static int fsck_inode_rm(struct btree_trans *trans, u64 inum, u32 snapshot)
-{
-       struct bch_fs *c = trans->c;
-       struct btree_iter iter = { NULL };
-       struct bkey_i_inode_generation delete;
-       struct bch_inode_unpacked inode_u;
-       struct bkey_s_c k;
-       int ret;
-
-       do {
-               ret   = bch2_btree_delete_range_trans(trans, BTREE_ID_extents,
-                                                     SPOS(inum, 0, snapshot),
-                                                     SPOS(inum, U64_MAX, snapshot),
-                                                     0, NULL) ?:
-                       bch2_btree_delete_range_trans(trans, BTREE_ID_dirents,
-                                                     SPOS(inum, 0, snapshot),
-                                                     SPOS(inum, U64_MAX, snapshot),
-                                                     0, NULL) ?:
-                       bch2_btree_delete_range_trans(trans, BTREE_ID_xattrs,
-                                                     SPOS(inum, 0, snapshot),
-                                                     SPOS(inum, U64_MAX, snapshot),
-                                                     0, NULL);
-       } while (ret == -BCH_ERR_transaction_restart_nested);
-       if (ret)
-               goto err;
-retry:
-       bch2_trans_begin(trans);
-
-       k = bch2_bkey_get_iter(trans, &iter, BTREE_ID_inodes,
-                              SPOS(0, inum, snapshot), BTREE_ITER_INTENT);
-       ret = bkey_err(k);
-       if (ret)
-               goto err;
-
-       if (!bkey_is_inode(k.k)) {
-               bch2_fs_inconsistent(c,
-                                    "inode %llu:%u not found when deleting",
-                                    inum, snapshot);
-               ret = -EIO;
-               goto err;
-       }
-
-       bch2_inode_unpack(k, &inode_u);
-
-       /* Subvolume root? */
-       if (inode_u.bi_subvol)
-               bch_warn(c, "deleting inode %llu marked as unlinked, but also a subvolume root!?", inode_u.bi_inum);
-
-       bkey_inode_generation_init(&delete.k_i);
-       delete.k.p = iter.pos;
-       delete.v.bi_generation = cpu_to_le32(inode_u.bi_generation + 1);
-
-       ret   = bch2_trans_update(trans, &iter, &delete.k_i, 0) ?:
-               bch2_trans_commit(trans, NULL, NULL,
-                               BTREE_INSERT_NOFAIL);
-err:
-       bch2_trans_iter_exit(trans, &iter);
-       if (bch2_err_matches(ret, BCH_ERR_transaction_restart))
-               goto retry;
-
-       return ret ?: -BCH_ERR_transaction_restart_nested;
-}
-
 static int __remove_dirent(struct btree_trans *trans, struct bpos pos)
 {
        struct bch_fs *c = trans->c;
@@ -340,13 +275,13 @@ static int lookup_lostfound(struct btree_trans *trans, u32 subvol,
        }
 
        if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
-               bch_err(c, "error looking up lost+found: %s", bch2_err_str(ret));
+               bch_err_fn(c, ret);
        if (ret)
                return ret;
 
        if (d_type != DT_DIR) {
                bch_err(c, "error looking up lost+found: not a directory");
-               return ret;
+               return -BCH_ERR_ENOENT_not_directory;
        }
 
        /*
@@ -363,7 +298,7 @@ create_lostfound:
                                0, 0, S_IFDIR|0700, 0, NULL, NULL,
                                (subvol_inum) { }, 0);
        if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
-               bch_err(c, "error creating lost+found: %s", bch2_err_str(ret));
+               bch_err_msg(c, ret, "creating lost+found");
        return ret;
 }
 
@@ -427,8 +362,7 @@ static int reattach_inode(struct btree_trans *trans,
                                  BTREE_INSERT_NOFAIL,
                        __reattach_inode(trans, inode, inode_snapshot));
        if (ret) {
-               bch_err(trans->c, "error reattaching inode %llu: %s",
-                       inode->bi_inum, bch2_err_str(ret));
+               bch_err_msg(trans->c, ret, "reattaching inode %llu", inode->bi_inum);
                return ret;
        }
 
@@ -471,6 +405,28 @@ static inline void snapshots_seen_init(struct snapshots_seen *s)
        memset(s, 0, sizeof(*s));
 }
 
+static int snapshots_seen_add_inorder(struct bch_fs *c, struct snapshots_seen *s, u32 id)
+{
+       struct snapshots_seen_entry *i, n = {
+               .id     = id,
+               .equiv  = bch2_snapshot_equiv(c, id),
+       };
+       int ret = 0;
+
+       darray_for_each(s->ids, i) {
+               if (i->id == id)
+                       return 0;
+               if (i->id > id)
+                       break;
+       }
+
+       ret = darray_insert_item(&s->ids, i - s->ids.data, n);
+       if (ret)
+               bch_err(c, "error reallocating snapshots_seen table (size %zu)",
+                       s->ids.size);
+       return ret;
+}
+
 static int snapshots_seen_update(struct bch_fs *c, struct snapshots_seen *s,
                                 enum btree_id btree_id, struct bpos pos)
 {
@@ -515,13 +471,17 @@ static int snapshots_seen_update(struct bch_fs *c, struct snapshots_seen *s,
  * key_visible_in_snapshot - returns true if @id is a descendent of @ancestor,
  * and @ancestor hasn't been overwritten in @seen
  *
- * That is, returns whether key in @ancestor snapshot is visible in @id snapshot
+ * @c:         filesystem handle
+ * @seen:      list of snapshot ids already seen at current position
+ * @id:                descendent snapshot id
+ * @ancestor:  ancestor snapshot id
+ *
+ * Returns:    whether key in @ancestor snapshot is visible in @id snapshot
  */
 static bool key_visible_in_snapshot(struct bch_fs *c, struct snapshots_seen *seen,
                                    u32 id, u32 ancestor)
 {
        ssize_t i;
-       u32 top = seen->ids.nr ? seen->ids.data[seen->ids.nr - 1].equiv : 0;
 
        EBUG_ON(id > ancestor);
        EBUG_ON(!bch2_snapshot_is_equiv(c, id));
@@ -529,7 +489,7 @@ static bool key_visible_in_snapshot(struct bch_fs *c, struct snapshots_seen *see
 
        /* @ancestor should be the snapshot most recently added to @seen */
        EBUG_ON(ancestor != seen->pos.snapshot);
-       EBUG_ON(ancestor != top);
+       EBUG_ON(ancestor != seen->ids.data[seen->ids.nr - 1].equiv);
 
        if (id == ancestor)
                return true;
@@ -561,14 +521,16 @@ static bool key_visible_in_snapshot(struct bch_fs *c, struct snapshots_seen *see
  * snapshot id @dst, test whether there is some snapshot in which @dst is
  * visible.
  *
- * This assumes we're visiting @src keys in natural key order.
+ * @c:         filesystem handle
+ * @s:         list of snapshot IDs already seen at @src
+ * @src:       snapshot ID of src key
+ * @dst:       snapshot ID of dst key
+ * Returns:    true if there is some snapshot in which @dst is visible
  *
- * @s  - list of snapshot IDs already seen at @src
- * @src        - snapshot ID of src key
- * @dst        - snapshot ID of dst key
+ * Assumes we're visiting @src keys in natural key order
  */
-static int ref_visible(struct bch_fs *c, struct snapshots_seen *s,
-                      u32 src, u32 dst)
+static bool ref_visible(struct bch_fs *c, struct snapshots_seen *s,
+                       u32 src, u32 dst)
 {
        return dst <= src
                ? key_visible_in_snapshot(c, s, dst, src)
@@ -659,10 +621,7 @@ static int get_inodes_all_snapshots(struct btree_trans *trans,
 
        w->first_this_inode = true;
 
-       if (trans_was_restarted(trans, restart_count))
-               return -BCH_ERR_transaction_restart_nested;
-
-       return 0;
+       return trans_was_restarted(trans, restart_count);
 }
 
 static struct inode_walker_entry *
@@ -863,7 +822,7 @@ bad_hash:
                      bch2_bkey_val_to_text(&buf, c, hash_k), buf.buf))) {
                ret = hash_redo_key(trans, desc, hash_info, k_iter, hash_k);
                if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
-                       bch_err(c, "hash_redo_key err %s", bch2_err_str(ret));
+                       bch_err_fn(c, ret);
                if (ret)
                        return ret;
                ret = -BCH_ERR_transaction_restart_nested;
@@ -894,14 +853,6 @@ static int check_inode(struct btree_trans *trans,
        if (ret)
                goto err;
 
-       /*
-        * if snapshot id isn't a leaf node, skip it - deletion in
-        * particular is not atomic, so on the internal snapshot nodes
-        * we can see inodes marked for deletion after a clean shutdown
-        */
-       if (bch2_snapshot_is_internal_node(c, k.k->p.snapshot))
-               return 0;
-
        if (!bkey_is_inode(k.k))
                return 0;
 
@@ -923,6 +874,28 @@ static int check_inode(struct btree_trans *trans,
                return -EINVAL;
        }
 
+       if ((u.bi_flags & (BCH_INODE_I_SIZE_DIRTY|BCH_INODE_UNLINKED)) &&
+           bch2_key_has_snapshot_overwrites(trans, BTREE_ID_inodes, k.k->p)) {
+               struct bpos new_min_pos;
+
+               ret = bch2_propagate_key_to_snapshot_leaves(trans, iter->btree_id, k, &new_min_pos);
+               if (ret)
+                       goto err;
+
+               u.bi_flags &= ~BCH_INODE_I_SIZE_DIRTY|BCH_INODE_UNLINKED;
+
+               ret = __write_inode(trans, &u, iter->pos.snapshot);
+               if (ret) {
+                       if (!bch2_err_matches(ret, BCH_ERR_transaction_restart))
+                               bch_err_msg(c, ret, "in fsck updating inode");
+                       return ret;
+               }
+
+               if (!bpos_eq(new_min_pos, POS_MIN))
+                       bch2_btree_iter_set_pos(iter, bpos_predecessor(new_min_pos));
+               return 0;
+       }
+
        if (u.bi_flags & BCH_INODE_UNLINKED &&
            (!c->sb.clean ||
             fsck_err(c, "filesystem marked clean, but inode %llu unlinked",
@@ -930,10 +903,9 @@ static int check_inode(struct btree_trans *trans,
                bch2_trans_unlock(trans);
                bch2_fs_lazy_rw(c);
 
-               ret = fsck_inode_rm(trans, u.bi_inum, iter->pos.snapshot);
+               ret = bch2_inode_rm_snapshot(trans, u.bi_inum, iter->pos.snapshot);
                if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
-                       bch_err(c, "error in fsck: error while deleting inode: %s",
-                               bch2_err_str(ret));
+                       bch_err_msg(c, ret, "in fsck deleting inode");
                return ret;
        }
 
@@ -956,8 +928,7 @@ static int check_inode(struct btree_trans *trans,
                                POS(u.bi_inum, U64_MAX),
                                0, NULL);
                if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
-                       bch_err(c, "error in fsck: error truncating inode: %s",
-                               bch2_err_str(ret));
+                       bch_err_msg(c, ret, "in fsck truncating inode");
                if (ret)
                        return ret;
 
@@ -982,8 +953,7 @@ static int check_inode(struct btree_trans *trans,
 
                sectors = bch2_count_inode_sectors(trans, u.bi_inum, iter->pos.snapshot);
                if (sectors < 0) {
-                       bch_err(c, "error in fsck: error recounting inode sectors: %s",
-                               bch2_err_str(sectors));
+                       bch_err_msg(c, sectors, "fsck recounting inode sectors");
                        return sectors;
                }
 
@@ -1001,13 +971,14 @@ static int check_inode(struct btree_trans *trans,
 
        if (do_update) {
                ret = __write_inode(trans, &u, iter->pos.snapshot);
-               if (ret)
-                       bch_err(c, "error in fsck: error updating inode: %s",
-                               bch2_err_str(ret));
+               if (ret) {
+                       bch_err_msg(c, ret, "in fsck updating inode");
+                       return ret;
+               }
        }
 err:
 fsck_err:
-       if (ret)
+       if (ret && !bch2_err_matches(ret, BCH_ERR_transaction_restart))
                bch_err_fn(c, ret);
        return ret;
 }
@@ -1016,7 +987,7 @@ noinline_for_stack
 int bch2_check_inodes(struct bch_fs *c)
 {
        bool full = c->opts.fsck;
-       struct btree_trans trans;
+       struct btree_trans *trans = bch2_trans_get(c);
        struct btree_iter iter;
        struct bch_inode_unpacked prev = { 0 };
        struct snapshots_seen s;
@@ -1024,16 +995,15 @@ int bch2_check_inodes(struct bch_fs *c)
        int ret;
 
        snapshots_seen_init(&s);
-       bch2_trans_init(&trans, c, BTREE_ITER_MAX, 0);
 
-       ret = for_each_btree_key_commit(&trans, iter, BTREE_ID_inodes,
+       ret = for_each_btree_key_commit(trans, iter, BTREE_ID_inodes,
                        POS_MIN,
                        BTREE_ITER_PREFETCH|BTREE_ITER_ALL_SNAPSHOTS, k,
                        NULL, NULL, BTREE_INSERT_LAZY_RW|BTREE_INSERT_NOFAIL,
-               check_inode(&trans, &iter, k, &prev, &s, full));
+               check_inode(trans, &iter, k, &prev, &s, full));
 
-       bch2_trans_exit(&trans);
        snapshots_seen_exit(&s);
+       bch2_trans_put(trans);
        if (ret)
                bch_err_fn(c, ret);
        return ret;
@@ -1108,7 +1078,7 @@ static int check_i_sectors(struct btree_trans *trans, struct inode_walker *w)
                            w->last_pos.inode, i->snapshot,
                            i->inode.bi_sectors, i->count)) {
                        i->inode.bi_sectors = i->count;
-                       ret = write_inode(trans, &i->inode, i->snapshot);
+                       ret = fsck_write_inode(trans, &i->inode, i->snapshot);
                        if (ret)
                                break;
                }
@@ -1116,9 +1086,7 @@ static int check_i_sectors(struct btree_trans *trans, struct inode_walker *w)
 fsck_err:
        if (ret)
                bch_err_fn(c, ret);
-       if (!ret && trans_was_restarted(trans, restart_count))
-               ret = -BCH_ERR_transaction_restart_nested;
-       return ret;
+       return ret ?: trans_was_restarted(trans, restart_count);
 }
 
 struct extent_end {
@@ -1186,80 +1154,116 @@ static int extent_ends_at(struct bch_fs *c,
 
 static int overlapping_extents_found(struct btree_trans *trans,
                                     enum btree_id btree,
-                                    struct bpos pos1, struct bkey pos2,
-                                    bool *fixed)
+                                    struct bpos pos1, struct snapshots_seen *pos1_seen,
+                                    struct bkey pos2,
+                                    bool *fixed,
+                                    struct extent_end *extent_end)
 {
        struct bch_fs *c = trans->c;
        struct printbuf buf = PRINTBUF;
-       struct btree_iter iter;
-       struct bkey_s_c k;
-       u32 snapshot = min(pos1.snapshot, pos2.p.snapshot);
+       struct btree_iter iter1, iter2 = { NULL };
+       struct bkey_s_c k1, k2;
        int ret;
 
        BUG_ON(bkey_le(pos1, bkey_start_pos(&pos2)));
 
-       prt_str(&buf, "\n  ");
-       bch2_bpos_to_text(&buf, pos1);
-       prt_str(&buf, "\n  ");
-
-       bch2_bkey_to_text(&buf, &pos2);
-       prt_str(&buf, "\n  ");
-
-       bch2_trans_iter_init(trans, &iter, btree, SPOS(pos1.inode, pos1.offset - 1, snapshot), 0);
-       k = bch2_btree_iter_peek_upto(&iter, POS(pos1.inode, U64_MAX));
-       ret = bkey_err(k);
+       bch2_trans_iter_init(trans, &iter1, btree, pos1,
+                            BTREE_ITER_ALL_SNAPSHOTS|
+                            BTREE_ITER_NOT_EXTENTS);
+       k1 = bch2_btree_iter_peek_upto(&iter1, POS(pos1.inode, U64_MAX));
+       ret = bkey_err(k1);
        if (ret)
                goto err;
 
-       bch2_bkey_val_to_text(&buf, c, k);
+       prt_str(&buf, "\n  ");
+       bch2_bkey_val_to_text(&buf, c, k1);
+
+       if (!bpos_eq(pos1, k1.k->p)) {
+               prt_str(&buf, "\n  wanted\n  ");
+               bch2_bpos_to_text(&buf, pos1);
+               prt_str(&buf, "\n  ");
+               bch2_bkey_to_text(&buf, &pos2);
 
-       if (!bpos_eq(pos1, k.k->p)) {
-               bch_err(c, "%s: error finding first overlapping extent when repairing%s",
+               bch_err(c, "%s: error finding first overlapping extent when repairing, got%s",
                        __func__, buf.buf);
                ret = -BCH_ERR_internal_fsck_err;
                goto err;
        }
 
+       bch2_trans_copy_iter(&iter2, &iter1);
+
        while (1) {
-               bch2_btree_iter_advance(&iter);
+               bch2_btree_iter_advance(&iter2);
 
-               k = bch2_btree_iter_peek_upto(&iter, POS(pos1.inode, U64_MAX));
-               ret = bkey_err(k);
+               k2 = bch2_btree_iter_peek_upto(&iter2, POS(pos1.inode, U64_MAX));
+               ret = bkey_err(k2);
                if (ret)
                        goto err;
 
-               if (bkey_ge(k.k->p, pos2.p))
+               if (bpos_ge(k2.k->p, pos2.p))
                        break;
-
        }
 
        prt_str(&buf, "\n  ");
-       bch2_bkey_val_to_text(&buf, c, k);
+       bch2_bkey_val_to_text(&buf, c, k2);
 
-       if (bkey_gt(k.k->p, pos2.p) ||
-           pos2.size != k.k->size) {
+       if (bpos_gt(k2.k->p, pos2.p) ||
+           pos2.size != k2.k->size) {
                bch_err(c, "%s: error finding seconding overlapping extent when repairing%s",
                        __func__, buf.buf);
                ret = -BCH_ERR_internal_fsck_err;
                goto err;
        }
 
+       prt_printf(&buf, "\n  overwriting %s extent",
+                  pos1.snapshot >= pos2.p.snapshot ? "first" : "second");
+
        if (fsck_err(c, "overlapping extents%s", buf.buf)) {
-               struct bpos update_pos = pos1.snapshot < pos2.p.snapshot ? pos1 : pos2.p;
-               struct btree_iter update_iter;
+               struct btree_iter *old_iter = &iter1;
+               struct disk_reservation res = { 0 };
 
-               struct bkey_i *update = bch2_bkey_get_mut(trans, &update_iter,
-                                               btree, update_pos,
-                                               BTREE_UPDATE_INTERNAL_SNAPSHOT_NODE);
-               bch2_trans_iter_exit(trans, &update_iter);
-               if ((ret = PTR_ERR_OR_ZERO(update)))
+               if (pos1.snapshot < pos2.p.snapshot) {
+                       old_iter = &iter2;
+                       swap(k1, k2);
+               }
+
+               trans->extra_journal_res += bch2_bkey_sectors_compressed(k2);
+
+               ret =   bch2_trans_update_extent_overwrite(trans, old_iter,
+                               BTREE_UPDATE_INTERNAL_SNAPSHOT_NODE,
+                               k1, k2) ?:
+                       bch2_trans_commit(trans, &res, NULL,
+                               BTREE_INSERT_LAZY_RW|BTREE_INSERT_NOFAIL);
+               bch2_disk_reservation_put(c, &res);
+
+               if (ret)
                        goto err;
 
                *fixed = true;
+
+               if (pos1.snapshot == pos2.p.snapshot) {
+                       /*
+                        * We overwrote the first extent, and did the overwrite
+                        * in the same snapshot:
+                        */
+                       extent_end->offset = bkey_start_offset(&pos2);
+               } else if (pos1.snapshot > pos2.p.snapshot) {
+                       /*
+                        * We overwrote the first extent in pos2's snapshot:
+                        */
+                       ret = snapshots_seen_add_inorder(c, pos1_seen, pos2.p.snapshot);
+               } else {
+                       /*
+                        * We overwrote the second extent - restart
+                        * check_extent() from the top:
+                        */
+                       ret = -BCH_ERR_transaction_restart_nested;
+               }
        }
 fsck_err:
 err:
-       bch2_trans_iter_exit(trans, &iter);
+       bch2_trans_iter_exit(trans, &iter2);
+       bch2_trans_iter_exit(trans, &iter1);
        printbuf_exit(&buf);
        return ret;
 }
@@ -1269,11 +1273,11 @@ static int check_overlapping_extents(struct btree_trans *trans,
                              struct extent_ends *extent_ends,
                              struct bkey_s_c k,
                              u32 equiv,
-                             struct btree_iter *iter)
+                             struct btree_iter *iter,
+                             bool *fixed)
 {
        struct bch_fs *c = trans->c;
        struct extent_end *i;
-       bool fixed = false;
        int ret = 0;
 
        /* transaction restart, running again */
@@ -1296,7 +1300,8 @@ static int check_overlapping_extents(struct btree_trans *trans,
                                                SPOS(iter->pos.inode,
                                                     i->offset,
                                                     i->snapshot),
-                                               *k.k, &fixed);
+                                               &i->seen,
+                                               *k.k, fixed, i);
                if (ret)
                        goto err;
        }
@@ -1307,7 +1312,7 @@ static int check_overlapping_extents(struct btree_trans *trans,
 
        extent_ends->last_pos = k.k->p;
 err:
-       return ret ?: fixed;
+       return ret;
 }
 
 static int check_extent(struct btree_trans *trans, struct btree_iter *iter,
@@ -1362,13 +1367,10 @@ static int check_extent(struct btree_trans *trans, struct btree_iter *iter,
                        goto delete;
 
                ret = check_overlapping_extents(trans, s, extent_ends, k,
-                                               equiv.snapshot, iter);
-               if (ret < 0)
-                       goto err;
-
+                                               equiv.snapshot, iter,
+                                               &inode->recalculate_sums);
                if (ret)
-                       inode->recalculate_sums = true;
-               ret = 0;
+                       goto err;
        }
 
        /*
@@ -1434,7 +1436,7 @@ int bch2_check_extents(struct bch_fs *c)
 {
        struct inode_walker w = inode_walker_init();
        struct snapshots_seen s;
-       struct btree_trans trans;
+       struct btree_trans *trans = bch2_trans_get(c);
        struct btree_iter iter;
        struct bkey_s_c k;
        struct extent_ends extent_ends;
@@ -1443,23 +1445,22 @@ int bch2_check_extents(struct bch_fs *c)
 
        snapshots_seen_init(&s);
        extent_ends_init(&extent_ends);
-       bch2_trans_init(&trans, c, BTREE_ITER_MAX, 0);
 
-       ret = for_each_btree_key_commit(&trans, iter, BTREE_ID_extents,
+       ret = for_each_btree_key_commit(trans, iter, BTREE_ID_extents,
                        POS(BCACHEFS_ROOT_INO, 0),
                        BTREE_ITER_PREFETCH|BTREE_ITER_ALL_SNAPSHOTS, k,
                        &res, NULL,
                        BTREE_INSERT_LAZY_RW|BTREE_INSERT_NOFAIL, ({
                bch2_disk_reservation_put(c, &res);
-               check_extent(&trans, &iter, k, &w, &s, &extent_ends);
+               check_extent(trans, &iter, k, &w, &s, &extent_ends);
        })) ?:
-       check_i_sectors(&trans, &w);
+       check_i_sectors(trans, &w);
 
        bch2_disk_reservation_put(c, &res);
        extent_ends_exit(&extent_ends);
        inode_walker_exit(&w);
-       bch2_trans_exit(&trans);
        snapshots_seen_exit(&s);
+       bch2_trans_put(trans);
 
        if (ret)
                bch_err_fn(c, ret);
@@ -1494,7 +1495,7 @@ static int check_subdir_count(struct btree_trans *trans, struct inode_walker *w)
                                "directory %llu:%u with wrong i_nlink: got %u, should be %llu",
                                w->last_pos.inode, i->snapshot, i->inode.bi_nlink, i->count)) {
                        i->inode.bi_nlink = i->count;
-                       ret = write_inode(trans, &i->inode, i->snapshot);
+                       ret = fsck_write_inode(trans, &i->inode, i->snapshot);
                        if (ret)
                                break;
                }
@@ -1502,9 +1503,7 @@ static int check_subdir_count(struct btree_trans *trans, struct inode_walker *w)
 fsck_err:
        if (ret)
                bch_err_fn(c, ret);
-       if (!ret && trans_was_restarted(trans, restart_count))
-               ret = -BCH_ERR_transaction_restart_nested;
-       return ret;
+       return ret ?: trans_was_restarted(trans, restart_count);
 }
 
 static int check_dirent_target(struct btree_trans *trans,
@@ -1669,7 +1668,7 @@ static int check_dirent(struct btree_trans *trans, struct btree_iter *iter,
        if (ret < 0)
                goto err;
 
-       if (dir->first_this_inode)
+       if (dir->first_this_inode && dir->inodes.nr)
                *hash_info = bch2_hash_info_init(c, &dir->inodes.data[0].inode);
        dir->first_this_inode = false;
 
@@ -1802,23 +1801,22 @@ int bch2_check_dirents(struct bch_fs *c)
        struct inode_walker target = inode_walker_init();
        struct snapshots_seen s;
        struct bch_hash_info hash_info;
-       struct btree_trans trans;
+       struct btree_trans *trans = bch2_trans_get(c);
        struct btree_iter iter;
        struct bkey_s_c k;
        int ret = 0;
 
        snapshots_seen_init(&s);
-       bch2_trans_init(&trans, c, BTREE_ITER_MAX, 0);
 
-       ret = for_each_btree_key_commit(&trans, iter, BTREE_ID_dirents,
+       ret = for_each_btree_key_commit(trans, iter, BTREE_ID_dirents,
                        POS(BCACHEFS_ROOT_INO, 0),
                        BTREE_ITER_PREFETCH|BTREE_ITER_ALL_SNAPSHOTS,
                        k,
                        NULL, NULL,
                        BTREE_INSERT_LAZY_RW|BTREE_INSERT_NOFAIL,
-               check_dirent(&trans, &iter, k, &hash_info, &dir, &target, &s));
+               check_dirent(trans, &iter, k, &hash_info, &dir, &target, &s));
 
-       bch2_trans_exit(&trans);
+       bch2_trans_put(trans);
        snapshots_seen_exit(&s);
        inode_walker_exit(&dir);
        inode_walker_exit(&target);
@@ -1846,7 +1844,7 @@ static int check_xattr(struct btree_trans *trans, struct btree_iter *iter,
        if (ret)
                return ret;
 
-       if (inode->first_this_inode)
+       if (inode->first_this_inode && inode->inodes.nr)
                *hash_info = bch2_hash_info_init(c, &inode->inodes.data[0].inode);
        inode->first_this_inode = false;
 
@@ -1872,23 +1870,18 @@ int bch2_check_xattrs(struct bch_fs *c)
 {
        struct inode_walker inode = inode_walker_init();
        struct bch_hash_info hash_info;
-       struct btree_trans trans;
        struct btree_iter iter;
        struct bkey_s_c k;
        int ret = 0;
 
-       bch2_trans_init(&trans, c, BTREE_ITER_MAX, 0);
-
-       ret = for_each_btree_key_commit(&trans, iter, BTREE_ID_xattrs,
+       ret = bch2_trans_run(c,
+               for_each_btree_key_commit(trans, iter, BTREE_ID_xattrs,
                        POS(BCACHEFS_ROOT_INO, 0),
                        BTREE_ITER_PREFETCH|BTREE_ITER_ALL_SNAPSHOTS,
                        k,
                        NULL, NULL,
                        BTREE_INSERT_LAZY_RW|BTREE_INSERT_NOFAIL,
-               check_xattr(&trans, &iter, k, &hash_info, &inode));
-
-       bch2_trans_exit(&trans);
-
+               check_xattr(trans, &iter, k, &hash_info, &inode)));
        if (ret)
                bch_err_fn(c, ret);
        return ret;
@@ -1920,10 +1913,10 @@ static int check_root_trans(struct btree_trans *trans)
                ret = commit_do(trans, NULL, NULL,
                                      BTREE_INSERT_NOFAIL|
                                      BTREE_INSERT_LAZY_RW,
-                       __bch2_btree_insert(trans, BTREE_ID_subvolumes,
+                       bch2_btree_insert_trans(trans, BTREE_ID_subvolumes,
                                            &root_subvol.k_i, 0));
                if (ret) {
-                       bch_err(c, "error writing root subvol: %s", bch2_err_str(ret));
+                       bch_err_msg(c, ret, "writing root subvol");
                        goto err;
                }
 
@@ -1942,7 +1935,7 @@ static int check_root_trans(struct btree_trans *trans)
 
                ret = __write_inode(trans, &root_inode, snapshot);
                if (ret)
-                       bch_err(c, "error writing root inode: %s", bch2_err_str(ret));
+                       bch_err_msg(c, ret, "writing root inode");
        }
 err:
 fsck_err:
@@ -1957,7 +1950,7 @@ int bch2_check_root(struct bch_fs *c)
        ret = bch2_trans_do(c, NULL, NULL,
                             BTREE_INSERT_NOFAIL|
                             BTREE_INSERT_LAZY_RW,
-               check_root_trans(&trans));
+               check_root_trans(trans));
 
        if (ret)
                bch_err_fn(c, ret);
@@ -2109,16 +2102,14 @@ fsck_err:
  */
 int bch2_check_directory_structure(struct bch_fs *c)
 {
-       struct btree_trans trans;
+       struct btree_trans *trans = bch2_trans_get(c);
        struct btree_iter iter;
        struct bkey_s_c k;
        struct bch_inode_unpacked u;
        pathbuf path = { 0, };
        int ret;
 
-       bch2_trans_init(&trans, c, BTREE_ITER_MAX, 0);
-
-       for_each_btree_key(&trans, iter, BTREE_ID_inodes, POS_MIN,
+       for_each_btree_key(trans, iter, BTREE_ID_inodes, POS_MIN,
                           BTREE_ITER_INTENT|
                           BTREE_ITER_PREFETCH|
                           BTREE_ITER_ALL_SNAPSHOTS, k, ret) {
@@ -2135,12 +2126,12 @@ int bch2_check_directory_structure(struct bch_fs *c)
                if (u.bi_flags & BCH_INODE_UNLINKED)
                        continue;
 
-               ret = check_path(&trans, &path, &u, iter.pos.snapshot);
+               ret = check_path(trans, &path, &u, iter.pos.snapshot);
                if (ret)
                        break;
        }
-       bch2_trans_iter_exit(&trans, &iter);
-       bch2_trans_exit(&trans);
+       bch2_trans_iter_exit(trans, &iter);
+       bch2_trans_put(trans);
        darray_exit(&path);
 
        if (ret)
@@ -2148,8 +2139,6 @@ int bch2_check_directory_structure(struct bch_fs *c)
        return ret;
 }
 
-/* check_nlink pass: */
-
 struct nlink_table {
        size_t          nr;
        size_t          size;
@@ -2231,15 +2220,13 @@ static int check_nlinks_find_hardlinks(struct bch_fs *c,
                                       struct nlink_table *t,
                                       u64 start, u64 *end)
 {
-       struct btree_trans trans;
+       struct btree_trans *trans = bch2_trans_get(c);
        struct btree_iter iter;
        struct bkey_s_c k;
        struct bch_inode_unpacked u;
        int ret = 0;
 
-       bch2_trans_init(&trans, c, BTREE_ITER_MAX, 0);
-
-       for_each_btree_key(&trans, iter, BTREE_ID_inodes,
+       for_each_btree_key(trans, iter, BTREE_ID_inodes,
                           POS(0, start),
                           BTREE_ITER_INTENT|
                           BTREE_ITER_PREFETCH|
@@ -2268,8 +2255,8 @@ static int check_nlinks_find_hardlinks(struct bch_fs *c,
                }
 
        }
-       bch2_trans_iter_exit(&trans, &iter);
-       bch2_trans_exit(&trans);
+       bch2_trans_iter_exit(trans, &iter);
+       bch2_trans_put(trans);
 
        if (ret)
                bch_err(c, "error in fsck: btree error %i while walking inodes", ret);
@@ -2281,7 +2268,7 @@ noinline_for_stack
 static int check_nlinks_walk_dirents(struct bch_fs *c, struct nlink_table *links,
                                     u64 range_start, u64 range_end)
 {
-       struct btree_trans trans;
+       struct btree_trans *trans = bch2_trans_get(c);
        struct snapshots_seen s;
        struct btree_iter iter;
        struct bkey_s_c k;
@@ -2290,9 +2277,7 @@ static int check_nlinks_walk_dirents(struct bch_fs *c, struct nlink_table *links
 
        snapshots_seen_init(&s);
 
-       bch2_trans_init(&trans, c, BTREE_ITER_MAX, 0);
-
-       for_each_btree_key(&trans, iter, BTREE_ID_dirents, POS_MIN,
+       for_each_btree_key(trans, iter, BTREE_ID_dirents, POS_MIN,
                           BTREE_ITER_INTENT|
                           BTREE_ITER_PREFETCH|
                           BTREE_ITER_ALL_SNAPSHOTS, k, ret) {
@@ -2312,12 +2297,12 @@ static int check_nlinks_walk_dirents(struct bch_fs *c, struct nlink_table *links
                        break;
                }
        }
-       bch2_trans_iter_exit(&trans, &iter);
+       bch2_trans_iter_exit(trans, &iter);
 
        if (ret)
                bch_err(c, "error in fsck: btree error %i while walking dirents", ret);
 
-       bch2_trans_exit(&trans);
+       bch2_trans_put(trans);
        snapshots_seen_exit(&s);
        return ret;
 }
@@ -2368,22 +2353,17 @@ static int check_nlinks_update_hardlinks(struct bch_fs *c,
                               struct nlink_table *links,
                               u64 range_start, u64 range_end)
 {
-       struct btree_trans trans;
        struct btree_iter iter;
        struct bkey_s_c k;
        size_t idx = 0;
        int ret = 0;
 
-       bch2_trans_init(&trans, c, BTREE_ITER_MAX, 0);
-
-       ret = for_each_btree_key_commit(&trans, iter, BTREE_ID_inodes,
-                       POS(0, range_start),
-                       BTREE_ITER_INTENT|BTREE_ITER_PREFETCH|BTREE_ITER_ALL_SNAPSHOTS, k,
-                       NULL, NULL, BTREE_INSERT_LAZY_RW|BTREE_INSERT_NOFAIL,
-               check_nlinks_update_inode(&trans, &iter, k, links, &idx, range_end));
-
-       bch2_trans_exit(&trans);
-
+       ret = bch2_trans_run(c,
+               for_each_btree_key_commit(trans, iter, BTREE_ID_inodes,
+                               POS(0, range_start),
+                               BTREE_ITER_INTENT|BTREE_ITER_PREFETCH|BTREE_ITER_ALL_SNAPSHOTS, k,
+                               NULL, NULL, BTREE_INSERT_LAZY_RW|BTREE_INSERT_NOFAIL,
+                       check_nlinks_update_inode(trans, &iter, k, links, &idx, range_end)));
        if (ret < 0) {
                bch_err(c, "error in fsck: btree error %i while walking inodes", ret);
                return ret;
@@ -2465,13 +2445,12 @@ int bch2_fix_reflink_p(struct bch_fs *c)
                return 0;
 
        ret = bch2_trans_run(c,
-               for_each_btree_key_commit(&trans, iter,
+               for_each_btree_key_commit(trans, iter,
                                BTREE_ID_extents, POS_MIN,
                                BTREE_ITER_INTENT|BTREE_ITER_PREFETCH|
                                BTREE_ITER_ALL_SNAPSHOTS, k,
                                NULL, NULL, BTREE_INSERT_NOFAIL|BTREE_INSERT_LAZY_RW,
-                       fix_reflink_p_key(&trans, &iter, k)));
-
+                       fix_reflink_p_key(trans, &iter, k)));
        if (ret)
                bch_err_fn(c, ret);
        return ret;