qemu-arg defines a standardized API for argument parsing, help displaying and texi generation/sync.
The implementation supports command + arguments form (i.e qemu-img requirements) and a more general simple arguments parsing. So we can parse: $ prog <command> --arg1 --arg2 $ prog --arg1 --arg2 We support the following: + basic arguments validation (i.e required arguments and required values); + basic arguments transformations (integer, bool values) + repeated/"cumullated" arguments (i.e -o opt1=val -o opt2=val2 will result the string "opt1=val,opt2=val2") + positional arguments; + identified positional for fixed/defined numbers of expected positional args; + listed positional for N expected positional args; + help messages generation; + texi generation; + default value setting; + mutually exclusive arguments; + display and parsing decorated arguments ("--argument value" and "--argument=value" are valid) Signed-off-by: Leandro Dorileo <l...@dorileo.org> --- include/qemu/qemu-arg.h | 287 ++++++++++++++++ util/Makefile.objs | 1 + util/qemu-arg.c | 887 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1175 insertions(+) create mode 100644 include/qemu/qemu-arg.h create mode 100644 util/qemu-arg.c diff --git a/include/qemu/qemu-arg.h b/include/qemu/qemu-arg.h new file mode 100644 index 0000000..c8d8fb4 --- /dev/null +++ b/include/qemu/qemu-arg.h @@ -0,0 +1,287 @@ +/* + * QEMU argument helper + * + * Copyright (c) 2014 Leandro Dorileo <l...@dorileo.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef _QEMU_ARG_H_ +#define _QEMU_ARG_H_ + +#include <libintl.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +typedef struct _QemuArgContext QemuArgContext; +typedef struct _QemuArgCommand QemuArgCommand; + +typedef enum _QemuArgOptType { + QEMU_ARG_OPT_TYPE_INT, + QEMU_ARG_OPT_TYPE_BOOL, + QEMU_ARG_OPT_TYPE_STR, + QEMU_ARG_OPT_TYPE_POSITIONAL, + QEMU_ARG_OPT_TYPE_POSITIONAL_LIST, + QEMU_ARG_OPT_TYPE_DEPRECATED, + QEMU_ARG_OPT_TYPE_GROUP, + QEMU_ARG_OPT_TYPE_SENTINEL, +} QemuArgOptType; + +typedef struct _QemuArgIntValue { + /** default value */ + int def_val; + + /** user value pointer */ + int *value; +} QemuArgIntValue; + +typedef struct _QemuArgBoolValue { + /** default value */ + bool def_val; + + /** user value pointer */ + bool *value; +} QemuArgBoolValue; + +typedef struct _QemuArgStrValue { + /** default value */ + char *def_val; + + /** user value pointer */ + char **value; +} QemuArgStrValue; + +typedef struct _QemuArgStrListValue { + /** default value */ + char **def_val; + + /** user value pointer */ + char ***value; + + /** list elements counter */ + int list_cnt; +} QemuArgStrListValue; + +typedef enum _QemuArgOptFlag { + ARG_FLAG_NONE = 0, + + /** provide many arguments instances, their value are concatenated in a + comman separated string */ + ARG_FLAG_CUMULATE = 1 << 0, + + /** the argument is required */ + ARG_FLAG_REQUIRED = 1 << 1, + + /** the argument requires a value like --foo bar where --foo requires bar */ + ARG_FLAG_REQ_VALUE = 1 << 2, +} QemuArgOptFlag; + +typedef struct _QemuArgOpt { + /** argument type, bool, int, str, etc @see QemuArgOptType */ + QemuArgOptType type; + + /** the argument's short name i.e -c */ + const char short_name; + + /** argument's long name i.e --cache */ + const char *long_name; + + /** argument's description, used to display a hint about the argument's + value i.e -f fmt where fmt is the arg's desc */ + const char *desc; + + /** help message, describes the argument */ + const char *help; + + /** some behavior flags @see QemuArgOptFlag for possible modifiers */ + int flags; + + /** indicates the argument was set, for bool values it tells we got the + argument and the others we got the argument's value */ + bool value_set; + + /** value pointer, @see QemuArgIntValue @see QemuArgBoolValue + @see QemuArgStrValue @see QemuArgStrListValue */ + const void *value; +} QemuArgOpt; + +struct _QemuArgCommand { + /** the command itself i.e commit, create */ + const char *cmd; + + /** help msg displayed on main help msg - command listing msg */ + const char *help; + + /** the command's arguments */ + QemuArgOpt *args; + + /** number of arguments */ + int args_cnt; + + /** mutual exclusive groups - it's an array of QemuArgOpt arrays, each array + composes arguments mutually exclusive */ + QemuArgOpt **mutual_groups; + + int (* cb)(const QemuArgContext *ctx, const QemuArgCommand *cmd); +}; + +struct _QemuArgContext { + /** text displayed on top of help msg */ + const char *prologue; + + /** text displayed on bottom of help msg */ + const char *epilogue; + + /** the program name, usage on "usage" msg */ + const char *prog_name; + + /** command list */ + const QemuArgCommand *commands; + + /** non command usage or common arguments */ + QemuArgOpt *args; + + /** number of arguments (non command usage) */ + int args_cnt; + + /** non command usage */ + QemuArgOpt **mutual_groups; + + /** support decorated mode, like --arg=value. actually we'll always parse + this form, this flag will indicate we must show the long options on help + messages @see OPT_DECORATE_LONG and @see OPT_DECORATE_SHORT */ + int decorate; +}; + +/** decoration modes, OPT_DECORATE_LONG | OPT_DECORATE_SHORT will decorate both + short and long arguments */ +#define OPT_DECORATE_LONG (1 << 0) +#define OPT_DECORATE_SHORT (1 << 1) + +/** type wrappers macros */ +#define INT_VALUE(_val, _default) \ + &(QemuArgIntValue) {.value = _val, .def_val = _default} \ + +#define BOOL_VALUE(_val, _default) \ + &(QemuArgBoolValue) {.value = _val, .def_val = _default} \ + +#define STR_VALUE(_val, _default) \ + &(QemuArgStrValue) {.value = _val, .def_val = (char *)_default} \ + +#define STR_LIST_VALUE(_val, _default) \ + &(QemuArgStrListValue) {.value = _val, .def_val = _default} \ + +/** QemuArgOpt constructors */ +#define OPT_BOOL(s, l, h, p, df) \ + {QEMU_ARG_OPT_TYPE_BOOL, s, l, NULL, h, ARG_FLAG_NONE, false, \ + BOOL_VALUE(p, df)} \ + +#define OPT_STR(s, l, d, h, p, df) \ + {QEMU_ARG_OPT_TYPE_STR, s, l, d, h, ARG_FLAG_REQ_VALUE, false, \ + STR_VALUE(p, df)} \ + +#define OPT_INT(s, l, d, h, p, df) \ + {QEMU_ARG_OPT_TYPE_INT, s, l, d, h, ARG_FLAG_REQ_VALUE, false, \ + INT_VALUE(p, df)} \ + +#define OPT_CUMUL_STR(s, l, d, h, p, df) \ + {QEMU_ARG_OPT_TYPE_STR, s, l, d, h, \ + ARG_FLAG_CUMULATE | ARG_FLAG_REQ_VALUE, false, \ + STR_VALUE(p, df)} \ + +#define OPT_CUMUL_STR_REQ(s, l, d, h, p, df) \ + {QEMU_ARG_OPT_TYPE_STR, s, l, d, h, \ + ARG_FLAG_CUMULATE | ARG_FLAG_REQ_VALUE | ARG_FLAG_REQUIRED, \ + false, STR_VALUE(p, df)} \ + +#define OPT_POS(d, h, p, df) \ + {QEMU_ARG_OPT_TYPE_POSITIONAL, 0, NULL, d, h, ARG_FLAG_NONE, \ + false, STR_VALUE(p, df)} \ + +#define OPT_POS_REQ(d, h, p, df) \ + {QEMU_ARG_OPT_TYPE_POSITIONAL, 0, NULL, d, h, ARG_FLAG_REQUIRED, \ + false, STR_VALUE(p, df)} \ + +#define OPT_POS_LIST(d, h, p, df) \ + {QEMU_ARG_OPT_TYPE_POSITIONAL_LIST, 0, NULL, d, h, ARG_FLAG_NONE, \ + false, STR_LIST_VALUE(p, df)} \ + +#define OPT_POS_LIST_REQ(d, h, p, df) \ + {QEMU_ARG_OPT_TYPE_POSITIONAL_LIST, 0, NULL, d, h, \ + ARG_FLAG_REQUIRED, false, STR_LIST_VALUE(p, df)} \ + +#define OPT_DEP(s, l, h) \ + {QEMU_ARG_OPT_TYPE_DEPRECATED, s, l, NULL, h, ARG_FLAG_NONE} \ + +#define OPT_GROUP(d) \ + {QEMU_ARG_OPT_TYPE_GROUP, 0, NULL, d, NULL, ARG_FLAG_NONE} \ + +/** command and context macro constructor */ +#define OPT_CMD(c, h, a, m, cb) \ + {c, h, a, sizeof(a) / sizeof(QemuArgOpt), m, cb} \ + +#define OPT_CTX(n, p, e, pn, c, a, m, d) \ + const QemuArgContext n = {p, e, pn, c, a, \ + sizeof(a) / sizeof(QemuArgOpt), m, d} \ + +#define OPT_MUTUAL_GROUP_SENTINEL NULL +#define OPT_SENTINEL { QEMU_ARG_OPT_TYPE_SENTINEL } +#define CMD_SENTINEL { NULL } + +/** + * + * @param argc The program arguments counter + * @param argv The program arguments vector + * @param ctx The qemu arg context of interest + * + * Parse commands. + * + * @return negative number on failure, 0 otherwise. + */ +int qemu_arg_parse(int argc, char *argv[], const QemuArgContext *ctx); + +/** + * @param ctx the context to be cleaned up + * + * Clean up the argument context. + */ +void qemu_arg_context_cleanup(const QemuArgContext *ctx); + +/** + * @param cmd The command of interest + * + * Query a command about its parsed positional arguments list. + * + * @return How many items a positional list has. @see OPT_POS_LIST and + * @see OPT_POS_LIST_REQ + */ +int qemu_arg_get_command_positional_list_cnt(const QemuArgCommand *cmd); + +/** + * @param ctx The qemu arg context of interest + * @param cmd The qemu command of interest + * + * Print a command help message. + */ +void qemu_arg_print_command_help(const QemuArgContext *ctx, + const QemuArgCommand *cmd); + +#endif /* _QEMU_ARG_H_ */ diff --git a/util/Makefile.objs b/util/Makefile.objs index 937376b..9d05ec3 100644 --- a/util/Makefile.objs +++ b/util/Makefile.objs @@ -14,3 +14,4 @@ util-obj-y += crc32c.o util-obj-y += throttle.o util-obj-y += getauxval.o util-obj-y += readline.o +util-obj-y += qemu-arg.o diff --git a/util/qemu-arg.c b/util/qemu-arg.c new file mode 100644 index 0000000..e611d97 --- /dev/null +++ b/util/qemu-arg.c @@ -0,0 +1,887 @@ +/* + * QEMU argument helper + * + * Copyright (c) 2014 Leandro Dorileo <l...@dorileo.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/qemu-arg.h" + +#define SUCCESS 0 +#define ERR -1 + +#define HELP_BLOCK_PADDING 25 +#define HELP_BLOCK_WIDTH 90 +#define HELP_DESC_BLOCK_WIDTH (HELP_BLOCK_WIDTH - HELP_BLOCK_PADDING) + +typedef void (*HelpFunc)(const QemuArgContext *ctx, const void *opaque); + +#define OPT_INT_VAL_GET(_opt) \ + ((QemuArgIntValue *)_opt->value) \ + +#define OPT_BOOL_VAL_GET(_opt) \ + ((QemuArgBoolValue *)_opt->value) \ + +#define OPT_STR_VAL_GET(_opt) \ + ((QemuArgStrValue *)_opt->value) \ + +#define OPT_STR_LIST_VAL_GET(_opt) \ + ((QemuArgStrListValue *)_opt->value) \ + +#define OPT_HELP \ + "show this help message. <command> -h will show the command " \ + "specific help message." \ + +static QemuArgOpt _internal_arg_opts[] = { + OPT_STR('h', "help", NULL, OPT_HELP, NULL, NULL), + OPT_SENTINEL +}; + +static void print_help_block(const char *help, int padding_len, + int length) +{ + const char *c; + int i, j; + + for (i = 0, c = help; *c != '\0'; c++, i++) { + /** should break the line? */ + if (i >= length && *c == ' ') { + printf("\n"); + for (j = 0; padding_len && j <= padding_len; j++) { + printf(" "); + } + i = 0; + continue; + } + printf("%c", *c); + } +} + +static int format_short_opt(QemuArgOpt *opt, int decorate, bool texi) +{ + int cnt = 0; + + if (!opt->short_name) { + return cnt; + } + + cnt += printf("-%c", opt->short_name); + + if (decorate & OPT_DECORATE_SHORT && opt->desc) { + if (texi) { + cnt += printf("=@var{%s}", opt->desc); + } else { + cnt += printf("=%s", opt->desc); + } + } else if (opt->desc) { + if (texi) { + cnt += printf(" @var{%s}", opt->desc); + } else { + cnt += printf(" %s", opt->desc); + } + } + + if (opt->long_name) { + cnt += printf(", "); + } + + return cnt; +} + +static int format_long_opt(QemuArgOpt *opt, int decorate, bool texi) +{ + int cnt = 0; + + if (!opt->long_name) { + return cnt; + } + + cnt += printf("--%s", opt->long_name); + + if (decorate & OPT_DECORATE_LONG && opt->desc) { + if (texi) { + cnt += printf("=@var{%s}", opt->desc); + } else { + cnt += printf("=%s", opt->desc); + } + } else if (opt->desc) { + if (texi) { + cnt += printf(" @var{%s}", opt->desc); + } else { + cnt += printf(" %s", opt->desc); + } + } + + return cnt; +} + +static int format_mutual_opt(QemuArgOpt **opt, int decorate, bool texi) +{ + QemuArgOpt **mutual, *itr; + int cnt = 0; + + for (mutual = opt; *mutual; mutual++) { + cnt += printf(" ["); + + for (itr = *mutual; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + cnt += format_short_opt(itr, decorate, texi); + cnt += format_long_opt(itr, decorate, texi); + + if ((itr + 1)->type != QEMU_ARG_OPT_TYPE_SENTINEL) { + cnt += printf(" | "); + } + } + + cnt += printf("]"); + } + + return cnt; +} + +static void print_positional_arg_list(QemuArgOpt *list) +{ + QemuArgOpt *itr; + + for (itr = list; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + int cnt = 0; + + if (itr->type != QEMU_ARG_OPT_TYPE_POSITIONAL && + itr->type != QEMU_ARG_OPT_TYPE_POSITIONAL_LIST) { + continue; + } + + cnt += printf(" %s", itr->desc); + + if (cnt < HELP_BLOCK_PADDING) { + for (; cnt < HELP_BLOCK_PADDING; cnt++) { + printf(" "); + } + } else { + printf("\n"); + for (cnt = 0; cnt < HELP_BLOCK_PADDING; cnt++) { + printf(" "); + } + } + + print_help_block(itr->help, HELP_BLOCK_PADDING - 1, + HELP_DESC_BLOCK_WIDTH); + + printf("\n"); + } +} + +static void print_arg_list(QemuArgOpt *list, int decorate) +{ + QemuArgOpt *itr; + + for (itr = list; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + int cnt = 0; + + if (itr->type == QEMU_ARG_OPT_TYPE_GROUP) { + printf("\n%s options:\n", itr->desc); + continue; + } + + cnt += printf(" "); + cnt += format_short_opt(itr, decorate, false); + cnt += format_long_opt(itr, decorate, false); + + if (cnt < HELP_BLOCK_PADDING) { + for (; cnt < HELP_BLOCK_PADDING; cnt++) { + printf(" "); + } + } else { + printf("\n"); + for (cnt = 0; cnt < HELP_BLOCK_PADDING; cnt++) { + printf(" "); + } + } + + print_help_block(itr->help, HELP_BLOCK_PADDING - 1, + HELP_DESC_BLOCK_WIDTH); + printf("\n"); + } +} + +static void print_help(const QemuArgContext *ctx) +{ + const QemuArgCommand *cmd; + + if (ctx->prologue) { + printf("%s\n\n", ctx->prologue); + } + + printf("%s\n", "Global options:"); + print_arg_list(_internal_arg_opts, 0); + + if (ctx->args) { + print_arg_list(ctx->args, ctx->decorate); + printf("\n"); + } + + if (ctx->commands) { + printf("%s\n", "Commands:"); + + for (cmd = ctx->commands; cmd->cmd; cmd++) { + printf(" %-10s %s\n", cmd->cmd, cmd->help); + } + printf("\n"); + } + + if (ctx->epilogue) { + print_help_block(ctx->epilogue, 0, HELP_BLOCK_WIDTH); + printf("\n"); + } +} + +static int format_positional_opt(QemuArgOpt *opt, bool texi) +{ + int cnt = 0; + + if (!(opt->flags & ARG_FLAG_REQUIRED)) { + cnt += printf(" ["); + } else { + cnt += printf(" "); + } + + if (texi) { + cnt += printf("@var{%s}", opt->desc); + } else { + cnt += printf("%s", opt->desc); + } + + if (!(opt->flags & ARG_FLAG_REQUIRED)) { + cnt += printf("]"); + } + + return cnt; +} + +static void print_command_desc(const QemuArgContext *ctx, + const QemuArgCommand *cmd, bool texi) +{ + QemuArgOpt *itr; + + printf("%s", cmd->cmd); + +#define PRINT_OPT(_args) \ + for (itr = _args; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { \ + if (itr->type == QEMU_ARG_OPT_TYPE_DEPRECATED || \ + itr->type == QEMU_ARG_OPT_TYPE_GROUP) { \ + continue; \ + } \ + if (itr->type == QEMU_ARG_OPT_TYPE_POSITIONAL || \ + itr->type == QEMU_ARG_OPT_TYPE_POSITIONAL_LIST) { \ + format_positional_opt(itr, texi); \ + } else { \ + printf(" ["); \ + format_short_opt(itr, ctx->decorate, texi); \ + format_long_opt(itr, ctx->decorate, texi); \ + printf("]"); \ + } \ + } \ + + if (ctx->args) { + PRINT_OPT(ctx->args); + } + + if (cmd->mutual_groups) { + format_mutual_opt(cmd->mutual_groups, ctx->decorate, texi); + } + + if (cmd->args) { + PRINT_OPT(cmd->args); + } + +#undef PRINT_OPT +} + +static void print_command_usage(const QemuArgContext *ctx, + const QemuArgCommand *cmd) +{ + printf("usage:"); + + if (ctx->prog_name) { + printf(" %s ", ctx->prog_name); + } + + print_command_desc(ctx, cmd, false); + printf("\n\n"); +} + +static bool command_has_positional(const QemuArgCommand *cmd) +{ + QemuArgOpt *itr; + + if (!cmd->args) { + return false; + } + + for (itr = cmd->args; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + if (itr->type == QEMU_ARG_OPT_TYPE_POSITIONAL || + QEMU_ARG_OPT_TYPE_POSITIONAL_LIST) { + return true; + } + } + + return false; +} + +void qemu_arg_print_command_help(const QemuArgContext *ctx, + const QemuArgCommand *cmd) +{ + QemuArgOpt **itr; + bool cmd_opts = false; + + if (ctx->prologue) { + printf("%s\n\n", ctx->prologue); + } + + print_command_usage(ctx, cmd); + + printf("%s\n", "Common options:"); + print_arg_list(_internal_arg_opts, 0); + + if (ctx->args) { + print_arg_list(ctx->args, ctx->decorate); + } + printf("\n"); + + if (command_has_positional(cmd)) { + printf("%s\n", "Positional options:"); + print_positional_arg_list(cmd->args); + printf("\n"); + } + + if (cmd->mutual_groups) { + printf("%s\n", "Command options:"); + + for (itr = cmd->mutual_groups; *itr; itr++) { + print_arg_list(*itr, ctx->decorate); + } + cmd_opts = true; + } + + if (cmd->args) { + if (!cmd_opts) { + printf("%s\n", "Command options:"); + } + + if (cmd->args) { + print_arg_list(cmd->args, ctx->decorate); + } + cmd_opts = true; + } + + if (cmd_opts) { + printf("\n"); + } + + if (ctx->epilogue) { + print_help_block(ctx->epilogue, 0, HELP_BLOCK_WIDTH); + printf("\n"); + } +} + +static void print_command_help_cb(const QemuArgContext *ctx, const void *opaque) +{ + qemu_arg_print_command_help(ctx, opaque); +} + +static const QemuArgCommand *find_command(char *cmd, const QemuArgCommand *list) +{ + const QemuArgCommand *itr; + + for (itr = list; itr->cmd; itr++) { + if (!strcmp(cmd, itr->cmd)) { + return itr; + } + } + + return NULL; +} + +static QemuArgOpt *find_long_opt(char *opt, char *end, QemuArgOpt *list) +{ + QemuArgOpt *itr; + + if (!list) { + return NULL; + } + + for (itr = list; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + if (itr->long_name && !strncmp(itr->long_name, opt, end - opt)) { + return itr; + } + } + + return NULL; +} + +static QemuArgOpt *find_short_opt(char opt, QemuArgOpt *list) +{ + QemuArgOpt *itr; + + if (!list) { + return NULL; + } + + for (itr = list; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + if (itr->short_name && itr->short_name == opt) { + return itr; + } + } + + return NULL; +} + +static QemuArgOpt *find_opt(char *opt, char *end, QemuArgOpt *list) +{ + char *long_opt, short_opt = 0; + size_t len; + + len = strlen("--"); + if (!strncmp(opt, "--", len)) { + long_opt = opt + len; + return find_long_opt(long_opt, end, list); + } + + if (strlen(opt) == 2 && opt[0] == '-') { + short_opt = opt[1]; + return find_short_opt(short_opt, list); + } + + return NULL; +} + +static QemuArgOpt *opt_get_next_positional(char *opt, QemuArgOpt *args) +{ + QemuArgOpt *itr; + + for (itr = args; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + if (itr->type == QEMU_ARG_OPT_TYPE_POSITIONAL_LIST) { + return itr; + } + + if (itr->type == QEMU_ARG_OPT_TYPE_POSITIONAL && itr->value && + !itr->value_set) { + return itr; + } + } + + return NULL; +} + +static bool opt_set_value(char *curr, char *next, int *cnt, QemuArgOpt *opt) +{ + if (opt->flags & ARG_FLAG_REQ_VALUE && !next) { + printf("argument %s requires a value\n", curr); + return false; + } + + if (opt->value && opt->type == QEMU_ARG_OPT_TYPE_BOOL) { + *OPT_BOOL_VAL_GET(opt)->value = true; + opt->value_set = true; + return true; + } + + if (opt->value && opt->type == QEMU_ARG_OPT_TYPE_INT) { + *OPT_INT_VAL_GET(opt)->value = atoi(next); + opt->value_set = true; + return true; + } + + if (opt->value && opt->type == QEMU_ARG_OPT_TYPE_STR) { + if (opt->flags & ARG_FLAG_CUMULATE) { + char **str = OPT_STR_VAL_GET(opt)->value; + char *cp; + + if (*str) { + cp = strdup(*str); + free(*str); + + *str = calloc(1, strlen(cp) + strlen(next) + 1); + sprintf(*str, "%s,%s", cp, next); + + free(cp); + } else { + *str = calloc(1, strlen(next) + 1); + sprintf(*str, "%s", next); + } + } else { + *OPT_STR_VAL_GET(opt)->value = next; + } + + *cnt += 1; + opt->value_set = true; + return true; + } + + if (opt->value && opt->type == QEMU_ARG_OPT_TYPE_POSITIONAL) { + *OPT_STR_VAL_GET(opt)->value = curr; + opt->value_set = true; + return true; + } + + if (opt->value && opt->type == QEMU_ARG_OPT_TYPE_POSITIONAL_LIST) { + char **val; + size_t value_size; + int list_cnt; + + val = *OPT_STR_LIST_VAL_GET(opt)->value; + + list_cnt = OPT_STR_LIST_VAL_GET(opt)->list_cnt; + if (!list_cnt) { + val = realloc(val, sizeof(char *)); + } + + value_size = sizeof(val) + sizeof(char *); + val = realloc(val, value_size); + + val[list_cnt] = (char *)curr; + + list_cnt++; + *OPT_STR_LIST_VAL_GET(opt)->value = val; + OPT_STR_LIST_VAL_GET(opt)->list_cnt = list_cnt; + val[list_cnt] = NULL; + + opt->value_set = true; + return true; + } + + return true; +} + +static QemuArgOpt *find_opt_set(QemuArgOpt *list, QemuArgOpt *opt) +{ + QemuArgOpt *itr; + + for (itr = list; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + if (itr->value_set && itr != opt) { + return itr; + } + } + + return NULL; +} + +static bool validate_required(const QemuArgContext *ctx, QemuArgOpt *opt) +{ + QemuArgOpt *itr; + + for (itr = opt; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + if (itr->flags & ARG_FLAG_REQUIRED && !itr->value_set) { + return false; + } + } + + return true; +} + +static void set_default_values(QemuArgOpt *opt) +{ + QemuArgOpt *itr; + + for (itr = opt; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + if (itr->value_set || !itr->value) { + continue; + } + + if (itr->type == QEMU_ARG_OPT_TYPE_INT) { + *OPT_INT_VAL_GET(itr)->value = OPT_INT_VAL_GET(itr)->def_val; + } else if (itr->type == QEMU_ARG_OPT_TYPE_STR) { + *OPT_STR_VAL_GET(itr)->value = OPT_STR_VAL_GET(itr)->def_val; + } else if (itr->type == QEMU_ARG_OPT_TYPE_BOOL) { + OPT_BOOL_VAL_GET(itr)->value = &OPT_BOOL_VAL_GET(itr)->def_val; + } + } +} + +static int arg_parse(int argc, char *argv[], int begin, + const QemuArgContext *ctx, QemuArgOpt *args, + QemuArgOpt **mutual_groups, HelpFunc cb, + const void *opaque) +{ + int i; + + for (i = begin; i < argc; i++) { + QemuArgOpt *opt, **itr, *mutual; + char *curr, *next, *curr_end, size_char; + bool positional, size; + + size = false; + opt = NULL; + curr = argv[i]; + curr_end = strchr(curr, '='); + + if (!curr_end) { + curr_end = curr + strlen(curr); + next = argv[i + 1]; + } else { + next = curr_end + 1; + } + + if (!strncmp(curr, "-h", curr_end - curr) || + !strncmp(curr, "--help", curr_end - curr)) { + cb(ctx, opaque); + return SUCCESS; + } + + opt = find_opt(curr, curr_end, args); + if (!opt && mutual_groups) { + for (itr = mutual_groups; *itr; itr++) { + opt = find_opt(curr, curr_end, *itr); + + if (opt) { + mutual = find_opt_set(*itr, opt); + if (mutual) { + printf("the following arguments are mutually exclusive " + "and should not be used in conjunction:\n"); + format_mutual_opt(itr, ctx->decorate, false); + printf("\n"); + + return ERR; + } + } + } + } + + if (opt && opt->type == QEMU_ARG_OPT_TYPE_DEPRECATED) { + printf("%s\n", opt->help); + return ERR; + } + + size_char = curr[strlen(curr) - 1]; + + if (size_char == 'k' || size_char == 'K' || size_char == 'M' || + size_char == 'G' || size_char == 'T' || size_char == 'P' || + size_char == 'E') { + size = true; + } + + /** not a valid --argument or not a size specifier (like qemu-img does + for resize i.e +1G or -1G) **/ + positional = curr[0] != '-' || size; + if (!opt && !positional) { + printf("unknown option %s\n", curr); + return ERR; + } else if (positional) { + opt = opt_get_next_positional(curr, args); + if (!opt) { + continue; + } + } + + if (opt->value_set && !(opt->flags & ARG_FLAG_CUMULATE) && + opt->type != QEMU_ARG_OPT_TYPE_BOOL && + opt->type != QEMU_ARG_OPT_TYPE_POSITIONAL_LIST) { + printf("repeated argument, you can provide %s argument just" + " once.\n", curr); + return ERR; + } + + if (!opt_set_value(curr, next, &i, opt)) { + return ERR; + } + } + + return 0; +} + +static QemuArgOpt *merge_args(const QemuArgOpt *list, int list_cnt, + const QemuArgOpt *list2, int list2_cnt) +{ + QemuArgOpt *tmp, *lst; + size_t list_size, list2_size; + + if (list_cnt && list2_cnt) { + list_cnt--; + } + + list_size = list_cnt * sizeof(QemuArgOpt); + list2_size = list2_cnt * sizeof(QemuArgOpt); + + lst = calloc(1, list_size + list2_size); + lst = memcpy(lst, list, list_size); + + tmp = lst + list_cnt; + tmp = memcpy(tmp, list2, list2_size); + + return lst; +} + +static void generate_hx(const QemuArgContext *ctx) +{ + const QemuArgCommand *itr; + + printf("HXCOMM generated with %s generate-hx\n\n", + ctx->prog_name); + + printf("STEXI\n" + "@table @option\n" + "ETEXI\n\n"); + + for (itr = ctx->commands; itr->cmd; itr++) { + printf("STEXI\n"); + printf("@item "); + print_command_desc(ctx, itr, true); + + if (!(itr + 1)->cmd) { + printf("\n@end table"); + } + + printf("\nETEXI\n\n"); + } +} + +static int command_parse(int argc, char *argv[], const QemuArgContext *ctx) +{ + const QemuArgCommand *cmd; + QemuArgOpt *args; + int ret; + + ret = 0; + + if (!strcmp(argv[1], "generate-hx")) { + generate_hx(ctx); + return SUCCESS; + } + + cmd = find_command(argv[1], ctx->commands); + if (!cmd) { + print_help(ctx); + return ERR; + } + + if (!cmd->args) { + return ERR; + } + + args = merge_args(ctx->args, ctx->args_cnt, cmd->args, cmd->args_cnt); + ret = arg_parse(argc, argv, 2, ctx, args, cmd->mutual_groups, + print_command_help_cb, cmd); + + if (!validate_required(ctx, args)) { + qemu_arg_print_command_help(ctx, cmd); + goto err; + } + + set_default_values(args); + + if (cmd->cb) { + ret = cmd->cb(ctx, cmd); + } + +err: + free(args); + return ret; +} + +static void print_help_cb(const QemuArgContext *ctx, const void *opaque) +{ + print_help(ctx); +} + +static int arg_list_parse(int argc, char *argv[], const QemuArgContext *ctx) +{ + int ret = 0; + + ret = arg_parse(argc, argv, 1, ctx, ctx->args, ctx->mutual_groups, + print_help_cb, NULL); + + if (!validate_required(ctx, ctx->args)) { + print_help(ctx); + return ERR; + } + + set_default_values(ctx->args); + + return ret; +} + +int qemu_arg_parse(int argc, char *argv[], const QemuArgContext *ctx) +{ + if (argc == 1) { + print_help(ctx); + return ERR; + } + + if (ctx->commands) { + return command_parse(argc, argv, ctx); + } else { + return arg_list_parse(argc, argv, ctx); + } +} + +static void opt_cleanup(QemuArgOpt *opt) +{ + QemuArgOpt *itr; + + for (itr = opt; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + if (!itr->value) { + continue; + } + + if (itr->type == QEMU_ARG_OPT_TYPE_POSITIONAL_LIST) { + free(*OPT_STR_LIST_VAL_GET(itr)->value); + } else if (itr->flags & ARG_FLAG_CUMULATE) { + free(*OPT_STR_VAL_GET(itr)->value); + } + } +} + +void qemu_arg_context_cleanup(const QemuArgContext *ctx) +{ + const QemuArgCommand *itr; + QemuArgOpt **opt; + + for (itr = ctx->commands; itr->cmd; itr++) { + if (!itr->args || !itr->mutual_groups) { + continue; + } + + opt_cleanup(itr->args); + + if (!itr->mutual_groups) { + continue; + } + + for (opt = itr->mutual_groups; *opt; opt++) { + opt_cleanup(*opt); + } + } +} + +int qemu_arg_get_command_positional_list_cnt(const QemuArgCommand *cmd) +{ + QemuArgOpt *itr; + + if (!cmd) { + return 0; + } + + for (itr = cmd->args; itr->type != QEMU_ARG_OPT_TYPE_SENTINEL; itr++) { + if (itr->type == QEMU_ARG_OPT_TYPE_POSITIONAL_LIST) { + return OPT_STR_LIST_VAL_GET(itr)->list_cnt; + } + } + + return 0; +} -- 1.9.0