linux-stable/fs/bcachefs/sb-downgrade.c
Kent Overstreet d2554263ad bcachefs: Split out recovery_passes.c
We've grown a fair amount of code for managing recovery passes; tracking
which ones we're running, which ones need to be run, and flagging in the
superblock which ones need to be run on the next recovery.

So it's worth splitting out into its own file, this code is pretty
different from the code in recovery.c.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
2024-03-31 20:36:11 -04:00

266 lines
7.5 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Superblock section that contains a list of recovery passes to run when
* downgrading past a given version
*/
#include "bcachefs.h"
#include "darray.h"
#include "recovery_passes.h"
#include "sb-downgrade.h"
#include "sb-errors.h"
#include "super-io.h"
#define RECOVERY_PASS_ALL_FSCK BIT_ULL(63)
/*
* Upgrade, downgrade tables - run certain recovery passes, fix certain errors
*
* x(version, recovery_passes, errors...)
*/
#define UPGRADE_TABLE() \
x(backpointers, \
RECOVERY_PASS_ALL_FSCK) \
x(inode_v3, \
RECOVERY_PASS_ALL_FSCK) \
x(unwritten_extents, \
RECOVERY_PASS_ALL_FSCK) \
x(bucket_gens, \
BIT_ULL(BCH_RECOVERY_PASS_bucket_gens_init)| \
RECOVERY_PASS_ALL_FSCK) \
x(lru_v2, \
RECOVERY_PASS_ALL_FSCK) \
x(fragmentation_lru, \
RECOVERY_PASS_ALL_FSCK) \
x(no_bps_in_alloc_keys, \
RECOVERY_PASS_ALL_FSCK) \
x(snapshot_trees, \
RECOVERY_PASS_ALL_FSCK) \
x(snapshot_skiplists, \
BIT_ULL(BCH_RECOVERY_PASS_check_snapshots), \
BCH_FSCK_ERR_snapshot_bad_depth, \
BCH_FSCK_ERR_snapshot_bad_skiplist) \
x(deleted_inodes, \
BIT_ULL(BCH_RECOVERY_PASS_check_inodes), \
BCH_FSCK_ERR_unlinked_inode_not_on_deleted_list) \
x(rebalance_work, \
BIT_ULL(BCH_RECOVERY_PASS_set_fs_needs_rebalance)) \
x(subvolume_fs_parent, \
BIT_ULL(BCH_RECOVERY_PASS_check_dirents), \
BCH_FSCK_ERR_subvol_fs_path_parent_wrong) \
x(btree_subvolume_children, \
BIT_ULL(BCH_RECOVERY_PASS_check_subvols), \
BCH_FSCK_ERR_subvol_children_not_set)
#define DOWNGRADE_TABLE()
struct upgrade_downgrade_entry {
u64 recovery_passes;
u16 version;
u16 nr_errors;
const u16 *errors;
};
#define x(ver, passes, ...) static const u16 upgrade_##ver##_errors[] = { __VA_ARGS__ };
UPGRADE_TABLE()
#undef x
static const struct upgrade_downgrade_entry upgrade_table[] = {
#define x(ver, passes, ...) { \
.recovery_passes = passes, \
.version = bcachefs_metadata_version_##ver,\
.nr_errors = ARRAY_SIZE(upgrade_##ver##_errors), \
.errors = upgrade_##ver##_errors, \
},
UPGRADE_TABLE()
#undef x
};
void bch2_sb_set_upgrade(struct bch_fs *c,
unsigned old_version,
unsigned new_version)
{
lockdep_assert_held(&c->sb_lock);
struct bch_sb_field_ext *ext = bch2_sb_field_get(c->disk_sb.sb, ext);
for (const struct upgrade_downgrade_entry *i = upgrade_table;
i < upgrade_table + ARRAY_SIZE(upgrade_table);
i++)
if (i->version > old_version && i->version <= new_version) {
u64 passes = i->recovery_passes;
if (passes & RECOVERY_PASS_ALL_FSCK)
passes |= bch2_fsck_recovery_passes();
passes &= ~RECOVERY_PASS_ALL_FSCK;
ext->recovery_passes_required[0] |=
cpu_to_le64(bch2_recovery_passes_to_stable(passes));
for (const u16 *e = i->errors;
e < i->errors + i->nr_errors;
e++) {
__set_bit(*e, c->sb.errors_silent);
ext->errors_silent[*e / 64] |= cpu_to_le64(BIT_ULL(*e % 64));
}
}
}
#define x(ver, passes, ...) static const u16 downgrade_ver_##errors[] = { __VA_ARGS__ };
DOWNGRADE_TABLE()
#undef x
static const struct upgrade_downgrade_entry downgrade_table[] = {
#define x(ver, passes, ...) { \
.recovery_passes = passes, \
.version = bcachefs_metadata_version_##ver,\
.nr_errors = ARRAY_SIZE(downgrade_##ver##_errors), \
.errors = downgrade_##ver##_errors, \
},
DOWNGRADE_TABLE()
#undef x
};
static inline const struct bch_sb_field_downgrade_entry *
downgrade_entry_next_c(const struct bch_sb_field_downgrade_entry *e)
{
return (void *) &e->errors[le16_to_cpu(e->nr_errors)];
}
#define for_each_downgrade_entry(_d, _i) \
for (const struct bch_sb_field_downgrade_entry *_i = (_d)->entries; \
(void *) _i < vstruct_end(&(_d)->field) && \
(void *) &_i->errors[0] < vstruct_end(&(_d)->field); \
_i = downgrade_entry_next_c(_i))
static int bch2_sb_downgrade_validate(struct bch_sb *sb, struct bch_sb_field *f,
struct printbuf *err)
{
struct bch_sb_field_downgrade *e = field_to_type(f, downgrade);
for_each_downgrade_entry(e, i) {
if (BCH_VERSION_MAJOR(le16_to_cpu(i->version)) !=
BCH_VERSION_MAJOR(le16_to_cpu(sb->version))) {
prt_printf(err, "downgrade entry with mismatched major version (%u != %u)",
BCH_VERSION_MAJOR(le16_to_cpu(i->version)),
BCH_VERSION_MAJOR(le16_to_cpu(sb->version)));
return -BCH_ERR_invalid_sb_downgrade;
}
}
return 0;
}
static void bch2_sb_downgrade_to_text(struct printbuf *out, struct bch_sb *sb,
struct bch_sb_field *f)
{
struct bch_sb_field_downgrade *e = field_to_type(f, downgrade);
if (out->nr_tabstops <= 1)
printbuf_tabstop_push(out, 16);
for_each_downgrade_entry(e, i) {
prt_str(out, "version:");
prt_tab(out);
bch2_version_to_text(out, le16_to_cpu(i->version));
prt_newline(out);
prt_str(out, "recovery passes:");
prt_tab(out);
prt_bitflags(out, bch2_recovery_passes,
bch2_recovery_passes_from_stable(le64_to_cpu(i->recovery_passes[0])));
prt_newline(out);
prt_str(out, "errors:");
prt_tab(out);
bool first = true;
for (unsigned j = 0; j < le16_to_cpu(i->nr_errors); j++) {
if (!first)
prt_char(out, ',');
first = false;
unsigned e = le16_to_cpu(i->errors[j]);
prt_str(out, e < BCH_SB_ERR_MAX ? bch2_sb_error_strs[e] : "(unknown)");
}
prt_newline(out);
}
}
const struct bch_sb_field_ops bch_sb_field_ops_downgrade = {
.validate = bch2_sb_downgrade_validate,
.to_text = bch2_sb_downgrade_to_text,
};
int bch2_sb_downgrade_update(struct bch_fs *c)
{
darray_char table = {};
int ret = 0;
for (const struct upgrade_downgrade_entry *src = downgrade_table;
src < downgrade_table + ARRAY_SIZE(downgrade_table);
src++) {
if (BCH_VERSION_MAJOR(src->version) != BCH_VERSION_MAJOR(le16_to_cpu(c->disk_sb.sb->version)))
continue;
struct bch_sb_field_downgrade_entry *dst;
unsigned bytes = sizeof(*dst) + sizeof(dst->errors[0]) * src->nr_errors;
ret = darray_make_room(&table, bytes);
if (ret)
goto out;
dst = (void *) &darray_top(table);
dst->version = cpu_to_le16(src->version);
dst->recovery_passes[0] = cpu_to_le64(src->recovery_passes);
dst->recovery_passes[1] = 0;
dst->nr_errors = cpu_to_le16(src->nr_errors);
for (unsigned i = 0; i < src->nr_errors; i++)
dst->errors[i] = cpu_to_le16(src->errors[i]);
table.nr += bytes;
}
struct bch_sb_field_downgrade *d = bch2_sb_field_get(c->disk_sb.sb, downgrade);
unsigned sb_u64s = DIV_ROUND_UP(sizeof(*d) + table.nr, sizeof(u64));
if (d && le32_to_cpu(d->field.u64s) > sb_u64s)
goto out;
d = bch2_sb_field_resize(&c->disk_sb, downgrade, sb_u64s);
if (!d) {
ret = -BCH_ERR_ENOSPC_sb_downgrade;
goto out;
}
memcpy(d->entries, table.data, table.nr);
memset_u64s_tail(d->entries, 0, table.nr);
out:
darray_exit(&table);
return ret;
}
void bch2_sb_set_downgrade(struct bch_fs *c, unsigned new_minor, unsigned old_minor)
{
struct bch_sb_field_downgrade *d = bch2_sb_field_get(c->disk_sb.sb, downgrade);
if (!d)
return;
struct bch_sb_field_ext *ext = bch2_sb_field_get(c->disk_sb.sb, ext);
for_each_downgrade_entry(d, i) {
unsigned minor = BCH_VERSION_MINOR(le16_to_cpu(i->version));
if (new_minor < minor && minor <= old_minor) {
ext->recovery_passes_required[0] |= i->recovery_passes[0];
ext->recovery_passes_required[1] |= i->recovery_passes[1];
for (unsigned j = 0; j < le16_to_cpu(i->nr_errors); j++) {
unsigned e = le16_to_cpu(i->errors[j]);
if (e < BCH_SB_ERR_MAX)
__set_bit(e, c->sb.errors_silent);
if (e < sizeof(ext->errors_silent) * 8)
__set_bit_le64(e, ext->errors_silent);
}
}
}
}