lttng: Add add-trigger command
authorSimon Marchi <simon.marchi@efficios.com>
Tue, 3 Dec 2019 21:05:21 +0000 (16:05 -0500)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Wed, 24 Feb 2021 20:47:39 +0000 (15:47 -0500)
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Change-Id: Ibacbaba8f6d4ef84cd8b846a6d9abbd6c39e6ebf
Depends-on: lttng-ust: I5a800fc92e588c2a6a0e26282b0ad5f31c044479

src/bin/lttng/Makefile.am
src/bin/lttng/command.h
src/bin/lttng/commands/add_trigger.c [new file with mode: 0644]
src/bin/lttng/commands/enable_events.c
src/bin/lttng/lttng.c
src/common/argpar/argpar.c
src/common/argpar/argpar.h
src/common/dynamic-array.h

index c630c6d5cb3ad963052dcd9bc229f7e5954c2b6e..83d078b22df3a5ec852e8aa882a57b90f9dc66a6 100644 (file)
@@ -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)
index 8f1c7be408667822d35b45821c965f7841ab3670..8961bad015939658b4f712534e9b899977c7d4d3 100644 (file)
@@ -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 (file)
index 0000000..401fa76
--- /dev/null
@@ -0,0 +1,1812 @@
+/*
+ * Copyright (C) 2021 Simon Marchi <simon.marchi@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+
+#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 <lttng/event-rule/event-rule-internal.h>
+#include <lttng/lttng.h>
+
+#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 <lttng-add-trigger.1.h>
+;
+#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;
+}
index 9d05a9a2613517ac1ec15396479667666f8d7bfd..627b7ebf9ebeaa1a91ee781adcc023b098780073 100644 (file)
@@ -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)
index 4907a43ba19595cc1b90906a59e2012088007cf8..e760bdbe1311b996ee3eb698adbf79c5e8754e80 100644 (file)
@@ -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},
index e3d392f0d5df96b87775829fd07e7835b91e8172..bf19808c54715657330d926268a3f04dd17a350c 100644 (file)
 # 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;
 }
 
index 00334cd6fb9d6f0c14a7fc08f5a21d114e778e65..43dbd0d56fc1bad4be1f7a8f67c56d53c66c8f33 100644 (file)
@@ -9,6 +9,77 @@
 
 #include <stdbool.h>
 
+/*
+ * 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 */
index 0b488a39cd52f95b785f2f3a52ee48f6c3e9cd0b..71cf9af189c97a8ef6d30ffabce1f9ba0946928b 100644 (file)
@@ -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
This page took 0.055527 seconds and 4 git commands to generate.