diff mbox series

[v2,8/9] btrfs-progs: mkfs: introduce experimental --subvol option

Message ID 9a662ec107f96350c8b97131bb2094f16352fd65.1697754500.git.wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series btrfs-progs: mkfs: introduce an experimental --subvol option | expand

Commit Message

Qu Wenruo Oct. 19, 2023, 10:30 p.m. UTC
Although mkfs.btrfs supports --rootdir to fill the target filesystem, it
doesn't have the ability to create any subvolume.

This patch introduce a very basic version of --subvol for mkfs.btrfs,
the limits are:

- No co-operation with --rootdir
  This requires --rootdir to have extra handling for any existing
  inodes.
  (Currently --rootdir assumes the fs tree is completely empty)

- No multiple --subvol options supports
  This requires us to collect and sort all the paths and start creating
  subvolumes from the shortest path.
  Furthermore this requires us to create subvolume under another
  subvolume.

For now, this patch focus on the basic checks on the provided subvolume
path, to wipe out any invalid things like ".." or something like "//////".

We support something like "//dir1/dir2///subvol///" just like VFS path
(duplicated '/' would just be ignored).

Issue: #42
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 mkfs/main.c    |  25 ++++++++
 mkfs/rootdir.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++
 mkfs/rootdir.h |   1 +
 3 files changed, 183 insertions(+)
diff mbox series

Patch

diff --git a/mkfs/main.c b/mkfs/main.c
index e5201fbc9c31..c0fdf6e94a3c 100644
--- a/mkfs/main.c
+++ b/mkfs/main.c
@@ -436,6 +436,9 @@  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"),
+#if EXPERIMENTAL
+	OPTLINE("--subvol SUBVOL_NAME", "create a subvolume and all its parent directory"),
+#endif
 	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"),
@@ -1110,6 +1113,7 @@  int BOX_MAIN(mkfs)(int argc, char **argv)
 	char *label = NULL;
 	int nr_global_roots = sysconf(_SC_NPROCESSORS_ONLN);
 	char *source_dir = NULL;
+	char *subvol = NULL;
 
 	cpu_detect_flags();
 	hash_init_accel();
@@ -1123,6 +1127,7 @@  int BOX_MAIN(mkfs)(int argc, char **argv)
 			GETOPT_VAL_CHECKSUM,
 			GETOPT_VAL_GLOBAL_ROOTS,
 			GETOPT_VAL_DEVICE_UUID,
+			GETOPT_VAL_SUBVOL,
 		};
 		static const struct option long_options[] = {
 			{ "byte-count", required_argument, NULL, 'b' },
@@ -1151,6 +1156,7 @@  int BOX_MAIN(mkfs)(int argc, char **argv)
 			{ "shrink", no_argument, NULL, GETOPT_VAL_SHRINK },
 #if EXPERIMENTAL
 			{ "num-global-roots", required_argument, NULL, GETOPT_VAL_GLOBAL_ROOTS },
+			{ "subvol", required_argument, NULL, GETOPT_VAL_SUBVOL},
 #endif
 			{ "help", no_argument, NULL, GETOPT_VAL_HELP },
 			{ NULL, 0, NULL, 0}
@@ -1283,6 +1289,10 @@  int BOX_MAIN(mkfs)(int argc, char **argv)
 				btrfs_warn_experimental("Feature: num-global-roots is part of exten-tree-v2");
 				nr_global_roots = (int)arg_strtou64(optarg);
 				break;
+			case GETOPT_VAL_SUBVOL:
+				btrfs_warn_experimental("Option --subvol is still experimental");
+				subvol = optarg;
+				break;
 			case GETOPT_VAL_HELP:
 			default:
 				usage(&mkfs_cmd, c != GETOPT_VAL_HELP);
@@ -1319,6 +1329,11 @@  int BOX_MAIN(mkfs)(int argc, char **argv)
 		goto error;
 	}
 
+	if (source_dir && subvol) {
+		error("--subvol is not yet supported with --rootdir");
+		goto error;
+	}
+
 	if (*fs_uuid) {
 		uuid_t dummy_uuid;
 
@@ -1875,6 +1890,16 @@  raid_groups:
 		goto out;
 	}
 
+	/* Create the subvolumes. */
+	if (subvol) {
+		ret = btrfs_make_subvolume_at(fs_info, subvol);
+		if (ret < 0) {
+			errno = -ret;
+			error("failed to create subvolume \"%s\": %m", subvol);
+			goto out;
+		}
+	}
+
 	if (source_dir) {
 		pr_verbose(LOG_DEFAULT, "Rootdir from:       %s\n", source_dir);
 		ret = btrfs_mkfs_fill_dir(source_dir, root);
diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c
index 4ae9f435a7b7..8d32ca4be77d 100644
--- a/mkfs/rootdir.c
+++ b/mkfs/rootdir.c
@@ -36,6 +36,8 @@ 
 #include "kernel-shared/disk-io.h"
 #include "kernel-shared/transaction.h"
 #include "kernel-shared/file-item.h"
+#include "kernel-shared/messages.h"
+#include "common/inode.h"
 #include "common/internal.h"
 #include "common/messages.h"
 #include "common/path-utils.h"
@@ -1012,3 +1014,158 @@  int btrfs_mkfs_shrink_fs(struct btrfs_fs_info *fs_info, u64 *new_size_ret,
 	}
 	return ret;
 }
