From 4624dad0d79b63be45c5c6d7eb2920ba09746f06 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Tue, 3 Dec 2019 16:05:21 -0500 Subject: [PATCH] lttng: Add add-trigger command MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Simon Marchi Signed-off-by: Jérémie Galarneau Change-Id: Ibacbaba8f6d4ef84cd8b846a6d9abbd6c39e6ebf Depends-on: lttng-ust: I5a800fc92e588c2a6a0e26282b0ad5f31c044479 --- src/bin/lttng/Makefile.am | 5 +- src/bin/lttng/command.h | 1 + src/bin/lttng/commands/add_trigger.c | 1812 ++++++++++++++++++++++++ src/bin/lttng/commands/enable_events.c | 11 +- src/bin/lttng/lttng.c | 1 + src/common/argpar/argpar.c | 361 +++-- src/common/argpar/argpar.h | 207 ++- src/common/dynamic-array.h | 17 + 8 files changed, 2237 insertions(+), 178 deletions(-) create mode 100644 src/bin/lttng/commands/add_trigger.c diff --git a/src/bin/lttng/Makefile.am b/src/bin/lttng/Makefile.am index c630c6d5c..83d078b22 100644 --- a/src/bin/lttng/Makefile.am +++ b/src/bin/lttng/Makefile.am @@ -30,8 +30,8 @@ lttng_SOURCES = command.h conf.c conf.h commands/start.c \ commands/disable_rotation.c \ commands/clear.c \ loglevel.c loglevel.h \ - utils.c utils.h \ - lttng.c \ + commands/add_trigger.c \ + utils.c utils.h lttng.c \ uprobe.c uprobe.h lttng_CFLAGS = $(AM_CFLAGS) $(POPT_CFLAGS) @@ -41,4 +41,5 @@ 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/libargpar.la \ $(POPT_LIBS) diff --git a/src/bin/lttng/command.h b/src/bin/lttng/command.h index 8f1c7be40..8961bad01 100644 --- a/src/bin/lttng/command.h +++ b/src/bin/lttng/command.h @@ -77,6 +77,7 @@ DECL_COMMAND(rotate); DECL_COMMAND(enable_rotation); DECL_COMMAND(disable_rotation); DECL_COMMAND(clear); +DECL_COMMAND(add_trigger); extern int cmd_help(int argc, const char **argv, const struct cmd_struct commands[]); diff --git a/src/bin/lttng/commands/add_trigger.c b/src/bin/lttng/commands/add_trigger.c new file mode 100644 index 000000000..401fa762e --- /dev/null +++ b/src/bin/lttng/commands/add_trigger.c @@ -0,0 +1,1812 @@ +/* + * Copyright (C) 2021 Simon Marchi + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include + +#include "../command.h" +#include "../loglevel.h" +#include "../uprobe.h" + +#include "common/argpar/argpar.h" +#include "common/dynamic-array.h" +#include "common/string-utils/string-utils.h" +#include "common/utils.h" +/* For lttng_event_rule_type_str(). */ +#include +#include + +#if (LTTNG_SYMBOL_NAME_LEN == 256) +#define LTTNG_SYMBOL_NAME_LEN_SCANF_IS_A_BROKEN_API "255" +#endif + +#ifdef LTTNG_EMBED_HELP +static const char help_msg[] = +#include +; +#endif + +enum { + OPT_HELP, + OPT_LIST_OPTIONS, + + OPT_CONDITION, + OPT_ACTION, + OPT_ID, + OPT_FIRE_ONCE_AFTER, + OPT_FIRE_EVERY, + OPT_USER_ID, + + OPT_ALL, + OPT_FILTER, + OPT_EXCLUDE, + OPT_LOGLEVEL, + OPT_LOGLEVEL_ONLY, + + OPT_USERSPACE, + OPT_KERNEL, + OPT_LOG4J, + OPT_JUL, + OPT_PYTHON, + + OPT_FUNCTION, + OPT_PROBE, + OPT_USERSPACE_PROBE, + OPT_SYSCALL, + OPT_TRACEPOINT, + + OPT_NAME, + OPT_MAX_SIZE, + OPT_DATA_URL, + OPT_CTRL_URL, + OPT_URL, + OPT_PATH, +}; + +static const struct argpar_opt_descr event_rule_opt_descrs[] = { + { OPT_ALL, 'a', "all", false }, + { OPT_FILTER, 'f', "filter", true }, + { OPT_EXCLUDE, 'x', "exclude", true }, + { OPT_LOGLEVEL, '\0', "loglevel", true }, + { OPT_LOGLEVEL_ONLY, '\0', "loglevel-only", true }, + + /* Domains */ + { OPT_USERSPACE, 'u', "userspace", false }, + { OPT_KERNEL, 'k', "kernel", false }, + { OPT_LOG4J, 'l', "log4j", false }, + { OPT_JUL, 'j', "jul", false }, + { OPT_PYTHON, 'p', "python", false }, + + /* Event rule types */ + { OPT_FUNCTION, '\0', "function", true }, + { OPT_PROBE, '\0', "probe", true }, + { OPT_USERSPACE_PROBE, '\0', "userspace-probe", true }, + { OPT_SYSCALL, '\0', "syscall" }, + { OPT_TRACEPOINT, '\0', "tracepoint" }, + + ARGPAR_OPT_DESCR_SENTINEL +}; + +static +bool assign_domain_type(enum lttng_domain_type *dest, + enum lttng_domain_type src) +{ + bool ret; + + if (*dest == LTTNG_DOMAIN_NONE || *dest == src) { + *dest = src; + ret = true; + } else { + ERR("Multiple domains specified."); + ret = false; + } + + return ret; +} + +static +bool assign_event_rule_type(enum lttng_event_rule_type *dest, + enum lttng_event_rule_type src) +{ + bool ret; + + if (*dest == LTTNG_EVENT_RULE_TYPE_UNKNOWN || *dest == src) { + *dest = src; + ret = true; + } else { + ERR("Multiple event types specified."); + ret = false; + } + + return ret; +} + +static +bool assign_string(char **dest, const char *src, const char *opt_name) +{ + bool ret; + + if (*dest) { + ERR("Duplicate '%s' given.", opt_name); + goto error; + } + + *dest = strdup(src); + if (!*dest) { + PERROR("Failed to allocate string '%s'.", opt_name); + goto error; + } + + ret = true; + goto end; + +error: + ret = false; + +end: + return ret; +} + +/* This is defined in enable_events.c. */ +LTTNG_HIDDEN +int create_exclusion_list_and_validate(const char *event_name, + const char *exclusions_arg, + char ***exclusion_list); + +/* + * Parse `str` as a log level in domain `domain_type`. Return -1 if the string + * is not recognized as a valid log level. + */ +static +int parse_loglevel_string(const char *str, enum lttng_domain_type domain_type) +{ + switch (domain_type) { + case LTTNG_DOMAIN_UST: + { + enum lttng_loglevel loglevel; + const int ret = loglevel_name_to_value(str, &loglevel); + + return ret == -1 ? ret : (int) loglevel; + } + case LTTNG_DOMAIN_LOG4J: + { + enum lttng_loglevel_log4j loglevel; + const int ret = loglevel_log4j_name_to_value(str, &loglevel); + + return ret == -1 ? ret : (int) loglevel; + } + case LTTNG_DOMAIN_JUL: + { + enum lttng_loglevel_jul loglevel; + const int ret = loglevel_jul_name_to_value(str, &loglevel); + + return ret == -1 ? ret : (int) loglevel; + } + case LTTNG_DOMAIN_PYTHON: + { + enum lttng_loglevel_python loglevel; + const int ret = loglevel_python_name_to_value(str, &loglevel); + + return ret == -1 ? ret : (int) loglevel; + } + default: + /* Invalid domain type. */ + abort(); + } +} + +static int parse_kernel_probe_opts(const char *source, + struct lttng_kernel_probe_location **location) +{ + int ret = 0; + int match; + char s_hex[19]; + char name[LTTNG_SYMBOL_NAME_LEN]; + char *symbol_name = NULL; + uint64_t offset; + + /* Check for symbol+offset. */ + match = sscanf(source, + "%" LTTNG_SYMBOL_NAME_LEN_SCANF_IS_A_BROKEN_API + "[^'+']+%18s", + name, s_hex); + if (match == 2) { + if (*s_hex == '\0') { + ERR("Kernel probe symbol offset is missing."); + goto error; + } + + symbol_name = strndup(name, LTTNG_SYMBOL_NAME_LEN); + if (!symbol_name) { + PERROR("Failed to copy kernel probe location symbol name."); + goto error; + } + offset = strtoul(s_hex, NULL, 0); + + *location = lttng_kernel_probe_location_symbol_create( + symbol_name, offset); + if (!location) { + ERR("Failed to create symbol kernel probe location."); + goto error; + } + + goto end; + } + + /* Check for symbol. */ + if (isalpha(name[0]) || name[0] == '_') { + match = sscanf(source, + "%" LTTNG_SYMBOL_NAME_LEN_SCANF_IS_A_BROKEN_API + "s", + name); + if (match == 1) { + symbol_name = strndup(name, LTTNG_SYMBOL_NAME_LEN); + if (!symbol_name) { + ERR("Failed to copy kernel probe location symbol name."); + goto error; + } + + *location = lttng_kernel_probe_location_symbol_create( + symbol_name, 0); + if (!location) { + ERR("Failed to create symbol kernel probe location."); + goto error; + } + + goto end; + } + } + + /* Check for address. */ + match = sscanf(source, "%18s", s_hex); + if (match > 0) { + uint64_t address; + + if (*s_hex == '\0') { + ERR("Invalid kernel probe location address."); + goto error; + } + + address = strtoul(s_hex, NULL, 0); + *location = lttng_kernel_probe_location_address_create(address); + if (!location) { + ERR("Failed to create symbol kernel probe location."); + goto error; + } + + goto end; + } + +error: + /* No match */ + ret = -1; + *location = NULL; + +end: + free(symbol_name); + return ret; +} + +static struct lttng_event_rule *parse_event_rule(int *argc, const char ***argv) +{ + struct lttng_event_rule *er = NULL; + enum lttng_domain_type domain_type = LTTNG_DOMAIN_NONE; + 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; + int consumed_args = -1; + struct lttng_kernel_probe_location *kernel_probe_location = NULL; + struct lttng_userspace_probe_location *userspace_probe_location = NULL; + + /* Was the -a/--all flag provided? */ + bool all_events = false; + + /* Tracepoint name (non-option argument). */ + const char *tracepoint_name = NULL; + + /* Holds the argument of --probe / --userspace-probe. */ + char *source = NULL; + + /* Filter. */ + char *filter = NULL; + + /* Exclude. */ + char *exclude = NULL; + char **exclusion_list = NULL; + + /* Log level. */ + char *loglevel_str = NULL; + bool loglevel_only = false; + + state = argpar_state_create(*argc, *argv, event_rule_opt_descrs); + if (!state) { + ERR("Failed to allocate an argpar state."); + goto error; + } + + while (true) { + enum argpar_state_parse_next_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); + 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) { + 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; + + switch (item_opt->descr->id) { + /* Domains. */ + case OPT_USERSPACE: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_UST)) { + goto error; + } + + break; + case OPT_KERNEL: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_KERNEL)) { + goto error; + } + + break; + case OPT_LOG4J: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_LOG4J)) { + goto error; + } + + break; + case OPT_JUL: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_JUL)) { + goto error; + } + + break; + case OPT_PYTHON: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_PYTHON)) { + goto error; + } + + break; + + /* Event rule types */ + case OPT_FUNCTION: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_KRETPROBE)) { + goto error; + } + + break; + case OPT_PROBE: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_KPROBE)) { + goto error; + } + + if (!assign_string(&source, item_opt->arg, "source")) { + goto error; + } + + break; + case OPT_USERSPACE_PROBE: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_UPROBE)) { + goto error; + } + + if (!assign_string(&source, item_opt->arg, "source")) { + goto error; + } + + break; + case OPT_SYSCALL: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_SYSCALL)) { + goto error; + } + + break; + case OPT_TRACEPOINT: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_TRACEPOINT)) { + goto error; + } + + break; + case OPT_ALL: + all_events = true; + break; + case OPT_FILTER: + if (!assign_string(&filter, item_opt->arg, + "--filter/-f")) { + goto error; + } + + break; + case OPT_EXCLUDE: + if (!assign_string(&exclude, item_opt->arg, + "--exclude/-x")) { + goto error; + } + + break; + case OPT_LOGLEVEL: + case OPT_LOGLEVEL_ONLY: + if (!assign_string(&loglevel_str, item_opt->arg, + "--loglevel/--loglevel-only")) { + goto error; + } + + loglevel_only = item_opt->descr->id == + OPT_LOGLEVEL_ONLY; + break; + default: + abort(); + } + } else { + const struct argpar_item_non_opt *item_non_opt = + (const struct argpar_item_non_opt *) + item; + + /* + * Don't accept two non-option arguments/tracepoint + * names. + */ + if (tracepoint_name) { + ERR("Unexpected argument '%s'", + item_non_opt->arg); + goto error; + } + + tracepoint_name = item_non_opt->arg; + } + } + + if (event_rule_type == LTTNG_EVENT_RULE_TYPE_UNKNOWN) { + event_rule_type = LTTNG_EVENT_RULE_TYPE_TRACEPOINT; + } + + /* + * Option -a is applicable to event rules of type tracepoint and + * syscall, and it is equivalent to using "*" as the tracepoint name. + */ + if (all_events) { + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + break; + default: + ERR("Can't use -a/--all with %s event rules.", + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + + if (tracepoint_name) { + ERR("Can't provide a tracepoint name with -a/--all."); + goto error; + } + + /* In which case, it's equivalent to tracepoint name "*". */ + tracepoint_name = "*"; + } + + /* + * A tracepoint name (or -a, for the event rule types that accept it) + * is required. + */ + if (!tracepoint_name) { + ERR("Need to provide either a tracepoint name or -a/--all."); + goto error; + } + + /* + * We don't support multiple tracepoint names for now. + */ + if (strchr(tracepoint_name, ',')) { + ERR("Comma separated tracepoint names are not supported."); + goto error; + } + + /* + * Update *argc and *argv so our caller can keep parsing what follows. + */ + consumed_args = argpar_state_get_ingested_orig_args(state); + assert(consumed_args >= 0); + *argc -= consumed_args; + *argv += consumed_args; + + /* Need to specify a domain. */ + if (domain_type == LTTNG_DOMAIN_NONE) { + ERR("Please specify a domain (--kernel/--userspace/--jul/--log4j/--python)."); + goto error; + } + + /* Validate event rule type against domain. */ + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_KPROBE: + case LTTNG_EVENT_RULE_TYPE_KRETPROBE: + case LTTNG_EVENT_RULE_TYPE_UPROBE: + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + if (domain_type != LTTNG_DOMAIN_KERNEL) { + ERR("Event type not available for user-space tracing."); + goto error; + } + break; + + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + break; + + default: + abort(); + } + + /* + * Adding a filter to a probe, function or userspace-probe would be + * denied by the kernel tracer as it's not supported at the moment. We + * do an early check here to warn the user. + */ + if (filter && domain_type == LTTNG_DOMAIN_KERNEL) { + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + break; + default: + ERR("Filter expressions are not supported for %s event rules.", + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + } + + /* If --exclude/-x was passed, split it into an exclusion list. */ + if (exclude) { + if (domain_type != LTTNG_DOMAIN_UST) { + ERR("Event name exclusions are not yet implemented for %s event rules.", + get_domain_str(domain_type)); + goto error; + } + + + if (create_exclusion_list_and_validate(tracepoint_name, exclude, + &exclusion_list) != 0) { + ERR("Failed to create exclusion list."); + goto error; + } + } + + if (loglevel_str && event_rule_type != LTTNG_EVENT_RULE_TYPE_TRACEPOINT) { + ERR("Log levels are only applicable to tracepoint event rules."); + goto error; + } + + /* Finally, create the event rule object. */ + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + { + enum lttng_event_rule_status event_rule_status; + + er = lttng_event_rule_tracepoint_create(domain_type); + if (!er) { + ERR("Failed to create tracepoint event rule."); + goto error; + } + + /* Set pattern. */ + event_rule_status = lttng_event_rule_tracepoint_set_pattern( + er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set tracepoint event rule's pattern to '%s'.", + tracepoint_name); + goto error; + } + + /* Set filter. */ + if (filter) { + event_rule_status = lttng_event_rule_tracepoint_set_filter( + er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set tracepoint event rule's filter to '%s'.", + filter); + goto error; + } + } + + /* Set exclusion list. */ + if (exclusion_list) { + int n; + + for (n = 0; exclusion_list[n]; n++) { + event_rule_status = lttng_event_rule_tracepoint_add_exclusion( + er, + exclusion_list[n]); + if (event_rule_status != + LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set tracepoint exclusion list element '%s'", + exclusion_list[n]); + goto error; + } + } + } + + if (loglevel_str) { + int loglevel; + + if (domain_type == LTTNG_DOMAIN_KERNEL) { + ERR("Log levels are not supported by the kernel tracer."); + goto error; + } + + loglevel = parse_loglevel_string( + loglevel_str, domain_type); + if (loglevel < 0) { + ERR("Failed to parse `%s` as a log level.", + loglevel_str); + goto error; + } + + if (loglevel_only) { + event_rule_status = lttng_event_rule_tracepoint_set_log_level( + er, loglevel); + } else { + event_rule_status = lttng_event_rule_tracepoint_set_log_level_range_lower_bound( + er, loglevel); + } + + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set log level on event fule."); + goto error; + } + } + + break; + } + case LTTNG_EVENT_RULE_TYPE_KPROBE: + { + int ret; + enum lttng_event_rule_status event_rule_status; + + er = lttng_event_rule_kprobe_create(); + if (!er) { + ERR("Failed to create kprobe event rule."); + goto error; + } + + ret = parse_kernel_probe_opts(source, &kernel_probe_location); + if (ret) { + ERR("Failed to parse kernel probe location."); + goto error; + } + + event_rule_status = lttng_event_rule_kprobe_set_name(er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set kprobe event rule's name to '%s'.", tracepoint_name); + goto error; + } + + assert(kernel_probe_location); + event_rule_status = lttng_event_rule_kprobe_set_location(er, kernel_probe_location); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set kprobe event rule's location."); + goto error; + } + + break; + } + case LTTNG_EVENT_RULE_TYPE_UPROBE: + { + int ret; + enum lttng_event_rule_status event_rule_status; + + ret = parse_userspace_probe_opts( + source, &userspace_probe_location); + if (ret) { + ERR("Failed to parse user space probe location."); + goto error; + } + + er = lttng_event_rule_uprobe_create(); + if (!er) { + ERR("Failed to create user space probe event rule."); + goto error; + } + + event_rule_status = lttng_event_rule_uprobe_set_location( + er, userspace_probe_location); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set user space probe event rule's location."); + goto error; + } + + event_rule_status = lttng_event_rule_uprobe_set_name( + er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set user space probe event rule's name to '%s'.", + tracepoint_name); + goto error; + } + + break; + } + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + { + enum lttng_event_rule_status event_rule_status; + + er = lttng_event_rule_syscall_create(); + if (!er) { + ERR("Failed to create syscall event rule."); + goto error; + } + + event_rule_status = lttng_event_rule_syscall_set_pattern( + er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set syscall event rule's pattern to '%s'.", + tracepoint_name); + goto error; + } + + if (filter) { + event_rule_status = lttng_event_rule_syscall_set_filter( + er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set syscall event rule's filter to '%s'.", + filter); + goto error; + } + } + + break; + } + default: + abort(); + goto error; + } + + goto end; + +error: + lttng_event_rule_destroy(er); + er = NULL; + +end: + argpar_item_destroy(item); + free(error); + argpar_state_destroy(state); + free(filter); + free(exclude); + free(loglevel_str); + strutils_free_null_terminated_array_of_strings(exclusion_list); + lttng_kernel_probe_location_destroy(kernel_probe_location); + lttng_userspace_probe_location_destroy(userspace_probe_location); + return er; +} + +static +struct lttng_condition *handle_condition_event(int *argc, const char ***argv) +{ + struct lttng_event_rule *er; + struct lttng_condition *c; + + er = parse_event_rule(argc, argv); + if (!er) { + c = NULL; + goto end; + } + + c = lttng_condition_event_rule_create(er); + lttng_event_rule_destroy(er); + if (!c) { + goto end; + } + +end: + return c; +} + +static +struct lttng_condition *handle_condition_session_consumed_size(int *argc, const char ***argv) +{ + struct lttng_condition *cond = NULL; + struct argpar_state *state = NULL; + struct argpar_item *item = NULL; + const char *threshold_arg = NULL; + const char *session_name_arg = NULL; + uint64_t threshold; + char *error = NULL; + enum lttng_condition_status condition_status; + + state = argpar_state_create(*argc, *argv, event_rule_opt_descrs); + if (!state) { + ERR("Failed to allocate an argpar state."); + goto error; + } + + while (true) { + enum argpar_state_parse_next_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); + 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) { + 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; + + switch (item_opt->descr->id) { + default: + 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; + + switch (item_non_opt->non_opt_index) { + case 0: + session_name_arg = item_non_opt->arg; + break; + case 1: + threshold_arg = item_non_opt->arg; + break; + default: + ERR("Unexpected argument `%s`.", + item_non_opt->arg); + goto error; + } + } + } + + *argc -= argpar_state_get_ingested_orig_args(state); + *argv += argpar_state_get_ingested_orig_args(state); + + if (!session_name_arg) { + ERR("Missing session name argument."); + goto error; + } + + if (!threshold_arg) { + ERR("Missing threshold argument."); + goto error; + } + + if (utils_parse_size_suffix(threshold_arg, &threshold) != 0) { + ERR("Failed to parse `%s` as a size.", threshold_arg); + goto error; + } + + cond = lttng_condition_session_consumed_size_create(); + if (!cond) { + ERR("Failed to allocate a session consumed size condition."); + goto error; + } + + condition_status = lttng_condition_session_consumed_size_set_session_name( + cond, session_name_arg); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to set session consumed size condition's session name to '%s'.", + session_name_arg); + goto error; + } + + condition_status = lttng_condition_session_consumed_size_set_threshold( + cond, threshold); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to set session consumed size condition threshold."); + goto error; + } + + goto end; + +error: + lttng_condition_destroy(cond); + cond = NULL; + +end: + argpar_state_destroy(state); + argpar_item_destroy(item); + free(error); + return cond; +} + +static +struct lttng_condition *handle_condition_buffer_usage_high(int *argc, const char ***argv) +{ + ERR("High buffer usage threshold conditions are unsupported for the moment."); + return NULL; +} + +static +struct lttng_condition *handle_condition_buffer_usage_low(int *argc, const char ***argv) +{ + ERR("Low buffer usage threshold conditions are unsupported for the moment."); + return NULL; +} + +static +struct lttng_condition *handle_condition_session_rotation_ongoing(int *argc, const char ***argv) +{ + ERR("Session rotation ongoing conditions are unsupported for the moment."); + return NULL; +} + +static +struct lttng_condition *handle_condition_session_rotation_completed(int *argc, const char ***argv) +{ + ERR("Session rotation completed conditions are unsupported for the moment."); + return NULL; +} + +struct condition_descr { + const char *name; + struct lttng_condition *(*handler) (int *argc, const char ***argv); +}; + +static const +struct condition_descr condition_descrs[] = { + { "on-event", handle_condition_event }, + { "on-session-consumed-size", handle_condition_session_consumed_size }, + { "on-buffer-usage-high", handle_condition_buffer_usage_high }, + { "on-buffer-usage-low", handle_condition_buffer_usage_low }, + { "on-session-rotation-ongoing", handle_condition_session_rotation_ongoing }, + { "on-session-rotation-completed", handle_condition_session_rotation_completed }, +}; + +static +struct lttng_condition *parse_condition(int *argc, const char ***argv) +{ + int i; + struct lttng_condition *cond; + const char *condition_name; + const struct condition_descr *descr = NULL; + + if (*argc == 0) { + ERR("Missing condition name."); + goto error; + } + + condition_name = (*argv)[0]; + + (*argc)--; + (*argv)++; + + for (i = 0; i < ARRAY_SIZE(condition_descrs); i++) { + if (strcmp(condition_name, condition_descrs[i].name) == 0) { + descr = &condition_descrs[i]; + break; + } + } + + if (!descr) { + ERR("Unknown condition name '%s'", condition_name); + goto error; + } + + cond = descr->handler(argc, argv); + if (!cond) { + /* The handler has already printed an error message. */ + goto error; + } + + goto end; +error: + cond = NULL; +end: + return cond; +} + + +static +struct lttng_action *handle_action_notify(int *argc, const char ***argv) +{ + return lttng_action_notify_create(); +} + +static const struct argpar_opt_descr no_opt_descrs[] = { + ARGPAR_OPT_DESCR_SENTINEL +}; + +/* + * Generic handler for a kind of action that takes a session name as its sole + * argument. + */ + +static +struct lttng_action *handle_action_simple_session( + int *argc, const char ***argv, + struct lttng_action *(*create_action_cb)(void), + enum lttng_action_status (*set_session_name_cb)(struct lttng_action *, const char *), + const char *action_name) +{ + struct lttng_action *action = NULL; + struct argpar_state *state = NULL; + struct argpar_item *item = NULL; + const char *session_name_arg = NULL; + char *error = NULL; + enum lttng_action_status action_status; + + state = argpar_state_create(*argc, *argv, no_opt_descrs); + if (!state) { + ERR("Failed to allocate an argpar state."); + goto error; + } + + while (true) { + enum argpar_state_parse_next_status status; + const struct argpar_item_non_opt *item_non_opt; + + 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); + 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) { + break; + } + + assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + assert(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + + item_non_opt = (const struct argpar_item_non_opt *) item; + + switch (item_non_opt->non_opt_index) { + case 0: + session_name_arg = item_non_opt->arg; + break; + default: + ERR("Unexpected argument `%s`.", item_non_opt->arg); + goto error; + } + } + + *argc -= argpar_state_get_ingested_orig_args(state); + *argv += argpar_state_get_ingested_orig_args(state); + + if (!session_name_arg) { + ERR("Missing session name."); + goto error; + } + + action = create_action_cb(); + if (!action) { + ERR("Failed to allocate %s session action.", action_name); + goto error; + } + + action_status = set_session_name_cb(action, session_name_arg); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to set action %s session's session name to '%s'.", + action_name, session_name_arg); + goto error; + } + + goto end; + +error: + lttng_action_destroy(action); + action = NULL; + free(error); +end: + return action; +} + +static +struct lttng_action *handle_action_start_session(int *argc, + const char ***argv) +{ + return handle_action_simple_session(argc, argv, + lttng_action_start_session_create, + lttng_action_start_session_set_session_name, + "start"); +} + +static +struct lttng_action *handle_action_stop_session(int *argc, + const char ***argv) +{ + return handle_action_simple_session(argc, argv, + lttng_action_stop_session_create, + lttng_action_stop_session_set_session_name, + "stop"); +} + +static +struct lttng_action *handle_action_rotate_session(int *argc, + const char ***argv) +{ + return handle_action_simple_session(argc, argv, + lttng_action_rotate_session_create, + lttng_action_rotate_session_set_session_name, + "rotate"); +} + +static const struct argpar_opt_descr snapshot_action_opt_descrs[] = { + { OPT_NAME, 'n', "name", true }, + { OPT_MAX_SIZE, 'm', "max-size", true }, + { OPT_CTRL_URL, '\0', "ctrl-url", true }, + { OPT_DATA_URL, '\0', "data-url", true }, + { OPT_URL, '\0', "url", true }, + { OPT_PATH, '\0', "path", true }, + ARGPAR_OPT_DESCR_SENTINEL +}; + +static +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; + const char *session_name_arg = NULL; + char *snapshot_name_arg = NULL; + char *ctrl_url_arg = NULL; + char *data_url_arg = NULL; + char *max_size_arg = NULL; + char *url_arg = NULL; + char *path_arg = NULL; + char *error = NULL; + enum lttng_action_status action_status; + struct lttng_snapshot_output *snapshot_output = NULL; + 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."); + goto error; + } + + while (true) { + enum argpar_state_parse_next_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); + 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) { + 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; + + switch (item_opt->descr->id) { + case OPT_NAME: + if (!assign_string(&snapshot_name_arg, item_opt->arg, "--name/-n")) { + goto error; + } + + break; + case OPT_MAX_SIZE: + if (!assign_string(&max_size_arg, item_opt->arg, "--max-size/-m")) { + goto error; + } + + break; + case OPT_CTRL_URL: + if (!assign_string(&ctrl_url_arg, item_opt->arg, "--ctrl-url")) { + goto error; + } + + break; + case OPT_DATA_URL: + if (!assign_string(&data_url_arg, item_opt->arg, "--data-url")) { + goto error; + } + + break; + case OPT_URL: + if (!assign_string(&url_arg, item_opt->arg, "--url")) { + goto error; + } + + break; + case OPT_PATH: + if (!assign_string(&path_arg, item_opt->arg, "--path")) { + goto error; + } + + break; + default: + 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; + + switch (item_non_opt->non_opt_index) { + case 0: + session_name_arg = item_non_opt->arg; + break; + default: + ERR("Unexpected argument `%s`.", + item_non_opt->arg); + goto error; + } + } + } + + *argc -= argpar_state_get_ingested_orig_args(state); + *argv += argpar_state_get_ingested_orig_args(state); + + if (!session_name_arg) { + ERR("Missing session name."); + goto error; + } + + /* --ctrl-url and --data-url must come in pair. */ + if (ctrl_url_arg && !data_url_arg) { + ERR("--ctrl-url is specified, but --data-url is missing."); + goto error; + } + + if (!ctrl_url_arg && data_url_arg) { + ERR("--data-url is specified, but --ctrl-url is missing."); + goto error; + } + + locations_specified += !!(ctrl_url_arg || data_url_arg); + locations_specified += !!url_arg; + locations_specified += !!path_arg; + + /* --ctrl-url/--data-url, --url and --path are mutually exclusive. */ + if (locations_specified > 1) { + ERR("The --ctrl-url/--data-url, --url, and --path options can't be used together."); + goto error; + } + + /* + * Did the user specify an option that implies using a + * custom/unregistered output? + */ + if (url_arg || ctrl_url_arg || path_arg) { + snapshot_output = lttng_snapshot_output_create(); + if (!snapshot_output) { + ERR("Failed to allocate a snapshot output."); + goto error; + } + } + + action = lttng_action_snapshot_session_create(); + if (!action) { + ERR("Failed to allocate snapshot session action."); + goto error; + } + + action_status = lttng_action_snapshot_session_set_session_name( + action, session_name_arg); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to set action snapshot session's session name to '%s'.", + session_name_arg); + goto error; + } + + if (snapshot_name_arg) { + if (!snapshot_output) { + ERR("Can't provide a snapshot output name without a snapshot output destination."); + goto error; + } + + ret = lttng_snapshot_output_set_name( + snapshot_name_arg, snapshot_output); + if (ret != 0) { + ERR("Failed to set name of snapshot output."); + goto error; + } + } + + if (max_size_arg) { + uint64_t max_size; + + if (!snapshot_output) { + ERR("Can't provide a snapshot output max size without a snapshot output destination."); + goto error; + } + + ret = utils_parse_size_suffix(max_size_arg, &max_size); + if (ret != 0) { + ERR("Failed to parse `%s` as a size.", max_size_arg); + goto error; + } + + ret = lttng_snapshot_output_set_size(max_size, snapshot_output); + if (ret != 0) { + ERR("Failed to set snapshot output's max size to %" PRIu64 " bytes.", + max_size); + goto error; + } + } + + if (url_arg) { + int num_uris; + struct lttng_uri *uris; + + if (!strstr(url_arg, "://")) { + ERR("Failed to parse '%s' as an URL.", url_arg); + goto error; + } + + num_uris = uri_parse_str_urls(url_arg, NULL, &uris); + if (num_uris < 1) { + ERR("Failed to parse '%s' as an URL.", url_arg); + goto error; + } + + if (uris[0].dtype == LTTNG_DST_PATH) { + ret = lttng_snapshot_output_set_local_path( + uris[0].dst.path, snapshot_output); + free(uris); + if (ret != 0) { + ERR("Failed to assign '%s' as a local destination.", + url_arg); + goto error; + } + } else { + ret = lttng_snapshot_output_set_network_url( + url_arg, snapshot_output); + free(uris); + if (ret != 0) { + ERR("Failed to assign '%s' as a network URL.", + url_arg); + goto error; + } + } + } + + if (path_arg) { + ret = lttng_snapshot_output_set_local_path( + path_arg, snapshot_output); + if (ret != 0) { + ERR("Failed to parse '%s' as a local path.", path_arg); + goto error; + } + } + + if (ctrl_url_arg) { + /* + * Two argument form, network output with separate control and + * data URLs. + */ + ret = lttng_snapshot_output_set_network_urls( + ctrl_url_arg, data_url_arg, snapshot_output); + if (ret != 0) { + ERR("Failed to parse `%s` and `%s` as control and data URLs.", + ctrl_url_arg, data_url_arg); + goto error; + } + } + + if (snapshot_output) { + action_status = lttng_action_snapshot_session_set_output( + action, snapshot_output); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to set snapshot session action's output."); + goto error; + } + + /* Ownership of `snapshot_output` has been transferred to the action. */ + snapshot_output = NULL; + } + + goto end; + +error: + lttng_action_destroy(action); + action = NULL; + free(error); +end: + free(snapshot_name_arg); + free(path_arg); + free(url_arg); + free(ctrl_url_arg); + free(data_url_arg); + free(snapshot_output); + argpar_state_destroy(state); + return action; +} + +struct action_descr { + const char *name; + struct lttng_action *(*handler) (int *argc, const char ***argv); +}; + +static const +struct action_descr action_descrs[] = { + { "notify", handle_action_notify }, + { "start-session", handle_action_start_session }, + { "stop-session", handle_action_stop_session }, + { "rotate-session", handle_action_rotate_session }, + { "snapshot-session", handle_action_snapshot_session }, +}; + +static +struct lttng_action *parse_action(int *argc, const char ***argv) +{ + int i; + struct lttng_action *action; + const char *action_name; + const struct action_descr *descr = NULL; + + if (*argc == 0) { + ERR("Missing action name."); + goto error; + } + + action_name = (*argv)[0]; + + (*argc)--; + (*argv)++; + + for (i = 0; i < ARRAY_SIZE(action_descrs); i++) { + if (strcmp(action_name, action_descrs[i].name) == 0) { + descr = &action_descrs[i]; + break; + } + } + + if (!descr) { + ERR("Unknown action name: %s", action_name); + goto error; + } + + action = descr->handler(argc, argv); + if (!action) { + /* The handler has already printed an error message. */ + goto error; + } + + goto end; +error: + action = NULL; +end: + return action; +} + +static const +struct argpar_opt_descr add_trigger_options[] = { + { OPT_HELP, 'h', "help", false }, + { OPT_LIST_OPTIONS, '\0', "list-options", false }, + { OPT_CONDITION, '\0', "condition", false }, + { OPT_ACTION, '\0', "action", false }, + { OPT_ID, '\0', "id", true }, + { OPT_FIRE_ONCE_AFTER, '\0', "fire-once-after", true }, + { OPT_FIRE_EVERY, '\0', "fire-every", true }, + { OPT_USER_ID, '\0', "user-id", true }, + ARGPAR_OPT_DESCR_SENTINEL, +}; + +static +void lttng_actions_destructor(void *p) +{ + struct lttng_action *action = p; + + lttng_action_destroy(action); +} + +int cmd_add_trigger(int argc, const char **argv) +{ + int ret; + int my_argc = argc - 1; + 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 lttng_action *action_group = NULL; + struct lttng_action *action = NULL; + struct lttng_trigger *trigger = NULL; + char *error = NULL; + char *id = NULL; + int i; + char *fire_once_after_str = NULL; + char *fire_every_str = NULL; + char *user_id = NULL; + + lttng_dynamic_pointer_array_init(&actions, lttng_actions_destructor); + + while (true) { + enum argpar_state_parse_next_status status; + const struct argpar_item_opt *item_opt; + int ingested_args; + + argpar_state_destroy(argpar_state); + argpar_state = argpar_state_create(my_argc, my_argv, + add_trigger_options); + if (!argpar_state) { + ERR("Failed to create argpar state."); + 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); + 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) { + 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; + + ERR("Unexpected argument `%s`.", item_non_opt->arg); + goto error; + } + + item_opt = (const struct argpar_item_opt *) argpar_item; + + ingested_args = argpar_state_get_ingested_orig_args( + argpar_state); + + my_argc -= ingested_args; + my_argv += ingested_args; + + switch (item_opt->descr->id) { + case OPT_HELP: + SHOW_HELP(); + ret = 0; + goto end; + case OPT_LIST_OPTIONS: + list_cmd_options_argpar(stdout, add_trigger_options); + ret = 0; + goto end; + case OPT_CONDITION: + { + if (condition) { + ERR("A --condition was already given."); + goto error; + } + + condition = parse_condition(&my_argc, &my_argv); + if (!condition) { + /* + * An error message was already printed by + * parse_condition. + */ + goto error; + } + + break; + } + case OPT_ACTION: + { + action = parse_action(&my_argc, &my_argv); + if (!action) { + /* + * An error message was already printed by + * parse_condition. + */ + goto error; + } + + ret = lttng_dynamic_pointer_array_add_pointer( + &actions, action); + if (ret) { + ERR("Failed to add pointer to pointer array."); + goto error; + } + + /* Ownership of the action was transferred to the group. */ + action = NULL; + + break; + } + case OPT_ID: + { + if (!assign_string(&id, item_opt->arg, "--id")) { + goto error; + } + + break; + } + case OPT_FIRE_ONCE_AFTER: + { + if (!assign_string(&fire_once_after_str, item_opt->arg, + "--fire-once-after")) { + goto error; + } + + break; + } + case OPT_FIRE_EVERY: + { + if (!assign_string(&fire_every_str, item_opt->arg, + "--fire-every")) { + goto error; + } + + break; + } + case OPT_USER_ID: + { + if (!assign_string(&user_id, item_opt->arg, + "--user-id")) { + goto error; + } + + break; + } + default: + abort(); + } + } + + if (!condition) { + ERR("Missing --condition."); + goto error; + } + + if (lttng_dynamic_pointer_array_get_count(&actions) == 0) { + ERR("Need at least one --action."); + goto error; + } + + if (fire_every_str && fire_once_after_str) { + ERR("Can't specify both --fire-once-after and --fire-every."); + goto error; + } + + action_group = lttng_action_group_create(); + if (!action_group) { + goto error; + } + + for (i = 0; i < lttng_dynamic_pointer_array_get_count(&actions); i++) { + enum lttng_action_status status; + + action = lttng_dynamic_pointer_array_steal_pointer(&actions, i); + + status = lttng_action_group_add_action(action_group, action); + if (status != LTTNG_ACTION_STATUS_OK) { + goto error; + } + + /* + * The `lttng_action_group_add_action()` takes a reference to + * the action. We can destroy ours. + */ + lttng_action_destroy(action); + action = NULL; + } + + trigger = lttng_trigger_create(condition, action_group); + if (!trigger) { + goto error; + } + + if (id) { + enum lttng_trigger_status trigger_status = + lttng_trigger_set_name(trigger, id); + + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + ERR("Failed to set trigger id."); + goto error; + } + } + + if (fire_once_after_str) { + unsigned long long threshold; + enum lttng_trigger_status trigger_status; + + if (utils_parse_unsigned_long_long(fire_once_after_str, &threshold) != 0) { + ERR("Failed to parse `%s` as an integer.", fire_once_after_str); + goto error; + } + + trigger_status = lttng_trigger_set_firing_policy(trigger, + LTTNG_TRIGGER_FIRING_POLICY_ONCE_AFTER_N, + threshold); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + ERR("Failed to set trigger's policy to `fire once after N`."); + goto error; + } + } + + if (fire_every_str) { + unsigned long long threshold; + enum lttng_trigger_status trigger_status; + + if (utils_parse_unsigned_long_long(fire_every_str, &threshold) != 0) { + ERR("Failed to parse `%s` as an integer.", fire_every_str); + goto error; + } + + trigger_status = lttng_trigger_set_firing_policy(trigger, + LTTNG_TRIGGER_FIRING_POLICY_EVERY_N, threshold); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + ERR("Failed to set trigger's policy to `fire every N`."); + goto error; + } + } + + if (user_id) { + enum lttng_trigger_status trigger_status; + char *end; + long long uid; + + errno = 0; + uid = strtol(user_id, &end, 10); + if (end == user_id || *end != '\0' || errno != 0) { + ERR("Failed to parse `%s` as a user id.", user_id); + } + + trigger_status = lttng_trigger_set_owner_uid(trigger, uid); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + ERR("Failed to set trigger's user identity."); + goto error; + } + } + + ret = lttng_register_trigger(trigger); + if (ret) { + ERR("Failed to register trigger: %s.", lttng_strerror(ret)); + goto error; + } + + MSG("Trigger registered successfully."); + + goto end; + +error: + ret = 1; + +end: + argpar_state_destroy(argpar_state); + argpar_item_destroy(argpar_item); + lttng_dynamic_pointer_array_reset(&actions); + lttng_condition_destroy(condition); + lttng_action_destroy(action_group); + lttng_action_destroy(action); + lttng_trigger_destroy(trigger); + free(error); + free(id); + free(fire_once_after_str); + free(fire_every_str); + free(user_id); + return ret; +} diff --git a/src/bin/lttng/commands/enable_events.c b/src/bin/lttng/commands/enable_events.c index 9d05a9a26..627b7ebf9 100644 --- a/src/bin/lttng/commands/enable_events.c +++ b/src/bin/lttng/commands/enable_events.c @@ -323,7 +323,16 @@ end: return ret; } -static +/* + * FIXME: find a good place to declare this since add trigger also uses it + */ +LTTNG_HIDDEN +int create_exclusion_list_and_validate(const char *event_name, + const char *exclusions_arg, + char ***exclusion_list); + + +LTTNG_HIDDEN int create_exclusion_list_and_validate(const char *event_name, const char *exclusions_arg, char ***exclusion_list) diff --git a/src/bin/lttng/lttng.c b/src/bin/lttng/lttng.c index 4907a43ba..e760bdbe1 100644 --- a/src/bin/lttng/lttng.c +++ b/src/bin/lttng/lttng.c @@ -65,6 +65,7 @@ static struct option long_options[] = { /* First level command */ static struct cmd_struct commands[] = { { "add-context", cmd_add_context}, + { "add-trigger", cmd_add_trigger}, { "create", cmd_create}, { "clear", cmd_clear}, { "destroy", cmd_destroy}, diff --git a/src/common/argpar/argpar.c b/src/common/argpar/argpar.c index e3d392f0d..bf19808c5 100644 --- a/src/common/argpar/argpar.c +++ b/src/common/argpar/argpar.c @@ -25,6 +25,38 @@ # define ARGPAR_PRINTF_FORMAT printf #endif +/* + * Structure holding the argpar state between successive argpar_state_parse_next + * calls. + * + * Created with `argpar_state_create` and destroyed with `argpar_state_destroy`. + */ +struct argpar_state { + /* + * Data provided by the user in argpar_state_create, does not change + * afterwards. + */ + unsigned int argc; + const char * const *argv; + const struct argpar_opt_descr *descrs; + + /* + * Index of the argument to process in the next argpar_state_parse_next + * call. + */ + unsigned int i; + + /* 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 + * call. + */ + const char *short_opt_ch; +}; + static __attribute__((format(ARGPAR_PRINTF_FORMAT, 1, 0))) char *argpar_vasprintf(const char *fmt, va_list args) { @@ -104,8 +136,8 @@ end: return success; } -static -void destroy_item(struct argpar_item * const item) +ARGPAR_HIDDEN +void argpar_item_destroy(struct argpar_item *item) { if (!item) { goto end; @@ -163,7 +195,7 @@ void destroy_item_array(struct argpar_item_array * const array) unsigned int i; for (i = 0; i < array->n_items; i++) { - destroy_item(array->items[i]); + argpar_item_destroy(array->items[i]); } free(array->items); @@ -224,7 +256,7 @@ struct argpar_item_opt *create_opt_item( goto end; error: - destroy_item(&opt_item->base); + argpar_item_destroy(&opt_item->base); opt_item = NULL; end: @@ -285,72 +317,78 @@ 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) + struct argpar_state *state, + char **error, + struct argpar_item **item) { enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; - const char *short_opt_ch = short_opts; + bool used_next_orig_arg = false; if (strlen(short_opts) == 0) { - argpar_string_append_printf(&parse_ret->error, "Invalid argument"); + argpar_string_append_printf(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; + if (!state->short_opt_ch) { + state->short_opt_ch = short_opts; + } - /* 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; - } + const char *opt_arg = NULL; + const struct argpar_opt_descr *descr; + struct argpar_item_opt *opt_item; - 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; - } + /* Find corresponding option descriptor */ + descr = find_descr(descrs, *state->short_opt_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); + goto error; + } - /* - * 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; - } + if (descr->with_arg) { + if (state->short_opt_ch[1]) { + /* `-oarg` form */ + opt_arg = &state->short_opt_ch[1]; + } else { + /* `-o arg` form */ + 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) { + /* + * 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; goto error; } + } - if (!push_item(parse_ret->items, &opt_item->base)) { - goto error; - } + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + goto error; + } - if (descr->with_arg) { - /* Option has an argument: no more options */ - break; - } + *item = &opt_item->base; - /* Go to next short option */ - short_opt_ch++; + state->short_opt_ch++; + + if (descr->with_arg || !*state->short_opt_ch) { + /* Option has an argument: no more options */ + state->short_opt_ch = NULL; + + if (used_next_orig_arg) { + state->i += 2; + } else { + state->i += 1; + } } goto end; @@ -368,13 +406,15 @@ 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) + struct argpar_state *state, + char **error, + struct argpar_item **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; + bool used_next_orig_arg = false; /* Option's argument, if any */ const char *opt_arg = NULL; @@ -389,7 +429,7 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, const char *long_opt_name = long_opt_arg; if (strlen(long_opt_arg) == 0) { - argpar_string_append_printf(&parse_ret->error, + argpar_string_append_printf(error, "Invalid argument"); goto error; } @@ -401,7 +441,7 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, /* Isolate the option name */ if (long_opt_name_size > max_len) { - argpar_string_append_printf(&parse_ret->error, + argpar_string_append_printf(error, "Invalid argument `--%s`", long_opt_arg); goto error; } @@ -414,7 +454,7 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, /* Find corresponding option descriptor */ descr = find_descr(descrs, '\0', long_opt_name); if (!descr) { - argpar_string_append_printf(&parse_ret->error, + argpar_string_append_printf(error, "Unknown option `--%s`", long_opt_name); ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; goto error; @@ -428,14 +468,14 @@ 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(&parse_ret->error, + argpar_string_append_printf(error, "Missing required argument for option `--%s`", long_opt_name); goto error; } opt_arg = next_orig_arg; - *used_next_orig_arg = true; + used_next_orig_arg = true; } } @@ -445,10 +485,13 @@ enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, goto error; } - if (!push_item(parse_ret->items, &opt_item->base)) { - goto error; + if (used_next_orig_arg) { + state->i += 2; + } else { + state->i += 1; } + *item = &opt_item->base; goto end; error: @@ -464,8 +507,9 @@ 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) + struct argpar_state *state, + char **error, + struct argpar_item **item) { enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; @@ -474,13 +518,11 @@ 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, parse_ret, - used_next_orig_arg); + next_orig_arg, descrs, state, error, item); } else { /* Short option */ ret = parse_short_opts(&orig_arg[1], - next_orig_arg, descrs, parse_ret, - used_next_orig_arg); + next_orig_arg, descrs, state, error, item); } return ret; @@ -511,6 +553,102 @@ end: return success; } +ARGPAR_HIDDEN +struct argpar_state *argpar_state_create( + unsigned int argc, + const char * const *argv, + const struct argpar_opt_descr * const descrs) +{ + struct argpar_state *state; + + state = argpar_zalloc(struct argpar_state); + if (!state) { + goto end; + } + + state->argc = argc; + state->argv = argv; + state->descrs = descrs; + +end: + return state; +} + +ARGPAR_HIDDEN +void argpar_state_destroy(struct argpar_state *state) +{ + free(state); +} + +ARGPAR_HIDDEN +enum argpar_state_parse_next_status argpar_state_parse_next( + struct argpar_state *state, + struct argpar_item **item, + char **error) +{ + enum argpar_state_parse_next_status status; + + ARGPAR_ASSERT(state->i <= state->argc); + + *error = NULL; + + if (state->i == state->argc) { + status = ARGPAR_STATE_PARSE_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; + + if (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); + + if (!non_opt_item) { + status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR; + goto end; + } + + state->non_opt_index++; + state->i++; + + *item = &non_opt_item->base; + status = ARGPAR_STATE_PARSE_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); + switch (parse_orig_arg_opt_ret) { + case PARSE_ORIG_ARG_OPT_RET_OK: + status = ARGPAR_STATE_PARSE_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; + break; + default: + abort(); + } + +end: + return status; +} + +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, @@ -518,87 +656,70 @@ struct argpar_parse_ret argpar_parse(unsigned int argc, bool fail_on_unknown_opt) { struct argpar_parse_ret parse_ret = { 0 }; - unsigned int i; - unsigned int non_opt_index = 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; } - 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; - } + 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; + } - continue; - } + while (true) { + enum argpar_state_parse_next_status status; - /* 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: + 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; - case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT: - ARGPAR_ASSERT(!used_next_orig_arg); - + } 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, i, orig_arg); + &parse_ret.error, parse_ret.ingested_orig_args, + argv[parse_ret.ingested_orig_args]); + status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR; 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(); + break; } - if (used_next_orig_arg) { - i++; + ARGPAR_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + + if (!push_item(parse_ret.items, item)) { + goto error; } + item = NULL; } - parse_ret.ingested_orig_args = argc; - free(parse_ret.error); - parse_ret.error = 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; } diff --git a/src/common/argpar/argpar.h b/src/common/argpar/argpar.h index 00334cd6f..43dbd0d56 100644 --- a/src/common/argpar/argpar.h +++ b/src/common/argpar/argpar.h @@ -9,6 +9,77 @@ #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). + */ + /* Sentinel for an option descriptor array */ #define ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } @@ -25,6 +96,9 @@ #define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) #endif +/* Forward-declaration for the opaque type. */ +struct argpar_state; + /* Option descriptor */ struct argpar_opt_descr { /* Numeric ID for this option */ @@ -106,66 +180,20 @@ struct argpar_parse_ret { }; /* - * 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). + * Parses arguments in `argv` until the end is reached or an error is + * encountered. * * 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. + * (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 + * 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 * @@ -214,4 +242,73 @@ struct argpar_parse_ret argpar_parse(unsigned int argc, ARGPAR_HIDDEN void argpar_parse_ret_fini(struct argpar_parse_ret *ret); +/* + * 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); + +/* + * Destroys an instance of `struct argpar_state`. + */ +ARGPAR_HIDDEN +void argpar_state_destroy(struct argpar_state *state); + + +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, +}; + +/* + * 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); + +/* + * Return the number of ingested elements from argv that were required to + * produce the previously returned items. + */ +ARGPAR_HIDDEN +int argpar_state_get_ingested_orig_args(struct argpar_state *state); + +/* + * Destroy an instance of `struct argpar_item`, as returned by + * argpar_state_parse_next. + */ +ARGPAR_HIDDEN +void argpar_item_destroy(struct argpar_item *item); + +#define ARGPAR_ITEM_DESTROY_AND_RESET(_item) \ + { \ + argpar_item_destroy(_item); \ + _item = NULL; \ + } + + #endif /* BABELTRACE_ARGPAR_H */ diff --git a/src/common/dynamic-array.h b/src/common/dynamic-array.h index 0b488a39c..71cf9af18 100644 --- a/src/common/dynamic-array.h +++ b/src/common/dynamic-array.h @@ -140,6 +140,23 @@ void *lttng_dynamic_pointer_array_get_pointer( return *element; } +/* + * Returns the pointer at index `index`, sets the array slot to NULL. Does not + * run the destructor. + */ + +static inline +void *lttng_dynamic_pointer_array_steal_pointer( + struct lttng_dynamic_pointer_array *array, size_t index) +{ + void **p_element = lttng_dynamic_array_get_element(&array->array, index); + void *element = *p_element; + + *p_element = NULL; + + return element; +} + /* * Add a pointer to the end of a dynamic pointer array. The array's element * count is increased by one and its underlying capacity is adjusted -- 2.34.1