diff --git a/Documentation/mkfs.btrfs.rst b/Documentation/mkfs.btrfs.rst index 0e9e84ad..629231a2 100644 --- a/Documentation/mkfs.btrfs.rst +++ b/Documentation/mkfs.btrfs.rst @@ -155,11 +155,15 @@ OPTIONS contain the files from *rootdir*. Since version 4.14.1 the filesystem size is not minimized. Please see option *--shrink* if you need that functionality. --u|--subvol +-u|--subvol : Specify that *subdir* is to be created as a subvolume rather than a regular directory. The option *--rootdir* must also be specified, and *subdir* must be an existing subdirectory within it. This option can be specified multiple times. + *flags* is an optional comma-separated list of modifiers. Valid choices are: + + * *default*: create as default subvolume (this can only be specified once) + --shrink Shrink the filesystem to its minimal size, only works with *--rootdir* option. diff --git a/mkfs/main.c b/mkfs/main.c index 57d9fcc7..b0d4fd0b 100644 --- a/mkfs/main.c +++ b/mkfs/main.c @@ -440,7 +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, can be specified multiple times"), + OPTLINE("-u|--subvol SUBDIR:FLAGS", "create SUBDIR as subvolume rather than normal directory, can be specified multiple times"), 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"), @@ -1015,6 +1015,46 @@ static void *prepare_one_device(void *ctx) return NULL; } +static int parse_subvol_flags(struct rootdir_subvol *subvol, const char *flags) +{ + char *buf, *orig_buf; + int ret; + + buf = orig_buf = strdup(flags); + + if (!buf) { + error_msg(ERROR_MSG_MEMORY, NULL); + ret = -ENOMEM; + goto out; + } + + while (true) { + char *comma = strstr(buf, ","); + + if (comma) + *comma = 0; + + if (!strcmp(buf, "default")) { + subvol->is_default = true; + } else if (buf[0] != 0) { + error("unrecognized subvol flag \"%s\"", buf); + ret = 1; + goto out; + } + + if (comma) + buf = comma + 1; + else + break; + } + + ret = 0; + +out: + free(orig_buf); + return ret; +} + int BOX_MAIN(mkfs)(int argc, char **argv) { char *file; @@ -1058,6 +1098,7 @@ int BOX_MAIN(mkfs)(int argc, char **argv) char *source_dir = NULL; size_t source_dir_len = 0; struct rootdir_subvol *rds; + bool has_default_subvol = false; LIST_HEAD(subvols); cpu_detect_flags(); @@ -1215,16 +1256,49 @@ int BOX_MAIN(mkfs)(int argc, char **argv) break; case 'u': { struct rootdir_subvol *subvol; + char *colon; - subvol = malloc(sizeof(struct rootdir_subvol)); + subvol = calloc(1, sizeof(struct rootdir_subvol)); if (!subvol) { error_msg(ERROR_MSG_MEMORY, NULL); ret = 1; goto error; } - subvol->dir = strdup(optarg); - subvol->full_path = NULL; + colon = strstr(optarg, ":"); + + if (colon) { + /* Make sure we choose the last colon in + * optarg, in case the subvol name + * itself contains a colon. */ + do { + char *colon2; + + colon2 = strstr(colon + 1, ":"); + + if (colon2) + colon = colon2; + else + break; + } while (true); + + subvol->dir = strndup(optarg, colon - optarg); + if (parse_subvol_flags(subvol, colon + 1)) { + ret = 1; + goto error; + } + } else { + subvol->dir = strdup(optarg); + } + + if (subvol->is_default) { + if (has_default_subvol) { + error("subvol default flag can only be specified once"); + ret = 1; + goto error; + } + has_default_subvol = true; + } list_add_tail(&subvol->list, &subvols); break; diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c index 3cc94316..c31651d0 100644 --- a/mkfs/rootdir.c +++ b/mkfs/rootdir.c @@ -99,6 +99,7 @@ static u64 g_hardlink_count; static struct btrfs_trans_handle *g_trans = NULL; static struct list_head *g_subvols; static u64 next_subvol_id = BTRFS_FIRST_FREE_OBJECTID; +static u64 default_subvol_id; static inline struct inode_entry *rootdir_path_last(struct rootdir_path *path) { @@ -436,6 +437,9 @@ static int ftw_add_subvol(const char *full_path, const struct stat *st, return ret; } + if (subvol->is_default) + default_subvol_id = subvol_id; + key.objectid = subvol_id; key.type = BTRFS_ROOT_ITEM_KEY; key.offset = (u64)-1; @@ -701,6 +705,47 @@ static int ftw_add_inode(const char *full_path, const struct stat *st, return 0; }; +static int set_default_subvolume(struct btrfs_trans_handle *trans) +{ + struct btrfs_path path = { 0 }; + struct btrfs_dir_item *di; + struct btrfs_key location; + struct extent_buffer *leaf; + struct btrfs_disk_key disk_key; + u64 features; + + di = btrfs_lookup_dir_item(trans, trans->fs_info->tree_root, &path, + btrfs_super_root_dir(trans->fs_info->super_copy), + "default", 7, 1); + if (IS_ERR_OR_NULL(di)) { + btrfs_release_path(&path); + + if (di) + return PTR_ERR(di); + else + return -ENOENT; + } + + leaf = path.nodes[0]; + + location.objectid = default_subvol_id; + location.type = BTRFS_ROOT_ITEM_KEY; + location.offset = 0; + + btrfs_cpu_key_to_disk(&disk_key, &location); + btrfs_set_dir_item_key(leaf, di, &disk_key); + + btrfs_mark_buffer_dirty(leaf); + + btrfs_release_path(&path); + + features = btrfs_super_incompat_flags(trans->fs_info->super_copy); + features |= BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL; + btrfs_set_super_incompat_flags(trans->fs_info->super_copy, features); + + return 0; +} + int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir, struct btrfs_root *root, struct list_head *subvols) { @@ -732,6 +777,14 @@ int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir while (current_path.level > 0) rootdir_path_pop(¤t_path); + if (default_subvol_id != 0) { + ret = set_default_subvolume(trans); + if (ret < 0) { + error("error setting default subvolume: %d", ret); + return ret; + } + } + return 0; } diff --git a/mkfs/rootdir.h b/mkfs/rootdir.h index 128e9e09..871889d9 100644 --- a/mkfs/rootdir.h +++ b/mkfs/rootdir.h @@ -32,6 +32,7 @@ struct rootdir_subvol { struct list_head list; char *dir; char *full_path; + bool is_default; }; int btrfs_mkfs_fill_dir(struct btrfs_trans_handle *trans, const char *source_dir,