diff mbox

[RFC,1/2] qemu-arg: introduce a general purpose argument parser

Message ID 1394304438-14848-2-git-send-email-l@dorileo.org
State New
Headers show

Commit Message

Leandro Dorileo March 8, 2014, 6:47 p.m. UTC
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 mbox

Patch

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;
+}