btrfs-progs/common/fsfeatures.c
David Sterba b421fdff95 btrfs-progs: move raid-stripe-tree and squota build out of experimental
The kernel patches for RST and squota are queued for 6.7, we need to be
able to test the features so it's not necessary to hide the mkfs support
under experimental build. The kernel may still need debug build to
enable mount.

Signed-off-by: David Sterba <dsterba@suse.com>
2023-10-17 19:33:59 +02:00

709 lines
18 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*/
#include "kerncompat.h"
#include <sys/utsname.h>
#include <sys/ioctl.h>
#include <linux/version.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "kernel-lib/sizes.h"
#include "kernel-shared/uapi/btrfs.h"
#include "kernel-shared/uapi/btrfs_tree.h"
#include "kernel-shared/ctree.h"
#include "common/fsfeatures.h"
#include "common/string-utils.h"
#include "common/sysfs-utils.h"
#include "common/messages.h"
/*
* Insert a root item for temporary tree root
*
* Only used in make_btrfs_v2().
*/
#define VERSION_TO_STRING3(name, a,b,c) \
.name ## _str = #a "." #b "." #c, \
.name ## _ver = KERNEL_VERSION(a,b,c)
#define VERSION_TO_STRING2(name, a,b) \
.name ## _str = #a "." #b, \
.name ## _ver = KERNEL_VERSION(a,b,0)
#define VERSION_NULL(name) \
.name ## _str = NULL, \
.name ## _ver = 0
/*
* For feature names that are only an alias we don't need to duplicate
* versions.
*
* When compat_str is NULL, the feature descriptor is an alias.
*/
#define VERSION_ALIAS \
VERSION_NULL(compat), \
VERSION_NULL(safe), \
VERSION_NULL(default)
enum feature_source {
FS_FEATURES,
RUNTIME_FEATURES,
};
/*
* Feature stability status and versions: compat <= safe <= default
*/
struct btrfs_feature {
const char *name;
/*
* At least one of the bit must be set in the following *_flag member.
*
* For features like list-all and quota which don't have any
* incompat/compat_ro bit set, it go to runtime_flag.
*/
u64 incompat_flag;
u64 compat_ro_flag;
u64 runtime_flag;
const char *sysfs_name;
/*
* Compatibility with kernel of given version. Filesystem can be
* mounted.
*/
const char *compat_str;
u32 compat_ver;
/*
* Considered safe for use, but is not on by default, even if the
* kernel supports the feature.
*/
const char *safe_str;
u32 safe_ver;
/*
* Considered safe for use and will be turned on by default if
* supported by the running kernel.
*/
const char *default_str;
u32 default_ver;
const char *desc;
};
/*
* Keep the list sorted by compat version.
*/
static const struct btrfs_feature mkfs_features[] = {
{
.name = "mixed-bg",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS,
.sysfs_name = "mixed_groups",
VERSION_TO_STRING3(compat, 2,6,37),
VERSION_TO_STRING3(safe, 2,6,37),
VERSION_NULL(default),
.desc = "mixed data and metadata block groups"
},
{
.name = "quota",
.runtime_flag = BTRFS_FEATURE_RUNTIME_QUOTA,
.sysfs_name = NULL,
VERSION_TO_STRING2(compat, 3,4),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = "quota support (qgroups)"
},
{
.name = "extref",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_EXTENDED_IREF,
.sysfs_name = "extended_iref",
VERSION_TO_STRING2(compat, 3,7),
VERSION_TO_STRING2(safe, 3,12),
VERSION_TO_STRING2(default, 3,12),
.desc = "increased hardlink limit per file to 65536"
}, {
.name = "raid56",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_RAID56,
.sysfs_name = "raid56",
VERSION_TO_STRING2(compat, 3,9),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = "raid56 extended format"
}, {
.name = "skinny-metadata",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_SKINNY_METADATA,
.sysfs_name = "skinny_metadata",
VERSION_TO_STRING2(compat, 3,10),
VERSION_TO_STRING2(safe, 3,18),
VERSION_TO_STRING2(default, 3,18),
.desc = "reduced-size metadata extent refs"
}, {
.name = "no-holes",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_NO_HOLES,
.sysfs_name = "no_holes",
VERSION_TO_STRING2(compat, 3,14),
VERSION_TO_STRING2(safe, 4,0),
VERSION_TO_STRING2(default, 5,15),
.desc = "no explicit hole extents for files"
},
{
.name = "fst",
.compat_ro_flag = BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE |
BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID,
.sysfs_name = "free_space_tree",
VERSION_ALIAS,
.desc = "free-space-tree alias"
},
{
.name = "free-space-tree",
.compat_ro_flag = BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE |
BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID,
.sysfs_name = "free_space_tree",
VERSION_TO_STRING2(compat, 4,5),
VERSION_TO_STRING2(safe, 4,9),
VERSION_TO_STRING2(default, 5,15),
.desc = "free space tree (space_cache=v2)"
},
{
.name = "raid1c34",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_RAID1C34,
.sysfs_name = "raid1c34",
VERSION_TO_STRING2(compat, 5,5),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = "RAID1 with 3 or 4 copies"
},
#ifdef BTRFS_ZONED
{
.name = "zoned",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_ZONED,
.sysfs_name = "zoned",
VERSION_TO_STRING2(compat, 5,12),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = "support zoned devices"
},
#endif
#if EXPERIMENTAL
{
.name = "extent-tree-v2",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2,
.sysfs_name = "extent_tree_v2",
VERSION_TO_STRING2(compat, 5,15),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = "new extent tree format"
},
#endif
{
.name = "bgt",
.compat_ro_flag = BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE,
.sysfs_name = "block_group_tree",
VERSION_ALIAS,
.desc = "block-group-tree alias"
},
{
.name = "block-group-tree",
.compat_ro_flag = BTRFS_FEATURE_COMPAT_RO_BLOCK_GROUP_TREE,
.sysfs_name = "block_group_tree",
VERSION_TO_STRING2(compat, 6,1),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = "block group tree to reduce mount time"
},
{
.name = "rst",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE,
.sysfs_name = "raid_stripe_tree",
VERSION_ALIAS,
.desc = "raid-stripe-tree alias"
},
{
.name = "raid-stripe-tree",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE,
.sysfs_name = "raid_stripe_tree",
VERSION_TO_STRING2(compat, 6,7),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = "raid stripe tree"
},
{
.name = "squota",
.incompat_flag = BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA,
.sysfs_name = "simple_quota",
VERSION_TO_STRING2(compat, 6,7),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = "squota support (simple accounting qgroups)"
},
/* Keep this one last */
{
.name = "list-all",
.runtime_flag = BTRFS_FEATURE_RUNTIME_LIST_ALL,
.sysfs_name = NULL,
VERSION_NULL(compat),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = NULL
}
};
static const struct btrfs_feature runtime_features[] = {
{
.name = "quota",
.runtime_flag = BTRFS_FEATURE_RUNTIME_QUOTA,
.sysfs_name = NULL,
VERSION_TO_STRING2(compat, 3,4),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = "quota support (qgroups)"
}, {
.name = "free-space-tree",
.compat_ro_flag = BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE |
BTRFS_FEATURE_COMPAT_RO_FREE_SPACE_TREE_VALID,
.sysfs_name = "free_space_tree",
VERSION_TO_STRING2(compat, 4,5),
VERSION_TO_STRING2(safe, 4,9),
VERSION_TO_STRING2(default, 5,15),
.desc = "free space tree (space_cache=v2)"
},
/* Keep this one last */
{
.name = "list-all",
.runtime_flag = BTRFS_FEATURE_RUNTIME_LIST_ALL,
.sysfs_name = NULL,
VERSION_NULL(compat),
VERSION_NULL(safe),
VERSION_NULL(default),
.desc = NULL
}
};
static bool feature_name_is_alias(const struct btrfs_feature *feature)
{
return feature->compat_str == NULL;
}
/*
* This is a sanity check to make sure BTRFS_FEATURE_STRING_BUF_SIZE is large
* enough to contain all strings.
*
* All callers using btrfs_parse_*_features_to_string() should call this first.
*/
void btrfs_assert_feature_buf_size(void)
{
int total_size = 0;
int i;
/*
* This is a little over-calculated, as we include ", list-all".
* But 10 extra bytes should not be a big deal.
*/
for (i = 0; i < ARRAY_SIZE(mkfs_features); i++)
/* The extra 2 bytes are for the ", " prefix. */
total_size += strlen(mkfs_features[i].name) + 2;
BUG_ON(BTRFS_FEATURE_STRING_BUF_SIZE < total_size);
total_size = 0;
for (i = 0; i < ARRAY_SIZE(runtime_features); i++)
total_size += strlen(runtime_features[i].name) + 2;
BUG_ON(BTRFS_FEATURE_STRING_BUF_SIZE < total_size);
}
static size_t get_feature_array_size(enum feature_source source)
{
if (source == FS_FEATURES)
return ARRAY_SIZE(mkfs_features);
if (source == RUNTIME_FEATURES)
return ARRAY_SIZE(runtime_features);
return 0;
}
static const struct btrfs_feature *get_feature(int i, enum feature_source source)
{
if (source == FS_FEATURES)
return &mkfs_features[i];
if (source == RUNTIME_FEATURES)
return &runtime_features[i];
return NULL;
}
static int parse_one_fs_feature(const char *name,
struct btrfs_mkfs_features *features,
enum feature_source source)
{
const int array_size = get_feature_array_size(source);
int i;
int found = 0;
for (i = 0; i < array_size; i++) {
const struct btrfs_feature *feat = get_feature(i, source);
if (name[0] == '^' && !strcmp(feat->name, name + 1)) {
features->compat_ro_flags &= ~feat->compat_ro_flag;
features->incompat_flags &= ~feat->incompat_flag;
features->runtime_flags &= ~feat->runtime_flag;
found = 1;
} else if (!strcmp(feat->name, name)) {
features->compat_ro_flags |= feat->compat_ro_flag;
features->incompat_flags |= feat->incompat_flag;
features->runtime_flags |= feat->runtime_flag;
found = 1;
}
}
return !found;
}
static void parse_features_to_string(char *buf,
const struct btrfs_mkfs_features *features,
enum feature_source source)
{
const int array_size = get_feature_array_size(source);
int i;
buf[0] = 0;
for (i = 0; i < array_size; i++) {
const struct btrfs_feature *feat = get_feature(i, source);
if (feature_name_is_alias(feat))
continue;
if (features->compat_ro_flags & feat->compat_ro_flag ||
features->incompat_flags & feat->incompat_flag ||
features->runtime_flags & feat->runtime_flag) {
if (*buf)
strcat(buf, ", ");
strcat(buf, feat->name);
}
}
}
void btrfs_parse_fs_features_to_string(char *buf,
const struct btrfs_mkfs_features *features)
{
parse_features_to_string(buf, features, FS_FEATURES);
}
void btrfs_parse_runtime_features_to_string(char *buf,
const struct btrfs_mkfs_features *features)
{
parse_features_to_string(buf, features, RUNTIME_FEATURES);
}
static void process_features(struct btrfs_mkfs_features *features,
enum feature_source source)
{
const int array_size = get_feature_array_size(source);
int i;
for (i = 0; i < array_size; i++) {
const struct btrfs_feature *feat = get_feature(i, source);
if ((features->compat_ro_flags & feat->compat_ro_flag ||
features->incompat_flags & feat->incompat_flag ||
features->runtime_flags & feat->runtime_flag) &&
feat->name && feat->desc) {
printf("Turning ON incompat feature '%s': %s\n",
feat->name, feat->desc);
}
}
}
void btrfs_process_fs_features(struct btrfs_mkfs_features *features)
{
process_features(features, FS_FEATURES);
}
void btrfs_process_runtime_features(struct btrfs_mkfs_features *features)
{
process_features(features, RUNTIME_FEATURES);
}
static void list_all_features(const struct btrfs_mkfs_features *allowed,
enum feature_source source)
{
const int array_size = get_feature_array_size(source);
int i;
char *prefix;
if (source == FS_FEATURES)
prefix = "Filesystem";
else if (source == RUNTIME_FEATURES)
prefix = "Runtime";
else
prefix = "UNKNOWN";
fprintf(stderr, "%s features available:\n", prefix);
for (i = 0; i < array_size - 1; i++) {
const struct btrfs_feature *feat = get_feature(i, source);
const char *sep = "";
/* The feature is not in the allowed one, skip it. */
if (allowed &&
!(feat->compat_ro_flag & allowed->compat_ro_flags ||
feat->incompat_flag & allowed->incompat_flags ||
feat->runtime_flag & allowed->runtime_flags))
continue;
fprintf(stderr, "%-20s- %s", feat->name, feat->desc);
if (feature_name_is_alias(feat)) {
fprintf(stderr, "\n");
continue;
}
fprintf(stderr, " (");
if (feat->compat_ver) {
fprintf(stderr, "compat=%s", feat->compat_str);
sep = ", ";
}
if (feat->safe_ver) {
fprintf(stderr, "%ssafe=%s", sep, feat->safe_str);
sep = ", ";
}
if (feat->default_ver)
fprintf(stderr, "%sdefault=%s", sep, feat->default_str);
fprintf(stderr, ")\n");
}
}
/* @allowed can be null, then all features will be listed. */
void btrfs_list_all_fs_features(const struct btrfs_mkfs_features *allowed)
{
list_all_features(allowed, FS_FEATURES);
}
/* @allowed can be null, then all runtime features will be listed. */
void btrfs_list_all_runtime_features(const struct btrfs_mkfs_features *allowed)
{
list_all_features(allowed, RUNTIME_FEATURES);
}
/*
* Return NULL if all features were parsed fine, otherwise return the name of
* the first unparsed.
*/
static char *parse_features(char *namelist,
struct btrfs_mkfs_features *features,
enum feature_source source)
{
char *this_char;
char *save_ptr = NULL; /* Satisfy static checkers */
for (this_char = strtok_r(namelist, ",", &save_ptr);
this_char != NULL;
this_char = strtok_r(NULL, ",", &save_ptr)) {
if (parse_one_fs_feature(this_char, features, source))
return this_char;
}
return NULL;
}
char *btrfs_parse_fs_features(char *namelist,
struct btrfs_mkfs_features *features)
{
return parse_features(namelist, features, FS_FEATURES);
}
char *btrfs_parse_runtime_features(char *namelist,
struct btrfs_mkfs_features *features)
{
return parse_features(namelist, features, RUNTIME_FEATURES);
}
void print_kernel_version(FILE *stream, u32 version)
{
u32 v[3];
v[0] = version & 0xFF;
v[1] = (version >> 8) & 0xFF;
v[2] = version >> 16;
fprintf(stream, "%u.%u", v[2], v[1]);
if (v[0])
fprintf(stream, ".%u", v[0]);
}
u32 get_running_kernel_version(void)
{
struct utsname utsbuf;
char *tmp;
char *saveptr = NULL;
u32 version;
uname(&utsbuf);
if (strcmp(utsbuf.sysname, "Linux") != 0) {
error("unsupported system: %s", utsbuf.sysname);
exit(1);
}
/* 1.2.3-4-name */
tmp = strchr(utsbuf.release, '-');
if (tmp)
*tmp = 0;
tmp = strtok_r(utsbuf.release, ".", &saveptr);
if (!string_is_numerical(tmp))
return (u32)-1;
version = atoi(tmp) << 16;
tmp = strtok_r(NULL, ".", &saveptr);
if (!string_is_numerical(tmp))
return (u32)-1;
version |= atoi(tmp) << 8;
tmp = strtok_r(NULL, ".", &saveptr);
/* Relaxed format accepts eg. 1.2.3+ */
if (tmp && string_is_numerical(tmp))
version |= atoi(tmp);
return version;
}
/*
* The buffer size is strlen of "4096 8192 16384 32768 65536", which is 28,
* then round up to 32.
*/
#define SUPPORTED_SECTORSIZE_BUF_SIZE 32
/*
* Check if current kernel supports the given size
*/
static bool check_supported_sectorsize(u32 sectorsize)
{
char supported_buf[SUPPORTED_SECTORSIZE_BUF_SIZE] = { 0 };
char sectorsize_buf[SUPPORTED_SECTORSIZE_BUF_SIZE] = { 0 };
char *this_char;
char *save_ptr = NULL;
int fd;
int ret;
fd = sysfs_open_file("features/supported_sectorsizes");
if (fd < 0)
return false;
ret = sysfs_read_file(fd, supported_buf, SUPPORTED_SECTORSIZE_BUF_SIZE);
close(fd);
if (ret < 0)
return false;
snprintf(sectorsize_buf, SUPPORTED_SECTORSIZE_BUF_SIZE, "%u", sectorsize);
for (this_char = strtok_r(supported_buf, " ", &save_ptr);
this_char != NULL;
this_char = strtok_r(NULL, " ", &save_ptr)) {
/*
* Also check the terminal '\0' to handle cases like
* "4096" and "40960".
*/
if (!strncmp(this_char, sectorsize_buf, strlen(sectorsize_buf) + 1))
return true;
}
return false;
}
int btrfs_check_sectorsize(u32 sectorsize)
{
bool sectorsize_checked = false;
u32 page_size = (u32)sysconf(_SC_PAGESIZE);
if (!is_power_of_2(sectorsize)) {
error("invalid sectorsize %u, must be power of 2", sectorsize);
return -EINVAL;
}
if (sectorsize < SZ_4K || sectorsize > SZ_64K) {
error("invalid sectorsize %u, expected range is [4K, 64K]",
sectorsize);
return -EINVAL;
}
if (page_size == sectorsize)
sectorsize_checked = true;
else
sectorsize_checked = check_supported_sectorsize(sectorsize);
if (!sectorsize_checked)
warning(
"the filesystem may not be mountable, sectorsize %u doesn't match page size %u",
sectorsize, page_size);
return 0;
}
int btrfs_check_nodesize(u32 nodesize, u32 sectorsize,
struct btrfs_mkfs_features *features)
{
if (nodesize < sectorsize) {
error("illegal nodesize %u (smaller than %u)",
nodesize, sectorsize);
return -1;
} else if (nodesize > BTRFS_MAX_METADATA_BLOCKSIZE) {
error("illegal nodesize %u (larger than %u)",
nodesize, BTRFS_MAX_METADATA_BLOCKSIZE);
return -1;
} else if (nodesize & (sectorsize - 1)) {
error("illegal nodesize %u (not aligned to %u)",
nodesize, sectorsize);
return -1;
} else if (features->incompat_flags &
BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS &&
nodesize != sectorsize) {
error(
"illegal nodesize %u (not equal to %u for mixed block group)",
nodesize, sectorsize);
return -1;
}
return 0;
}
int btrfs_check_features(const struct btrfs_mkfs_features *features,
const struct btrfs_mkfs_features *allowed)
{
if (features->compat_ro_flags & ~allowed->compat_ro_flags ||
features->incompat_flags & ~allowed->incompat_flags ||
features->runtime_flags & ~allowed->runtime_flags)
return -EINVAL;
return 0;
}
/*
* Check if the BTRFS_IOC_TREE_SEARCH_V2 ioctl is supported on a given
* filesystem, opened at fd
*/
int btrfs_tree_search2_ioctl_supported(int fd)
{
struct btrfs_ioctl_search_args_v2 *args2;
struct btrfs_ioctl_search_key *sk;
int args2_size = 1024;
char args2_buf[args2_size];
int ret;
args2 = (struct btrfs_ioctl_search_args_v2 *)args2_buf;
sk = &(args2->key);
/*
* Search for the extent tree item in the root tree.
*/
sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
sk->min_objectid = BTRFS_EXTENT_TREE_OBJECTID;
sk->max_objectid = BTRFS_EXTENT_TREE_OBJECTID;
sk->min_type = BTRFS_ROOT_ITEM_KEY;
sk->max_type = BTRFS_ROOT_ITEM_KEY;
sk->min_offset = 0;
sk->max_offset = (u64)-1;
sk->min_transid = 0;
sk->max_transid = (u64)-1;
sk->nr_items = 1;
args2->buf_size = args2_size - sizeof(struct btrfs_ioctl_search_args_v2);
ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH_V2, args2);
if (ret == -EOPNOTSUPP)
return 0;
else if (ret == 0)
return 1;
return ret;
}