]> git.sesse.net Git - bcachefs-tools-debian/blobdiff - libbcachefs/btree_io.c
Disable pristine-tar option in gbp.conf, since there is no pristine-tar branch.
[bcachefs-tools-debian] / libbcachefs / btree_io.c
index ff390c51688fa8b55d194f44dad43557f0772bf2..86415701b824076f10fc79e8bee220ed7075e328 100644 (file)
 #include "debug.h"
 #include "error.h"
 #include "extents.h"
-#include "io.h"
+#include "io_write.h"
 #include "journal_reclaim.h"
 #include "journal_seq_blacklist.h"
+#include "recovery.h"
 #include "super-io.h"
+#include "trace.h"
 
 #include <linux/sched/mm.h>
-#include <trace/events/bcachefs.h>
 
 void bch2_btree_node_io_unlock(struct btree *b)
 {
@@ -33,7 +34,7 @@ void bch2_btree_node_io_unlock(struct btree *b)
 
 void bch2_btree_node_io_lock(struct btree *b)
 {
-       BUG_ON(lock_class_is_held(&bch2_btree_node_lock_key));
+       bch2_assert_btree_nodes_not_locked();
 
        wait_on_bit_lock_io(&b->flags, BTREE_NODE_write_in_flight,
                            TASK_UNINTERRUPTIBLE);
@@ -53,7 +54,7 @@ void __bch2_btree_node_wait_on_write(struct btree *b)
 
 void bch2_btree_node_wait_on_read(struct btree *b)
 {
-       BUG_ON(lock_class_is_held(&bch2_btree_node_lock_key));
+       bch2_assert_btree_nodes_not_locked();
 
        wait_on_bit_io(&b->flags, BTREE_NODE_read_in_flight,
                       TASK_UNINTERRUPTIBLE);
@@ -61,7 +62,7 @@ void bch2_btree_node_wait_on_read(struct btree *b)
 
 void bch2_btree_node_wait_on_write(struct btree *b)
 {
-       BUG_ON(lock_class_is_held(&bch2_btree_node_lock_key));
+       bch2_assert_btree_nodes_not_locked();
 
        wait_on_bit_io(&b->flags, BTREE_NODE_write_in_flight,
                       TASK_UNINTERRUPTIBLE);
@@ -77,13 +78,13 @@ static void verify_no_dups(struct btree *b,
        if (start == end)
                return;
 
-       for (p = start, k = bkey_next(start);
+       for (p = start, k = bkey_p_next(start);
             k != end;
-            p = k, k = bkey_next(k)) {
+            p = k, k = bkey_p_next(k)) {
                struct bkey l = bkey_unpack_key(b, p);
                struct bkey r = bkey_unpack_key(b, k);
 
-               BUG_ON(bpos_cmp(l.p, bkey_start_pos(&r)) >= 0);
+               BUG_ON(bpos_ge(l.p, bkey_start_pos(&r)));
        }
 #endif
 }
@@ -92,7 +93,7 @@ static void set_needs_whiteout(struct bset *i, int v)
 {
        struct bkey_packed *k;
 
-       for (k = i->start; k != vstruct_last(i); k = bkey_next(k))
+       for (k = i->start; k != vstruct_last(i); k = bkey_p_next(k))
                k->needs_whiteout = v;
 }
 
@@ -102,7 +103,7 @@ static void btree_bounce_free(struct bch_fs *c, size_t size,
        if (used_mempool)
                mempool_free(p, &c->btree_bounce_pool);
        else
-               vpfree(p, size);
+               kvfree(p);
 }
 
 static void *btree_bounce_alloc(struct bch_fs *c, size_t size,
@@ -111,13 +112,13 @@ static void *btree_bounce_alloc(struct bch_fs *c, size_t size,
        unsigned flags = memalloc_nofs_save();
        void *p;
 
-       BUG_ON(size > btree_bytes(c));
+       BUG_ON(size > c->opts.btree_node_size);
 
        *used_mempool = false;
-       p = vpmalloc(size, __GFP_NOWARN|GFP_NOWAIT);
+       p = kvmalloc(size, __GFP_NOWARN|GFP_NOWAIT);
        if (!p) {
                *used_mempool = true;
-               p = mempool_alloc(&c->btree_bounce_pool, GFP_NOIO);
+               p = mempool_alloc(&c->btree_bounce_pool, GFP_NOFS);
        }
        memalloc_nofs_restore(flags);
        return p;
@@ -173,9 +174,9 @@ static void bch2_sort_whiteouts(struct bch_fs *c, struct btree *b)
 
        ptrs = ptrs_end = ((void *) new_whiteouts + bytes);
 
-       for (k = unwritten_whiteouts_start(c, b);
-            k != unwritten_whiteouts_end(c, b);
-            k = bkey_next(k))
+       for (k = unwritten_whiteouts_start(b);
+            k != unwritten_whiteouts_end(b);
+            k = bkey_p_next(k))
                *--ptrs = k;
 
        sort_bkey_ptrs(b, ptrs, ptrs_end - ptrs);
@@ -183,15 +184,15 @@ static void bch2_sort_whiteouts(struct bch_fs *c, struct btree *b)
        k = new_whiteouts;
 
        while (ptrs != ptrs_end) {
-               bkey_copy(k, *ptrs);
-               k = bkey_next(k);
+               bkey_p_copy(k, *ptrs);
+               k = bkey_p_next(k);
                ptrs++;
        }
 
        verify_no_dups(b, new_whiteouts,
                       (void *) ((u64 *) new_whiteouts + b->whiteout_u64s));
 
-       memcpy_u64s(unwritten_whiteouts_start(c, b),
+       memcpy_u64s(unwritten_whiteouts_start(b),
                    new_whiteouts, b->whiteout_u64s);
 
        btree_bounce_free(c, bytes, used_mempool, new_whiteouts);
@@ -256,11 +257,11 @@ static bool bch2_drop_whiteouts(struct btree *b, enum compact_mode mode)
                out = i->start;
 
                for (k = start; k != end; k = n) {
-                       n = bkey_next(k);
+                       n = bkey_p_next(k);
 
                        if (!bkey_deleted(k)) {
-                               bkey_copy(out, k);
-                               out = bkey_next(out);
+                               bkey_p_copy(out, k);
+                               out = bkey_p_next(out);
                        } else {
                                BUG_ON(k->needs_whiteout);
                        }
@@ -291,7 +292,7 @@ static void btree_node_sort(struct bch_fs *c, struct btree *b,
                            bool filter_whiteouts)
 {
        struct btree_node *out;
-       struct sort_iter sort_iter;
+       struct sort_iter_stack sort_iter;
        struct bset_tree *t;
        struct bset *start_bset = bset(b, &b->set[start_idx]);
        bool used_mempool = false;
@@ -300,33 +301,33 @@ static void btree_node_sort(struct bch_fs *c, struct btree *b,
        bool sorting_entire_node = start_idx == 0 &&
                end_idx == b->nsets;
 
-       sort_iter_init(&sort_iter, b);
+       sort_iter_stack_init(&sort_iter, b);
 
        for (t = b->set + start_idx;
             t < b->set + end_idx;
             t++) {
                u64s += le16_to_cpu(bset(b, t)->u64s);
-               sort_iter_add(&sort_iter,
+               sort_iter_add(&sort_iter.iter,
                              btree_bkey_first(b, t),
                              btree_bkey_last(b, t));
        }
 
        bytes = sorting_entire_node
-               ? btree_bytes(c)
+               ? btree_buf_bytes(b)
                : __vstruct_bytes(struct btree_node, u64s);
 
        out = btree_bounce_alloc(c, bytes, &used_mempool);
 
        start_time = local_clock();
 
-       u64s = bch2_sort_keys(out->keys.start, &sort_iter, filter_whiteouts);
+       u64s = bch2_sort_keys(out->keys.start, &sort_iter.iter, filter_whiteouts);
 
        out->keys.u64s = cpu_to_le16(u64s);
 
        BUG_ON(vstruct_end(&out->keys) > (void *) out + bytes);
 
        if (sorting_entire_node)
-               bch2_time_stats_update(&c->times[BCH_TIME_btree_node_sort],
+               time_stats_update(&c->times[BCH_TIME_btree_node_sort],
                                       start_time);
 
        /* Make sure we preserve bset journal_seq: */
@@ -335,9 +336,9 @@ static void btree_node_sort(struct bch_fs *c, struct btree *b,
        start_bset->journal_seq = cpu_to_le64(seq);
 
        if (sorting_entire_node) {
-               unsigned u64s = le16_to_cpu(out->keys.u64s);
+               u64s = le16_to_cpu(out->keys.u64s);
 
-               BUG_ON(bytes != btree_bytes(c));
+               BUG_ON(bytes != btree_buf_bytes(b));
 
                /*
                 * Our temporary buffer is the same size as the btree node's
@@ -396,7 +397,7 @@ void bch2_btree_sort_into(struct bch_fs *c,
                        &dst->format,
                        true);
 
-       bch2_time_stats_update(&c->times[BCH_TIME_btree_node_sort],
+       time_stats_update(&c->times[BCH_TIME_btree_node_sort],
                               start_time);
 
        set_btree_bset_end(dst, dst->set);
@@ -409,8 +410,6 @@ void bch2_btree_sort_into(struct bch_fs *c,
        bch2_verify_btree_nr_keys(dst);
 }
 
-#define SORT_CRIT      (4096 / sizeof(u64))
-
 /*
  * We're about to add another bset to the btree node, so if there's currently
  * too many bsets - sort some of them together:
@@ -450,6 +449,24 @@ void bch2_btree_build_aux_trees(struct btree *b)
                                t == bset_tree_last(b));
 }
 
+/*
+ * If we have MAX_BSETS (3) bsets, should we sort them all down to just one?
+ *
+ * The first bset is going to be of similar order to the size of the node, the
+ * last bset is bounded by btree_write_set_buffer(), which is set to keep the
+ * memmove on insert from being too expensive: the middle bset should, ideally,
+ * be the geometric mean of the first and the last.
+ *
+ * Returns true if the middle bset is greater than that geometric mean:
+ */
+static inline bool should_compact_all(struct bch_fs *c, struct btree *b)
+{
+       unsigned mid_u64s_bits =
+               (ilog2(btree_max_u64s(c)) + BTREE_WRITE_SET_U64s_BITS) / 2;
+
+       return bset_u64s(&b->set[1]) > 1U << mid_u64s_bits;
+}
+
 /*
  * @bch_btree_init_next - initialize a new (unwritten) bset that can then be
  * inserted into
@@ -465,21 +482,16 @@ void bch2_btree_init_next(struct btree_trans *trans, struct btree *b)
        struct btree_node_entry *bne;
        bool reinit_iter = false;
 
-       EBUG_ON(!(b->c.lock.state.seq & 1));
+       EBUG_ON(!six_lock_counts(&b->c.lock).n[SIX_LOCK_write]);
        BUG_ON(bset_written(b, bset(b, &b->set[1])));
+       BUG_ON(btree_node_just_written(b));
 
        if (b->nsets == MAX_BSETS &&
-           !btree_node_write_in_flight(b)) {
-               unsigned log_u64s[] = {
-                       ilog2(bset_u64s(&b->set[0])),
-                       ilog2(bset_u64s(&b->set[1])),
-                       ilog2(bset_u64s(&b->set[2])),
-               };
-
-               if (log_u64s[1] >= (log_u64s[0] + log_u64s[2]) / 2) {
-                       bch2_btree_node_write(c, b, SIX_LOCK_write, 0);
-                       reinit_iter = true;
-               }
+           !btree_node_write_in_flight(b) &&
+           should_compact_all(c, b)) {
+               bch2_btree_node_write(c, b, SIX_LOCK_write,
+                                     BTREE_WRITE_init_next_bset);
+               reinit_iter = true;
        }
 
        if (b->nsets == MAX_BSETS &&
@@ -490,7 +502,7 @@ void bch2_btree_init_next(struct btree_trans *trans, struct btree *b)
 
        bne = want_new_bset(c, b);
        if (bne)
-               bch2_bset_init_next(c, b, bne);
+               bch2_bset_init_next(b, bne);
 
        bch2_btree_build_aux_trees(b);
 
@@ -498,94 +510,104 @@ void bch2_btree_init_next(struct btree_trans *trans, struct btree *b)
                bch2_trans_node_reinit_iter(trans, b);
 }
 
-static void btree_pos_to_text(struct printbuf *out, struct bch_fs *c,
-                         struct btree *b)
-{
-       pr_buf(out, "%s level %u/%u\n  ",
-              bch2_btree_ids[b->c.btree_id],
-              b->c.level,
-              c->btree_roots[b->c.btree_id].level);
-       bch2_bkey_val_to_text(out, c, bkey_i_to_s_c(&b->key));
-}
-
 static void btree_err_msg(struct printbuf *out, struct bch_fs *c,
                          struct bch_dev *ca,
                          struct btree *b, struct bset *i,
                          unsigned offset, int write)
 {
-       pr_buf(out, "error validating btree node ");
-       if (write)
-               pr_buf(out, "before write ");
+       prt_printf(out, bch2_log_msg(c, "%s"),
+                  write == READ
+                  ? "error validating btree node "
+                  : "corrupt btree node before write ");
        if (ca)
-               pr_buf(out, "on %s ", ca->name);
-       pr_buf(out, "at btree ");
-       btree_pos_to_text(out, c, b);
+               prt_printf(out, "on %s ", ca->name);
+       prt_printf(out, "at btree ");
+       bch2_btree_pos_to_text(out, c, b);
 
-       pr_buf(out, "\n  node offset %u", b->written);
+       prt_printf(out, "\n  node offset %u/%u",
+                  b->written, btree_ptr_sectors_written(&b->key));
        if (i)
-               pr_buf(out, " bset u64s %u", le16_to_cpu(i->u64s));
+               prt_printf(out, " bset u64s %u", le16_to_cpu(i->u64s));
+       prt_str(out, ": ");
 }
 
-enum btree_err_type {
-       BTREE_ERR_FIXABLE,
-       BTREE_ERR_WANT_RETRY,
-       BTREE_ERR_MUST_RETRY,
-       BTREE_ERR_FATAL,
-};
+__printf(9, 10)
+static int __btree_err(int ret,
+                      struct bch_fs *c,
+                      struct bch_dev *ca,
+                      struct btree *b,
+                      struct bset *i,
+                      int write,
+                      bool have_retry,
+                      enum bch_sb_error_id err_type,
+                      const char *fmt, ...)
+{
+       struct printbuf out = PRINTBUF;
+       va_list args;
 
-enum btree_validate_ret {
-       BTREE_RETRY_READ = 64,
-};
+       btree_err_msg(&out, c, ca, b, i, b->written, write);
+
+       va_start(args, fmt);
+       prt_vprintf(&out, fmt, args);
+       va_end(args);
 
-#define btree_err(type, c, ca, b, i, msg, ...)                         \
+       if (write == WRITE) {
+               bch2_print_string_as_lines(KERN_ERR, out.buf);
+               ret = c->opts.errors == BCH_ON_ERROR_continue
+                       ? 0
+                       : -BCH_ERR_fsck_errors_not_fixed;
+               goto out;
+       }
+
+       if (!have_retry && ret == -BCH_ERR_btree_node_read_err_want_retry)
+               ret = -BCH_ERR_btree_node_read_err_fixable;
+       if (!have_retry && ret == -BCH_ERR_btree_node_read_err_must_retry)
+               ret = -BCH_ERR_btree_node_read_err_bad_node;
+
+       if (ret != -BCH_ERR_btree_node_read_err_fixable)
+               bch2_sb_error_count(c, err_type);
+
+       switch (ret) {
+       case -BCH_ERR_btree_node_read_err_fixable:
+               ret = bch2_fsck_err(c, FSCK_CAN_FIX, err_type, "%s", out.buf);
+               if (ret != -BCH_ERR_fsck_fix &&
+                   ret != -BCH_ERR_fsck_ignore)
+                       goto fsck_err;
+               ret = -BCH_ERR_fsck_fix;
+               break;
+       case -BCH_ERR_btree_node_read_err_want_retry:
+       case -BCH_ERR_btree_node_read_err_must_retry:
+               bch2_print_string_as_lines(KERN_ERR, out.buf);
+               break;
+       case -BCH_ERR_btree_node_read_err_bad_node:
+               bch2_print_string_as_lines(KERN_ERR, out.buf);
+               ret = bch2_topology_error(c);
+               break;
+       case -BCH_ERR_btree_node_read_err_incompatible:
+               bch2_print_string_as_lines(KERN_ERR, out.buf);
+               ret = -BCH_ERR_fsck_errors_not_fixed;
+               break;
+       default:
+               BUG();
+       }
+out:
+fsck_err:
+       printbuf_exit(&out);
+       return ret;
+}
+
+#define btree_err(type, c, ca, b, i, _err_type, msg, ...)              \
 ({                                                                     \
-       __label__ out;                                                  \
-       struct printbuf out = PRINTBUF;                                 \
-                                                                       \
-       btree_err_msg(&out, c, ca, b, i, b->written, write);            \
-       pr_buf(&out, ": " msg, ##__VA_ARGS__);                          \
+       int _ret = __btree_err(type, c, ca, b, i, write, have_retry,    \
+                              BCH_FSCK_ERR_##_err_type,                \
+                              msg, ##__VA_ARGS__);                     \
                                                                        \
-       if (type == BTREE_ERR_FIXABLE &&                                \
-           write == READ &&                                            \
-           !test_bit(BCH_FS_INITIAL_GC_DONE, &c->flags)) {             \
-               mustfix_fsck_err(c, "%s", out.buf);                     \
-               goto out;                                               \
+       if (_ret != -BCH_ERR_fsck_fix) {                                \
+               ret = _ret;                                             \
+               goto fsck_err;                                          \
        }                                                               \
                                                                        \
-       switch (write) {                                                \
-       case READ:                                                      \
-               bch_err(c, "%s", out.buf);                              \
-                                                                       \
-               switch (type) {                                         \
-               case BTREE_ERR_FIXABLE:                                 \
-                       ret = BCH_FSCK_ERRORS_NOT_FIXED;                \
-                       goto fsck_err;                                  \
-               case BTREE_ERR_WANT_RETRY:                              \
-                       if (have_retry) {                               \
-                               ret = BTREE_RETRY_READ;                 \
-                               goto fsck_err;                          \
-                       }                                               \
-                       break;                                          \
-               case BTREE_ERR_MUST_RETRY:                              \
-                       ret = BTREE_RETRY_READ;                         \
-                       goto fsck_err;                                  \
-               case BTREE_ERR_FATAL:                                   \
-                       ret = BCH_FSCK_ERRORS_NOT_FIXED;                \
-                       goto fsck_err;                                  \
-               }                                                       \
-               break;                                                  \
-       case WRITE:                                                     \
-               bch_err(c, "corrupt metadata before write: %s", out.buf);\
-                                                                       \
-               if (bch2_fs_inconsistent(c)) {                          \
-                       ret = BCH_FSCK_ERRORS_NOT_FIXED;                \
-                       goto fsck_err;                                  \
-               }                                                       \
-               break;                                                  \
-       }                                                               \
-out:                                                                   \
-       printbuf_exit(&out);                                            \
-       true;                                                           \
+       *saw_error = true;                                              \
 })
 
 #define btree_err_on(cond, ...)        ((cond) ? btree_err(__VA_ARGS__) : false)
@@ -594,18 +616,16 @@ out:                                                                      \
  * When btree topology repair changes the start or end of a node, that might
  * mean we have to drop keys that are no longer inside the node:
  */
+__cold
 void bch2_btree_node_drop_keys_outside_node(struct btree *b)
 {
        struct bset_tree *t;
-       struct bkey_s_c k;
-       struct bkey unpacked;
-       struct btree_node_iter iter;
 
        for_each_bset(b, t) {
                struct bset *i = bset(b, t);
                struct bkey_packed *k;
 
-               for (k = i->start; k != vstruct_last(i); k = bkey_next(k))
+               for (k = i->start; k != vstruct_last(i); k = bkey_p_next(k))
                        if (bkey_cmp_left_packed(b, k, &b->data->min_key) >= 0)
                                break;
 
@@ -616,47 +636,56 @@ void bch2_btree_node_drop_keys_outside_node(struct btree *b)
                                          (u64 *) vstruct_end(i) - (u64 *) k);
                        i->u64s = cpu_to_le16(le16_to_cpu(i->u64s) - shift);
                        set_btree_bset_end(b, t);
-                       bch2_bset_set_no_aux_tree(b, t);
                }
 
-               for (k = i->start; k != vstruct_last(i); k = bkey_next(k))
+               for (k = i->start; k != vstruct_last(i); k = bkey_p_next(k))
                        if (bkey_cmp_left_packed(b, k, &b->data->max_key) > 0)
                                break;
 
                if (k != vstruct_last(i)) {
                        i->u64s = cpu_to_le16((u64 *) k - (u64 *) i->start);
                        set_btree_bset_end(b, t);
-                       bch2_bset_set_no_aux_tree(b, t);
                }
        }
 
+       /*
+        * Always rebuild search trees: eytzinger search tree nodes directly
+        * depend on the values of min/max key:
+        */
+       bch2_bset_set_no_aux_tree(b, b->set);
        bch2_btree_build_aux_trees(b);
 
+       struct bkey_s_c k;
+       struct bkey unpacked;
+       struct btree_node_iter iter;
        for_each_btree_node_key_unpack(b, k, &iter, &unpacked) {
-               BUG_ON(bpos_cmp(k.k->p, b->data->min_key) < 0);
-               BUG_ON(bpos_cmp(k.k->p, b->data->max_key) > 0);
+               BUG_ON(bpos_lt(k.k->p, b->data->min_key));
+               BUG_ON(bpos_gt(k.k->p, b->data->max_key));
        }
 }
 
 static int validate_bset(struct bch_fs *c, struct bch_dev *ca,
                         struct btree *b, struct bset *i,
                         unsigned offset, unsigned sectors,
-                        int write, bool have_retry)
+                        int write, bool have_retry, bool *saw_error)
 {
        unsigned version = le16_to_cpu(i->version);
-       const char *err;
        struct printbuf buf1 = PRINTBUF;
        struct printbuf buf2 = PRINTBUF;
        int ret = 0;
 
-       btree_err_on((version != BCH_BSET_VERSION_OLD &&
-                     version < bcachefs_metadata_version_min) ||
-                    version >= bcachefs_metadata_version_max,
-                    BTREE_ERR_FATAL, c, ca, b, i,
-                    "unsupported bset version");
+       btree_err_on(!bch2_version_compatible(version),
+                    -BCH_ERR_btree_node_read_err_incompatible,
+                    c, ca, b, i,
+                    btree_node_unsupported_version,
+                    "unsupported bset version %u.%u",
+                    BCH_VERSION_MAJOR(version),
+                    BCH_VERSION_MINOR(version));
 
        if (btree_err_on(version < c->sb.version_min,
-                        BTREE_ERR_FIXABLE, c, NULL, b, i,
+                        -BCH_ERR_btree_node_read_err_fixable,
+                        c, NULL, b, i,
+                        btree_node_bset_older_than_sb_min,
                         "bset version %u older than superblock version_min %u",
                         version, c->sb.version_min)) {
                mutex_lock(&c->sb_lock);
@@ -665,8 +694,11 @@ static int validate_bset(struct bch_fs *c, struct bch_dev *ca,
                mutex_unlock(&c->sb_lock);
        }
 
-       if (btree_err_on(version > c->sb.version,
-                        BTREE_ERR_FIXABLE, c, NULL, b, i,
+       if (btree_err_on(BCH_VERSION_MAJOR(version) >
+                        BCH_VERSION_MAJOR(c->sb.version),
+                        -BCH_ERR_btree_node_read_err_fixable,
+                        c, NULL, b, i,
+                        btree_node_bset_newer_than_sb,
                         "bset version %u newer than superblock version %u",
                         version, c->sb.version)) {
                mutex_lock(&c->sb_lock);
@@ -676,11 +708,15 @@ static int validate_bset(struct bch_fs *c, struct bch_dev *ca,
        }
 
        btree_err_on(BSET_SEPARATE_WHITEOUTS(i),
-                    BTREE_ERR_FATAL, c, ca, b, i,
+                    -BCH_ERR_btree_node_read_err_incompatible,
+                    c, ca, b, i,
+                    btree_node_unsupported_version,
                     "BSET_SEPARATE_WHITEOUTS no longer supported");
 
        if (btree_err_on(offset + sectors > btree_sectors(c),
-                        BTREE_ERR_FIXABLE, c, ca, b, i,
+                        -BCH_ERR_btree_node_read_err_fixable,
+                        c, ca, b, i,
+                        bset_past_end_of_btree_node,
                         "bset past end of btree node")) {
                i->u64s = 0;
                ret = 0;
@@ -688,12 +724,15 @@ static int validate_bset(struct bch_fs *c, struct bch_dev *ca,
        }
 
        btree_err_on(offset && !i->u64s,
-                    BTREE_ERR_FIXABLE, c, ca, b, i,
+                    -BCH_ERR_btree_node_read_err_fixable,
+                    c, ca, b, i,
+                    bset_empty,
                     "empty bset");
 
-       btree_err_on(BSET_OFFSET(i) &&
-                    BSET_OFFSET(i) != offset,
-                    BTREE_ERR_WANT_RETRY, c, ca, b, i,
+       btree_err_on(BSET_OFFSET(i) && BSET_OFFSET(i) != offset,
+                    -BCH_ERR_btree_node_read_err_want_retry,
+                    c, ca, b, i,
+                    bset_wrong_sector_offset,
                     "bset at wrong sector offset");
 
        if (!offset) {
@@ -707,16 +746,22 @@ static int validate_bset(struct bch_fs *c, struct bch_dev *ca,
 
                        /* XXX endianness */
                        btree_err_on(bp->seq != bn->keys.seq,
-                                    BTREE_ERR_MUST_RETRY, c, ca, b, NULL,
+                                    -BCH_ERR_btree_node_read_err_must_retry,
+                                    c, ca, b, NULL,
+                                    bset_bad_seq,
                                     "incorrect sequence number (wrong btree node)");
                }
 
                btree_err_on(BTREE_NODE_ID(bn) != b->c.btree_id,
-                            BTREE_ERR_MUST_RETRY, c, ca, b, i,
+                            -BCH_ERR_btree_node_read_err_must_retry,
+                            c, ca, b, i,
+                            btree_node_bad_btree,
                             "incorrect btree id");
 
                btree_err_on(BTREE_NODE_LEVEL(bn) != b->c.level,
-                            BTREE_ERR_MUST_RETRY, c, ca, b, i,
+                            -BCH_ERR_btree_node_read_err_must_retry,
+                            c, ca, b, i,
+                            btree_node_bad_level,
                             "incorrect level");
 
                if (!write)
@@ -732,8 +777,10 @@ static int validate_bset(struct bch_fs *c, struct bch_dev *ca,
                                b->data->max_key = b->key.k.p;
                        }
 
-                       btree_err_on(bpos_cmp(b->data->min_key, bp->min_key),
-                                    BTREE_ERR_MUST_RETRY, c, ca, b, NULL,
+                       btree_err_on(!bpos_eq(b->data->min_key, bp->min_key),
+                                    -BCH_ERR_btree_node_read_err_must_retry,
+                                    c, ca, b, NULL,
+                                    btree_node_bad_min_key,
                                     "incorrect min_key: got %s should be %s",
                                     (printbuf_reset(&buf1),
                                      bch2_bpos_to_text(&buf1, bn->min_key), buf1.buf),
@@ -741,8 +788,10 @@ static int validate_bset(struct bch_fs *c, struct bch_dev *ca,
                                      bch2_bpos_to_text(&buf2, bp->min_key), buf2.buf));
                }
 
-               btree_err_on(bpos_cmp(bn->max_key, b->key.k.p),
-                            BTREE_ERR_MUST_RETRY, c, ca, b, i,
+               btree_err_on(!bpos_eq(bn->max_key, b->key.k.p),
+                            -BCH_ERR_btree_node_read_err_must_retry,
+                            c, ca, b, i,
+                            btree_node_bad_max_key,
                             "incorrect max key %s",
                             (printbuf_reset(&buf1),
                              bch2_bpos_to_text(&buf1, bn->max_key), buf1.buf));
@@ -751,10 +800,14 @@ static int validate_bset(struct bch_fs *c, struct bch_dev *ca,
                        compat_btree_node(b->c.level, b->c.btree_id, version,
                                          BSET_BIG_ENDIAN(i), write, bn);
 
-               err = bch2_bkey_format_validate(&bn->format);
-               btree_err_on(err,
-                            BTREE_ERR_FATAL, c, ca, b, i,
-                            "invalid bkey format: %s", err);
+               btree_err_on(bch2_bkey_format_invalid(c, &bn->format, write, &buf1),
+                            -BCH_ERR_btree_node_read_err_bad_node,
+                            c, ca, b, i,
+                            btree_node_bad_format,
+                            "invalid bkey format: %s\n  %s", buf1.buf,
+                            (printbuf_reset(&buf2),
+                             bch2_bkey_format_to_text(&buf2, &bn->format), buf2.buf));
+               printbuf_reset(&buf1);
 
                compat_bformat(b->c.level, b->c.btree_id, version,
                               BSET_BIG_ENDIAN(i), write,
@@ -773,13 +826,30 @@ static int bset_key_invalid(struct bch_fs *c, struct btree *b,
                            struct printbuf *err)
 {
        return __bch2_bkey_invalid(c, k, btree_node_type(b), READ, err) ?:
-               (!updated_range ? bch2_bkey_in_btree_node(b, k, err) : 0) ?:
+               (!updated_range ? bch2_bkey_in_btree_node(c, b, k, err) : 0) ?:
                (rw == WRITE ? bch2_bkey_val_invalid(c, k, READ, err) : 0);
 }
 
+static bool __bkey_valid(struct bch_fs *c, struct btree *b,
+                        struct bset *i, struct bkey_packed *k)
+{
+       if (bkey_p_next(k) > vstruct_last(i))
+               return false;
+
+       if (k->format > KEY_FORMAT_CURRENT)
+               return false;
+
+       struct printbuf buf = PRINTBUF;
+       struct bkey tmp;
+       struct bkey_s u = __bkey_disassemble(b, k, &tmp);
+       bool ret = __bch2_bkey_invalid(c, u.s_c, btree_node_type(b), READ, &buf);
+       printbuf_exit(&buf);
+       return ret;
+}
+
 static int validate_bset_keys(struct bch_fs *c, struct btree *b,
-                        struct bset *i, unsigned *whiteout_u64s,
-                        int write, bool have_retry)
+                        struct bset *i, int write,
+                        bool have_retry, bool *saw_error)
 {
        unsigned version = le16_to_cpu(i->version);
        struct bkey_packed *k, *prev = NULL;
@@ -792,22 +862,23 @@ static int validate_bset_keys(struct bch_fs *c, struct btree *b,
             k != vstruct_last(i);) {
                struct bkey_s u;
                struct bkey tmp;
+               unsigned next_good_key;
 
-               if (btree_err_on(bkey_next(k) > vstruct_last(i),
-                                BTREE_ERR_FIXABLE, c, NULL, b, i,
+               if (btree_err_on(bkey_p_next(k) > vstruct_last(i),
+                                -BCH_ERR_btree_node_read_err_fixable,
+                                c, NULL, b, i,
+                                btree_node_bkey_past_bset_end,
                                 "key extends past end of bset")) {
                        i->u64s = cpu_to_le16((u64 *) k - i->_data);
                        break;
                }
 
                if (btree_err_on(k->format > KEY_FORMAT_CURRENT,
-                                BTREE_ERR_FIXABLE, c, NULL, b, i,
-                                "invalid bkey format %u", k->format)) {
-                       i->u64s = cpu_to_le16(le16_to_cpu(i->u64s) - k->u64s);
-                       memmove_u64s_down(k, bkey_next(k),
-                                         (u64 *) vstruct_end(i) - (u64 *) k);
-                       continue;
-               }
+                                -BCH_ERR_btree_node_read_err_fixable,
+                                c, NULL, b, i,
+                                btree_node_bkey_bad_format,
+                                "invalid bkey format %u", k->format))
+                       goto drop_this_key;
 
                /* XXX: validate k->u64s */
                if (!write)
@@ -820,17 +891,15 @@ static int validate_bset_keys(struct bch_fs *c, struct btree *b,
                printbuf_reset(&buf);
                if (bset_key_invalid(c, b, u.s_c, updated_range, write, &buf)) {
                        printbuf_reset(&buf);
-                       pr_buf(&buf, "invalid bkey:  ");
                        bset_key_invalid(c, b, u.s_c, updated_range, write, &buf);
-                       pr_buf(&buf, "\n  ");
+                       prt_printf(&buf, "\n  ");
                        bch2_bkey_val_to_text(&buf, c, u.s_c);
 
-                       btree_err(BTREE_ERR_FIXABLE, c, NULL, b, i, "%s", buf.buf);
-
-                       i->u64s = cpu_to_le16(le16_to_cpu(i->u64s) - k->u64s);
-                       memmove_u64s_down(k, bkey_next(k),
-                                         (u64 *) vstruct_end(i) - (u64 *) k);
-                       continue;
+                       btree_err(-BCH_ERR_btree_node_read_err_fixable,
+                                 c, NULL, b, i,
+                                 btree_node_bad_bkey,
+                                 "invalid bkey: %s", buf.buf);
+                       goto drop_this_key;
                }
 
                if (write)
@@ -842,23 +911,50 @@ static int validate_bset_keys(struct bch_fs *c, struct btree *b,
                        struct bkey up = bkey_unpack_key(b, prev);
 
                        printbuf_reset(&buf);
-                       pr_buf(&buf, "keys out of order: ");
+                       prt_printf(&buf, "keys out of order: ");
                        bch2_bkey_to_text(&buf, &up);
-                       pr_buf(&buf, " > ");
+                       prt_printf(&buf, " > ");
                        bch2_bkey_to_text(&buf, u.k);
 
-                       bch2_dump_bset(c, b, i, 0);
-
-                       if (btree_err(BTREE_ERR_FIXABLE, c, NULL, b, i, "%s", buf.buf)) {
-                               i->u64s = cpu_to_le16(le16_to_cpu(i->u64s) - k->u64s);
-                               memmove_u64s_down(k, bkey_next(k),
-                                                 (u64 *) vstruct_end(i) - (u64 *) k);
-                               continue;
-                       }
+                       if (btree_err(-BCH_ERR_btree_node_read_err_fixable,
+                                     c, NULL, b, i,
+                                     btree_node_bkey_out_of_order,
+                                     "%s", buf.buf))
+                               goto drop_this_key;
                }
 
                prev = k;
-               k = bkey_next(k);
+               k = bkey_p_next(k);
+               continue;
+drop_this_key:
+               next_good_key = k->u64s;
+
+               if (!next_good_key ||
+                   (BSET_BIG_ENDIAN(i) == CPU_BIG_ENDIAN &&
+                    version >= bcachefs_metadata_version_snapshot)) {
+                       /*
+                        * only do scanning if bch2_bkey_compat() has nothing to
+                        * do
+                        */
+
+                       if (!__bkey_valid(c, b, i, (void *) ((u64 *) k + next_good_key))) {
+                               for (next_good_key = 1;
+                                    next_good_key < (u64 *) vstruct_last(i) - (u64 *) k;
+                                    next_good_key++)
+                                       if (__bkey_valid(c, b, i, (void *) ((u64 *) k + next_good_key)))
+                                               goto got_good_key;
+
+                       }
+
+                       /*
+                        * didn't find a good key, have to truncate the rest of
+                        * the bset
+                        */
+                       next_good_key = (u64 *) vstruct_last(i) - (u64 *) k;
+               }
+got_good_key:
+               le16_add_cpu(&i->u64s, -next_good_key);
+               memmove_u64s_down(k, bkey_p_next(k), (u64 *) vstruct_end(i) - (u64 *) k);
        }
 fsck_err:
        printbuf_exit(&buf);
@@ -866,72 +962,98 @@ fsck_err:
 }
 
 int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
-                             struct btree *b, bool have_retry)
+                             struct btree *b, bool have_retry, bool *saw_error)
 {
        struct btree_node_entry *bne;
        struct sort_iter *iter;
        struct btree_node *sorted;
        struct bkey_packed *k;
-       struct bch_extent_ptr *ptr;
        struct bset *i;
        bool used_mempool, blacklisted;
        bool updated_range = b->key.k.type == KEY_TYPE_btree_ptr_v2 &&
                BTREE_PTR_RANGE_UPDATED(&bkey_i_to_btree_ptr_v2(&b->key)->v);
        unsigned u64s;
-       unsigned blacklisted_written, nonblacklisted_written = 0;
        unsigned ptr_written = btree_ptr_sectors_written(&b->key);
        struct printbuf buf = PRINTBUF;
-       int ret, retry_read = 0, write = READ;
+       int ret = 0, retry_read = 0, write = READ;
+       u64 start_time = local_clock();
 
        b->version_ondisk = U16_MAX;
+       /* We might get called multiple times on read retry: */
+       b->written = 0;
 
-       iter = mempool_alloc(&c->fill_iter, GFP_NOIO);
-       sort_iter_init(iter, b);
-       iter->size = (btree_blocks(c) + 1) * 2;
+       iter = mempool_alloc(&c->fill_iter, GFP_NOFS);
+       sort_iter_init(iter, b, (btree_blocks(c) + 1) * 2);
 
        if (bch2_meta_read_fault("btree"))
-               btree_err(BTREE_ERR_MUST_RETRY, c, ca, b, NULL,
+               btree_err(-BCH_ERR_btree_node_read_err_must_retry,
+                         c, ca, b, NULL,
+                         btree_node_fault_injected,
                          "dynamic fault");
 
        btree_err_on(le64_to_cpu(b->data->magic) != bset_magic(c),
-                    BTREE_ERR_MUST_RETRY, c, ca, b, NULL,
+                    -BCH_ERR_btree_node_read_err_must_retry,
+                    c, ca, b, NULL,
+                    btree_node_bad_magic,
                     "bad magic: want %llx, got %llx",
                     bset_magic(c), le64_to_cpu(b->data->magic));
 
-       btree_err_on(!b->data->keys.seq,
-                    BTREE_ERR_MUST_RETRY, c, ca, b, NULL,
-                    "bad btree header: seq 0");
-
        if (b->key.k.type == KEY_TYPE_btree_ptr_v2) {
                struct bch_btree_ptr_v2 *bp =
                        &bkey_i_to_btree_ptr_v2(&b->key)->v;
 
+               bch2_bpos_to_text(&buf, b->data->min_key);
+               prt_str(&buf, "-");
+               bch2_bpos_to_text(&buf, b->data->max_key);
+
                btree_err_on(b->data->keys.seq != bp->seq,
-                            BTREE_ERR_MUST_RETRY, c, ca, b, NULL,
-                            "got wrong btree node (seq %llx want %llx)",
-                            b->data->keys.seq, bp->seq);
+                            -BCH_ERR_btree_node_read_err_must_retry,
+                            c, ca, b, NULL,
+                            btree_node_bad_seq,
+                            "got wrong btree node (want %llx got %llx)\n"
+                            "got btree %s level %llu pos %s",
+                            bp->seq, b->data->keys.seq,
+                            bch2_btree_id_str(BTREE_NODE_ID(b->data)),
+                            BTREE_NODE_LEVEL(b->data),
+                            buf.buf);
+       } else {
+               btree_err_on(!b->data->keys.seq,
+                            -BCH_ERR_btree_node_read_err_must_retry,
+                            c, ca, b, NULL,
+                            btree_node_bad_seq,
+                            "bad btree header: seq 0");
        }
 
        while (b->written < (ptr_written ?: btree_sectors(c))) {
-               unsigned sectors, whiteout_u64s = 0;
+               unsigned sectors;
                struct nonce nonce;
-               struct bch_csum csum;
                bool first = !b->written;
+               bool csum_bad;
 
                if (!b->written) {
                        i = &b->data->keys;
 
                        btree_err_on(!bch2_checksum_type_valid(c, BSET_CSUM_TYPE(i)),
-                                    BTREE_ERR_WANT_RETRY, c, ca, b, i,
-                                    "unknown checksum type %llu",
-                                    BSET_CSUM_TYPE(i));
+                                    -BCH_ERR_btree_node_read_err_want_retry,
+                                    c, ca, b, i,
+                                    bset_unknown_csum,
+                                    "unknown checksum type %llu", BSET_CSUM_TYPE(i));
 
                        nonce = btree_nonce(i, b->written << 9);
-                       csum = csum_vstruct(c, BSET_CSUM_TYPE(i), nonce, b->data);
 
-                       btree_err_on(bch2_crc_cmp(csum, b->data->csum),
-                                    BTREE_ERR_WANT_RETRY, c, ca, b, i,
-                                    "invalid checksum");
+                       struct bch_csum csum = csum_vstruct(c, BSET_CSUM_TYPE(i), nonce, b->data);
+                       csum_bad = bch2_crc_cmp(b->data->csum, csum);
+                       if (csum_bad)
+                               bch2_io_error(ca, BCH_MEMBER_ERROR_checksum);
+
+                       btree_err_on(csum_bad,
+                                    -BCH_ERR_btree_node_read_err_want_retry,
+                                    c, ca, b, i,
+                                    bset_bad_csum,
+                                    "%s",
+                                    (printbuf_reset(&buf),
+                                     bch2_csum_err_msg(&buf, BSET_CSUM_TYPE(i), b->data->csum, csum),
+                                     buf.buf));
 
                        ret = bset_encrypt(c, i, b->written << 9);
                        if (bch2_fs_fatal_err_on(ret, c,
@@ -940,7 +1062,9 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
 
                        btree_err_on(btree_node_type_is_extents(btree_node_type(b)) &&
                                     !BTREE_NODE_NEW_EXTENT_OVERWRITE(b->data),
-                                    BTREE_ERR_FATAL, c, NULL, b, NULL,
+                                    -BCH_ERR_btree_node_read_err_incompatible,
+                                    c, NULL, b, NULL,
+                                    btree_node_unsupported_version,
                                     "btree node does not have NEW_EXTENT_OVERWRITE set");
 
                        sectors = vstruct_sectors(b->data, c->block_bits);
@@ -952,16 +1076,25 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
                                break;
 
                        btree_err_on(!bch2_checksum_type_valid(c, BSET_CSUM_TYPE(i)),
-                                    BTREE_ERR_WANT_RETRY, c, ca, b, i,
-                                    "unknown checksum type %llu",
-                                    BSET_CSUM_TYPE(i));
+                                    -BCH_ERR_btree_node_read_err_want_retry,
+                                    c, ca, b, i,
+                                    bset_unknown_csum,
+                                    "unknown checksum type %llu", BSET_CSUM_TYPE(i));
 
                        nonce = btree_nonce(i, b->written << 9);
-                       csum = csum_vstruct(c, BSET_CSUM_TYPE(i), nonce, bne);
-
-                       btree_err_on(bch2_crc_cmp(csum, bne->csum),
-                                    BTREE_ERR_WANT_RETRY, c, ca, b, i,
-                                    "invalid checksum");
+                       struct bch_csum csum = csum_vstruct(c, BSET_CSUM_TYPE(i), nonce, bne);
+                       csum_bad = bch2_crc_cmp(bne->csum, csum);
+                       if (csum_bad)
+                               bch2_io_error(ca, BCH_MEMBER_ERROR_checksum);
+
+                       btree_err_on(csum_bad,
+                                    -BCH_ERR_btree_node_read_err_want_retry,
+                                    c, ca, b, i,
+                                    bset_bad_csum,
+                                    "%s",
+                                    (printbuf_reset(&buf),
+                                     bch2_csum_err_msg(&buf, BSET_CSUM_TYPE(i), bne->csum, csum),
+                                     buf.buf));
 
                        ret = bset_encrypt(c, i, b->written << 9);
                        if (bch2_fs_fatal_err_on(ret, c,
@@ -975,15 +1108,14 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
                                        le16_to_cpu(i->version));
 
                ret = validate_bset(c, ca, b, i, b->written, sectors,
-                                   READ, have_retry);
+                                   READ, have_retry, saw_error);
                if (ret)
                        goto fsck_err;
 
                if (!b->written)
                        btree_node_set_format(b, b->data->format);
 
-               ret = validate_bset_keys(c, b, i, &whiteout_u64s,
-                                   READ, have_retry);
+               ret = validate_bset_keys(c, b, i, READ, have_retry, saw_error);
                if (ret)
                        goto fsck_err;
 
@@ -994,12 +1126,16 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
                                        true);
 
                btree_err_on(blacklisted && first,
-                            BTREE_ERR_FIXABLE, c, ca, b, i,
+                            -BCH_ERR_btree_node_read_err_fixable,
+                            c, ca, b, i,
+                            bset_blacklisted_journal_seq,
                             "first btree node bset has blacklisted journal seq (%llu)",
                             le64_to_cpu(i->journal_seq));
 
                btree_err_on(blacklisted && ptr_written,
-                            BTREE_ERR_FIXABLE, c, ca, b, i,
+                            -BCH_ERR_btree_node_read_err_fixable,
+                            c, ca, b, i,
+                            first_bset_blacklisted_journal_seq,
                             "found blacklisted bset (journal seq %llu) in btree node at offset %u-%u/%u",
                             le64_to_cpu(i->journal_seq),
                             b->written, b->written + sectors, ptr_written);
@@ -1009,46 +1145,33 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
                if (blacklisted && !first)
                        continue;
 
-               sort_iter_add(iter, i->start,
-                             vstruct_idx(i, whiteout_u64s));
-
                sort_iter_add(iter,
-                             vstruct_idx(i, whiteout_u64s),
+                             vstruct_idx(i, 0),
                              vstruct_last(i));
-
-               nonblacklisted_written = b->written;
        }
 
        if (ptr_written) {
                btree_err_on(b->written < ptr_written,
-                            BTREE_ERR_WANT_RETRY, c, ca, b, NULL,
+                            -BCH_ERR_btree_node_read_err_want_retry,
+                            c, ca, b, NULL,
+                            btree_node_data_missing,
                             "btree node data missing: expected %u sectors, found %u",
                             ptr_written, b->written);
        } else {
                for (bne = write_block(b);
-                    bset_byte_offset(b, bne) < btree_bytes(c);
+                    bset_byte_offset(b, bne) < btree_buf_bytes(b);
                     bne = (void *) bne + block_bytes(c))
                        btree_err_on(bne->keys.seq == b->data->keys.seq &&
                                     !bch2_journal_seq_is_blacklisted(c,
                                                                      le64_to_cpu(bne->keys.journal_seq),
                                                                      true),
-                                    BTREE_ERR_WANT_RETRY, c, ca, b, NULL,
+                                    -BCH_ERR_btree_node_read_err_want_retry,
+                                    c, ca, b, NULL,
+                                    btree_node_bset_after_end,
                                     "found bset signature after last bset");
-
-               /*
-                * Blacklisted bsets are those that were written after the most recent
-                * (flush) journal write. Since there wasn't a flush, they may not have
-                * made it to all devices - which means we shouldn't write new bsets
-                * after them, as that could leave a gap and then reads from that device
-                * wouldn't find all the bsets in that btree node - which means it's
-                * important that we start writing new bsets after the most recent _non_
-                * blacklisted bset:
-                */
-               blacklisted_written = b->written;
-               b->written = nonblacklisted_written;
        }
 
-       sorted = btree_bounce_alloc(c, btree_bytes(c), &used_mempool);
+       sorted = btree_bounce_alloc(c, btree_buf_bytes(b), &used_mempool);
        sorted->keys.u64s = 0;
 
        set_btree_bset(b, b->set, &b->data->keys);
@@ -1064,7 +1187,7 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
 
        BUG_ON(b->nr.live_u64s != u64s);
 
-       btree_bounce_free(c, btree_bytes(c), used_mempool, sorted);
+       btree_bounce_free(c, btree_buf_bytes(b), used_mempool, sorted);
 
        if (updated_range)
                bch2_btree_node_drop_keys_outside_node(b);
@@ -1081,17 +1204,20 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
                     !bversion_cmp(u.k->version, MAX_VERSION))) {
                        printbuf_reset(&buf);
 
-                       pr_buf(&buf, "invalid bkey: ");
+                       prt_printf(&buf, "invalid bkey: ");
                        bch2_bkey_val_invalid(c, u.s_c, READ, &buf);
-                       pr_buf(&buf, "\n  ");
+                       prt_printf(&buf, "\n  ");
                        bch2_bkey_val_to_text(&buf, c, u.s_c);
 
-                       btree_err(BTREE_ERR_FIXABLE, c, NULL, b, i, "%s", buf.buf);
+                       btree_err(-BCH_ERR_btree_node_read_err_fixable,
+                                 c, NULL, b, i,
+                                 btree_node_bad_bkey,
+                                 "%s", buf.buf);
 
                        btree_keys_account_key_drop(&b->nr, 0, k);
 
                        i->u64s = cpu_to_le16(le16_to_cpu(i->u64s) - k->u64s);
-                       memmove_u64s_down(k, bkey_next(k),
+                       memmove_u64s_down(k, bkey_p_next(k),
                                          (u64 *) vstruct_end(i) - (u64 *) k);
                        set_btree_bset_end(b, b->set);
                        continue;
@@ -1103,7 +1229,7 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
                        bp.v->mem_ptr = 0;
                }
 
-               k = bkey_next(k);
+               k = bkey_p_next(k);
        }
 
        bch2_bset_build_aux_tree(b, b->set, false);
@@ -1113,9 +1239,9 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
        btree_node_reset_sib_u64s(b);
 
        bkey_for_each_ptr(bch2_bkey_ptrs(bkey_i_to_s(&b->key)), ptr) {
-               struct bch_dev *ca = bch_dev_bkey_exists(c, ptr->dev);
+               struct bch_dev *ca2 = bch_dev_bkey_exists(c, ptr->dev);
 
-               if (ca->mi.state != BCH_MEMBER_STATE_rw)
+               if (ca2->mi.state != BCH_MEMBER_STATE_rw)
                        set_btree_node_need_rewrite(b);
        }
 
@@ -1124,14 +1250,14 @@ int bch2_btree_node_read_done(struct bch_fs *c, struct bch_dev *ca,
 out:
        mempool_free(iter, &c->fill_iter);
        printbuf_exit(&buf);
+       time_stats_update(&c->times[BCH_TIME_btree_node_read_done], start_time);
        return retry_read;
 fsck_err:
-       if (ret == BTREE_RETRY_READ) {
+       if (ret == -BCH_ERR_btree_node_read_err_want_retry ||
+           ret == -BCH_ERR_btree_node_read_err_must_retry)
                retry_read = 1;
-       } else {
-               bch2_inconsistent_error(c);
+       else
                set_btree_node_read_error(b);
-       }
        goto out;
 }
 
@@ -1155,10 +1281,9 @@ static void btree_node_read_work(struct work_struct *work)
                bch_info(c, "retrying read");
                ca = bch_dev_bkey_exists(c, rb->pick.ptr.dev);
                rb->have_ioref          = bch2_dev_get_ioref(ca, READ);
-               bio_reset(bio);
-               bio->bi_opf             = REQ_OP_READ|REQ_SYNC|REQ_META;
+               bio_reset(bio, NULL, REQ_OP_READ|REQ_SYNC|REQ_META);
                bio->bi_iter.bi_sector  = rb->pick.ptr.offset;
-               bio->bi_iter.bi_size    = btree_bytes(c);
+               bio->bi_iter.bi_size    = btree_buf_bytes(b);
 
                if (rb->have_ioref) {
                        bio_set_dev(bio, ca->disk_sb.bdev);
@@ -1168,8 +1293,9 @@ static void btree_node_read_work(struct work_struct *work)
                }
 start:
                printbuf_reset(&buf);
-               btree_pos_to_text(&buf, c, b);
-               bch2_dev_io_err_on(bio->bi_status, ca, "btree read error %s for %s",
+               bch2_btree_pos_to_text(&buf, c, b);
+               bch2_dev_io_err_on(bio->bi_status, ca, BCH_MEMBER_ERROR_read,
+                                  "btree read error %s for %s",
                                   bch2_blk_status_to_str(bio->bi_status), buf.buf);
                if (rb->have_ioref)
                        percpu_ref_put(&ca->io_ref);
@@ -1182,7 +1308,7 @@ start:
                                &failed, &rb->pick) > 0;
 
                if (!bio->bi_status &&
-                   !bch2_btree_node_read_done(c, ca, b, can_retry)) {
+                   !bch2_btree_node_read_done(c, ca, b, can_retry, &saw_error)) {
                        if (retry)
                                bch_info(c, "retry success");
                        break;
@@ -1196,14 +1322,20 @@ start:
                }
        }
 
-       bch2_time_stats_update(&c->times[BCH_TIME_btree_node_read],
+       time_stats_update(&c->times[BCH_TIME_btree_node_read],
                               rb->start_time);
        bio_put(&rb->bio);
-       printbuf_exit(&buf);
 
-       if (saw_error && !btree_node_read_error(b))
+       if (saw_error && !btree_node_read_error(b)) {
+               printbuf_reset(&buf);
+               bch2_bpos_to_text(&buf, b->key.k.p);
+               bch_info(c, "%s: rewriting btree node at btree=%s level=%u %s due to error",
+                        __func__, bch2_btree_id_str(b->c.btree_id), b->c.level, buf.buf);
+
                bch2_btree_node_rewrite_async(c, b);
+       }
 
+       printbuf_exit(&buf);
        clear_btree_node_read_in_flight(b);
        wake_up_bit(&b->flags, BTREE_NODE_read_in_flight);
 }
@@ -1216,6 +1348,7 @@ static void btree_node_read_endio(struct bio *bio)
 
        if (rb->have_ioref) {
                struct bch_dev *ca = bch_dev_bkey_exists(c, rb->pick.ptr.dev);
+
                bch2_latency_acct(ca, rb->start_time, READ);
        }
 
@@ -1229,7 +1362,7 @@ struct btree_node_read_all {
        unsigned                nr;
        void                    *buf[BCH_REPLICAS_MAX];
        struct bio              *bio[BCH_REPLICAS_MAX];
-       int                     err[BCH_REPLICAS_MAX];
+       blk_status_t            err[BCH_REPLICAS_MAX];
 };
 
 static unsigned btree_node_sectors_written(struct bch_fs *c, void *data)
@@ -1274,10 +1407,9 @@ static bool btree_node_has_extra_bsets(struct bch_fs *c, unsigned offset, void *
        return offset;
 }
 
-static void btree_node_read_all_replicas_done(struct closure *cl)
+static CLOSURE_CALLBACK(btree_node_read_all_replicas_done)
 {
-       struct btree_node_read_all *ra =
-               container_of(cl, struct btree_node_read_all, cl);
+       closure_type(ra, struct btree_node_read_all, cl);
        struct bch_fs *c = ra->c;
        struct btree *b = ra->b;
        struct printbuf buf = PRINTBUF;
@@ -1287,6 +1419,7 @@ static void btree_node_read_all_replicas_done(struct closure *cl)
        unsigned i, written = 0, written2 = 0;
        __le64 seq = b->key.k.type == KEY_TYPE_btree_ptr_v2
                ? bkey_i_to_btree_ptr_v2(&b->key)->v.seq : 0;
+       bool _saw_error = false, *saw_error = &_saw_error;
 
        for (i = 0; i < ra->nr; i++) {
                struct btree_node *bn = ra->buf[i];
@@ -1305,14 +1438,20 @@ static void btree_node_read_all_replicas_done(struct closure *cl)
                }
 
                written2 = btree_node_sectors_written(c, ra->buf[i]);
-               if (btree_err_on(written2 != written, BTREE_ERR_FIXABLE, c, NULL, b, NULL,
+               if (btree_err_on(written2 != written, -BCH_ERR_btree_node_read_err_fixable,
+                                c, NULL, b, NULL,
+                                btree_node_replicas_sectors_written_mismatch,
                                 "btree node sectors written mismatch: %u != %u",
                                 written, written2) ||
                    btree_err_on(btree_node_has_extra_bsets(c, written2, ra->buf[i]),
-                                BTREE_ERR_FIXABLE, c, NULL, b, NULL,
+                                -BCH_ERR_btree_node_read_err_fixable,
+                                c, NULL, b, NULL,
+                                btree_node_bset_after_end,
                                 "found bset signature after last bset") ||
                    btree_err_on(memcmp(ra->buf[best], ra->buf[i], written << 9),
-                                BTREE_ERR_FIXABLE, c, NULL, b, NULL,
+                                -BCH_ERR_btree_node_read_err_fixable,
+                                c, NULL, b, NULL,
+                                btree_node_replicas_data_mismatch,
                                 "btree node replicas content mismatch"))
                        dump_bset_maps = true;
 
@@ -1344,10 +1483,10 @@ fsck_err:
                                        sectors = vstruct_sectors(bne, c->block_bits);
                                }
 
-                               pr_buf(&buf, " %u-%u", offset, offset + sectors);
+                               prt_printf(&buf, " %u-%u", offset, offset + sectors);
                                if (bne && bch2_journal_seq_is_blacklisted(c,
                                                        le64_to_cpu(bne->keys.journal_seq), false))
-                                       pr_buf(&buf, "*");
+                                       prt_printf(&buf, "*");
                                offset += sectors;
                        }
 
@@ -1355,14 +1494,14 @@ fsck_err:
                                bne = ra->buf[i] + (offset << 9);
                                if (bne->keys.seq == bn->keys.seq) {
                                        if (!gap)
-                                               pr_buf(&buf, " GAP");
+                                               prt_printf(&buf, " GAP");
                                        gap = true;
 
                                        sectors = vstruct_sectors(bne, c->block_bits);
-                                       pr_buf(&buf, " %u-%u", offset, offset + sectors);
+                                       prt_printf(&buf, " %u-%u", offset, offset + sectors);
                                        if (bch2_journal_seq_is_blacklisted(c,
                                                        le64_to_cpu(bne->keys.journal_seq), false))
-                                               pr_buf(&buf, "*");
+                                               prt_printf(&buf, "*");
                                }
                                offset++;
                        }
@@ -1372,14 +1511,16 @@ fsck_err:
        }
 
        if (best >= 0) {
-               memcpy(b->data, ra->buf[best], btree_bytes(c));
-               ret = bch2_btree_node_read_done(c, NULL, b, false);
+               memcpy(b->data, ra->buf[best], btree_buf_bytes(b));
+               ret = bch2_btree_node_read_done(c, NULL, b, false, saw_error);
        } else {
                ret = -1;
        }
 
        if (ret)
                set_btree_node_read_error(b);
+       else if (*saw_error)
+               bch2_btree_node_rewrite_async(c, b);
 
        for (i = 0; i < ra->nr; i++) {
                mempool_free(ra->buf[i], &c->btree_bounce_pool);
@@ -1403,6 +1544,7 @@ static void btree_node_read_all_replicas_endio(struct bio *bio)
 
        if (rb->have_ioref) {
                struct bch_dev *ca = bch_dev_bkey_exists(c, rb->pick.ptr.dev);
+
                bch2_latency_acct(ca, rb->start_time, READ);
        }
 
@@ -1425,7 +1567,7 @@ static int btree_node_read_all_replicas(struct bch_fs *c, struct btree *b, bool
 
        ra = kzalloc(sizeof(*ra), GFP_NOFS);
        if (!ra)
-               return -ENOMEM;
+               return -BCH_ERR_ENOMEM_btree_node_read_all_replicas;
 
        closure_init(&ra->cl, NULL);
        ra->c   = c;
@@ -1434,8 +1576,10 @@ static int btree_node_read_all_replicas(struct bch_fs *c, struct btree *b, bool
 
        for (i = 0; i < ra->nr; i++) {
                ra->buf[i] = mempool_alloc(&c->btree_bounce_pool, GFP_NOFS);
-               ra->bio[i] = bio_alloc_bioset(GFP_NOFS, buf_pages(ra->buf[i],
-                                                                 btree_bytes(c)),
+               ra->bio[i] = bio_alloc_bioset(NULL,
+                                             buf_pages(ra->buf[i], btree_buf_bytes(b)),
+                                             REQ_OP_READ|REQ_SYNC|REQ_META,
+                                             GFP_NOFS,
                                              &c->btree_bio);
        }
 
@@ -1451,10 +1595,9 @@ static int btree_node_read_all_replicas(struct bch_fs *c, struct btree *b, bool
                rb->have_ioref          = bch2_dev_get_ioref(ca, READ);
                rb->idx                 = i;
                rb->pick                = pick;
-               rb->bio.bi_opf          = REQ_OP_READ|REQ_SYNC|REQ_META;
                rb->bio.bi_iter.bi_sector = pick.ptr.offset;
                rb->bio.bi_end_io       = btree_node_read_all_replicas_endio;
-               bch2_bio_map(&rb->bio, ra->buf[i], btree_bytes(c));
+               bch2_bio_map(&rb->bio, ra->buf[i], btree_buf_bytes(b));
 
                if (rb->have_ioref) {
                        this_cpu_add(ca->io_done->sectors[READ][BCH_DATA_btree],
@@ -1472,7 +1615,7 @@ static int btree_node_read_all_replicas(struct bch_fs *c, struct btree *b, bool
 
        if (sync) {
                closure_sync(&ra->cl);
-               btree_node_read_all_replicas_done(&ra->cl);
+               btree_node_read_all_replicas_done(&ra->cl.work);
        } else {
                continue_at(&ra->cl, btree_node_read_all_replicas_done,
                            c->io_complete_wq);
@@ -1481,36 +1624,49 @@ static int btree_node_read_all_replicas(struct bch_fs *c, struct btree *b, bool
        return 0;
 }
 
-void bch2_btree_node_read(struct bch_fs *c, struct btree *b,
+void bch2_btree_node_read(struct btree_trans *trans, struct btree *b,
                          bool sync)
 {
+       struct bch_fs *c = trans->c;
        struct extent_ptr_decoded pick;
        struct btree_read_bio *rb;
        struct bch_dev *ca;
        struct bio *bio;
-       struct printbuf buf = PRINTBUF;
        int ret;
 
-       btree_pos_to_text(&buf, c, b);
-       trace_btree_read(c, b);
+       trace_and_count(c, btree_node_read, trans, b);
 
        if (bch2_verify_all_btree_replicas &&
            !btree_node_read_all_replicas(c, b, sync))
-               goto out;
+               return;
 
        ret = bch2_bkey_pick_read_device(c, bkey_i_to_s_c(&b->key),
                                         NULL, &pick);
-       if (bch2_fs_fatal_err_on(ret <= 0, c,
-                       "btree node read error: no device to read from\n"
-                       " at %s", buf.buf)) {
+
+       if (ret <= 0) {
+               struct printbuf buf = PRINTBUF;
+
+               prt_str(&buf, "btree node read error: no device to read from\n at ");
+               bch2_btree_pos_to_text(&buf, c, b);
+               bch_err(c, "%s", buf.buf);
+
+               if (c->recovery_passes_explicit & BIT_ULL(BCH_RECOVERY_PASS_check_topology) &&
+                   c->curr_recovery_pass > BCH_RECOVERY_PASS_check_topology)
+                       bch2_fatal_error(c);
+
                set_btree_node_read_error(b);
-               goto out;
+               clear_btree_node_read_in_flight(b);
+               wake_up_bit(&b->flags, BTREE_NODE_read_in_flight);
+               printbuf_exit(&buf);
+               return;
        }
 
        ca = bch_dev_bkey_exists(c, pick.ptr.dev);
 
-       bio = bio_alloc_bioset(GFP_NOIO, buf_pages(b->data,
-                                                  btree_bytes(c)),
+       bio = bio_alloc_bioset(NULL,
+                              buf_pages(b->data, btree_buf_bytes(b)),
+                              REQ_OP_READ|REQ_SYNC|REQ_META,
+                              GFP_NOFS,
                               &c->btree_bio);
        rb = container_of(bio, struct btree_read_bio, bio);
        rb->c                   = c;
@@ -1520,10 +1676,9 @@ void bch2_btree_node_read(struct bch_fs *c, struct btree *b,
        rb->have_ioref          = bch2_dev_get_ioref(ca, READ);
        rb->pick                = pick;
        INIT_WORK(&rb->work, btree_node_read_work);
-       bio->bi_opf             = REQ_OP_READ|REQ_SYNC|REQ_META;
        bio->bi_iter.bi_sector  = pick.ptr.offset;
        bio->bi_end_io          = btree_node_read_endio;
-       bch2_bio_map(bio, b->data, btree_bytes(c));
+       bch2_bio_map(bio, b->data, btree_buf_bytes(b));
 
        if (rb->have_ioref) {
                this_cpu_add(ca->io_done->sectors[READ][BCH_DATA_btree],
@@ -1532,7 +1687,7 @@ void bch2_btree_node_read(struct bch_fs *c, struct btree *b,
 
                if (sync) {
                        submit_bio_wait(bio);
-
+                       bch2_latency_acct(ca, rb->start_time, READ);
                        btree_node_read_work(&rb->work);
                } else {
                        submit_bio(bio);
@@ -1545,13 +1700,12 @@ void bch2_btree_node_read(struct bch_fs *c, struct btree *b,
                else
                        queue_work(c->io_complete_wq, &rb->work);
        }
-out:
-       printbuf_exit(&buf);
 }
 
-int bch2_btree_root_read(struct bch_fs *c, enum btree_id id,
-                       const struct bkey_i *k, unsigned level)
+static int __bch2_btree_root_read(struct btree_trans *trans, enum btree_id id,
+                                 const struct bkey_i *k, unsigned level)
 {
+       struct bch_fs *c = trans->c;
        struct closure cl;
        struct btree *b;
        int ret;
@@ -1559,12 +1713,12 @@ int bch2_btree_root_read(struct bch_fs *c, enum btree_id id,
        closure_init_stack(&cl);
 
        do {
-               ret = bch2_btree_cache_cannibalize_lock(c, &cl);
+               ret = bch2_btree_cache_cannibalize_lock(trans, &cl);
                closure_sync(&cl);
        } while (ret);
 
-       b = bch2_btree_node_mem_alloc(c, level != 0);
-       bch2_btree_cache_cannibalize_unlock(c);
+       b = bch2_btree_node_mem_alloc(trans, level != 0);
+       bch2_btree_cache_cannibalize_unlock(trans);
 
        BUG_ON(IS_ERR(b));
 
@@ -1573,7 +1727,7 @@ int bch2_btree_root_read(struct bch_fs *c, enum btree_id id,
 
        set_btree_node_read_in_flight(b);
 
-       bch2_btree_node_read(c, b, true);
+       bch2_btree_node_read(trans, b, true);
 
        if (btree_node_read_error(b)) {
                bch2_btree_node_hash_remove(&c->btree_cache, b);
@@ -1582,7 +1736,7 @@ int bch2_btree_root_read(struct bch_fs *c, enum btree_id id,
                list_move(&b->list, &c->btree_cache.freeable);
                mutex_unlock(&c->btree_cache.lock);
 
-               ret = -EIO;
+               ret = -BCH_ERR_btree_node_read_error;
                goto err;
        }
 
@@ -1594,8 +1748,14 @@ err:
        return ret;
 }
 
-void bch2_btree_complete_write(struct bch_fs *c, struct btree *b,
-                             struct btree_write *w)
+int bch2_btree_root_read(struct bch_fs *c, enum btree_id id,
+                       const struct bkey_i *k, unsigned level)
+{
+       return bch2_trans_run(c, __bch2_btree_root_read(trans, id, k, level));
+}
+
+static void bch2_btree_complete_write(struct bch_fs *c, struct btree *b,
+                                     struct btree_write *w)
 {
        unsigned long old, new, v = READ_ONCE(b->will_make_reachable);
 
@@ -1617,6 +1777,7 @@ static void __btree_node_write_done(struct bch_fs *c, struct btree *b)
 {
        struct btree_write *w = btree_prev_write(b);
        unsigned long old, new, v;
+       unsigned type = 0;
 
        bch2_btree_complete_write(c, b, w);
 
@@ -1635,6 +1796,9 @@ static void __btree_node_write_done(struct bch_fs *c, struct btree *b)
                        new |=  (1U << BTREE_NODE_write_in_flight_inner);
                        new |=  (1U << BTREE_NODE_just_written);
                        new ^=  (1U << BTREE_NODE_write_idx);
+
+                       type = new & BTREE_WRITE_TYPE_MASK;
+                       new &= ~BTREE_WRITE_TYPE_MASK;
                } else {
                        new &= ~(1U << BTREE_NODE_write_in_flight);
                        new &= ~(1U << BTREE_NODE_write_in_flight_inner);
@@ -1642,16 +1806,20 @@ static void __btree_node_write_done(struct bch_fs *c, struct btree *b)
        } while ((v = cmpxchg(&b->flags, old, new)) != old);
 
        if (new & (1U << BTREE_NODE_write_in_flight))
-               __bch2_btree_node_write(c, b, BTREE_WRITE_ALREADY_STARTED);
+               __bch2_btree_node_write(c, b, BTREE_WRITE_ALREADY_STARTED|type);
        else
                wake_up_bit(&b->flags, BTREE_NODE_write_in_flight);
 }
 
 static void btree_node_write_done(struct bch_fs *c, struct btree *b)
 {
-       six_lock_read(&b->c.lock, NULL, NULL);
+       struct btree_trans *trans = bch2_trans_get(c);
+
+       btree_node_lock_nopath_nofail(trans, &b->c, SIX_LOCK_read);
        __btree_node_write_done(c, b);
        six_unlock_read(&b->c.lock);
+
+       bch2_trans_put(trans);
 }
 
 static void btree_node_write_work(struct work_struct *work)
@@ -1661,7 +1829,7 @@ static void btree_node_write_work(struct work_struct *work)
        struct bch_fs *c        = wbio->wbio.c;
        struct btree *b         = wbio->wbio.bio.bi_private;
        struct bch_extent_ptr *ptr;
-       int ret;
+       int ret = 0;
 
        btree_bounce_free(c,
                wbio->data_bytes,
@@ -1671,8 +1839,10 @@ static void btree_node_write_work(struct work_struct *work)
        bch2_bkey_drop_ptrs(bkey_i_to_s(&wbio->key), ptr,
                bch2_dev_list_has_dev(wbio->wbio.failed, ptr->dev));
 
-       if (!bch2_bkey_nr_ptrs(bkey_i_to_s_c(&wbio->key)))
+       if (!bch2_bkey_nr_ptrs(bkey_i_to_s_c(&wbio->key))) {
+               ret = -BCH_ERR_btree_node_write_all_failed;
                goto err;
+       }
 
        if (wbio->wbio.first_btree_write) {
                if (wbio->wbio.failed.nr) {
@@ -1680,8 +1850,12 @@ static void btree_node_write_work(struct work_struct *work)
                }
        } else {
                ret = bch2_trans_do(c, NULL, NULL, 0,
-                       bch2_btree_node_update_key_get_iter(&trans, b, &wbio->key,
-                                                           !wbio->wbio.failed.nr));
+                       bch2_btree_node_update_key_get_iter(trans, b, &wbio->key,
+                                       BCH_WATERMARK_reclaim|
+                                       BCH_TRANS_COMMIT_journal_reclaim|
+                                       BCH_TRANS_COMMIT_no_enospc|
+                                       BCH_TRANS_COMMIT_no_check_rw,
+                                       !wbio->wbio.failed.nr));
                if (ret)
                        goto err;
        }
@@ -1691,7 +1865,8 @@ out:
        return;
 err:
        set_btree_node_noevict(b);
-       bch2_fs_fatal_error(c, "fatal error writing btree node");
+       if (!bch2_err_matches(ret, EROFS))
+               bch2_fs_fatal_error(c, "fatal error writing btree node: %s", bch2_err_str(ret));
        goto out;
 }
 
@@ -1709,7 +1884,8 @@ static void btree_node_write_endio(struct bio *bio)
        if (wbio->have_ioref)
                bch2_latency_acct(ca, wbio->submit_time, WRITE);
 
-       if (bch2_dev_io_err_on(bio->bi_status, ca, "btree write error: %s",
+       if (bch2_dev_io_err_on(bio->bi_status, ca, BCH_MEMBER_ERROR_write,
+                              "btree write error: %s",
                               bch2_blk_status_to_str(bio->bi_status)) ||
            bch2_meta_write_fault("btree")) {
                spin_lock_irqsave(&c->btree_write_error_lock, flags);
@@ -1735,8 +1911,8 @@ static void btree_node_write_endio(struct bio *bio)
 static int validate_bset_for_write(struct bch_fs *c, struct btree *b,
                                   struct bset *i, unsigned sectors)
 {
-       unsigned whiteout_u64s = 0;
        struct printbuf buf = PRINTBUF;
+       bool saw_error;
        int ret;
 
        ret = bch2_bkey_invalid(c, bkey_i_to_s_c(&b->key),
@@ -1748,8 +1924,8 @@ static int validate_bset_for_write(struct bch_fs *c, struct btree *b,
        if (ret)
                return ret;
 
-       ret = validate_bset_keys(c, b, i, &whiteout_u64s, WRITE, false) ?:
-               validate_bset(c, NULL, b, i, b->written, sectors, WRITE, false);
+       ret = validate_bset_keys(c, b, i, WRITE, false, &saw_error) ?:
+               validate_bset(c, NULL, b, i, b->written, sectors, WRITE, false, &saw_error);
        if (ret) {
                bch2_inconsistent_error(c);
                dump_stack();
@@ -1761,15 +1937,15 @@ static int validate_bset_for_write(struct bch_fs *c, struct btree *b,
 static void btree_write_submit(struct work_struct *work)
 {
        struct btree_write_bio *wbio = container_of(work, struct btree_write_bio, work);
-       struct bch_extent_ptr *ptr;
-       __BKEY_PADDED(k, BKEY_BTREE_PTR_VAL_U64s_MAX) tmp;
+       BKEY_PADDED_ONSTACK(k, BKEY_BTREE_PTR_VAL_U64s_MAX) tmp;
 
        bkey_copy(&tmp.k, &wbio->key);
 
        bkey_for_each_ptr(bch2_bkey_ptrs(bkey_i_to_s(&tmp.k)), ptr)
                ptr->offset += wbio->sector_offset;
 
-       bch2_submit_wbio_replicas(&wbio->wbio, wbio->wbio.c, BCH_DATA_btree, &tmp.k);
+       bch2_submit_wbio_replicas(&wbio->wbio, wbio->wbio.c, BCH_DATA_btree,
+                                 &tmp.k, false);
 }
 
 void __bch2_btree_node_write(struct bch_fs *c, struct btree *b, unsigned flags)
@@ -1779,13 +1955,14 @@ void __bch2_btree_node_write(struct bch_fs *c, struct btree *b, unsigned flags)
        struct bset *i;
        struct btree_node *bn = NULL;
        struct btree_node_entry *bne = NULL;
-       struct sort_iter sort_iter;
+       struct sort_iter_stack sort_iter;
        struct nonce nonce;
        unsigned bytes_to_write, sectors_to_write, bytes, u64s;
        u64 seq = 0;
        bool used_mempool;
        unsigned long old, new;
        bool validate_before_checksum = false;
+       enum btree_write_type type = flags & BTREE_WRITE_TYPE_MASK;
        void *data;
        int ret;
 
@@ -1821,6 +1998,10 @@ void __bch2_btree_node_write(struct bch_fs *c, struct btree *b, unsigned flags)
                if (old & (1 << BTREE_NODE_write_in_flight))
                        return;
 
+               if (flags & BTREE_WRITE_ONLY_IF_NEED)
+                       type = new & BTREE_WRITE_TYPE_MASK;
+               new &= ~BTREE_WRITE_TYPE_MASK;
+
                new &= ~(1 << BTREE_NODE_dirty);
                new &= ~(1 << BTREE_NODE_need_write);
                new |=  (1 << BTREE_NODE_write_in_flight);
@@ -1832,6 +2013,8 @@ void __bch2_btree_node_write(struct bch_fs *c, struct btree *b, unsigned flags)
        if (new & (1U << BTREE_NODE_need_write))
                return;
 do_write:
+       BUG_ON((type == BTREE_WRITE_initial) != (b->written == 0));
+
        atomic_dec(&c->btree_cache.dirty);
 
        BUG_ON(btree_node_fake(b));
@@ -1845,7 +2028,7 @@ do_write:
 
        bch2_sort_whiteouts(c, b);
 
-       sort_iter_init(&sort_iter, b);
+       sort_iter_stack_init(&sort_iter, b);
 
        bytes = !b->written
                ? sizeof(struct btree_node)
@@ -1860,7 +2043,7 @@ do_write:
                        continue;
 
                bytes += le16_to_cpu(i->u64s) * sizeof(u64);
-               sort_iter_add(&sort_iter,
+               sort_iter_add(&sort_iter.iter,
                              btree_bkey_first(b, t),
                              btree_bkey_last(b, t));
                seq = max(seq, le64_to_cpu(i->journal_seq));
@@ -1889,16 +2072,18 @@ do_write:
        i->journal_seq  = cpu_to_le64(seq);
        i->u64s         = 0;
 
-       sort_iter_add(&sort_iter,
-                     unwritten_whiteouts_start(c, b),
-                     unwritten_whiteouts_end(c, b));
+       sort_iter_add(&sort_iter.iter,
+                     unwritten_whiteouts_start(b),
+                     unwritten_whiteouts_end(b));
        SET_BSET_SEPARATE_WHITEOUTS(i, false);
 
        b->whiteout_u64s = 0;
 
-       u64s = bch2_sort_keys(i->start, &sort_iter, false);
+       u64s = bch2_sort_keys(i->start, &sort_iter.iter, false);
        le16_add_cpu(&i->u64s, u64s);
 
+       BUG_ON(!b->written && i->u64s != b->data->keys.u64s);
+
        set_needs_whiteout(i, false);
 
        /* do we have data to write? */
@@ -1908,6 +2093,10 @@ do_write:
        bytes_to_write = vstruct_end(i) - data;
        sectors_to_write = round_up(bytes_to_write, block_bytes(c)) >> 9;
 
+       if (!b->written &&
+           b->key.k.type == KEY_TYPE_btree_ptr_v2)
+               BUG_ON(btree_ptr_sectors_written(&b->key) != sectors_to_write);
+
        memset(data + bytes_to_write, 0,
               (sectors_to_write << 9) - bytes_to_write);
 
@@ -1915,9 +2104,7 @@ do_write:
        BUG_ON(BSET_BIG_ENDIAN(i) != CPU_BIG_ENDIAN);
        BUG_ON(i->seq != b->data->keys.seq);
 
-       i->version = c->sb.version < bcachefs_metadata_version_bkey_renumber
-               ? cpu_to_le16(BCH_BSET_VERSION_OLD)
-               : cpu_to_le16(c->sb.version);
+       i->version = cpu_to_le16(c->sb.version);
        SET_BSET_OFFSET(i, b->written);
        SET_BSET_CSUM_TYPE(i, bch2_meta_checksum_type(c));
 
@@ -1972,10 +2159,12 @@ do_write:
            c->opts.nochanges)
                goto err;
 
-       trace_btree_write(b, bytes_to_write, sectors_to_write);
+       trace_and_count(c, btree_node_write, b, bytes_to_write, sectors_to_write);
 
-       wbio = container_of(bio_alloc_bioset(GFP_NOIO,
+       wbio = container_of(bio_alloc_bioset(NULL,
                                buf_pages(data, sectors_to_write << 9),
+                               REQ_OP_WRITE|REQ_META,
+                               GFP_NOFS,
                                &c->btree_bio),
                            struct btree_write_bio, wbio.bio);
        wbio_init(&wbio->wbio.bio);
@@ -1985,7 +2174,6 @@ do_write:
        wbio->wbio.c                    = c;
        wbio->wbio.used_mempool         = used_mempool;
        wbio->wbio.first_btree_write    = !b->written;
-       wbio->wbio.bio.bi_opf           = REQ_OP_WRITE|REQ_META;
        wbio->wbio.bio.bi_end_io        = btree_node_write_endio;
        wbio->wbio.bio.bi_private       = b;
 
@@ -1995,27 +2183,18 @@ do_write:
 
        b->written += sectors_to_write;
 
-       if (wbio->wbio.first_btree_write &&
-           b->key.k.type == KEY_TYPE_btree_ptr_v2)
-               bkey_i_to_btree_ptr_v2(&b->key)->v.sectors_written =
-                       cpu_to_le16(b->written);
-
        if (wbio->key.k.type == KEY_TYPE_btree_ptr_v2)
                bkey_i_to_btree_ptr_v2(&wbio->key)->v.sectors_written =
                        cpu_to_le16(b->written);
 
-       atomic64_inc(&c->btree_writes_nr);
-       atomic64_add(sectors_to_write, &c->btree_writes_sectors);
+       atomic64_inc(&c->btree_write_stats[type].nr);
+       atomic64_add(bytes_to_write, &c->btree_write_stats[type].bytes);
 
        INIT_WORK(&wbio->work, btree_write_submit);
        queue_work(c->io_complete_wq, &wbio->work);
        return;
 err:
        set_btree_node_noevict(b);
-       if (!b->written &&
-           b->key.k.type == KEY_TYPE_btree_ptr_v2)
-               bkey_i_to_btree_ptr_v2(&b->key)->v.sectors_written =
-                       cpu_to_le16(sectors_to_write);
        b->written += sectors_to_write;
 nowrite:
        btree_bounce_free(c, bytes, used_mempool, data);
@@ -2071,7 +2250,7 @@ bool bch2_btree_post_write_cleanup(struct bch_fs *c, struct btree *b)
 
        bne = want_new_bset(c, b);
        if (bne)
-               bch2_bset_init_next(c, b, bne);
+               bch2_bset_init_next(b, bne);
 
        bch2_btree_build_aux_trees(b);
 
@@ -2137,3 +2316,33 @@ bool bch2_btree_flush_all_writes(struct bch_fs *c)
 {
        return __bch2_btree_flush_all(c, BTREE_NODE_write_in_flight);
 }
+
+static const char * const bch2_btree_write_types[] = {
+#define x(t, n) [n] = #t,
+       BCH_BTREE_WRITE_TYPES()
+       NULL
+};
+
+void bch2_btree_write_stats_to_text(struct printbuf *out, struct bch_fs *c)
+{
+       printbuf_tabstop_push(out, 20);
+       printbuf_tabstop_push(out, 10);
+
+       prt_tab(out);
+       prt_str(out, "nr");
+       prt_tab(out);
+       prt_str(out, "size");
+       prt_newline(out);
+
+       for (unsigned i = 0; i < BTREE_WRITE_TYPE_NR; i++) {
+               u64 nr          = atomic64_read(&c->btree_write_stats[i].nr);
+               u64 bytes       = atomic64_read(&c->btree_write_stats[i].bytes);
+
+               prt_printf(out, "%s:", bch2_btree_write_types[i]);
+               prt_tab(out);
+               prt_u64(out, nr);
+               prt_tab(out);
+               prt_human_readable_u64(out, nr ? div64_u64(bytes, nr) : 0);
+               prt_newline(out);
+       }
+}