Signed-off-by: Christian Brauner <brauner@xxxxxxxxxx> --- fs/btrfs/Makefile | 2 +- fs/btrfs/disk-io.c | 9 +- fs/btrfs/disk-io.h | 5 +- fs/btrfs/params.c | 1770 ++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/btrfs/params.h | 21 + fs/btrfs/super.c | 1500 +------------------------------------------- fs/btrfs/super.h | 7 +- 7 files changed, 1815 insertions(+), 1499 deletions(-) diff --git a/fs/btrfs/Makefile b/fs/btrfs/Makefile index 90d53209755b..62886b39fa7f 100644 --- a/fs/btrfs/Makefile +++ b/fs/btrfs/Makefile @@ -33,7 +33,7 @@ btrfs-y += super.o ctree.o extent-tree.o print-tree.o root-tree.o dir-item.o \ uuid-tree.o props.o free-space-tree.o tree-checker.o space-info.o \ block-rsv.o delalloc-space.o block-group.o discard.o reflink.o \ subpage.o tree-mod-log.o extent-io-tree.o fs.o messages.o bio.o \ - lru_cache.o + lru_cache.o params.o btrfs-$(CONFIG_BTRFS_FS_POSIX_ACL) += acl.o btrfs-$(CONFIG_BTRFS_FS_CHECK_INTEGRITY) += check-integrity.o diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index fbf9006c6234..20b0df76703d 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -52,6 +52,7 @@ #include "relocation.h" #include "scrub.h" #include "super.h" +#include "params.h" #define BTRFS_SUPER_FLAG_SUPP (BTRFS_HEADER_FLAG_WRITTEN |\ BTRFS_HEADER_FLAG_RELOC |\ @@ -3339,8 +3340,8 @@ int btrfs_check_features(struct btrfs_fs_info *fs_info, bool is_rw_mount) return 0; } -int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_devices, - char *options) +int __cold open_ctree(struct fs_context *fc, struct super_block *sb, + struct btrfs_fs_devices *fs_devices) { u32 sectorsize; u32 nodesize; @@ -3477,8 +3478,8 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size; fs_info->stripesize = stripesize; - ret = btrfs_parse_options(fs_info, options, sb->s_flags); - if (ret) + ret = btrfs_fs_params_verify(fs_info, fc); + if (ret < 0) goto fail_alloc; ret = btrfs_check_features(fs_info, !sb_rdonly(sb)); diff --git a/fs/btrfs/disk-io.h b/fs/btrfs/disk-io.h index 4d5772330110..35d0cb97a1a9 100644 --- a/fs/btrfs/disk-io.h +++ b/fs/btrfs/disk-io.h @@ -45,9 +45,8 @@ void btrfs_clear_oneshot_options(struct btrfs_fs_info *fs_info); int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info); int btrfs_check_super_csum(struct btrfs_fs_info *fs_info, const struct btrfs_super_block *disk_sb); -int __cold open_ctree(struct super_block *sb, - struct btrfs_fs_devices *fs_devices, - char *options); +int __cold open_ctree(struct fs_context *fc, struct super_block *sb, + struct btrfs_fs_devices *fs_devices); void __cold close_ctree(struct btrfs_fs_info *fs_info); int btrfs_validate_super(struct btrfs_fs_info *fs_info, struct btrfs_super_block *sb, int mirror_num); diff --git a/fs/btrfs/params.c b/fs/btrfs/params.c new file mode 100644 index 000000000000..0e90caef2ca4 --- /dev/null +++ b/fs/btrfs/params.c @@ -0,0 +1,1770 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Christian Brauner. + */ + +#include "ctree.h" +#include "defrag.h" +#include "dev-replace.h" +#include "dir-item.h" +#include "discard.h" +#include "disk-io.h" +#include "messages.h" +#include "qgroup.h" +#include "scrub.h" +#include "super.h" +#include "zoned.h" +#include "accessors.h" +#include "params.h" + +enum btrfs_get_tree_t { + BTRFS_FS_CONTEXT_PREPARE, + BTRFS_FS_CONTEXT_SUPER, + BTRFS_FS_CONTEXT_SUBTREE, +}; + +enum space_cache_t { + BTRFS_FS_CONTEXT_SPACE_CACHE_DEFAULT = 0, + BTRFS_FS_CONTEXT_SPACE_CACHE_V1 = 1, + BTRFS_FS_CONTEXT_SPACE_CACHE_V2 = 2, + BTRFS_FS_CONTEXT_SPACE_CACHE_OFF = 3, +}; + +/* Let the btrfs_fs_context hold all mount options. */ +struct btrfs_fs_context { + refcount_t refs; + enum btrfs_get_tree_t phase; + + /* subvolume mount options */ + char *subvol_name; + u64 subvol_id; + struct vfsmount *root_mnt; + + /* generic mount options */ + unsigned long mount_opt; + + u8 has_space_cache:1; + u8 has_compression:1; + u8 has_commit_interval:1; + u8 has_max_inline:1; + u8 has_metadata_ratio:1; + u8 has_clear_cache:1; +#ifdef CONFIG_BTRFS_FS_CHECK_INTEGRITY + u8 has_check_integrity_print_mask:1; + u32 check_integrity_print_mask; +#endif + + enum space_cache_t space_cache; + unsigned long compress_type:4; + unsigned int compress_level; + u32 commit_interval; + u64 max_inline; + u32 metadata_ratio; + u32 thread_pool_size; + + /* device mount options */ + size_t capacity; + size_t nr; + char **device_paths; +}; + +enum { + Opt_acl, + Opt_clear_cache, + Opt_commit_interval, + Opt_compress, + Opt_compress_force, + Opt_degraded, + Opt_device, + Opt_fatal_errors, + Opt_flushoncommit, + Opt_max_inline, + Opt_barrier, + Opt_datacow, + Opt_datasum, + Opt_defrag, + Opt_discard, + Opt_nodiscard, + Opt_norecovery, + Opt_ratio, + Opt_rescan_uuid_tree, + Opt_skip_balance, + Opt_space_cache, + Opt_no_space_cache, + Opt_ssd, + Opt_ssd_spread, + Opt_subvol, + Opt_subvolid, + Opt_thread_pool, + Opt_treelog, + Opt_user_subvol_rm_allowed, + + /* Rescue options */ + Opt_rescue, + Opt_usebackuproot, + Opt_nologreplay, + + /* Deprecated options */ + Opt_recovery, + Opt_inode_cache, + + /* Debugging options */ + Opt_check_integrity, + Opt_check_integrity_including_extent_data, + Opt_check_integrity_print_mask, + Opt_enospc_debug, +#ifdef CONFIG_BTRFS_DEBUG + Opt_fragment, +#endif +#ifdef CONFIG_BTRFS_FS_REF_VERIFY + Opt_ref_verify, +#endif +}; + +enum { + BTRFS_PARAM_RESCUE_USEBACKUPROOT, + BTRFS_PARAM_RESCUE_NOLOGREPLAY, + BTRFS_PARAM_RESCUE_IGNOREBADROOTS, + BTRFS_PARAM_RESCUE_IGNOREDATACSUMS, + BTRFS_PARAM_RESCUE_RESCUE_ALL, +}; + +static const struct constant_table btrfs_rescue_parameters[] = { + { "usebackuproot", BTRFS_PARAM_RESCUE_USEBACKUPROOT }, + { "nologreplay", BTRFS_PARAM_RESCUE_NOLOGREPLAY }, + { "ignorebadroots", BTRFS_PARAM_RESCUE_IGNOREBADROOTS }, + { "ibadroots", BTRFS_PARAM_RESCUE_IGNOREBADROOTS }, + { "ignoredatacsums", BTRFS_PARAM_RESCUE_IGNOREDATACSUMS }, + { "all", BTRFS_PARAM_RESCUE_RESCUE_ALL }, + {} +}; + +#ifdef CONFIG_BTRFS_DEBUG +enum { + BTRFS_PARAM_FRAGMENT_DATA, + BTRFS_PARAM_FRAGMENT_METADATA, + BTRFS_PARAM_FRAGMENT_ALL, +}; + +static const struct constant_table btrfs_fragment_parameters[] = { + { "data", BTRFS_PARAM_FRAGMENT_DATA }, + { "metadata", BTRFS_PARAM_FRAGMENT_METADATA }, + { "all", BTRFS_PARAM_FRAGMENT_ALL }, + {} +}; +#endif + +#define fsparam_string_empty(NAME, OPT) \ + __fsparam(fs_param_is_string, NAME, OPT, fs_param_can_be_empty, NULL) + +const struct fs_parameter_spec btrfs_parameter_spec[] = { + fsparam_flag_no ("acl", Opt_acl), + fsparam_flag ("clear_cache", Opt_clear_cache), + fsparam_u32 ("commit", Opt_commit_interval), + fsparam_flag ("compress", Opt_compress), + fsparam_string ("compress", Opt_compress), + fsparam_flag ("compress-force", Opt_compress_force), + fsparam_string ("compress-force", Opt_compress_force), + fsparam_flag ("degraded", Opt_degraded), + fsparam_string_empty ("device", Opt_device), + fsparam_string ("fatal_errors", Opt_fatal_errors), + fsparam_flag_no ("flushoncommit", Opt_flushoncommit), + fsparam_flag_no ("inode_cache", Opt_inode_cache), + fsparam_string ("max_inline", Opt_max_inline), + fsparam_flag_no ("barrier", Opt_barrier), + fsparam_flag_no ("datacow", Opt_datacow), + fsparam_flag_no ("datasum", Opt_datasum), + fsparam_flag_no ("autodefrag", Opt_defrag), + fsparam_flag ("discard", Opt_discard), + fsparam_string ("discard", Opt_discard), + fsparam_flag ("nodiscard", Opt_nodiscard), + + /* norecovery is deprecated */ + fsparam_flag ("recovery", Opt_recovery), + fsparam_flag ("norecovery", Opt_norecovery), + fsparam_u32 ("metadata_ratio", Opt_ratio), + fsparam_flag ("rescan_uuid_tree", Opt_rescan_uuid_tree), + fsparam_flag ("skip_balance", Opt_skip_balance), + fsparam_flag ("space_cache", Opt_space_cache), + fsparam_string ("space_cache", Opt_space_cache), + fsparam_flag ("nospace_cache", Opt_no_space_cache), + fsparam_flag_no ("ssd", Opt_ssd), + fsparam_flag_no ("ssd_spread", Opt_ssd_spread), + fsparam_flag ("subvol", Opt_subvol), + fsparam_string_empty ("subvol", Opt_subvol), + fsparam_u64 ("subvolid", Opt_subvolid), + fsparam_u32 ("thread_pool", Opt_thread_pool), + fsparam_flag_no ("treelog", Opt_treelog), + fsparam_flag ("user_subvol_rm_allowed", Opt_user_subvol_rm_allowed), + + /* Rescue options */ + fsparam_enum ("rescue", Opt_rescue, btrfs_rescue_parameters), + + /* Deprecated, with alias rescue=nologreplay */ + fsparam_flag ("nologreplay", Opt_nologreplay), + + /* Deprecated, with alias rescue=usebackuproot */ + fsparam_flag ("usebackuproot", Opt_usebackuproot), + + /* Debugging options */ + fsparam_flag ("check_int", Opt_check_integrity), + fsparam_flag ("check_int_data", Opt_check_integrity_including_extent_data), + fsparam_u32 ("check_int_print_mask", Opt_check_integrity_print_mask), + fsparam_flag_no ("enospc_debug", Opt_enospc_debug), + +#ifdef CONFIG_BTRFS_DEBUG + fsparam_enum ("fragment", Opt_fragment, btrfs_fragment_parameters), +#endif +#ifdef CONFIG_BTRFS_FS_REF_VERIFY + fsparam_flag ("ref_verify", Opt_ref_verify), +#endif + + {} +}; + +#define btrfs_param_info(fc, fmt, args...) \ + do { \ + static_assert(__same_type(typeof(fc), struct fs_context *)); \ + btrfs_info(fc->s_fs_info, fmt, ##args); \ + } while (0) + +#define btrfs_param_warn(fc, fmt, args...) \ + do { \ + static_assert(__same_type(typeof(fc), struct fs_context *)); \ + btrfs_warn(fc->s_fs_info, fmt, ##args); \ + } while (0) + +#define btrfs_param_err(fc, fmt, args...) \ + do { \ + static_assert(__same_type(typeof(fc), struct fs_context *)); \ + btrfs_err(fc->s_fs_info, fmt, ##args); \ + } while (0) + +#define btrfs_param_clear_info(fc, ctx, opt, fmt, args...) \ + do { \ + static_assert( \ + __same_type(typeof(ctx), struct btrfs_fs_context *)); \ + if (btrfs_test_opt(ctx, opt)) \ + btrfs_param_info(fc, fmt, ##args); \ + btrfs_clear_opt(ctx->mount_opt, opt); \ + } while (0) + +#define btrfs_param_set_info(fc, ctx, opt, fmt, args...) \ + do { \ + static_assert( \ + __same_type(typeof(ctx), struct btrfs_fs_context *)); \ + if (!btrfs_test_opt(ctx, opt)) \ + btrfs_param_info(fc, fmt, ##args); \ + btrfs_set_opt(ctx->mount_opt, opt); \ + } while (0) + +static void btrfs_fs_context_to_info(struct btrfs_fs_context *ctx, + struct btrfs_fs_info *fs_info) +{ + fs_info->mount_opt = ctx->mount_opt; + + if (ctx->has_metadata_ratio) + fs_info->metadata_ratio = ctx->metadata_ratio; + + if (ctx->has_compression) { + fs_info->compress_type = ctx->compress_type; + fs_info->compress_level = ctx->compress_level; + } + + if (ctx->has_commit_interval) + fs_info->commit_interval = ctx->commit_interval; + + if (ctx->has_max_inline) + fs_info->max_inline = + min_t(u64, ctx->max_inline, fs_info->sectorsize); + + if (ctx->thread_pool_size) + fs_info->thread_pool_size = ctx->thread_pool_size; + +#ifdef CONFIG_BTRFS_FS_CHECK_INTEGRITY + if (ctx->has_check_integrity_print_mask) + fs_info->check_integrity_print_mask = + ctx->check_integrity_print_mask; +#endif +} + +static void btrfs_fs_info_to_context(struct btrfs_fs_context *ctx, + struct btrfs_fs_info *fs_info) +{ + ctx->mount_opt = fs_info->mount_opt; + ctx->compress_type = fs_info->compress_type; + ctx->compress_level = fs_info->compress_level; + ctx->thread_pool_size = fs_info->thread_pool_size; + ctx->commit_interval = fs_info->commit_interval; + ctx->metadata_ratio = fs_info->metadata_ratio; + ctx->max_inline = fs_info->max_inline; +#ifdef CONFIG_BTRFS_FS_CHECK_INTEGRITY + ctx->check_integrity_print_mask = fs_info->check_integrity_print_mask; +#endif +} + +static bool check_ro_option(struct fs_context *fc, struct btrfs_fs_context *ctx, + unsigned long opt, const char *opt_name) +{ + if (!(ctx->mount_opt & opt)) + return false; + + btrfs_param_err(fc, "%s must be used with ro mount option", opt_name); + return true; +} + +int btrfs_fs_params_verify(struct btrfs_fs_info *info, struct fs_context *fc) +{ + struct btrfs_fs_context *ctx = fc->fs_private; + bool is_remount = (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE); + int ret = 0; + + /* + * At this point we've read the superblock from disk and we need + * to take the default space cache options into account. + */ + if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE)) + btrfs_set_opt(ctx->mount_opt, FREE_SPACE_TREE); + else if (btrfs_free_space_cache_v1_active(info)) { + if (btrfs_is_zoned(info)) { + btrfs_param_info(fc, "zoned: clearing existing space cache"); + btrfs_set_super_cache_generation(info->super_copy, 0); + } else { + btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); + } + } + + /* + * To set space cache options we need to have the on-disk + * superblock information around. During mount this needs to + * happen after the on-disk superblock has been read from disk. + * For reconfiguration this has already happened. + */ + if (ctx->has_space_cache) { + switch (ctx->space_cache) { + case BTRFS_FS_CONTEXT_SPACE_CACHE_DEFAULT: + break; + case BTRFS_FS_CONTEXT_SPACE_CACHE_V1: + /* + * We already set FREE_SPACE_TREE above because + * we have compat_ro(FREE_SPACE_TREE) set, and + * we aren't going to allow v1 to be set for + * extent tree v2, simply ignore this setting if + * we're extent tree v2. + */ + if (btrfs_fs_incompat(info, EXTENT_TREE_V2)) + break; + + btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + if (is_remount && !btrfs_test_opt(info, SPACE_CACHE)) + btrfs_param_info(fc, + "enabling disk space caching"); + btrfs_set_opt(ctx->mount_opt, SPACE_CACHE); + break; + case BTRFS_FS_CONTEXT_SPACE_CACHE_V2: + /* + * We already set FREE_SPACE_TREE above because + * we have compat_ro(FREE_SPACE_TREE) set, and + * we aren't going to allow v1 to be set for + * extent tree v2, simply ignore this setting if + * we're extent tree v2. + */ + if (btrfs_fs_incompat(info, EXTENT_TREE_V2)) + break; + + btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); + if (is_remount && + !btrfs_test_opt(info, FREE_SPACE_TREE)) + btrfs_param_info(fc, "enabling free space tree"); + btrfs_set_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + case BTRFS_FS_CONTEXT_SPACE_CACHE_OFF: + /* + * We cannot operate without the free space tree + * with extent tree v2, ignore this option. + */ + if (btrfs_fs_incompat(info, EXTENT_TREE_V2)) + break; + + if (is_remount && btrfs_test_opt(info, SPACE_CACHE)) + btrfs_param_info( + fc, "disabling disk space caching"); + btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE); + + if (is_remount && btrfs_test_opt(info, FREE_SPACE_TREE)) + btrfs_param_info(fc, + "disabling free space tree"); + btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE); + break; + default: + /* not reached */ + WARN_ON(true); + ret = -EINVAL; + } + } + + /* + * We cannot clear the free space tree with extent tree v2, + * ignore this option. + */ + if (!btrfs_fs_incompat(info, EXTENT_TREE_V2) && ctx->has_clear_cache) { + if (is_remount && btrfs_test_opt(info, CLEAR_CACHE)) + btrfs_param_info(fc, "force clearing of disk cache"); + btrfs_set_opt(ctx->mount_opt, CLEAR_CACHE); + } + + if (!(fc->sb_flags & SB_RDONLY)) { + if (check_ro_option(fc, ctx, BTRFS_MOUNT_NOLOGREPLAY, + "nologreplay") || + check_ro_option(fc, ctx, BTRFS_MOUNT_IGNOREBADROOTS, + "ignorebadroots") || + check_ro_option(fc, ctx, BTRFS_MOUNT_IGNOREDATACSUMS, + "ignoredatacsums")) + ret = -EINVAL; + } + + if (ctx->has_compression) { + switch (ctx->compress_type) { + case BTRFS_COMPRESS_LZO: + btrfs_set_fs_incompat(info, COMPRESS_LZO); + break; + case BTRFS_COMPRESS_ZSTD: + btrfs_set_fs_incompat(info, COMPRESS_ZSTD); + break; + } + } + + if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE) && + !btrfs_test_opt(ctx, FREE_SPACE_TREE) && + !btrfs_test_opt(ctx, CLEAR_CACHE)) { + btrfs_param_err(fc, "cannot disable free space tree"); + ret = -EINVAL; + } + if (btrfs_fs_compat_ro(info, BLOCK_GROUP_TREE) && + !btrfs_test_opt(ctx, FREE_SPACE_TREE)) { + btrfs_param_err(fc, + "cannot disable free space tree with block-group-tree feature"); + ret = -EINVAL; + } + if (!ret) + ret = btrfs_check_mountopts_zoned(info); + if (!ret && !is_remount) { + if (btrfs_test_opt(ctx, SPACE_CACHE)) + btrfs_param_info(fc, "disk space caching is enabled"); + if (btrfs_test_opt(ctx, FREE_SPACE_TREE)) + btrfs_param_info(fc, "using free space tree"); + } + + /* + * Copy mount options over into superblock. On failure the old + * values will be restored. Technically, this can and should at + * some point be rewritten to only rely on @btrfs_fs_context and + * to defer committing the new values after all validation has + * been done. This would work but would mean a lot more work. + */ + btrfs_fs_context_to_info(ctx, info); + return ret; +} + +static void btrfs_parse_param_drop_devices(struct btrfs_fs_context *ctx) +{ + for (size_t nr = 0; nr < ctx->nr; nr++) { + kfree(ctx->device_paths[nr]); + ctx->device_paths[nr] = NULL; + } + ctx->nr = 0; +} + +/* + * Currently we parse the device names. What we should really do in the + * future is to resolve these device names to paths and stash paths + * here. And then resolve those paths to block devices during + * ->get_tree(). + */ +static int btrfs_parse_param_device(struct fs_parameter *param, + struct fs_context *fc) +{ + struct btrfs_fs_context *ctx = fc->fs_private; + char **pp; + + if (!*param->string) { + btrfs_parse_param_drop_devices(ctx); + return 0; + } + + if (ctx->nr + 1 >= ctx->capacity) { + pp = krealloc_array(ctx->device_paths, ctx->nr + 1, + sizeof(*ctx->device_paths), + GFP_KERNEL_ACCOUNT); + if (!pp) + return -ENOMEM; + + ctx->device_paths = pp; + ctx->capacity = ctx->nr + 1; + } + + /* + * It's legitimate to steal the parameter's value. We just need + * to make sure to NULL it. + */ + ctx->device_paths[ctx->nr] = param->string; + param->string = NULL; + ctx->nr++; + return 0; +} + +static inline bool btrfs_param_is_flag(const struct fs_parameter *param) +{ + return param->type == fs_value_is_flag; +} + +static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) +{ + struct btrfs_fs_context *ctx = fc->fs_private; + bool is_remount = (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE); + struct fs_parse_result result; + char *compress_type; + bool compress_force = false, saved_compress_type, saved_compress_force, + saved_compress_level; + int no_compress = 0; + char *dup = NULL; + int ret = 0; + int opt; + + opt = fs_parse(fc, btrfs_parameter_spec, param, &result); + if (opt < 0) + return opt; + + switch (opt) { + case Opt_degraded: + btrfs_param_info(fc, "allowing degraded mounts"); + btrfs_set_opt(ctx->mount_opt, DEGRADED); + break; + case Opt_subvol: + if (is_remount && !fc->oldapi) + return -EINVAL; + + /* "subvol", just ignore */ + if (btrfs_param_is_flag(param)) + break; + + if (*param->string) { + dup = kstrdup(param->string, GFP_KERNEL); + if (!dup) { + ret = -ENOMEM; + break; + } + } + kfree(ctx->subvol_name); + ctx->subvol_name = dup; + break; + case Opt_subvolid: + if (is_remount && !fc->oldapi) + return -EINVAL; + + if (result.uint_64 == 0) + ctx->subvol_id = BTRFS_FS_TREE_OBJECTID; + else + ctx->subvol_id = result.uint_64; + break; + case Opt_device: + if (is_remount && !fc->oldapi) + ret = -EINVAL; + else + ret = btrfs_parse_param_device(param, fc); + break; + case Opt_datasum: + if (!result.negated) { + if (btrfs_test_opt(ctx, NODATASUM)) { + if (btrfs_test_opt(ctx, NODATACOW)) + btrfs_param_info(fc, "setting datasum, datacow enabled"); + else + btrfs_param_info(fc, "setting datasum"); + } + btrfs_clear_opt(ctx->mount_opt, NODATACOW); + btrfs_clear_opt(ctx->mount_opt, NODATASUM); + } else { + btrfs_param_set_info(fc, ctx, NODATASUM, "setting nodatasum"); + } + break; + case Opt_datacow: + if (!result.negated) { + btrfs_param_clear_info(fc, ctx, NODATACOW, "setting datacow"); + } else { + if (!btrfs_test_opt(ctx, NODATACOW)) { + if (!btrfs_test_opt(ctx, COMPRESS) || + !btrfs_test_opt(ctx, FORCE_COMPRESS)) { + btrfs_param_info(fc, "setting nodatacow, compression disabled"); + } else { + btrfs_param_info(fc, "setting nodatacow"); + } + } + btrfs_clear_opt(ctx->mount_opt, COMPRESS); + btrfs_clear_opt(ctx->mount_opt, FORCE_COMPRESS); + btrfs_set_opt(ctx->mount_opt, NODATACOW); + btrfs_set_opt(ctx->mount_opt, NODATASUM); + } + break; + case Opt_compress_force: + compress_force = true; + fallthrough; + case Opt_compress: + saved_compress_type = btrfs_test_opt(ctx, COMPRESS) ? + ctx->compress_type : + BTRFS_COMPRESS_NONE; + saved_compress_force = btrfs_test_opt(ctx, FORCE_COMPRESS); + saved_compress_level = ctx->compress_level; + + if (btrfs_param_is_flag(param) || + strncmp(param->string, "zlib", 4) == 0) { + compress_type = "zlib"; + + ctx->compress_type = BTRFS_COMPRESS_ZLIB; + ctx->compress_level = BTRFS_ZLIB_DEFAULT_LEVEL; + /* + * args[0] contains uninitialized data since + * for these tokens we don't expect any + * parameter. + */ + if (!btrfs_param_is_flag(param)) + ctx->compress_level = btrfs_compress_str2level( + BTRFS_COMPRESS_ZLIB, param->string + 4); + btrfs_set_opt(ctx->mount_opt, COMPRESS); + btrfs_clear_opt(ctx->mount_opt, NODATACOW); + btrfs_clear_opt(ctx->mount_opt, NODATASUM); + no_compress = 0; + } else if (strncmp(param->string, "lzo", 3) == 0) { + compress_type = "lzo"; + ctx->compress_type = BTRFS_COMPRESS_LZO; + ctx->compress_level = 0; + btrfs_set_opt(ctx->mount_opt, COMPRESS); + btrfs_clear_opt(ctx->mount_opt, NODATACOW); + btrfs_clear_opt(ctx->mount_opt, NODATASUM); + no_compress = 0; + } else if (strncmp(param->string, "zstd", 4) == 0) { + compress_type = "zstd"; + ctx->compress_type = BTRFS_COMPRESS_ZSTD; + ctx->compress_level = btrfs_compress_str2level( + BTRFS_COMPRESS_ZSTD, param->string + 4); + btrfs_set_opt(ctx->mount_opt, COMPRESS); + btrfs_clear_opt(ctx->mount_opt, NODATACOW); + btrfs_clear_opt(ctx->mount_opt, NODATASUM); + no_compress = 0; + } else if (strncmp(param->string, "no", 2) == 0) { + compress_type = "no"; + ctx->compress_level = 0; + ctx->compress_type = 0; + btrfs_clear_opt(ctx->mount_opt, COMPRESS); + btrfs_clear_opt(ctx->mount_opt, FORCE_COMPRESS); + compress_force = false; + no_compress++; + } else { + btrfs_param_warn(fc, "unrecognized compression value %s", + param->string); + ret = -EINVAL; + break; + } + + if (compress_force) { + btrfs_set_opt(ctx->mount_opt, FORCE_COMPRESS); + } else { + /* + * If we remount from compress-force=xxx to + * compress=xxx, we need clear FORCE_COMPRESS + * flag, otherwise, there is no way for users + * to disable forcible compression separately. + */ + btrfs_clear_opt(ctx->mount_opt, FORCE_COMPRESS); + } + if (no_compress == 1) { + btrfs_param_info(fc, "use no compression"); + } else if ((ctx->compress_type != saved_compress_type) || + (compress_force != saved_compress_force) || + (ctx->compress_level != saved_compress_level)) { + btrfs_param_info(fc, "%s %s compression, level %d", + (compress_force) ? "force" : "use", + compress_type, ctx->compress_level); + } + ctx->has_compression = 1; + compress_force = false; + break; + case Opt_ssd: + if (!result.negated) { + btrfs_param_set_info(fc, ctx, SSD, + "enabling ssd optimizations"); + btrfs_clear_opt(ctx->mount_opt, NOSSD); + } else { + btrfs_set_opt(ctx->mount_opt, NOSSD); + btrfs_param_clear_info(fc, ctx, SSD, + "not using ssd optimizations"); + /* also clear Opt_ssd_spread */ + btrfs_param_clear_info(fc, ctx, SSD_SPREAD, + "not using spread ssd allocation scheme"); + } + break; + case Opt_ssd_spread: + if (!result.negated) { + btrfs_param_set_info(fc, ctx, SSD, + "enabling ssd optimizations"); + btrfs_param_set_info(fc, ctx, SSD_SPREAD, + "using spread ssd allocation scheme"); + btrfs_clear_opt(ctx->mount_opt, NOSSD); + } else { + btrfs_param_clear_info(fc, ctx, SSD_SPREAD, + "not using spread ssd allocation scheme"); + } + break; + case Opt_barrier: + if (!result.negated) + btrfs_param_clear_info(fc, ctx, NOBARRIER, + "turning on barriers"); + else + btrfs_param_set_info(fc, ctx, NOBARRIER, + "turning off barriers"); + break; + case Opt_thread_pool: + if (result.uint_32 == 0) { + btrfs_param_err(fc, "invalid value 0 for thread_pool"); + ret = -EINVAL; + break; + } + ctx->thread_pool_size = result.uint_32; + break; + case Opt_max_inline: + dup = kstrdup(param->string, GFP_KERNEL); + if (!dup) { + ret = -ENOMEM; + break; + } + ctx->has_max_inline = 1; + ctx->max_inline = memparse(dup, NULL); + kfree(dup); + btrfs_param_info(fc, "max_inline at %llu", ctx->max_inline); + break; + case Opt_acl: + if (!result.negated) { +#ifdef CONFIG_BTRFS_FS_POSIX_ACL + fc->sb_flags |= SB_POSIXACL; +#else + btrfs_param_err(err, "support for ACL not compiled in!"); + ret = -EINVAL; +#endif + break; + } + + fc->sb_flags &= ~SB_POSIXACL; + break; + case Opt_treelog: + if (!result.negated) + btrfs_param_clear_info(fc, ctx, NOTREELOG, + "enabling tree log"); + else + btrfs_param_set_info(fc, ctx, NOTREELOG, + "disabling tree log"); + break; + case Opt_norecovery: + fallthrough; + case Opt_nologreplay: + btrfs_param_warn(fc, "'nologreplay' is deprecated, use 'rescue=nologreplay' instead"); + btrfs_param_set_info(fc, ctx, NOLOGREPLAY, + "disabling log replay at mount time"); + break; + case Opt_flushoncommit: + if (!result.negated) + btrfs_param_set_info(fc, ctx, FLUSHONCOMMIT, + "turning on flush-on-commit"); + else + btrfs_param_clear_info(fc, ctx, FLUSHONCOMMIT, + "turning off flush-on-commit"); + break; + case Opt_ratio: + ctx->has_metadata_ratio = 1; + ctx->metadata_ratio = result.uint_32; + btrfs_param_info(fc, "metadata ratio %u", ctx->metadata_ratio); + break; + case Opt_nodiscard: + btrfs_param_clear_info(fc, ctx, DISCARD_SYNC, + "turning off discard"); + btrfs_param_clear_info(fc, ctx, DISCARD_ASYNC, + "turning off async discard"); + btrfs_set_opt(ctx->mount_opt, NODISCARD); + break; + case Opt_discard: + if (btrfs_param_is_flag(param) || + strcmp(param->string, "sync") == 0) { + btrfs_clear_opt(ctx->mount_opt, DISCARD_ASYNC); + btrfs_param_set_info(fc, ctx, DISCARD_SYNC, + "turning on sync discard"); + } else if (strcmp(param->string, "async") == 0) { + btrfs_clear_opt(ctx->mount_opt, DISCARD_SYNC); + btrfs_param_set_info(fc, ctx, DISCARD_ASYNC, + "turning on async discard"); + } else { + btrfs_param_err(fc, "unrecognized discard mode value %s", + param->string); + ret = -EINVAL; + break; + } + btrfs_clear_opt(ctx->mount_opt, NODISCARD); + break; + case Opt_no_space_cache: + ctx->has_space_cache = 1; + ctx->space_cache = BTRFS_FS_CONTEXT_SPACE_CACHE_OFF; + if (btrfs_test_opt(ctx, SPACE_CACHE)) + btrfs_param_clear_info(fc, ctx, SPACE_CACHE, + "disabling disk space caching"); + if (btrfs_test_opt(ctx, FREE_SPACE_TREE)) + btrfs_param_clear_info(fc, ctx, FREE_SPACE_TREE, + "disabling free space tree"); + break; + case Opt_space_cache: + if (btrfs_param_is_flag(param) || + strcmp(param->string, "v1") == 0) { + ctx->has_space_cache = 1; + ctx->space_cache = BTRFS_FS_CONTEXT_SPACE_CACHE_V1; + } else if (strcmp(param->string, "v2") == 0) { + ctx->has_space_cache = 1; + ctx->space_cache = BTRFS_FS_CONTEXT_SPACE_CACHE_V2; + } else { + btrfs_param_err(fc, "unrecognized space_cache value %s", + param->string); + ret = -EINVAL; + break; + } + break; + case Opt_rescan_uuid_tree: + btrfs_set_opt(ctx->mount_opt, RESCAN_UUID_TREE); + break; + case Opt_inode_cache: + btrfs_param_warn(fc, "the 'inode_cache' option is deprecated and has no effect since 5.11"); + break; + case Opt_clear_cache: + ctx->has_clear_cache = 1; + break; + case Opt_user_subvol_rm_allowed: + btrfs_set_opt(ctx->mount_opt, USER_SUBVOL_RM_ALLOWED); + break; + case Opt_enospc_debug: + if (!result.negated) + btrfs_set_opt(ctx->mount_opt, ENOSPC_DEBUG); + else + btrfs_clear_opt(ctx->mount_opt, ENOSPC_DEBUG); + break; + case Opt_defrag: + if (!result.negated) + btrfs_param_set_info(fc, ctx, AUTO_DEFRAG, + "enabling auto defrag"); + else + btrfs_param_clear_info(fc, ctx, AUTO_DEFRAG, + "disabling auto defrag"); + break; + case Opt_recovery: + fallthrough; + case Opt_usebackuproot: + btrfs_param_warn(fc, + "'%s' is deprecated, use 'rescue=usebackuproot' instead", + opt == Opt_recovery ? "recovery" : "usebackuproot"); + btrfs_param_info(fc, "trying to use backup root at mount time"); + btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); + break; + case Opt_skip_balance: + btrfs_set_opt(ctx->mount_opt, SKIP_BALANCE); + break; +#ifdef CONFIG_BTRFS_FS_CHECK_INTEGRITY + case Opt_check_integrity_including_extent_data: + btrfs_param_info(fc, "enabling check integrity including extent data"); + btrfs_set_opt(ctx->mount_opt, CHECK_INTEGRITY_DATA); + btrfs_set_opt(ctx->mount_opt, CHECK_INTEGRITY); + break; + case Opt_check_integrity: + btrfs_param_info(fc, "enabling check integrity"); + btrfs_set_opt(ctx->mount_opt, CHECK_INTEGRITY); + break; + case Opt_check_integrity_print_mask: + ctx->has_check_integrity_print_mask = 1; + ctx->check_integrity_print_mask = result.uint_32; + btrfs_param_info(fc, "check_integrity_print_mask 0x%x", + ctx->check_integrity_print_mask); + break; +#else + case Opt_check_integrity_including_extent_data: + fallthrough; + case Opt_check_integrity: + fallthrough; + case Opt_check_integrity_print_mask: + btrfs_param_err(fc, "support for check_integrity* not compiled in!"); + ret = -EINVAL; + break; +#endif + case Opt_fatal_errors: + if (strcmp(param->string, "panic") == 0) { + btrfs_set_opt(ctx->mount_opt, PANIC_ON_FATAL_ERROR); + } else if (strcmp(param->string, "bug") == 0) { + btrfs_clear_opt(ctx->mount_opt, PANIC_ON_FATAL_ERROR); + } else { + btrfs_param_err(fc, "unrecognized fatal_errors value %s", + param->string); + ret = -EINVAL; + } + break; + case Opt_commit_interval: + ctx->has_commit_interval = 1; + if (result.uint_32 == 0) { + btrfs_param_info(fc, "using default commit interval %us", + BTRFS_DEFAULT_COMMIT_INTERVAL); + ctx->commit_interval = BTRFS_DEFAULT_COMMIT_INTERVAL; + break; + } + + if (result.uint_32 > 300) + btrfs_param_warn(fc, "excessive commit interval %d", + result.uint_32); + + ctx->commit_interval = result.uint_32; + break; + case Opt_rescue: + switch (result.uint_32) { + case BTRFS_PARAM_RESCUE_USEBACKUPROOT: + btrfs_param_info(fc, "trying to use backup root at mount time"); + btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); + break; + case BTRFS_PARAM_RESCUE_NOLOGREPLAY: + btrfs_param_set_info(fc, ctx, NOLOGREPLAY, + "disabling log replay at mount time"); + break; + case BTRFS_PARAM_RESCUE_IGNOREBADROOTS: + btrfs_param_set_info(fc, ctx, IGNOREBADROOTS, + "ignoring bad roots"); + break; + case BTRFS_PARAM_RESCUE_IGNOREDATACSUMS: + btrfs_param_set_info(fc, ctx, IGNOREDATACSUMS, + "ignoring data csums"); + break; + case BTRFS_PARAM_RESCUE_RESCUE_ALL: + btrfs_param_info(fc, + "enabling all of the rescue options"); + btrfs_param_set_info(fc, ctx, IGNOREDATACSUMS, + "ignoring data csums"); + btrfs_param_set_info(fc, ctx, IGNOREBADROOTS, + "ignoring bad roots"); + btrfs_param_set_info(fc, ctx, NOLOGREPLAY, + "disabling log replay at mount time"); + break; + default: + /* not reached */ + WARN_ON(true); + ret = -EINVAL; + } + break; +#ifdef CONFIG_BTRFS_DEBUG + case Opt_fragment: + switch (result.uint_32) { + case BTRFS_PARAM_FRAGMENT_DATA: + btrfs_param_info(fc, "fragmenting data"); + btrfs_set_opt(ctx->mount_opt, FRAGMENT_DATA); + break; + case BTRFS_PARAM_FRAGMENT_METADATA: + btrfs_param_info(fc, "fragmenting metadata"); + btrfs_set_opt(ctx->mount_opt, FRAGMENT_METADATA); + break; + case BTRFS_PARAM_FRAGMENT_ALL: + btrfs_param_info(fc, "fragmenting all space"); + btrfs_set_opt(ctx->mount_opt, FRAGMENT_DATA); + btrfs_set_opt(ctx->mount_opt, FRAGMENT_METADATA); + break; + default: + /* not reached */ + WARN_ON(true); + ret = -EINVAL; + } + break; +#endif +#ifdef CONFIG_BTRFS_FS_REF_VERIFY + case Opt_ref_verify: + btrfs_param_info(fc, "doing ref verification"); + btrfs_set_opt(ctx->mount_opt, REF_VERIFY); + break; +#endif + default: + /* We have a bug in the VFS parser. */ + WARN_ON(true); + ret = -EINVAL; + } + + return ret; +} + +static int btrfs_reconfigure(struct fs_context *fc); + +static int btrfs_legacy_reconfigure(struct fs_context *root_fc) +{ + int ret; + struct btrfs_fs_context *ctx = root_fc->fs_private; + struct vfsmount *root_mnt = ctx->root_mnt; + + root_fc->sb_flags &= ~SB_RDONLY; + down_write(&root_mnt->mnt_sb->s_umount); + ret = btrfs_reconfigure(root_fc); + up_write(&root_mnt->mnt_sb->s_umount); + return ret; +} + +static int get_default_subvol_objectid(struct btrfs_fs_info *fs_info, u64 *objectid) +{ + struct btrfs_root *root = fs_info->tree_root; + struct btrfs_dir_item *di; + struct btrfs_path *path; + struct btrfs_key location; + struct fscrypt_str name = FSTR_INIT("default", 7); + u64 dir_id; + + path = btrfs_alloc_path(); + if (!path) + return -ENOMEM; + + /* + * Find the "default" dir item which points to the root item that we + * will mount by default if we haven't been given a specific subvolume + * to mount. + */ + dir_id = btrfs_super_root_dir(fs_info->super_copy); + di = btrfs_lookup_dir_item(NULL, root, path, dir_id, &name, 0); + if (IS_ERR(di)) { + btrfs_free_path(path); + return PTR_ERR(di); + } + if (!di) { + /* + * Ok the default dir item isn't there. This is weird since + * it's always been there, but don't freak out, just try and + * mount the top-level subvolume. + */ + btrfs_free_path(path); + *objectid = BTRFS_FS_TREE_OBJECTID; + return 0; + } + + btrfs_dir_item_key_to_cpu(path->nodes[0], di, &location); + btrfs_free_path(path); + *objectid = location.objectid; + return 0; +} + +/* + * subvolumes are identified by ino 256 + */ +static inline int is_subvolume_inode(struct inode *inode) +{ + if (inode && inode->i_ino == BTRFS_FIRST_FREE_OBJECTID) + return 1; + return 0; +} + +static struct dentry *mount_subvol(struct fs_context *fc) +{ + struct btrfs_fs_context *ctx = fc->fs_private; + struct super_block *s = ctx->root_mnt->mnt_sb; + struct btrfs_fs_info *fs_info = btrfs_sb(s); + struct inode *root_inode; + u64 root_objectid; + struct dentry *root; + int ret; + + if (WARN_ON(ctx->phase != BTRFS_FS_CONTEXT_SUBTREE)) + return ERR_PTR(-EINVAL); + + if (!ctx->subvol_name) { + if (!ctx->subvol_id) { + ret = get_default_subvol_objectid(fs_info, + &ctx->subvol_id); + if (ret) + return ERR_PTR(ret); + } + ctx->subvol_name = btrfs_get_subvol_name_from_objectid( + fs_info, ctx->subvol_id); + if (IS_ERR(ctx->subvol_name)) { + root = ERR_CAST(ctx->subvol_name); + ctx->subvol_name = NULL; + return root; + } + + } + + root = mount_subtree(ctx->root_mnt, ctx->subvol_name); + ctx->root_mnt = NULL; /* @ctx->root_mnt has been consumed */ + if (IS_ERR(root)) + return root; + + root_inode = d_inode(root); + root_objectid = BTRFS_I(root_inode)->root->root_key.objectid; + + ret = 0; + if (!is_subvolume_inode(root_inode)) { + btrfs_err(fs_info, "'%s' is not a valid subvolume", + ctx->subvol_name); + ret = -EINVAL; + } + if (ctx->subvol_id && root_objectid != ctx->subvol_id) { + /* + * This will also catch a race condition where a + * subvolume which was passed by ID is renamed and + * another subvolume is renamed over the old location. + */ + btrfs_err(fs_info, "subvol '%s' does not match subvolid %llu", + ctx->subvol_name, ctx->subvol_id); + ret = -EINVAL; + } + if (ret) { + dput(root); + root = ERR_PTR(ret); + /* + * On successful mount_subtree() we'll trade vfsmount + * reference for a super block reference and will have + * locked the super block again after fc_mount() + * unlocked it. + */ + deactivate_locked_super(s); + } + + return root; +} + +static int btrfs_get_tree_common(struct fs_context *fc) +{ + struct vfsmount *root_mnt = NULL; + struct fs_context *root_fc; + struct dentry *root_dentry; + struct btrfs_fs_context *ctx = fc->fs_private; + int ret; + + if (WARN_ON(ctx->phase != BTRFS_FS_CONTEXT_PREPARE)) + return -EINVAL; + + root_fc = vfs_dup_fs_context(fc); + if (IS_ERR(root_fc)) + return PTR_ERR(root_fc); + + /* + * We've duplicated the security mount options above and we only + * need them to be set when we really create a new superblock. + * They're irrelevant when we mount the subvolume as the + * superblock does already exist at that point. So free the + * security blob here. + */ + security_free_mnt_opts(&fc->security); + fc->security = NULL; + + /* Create the superblock so we can mount a subtree later. */ + ctx->phase = BTRFS_FS_CONTEXT_SUPER; + + root_mnt = fc_mount(root_fc); + if (PTR_ERR_OR_ZERO(root_mnt) == -EBUSY) { + bool ro2rw = !(root_fc->sb_flags & SB_RDONLY); + + if (ro2rw) + root_fc->sb_flags |= SB_RDONLY; + else + root_fc->sb_flags &= ~SB_RDONLY; + + root_mnt = fc_mount(root_fc); + if (IS_ERR(root_mnt)) { + put_fs_context(root_fc); + return PTR_ERR(root_mnt); + } + ctx->root_mnt = root_mnt; + + /* + * Ever since commit 0723a0473fb4 ("btrfs: allow + * mounting btrfs subvolumes with different ro/rw + * options") the following works: + * + * (i) mount /dev/sda3 -o subvol=foo,ro /mnt/foo + * (ii) mount /dev/sda3 -o subvol=bar,rw /mnt/bar + * + * which looks nice and innocent but is actually pretty + * intricate and deserves a long comment. + * + * On another filesystem a subvolume mount is close to + * something like: + * + * (iii) # create rw superblock + initial mount + * mount -t xfs /dev/sdb /opt/ + * + * # create ro bind mount + * mount --bind -o ro /opt/foo /mnt/foo + * + * # unmount initial mount + * umount /opt + * + * Of course, there's some special subvolume sauce and + * there's the fact that the sb->s_root dentry is really + * swapped after mount_subtree(). But conceptually it's + * very close and will help us understand the issue. + * + * The old mount api didn't cleanly distinguish between + * a mount being made ro and a superblock being made ro. + * The only way to change the ro state of either object + * was by passing MS_RDONLY. If a new mount was created + * via mount(2) such as: + * + * mount("/dev/sdb", "/mnt", "xfs", MS_RDONLY, NULL); + * + * the MS_RDONLY flag being specified had two effects: + * + * (1) MNT_READONLY was raised -> the resulting mount + * got @mnt->mnt_flags |= MNT_READONLY raised. + * + * (2) MS_RDONLY was passed to the filesystem's mount + * method and the filesystems made the superblock + * ro. Note, how SB_RDONLY has the same value as + * MS_RDONLY and is raised whenever MS_RDONLY is + * passed through mount(2). + * + * Creating a subtree mount via (iii) ends up leaving a + * rw superblock with a subtree mounted ro. + * + * But consider the effect on the old mount api on btrfs + * subvolume mounting which combines the distinct step + * in (iii) into a a single step. + * + * By issuing (i) both the mount and the superblock are + * turned ro. Now when (ii) is issued the superblock is + * ro and thus even if the mount created for (ii) is rw + * it wouldn't help. Hence, btrfs needed to transition + * the superblock from ro to rw for (ii) which it did + * using an internal remount call (a bold choice...). + * + * IOW, subvolume mounting was inherently messy due to + * the ambiguity of MS_RDONLY in mount(2). Note, this + * ambiguity has mount(8) always translate "ro" to + * MS_RDONLY. IOW, in both (i) and (ii) "ro" becomes + * MS_RDONLY when passed by mount(8) to mount(2). + * + * Enter the new mount api. The new mount api + * disambiguates making a mount ro and making a + * superblock ro. + * + * (3) To turn a mount ro the MOUNT_ATTR_RDONLY flag can + * be used with either fsmount() or mount_setattr(). + * This is a pure VFS level change for a specific + * mount or mount tree that is never seen by the + * filesystem itself. + * + * (4) To turn a superblock ro the "ro" flag must be + * used with fsconfig(FSCONFIG_SET_FLAG, "ro"). This + * option is seen by the filesytem in fc->sb_flags. + * + * This disambiguation has rather positive consequences. + * Mounting a subvolume ro will not also turn the + * superblock ro. Only the mount for the subvolume will + * become ro. + * + * So, if the superblock creation request comes from the + * new mount api the caller must've explicitly done: + * + * fsconfig(FSCONFIG_SET_FLAG, "ro") + * fsmount/mount_setattr(MOUNT_ATTR_RDONLY) + * + * IOW, at some point the caller must have explicitly + * turned the whole superblock ro and we shouldn't just + * undo it like we did for the old mount api. In any + * case, it lets us avoid this nasty hack in the new + * mount api. + * + * Consequently, the remounting hack must only be used + * for requests originating from the old mount api and + * should be marked for full deprecation so it can be + * turned off in a couple of years. + * + * The new mount api has no reason to support this hack. + */ + if (root_fc->oldapi && ro2rw) { + /* + * This magic internal remount is a pretty bold + * move as the VFS reserves the right to protect + * ro->rw transitions on the VFS layer similar + * to how it protects rw->ro transitions. + */ + ret = btrfs_legacy_reconfigure(root_fc); + if (ret) + root_mnt = ERR_PTR(ret); + } + } + put_fs_context(root_fc); + if (IS_ERR(root_mnt)) + return PTR_ERR(root_mnt); + ctx->root_mnt = root_mnt; + + root_dentry = mount_subvol(fc); + if (IS_ERR(root_dentry)) + return PTR_ERR(root_dentry); + + fc->root = root_dentry; + return 0; +} + +static int btrfs_test_super(struct super_block *s, struct fs_context *fc) +{ + struct btrfs_fs_info *p = fc->s_fs_info; + struct btrfs_fs_info *fs_info = btrfs_sb(s); + + return fs_info->fs_devices == p->fs_devices; +} + +static int btrfs_set_super(struct super_block *s, struct fs_context *fc) +{ + return set_anon_super_fc(s, fc->s_fs_info); +} + +static int btrfs_get_tree_super(struct fs_context *fc) +{ + struct btrfs_fs_context *ctx = fc->fs_private; + struct btrfs_fs_info *fs_info = fc->s_fs_info; + struct block_device *bdev = NULL; + struct super_block *s; + struct btrfs_device *device = NULL; + struct btrfs_fs_devices *fs_devices = NULL; + blk_mode_t mode = sb_open_mode(fc->sb_flags); + int error = 0; + + if (WARN_ON(ctx->phase != BTRFS_FS_CONTEXT_SUPER)) + return -EINVAL; + + fs_info->super_copy = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL); + fs_info->super_for_commit = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL); + if (!fs_info->super_copy || !fs_info->super_for_commit) + return -ENOMEM; + + mutex_lock(&uuid_mutex); + + for (size_t nr = 0; nr < ctx->nr; nr++) { + device = btrfs_scan_one_device(ctx->device_paths[nr], mode); + if (IS_ERR(device)) { + mutex_unlock(&uuid_mutex); + return -ENOMEM; + } + } + + device = btrfs_scan_one_device(fc->source, mode); + if (IS_ERR(device)) { + mutex_unlock(&uuid_mutex); + return PTR_ERR(device); + } + + fs_devices = device->fs_devices; + fs_info->fs_devices = fs_devices; + + error = btrfs_open_devices(fs_devices, mode, &btrfs_fs_type); + mutex_unlock(&uuid_mutex); + if (error) + return error; + + if (!(fc->sb_flags & SB_RDONLY) && fs_devices->rw_devices == 0) { + error = -EACCES; + goto error_close_devices; + } + + bdev = fs_devices->latest_dev->bdev; + + fc->sb_flags |= SB_NOSEC; + /* + * If a new superblock is allocated then fc->s_fs_info will have + * been transfered to sb->s_fs_info and will be cleaned up by + * ->kill_sb() if we fail afterwards. + * + * If no matching or an existing superblock is found + * fc->s_fs_info will be left alone and cleaned up during + * btrfs_free(). + */ + s = sget_fc(fc, btrfs_test_super, btrfs_set_super); + if (IS_ERR(s)) { + error = PTR_ERR(s); + goto error_close_devices; + } + + if (s->s_root) { + btrfs_close_devices(fs_devices); + if ((fc->sb_flags ^ s->s_flags) & SB_RDONLY) + error = -EBUSY; + } else { + snprintf(s->s_id, sizeof(s->s_id), "%pg", bdev); + shrinker_debugfs_rename(&s->s_shrink, "sb-%s:%s", btrfs_fs_type.name, + s->s_id); + btrfs_sb(s)->bdev_holder = &btrfs_fs_type; + error = btrfs_fill_super(fc, s, fs_devices); + } + if (error) { + /* we can rely on @fs_devices having been closed */ + deactivate_locked_super(s); + return error; + } + + fc->root = dget(s->s_root); + ctx->phase = BTRFS_FS_CONTEXT_SUBTREE; + return 0; + +error_close_devices: + btrfs_close_devices(fs_devices); + return error; +} + +int btrfs_get_tree(struct fs_context *fc) +{ + struct btrfs_fs_context *ctx = fc->fs_private; + + if (ctx->phase == BTRFS_FS_CONTEXT_SUPER) + return btrfs_get_tree_super(fc); + + return btrfs_get_tree_common(fc); +} + +static int btrfs_dup_fs_context(struct fs_context *fc, struct fs_context *src_fc) +{ + struct btrfs_fs_context *ctx = src_fc->fs_private; + struct btrfs_fs_info *fs_info; + + /* + * Setup a dummy root and fs_info for test/set super. This is + * because we don't actually fill this stuff out until + * open_ctree, but we need then open_ctree will properly + * initialize the file system specific settings later. + * btrfs_init_fs_info initializes the static elements of the + * fs_info (locks and such) to make cleanup easier if we find a + * superblock with our given fs_devices later on at sget_fc() + * time. + */ + fs_info = kvzalloc(sizeof(struct btrfs_fs_info), GFP_KERNEL); + if (!fs_info) + return -ENOMEM; + btrfs_init_fs_info(fs_info); + + refcount_inc(&ctx->refs); + + /* + * Steal the fs_context::source from original context. We only + * need it to create the superblock, but not for the subtree. + */ + fc->source = src_fc->source; + src_fc->source = NULL; + fc->fs_private = ctx; + fc->s_fs_info = fs_info; + + return 0; +} + +static inline void btrfs_free_fs_context_private(struct btrfs_fs_context *ctx) +{ + if (refcount_dec_and_test(&ctx->refs)) { + mntput(ctx->root_mnt); + btrfs_parse_param_drop_devices(ctx); + kfree(ctx->subvol_name); + kfree(ctx); + } +} + +static void btrfs_free_fs_context(struct fs_context *fc) +{ + struct btrfs_fs_context *ctx = fc->fs_private; + struct btrfs_fs_info *fs_info = fc->s_fs_info; + + if (ctx) + btrfs_free_fs_context_private(ctx); + + if (fs_info) + btrfs_free_fs_info(fs_info); +} + +static inline void btrfs_remount_begin(struct btrfs_fs_info *fs_info, + unsigned long old_opts, int flags) +{ + if (btrfs_raw_test_opt(old_opts, AUTO_DEFRAG) && + (!btrfs_raw_test_opt(fs_info->mount_opt, AUTO_DEFRAG) || + (flags & SB_RDONLY))) { + /* wait for any defraggers to finish */ + wait_event(fs_info->transaction_wait, + (atomic_read(&fs_info->defrag_running) == 0)); + if (flags & SB_RDONLY) + sync_filesystem(fs_info->sb); + } +} + +static inline void btrfs_remount_cleanup(struct btrfs_fs_info *fs_info, + unsigned long old_opts) +{ + const bool cache_opt = btrfs_test_opt(fs_info, SPACE_CACHE); + + /* + * We need to cleanup all defragable inodes if the autodefragment is + * close or the filesystem is read only. + */ + if (btrfs_raw_test_opt(old_opts, AUTO_DEFRAG) && + (!btrfs_raw_test_opt(fs_info->mount_opt, AUTO_DEFRAG) || sb_rdonly(fs_info->sb))) { + btrfs_cleanup_defrag_inodes(fs_info); + } + + /* If we toggled discard async */ + if (!btrfs_raw_test_opt(old_opts, DISCARD_ASYNC) && + btrfs_test_opt(fs_info, DISCARD_ASYNC)) + btrfs_discard_resume(fs_info); + else if (btrfs_raw_test_opt(old_opts, DISCARD_ASYNC) && + !btrfs_test_opt(fs_info, DISCARD_ASYNC)) + btrfs_discard_cleanup(fs_info); + + /* If we toggled space cache */ + if (cache_opt != btrfs_free_space_cache_v1_active(fs_info)) + btrfs_set_free_space_cache_v1_active(fs_info, cache_opt); +} + +static void btrfs_resize_thread_pool(struct btrfs_fs_info *fs_info, + u32 new_pool_size, u32 old_pool_size) +{ + if (new_pool_size == old_pool_size) + return; + + fs_info->thread_pool_size = new_pool_size; + + btrfs_info(fs_info, "resize thread pool %d -> %d", + old_pool_size, new_pool_size); + + btrfs_workqueue_set_max(fs_info->workers, new_pool_size); + btrfs_workqueue_set_max(fs_info->hipri_workers, new_pool_size); + btrfs_workqueue_set_max(fs_info->delalloc_workers, new_pool_size); + btrfs_workqueue_set_max(fs_info->caching_workers, new_pool_size); + workqueue_set_max_active(fs_info->endio_workers, new_pool_size); + workqueue_set_max_active(fs_info->endio_meta_workers, new_pool_size); + btrfs_workqueue_set_max(fs_info->endio_write_workers, new_pool_size); + btrfs_workqueue_set_max(fs_info->endio_freespace_worker, new_pool_size); + btrfs_workqueue_set_max(fs_info->delayed_workers, new_pool_size); +} + +static int btrfs_reconfigure(struct fs_context *fc) +{ + struct btrfs_fs_context *ctx = fc->fs_private; + struct super_block *sb = fc->root->d_sb; + struct btrfs_fs_info *fs_info = btrfs_sb(sb); + unsigned old_flags = sb->s_flags; + unsigned long old_opts = fs_info->mount_opt; + unsigned long old_compress_type = fs_info->compress_type; + u64 old_max_inline = fs_info->max_inline; + u32 old_thread_pool_size = fs_info->thread_pool_size; + u32 old_metadata_ratio = fs_info->metadata_ratio; + int ret = 0; + + sync_filesystem(sb); + set_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state); + + /* + * If ctx->phase is BTRFS_FS_CONTEXT_SUPER we've just created or + * gotten a reference on a superblock immediately followed by an + * internal remount. That can only happen for the old mount api. + */ + if (ctx->phase != BTRFS_FS_CONTEXT_SUPER) + ret = btrfs_fs_params_verify(fs_info, fc); + else if (WARN_ON(!fc->oldapi)) + ret = -EINVAL; + if (ret) + goto restore; + + ret = btrfs_check_features(fs_info, !(fc->sb_flags & SB_RDONLY)); + if (ret < 0) + goto restore; + + btrfs_remount_begin(fs_info, old_opts, fc->sb_flags); + btrfs_resize_thread_pool(fs_info, fs_info->thread_pool_size, + old_thread_pool_size); + + if ((bool)btrfs_test_opt(fs_info, FREE_SPACE_TREE) != + (bool)btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE) && + (!sb_rdonly(sb) || (fc->sb_flags & SB_RDONLY))) { + btrfs_param_warn(fc, + "remount supports changing free space tree only from ro to rw"); + /* Make sure free space cache options match the state on disk */ + if (btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE)) { + btrfs_set_opt(fs_info->mount_opt, FREE_SPACE_TREE); + btrfs_clear_opt(fs_info->mount_opt, SPACE_CACHE); + } + if (btrfs_free_space_cache_v1_active(fs_info)) { + btrfs_clear_opt(fs_info->mount_opt, FREE_SPACE_TREE); + btrfs_set_opt(fs_info->mount_opt, SPACE_CACHE); + } + } + + if ((bool)(fc->sb_flags & SB_RDONLY) == sb_rdonly(sb)) + goto out; + + if (fc->sb_flags & SB_RDONLY) { + /* + * this also happens on 'umount -rf' or on shutdown, when + * the filesystem is busy. + */ + cancel_work_sync(&fs_info->async_reclaim_work); + cancel_work_sync(&fs_info->async_data_reclaim_work); + + btrfs_discard_cleanup(fs_info); + + /* wait for the uuid_scan task to finish */ + down(&fs_info->uuid_tree_rescan_sem); + /* avoid complains from lockdep et al. */ + up(&fs_info->uuid_tree_rescan_sem); + + btrfs_set_sb_rdonly(sb); + + /* + * Setting SB_RDONLY will put the cleaner thread to + * sleep at the next loop if it's already active. + * If it's already asleep, we'll leave unused block + * groups on disk until we're mounted read-write again + * unless we clean them up here. + */ + btrfs_delete_unused_bgs(fs_info); + + /* + * The cleaner task could be already running before we set the + * flag BTRFS_FS_STATE_RO (and SB_RDONLY in the superblock). + * We must make sure that after we finish the remount, i.e. after + * we call btrfs_commit_super(), the cleaner can no longer start + * a transaction - either because it was dropping a dead root, + * running delayed iputs or deleting an unused block group (the + * cleaner picked a block group from the list of unused block + * groups before we were able to in the previous call to + * btrfs_delete_unused_bgs()). + */ + wait_on_bit(&fs_info->flags, BTRFS_FS_CLEANER_RUNNING, + TASK_UNINTERRUPTIBLE); + + /* + * We've set the superblock to RO mode, so we might have made + * the cleaner task sleep without running all pending delayed + * iputs. Go through all the delayed iputs here, so that if an + * unmount happens without remounting RW we don't end up at + * finishing close_ctree() with a non-empty list of delayed + * iputs. + */ + btrfs_run_delayed_iputs(fs_info); + + btrfs_dev_replace_suspend_for_unmount(fs_info); + btrfs_scrub_cancel(fs_info); + btrfs_pause_balance(fs_info); + + /* + * Pause the qgroup rescan worker if it is running. We don't want + * it to be still running after we are in RO mode, as after that, + * by the time we unmount, it might have left a transaction open, + * so we would leak the transaction and/or crash. + */ + btrfs_qgroup_wait_for_completion(fs_info, false); + + ret = btrfs_commit_super(fs_info); + if (ret) + goto restore; + } else { + if (BTRFS_FS_ERROR(fs_info)) { + btrfs_err(fs_info, + "Remounting read-write after error is not allowed"); + ret = -EINVAL; + goto restore; + } + if (fs_info->fs_devices->rw_devices == 0) { + ret = -EACCES; + goto restore; + } + + if (!btrfs_check_rw_degradable(fs_info, NULL)) { + btrfs_param_warn( + fc, + "too many missing devices, writable remount is not allowed"); + ret = -EACCES; + goto restore; + } + + if (btrfs_super_log_root(fs_info->super_copy) != 0) { + btrfs_param_warn( + fc, + "mount required to replay tree-log, cannot remount read-write"); + ret = -EINVAL; + goto restore; + } + + /* + * NOTE: when remounting with a change that does writes, don't + * put it anywhere above this point, as we are not sure to be + * safe to write until we pass the above checks. + */ + ret = btrfs_start_pre_rw_mount(fs_info); + if (ret) + goto restore; + + btrfs_clear_sb_rdonly(sb); + + set_bit(BTRFS_FS_OPEN, &fs_info->flags); + } +out: + + if (fc->sb_flags & SB_POSIXACL) + sb->s_flags |= SB_POSIXACL; + else + sb->s_flags &= ~SB_POSIXACL; + + wake_up_process(fs_info->transaction_kthread); + btrfs_remount_cleanup(fs_info, old_opts); + btrfs_clear_oneshot_options(fs_info); + clear_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state); + + return 0; + +restore: + /* We've hit an error - don't reset SB_RDONLY */ + if (sb_rdonly(sb)) + old_flags |= SB_RDONLY; + if (!(old_flags & SB_RDONLY)) + clear_bit(BTRFS_FS_STATE_RO, &fs_info->fs_state); + sb->s_flags = old_flags; + fs_info->mount_opt = old_opts; + fs_info->compress_type = old_compress_type; + fs_info->max_inline = old_max_inline; + btrfs_resize_thread_pool(fs_info, + old_thread_pool_size, fs_info->thread_pool_size); + fs_info->metadata_ratio = old_metadata_ratio; + btrfs_remount_cleanup(fs_info, old_opts); + clear_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state); + + return ret; +} + +static const struct fs_context_operations btrfs_context_ops = { + .parse_param = btrfs_parse_param, + .get_tree = btrfs_get_tree, + .reconfigure = btrfs_reconfigure, + .free = btrfs_free_fs_context, + .dup = btrfs_dup_fs_context, +}; + +int btrfs_init_fs_context(struct fs_context *fc) +{ + struct btrfs_fs_context *ctx; + + ctx = kzalloc(sizeof(struct btrfs_fs_context), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + refcount_set(&ctx->refs, 1); + ctx->phase = BTRFS_FS_CONTEXT_PREPARE; + + /* + * If this is a remount make sure that we copy the current mount + * options into @ctx so checking for current options during + * parameter parsing is identical for both remount and a regular + * mount. + */ + if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) + btrfs_fs_info_to_context(ctx, btrfs_sb(fc->root->d_sb)); + else + ctx->compress_type = BTRFS_COMPRESS_ZLIB; + +#ifdef CONFIG_BTRFS_FS_POSIX_ACL + fc->sb_flags |= SB_POSIXACL; + fc->sb_flags |= SB_I_VERSION; +#endif + fc->fs_private = ctx; + fc->ops = &btrfs_context_ops; + return 0; +} diff --git a/fs/btrfs/params.h b/fs/btrfs/params.h new file mode 100644 index 000000000000..85ccaa4a660f --- /dev/null +++ b/fs/btrfs/params.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Christian Brauner. + */ + +#ifndef BTRFS_FS_CONTEXT_H +#define BTRFS_FS_CONTEXT_H + +#include <linux/btrfs.h> +#include <linux/fs.h> +#include <linux/fs_context.h> +#include <linux/fs_parser.h> +#include "fs.h" + +extern const struct fs_parameter_spec btrfs_parameter_spec[]; + +int btrfs_fs_params_verify(struct btrfs_fs_info *info, struct fs_context *fc); +int btrfs_get_tree(struct fs_context *fc); +int btrfs_init_fs_context(struct fs_context *fc); + +#endif diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index 1c3c1d7ad68c..65172cfdad22 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -59,905 +59,17 @@ #include "verity.h" #include "super.h" #include "extent-tree.h" +#include "params.h" #define CREATE_TRACE_POINTS #include <trace/events/btrfs.h> static const struct super_operations btrfs_super_ops; -/* - * Types for mounting the default subvolume and a subvolume explicitly - * requested by subvol=/path. That way the callchain is straightforward and we - * don't have to play tricks with the mount options and recursive calls to - * btrfs_mount. - * - * The new btrfs_root_fs_type also servers as a tag for the bdev_holder. - */ -static struct file_system_type btrfs_fs_type; -static struct file_system_type btrfs_root_fs_type; - -static int btrfs_remount(struct super_block *sb, int *flags, char *data); - static void btrfs_put_super(struct super_block *sb) { close_ctree(btrfs_sb(sb)); } -enum { - Opt_acl, Opt_noacl, - Opt_clear_cache, - Opt_commit_interval, - Opt_compress, - Opt_compress_force, - Opt_compress_force_type, - Opt_compress_type, - Opt_degraded, - Opt_device, - Opt_fatal_errors, - Opt_flushoncommit, Opt_noflushoncommit, - Opt_max_inline, - Opt_barrier, Opt_nobarrier, - Opt_datacow, Opt_nodatacow, - Opt_datasum, Opt_nodatasum, - Opt_defrag, Opt_nodefrag, - Opt_discard, Opt_nodiscard, - Opt_discard_mode, - Opt_norecovery, - Opt_ratio, - Opt_rescan_uuid_tree, - Opt_skip_balance, - Opt_space_cache, Opt_no_space_cache, - Opt_space_cache_version, - Opt_ssd, Opt_nossd, - Opt_ssd_spread, Opt_nossd_spread, - Opt_subvol, - Opt_subvol_empty, - Opt_subvolid, - Opt_thread_pool, - Opt_treelog, Opt_notreelog, - Opt_user_subvol_rm_allowed, - - /* Rescue options */ - Opt_rescue, - Opt_usebackuproot, - Opt_nologreplay, - Opt_ignorebadroots, - Opt_ignoredatacsums, - Opt_rescue_all, - - /* Deprecated options */ - Opt_recovery, - Opt_inode_cache, Opt_noinode_cache, - - /* Debugging options */ - Opt_check_integrity, - Opt_check_integrity_including_extent_data, - Opt_check_integrity_print_mask, - Opt_enospc_debug, Opt_noenospc_debug, -#ifdef CONFIG_BTRFS_DEBUG - Opt_fragment_data, Opt_fragment_metadata, Opt_fragment_all, -#endif -#ifdef CONFIG_BTRFS_FS_REF_VERIFY - Opt_ref_verify, -#endif - Opt_err, -}; - -static const match_table_t tokens = { - {Opt_acl, "acl"}, - {Opt_noacl, "noacl"}, - {Opt_clear_cache, "clear_cache"}, - {Opt_commit_interval, "commit=%u"}, - {Opt_compress, "compress"}, - {Opt_compress_type, "compress=%s"}, - {Opt_compress_force, "compress-force"}, - {Opt_compress_force_type, "compress-force=%s"}, - {Opt_degraded, "degraded"}, - {Opt_device, "device=%s"}, - {Opt_fatal_errors, "fatal_errors=%s"}, - {Opt_flushoncommit, "flushoncommit"}, - {Opt_noflushoncommit, "noflushoncommit"}, - {Opt_inode_cache, "inode_cache"}, - {Opt_noinode_cache, "noinode_cache"}, - {Opt_max_inline, "max_inline=%s"}, - {Opt_barrier, "barrier"}, - {Opt_nobarrier, "nobarrier"}, - {Opt_datacow, "datacow"}, - {Opt_nodatacow, "nodatacow"}, - {Opt_datasum, "datasum"}, - {Opt_nodatasum, "nodatasum"}, - {Opt_defrag, "autodefrag"}, - {Opt_nodefrag, "noautodefrag"}, - {Opt_discard, "discard"}, - {Opt_discard_mode, "discard=%s"}, - {Opt_nodiscard, "nodiscard"}, - {Opt_norecovery, "norecovery"}, - {Opt_ratio, "metadata_ratio=%u"}, - {Opt_rescan_uuid_tree, "rescan_uuid_tree"}, - {Opt_skip_balance, "skip_balance"}, - {Opt_space_cache, "space_cache"}, - {Opt_no_space_cache, "nospace_cache"}, - {Opt_space_cache_version, "space_cache=%s"}, - {Opt_ssd, "ssd"}, - {Opt_nossd, "nossd"}, - {Opt_ssd_spread, "ssd_spread"}, - {Opt_nossd_spread, "nossd_spread"}, - {Opt_subvol, "subvol=%s"}, - {Opt_subvol_empty, "subvol="}, - {Opt_subvolid, "subvolid=%s"}, - {Opt_thread_pool, "thread_pool=%u"}, - {Opt_treelog, "treelog"}, - {Opt_notreelog, "notreelog"}, - {Opt_user_subvol_rm_allowed, "user_subvol_rm_allowed"}, - - /* Rescue options */ - {Opt_rescue, "rescue=%s"}, - /* Deprecated, with alias rescue=nologreplay */ - {Opt_nologreplay, "nologreplay"}, - /* Deprecated, with alias rescue=usebackuproot */ - {Opt_usebackuproot, "usebackuproot"}, - - /* Deprecated options */ - {Opt_recovery, "recovery"}, - - /* Debugging options */ - {Opt_check_integrity, "check_int"}, - {Opt_check_integrity_including_extent_data, "check_int_data"}, - {Opt_check_integrity_print_mask, "check_int_print_mask=%u"}, - {Opt_enospc_debug, "enospc_debug"}, - {Opt_noenospc_debug, "noenospc_debug"}, -#ifdef CONFIG_BTRFS_DEBUG - {Opt_fragment_data, "fragment=data"}, - {Opt_fragment_metadata, "fragment=metadata"}, - {Opt_fragment_all, "fragment=all"}, -#endif -#ifdef CONFIG_BTRFS_FS_REF_VERIFY - {Opt_ref_verify, "ref_verify"}, -#endif - {Opt_err, NULL}, -}; - -static const match_table_t rescue_tokens = { - {Opt_usebackuproot, "usebackuproot"}, - {Opt_nologreplay, "nologreplay"}, - {Opt_ignorebadroots, "ignorebadroots"}, - {Opt_ignorebadroots, "ibadroots"}, - {Opt_ignoredatacsums, "ignoredatacsums"}, - {Opt_ignoredatacsums, "idatacsums"}, - {Opt_rescue_all, "all"}, - {Opt_err, NULL}, -}; - -static bool check_ro_option(struct btrfs_fs_info *fs_info, unsigned long opt, - const char *opt_name) -{ - if (fs_info->mount_opt & opt) { - btrfs_err(fs_info, "%s must be used with ro mount option", - opt_name); - return true; - } - return false; -} - -static int parse_rescue_options(struct btrfs_fs_info *info, const char *options) -{ - char *opts; - char *orig; - char *p; - substring_t args[MAX_OPT_ARGS]; - int ret = 0; - - opts = kstrdup(options, GFP_KERNEL); - if (!opts) - return -ENOMEM; - orig = opts; - - while ((p = strsep(&opts, ":")) != NULL) { - int token; - - if (!*p) - continue; - token = match_token(p, rescue_tokens, args); - switch (token){ - case Opt_usebackuproot: - btrfs_info(info, - "trying to use backup root at mount time"); - btrfs_set_opt(info->mount_opt, USEBACKUPROOT); - break; - case Opt_nologreplay: - btrfs_set_and_info(info, NOLOGREPLAY, - "disabling log replay at mount time"); - break; - case Opt_ignorebadroots: - btrfs_set_and_info(info, IGNOREBADROOTS, - "ignoring bad roots"); - break; - case Opt_ignoredatacsums: - btrfs_set_and_info(info, IGNOREDATACSUMS, - "ignoring data csums"); - break; - case Opt_rescue_all: - btrfs_info(info, "enabling all of the rescue options"); - btrfs_set_and_info(info, IGNOREDATACSUMS, - "ignoring data csums"); - btrfs_set_and_info(info, IGNOREBADROOTS, - "ignoring bad roots"); - btrfs_set_and_info(info, NOLOGREPLAY, - "disabling log replay at mount time"); - break; - case Opt_err: - btrfs_info(info, "unrecognized rescue option '%s'", p); - ret = -EINVAL; - goto out; - default: - break; - } - - } -out: - kfree(orig); - return ret; -} - -/* - * Regular mount options parser. Everything that is needed only when - * reading in a new superblock is parsed here. - * XXX JDM: This needs to be cleaned up for remount. - */ -int btrfs_parse_options(struct btrfs_fs_info *info, char *options, - unsigned long new_flags) -{ - substring_t args[MAX_OPT_ARGS]; - char *p, *num; - int intarg; - int ret = 0; - char *compress_type; - bool compress_force = false; - enum btrfs_compression_type saved_compress_type; - int saved_compress_level; - bool saved_compress_force; - int no_compress = 0; - const bool remounting = test_bit(BTRFS_FS_STATE_REMOUNTING, &info->fs_state); - - if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE)) - btrfs_set_opt(info->mount_opt, FREE_SPACE_TREE); - else if (btrfs_free_space_cache_v1_active(info)) { - if (btrfs_is_zoned(info)) { - btrfs_info(info, - "zoned: clearing existing space cache"); - btrfs_set_super_cache_generation(info->super_copy, 0); - } else { - btrfs_set_opt(info->mount_opt, SPACE_CACHE); - } - } - - /* - * Even the options are empty, we still need to do extra check - * against new flags - */ - if (!options) - goto check; - - while ((p = strsep(&options, ",")) != NULL) { - int token; - if (!*p) - continue; - - token = match_token(p, tokens, args); - switch (token) { - case Opt_degraded: - btrfs_info(info, "allowing degraded mounts"); - btrfs_set_opt(info->mount_opt, DEGRADED); - break; - case Opt_subvol: - case Opt_subvol_empty: - case Opt_subvolid: - case Opt_device: - /* - * These are parsed by btrfs_parse_subvol_options or - * btrfs_parse_device_options and can be ignored here. - */ - break; - case Opt_nodatasum: - btrfs_set_and_info(info, NODATASUM, - "setting nodatasum"); - break; - case Opt_datasum: - if (btrfs_test_opt(info, NODATASUM)) { - if (btrfs_test_opt(info, NODATACOW)) - btrfs_info(info, - "setting datasum, datacow enabled"); - else - btrfs_info(info, "setting datasum"); - } - btrfs_clear_opt(info->mount_opt, NODATACOW); - btrfs_clear_opt(info->mount_opt, NODATASUM); - break; - case Opt_nodatacow: - if (!btrfs_test_opt(info, NODATACOW)) { - if (!btrfs_test_opt(info, COMPRESS) || - !btrfs_test_opt(info, FORCE_COMPRESS)) { - btrfs_info(info, - "setting nodatacow, compression disabled"); - } else { - btrfs_info(info, "setting nodatacow"); - } - } - btrfs_clear_opt(info->mount_opt, COMPRESS); - btrfs_clear_opt(info->mount_opt, FORCE_COMPRESS); - btrfs_set_opt(info->mount_opt, NODATACOW); - btrfs_set_opt(info->mount_opt, NODATASUM); - break; - case Opt_datacow: - btrfs_clear_and_info(info, NODATACOW, - "setting datacow"); - break; - case Opt_compress_force: - case Opt_compress_force_type: - compress_force = true; - fallthrough; - case Opt_compress: - case Opt_compress_type: - saved_compress_type = btrfs_test_opt(info, - COMPRESS) ? - info->compress_type : BTRFS_COMPRESS_NONE; - saved_compress_force = - btrfs_test_opt(info, FORCE_COMPRESS); - saved_compress_level = info->compress_level; - if (token == Opt_compress || - token == Opt_compress_force || - strncmp(args[0].from, "zlib", 4) == 0) { - compress_type = "zlib"; - - info->compress_type = BTRFS_COMPRESS_ZLIB; - info->compress_level = BTRFS_ZLIB_DEFAULT_LEVEL; - /* - * args[0] contains uninitialized data since - * for these tokens we don't expect any - * parameter. - */ - if (token != Opt_compress && - token != Opt_compress_force) - info->compress_level = - btrfs_compress_str2level( - BTRFS_COMPRESS_ZLIB, - args[0].from + 4); - btrfs_set_opt(info->mount_opt, COMPRESS); - btrfs_clear_opt(info->mount_opt, NODATACOW); - btrfs_clear_opt(info->mount_opt, NODATASUM); - no_compress = 0; - } else if (strncmp(args[0].from, "lzo", 3) == 0) { - compress_type = "lzo"; - info->compress_type = BTRFS_COMPRESS_LZO; - info->compress_level = 0; - btrfs_set_opt(info->mount_opt, COMPRESS); - btrfs_clear_opt(info->mount_opt, NODATACOW); - btrfs_clear_opt(info->mount_opt, NODATASUM); - btrfs_set_fs_incompat(info, COMPRESS_LZO); - no_compress = 0; - } else if (strncmp(args[0].from, "zstd", 4) == 0) { - compress_type = "zstd"; - info->compress_type = BTRFS_COMPRESS_ZSTD; - info->compress_level = - btrfs_compress_str2level( - BTRFS_COMPRESS_ZSTD, - args[0].from + 4); - btrfs_set_opt(info->mount_opt, COMPRESS); - btrfs_clear_opt(info->mount_opt, NODATACOW); - btrfs_clear_opt(info->mount_opt, NODATASUM); - btrfs_set_fs_incompat(info, COMPRESS_ZSTD); - no_compress = 0; - } else if (strncmp(args[0].from, "no", 2) == 0) { - compress_type = "no"; - info->compress_level = 0; - info->compress_type = 0; - btrfs_clear_opt(info->mount_opt, COMPRESS); - btrfs_clear_opt(info->mount_opt, FORCE_COMPRESS); - compress_force = false; - no_compress++; - } else { - btrfs_err(info, "unrecognized compression value %s", - args[0].from); - ret = -EINVAL; - goto out; - } - - if (compress_force) { - btrfs_set_opt(info->mount_opt, FORCE_COMPRESS); - } else { - /* - * If we remount from compress-force=xxx to - * compress=xxx, we need clear FORCE_COMPRESS - * flag, otherwise, there is no way for users - * to disable forcible compression separately. - */ - btrfs_clear_opt(info->mount_opt, FORCE_COMPRESS); - } - if (no_compress == 1) { - btrfs_info(info, "use no compression"); - } else if ((info->compress_type != saved_compress_type) || - (compress_force != saved_compress_force) || - (info->compress_level != saved_compress_level)) { - btrfs_info(info, "%s %s compression, level %d", - (compress_force) ? "force" : "use", - compress_type, info->compress_level); - } - compress_force = false; - break; - case Opt_ssd: - btrfs_set_and_info(info, SSD, - "enabling ssd optimizations"); - btrfs_clear_opt(info->mount_opt, NOSSD); - break; - case Opt_ssd_spread: - btrfs_set_and_info(info, SSD, - "enabling ssd optimizations"); - btrfs_set_and_info(info, SSD_SPREAD, - "using spread ssd allocation scheme"); - btrfs_clear_opt(info->mount_opt, NOSSD); - break; - case Opt_nossd: - btrfs_set_opt(info->mount_opt, NOSSD); - btrfs_clear_and_info(info, SSD, - "not using ssd optimizations"); - fallthrough; - case Opt_nossd_spread: - btrfs_clear_and_info(info, SSD_SPREAD, - "not using spread ssd allocation scheme"); - break; - case Opt_barrier: - btrfs_clear_and_info(info, NOBARRIER, - "turning on barriers"); - break; - case Opt_nobarrier: - btrfs_set_and_info(info, NOBARRIER, - "turning off barriers"); - break; - case Opt_thread_pool: - ret = match_int(&args[0], &intarg); - if (ret) { - btrfs_err(info, "unrecognized thread_pool value %s", - args[0].from); - goto out; - } else if (intarg == 0) { - btrfs_err(info, "invalid value 0 for thread_pool"); - ret = -EINVAL; - goto out; - } - info->thread_pool_size = intarg; - break; - case Opt_max_inline: - num = match_strdup(&args[0]); - if (num) { - info->max_inline = memparse(num, NULL); - kfree(num); - - if (info->max_inline) { - info->max_inline = min_t(u64, - info->max_inline, - info->sectorsize); - } - btrfs_info(info, "max_inline at %llu", - info->max_inline); - } else { - ret = -ENOMEM; - goto out; - } - break; - case Opt_acl: -#ifdef CONFIG_BTRFS_FS_POSIX_ACL - info->sb->s_flags |= SB_POSIXACL; - break; -#else - btrfs_err(info, "support for ACL not compiled in!"); - ret = -EINVAL; - goto out; -#endif - case Opt_noacl: - info->sb->s_flags &= ~SB_POSIXACL; - break; - case Opt_notreelog: - btrfs_set_and_info(info, NOTREELOG, - "disabling tree log"); - break; - case Opt_treelog: - btrfs_clear_and_info(info, NOTREELOG, - "enabling tree log"); - break; - case Opt_norecovery: - case Opt_nologreplay: - btrfs_warn(info, - "'nologreplay' is deprecated, use 'rescue=nologreplay' instead"); - btrfs_set_and_info(info, NOLOGREPLAY, - "disabling log replay at mount time"); - break; - case Opt_flushoncommit: - btrfs_set_and_info(info, FLUSHONCOMMIT, - "turning on flush-on-commit"); - break; - case Opt_noflushoncommit: - btrfs_clear_and_info(info, FLUSHONCOMMIT, - "turning off flush-on-commit"); - break; - case Opt_ratio: - ret = match_int(&args[0], &intarg); - if (ret) { - btrfs_err(info, "unrecognized metadata_ratio value %s", - args[0].from); - goto out; - } - info->metadata_ratio = intarg; - btrfs_info(info, "metadata ratio %u", - info->metadata_ratio); - break; - case Opt_discard: - case Opt_discard_mode: - if (token == Opt_discard || - strcmp(args[0].from, "sync") == 0) { - btrfs_clear_opt(info->mount_opt, DISCARD_ASYNC); - btrfs_set_and_info(info, DISCARD_SYNC, - "turning on sync discard"); - } else if (strcmp(args[0].from, "async") == 0) { - btrfs_clear_opt(info->mount_opt, DISCARD_SYNC); - btrfs_set_and_info(info, DISCARD_ASYNC, - "turning on async discard"); - } else { - btrfs_err(info, "unrecognized discard mode value %s", - args[0].from); - ret = -EINVAL; - goto out; - } - btrfs_clear_opt(info->mount_opt, NODISCARD); - break; - case Opt_nodiscard: - btrfs_clear_and_info(info, DISCARD_SYNC, - "turning off discard"); - btrfs_clear_and_info(info, DISCARD_ASYNC, - "turning off async discard"); - btrfs_set_opt(info->mount_opt, NODISCARD); - break; - case Opt_space_cache: - case Opt_space_cache_version: - /* - * We already set FREE_SPACE_TREE above because we have - * compat_ro(FREE_SPACE_TREE) set, and we aren't going - * to allow v1 to be set for extent tree v2, simply - * ignore this setting if we're extent tree v2. - */ - if (btrfs_fs_incompat(info, EXTENT_TREE_V2)) - break; - if (token == Opt_space_cache || - strcmp(args[0].from, "v1") == 0) { - btrfs_clear_opt(info->mount_opt, - FREE_SPACE_TREE); - btrfs_set_and_info(info, SPACE_CACHE, - "enabling disk space caching"); - } else if (strcmp(args[0].from, "v2") == 0) { - btrfs_clear_opt(info->mount_opt, - SPACE_CACHE); - btrfs_set_and_info(info, FREE_SPACE_TREE, - "enabling free space tree"); - } else { - btrfs_err(info, "unrecognized space_cache value %s", - args[0].from); - ret = -EINVAL; - goto out; - } - break; - case Opt_rescan_uuid_tree: - btrfs_set_opt(info->mount_opt, RESCAN_UUID_TREE); - break; - case Opt_no_space_cache: - /* - * We cannot operate without the free space tree with - * extent tree v2, ignore this option. - */ - if (btrfs_fs_incompat(info, EXTENT_TREE_V2)) - break; - if (btrfs_test_opt(info, SPACE_CACHE)) { - btrfs_clear_and_info(info, SPACE_CACHE, - "disabling disk space caching"); - } - if (btrfs_test_opt(info, FREE_SPACE_TREE)) { - btrfs_clear_and_info(info, FREE_SPACE_TREE, - "disabling free space tree"); - } - break; - case Opt_inode_cache: - case Opt_noinode_cache: - btrfs_warn(info, - "the 'inode_cache' option is deprecated and has no effect since 5.11"); - break; - case Opt_clear_cache: - /* - * We cannot clear the free space tree with extent tree - * v2, ignore this option. - */ - if (btrfs_fs_incompat(info, EXTENT_TREE_V2)) - break; - btrfs_set_and_info(info, CLEAR_CACHE, - "force clearing of disk cache"); - break; - case Opt_user_subvol_rm_allowed: - btrfs_set_opt(info->mount_opt, USER_SUBVOL_RM_ALLOWED); - break; - case Opt_enospc_debug: - btrfs_set_opt(info->mount_opt, ENOSPC_DEBUG); - break; - case Opt_noenospc_debug: - btrfs_clear_opt(info->mount_opt, ENOSPC_DEBUG); - break; - case Opt_defrag: - btrfs_set_and_info(info, AUTO_DEFRAG, - "enabling auto defrag"); - break; - case Opt_nodefrag: - btrfs_clear_and_info(info, AUTO_DEFRAG, - "disabling auto defrag"); - break; - case Opt_recovery: - case Opt_usebackuproot: - btrfs_warn(info, - "'%s' is deprecated, use 'rescue=usebackuproot' instead", - token == Opt_recovery ? "recovery" : - "usebackuproot"); - btrfs_info(info, - "trying to use backup root at mount time"); - btrfs_set_opt(info->mount_opt, USEBACKUPROOT); - break; - case Opt_skip_balance: - btrfs_set_opt(info->mount_opt, SKIP_BALANCE); - break; -#ifdef CONFIG_BTRFS_FS_CHECK_INTEGRITY - case Opt_check_integrity_including_extent_data: - btrfs_info(info, - "enabling check integrity including extent data"); - btrfs_set_opt(info->mount_opt, CHECK_INTEGRITY_DATA); - btrfs_set_opt(info->mount_opt, CHECK_INTEGRITY); - break; - case Opt_check_integrity: - btrfs_info(info, "enabling check integrity"); - btrfs_set_opt(info->mount_opt, CHECK_INTEGRITY); - break; - case Opt_check_integrity_print_mask: - ret = match_int(&args[0], &intarg); - if (ret) { - btrfs_err(info, - "unrecognized check_integrity_print_mask value %s", - args[0].from); - goto out; - } - info->check_integrity_print_mask = intarg; - btrfs_info(info, "check_integrity_print_mask 0x%x", - info->check_integrity_print_mask); - break; -#else - case Opt_check_integrity_including_extent_data: - case Opt_check_integrity: - case Opt_check_integrity_print_mask: - btrfs_err(info, - "support for check_integrity* not compiled in!"); - ret = -EINVAL; - goto out; -#endif - case Opt_fatal_errors: - if (strcmp(args[0].from, "panic") == 0) { - btrfs_set_opt(info->mount_opt, - PANIC_ON_FATAL_ERROR); - } else if (strcmp(args[0].from, "bug") == 0) { - btrfs_clear_opt(info->mount_opt, - PANIC_ON_FATAL_ERROR); - } else { - btrfs_err(info, "unrecognized fatal_errors value %s", - args[0].from); - ret = -EINVAL; - goto out; - } - break; - case Opt_commit_interval: - intarg = 0; - ret = match_int(&args[0], &intarg); - if (ret) { - btrfs_err(info, "unrecognized commit_interval value %s", - args[0].from); - ret = -EINVAL; - goto out; - } - if (intarg == 0) { - btrfs_info(info, - "using default commit interval %us", - BTRFS_DEFAULT_COMMIT_INTERVAL); - intarg = BTRFS_DEFAULT_COMMIT_INTERVAL; - } else if (intarg > 300) { - btrfs_warn(info, "excessive commit interval %d", - intarg); - } - info->commit_interval = intarg; - break; - case Opt_rescue: - ret = parse_rescue_options(info, args[0].from); - if (ret < 0) { - btrfs_err(info, "unrecognized rescue value %s", - args[0].from); - goto out; - } - break; -#ifdef CONFIG_BTRFS_DEBUG - case Opt_fragment_all: - btrfs_info(info, "fragmenting all space"); - btrfs_set_opt(info->mount_opt, FRAGMENT_DATA); - btrfs_set_opt(info->mount_opt, FRAGMENT_METADATA); - break; - case Opt_fragment_metadata: - btrfs_info(info, "fragmenting metadata"); - btrfs_set_opt(info->mount_opt, - FRAGMENT_METADATA); - break; - case Opt_fragment_data: - btrfs_info(info, "fragmenting data"); - btrfs_set_opt(info->mount_opt, FRAGMENT_DATA); - break; -#endif -#ifdef CONFIG_BTRFS_FS_REF_VERIFY - case Opt_ref_verify: - btrfs_info(info, "doing ref verification"); - btrfs_set_opt(info->mount_opt, REF_VERIFY); - break; -#endif - case Opt_err: - btrfs_err(info, "unrecognized mount option '%s'", p); - ret = -EINVAL; - goto out; - default: - break; - } - } -check: - /* We're read-only, don't have to check. */ - if (new_flags & SB_RDONLY) - goto out; - - if (check_ro_option(info, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") || - check_ro_option(info, BTRFS_MOUNT_IGNOREBADROOTS, "ignorebadroots") || - check_ro_option(info, BTRFS_MOUNT_IGNOREDATACSUMS, "ignoredatacsums")) - ret = -EINVAL; -out: - if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE) && - !btrfs_test_opt(info, FREE_SPACE_TREE) && - !btrfs_test_opt(info, CLEAR_CACHE)) { - btrfs_err(info, "cannot disable free space tree"); - ret = -EINVAL; - } - if (btrfs_fs_compat_ro(info, BLOCK_GROUP_TREE) && - !btrfs_test_opt(info, FREE_SPACE_TREE)) { - btrfs_err(info, "cannot disable free space tree with block-group-tree feature"); - ret = -EINVAL; - } - if (!ret) - ret = btrfs_check_mountopts_zoned(info); - if (!ret && !remounting) { - if (btrfs_test_opt(info, SPACE_CACHE)) - btrfs_info(info, "disk space caching is enabled"); - if (btrfs_test_opt(info, FREE_SPACE_TREE)) - btrfs_info(info, "using free space tree"); - } - return ret; -} - -/* - * Parse mount options that are required early in the mount process. - * - * All other options will be parsed on much later in the mount process and - * only when we need to allocate a new super block. - */ -static int btrfs_parse_device_options(const char *options, blk_mode_t flags) -{ - substring_t args[MAX_OPT_ARGS]; - char *device_name, *opts, *orig, *p; - struct btrfs_device *device = NULL; - int error = 0; - - lockdep_assert_held(&uuid_mutex); - - if (!options) - return 0; - - /* - * strsep changes the string, duplicate it because btrfs_parse_options - * gets called later - */ - opts = kstrdup(options, GFP_KERNEL); - if (!opts) - return -ENOMEM; - orig = opts; - - while ((p = strsep(&opts, ",")) != NULL) { - int token; - - if (!*p) - continue; - - token = match_token(p, tokens, args); - if (token == Opt_device) { - device_name = match_strdup(&args[0]); - if (!device_name) { - error = -ENOMEM; - goto out; - } - device = btrfs_scan_one_device(device_name, flags); - kfree(device_name); - if (IS_ERR(device)) { - error = PTR_ERR(device); - goto out; - } - } - } - -out: - kfree(orig); - return error; -} - -/* - * Parse mount options that are related to subvolume id - * - * The value is later passed to mount_subvol() - */ -static int btrfs_parse_subvol_options(const char *options, char **subvol_name, - u64 *subvol_objectid) -{ - substring_t args[MAX_OPT_ARGS]; - char *opts, *orig, *p; - int error = 0; - u64 subvolid; - - if (!options) - return 0; - - /* - * strsep changes the string, duplicate it because - * btrfs_parse_device_options gets called later - */ - opts = kstrdup(options, GFP_KERNEL); - if (!opts) - return -ENOMEM; - orig = opts; - - while ((p = strsep(&opts, ",")) != NULL) { - int token; - if (!*p) - continue; - - token = match_token(p, tokens, args); - switch (token) { - case Opt_subvol: - kfree(*subvol_name); - *subvol_name = match_strdup(&args[0]); - if (!*subvol_name) { - error = -ENOMEM; - goto out; - } - break; - case Opt_subvolid: - error = match_u64(&args[0], &subvolid); - if (error) - goto out; - - /* we want the original fs_tree */ - if (subvolid == 0) - subvolid = BTRFS_FS_TREE_OBJECTID; - - *subvol_objectid = subvolid; - break; - default: - break; - } - } - -out: - kfree(orig); - return error; -} - char *btrfs_get_subvol_name_from_objectid(struct btrfs_fs_info *fs_info, u64 subvol_objectid) { @@ -1080,50 +192,8 @@ char *btrfs_get_subvol_name_from_objectid(struct btrfs_fs_info *fs_info, return ERR_PTR(ret); } -static int get_default_subvol_objectid(struct btrfs_fs_info *fs_info, u64 *objectid) -{ - struct btrfs_root *root = fs_info->tree_root; - struct btrfs_dir_item *di; - struct btrfs_path *path; - struct btrfs_key location; - struct fscrypt_str name = FSTR_INIT("default", 7); - u64 dir_id; - - path = btrfs_alloc_path(); - if (!path) - return -ENOMEM; - - /* - * Find the "default" dir item which points to the root item that we - * will mount by default if we haven't been given a specific subvolume - * to mount. - */ - dir_id = btrfs_super_root_dir(fs_info->super_copy); - di = btrfs_lookup_dir_item(NULL, root, path, dir_id, &name, 0); - if (IS_ERR(di)) { - btrfs_free_path(path); - return PTR_ERR(di); - } - if (!di) { - /* - * Ok the default dir item isn't there. This is weird since - * it's always been there, but don't freak out, just try and - * mount the top-level subvolume. - */ - btrfs_free_path(path); - *objectid = BTRFS_FS_TREE_OBJECTID; - return 0; - } - - btrfs_dir_item_key_to_cpu(path->nodes[0], di, &location); - btrfs_free_path(path); - *objectid = location.objectid; - return 0; -} - -static int btrfs_fill_super(struct super_block *sb, - struct btrfs_fs_devices *fs_devices, - void *data) +int btrfs_fill_super(struct fs_context *fc, struct super_block *sb, + struct btrfs_fs_devices *fs_devices) { struct inode *inode; struct btrfs_fs_info *fs_info = btrfs_sb(sb); @@ -1139,10 +209,6 @@ static int btrfs_fill_super(struct super_block *sb, #endif sb->s_xattr = btrfs_xattr_handlers; sb->s_time_gran = 1; -#ifdef CONFIG_BTRFS_FS_POSIX_ACL - sb->s_flags |= SB_POSIXACL; -#endif - sb->s_flags |= SB_I_VERSION; sb->s_iflags |= SB_I_CGROUPWB; err = super_setup_bdi(sb); @@ -1151,7 +217,7 @@ static int btrfs_fill_super(struct super_block *sb, return err; } - err = open_ctree(sb, fs_devices, (char *)data); + err = open_ctree(fc, sb, fs_devices); if (err) { btrfs_err(fs_info, "open_ctree failed"); return err; @@ -1334,542 +400,6 @@ static int btrfs_show_options(struct seq_file *seq, struct dentry *dentry) return 0; } -static int btrfs_test_super(struct super_block *s, void *data) -{ - struct btrfs_fs_info *p = data; - struct btrfs_fs_info *fs_info = btrfs_sb(s); - - return fs_info->fs_devices == p->fs_devices; -} - -static int btrfs_set_super(struct super_block *s, void *data) -{ - int err = set_anon_super(s, data); - if (!err) - s->s_fs_info = data; - return err; -} - -/* - * subvolumes are identified by ino 256 - */ -static inline int is_subvolume_inode(struct inode *inode) -{ - if (inode && inode->i_ino == BTRFS_FIRST_FREE_OBJECTID) - return 1; - return 0; -} - -static struct dentry *mount_subvol(const char *subvol_name, u64 subvol_objectid, - struct vfsmount *mnt) -{ - struct dentry *root; - int ret; - - if (!subvol_name) { - if (!subvol_objectid) { - ret = get_default_subvol_objectid(btrfs_sb(mnt->mnt_sb), - &subvol_objectid); - if (ret) { - root = ERR_PTR(ret); - goto out; - } - } - subvol_name = btrfs_get_subvol_name_from_objectid( - btrfs_sb(mnt->mnt_sb), subvol_objectid); - if (IS_ERR(subvol_name)) { - root = ERR_CAST(subvol_name); - subvol_name = NULL; - goto out; - } - - } - - root = mount_subtree(mnt, subvol_name); - /* mount_subtree() drops our reference on the vfsmount. */ - mnt = NULL; - - if (!IS_ERR(root)) { - struct super_block *s = root->d_sb; - struct btrfs_fs_info *fs_info = btrfs_sb(s); - struct inode *root_inode = d_inode(root); - u64 root_objectid = BTRFS_I(root_inode)->root->root_key.objectid; - - ret = 0; - if (!is_subvolume_inode(root_inode)) { - btrfs_err(fs_info, "'%s' is not a valid subvolume", - subvol_name); - ret = -EINVAL; - } - if (subvol_objectid && root_objectid != subvol_objectid) { - /* - * This will also catch a race condition where a - * subvolume which was passed by ID is renamed and - * another subvolume is renamed over the old location. - */ - btrfs_err(fs_info, - "subvol '%s' does not match subvolid %llu", - subvol_name, subvol_objectid); - ret = -EINVAL; - } - if (ret) { - dput(root); - root = ERR_PTR(ret); - deactivate_locked_super(s); - } - } - -out: - mntput(mnt); - kfree(subvol_name); - return root; -} - -/* - * Find a superblock for the given device / mount point. - * - * Note: This is based on mount_bdev from fs/super.c with a few additions - * for multiple device setup. Make sure to keep it in sync. - */ -static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, - int flags, const char *device_name, void *data) -{ - struct block_device *bdev = NULL; - struct super_block *s; - struct btrfs_device *device = NULL; - struct btrfs_fs_devices *fs_devices = NULL; - struct btrfs_fs_info *fs_info = NULL; - void *new_sec_opts = NULL; - blk_mode_t mode = sb_open_mode(flags); - int error = 0; - - if (data) { - error = security_sb_eat_lsm_opts(data, &new_sec_opts); - if (error) - return ERR_PTR(error); - } - - /* - * Setup a dummy root and fs_info for test/set super. This is because - * we don't actually fill this stuff out until open_ctree, but we need - * then open_ctree will properly initialize the file system specific - * settings later. btrfs_init_fs_info initializes the static elements - * of the fs_info (locks and such) to make cleanup easier if we find a - * superblock with our given fs_devices later on at sget() time. - */ - fs_info = kvzalloc(sizeof(struct btrfs_fs_info), GFP_KERNEL); - if (!fs_info) { - error = -ENOMEM; - goto error_sec_opts; - } - btrfs_init_fs_info(fs_info); - - fs_info->super_copy = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL); - fs_info->super_for_commit = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL); - if (!fs_info->super_copy || !fs_info->super_for_commit) { - error = -ENOMEM; - goto error_fs_info; - } - - mutex_lock(&uuid_mutex); - error = btrfs_parse_device_options(data, mode); - if (error) { - mutex_unlock(&uuid_mutex); - goto error_fs_info; - } - - device = btrfs_scan_one_device(device_name, mode); - if (IS_ERR(device)) { - mutex_unlock(&uuid_mutex); - error = PTR_ERR(device); - goto error_fs_info; - } - - fs_devices = device->fs_devices; - fs_info->fs_devices = fs_devices; - - error = btrfs_open_devices(fs_devices, mode, fs_type); - mutex_unlock(&uuid_mutex); - if (error) - goto error_fs_info; - - if (!(flags & SB_RDONLY) && fs_devices->rw_devices == 0) { - error = -EACCES; - goto error_close_devices; - } - - bdev = fs_devices->latest_dev->bdev; - s = sget(fs_type, btrfs_test_super, btrfs_set_super, flags | SB_NOSEC, - fs_info); - if (IS_ERR(s)) { - error = PTR_ERR(s); - goto error_close_devices; - } - - if (s->s_root) { - btrfs_close_devices(fs_devices); - btrfs_free_fs_info(fs_info); - if ((flags ^ s->s_flags) & SB_RDONLY) - error = -EBUSY; - } else { - snprintf(s->s_id, sizeof(s->s_id), "%pg", bdev); - shrinker_debugfs_rename(&s->s_shrink, "sb-%s:%s", fs_type->name, - s->s_id); - btrfs_sb(s)->bdev_holder = fs_type; - error = btrfs_fill_super(s, fs_devices, data); - } - if (!error) - error = security_sb_set_mnt_opts(s, new_sec_opts, 0, NULL); - security_free_mnt_opts(&new_sec_opts); - if (error) { - deactivate_locked_super(s); - return ERR_PTR(error); - } - - return dget(s->s_root); - -error_close_devices: - btrfs_close_devices(fs_devices); -error_fs_info: - btrfs_free_fs_info(fs_info); -error_sec_opts: - security_free_mnt_opts(&new_sec_opts); - return ERR_PTR(error); -} - -/* - * Mount function which is called by VFS layer. - * - * In order to allow mounting a subvolume directly, btrfs uses mount_subtree() - * which needs vfsmount* of device's root (/). This means device's root has to - * be mounted internally in any case. - * - * Operation flow: - * 1. Parse subvol id related options for later use in mount_subvol(). - * - * 2. Mount device's root (/) by calling vfs_kern_mount(). - * - * NOTE: vfs_kern_mount() is used by VFS to call btrfs_mount() in the - * first place. In order to avoid calling btrfs_mount() again, we use - * different file_system_type which is not registered to VFS by - * register_filesystem() (btrfs_root_fs_type). As a result, - * btrfs_mount_root() is called. The return value will be used by - * mount_subtree() in mount_subvol(). - * - * 3. Call mount_subvol() to get the dentry of subvolume. Since there is - * "btrfs subvolume set-default", mount_subvol() is called always. - */ -static struct dentry *btrfs_mount(struct file_system_type *fs_type, int flags, - const char *device_name, void *data) -{ - struct vfsmount *mnt_root; - struct dentry *root; - char *subvol_name = NULL; - u64 subvol_objectid = 0; - int error = 0; - - error = btrfs_parse_subvol_options(data, &subvol_name, - &subvol_objectid); - if (error) { - kfree(subvol_name); - return ERR_PTR(error); - } - - /* mount device's root (/) */ - mnt_root = vfs_kern_mount(&btrfs_root_fs_type, flags, device_name, data); - if (PTR_ERR_OR_ZERO(mnt_root) == -EBUSY) { - if (flags & SB_RDONLY) { - mnt_root = vfs_kern_mount(&btrfs_root_fs_type, - flags & ~SB_RDONLY, device_name, data); - } else { - mnt_root = vfs_kern_mount(&btrfs_root_fs_type, - flags | SB_RDONLY, device_name, data); - if (IS_ERR(mnt_root)) { - root = ERR_CAST(mnt_root); - kfree(subvol_name); - goto out; - } - - down_write(&mnt_root->mnt_sb->s_umount); - error = btrfs_remount(mnt_root->mnt_sb, &flags, NULL); - up_write(&mnt_root->mnt_sb->s_umount); - if (error < 0) { - root = ERR_PTR(error); - mntput(mnt_root); - kfree(subvol_name); - goto out; - } - } - } - if (IS_ERR(mnt_root)) { - root = ERR_CAST(mnt_root); - kfree(subvol_name); - goto out; - } - - /* mount_subvol() will free subvol_name and mnt_root */ - root = mount_subvol(subvol_name, subvol_objectid, mnt_root); - -out: - return root; -} - -static void btrfs_resize_thread_pool(struct btrfs_fs_info *fs_info, - u32 new_pool_size, u32 old_pool_size) -{ - if (new_pool_size == old_pool_size) - return; - - fs_info->thread_pool_size = new_pool_size; - - btrfs_info(fs_info, "resize thread pool %d -> %d", - old_pool_size, new_pool_size); - - btrfs_workqueue_set_max(fs_info->workers, new_pool_size); - btrfs_workqueue_set_max(fs_info->hipri_workers, new_pool_size); - btrfs_workqueue_set_max(fs_info->delalloc_workers, new_pool_size); - btrfs_workqueue_set_max(fs_info->caching_workers, new_pool_size); - workqueue_set_max_active(fs_info->endio_workers, new_pool_size); - workqueue_set_max_active(fs_info->endio_meta_workers, new_pool_size); - btrfs_workqueue_set_max(fs_info->endio_write_workers, new_pool_size); - btrfs_workqueue_set_max(fs_info->endio_freespace_worker, new_pool_size); - btrfs_workqueue_set_max(fs_info->delayed_workers, new_pool_size); -} - -static inline void btrfs_remount_begin(struct btrfs_fs_info *fs_info, - unsigned long old_opts, int flags) -{ - if (btrfs_raw_test_opt(old_opts, AUTO_DEFRAG) && - (!btrfs_raw_test_opt(fs_info->mount_opt, AUTO_DEFRAG) || - (flags & SB_RDONLY))) { - /* wait for any defraggers to finish */ - wait_event(fs_info->transaction_wait, - (atomic_read(&fs_info->defrag_running) == 0)); - if (flags & SB_RDONLY) - sync_filesystem(fs_info->sb); - } -} - -static inline void btrfs_remount_cleanup(struct btrfs_fs_info *fs_info, - unsigned long old_opts) -{ - const bool cache_opt = btrfs_test_opt(fs_info, SPACE_CACHE); - - /* - * We need to cleanup all defragable inodes if the autodefragment is - * close or the filesystem is read only. - */ - if (btrfs_raw_test_opt(old_opts, AUTO_DEFRAG) && - (!btrfs_raw_test_opt(fs_info->mount_opt, AUTO_DEFRAG) || sb_rdonly(fs_info->sb))) { - btrfs_cleanup_defrag_inodes(fs_info); - } - - /* If we toggled discard async */ - if (!btrfs_raw_test_opt(old_opts, DISCARD_ASYNC) && - btrfs_test_opt(fs_info, DISCARD_ASYNC)) - btrfs_discard_resume(fs_info); - else if (btrfs_raw_test_opt(old_opts, DISCARD_ASYNC) && - !btrfs_test_opt(fs_info, DISCARD_ASYNC)) - btrfs_discard_cleanup(fs_info); - - /* If we toggled space cache */ - if (cache_opt != btrfs_free_space_cache_v1_active(fs_info)) - btrfs_set_free_space_cache_v1_active(fs_info, cache_opt); -} - -static int btrfs_remount(struct super_block *sb, int *flags, char *data) -{ - struct btrfs_fs_info *fs_info = btrfs_sb(sb); - unsigned old_flags = sb->s_flags; - unsigned long old_opts = fs_info->mount_opt; - unsigned long old_compress_type = fs_info->compress_type; - u64 old_max_inline = fs_info->max_inline; - u32 old_thread_pool_size = fs_info->thread_pool_size; - u32 old_metadata_ratio = fs_info->metadata_ratio; - int ret; - - sync_filesystem(sb); - set_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state); - - if (data) { - void *new_sec_opts = NULL; - - ret = security_sb_eat_lsm_opts(data, &new_sec_opts); - if (!ret) - ret = security_sb_remount(sb, new_sec_opts); - security_free_mnt_opts(&new_sec_opts); - if (ret) - goto restore; - } - - ret = btrfs_parse_options(fs_info, data, *flags); - if (ret) - goto restore; - - ret = btrfs_check_features(fs_info, !(*flags & SB_RDONLY)); - if (ret < 0) - goto restore; - - btrfs_remount_begin(fs_info, old_opts, *flags); - btrfs_resize_thread_pool(fs_info, - fs_info->thread_pool_size, old_thread_pool_size); - - if ((bool)btrfs_test_opt(fs_info, FREE_SPACE_TREE) != - (bool)btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE) && - (!sb_rdonly(sb) || (*flags & SB_RDONLY))) { - btrfs_warn(fs_info, - "remount supports changing free space tree only from ro to rw"); - /* Make sure free space cache options match the state on disk */ - if (btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE)) { - btrfs_set_opt(fs_info->mount_opt, FREE_SPACE_TREE); - btrfs_clear_opt(fs_info->mount_opt, SPACE_CACHE); - } - if (btrfs_free_space_cache_v1_active(fs_info)) { - btrfs_clear_opt(fs_info->mount_opt, FREE_SPACE_TREE); - btrfs_set_opt(fs_info->mount_opt, SPACE_CACHE); - } - } - - if ((bool)(*flags & SB_RDONLY) == sb_rdonly(sb)) - goto out; - - if (*flags & SB_RDONLY) { - /* - * this also happens on 'umount -rf' or on shutdown, when - * the filesystem is busy. - */ - cancel_work_sync(&fs_info->async_reclaim_work); - cancel_work_sync(&fs_info->async_data_reclaim_work); - - btrfs_discard_cleanup(fs_info); - - /* wait for the uuid_scan task to finish */ - down(&fs_info->uuid_tree_rescan_sem); - /* avoid complains from lockdep et al. */ - up(&fs_info->uuid_tree_rescan_sem); - - btrfs_set_sb_rdonly(sb); - - /* - * Setting SB_RDONLY will put the cleaner thread to - * sleep at the next loop if it's already active. - * If it's already asleep, we'll leave unused block - * groups on disk until we're mounted read-write again - * unless we clean them up here. - */ - btrfs_delete_unused_bgs(fs_info); - - /* - * The cleaner task could be already running before we set the - * flag BTRFS_FS_STATE_RO (and SB_RDONLY in the superblock). - * We must make sure that after we finish the remount, i.e. after - * we call btrfs_commit_super(), the cleaner can no longer start - * a transaction - either because it was dropping a dead root, - * running delayed iputs or deleting an unused block group (the - * cleaner picked a block group from the list of unused block - * groups before we were able to in the previous call to - * btrfs_delete_unused_bgs()). - */ - wait_on_bit(&fs_info->flags, BTRFS_FS_CLEANER_RUNNING, - TASK_UNINTERRUPTIBLE); - - /* - * We've set the superblock to RO mode, so we might have made - * the cleaner task sleep without running all pending delayed - * iputs. Go through all the delayed iputs here, so that if an - * unmount happens without remounting RW we don't end up at - * finishing close_ctree() with a non-empty list of delayed - * iputs. - */ - btrfs_run_delayed_iputs(fs_info); - - btrfs_dev_replace_suspend_for_unmount(fs_info); - btrfs_scrub_cancel(fs_info); - btrfs_pause_balance(fs_info); - - /* - * Pause the qgroup rescan worker if it is running. We don't want - * it to be still running after we are in RO mode, as after that, - * by the time we unmount, it might have left a transaction open, - * so we would leak the transaction and/or crash. - */ - btrfs_qgroup_wait_for_completion(fs_info, false); - - ret = btrfs_commit_super(fs_info); - if (ret) - goto restore; - } else { - if (BTRFS_FS_ERROR(fs_info)) { - btrfs_err(fs_info, - "Remounting read-write after error is not allowed"); - ret = -EINVAL; - goto restore; - } - if (fs_info->fs_devices->rw_devices == 0) { - ret = -EACCES; - goto restore; - } - - if (!btrfs_check_rw_degradable(fs_info, NULL)) { - btrfs_warn(fs_info, - "too many missing devices, writable remount is not allowed"); - ret = -EACCES; - goto restore; - } - - if (btrfs_super_log_root(fs_info->super_copy) != 0) { - btrfs_warn(fs_info, - "mount required to replay tree-log, cannot remount read-write"); - ret = -EINVAL; - goto restore; - } - - /* - * NOTE: when remounting with a change that does writes, don't - * put it anywhere above this point, as we are not sure to be - * safe to write until we pass the above checks. - */ - ret = btrfs_start_pre_rw_mount(fs_info); - if (ret) - goto restore; - - btrfs_clear_sb_rdonly(sb); - - set_bit(BTRFS_FS_OPEN, &fs_info->flags); - } -out: - /* - * We need to set SB_I_VERSION here otherwise it'll get cleared by VFS, - * since the absence of the flag means it can be toggled off by remount. - */ - *flags |= SB_I_VERSION; - - wake_up_process(fs_info->transaction_kthread); - btrfs_remount_cleanup(fs_info, old_opts); - btrfs_clear_oneshot_options(fs_info); - clear_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state); - - return 0; - -restore: - /* We've hit an error - don't reset SB_RDONLY */ - if (sb_rdonly(sb)) - old_flags |= SB_RDONLY; - if (!(old_flags & SB_RDONLY)) - clear_bit(BTRFS_FS_STATE_RO, &fs_info->fs_state); - sb->s_flags = old_flags; - fs_info->mount_opt = old_opts; - fs_info->compress_type = old_compress_type; - fs_info->max_inline = old_max_inline; - btrfs_resize_thread_pool(fs_info, - old_thread_pool_size, fs_info->thread_pool_size); - fs_info->metadata_ratio = old_metadata_ratio; - btrfs_remount_cleanup(fs_info, old_opts); - clear_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state); - - return ret; -} - /* Used to sort the devices by max_avail(descending sort) */ static int btrfs_cmp_device_free_bytes(const void *a, const void *b) { @@ -2134,20 +664,13 @@ static void btrfs_kill_super(struct super_block *sb) btrfs_free_fs_info(fs_info); } -static struct file_system_type btrfs_fs_type = { - .owner = THIS_MODULE, - .name = "btrfs", - .mount = btrfs_mount, - .kill_sb = btrfs_kill_super, - .fs_flags = FS_REQUIRES_DEV | FS_BINARY_MOUNTDATA, -}; - -static struct file_system_type btrfs_root_fs_type = { - .owner = THIS_MODULE, - .name = "btrfs", - .mount = btrfs_mount_root, - .kill_sb = btrfs_kill_super, - .fs_flags = FS_REQUIRES_DEV | FS_BINARY_MOUNTDATA | FS_ALLOW_IDMAP, +struct file_system_type btrfs_fs_type = { + .owner = THIS_MODULE, + .name = "btrfs", + .init_fs_context = btrfs_init_fs_context, + .parameters = btrfs_parameter_spec, + .kill_sb = btrfs_kill_super, + .fs_flags = FS_REQUIRES_DEV | FS_BINARY_MOUNTDATA | FS_ALLOW_IDMAP, }; MODULE_ALIAS_FS("btrfs"); @@ -2352,7 +875,6 @@ static const struct super_operations btrfs_super_ops = { .destroy_inode = btrfs_destroy_inode, .free_inode = btrfs_free_inode, .statfs = btrfs_statfs, - .remount_fs = btrfs_remount, .freeze_fs = btrfs_freeze, .unfreeze_fs = btrfs_unfreeze, }; diff --git a/fs/btrfs/super.h b/fs/btrfs/super.h index 8dbb909b364f..2901eea1541c 100644 --- a/fs/btrfs/super.h +++ b/fs/btrfs/super.h @@ -3,8 +3,8 @@ #ifndef BTRFS_SUPER_H #define BTRFS_SUPER_H -int btrfs_parse_options(struct btrfs_fs_info *info, char *options, - unsigned long new_flags); +extern struct file_system_type btrfs_fs_type; + int btrfs_sync_fs(struct super_block *sb, int wait); char *btrfs_get_subvol_name_from_objectid(struct btrfs_fs_info *fs_info, u64 subvol_objectid); @@ -26,4 +26,7 @@ static inline void btrfs_clear_sb_rdonly(struct super_block *sb) clear_bit(BTRFS_FS_STATE_RO, &btrfs_sb(sb)->fs_state); } +int btrfs_fill_super(struct fs_context *fc, struct super_block *sb, + struct btrfs_fs_devices *fs_devices); + #endif -- 2.34.1