X-Git-Url: https://git.lttng.org/?p=lttng-tools.git;a=blobdiff_plain;f=src%2Fbin%2Flttng%2Fcommands%2Fadd_trigger.cpp;fp=src%2Fbin%2Flttng%2Fcommands%2Fadd_trigger.cpp;h=939d510802eddf18420cb80e7d530e94a68cdcee;hp=0000000000000000000000000000000000000000;hb=48a4000561343808724f7cb5fa8c131877489ccd;hpb=81663f073dbfb4b61c06a0ceb8ca33c4fc41b1c5 diff --git a/src/bin/lttng/commands/add_trigger.cpp b/src/bin/lttng/commands/add_trigger.cpp new file mode 100644 index 000000000..939d51080 --- /dev/null +++ b/src/bin/lttng/commands/add_trigger.cpp @@ -0,0 +1,2475 @@ +/* + * Copyright (C) 2021 Simon Marchi + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include + +#include "../command.h" +#include "../loglevel.h" +#include "../uprobe.h" + +#include "common/argpar/argpar.h" +#include "common/dynamic-array.h" +#include "common/mi-lttng.h" +#include "common/string-utils/string-utils.h" +#include "common/utils.h" +#include +/* For lttng_event_rule_type_str(). */ +#include +#include +#include "common/filter/filter-ast.h" +#include "common/filter/filter-ir.h" +#include "common/dynamic-array.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 +; +#endif + +enum { + OPT_HELP, + OPT_LIST_OPTIONS, + + OPT_CONDITION, + OPT_ACTION, + OPT_ID, + OPT_OWNER_UID, + OPT_RATE_POLICY, + + OPT_NAME, + OPT_FILTER, + OPT_EXCLUDE_NAME, + OPT_EVENT_NAME, + OPT_LOG_LEVEL, + + OPT_TYPE, + OPT_LOCATION, + + OPT_MAX_SIZE, + OPT_DATA_URL, + OPT_CTRL_URL, + OPT_URL, + OPT_PATH, + + OPT_CAPTURE, +}; + +static const struct argpar_opt_descr event_rule_opt_descrs[] = { + { OPT_FILTER, 'f', "filter", true }, + { OPT_NAME, 'n', "name", true }, + { OPT_EXCLUDE_NAME, 'x', "exclude-name", true }, + { OPT_LOG_LEVEL, 'l', "log-level", true }, + { OPT_EVENT_NAME, 'E', "event-name", true }, + + { OPT_TYPE, 't', "type", true }, + { OPT_LOCATION, 'L', "location", true }, + + /* Capture descriptor */ + { OPT_CAPTURE, '\0', "capture", true }, + + ARGPAR_OPT_DESCR_SENTINEL +}; + +static +bool has_syscall_prefix(const char *arg) +{ + bool matches = false; + const char kernel_syscall_type_opt_prefix[] = "kernel:syscall"; + const size_t kernel_syscall_type_opt_prefix_len = + sizeof(kernel_syscall_type_opt_prefix) - 1; + const char syscall_type_opt_prefix[] = "syscall"; + const size_t syscall_type_opt_prefix_len = + sizeof(syscall_type_opt_prefix) - 1; + + if (strncmp(arg, syscall_type_opt_prefix, + syscall_type_opt_prefix_len) == 0) { + matches = true; + } else if (strncmp(arg, kernel_syscall_type_opt_prefix, + kernel_syscall_type_opt_prefix_len) == 0) { + matches = true; + } else { + matches = false; + } + + return matches; +} + +static +bool assign_event_rule_type(enum lttng_event_rule_type *dest, const char *arg) +{ + bool ret; + + if (*dest != LTTNG_EVENT_RULE_TYPE_UNKNOWN) { + ERR("More than one `--type` was specified."); + goto error; + } + + if (strcmp(arg, "user") == 0 || strcmp(arg, "user:tracepoint") == 0) { + *dest = LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT; + } else if (strcmp(arg, "kernel") == 0 || + strcmp(arg, "kernel:tracepoint") == 0) { + *dest = LTTNG_EVENT_RULE_TYPE_KERNEL_TRACEPOINT; + } else if (strcmp(arg, "jul") == 0 || strcmp(arg, "jul:logging") == 0) { + *dest = LTTNG_EVENT_RULE_TYPE_JUL_LOGGING; + } else if (strcmp(arg, "log4j") == 0 || + strcmp(arg, "log4j:logging") == 0) { + *dest = LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING; + } else if (strcmp(arg, "python") == 0 || + strcmp(arg, "python:logging") == 0) { + *dest = LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING; + } else if (strcmp(arg, "kprobe") == 0 || + strcmp(arg, "kernel:kprobe") == 0) { + *dest = LTTNG_EVENT_RULE_TYPE_KERNEL_KPROBE; + } else if (strcmp(arg, "kernel:uprobe") == 0) { + *dest = LTTNG_EVENT_RULE_TYPE_KERNEL_UPROBE; + } else if (has_syscall_prefix(arg)) { + /* + * Matches the following: + * - syscall + * - syscall:entry + * - syscall:exit + * - syscall:entry+exit + * - syscall:* + * - kernel:syscall + * - kernel:syscall:entry + * - kernel:syscall:exit + * - kernel:syscall:entry+exit + * - kernel:syscall:* + * + * Validation for the right side is left to further usage sites. + */ + *dest = LTTNG_EVENT_RULE_TYPE_KERNEL_SYSCALL; + } else { + ERR("Invalid `--type` value: %s", arg); + goto error; + } + + ret = true; + goto end; + +error: + ret = false; + +end: + 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; +} + +static bool parse_syscall_emission_site_from_type(const char *str, + enum lttng_event_rule_kernel_syscall_emission_site *type) +{ + bool ret = false; + const char kernel_prefix[] = "kernel:"; + const size_t kernel_prefix_len = sizeof(kernel_prefix) - 1; + + /* + * If the passed string is of the form "kernel:syscall*", move the + * pointer passed "kernel:". + */ + if (strncmp(str, kernel_prefix, kernel_prefix_len) == 0) { + str = &str[kernel_prefix_len]; + } + + if (strcmp(str, "syscall") == 0 || + strcmp(str, "syscall:entry+exit") == 0) { + *type = LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_ENTRY_EXIT; + } else if (strcmp(str, "syscall:entry") == 0) { + *type = LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_ENTRY; + } else if (strcmp(str, "syscall:exit") == 0) { + *type = LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_EXIT; + } else { + goto error; + } + + ret = true; + +error: + return ret; +} + +/* + * Parse `str` as a log level against the passed event rule type. + * + * Return the log level in `*log_level`. Return true in `*log_level_only` if + * the string specifies exactly this log level, false if it specifies at least + * this log level. + * + * Return true if the string was successfully parsed as a log level string. + */ +static bool parse_log_level_string(const char *str, + enum lttng_event_rule_type event_rule_type, + int *log_level, + bool *log_level_only) +{ + bool ret; + + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT: + { + enum lttng_loglevel log_level_min, log_level_max; + if (!loglevel_parse_range_string( + str, &log_level_min, &log_level_max)) { + goto error; + } + + /* Only support VAL and VAL.. for now. */ + if (log_level_min != log_level_max && + log_level_max != LTTNG_LOGLEVEL_EMERG) { + goto error; + } + + *log_level = (int) log_level_min; + *log_level_only = log_level_min == log_level_max; + break; + } + case LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING: + { + enum lttng_loglevel_log4j log_level_min, log_level_max; + if (!loglevel_log4j_parse_range_string( + str, &log_level_min, &log_level_max)) { + goto error; + } + + /* Only support VAL and VAL.. for now. */ + if (log_level_min != log_level_max && + log_level_max != LTTNG_LOGLEVEL_LOG4J_FATAL) { + goto error; + } + + *log_level = (int) log_level_min; + *log_level_only = log_level_min == log_level_max; + break; + } + case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING: + { + enum lttng_loglevel_jul log_level_min, log_level_max; + if (!loglevel_jul_parse_range_string( + str, &log_level_min, &log_level_max)) { + goto error; + } + + /* Only support VAL and VAL.. for now. */ + if (log_level_min != log_level_max && + log_level_max != LTTNG_LOGLEVEL_JUL_SEVERE) { + goto error; + } + + *log_level = (int) log_level_min; + *log_level_only = log_level_min == log_level_max; + break; + } + case LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING: + { + enum lttng_loglevel_python log_level_min, log_level_max; + if (!loglevel_python_parse_range_string( + str, &log_level_min, &log_level_max)) { + goto error; + } + + /* Only support VAL and VAL.. for now. */ + if (log_level_min != log_level_max && + log_level_max != + LTTNG_LOGLEVEL_PYTHON_CRITICAL) { + goto error; + } + + *log_level = (int) log_level_min; + *log_level_only = log_level_min == log_level_max; + break; + } + default: + /* Invalid domain type. */ + abort(); + } + + ret = true; + goto end; + +error: + ret = false; + +end: + return ret; +} + +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_expr *ir_op_load_expr_to_event_expr( + const struct ir_load_expression *load_expr, + const char *capture_str) +{ + char *provider_name = NULL; + struct lttng_event_expr *event_expr = NULL; + const struct ir_load_expression_op *load_expr_op = load_expr->child; + const enum ir_load_expression_type load_expr_child_type = + load_expr_op->type; + + switch (load_expr_child_type) { + case IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT: + case IR_LOAD_EXPRESSION_GET_CONTEXT_ROOT: + { + const char *field_name; + + load_expr_op = load_expr_op->next; + LTTNG_ASSERT(load_expr_op); + LTTNG_ASSERT(load_expr_op->type == IR_LOAD_EXPRESSION_GET_SYMBOL); + field_name = load_expr_op->u.symbol; + LTTNG_ASSERT(field_name); + + event_expr = load_expr_child_type == IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT ? + lttng_event_expr_event_payload_field_create(field_name) : + lttng_event_expr_channel_context_field_create(field_name); + if (!event_expr) { + ERR("Failed to create %s event expression: field name = `%s`.", + load_expr_child_type == IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT ? + "payload field" : "channel context", + field_name); + goto error; + } + + break; + } + case IR_LOAD_EXPRESSION_GET_APP_CONTEXT_ROOT: + { + const char *colon; + const char *type_name; + const char *field_name; + + load_expr_op = load_expr_op->next; + LTTNG_ASSERT(load_expr_op); + LTTNG_ASSERT(load_expr_op->type == IR_LOAD_EXPRESSION_GET_SYMBOL); + field_name = load_expr_op->u.symbol; + LTTNG_ASSERT(field_name); + + /* + * The field name needs to be of the form PROVIDER:TYPE. We + * split it here. + */ + colon = strchr(field_name, ':'); + if (!colon) { + ERR("Invalid app-specific context field name: missing colon in `%s`.", + field_name); + goto error; + } + + type_name = colon + 1; + if (*type_name == '\0') { + ERR("Invalid app-specific context field name: missing type name after colon in `%s`.", + field_name); + goto error; + } + + provider_name = strndup(field_name, colon - field_name); + if (!provider_name) { + PERROR("Failed to allocate field name string"); + goto error; + } + + event_expr = lttng_event_expr_app_specific_context_field_create( + provider_name, type_name); + if (!event_expr) { + ERR("Failed to create app-specific context field event expression: provider name = `%s`, type name = `%s`", + provider_name, type_name); + goto error; + } + + break; + } + default: + ERR("%s: unexpected load expr type %d.", __func__, + load_expr_op->type); + abort(); + } + + load_expr_op = load_expr_op->next; + + /* There may be a single array index after that. */ + if (load_expr_op->type == IR_LOAD_EXPRESSION_GET_INDEX) { + struct lttng_event_expr *index_event_expr; + const uint64_t index = load_expr_op->u.index; + + index_event_expr = lttng_event_expr_array_field_element_create(event_expr, index); + if (!index_event_expr) { + ERR("Failed to create array field element event expression."); + goto error; + } + + event_expr = index_event_expr; + load_expr_op = load_expr_op->next; + } + + switch (load_expr_op->type) { + case IR_LOAD_EXPRESSION_LOAD_FIELD: + /* + * This is what we expect, IR_LOAD_EXPRESSION_LOAD_FIELD is + * always found at the end of the chain. + */ + break; + case IR_LOAD_EXPRESSION_GET_SYMBOL: + ERR("While parsing expression `%s`: Capturing subfields is not supported.", + capture_str); + goto error; + + default: + ERR("%s: unexpected load expression operator %s.", __func__, + ir_load_expression_type_str(load_expr_op->type)); + abort(); + } + + goto end; + +error: + lttng_event_expr_destroy(event_expr); + event_expr = NULL; + +end: + free(provider_name); + + return event_expr; +} + +static +struct lttng_event_expr *ir_op_load_to_event_expr( + const struct ir_op *ir, const char *capture_str) +{ + struct lttng_event_expr *event_expr = NULL; + + LTTNG_ASSERT(ir->op == IR_OP_LOAD); + + switch (ir->data_type) { + case IR_DATA_EXPRESSION: + { + const struct ir_load_expression *ir_load_expr = + ir->u.load.u.expression; + + event_expr = ir_op_load_expr_to_event_expr( + ir_load_expr, capture_str); + break; + } + default: + ERR("%s: unexpected data type: %s.", __func__, + ir_data_type_str(ir->data_type)); + abort(); + } + + return event_expr; +} + +static +const char *ir_operator_type_human_str(enum ir_op_type op) +{ + const char *name; + + switch (op) { + case IR_OP_BINARY: + name = "Binary"; + break; + case IR_OP_UNARY: + name = "Unary"; + break; + case IR_OP_LOGICAL: + name = "Logical"; + break; + default: + abort(); + } + + return name; +} + +static +struct lttng_event_expr *ir_op_root_to_event_expr(const struct ir_op *ir, + const char *capture_str) +{ + struct lttng_event_expr *event_expr = NULL; + + LTTNG_ASSERT(ir->op == IR_OP_ROOT); + ir = ir->u.root.child; + + switch (ir->op) { + case IR_OP_LOAD: + event_expr = ir_op_load_to_event_expr(ir, capture_str); + break; + case IR_OP_BINARY: + case IR_OP_UNARY: + case IR_OP_LOGICAL: + ERR("While parsing expression `%s`: %s operators are not allowed in capture expressions.", + capture_str, + ir_operator_type_human_str(ir->op)); + break; + default: + ERR("%s: unexpected IR op type: %s.", __func__, + ir_op_type_str(ir->op)); + abort(); + } + + return event_expr; +} + +static +void destroy_event_expr(void *ptr) +{ + lttng_event_expr_destroy((lttng_event_expr *) ptr); +} + +struct parse_event_rule_res { + /* Owned by this. */ + struct lttng_event_rule *er; + + /* Array of `struct lttng_event_expr *` */ + struct lttng_dynamic_pointer_array capture_descriptors; +}; + +static +struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv) +{ + enum lttng_event_rule_type event_rule_type = + LTTNG_EVENT_RULE_TYPE_UNKNOWN; + struct argpar_state *state; + struct argpar_item *item = NULL; + char *error = NULL; + int consumed_args = -1; + struct lttng_kernel_probe_location *kernel_probe_location = NULL; + struct lttng_userspace_probe_location *userspace_probe_location = NULL; + struct parse_event_rule_res res = { 0 }; + struct lttng_event_expr *event_expr = NULL; + struct filter_parser_ctx *parser_ctx = NULL; + struct lttng_log_level_rule *log_level_rule = NULL; + + /* Event rule type option */ + char *event_rule_type_str = NULL; + + /* Tracepoint and syscall options. */ + char *name = NULL; + /* Array of strings. */ + struct lttng_dynamic_pointer_array exclude_names; + + /* For userspace / kernel probe and function. */ + char *location = NULL; + char *event_name = NULL; + + /* Filter. */ + char *filter = NULL; + + /* Log level. */ + char *log_level_str = NULL; + + lttng_dynamic_pointer_array_init(&res.capture_descriptors, + destroy_event_expr); + + lttng_dynamic_pointer_array_init(&exclude_names, free); + + state = argpar_state_create(*argc, *argv, event_rule_opt_descrs); + if (!state) { + ERR("Failed to allocate an argpar state."); + 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; + } + + LTTNG_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_TYPE: + if (!assign_event_rule_type(&event_rule_type, + item_opt->arg)) { + goto error; + } + + /* Save the string for later use. */ + if (!assign_string(&event_rule_type_str, + item_opt->arg, + "--type/-t")) { + goto error; + } + + break; + case OPT_LOCATION: + if (!assign_string(&location, + item_opt->arg, + "--location/-L")) { + goto error; + } + + break; + case OPT_EVENT_NAME: + if (!assign_string(&event_name, + item_opt->arg, + "--event-name/-E")) { + goto error; + } + + break; + case OPT_FILTER: + if (!assign_string(&filter, item_opt->arg, + "--filter/-f")) { + goto error; + } + + break; + case OPT_NAME: + if (!assign_string(&name, item_opt->arg, + "--name/-n")) { + goto error; + } + + break; + case OPT_EXCLUDE_NAME: + { + int ret; + + ret = lttng_dynamic_pointer_array_add_pointer( + &exclude_names, + strdup(item_opt->arg)); + if (ret != 0) { + ERR("Failed to add pointer to dynamic pointer array."); + goto error; + } + + break; + } + case OPT_LOG_LEVEL: + if (!assign_string(&log_level_str, + item_opt->arg, "--log-level/-l")) { + goto error; + } + + break; + case OPT_CAPTURE: + { + int ret; + const char *capture_str = item_opt->arg; + + ret = filter_parser_ctx_create_from_filter_expression( + capture_str, &parser_ctx); + if (ret) { + ERR("Failed to parse capture expression `%s`.", + capture_str); + goto error; + } + + event_expr = ir_op_root_to_event_expr( + parser_ctx->ir_root, + capture_str); + if (!event_expr) { + /* + * ir_op_root_to_event_expr has printed + * an error message. + */ + goto error; + } + + ret = lttng_dynamic_pointer_array_add_pointer( + &res.capture_descriptors, + event_expr); + if (ret) { + goto error; + } + + /* + * The ownership of event expression was + * transferred to the dynamic array. + */ + event_expr = NULL; + + break; + } + default: + abort(); + } + } else { + const struct argpar_item_non_opt *item_non_opt = + (const struct argpar_item_non_opt *) + item; + + /* Don't accept non-option arguments. */ + ERR("Unexpected argument '%s'", item_non_opt->arg); + goto error; + } + } + + if (event_rule_type == LTTNG_EVENT_RULE_TYPE_UNKNOWN) { + ERR("Event rule requires a --type."); + goto error; + } + + /* + * Option --name is applicable to event rules of type kernel, user, jul, + * log4j,python and syscall. If --name is omitted, it is implicitly + * "*". + */ + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_KERNEL_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING: + case LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING: + case LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING: + case LTTNG_EVENT_RULE_TYPE_KERNEL_SYSCALL: + if (!name) { + name = strdup("*"); + } + break; + + default: + if (name) { + ERR("Can't use --name with %s event rules.", + lttng_event_rule_type_str( + event_rule_type)); + goto error; + } + + if (lttng_dynamic_pointer_array_get_count(&exclude_names) > 0) { + ERR("Can't use --exclude-name/-x with %s event rules.", + lttng_event_rule_type_str( + event_rule_type)); + goto error; + } + } + + /* + * Option --location is only applicable to (and mandatory for) event + * rules of type {k,u}probe and function. + * + * Option --event-name is only applicable to event rules of type probe. + * If omitted, it defaults to the location. + */ + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_KERNEL_KPROBE: + case LTTNG_EVENT_RULE_TYPE_KERNEL_UPROBE: + if (!location) { + ERR("Event rule of type %s requires a --location.", + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + + if (!event_name) { + event_name = strdup(location); + } + + break; + + default: + if (location) { + ERR("Can't use --location with %s event rules.", + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + + if (event_name) { + ERR("Can't use --event-name with %s event rules.", + lttng_event_rule_type_str( + event_rule_type)); + goto error; + } + } + + /* + * Update *argc and *argv so our caller can keep parsing what follows. + */ + consumed_args = argpar_state_get_ingested_orig_args(state); + LTTNG_ASSERT(consumed_args >= 0); + *argc -= consumed_args; + *argv += consumed_args; + + /* + * 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) { + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_KERNEL_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING: + case LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING: + case LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING: + case LTTNG_EVENT_RULE_TYPE_KERNEL_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-name/-x was passed, split it into an exclusion list. + * Exclusions are only supported by + * LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT for now. + */ + if (lttng_dynamic_pointer_array_get_count(&exclude_names) > 0) { + if (event_rule_type != LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT) { + ERR("Event name exclusions are not yet implemented for %s event rules.", + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + + if (validate_exclusion_list(name, &exclude_names) != 0) { + /* + * Assume validate_exclusion_list already prints an + * error message. + */ + goto error; + } + } + + if (log_level_str) { + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING: + case LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING: + case LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING: + { + int log_level; + bool log_level_only; + + if (strcmp(log_level_str, "..") == 0) { + /* + * ".." is the same as passing no log level + * option and correspond to the "ANY" case. + */ + break; + } + + if (!parse_log_level_string(log_level_str, event_rule_type, + &log_level, &log_level_only)) { + ERR("Failed to parse log level string `%s`.", + log_level_str); + goto error; + } + + if (log_level_only) { + log_level_rule = lttng_log_level_rule_exactly_create(log_level); + } else { + log_level_rule = lttng_log_level_rule_at_least_as_severe_as_create(log_level); + } + + if (log_level_rule == NULL) { + ERR("Failed to create log level rule object."); + goto error; + } + break; + } + default: + ERR("Log levels are not supported for %s event rules.", + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + } + + /* Finally, create the event rule object. */ + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT: + { + enum lttng_event_rule_status event_rule_status; + + res.er = lttng_event_rule_user_tracepoint_create(); + if (!res.er) { + ERR("Failed to create user_tracepoint event rule."); + goto error; + } + + /* Set pattern. */ + event_rule_status = lttng_event_rule_user_tracepoint_set_name_pattern( + res.er, name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set user_tracepoint event rule's pattern to '%s'.", + name); + goto error; + } + + /* Set filter. */ + if (filter) { + event_rule_status = lttng_event_rule_user_tracepoint_set_filter( + res.er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set user_tracepoint event rule's filter to '%s'.", + filter); + goto error; + } + } + + /* Set exclusion list. */ + if (lttng_dynamic_pointer_array_get_count(&exclude_names) > 0) { + int n; + int count = lttng_dynamic_pointer_array_get_count( + &exclude_names); + + for (n = 0; n < count; n++) { + const char *exclude_name = + (const char *) lttng_dynamic_pointer_array_get_pointer( + &exclude_names, + n); + + event_rule_status = + lttng_event_rule_user_tracepoint_add_name_pattern_exclusion( + res.er, + exclude_name); + if (event_rule_status != + LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set user_tracepoint exclusion list element '%s'", + exclude_name); + goto error; + } + } + } + + if (log_level_rule) { + event_rule_status = + lttng_event_rule_user_tracepoint_set_log_level_rule( + res.er, log_level_rule); + + 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_KERNEL_TRACEPOINT: + { + enum lttng_event_rule_status event_rule_status; + + res.er = lttng_event_rule_kernel_tracepoint_create(); + if (!res.er) { + ERR("Failed to create kernel_tracepoint event rule."); + goto error; + } + + /* Set pattern. */ + event_rule_status = lttng_event_rule_kernel_tracepoint_set_name_pattern( + res.er, name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set kernel_tracepoint event rule's pattern to '%s'.", + name); + goto error; + } + + /* Set filter. */ + if (filter) { + event_rule_status = lttng_event_rule_kernel_tracepoint_set_filter( + res.er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set kernel_tracepoint event rule's filter to '%s'.", + filter); + goto error; + } + } + break; + } + case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING: + { + enum lttng_event_rule_status event_rule_status; + + res.er = lttng_event_rule_jul_logging_create(); + if (!res.er) { + ERR("Failed to create jul_logging event rule."); + goto error; + } + + /* Set pattern. */ + event_rule_status = lttng_event_rule_jul_logging_set_name_pattern( + res.er, name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set jul_logging event rule's pattern to '%s'.", + name); + goto error; + } + + /* Set filter. */ + if (filter) { + event_rule_status = lttng_event_rule_jul_logging_set_filter( + res.er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set jul_logging event rule's filter to '%s'.", + filter); + goto error; + } + } + + if (log_level_rule) { + event_rule_status = + lttng_event_rule_jul_logging_set_log_level_rule( + res.er, log_level_rule); + + 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_LOG4J_LOGGING: + { + enum lttng_event_rule_status event_rule_status; + + res.er = lttng_event_rule_log4j_logging_create(); + if (!res.er) { + ERR("Failed to create jul_logging event rule."); + goto error; + } + + /* Set pattern. */ + event_rule_status = lttng_event_rule_log4j_logging_set_name_pattern( + res.er, name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set jul_logging event rule's pattern to '%s'.", + name); + goto error; + } + + /* Set filter. */ + if (filter) { + event_rule_status = lttng_event_rule_log4j_logging_set_filter( + res.er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set jul_logging event rule's filter to '%s'.", + filter); + goto error; + } + } + + if (log_level_rule) { + event_rule_status = + lttng_event_rule_log4j_logging_set_log_level_rule( + res.er, log_level_rule); + + 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_PYTHON_LOGGING: + { + enum lttng_event_rule_status event_rule_status; + + res.er = lttng_event_rule_python_logging_create(); + if (!res.er) { + ERR("Failed to create jul_logging event rule."); + goto error; + } + + /* Set pattern. */ + event_rule_status = lttng_event_rule_python_logging_set_name_pattern( + res.er, name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set jul_logging event rule's pattern to '%s'.", + name); + goto error; + } + + /* Set filter. */ + if (filter) { + event_rule_status = lttng_event_rule_python_logging_set_filter( + res.er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set jul_logging event rule's filter to '%s'.", + filter); + goto error; + } + } + + if (log_level_rule) { + event_rule_status = + lttng_event_rule_python_logging_set_log_level_rule( + res.er, log_level_rule); + + 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_KERNEL_KPROBE: + { + int ret; + enum lttng_event_rule_status event_rule_status; + + ret = parse_kernel_probe_opts( + location, &kernel_probe_location); + if (ret) { + ERR("Failed to parse kernel probe location."); + goto error; + } + + LTTNG_ASSERT(kernel_probe_location); + res.er = lttng_event_rule_kernel_kprobe_create(kernel_probe_location); + if (!res.er) { + ERR("Failed to create kprobe event rule."); + goto error; + } + + event_rule_status = + lttng_event_rule_kernel_kprobe_set_event_name( + res.er, event_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set kprobe event rule's name to '%s'.", + event_name); + goto error; + } + + break; + } + case LTTNG_EVENT_RULE_TYPE_KERNEL_UPROBE: + { + int ret; + enum lttng_event_rule_status event_rule_status; + + ret = parse_userspace_probe_opts( + location, &userspace_probe_location); + if (ret) { + ERR("Failed to parse user space probe location."); + goto error; + } + + res.er = lttng_event_rule_kernel_uprobe_create(userspace_probe_location); + if (!res.er) { + ERR("Failed to create userspace probe event rule."); + goto error; + } + + event_rule_status = + lttng_event_rule_kernel_uprobe_set_event_name( + res.er, event_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set user space probe event rule's name to '%s'.", + event_name); + goto error; + } + + break; + } + case LTTNG_EVENT_RULE_TYPE_KERNEL_SYSCALL: + { + enum lttng_event_rule_status event_rule_status; + enum lttng_event_rule_kernel_syscall_emission_site emission_site; + + if (!parse_syscall_emission_site_from_type( + event_rule_type_str, &emission_site)) { + ERR("Failed to parse syscall type '%s'.", event_rule_type_str); + goto error; + } + + res.er = lttng_event_rule_kernel_syscall_create(emission_site); + if (!res.er) { + ERR("Failed to create syscall event rule."); + goto error; + } + + event_rule_status = lttng_event_rule_kernel_syscall_set_name_pattern( + res.er, name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set syscall event rule's pattern to '%s'.", + name); + goto error; + } + + if (filter) { + event_rule_status = lttng_event_rule_kernel_syscall_set_filter( + res.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(res.er); + res.er = NULL; + lttng_dynamic_pointer_array_reset(&res.capture_descriptors); + +end: + if (parser_ctx) { + filter_parser_ctx_free(parser_ctx); + } + + lttng_event_expr_destroy(event_expr); + argpar_item_destroy(item); + free(error); + argpar_state_destroy(state); + free(filter); + free(name); + lttng_dynamic_pointer_array_reset(&exclude_names); + free(log_level_str); + free(location); + free(event_name); + free(event_rule_type_str); + + lttng_kernel_probe_location_destroy(kernel_probe_location); + lttng_userspace_probe_location_destroy(userspace_probe_location); + lttng_log_level_rule_destroy(log_level_rule); + return res; +} + +static +struct lttng_condition *handle_condition_event(int *argc, const char ***argv) +{ + struct parse_event_rule_res res; + struct lttng_condition *c; + size_t i; + + res = parse_event_rule(argc, argv); + if (!res.er) { + c = NULL; + goto error; + } + + c = lttng_condition_event_rule_matches_create(res.er); + lttng_event_rule_destroy(res.er); + res.er = NULL; + if (!c) { + goto error; + } + + for (i = 0; i < lttng_dynamic_pointer_array_get_count(&res.capture_descriptors); + i++) { + enum lttng_condition_status status; + struct lttng_event_expr **expr = + (lttng_event_expr **) lttng_dynamic_array_get_element( + &res.capture_descriptors.array, i); + + LTTNG_ASSERT(expr); + LTTNG_ASSERT(*expr); + status = lttng_condition_event_rule_matches_append_capture_descriptor( + c, *expr); + if (status != LTTNG_CONDITION_STATUS_OK) { + if (status == LTTNG_CONDITION_STATUS_UNSUPPORTED) { + ERR("The capture feature is unsupported by the event-rule condition type"); + } + + goto error; + } + + /* Ownership of event expression moved to `c` */ + *expr = NULL; + } + + goto end; + +error: + lttng_condition_destroy(c); + c = NULL; + +end: + lttng_dynamic_pointer_array_reset(&res.capture_descriptors); + lttng_event_rule_destroy(res.er); + return c; +} + +struct condition_descr { + const char *name; + struct lttng_condition *(*handler) (int *argc, const char ***argv); +}; + +static const +struct condition_descr condition_descrs[] = { + { "event-rule-matches", handle_condition_event }, +}; + +static +struct lttng_condition *parse_condition(const char *condition_name, int *argc, + const char ***argv) +{ + int i; + struct lttng_condition *cond; + const struct condition_descr *descr = NULL; + + 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_rate_policy *parse_rate_policy(const char *policy_str) +{ + int ret; + size_t num_token = 0; + struct lttng_dynamic_pointer_array tokens; + struct lttng_rate_policy *policy = NULL; + enum lttng_rate_policy_type policy_type; + unsigned long long value; + char *policy_type_str; + char *policy_value_str; + + LTTNG_ASSERT(policy_str); + lttng_dynamic_pointer_array_init(&tokens, NULL); + + /* Rate policy fields are separated by ':'. */ + ret = strutils_split(policy_str, ':', 1, &tokens); + if (ret == 0) { + num_token = lttng_dynamic_pointer_array_get_count(&tokens); + } + + /* + * Early sanity check that the number of parameter is exactly 2. + * i.e : type:value + */ + if (num_token != 2) { + ERR("Rate policy format is invalid."); + goto end; + } + + policy_type_str = (char *) lttng_dynamic_pointer_array_get_pointer(&tokens, 0); + policy_value_str = (char *) lttng_dynamic_pointer_array_get_pointer(&tokens, 1); + + /* Parse the type. */ + if (strcmp(policy_type_str, "once-after") == 0) { + policy_type = LTTNG_RATE_POLICY_TYPE_ONCE_AFTER_N; + } else if (strcmp(policy_type_str, "every") == 0) { + policy_type = LTTNG_RATE_POLICY_TYPE_EVERY_N; + } else { + ERR("Rate policy type `%s` unknown.", policy_type_str); + goto end; + } + + /* Parse the value. */ + if (utils_parse_unsigned_long_long(policy_value_str, &value) != 0) { + ERR("Failed to parse rate policy value `%s` as an integer.", + policy_value_str); + goto end; + } + + if (value == 0) { + ERR("Rate policy value `%s` must be > 0.", policy_value_str); + goto end; + } + + switch (policy_type) { + case LTTNG_RATE_POLICY_TYPE_EVERY_N: + policy = lttng_rate_policy_every_n_create(value); + break; + case LTTNG_RATE_POLICY_TYPE_ONCE_AFTER_N: + policy = lttng_rate_policy_once_after_n_create(value); + break; + default: + abort(); + } + + if (policy == NULL) { + ERR("Failed to create rate policy `%s`.", policy_str); + } + +end: + lttng_dynamic_pointer_array_reset(&tokens); + return policy; +} + +static const struct argpar_opt_descr notify_action_opt_descrs[] = { + { OPT_RATE_POLICY, '\0', "rate-policy", true }, + ARGPAR_OPT_DESCR_SENTINEL +}; + +static +struct lttng_action *handle_action_notify(int *argc, const char ***argv) +{ + struct lttng_action *action = NULL; + struct argpar_state *state = NULL; + struct argpar_item *item = NULL; + char *error = NULL; + struct lttng_rate_policy *policy = NULL; + + state = argpar_state_create(*argc, *argv, notify_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; + } + + LTTNG_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_RATE_POLICY: + { + policy = parse_rate_policy(item_opt->arg); + if (!policy) { + goto error; + } + break; + } + default: + abort(); + } + } else { + const struct argpar_item_non_opt *item_non_opt; + + LTTNG_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) { + 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); + + action = lttng_action_notify_create(); + if (!action) { + ERR("Failed to create notify action"); + goto error; + } + + if (policy) { + enum lttng_action_status status; + status = lttng_action_notify_set_rate_policy(action, policy); + if (status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to set rate policy"); + goto error; + } + } + + goto end; + +error: + lttng_action_destroy(action); + action = NULL; +end: + free(error); + lttng_rate_policy_destroy(policy); + argpar_state_destroy(state); + argpar_item_destroy(item); + return action; +} + +/* + * Generic handler for a kind of action that takes a session name and an + * optional rate policy. + */ + +static struct lttng_action *handle_action_simple_session_with_policy(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 *), + enum lttng_action_status (*set_rate_policy_cb)( + struct lttng_action *, + const struct lttng_rate_policy *), + 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; + struct lttng_rate_policy *policy = NULL; + + LTTNG_ASSERT(set_session_name_cb); + LTTNG_ASSERT(set_rate_policy_cb); + + const struct argpar_opt_descr rate_policy_opt_descrs[] = { + { OPT_RATE_POLICY, '\0', "rate-policy", true }, + ARGPAR_OPT_DESCR_SENTINEL + }; + + state = argpar_state_create(*argc, *argv, rate_policy_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; + } + + LTTNG_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_RATE_POLICY: + { + policy = parse_rate_policy(item_opt->arg); + if (!policy) { + goto error; + } + break; + } + default: + abort(); + } + } else { + const struct argpar_item_non_opt *item_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; + } + + if (policy) { + action_status = set_rate_policy_cb(action, policy); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to set rate policy"); + goto error; + } + } + + goto end; + +error: + lttng_action_destroy(action); + action = NULL; + argpar_item_destroy(item); +end: + lttng_rate_policy_destroy(policy); + free(error); + argpar_state_destroy(state); + return action; +} + +static +struct lttng_action *handle_action_start_session(int *argc, + const char ***argv) +{ + return handle_action_simple_session_with_policy(argc, argv, + lttng_action_start_session_create, + lttng_action_start_session_set_session_name, + lttng_action_start_session_set_rate_policy, "start"); +} + +static +struct lttng_action *handle_action_stop_session(int *argc, + const char ***argv) +{ + return handle_action_simple_session_with_policy(argc, argv, + lttng_action_stop_session_create, + lttng_action_stop_session_set_session_name, + lttng_action_stop_session_set_rate_policy, "stop"); +} + +static +struct lttng_action *handle_action_rotate_session(int *argc, + const char ***argv) +{ + return handle_action_simple_session_with_policy(argc, argv, + lttng_action_rotate_session_create, + lttng_action_rotate_session_set_session_name, + lttng_action_rotate_session_set_rate_policy, + "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 }, + { OPT_RATE_POLICY, '\0', "rate-policy", 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; + struct lttng_rate_policy *policy = 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; + } + + LTTNG_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; + case OPT_RATE_POLICY: + { + policy = parse_rate_policy(item_opt->arg); + if (!policy) { + goto error; + } + break; + } + default: + abort(); + } + } else { + const struct argpar_item_non_opt *item_non_opt; + + LTTNG_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; + } + + if (policy) { + enum lttng_action_status status; + status = lttng_action_snapshot_session_set_rate_policy( + action, policy); + if (status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to set rate policy"); + goto error; + } + } + + 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); + free(max_size_arg); + lttng_rate_policy_destroy(policy); + argpar_state_destroy(state); + argpar_item_destroy(item); + 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(const char *action_name, int *argc, const char ***argv) +{ + int i; + struct lttng_action *action; + const struct action_descr *descr = NULL; + + 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", true }, + { OPT_ACTION, '\0', "action", true }, + { OPT_NAME, '\0', "name", true }, + { OPT_OWNER_UID, '\0', "owner-uid", true }, + ARGPAR_OPT_DESCR_SENTINEL, +}; + +static +void lttng_actions_destructor(void *p) +{ + struct lttng_action *action = (lttng_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_list = NULL; + struct lttng_action *action = NULL; + struct lttng_trigger *trigger = NULL; + char *error = NULL; + char *name = NULL; + int i; + char *owner_uid = NULL; + enum lttng_error_code ret_code; + struct mi_writer *mi_writer = NULL; + + lttng_dynamic_pointer_array_init(&actions, lttng_actions_destructor); + + if (lttng_opt_mi) { + mi_writer = mi_lttng_writer_create( + fileno(stdout), lttng_opt_mi); + if (!mi_writer) { + ret = CMD_ERROR; + goto error; + } + + /* Open command element. */ + ret = mi_lttng_writer_command_open(mi_writer, + mi_lttng_element_command_add_trigger); + if (ret) { + ret = CMD_ERROR; + goto error; + } + + /* Open output element. */ + ret = mi_lttng_writer_open_element( + mi_writer, mi_lttng_element_command_output); + if (ret) { + ret = CMD_ERROR; + goto error; + } + } + + 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; + } + + LTTNG_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(item_opt->arg, &my_argc, &my_argv); + if (!condition) { + /* + * An error message was already printed by + * parse_condition. + */ + goto error; + } + + break; + } + case OPT_ACTION: + { + action = parse_action(item_opt->arg, &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 list. */ + action = NULL; + + break; + } + case OPT_NAME: + { + if (!assign_string(&name, item_opt->arg, "--name")) { + goto error; + } + + break; + } + case OPT_OWNER_UID: + { + if (!assign_string(&owner_uid, item_opt->arg, + "--owner-uid")) { + 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; + } + + action_list = lttng_action_list_create(); + if (!action_list) { + goto error; + } + + for (i = 0; i < lttng_dynamic_pointer_array_get_count(&actions); i++) { + enum lttng_action_status status; + + action = (lttng_action *) lttng_dynamic_pointer_array_steal_pointer(&actions, i); + + status = lttng_action_list_add_action(action_list, action); + if (status != LTTNG_ACTION_STATUS_OK) { + goto error; + } + + /* + * The `lttng_action_list_add_action()` takes a reference to + * the action. We can destroy ours. + */ + lttng_action_destroy(action); + action = NULL; + } + + trigger = lttng_trigger_create(condition, action_list); + if (!trigger) { + goto error; + } + + if (owner_uid) { + enum lttng_trigger_status trigger_status; + char *end; + long long uid; + + errno = 0; + uid = strtol(owner_uid, &end, 10); + if (end == owner_uid || *end != '\0' || errno != 0) { + ERR("Failed to parse `%s` as a user id.", owner_uid); + goto error; + } + + 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; + } + } + + if (name) { + ret_code = lttng_register_trigger_with_name(trigger, name); + } else { + ret_code = lttng_register_trigger_with_automatic_name(trigger); + } + + if (ret_code != LTTNG_OK) { + ERR("Failed to register trigger: %s.", + lttng_strerror(-ret_code)); + goto error; + } + + if (lttng_opt_mi) { + ret_code = lttng_trigger_mi_serialize(trigger, mi_writer, NULL); + if (ret_code != LTTNG_OK) { + goto error; + } + } else { + const char *returned_trigger_name; + const enum lttng_trigger_status trigger_status = + lttng_trigger_get_name(trigger, + &returned_trigger_name); + + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + WARN("Failed to retrieve the added trigger's name."); + } else { + MSG("Added trigger `%s`.", returned_trigger_name); + } + } + + ret = 0; + + goto end; + +error: + ret = 1; + +end: + /* Mi closing. */ + if (lttng_opt_mi && mi_writer) { + int mi_ret; + + /* Close output element. */ + mi_ret = mi_lttng_writer_close_element(mi_writer); + if (mi_ret) { + ret = 1; + goto cleanup; + } + + mi_ret = mi_lttng_writer_write_element_bool(mi_writer, + mi_lttng_element_command_success, ret ? 0 : 1); + if (mi_ret) { + ret = 1; + goto cleanup; + } + + /* Command element close. */ + mi_ret = mi_lttng_writer_command_close(mi_writer); + if (mi_ret) { + ret = 1; + goto cleanup; + } + } + +cleanup: + argpar_state_destroy(argpar_state); + argpar_item_destroy(argpar_item); + lttng_dynamic_pointer_array_reset(&actions); + lttng_condition_destroy(condition); + lttng_action_destroy(action_list); + lttng_action_destroy(action); + lttng_trigger_destroy(trigger); + free(error); + free(name); + free(owner_uid); + if (mi_writer && mi_lttng_writer_destroy(mi_writer)) { + /* Preserve original error code. */ + ret = ret ? ret : CMD_ERROR; + } + + return ret; +}