btrfs-progs: subvolume create: accept multiple arguments

This patch would make "btrfs subvolume create" to accept multiple
arguments, just like "mkdir".

The existing options like "-i <qgroupid>" and "-p" would all be applied
to all subvolume(s).

If one destination failed, the command would return 1, while still retry
the remaining destinations.

Issue: #695
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Qu Wenruo 2023-11-02 16:03:49 +10:30 committed by David Sterba
parent 943a69ef2d
commit 5aa959fb34
2 changed files with 95 additions and 72 deletions

View File

@ -49,11 +49,18 @@ do not affect the files in the original subvolume.
SUBCOMMAND SUBCOMMAND
----------- -----------
create [options] [<dest>/]<name> create [options] [<dest>/]<name> [[<dest2>/]<name2> ...]
Create a subvolume *name* in *dest*. Create subvolume(s) at the destination(s).
If *dest* is not given, subvolume *name* will be created in the current If *dest* part of the path is not given, subvolume *name* will be
directory. created in the current directory.
If multiple desinations are given, then given options are applied to all
subvolumes.
If failure happens for any of the destinations, the command would
still retry the remaining destinations, but would return 1 to indicate
the failure (similar to what :command:`mkdir` would do.
``Options`` ``Options``

View File

@ -114,81 +114,38 @@ static const char * const subvolume_cmd_group_usage[] = {
}; };
static const char * const cmd_subvolume_create_usage[] = { static const char * const cmd_subvolume_create_usage[] = {
"btrfs subvolume create [options] [<dest>/]<name>", "btrfs subvolume create [options] [<dest>/]<name> [[<dest2>/]<name2> ...]",
"Create a subvolume", "Create subvolume(s)",
"Create a subvolume <name> in <dest>. If <dest> is not given", "Create subvolume(s) at specified destination. If <dest> is not given",
"subvolume <name> will be created in the current directory.", "subvolume <name> will be created in the current directory. Options apply",
"to all created subvolumes.",
"", "",
OPTLINE("-i <qgroupid>", "add the newly created subvolume to a qgroup. This option can be given multiple times."), OPTLINE("-i <qgroupid>", "add the newly created subvolume(s) to a qgroup. This option can be given multiple times."),
OPTLINE("-p|--parents", "create any missing parent directories for each argument (like mkdir -p)"), OPTLINE("-p|--parents", "create any missing parent directories for each argument (like mkdir -p)"),
HELPINFO_INSERT_GLOBALS, HELPINFO_INSERT_GLOBALS,
HELPINFO_INSERT_QUIET, HELPINFO_INSERT_QUIET,
NULL NULL
}; };
static int cmd_subvolume_create(const struct cmd_struct *cmd, int argc, char **argv) static int create_one_subvolume(const char *dst, struct btrfs_qgroup_inherit *inherit,
bool create_parents)
{ {
int retval, res, len; int ret;
int len;
int fddst = -1; int fddst = -1;
char *dupname = NULL; char *dupname = NULL;
char *dupdir = NULL; char *dupdir = NULL;
char *newname; char *newname;
char *dstdir; char *dstdir;
char *dst;
struct btrfs_qgroup_inherit *inherit = NULL;
DIR *dirstream = NULL; DIR *dirstream = NULL;
bool create_parents = false;
optind = 0; ret = path_is_dir(dst);
while (1) { if (ret < 0 && ret != -ENOENT) {
int c; errno = -ret;
static const struct option long_options[] = {
{ "parents", no_argument, NULL, 'p' },
{ NULL, 0, NULL, 0 }
};
c = getopt_long(argc, argv, "i:p", long_options, NULL);
if (c < 0)
break;
switch (c) {
case 'c':
res = btrfs_qgroup_inherit_add_copy(&inherit, optarg, 0);
if (res) {
retval = res;
goto out;
}
break;
case 'i':
res = btrfs_qgroup_inherit_add_group(&inherit, optarg);
if (res) {
retval = res;
goto out;
}
break;
case 'p':
create_parents = true;
break;
default:
usage_unknown_option(cmd, argv);
}
}
if (check_argc_exact(argc - optind, 1)) {
retval = 1;
goto out;
}
dst = argv[optind];
retval = 1; /* failure */
res = path_is_dir(dst);
if (res < 0 && res != -ENOENT) {
errno = -res;
error("cannot access %s: %m", dst); error("cannot access %s: %m", dst);
goto out; goto out;
} }
if (res >= 0) { if (ret >= 0) {
error("target path already exists: %s", dst); error("target path already exists: %s", dst);
goto out; goto out;
} }
@ -230,15 +187,15 @@ static int cmd_subvolume_create(const struct cmd_struct *cmd, int argc, char **a
token = strtok(dstdir_dup, "/"); token = strtok(dstdir_dup, "/");
while (token) { while (token) {
strcat(p, token); strcat(p, token);
res = path_is_dir(p); ret = path_is_dir(p);
if (res == -ENOENT) { if (ret == -ENOENT) {
res = mkdir(p, 0777); ret = mkdir(p, 0777);
if (res < 0) { if (ret < 0) {
error("failed to create directory %s: %m", p); error("failed to create directory %s: %m", p);
goto out; goto out;
} }
} else if (res <= 0) { } else if (ret <= 0) {
errno = res; errno = ret ;
error("failed to check directory %s before creation: %m", p); error("failed to check directory %s before creation: %m", p);
goto out; goto out;
} }
@ -261,28 +218,87 @@ static int cmd_subvolume_create(const struct cmd_struct *cmd, int argc, char **a
args.size = btrfs_qgroup_inherit_size(inherit); args.size = btrfs_qgroup_inherit_size(inherit);
args.qgroup_inherit = inherit; args.qgroup_inherit = inherit;
res = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE_V2, &args); ret = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE_V2, &args);
} else { } else {
struct btrfs_ioctl_vol_args args; struct btrfs_ioctl_vol_args args;
memset(&args, 0, sizeof(args)); memset(&args, 0, sizeof(args));
strncpy_null(args.name, newname); strncpy_null(args.name, newname);
res = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE, &args); ret = ioctl(fddst, BTRFS_IOC_SUBVOL_CREATE, &args);
} }
if (res < 0) { if (ret < 0) {
error("cannot create subvolume: %m"); error("cannot create subvolume: %m");
goto out; goto out;
} }
retval = 0; /* success */
out: out:
close_file_or_dir(fddst, dirstream); close_file_or_dir(fddst, dirstream);
free(inherit);
free(dupname); free(dupname);
free(dupdir); free(dupdir);
return ret;
}
static int cmd_subvolume_create(const struct cmd_struct *cmd, int argc, char **argv)
{
int retval, ret;
struct btrfs_qgroup_inherit *inherit = NULL;
bool has_error = false;
bool create_parents = false;
optind = 0;
while (1) {
int c;
static const struct option long_options[] = {
{ "parents", no_argument, NULL, 'p' },
{ NULL, 0, NULL, 0 }
};
c = getopt_long(argc, argv, "i:p", long_options, NULL);
if (c < 0)
break;
switch (c) {
case 'c':
ret = btrfs_qgroup_inherit_add_copy(&inherit, optarg, 0);
if (ret) {
retval = ret;
goto out;
}
break;
case 'i':
ret = btrfs_qgroup_inherit_add_group(&inherit, optarg);
if (ret) {
retval = ret;
goto out;
}
break;
case 'p':
create_parents = true;
break;
default:
usage_unknown_option(cmd, argv);
}
}
if (check_argc_min(argc - optind, 1)) {
retval = 1;
goto out;
}
retval = 1;
for (int i = optind; i < argc; i++) {
ret = create_one_subvolume(argv[i], inherit, create_parents);
if (ret < 0)
has_error = true;
}
if (!has_error)
retval = 0;
out:
free(inherit);
return retval; return retval;
} }
static DEFINE_SIMPLE_COMMAND(subvolume_create, "create"); static DEFINE_SIMPLE_COMMAND(subvolume_create, "create");