]> git.sesse.net Git - bcachefs-tools-debian/blobdiff - libbcachefs/super-io.c
Update bcachefs sources to 4b5917839c bcachefs: Fix a null ptr deref in check_xattr()
[bcachefs-tools-debian] / libbcachefs / super-io.c
index b9af78203fb895fbec60da9d6d8435e02e271cd8..d2d3eba4d229170f0d29080952f5948b30e99117 100644 (file)
@@ -4,6 +4,7 @@
 #include "btree_update_interior.h"
 #include "buckets.h"
 #include "checksum.h"
+#include "counters.h"
 #include "disk_groups.h"
 #include "ec.h"
 #include "error.h"
 #include "journal_io.h"
 #include "journal_sb.h"
 #include "journal_seq_blacklist.h"
+#include "recovery.h"
 #include "replicas.h"
 #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/sort.h>
 
-#include <trace/events/bcachefs.h>
+struct bch2_metadata_version {
+       u16             version;
+       const char      *name;
+       u64             recovery_passes;
+};
+
+static const struct bch2_metadata_version bch2_metadata_versions[] = {
+#define x(n, v, _recovery_passes) {            \
+       .version = v,                           \
+       .name = #n,                             \
+       .recovery_passes = _recovery_passes,    \
+},
+       BCH_METADATA_VERSIONS()
+#undef x
+};
+
+void bch2_version_to_text(struct printbuf *out, unsigned v)
+{
+       const char *str = "(unknown version)";
+
+       for (unsigned i = 0; i < ARRAY_SIZE(bch2_metadata_versions); i++)
+               if (bch2_metadata_versions[i].version == v) {
+                       str = bch2_metadata_versions[i].name;
+                       break;
+               }
+
+       prt_printf(out, "%u.%u: %s", BCH_VERSION_MAJOR(v), BCH_VERSION_MINOR(v), str);
+}
+
+unsigned bch2_latest_compatible_version(unsigned v)
+{
+       if (!BCH_VERSION_MAJOR(v))
+               return v;
+
+       for (unsigned i = 0; i < ARRAY_SIZE(bch2_metadata_versions); i++)
+               if (bch2_metadata_versions[i].version > v &&
+                   BCH_VERSION_MAJOR(bch2_metadata_versions[i].version) ==
+                   BCH_VERSION_MAJOR(v))
+                       v = bch2_metadata_versions[i].version;
+
+       return v;
+}
+
+u64 bch2_upgrade_recovery_passes(struct bch_fs *c,
+                                unsigned old_version,
+                                unsigned new_version)
+{
+       u64 ret = 0;
+
+       for (const struct bch2_metadata_version *i = bch2_metadata_versions;
+            i < bch2_metadata_versions + ARRAY_SIZE(bch2_metadata_versions);
+            i++)
+               if (i->version > old_version && i->version <= new_version) {
+                       if (i->recovery_passes & RECOVERY_PASS_ALL_FSCK)
+                               ret |= bch2_fsck_recovery_passes();
+                       ret |= i->recovery_passes;
+               }
+
+       return ret &= ~RECOVERY_PASS_ALL_FSCK;
+}
 
 const char * const bch2_sb_fields[] = {
 #define x(name, nr)    #name,
@@ -136,14 +197,14 @@ 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);
 
@@ -153,7 +214,7 @@ int bch2_sb_realloc(struct bch_sb_handle *sb, unsigned u64s)
 
        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;
