From: Michael Tokarev Subject: qemu-img options rework Forwarded: https://lore.kernel.org/qemu-devel/20240927061121.573271-1-mjt@tls.msk.ru/ This is a patchset which has been sent to qemu upstream for review a number of times, but the review stalled. diff --git a/docs/tools/qemu-img.rst b/docs/tools/qemu-img.rst --- a/docs/tools/qemu-img.rst +++ b/docs/tools/qemu-img.rst @@ -257,5 +257,5 @@ Parameters to snapshot subcommand: .. option:: -l - Lists all snapshots in the given image + Lists all snapshots in the given image (default action) Command description: @@ -664,5 +664,5 @@ Command description: to copy. -.. option:: snapshot [--object OBJECTDEF] [--image-opts] [-U] [-q] [-l | -a SNAPSHOT | -c SNAPSHOT | -d SNAPSHOT] FILENAME +.. option:: snapshot [--object OBJECTDEF] [-f FMT | --image-opts] [-U] [-q] [-l | -a SNAPSHOT | -c SNAPSHOT | -d SNAPSHOT] FILENAME List, apply, create or delete snapshots in image *FILENAME*. diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -85,7 +85,7 @@ ERST DEF("snapshot", img_snapshot, - "snapshot [--object objectdef] [--image-opts] [-U] [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename") + "snapshot [--object objectdef] [-f fmt | --image-opts] [-U] [-q] [-l | -a snapshot | -c snapshot | -d snapshot] filename") SRST -.. option:: snapshot [--object OBJECTDEF] [--image-opts] [-U] [-q] [-l | -a SNAPSHOT | -c SNAPSHOT | -d SNAPSHOT] FILENAME +.. option:: snapshot [--object OBJECTDEF] [-f FMT | --image-opts] [-U] [-q] [-l | -a SNAPSHOT | -c SNAPSHOT | -d SNAPSHOT] FILENAME ERST diff --git a/qemu-img.c b/qemu-img.c --- a/qemu-img.c +++ b/qemu-img.c @@ -61,10 +61,10 @@ typedef struct img_cmd_t { const char *name; - int (*handler)(int argc, char **argv); + int (*handler)(const struct img_cmd_t *ccmd, int argc, char **argv); + const char *description; } img_cmd_t; enum { OPTION_OUTPUT = 256, - OPTION_BACKING_CHAIN = 257, OPTION_OBJECT = 258, OPTION_IMAGE_OPTS = 259, @@ -73,5 +73,4 @@ enum { OPTION_NO_DRAIN = 262, OPTION_TARGET_IMAGE_OPTS = 263, - OPTION_SIZE = 264, OPTION_PREALLOCATION = 265, OPTION_SHRINK = 266, @@ -97,11 +96,13 @@ typedef enum OutputFormat { #define BDRV_DEFAULT_CACHE "writeback" -static void format_print(void *opaque, const char *name) +static G_NORETURN +void tryhelp(const char *argv0) { - printf(" %s", name); + error_printf("Try '%s --help' for more info\n", argv0); + exit(EXIT_FAILURE); } -static G_NORETURN G_GNUC_PRINTF(1, 2) -void error_exit(const char *fmt, ...) +static G_NORETURN G_GNUC_PRINTF(2, 3) +void error_exit(const char *argv0, const char *fmt, ...) { va_list ap; @@ -111,126 +112,43 @@ void error_exit(const char *fmt, ...) va_end(ap); - error_printf("Try 'qemu-img --help' for more information\n"); - exit(EXIT_FAILURE); -} - -static G_NORETURN -void missing_argument(const char *option) -{ - error_exit("missing argument for option '%s'", option); + tryhelp(argv0); } +/* + * Print --help output for a command and exit. + * syntax and description are multi-line with trailing EOL + * (to allow easy extending of the text) + * syntax has each subsequent line indented by 8 chars. + * desrciption is indented by 2 chars for argument on each own line, + * and with 5 chars for argument description (like -h arg below). + */ static G_NORETURN -void unrecognized_option(const char *option) +void cmd_help(const img_cmd_t *ccmd, + const char *syntax, const char *arguments) { - error_exit("unrecognized option '%s'", option); + printf( +"Usage:\n" +"%s. Usage:\n" +"\n" +" %s %s %s" +"\n" +"Arguments:\n" +" -h, --help\n" +" print this help and exit\n" +"%s\n", + ccmd->description, "qemu-img", ccmd->name, + syntax, arguments); + exit(EXIT_SUCCESS); } -/* Please keep in synch with docs/tools/qemu-img.rst */ -static G_NORETURN -void help(void) +static OutputFormat parse_output_format(const char *argv0, const char *arg) { - const char *help_msg = - QEMU_IMG_VERSION - "usage: qemu-img [standard options] command [command options]\n" - "QEMU disk image utility\n" - "\n" - " '-h', '--help' display this help and exit\n" - " '-V', '--version' output version information and exit\n" - " '-T', '--trace' [[enable=]][,events=][,file=]\n" - " specify tracing options\n" - "\n" - "Command syntax:\n" -#define DEF(option, callback, arg_string) \ - " " arg_string "\n" -#include "qemu-img-cmds.h" -#undef DEF - "\n" - "Command parameters:\n" - " 'filename' is a disk image filename\n" - " 'objectdef' is a QEMU user creatable object definition. See the qemu(1)\n" - " manual page for a description of the object properties. The most common\n" - " object type is a 'secret', which is used to supply passwords and/or\n" - " encryption keys.\n" - " 'fmt' is the disk image format. It is guessed automatically in most cases\n" - " 'cache' is the cache mode used to write the output disk image, the valid\n" - " options are: 'none', 'writeback' (default, except for convert), 'writethrough',\n" - " 'directsync' and 'unsafe' (default for convert)\n" - " 'src_cache' is the cache mode used to read input disk images, the valid\n" - " options are the same as for the 'cache' option\n" - " 'size' is the disk image size in bytes. Optional suffixes\n" - " 'k' or 'K' (kilobyte, 1024), 'M' (megabyte, 1024k), 'G' (gigabyte, 1024M),\n" - " 'T' (terabyte, 1024G), 'P' (petabyte, 1024T) and 'E' (exabyte, 1024P) are\n" - " supported. 'b' is ignored.\n" - " 'output_filename' is the destination disk image filename\n" - " 'output_fmt' is the destination format\n" - " 'options' is a comma separated list of format specific options in a\n" - " name=value format. Use -o help for an overview of the options supported by\n" - " the used format\n" - " 'snapshot_param' is param used for internal snapshot, format\n" - " is 'snapshot.id=[ID],snapshot.name=[NAME]', or\n" - " '[ID_OR_NAME]'\n" - " '-c' indicates that target image must be compressed (qcow format only)\n" - " '-u' allows unsafe backing chains. For rebasing, it is assumed that old and\n" - " new backing file match exactly. The image doesn't need a working\n" - " backing file before rebasing in this case (useful for renaming the\n" - " backing file). For image creation, allow creating without attempting\n" - " to open the backing file.\n" - " '-h' with or without a command shows this help and lists the supported formats\n" - " '-p' show progress of command (only certain commands)\n" - " '-q' use Quiet mode - do not print any output (except errors)\n" - " '-S' indicates the consecutive number of bytes (defaults to 4k) that must\n" - " contain only zeros for qemu-img to create a sparse image during\n" - " conversion. If the number of bytes is 0, the source will not be scanned for\n" - " unallocated or zero sectors, and the destination image will always be\n" - " fully allocated\n" - " '--output' takes the format in which the output must be done (human or json)\n" - " '-n' skips the target volume creation (useful if the volume is created\n" - " prior to running qemu-img)\n" - "\n" - "Parameters to bitmap subcommand:\n" - " 'bitmap' is the name of the bitmap to manipulate, through one or more\n" - " actions from '--add', '--remove', '--clear', '--enable', '--disable',\n" - " or '--merge source'\n" - " '-g granularity' sets the granularity for '--add' actions\n" - " '-b source' and '-F src_fmt' tell '--merge' actions to find the source\n" - " bitmaps from an alternative file\n" - "\n" - "Parameters to check subcommand:\n" - " '-r' tries to repair any inconsistencies that are found during the check.\n" - " '-r leaks' repairs only cluster leaks, whereas '-r all' fixes all\n" - " kinds of errors, with a higher risk of choosing the wrong fix or\n" - " hiding corruption that has already occurred.\n" - "\n" - "Parameters to convert subcommand:\n" - " '--bitmaps' copies all top-level persistent bitmaps to destination\n" - " '-m' specifies how many coroutines work in parallel during the convert\n" - " process (defaults to 8)\n" - " '-W' allow to write to the target out of order rather than sequential\n" - "\n" - "Parameters to snapshot subcommand:\n" - " 'snapshot' is the name of the snapshot to create, apply or delete\n" - " '-a' applies a snapshot (revert disk to saved state)\n" - " '-c' creates a snapshot\n" - " '-d' deletes a snapshot\n" - " '-l' lists all snapshots in the given image\n" - "\n" - "Parameters to compare subcommand:\n" - " '-f' first image format\n" - " '-F' second image format\n" - " '-s' run in Strict mode - fail on different image size or sector allocation\n" - "\n" - "Parameters to dd subcommand:\n" - " 'bs=BYTES' read and write up to BYTES bytes at a time " - "(default: 512)\n" - " 'count=N' copy only N input blocks\n" - " 'if=FILE' read from FILE\n" - " 'of=FILE' write to FILE\n" - " 'skip=N' skip N bs-sized blocks at the start of input\n"; - - printf("%s\nSupported formats:", help_msg); - bdrv_iterate_format(format_print, NULL, false); - printf("\n\n" QEMU_HELP_BOTTOM "\n"); - exit(EXIT_SUCCESS); + if (!strcmp(arg, "json")) { + return OFORMAT_JSON; + } else if (!strcmp(arg, "human")) { + return OFORMAT_HUMAN; + } else { + error_exit(argv0, "--output expects 'human' or 'json' not '%s'", arg); + } } @@ -482,16 +400,14 @@ static int add_old_style_options(const char *fmt, QemuOpts *opts, } -static int64_t cvtnum_full(const char *name, const char *value, int64_t min, - int64_t max) +static int64_t cvtnum_full(const char *name, const char *value, + bool issize, int64_t min, int64_t max) { int err; uint64_t res; - err = qemu_strtosz(value, NULL, &res); + err = issize ? qemu_strtosz(value, NULL, &res) : + qemu_strtou64(value, NULL, 0, &res); if (err < 0 && err != -ERANGE) { - error_report("Invalid %s specified. You may use " - "k, M, G, T, P or E suffixes for", name); - error_report("kilobytes, megabytes, gigabytes, terabytes, " - "petabytes and exabytes."); + error_report("Invalid %s specified: '%s'.", name, value); return err; } @@ -504,13 +420,13 @@ static int64_t cvtnum_full(const char *name, const char *value, int64_t min, } -static int64_t cvtnum(const char *name, const char *value) +static int64_t cvtnum(const char *name, const char *value, bool issize) { - return cvtnum_full(name, value, 0, INT64_MAX); + return cvtnum_full(name, value, issize, 0, INT64_MAX); } -static int img_create(int argc, char **argv) +static int img_create(const img_cmd_t *ccmd, int argc, char **argv) { int c; - uint64_t img_size = -1; + int64_t img_size = -1; const char *fmt = "raw"; const char *base_fmt = NULL; @@ -525,8 +441,14 @@ static int img_create(int argc, char **argv) static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"quiet", no_argument, 0, 'q'}, {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, + {"backing", required_argument, 0, 'b'}, + {"backing-format", required_argument, 0, 'F'}, + {"backing-unsafe", no_argument, 0, 'u'}, + {"options", required_argument, 0, 'o'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":F:b:f:ho:qu", + c = getopt_long(argc, argv, "F:b:f:ho:qu", long_options, NULL); if (c == -1) { @@ -534,12 +456,31 @@ static int img_create(int argc, char **argv) } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"[-f FMT] [-o FMT_OPTS] [-b BACKING_FILENAME [-F BACKING_FMT]]\n" +" [--object OBJDEF] [-u] FILENAME [SIZE[bkKMGTPE]]\n" +, +" -q, --quiet\n" +" quiet operations\n" +" -f, --format FMT\n" +" specifies format of the new image, default is raw\n" +" -o, --options FMT_OPTS\n" +" format-specific options ('-o list' for list)\n" +" -b, --backing BACKING_FILENAME\n" +" stack new image on top of BACKING_FILENAME\n" +" (for formats which support stacking)\n" +" -F, --backing-format BACKING_FMT\n" +" specify format of BACKING_FILENAME\n" +" -u, --backing-unsafe\n" +" do not fail if BACKING_FMT can not be read\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" FILENAME\n" +" image file to create. It will be overridden if exists\n" +" SIZE\n" +" image size with optional suffix (multiplies in 1024)\n" +" SIZE is required unless BACKING_IMG is specified,\n" +" in which case it will be the same as size of BACKING_IMG\n" +); break; case 'F': @@ -566,4 +507,6 @@ static int img_create(int argc, char **argv) user_creatable_process_cmdline(optarg); break; + default: + tryhelp(argv[0]); } } @@ -577,5 +520,5 @@ static int img_create(int argc, char **argv) if (optind >= argc) { - error_exit("Expecting image file name"); + error_exit(argv[0], "Expecting image file name"); } optind++; @@ -583,14 +526,11 @@ static int img_create(int argc, char **argv) /* Get image size, if specified */ if (optind < argc) { - int64_t sval; - - sval = cvtnum("image size", argv[optind++]); - if (sval < 0) { + img_size = cvtnum("image size", argv[optind++], true); + if (img_size < 0) { goto fail; } - img_size = (uint64_t)sval; } if (optind != argc) { - error_exit("Unexpected argument: %s", argv[optind]); + error_exit(argv[0], "Unexpected argument: %s", argv[optind]); } @@ -717,9 +657,9 @@ static int collect_image_check(BlockDriverState *bs, * 63 - Checks are not supported by the image format */ -static int img_check(int argc, char **argv) +static int img_check(const img_cmd_t *ccmd, int argc, char **argv) { int c, ret; OutputFormat output_format = OFORMAT_HUMAN; - const char *filename, *fmt, *output, *cache; + const char *filename, *fmt, *cache; BlockBackend *blk; BlockDriverState *bs; @@ -733,5 +673,4 @@ static int img_check(int argc, char **argv) fmt = NULL; - output = NULL; cache = BDRV_DEFAULT_CACHE; @@ -740,5 +679,7 @@ static int img_check(int argc, char **argv) static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"quiet", no_argument, 0, 'q'}, {"format", required_argument, 0, 'f'}, + {"cache", required_argument, 0, 'T'}, {"repair", required_argument, 0, 'r'}, {"output", required_argument, 0, OPTION_OUTPUT}, @@ -748,5 +689,5 @@ static int img_check(int argc, char **argv) {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:r:T:qU", + c = getopt_long(argc, argv, "hf:r:T:qU", long_options, &option_index); if (c == -1) { @@ -754,12 +695,30 @@ static int img_check(int argc, char **argv) } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"[-f FMT | --image-opts] [-T CACHE_MODE] [-r] [-u]\n" +" [--output human|json] [--object OBJDEF] FILENAME\n" +, +" -q, --quiet\n" +" quiet operations\n" +" -f, --format FMT\n" +" specifies format of the image explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -T, --cache CACHE_MODE\n" +" image cache mode (" BDRV_DEFAULT_CACHE ")\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" --output human|json\n" +" output format\n" +" -r, --repair leaks|all\n" +" repair particular aspect of the image\n" +" (image will be open in read-write mode, incompatible with --force-share)\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" FILENAME\n" +" the image file (or image specification) to operate on\n" +); break; case 'f': @@ -774,10 +733,11 @@ static int img_check(int argc, char **argv) fix = BDRV_FIX_LEAKS | BDRV_FIX_ERRORS; } else { - error_exit("Unknown option value for -r " - "(expecting 'leaks' or 'all'): %s", optarg); + error_exit(argv[0], + "--repair (-r) expects 'leaks' or 'all' not '%s'", + optarg); } break; case OPTION_OUTPUT: - output = optarg; + output_format = parse_output_format(argv[0], optarg); break; case 'T': @@ -796,20 +756,13 @@ static int img_check(int argc, char **argv) image_opts = true; break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind++]; - if (output && !strcmp(output, "json")) { - output_format = OFORMAT_JSON; - } else if (output && !strcmp(output, "human")) { - output_format = OFORMAT_HUMAN; - } else if (output) { - error_report("--output must be used with human or json as argument."); - return 1; - } - ret = bdrv_parse_cache_mode(cache, &flags, &writethrough); if (ret < 0) { @@ -949,5 +902,5 @@ static void run_block_job(BlockJob *job, Error **errp) } -static int img_commit(int argc, char **argv) +static int img_commit(const img_cmd_t *ccmd, int argc, char **argv) { int c, ret, flags; @@ -969,9 +922,16 @@ static int img_commit(int argc, char **argv) static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"quiet", no_argument, 0, 'q'}, {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"cache", required_argument, 0, 't'}, + {"drop", no_argument, 0, 'd'}, + {"base", required_argument, 0, 'b'}, + {"progress", no_argument, 0, 'p'}, + {"rate", required_argument, 0, 'r'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:ht:b:dpqr:", + c = getopt_long(argc, argv, "f:ht:b:dpqr:", long_options, NULL); if (c == -1) { @@ -979,12 +939,31 @@ static int img_commit(int argc, char **argv) } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"[-f FMT | --image-opts] [-t CACHE_MODE] [-b BASE_IMG] [-d]\n" +" [-r RATE] [--object OBJDEF] FILENAME\n" +, +" -q, --quiet\n" +" quiet operations\n" +" -p, --progress\n" +" show operation progress\n" +" -f, --format FMT\n" +" specify FILENAME image format explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -t, --cache CACHE_MODE image cache mode (" BDRV_DEFAULT_CACHE ")\n" +" -d, --drop\n" +" skip emptying FILENAME on completion\n" +" -b, --base BASE_IMG\n" +" image in the backing chain to which to commit changes\n" +" instead of the previous one (implies --drop)\n" +" -r, --rate RATE\n" +" I/O rate limit\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" FILENAME\n" +" name of the image file to operate on\n" +); break; case 'f': @@ -1009,5 +988,5 @@ static int img_commit(int argc, char **argv) break; case 'r': - rate_limit = cvtnum("rate limit", optarg); + rate_limit = cvtnum("rate limit", optarg, true); if (rate_limit < 0) { return 1; @@ -1020,4 +999,6 @@ static int img_commit(int argc, char **argv) image_opts = true; break; + default: + tryhelp(argv[0]); } } @@ -1029,5 +1010,5 @@ static int img_commit(int argc, char **argv) if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind++]; @@ -1356,5 +1337,5 @@ static int check_empty_sectors(BlockBackend *blk, int64_t offset, * >1 - Error occurred */ -static int img_compare(int argc, char **argv) +static int img_compare(const img_cmd_t *ccmd, int argc, char **argv) { const char *fmt1 = NULL, *fmt2 = NULL, *cache, *filename1, *filename2; @@ -1381,10 +1362,18 @@ static int img_compare(int argc, char **argv) static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"quiet", no_argument, 0, 'q'}, {"object", required_argument, 0, OPTION_OBJECT}, + {"cache", required_argument, 0, 'T'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"a-format", required_argument, 0, 'f'}, + {"left-format", required_argument, 0, 'f'}, + {"b-format", required_argument, 0, 'F'}, + {"right-format", required_argument, 0, 'F'}, {"force-share", no_argument, 0, 'U'}, + {"strict", no_argument, 0, 's'}, + {"progress", no_argument, 0, 'p'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:F:T:pqsU", + c = getopt_long(argc, argv, "hf:F:T:pqsU", long_options, NULL); if (c == -1) { @@ -1392,12 +1381,31 @@ static int img_compare(int argc, char **argv) } switch (c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"[--image-opts | [-f FMT] [-F FMT]] [-s]\n" +" [-T CACHE] [-U] [--object OBJDEF] FILENAME1 FILENAME2\n" +, +" -q, --quiet\n" +" quiet operation\n" +" -p, --progress\n" +" show operation progress\n" +" -f, --a-format FMT\n" +" specify FILENAME1 image format explicitly\n" +" -F, --b-format FMT\n" +" specify FILENAME2 image format explicitly\n" +" --image-opts\n" +" indicates that FILENAMEs are complete image specifications\n" +" instead of file names (incompatible with --a-format and --b-format)\n" +" -s, --strict\n" +" strict mode, also check if sizes are equal\n" +" -T, --cache CACHE_MODE\n" +" images caching mode (" BDRV_DEFAULT_CACHE ")\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" FILENAME1, FILENAME2\n" +" image files (or specifications) to compare\n" +); break; case 'f': @@ -1440,4 +1448,6 @@ static int img_compare(int argc, char **argv) image_opts = true; break; + default: + tryhelp(argv[0]); } } @@ -1450,5 +1460,5 @@ static int img_compare(int argc, char **argv) if (optind != argc - 2) { - error_exit("Expecting two image file names"); + error_exit(argv[0], "Expecting two image file names"); } filename1 = argv[optind++]; @@ -2232,5 +2242,5 @@ static void set_rate_limit(BlockBackend *blk, int64_t rate_limit) } -static int img_convert(int argc, char **argv) +static int img_convert(const img_cmd_t *ccmd, int argc, char **argv) { int c, bs_i, flags, src_flags = BDRV_O_NO_SHARE; @@ -2268,6 +2278,17 @@ static int img_convert(int argc, char **argv) static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"quiet", no_argument, 0, 'q'}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"source-image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"source-format", required_argument, 0, 'f'}, + {"source-cache", required_argument, 0, 'T'}, + {"snapshot", required_argument, 0, 'l'}, + {"sparse-size", required_argument, 0, 'S'}, + {"output-format", required_argument, 0, 'O'}, + {"options", required_argument, 0, 'o'}, + {"output-cache", required_argument, 0, 't'}, + {"backing", required_argument, 0, 'B'}, + {"backing-format", required_argument, 0, 'F'}, {"force-share", no_argument, 0, 'U'}, {"target-image-opts", no_argument, 0, OPTION_TARGET_IMAGE_OPTS}, @@ -2276,20 +2297,79 @@ static int img_convert(int argc, char **argv) {"bitmaps", no_argument, 0, OPTION_BITMAPS}, {"skip-broken-bitmaps", no_argument, 0, OPTION_SKIP_BROKEN}, + {"rate", required_argument, 0, 'r'}, + {"parallel", required_argument, 0, 'm'}, + {"oob-writes", no_argument, 0, 'W'}, + {"copy-range-offloading", no_argument, 0, 'C'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:O:B:CcF:o:l:S:pt:T:qnm:WUr:", + c = getopt_long(argc, argv, "hf:O:B:CcF:o:l:S:pt:T:qnm:WUr:", long_options, NULL); if (c == -1) { break; } - switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; + switch (c) { case 'h': - help(); + cmd_help(ccmd, +"[-f SRC_FMT|--image-opts] [-T SRC_CACHE] [--bitmaps [--skip-broken-bitmaps]]\n" +" [-o TGT_OPTS|--target-image-opts] [-t TGT_CACHE] [-n]\n" +" [-B BACKING_FILENAME [-F BACKING_FMT]]\n" +" SRC_FILENAME [SRC_FILENAME2 [...]] TGT_FILENAME\n" +, +" -q, --quiet\n" +" quiet operations\n" +" -p, --progress\n" +" show operation progress\n" +" -f, --source-format SRC_FMT\n" +" specify SRC_FILENAME source image format explicitly\n" +" --source-image-opts\n" +" indicates that SRC_FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --source-format)\n" +" -l, --source-snapshot SNAPSHOT_PARAMS\n" +" specify source snapshot parameters\n" +" -T, --source-cache SRC_CACHE\n" +" source image(s) cache mode (" BDRV_DEFAULT_CACHE ")\n" +" -O, --target-format TGT_FMT\n" +" specify TGT_FILENAME image format (default is raw)\n" +" --target-image-opts\n" +" indicates that TGT_FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --output-format)\n" +" -o, --target-options TGT_OPTS\n" +" TARGET_FMT-specific options\n" +" -c, --compress\n" +" create compressed output image (qcow and qcow2 format only)\n" +" -t, --target-cache TGT_CACHE\n" +" cache mode when opening output image (unsafe)\n" +" -B, --backing BACKING_FILENAME\n" +" create output to be a CoW on top of BACKING_FILENAME\n" +" -F, --backing-format BACKING_FMT\n" +" specify BACKING_FILENAME image format explicitly\n" +" -n, --no-create\n" +" omit target volume creation (eg on rbd)\n" +" --target-is-zero\n" +" -S, --sparse-size SPARSE_SIZE\n" +" XXX todo\n" +" --bitmaps\n" +" also copy any persistent bitmaps present in source\n" +" --skip-broken-bitmaps\n" +" skip (do not error out) any broken bitmaps\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +" -r, --rate RATE\n" +" I/O rate limit\n" +" -m, --parallel NUM_COROUTINES\n" +" specify parallelism (default 8)\n" +" -C, --copy-range-offloading\n" +" use copy_range offloading\n" +" --salvage\n" +" XXX todo\n" +" -W, --oob-writes\n" +" enable out-of-order writes to improve performance\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" SRC_FILENAME\n" +" source image file name (or specification with --image-opts)\n" +" TGT_FILENAME\n" +" target (output) image file name\n" +); break; case 'f': @@ -2333,5 +2413,5 @@ static int img_convert(int argc, char **argv) int64_t sval; - sval = cvtnum("buffer size for sparse output", optarg); + sval = cvtnum("buffer size for sparse output", optarg, true); if (sval < 0) { goto fail_getopt; @@ -2365,8 +2445,7 @@ static int img_convert(int argc, char **argv) break; case 'm': - if (qemu_strtol(optarg, NULL, 0, &s.num_coroutines) || - s.num_coroutines < 1 || s.num_coroutines > MAX_COROUTINES) { - error_report("Invalid number of coroutines. Allowed number of" - " coroutines is between 1 and %d", MAX_COROUTINES); + s.num_coroutines = cvtnum_full("number of coroutines", optarg, + false, 1, MAX_COROUTINES); + if (s.num_coroutines < 0) { goto fail_getopt; } @@ -2379,5 +2458,5 @@ static int img_convert(int argc, char **argv) break; case 'r': - rate_limit = cvtnum("rate limit", optarg); + rate_limit = cvtnum("rate limit", optarg, true); if (rate_limit < 0) { goto fail_getopt; @@ -2410,4 +2489,6 @@ static int img_convert(int argc, char **argv) skip_broken = true; break; + default: + tryhelp(argv[0]); } } @@ -3000,10 +3081,10 @@ err: } -static int img_info(int argc, char **argv) +static int img_info(const img_cmd_t *ccmd, int argc, char **argv) { int c; OutputFormat output_format = OFORMAT_HUMAN; bool chain = false; - const char *filename, *fmt, *output; + const char *filename, *fmt; BlockGraphInfoList *list; bool image_opts = false; @@ -3011,12 +3092,10 @@ static int img_info(int argc, char **argv) fmt = NULL; - output = NULL; for(;;) { - int option_index = 0; static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"format", required_argument, 0, 'f'}, {"output", required_argument, 0, OPTION_OUTPUT}, - {"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN}, + {"backing-chain", no_argument, 0, 'b'}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, @@ -3024,18 +3103,32 @@ static int img_info(int argc, char **argv) {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:hU", - long_options, &option_index); + c = getopt_long(argc, argv, "f:hbU", + long_options, NULL); if (c == -1) { break; } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"[-f FMT | --image-opts] [-b] [-U] [--object OBJDEF]\n" +" [--output human|json] FILENAME\n" +, +" -f, --format FMT\n" +" specify FILENAME image format explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -b, --backing-chain\n" +" display information about backing chaing\n" +" (in case the image is stacked\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" --output human|json\n" +" specify output format name (default human)\n" +" FILENAME\n" +" image file name (or specification with --image-opts)\n" +); break; case 'f': @@ -3046,7 +3139,7 @@ static int img_info(int argc, char **argv) break; case OPTION_OUTPUT: - output = optarg; + output_format = parse_output_format(argv[0], optarg); break; - case OPTION_BACKING_CHAIN: + case 'b': chain = true; break; @@ -3057,20 +3150,13 @@ static int img_info(int argc, char **argv) image_opts = true; break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind++]; - if (output && !strcmp(output, "json")) { - output_format = OFORMAT_JSON; - } else if (output && !strcmp(output, "human")) { - output_format = OFORMAT_HUMAN; - } else if (output) { - error_report("--output must be used with human or json as argument."); - return 1; - } - list = collect_image_info_list(image_opts, filename, fmt, chain, force_share); @@ -3225,5 +3311,5 @@ static inline bool entry_mergeable(const MapEntry *curr, const MapEntry *next) } -static int img_map(int argc, char **argv) +static int img_map(const img_cmd_t *ccmd, int argc, char **argv) { int c; @@ -3231,5 +3317,5 @@ static int img_map(int argc, char **argv) BlockBackend *blk; BlockDriverState *bs; - const char *filename, *fmt, *output; + const char *filename, *fmt; int64_t length; MapEntry curr = { .length = 0 }, next; @@ -3241,7 +3327,5 @@ static int img_map(int argc, char **argv) fmt = NULL; - output = NULL; for (;;) { - int option_index = 0; static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, @@ -3255,18 +3339,31 @@ static int img_map(int argc, char **argv) {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:s:l:hU", - long_options, &option_index); + c = getopt_long(argc, argv, "f:s:l:hU", + long_options, NULL); if (c == -1) { break; } switch (c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"[-f FMT | --image-opts] [--object OBJDEF] [--output human|json]\n" +" [--start-offset OFFSET] [--max-length LENGTH] [-U] FILENAME\n" +, +" -f, --format FMT\n" +" specify FILENAME image format explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" --start-offset OFFSET\n" +" --max-length LENGTH\n" +" --output human|json\n" +" specify output format name (default human)\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" FILENAME\n" +" image file name (or specification with --image-opts)\n" +); break; case 'f': @@ -3277,8 +3374,8 @@ static int img_map(int argc, char **argv) break; case OPTION_OUTPUT: - output = optarg; + output_format = parse_output_format(argv[0], optarg); break; case 's': - start_offset = cvtnum("start offset", optarg); + start_offset = cvtnum("start offset", optarg, true); if (start_offset < 0) { return 1; @@ -3286,5 +3383,5 @@ static int img_map(int argc, char **argv) break; case 'l': - max_length = cvtnum("max length", optarg); + max_length = cvtnum("max length", optarg, true); if (max_length < 0) { return 1; @@ -3297,20 +3394,13 @@ static int img_map(int argc, char **argv) image_opts = true; break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind]; - if (output && !strcmp(output, "json")) { - output_format = OFORMAT_JSON; - } else if (output && !strcmp(output, "human")) { - output_format = OFORMAT_HUMAN; - } else if (output) { - error_report("--output must be used with human or json as argument."); - return 1; - } - blk = img_open(image_opts, filename, fmt, 0, false, false, force_share); if (!blk) { @@ -3369,16 +3459,17 @@ out: } -#define SNAPSHOT_LIST 1 -#define SNAPSHOT_CREATE 2 -#define SNAPSHOT_APPLY 3 -#define SNAPSHOT_DELETE 4 +/* the same as options */ +#define SNAPSHOT_LIST 'l' +#define SNAPSHOT_CREATE 'c' +#define SNAPSHOT_APPLY 'a' +#define SNAPSHOT_DELETE 'd' -static int img_snapshot(int argc, char **argv) +static int img_snapshot(const img_cmd_t *ccmd, int argc, char **argv) { BlockBackend *blk; BlockDriverState *bs; QEMUSnapshotInfo sn; - char *filename, *snapshot_name = NULL; - int c, ret = 0, bdrv_oflags; + char *filename, *fmt = NULL, *snapshot_name = NULL; + int c, ret = 0; int action = 0; bool quiet = false; @@ -3388,15 +3479,20 @@ static int img_snapshot(int argc, char **argv) int64_t rt; - bdrv_oflags = BDRV_O_RDWR; /* Parse commandline parameters */ for(;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"quiet", no_argument, 0, 'q'}, {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"force-share", no_argument, 0, 'U'}, + {"list", no_argument, 0, SNAPSHOT_LIST}, + {"apply", no_argument, 0, SNAPSHOT_APPLY}, + {"create", no_argument, 0, SNAPSHOT_CREATE}, + {"delete", no_argument, 0, SNAPSHOT_DELETE}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":la:c:d:hqU", + c = getopt_long(argc, argv, "la:c:d:f:hqU", long_options, NULL); if (c == -1) { @@ -3404,43 +3500,44 @@ static int img_snapshot(int argc, char **argv) } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); - return 0; - case 'l': - if (action) { - error_exit("Cannot mix '-l', '-a', '-c', '-d'"); - return 0; - } - action = SNAPSHOT_LIST; - bdrv_oflags &= ~BDRV_O_RDWR; /* no need for RW */ + cmd_help(ccmd, +"[-f FMT | --image-opts] [-l | -a|-c|-d SNAPSHOT]\n" +" [-U] [--object OBJDEF] FILENAME\n" +, +" -q, --quiet\n" +" quiet operations\n" +" -f, --format FMT\n" +" specify FILENAME format explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" Operation, one of:\n" +" -l, --list\n" +" list snapshots in FILENAME (the default)\n" +" -c, --create SNAPSHOT\n" +" create named snapshot\n" +" -a, --apply SNAPSHOT\n" +" apply named snapshot to the base\n" +" -d, --delete SNAPSHOT\n" +" delete named snapshot\n" +" FILENAME - image file name (or specification with --image-opts)\n" +); break; - case 'a': - if (action) { - error_exit("Cannot mix '-l', '-a', '-c', '-d'"); - return 0; - } - action = SNAPSHOT_APPLY; - snapshot_name = optarg; + case 'f': + fmt = optarg; break; - case 'c': + case SNAPSHOT_LIST: + case SNAPSHOT_APPLY: + case SNAPSHOT_CREATE: + case SNAPSHOT_DELETE: if (action) { - error_exit("Cannot mix '-l', '-a', '-c', '-d'"); + error_exit(argv[0], "Cannot mix '-l', '-a', '-c', '-d'"); return 0; } - action = SNAPSHOT_CREATE; - snapshot_name = optarg; - break; - case 'd': - if (action) { - error_exit("Cannot mix '-l', '-a', '-c', '-d'"); - return 0; - } - action = SNAPSHOT_DELETE; + action = c; snapshot_name = optarg; break; @@ -3457,15 +3554,22 @@ static int img_snapshot(int argc, char **argv) image_opts = true; break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[optind++]; + if (!action) { + action = SNAPSHOT_LIST; + } + /* Open the image */ - blk = img_open(image_opts, filename, NULL, bdrv_oflags, false, quiet, - force_share); + blk = img_open(image_opts, filename, fmt, + action == SNAPSHOT_LIST ? 0 : BDRV_O_RDWR, + false, quiet, force_share); if (!blk) { return 1; @@ -3532,5 +3636,5 @@ static int img_snapshot(int argc, char **argv) } -static int img_rebase(int argc, char **argv) +static int img_rebase(const img_cmd_t *ccmd, int argc, char **argv) { BlockBackend *blk = NULL, *blk_old_backing = NULL, *blk_new_backing = NULL; @@ -3563,24 +3667,59 @@ static int img_rebase(int argc, char **argv) static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"quiet", no_argument, 0, 'q'}, + {"progress", no_argument, 0, 'p'}, {"object", required_argument, 0, OPTION_OBJECT}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"force-share", no_argument, 0, 'U'}, + {"format", required_argument, 0, 'f'}, + {"cache", required_argument, 0, 't'}, {"compress", no_argument, 0, 'c'}, + {"backing", required_argument, 0, 'b'}, + {"backing-format", required_argument, 0, 'F'}, + {"backing-cache", required_argument, 0, 'T'}, + {"backing-unsafe", no_argument, 0, 'u'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hf:F:b:upt:T:qUc", + c = getopt_long(argc, argv, "hf:F:b:upt:T:qUc", long_options, NULL); if (c == -1) { break; } - switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; + switch (c) { case 'h': - help(); + cmd_help(ccmd, +"[-f FMT | --image-opts] [-t CACHE] [-q] [-U] [-p]\n" +" [-b BACKING_FILENAME [-F BACKING_FMT] [-T BACKING_CACHE]] [-u]\n" +" [--object OBJDEF] [-c] FILENAME\n" +"Rebases FILENAME on top of BACKING_FILENAME or no backing file\n" +, +" -q, --quiet\n" +" quiet operation\n" +" -p, --progress\n" +" show progress indicator\n" +" -f, --format FMT\n" +" specify FILENAME format explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -t, --cache CACHE\n" +" cache mode for FILENAME (" BDRV_DEFAULT_CACHE ")\n" +" -b, --backing BACKING_FILENAME|\"\"\n" +" rebase onto this file (or no backing file)\n" +" -F, --backing-format BACKING_FMT\n" +" specify format for BACKING_FILENAME\n" +" -T, --backing-cache CACHE\n" +" BACKING_FILENAME cache mode (" BDRV_DEFAULT_CACHE ")\n" +" -u, --backing-unsafe\n" +" do not fail if BACKING_FILENAME can not be read\n" +" -c, --compress\n" +" compress image (when image supports this)\n" +" -U, --force-share\n" +" open image in shared mode for concurrent access\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" FILENAME\n" +" image file name (or specification with --image-opts)\n" +); return 0; case 'f': @@ -3620,4 +3759,6 @@ static int img_rebase(int argc, char **argv) compress = true; break; + default: + tryhelp(argv[0]); } } @@ -3628,8 +3769,9 @@ static int img_rebase(int argc, char **argv) if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } if (!unsafe && !out_baseimg) { - error_exit("Must specify backing file (-b) or use unsafe mode (-u)"); + error_exit(argv[0], + "Must specify backing file (-b) or use unsafe mode (-u)"); } filename = argv[optind++]; @@ -4025,9 +4167,9 @@ out: } -static int img_resize(int argc, char **argv) +static int img_resize(const img_cmd_t *ccmd, int argc, char **argv) { Error *err = NULL; int c, ret, relative; - const char *filename, *fmt, *size; + const char *filename = NULL, *fmt = NULL, *size = NULL; int64_t n, total_size, current_size; bool quiet = false; @@ -4052,19 +4194,11 @@ static int img_resize(int argc, char **argv) bool shrink = false; - /* Remove size from argv manually so that negative numbers are not treated - * as options by getopt. */ - if (argc < 3) { - error_exit("Not enough arguments"); - return 1; - } - - size = argv[--argc]; - /* Parse getopt arguments */ - fmt = NULL; for(;;) { static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"quiet", no_argument, 0, 'q'}, {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"preallocation", required_argument, 0, OPTION_PREALLOCATION}, @@ -4072,5 +4206,5 @@ static int img_resize(int argc, char **argv) {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":f:hq", + c = getopt_long(argc, argv, "-f:hq", long_options, NULL); if (c == -1) { @@ -4078,13 +4212,29 @@ static int img_resize(int argc, char **argv) } switch(c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); - break; + cmd_help(ccmd, +"[-f FMT | --image-opts] [--preallocation PREALLOC] [--shrink]\n" +" [--object OBJECTDEF] [-q] FILENAME [+-]SIZE[bkKMGTPE]\n" +, +" -q, --quiet\n" +" quiet operation\n" +" -f, --format FMT\n" +" specify FILENAME format explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" --shrink\n" +" allow operation when new size is smaller than original\n" +" --preallocation PREALLOC\n" +" specify preallocation type for the new areas\n" +" --object OBJDEF\n" +" QEMU user-creatable object (eg encryption key)\n" +" FILENAME\n" +" image file (specification) to resize\n" +" [+-]SIZE[bkKMGTPE]\n" +" new image size or amount by which to shrink/grow,\n" +" with optional suffix (1024-based multiplies)\n" +); + return 0; case 'f': fmt = optarg; @@ -4110,10 +4260,35 @@ static int img_resize(int argc, char **argv) shrink = true; break; + case 1: /* a non-optional argument */ + if (!filename) { + filename = optarg; + /* see if we have -size (number) next to filename */ + if (optind < argc) { + size = argv[optind]; + if (size[0] == '-' && size[1] >= '0' && size[1] <= '9') { + ++optind; + } else { + size = NULL; + } + } + } else if (!size) { + size = optarg; + } else { + error_exit(argv[0], "Extra argument(s) in command line"); + } + break; + default: + tryhelp(argv[0]); } } - if (optind != argc - 1) { - error_exit("Expecting image file name and size"); + if (!filename && optind < argc) { + filename = argv[optind++]; + } + if (!size && optind < argc) { + size = argv[optind++]; + } + if (!filename || !size || optind < argc) { + error_exit(argv[0], "Expecting image file name and size"); } - filename = argv[optind++]; /* Choose grow, shrink, or absolute resize mode */ @@ -4238,5 +4413,5 @@ static int print_amend_option_help(const char *format) } -static int img_amend(int argc, char **argv) +static int img_amend(const img_cmd_t *ccmd, int argc, char **argv) { Error *err = NULL; @@ -4258,10 +4433,15 @@ static int img_amend(int argc, char **argv) static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, + {"quiet", no_argument, 0, 'q'}, + {"progress", no_argument, 0, 'p'}, {"object", required_argument, 0, OPTION_OBJECT}, + {"format", required_argument, 0, 'f'}, + {"cache", required_argument, 0, 't'}, + {"options", required_argument, 0, 'o'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"force", no_argument, 0, OPTION_FORCE}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":ho:f:t:pq", + c = getopt_long(argc, argv, "ho:f:t:pq", long_options, NULL); if (c == -1) { @@ -4270,12 +4450,23 @@ static int img_amend(int argc, char **argv) switch (c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"[-f FMT | --image-opts] [t CACHE] [--force] [-p] [-q]\n" +" [--object OBJDEF -o OPTIONS FILENAME\n" +, +" -q, --quiet\n" +" quiet operation\n" +" -p, --progres\n" +" show progress\n" +" -f, --format FMT\n" +" specify FILENAME format explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -t, --cache CACHE\n" +" cache mode for FILENAME (" BDRV_DEFAULT_CACHE ")\n" +" --force\n" +" allow certain unsafe operations\n" +); break; case 'o': @@ -4306,9 +4497,11 @@ static int img_amend(int argc, char **argv) force = true; break; + default: + tryhelp(argv[0]); } } if (!options) { - error_exit("Must specify options (-o)"); + error_exit(argv[0], "Must specify options (-o)"); } @@ -4502,5 +4695,5 @@ static void bench_cb(void *opaque, int ret) } -static int img_bench(int argc, char **argv) +static int img_bench(const img_cmd_t *ccmd, int argc, char **argv) { int c, ret = 0; @@ -4512,7 +4705,7 @@ static int img_bench(int argc, char **argv) int depth = 64; int64_t offset = 0; - size_t bufsize = 4096; + ssize_t bufsize = 4096; int pattern = 0; - size_t step = 0; + ssize_t step = 0; int flush_interval = 0; bool drain_on_flush = true; @@ -4530,13 +4723,23 @@ static int img_bench(int argc, char **argv) static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, - {"flush-interval", required_argument, 0, OPTION_FLUSH_INTERVAL}, + {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"cache", required_argument, 0, 't'}, + {"count", required_argument, 0, 'c'}, + {"depth", required_argument, 0, 'd'}, + {"offset", required_argument, 0, 'o'}, + {"buffer-size", required_argument, 0, 's'}, + {"step-size", required_argument, 0, 'S'}, + {"aio", required_argument, 0, 'i'}, + {"native", no_argument, 0, 'n'}, + {"write", no_argument, 0, 'w'}, {"pattern", required_argument, 0, OPTION_PATTERN}, + {"flush-interval", required_argument, 0, OPTION_FLUSH_INTERVAL}, {"no-drain", no_argument, 0, OPTION_NO_DRAIN}, {"force-share", no_argument, 0, 'U'}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":hc:d:f:ni:o:qs:S:t:wU", long_options, - NULL); + c = getopt_long(argc, argv, "hc:d:f:ni:o:qs:S:t:wU", + long_options, NULL); if (c == -1) { break; @@ -4544,35 +4747,57 @@ static int img_bench(int argc, char **argv) switch (c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"[-f FMT | --image-opts] [-t CACHE] [-c COUNT] [-d DEPTH]\n" +" [-o OFFSET] [-s BUFFER_SIZE] [-S STEP_SIZE] [-i AIO] [-n]\n" +" [-w [--pattern PATTERN] [--flush-interval INTERVAL [--no-drain]]]\n" +, +" -q, --quiet\n" +" quiet operations\n" +" -f, --format FMT\n" +" specify FILENAME format explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -t, --cache CACHE\n" +" cache mode for FILENAME (" BDRV_DEFAULT_CACHE ")\n" +" -c, --count COUNT\n" +" number of I/O requests to perform\n" +" -s, --buffer-size BUFFER_SIZE\n" +" size of each I/O request\n" +" -d, --depth DEPTH\n" +" number of requests to perform in parallel\n" +" -o, --offset OFFSET\n" +" start first request at this OFFSET\n" +" -S, --step-size STEP_SIZE\n" +" each next request offset increment\n" +" -i, --aio AIO\n" +" async-io backend (threads, native, io_uring)\n" +" -n, --native\n" +" use native AIO backend if possible\n" +" -w, --write\n" +" perform write test (default is read)\n" +" --pattern PATTERN\n" +" write this pattern byte instead of zero\n" +" --flush-interval FLUSH_INTERVAL\n" +" issue flush after this number of requests\n" +" --no-drain\n" +" do not wait when flushing pending requests\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +); break; case 'c': - { - unsigned long res; - - if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > INT_MAX) { - error_report("Invalid request count specified"); + count = cvtnum_full("request count", optarg, false, 1, INT_MAX); + if (count < 0) { return 1; } - count = res; break; - } case 'd': - { - unsigned long res; - - if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > INT_MAX) { - error_report("Invalid queue depth specified"); + depth = cvtnum_full("queue depth", optarg, false, 1, INT_MAX); + if (depth < 0) { return 1; } - depth = res; break; - } case 'f': fmt = optarg; @@ -4590,39 +4815,24 @@ static int img_bench(int argc, char **argv) break; case 'o': - { - offset = cvtnum("offset", optarg); + offset = cvtnum("offset", optarg, true); if (offset < 0) { return 1; } break; - } - break; case 'q': quiet = true; break; case 's': - { - int64_t sval; - - sval = cvtnum_full("buffer size", optarg, 0, INT_MAX); - if (sval < 0) { + bufsize = cvtnum_full("buffer size", optarg, true, 1, INT_MAX); + if (bufsize < 0) { return 1; } - - bufsize = sval; break; - } case 'S': - { - int64_t sval; - - sval = cvtnum_full("step_size", optarg, 0, INT_MAX); - if (sval < 0) { + step = cvtnum_full("step size", optarg, true, 0, INT_MAX); + if (step < 0) { return 1; } - - step = sval; break; - } case 't': ret = bdrv_parse_cache_mode(optarg, &flags, &writethrough); @@ -4641,25 +4851,16 @@ static int img_bench(int argc, char **argv) break; case OPTION_PATTERN: - { - unsigned long res; - - if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > 0xff) { - error_report("Invalid pattern byte specified"); + pattern = cvtnum_full("pattern byte", optarg, false, 0, 0xff); + if (pattern < 0) { return 1; } - pattern = res; break; - } case OPTION_FLUSH_INTERVAL: - { - unsigned long res; - - if (qemu_strtoul(optarg, NULL, 0, &res) < 0 || res > INT_MAX) { - error_report("Invalid flush interval specified"); + flush_interval = cvtnum_full("flush interval", optarg, + false, 0, INT_MAX); + if (flush_interval < 0) { return 1; } - flush_interval = res; break; - } case OPTION_NO_DRAIN: drain_on_flush = false; @@ -4668,9 +4869,11 @@ static int img_bench(int argc, char **argv) image_opts = true; break; + default: + tryhelp(argv[0]); } } if (optind != argc - 1) { - error_exit("Expecting one image file name"); + error_exit(argv[0], "Expecting one image file name"); } filename = argv[argc - 1]; @@ -4772,5 +4975,5 @@ typedef struct ImgBitmapAction { } ImgBitmapAction; -static int img_bitmap(int argc, char **argv) +static int img_bitmap(const img_cmd_t *ccmd, int argc, char **argv) { Error *err = NULL; @@ -4807,5 +5010,6 @@ static int img_bitmap(int argc, char **argv) {0, 0, 0, 0} }; - c = getopt_long(argc, argv, ":b:f:F:g:h", long_options, NULL); + c = getopt_long(argc, argv, "b:f:F:g:h", + long_options, NULL); if (c == -1) { break; @@ -4813,12 +5017,33 @@ static int img_bitmap(int argc, char **argv) switch (c) { - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; case 'h': - help(); + cmd_help(ccmd, +"( --merge SOURCE | --add | --remove | --clear |\n" +" --enable | --disable ).. [-f FMT | --image-opts]\n" +" [ -b SRC_FILENAME [-F SOURCE_FMT]] [-g SIZE[KMGTPE]] [--object OBJDEF]\n" +" FILENAME BITMAP\n" +, +" -f, --format FMT\n" +" specify FILENAME format explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" --add\n" +" creates BITMAP, enables to record future edits\n" +" -g, --granularity SIZE[KMGTPE]\n" +" sets non-default bitmap granularity for --add to this size\n" +" --remove\n" +" removes BITMAP\n" +" --clear\n" +" clears BITMAP\n" +" --enable, --disable\n" +" starts and stops recording future edits to BITMAP\n" +" --merge SRC_FILENAME\n" +" merges contents of SRC_FILENAME bitmap into BITMAP\n" +" -b, --source-file SRC_FILENAME\n" +" select alternative source file for --merge\n" +" -F, --source-format SRC_FMT\n" +" specify format for SRC_FILENAME explicitly\n" +); break; case 'b': @@ -4832,5 +5057,5 @@ static int img_bitmap(int argc, char **argv) break; case 'g': - granularity = cvtnum("granularity", optarg); + granularity = cvtnum("granularity", optarg, true); if (granularity < 0) { return 1; @@ -4876,4 +5101,6 @@ static int img_bitmap(int argc, char **argv) image_opts = true; break; + default: + tryhelp(argv[0]); } } @@ -5018,5 +5245,5 @@ static int img_dd_bs(const char *arg, int64_t res; - res = cvtnum_full("bs", arg, 1, INT_MAX); + res = cvtnum_full("bs", arg, true, 1, INT_MAX); if (res < 0) { @@ -5032,5 +5259,5 @@ static int img_dd_count(const char *arg, struct DdInfo *dd) { - dd->count = cvtnum("count", arg); + dd->count = cvtnum("count", arg, false); if (dd->count < 0) { @@ -5063,5 +5290,5 @@ static int img_dd_skip(const char *arg, struct DdInfo *dd) { - in->offset = cvtnum("skip", arg); + in->offset = cvtnum("skip", arg, false); if (in->offset < 0) { @@ -5072,5 +5299,5 @@ static int img_dd_skip(const char *arg, } -static int img_dd(int argc, char **argv) +static int img_dd(const img_cmd_t *ccmd, int argc, char **argv) { int ret = 0; @@ -5117,4 +5344,6 @@ static int img_dd(int argc, char **argv) { "help", no_argument, 0, 'h'}, { "object", required_argument, 0, OPTION_OBJECT}, + { "format", required_argument, 0, 'f'}, + { "output-format", required_argument, 0, 'O'}, { "image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, { "force-share", no_argument, 0, 'U'}, @@ -5122,9 +5351,33 @@ static int img_dd(int argc, char **argv) }; - while ((c = getopt_long(argc, argv, ":hf:O:U", long_options, NULL))) { + while ((c = getopt_long(argc, argv, "hf:O:U", long_options, NULL))) { if (c == EOF) { break; } switch (c) { + case 'h': + cmd_help(ccmd, +"[-f FMT|--image-opts] [-O OUTPUT_FMT] [-U]\n" +" [bs=BLOCK_SIZE] [count=BLOCKS] if=INPUT of=OUTPUT\n" +, +" -f, --format FMT\n" +" specify format for INPUT explicitly\n" +" --image-opts\n" +" indicates that INPUT is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -O, --output-format OUTPUT_FMT\n" +" format of the OUTPUT (default raw)\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +" bs=BLOCK_SIZE[kKMGTP]\n" +" size of I/O block (default 512)\n" +" count=COUNT\n" +" number of blocks to convert (default whole INPUT)\n" +" if=INPUT\n" +" input file name (or image specification with --image-opts)\n" +" of=OUTPUT\n" +" output file name to create\n" +); + break; case 'O': out_fmt = optarg; @@ -5133,13 +5386,4 @@ static int img_dd(int argc, char **argv) fmt = optarg; break; - case ':': - missing_argument(argv[optind - 1]); - break; - case '?': - unrecognized_option(argv[optind - 1]); - break; - case 'h': - help(); - break; case 'U': force_share = true; @@ -5151,4 +5395,6 @@ static int img_dd(int argc, char **argv) image_opts = true; break; + default: + tryhelp(argv[0]); } } @@ -5340,15 +5586,6 @@ static void dump_json_block_measure_info(BlockMeasureInfo *info) } -static int img_measure(int argc, char **argv) +static int img_measure(const img_cmd_t *ccmd, int argc, char **argv) { - static const struct option long_options[] = { - {"help", no_argument, 0, 'h'}, - {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, - {"object", required_argument, 0, OPTION_OBJECT}, - {"output", required_argument, 0, OPTION_OUTPUT}, - {"size", required_argument, 0, OPTION_SIZE}, - {"force-share", no_argument, 0, 'U'}, - {0, 0, 0, 0} - }; OutputFormat output_format = OFORMAT_HUMAN; BlockBackend *in_blk = NULL; @@ -5365,5 +5602,5 @@ static int img_measure(int argc, char **argv) QemuOptsList *create_opts = NULL; bool image_opts = false; - uint64_t img_size = UINT64_MAX; + int64_t img_size = -1; BlockMeasureInfo *info = NULL; Error *local_err = NULL; @@ -5371,10 +5608,45 @@ static int img_measure(int argc, char **argv) int c; + static const struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"target-format", required_argument, 0, 'O'}, + {"format", required_argument, 0, 'f'}, + {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, + {"options", required_argument, 0, 'o'}, + {"snapshot", required_argument, 0, 'l'}, + {"object", required_argument, 0, OPTION_OBJECT}, + {"output", required_argument, 0, OPTION_OUTPUT}, + {"size", required_argument, 0, 's'}, + {"force-share", no_argument, 0, 'U'}, + {0, 0, 0, 0} + }; + while ((c = getopt_long(argc, argv, "hf:O:o:l:U", long_options, NULL)) != -1) { switch (c) { - case '?': case 'h': - help(); + cmd_help(ccmd, +"[-f FMT|--image-opts] [-o OPTIONS] [-O OUTPUT_FMT]\n" +" [--output OFMT] [--object OBJDEF] [-l SNAPSHOT_PARAM]\n" +" (--size SIZE | FILENAME)\n" +, +" -O, --target-format FMT\n" +" desired target/output image format (default raw)\n" +" -s, --size SIZE\n" +" measure file size for given image size\n" +" FILENAME\n" +" measure file size required to convert from FILENAME\n" +" -f, --format\n" +" specify format of FILENAME explicitly\n" +" --image-opts\n" +" indicates that FILENAME is a complete image specification\n" +" instead of a file name (incompatible with --format)\n" +" -l, --snapshot SNAPSHOT\n" +" use this snapshot in FILENAME as source\n" +" --output human|json\n" +" output format\n" +" -U, --force-share\n" +" open images in shared mode for concurrent access\n" +); break; case 'f': @@ -5412,25 +5684,14 @@ static int img_measure(int argc, char **argv) break; case OPTION_OUTPUT: - if (!strcmp(optarg, "json")) { - output_format = OFORMAT_JSON; - } else if (!strcmp(optarg, "human")) { - output_format = OFORMAT_HUMAN; - } else { - error_report("--output must be used with human or json " - "as argument."); - goto out; - } + output_format = parse_output_format(argv[0], optarg); break; - case OPTION_SIZE: - { - int64_t sval; - - sval = cvtnum("image size", optarg); - if (sval < 0) { + case 's': + img_size = cvtnum("image size", optarg, true); + if (img_size < 0) { goto out; } - img_size = (uint64_t)sval; - } - break; + break; + default: + tryhelp(argv[0]); } } @@ -5447,9 +5708,9 @@ static int img_measure(int argc, char **argv) goto out; } - if (filename && img_size != UINT64_MAX) { + if (filename && img_size != -1) { error_report("--size N cannot be used together with a filename."); goto out; } - if (!filename && img_size == UINT64_MAX) { + if (!filename && img_size == -1) { error_report("Either --size N or one filename must be specified."); goto out; @@ -5499,5 +5760,5 @@ static int img_measure(int argc, char **argv) } } - if (img_size != UINT64_MAX) { + if (img_size != -1) { qemu_opt_set_number(opts, BLOCK_OPT_SIZE, img_size, &error_abort); } @@ -5533,11 +5794,47 @@ out: static const img_cmd_t img_cmds[] = { -#define DEF(option, callback, arg_string) \ - { option, callback }, -#include "qemu-img-cmds.h" -#undef DEF + { "amend", img_amend, + "Update format-specific options of the image" }, + { "bench", img_bench, + "Run simple image benchmark" }, + { "bitmap", img_bitmap, + "Perform modifications of the persistent bitmap in the image" }, + { "check", img_check, + "Check basic image integrity" }, + { "commit", img_commit, + "Commit image to its backing file" }, + { "compare", img_compare, + "Check if two images have the same contents" }, + { "convert", img_convert, + "Copy one image to another with optional format conversion" }, + { "create", img_create, + "Create and format new image file" }, + { "dd", img_dd, + "Copy input to output with optional format conversion" }, + { "info", img_info, + "Display information about image" }, + { "map", img_map, + "Dump image metadata" }, + { "measure", img_measure, + "Calculate file size requred for a new image" }, + { "rebase", img_rebase, + "Change backing file of the image" }, + { "resize", img_resize, + "Resize the image to the new size" }, + { "snapshot", img_snapshot, + "List or manipulate snapshots within image" }, { NULL, NULL, }, }; +static void format_print(void *opaque, const char *name) +{ + int *np = opaque; + if (*np + strlen(name) > 75) { + printf("\n "); + *np = 1; + } + *np += printf(" %s", name); +} + int main(int argc, char **argv) { @@ -5567,21 +5864,37 @@ int main(int argc, char **argv) module_call_init(MODULE_INIT_QOM); bdrv_init(); - if (argc < 2) { - error_exit("Not enough arguments"); - } qemu_add_opts(&qemu_source_opts); qemu_add_opts(&qemu_trace_opts); - while ((c = getopt_long(argc, argv, "+:hVT:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "+hVT:", long_options, NULL)) != -1) { switch (c) { - case ':': - missing_argument(argv[optind - 1]); - return 0; - case '?': - unrecognized_option(argv[optind - 1]); - return 0; case 'h': - help(); + printf( +QEMU_IMG_VERSION +"QEMU disk image utility. Usage:\n" +"\n" +" qemu-img [standard options] COMMAND [--help | command options]\n" +"\n" +"Standard options:\n" +" -h, --help\n" +" display this help and exit\n" +" -V, --version\n" +" display version info and exit\n" +" -T,--trace TRACE\n" +" specify tracing options:\n" +" [[enable=]][,events=][,file=]\n" +"\n" +"Recognized commands (run qemu-img COMMAND --help for command-specific help):\n\n"); + for (cmd = img_cmds; cmd->name != NULL; cmd++) { + printf(" %s - %s\n", cmd->name, cmd->description); + } + printf("\nSupported image formats:\n"); + c = 99; /* force a newline */ + bdrv_iterate_format(format_print, &c, false); + if (c) { + printf("\n"); + } + printf("\n" QEMU_HELP_BOTTOM "\n"); return 0; case 'V': @@ -5591,16 +5904,14 @@ int main(int argc, char **argv) trace_opt_parse(optarg); break; + default: + tryhelp(argv[0]); } } - cmdname = argv[optind]; - - /* reset getopt_long scanning */ - argc -= optind; - if (argc < 1) { - return 0; + if (optind >= argc) { + error_exit(argv[0], "Not enough arguments"); } - argv += optind; - qemu_reset_optind(); + + cmdname = argv[optind]; if (!trace_init_backends()) { @@ -5613,9 +5924,15 @@ int main(int argc, char **argv) for (cmd = img_cmds; cmd->name != NULL; cmd++) { if (!strcmp(cmdname, cmd->name)) { - return cmd->handler(argc, argv); + g_autofree char *argv0 = g_strdup_printf("%s %s", argv[0], cmdname); + /* reset options and getopt processing (incl return order) */ + argv += optind; + argc -= optind; + qemu_reset_optind(); + argv[0] = argv0; + return cmd->handler(cmd, argc, argv); } } /* not found */ - error_exit("Command not found: %s", cmdname); + error_exit(argv[0], "Command not found: %s", cmdname); } diff --git a/tests/qemu-iotests/049.out b/tests/qemu-iotests/049.out --- a/tests/qemu-iotests/049.out +++ b/tests/qemu-iotests/049.out @@ -99,6 +99,5 @@ qemu-img: TEST_DIR/t.qcow2: Value '-1024' is out of range for parameter 'size' qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- -1k -qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for -qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. +qemu-img: Invalid image size specified: '-1k'. qemu-img create -f qcow2 -o size=-1k TEST_DIR/t.qcow2 @@ -108,6 +107,5 @@ and exabytes, respectively. qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- 1kilobyte -qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for -qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. +qemu-img: Invalid image size specified: '1kilobyte'. qemu-img create -f qcow2 -o size=1kilobyte TEST_DIR/t.qcow2 @@ -117,6 +115,5 @@ and exabytes, respectively. qemu-img create -f qcow2 TEST_DIR/t.qcow2 -- foobar -qemu-img: Invalid image size specified. You may use k, M, G, T, P or E suffixes for -qemu-img: kilobytes, megabytes, gigabytes, terabytes, petabytes and exabytes. +qemu-img: Invalid image size specified: 'foobar'. qemu-img create -f qcow2 -o size=foobar TEST_DIR/t.qcow2