linux/fs/bcachefs/opts.c
Kent Overstreet 13c1e583f9 bcachefs: Improve -o norecovery; opts.recovery_pass_limit
This adds opts.recovery_pass_limit, and redoes -o norecovery to make use
of it; this fixes some issues with -o norecovery so it can be safely
used for data recovery.

Norecovery means "don't do journal replay"; it's an important data
recovery tool when we're getting stuck in journal replay.

When using it this way we need to make sure we don't free journal keys
after startup, so we continue to overlay them: thus it needs to imply
retain_recovery_info, as well as nochanges.

recovery_pass_limit is an explicit option for telling recovery to exit
after a specific recovery pass; this is a much cleaner way of
implementing -o norecovery, as well as being a useful debug feature in
its own right.

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

607 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include "bcachefs.h"
#include "compress.h"
#include "disk_groups.h"
#include "error.h"
#include "opts.h"
#include "recovery_passes.h"
#include "super-io.h"
#include "util.h"
#define x(t, n, ...) [n] = #t,
const char * const bch2_error_actions[] = {
BCH_ERROR_ACTIONS()
NULL
};
const char * const bch2_fsck_fix_opts[] = {
BCH_FIX_ERRORS_OPTS()
NULL
};
const char * const bch2_version_upgrade_opts[] = {
BCH_VERSION_UPGRADE_OPTS()
NULL
};
const char * const bch2_sb_features[] = {
BCH_SB_FEATURES()
NULL
};
const char * const bch2_sb_compat[] = {
BCH_SB_COMPAT()
NULL
};
const char * const __bch2_btree_ids[] = {
BCH_BTREE_IDS()
NULL
};
const char * const bch2_csum_types[] = {
BCH_CSUM_TYPES()
NULL
};
const char * const bch2_csum_opts[] = {
BCH_CSUM_OPTS()
NULL
};
const char * const __bch2_compression_types[] = {
BCH_COMPRESSION_TYPES()
NULL
};
const char * const bch2_compression_opts[] = {
BCH_COMPRESSION_OPTS()
NULL
};
const char * const bch2_str_hash_types[] = {
BCH_STR_HASH_TYPES()
NULL
};
const char * const bch2_str_hash_opts[] = {
BCH_STR_HASH_OPTS()
NULL
};
const char * const __bch2_data_types[] = {
BCH_DATA_TYPES()
NULL
};
const char * const bch2_member_states[] = {
BCH_MEMBER_STATES()
NULL
};
const char * const bch2_jset_entry_types[] = {
BCH_JSET_ENTRY_TYPES()
NULL
};
const char * const bch2_fs_usage_types[] = {
BCH_FS_USAGE_TYPES()
NULL
};
#undef x
static int bch2_opt_fix_errors_parse(struct bch_fs *c, const char *val, u64 *res,
struct printbuf *err)
{
if (!val) {
*res = FSCK_FIX_yes;
} else {
int ret = match_string(bch2_fsck_fix_opts, -1, val);
if (ret < 0 && err)
prt_str(err, "fix_errors: invalid selection");
if (ret < 0)
return ret;
*res = ret;
}
return 0;
}
static void bch2_opt_fix_errors_to_text(struct printbuf *out,
struct bch_fs *c,
struct bch_sb *sb,
u64 v)
{
prt_str(out, bch2_fsck_fix_opts[v]);
}
#define bch2_opt_fix_errors (struct bch_opt_fn) { \
.parse = bch2_opt_fix_errors_parse, \
.to_text = bch2_opt_fix_errors_to_text, \
}
const char * const bch2_d_types[BCH_DT_MAX] = {
[DT_UNKNOWN] = "unknown",
[DT_FIFO] = "fifo",
[DT_CHR] = "chr",
[DT_DIR] = "dir",
[DT_BLK] = "blk",
[DT_REG] = "reg",
[DT_LNK] = "lnk",
[DT_SOCK] = "sock",
[DT_WHT] = "whiteout",
[DT_SUBVOL] = "subvol",
};
u64 BCH2_NO_SB_OPT(const struct bch_sb *sb)
{
BUG();
}
void SET_BCH2_NO_SB_OPT(struct bch_sb *sb, u64 v)
{
BUG();
}
void bch2_opts_apply(struct bch_opts *dst, struct bch_opts src)
{
#define x(_name, ...) \
if (opt_defined(src, _name)) \
opt_set(*dst, _name, src._name);
BCH_OPTS()
#undef x
}
bool bch2_opt_defined_by_id(const struct bch_opts *opts, enum bch_opt_id id)
{
switch (id) {
#define x(_name, ...) \
case Opt_##_name: \
return opt_defined(*opts, _name);
BCH_OPTS()
#undef x
default:
BUG();
}
}
u64 bch2_opt_get_by_id(const struct bch_opts *opts, enum bch_opt_id id)
{
switch (id) {
#define x(_name, ...) \
case Opt_##_name: \
return opts->_name;
BCH_OPTS()
#undef x
default:
BUG();
}
}
void bch2_opt_set_by_id(struct bch_opts *opts, enum bch_opt_id id, u64 v)
{
switch (id) {
#define x(_name, ...) \
case Opt_##_name: \
opt_set(*opts, _name, v); \
break;
BCH_OPTS()
#undef x
default:
BUG();
}
}
const struct bch_option bch2_opt_table[] = {
#define OPT_BOOL() .type = BCH_OPT_BOOL, .min = 0, .max = 2
#define OPT_UINT(_min, _max) .type = BCH_OPT_UINT, \
.min = _min, .max = _max
#define OPT_STR(_choices) .type = BCH_OPT_STR, \
.min = 0, .max = ARRAY_SIZE(_choices), \
.choices = _choices
#define OPT_STR_NOLIMIT(_choices) .type = BCH_OPT_STR, \
.min = 0, .max = U64_MAX, \
.choices = _choices
#define OPT_FN(_fn) .type = BCH_OPT_FN, .fn = _fn
#define x(_name, _bits, _flags, _type, _sb_opt, _default, _hint, _help) \
[Opt_##_name] = { \
.attr = { \
.name = #_name, \
.mode = (_flags) & OPT_RUNTIME ? 0644 : 0444, \
}, \
.flags = _flags, \
.hint = _hint, \
.help = _help, \
.get_sb = _sb_opt, \
.set_sb = SET_##_sb_opt, \
_type \
},
BCH_OPTS()
#undef x
};
int bch2_opt_lookup(const char *name)
{
const struct bch_option *i;
for (i = bch2_opt_table;
i < bch2_opt_table + ARRAY_SIZE(bch2_opt_table);
i++)
if (!strcmp(name, i->attr.name))
return i - bch2_opt_table;
return -1;
}
struct synonym {
const char *s1, *s2;
};
static const struct synonym bch_opt_synonyms[] = {
{ "quota", "usrquota" },
};
static int bch2_mount_opt_lookup(const char *name)
{
const struct synonym *i;
for (i = bch_opt_synonyms;
i < bch_opt_synonyms + ARRAY_SIZE(bch_opt_synonyms);
i++)
if (!strcmp(name, i->s1))
name = i->s2;
return bch2_opt_lookup(name);
}
int bch2_opt_validate(const struct bch_option *opt, u64 v, struct printbuf *err)
{
if (v < opt->min) {
if (err)
prt_printf(err, "%s: too small (min %llu)",
opt->attr.name, opt->min);
return -BCH_ERR_ERANGE_option_too_small;
}
if (opt->max && v >= opt->max) {
if (err)
prt_printf(err, "%s: too big (max %llu)",
opt->attr.name, opt->max);
return -BCH_ERR_ERANGE_option_too_big;
}
if ((opt->flags & OPT_SB_FIELD_SECTORS) && (v & 511)) {
if (err)
prt_printf(err, "%s: not a multiple of 512",
opt->attr.name);
return -BCH_ERR_opt_parse_error;
}
if ((opt->flags & OPT_MUST_BE_POW_2) && !is_power_of_2(v)) {
if (err)
prt_printf(err, "%s: must be a power of two",
opt->attr.name);
return -BCH_ERR_opt_parse_error;
}
if (opt->fn.validate)
return opt->fn.validate(v, err);
return 0;
}
int bch2_opt_parse(struct bch_fs *c,
const struct bch_option *opt,
const char *val, u64 *res,
struct printbuf *err)
{
ssize_t ret;
switch (opt->type) {
case BCH_OPT_BOOL:
if (val) {
ret = kstrtou64(val, 10, res);
} else {
ret = 0;
*res = 1;
}
if (ret < 0 || (*res != 0 && *res != 1)) {
if (err)
prt_printf(err, "%s: must be bool", opt->attr.name);
return ret < 0 ? ret : -BCH_ERR_option_not_bool;
}
break;
case BCH_OPT_UINT:
if (!val) {
prt_printf(err, "%s: required value",
opt->attr.name);
return -EINVAL;
}
ret = opt->flags & OPT_HUMAN_READABLE
? bch2_strtou64_h(val, res)
: kstrtou64(val, 10, res);
if (ret < 0) {
if (err)
prt_printf(err, "%s: must be a number",
opt->attr.name);
return ret;
}
break;
case BCH_OPT_STR:
if (!val) {
prt_printf(err, "%s: required value",
opt->attr.name);
return -EINVAL;
}
ret = match_string(opt->choices, -1, val);
if (ret < 0) {
if (err)
prt_printf(err, "%s: invalid selection",
opt->attr.name);
return ret;
}
*res = ret;
break;
case BCH_OPT_FN:
ret = opt->fn.parse(c, val, res, err);
if (ret < 0) {
if (err)
prt_printf(err, "%s: parse error",
opt->attr.name);
return ret;
}
}
return bch2_opt_validate(opt, *res, err);
}
void bch2_opt_to_text(struct printbuf *out,
struct bch_fs *c, struct bch_sb *sb,
const struct bch_option *opt, u64 v,
unsigned flags)
{
if (flags & OPT_SHOW_MOUNT_STYLE) {
if (opt->type == BCH_OPT_BOOL) {
prt_printf(out, "%s%s",
v ? "" : "no",
opt->attr.name);
return;
}
prt_printf(out, "%s=", opt->attr.name);
}
switch (opt->type) {
case BCH_OPT_BOOL:
case BCH_OPT_UINT:
if (opt->flags & OPT_HUMAN_READABLE)
prt_human_readable_u64(out, v);
else
prt_printf(out, "%lli", v);
break;
case BCH_OPT_STR:
if (flags & OPT_SHOW_FULL_LIST)
prt_string_option(out, opt->choices, v);
else
prt_str(out, opt->choices[v]);
break;
case BCH_OPT_FN:
opt->fn.to_text(out, c, sb, v);
break;
default:
BUG();
}
}
int bch2_opt_check_may_set(struct bch_fs *c, int id, u64 v)
{
int ret = 0;
switch (id) {
case Opt_compression:
case Opt_background_compression:
ret = bch2_check_set_has_compressed_data(c, v);
break;
case Opt_erasure_code:
if (v)
bch2_check_set_feature(c, BCH_FEATURE_ec);
break;
}
return ret;
}
int bch2_opts_check_may_set(struct bch_fs *c)
{
unsigned i;
int ret;
for (i = 0; i < bch2_opts_nr; i++) {
ret = bch2_opt_check_may_set(c, i,
bch2_opt_get_by_id(&c->opts, i));
if (ret)
return ret;
}
return 0;
}
int bch2_parse_mount_opts(struct bch_fs *c, struct bch_opts *opts,
char *options)
{
char *copied_opts, *copied_opts_start;
char *opt, *name, *val;
int ret, id;
struct printbuf err = PRINTBUF;
u64 v;
if (!options)
return 0;
/*
* sys_fsconfig() is now occasionally providing us with option lists
* starting with a comma - weird.
*/
if (*options == ',')
options++;
copied_opts = kstrdup(options, GFP_KERNEL);
if (!copied_opts)
return -ENOMEM;
copied_opts_start = copied_opts;
while ((opt = strsep(&copied_opts, ",")) != NULL) {
name = strsep(&opt, "=");
val = opt;
id = bch2_mount_opt_lookup(name);
/* Check for the form "noopt", negation of a boolean opt: */
if (id < 0 &&
!val &&
!strncmp("no", name, 2)) {
id = bch2_mount_opt_lookup(name + 2);
val = "0";
}
/* Unknown options are ignored: */
if (id < 0)
continue;
if (!(bch2_opt_table[id].flags & OPT_MOUNT))
goto bad_opt;
if (id == Opt_acl &&
!IS_ENABLED(CONFIG_BCACHEFS_POSIX_ACL))
goto bad_opt;
if ((id == Opt_usrquota ||
id == Opt_grpquota) &&
!IS_ENABLED(CONFIG_BCACHEFS_QUOTA))
goto bad_opt;
ret = bch2_opt_parse(c, &bch2_opt_table[id], val, &v, &err);
if (ret < 0)
goto bad_val;
bch2_opt_set_by_id(opts, id, v);
}
ret = 0;
goto out;
bad_opt:
pr_err("Bad mount option %s", name);
ret = -BCH_ERR_option_name;
goto out;
bad_val:
pr_err("Invalid mount option %s", err.buf);
ret = -BCH_ERR_option_value;
goto out;
out:
kfree(copied_opts_start);
printbuf_exit(&err);
return ret;
}
u64 bch2_opt_from_sb(struct bch_sb *sb, enum bch_opt_id id)
{
const struct bch_option *opt = bch2_opt_table + id;
u64 v;
v = opt->get_sb(sb);
if (opt->flags & OPT_SB_FIELD_ILOG2)
v = 1ULL << v;
if (opt->flags & OPT_SB_FIELD_SECTORS)
v <<= 9;
return v;
}
/*
* Initial options from superblock - here we don't want any options undefined,
* any options the superblock doesn't specify are set to 0:
*/
int bch2_opts_from_sb(struct bch_opts *opts, struct bch_sb *sb)
{
unsigned id;
for (id = 0; id < bch2_opts_nr; id++) {
const struct bch_option *opt = bch2_opt_table + id;
if (opt->get_sb == BCH2_NO_SB_OPT)
continue;
bch2_opt_set_by_id(opts, id, bch2_opt_from_sb(sb, id));
}
return 0;
}
void __bch2_opt_set_sb(struct bch_sb *sb, const struct bch_option *opt, u64 v)
{
if (opt->set_sb == SET_BCH2_NO_SB_OPT)
return;
if (opt->flags & OPT_SB_FIELD_SECTORS)
v >>= 9;
if (opt->flags & OPT_SB_FIELD_ILOG2)
v = ilog2(v);
opt->set_sb(sb, v);
}
void bch2_opt_set_sb(struct bch_fs *c, const struct bch_option *opt, u64 v)
{
if (opt->set_sb == SET_BCH2_NO_SB_OPT)
return;
mutex_lock(&c->sb_lock);
__bch2_opt_set_sb(c->disk_sb.sb, opt, v);
bch2_write_super(c);
mutex_unlock(&c->sb_lock);
}
/* io opts: */
struct bch_io_opts bch2_opts_to_inode_opts(struct bch_opts src)
{
return (struct bch_io_opts) {
#define x(_name, _bits) ._name = src._name,
BCH_INODE_OPTS()
#undef x
};
}
bool bch2_opt_is_inode_opt(enum bch_opt_id id)
{
static const enum bch_opt_id inode_opt_list[] = {
#define x(_name, _bits) Opt_##_name,
BCH_INODE_OPTS()
#undef x
};
unsigned i;
for (i = 0; i < ARRAY_SIZE(inode_opt_list); i++)
if (inode_opt_list[i] == id)
return true;
return false;
}