Skip to content

Commit

Permalink
btrfs-progs: add --subvol option to mkfs.btrfs
Browse files Browse the repository at this point in the history
This patch adds a --subvol option, which tells mkfs.btrfs to create the
specified directories as subvolumes.

Given a populated directory img, the command

$ mkfs.btrfs --rootdir img --subvol usr --subvol home --subvol home/username /dev/loop0

will create subvolumes usr and home within the FS root, and subvolume
username within the home subvolume. It will fail if any of the
directories do not yet exist.

Signed-off-by: Mark Harmstone <[email protected]>
  • Loading branch information
maharmstone committed Aug 13, 2024
1 parent 29da0fe commit 70b9d17
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 42 deletions.
165 changes: 153 additions & 12 deletions mkfs/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ static const char * const mkfs_usage[] = {
"Creation:",
OPTLINE("-b|--byte-count SIZE", "set size of each device to SIZE (filesystem size is sum of all device sizes)"),
OPTLINE("-r|--rootdir DIR", "copy files from DIR to the image root directory"),
OPTLINE("-u|--subvol SUBDIR", "create SUBDIR as subvolume rather than normal directory"),
OPTLINE("--shrink", "(with --rootdir) shrink the filled filesystem to minimal size"),
OPTLINE("-K|--nodiscard", "do not perform whole device TRIM"),
OPTLINE("-f|--force", "force overwrite of existing filesystem"),
Expand Down Expand Up @@ -1055,6 +1056,9 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
char *label = NULL;
int nr_global_roots = sysconf(_SC_NPROCESSORS_ONLN);
char *source_dir = NULL;
size_t source_dir_len = 0;
struct rootdir_subvol *rds;
LIST_HEAD(subvols);

cpu_detect_flags();
hash_init_accel();
Expand Down Expand Up @@ -1085,6 +1089,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
{ "data", required_argument, NULL, 'd' },
{ "version", no_argument, NULL, 'V' },
{ "rootdir", required_argument, NULL, 'r' },
{ "subvol", required_argument, NULL, 'u' },
{ "nodiscard", no_argument, NULL, 'K' },
{ "features", required_argument, NULL, 'O' },
{ "runtime-features", required_argument, NULL, 'R' },
Expand All @@ -1102,7 +1107,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
{ NULL, 0, NULL, 0}
};

c = getopt_long(argc, argv, "A:b:fl:n:s:m:d:L:R:O:r:U:VvMKq",
c = getopt_long(argc, argv, "A:b:fl:n:s:m:d:L:R:O:r:U:VvMKqu:",
long_options, NULL);
if (c < 0)
break;
Expand Down Expand Up @@ -1208,6 +1213,22 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
free(source_dir);
source_dir = strdup(optarg);
break;
case 'u': {
struct rootdir_subvol *s;

s = malloc(sizeof(struct rootdir_subvol));
if (!s) {
error("out of memory");
ret = 1;
goto error;
}

s->dir = strdup(optarg);
s->full_path = NULL;

list_add_tail(&s->list, &subvols);
break;
}
case 'U':
strncpy_null(fs_uuid, optarg, BTRFS_UUID_UNPARSED_SIZE);
break;
Expand Down Expand Up @@ -1272,6 +1293,90 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
ret = 1;
goto error;
}
if (!list_empty(&subvols) && source_dir == NULL) {
error("the option --subvol must be used with --rootdir");
ret = 1;
goto error;
}

if (source_dir) {
char *canonical = realpath(source_dir, NULL);

if (!canonical) {
error("could not get canonical path to %s", source_dir);
ret = 1;
goto error;
}

free(source_dir);
source_dir = canonical;
source_dir_len = strlen(source_dir);
}

list_for_each_entry(rds, &subvols, list) {
char *path, *canonical;
struct rootdir_subvol *rds2;
size_t dir_len;

dir_len = strlen(rds->dir);

path = malloc(source_dir_len + 1 + dir_len + 1);
if (!path) {
error("out of memory");
ret = 1;
goto error;
}

memcpy(path, source_dir, source_dir_len);
path[source_dir_len] = '/';
memcpy(path + source_dir_len + 1, rds->dir, dir_len + 1);

canonical = realpath(path, NULL);
if (!canonical) {
error("could not get canonical path to %s", rds->dir);
free(path);
ret = 1;
goto error;
}

free(path);
path = canonical;

if (!path_exists(path)) {
error("subvol %s does not exist", rds->dir);
free(path);
ret = 1;
goto error;
}

if (!path_is_dir(path)) {
error("subvol %s is not a directory", rds->dir);
free(path);
ret = 1;
goto error;
}

rds->full_path = path;

if (strlen(path) < source_dir_len + 1 ||
memcmp(path, source_dir, source_dir_len) ||
path[source_dir_len] != '/') {
error("subvol %s is not a child of %s", rds->dir,
source_dir);
ret = 1;
goto error;
}

for (rds2 = list_first_entry(&subvols, struct rootdir_subvol, list);
rds2 != rds; rds2 = list_next_entry(rds2, list)) {
if (!strcmp(rds2->full_path, path)) {
error("subvol %s specified more than once",
rds->dir);
ret = 1;
goto error;
}
}
}

if (*fs_uuid) {
uuid_t dummy_uuid;
Expand Down Expand Up @@ -1821,24 +1926,37 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
error_msg(ERROR_MSG_START_TRANS, "%m");
goto out;
}
ret = btrfs_rebuild_uuid_tree(fs_info);
if (ret < 0)
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) {
pr_verbose(LOG_DEFAULT, "Rootdir from: %s\n", source_dir);
ret = btrfs_mkfs_fill_dir(source_dir, root);

trans = btrfs_start_transaction(root, 1);
if (IS_ERR(trans)) {
errno = -PTR_ERR(trans);
error_msg(ERROR_MSG_START_TRANS, "%m");
goto out;
}

ret = btrfs_mkfs_fill_dir(trans, source_dir, root,
&subvols);
if (ret) {
error("error while filling filesystem: %d", ret);
btrfs_abort_transaction(trans, ret);
goto out;
}

ret = btrfs_commit_transaction(trans, root);
if (ret) {
errno = -ret;
error_msg(ERROR_MSG_COMMIT_TRANS, "%m");
goto out;
}

list_for_each_entry(rds, &subvols, list) {
pr_verbose(LOG_DEFAULT, " Subvol from: %s\n",
rds->full_path);
}

if (shrink_rootdir) {
pr_verbose(LOG_DEFAULT, " Shrink: yes\n");
ret = btrfs_mkfs_shrink_fs(fs_info, &shrink_size,
Expand All @@ -1853,6 +1971,17 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
}
}

ret = btrfs_rebuild_uuid_tree(fs_info);
if (ret < 0)
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 (features.runtime_flags & BTRFS_FEATURE_RUNTIME_QUOTA ||
features.incompat_flags & BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA) {
ret = setup_quota_root(fs_info);
Expand Down Expand Up @@ -1946,6 +2075,18 @@ int BOX_MAIN(mkfs)(int argc, char **argv)
free(label);
free(source_dir);

while (!list_empty(&subvols)) {
struct rootdir_subvol *head = list_entry(subvols.next,
struct rootdir_subvol,
list);

free(head->dir);
free(head->full_path);

list_del(&head->list);
free(head);
}

return !!ret;

success:
Expand Down
Loading

0 comments on commit 70b9d17

Please sign in to comment.