From: Simon Marchi Date: Fri, 20 Aug 2021 18:39:20 +0000 (-0400) Subject: argpar: sync with upstream - adjust to iterator API X-Git-Tag: v2.13.2~11 X-Git-Url: https://git.lttng.org./?a=commitdiff_plain;h=ea76a8bbde6896bddfa656627e8f12e0b9f55a5e;p=lttng-tools.git argpar: sync with upstream - adjust to iterator API Sync with commit 143cec42e14e ("Force usage of ARGPAR_ASSERT() condition when NDEBUG is defined"). The main change in this sync is the API that changed from parse-all-at-once (the `argpar_parse` function) to something based on an iterator, where we need to call `argpar_iter_next` to obtain the next item. This was prototyped here (in lttng-tools), so this patch converts the code to the API that was actually implemented in upstream argpar. A difference between what we had and the current argpar API is that argpar does not provide a formatted error string anymore. It provides an `argpar_error` object contaning all the raw information needed to create such string. The new `format_arg_error_v` function formats the errors using the exact same syntax as argpar did, such that no changes in the tests are necessary. The new `parse_next_item` function factors out the code around calling argpar_iter_next that would otherwise be duplicated at a few places. These two new functions are placed into a new `argpar-utils` convenience library. I originally put them in the `libcommon.la` convenience library, but that caused some parts of the code that don't do any argument parsing (e.g. liblttng-ctl) to have to be linked against argpar. As a separate library, we can limit that to just the `lttng` binary. Change-Id: I94aa90ffcd93f52b6073c4cd7caca78cfd0f2e05 Signed-off-by: Simon Marchi Signed-off-by: Jérémie Galarneau --- diff --git a/configure.ac b/configure.ac index b12d937f9..ac0fe1fe0 100644 --- a/configure.ac +++ b/configure.ac @@ -1091,6 +1091,7 @@ AC_CONFIG_FILES([ src/Makefile src/common/Makefile src/common/argpar/Makefile + src/common/argpar-utils/Makefile src/common/bytecode/Makefile src/common/kernel-ctl/Makefile src/common/kernel-consumer/Makefile diff --git a/src/bin/lttng/Makefile.am b/src/bin/lttng/Makefile.am index 50ab92988..7de01c292 100644 --- a/src/bin/lttng/Makefile.am +++ b/src/bin/lttng/Makefile.am @@ -43,5 +43,6 @@ lttng_LDADD = $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la \ $(top_builddir)/src/common/config/libconfig.la \ $(top_builddir)/src/common/string-utils/libstring-utils.la \ $(top_builddir)/src/common/filter/libfilter.la \ + $(top_builddir)/src/common/argpar-utils/libargpar-utils.la \ $(top_builddir)/src/common/argpar/libargpar.la \ $(POPT_LIBS) diff --git a/src/bin/lttng/commands/add_trigger.c b/src/bin/lttng/commands/add_trigger.c index c73154b04..cc4eb1a05 100644 --- a/src/bin/lttng/commands/add_trigger.c +++ b/src/bin/lttng/commands/add_trigger.c @@ -8,12 +8,14 @@ #include #include #include +#include #include "../command.h" #include "../loglevel.h" #include "../uprobe.h" #include "common/argpar/argpar.h" +#include "common/argpar-utils/argpar-utils.h" #include "common/dynamic-array.h" #include "common/mi-lttng.h" #include "common/string-utils/string-utils.h" @@ -651,9 +653,8 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv) { enum lttng_event_rule_type event_rule_type = LTTNG_EVENT_RULE_TYPE_UNKNOWN; - struct argpar_state *state; - struct argpar_item *item = NULL; - char *error = NULL; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; int consumed_args = -1; struct lttng_kernel_probe_location *kernel_probe_location = NULL; struct lttng_userspace_probe_location *userspace_probe_location = NULL; @@ -685,74 +686,67 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv) lttng_dynamic_pointer_array_init(&exclude_names, free); - state = argpar_state_create(*argc, *argv, event_rule_opt_descrs); - if (!state) { - ERR("Failed to allocate an argpar state."); + argpar_iter = argpar_iter_create(*argc, *argv, event_rule_opt_descrs); + if (!argpar_iter) { + ERR("Failed to allocate an argpar iter."); goto error; } while (true) { - enum argpar_state_parse_next_status status; + enum parse_next_item_status status; - ARGPAR_ITEM_DESTROY_AND_RESET(item); - status = argpar_state_parse_next(state, &item, &error); - if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { - ERR("%s", error); + status = parse_next_item(argpar_iter, &argpar_item, *argv, + false, NULL); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { goto error; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { - /* Just stop parsing here. */ - break; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { break; } - assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + assert(status == PARSE_NEXT_ITEM_STATUS_OK); - if (item->type == ARGPAR_ITEM_TYPE_OPT) { - const struct argpar_item_opt *item_opt = - (const struct argpar_item_opt *) item; + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *descr = + argpar_item_opt_descr(argpar_item); + const char *arg = argpar_item_opt_arg(argpar_item); - switch (item_opt->descr->id) { + switch (descr->id) { case OPT_TYPE: - if (!assign_event_rule_type(&event_rule_type, - item_opt->arg)) { + if (!assign_event_rule_type(&event_rule_type, arg)) { goto error; } /* Save the string for later use. */ - if (!assign_string(&event_rule_type_str, - item_opt->arg, - "--type/-t")) { + if (!assign_string(&event_rule_type_str, arg, + "--type/-t")) { goto error; } break; case OPT_LOCATION: - if (!assign_string(&location, - item_opt->arg, + if (!assign_string(&location, arg, "--location/-L")) { goto error; } break; case OPT_EVENT_NAME: - if (!assign_string(&event_name, - item_opt->arg, - "--event-name/-E")) { + if (!assign_string(&event_name, arg, + "--event-name/-E")) { goto error; } break; case OPT_FILTER: - if (!assign_string(&filter, item_opt->arg, - "--filter/-f")) { + if (!assign_string(&filter, arg, + "--filter/-f")) { goto error; } break; case OPT_NAME: - if (!assign_string(&name, item_opt->arg, - "--name/-n")) { + if (!assign_string(&name, arg, + "--name/-n")) { goto error; } @@ -763,7 +757,7 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv) ret = lttng_dynamic_pointer_array_add_pointer( &exclude_names, - strdup(item_opt->arg)); + strdup(arg)); if (ret != 0) { ERR("Failed to add pointer to dynamic pointer array."); goto error; @@ -772,8 +766,8 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv) break; } case OPT_LOG_LEVEL: - if (!assign_string(&log_level_str, - item_opt->arg, "--log-level/-l")) { + if (!assign_string(&log_level_str, arg, + "--log-level/-l")) { goto error; } @@ -781,19 +775,16 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv) case OPT_CAPTURE: { int ret; - const char *capture_str = item_opt->arg; ret = filter_parser_ctx_create_from_filter_expression( - capture_str, &parser_ctx); + arg, &parser_ctx); if (ret) { - ERR("Failed to parse capture expression `%s`.", - capture_str); + ERR("Failed to parse capture expression `%s`.", arg); goto error; } event_expr = ir_op_root_to_event_expr( - parser_ctx->ir_root, - capture_str); + parser_ctx->ir_root, arg); if (!event_expr) { /* * ir_op_root_to_event_expr has printed @@ -821,12 +812,10 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv) abort(); } } else { - const struct argpar_item_non_opt *item_non_opt = - (const struct argpar_item_non_opt *) - item; + const char *arg = argpar_item_non_opt_arg(argpar_item); /* Don't accept non-option arguments. */ - ERR("Unexpected argument '%s'", item_non_opt->arg); + ERR("Unexpected argument '%s'", arg); goto error; } } @@ -909,7 +898,7 @@ struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv) /* * Update *argc and *argv so our caller can keep parsing what follows. */ - consumed_args = argpar_state_get_ingested_orig_args(state); + consumed_args = argpar_iter_ingested_orig_args(argpar_iter); assert(consumed_args >= 0); *argc -= consumed_args; *argv += consumed_args; @@ -1341,9 +1330,8 @@ end: } lttng_event_expr_destroy(event_expr); - argpar_item_destroy(item); - free(error); - argpar_state_destroy(state); + argpar_item_destroy(argpar_item); + argpar_iter_destroy(argpar_iter); free(filter); free(name); lttng_dynamic_pointer_array_reset(&exclude_names); @@ -1539,42 +1527,38 @@ static struct lttng_action *handle_action_notify(int *argc, const char ***argv) { struct lttng_action *action = NULL; - struct argpar_state *state = NULL; - struct argpar_item *item = NULL; - char *error = NULL; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; struct lttng_rate_policy *policy = NULL; - state = argpar_state_create(*argc, *argv, notify_action_opt_descrs); - if (!state) { - ERR("Failed to allocate an argpar state."); + argpar_iter = argpar_iter_create(*argc, *argv, notify_action_opt_descrs); + if (!argpar_iter) { + ERR("Failed to allocate an argpar iter."); goto error; } while (true) { - enum argpar_state_parse_next_status status; + enum parse_next_item_status status; - ARGPAR_ITEM_DESTROY_AND_RESET(item); - status = argpar_state_parse_next(state, &item, &error); - if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { - ERR("%s", error); + status = parse_next_item(argpar_iter, &argpar_item, *argv, + false, "While parsing `notify` action:"); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { goto error; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { - /* Just stop parsing here. */ - break; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { break; } - assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + assert(status == PARSE_NEXT_ITEM_STATUS_OK); - if (item->type == ARGPAR_ITEM_TYPE_OPT) { - const struct argpar_item_opt *item_opt = - (const struct argpar_item_opt *) item; + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *descr = + argpar_item_opt_descr(argpar_item); + const char *arg = argpar_item_opt_arg(argpar_item); - switch (item_opt->descr->id) { + switch (descr->id) { case OPT_RATE_POLICY: { - policy = parse_rate_policy(item_opt->arg); + policy = parse_rate_policy(arg); if (!policy) { goto error; } @@ -1584,23 +1568,15 @@ struct lttng_action *handle_action_notify(int *argc, const char ***argv) abort(); } } else { - const struct argpar_item_non_opt *item_non_opt; - - assert(item->type == ARGPAR_ITEM_TYPE_NON_OPT); - - item_non_opt = (const struct argpar_item_non_opt *) item; + const char *arg = argpar_item_non_opt_arg(argpar_item); - switch (item_non_opt->non_opt_index) { - default: - ERR("Unexpected argument `%s`.", - item_non_opt->arg); - goto error; - } + ERR("Unexpected argument `%s`.", arg); + goto error; } } - *argc -= argpar_state_get_ingested_orig_args(state); - *argv += argpar_state_get_ingested_orig_args(state); + *argc -= argpar_iter_ingested_orig_args(argpar_iter); + *argv += argpar_iter_ingested_orig_args(argpar_iter); action = lttng_action_notify_create(); if (!action) { @@ -1623,10 +1599,9 @@ error: lttng_action_destroy(action); action = NULL; end: - free(error); lttng_rate_policy_destroy(policy); - argpar_state_destroy(state); - argpar_item_destroy(item); + argpar_item_destroy(argpar_item); + argpar_iter_destroy(argpar_iter); return action; } @@ -1646,10 +1621,9 @@ static struct lttng_action *handle_action_simple_session_with_policy(int *argc, const char *action_name) { struct lttng_action *action = NULL; - struct argpar_state *state = NULL; - struct argpar_item *item = NULL; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; const char *session_name_arg = NULL; - char *error = NULL; enum lttng_action_status action_status; struct lttng_rate_policy *policy = NULL; @@ -1661,37 +1635,34 @@ static struct lttng_action *handle_action_simple_session_with_policy(int *argc, ARGPAR_OPT_DESCR_SENTINEL }; - state = argpar_state_create(*argc, *argv, rate_policy_opt_descrs); - if (!state) { - ERR("Failed to allocate an argpar state."); + argpar_iter = argpar_iter_create(*argc, *argv, rate_policy_opt_descrs); + if (!argpar_iter) { + ERR("Failed to allocate an argpar iter."); goto error; } while (true) { - enum argpar_state_parse_next_status status; + enum parse_next_item_status status; - ARGPAR_ITEM_DESTROY_AND_RESET(item); - status = argpar_state_parse_next(state, &item, &error); - if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { - ERR("%s", error); + status = parse_next_item(argpar_iter, &argpar_item, *argv, + false, "While parsing `%s` action:", action_name); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { goto error; - } else if (status == - ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { - /* Just stop parsing here. */ - break; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { break; } - assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); - if (item->type == ARGPAR_ITEM_TYPE_OPT) { - const struct argpar_item_opt *item_opt = - (const struct argpar_item_opt *) item; + assert(status == PARSE_NEXT_ITEM_STATUS_OK); + + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *descr = + argpar_item_opt_descr(argpar_item); + const char *arg = argpar_item_opt_arg(argpar_item); - switch (item_opt->descr->id) { + switch (descr->id) { case OPT_RATE_POLICY: { - policy = parse_rate_policy(item_opt->arg); + policy = parse_rate_policy(arg); if (!policy) { goto error; } @@ -1701,23 +1672,22 @@ static struct lttng_action *handle_action_simple_session_with_policy(int *argc, abort(); } } else { - const struct argpar_item_non_opt *item_non_opt; - item_non_opt = (const struct argpar_item_non_opt *) item; + const char *arg = argpar_item_non_opt_arg(argpar_item); + unsigned int idx = argpar_item_non_opt_non_opt_index(argpar_item); - switch (item_non_opt->non_opt_index) { + switch (idx) { case 0: - session_name_arg = item_non_opt->arg; + session_name_arg = arg; break; default: - ERR("Unexpected argument `%s`.", - item_non_opt->arg); + ERR("Unexpected argument `%s`.", arg); goto error; } } } - *argc -= argpar_state_get_ingested_orig_args(state); - *argv += argpar_state_get_ingested_orig_args(state); + *argc -= argpar_iter_ingested_orig_args(argpar_iter); + *argv += argpar_iter_ingested_orig_args(argpar_iter); if (!session_name_arg) { ERR("Missing session name."); @@ -1750,11 +1720,11 @@ static struct lttng_action *handle_action_simple_session_with_policy(int *argc, error: lttng_action_destroy(action); action = NULL; - argpar_item_destroy(item); + end: lttng_rate_policy_destroy(policy); - free(error); - argpar_state_destroy(state); + argpar_item_destroy(argpar_item); + argpar_iter_destroy(argpar_iter); return action; } @@ -1805,8 +1775,8 @@ struct lttng_action *handle_action_snapshot_session(int *argc, const char ***argv) { struct lttng_action *action = NULL; - struct argpar_state *state = NULL; - struct argpar_item *item = NULL; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; const char *session_name_arg = NULL; char *snapshot_name_arg = NULL; char *ctrl_url_arg = NULL; @@ -1821,73 +1791,70 @@ struct lttng_action *handle_action_snapshot_session(int *argc, int ret; unsigned int locations_specified = 0; - state = argpar_state_create(*argc, *argv, snapshot_action_opt_descrs); - if (!state) { - ERR("Failed to allocate an argpar state."); + argpar_iter = argpar_iter_create(*argc, *argv, snapshot_action_opt_descrs); + if (!argpar_iter) { + ERR("Failed to allocate an argpar iter."); goto error; } while (true) { - enum argpar_state_parse_next_status status; + enum parse_next_item_status status; - ARGPAR_ITEM_DESTROY_AND_RESET(item); - status = argpar_state_parse_next(state, &item, &error); - if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { - ERR("%s", error); + status = parse_next_item(argpar_iter, &argpar_item, *argv, + false, "While parsing `snapshot` action:"); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { goto error; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { - /* Just stop parsing here. */ - break; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { break; } - assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + assert(status == PARSE_NEXT_ITEM_STATUS_OK); - if (item->type == ARGPAR_ITEM_TYPE_OPT) { - const struct argpar_item_opt *item_opt = - (const struct argpar_item_opt *) item; + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *descr = + argpar_item_opt_descr(argpar_item); + const char *arg = argpar_item_opt_arg(argpar_item); - switch (item_opt->descr->id) { + switch (descr->id) { case OPT_NAME: - if (!assign_string(&snapshot_name_arg, item_opt->arg, "--name/-n")) { + if (!assign_string(&snapshot_name_arg, arg, "--name/-n")) { goto error; } break; case OPT_MAX_SIZE: - if (!assign_string(&max_size_arg, item_opt->arg, "--max-size/-m")) { + if (!assign_string(&max_size_arg, arg, "--max-size/-m")) { goto error; } break; case OPT_CTRL_URL: - if (!assign_string(&ctrl_url_arg, item_opt->arg, "--ctrl-url")) { + if (!assign_string(&ctrl_url_arg, arg, "--ctrl-url")) { goto error; } break; case OPT_DATA_URL: - if (!assign_string(&data_url_arg, item_opt->arg, "--data-url")) { + if (!assign_string(&data_url_arg, arg, "--data-url")) { goto error; } break; case OPT_URL: - if (!assign_string(&url_arg, item_opt->arg, "--url")) { + if (!assign_string(&url_arg, arg, "--url")) { goto error; } break; case OPT_PATH: - if (!assign_string(&path_arg, item_opt->arg, "--path")) { + if (!assign_string(&path_arg, arg, "--path")) { goto error; } break; case OPT_RATE_POLICY: { - policy = parse_rate_policy(item_opt->arg); + policy = parse_rate_policy(arg); if (!policy) { goto error; } @@ -1897,26 +1864,22 @@ struct lttng_action *handle_action_snapshot_session(int *argc, abort(); } } else { - const struct argpar_item_non_opt *item_non_opt; - - assert(item->type == ARGPAR_ITEM_TYPE_NON_OPT); - - item_non_opt = (const struct argpar_item_non_opt *) item; + const char *arg = argpar_item_non_opt_arg(argpar_item); + unsigned int idx = argpar_item_non_opt_non_opt_index(argpar_item); - switch (item_non_opt->non_opt_index) { + switch (idx) { case 0: - session_name_arg = item_non_opt->arg; + session_name_arg = arg; break; default: - ERR("Unexpected argument `%s`.", - item_non_opt->arg); + ERR("Unexpected argument `%s`.", arg); goto error; } } } - *argc -= argpar_state_get_ingested_orig_args(state); - *argv += argpar_state_get_ingested_orig_args(state); + *argc -= argpar_iter_ingested_orig_args(argpar_iter); + *argv += argpar_iter_ingested_orig_args(argpar_iter); if (!session_name_arg) { ERR("Missing session name."); @@ -2102,8 +2065,8 @@ end: free(snapshot_output); free(max_size_arg); lttng_rate_policy_destroy(policy); - argpar_state_destroy(state); - argpar_item_destroy(item); + argpar_item_destroy(argpar_item); + argpar_iter_destroy(argpar_iter); return action; } @@ -2179,12 +2142,11 @@ int cmd_add_trigger(int argc, const char **argv) const char **my_argv = argv + 1; struct lttng_condition *condition = NULL; struct lttng_dynamic_pointer_array actions; - struct argpar_state *argpar_state = NULL; - struct argpar_item *argpar_item = NULL; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; struct lttng_action *action_list = NULL; struct lttng_action *action = NULL; struct lttng_trigger *trigger = NULL; - char *error = NULL; char *name = NULL; int i; char *owner_uid = NULL; @@ -2219,50 +2181,44 @@ int cmd_add_trigger(int argc, const char **argv) } while (true) { - enum argpar_state_parse_next_status status; - const struct argpar_item_opt *item_opt; + enum parse_next_item_status status; int ingested_args; + const struct argpar_opt_descr *descr; + const char *arg; - argpar_state_destroy(argpar_state); - argpar_state = argpar_state_create(my_argc, my_argv, + argpar_iter_destroy(argpar_iter); + argpar_iter = argpar_iter_create(my_argc, my_argv, add_trigger_options); - if (!argpar_state) { - ERR("Failed to create argpar state."); + if (!argpar_iter) { + ERR("Failed to create argpar iter."); goto error; } - ARGPAR_ITEM_DESTROY_AND_RESET(argpar_item); - status = argpar_state_parse_next(argpar_state, &argpar_item, &error); - if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { - ERR("%s", error); + status = parse_next_item(argpar_iter, &argpar_item, my_argv, + true, NULL); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { goto error; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { - ERR("%s", error); - goto error; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { break; } - assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); - - if (argpar_item->type == ARGPAR_ITEM_TYPE_NON_OPT) { - const struct argpar_item_non_opt *item_non_opt = - (const struct argpar_item_non_opt *) - argpar_item; + assert(status == PARSE_NEXT_ITEM_STATUS_OK); - ERR("Unexpected argument `%s`.", item_non_opt->arg); + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_NON_OPT) { + ERR("Unexpected argument `%s`.", + argpar_item_non_opt_arg(argpar_item)); goto error; } - item_opt = (const struct argpar_item_opt *) argpar_item; - - ingested_args = argpar_state_get_ingested_orig_args( - argpar_state); + ingested_args = argpar_iter_ingested_orig_args(argpar_iter); my_argc -= ingested_args; my_argv += ingested_args; - switch (item_opt->descr->id) { + descr = argpar_item_opt_descr(argpar_item); + arg = argpar_item_opt_arg(argpar_item); + + switch (descr->id) { case OPT_HELP: SHOW_HELP(); ret = 0; @@ -2278,7 +2234,7 @@ int cmd_add_trigger(int argc, const char **argv) goto error; } - condition = parse_condition(item_opt->arg, &my_argc, &my_argv); + condition = parse_condition(arg, &my_argc, &my_argv); if (!condition) { /* * An error message was already printed by @@ -2291,7 +2247,7 @@ int cmd_add_trigger(int argc, const char **argv) } case OPT_ACTION: { - action = parse_action(item_opt->arg, &my_argc, &my_argv); + action = parse_action(arg, &my_argc, &my_argv); if (!action) { /* * An error message was already printed by @@ -2314,7 +2270,7 @@ int cmd_add_trigger(int argc, const char **argv) } case OPT_NAME: { - if (!assign_string(&name, item_opt->arg, "--name")) { + if (!assign_string(&name, arg, "--name")) { goto error; } @@ -2322,7 +2278,7 @@ int cmd_add_trigger(int argc, const char **argv) } case OPT_OWNER_UID: { - if (!assign_string(&owner_uid, item_opt->arg, + if (!assign_string(&owner_uid, arg, "--owner-uid")) { goto error; } @@ -2456,14 +2412,13 @@ end: } cleanup: - argpar_state_destroy(argpar_state); + argpar_iter_destroy(argpar_iter); argpar_item_destroy(argpar_item); lttng_dynamic_pointer_array_reset(&actions); lttng_condition_destroy(condition); lttng_action_destroy(action_list); lttng_action_destroy(action); lttng_trigger_destroy(trigger); - free(error); free(name); free(owner_uid); if (mi_writer && mi_lttng_writer_destroy(mi_writer)) { diff --git a/src/bin/lttng/commands/list_triggers.c b/src/bin/lttng/commands/list_triggers.c index 0ef94f836..69d1e0f0a 100644 --- a/src/bin/lttng/commands/list_triggers.c +++ b/src/bin/lttng/commands/list_triggers.c @@ -10,6 +10,7 @@ #include "../command.h" #include "common/argpar/argpar.h" +#include "common/argpar-utils/argpar-utils.h" #include "common/dynamic-array.h" #include "common/mi-lttng.h" /* For lttng_condition_type_str(). */ @@ -1317,27 +1318,38 @@ static enum lttng_error_code mi_error_query_condition_callback( int cmd_list_triggers(int argc, const char **argv) { int ret; - struct argpar_parse_ret argpar_parse_ret = {}; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; struct lttng_triggers *triggers = NULL; - int i; struct mi_writer *mi_writer = NULL; - argpar_parse_ret = argpar_parse( - argc - 1, argv + 1, list_trigger_options, true); - if (!argpar_parse_ret.items) { - ERR("%s", argpar_parse_ret.error); + argc--; + argv++; + + argpar_iter = argpar_iter_create(argc, argv, list_trigger_options); + if (!argpar_iter) { + ERR("Failed to allocate an argpar iter."); goto error; } - for (i = 0; i < argpar_parse_ret.items->n_items; i++) { - const struct argpar_item *item = - argpar_parse_ret.items->items[i]; + while (true) { + enum parse_next_item_status status; + + status = parse_next_item(argpar_iter, &argpar_item, argv, + true, NULL); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { + goto error; + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { + break; + } + + assert(status == PARSE_NEXT_ITEM_STATUS_OK); - if (item->type == ARGPAR_ITEM_TYPE_OPT) { - const struct argpar_item_opt *item_opt = - (const struct argpar_item_opt *) item; + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *descr = + argpar_item_opt_descr(argpar_item); - switch (item_opt->descr->id) { + switch (descr->id) { case OPT_HELP: SHOW_HELP(); ret = 0; @@ -1354,10 +1366,8 @@ int cmd_list_triggers(int argc, const char **argv) } } else { - const struct argpar_item_non_opt *item_non_opt = - (const struct argpar_item_non_opt *) item; - - ERR("Unexpected argument: %s", item_non_opt->arg); + ERR("Unexpected argument: %s", + argpar_item_non_opt_arg(argpar_item)); } } @@ -1438,7 +1448,8 @@ error: ret = 1; end: - argpar_parse_ret_fini(&argpar_parse_ret); + argpar_item_destroy(argpar_item); + argpar_iter_destroy(argpar_iter); lttng_triggers_destroy(triggers); /* Mi clean-up. */ if (mi_writer && mi_lttng_writer_destroy(mi_writer)) { diff --git a/src/bin/lttng/commands/remove_trigger.c b/src/bin/lttng/commands/remove_trigger.c index ce0e83ec7..6f234e626 100644 --- a/src/bin/lttng/commands/remove_trigger.c +++ b/src/bin/lttng/commands/remove_trigger.c @@ -7,6 +7,7 @@ #include "../command.h" #include "common/argpar/argpar.h" +#include "common/argpar-utils/argpar-utils.h" #include "common/mi-lttng.h" #include #include @@ -61,7 +62,8 @@ int cmd_remove_trigger(int argc, const char **argv) { enum lttng_error_code ret_code; int ret; - struct argpar_parse_ret argpar_parse_ret = {}; + struct argpar_iter *argpar_iter = NULL; + const struct argpar_item *argpar_item = NULL; const char *name = NULL; int i; struct lttng_triggers *triggers = NULL; @@ -97,22 +99,34 @@ int cmd_remove_trigger(int argc, const char **argv) } } - argpar_parse_ret = argpar_parse(argc - 1, argv + 1, - remove_trigger_options, true); - if (!argpar_parse_ret.items) { - ERR("%s", argpar_parse_ret.error); + argc--; + argv++; + + argpar_iter = argpar_iter_create(argc, argv, remove_trigger_options); + if (!argpar_iter) { + ERR("Failed to allocate an argpar iter."); goto error; } - for (i = 0; i < argpar_parse_ret.items->n_items; i++) { - const struct argpar_item *item = - argpar_parse_ret.items->items[i]; + while (true) { + enum parse_next_item_status status; + + status = parse_next_item(argpar_iter, &argpar_item, argv, + true, NULL); + if (status == PARSE_NEXT_ITEM_STATUS_ERROR) { + goto error; + } else if (status == PARSE_NEXT_ITEM_STATUS_END) { + break; + } + + assert(status == PARSE_NEXT_ITEM_STATUS_OK); - if (item->type == ARGPAR_ITEM_TYPE_OPT) { - const struct argpar_item_opt *item_opt = - (const struct argpar_item_opt *) item; + if (argpar_item_type(argpar_item) == ARGPAR_ITEM_TYPE_OPT) { + const struct argpar_opt_descr *descr = + argpar_item_opt_descr(argpar_item); + const char *arg = argpar_item_opt_arg(argpar_item); - switch (item_opt->descr->id) { + switch (descr->id) { case OPT_HELP: SHOW_HELP(); ret = 0; @@ -124,7 +138,7 @@ int cmd_remove_trigger(int argc, const char **argv) goto end; case OPT_OWNER_UID: { - if (!assign_string(&owner_uid, item_opt->arg, + if (!assign_string(&owner_uid, arg, "--owner-uid")) { goto error; } @@ -134,15 +148,14 @@ int cmd_remove_trigger(int argc, const char **argv) abort(); } } else { - const struct argpar_item_non_opt *item_non_opt = - (const struct argpar_item_non_opt *) item; + const char *arg = argpar_item_non_opt_arg(argpar_item); if (name) { - ERR("Unexpected argument '%s'", item_non_opt->arg); + ERR("Unexpected argument '%s'", arg); goto error; } - name = item_non_opt->arg; + name = arg; } } @@ -251,7 +264,8 @@ end: } cleanup: - argpar_parse_ret_fini(&argpar_parse_ret); + argpar_item_destroy(argpar_item); + argpar_iter_destroy(argpar_iter); lttng_triggers_destroy(triggers); free(owner_uid); diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 5d1904d9c..2835e9f92 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -10,7 +10,8 @@ SUBDIRS = \ fd-tracker \ string-utils \ filter \ - hashtable + hashtable \ + argpar-utils # Make sure to always distribute all folders # since SUBDIRS is decided at configure time. @@ -31,7 +32,8 @@ DIST_SUBDIRS = \ fd-tracker \ bytecode \ filter \ - argpar + argpar \ + argpar-utils # Common library noinst_LTLIBRARIES = libcommon.la diff --git a/src/common/argpar-utils/Makefile.am b/src/common/argpar-utils/Makefile.am new file mode 100644 index 000000000..00d31c90f --- /dev/null +++ b/src/common/argpar-utils/Makefile.am @@ -0,0 +1,5 @@ +noinst_LTLIBRARIES = libargpar-utils.la + +libargpar_utils_la_SOURCES = \ + argpar-utils.c \ + argpar-utils.h diff --git a/src/common/argpar-utils/argpar-utils.c b/src/common/argpar-utils/argpar-utils.c new file mode 100644 index 000000000..e6572a0b2 --- /dev/null +++ b/src/common/argpar-utils/argpar-utils.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2021 Simon Marchi + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "argpar-utils.h" + +#include + +#include +#include + +#define WHILE_PARSING_ARG_N_ARG_FMT "While parsing argument #%d (`%s`): " + +/* + * Given argpar error status `status` and error `error`, return a formatted + * error message describing the error. + * + * `argv` is the argument vector that was being parsed. + * + * `context_fmt`, if non-NULL, is formatted using `args` and prepended to the + * error message. + * + * The returned string must be freed by the caller. + */ +static ATTR_FORMAT_PRINTF(3, 0) +char *format_arg_error_v(const struct argpar_error *error, + const char **argv, const char *context_fmt, va_list args) +{ + char *str = NULL; + char *str_ret = NULL; + int ret; + + if (context_fmt) { + ret = vasprintf(&str, context_fmt, args); + if (ret == -1) { + /* + * If vasprintf fails, the content of str is undefined, + * and we shouldn't try to free it. + */ + str = NULL; + goto end; + } + + ret = strutils_append_str(&str, ": "); + if (ret < 0) { + goto end; + } + } + + switch (argpar_error_type(error)) + { + case ARGPAR_ERROR_TYPE_MISSING_OPT_ARG: + { + int orig_index = argpar_error_orig_index(error); + const char *arg = argv[orig_index]; + + ret = strutils_appendf(&str, + WHILE_PARSING_ARG_N_ARG_FMT "Missing required argument for option `%s`", + orig_index + 1, arg, arg); + if (ret < 0) { + goto end; + } + + break; + } + case ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG: + { + bool is_short; + const struct argpar_opt_descr *descr = + argpar_error_opt_descr(error, &is_short); + int orig_index = argpar_error_orig_index(error); + const char *arg = argv[orig_index]; + + if (is_short) { + ret = strutils_appendf(&str, + WHILE_PARSING_ARG_N_ARG_FMT "Unexpected argument for option `-%c`", + orig_index + 1, arg, descr->short_name); + } else { + ret = strutils_appendf(&str, + WHILE_PARSING_ARG_N_ARG_FMT "Unexpected argument for option `--%s`", + orig_index + 1, arg, descr->long_name); + } + + if (ret < 0) { + goto end; + } + + break; + } + case ARGPAR_ERROR_TYPE_UNKNOWN_OPT: + { + const char *unknown_opt = argpar_error_unknown_opt_name(error); + + ret = strutils_appendf(&str, + "Unknown option `%s`", unknown_opt); + + if (ret < 0) { + goto end; + } + + break; + } + default: + abort (); + } + + str_ret = str; + str = NULL; + +end: + free(str); + return str_ret; +} + +LTTNG_HIDDEN +enum parse_next_item_status parse_next_item(struct argpar_iter *iter, + const struct argpar_item **item, const char **argv, + bool unknown_opt_is_error, const char *context_fmt, ...) +{ + enum argpar_iter_next_status status; + const struct argpar_error *error = NULL; + enum parse_next_item_status ret; + + ARGPAR_ITEM_DESTROY_AND_RESET(*item); + status = argpar_iter_next(iter, item, &error); + + switch (status) { + case ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY: + ERR("Failed to get next argpar item."); + ret = PARSE_NEXT_ITEM_STATUS_ERROR; + break; + case ARGPAR_ITER_NEXT_STATUS_ERROR: + { + va_list args; + char *err_str; + + if (argpar_error_type(error) == ARGPAR_ERROR_TYPE_UNKNOWN_OPT && + !unknown_opt_is_error) { + ret = PARSE_NEXT_ITEM_STATUS_END; + break; + } + + va_start(args, context_fmt); + err_str = format_arg_error_v(error, argv, context_fmt, args); + va_end(args); + + if (err_str) { + ERR("%s", err_str); + free(err_str); + } else { + ERR("%s", "Failed to format argpar error."); + } + + ret = PARSE_NEXT_ITEM_STATUS_ERROR; + break; + } + case ARGPAR_ITER_NEXT_STATUS_END: + ret = PARSE_NEXT_ITEM_STATUS_END; + break; + case ARGPAR_ITER_NEXT_STATUS_OK: + ret = PARSE_NEXT_ITEM_STATUS_OK; + break; + default: + abort(); + } + + argpar_error_destroy(error); + + return ret; +} diff --git a/src/common/argpar-utils/argpar-utils.h b/src/common/argpar-utils/argpar-utils.h new file mode 100644 index 000000000..358c5f64b --- /dev/null +++ b/src/common/argpar-utils/argpar-utils.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 Simon Marchi + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#ifndef COMMON_ARGPAR_UTILS_H +#define COMMON_ARGPAR_UTILS_H + +#include + +#include +#include +#include + +enum parse_next_item_status +{ + PARSE_NEXT_ITEM_STATUS_OK = 0, + PARSE_NEXT_ITEM_STATUS_END = 1, + PARSE_NEXT_ITEM_STATUS_ERROR = -1, +}; + +/* + * Parse the next argpar item using `iter`. + * + * The item in `*item` is always freed and cleared on entry. + * + * If an item is parsed successfully, return the new item in `*item` and return + * PARSE_NEXT_ITEM_STATUS_OK. + * + * If the end of the argument list is reached, return + * PARSE_NEXT_ITEM_STATUS_END. + * + * On error, print a descriptive error message and return + * PARSE_NEXT_ITEM_STATUS_ERROR. If `context_fmt` is non-NULL, it is formatted + * using the following arguments and prepended to the error message. + * + * If `unknown_opt_is_error` is true, an unknown option is considered an error. + * Otherwise, it is considered as the end of the argument list. + */ +LTTNG_HIDDEN ATTR_FORMAT_PRINTF(5, 6) +enum parse_next_item_status parse_next_item(struct argpar_iter *iter, + const struct argpar_item **item, const char **argv, + bool unknown_opt_is_error, const char *context_fmt, ...); + +#endif diff --git a/src/common/argpar/argpar.c b/src/common/argpar/argpar.c index 68bb5a426..82b561cea 100644 --- a/src/common/argpar/argpar.c +++ b/src/common/argpar/argpar.c @@ -1,10 +1,10 @@ /* * SPDX-License-Identifier: MIT * - * Copyright 2019 Philippe Proulx + * Copyright (c) 2019-2021 Philippe Proulx + * Copyright (c) 2020-2021 Simon Marchi */ -#include #include #include #include @@ -13,231 +13,203 @@ #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_REALLOC(_ptr, _type, _nmemb) \ + ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type))) -#define ARGPAR_ASSERT(_cond) assert(_cond) +#define ARGPAR_CALLOC(_type, _nmemb) \ + ((_type *) calloc((_nmemb), sizeof(_type))) -#ifdef __MINGW_PRINTF_FORMAT -# define ARGPAR_PRINTF_FORMAT __MINGW_PRINTF_FORMAT +#define ARGPAR_ZALLOC(_type) ARGPAR_CALLOC(_type, 1) + +#ifdef NDEBUG +/* + * Force usage of the assertion condition to prevent unused variable warnings + * when `assert()` are disabled by the `NDEBUG` definition. + */ +# define ARGPAR_ASSERT(_cond) ((void) sizeof((void) (_cond), 0)) #else -# define ARGPAR_PRINTF_FORMAT printf +# include +# define ARGPAR_ASSERT(_cond) assert(_cond) #endif /* - * Structure holding the argpar state between successive argpar_state_parse_next - * calls. + * An argpar iterator. * - * Created with `argpar_state_create` and destroyed with `argpar_state_destroy`. + * Such a structure contains the state of an iterator between calls to + * argpar_iter_next(). */ -struct argpar_state { +struct argpar_iter { /* - * Data provided by the user in argpar_state_create, does not change + * Data provided by the user to argpar_iter_create(); immutable * afterwards. */ - unsigned int argc; - const char * const *argv; - const struct argpar_opt_descr *descrs; + struct { + unsigned int argc; + const char * const *argv; + const struct argpar_opt_descr *descrs; + } user; /* - * Index of the argument to process in the next argpar_state_parse_next - * call. + * Index of the argument to process in the next + * argpar_iter_next() call. */ unsigned int i; - /* Counter of non-option arguments. */ + /* Counter of non-option arguments */ int non_opt_index; /* - * Short option state: if set, we are in the middle of a short option - * group, so we should resume there at the next argpar_state_parse_next + * Current character within the current short option group: if + * it's not `NULL`, the parser is within a short option group, + * therefore it must resume there in the next argpar_iter_next() * call. */ - const char *short_opt_ch; -}; + const char *short_opt_group_ch; -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); + /* Temporary character buffer which only grows */ + struct { + size_t size; + char *data; + } tmp_buf; +}; -end: - va_end(args2); - return str; -} +/* Base parsing item */ +struct argpar_item { + enum argpar_item_type type; +}; +/* Option parsing item */ +struct argpar_item_opt { + struct argpar_item base; -static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 2))) -char *argpar_asprintf(const char *fmt, ...) -{ - va_list args; - char *str; + /* Corresponding descriptor */ + const struct argpar_opt_descr *descr; - va_start(args, fmt); - str = argpar_vasprintf(fmt, args); - va_end(args); + /* Argument, or `NULL` if none; owned by this */ + char *arg; +}; - return str; -} +/* Non-option parsing item */ +struct argpar_item_non_opt { + struct argpar_item base; -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; + /* + * Complete argument, pointing to one of the entries of the + * original arguments (`argv`). + */ + const char *arg; - ARGPAR_ASSERT(str); + /* + * Index of this argument amongst all original arguments + * (`argv`). + */ + unsigned int orig_index; - va_start(args, fmt); - addendum = argpar_vasprintf(fmt, args); - va_end(args); + /* Index of this argument amongst other non-option arguments */ + unsigned int non_opt_index; +}; - if (!addendum) { - success = false; - goto end; - } +/* Parsing error */ +struct argpar_error { + /* Error type */ + enum argpar_error_type type; - new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum); - if (!new_str) { - success = false; - goto end; - } + /* Original argument index */ + unsigned int orig_index; - free(*str); - *str = new_str; + /* Name of unknown option; owned by this */ + char *unknown_opt_name; - success = true; + /* Option descriptor */ + const struct argpar_opt_descr *opt_descr; -end: - free(addendum); + /* `true` if a short option caused the error */ + bool is_short; +}; - return success; +ARGPAR_HIDDEN +enum argpar_item_type argpar_item_type(const struct argpar_item * const item) +{ + ARGPAR_ASSERT(item); + return item->type; } ARGPAR_HIDDEN -void argpar_item_destroy(struct argpar_item *item) +const struct argpar_opt_descr *argpar_item_opt_descr( + const 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; + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_OPT); + return ((const struct argpar_item_opt *) item)->descr; } -static -bool push_item(struct argpar_item_array * const array, - struct argpar_item * const item) +ARGPAR_HIDDEN +const char *argpar_item_opt_arg(const 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; + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_OPT); + return ((const struct argpar_item_opt *) item)->arg; } -static -void destroy_item_array(struct argpar_item_array * const array) +ARGPAR_HIDDEN +const char *argpar_item_non_opt_arg(const struct argpar_item * const item) { - if (array) { - unsigned int i; - - for (i = 0; i < array->n_items; i++) { - argpar_item_destroy(array->items[i]); - } + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + return ((const struct argpar_item_non_opt *) item)->arg; +} - free(array->items); - free(array); - } +ARGPAR_HIDDEN +unsigned int argpar_item_non_opt_orig_index( + const struct argpar_item * const item) +{ + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + return ((const struct argpar_item_non_opt *) item)->orig_index; } -static -struct argpar_item_array *new_item_array(void) +ARGPAR_HIDDEN +unsigned int argpar_item_non_opt_non_opt_index( + const struct argpar_item * const item) { - struct argpar_item_array *ret; - const int initial_size = 10; + ARGPAR_ASSERT(item); + ARGPAR_ASSERT(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + return ((const struct argpar_item_non_opt *) item)->non_opt_index; +} - ret = argpar_zalloc(struct argpar_item_array); - if (!ret) { +ARGPAR_HIDDEN +void argpar_item_destroy(const struct argpar_item * const item) +{ + if (!item) { goto end; } - ret->items = argpar_calloc(struct argpar_item *, initial_size); - if (!ret->items) { - goto error; - } - - ret->n_alloc = initial_size; + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt * const opt_item = + (struct argpar_item_opt *) item; - goto end; + free(opt_item->arg); + } -error: - destroy_item_array(ret); - ret = NULL; + free((void *) item); end: - return ret; + return; } +/* + * Creates and returns an option parsing item for the descriptor `descr` + * and having the argument `arg` (copied; may be `NULL`). + * + * Returns `NULL` on memory error. + */ 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); + ARGPAR_ZALLOC(struct argpar_item_opt); if (!opt_item) { goto end; @@ -263,13 +235,20 @@ end: return opt_item; } +/* + * Creates and returns a non-option parsing item for the original + * argument `arg` having the original index `orig_index` and the + * non-option index `non_opt_index`. + * + * Returns `NULL` on memory error. + */ 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); + ARGPAR_ZALLOC(struct argpar_item_non_opt); if (!non_opt_item) { goto end; @@ -284,6 +263,126 @@ end: return non_opt_item; } +/* + * If `error` is not `NULL`, sets the error `error` to a new parsing + * error object, setting its `unknown_opt_name`, `opt_descr`, and + * `is_short` members from the parameters. + * + * `unknown_opt_name` is the unknown option name without any `-` or `--` + * prefix: `is_short` controls which type of unknown option it is. + * + * Returns 0 on success (including if `error` is `NULL`) or -1 on memory + * error. + */ +static +int set_error(struct argpar_error ** const error, + enum argpar_error_type type, + const char * const unknown_opt_name, + const struct argpar_opt_descr * const opt_descr, + const bool is_short) +{ + int ret = 0; + + if (!error) { + goto end; + } + + *error = ARGPAR_ZALLOC(struct argpar_error); + if (!*error) { + goto error; + } + + (*error)->type = type; + + if (unknown_opt_name) { + (*error)->unknown_opt_name = ARGPAR_CALLOC(char, + strlen(unknown_opt_name) + 1 + (is_short ? 1 : 2)); + if (!(*error)->unknown_opt_name) { + goto error; + } + + if (is_short) { + strcpy((*error)->unknown_opt_name, "-"); + } else { + strcpy((*error)->unknown_opt_name, "--"); + } + + strcat((*error)->unknown_opt_name, unknown_opt_name); + } + + (*error)->opt_descr = opt_descr; + (*error)->is_short = is_short; + goto end; + +error: + argpar_error_destroy(*error); + ret = -1; + +end: + return ret; +} + +ARGPAR_HIDDEN +enum argpar_error_type argpar_error_type( + const struct argpar_error * const error) +{ + ARGPAR_ASSERT(error); + return error->type; +} + +ARGPAR_HIDDEN +unsigned int argpar_error_orig_index(const struct argpar_error * const error) +{ + ARGPAR_ASSERT(error); + return error->orig_index; +} + +ARGPAR_HIDDEN +const char *argpar_error_unknown_opt_name( + const struct argpar_error * const error) +{ + ARGPAR_ASSERT(error); + ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_UNKNOWN_OPT); + ARGPAR_ASSERT(error->unknown_opt_name); + return error->unknown_opt_name; +} + +ARGPAR_HIDDEN +const struct argpar_opt_descr *argpar_error_opt_descr( + const struct argpar_error * const error, bool * const is_short) +{ + ARGPAR_ASSERT(error); + ARGPAR_ASSERT(error->type == ARGPAR_ERROR_TYPE_MISSING_OPT_ARG || + error->type == ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG); + ARGPAR_ASSERT(error->opt_descr); + + if (is_short) { + *is_short = error->is_short; + } + + return error->opt_descr; +} + +ARGPAR_HIDDEN +void argpar_error_destroy(const struct argpar_error * const error) +{ + if (error) { + free(error->unknown_opt_name); + free((void *) error); + } +} + +/* + * Finds and returns the _first_ descriptor having the short option name + * `short_name` or the long option name `long_name` within the option + * descriptors `descrs`. + * + * `short_name` may be `'\0'` to not consider it. + * + * `long_name` may be `NULL` to not consider it. + * + * Returns `NULL` if no descriptor is found. + */ static const struct argpar_opt_descr *find_descr( const struct argpar_opt_descr * const descrs, @@ -307,49 +406,63 @@ end: return !descr->short_name && !descr->long_name ? NULL : descr; } +/* Return type of parse_short_opt_group() and parse_long_opt() */ 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, + PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY = -2, }; +/* + * Parses the short option group argument `short_opt_group`, starting + * where needed depending on the state of `iter`. + * + * On success, sets `*item`. + * + * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets + * `*error`. + */ static -enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, +enum parse_orig_arg_opt_ret parse_short_opt_group( + const char * const short_opt_group, const char * const next_orig_arg, const struct argpar_opt_descr * const descrs, - struct argpar_state *state, - char **error, - struct argpar_item **item) + struct argpar_iter * const iter, + struct argpar_error ** const error, + struct argpar_item ** const item) { enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; bool used_next_orig_arg = false; - - if (strlen(short_opts) == 0) { - argpar_string_append_printf(error, "Invalid argument"); - goto error; - } - - if (!state->short_opt_ch) { - state->short_opt_ch = short_opts; - } - const char *opt_arg = NULL; const struct argpar_opt_descr *descr; struct argpar_item_opt *opt_item; + ARGPAR_ASSERT(strlen(short_opt_group) != 0); + + if (!iter->short_opt_group_ch) { + iter->short_opt_group_ch = short_opt_group; + } + /* Find corresponding option descriptor */ - descr = find_descr(descrs, *state->short_opt_ch, NULL); + descr = find_descr(descrs, *iter->short_opt_group_ch, NULL); if (!descr) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; - argpar_string_append_printf(error, - "Unknown option `-%c`", *state->short_opt_ch); + const char unknown_opt_name[] = + {*iter->short_opt_group_ch, '\0'}; + + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_UNKNOWN_OPT, + unknown_opt_name, NULL, true)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + goto error; } if (descr->with_arg) { - if (state->short_opt_ch[1]) { + if (iter->short_opt_group_ch[1]) { /* `-oarg` form */ - opt_arg = &state->short_opt_ch[1]; + opt_arg = &iter->short_opt_group_ch[1]; } else { /* `-o arg` form */ opt_arg = next_orig_arg; @@ -357,15 +470,18 @@ enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, } /* - * We accept `-o ''` (empty option's argument), - * but not `-o` alone if an option's argument is - * expected. - */ - if (!opt_arg || (state->short_opt_ch[1] && strlen(opt_arg) == 0)) { - argpar_string_append_printf(error, - "Missing required argument for option `-%c`", - *state->short_opt_ch); - used_next_orig_arg = false; + * We accept `-o ''` (empty option argument), but not + * `-o` alone if an option argument is expected. + */ + if (!opt_arg || (iter->short_opt_group_ch[1] && + strlen(opt_arg) == 0)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, + NULL, descr, true)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + goto error; } } @@ -373,44 +489,49 @@ enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, /* Create and append option argument */ opt_item = create_opt_item(descr, opt_arg); if (!opt_item) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; goto error; } *item = &opt_item->base; + iter->short_opt_group_ch++; - state->short_opt_ch++; - - if (descr->with_arg || !*state->short_opt_ch) { + if (descr->with_arg || !*iter->short_opt_group_ch) { /* Option has an argument: no more options */ - state->short_opt_ch = NULL; + iter->short_opt_group_ch = NULL; if (used_next_orig_arg) { - state->i += 2; + iter->i += 2; } else { - state->i += 1; + iter->i++; } } goto end; error: - if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR; - } + ARGPAR_ASSERT(ret != PARSE_ORIG_ARG_OPT_RET_OK); end: return ret; } +/* + * Parses the long option argument `long_opt_arg`. + * + * On success, sets `*item`. + * + * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets + * `*error`. + */ 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_state *state, - char **error, - struct argpar_item **item) + struct argpar_iter * const iter, + struct argpar_error ** const error, + struct argpar_item ** const item) { - 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; @@ -422,17 +543,10 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, /* 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(error, - "Invalid argument"); - goto error; - } + ARGPAR_ASSERT(strlen(long_opt_arg) != 0); /* Find the first `=` in original argument */ eq_pos = strchr(long_opt_arg, '='); @@ -440,23 +554,31 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, 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(error, - "Invalid argument `--%s`", long_opt_arg); - goto error; + while (long_opt_name_size > iter->tmp_buf.size - 1) { + iter->tmp_buf.size *= 2; + iter->tmp_buf.data = ARGPAR_REALLOC(iter->tmp_buf.data, + char, iter->tmp_buf.size); + if (!iter->tmp_buf.data) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + goto error; + } } - memcpy(buf, long_opt_arg, long_opt_name_size); - buf[long_opt_name_size] = '\0'; - long_opt_name = buf; + memcpy(iter->tmp_buf.data, long_opt_arg, long_opt_name_size); + iter->tmp_buf.data[long_opt_name_size] = '\0'; + long_opt_name = iter->tmp_buf.data; } /* Find corresponding option descriptor */ descr = find_descr(descrs, '\0', long_opt_name); if (!descr) { - argpar_string_append_printf(error, - "Unknown option `--%s`", long_opt_name); - ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_UNKNOWN_OPT, + long_opt_name, NULL, false)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + goto error; } @@ -468,9 +590,13 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, } else { /* `--long-opt arg` style */ if (!next_orig_arg) { - argpar_string_append_printf(error, - "Missing required argument for option `--%s`", - long_opt_name); + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, + NULL, descr, false)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + goto error; } @@ -482,9 +608,13 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, * Unexpected `--opt=arg` style for a long option which * doesn't accept an argument. */ - argpar_string_append_printf(error, - "Unexpected argument for option `--%s`", - long_opt_name); + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + + if (set_error(error, ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, + NULL, descr, false)) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY; + } + goto error; } @@ -495,30 +625,36 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, } if (used_next_orig_arg) { - state->i += 2; + iter->i += 2; } else { - state->i += 1; + iter->i++; } *item = &opt_item->base; goto end; error: - if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { - ret = PARSE_ORIG_ARG_OPT_RET_ERROR; - } + ARGPAR_ASSERT(ret != PARSE_ORIG_ARG_OPT_RET_OK); end: return ret; } +/* + * Parses the original argument `orig_arg`. + * + * On success, sets `*item`. + * + * On error (except for `PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY`), sets + * `*error`. + */ 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_state *state, - char **error, - struct argpar_item **item) + struct argpar_iter * const iter, + struct argpar_error ** const error, + struct argpar_item ** const item) { enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; @@ -527,122 +663,115 @@ enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, if (orig_arg[1] == '-') { /* Long option */ ret = parse_long_opt(&orig_arg[2], - next_orig_arg, descrs, state, error, item); + next_orig_arg, descrs, iter, error, item); } else { /* Short option */ - ret = parse_short_opts(&orig_arg[1], - next_orig_arg, descrs, state, error, item); + ret = parse_short_opt_group(&orig_arg[1], + next_orig_arg, descrs, iter, error, item); } 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_state *argpar_state_create( - unsigned int argc, - const char * const *argv, +struct argpar_iter *argpar_iter_create(const unsigned int argc, + const char * const * const argv, const struct argpar_opt_descr * const descrs) { - struct argpar_state *state; + struct argpar_iter *iter = ARGPAR_ZALLOC(struct argpar_iter); - state = argpar_zalloc(struct argpar_state); - if (!state) { + if (!iter) { goto end; } - state->argc = argc; - state->argv = argv; - state->descrs = descrs; + iter->user.argc = argc; + iter->user.argv = argv; + iter->user.descrs = descrs; + iter->tmp_buf.size = 128; + iter->tmp_buf.data = ARGPAR_CALLOC(char, iter->tmp_buf.size); + if (!iter->tmp_buf.data) { + argpar_iter_destroy(iter); + iter = NULL; + goto end; + } end: - return state; + return iter; } ARGPAR_HIDDEN -void argpar_state_destroy(struct argpar_state *state) +void argpar_iter_destroy(struct argpar_iter * const iter) { - free(state); + if (iter) { + free(iter->tmp_buf.data); + free(iter); + } } ARGPAR_HIDDEN -enum argpar_state_parse_next_status argpar_state_parse_next( - struct argpar_state *state, - struct argpar_item **item, - char **error) +enum argpar_iter_next_status argpar_iter_next( + struct argpar_iter * const iter, + const struct argpar_item ** const item, + const struct argpar_error ** const error) { - enum argpar_state_parse_next_status status; + enum argpar_iter_next_status status; + enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret; + const char *orig_arg; + const char *next_orig_arg; + struct argpar_error ** const nc_error = (struct argpar_error **) error; - ARGPAR_ASSERT(state->i <= state->argc); + ARGPAR_ASSERT(iter->i <= iter->user.argc); - *error = NULL; + if (error) { + *nc_error = NULL; + } - if (state->i == state->argc) { - status = ARGPAR_STATE_PARSE_NEXT_STATUS_END; + if (iter->i == iter->user.argc) { + status = ARGPAR_ITER_NEXT_STATUS_END; goto end; } - enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret; - const char * const orig_arg = state->argv[state->i]; - const char * const next_orig_arg = - state->i < (state->argc - 1) ? state->argv[state->i + 1] : NULL; + orig_arg = iter->user.argv[iter->i]; + next_orig_arg = + iter->i < (iter->user.argc - 1) ? + iter->user.argv[iter->i + 1] : NULL; - if (orig_arg[0] != '-') { + if (strcmp(orig_arg, "-") == 0 || strcmp(orig_arg, "--") == 0 || + orig_arg[0] != '-') { /* Non-option argument */ - struct argpar_item_non_opt *non_opt_item = - create_non_opt_item(orig_arg, state->i, state->non_opt_index); + const struct argpar_item_non_opt * const non_opt_item = + create_non_opt_item(orig_arg, iter->i, + iter->non_opt_index); if (!non_opt_item) { - status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR; + status = ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY; goto end; } - state->non_opt_index++; - state->i++; - + iter->non_opt_index++; + iter->i++; *item = &non_opt_item->base; - status = ARGPAR_STATE_PARSE_NEXT_STATUS_OK; + status = ARGPAR_ITER_NEXT_STATUS_OK; goto end; } /* Option argument */ parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg, - next_orig_arg, state->descrs, state, error, item); + next_orig_arg, iter->user.descrs, iter, nc_error, + (struct argpar_item **) item); switch (parse_orig_arg_opt_ret) { case PARSE_ORIG_ARG_OPT_RET_OK: - status = ARGPAR_STATE_PARSE_NEXT_STATUS_OK; + status = ARGPAR_ITER_NEXT_STATUS_OK; break; - case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT: - status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT; - break;; case PARSE_ORIG_ARG_OPT_RET_ERROR: - prepend_while_parsing_arg_to_error( - error, state->i, orig_arg); - status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR; + if (error) { + ARGPAR_ASSERT(*error); + (*nc_error)->orig_index = iter->i; + } + status = ARGPAR_ITER_NEXT_STATUS_ERROR; + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR_MEMORY: + status = ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY; break; default: abort(); @@ -653,93 +782,8 @@ end: } ARGPAR_HIDDEN -int argpar_state_get_ingested_orig_args(struct argpar_state *state) -{ - return state->i; -} - -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) +unsigned int argpar_iter_ingested_orig_args( + const struct argpar_iter * const iter) { - struct argpar_parse_ret parse_ret = { 0 }; - struct argpar_item *item = NULL; - struct argpar_state *state = NULL; - - parse_ret.items = new_item_array(); - if (!parse_ret.items) { - parse_ret.error = strdup("Failed to create items array."); - ARGPAR_ASSERT(parse_ret.error); - goto error; - } - - state = argpar_state_create(argc, argv, descrs); - if (!state) { - parse_ret.error = strdup("Failed to create argpar state."); - ARGPAR_ASSERT(parse_ret.error); - goto error; - } - - while (true) { - enum argpar_state_parse_next_status status; - - status = argpar_state_parse_next(state, &item, &parse_ret.error); - if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { - goto error; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { - break; - } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { - if (fail_on_unknown_opt) { - parse_ret.ingested_orig_args = - argpar_state_get_ingested_orig_args(state); - prepend_while_parsing_arg_to_error( - &parse_ret.error, parse_ret.ingested_orig_args, - argv[parse_ret.ingested_orig_args]); - status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR; - goto error; - } - - free(parse_ret.error); - parse_ret.error = NULL; - break; - } - - ARGPAR_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); - - if (!push_item(parse_ret.items, item)) { - goto error; - } - item = NULL; - } - - ARGPAR_ASSERT(!parse_ret.error); - parse_ret.ingested_orig_args = - argpar_state_get_ingested_orig_args(state); - goto end; - -error: - ARGPAR_ASSERT(parse_ret.error); - - /* That's how we indicate that an error occurred */ - destroy_item_array(parse_ret.items); - parse_ret.items = NULL; - -end: - argpar_state_destroy(state); - argpar_item_destroy(item); - 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; + return iter->i; } diff --git a/src/common/argpar/argpar.h b/src/common/argpar/argpar.h index 43dbd0d56..27503c56f 100644 --- a/src/common/argpar/argpar.h +++ b/src/common/argpar/argpar.h @@ -1,314 +1,715 @@ /* * SPDX-License-Identifier: MIT * - * Copyright 2019 Philippe Proulx + * Copyright (c) 2019-2021 Philippe Proulx + * Copyright (c) 2020-2021 Simon Marchi */ -#ifndef BABELTRACE_ARGPAR_H -#define BABELTRACE_ARGPAR_H +#ifndef ARGPAR_ARGPAR_H +#define ARGPAR_ARGPAR_H #include -/* - * argpar is a library that provides facilities for argument parsing. - * - * Two APIs are available: - * - * - The iterator-style API, where you initialize a state object with - * `argpar_state_create`, then repeatedly call `argpar_state_parse_next` to - * get the arguments, until (1) there are no more arguments, (2) the parser - * encounters an error (e.g. unknown option) or (3) you get bored. This - * API gives you more control on when to stop parsing the arguments. - * - * - The parse-everything-in-one-shot-API, where you call `argpar_parse`, - * which parses the arguments until (1) there are not more arguments or - * (2) it encounters a parser error. It returns you a list of all the - * arguments it was able to parse, which you can consult at your leisure. - * - * The following describes how arguments are parsed, and applies to both APIs. - * - * argpar parses the arguments `argv` of which the count is `argc` using the - * sentinel-terminated (use `ARGPAR_OPT_DESCR_SENTINEL`) option - * descriptor array `descrs`. - * - * argpar 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 parser 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 parser accepts duplicate options (it will output one item for each - * instance). - * - * The returned items are of the type `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 items are returned 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 - * - * found items are returned in this order: option item (--hello), option item - * (--meow=23), non-option item (/path/to/file) and option item (-b). - */ +/*! +@mainpage -/* Sentinel for an option descriptor array */ -#define ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } +See the \ref api module. + +@addtogroup api argpar API +@{ + +argpar is a library which provides an iterator-based API to parse +command-line arguments. + +The argpar parser supports: + +
    +
  • + Short options without an argument, possibly tied together: + + @code{.unparsed} + -f -auf -n + @endcode + +
  • + Short options with arguments: + + @code{.unparsed} + -b 45 -f/mein/file -xyzhello + @endcode + +
  • + Long options without an argument: + + @code{.unparsed} + --five-guys --burger-king --pizza-hut --subway + @endcode + +
  • + Long options with arguments (two original arguments or a single + one with a = character): + + @code{.unparsed} + --security enable --time=18.56 + @endcode + +
  • + Non-option arguments (anything else, including + - and \--). + + 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 + - as of this version. +
