mirror of
https://github.com/kdave/btrfs-progs
synced 2024-12-23 22:53:35 +00:00
f7a768d624
The leafsize has never been different from nodesize and since 4.0 (2015) it's been alias for nodesize. This should be enough time for everybody to update so the support is removed. Signed-off-by: David Sterba <dsterba@suse.com>
1828 lines
48 KiB
C
1828 lines
48 KiB
C
/*
|
|
* Copyright (C) 2007 Oracle. All rights reserved.
|
|
*
|
|
* 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/stat.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <getopt.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <pthread.h>
|
|
#include <uuid/uuid.h>
|
|
#include "kernel-lib/list.h"
|
|
#include "kernel-lib/list_sort.h"
|
|
#include "kernel-lib/rbtree.h"
|
|
#include "kernel-lib/sizes.h"
|
|
#include "kernel-shared/ctree.h"
|
|
#include "kernel-shared/disk-io.h"
|
|
#include "kernel-shared/volumes.h"
|
|
#include "kernel-shared/transaction.h"
|
|
#include "kernel-shared/zoned.h"
|
|
#include "crypto/crc32c.h"
|
|
#include "common/defs.h"
|
|
#include "common/internal.h"
|
|
#include "common/messages.h"
|
|
#include "common/utils.h"
|
|
#include "common/path-utils.h"
|
|
#include "common/device-utils.h"
|
|
#include "common/device-scan.h"
|
|
#include "common/help.h"
|
|
#include "common/rbtree-utils.h"
|
|
#include "common/parse-utils.h"
|
|
#include "common/fsfeatures.h"
|
|
#include "common/box.h"
|
|
#include "common/units.h"
|
|
#include "common/string-utils.h"
|
|
#include "check/qgroup-verify.h"
|
|
#include "mkfs/common.h"
|
|
#include "mkfs/rootdir.h"
|
|
|
|
struct mkfs_allocation {
|
|
u64 data;
|
|
u64 metadata;
|
|
u64 mixed;
|
|
u64 system;
|
|
};
|
|
|
|
static bool opt_zero_end = true;
|
|
static bool opt_discard = true;
|
|
static bool opt_zoned = true;
|
|
static int opt_oflags = O_RDWR;
|
|
|
|
struct prepare_device_progress {
|
|
char *file;
|
|
u64 dev_block_count;
|
|
u64 block_count;
|
|
int ret;
|
|
};
|
|
|
|
static int create_metadata_block_groups(struct btrfs_root *root, bool mixed,
|
|
struct mkfs_allocation *allocation)
|
|
{
|
|
struct btrfs_fs_info *fs_info = root->fs_info;
|
|
struct btrfs_trans_handle *trans;
|
|
struct btrfs_space_info *sinfo;
|
|
u64 flags = BTRFS_BLOCK_GROUP_METADATA;
|
|
u64 chunk_start = 0;
|
|
u64 chunk_size = 0;
|
|
u64 system_group_size = BTRFS_MKFS_SYSTEM_GROUP_SIZE;
|
|
int ret;
|
|
|
|
if (btrfs_is_zoned(fs_info)) {
|
|
/* Two zones are reserved for superblock */
|
|
system_group_size = fs_info->zone_size;
|
|
}
|
|
|
|
if (mixed)
|
|
flags |= BTRFS_BLOCK_GROUP_DATA;
|
|
|
|
/* Create needed space info to trace extents reservation */
|
|
ret = update_space_info(fs_info, flags, 0, 0, &sinfo);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
trans = btrfs_start_transaction(root, 1);
|
|
BUG_ON(IS_ERR(trans));
|
|
|
|
root->fs_info->system_allocs = 1;
|
|
/*
|
|
* We already created the block group item for our temporary system
|
|
* chunk in make_btrfs(), so account for the size here.
|
|
*/
|
|
allocation->system += system_group_size;
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (mixed) {
|
|
ret = btrfs_alloc_chunk(trans, fs_info,
|
|
&chunk_start, &chunk_size,
|
|
BTRFS_BLOCK_GROUP_METADATA |
|
|
BTRFS_BLOCK_GROUP_DATA);
|
|
if (ret == -ENOSPC) {
|
|
error("no space to allocate data/metadata chunk");
|
|
goto err;
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
ret = btrfs_make_block_group(trans, fs_info, 0,
|
|
BTRFS_BLOCK_GROUP_METADATA |
|
|
BTRFS_BLOCK_GROUP_DATA,
|
|
chunk_start, chunk_size);
|
|
if (ret)
|
|
return ret;
|
|
allocation->mixed += chunk_size;
|
|
} else {
|
|
ret = btrfs_alloc_chunk(trans, fs_info,
|
|
&chunk_start, &chunk_size,
|
|
BTRFS_BLOCK_GROUP_METADATA);
|
|
if (ret == -ENOSPC) {
|
|
error("no space to allocate metadata chunk");
|
|
goto err;
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
ret = btrfs_make_block_group(trans, fs_info, 0,
|
|
BTRFS_BLOCK_GROUP_METADATA,
|
|
chunk_start, chunk_size);
|
|
allocation->metadata += chunk_size;
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
root->fs_info->system_allocs = 0;
|
|
ret = btrfs_commit_transaction(trans, root);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static int create_data_block_groups(struct btrfs_trans_handle *trans,
|
|
struct btrfs_root *root, bool mixed,
|
|
struct mkfs_allocation *allocation)
|
|
{
|
|
struct btrfs_fs_info *fs_info = root->fs_info;
|
|
u64 chunk_start = 0;
|
|
u64 chunk_size = 0;
|
|
int ret = 0;
|
|
|
|
if (!mixed) {
|
|
struct btrfs_space_info *sinfo;
|
|
|
|
ret = update_space_info(fs_info, BTRFS_BLOCK_GROUP_DATA,
|
|
0, 0, &sinfo);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = btrfs_alloc_chunk(trans, fs_info,
|
|
&chunk_start, &chunk_size,
|
|
BTRFS_BLOCK_GROUP_DATA);
|
|
if (ret == -ENOSPC) {
|
|
error("no space to allocate data chunk");
|
|
goto err;
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
ret = btrfs_make_block_group(trans, fs_info, 0,
|
|
BTRFS_BLOCK_GROUP_DATA,
|
|
chunk_start, chunk_size);
|
|
allocation->data += chunk_size;
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static int make_root_dir(struct btrfs_trans_handle *trans,
|
|
struct btrfs_root *root)
|
|
{
|
|
struct btrfs_key location;
|
|
int ret;
|
|
|
|
ret = btrfs_make_root_dir(trans, root->fs_info->tree_root,
|
|
BTRFS_ROOT_TREE_DIR_OBJECTID);
|
|
if (ret)
|
|
goto err;
|
|
ret = btrfs_make_root_dir(trans, root, BTRFS_FIRST_FREE_OBJECTID);
|
|
if (ret)
|
|
goto err;
|
|
memcpy(&location, &root->fs_info->fs_root->root_key, sizeof(location));
|
|
location.offset = (u64)-1;
|
|
ret = btrfs_insert_dir_item(trans, root->fs_info->tree_root,
|
|
"default", 7,
|
|
btrfs_super_root_dir(root->fs_info->super_copy),
|
|
&location, BTRFS_FT_DIR, 0);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = btrfs_insert_inode_ref(trans, root->fs_info->tree_root,
|
|
"default", 7, location.objectid,
|
|
BTRFS_ROOT_TREE_DIR_OBJECTID, 0);
|
|
if (ret)
|
|
goto err;
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static int __recow_root(struct btrfs_trans_handle *trans, struct btrfs_root *root)
|
|
{
|
|
struct btrfs_path path;
|
|
struct btrfs_key key;
|
|
int ret;
|
|
|
|
btrfs_init_path(&path);
|
|
key.objectid = 0;
|
|
key.type = 0;
|
|
key.offset = 0;
|
|
|
|
/* Get a path to the left-most leaves */
|
|
ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
while (true) {
|
|
struct btrfs_key found_key;
|
|
|
|
/*
|
|
* Our parent nodes must not be newer than the leaf, thus if
|
|
* the leaf is as new as the transaction, no need to re-COW.
|
|
*/
|
|
if (btrfs_header_generation(path.nodes[0]) == trans->transid)
|
|
goto next;
|
|
|
|
/*
|
|
* Grab the key of current tree block and do a COW search to
|
|
* the current tree block.
|
|
*/
|
|
btrfs_item_key_to_cpu(path.nodes[0], &key, 0);
|
|
btrfs_release_path(&path);
|
|
|
|
/* This will ensure this leaf and all its parent get COWed */
|
|
ret = btrfs_search_slot(trans, root, &key, &path, 0, 1);
|
|
if (ret < 0)
|
|
goto out;
|
|
ret = 0;
|
|
btrfs_item_key_to_cpu(path.nodes[0], &found_key, 0);
|
|
ASSERT(btrfs_comp_cpu_keys(&key, &found_key) == 0);
|
|
|
|
next:
|
|
ret = btrfs_next_leaf(root, &path);
|
|
if (ret < 0)
|
|
goto out;
|
|
if (ret > 0) {
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
btrfs_release_path(&path);
|
|
return ret;
|
|
}
|
|
|
|
static int recow_global_roots(struct btrfs_trans_handle *trans)
|
|
{
|
|
struct btrfs_fs_info *fs_info = trans->fs_info;
|
|
struct btrfs_root *root;
|
|
struct rb_node *n;
|
|
int ret = 0;
|
|
|
|
for (n = rb_first(&fs_info->global_roots_tree); n; n = rb_next(n)) {
|
|
root = rb_entry(n, struct btrfs_root, rb_node);
|
|
ret = __recow_root(trans, root);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int recow_roots(struct btrfs_trans_handle *trans,
|
|
struct btrfs_root *root)
|
|
{
|
|
struct btrfs_fs_info *info = root->fs_info;
|
|
int ret;
|
|
|
|
ret = __recow_root(trans, info->fs_root);
|
|
if (ret)
|
|
return ret;
|
|
ret = __recow_root(trans, info->tree_root);
|
|
if (ret)
|
|
return ret;
|
|
ret = __recow_root(trans, info->chunk_root);
|
|
if (ret)
|
|
return ret;
|
|
ret = __recow_root(trans, info->dev_root);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (btrfs_fs_compat_ro(info, BLOCK_GROUP_TREE)) {
|
|
ret = __recow_root(trans, info->block_group_root);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
ret = recow_global_roots(trans);
|
|
if (ret)
|
|
return ret;
|
|
return 0;
|
|
}
|
|
|
|
static int create_one_raid_group(struct btrfs_trans_handle *trans,
|
|
struct btrfs_root *root, u64 type,
|
|
struct mkfs_allocation *allocation)
|
|
|
|
{
|
|
struct btrfs_fs_info *fs_info = root->fs_info;
|
|
u64 chunk_start;
|
|
u64 chunk_size;
|
|
int ret;
|
|
|
|
ret = btrfs_alloc_chunk(trans, fs_info,
|
|
&chunk_start, &chunk_size, type);
|
|
if (ret == -ENOSPC) {
|
|
error("not enough free space to allocate chunk");
|
|
exit(1);
|
|
}
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = btrfs_make_block_group(trans, fs_info, 0,
|
|
type, chunk_start, chunk_size);
|
|
|
|
type &= BTRFS_BLOCK_GROUP_TYPE_MASK;
|
|
if (type == BTRFS_BLOCK_GROUP_DATA) {
|
|
allocation->data += chunk_size;
|
|
} else if (type == BTRFS_BLOCK_GROUP_METADATA) {
|
|
allocation->metadata += chunk_size;
|
|
} else if (type == BTRFS_BLOCK_GROUP_SYSTEM) {
|
|
allocation->system += chunk_size;
|
|
} else if (type ==
|
|
(BTRFS_BLOCK_GROUP_METADATA | BTRFS_BLOCK_GROUP_DATA)) {
|
|
allocation->mixed += chunk_size;
|
|
} else {
|
|
error("unrecognized profile type: 0x%llx", type);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int create_raid_groups(struct btrfs_trans_handle *trans,
|
|
struct btrfs_root *root, u64 data_profile,
|
|
u64 metadata_profile, bool mixed,
|
|
struct mkfs_allocation *allocation)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (metadata_profile) {
|
|
u64 meta_flags = BTRFS_BLOCK_GROUP_METADATA;
|
|
|
|
ret = create_one_raid_group(trans, root,
|
|
BTRFS_BLOCK_GROUP_SYSTEM |
|
|
metadata_profile, allocation);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (mixed)
|
|
meta_flags |= BTRFS_BLOCK_GROUP_DATA;
|
|
|
|
ret = create_one_raid_group(trans, root, meta_flags |
|
|
metadata_profile, allocation);
|
|
if (ret)
|
|
return ret;
|
|
|
|
}
|
|
if (!mixed && data_profile) {
|
|
ret = create_one_raid_group(trans, root,
|
|
BTRFS_BLOCK_GROUP_DATA |
|
|
data_profile, allocation);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void print_usage(int ret)
|
|
{
|
|
printf("Usage: mkfs.btrfs [options] dev [ dev ... ]\n");
|
|
printf("Options:\n");
|
|
printf(" allocation profiles:\n");
|
|
printf("\t-d|--data PROFILE data profile, raid0, raid1, raid1c3, raid1c4, raid5, raid6, raid10, dup or single\n");
|
|
printf("\t-m|--metadata PROFILE metadata profile, values like for data profile\n");
|
|
printf("\t-M|--mixed mix metadata and data together\n");
|
|
printf(" features:\n");
|
|
printf("\t--csum TYPE\n");
|
|
printf("\t--checksum TYPE checksum algorithm to use, crc32c (default), xxhash, sha256, blake2\n");
|
|
printf("\t-n|--nodesize SIZE size of btree nodes\n");
|
|
printf("\t-s|--sectorsize SIZE data block size (may not be mountable by current kernel)\n");
|
|
printf("\t-O|--features LIST comma separated list of filesystem features (use '-O list-all' to list features)\n");
|
|
printf("\t-R|--runtime-features LIST comma separated list of runtime features (use '-R list-all' to list runtime features)\n");
|
|
printf("\t-L|--label LABEL set the filesystem label\n");
|
|
printf("\t-U|--uuid UUID specify the filesystem UUID (must be unique)\n");
|
|
printf(" creation:\n");
|
|
printf("\t-b|--byte-count SIZE set size of each device to SIZE (filesystem size is sum of all device sizes)\n");
|
|
printf("\t-r|--rootdir DIR copy files from DIR to the image root directory\n");
|
|
printf("\t--shrink (with --rootdir) shrink the filled filesystem to minimal size\n");
|
|
printf("\t-K|--nodiscard do not perform whole device TRIM\n");
|
|
printf("\t-f|--force force overwrite of existing filesystem\n");
|
|
printf(" general:\n");
|
|
printf("\t-q|--quiet no messages except errors\n");
|
|
printf("\t-v|--verbose increase verbosity level, default is 1\n");
|
|
printf("\t-V|--version print the mkfs.btrfs version and exit\n");
|
|
printf("\t--help print this help and exit\n");
|
|
printf(" deprecated:\n");
|
|
printf("\t-l|--leafsize SIZE removed in 6.0, use --nodesize\n");
|
|
exit(ret);
|
|
}
|
|
|
|
static int zero_output_file(int out_fd, u64 size)
|
|
{
|
|
int loop_num;
|
|
u64 location = 0;
|
|
char buf[SZ_4K];
|
|
int ret = 0, i;
|
|
ssize_t written;
|
|
|
|
memset(buf, 0, SZ_4K);
|
|
|
|
/* Only zero out the first 1M */
|
|
loop_num = SZ_1M / SZ_4K;
|
|
for (i = 0; i < loop_num; i++) {
|
|
written = pwrite64(out_fd, buf, SZ_4K, location);
|
|
if (written != SZ_4K)
|
|
ret = -EIO;
|
|
location += SZ_4K;
|
|
}
|
|
|
|
/* Then enlarge the file to size */
|
|
written = pwrite64(out_fd, buf, 1, size - 1);
|
|
if (written < 1)
|
|
ret = -EIO;
|
|
return ret;
|
|
}
|
|
|
|
static int _cmp_device_by_id(void *priv, struct list_head *a,
|
|
struct list_head *b)
|
|
{
|
|
return list_entry(a, struct btrfs_device, dev_list)->devid -
|
|
list_entry(b, struct btrfs_device, dev_list)->devid;
|
|
}
|
|
|
|
static void list_all_devices(struct btrfs_root *root)
|
|
{
|
|
struct btrfs_fs_devices *fs_devices;
|
|
struct btrfs_device *device;
|
|
int number_of_devices = 0;
|
|
u64 total_block_count = 0;
|
|
|
|
fs_devices = root->fs_info->fs_devices;
|
|
|
|
list_for_each_entry(device, &fs_devices->devices, dev_list)
|
|
number_of_devices++;
|
|
|
|
list_sort(NULL, &fs_devices->devices, _cmp_device_by_id);
|
|
|
|
printf("Number of devices: %d\n", number_of_devices);
|
|
/* printf("Total devices size: %10s\n", */
|
|
/* pretty_size(total_block_count)); */
|
|
printf("Devices:\n");
|
|
printf(" ID SIZE PATH\n");
|
|
list_for_each_entry(device, &fs_devices->devices, dev_list) {
|
|
printf(" %3llu %10s %s\n",
|
|
device->devid,
|
|
pretty_size(device->total_bytes),
|
|
device->name);
|
|
total_block_count += device->total_bytes;
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
static int is_temp_block_group(struct extent_buffer *node,
|
|
struct btrfs_block_group_item *bgi,
|
|
u64 data_profile, u64 meta_profile,
|
|
u64 sys_profile)
|
|
{
|
|
u64 flag = btrfs_block_group_flags(node, bgi);
|
|
u64 flag_type = flag & BTRFS_BLOCK_GROUP_TYPE_MASK;
|
|
u64 flag_profile = flag & BTRFS_BLOCK_GROUP_PROFILE_MASK;
|
|
u64 used = btrfs_block_group_used(node, bgi);
|
|
|
|
/*
|
|
* Chunks meets all the following conditions is a temp chunk
|
|
* 1) Empty chunk
|
|
* Temp chunk is always empty.
|
|
*
|
|
* 2) profile mismatch with mkfs profile.
|
|
* Temp chunk is always in SINGLE
|
|
*
|
|
* 3) Size differs with mkfs_alloc
|
|
* Special case for SINGLE/SINGLE btrfs.
|
|
* In that case, temp data chunk and real data chunk are always empty.
|
|
* So we need to use mkfs_alloc to be sure which chunk is the newly
|
|
* allocated.
|
|
*
|
|
* Normally, new chunk size is equal to mkfs one (One chunk)
|
|
* If it has multiple chunks, we just refuse to delete any one.
|
|
* As they are all single, so no real problem will happen.
|
|
* So only use condition 1) and 2) to judge them.
|
|
*/
|
|
if (used != 0)
|
|
return 0;
|
|
switch (flag_type) {
|
|
case BTRFS_BLOCK_GROUP_DATA:
|
|
case BTRFS_BLOCK_GROUP_DATA | BTRFS_BLOCK_GROUP_METADATA:
|
|
data_profile &= BTRFS_BLOCK_GROUP_PROFILE_MASK;
|
|
if (flag_profile != data_profile)
|
|
return 1;
|
|
break;
|
|
case BTRFS_BLOCK_GROUP_METADATA:
|
|
meta_profile &= BTRFS_BLOCK_GROUP_PROFILE_MASK;
|
|
if (flag_profile != meta_profile)
|
|
return 1;
|
|
break;
|
|
case BTRFS_BLOCK_GROUP_SYSTEM:
|
|
sys_profile &= BTRFS_BLOCK_GROUP_PROFILE_MASK;
|
|
if (flag_profile != sys_profile)
|
|
return 1;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Note: if current is a block group, it will skip it anyway */
|
|
static int next_block_group(struct btrfs_root *root,
|
|
struct btrfs_path *path)
|
|
{
|
|
struct btrfs_key key;
|
|
int ret = 0;
|
|
|
|
while (1) {
|
|
ret = btrfs_next_item(root, path);
|
|
if (ret)
|
|
goto out;
|
|
|
|
btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
|
|
if (key.type == BTRFS_BLOCK_GROUP_ITEM_KEY)
|
|
goto out;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/* This function will cleanup */
|
|
static int cleanup_temp_chunks(struct btrfs_fs_info *fs_info,
|
|
struct mkfs_allocation *alloc,
|
|
u64 data_profile, u64 meta_profile,
|
|
u64 sys_profile)
|
|
{
|
|
struct btrfs_trans_handle *trans = NULL;
|
|
struct btrfs_block_group_item *bgi;
|
|
struct btrfs_root *root = btrfs_block_group_root(fs_info);
|
|
struct btrfs_key key;
|
|
struct btrfs_key found_key;
|
|
struct btrfs_path path;
|
|
int ret = 0;
|
|
|
|
btrfs_init_path(&path);
|
|
trans = btrfs_start_transaction(root, 1);
|
|
BUG_ON(IS_ERR(trans));
|
|
|
|
key.objectid = 0;
|
|
key.type = BTRFS_BLOCK_GROUP_ITEM_KEY;
|
|
key.offset = 0;
|
|
|
|
while (1) {
|
|
/*
|
|
* as the rest of the loop may modify the tree, we need to
|
|
* start a new search each time.
|
|
*/
|
|
ret = btrfs_search_slot(trans, root, &key, &path, 0, 0);
|
|
if (ret < 0)
|
|
goto out;
|
|
/* Don't pollute ret for >0 case */
|
|
if (ret > 0)
|
|
ret = 0;
|
|
|
|
btrfs_item_key_to_cpu(path.nodes[0], &found_key,
|
|
path.slots[0]);
|
|
if (found_key.objectid < key.objectid)
|
|
goto out;
|
|
if (found_key.type != BTRFS_BLOCK_GROUP_ITEM_KEY) {
|
|
ret = next_block_group(root, &path);
|
|
if (ret < 0)
|
|
goto out;
|
|
if (ret > 0) {
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
btrfs_item_key_to_cpu(path.nodes[0], &found_key,
|
|
path.slots[0]);
|
|
}
|
|
|
|
bgi = btrfs_item_ptr(path.nodes[0], path.slots[0],
|
|
struct btrfs_block_group_item);
|
|
if (is_temp_block_group(path.nodes[0], bgi,
|
|
data_profile, meta_profile,
|
|
sys_profile)) {
|
|
u64 flags = btrfs_block_group_flags(path.nodes[0], bgi);
|
|
|
|
ret = btrfs_remove_block_group(trans,
|
|
found_key.objectid, found_key.offset);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
if ((flags & BTRFS_BLOCK_GROUP_TYPE_MASK) ==
|
|
BTRFS_BLOCK_GROUP_DATA)
|
|
alloc->data -= found_key.offset;
|
|
else if ((flags & BTRFS_BLOCK_GROUP_TYPE_MASK) ==
|
|
BTRFS_BLOCK_GROUP_METADATA)
|
|
alloc->metadata -= found_key.offset;
|
|
else if ((flags & BTRFS_BLOCK_GROUP_TYPE_MASK) ==
|
|
BTRFS_BLOCK_GROUP_SYSTEM)
|
|
alloc->system -= found_key.offset;
|
|
else if ((flags & BTRFS_BLOCK_GROUP_TYPE_MASK) ==
|
|
(BTRFS_BLOCK_GROUP_METADATA |
|
|
BTRFS_BLOCK_GROUP_DATA))
|
|
alloc->mixed -= found_key.offset;
|
|
}
|
|
btrfs_release_path(&path);
|
|
key.objectid = found_key.objectid + found_key.offset;
|
|
}
|
|
out:
|
|
if (trans)
|
|
btrfs_commit_transaction(trans, root);
|
|
btrfs_release_path(&path);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Just update chunk allocation info, since --rootdir may allocate new
|
|
* chunks which is not updated in @allocation structure.
|
|
*/
|
|
static void update_chunk_allocation(struct btrfs_fs_info *fs_info,
|
|
struct mkfs_allocation *allocation)
|
|
{
|
|
struct btrfs_block_group *bg_cache;
|
|
const u64 mixed_flag = BTRFS_BLOCK_GROUP_DATA | BTRFS_BLOCK_GROUP_METADATA;
|
|
u64 search_start = 0;
|
|
|
|
allocation->mixed = 0;
|
|
allocation->data = 0;
|
|
allocation->metadata = 0;
|
|
allocation->system = 0;
|
|
while (1) {
|
|
bg_cache = btrfs_lookup_first_block_group(fs_info,
|
|
search_start);
|
|
if (!bg_cache)
|
|
break;
|
|
if ((bg_cache->flags & mixed_flag) == mixed_flag)
|
|
allocation->mixed += bg_cache->length;
|
|
else if (bg_cache->flags & BTRFS_BLOCK_GROUP_DATA)
|
|
allocation->data += bg_cache->length;
|
|
else if (bg_cache->flags & BTRFS_BLOCK_GROUP_METADATA)
|
|
allocation->metadata += bg_cache->length;
|
|
else
|
|
allocation->system += bg_cache->length;
|
|
search_start = bg_cache->start + bg_cache->length;
|
|
}
|
|
}
|
|
|
|
static int create_data_reloc_tree(struct btrfs_trans_handle *trans)
|
|
{
|
|
struct btrfs_fs_info *fs_info = trans->fs_info;
|
|
struct btrfs_inode_item *inode;
|
|
struct btrfs_root *root;
|
|
struct btrfs_path path;
|
|
struct btrfs_key key = {
|
|
.objectid = BTRFS_DATA_RELOC_TREE_OBJECTID,
|
|
.type = BTRFS_ROOT_ITEM_KEY,
|
|
};
|
|
u64 ino = BTRFS_FIRST_FREE_OBJECTID;
|
|
char *name = "..";
|
|
int ret;
|
|
|
|
root = btrfs_create_tree(trans, fs_info, &key);
|
|
if (IS_ERR(root)) {
|
|
ret = PTR_ERR(root);
|
|
goto out;
|
|
}
|
|
/* Update dirid as created tree has default dirid 0 */
|
|
btrfs_set_root_dirid(&root->root_item, ino);
|
|
ret = btrfs_update_root(trans, fs_info->tree_root, &root->root_key,
|
|
&root->root_item);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
/* Cache this tree so it can be cleaned up at close_ctree() */
|
|
ret = rb_insert(&fs_info->fs_root_tree, &root->rb_node,
|
|
btrfs_fs_roots_compare_roots);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
/* Insert INODE_ITEM */
|
|
ret = btrfs_new_inode(trans, root, ino, 0755 | S_IFDIR);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
/* then INODE_REF */
|
|
ret = btrfs_insert_inode_ref(trans, root, name, strlen(name), ino, ino,
|
|
0);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
/* Update nlink of that inode item */
|
|
key.objectid = ino;
|
|
key.type = BTRFS_INODE_ITEM_KEY;
|
|
key.offset = 0;
|
|
btrfs_init_path(&path);
|
|
|
|
ret = btrfs_search_slot(trans, root, &key, &path, 0, 1);
|
|
if (ret > 0) {
|
|
ret = -ENOENT;
|
|
btrfs_release_path(&path);
|
|
goto out;
|
|
}
|
|
if (ret < 0) {
|
|
btrfs_release_path(&path);
|
|
goto out;
|
|
}
|
|
inode = btrfs_item_ptr(path.nodes[0], path.slots[0],
|
|
struct btrfs_inode_item);
|
|
btrfs_set_inode_nlink(path.nodes[0], inode, 1);
|
|
btrfs_mark_buffer_dirty(path.nodes[0]);
|
|
btrfs_release_path(&path);
|
|
return 0;
|
|
out:
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int create_uuid_tree(struct btrfs_trans_handle *trans)
|
|
{
|
|
struct btrfs_fs_info *fs_info = trans->fs_info;
|
|
struct btrfs_root *root;
|
|
struct btrfs_key key = {
|
|
.objectid = BTRFS_UUID_TREE_OBJECTID,
|
|
.type = BTRFS_ROOT_ITEM_KEY,
|
|
};
|
|
int ret = 0;
|
|
|
|
ASSERT(fs_info->uuid_root == NULL);
|
|
root = btrfs_create_tree(trans, fs_info, &key);
|
|
if (IS_ERR(root)) {
|
|
ret = PTR_ERR(root);
|
|
goto out;
|
|
}
|
|
|
|
add_root_to_dirty_list(root);
|
|
fs_info->uuid_root = root;
|
|
ret = btrfs_uuid_tree_add(trans, fs_info->fs_root->root_item.uuid,
|
|
BTRFS_UUID_KEY_SUBVOL,
|
|
fs_info->fs_root->root_key.objectid);
|
|
if (ret < 0)
|
|
btrfs_abort_transaction(trans, ret);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int create_global_root(struct btrfs_trans_handle *trans, u64 objectid,
|
|
int root_id)
|
|
{
|
|
struct btrfs_fs_info *fs_info = trans->fs_info;
|
|
struct btrfs_root *root;
|
|
struct btrfs_key key = {
|
|
.objectid = objectid,
|
|
.type = BTRFS_ROOT_ITEM_KEY,
|
|
.offset = root_id,
|
|
};
|
|
int ret = 0;
|
|
|
|
root = btrfs_create_tree(trans, fs_info, &key);
|
|
if (IS_ERR(root)) {
|
|
ret = PTR_ERR(root);
|
|
goto out;
|
|
}
|
|
ret = btrfs_global_root_insert(fs_info, root);
|
|
out:
|
|
if (ret)
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int create_global_roots(struct btrfs_trans_handle *trans,
|
|
int nr_global_roots)
|
|
{
|
|
int ret, i;
|
|
|
|
for (i = 1; i < nr_global_roots; i++) {
|
|
ret = create_global_root(trans, BTRFS_EXTENT_TREE_OBJECTID, i);
|
|
if (ret)
|
|
return ret;
|
|
ret = create_global_root(trans, BTRFS_CSUM_TREE_OBJECTID, i);
|
|
if (ret)
|
|
return ret;
|
|
ret = create_global_root(trans, BTRFS_FREE_SPACE_TREE_OBJECTID, i);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
btrfs_set_super_nr_global_roots(trans->fs_info->super_copy,
|
|
nr_global_roots);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int insert_qgroup_items(struct btrfs_trans_handle *trans,
|
|
struct btrfs_fs_info *fs_info,
|
|
u64 qgroupid)
|
|
{
|
|
struct btrfs_path path;
|
|
struct btrfs_root *quota_root = fs_info->quota_root;
|
|
struct btrfs_key key;
|
|
int ret;
|
|
|
|
if (qgroupid >> BTRFS_QGROUP_LEVEL_SHIFT) {
|
|
error("qgroup level other than 0 is not supported yet");
|
|
return -ENOTTY;
|
|
}
|
|
|
|
key.objectid = 0;
|
|
key.type = BTRFS_QGROUP_INFO_KEY;
|
|
key.offset = qgroupid;
|
|
|
|
btrfs_init_path(&path);
|
|
ret = btrfs_insert_empty_item(trans, quota_root, &path, &key,
|
|
sizeof(struct btrfs_qgroup_info_item));
|
|
btrfs_release_path(&path);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
key.objectid = 0;
|
|
key.type = BTRFS_QGROUP_LIMIT_KEY;
|
|
key.offset = qgroupid;
|
|
ret = btrfs_insert_empty_item(trans, quota_root, &path, &key,
|
|
sizeof(struct btrfs_qgroup_limit_item));
|
|
btrfs_release_path(&path);
|
|
return ret;
|
|
}
|
|
|
|
static int setup_quota_root(struct btrfs_fs_info *fs_info)
|
|
{
|
|
struct btrfs_trans_handle *trans;
|
|
struct btrfs_qgroup_status_item *qsi;
|
|
struct btrfs_root *quota_root;
|
|
struct btrfs_path path;
|
|
struct btrfs_key key;
|
|
int qgroup_repaired = 0;
|
|
int ret;
|
|
|
|
/* One to modify tree root, one for quota root */
|
|
trans = btrfs_start_transaction(fs_info->tree_root, 2);
|
|
if (IS_ERR(trans)) {
|
|
ret = PTR_ERR(trans);
|
|
error("failed to start transaction: %d (%m)", ret);
|
|
return ret;
|
|
}
|
|
ret = btrfs_create_root(trans, fs_info, BTRFS_QUOTA_TREE_OBJECTID);
|
|
if (ret < 0) {
|
|
error("failed to create quota root: %d (%m)", ret);
|
|
goto fail;
|
|
}
|
|
quota_root = fs_info->quota_root;
|
|
|
|
key.objectid = 0;
|
|
key.type = BTRFS_QGROUP_STATUS_KEY;
|
|
key.offset = 0;
|
|
|
|
btrfs_init_path(&path);
|
|
ret = btrfs_insert_empty_item(trans, quota_root, &path, &key,
|
|
sizeof(*qsi));
|
|
if (ret < 0) {
|
|
error("failed to insert qgroup status item: %d (%m)", ret);
|
|
goto fail;
|
|
}
|
|
|
|
qsi = btrfs_item_ptr(path.nodes[0], path.slots[0],
|
|
struct btrfs_qgroup_status_item);
|
|
btrfs_set_qgroup_status_generation(path.nodes[0], qsi, 0);
|
|
btrfs_set_qgroup_status_rescan(path.nodes[0], qsi, 0);
|
|
|
|
/* Mark current status info inconsistent, and fix it later */
|
|
btrfs_set_qgroup_status_flags(path.nodes[0], qsi,
|
|
BTRFS_QGROUP_STATUS_FLAG_ON |
|
|
BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT);
|
|
btrfs_release_path(&path);
|
|
|
|
/* Currently mkfs will only create one subvolume */
|
|
ret = insert_qgroup_items(trans, fs_info, BTRFS_FS_TREE_OBJECTID);
|
|
if (ret < 0) {
|
|
error("failed to insert qgroup items: %d (%m)", ret);
|
|
goto fail;
|
|
}
|
|
|
|
ret = btrfs_commit_transaction(trans, fs_info->tree_root);
|
|
if (ret < 0) {
|
|
error("failed to commit current transaction: %d (%m)", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Qgroup is setup but with wrong info, use qgroup-verify
|
|
* infrastructure to repair them. (Just acts as offline rescan)
|
|
*/
|
|
ret = qgroup_verify_all(fs_info);
|
|
if (ret < 0) {
|
|
error("qgroup rescan failed: %d (%m)", ret);
|
|
return ret;
|
|
}
|
|
ret = repair_qgroups(fs_info, &qgroup_repaired, true);
|
|
if (ret < 0)
|
|
error("failed to fill qgroup info: %d (%m)", ret);
|
|
return ret;
|
|
fail:
|
|
btrfs_abort_transaction(trans, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Thread callback for device preparation */
|
|
static void *prepare_one_device(void *ctx)
|
|
{
|
|
struct prepare_device_progress *prepare_ctx = ctx;
|
|
int fd;
|
|
|
|
fd = open(prepare_ctx->file, opt_oflags);
|
|
if (fd < 0) {
|
|
error("unable to open %s: %m", prepare_ctx->file);
|
|
prepare_ctx->ret = -errno;
|
|
return NULL;
|
|
}
|
|
prepare_ctx->ret = btrfs_prepare_device(fd, prepare_ctx->file,
|
|
&prepare_ctx->dev_block_count,
|
|
prepare_ctx->block_count,
|
|
(bconf.verbose ? PREP_DEVICE_VERBOSE : 0) |
|
|
(opt_zero_end ? PREP_DEVICE_ZERO_END : 0) |
|
|
(opt_discard ? PREP_DEVICE_DISCARD : 0) |
|
|
(opt_zoned ? PREP_DEVICE_ZONED : 0));
|
|
close(fd);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int BOX_MAIN(mkfs)(int argc, char **argv)
|
|
{
|
|
char *file;
|
|
struct btrfs_root *root;
|
|
struct btrfs_fs_info *fs_info;
|
|
struct btrfs_trans_handle *trans;
|
|
struct open_ctree_flags ocf = { 0 };
|
|
int fd = -1;
|
|
int ret = 0;
|
|
int close_ret;
|
|
int i;
|
|
bool ssd = false;
|
|
bool shrink_rootdir = false;
|
|
u64 source_dir_size = 0;
|
|
u64 min_dev_size;
|
|
u64 shrink_size;
|
|
int device_count = 0;
|
|
int saved_optind;
|
|
pthread_t *t_prepare = NULL;
|
|
struct prepare_device_progress *prepare_ctx = NULL;
|
|
struct mkfs_allocation allocation = { 0 };
|
|
struct btrfs_mkfs_config mkfs_cfg;
|
|
u64 system_group_size;
|
|
/* Options */
|
|
bool force_overwrite = false;
|
|
u64 runtime_features = BTRFS_MKFS_DEFAULT_RUNTIME_FEATURES;
|
|
u64 features = BTRFS_MKFS_DEFAULT_FEATURES;
|
|
enum btrfs_csum_type csum_type = BTRFS_CSUM_TYPE_CRC32;
|
|
char fs_uuid[BTRFS_UUID_UNPARSED_SIZE] = { 0 };
|
|
u32 nodesize = 0;
|
|
bool nodesize_forced = false;
|
|
u32 sectorsize = 0;
|
|
u32 stripesize = 4096;
|
|
u64 metadata_profile = 0;
|
|
bool metadata_profile_set = false;
|
|
u64 data_profile = 0;
|
|
bool data_profile_set = false;
|
|
u64 block_count = 0;
|
|
u64 dev_block_count = 0;
|
|
bool mixed = false;
|
|
char *label = NULL;
|
|
int nr_global_roots = sysconf(_SC_NPROCESSORS_ONLN);
|
|
char *source_dir = NULL;
|
|
bool source_dir_set = false;
|
|
|
|
crc32c_optimization_init();
|
|
btrfs_config_init();
|
|
|
|
while(1) {
|
|
int c;
|
|
enum {
|
|
GETOPT_VAL_SHRINK = GETOPT_VAL_FIRST,
|
|
GETOPT_VAL_CHECKSUM,
|
|
GETOPT_VAL_GLOBAL_ROOTS,
|
|
};
|
|
static const struct option long_options[] = {
|
|
{ "byte-count", required_argument, NULL, 'b' },
|
|
{ "csum", required_argument, NULL,
|
|
GETOPT_VAL_CHECKSUM },
|
|
{ "checksum", required_argument, NULL,
|
|
GETOPT_VAL_CHECKSUM },
|
|
{ "force", no_argument, NULL, 'f' },
|
|
{ "leafsize", required_argument, NULL, 'l' },
|
|
{ "label", required_argument, NULL, 'L'},
|
|
{ "metadata", required_argument, NULL, 'm' },
|
|
{ "mixed", no_argument, NULL, 'M' },
|
|
{ "nodesize", required_argument, NULL, 'n' },
|
|
{ "sectorsize", required_argument, NULL, 's' },
|
|
{ "data", required_argument, NULL, 'd' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ "rootdir", required_argument, NULL, 'r' },
|
|
{ "nodiscard", no_argument, NULL, 'K' },
|
|
{ "features", required_argument, NULL, 'O' },
|
|
{ "runtime-features", required_argument, NULL, 'R' },
|
|
{ "uuid", required_argument, NULL, 'U' },
|
|
{ "quiet", 0, NULL, 'q' },
|
|
{ "verbose", 0, NULL, 'v' },
|
|
{ "shrink", no_argument, NULL, GETOPT_VAL_SHRINK },
|
|
#if EXPERIMENTAL
|
|
{ "num-global-roots", required_argument, NULL, GETOPT_VAL_GLOBAL_ROOTS },
|
|
#endif
|
|
{ "help", no_argument, NULL, GETOPT_VAL_HELP },
|
|
{ NULL, 0, NULL, 0}
|
|
};
|
|
|
|
c = getopt_long(argc, argv, "A:b:fl:n:s:m:d:L:R:O:r:U:VvMKq",
|
|
long_options, NULL);
|
|
if (c < 0)
|
|
break;
|
|
switch(c) {
|
|
case 'f':
|
|
force_overwrite = true;
|
|
break;
|
|
case 'd':
|
|
ret = parse_bg_profile(optarg, &data_profile);
|
|
if (ret) {
|
|
error("unknown data profile %s", optarg);
|
|
exit(1);
|
|
}
|
|
data_profile_set = true;
|
|
break;
|
|
case 'l':
|
|
/* Deprecated in 4.0 */
|
|
error("--leafsize has been removed in 6.0, use --nodesize");
|
|
ret = 1;
|
|
goto error;
|
|
case 'n':
|
|
nodesize = parse_size_from_string(optarg);
|
|
nodesize_forced = true;
|
|
break;
|
|
case 'L':
|
|
free(label);
|
|
ret = strlen(optarg);
|
|
if (ret >= BTRFS_LABEL_SIZE) {
|
|
error("label %s is too long (max %d)",
|
|
optarg, BTRFS_LABEL_SIZE - 1);
|
|
exit(1);
|
|
}
|
|
label = strdup(optarg);
|
|
break;
|
|
case 'm':
|
|
ret = parse_bg_profile(optarg, &metadata_profile);
|
|
if (ret) {
|
|
error("unknown metadata profile %s", optarg);
|
|
exit(1);
|
|
}
|
|
metadata_profile_set = true;
|
|
break;
|
|
case 'M':
|
|
mixed = true;
|
|
break;
|
|
case 'O': {
|
|
char *orig = strdup(optarg);
|
|
char *tmp = orig;
|
|
|
|
tmp = btrfs_parse_fs_features(tmp, &features);
|
|
if (tmp) {
|
|
error("unrecognized filesystem feature '%s'",
|
|
tmp);
|
|
free(orig);
|
|
goto error;
|
|
}
|
|
free(orig);
|
|
if (features & BTRFS_FEATURE_LIST_ALL) {
|
|
btrfs_list_all_fs_features(0);
|
|
goto success;
|
|
}
|
|
break;
|
|
}
|
|
case 'R': {
|
|
char *orig = strdup(optarg);
|
|
char *tmp = orig;
|
|
|
|
tmp = btrfs_parse_runtime_features(tmp,
|
|
&runtime_features);
|
|
if (tmp) {
|
|
error("unrecognized runtime feature '%s'",
|
|
tmp);
|
|
free(orig);
|
|
goto error;
|
|
}
|
|
free(orig);
|
|
if (runtime_features & BTRFS_FEATURE_LIST_ALL) {
|
|
btrfs_list_all_runtime_features(0);
|
|
goto success;
|
|
}
|
|
break;
|
|
}
|
|
case 's':
|
|
sectorsize = parse_size_from_string(optarg);
|
|
break;
|
|
case 'b':
|
|
block_count = parse_size_from_string(optarg);
|
|
opt_zero_end = false;
|
|
break;
|
|
case 'v':
|
|
bconf_be_verbose();
|
|
break;
|
|
case 'V':
|
|
printf("mkfs.btrfs, part of %s\n",
|
|
PACKAGE_STRING);
|
|
goto success;
|
|
case 'r':
|
|
free(source_dir);
|
|
source_dir = strdup(optarg);
|
|
source_dir_set = true;
|
|
break;
|
|
case 'U':
|
|
strncpy(fs_uuid, optarg,
|
|
BTRFS_UUID_UNPARSED_SIZE - 1);
|
|
break;
|
|
case 'K':
|
|
opt_discard = false;
|
|
break;
|
|
case 'q':
|
|
bconf_be_quiet();
|
|
break;
|
|
case GETOPT_VAL_SHRINK:
|
|
shrink_rootdir = true;
|
|
break;
|
|
case GETOPT_VAL_CHECKSUM:
|
|
csum_type = parse_csum_type(optarg);
|
|
break;
|
|
case GETOPT_VAL_GLOBAL_ROOTS:
|
|
nr_global_roots = (int)arg_strtou64(optarg);
|
|
break;
|
|
case GETOPT_VAL_HELP:
|
|
default:
|
|
print_usage(c != GETOPT_VAL_HELP);
|
|
}
|
|
}
|
|
|
|
if (bconf.verbose) {
|
|
printf("%s\n", PACKAGE_STRING);
|
|
printf("See %s for more information.\n\n", PACKAGE_URL);
|
|
}
|
|
|
|
if (!sectorsize)
|
|
sectorsize = (u32)sysconf(_SC_PAGESIZE);
|
|
if (btrfs_check_sectorsize(sectorsize))
|
|
goto error;
|
|
|
|
if (!nodesize)
|
|
nodesize = max_t(u32, sectorsize, BTRFS_MKFS_DEFAULT_NODE_SIZE);
|
|
|
|
stripesize = sectorsize;
|
|
saved_optind = optind;
|
|
device_count = argc - optind;
|
|
if (device_count == 0)
|
|
print_usage(1);
|
|
|
|
opt_zoned = !!(features & BTRFS_FEATURE_INCOMPAT_ZONED);
|
|
|
|
if (source_dir_set && device_count > 1) {
|
|
error("the option -r is limited to a single device");
|
|
goto error;
|
|
}
|
|
if (shrink_rootdir && !source_dir_set) {
|
|
error("the option --shrink must be used with --rootdir");
|
|
goto error;
|
|
}
|
|
|
|
if (*fs_uuid) {
|
|
uuid_t dummy_uuid;
|
|
|
|
if (uuid_parse(fs_uuid, dummy_uuid) != 0) {
|
|
error("could not parse UUID: %s", fs_uuid);
|
|
goto error;
|
|
}
|
|
if (!test_uuid_unique(fs_uuid)) {
|
|
error("non-unique UUID: %s", fs_uuid);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
while (device_count-- > 0) {
|
|
file = argv[optind++];
|
|
if (source_dir_set && path_exists(file) == 0)
|
|
ret = 0;
|
|
else if (path_is_block_device(file) == 1)
|
|
ret = test_dev_for_mkfs(file, force_overwrite);
|
|
else
|
|
ret = test_status_for_mkfs(file, force_overwrite);
|
|
|
|
if (ret)
|
|
goto error;
|
|
}
|
|
|
|
optind = saved_optind;
|
|
device_count = argc - optind;
|
|
|
|
file = argv[optind++];
|
|
ssd = device_get_rotational(file);
|
|
if (opt_zoned) {
|
|
if (!zone_size(file)) {
|
|
error("zoned: %s: zone size undefined", file);
|
|
exit(1);
|
|
}
|
|
} else if (zoned_model(file) == ZONED_HOST_MANAGED) {
|
|
if (bconf.verbose)
|
|
printf(
|
|
"Zoned: %s: host-managed device detected, setting zoned feature\n",
|
|
file);
|
|
opt_zoned = true;
|
|
features |= BTRFS_FEATURE_INCOMPAT_ZONED;
|
|
}
|
|
|
|
/*
|
|
* Set default profiles according to number of added devices.
|
|
* For mixed groups defaults are single/single.
|
|
*/
|
|
if (!mixed) {
|
|
u64 tmp;
|
|
|
|
if (!metadata_profile_set) {
|
|
if (device_count > 1)
|
|
tmp = BTRFS_MKFS_DEFAULT_META_MULTI_DEVICE;
|
|
else
|
|
tmp = BTRFS_MKFS_DEFAULT_META_ONE_DEVICE;
|
|
metadata_profile = tmp;
|
|
}
|
|
if (!data_profile_set) {
|
|
if (device_count > 1)
|
|
tmp = BTRFS_MKFS_DEFAULT_DATA_MULTI_DEVICE;
|
|
else
|
|
tmp = BTRFS_MKFS_DEFAULT_DATA_ONE_DEVICE;
|
|
data_profile = tmp;
|
|
}
|
|
} else {
|
|
if (metadata_profile_set || data_profile_set) {
|
|
if (metadata_profile != data_profile) {
|
|
error(
|
|
"with mixed block groups data and metadata profiles must be the same");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (!nodesize_forced)
|
|
nodesize = sectorsize;
|
|
}
|
|
|
|
/*
|
|
* FS features that can be set by other means than -O
|
|
* just set the bit here
|
|
*/
|
|
if (mixed)
|
|
features |= BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS;
|
|
|
|
if ((data_profile | metadata_profile) & BTRFS_BLOCK_GROUP_RAID56_MASK) {
|
|
features |= BTRFS_FEATURE_INCOMPAT_RAID56;
|
|
warning("RAID5/6 support has known problems is strongly discouraged\n"
|
|
"\t to be used besides testing or evaluation.\n");
|
|
}
|
|
|
|
if ((data_profile | metadata_profile) &
|
|
(BTRFS_BLOCK_GROUP_RAID1C3 | BTRFS_BLOCK_GROUP_RAID1C4)) {
|
|
features |= BTRFS_FEATURE_INCOMPAT_RAID1C34;
|
|
}
|
|
|
|
/* Extent tree v2 comes with a set of mandatory features. */
|
|
if (features & BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2) {
|
|
features |= BTRFS_FEATURE_INCOMPAT_NO_HOLES;
|
|
runtime_features |= BTRFS_RUNTIME_FEATURE_FREE_SPACE_TREE;
|
|
|
|
if (!nr_global_roots) {
|
|
error("you must set a non-zero num-global-roots value");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* Block group tree feature requires no-holes and free-space-tree. */
|
|
if (runtime_features & BTRFS_RUNTIME_FEATURE_BLOCK_GROUP_TREE &&
|
|
(!(features & BTRFS_FEATURE_INCOMPAT_NO_HOLES) ||
|
|
!(runtime_features & BTRFS_RUNTIME_FEATURE_FREE_SPACE_TREE))) {
|
|
error("block group tree requires no-holes and free-space-tree features");
|
|
exit(1);
|
|
}
|
|
if (opt_zoned) {
|
|
if (source_dir_set) {
|
|
error("the option -r and zoned mode are incompatible");
|
|
exit(1);
|
|
}
|
|
|
|
if (features & BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS) {
|
|
error("cannot enable mixed-bg in zoned mode");
|
|
exit(1);
|
|
}
|
|
|
|
if (features & BTRFS_FEATURE_INCOMPAT_RAID56) {
|
|
error("cannot enable RAID5/6 in zoned mode");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (btrfs_check_nodesize(nodesize, sectorsize,
|
|
features))
|
|
goto error;
|
|
|
|
if (sectorsize < sizeof(struct btrfs_super_block)) {
|
|
error("sectorsize smaller than superblock: %u < %zu",
|
|
sectorsize, sizeof(struct btrfs_super_block));
|
|
goto error;
|
|
}
|
|
|
|
min_dev_size = btrfs_min_dev_size(nodesize, mixed, metadata_profile,
|
|
data_profile);
|
|
/*
|
|
* Enlarge the destination file or create a new one, using the size
|
|
* calculated from source dir.
|
|
*
|
|
* This must be done before minimal device size checks.
|
|
*/
|
|
if (source_dir_set) {
|
|
int oflags = O_RDWR;
|
|
struct stat statbuf;
|
|
|
|
if (path_exists(file) == 0)
|
|
oflags |= O_CREAT;
|
|
|
|
fd = open(file, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
|
|
S_IROTH);
|
|
if (fd < 0) {
|
|
error("unable to open %s: %m", file);
|
|
goto error;
|
|
}
|
|
|
|
ret = fstat(fd, &statbuf);
|
|
if (ret < 0) {
|
|
error("unable to stat %s: %m", file);
|
|
ret = -errno;
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* Block_count not specified, use file/device size first.
|
|
* Or we will always use source_dir_size calculated for mkfs.
|
|
*/
|
|
if (!block_count)
|
|
block_count = btrfs_device_size(fd, &statbuf);
|
|
source_dir_size = btrfs_mkfs_size_dir(source_dir, sectorsize,
|
|
min_dev_size, metadata_profile, data_profile);
|
|
if (block_count < source_dir_size)
|
|
block_count = source_dir_size;
|
|
ret = zero_output_file(fd, block_count);
|
|
if (ret) {
|
|
error("unable to zero the output file");
|
|
close(fd);
|
|
goto error;
|
|
}
|
|
/* our "device" is the new image file */
|
|
dev_block_count = block_count;
|
|
close(fd);
|
|
}
|
|
/* Check device/block_count after the nodesize is determined */
|
|
if (block_count && block_count < min_dev_size) {
|
|
error("size %llu is too small to make a usable filesystem",
|
|
block_count);
|
|
error("minimum size for btrfs filesystem is %llu",
|
|
min_dev_size);
|
|
goto error;
|
|
}
|
|
/*
|
|
* 2 zones for the primary superblock
|
|
* 1 zone for the system block group
|
|
* 1 zone for a metadata block group
|
|
* 1 zone for a data block group
|
|
*/
|
|
if (opt_zoned && block_count && block_count < 5 * zone_size(file)) {
|
|
error("size %llu is too small to make a usable filesystem",
|
|
block_count);
|
|
error("minimum size for a zoned btrfs filesystem is %llu",
|
|
min_dev_size);
|
|
goto error;
|
|
}
|
|
|
|
for (i = saved_optind; i < saved_optind + device_count; i++) {
|
|
char *path;
|
|
|
|
path = argv[i];
|
|
ret = test_minimum_size(path, min_dev_size);
|
|
if (ret < 0) {
|
|
error("failed to check size for %s: %m", path);
|
|
goto error;
|
|
}
|
|
if (ret > 0) {
|
|
error("'%s' is too small to make a usable filesystem",
|
|
path);
|
|
error("minimum size for each btrfs device is %llu",
|
|
min_dev_size);
|
|
goto error;
|
|
}
|
|
}
|
|
ret = test_num_disk_vs_raid(metadata_profile, data_profile,
|
|
device_count, mixed, ssd);
|
|
if (ret)
|
|
goto error;
|
|
|
|
if (opt_zoned && (!zoned_profile_supported(BTRFS_BLOCK_GROUP_METADATA | metadata_profile) ||
|
|
!zoned_profile_supported(BTRFS_BLOCK_GROUP_DATA | data_profile))) {
|
|
error("zoned mode does not yet support RAID/DUP profiles, please specify '-d single -m single' manually");
|
|
goto error;
|
|
}
|
|
|
|
t_prepare = calloc(device_count, sizeof(*t_prepare));
|
|
prepare_ctx = calloc(device_count, sizeof(*prepare_ctx));
|
|
|
|
if (!t_prepare || !prepare_ctx) {
|
|
error("unable to alloc thread for preparing devices");
|
|
goto error;
|
|
}
|
|
|
|
opt_oflags = O_RDWR;
|
|
for (i = 0; i < device_count; i++) {
|
|
if (opt_zoned &&
|
|
zoned_model(argv[optind + i - 1]) == ZONED_HOST_MANAGED) {
|
|
opt_oflags |= O_DIRECT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Start threads */
|
|
for (i = 0; i < device_count; i++) {
|
|
prepare_ctx[i].file = argv[optind + i - 1];
|
|
prepare_ctx[i].block_count = block_count;
|
|
prepare_ctx[i].dev_block_count = block_count;
|
|
ret = pthread_create(&t_prepare[i], NULL, prepare_one_device,
|
|
&prepare_ctx[i]);
|
|
if (ret) {
|
|
errno = -ret;
|
|
error("failed to create thread for prepare device %s: %m",
|
|
prepare_ctx[i].file);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Wait for threads */
|
|
for (i = 0; i < device_count; i++)
|
|
pthread_join(t_prepare[i], NULL);
|
|
ret = prepare_ctx[0].ret;
|
|
|
|
if (ret) {
|
|
error("unable prepare device: %s", prepare_ctx[0].file);
|
|
goto error;
|
|
}
|
|
|
|
device_count--;
|
|
fd = open(file, opt_oflags);
|
|
if (fd < 0) {
|
|
error("unable to open %s: %m", file);
|
|
goto error;
|
|
}
|
|
dev_block_count = prepare_ctx[0].dev_block_count;
|
|
if (block_count && block_count > dev_block_count) {
|
|
error("%s is smaller than requested size, expected %llu, found %llu",
|
|
file, block_count, dev_block_count);
|
|
goto error;
|
|
}
|
|
|
|
/* To create the first block group and chunk 0 in make_btrfs */
|
|
system_group_size = (opt_zoned ? zone_size(file) : BTRFS_MKFS_SYSTEM_GROUP_SIZE);
|
|
if (dev_block_count < system_group_size) {
|
|
error("device is too small to make filesystem, must be at least %llu",
|
|
system_group_size);
|
|
goto error;
|
|
}
|
|
|
|
if (btrfs_bg_type_to_tolerated_failures(metadata_profile) <
|
|
btrfs_bg_type_to_tolerated_failures(data_profile))
|
|
warning("metadata has lower redundancy than data!\n");
|
|
|
|
printf("NOTE: several default settings have changed in version 5.15, please make sure\n");
|
|
printf(" this does not affect your deployments:\n");
|
|
printf(" - DUP for metadata (-m dup)\n");
|
|
printf(" - enabled no-holes (-O no-holes)\n");
|
|
printf(" - enabled free-space-tree (-R free-space-tree)\n");
|
|
printf("\n");
|
|
|
|
mkfs_cfg.label = label;
|
|
memcpy(mkfs_cfg.fs_uuid, fs_uuid, sizeof(mkfs_cfg.fs_uuid));
|
|
mkfs_cfg.num_bytes = dev_block_count;
|
|
mkfs_cfg.nodesize = nodesize;
|
|
mkfs_cfg.sectorsize = sectorsize;
|
|
mkfs_cfg.stripesize = stripesize;
|
|
mkfs_cfg.features = features;
|
|
mkfs_cfg.runtime_features = runtime_features;
|
|
mkfs_cfg.csum_type = csum_type;
|
|
mkfs_cfg.leaf_data_size = __BTRFS_LEAF_DATA_SIZE(nodesize);
|
|
if (opt_zoned)
|
|
mkfs_cfg.zone_size = zone_size(file);
|
|
else
|
|
mkfs_cfg.zone_size = 0;
|
|
|
|
ret = make_btrfs(fd, &mkfs_cfg);
|
|
if (ret) {
|
|
errno = -ret;
|
|
error("error during mkfs: %m");
|
|
goto error;
|
|
}
|
|
|
|
ocf.filename = file;
|
|
ocf.flags = OPEN_CTREE_WRITES | OPEN_CTREE_TEMPORARY_SUPER;
|
|
fs_info = open_ctree_fs_info(&ocf);
|
|
if (!fs_info) {
|
|
error("open ctree failed");
|
|
goto error;
|
|
}
|
|
close(fd);
|
|
fd = -1;
|
|
root = fs_info->fs_root;
|
|
|
|
ret = create_metadata_block_groups(root, mixed, &allocation);
|
|
if (ret) {
|
|
error("failed to create default block groups: %d", ret);
|
|
goto error;
|
|
}
|
|
|
|
trans = btrfs_start_transaction(root, 1);
|
|
if (IS_ERR(trans)) {
|
|
error("failed to start transaction");
|
|
goto error;
|
|
}
|
|
|
|
ret = create_data_block_groups(trans, root, mixed, &allocation);
|
|
if (ret) {
|
|
error("failed to create default data block groups: %d", ret);
|
|
goto error;
|
|
}
|
|
|
|
if (features & BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2) {
|
|
ret = create_global_roots(trans, nr_global_roots);
|
|
if (ret) {
|
|
error("failed to create global roots: %d", ret);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
ret = make_root_dir(trans, root);
|
|
if (ret) {
|
|
error("failed to setup the root directory: %d", ret);
|
|
goto error;
|
|
}
|
|
|
|
ret = btrfs_commit_transaction(trans, root);
|
|
if (ret) {
|
|
error("unable to commit transaction: %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
trans = btrfs_start_transaction(root, 1);
|
|
if (IS_ERR(trans)) {
|
|
error("failed to start transaction");
|
|
goto error;
|
|
}
|
|
|
|
if (device_count == 0)
|
|
goto raid_groups;
|
|
|
|
while (device_count-- > 0) {
|
|
int dev_index = argc - saved_optind - device_count - 1;
|
|
file = argv[optind++];
|
|
|
|
fd = open(file, opt_oflags);
|
|
if (fd < 0) {
|
|
error("unable to open %s: %m", file);
|
|
goto error;
|
|
}
|
|
ret = btrfs_device_already_in_root(root, fd,
|
|
BTRFS_SUPER_INFO_OFFSET);
|
|
if (ret) {
|
|
error("skipping duplicate device %s in the filesystem",
|
|
file);
|
|
close(fd);
|
|
continue;
|
|
}
|
|
dev_block_count = prepare_ctx[dev_index].dev_block_count;
|
|
|
|
if (prepare_ctx[dev_index].ret) {
|
|
errno = -prepare_ctx[dev_index].ret;
|
|
error("unable to prepare device %s: %m",
|
|
prepare_ctx[dev_index].file);
|
|
goto error;
|
|
}
|
|
|
|
ret = btrfs_add_to_fsid(trans, root, fd, file, dev_block_count,
|
|
sectorsize, sectorsize, sectorsize);
|
|
if (ret) {
|
|
error("unable to add %s to filesystem: %d", file, ret);
|
|
goto error;
|
|
}
|
|
if (bconf.verbose >= 2) {
|
|
struct btrfs_device *device;
|
|
|
|
device = container_of(fs_info->fs_devices->devices.next,
|
|
struct btrfs_device, dev_list);
|
|
printf("adding device %s id %llu\n", file, device->devid);
|
|
}
|
|
}
|
|
|
|
raid_groups:
|
|
ret = create_raid_groups(trans, root, data_profile,
|
|
metadata_profile, mixed, &allocation);
|
|
if (ret) {
|
|
error("unable to create raid groups: %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Commit current transaction so we can COW all existing tree blocks
|
|
* to newly created raid groups.
|
|
* As currently we use btrfs_search_slot() to COW tree blocks in
|
|
* recow_roots(), if a tree block is already modified in current trans,
|
|
* it won't be re-COWed, thus it will stay in temporary chunks.
|
|
*/
|
|
ret = btrfs_commit_transaction(trans, root);
|
|
if (ret) {
|
|
errno = -ret;
|
|
error("unable to commit transaction before recowing trees: %m");
|
|
goto out;
|
|
}
|
|
trans = btrfs_start_transaction(root, 1);
|
|
if (IS_ERR(trans)) {
|
|
errno = -PTR_ERR(trans);
|
|
error("failed to start transaction: %m");
|
|
goto error;
|
|
}
|
|
/* COW all tree blocks to newly created chunks */
|
|
ret = recow_roots(trans, root);
|
|
if (ret) {
|
|
errno = -ret;
|
|
error("unable to COW tree blocks to new profiles: %m");
|
|
goto out;
|
|
}
|
|
|
|
ret = create_data_reloc_tree(trans);
|
|
if (ret) {
|
|
error("unable to create data reloc tree: %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = create_uuid_tree(trans);
|
|
if (ret)
|
|
warning(
|
|
"unable to create uuid tree, will be created after mount: %d", ret);
|
|
|
|
ret = btrfs_commit_transaction(trans, root);
|
|
if (ret) {
|
|
error("unable to commit transaction: %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
ret = cleanup_temp_chunks(fs_info, &allocation, data_profile,
|
|
metadata_profile, metadata_profile);
|
|
if (ret < 0) {
|
|
error("failed to cleanup temporary chunks: %d", ret);
|
|
goto out;
|
|
}
|
|
|
|
if (source_dir_set) {
|
|
ret = btrfs_mkfs_fill_dir(source_dir, root, bconf.verbose);
|
|
if (ret) {
|
|
error("error while filling filesystem: %d", ret);
|
|
goto out;
|
|
}
|
|
if (shrink_rootdir) {
|
|
ret = btrfs_mkfs_shrink_fs(fs_info, &shrink_size,
|
|
shrink_rootdir);
|
|
if (ret < 0) {
|
|
error("error while shrinking filesystem: %d",
|
|
ret);
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (runtime_features & BTRFS_RUNTIME_FEATURE_QUOTA) {
|
|
ret = setup_quota_root(fs_info);
|
|
if (ret < 0) {
|
|
error("failed to initialize quota: %d (%m)", ret);
|
|
goto out;
|
|
}
|
|
}
|
|
if (bconf.verbose) {
|
|
char features_buf[64];
|
|
|
|
update_chunk_allocation(fs_info, &allocation);
|
|
printf("Label: %s\n", label);
|
|
printf("UUID: %s\n", mkfs_cfg.fs_uuid);
|
|
printf("Node size: %u\n", nodesize);
|
|
printf("Sector size: %u\n", sectorsize);
|
|
printf("Filesystem size: %s\n",
|
|
pretty_size(btrfs_super_total_bytes(fs_info->super_copy)));
|
|
printf("Block group profiles:\n");
|
|
if (allocation.data)
|
|
printf(" Data: %-8s %16s\n",
|
|
btrfs_group_profile_str(data_profile),
|
|
pretty_size(allocation.data));
|
|
if (allocation.metadata)
|
|
printf(" Metadata: %-8s %16s\n",
|
|
btrfs_group_profile_str(metadata_profile),
|
|
pretty_size(allocation.metadata));
|
|
if (allocation.mixed)
|
|
printf(" Data+Metadata: %-8s %16s\n",
|
|
btrfs_group_profile_str(data_profile),
|
|
pretty_size(allocation.mixed));
|
|
printf(" System: %-8s %16s\n",
|
|
btrfs_group_profile_str(metadata_profile),
|
|
pretty_size(allocation.system));
|
|
printf("SSD detected: %s\n", ssd ? "yes" : "no");
|
|
printf("Zoned device: %s\n", opt_zoned ? "yes" : "no");
|
|
if (opt_zoned)
|
|
printf(" Zone size: %s\n",
|
|
pretty_size(fs_info->zone_size));
|
|
btrfs_parse_fs_features_to_string(features_buf, features);
|
|
printf("Incompat features: %s\n", features_buf);
|
|
btrfs_parse_runtime_features_to_string(features_buf,
|
|
runtime_features);
|
|
printf("Runtime features: %s\n", features_buf);
|
|
printf("Checksum: %s",
|
|
btrfs_super_csum_name(mkfs_cfg.csum_type));
|
|
printf("\n");
|
|
|
|
list_all_devices(root);
|
|
|
|
if (mkfs_cfg.csum_type == BTRFS_CSUM_TYPE_SHA256) {
|
|
printf(
|
|
"NOTE: you may need to manually load kernel module implementing accelerated SHA256 in case\n"
|
|
" the generic implementation is built-in, before mount. Check lsmod or /proc/crypto\n\n"
|
|
);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The filesystem is now fully set up, commit the remaining changes and
|
|
* fix the signature as the last step before closing the devices.
|
|
*/
|
|
fs_info->finalize_on_close = 1;
|
|
out:
|
|
close_ret = close_ctree(root);
|
|
|
|
if (!close_ret) {
|
|
optind = saved_optind;
|
|
device_count = argc - optind;
|
|
while (device_count-- > 0) {
|
|
file = argv[optind++];
|
|
if (path_is_block_device(file) == 1)
|
|
btrfs_register_one_device(file);
|
|
}
|
|
}
|
|
|
|
if (!ret && close_ret) {
|
|
ret = close_ret;
|
|
error("failed to close ctree, the filesystem may be inconsistent: %d",
|
|
ret);
|
|
}
|
|
|
|
btrfs_close_all_devices();
|
|
free(t_prepare);
|
|
free(prepare_ctx);
|
|
free(label);
|
|
free(source_dir);
|
|
|
|
return !!ret;
|
|
|
|
error:
|
|
if (fd > 0)
|
|
close(fd);
|
|
|
|
free(t_prepare);
|
|
free(prepare_ctx);
|
|
free(label);
|
|
free(source_dir);
|
|
exit(1);
|
|
success:
|
|
exit(0);
|
|
}
|