From e2fb96d808fbd5f20c5489ed716426a9128c1dc7 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Thu, 5 Dec 2019 14:12:16 -0500 Subject: [PATCH] CLI: Import argpar MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This patch imports the argpar code into lttng-tools. At first, it will be used to implement the create-trigger command, but if it proves useful, it could help to implement all other commands too. Synced with the upstream argpar code as of 1ae22b5e. Change-Id: I26daed2e50e78e093d6d5d26320193193d907dd5 Signed-off-by: Simon Marchi Signed-off-by: Jérémie Galarneau Depends-on: lttng-ust: I5a800fc92e588c2a6a0e26282b0ad5f31c044479 --- configure.ac | 1 + src/common/Makefile.am | 9 +- src/common/argpar/Makefile.am | 3 + src/common/argpar/argpar.c | 615 ++++++++++++++++++++++++++++++++++ src/common/argpar/argpar.h | 217 ++++++++++++ 5 files changed, 843 insertions(+), 2 deletions(-) create mode 100644 src/common/argpar/Makefile.am create mode 100644 src/common/argpar/argpar.c create mode 100644 src/common/argpar/argpar.h diff --git a/configure.ac b/configure.ac index 0ff8cee49..ef6fc47af 100644 --- a/configure.ac +++ b/configure.ac @@ -1104,6 +1104,7 @@ AC_CONFIG_FILES([ extras/core-handler/Makefile src/Makefile src/common/Makefile + src/common/argpar/Makefile src/common/kernel-ctl/Makefile src/common/kernel-consumer/Makefile src/common/consumer/Makefile diff --git a/src/common/Makefile.am b/src/common/Makefile.am index bbddbe7eb..8a74b7282 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -2,7 +2,10 @@ AUTOMAKE_OPTIONS = subdir-objects -SUBDIRS = string-utils filter +SUBDIRS = \ + string-utils \ + filter \ + argpar # Make sure to always distribute all folders # since SUBDIRS is decided at configure time. @@ -21,7 +24,9 @@ DIST_SUBDIRS = \ consumer \ string-utils \ fd-tracker \ - filter + filter \ + argpar + # Common library noinst_LTLIBRARIES = libcommon.la EXTRA_DIST = mi-lttng-4.0.xsd diff --git a/src/common/argpar/Makefile.am b/src/common/argpar/Makefile.am new file mode 100644 index 000000000..175526abe --- /dev/null +++ b/src/common/argpar/Makefile.am @@ -0,0 +1,3 @@ +noinst_LTLIBRARIES = libargpar.la + +libargpar_la_SOURCES = argpar.c argpar.h diff --git a/src/common/argpar/argpar.c b/src/common/argpar/argpar.c new file mode 100644 index 000000000..e3d392f0d --- /dev/null +++ b/src/common/argpar/argpar.c @@ -0,0 +1,615 @@ +/* + * SPDX-License-Identifier: MIT + * + * Copyright 2019 Philippe Proulx + */ + +#include +#include +#include +#include +#include +#include + +#include "argpar.h" + +#define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type))) +#define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type))) +#define argpar_zalloc(_type) argpar_calloc(_type, 1) + +#define ARGPAR_ASSERT(_cond) assert(_cond) + +#ifdef __MINGW_PRINTF_FORMAT +# define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT +#else +# define ARGPAR_PRINTF_FORMAT printf +#endif + +static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0))) +char *argpar_vasprintf(const char *fmt, va_list args) +{ + int len1, len2; + char *str; + va_list args2; + + va_copy(args2, args); + + len1 = vsnprintf(NULL, 0, fmt, args); + if (len1 < 0) { + str = NULL; + goto end; + } + + str = malloc(len1 + 1); + if (!str) { + goto end; + } + + len2 = vsnprintf(str, len1 + 1, fmt, args2); + + ARGPAR_ASSERT(len1 == len2); + +end: + va_end(args2); + return str; +} + + +static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2))) +char *argpar_asprintf(const char *fmt, ...) +{ + va_list args; + char *str; + + va_start(args, fmt); + str = argpar_vasprintf(fmt, args); + va_end(args); + + return str; +} + +static __attribute__((format(ARGPAR_PRINTF_FORMAT, 2, 3))) +bool argpar_string_append_printf(char **str, const char *fmt, ...) +{ + char *new_str = NULL; + char *addendum; + bool success; + va_list args; + + ARGPAR_ASSERT(str); + + va_start(args, fmt); + addendum = argpar_vasprintf(fmt, args); + va_end(args); + + if (!addendum) { + success = false; + goto end; + } + + new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum); + if (!new_str) { + success = false; + goto end; + } + + free(*str); + *str = new_str; + + success = true; + +end: + free(addendum); + + return success; +} + +static +void destroy_item(struct argpar_item * const item) +{ + if (!item) { + goto end; + } + + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt * const opt_item = (void *) item; + + free((void *) opt_item->arg); + } + + free(item); + +end: + return; +} + +static +bool push_item(struct argpar_item_array * const array, + struct argpar_item * const item) +{ + bool success; + + ARGPAR_ASSERT(array); + ARGPAR_ASSERT(item); + + if (array->n_items == array->n_alloc) { + unsigned int new_n_alloc = array->n_alloc * 2; + struct argpar_item **new_items; + + new_items = argpar_realloc(array->items, + struct argpar_item *, new_n_alloc); + if (!new_items) { + success = false; + goto end; + } + + array->n_alloc = new_n_alloc; + array->items = new_items; + } + + array->items[array->n_items] = item; + array->n_items++; + + success = true; + +end: + return success; +} + +static +void destroy_item_array(struct argpar_item_array * const array) +{ + if (array) { + unsigned int i; + + for (i = 0; i < array->n_items; i++) { + destroy_item(array->items[i]); + } + + free(array->items); + free(array); + } +} + +static +struct argpar_item_array *new_item_array(void) +{ + struct argpar_item_array *ret; + const int initial_size = 10; + + ret = argpar_zalloc(struct argpar_item_array); + if (!ret) { + goto end; + } + + ret->items = argpar_calloc(struct argpar_item *, initial_size); + if (!ret->items) { + goto error; + } + + ret->n_alloc = initial_size; + + goto end; + +error: + destroy_item_array(ret); + ret = NULL; + +end: + return ret; +} + +static +struct argpar_item_opt *create_opt_item( + const struct argpar_opt_descr * const descr, + const char * const arg) +{ + struct argpar_item_opt *opt_item = + argpar_zalloc(struct argpar_item_opt); + + if (!opt_item) { + goto end; + } + + opt_item->base.type = ARGPAR_ITEM_TYPE_OPT; + opt_item->descr = descr; + + if (arg) { + opt_item->arg = strdup(arg); + if (!opt_item->arg) { + goto error; + } + } + + goto end; + +error: + destroy_item(&opt_item->base); + opt_item = NULL; + +end: + return opt_item; +} + +static +struct argpar_item_non_opt *create_non_opt_item(const char * const arg, + const unsigned int orig_index, + const unsigned int non_opt_index) +{ + struct argpar_item_non_opt * const non_opt_item = + argpar_zalloc(struct argpar_item_non_opt); + + if (!non_opt_item) { + goto end; + } + + non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT; + non_opt_item->arg = arg; + non_opt_item->orig_index = orig_index; + non_opt_item->non_opt_index = non_opt_index; + +end: + return non_opt_item; +} + +static +const struct argpar_opt_descr *find_descr( + const struct argpar_opt_descr * const descrs, + const char short_name, const char * const long_name) +{ + const struct argpar_opt_descr *descr; + + for (descr = descrs; descr->short_name || descr->long_name; descr++) { + if (short_name && descr->short_name && + short_name == descr->short_name) { + goto end; + } + + if (long_name && descr->long_name && + strcmp(long_name, descr->long_name) == 0) { + goto end; + } + } + +end: + return !descr->short_name && !descr->long_name ? NULL : descr; +} + +enum parse_orig_arg_opt_ret { + PARSE_ORIG_ARG_OPT_RET_OK, + PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT = -2, + PARSE_ORIG_ARG_OPT_RET_ERROR = -1, +}; + +static +enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, + const char * const next_orig_arg, + const struct argpar_opt_descr * const descrs, + struct argpar_parse_ret * const parse_ret, + bool * const used_next_orig_arg) +{ + enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + const char *short_opt_ch = short_opts; + + if (strlen(short_opts) == 0) { + argpar_string_append_printf(&parse_ret->error, "Invalid argument"); + goto error; + } + + while (*short_opt_ch) { + const char *opt_arg = NULL; + const struct argpar_opt_descr *descr; + struct argpar_item_opt *opt_item; + + /* Find corresponding option descriptor */ + descr = find_descr(descrs, *short_opt_ch, NULL); + if (!descr) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; + argpar_string_append_printf(&parse_ret->error, + "Unknown option `-%c`", *short_opt_ch); + goto error; + } + + if (descr->with_arg) { + if (short_opt_ch[1]) { + /* `-oarg` form */ + opt_arg = &short_opt_ch[1]; + } else { + /* `-o arg` form */ + opt_arg = next_orig_arg; + *used_next_orig_arg = true; + } + + /* + * We accept `-o ''` (empty option's argument), + * but not `-o` alone if an option's argument is + * expected. + */ + if (!opt_arg || (short_opt_ch[1] && strlen(opt_arg) == 0)) { + argpar_string_append_printf(&parse_ret->error, + "Missing required argument for option `-%c`", + *short_opt_ch); + *used_next_orig_arg = false; + goto error; + } + } + + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + goto error; + } + + if (!push_item(parse_ret->items, &opt_item->base)) { + goto error; + } + + if (descr->with_arg) { + /* Option has an argument: no more options */ + break; + } + + /* Go to next short option */ + short_opt_ch++; + } + + goto end; + +error: + if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + } + +end: + return ret; +} + +static +enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, + const char * const next_orig_arg, + const struct argpar_opt_descr * const descrs, + struct argpar_parse_ret * const parse_ret, + bool * const used_next_orig_arg) +{ + const size_t max_len = 127; + enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + const struct argpar_opt_descr *descr; + struct argpar_item_opt *opt_item; + + /* Option's argument, if any */ + const char *opt_arg = NULL; + + /* Position of first `=`, if any */ + const char *eq_pos; + + /* Buffer holding option name when `long_opt_arg` contains `=` */ + char buf[max_len + 1]; + + /* Option name */ + const char *long_opt_name = long_opt_arg; + + if (strlen(long_opt_arg) == 0) { + argpar_string_append_printf(&parse_ret->error, + "Invalid argument"); + goto error; + } + + /* Find the first `=` in original argument */ + eq_pos = strchr(long_opt_arg, '='); + if (eq_pos) { + const size_t long_opt_name_size = eq_pos - long_opt_arg; + + /* Isolate the option name */ + if (long_opt_name_size > max_len) { + argpar_string_append_printf(&parse_ret->error, + "Invalid argument `--%s`", long_opt_arg); + goto error; + } + + memcpy(buf, long_opt_arg, long_opt_name_size); + buf[long_opt_name_size] = '\0'; + long_opt_name = buf; + } + + /* Find corresponding option descriptor */ + descr = find_descr(descrs, '\0', long_opt_name); + if (!descr) { + argpar_string_append_printf(&parse_ret->error, + "Unknown option `--%s`", long_opt_name); + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; + goto error; + } + + /* Find option's argument if any */ + if (descr->with_arg) { + if (eq_pos) { + /* `--long-opt=arg` style */ + opt_arg = eq_pos + 1; + } else { + /* `--long-opt arg` style */ + if (!next_orig_arg) { + argpar_string_append_printf(&parse_ret->error, + "Missing required argument for option `--%s`", + long_opt_name); + goto error; + } + + opt_arg = next_orig_arg; + *used_next_orig_arg = true; + } + } + + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + goto error; + } + + if (!push_item(parse_ret->items, &opt_item->base)) { + goto error; + } + + goto end; + +error: + if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + } + +end: + return ret; +} + +static +enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, + const char * const next_orig_arg, + const struct argpar_opt_descr * const descrs, + struct argpar_parse_ret * const parse_ret, + bool * const used_next_orig_arg) +{ + enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + + ARGPAR_ASSERT(orig_arg[0] == '-'); + + if (orig_arg[1] == '-') { + /* Long option */ + ret = parse_long_opt(&orig_arg[2], + next_orig_arg, descrs, parse_ret, + used_next_orig_arg); + } else { + /* Short option */ + ret = parse_short_opts(&orig_arg[1], + next_orig_arg, descrs, parse_ret, + used_next_orig_arg); + } + + return ret; +} + +static +bool prepend_while_parsing_arg_to_error(char **error, + const unsigned int i, const char * const arg) +{ + char *new_error; + bool success; + + ARGPAR_ASSERT(error); + ARGPAR_ASSERT(*error); + + new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s", + i + 1, arg, *error); + if (!new_error) { + success = false; + goto end; + } + + free(*error); + *error = new_error; + success = true; + +end: + return success; +} + +ARGPAR_HIDDEN +struct argpar_parse_ret argpar_parse(unsigned int argc, + const char * const *argv, + const struct argpar_opt_descr * const descrs, + bool fail_on_unknown_opt) +{ + struct argpar_parse_ret parse_ret = { 0 }; + unsigned int i; + unsigned int non_opt_index = 0; + + parse_ret.items = new_item_array(); + if (!parse_ret.items) { + goto error; + } + + for (i = 0; i < argc; i++) { + enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret; + bool used_next_orig_arg = false; + const char * const orig_arg = argv[i]; + const char * const next_orig_arg = + i < argc - 1 ? argv[i + 1] : NULL; + + if (orig_arg[0] != '-') { + /* Non-option argument */ + struct argpar_item_non_opt *non_opt_item = + create_non_opt_item(orig_arg, i, non_opt_index); + + if (!non_opt_item) { + goto error; + } + + non_opt_index++; + + if (!push_item(parse_ret.items, &non_opt_item->base)) { + goto error; + } + + continue; + } + + /* Option argument */ + parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg, + next_orig_arg, descrs, &parse_ret, &used_next_orig_arg); + switch (parse_orig_arg_opt_ret) { + case PARSE_ORIG_ARG_OPT_RET_OK: + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT: + ARGPAR_ASSERT(!used_next_orig_arg); + + if (fail_on_unknown_opt) { + prepend_while_parsing_arg_to_error( + &parse_ret.error, i, orig_arg); + goto error; + } + + /* + * The current original argument is not + * considered ingested because it triggered an + * unknown option. + */ + parse_ret.ingested_orig_args = i; + free(parse_ret.error); + parse_ret.error = NULL; + goto end; + case PARSE_ORIG_ARG_OPT_RET_ERROR: + prepend_while_parsing_arg_to_error( + &parse_ret.error, i, orig_arg); + goto error; + default: + abort(); + } + + if (used_next_orig_arg) { + i++; + } + } + + parse_ret.ingested_orig_args = argc; + free(parse_ret.error); + parse_ret.error = NULL; + goto end; + +error: + /* That's how we indicate that an error occurred */ + destroy_item_array(parse_ret.items); + parse_ret.items = NULL; + +end: + return parse_ret; +} + +ARGPAR_HIDDEN +void argpar_parse_ret_fini(struct argpar_parse_ret *ret) +{ + ARGPAR_ASSERT(ret); + + destroy_item_array(ret->items); + ret->items = NULL; + + free(ret->error); + ret->error = NULL; +} diff --git a/src/common/argpar/argpar.h b/src/common/argpar/argpar.h new file mode 100644 index 000000000..00334cd6f --- /dev/null +++ b/src/common/argpar/argpar.h @@ -0,0 +1,217 @@ +/* + * SPDX-License-Identifier: MIT + * + * Copyright 2019 Philippe Proulx + */ + +#ifndef BABELTRACE_ARGPAR_H +#define BABELTRACE_ARGPAR_H + +#include + +/* Sentinel for an option descriptor array */ +#define ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } + +/* + * ARGPAR_HIDDEN: if argpar is used in some shared library, we don't want them + * to be exported by that library, so mark them as "hidden". + * + * On Windows, symbols are local unless explicitly exported, + * see https://gcc.gnu.org/wiki/Visibility + */ +#if defined(_WIN32) || defined(__CYGWIN__) +#define ARGPAR_HIDDEN +#else +#define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) +#endif + +/* Option descriptor */ +struct argpar_opt_descr { + /* Numeric ID for this option */ + const int id; + + /* Short option character, or `\0` */ + const char short_name; + + /* Long option name (without `--`), or `NULL` */ + const char * const long_name; + + /* True if this option has an argument */ + const bool with_arg; +}; + +/* Item type */ +enum argpar_item_type { + /* Option */ + ARGPAR_ITEM_TYPE_OPT, + + /* Non-option */ + ARGPAR_ITEM_TYPE_NON_OPT, +}; + +/* Base item */ +struct argpar_item { + enum argpar_item_type type; +}; + +/* Option item */ +struct argpar_item_opt { + struct argpar_item base; + + /* Corresponding descriptor */ + const struct argpar_opt_descr *descr; + + /* Argument, or `NULL` if none */ + const char *arg; +}; + +/* Non-option item */ +struct argpar_item_non_opt { + struct argpar_item base; + + /* + * Complete argument, pointing to one of the entries of the + * original arguments (`argv`). + */ + const char *arg; + + /* Index of this argument amongst all original arguments (`argv`) */ + unsigned int orig_index; + + /* Index of this argument amongst other non-option arguments */ + unsigned int non_opt_index; +}; + +struct argpar_item_array { + /* Array of `struct argpar_item *`, or `NULL` on error */ + struct argpar_item **items; + + /* Number of used slots in `items`. */ + unsigned int n_items; + + /* Number of allocated slots in `items`. */ + unsigned int n_alloc; +}; + +/* What is returned by argpar_parse() */ +struct argpar_parse_ret { + /* Array of `struct argpar_item *`, or `NULL` on error */ + struct argpar_item_array *items; + + /* Error string, or `NULL` if none */ + char *error; + + /* Number of original arguments (`argv`) ingested */ + unsigned int ingested_orig_args; +}; + +/* + * Parses the arguments `argv` of which the count is `argc` using the + * sentinel-terminated (use `ARGPAR_OPT_DESCR_SENTINEL`) option + * descriptor array `descrs`. + * + * This function considers ALL the elements of `argv`, including the + * first one, so that you would typically pass `argc - 1` and + * `&argv[1]` from what main() receives. + * + * This argument parser supports: + * + * * Short options without an argument, possibly tied together: + * + * -f -auf -n + * + * * Short options with argument: + * + * -b 45 -f/mein/file -xyzhello + * + * * Long options without an argument: + * + * --five-guys --burger-king --pizza-hut --subway + * + * * Long options with arguments: + * + * --security enable --time=18.56 + * + * * Non-option arguments (anything else). + * + * This function does not accept `-` or `--` as arguments. The latter + * means "end of options" for many command-line tools, but this function + * is all about keeping the order of the arguments, so it does not mean + * much to put them at the end. This has the side effect that a + * non-option argument cannot have the form of an option, for example if + * you need to pass the exact relative path `--component`. In that case, + * you would need to pass `./--component`. There's no generic way to + * escape `-` for the moment. + * + * This function accepts duplicate options (the resulting array of items + * contains one entry for each instance). + * + * On success, this function returns an array of items + * (`struct argpar_item *`). Each item is to be casted to the + * appropriate type (`struct argpar_item_opt *` or + * `struct argpar_item_non_opt *`) depending on its type. + * + * The returned array contains the items in the same order that the + * arguments were parsed, including non-option arguments. This means, + * for example, that for + * + * --hello --meow=23 /path/to/file -b + * + * the function returns an array of four items: two options, one + * non-option, and one option. + * + * In the returned structure, `ingested_orig_args` is the number of + * ingested arguments within `argv` to produce the resulting array of + * items. If `fail_on_unknown_opt` is true, then on success + * `ingested_orig_args` is equal to `argc`. Otherwise, + * `ingested_orig_args` contains the number of original arguments until + * an unknown _option_ occurs. For example, with + * + * --great --white contact nuance --shark nuclear + * + * if `--shark` is not described within `descrs` and + * `fail_on_unknown_opt` is false, then `ingested_orig_args` is 4 (two + * options, two non-options), whereas `argc` is 6. + * + * This makes it possible to know where a command name is, for example. + * With those arguments: + * + * --verbose --stuff=23 do-something --specific-opt -f -b + * + * and the descriptors for `--verbose` and `--stuff` only, the function + * returns the `--verbose` and `--stuff` option items, the + * `do-something` non-option item, and that three original arguments + * were ingested. This means you can start the next argument parsing + * stage, with option descriptors depending on the command name, at + * `&argv[3]`. + * + * Note that `ingested_orig_args` is not always equal to the number of + * returned items, as + * + * --hello -fdw + * + * for example contains two ingested original arguments, but four + * resulting items. + * + * On failure, the returned structure's `items` member is `NULL`, and + * the `error` string member contains details about the error. + * + * You can finalize the returned structure with + * argpar_parse_ret_fini(). + */ +ARGPAR_HIDDEN +struct argpar_parse_ret argpar_parse(unsigned int argc, + const char * const *argv, + const struct argpar_opt_descr *descrs, + bool fail_on_unknown_opt); + +/* + * Finalizes what is returned by argpar_parse(). + * + * It is safe to call argpar_parse() multiple times with the same + * structure. + */ +ARGPAR_HIDDEN +void argpar_parse_ret_fini(struct argpar_parse_ret *ret); + +#endif /* BABELTRACE_ARGPAR_H */ -- 2.34.1