]> git.sesse.net Git - bcachefs-tools-debian/blobdiff - libbcachefs/super-io.c
Update bcachefs sources to 25de2b00dc bcachefs: Change check for invalid key types
[bcachefs-tools-debian] / libbcachefs / super-io.c
index cbc5979a5181641fc1001740bfefebbdca07d1cc..472f5b216c654c184922aafba5f786145ca44170 100644 (file)
 #include "quota.h"
 #include "super-io.h"
 #include "super.h"
+#include "trace.h"
 #include "vstructs.h"
 #include "counters.h"
 
 #include <linux/backing-dev.h>
-#include <linux/pretty-printers.h>
 #include <linux/sort.h>
 
-#include <trace/events/bcachefs.h>
+static const char * const bch2_metadata_versions[] = {
+#define x(t, n) [n] = #t,
+       BCH_METADATA_VERSIONS()
+#undef x
+};
+
+void bch2_version_to_text(struct printbuf *out, unsigned v)
+{
+       const char *str = v < ARRAY_SIZE(bch2_metadata_versions)
+               ? bch2_metadata_versions[v]
+               : "(unknown version)";
+
+       prt_printf(out, "%u: %s", v, str);
+}
 
 const char * const bch2_sb_fields[] = {
 #define x(name, nr)    #name,
@@ -100,8 +113,7 @@ void bch2_sb_field_delete(struct bch_sb_handle *sb,
 
 void bch2_free_super(struct bch_sb_handle *sb)
 {
-       if (sb->bio)
-               kfree(sb->bio);
+       kfree(sb->bio);
        if (!IS_ERR_OR_NULL(sb->bdev))
                blkdev_put(sb->bdev, sb->mode);
 
@@ -138,25 +150,24 @@ int bch2_sb_realloc(struct bch_sb_handle *sb, unsigned u64s)
                return 0;
 
        if (dynamic_fault("bcachefs:add:super_realloc"))
-               return -ENOMEM;
+               return -BCH_ERR_ENOMEM_sb_realloc_injected;
 
        if (sb->have_bio) {
                unsigned nr_bvecs = DIV_ROUND_UP(new_buffer_size, PAGE_SIZE);
 
                bio = bio_kmalloc(nr_bvecs, GFP_KERNEL);
                if (!bio)
-                       return -ENOMEM;
+                       return -BCH_ERR_ENOMEM_sb_bio_realloc;
 
                bio_init(bio, NULL, bio->bi_inline_vecs, nr_bvecs, 0);
 
-               if (sb->bio)
-                       kfree(sb->bio);
+               kfree(sb->bio);
                sb->bio = bio;
        }
 
        new_sb = krealloc(sb->sb, new_buffer_size, GFP_NOFS|__GFP_ZERO);
        if (!new_sb)
-               return -ENOMEM;
+               return -BCH_ERR_ENOMEM_sb_buf_realloc;
 
        sb->sb = new_sb;
        sb->buffer_size = new_buffer_size;
@@ -213,25 +224,26 @@ static int validate_sb_layout(struct bch_sb_layout *layout, struct printbuf *out
        u64 offset, prev_offset, max_sectors;
        unsigned i;
 
-       if (uuid_le_cmp(layout->magic, BCACHE_MAGIC)) {
+       if (!uuid_equal(&layout->magic, &BCACHE_MAGIC) &&
+           !uuid_equal(&layout->magic, &BCHFS_MAGIC)) {
                prt_printf(out, "Not a bcachefs superblock layout");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_layout;
        }
 
        if (layout->layout_type != 0) {
                prt_printf(out, "Invalid superblock layout type %u",
                       layout->layout_type);
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_layout_type;
        }
 
        if (!layout->nr_superblocks) {
                prt_printf(out, "Invalid superblock layout: no superblocks");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_layout_nr_superblocks;
        }
 
        if (layout->nr_superblocks > ARRAY_SIZE(layout->sb_offset)) {
                prt_printf(out, "Invalid superblock layout: too many superblocks");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_layout_nr_superblocks;
        }
 
        max_sectors = 1 << layout->sb_max_size_bits;
@@ -245,7 +257,7 @@ static int validate_sb_layout(struct bch_sb_layout *layout, struct printbuf *out
                        prt_printf(out, "Invalid superblock layout: superblocks overlap\n"
                               "  (sb %u ends at %llu next starts at %llu",
                               i - 1, prev_offset + max_sectors, offset);
-                       return -EINVAL;
+                       return -BCH_ERR_invalid_sb_layout_superblocks_overlap;
                }
                prev_offset = offset;
        }
@@ -253,6 +265,44 @@ static int validate_sb_layout(struct bch_sb_layout *layout, struct printbuf *out
        return 0;
 }
 
+static int bch2_sb_compatible(struct bch_sb *sb, struct printbuf *out)
+{
+       u16 version             = le16_to_cpu(sb->version);
+       u16 version_min         = le16_to_cpu(sb->version_min);
+
+       if (!bch2_version_compatible(version)) {
+               prt_str(out, "Unsupported superblock version ");
+               bch2_version_to_text(out, version);
+               prt_str(out, " (min ");
+               bch2_version_to_text(out, bcachefs_metadata_version_min);
+               prt_str(out, ", max ");
+               bch2_version_to_text(out, bcachefs_metadata_version_current);
+               prt_str(out, ")");
+               return -BCH_ERR_invalid_sb_version;
+       }
+
+       if (!bch2_version_compatible(version_min)) {
+               prt_str(out, "Unsupported superblock version_min ");
+               bch2_version_to_text(out, version_min);
+               prt_str(out, " (min ");
+               bch2_version_to_text(out, bcachefs_metadata_version_min);
+               prt_str(out, ", max ");
+               bch2_version_to_text(out, bcachefs_metadata_version_current);
+               prt_str(out, ")");
+               return -BCH_ERR_invalid_sb_version;
+       }
+
+       if (version_min > version) {
+               prt_str(out, "Bad minimum version ");
+               bch2_version_to_text(out, version_min);
+               prt_str(out, ", greater than version field ");
+               bch2_version_to_text(out, version);
+               return -BCH_ERR_invalid_sb_version;
+       }
+
+       return 0;
+}
+
 static int bch2_sb_validate(struct bch_sb_handle *disk_sb, struct printbuf *out,
                            int rw)
 {
@@ -260,37 +310,17 @@ static int bch2_sb_validate(struct bch_sb_handle *disk_sb, struct printbuf *out,
        struct bch_sb_field *f;
        struct bch_sb_field_members *mi;
        enum bch_opt_id opt_id;
-       u32 version, version_min;
        u16 block_size;
        int ret;
 
-       version         = le16_to_cpu(sb->version);
-       version_min     = version >= bcachefs_metadata_version_bkey_renumber
-               ? le16_to_cpu(sb->version_min)
-               : version;
-
-       if (version    >= bcachefs_metadata_version_max) {
-               prt_printf(out, "Unsupported superblock version %u (min %u, max %u)",
-                      version, bcachefs_metadata_version_min, bcachefs_metadata_version_max);
-               return -EINVAL;
-       }
-
-       if (version_min < bcachefs_metadata_version_min) {
-               prt_printf(out, "Unsupported superblock version %u (min %u, max %u)",
-                      version_min, bcachefs_metadata_version_min, bcachefs_metadata_version_max);
-               return -EINVAL;
-       }
-
-       if (version_min > version) {
-               prt_printf(out, "Bad minimum version %u, greater than version field %u",
-                      version_min, version);
-               return -EINVAL;
-       }
+       ret = bch2_sb_compatible(sb, out);
+       if (ret)
+               return ret;
 
        if (sb->features[1] ||
            (le64_to_cpu(sb->features[0]) & (~0ULL << BCH_FEATURE_NR))) {
                prt_printf(out, "Filesystem has incompatible features");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_features;
        }
 
        block_size = le16_to_cpu(sb->block_size);
@@ -298,43 +328,43 @@ static int bch2_sb_validate(struct bch_sb_handle *disk_sb, struct printbuf *out,
        if (block_size > PAGE_SECTORS) {
                prt_printf(out, "Block size too big (got %u, max %u)",
                       block_size, PAGE_SECTORS);
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_block_size;
        }
 
-       if (bch2_is_zero(sb->user_uuid.b, sizeof(uuid_le))) {
+       if (bch2_is_zero(sb->user_uuid.b, sizeof(sb->user_uuid))) {
                prt_printf(out, "Bad user UUID (got zeroes)");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_uuid;
        }
 
-       if (bch2_is_zero(sb->uuid.b, sizeof(uuid_le))) {
+       if (bch2_is_zero(sb->uuid.b, sizeof(sb->uuid))) {
                prt_printf(out, "Bad intenal UUID (got zeroes)");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_uuid;
        }
 
        if (!sb->nr_devices ||
            sb->nr_devices > BCH_SB_MEMBERS_MAX) {
                prt_printf(out, "Bad number of member devices %u (max %u)",
                       sb->nr_devices, BCH_SB_MEMBERS_MAX);
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_too_many_members;
        }
 
        if (sb->dev_idx >= sb->nr_devices) {
                prt_printf(out, "Bad dev_idx (got %u, nr_devices %u)",
                       sb->dev_idx, sb->nr_devices);
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_dev_idx;
        }
 
        if (!sb->time_precision ||
            le32_to_cpu(sb->time_precision) > NSEC_PER_SEC) {
                prt_printf(out, "Invalid time precision: %u (min 1, max %lu)",
                       le32_to_cpu(sb->time_precision), NSEC_PER_SEC);
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_time_precision;
        }
 
        if (rw == READ) {
                /*
                 * Been seeing a bug where these are getting inexplicably
-                * zeroed, so we'r now validating them, but we have to be
+                * zeroed, so we're now validating them, but we have to be
                 * careful not to preven people's filesystems from mounting:
                 */
                if (!BCH_SB_JOURNAL_FLUSH_DELAY(sb))
@@ -365,15 +395,15 @@ static int bch2_sb_validate(struct bch_sb_handle *disk_sb, struct printbuf *out,
 
        vstruct_for_each(sb, f) {
                if (!f->u64s) {
-                       prt_printf(out, "Invalid superblock: optional with size 0 (type %u)",
+                       prt_printf(out, "Invalid superblock: optional field with size 0 (type %u)",
                               le32_to_cpu(f->type));
-                       return -EINVAL;
+                       return -BCH_ERR_invalid_sb_field_size;
                }
 
                if (vstruct_next(f) > vstruct_last(sb)) {
                        prt_printf(out, "Invalid superblock: optional field extends past end of superblock (type %u)",
                               le32_to_cpu(f->type));
-                       return -EINVAL;
+                       return -BCH_ERR_invalid_sb_field_size;
                }
        }
 
@@ -381,7 +411,7 @@ static int bch2_sb_validate(struct bch_sb_handle *disk_sb, struct printbuf *out,
        mi = bch2_sb_get_members(sb);
        if (!mi) {
                prt_printf(out, "Invalid superblock: member info area missing");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_members_missing;
        }
 
        ret = bch2_sb_field_validate(sb, &mi->field, out);
@@ -434,7 +464,7 @@ static void bch2_sb_update(struct bch_fs *c)
                ca->mi = bch2_mi_to_cpu(mi->members + i);
 }
 
-static void __copy_super(struct bch_sb_handle *dst_handle, struct bch_sb *src)
+static int __copy_super(struct bch_sb_handle *dst_handle, struct bch_sb *src)
 {
        struct bch_sb_field *src_f, *dst_f;
        struct bch_sb *dst = dst_handle->sb;
@@ -459,42 +489,45 @@ static void __copy_super(struct bch_sb_handle *dst_handle, struct bch_sb *src)
        memcpy(dst->compat,     src->compat,    sizeof(dst->compat));
 
        for (i = 0; i < BCH_SB_FIELD_NR; i++) {
+               int d;
+
                if ((1U << i) & BCH_SINGLE_DEVICE_SB_FIELDS)
                        continue;
 
                src_f = bch2_sb_field_get(src, i);
                dst_f = bch2_sb_field_get(dst, i);
+
+               d = (src_f ? le32_to_cpu(src_f->u64s) : 0) -
+                   (dst_f ? le32_to_cpu(dst_f->u64s) : 0);
+               if (d > 0) {
+                       int ret = bch2_sb_realloc(dst_handle, le32_to_cpu(dst_handle->sb->u64s) + d);
+                       if (ret)
+                               return ret;
+
+                       dst = dst_handle->sb;
+                       dst_f = bch2_sb_field_get(dst, i);
+               }
+
                dst_f = __bch2_sb_field_resize(dst_handle, dst_f,
                                src_f ? le32_to_cpu(src_f->u64s) : 0);
 
                if (src_f)
                        memcpy(dst_f, src_f, vstruct_bytes(src_f));
        }
+
+       return 0;
 }
 
 int bch2_sb_to_fs(struct bch_fs *c, struct bch_sb *src)
 {
-       struct bch_sb_field_journal *journal_buckets =
-               bch2_sb_get_journal(src);
-       unsigned journal_u64s = journal_buckets
-               ? le32_to_cpu(journal_buckets->field.u64s)
-               : 0;
        int ret;
 
        lockdep_assert_held(&c->sb_lock);
 
-       ret = bch2_sb_realloc(&c->disk_sb,
-                             le32_to_cpu(src->u64s) - journal_u64s);
-       if (ret)
-               return ret;
-
-       __copy_super(&c->disk_sb, src);
-
-       ret = bch2_sb_replicas_to_cpu_replicas(c);
-       if (ret)
-               return ret;
-
-       ret = bch2_sb_disk_groups_to_cpu(c);
+       ret =   bch2_sb_realloc(&c->disk_sb, 0) ?:
+               __copy_super(&c->disk_sb, src) ?:
+               bch2_sb_replicas_to_cpu_replicas(c) ?:
+               bch2_sb_disk_groups_to_cpu(c);
        if (ret)
                return ret;
 
@@ -504,21 +537,7 @@ int bch2_sb_to_fs(struct bch_fs *c, struct bch_sb *src)
 
 int bch2_sb_from_fs(struct bch_fs *c, struct bch_dev *ca)
 {
-       struct bch_sb *src = c->disk_sb.sb, *dst = ca->disk_sb.sb;
-       struct bch_sb_field_journal *journal_buckets =
-               bch2_sb_get_journal(dst);
-       unsigned journal_u64s = journal_buckets
-               ? le32_to_cpu(journal_buckets->field.u64s)
-               : 0;
-       unsigned u64s = le32_to_cpu(src->u64s) + journal_u64s;
-       int ret;
-
-       ret = bch2_sb_realloc(&ca->disk_sb, u64s);
-       if (ret)
-               return ret;
-
-       __copy_super(&ca->disk_sb, src);
-       return 0;
+       return __copy_super(&ca->disk_sb, c->disk_sb.sb);
 }
 
 /* read superblock: */
@@ -526,7 +545,6 @@ int bch2_sb_from_fs(struct bch_fs *c, struct bch_dev *ca)
 static int read_one_super(struct bch_sb_handle *sb, u64 offset, struct printbuf *err)
 {
        struct bch_csum csum;
-       u32 version, version_min;
        size_t bytes;
        int ret;
 reread:
@@ -540,45 +558,34 @@ reread:
                return ret;
        }
 
-       if (uuid_le_cmp(sb->sb->magic, BCACHE_MAGIC)) {
+       if (!uuid_equal(&sb->sb->magic, &BCACHE_MAGIC) &&
+           !uuid_equal(&sb->sb->magic, &BCHFS_MAGIC)) {
                prt_printf(err, "Not a bcachefs superblock");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_magic;
        }
 
-       version         = le16_to_cpu(sb->sb->version);
-       version_min     = version >= bcachefs_metadata_version_bkey_renumber
-               ? le16_to_cpu(sb->sb->version_min)
-               : version;
-
-       if (version    >= bcachefs_metadata_version_max) {
-               prt_printf(err, "Unsupported superblock version %u (min %u, max %u)",
-                      version, bcachefs_metadata_version_min, bcachefs_metadata_version_max);
-               return -EINVAL;
-       }
-
-       if (version_min < bcachefs_metadata_version_min) {
-               prt_printf(err, "Unsupported superblock version %u (min %u, max %u)",
-                      version_min, bcachefs_metadata_version_min, bcachefs_metadata_version_max);
-               return -EINVAL;
-       }
+       ret = bch2_sb_compatible(sb->sb, err);
+       if (ret)
+               return ret;
 
        bytes = vstruct_bytes(sb->sb);
 
        if (bytes > 512 << sb->sb->layout.sb_max_size_bits) {
                prt_printf(err, "Invalid superblock: too big (got %zu bytes, layout max %lu)",
                       bytes, 512UL << sb->sb->layout.sb_max_size_bits);
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_too_big;
        }
 
        if (bytes > sb->buffer_size) {
-               if (bch2_sb_realloc(sb, le32_to_cpu(sb->sb->u64s)))
-                       return -ENOMEM;
+               ret = bch2_sb_realloc(sb, le32_to_cpu(sb->sb->u64s));
+               if (ret)
+                       return ret;
                goto reread;
        }
 
        if (BCH_SB_CSUM_TYPE(sb->sb) >= BCH_CSUM_NR) {
                prt_printf(err, "unknown checksum type %llu", BCH_SB_CSUM_TYPE(sb->sb));
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_csum_type;
        }
 
        /* XXX: verify MACs */
@@ -587,7 +594,7 @@ reread:
 
        if (bch2_crc_cmp(csum, sb->sb->csum)) {
                prt_printf(err, "bad checksum");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_csum;
        }
 
        sb->seq = le64_to_cpu(sb->sb->seq);
@@ -698,7 +705,7 @@ got_super:
                prt_printf(&err, "block size (%u) smaller than device block size (%u)",
                       le16_to_cpu(sb->sb->block_size) << 9,
                       bdev_logical_block_size(sb->bdev));
-               ret = -EINVAL;
+               ret = -BCH_ERR_block_size_too_small;
                goto err;
        }
 
@@ -804,6 +811,11 @@ int bch2_write_super(struct bch_fs *c)
        closure_init_stack(cl);
        memset(&sb_written, 0, sizeof(sb_written));
 
+       if (c->opts.version_upgrade) {
+               c->disk_sb.sb->magic = BCHFS_MAGIC;
+               c->disk_sb.sb->layout.magic = BCHFS_MAGIC;
+       }
+
        le64_add_cpu(&c->disk_sb.sb->seq, 1);
 
        if (test_bit(BCH_FS_ERROR, &c->flags))
@@ -858,7 +870,7 @@ int bch2_write_super(struct bch_fs *c)
                                le64_to_cpu(ca->sb_read_scratch->seq),
                                ca->disk_sb.seq);
                        percpu_ref_put(&ca->io_ref);
-                       ret = -EROFS;
+                       ret = -BCH_ERR_erofs_sb_err;
                        goto out;
                }
 
@@ -868,7 +880,7 @@ int bch2_write_super(struct bch_fs *c)
                                le64_to_cpu(ca->sb_read_scratch->seq),
                                ca->disk_sb.seq);
                        percpu_ref_put(&ca->io_ref);
-                       ret = -EROFS;
+                       ret = -BCH_ERR_erofs_sb_err;
                        goto out;
                }
        }
@@ -948,7 +960,7 @@ static int bch2_sb_members_validate(struct bch_sb *sb,
        if ((void *) (mi->members + sb->nr_devices) >
            vstruct_end(&mi->field)) {
                prt_printf(err, "too many devices for section size");
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_members;
        }
 
        for (i = 0; i < sb->nr_devices; i++) {
@@ -960,28 +972,28 @@ static int bch2_sb_members_validate(struct bch_sb *sb,
                if (le64_to_cpu(m->nbuckets) > LONG_MAX) {
                        prt_printf(err, "device %u: too many buckets (got %llu, max %lu)",
                               i, le64_to_cpu(m->nbuckets), LONG_MAX);
-                       return -EINVAL;
+                       return -BCH_ERR_invalid_sb_members;
                }
 
                if (le64_to_cpu(m->nbuckets) -
                    le16_to_cpu(m->first_bucket) < BCH_MIN_NR_NBUCKETS) {
                        prt_printf(err, "device %u: not enough buckets (got %llu, max %u)",
                               i, le64_to_cpu(m->nbuckets), BCH_MIN_NR_NBUCKETS);
-                       return -EINVAL;
+                       return -BCH_ERR_invalid_sb_members;
                }
 
                if (le16_to_cpu(m->bucket_size) <
                    le16_to_cpu(sb->block_size)) {
                        prt_printf(err, "device %u: bucket size %u smaller than block size %u",
                               i, le16_to_cpu(m->bucket_size), le16_to_cpu(sb->block_size));
-                       return -EINVAL;
+                       return -BCH_ERR_invalid_sb_members;
                }
 
                if (le16_to_cpu(m->bucket_size) <
                    BCH_SB_BTREE_NODE_SIZE(sb)) {
                        prt_printf(err, "device %u: bucket size %u smaller than btree node size %llu",
                               i, le16_to_cpu(m->bucket_size), BCH_SB_BTREE_NODE_SIZE(sb));
-                       return -EINVAL;
+                       return -BCH_ERR_invalid_sb_members;
                }
        }
 
@@ -1113,12 +1125,12 @@ static int bch2_sb_crypt_validate(struct bch_sb *sb,
        if (vstruct_bytes(&crypt->field) < sizeof(*crypt)) {
                prt_printf(err, "wrong size (got %zu should be %zu)",
                       vstruct_bytes(&crypt->field), sizeof(*crypt));
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_crypt;
        }
 
        if (BCH_CRYPT_KDF_TYPE(crypt)) {
                prt_printf(err, "bad kdf type %llu", BCH_CRYPT_KDF_TYPE(crypt));
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_crypt;
        }
 
        return 0;
@@ -1256,7 +1268,8 @@ void bch2_journal_super_entries_add_common(struct bch_fs *c,
 
                u->entry.type   = BCH_JSET_ENTRY_data_usage;
                u->v            = cpu_to_le64(c->usage_base->replicas[i]);
-               memcpy(&u->r, e, replicas_entry_bytes(e));
+               unsafe_memcpy(&u->r, e, replicas_entry_bytes(e),
+                             "embedded variable length struct");
        }
 
        for_each_member_device(ca, c, dev) {
@@ -1354,7 +1367,7 @@ static int bch2_sb_clean_validate(struct bch_sb *sb,
        if (vstruct_bytes(&clean->field) < sizeof(*clean)) {
                prt_printf(err, "wrong size (got %zu should be %zu)",
                       vstruct_bytes(&clean->field), sizeof(*clean));
-               return -EINVAL;
+               return -BCH_ERR_invalid_sb_clean;
        }
 
        return 0;
@@ -1395,21 +1408,29 @@ static const struct bch_sb_field_ops *bch2_sb_field_ops[] = {
 #undef x
 };
 
+static const struct bch_sb_field_ops bch2_sb_field_null_ops = {
+       NULL
+};
+
+static const struct bch_sb_field_ops *bch2_sb_field_type_ops(unsigned type)
+{
+       return likely(type < ARRAY_SIZE(bch2_sb_field_ops))
+               ? bch2_sb_field_ops[type]
+               : &bch2_sb_field_null_ops;
+}
+
 static int bch2_sb_field_validate(struct bch_sb *sb, struct bch_sb_field *f,
                                  struct printbuf *err)
 {
        unsigned type = le32_to_cpu(f->type);
        struct printbuf field_err = PRINTBUF;
+       const struct bch_sb_field_ops *ops = bch2_sb_field_type_ops(type);
        int ret;
 
-       if (type >= BCH_SB_FIELD_NR)
-               return 0;
-
-       ret = bch2_sb_field_ops[type]->validate(sb, f, &field_err);
+       ret = ops->validate ? ops->validate(sb, f, &field_err) : 0;
        if (ret) {
                prt_printf(err, "Invalid superblock section %s: %s",
-                      bch2_sb_fields[type],
-                      field_err.buf);
+                          bch2_sb_fields[type], field_err.buf);
                prt_newline(err);
                bch2_sb_field_to_text(err, sb, f);
        }
@@ -1422,13 +1443,12 @@ void bch2_sb_field_to_text(struct printbuf *out, struct bch_sb *sb,
                           struct bch_sb_field *f)
 {
        unsigned type = le32_to_cpu(f->type);
-       const struct bch_sb_field_ops *ops = type < BCH_SB_FIELD_NR
-               ? bch2_sb_field_ops[type] : NULL;
+       const struct bch_sb_field_ops *ops = bch2_sb_field_type_ops(type);
 
        if (!out->nr_tabstops)
                printbuf_tabstop_push(out, 32);
 
-       if (ops)
+       if (type < BCH_SB_FIELD_NR)
                prt_printf(out, "%s", bch2_sb_fields[type]);
        else
                prt_printf(out, "(unknown field %u)", type);
@@ -1436,9 +1456,9 @@ void bch2_sb_field_to_text(struct printbuf *out, struct bch_sb *sb,
        prt_printf(out, " (size %zu):", vstruct_bytes(f));
        prt_newline(out);
 
-       if (ops && ops->to_text) {
+       if (ops->to_text) {
                printbuf_indent_add(out, 2);
-               bch2_sb_field_ops[type]->to_text(out, sb, f);
+               ops->to_text(out, sb, f);
                printbuf_indent_sub(out, 2);
        }
 }
@@ -1509,12 +1529,12 @@ void bch2_sb_to_text(struct printbuf *out, struct bch_sb *sb,
 
        prt_str(out, "Version:");
        prt_tab(out);
-       prt_printf(out, "%s", bch2_metadata_versions[le16_to_cpu(sb->version)]);
+       bch2_version_to_text(out, le16_to_cpu(sb->version));
        prt_newline(out);
 
        prt_printf(out, "Oldest version on disk:");
        prt_tab(out);
-       prt_printf(out, "%s", bch2_metadata_versions[le16_to_cpu(sb->version_min)]);
+       bch2_version_to_text(out, le16_to_cpu(sb->version_min));
        prt_newline(out);
 
        prt_printf(out, "Created:");