+ +Create a parsing iterator with argpar_iter_create(), then repeatedly +call argpar_iter_next() to access the parsing results (items), until one +of: + +- There are no more arguments. + +- The argument parser encounters an error (for example, an unknown + option). + +- You need to stop. + +argpar_iter_create() accepts duplicate option descriptors in +\p descrs (argpar_iter_next() produces one item for each +instance). + +A parsing item (the result of argpar_iter_next()) has the type +#argpar_item. + +Get the type (option or non-option) of an item with +\link argpar_item_type(const struct argpar_item *) argpar_item_type()\endlink. +Each item type has its set of dedicated functions +(\c argpar_item_opt_ and \c argpar_item_non_opt_ prefixes). + +argpar_iter_next() produces the items in the same order that it parses +original arguments, including non-option arguments. This means, for +example, that for: + +@code{.unparsed} +--hello --count=23 /path/to/file -ab --type file -- magie +@endcode + +argpar_iter_next() produces the following items, in this order: + +-# Option item (\--hello). +-# Option item (\--count with argument 23). +-# Non-option item (/path/to/file). +-# Option item (-a). +-# Option item (-b). +-# Option item (\--type with argument file). +-# Non-option item (\--). +-# Non-option item (magie). +*/ /* - * 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". + * If argpar is used in some shared library, we don't want said library + * to export its symbols, so mark them as "hidden". * - * On Windows, symbols are local unless explicitly exported, - * see https://gcc.gnu.org/wiki/Visibility + * On Windows, symbols are local unless explicitly exported; see + * . */ #if defined(_WIN32) || defined(__CYGWIN__) -#define ARGPAR_HIDDEN +# define ARGPAR_HIDDEN #else -#define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) +# define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) #endif -/* Forward-declaration for the opaque type. */ -struct argpar_state; +struct argpar_opt_descr; -/* Option descriptor */ -struct argpar_opt_descr { - /* Numeric ID for this option */ - const int id; +/*! +@name Item API +@{ +*/ - /* 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 */ +/*! +@brief + Type of a parsing item, as returned by + \link argpar_item_type(const struct argpar_item *) argpar_item_type()\endlink. +*/ enum argpar_item_type { - /* Option */ + /// Option ARGPAR_ITEM_TYPE_OPT, - /* Non-option */ + /// Non-option ARGPAR_ITEM_TYPE_NON_OPT, }; -/* Base item */ -struct argpar_item { - enum argpar_item_type type; -}; +/*! +@struct argpar_item + +@brief + Opaque parsing item type + +argpar_iter_next() sets a pointer to such a type. +*/ +struct argpar_item; + +/*! +@brief + Returns the type of the parsing item \p item. + +@param[in] item + Parsing item of which to get the type. + +@returns + Type of \p item. + +@pre + \p item is not \c NULL. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +enum argpar_item_type argpar_item_type(const struct argpar_item *item); + +/*! +@brief + Returns the option descriptor of the option parsing item \p item. + +@param[in] item + Option parsing item of which to get the option descriptor. + +@returns + Option descriptor of \p item. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_OPT. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const struct argpar_opt_descr *argpar_item_opt_descr( + const struct argpar_item *item); + +/*! +@brief + Returns the argument of the option parsing item \p item, or + \c NULL if none. + +@param[in] item + Option parsing item of which to get the argument. + +@returns + Argument of \p item, or \c NULL if none. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_OPT. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const char *argpar_item_opt_arg(const struct argpar_item *item); + +/*! +@brief + Returns the complete original argument, pointing to one of the + entries of the original arguments (in \p argv, as passed to + argpar_iter_create()), of the non-option parsing item \p item. + +@param[in] item + Non-option parsing item of which to get the complete original + argument. + +@returns + Complete original argument of \p item. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_NON_OPT. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const char *argpar_item_non_opt_arg(const struct argpar_item *item); + +/*! +@brief + Returns the index, within \em all the original arguments (in + \p argv, as passed to argpar_iter_create()), of the non-option + parsing item \p item. + +For example, with the following command line (all options have no +argument): + +@code{.unparsed} +-f -m meow --jus mix --kilo +@endcode + +The original argument index of \c meow is 2 while the original +argument index of \c mix is 4. + +@param[in] item + Non-option parsing item of which to get the original argument index. + +@returns + Original argument index of \p item. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_NON_OPT. + +@sa + argpar_item_non_opt_non_opt_index() -- Returns the non-option index + of a non-option parsing item. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +unsigned int argpar_item_non_opt_orig_index(const struct argpar_item *item); -/* Option item */ -struct argpar_item_opt { - struct argpar_item base; +/*! +@brief + Returns the index, within the parsed non-option parsing items, of + the non-option parsing item \p item. - /* Corresponding descriptor */ - const struct argpar_opt_descr *descr; +For example, with the following command line (all options have no +argument): - /* Argument, or `NULL` if none */ - const char *arg; +@code{.unparsed} +-f -m meow --jus mix --kilo +@endcode + +The non-option index of \c meow is 0 while the original +argument index of \c mix is 1. + +@param[in] item + Non-option parsing item of which to get the non-option index. + +@returns + Non-option index of \p item. + +@pre + \p item is not \c NULL. +@pre + \p item has the type #ARGPAR_ITEM_TYPE_NON_OPT. + +@sa + argpar_item_non_opt_orig_index() -- Returns the original argument + index of a non-option parsing item. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +unsigned int argpar_item_non_opt_non_opt_index(const struct argpar_item *item); + +/*! +@brief + Destroys the parsing item \p item. + +@param[in] item + Parsing item to destroy (may be \c NULL). +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +void argpar_item_destroy(const struct argpar_item *item); + +/*! +@def ARGPAR_ITEM_DESTROY_AND_RESET(_item) + +@brief + Calls argpar_item_destroy() with \p _item, and then sets \p _item + to \c NULL. + +@param[in] _item + Item to destroy and variable to reset + (const struct argpar_item * type). +*/ +#define ARGPAR_ITEM_DESTROY_AND_RESET(_item) \ + { \ + argpar_item_destroy(_item); \ + _item = NULL; \ + } + +/// @} + +/*! +@name Error API +@{ +*/ + +/*! +@brief + Parsing error type, as returned by + \link argpar_error_type(const struct argpar_error *) argpar_error_type()\endlink. +*/ +enum argpar_error_type { + /// Unknown option error + ARGPAR_ERROR_TYPE_UNKNOWN_OPT, + + /// Missing option argument error + ARGPAR_ERROR_TYPE_MISSING_OPT_ARG, + + /// Unexpected option argument error + ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG, }; -/* Non-option item */ -struct argpar_item_non_opt { - struct argpar_item base; +/*! +@struct argpar_error + +@brief + Opaque parsing error type +*/ +struct argpar_error; + +/*! +@brief + Returns the type of the parsing error object \p error. + +@param[in] error + Parsing error of which to get the type. + +@returns + Type of \p error. + +@pre + \p error is not \c NULL. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +enum argpar_error_type argpar_error_type(const struct argpar_error *error); - /* - * Complete argument, pointing to one of the entries of the - * original arguments (`argv`). - */ - const char *arg; +/*! +@brief + Returns the index of the original argument (in \p argv, as passed to + argpar_iter_create()) for which the parsing error described by + \p error occurred. - /* Index of this argument amongst all original arguments (`argv`) */ - unsigned int orig_index; +@param[in] error + Parsing error of which to get the original argument index. - /* Index of this argument amongst other non-option arguments */ - unsigned int non_opt_index; +@returns + Original argument index of \p error. + +@pre + \p error is not \c NULL. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +unsigned int argpar_error_orig_index(const struct argpar_error *error); + +/*! +@brief + Returns the name of the unknown option for which the parsing error + described by \p error occurred. + +The returned name includes any - or \-- +prefix. + +With the long option with argument form, for example +\--mireille=deyglun, this function only returns the name +part (\--mireille in the last example). + +@param[in] error + Parsing error of which to get the name of the unknown option. + +@returns + Name of the unknown option of \p error. + +@pre + \p error is not \c NULL. +@pre + The type of \p error, as returned by + \link argpar_error_type(const struct argpar_error *) argpar_error_type()\endlink, + is #ARGPAR_ERROR_TYPE_UNKNOWN_OPT. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const char *argpar_error_unknown_opt_name(const struct argpar_error *error); + +/*! +@brief + Returns the descriptor of the option for which the parsing error + described by \p error occurred. + +@param[in] error + Parsing error of which to get the option descriptor. +@param[out] is_short + @parblock + If not \c NULL, this function sets \p *is_short to: + + - \c true if the option for which \p error occurred is a short + option. + + - \c false if the option for which \p error occurred is a long + option. + @endparblock + +@returns + Descriptor of the option of \p error. + +@pre + \p error is not \c NULL. +@pre + The type of \p error, as returned by + \link argpar_error_type(const struct argpar_error *) argpar_error_type()\endlink, + is #ARGPAR_ERROR_TYPE_MISSING_OPT_ARG or + #ARGPAR_ERROR_TYPE_UNEXPECTED_OPT_ARG. +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +const struct argpar_opt_descr *argpar_error_opt_descr( + const struct argpar_error *error, bool *is_short); + +/*! +@brief + Destroys the parsing error \p error. + +@param[in] error + Parsing error to destroy (may be \c NULL). +*/ +/// @cond hidden_macro +ARGPAR_HIDDEN +/// @endcond +void argpar_error_destroy(const struct argpar_error *error); + +/// @} + +/*! +@name Iterator API +@{ +*/ + +/*! +@brief + Option descriptor + +argpar_iter_create() accepts an array of instances of such a type, +terminated with #ARGPAR_OPT_DESCR_SENTINEL, as its \p descrs parameter. + +The typical usage is, for example: + +@code +const struct argpar_opt_descr descrs[] = { + { 0, 'd', NULL, false }, + { 1, '\0', "squeeze", true }, + { 2, 'm', "meow", true }, + ARGPAR_OPT_DESCR_SENTINEL, }; +@endcode +*/ +struct argpar_opt_descr { + /// Numeric ID, to uniquely identify this descriptor + const int id; -struct argpar_item_array { - /* Array of `struct argpar_item *`, or `NULL` on error */ - struct argpar_item **items; + /// Short option character, or '\0' + const char short_name; - /* Number of used slots in `items`. */ - unsigned int n_items; + /// Long option name (without the \-- prefix), or \c NULL + const char * const long_name; - /* Number of allocated slots in `items`. */ - unsigned int n_alloc; + /// \c true if this option has an argument + const bool with_arg; }; -/* What is returned by argpar_parse() */ -struct argpar_parse_ret { - /* Array of `struct argpar_item *`, or `NULL` on error */ - struct argpar_item_array *items; +/*! +@brief + Sentinel for an option descriptor array - /* Error string, or `NULL` if none */ - char *error; +The typical usage is, for example: - /* Number of original arguments (`argv`) ingested */ - unsigned int ingested_orig_args; +@code +const struct argpar_opt_descr descrs[] = { + { 0, 'd', NULL, false }, + { 1, '\0', "squeeze", true }, + { 2, 'm', "meow", true }, + ARGPAR_OPT_DESCR_SENTINEL, }; +@endcode +*/ +#define ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } -/* - * Parses arguments in `argv` until the end is reached or an error is - * encountered. - * - * On success, this function returns an array of items - * (field `items` of `struct argpar_parse_ret`) corresponding to each parsed - * argument. - * - * 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(). - */ +/*! +@struct argpar_iter + +@brief + Opaque argpar iterator type + +argpar_iter_create() returns a pointer to such a type. +*/ +struct argpar_iter; + +/*! +@brief + Creates and returns an argument parsing iterator to parse the + original arguments \p argv of which the count is \p argc using the + option descriptors \p descrs. + +This function initializes the returned structure, but doesn't actually +start parsing the arguments. + +argpar considers \em all the elements of \p argv, including the first +one, so that you would typically pass (argc - 1) as \p argc +and \&argv[1] as \p argv from what main() +receives, or ignore the parsing item of the first call to +argpar_iter_next(). + +\p *argv and \p *descrs must \em not change for all of: + +- The lifetime of the returned iterator (until you call + argpar_iter_destroy()). + +- The lifetime of any parsing item (until you call + argpar_item_destroy()) which argpar_iter_next() creates from the + returned iterator. + +- The lifetime of any parsing error (until you call + argpar_error_destroy()) which argpar_iter_next() creates from the + returned iterator. + +@param[in] argc + Number of original arguments to parse in \p argv. +@param[in] argv + Original arguments to parse, of which the count is \p argc. +@param[in] descrs + @parblock + Option descriptor array, terminated with #ARGPAR_OPT_DESCR_SENTINEL. + + May contain duplicate entries. + @endparblock + +@returns + New argument parsing iterator, or \c NULL on memory error. + +@pre + \p argc is greater than 0. +@pre + \p argv is not \c NULL. +@pre + The first \p argc elements of \p argv are not \c NULL. +@pre + \p descrs is not \c NULL. + +@sa + argpar_iter_destroy() -- Destroys an argument parsing iterator. +*/ +/// @cond hidden_macro ARGPAR_HIDDEN -struct argpar_parse_ret argpar_parse(unsigned int argc, +/// @endcond +struct argpar_iter *argpar_iter_create(unsigned int argc, const char * const *argv, - const struct argpar_opt_descr *descrs, - bool fail_on_unknown_opt); + const struct argpar_opt_descr *descrs); -/* - * 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); +/*! +@brief + Destroys the argument parsing iterator \p iter. -/* - * Creates an instance of `struct argpar_state`. - * - * This sets up the argpar_state structure, but does not actually - * start parsing the arguments. - * - * When you are done with it, the state must be freed with - * `argpar_state_destroy`. - */ -ARGPAR_HIDDEN -struct argpar_state *argpar_state_create( - unsigned int argc, - const char * const *argv, - const struct argpar_opt_descr * const descrs); +@param[in] iter + Argument parsing iterator to destroy (may be \c NULL). -/* - * Destroys an instance of `struct argpar_state`. - */ +@sa + argpar_iter_create() -- Creates an argument parsing iterator. +*/ +/// @cond hidden_macro ARGPAR_HIDDEN -void argpar_state_destroy(struct argpar_state *state); +/// @endcond +void argpar_iter_destroy(struct argpar_iter *iter); +/*! +@brief + Return type of argpar_iter_next(). -enum argpar_state_parse_next_status { - ARGPAR_STATE_PARSE_NEXT_STATUS_OK, - ARGPAR_STATE_PARSE_NEXT_STATUS_END, - ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT, - ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR, -}; +Error status enumerators have a negative value. +*/ +enum argpar_iter_next_status { + /// Success + ARGPAR_ITER_NEXT_STATUS_OK, -/* - * Parses and returns the next argument from `state`. - * - * On success, an item describing the argument is returned in `*item` and - * ARGPAR_STATE_PARSE_NEXT_STATUS_OK is returned. The item must be freed with - * `argpar_item_destroy`. - * - * If there are no more arguments to parse, ARGPAR_STATE_PARSE_NEXT_STATUS_END - * is returned. - * - * On failure (status codes ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT and - * ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR), an error string is returned in `*error`. - * This string must be freed with `free`. - */ -enum argpar_state_parse_next_status argpar_state_parse_next( - struct argpar_state *state, - struct argpar_item **item, - char **error); + /// End of iteration (no more original arguments to parse) + ARGPAR_ITER_NEXT_STATUS_END, -/* - * Return the number of ingested elements from argv that were required to - * produce the previously returned items. - */ + /// Parsing error + ARGPAR_ITER_NEXT_STATUS_ERROR = -1, + + /// Memory error + ARGPAR_ITER_NEXT_STATUS_ERROR_MEMORY = -12, +}; + +/*! +@brief + Sets \p *item to the next item of the argument parsing iterator + \p iter and advances \p iter. + +If there are no more original arguments to parse, this function returns +#ARGPAR_ITER_NEXT_STATUS_END. + +@param[in] iter + Argument parsing iterator from which to get the next parsing item. +@param[out] item + @parblock + On success, \p *item is the next parsing item of \p iter. + + Destroy \p *item with argpar_item_destroy(). + @endparblock +@param[out] error + @parblock + When this function returns #ARGPAR_ITER_NEXT_STATUS_ERROR, + if this parameter is not \c NULL, \p *error contains details about + the error. + + Destroy \p *error with argpar_error_destroy(). + @endparblock + +@returns + Status code. + +@pre + \p iter is not \c NULL. +@pre + \p item is not \c NULL. +*/ +/// @cond hidden_macro ARGPAR_HIDDEN -int argpar_state_get_ingested_orig_args(struct argpar_state *state); +/// @endcond +enum argpar_iter_next_status argpar_iter_next( + struct argpar_iter *iter, const struct argpar_item **item, + const struct argpar_error **error); /* - * Destroy an instance of `struct argpar_item`, as returned by - * argpar_state_parse_next. + * Returns the number of ingested elements from `argv`, as passed to + * argpar_iter_create() to create `*iter`, that were required to produce + * the previously returned items. */ + +/*! +@brief + Returns the number of ingested original arguments (in + \p argv, as passed to argpar_iter_create() to create \p iter) that + the parser ingested to produce the \em previous parsing items. + +@param[in] iter + Argument parsing iterator of which to get the number of ingested + original arguments. + +@returns + Number of original arguments which \p iter ingested. + +@pre + \p iter is not \c NULL. +*/ +/// @cond hidden_macro ARGPAR_HIDDEN -void argpar_item_destroy(struct argpar_item *item); +/// @endcond +unsigned int argpar_iter_ingested_orig_args(const struct argpar_iter *iter); -#define ARGPAR_ITEM_DESTROY_AND_RESET(_item) \ - { \ - argpar_item_destroy(_item); \ - _item = NULL; \ - } +/// @} +/// @} -#endif /* BABELTRACE_ARGPAR_H */ +#endif /* ARGPAR_ARGPAR_H */ diff --git a/src/common/string-utils/string-utils.c b/src/common/string-utils/string-utils.c index 526604d14..80b9f7350 100644 --- a/src/common/string-utils/string-utils.c +++ b/src/common/string-utils/string-utils.c @@ -7,10 +7,12 @@ #define _LGPL_SOURCE #include +#include #include #include #include #include +#include #include "string-utils.h" #include "../macros.h" @@ -392,3 +394,50 @@ int strutils_append_str(char **s, const char *append) free(old); return 0; } + +LTTNG_HIDDEN +int strutils_appendf(char **s, const char *fmt, ...) +{ + char *new_str; + size_t oldlen = (*s) ? strlen(*s) : 0; + int ret; + va_list args; + + /* Compute length of formatted string we append. */ + va_start(args, fmt); + ret = vsnprintf(NULL, 0, fmt, args); + va_end(args); + + if (ret == -1) { + goto end; + } + + /* Allocate space for old string + new string + \0. */ + new_str = calloc(oldlen + ret + 1, 1); + if (!new_str) { + ret = -ENOMEM; + goto end; + } + + /* Copy old string, if there was one. */ + if (oldlen) { + strcpy(new_str, *s); + } + + /* Format new string in-place. */ + va_start(args, fmt); + ret = vsprintf(&new_str[oldlen], fmt, args); + va_end(args); + + if (ret == -1) { + ret = -1; + goto end; + } + + free(*s); + *s = new_str; + new_str = NULL; + +end: + return ret; +} diff --git a/src/common/string-utils/string-utils.h b/src/common/string-utils/string-utils.h index 55deba38b..25f110856 100644 --- a/src/common/string-utils/string-utils.h +++ b/src/common/string-utils/string-utils.h @@ -45,4 +45,11 @@ size_t strutils_array_of_strings_len(char * const *array); LTTNG_HIDDEN int strutils_append_str(char **str, const char *append); +/* + * Like `strutils_append_str`, but the appended string is formatted using + * `fmt` and the following arguments. + */ +LTTNG_HIDDEN +int strutils_appendf(char **s, const char *fmt, ...); + #endif /* _STRING_UTILS_H */