@@ -210,8 +271,8 @@ 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) &&
-           uuid_le_cmp(layout->magic, BCHFS_MAGIC)) {
+       if (!uuid_equal(&layout->magic, &BCACHE_MAGIC) &&
+           !uuid_equal(&layout->magic, &BCHFS_MAGIC)) {
                prt_printf(out, "Not a bcachefs superblock layout");
                return -BCH_ERR_invalid_sb_layout;
        }
@@ -251,40 +312,58 @@ static int validate_sb_layout(struct bch_sb_layout *layout, struct printbuf *out
        return 0;
 }
 
-static int bch2_sb_validate(struct bch_sb_handle *disk_sb, struct printbuf *out,
-                           int rw)
+static int bch2_sb_compatible(struct bch_sb *sb, struct printbuf *out)
 {
-       struct bch_sb *sb = disk_sb->sb;
-       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);
+       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 (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);
+       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_printf(out, "Bad minimum version %u, greater than version field %u",
-                      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)
+{
+       struct bch_sb *sb = disk_sb->sb;
+       struct bch_sb_field *f;
+       struct bch_sb_field_members *mi;
+       enum bch_opt_id opt_id;
+       u16 block_size;
+       int ret;
+
+       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");
@@ -299,12 +378,12 @@ static int bch2_sb_validate(struct bch_sb_handle *disk_sb, struct printbuf *out,
                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 -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 -BCH_ERR_invalid_sb_uuid;
        }
@@ -332,13 +411,16 @@ static int bch2_sb_validate(struct bch_sb_handle *disk_sb, struct printbuf *out,
        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))
                        SET_BCH_SB_JOURNAL_FLUSH_DELAY(sb, 1000);
                if (!BCH_SB_JOURNAL_RECLAIM_DELAY(sb))
                        SET_BCH_SB_JOURNAL_RECLAIM_DELAY(sb, 1000);
+
+               if (!BCH_SB_VERSION_UPGRADE_COMPLETE(sb))
+                       SET_BCH_SB_VERSION_UPGRADE_COMPLETE(sb, le16_to_cpu(sb->version));
        }
 
        for (opt_id = 0; opt_id < bch2_opts_nr; opt_id++) {
@@ -413,6 +495,7 @@ static void bch2_sb_update(struct bch_fs *c)
        c->sb.user_uuid         = src->user_uuid;
        c->sb.version           = le16_to_cpu(src->version);
        c->sb.version_min       = le16_to_cpu(src->version_min);
+       c->sb.version_upgrade_complete = BCH_SB_VERSION_UPGRADE_COMPLETE(src);
        c->sb.nr_devices        = src->nr_devices;
        c->sb.clean             = BCH_SB_CLEAN(src);
        c->sb.encryption_type   = BCH_SB_ENCRYPTION_TYPE(src);
@@ -513,7 +596,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:
@@ -527,28 +609,15 @@ reread:
                return ret;
        }
 
-       if (uuid_le_cmp(sb->sb->magic, BCACHE_MAGIC) &&
-           uuid_le_cmp(sb->sb->magic, BCHFS_MAGIC)) {
+       if (!uuid_equal(&sb->sb->magic, &BCACHE_MAGIC) &&
+           !uuid_equal(&sb->sb->magic, &BCHFS_MAGIC)) {
                prt_printf(err, "Not a bcachefs superblock");
                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 -BCH_ERR_invalid_sb_version;
-       }
-
-       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 -BCH_ERR_invalid_sb_version;
-       }
+       ret = bch2_sb_compatible(sb->sb, err);
+       if (ret)
+               return ret;
 
        bytes = vstruct_bytes(sb->sb);
 
@@ -559,8 +628,9 @@ reread:
        }
 
        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;
        }
 