+
+/*
+ * Create a subvolume at the path specififed by @full_path.
+ *
+ * The @full_path always starts at fs_tree root.
+ * All the parent directories would be created.
+ */
+int btrfs_make_subvolume_at(struct btrfs_fs_info *fs_info, const char *full_path)
+{
+	struct btrfs_root *root = fs_info->fs_root;
+	struct btrfs_root *subvol;
+	struct btrfs_trans_handle *trans;
+	u64 parent_ino = btrfs_root_dirid(&root->root_item);
+	u64 subvolid;
+	char *dump = strdup(full_path);
+	char *orig = dump;
+	char *filename;
+	int nr_filenames = 0;
+	int cur_filename = 0;
+	int ret = 0;
+
+	if (!dump)
+		return -ENOMEM;
+
+	/*
+	 * Get the number of valid filenames, this is to determine
+	 * if we're at the last filename (and needs to create a subvolume
+	 * other than a direcotry).
+	 */
+	while ((filename = strsep(&dump, "/")) != NULL) {
+		if (strlen(filename) == 0)
+			continue;
+		if (!strcmp(filename, "."))
+			continue;
+		if (!strcmp(filename, "..")) {
+			error("can not use \"..\" for subvolume path");
+			ret = -EINVAL;
+			goto out;
+		}
+		if (strlen(filename) > NAME_MAX) {
+			error("direcotry name \"%s\" is too long, limit is %d",
+			      filename, NAME_MAX);
+			ret = -EINVAL;
+			goto out;
+		}
+		nr_filenames++;
+	}
+	free(orig);
+	orig = NULL;
+	dump = NULL;
+
+	/* Just some garbage full of '/'. */
+	if (nr_filenames == 0) {
+		error("'%s' contains no valid subvolume name", full_path);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	dump = strdup(full_path);
+	orig = dump;
+
+	while ((filename = strsep(&dump, "/")) != NULL) {
+		u64 child_ino = 0;
+
+		if (strlen(filename) == 0)
+			continue;
+		if (!strcmp(filename, "."))
+			continue;
+		ASSERT(strcmp(filename, ".."));
+
+		if (cur_filename == nr_filenames - 1)
+			break;
+		/*
+		 * Need to modify the following items:
+		 * - Parent inode item
+		 *   Increase the size
+		 *
+		 * - Parent 1x DIR_INDEX and 1x DIR_ITEM items
+		 *
+		 * - New child inode item
+		 * - New child inode ref
+		 */
+		trans = btrfs_start_transaction(root, 4);
+		if (IS_ERR(trans)) {
+			errno = -ret;
+			error("failed to start transaction: %m");
+			ret = PTR_ERR(trans);
+			goto out;
+		}
+		ret = btrfs_mkdir(trans, root, filename, strlen(filename),
+				  parent_ino, &child_ino, 0755);
+		if (ret < 0) {
+			errno = -ret;
+			error("failed to create direcotry %s in root %lld: %m",
+			      filename, root->root_key.objectid);
+			goto out;
+		}
+		ret = btrfs_commit_transaction(trans, root);
+		if (ret < 0) {
+			errno = -ret;
+			error("failed to commit trans for direcotry %s in root %lld: %m",
+			      filename, root->root_key.objectid);
+			goto out;
+		}
+		parent_ino = child_ino;
+		cur_filename++;
+	}
+	if (!filename) {
+		ret = -EUCLEAN;
+		error("No valid subvolume name found");
+		goto out;
+	}
+
+	/* Create the final subvolume. */
+	trans = btrfs_start_transaction(fs_info->tree_root, 4);
+	if (IS_ERR(trans)) {
+		errno = -ret;
+		error("failed to start transaction for subvolume creation %s: %m",
+		      filename);
+		goto out;
+	}
+	ret = btrfs_find_free_objectid(NULL, fs_info->tree_root,
+				       BTRFS_FIRST_FREE_OBJECTID, &subvolid);
+	if (ret < 0) {
+		errno = -ret;
+		error("failed to find a free objectid for subvolume %s: %m",
+		      filename);
+		goto out;
+	}
+	subvol = btrfs_create_subvol(trans, subvolid);
+	if (IS_ERR(subvol)) {
+		ret = PTR_ERR(subvol);
+		errno = -ret;
+		error("failed to create subvolume %s: %m",
+		      filename);
+		goto out;
+	}
+
+	ret = btrfs_add_link(trans, subvol, btrfs_root_dirid(&subvol->root_item),
+			     fs_info->fs_root, parent_ino,
+			     filename, strlen(filename), NULL, false);
+	if (ret < 0) {
+		errno = -ret;
+		error("failed to link subvol %s: %m", filename);
+		goto out;
+	}
+	ret = btrfs_commit_transaction(trans, fs_info->tree_root);
+	if (ret < 0) {
+		errno = -ret;
+		error("failed to commit transaction: %m");
+	}
+out:
+	free(orig);
+	return ret;
+}
diff --git a/mkfs/rootdir.h b/mkfs/rootdir.h
index 8d5f6896c3d9..1b21f30e80b5 100644
--- a/mkfs/rootdir.h
+++ b/mkfs/rootdir.h
@@ -41,5 +41,6 @@  u64 btrfs_mkfs_size_dir(const char *dir_name, u32 sectorsize, u64 min_dev_size,
 			u64 meta_profile, u64 data_profile);
 int btrfs_mkfs_shrink_fs(struct btrfs_fs_info *fs_info, u64 *new_size_ret,
 			 bool shrink_file_size);
+int btrfs_make_subvolume_at(struct btrfs_fs_info *fs_info, const char *full_path);
 
 #endif