blob: b91bcc3c6ee3b51b80a876fd0ec125ab3d8d8811 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Filesystem parameterisation.
*
* Copyright (C) 2007 Oracle. All rights reserved.
* Copyright (C) 2018 Red Hat, Inc. All rights reserved.
*/
#include <linux/mount.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
#include "ctree.h"
#include "volumes.h"
#include "btrfs_inode.h"
#include "rcu-string.h"
enum btrfs_param {
Opt_acl,
Opt_alloc_start,
Opt_autodefrag,
Opt_barrier,
Opt_check_integrity,
Opt_check_integrity_including_extent_data,
Opt_check_integrity_print_mask,
Opt_clear_cache,
Opt_commit_interval,
Opt_compress,
Opt_compress_force,
Opt_datacow,
Opt_datasum,
Opt_degraded,
Opt_device,
Opt_discard,
Opt_enospc_debug,
Opt_fatal_errors,
Opt_flushoncommit,
Opt_fragment,
Opt_inode_cache,
Opt_max_inline,
Opt_metadata_ratio,
Opt_nologreplay,
Opt_nossd,
Opt_recovery,
Opt_ref_verify,
Opt_rescan_uuid_tree,
Opt_skip_balance,
Opt_source,
Opt_space_cache,
Opt_ssd,
Opt_ssd_spread,
Opt_subvol,
Opt_subvolid,
Opt_subvolrootid,
Opt_thread_pool,
Opt_treelog,
Opt_usebackuproot,
Opt_user_subvol_rm_allowed,
nr__btrfs_params
};
enum {
Opt_compress__zlib = 0, /* Arg is optional; default is 0 */
Opt_compress__zlib_1,
Opt_compress__zlib_2,
Opt_compress__zlib_3,
Opt_compress__zlib_4,
Opt_compress__zlib_5,
Opt_compress__zlib_6,
Opt_compress__zlib_7,
Opt_compress__zlib_8,
Opt_compress__zlib_9,
Opt_compress__lzo,
Opt_compress__zstd,
Opt_compress__no,
};
enum btrfs_space_cache {
Opt_space_cache__v1 = 0, /* Arg is optional; default is 0 */
Opt_space_cache__v2,
Opt_space_cache__no,
};
enum {
Opt_fatal_errors__bug,
Opt_fatal_errors__panic,
};
enum {
Opt_fragment__all,
Opt_fragment__data,
Opt_fragment__metadata,
};
static const struct fs_parameter_spec btrfs_param_specs[nr__btrfs_params] = {
[Opt_acl] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_alloc_start] = { fs_param_is_string, fs_param_deprecated },
[Opt_autodefrag] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_barrier] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_check_integrity] = { fs_param_is_flag },
[Opt_check_integrity_including_extent_data]= { fs_param_is_flag },
[Opt_check_integrity_print_mask]= { fs_param_is_u32 },
[Opt_clear_cache] = { fs_param_is_flag },
[Opt_commit_interval] = { fs_param_is_u32 },
[Opt_compress] = { fs_param_is_enum, fs_param_v_optional },
[Opt_compress_force] = { fs_param_is_enum, fs_param_v_optional },
[Opt_datacow] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_datasum] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_degraded] = { fs_param_is_flag },
[Opt_device] = { fs_param_is_string },
[Opt_discard] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_enospc_debug] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_fatal_errors] = { fs_param_is_enum },
[Opt_flushoncommit] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_fragment] = { fs_param_is_enum },
[Opt_inode_cache] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_max_inline] = { fs_param_is_string },
[Opt_metadata_ratio] = { fs_param_is_u32 },
[Opt_nologreplay] = { fs_param_is_flag },
[Opt_nossd] = { fs_param_is_flag },
[Opt_recovery] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_ref_verify] = { fs_param_is_flag },
[Opt_rescan_uuid_tree] = { fs_param_is_flag },
[Opt_skip_balance] = { fs_param_is_flag },
[Opt_source] = { fs_param_is_string },
[Opt_space_cache] = { fs_param_is_enum,
fs_param_v_optional | fs_param_neg_with_no },
[Opt_ssd] = { fs_param_is_flag },
[Opt_ssd_spread] = { fs_param_is_flag },
[Opt_subvol] = { fs_param_is_string, fs_param_v_optional },
[Opt_subvolid] = { fs_param_is_u64 },
[Opt_subvolrootid] = { fs_param_is_s32, fs_param_deprecated },
[Opt_thread_pool] = { fs_param_is_u32 },
[Opt_treelog] = { fs_param_is_flag, fs_param_neg_with_no },
[Opt_usebackuproot] = { fs_param_is_flag },
[Opt_user_subvol_rm_allowed] = { fs_param_is_flag },
};
static const char *const btrfs_param_keys[nr__btrfs_params] = {
[Opt_acl] = "acl",
[Opt_alloc_start] = "alloc_start",
[Opt_autodefrag] = "autodefrag",
[Opt_barrier] = "barrier",
[Opt_check_integrity] = "check_int",
[Opt_check_integrity_including_extent_data] = "check_int_data",
[Opt_check_integrity_print_mask] = "check_int_print_mask",
[Opt_clear_cache] = "clear_cache",
[Opt_commit_interval] = "commit",
[Opt_compress] = "compress",
[Opt_compress_force] = "compress-force",
[Opt_datacow] = "datacow",
[Opt_datasum] = "datasum",
[Opt_degraded] = "degraded",
[Opt_device] = "device",
[Opt_discard] = "discard",
[Opt_enospc_debug] = "enospc_debug",
[Opt_fatal_errors] = "fatal_errors",
[Opt_flushoncommit] = "flushoncommit",
[Opt_fragment] = "fragment",
[Opt_inode_cache] = "inode_cache",
[Opt_max_inline] = "max_inline",
[Opt_metadata_ratio] = "metadata_ratio",
[Opt_nologreplay] = "nologreplay",
[Opt_nossd] = "nossd",
[Opt_recovery] = "recovery",
[Opt_ref_verify] = "ref_verify",
[Opt_rescan_uuid_tree] = "rescan_uuid_tree",
[Opt_skip_balance] = "skip_balance",
[Opt_source] = "source",
[Opt_space_cache] = "space_cache",
[Opt_ssd] = "ssd",
[Opt_ssd_spread] = "ssd_spread",
[Opt_subvol] = "subvol",
[Opt_subvolid] = "subvolid",
[Opt_subvolrootid] = "subvolrootid",
[Opt_thread_pool] = "thread_pool",
[Opt_treelog] = "treelog",
[Opt_usebackuproot] = "usebackuproot",
[Opt_user_subvol_rm_allowed] = "user_subvol_rm_allowed",
};
static const struct fs_parameter_enum btrfs_param_enums[] = {
{ Opt_fragment, "all", Opt_fragment__all },
{ Opt_fragment, "data", Opt_fragment__data },
{ Opt_fragment, "metadata", Opt_fragment__metadata },
{ Opt_compress, "zlib", Opt_compress__zlib },
{ Opt_compress, "zlib:1", Opt_compress__zlib_1 },
{ Opt_compress, "zlib:2", Opt_compress__zlib_2 },
{ Opt_compress, "zlib:3", Opt_compress__zlib_3 },
{ Opt_compress, "zlib:4", Opt_compress__zlib_4 },
{ Opt_compress, "zlib:5", Opt_compress__zlib_5 },
{ Opt_compress, "zlib:6", Opt_compress__zlib_6 },
{ Opt_compress, "zlib:7", Opt_compress__zlib_7 },
{ Opt_compress, "zlib:8", Opt_compress__zlib_8 },
{ Opt_compress, "zlib:9", Opt_compress__zlib_9 },
{ Opt_compress, "lzo", Opt_compress__lzo },
{ Opt_compress, "zstd", Opt_compress__zstd },
{ Opt_compress, "no", Opt_compress__no },
{ Opt_compress_force, "zlib", Opt_compress__zlib },
{ Opt_compress_force, "zlib:1", Opt_compress__zlib_1 },
{ Opt_compress_force, "zlib:2", Opt_compress__zlib_2 },
{ Opt_compress_force, "zlib:3", Opt_compress__zlib_3 },
{ Opt_compress_force, "zlib:4", Opt_compress__zlib_4 },
{ Opt_compress_force, "zlib:5", Opt_compress__zlib_5 },
{ Opt_compress_force, "zlib:6", Opt_compress__zlib_6 },
{ Opt_compress_force, "zlib:7", Opt_compress__zlib_7 },
{ Opt_compress_force, "zlib:8", Opt_compress__zlib_8 },
{ Opt_compress_force, "zlib:9", Opt_compress__zlib_9 },
{ Opt_compress_force, "lzo", Opt_compress__lzo },
{ Opt_compress_force, "zstd", Opt_compress__zstd },
{ Opt_compress_force, "no", Opt_compress__no },
{ Opt_space_cache, "v1", Opt_space_cache__v1 },
{ Opt_space_cache, "v2", Opt_space_cache__v2 },
{ Opt_fatal_errors, "bug", Opt_fatal_errors__bug },
{ Opt_fatal_errors, "panic", Opt_fatal_errors__panic },
};
const struct fs_parameter_description btrfs_fs_parameters = {
.name = "btrfs",
.nr_params = nr__btrfs_params,
.nr_enums = ARRAY_SIZE(btrfs_param_enums),
.source_param = Opt_source,
.keys = btrfs_param_keys,
.specs = btrfs_param_specs,
.enums = btrfs_param_enums,
};
#define btrfs_mparam_set(ctx, opt) \
do { \
ctx->mount_opt |= BTRFS_MOUNT_##opt; \
ctx->mount_opt_mask |= BTRFS_MOUNT_##opt; \
} while (0)
#define btrfs_mparam_clear(ctx, opt) \
do { \
ctx->mount_opt &= ~BTRFS_MOUNT_##opt; \
ctx->mount_opt_mask |= BTRFS_MOUNT_##opt; \
} while (0)
struct btrfs_flag_map {
u8 bit;
#define BTRFS_FLAG_MAP_PRESENT 0x80 /* There is a rule here */
};
#define mount_opt(x) { BTRFS_MOUNT_##x | BTRFS_FLAG_MAP_PRESENT }
static const struct btrfs_flag_map btrfs_flag_map[nr__btrfs_params] = {
[Opt_barrier] = mount_opt(BARRIER),
[Opt_check_integrity] = mount_opt(CHECK_INTEGRITY),
[Opt_check_integrity_including_extent_data] =
mount_opt(CHECK_INTEGRITY_INCLUDING_EXTENT_DATA),
[Opt_clear_cache] = mount_opt(CLEAR_CACHE),
[Opt_compress] = mount_opt(COMPRESS),
[Opt_compress_force] = mount_opt(FORCE_COMPRESS),
[Opt_datacow] = mount_opt(DATACOW),
[Opt_datasum] = mount_opt(DATASUM),
[Opt_autodefrag] = mount_opt(AUTO_DEFRAG),
[Opt_degraded] = mount_opt(DEGRADED),
[Opt_discard] = mount_opt(DISCARD),
[Opt_enospc_debug] = mount_opt(ENOSPC_DEBUG),
[Opt_flushoncommit] = mount_opt(FLUSHONCOMMIT),
[Opt_inode_cache] = mount_opt(INODE_MAP_CACHE),
[Opt_nologreplay] = mount_opt(NOLOGREPLAY),
[Opt_nossd] = mount_opt(NOSSD),
[Opt_ref_verify] = mount_opt(REF_VERIFY),
[Opt_rescan_uuid_tree] = mount_opt(RESCAN_UUID_TREE),
[Opt_skip_balance] = mount_opt(SKIP_BALANCE),
[Opt_ssd] = mount_opt(SSD),
[Opt_ssd_spread] = mount_opt(SSD_SPREAD),
[Opt_treelog] = mount_opt(TREELOG),
[Opt_usebackuproot] = mount_opt(USEBACKUPROOT),
[Opt_user_subvol_rm_allowed] = mount_opt(USER_SUBVOL_RM_ALLOWED),
};
/*
* Implicit rules of option relationships.
*
* "implies" means turning on option $key turns on $target; turning off $target
* turns off $key. Note that we don't automatically calculate transitivity,
* but rather transitive relationships must be explicitly listed.
*
* "contradicts" means turning on option $key turns off $target and vice versa.
*
* The op is mentioned specifically just in case we want to add other options
* later.
*/
struct btrfs_implicit_rule {
u8 key;
u8 target;
};
#define _(key, op, target_key) { key, target_key }
static const struct btrfs_implicit_rule btrfs_opt_dependencies[] = {
_(Opt_datasum, DEPENDS_ON, Opt_datacow),
_(Opt_compress, DEPENDS_ON, Opt_datacow),
_(Opt_compress, DEPENDS_ON, Opt_datasum),
_(Opt_compress_force, DEPENDS_ON, Opt_datacow),
_(Opt_compress_force, DEPENDS_ON, Opt_datasum),
_(Opt_compress_force, DEPENDS_ON, Opt_compress),
_(Opt_ssd_spread, DEPENDS_ON, Opt_ssd),
_(Opt_check_integrity_including_extent_data, DEPENDS_ON, Opt_check_integrity),
};
static const struct btrfs_implicit_rule btrfs_opt_contradictions[] = {
_(Opt_ssd, CONTRADICTS, Opt_nossd),
_(Opt_ssd_spread, CONTRADICTS, Opt_nossd),
_(Opt_nossd, CONTRADICTS, Opt_ssd),
_(Opt_nossd, CONTRADICTS, Opt_ssd_spread),
};
/*
* Parse the mount options into a summary of what is explicitly changed.
*
* This is slightly complicated for BtrFS compared to something like NFS as we
* superblock creation process starts by reading the configuration from disk
* and then applies any parameter changes on top of that.
*
* This means that we need to track what changes we're going to make and how
* they interact - but we have to bear in mind that parameters are not
* commutative.
*/
static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct fs_parse_result result;
struct btrfs_fs_context *ctx = fc->fs_private;
unsigned int mask;
char **devs;
bool enabled;
int opt, i;
u8 bit;
pr_notice("PARM[%s] = %s\n", param->key, param->string);
opt = fs_parse(fc, &btrfs_fs_parameters, param, &result);
if (opt < 0)
return opt;
enabled = !result.negated;
__set_bit(opt, ctx->specified);
/* Do some initial weeding out of options that might not be or might no
* longer be supported.
*/
switch (opt) {
case Opt_source:
case Opt_device:
case Opt_subvol:
case Opt_subvolid:
case Opt_subvolrootid:
switch (fc->purpose) {
case FS_CONTEXT_FOR_USER_MOUNT:
case FS_CONTEXT_FOR_KERNEL_MOUNT:
case FS_CONTEXT_FOR_SUBMOUNT:
break;
default:
return invalf(fc, "btrfs: %s prohibited for remount",
param->key);
}
#ifndef CONFIG_BTRFS_FS_POSIX_ACL
case Opt_acl:
if (!enabled)
return 0;
return invalf(fc, "btrfs: Support for 'acl' not compiled in!");
#endif
case Opt_compress:
case Opt_compress_force:
if (result.uint_32 == Opt_compress__no) {
result.negated = true;
enabled = false;
}
break;
case Opt_space_cache:
if (result.negated)
result.uint_32 = Opt_space_cache__no;
break;
case Opt_recovery:
if (enabled) {
warnf(fc, "btrfs: 'recovery' is deprecated, use 'usebackuproot' instead");
result.key = Opt_usebackuproot;
} else {
btrfs_set_opt(ctx->mount_opt_mask, NOLOGREPLAY);
btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY);
}
break;
#ifndef CONFIG_BTRFS_FS_CHECK_INTEGRITY
case Opt_check_integrity_including_extent_data:
case Opt_check_integrity:
case Opt_check_integrity_print_mask:
return invalf(fc, "btrfs: Support for 'check_int*' not compiled in!");
#endif
#ifndef CONFIG_BTRFS_FS_REF_VERIFY
case Opt_ref_verify:
return 0;
#endif
#ifndef CONFIG_BTRFS_DEBUG
case Opt_fragment:
return invalf(fc, "btrfs: Support for 'fragment' not compiled in!");
#endif
}
/* Map simple option-switching flags into 1-bit changes. */
bit = btrfs_flag_map[result.key].bit;
if (bit) {
bit &= ~BTRFS_FLAG_MAP_PRESENT;
mask = 1 << bit;
ctx->mount_opt_mask |= mask;
ctx->mount_opt_explicit |= mask;
ctx->mount_opt &= ~mask;
if (enabled)
ctx->mount_opt |= mask;
}
if (enabled) {
/* Turn on all the options that this one depends on. */
for (i = 0; i < ARRAY_SIZE(btrfs_opt_dependencies); i++) {
unsigned int target = btrfs_opt_dependencies[i].target;
if (btrfs_opt_dependencies[i].key != result.key)
continue;
bit = btrfs_flag_map[target].bit;
bit &= ~BTRFS_FLAG_MAP_PRESENT;
mask = 1 << bit;
if (!(ctx->mount_opt & mask))
ctx->mount_opt_explicit &= ~mask;
ctx->mount_opt_mask |= mask;
ctx->mount_opt |= mask;
}
/* Turn off all contradictory options. */
for (i = 0; i < ARRAY_SIZE(btrfs_opt_contradictions); i++) {
unsigned int target = btrfs_opt_contradictions[i].target;
if (btrfs_opt_contradictions[i].key != result.key)
continue;
bit = btrfs_flag_map[target].bit;
bit &= ~BTRFS_FLAG_MAP_PRESENT;
mask = 1 << bit;
if (!(ctx->mount_opt & mask))
ctx->mount_opt_explicit &= ~mask;
ctx->mount_opt_mask |= mask;
ctx->mount_opt &= ~mask;
}
} else {
/* Cancel all options that would implicitly turn this on */
for (i = 0; i < ARRAY_SIZE(btrfs_opt_dependencies); i++) {
unsigned int dep = btrfs_opt_dependencies[i].key;
if (btrfs_opt_dependencies[i].target != result.key)
continue;
bit = btrfs_flag_map[dep].bit;
bit &= ~BTRFS_FLAG_MAP_PRESENT;
mask = 1 << bit;
if (ctx->mount_opt & mask)
ctx->mount_opt_explicit &= ~mask;
ctx->mount_opt_mask |= mask;
ctx->mount_opt &= ~mask;
}
}
switch (result.key) {
case Opt_source:
if (fc->source)
return invalf(fc, "btrfs: Multiple sources specified");
fc->source = param->string;
param->string = NULL;
break;
case Opt_device:
devs = krealloc(ctx->devices,
sizeof(char *) * (ctx->nr_devices + 1),
GFP_KERNEL);
if (!devs)
return -ENOMEM;
devs[ctx->nr_devices] = param->string;
param->string = NULL;
ctx->devices = devs;
ctx->nr_devices++;
break;
case Opt_subvol:
kfree(ctx->subvol_name);
ctx->subvol_name = param->string; /* NULL if negative */
param->string = NULL;
break;
case Opt_subvolid:
/* we want the original fs_tree */
if (result.uint_64 == 0)
ctx->subvol_objectid = BTRFS_FS_TREE_OBJECTID;
else
ctx->subvol_objectid = result.uint_64;
break;
case Opt_subvolrootid:
break;
case Opt_compress:
case Opt_compress_force:
switch (result.uint_32) {
case Opt_compress__zlib_1 ... Opt_compress__zlib_9:
ctx->compress_type = BTRFS_COMPRESS_ZLIB;
ctx->compress_level =
result.uint_32 - Opt_compress__zlib_1 + 1;
break;
case Opt_compress__zlib:
ctx->compress_type = BTRFS_COMPRESS_ZLIB;
ctx->compress_level = BTRFS_ZLIB_DEFAULT_LEVEL;
break;
case Opt_compress__lzo:
ctx->compress_type = BTRFS_COMPRESS_LZO;
break;
case Opt_compress__zstd:
ctx->compress_type = BTRFS_COMPRESS_ZSTD;
break;
case Opt_compress__no:
ctx->compress_type = BTRFS_COMPRESS_NONE;
break;
}
break;
case Opt_thread_pool:
if (result.uint_32 == 0)
return invalf(fc, "btrfs: %s: Bad value", param->key);
ctx->thread_pool_size = result.uint_32;
break;
case Opt_max_inline:
ctx->max_inline = memparse(param->string, NULL);
break;
case Opt_alloc_start:
infof(fc, "btrfs: Option alloc_start is obsolete, ignored");
break;
case Opt_metadata_ratio:
ctx->metadata_ratio = result.uint_32;
infof(fc, "btrfs: metadata ratio %u", ctx->metadata_ratio);
break;
case Opt_space_cache:
btrfs_set_opt(ctx->mount_opt_mask, SPACE_CACHE);
btrfs_set_opt(ctx->mount_opt_mask, FREE_SPACE_TREE);
switch (result.uint_32) {
case Opt_space_cache__no:
btrfs_set_opt(ctx->mount_opt_explicit, SPACE_CACHE);
btrfs_set_opt(ctx->mount_opt_explicit, FREE_SPACE_TREE);
btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE);
btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE);
break;
case Opt_space_cache__v1:
btrfs_set_opt(ctx->mount_opt_explicit, SPACE_CACHE);
btrfs_set_opt(ctx->mount_opt, SPACE_CACHE);
btrfs_clear_opt(ctx->mount_opt_explicit, FREE_SPACE_TREE);
btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE);
break;
case Opt_space_cache__v2:
btrfs_clear_opt(ctx->mount_opt_explicit, SPACE_CACHE);
btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE);
btrfs_set_opt(ctx->mount_opt_explicit, FREE_SPACE_TREE);
btrfs_set_opt(ctx->mount_opt, FREE_SPACE_TREE);
break;
}
break;
case Opt_acl:
if (enabled)
fc->sb_flags |= SB_POSIXACL;
else
fc->sb_flags &= ~SB_POSIXACL;
fc->sb_flags_mask |= SB_POSIXACL;
break;
#ifdef CONFIG_BTRFS_FS_CHECK_INTEGRITY
case Opt_check_integrity_print_mask:
ctx->check_integrity_print_mask = result.uint_32;
break;
#endif
case Opt_fatal_errors:
ctx->mount_opt_explicit |= BTRFS_MOUNT_PANIC_ON_FATAL_ERROR;
switch (result.uint_32) {
case Opt_fatal_errors__bug:
ctx->mount_opt &= ~BTRFS_MOUNT_PANIC_ON_FATAL_ERROR;
break;
case Opt_fatal_errors__panic:
ctx->mount_opt |= BTRFS_MOUNT_PANIC_ON_FATAL_ERROR;
break;
}
break;
case Opt_commit_interval:
if (result.uint_32 == 0) {
infof(fc, "btrfs: Using default commit interval %us",
BTRFS_DEFAULT_COMMIT_INTERVAL);
result.uint_32 = BTRFS_DEFAULT_COMMIT_INTERVAL;
} else if (result.uint_32 > 300) {
warnf(fc, "btrfs: Excessive commit interval %u",
result.uint_32);
}
ctx->commit_interval = result.uint_32;
break;
#ifdef CONFIG_BTRFS_DEBUG
case Opt_fragment:
mask = 0;
switch (result.uint_32) {
case Opt_fragment__all:
mask = BTRFS_MOUNT_FRAGMENT_DATA;
mask |= BTRFS_MOUNT_FRAGMENT_METADATA;
break;
case Opt_fragment__metadata:
mask = BTRFS_MOUNT_FRAGMENT_METADATA;
break;
case Opt_fragment__data:
mask |= BTRFS_MOUNT_FRAGMENT_DATA;
break;
}
ctx->mount_opt |= mask;
ctx->mount_opt_mask |= mask;
ctx->mount_opt_explicit |= mask;
break;
#endif
default:
break;
}
return 0;
}
static int btrfs_validate(struct fs_context *fc)
{
struct btrfs_fs_context *ctx = fc->fs_private;
if (btrfs_raw_test_opt(ctx->mount_opt_mask, NOLOGREPLAY) &&
btrfs_raw_test_opt(ctx->mount_opt, NOLOGREPLAY) &&
!(fc->sb_flags & SB_RDONLY))
return invalf(fc, "nologreplay must be used with ro mount option");
return 0;
}
static int btrfs_dup_context(struct fs_context *fc, struct fs_context *src_fc)
{
struct btrfs_fs_context *ctx, *src = src_fc->fs_private;
int i;
ctx = kmemdup(src, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->subvol_name = NULL;
ctx->devices = NULL;
ctx->root_mnt = NULL;
if (src->subvol_name) {
ctx->subvol_name = kstrdup(src->subvol_name, GFP_KERNEL);
if (!ctx->subvol_name)
goto nomem_ctx;
}
if (ctx->nr_devices) {
ctx->devices = kcalloc(ctx->nr_devices, sizeof(char *), GFP_KERNEL);
if (!ctx->devices)
goto nomem_sub;
for (i = 0; i < ctx->nr_devices; i++) {
ctx->devices[i] = kstrdup(src->devices[i], GFP_KERNEL);
if (!ctx->devices[i])
goto nomem_devs;
}
}
if (src_fc->source) {
fc->source = kstrdup(src_fc->source, GFP_KERNEL);
if (!fc->source)
goto nomem_devs;
}
fc->fs_private = ctx;
return 0;
nomem_devs:
for (i = 0; i < ctx->nr_devices; i++)
kfree(ctx->devices[i]);
kfree(ctx->devices);
nomem_sub:
kfree(ctx->subvol_name);
nomem_ctx:
kfree(ctx);
return -ENOMEM;
}
static void btrfs_free_context(struct fs_context *fc)
{
struct btrfs_fs_context *ctx = fc->fs_private;
struct btrfs_fs_info *info = fc->s_fs_info;
int i;
if (info)
free_fs_info(info);
if (ctx) {
mntput(ctx->root_mnt);
if (ctx->devices) {
for (i = 0; i < ctx->nr_devices; i++)
kfree(ctx->devices[i]);
kfree(ctx->devices);
}
kfree(ctx->subvol_name);
kfree(ctx);
}
}
static struct fs_context_operations btrfs_context_ops = {
.free = btrfs_free_context,
.dup = btrfs_dup_context,
.parse_param = btrfs_parse_param,
.validate = btrfs_validate,
.get_tree = btrfs_get_tree,
.reconfigure = btrfs_reconfigure,
};
/*
* Set up the filesystem configuration context.
*/
int btrfs_init_fs_context(struct fs_context *fc, struct dentry *reference)
{
struct btrfs_fs_context *ctx;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
fc->ops = &btrfs_context_ops;
fc->fs_private = ctx;
return 0;
}
static const char *btrfs_options[32] = {
[BTRFS_MOUNT_AUTO_DEFRAG] = "auto defrag",
[BTRFS_MOUNT_BARRIER] = "barriers",
[BTRFS_MOUNT_DATACOW] = "datacow",
[BTRFS_MOUNT_DATASUM] = "datasum",
[BTRFS_MOUNT_DEGRADED] = "degraded mounts",
[BTRFS_MOUNT_DISCARD] = "discard",
[BTRFS_MOUNT_FLUSHONCOMMIT] = "flush-on-commit",
[BTRFS_MOUNT_FREE_SPACE_TREE] = "free space tree",
[BTRFS_MOUNT_INODE_MAP_CACHE] = "inode map caching",
[BTRFS_MOUNT_NOLOGREPLAY] = "no log replay at mount time",
[BTRFS_MOUNT_REF_VERIFY] = "ref verification",
[BTRFS_MOUNT_SPACE_CACHE] = "disk space caching",
[BTRFS_MOUNT_SSD] = "ssd optimizations",
[BTRFS_MOUNT_SSD_SPREAD] = "spread ssd allocation scheme",
[BTRFS_MOUNT_TREELOG] = "tree log",
};
/*
* Apply the configuration to a superblock.
*/
void btrfs_apply_configuration(struct fs_context *fc, struct super_block *sb)
{
struct btrfs_fs_context *ctx = fc->fs_private;
struct btrfs_fs_info *info = sb->s_fs_info;
unsigned int changes = (ctx->mount_opt ^ info->mount_opt) & ctx->mount_opt_mask;
unsigned int explicit_changes = changes & ctx->mount_opt_explicit;
unsigned int implicit_changes = changes & ~ctx->mount_opt_explicit;
unsigned int mask, tmp;
int i;
for (tmp = explicit_changes; tmp; tmp &= ~(1 << i)) {
i = __ffs(tmp);
if (!btrfs_options[i])
continue;
if (ctx->mount_opt & (1 << i ))
btrfs_info(info, "Enabling %s", btrfs_options[i]);
else
btrfs_info(info, "Disabling %s", btrfs_options[i]);
}
for (tmp = implicit_changes; tmp; tmp &= ~(1 << i)) {
i = __ffs(tmp);
if (!btrfs_options[i])
continue;
if (ctx->mount_opt & (1 << i ))
btrfs_info(info, "Implicitly enabling %s", btrfs_options[i]);
else
btrfs_info(info, "Implicitly disabling %s", btrfs_options[i]);
}
if (btrfs_raw_test_opt(changes, COMPRESS) ||
btrfs_raw_test_opt(changes, FORCE_COMPRESS)) {
if (btrfs_raw_test_opt(ctx->mount_opt, COMPRESS))
btrfs_info(info, "Enabling %s%s compression, level %d",
btrfs_raw_test_opt(changes, FORCE_COMPRESS) ?
"Forced " : "",
btrfs_compress_type2str(ctx->compress_type),
ctx->compress_level);
else if (btrfs_raw_test_opt(explicit_changes, COMPRESS))
btrfs_info(info, "Disabling compression");
else
btrfs_info(info, "Implicitly disabling compression");
}
if (test_bit(Opt_max_inline, ctx->specified)) {
u64 max_inline = min_t(u64, ctx->max_inline, info->sectorsize);
ctx->max_inline = max_inline;
if (max_inline != info->max_inline)
btrfs_info(info, "max_inline at %llu", ctx->max_inline);
}
if (test_bit(Opt_metadata_ratio, ctx->specified) &&
ctx->metadata_ratio != info->metadata_ratio)
btrfs_info(info, "Metadata ratio %u", info->metadata_ratio);
if (btrfs_raw_test_opt(ctx->mount_opt & explicit_changes, CLEAR_CACHE))
btrfs_info(info, "Force clearing of disk cache");
if (btrfs_raw_test_opt(ctx->mount_opt & explicit_changes, USEBACKUPROOT))
btrfs_info(info, "Trying to use backup root at mount time");
#ifdef CONFIG_BTRFS_FS_CHECK_INTEGRITY
switch (ctx->mount_opt & explicit_changes &
((1 << BTRFS_MOUNT_CHECK_INTEGRITY) |
(1 << BTRFS_MOUNT_CHECK_INTEGRITY_INCLUDING_EXTENT_DATA))) {
case (1 << BTRFS_MOUNT_CHECK_INTEGRITY):
btrfs_info(info, "Enabling check integrity");
break;
case ((1 << BTRFS_MOUNT_CHECK_INTEGRITY) |
(1 << BTRFS_MOUNT_CHECK_INTEGRITY_INCLUDING_EXTENT_DATA)):
btrfs_info(info, "Enabling check integrity including extent data");
break;
}
if (test_bit(Opt_check_integrity_print_mask, ctx->specified) &&
ctx->check_integrity_print_mask != info->check_integrity_print_mask)
btrfs_info(info, "check_integrity_print_mask 0x%x",
ctx->check_integrity_print_mask);
#endif
#ifdef CONFIG_BTRFS_DEBUG
switch (ctx->mount_opt & explicit_changes &
((1 << BTRFS_MOUNT_FRAGMENT_DATA) |
(1 << BTRFS_MOUNT_FRAGMENT_METADATA))) {
case 0:
break;
case (1 << BTRFS_MOUNT_FRAGMENT_DATA):
btrfs_info(info, "fragmenting data");
break;
case (1 << BTRFS_MOUNT_FRAGMENT_METADATA):
btrfs_info(info, "fragmenting metadata");
break;
default:
btrfs_info(info, "fragmenting all space");
break;
}
#endif
/* Actually apply the options */
mask = changes;
mask &= ~(1 << BTRFS_MOUNT_INODE_MAP_CACHE);
tmp = READ_ONCE(info->mount_opt);
tmp &= ~mask;
tmp |= ctx->mount_opt & mask;
info->compress_level = ctx->compress_level;
info->compress_type = ctx->compress_type;
WRITE_ONCE(info->mount_opt, tmp);
if (btrfs_raw_test_opt(explicit_changes, INODE_MAP_CACHE)) {
if (btrfs_test_opt(info, INODE_MAP_CACHE)) {
btrfs_set_pending((info), SET_INODE_MAP_CACHE);
btrfs_clear_pending((info), CLEAR_INODE_MAP_CACHE);
} else {
btrfs_set_pending((info), CLEAR_INODE_MAP_CACHE);
btrfs_clear_pending((info), SET_INODE_MAP_CACHE);
}
}
if (test_bit(Opt_commit_interval, ctx->specified))
info->commit_interval = ctx->commit_interval;
if (test_bit(Opt_thread_pool, ctx->specified))
info->thread_pool_size = ctx->thread_pool_size;
#ifdef CONFIG_BTRFS_FS_CHECK_INTEGRITY
if (test_bit(Opt_check_integrity_print_mask, ctx->specified))
info->check_integrity_print_mask = ctx->check_integrity_print_mask;
#endif
if (test_bit(Opt_metadata_ratio, ctx->specified))
info->metadata_ratio = info->metadata_ratio;
if (test_bit(Opt_max_inline, ctx->specified))
info->max_inline = ctx->max_inline;
}
/*
* Display the options for /proc/mounts
*/
int btrfs_show_options(struct seq_file *seq, struct dentry *dentry)
{
struct btrfs_fs_info *info = btrfs_sb(dentry->d_sb);
const char *compress_type;
if (btrfs_test_opt(info, DEGRADED))
seq_puts(seq, ",degraded");
if (!btrfs_test_opt(info, DATASUM))
seq_puts(seq, ",nodatasum");
if (!btrfs_test_opt(info, DATACOW))
seq_puts(seq, ",nodatacow");
if (!btrfs_test_opt(info, BARRIER))
seq_puts(seq, ",nobarrier");
if (info->max_inline != BTRFS_DEFAULT_MAX_INLINE)
seq_printf(seq, ",max_inline=%llu", info->max_inline);
if (info->thread_pool_size != min_t(unsigned long,
num_online_cpus() + 2, 8))
seq_printf(seq, ",thread_pool=%u", info->thread_pool_size);
if (btrfs_test_opt(info, COMPRESS)) {
compress_type = btrfs_compress_type2str(info->compress_type);
if (btrfs_test_opt(info, FORCE_COMPRESS))
seq_printf(seq, ",compress-force=%s", compress_type);
else
seq_printf(seq, ",compress=%s", compress_type);
if (info->compress_level)
seq_printf(seq, ":%d", info->compress_level);
}
if (btrfs_test_opt(info, NOSSD))
seq_puts(seq, ",nossd");
if (btrfs_test_opt(info, SSD_SPREAD))
seq_puts(seq, ",ssd_spread");
else if (btrfs_test_opt(info, SSD))
seq_puts(seq, ",ssd");
if (!btrfs_test_opt(info, TREELOG))
seq_puts(seq, ",notreelog");
if (btrfs_test_opt(info, NOLOGREPLAY))
seq_puts(seq, ",nologreplay");
if (btrfs_test_opt(info, FLUSHONCOMMIT))
seq_puts(seq, ",flushoncommit");
if (btrfs_test_opt(info, DISCARD))
seq_puts(seq, ",discard");
if (!(info->sb->s_flags & SB_POSIXACL))
seq_puts(seq, ",noacl");
if (btrfs_test_opt(info, SPACE_CACHE))
seq_puts(seq, ",space_cache");
else if (btrfs_test_opt(info, FREE_SPACE_TREE))
seq_puts(seq, ",space_cache=v2");
else
seq_puts(seq, ",nospace_cache");
if (btrfs_test_opt(info, RESCAN_UUID_TREE))
seq_puts(seq, ",rescan_uuid_tree");
if (btrfs_test_opt(info, CLEAR_CACHE))
seq_puts(seq, ",clear_cache");
if (btrfs_test_opt(info, USER_SUBVOL_RM_ALLOWED))
seq_puts(seq, ",user_subvol_rm_allowed");
if (btrfs_test_opt(info, ENOSPC_DEBUG))
seq_puts(seq, ",enospc_debug");
if (btrfs_test_opt(info, AUTO_DEFRAG))
seq_puts(seq, ",autodefrag");
if (btrfs_test_opt(info, INODE_MAP_CACHE))
seq_puts(seq, ",inode_cache");
if (btrfs_test_opt(info, SKIP_BALANCE))
seq_puts(seq, ",skip_balance");
#ifdef CONFIG_BTRFS_FS_CHECK_INTEGRITY
if (btrfs_test_opt(info, CHECK_INTEGRITY_INCLUDING_EXTENT_DATA))
seq_puts(seq, ",check_int_data");
else if (btrfs_test_opt(info, CHECK_INTEGRITY))
seq_puts(seq, ",check_int");
if (info->check_integrity_print_mask)
seq_printf(seq, ",check_int_print_mask=%d",
info->check_integrity_print_mask);
#endif
if (info->metadata_ratio)
seq_printf(seq, ",metadata_ratio=%u", info->metadata_ratio);
if (btrfs_test_opt(info, PANIC_ON_FATAL_ERROR))
seq_puts(seq, ",fatal_errors=panic");
if (info->commit_interval != BTRFS_DEFAULT_COMMIT_INTERVAL)
seq_printf(seq, ",commit=%u", info->commit_interval);
#ifdef CONFIG_BTRFS_DEBUG
if (btrfs_test_opt(info, FRAGMENT_DATA))
seq_puts(seq, ",fragment=data");
if (btrfs_test_opt(info, FRAGMENT_METADATA))
seq_puts(seq, ",fragment=metadata");
#endif
if (btrfs_test_opt(info, REF_VERIFY))
seq_puts(seq, ",ref_verify");
seq_printf(seq, ",subvolid=%llu",
BTRFS_I(d_inode(dentry))->root->root_key.objectid);
seq_puts(seq, ",subvol=");
seq_dentry(seq, dentry, " \t\n\\");
return 0;
}