@@ -591,13 +661,18 @@ int bch2_read_super(const char *path, struct bch_opts *opts,
        struct printbuf err = PRINTBUF;
        __le64 *i;
        int ret;
-
-       pr_verbose_init(*opts, "");
-
+#ifndef __KERNEL__
+retry:
+#endif
        memset(sb, 0, sizeof(*sb));
        sb->mode        = FMODE_READ;
        sb->have_bio    = true;
 
+#ifndef __KERNEL__
+       if (opt_get(*opts, direct_io) == false)
+               sb->mode |= FMODE_BUFFERED;
+#endif
+
        if (!opt_get(*opts, noexcl))
                sb->mode |= FMODE_EXCL;
 
@@ -682,7 +757,13 @@ int bch2_read_super(const char *path, struct bch_opts *opts,
 
 got_super:
        if (le16_to_cpu(sb->sb->block_size) << 9 <
-           bdev_logical_block_size(sb->bdev)) {
+           bdev_logical_block_size(sb->bdev) &&
+           opt_get(*opts, direct_io)) {
+#ifndef __KERNEL__
+               opt_set(*opts, direct_io, false);
+               bch2_free_super(sb);
+               goto retry;
+#endif
                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));
@@ -700,7 +781,6 @@ got_super:
                goto err_no_print;
        }
 out:
-       pr_verbose_init(*opts, "ret %i", ret);
        printbuf_exit(&err);
        return ret;
 err:
@@ -792,10 +872,9 @@ 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;
-       }
+       /* Make sure we're using the new magic numbers: */
+       c->disk_sb.sb->magic = BCHFS_MAGIC;
+       c->disk_sb.sb->layout.magic = BCHFS_MAGIC;
 
        le64_add_cpu(&c->disk_sb.sb->seq, 1);
 
@@ -1158,6 +1237,32 @@ int bch2_sb_clean_validate_late(struct bch_fs *c, struct bch_sb_field_clean *cle
        return 0;
 }
 
+/* Downgrade if superblock is at a higher version than currently supported: */
+void bch2_sb_maybe_downgrade(struct bch_fs *c)
+{
+       lockdep_assert_held(&c->sb_lock);
+
+       /*
+        * Downgrade, if superblock is at a higher version than currently
+        * supported:
+        */
+       if (BCH_SB_VERSION_UPGRADE_COMPLETE(c->disk_sb.sb) > bcachefs_metadata_version_current)
+               SET_BCH_SB_VERSION_UPGRADE_COMPLETE(c->disk_sb.sb, bcachefs_metadata_version_current);
+       if (c->sb.version > bcachefs_metadata_version_current)
+               c->disk_sb.sb->version = cpu_to_le16(bcachefs_metadata_version_current);
+       if (c->sb.version_min > bcachefs_metadata_version_current)
+               c->disk_sb.sb->version_min = cpu_to_le16(bcachefs_metadata_version_current);
+       c->disk_sb.sb->compat[0] &= cpu_to_le64((1ULL << BCH_COMPAT_NR) - 1);
+}
+
+void bch2_sb_upgrade(struct bch_fs *c, unsigned new_version)
+{
+       lockdep_assert_held(&c->sb_lock);
+
+       c->disk_sb.sb->version = cpu_to_le16(new_version);
+       c->disk_sb.sb->features[0] |= cpu_to_le64(BCH_SB_FEATURES_ALL);
+}
+
 int bch2_fs_mark_dirty(struct bch_fs *c)
 {
        int ret;
@@ -1169,8 +1274,10 @@ int bch2_fs_mark_dirty(struct bch_fs *c)
 
        mutex_lock(&c->sb_lock);
        SET_BCH_SB_CLEAN(c->disk_sb.sb, false);
+
+       bch2_sb_maybe_downgrade(c);
        c->disk_sb.sb->features[0] |= cpu_to_le64(BCH_SB_FEATURES_ALWAYS);
-       c->disk_sb.sb->compat[0] &= cpu_to_le64((1ULL << BCH_COMPAT_NR) - 1);
+
        ret = bch2_write_super(c);
        mutex_unlock(&c->sb_lock);
 
@@ -1389,21 +1496,27 @@ 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;
+
+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);
        }
@@ -1416,13 +1529,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);
@@ -1430,9 +1542,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);
        }
 }
@@ -1503,12 +1615,17 @@ 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_str(out, "Version upgrade complete:");
+       prt_tab(out);
+       bch2_version_to_text(out, BCH_SB_VERSION_UPGRADE_COMPLETE(sb));
        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:");