From 9f449915b9d44ce3c9c9255f5d491a62545fee25 Mon Sep 17 00:00:00 2001 From: Philippe Proulx Date: Wed, 15 Feb 2017 11:38:36 -0500 Subject: [PATCH] Add support for "full" star globbing patterns in event names and filters MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This patch adds the support for "full" star-only globbing patterns to be used in event names and filter literal strings. A star-only globbing pattern is a globbing pattern with the star (`*`) being the only special character. This means `?` and character sets (`[abc-k]`) are not supported here. We cannot support them without a strategy to differentiate the globbing pattern because `?` and `[` are not special characters in event names passed on the command line and filter literal strings right now. The eventual strategy to support them would probably look like this for event names: lttng enable-event --userspace --glob 'hell?-wo*rl[Ddz]42' and like this for filter strings: filename =* "?sys*.[ch]" The reason this patch adds the feature for both the event names and the filter strings at the same time is that, for some agent domains, a filter string is used to filter the logger name. For example: lttng enable-event --python 'hello*world' In this case, the UST event name is always `lttng_python`, but a filter string is added to the event rule: logger_name == "hello*world" If I don't add support for filter strings in this patch, then the globbing feature for event names would not work for all the domains. src/bin/lttng/commands/enable_events.c -------------------------------------- The exclusion validation code is cleaner. strutils_split() is used to split the list (which also supports `\,` to escape delimiters). Then, if the event name is a globbing pattern which only contains a wildcard star at the end (the only valid globbing pattern before this patch), the exclusions which also only contain a wildcard star at the end or no star at all are validated like it was done previously, with the exception that escape characters are considered now (that is, in the exclusion `hello\*world`, `\*` is parsed as is: the star is not a wildcard). src/bin/lttng-sessiond/cmd.c ---------------------------- The event name validation function is removed because the only thing it checks is that a star can only appear at the end of the name. This is not true anymore. It is expected that the tracers's globbing matching algorithm expect a globbing pattern without two or more consecutive stars: hello**world Thus in _cmd_enable_event(), strutils_normalize_star_glob_pattern() is used to "normalize" the star globbing patterns of event names and exclusion names in place (if they exist). Normalizing here means crushing consecutive stars as a single one, without considering escaped stars: hello*\***world**** -> hello*\**world* Note that this also means that the event and exclusion names given by the user are not necessarily the ones remaining after the enable-event command is executed. This should not be a problem as `lttng status` shows the normalized names and normalization is an identity function when the string is already normalized. src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c ----------------------------------------------------- The literal string transformation is modified to include the type of literal string in the node amongst: * IR_LOAD_STRING_TYPE_PLAIN * IR_LOAD_STRING_TYPE_GLOB_STAR_END * IR_LOAD_STRING_TYPE_GLOB_STAR This type is used for post-validation and bytecode translation. src/lib/lttng-ctl/filter/filter-bytecode.h src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c ----------------------------------------------------------- A new load bytecode operation is added: FILTER_OP_LOAD_STAR_GLOB_STRING. When this operation is executed, it should load a string literal as a full star globbing pattern. The star-at-the-end-only use case is still expected to be handled by the FILTER_OP_LOAD_STRING operation to avoid changing anything to the current behaviour. src/bin/lttng-sessiond/lttng-ust-abi.h -------------------------------------- Version 7.1 bumped to version 7.2 because a new "load" filter operation is added to the list of bytecode operations, but the current operation codes are not changed, so a 7.2 filter interpreter should interpret a 7.1 filter bytecode just fine. src/common/kernel-ctl/kernel-ioctl.h ------------------------------------ Version 2.2 bumped to version 2.3 because a new "load" filter operation is added to the list of bytecode operations, but the current operation codes are not changed, so a 2.3 filter interpreter should interpret a 2.2 filter bytecode just fine. src/lib/lttng-ctl/filter/filter-visitor-ir-normalize-glob-patterns.c -------------------------------------------------------------------- This IR visitor normalizes the literal string nodes when their type is IR_LOAD_STRING_TYPE_GLOB_STAR_END or IR_LOAD_STRING_TYPE_GLOB_STAR. src/lib/lttng-ctl/filter/filter-visitor-ir-validate-globbing.c -------------------------------------------------------------- This IR visitor validates that: 1. When there's a binary operation between two literal strings, if one of them has the IR_LOAD_STRING_TYPE_GLOB_STAR type, the other one has the IR_LOAD_STRING_TYPE_PLAIN type. In other words, you cannot compare two globbing patterns, except for two globbing patterns with only a star at the end for backward compatibility reasons. 2. When there's a binary operation between two literal strings, if one of them is a (full) star globbing pattern, the binary operation is either == or !=. src/lib/lttng-ctl/filter/filter-visitor-ir-validate-string.c ------------------------------------------------------------ The code to ensure that a wildcard star can only appear at the end of a literal string is removed. src/lib/lttng-ctl/lttng-ctl.c ----------------------------- New visitors are called. Signed-off-by: Philippe Proulx Signed-off-by: Jérémie Galarneau Signed-off-by: Jérémie Galarneau --- src/bin/lttng-sessiond/Makefile.am | 3 +- src/bin/lttng-sessiond/cmd.c | 64 +--- src/bin/lttng/Makefile.am | 3 +- src/bin/lttng/commands/enable_events.c | 299 ++++++++++-------- src/lib/lttng-ctl/filter/Makefile.am | 3 + src/lib/lttng-ctl/filter/filter-ast.h | 2 + src/lib/lttng-ctl/filter/filter-bytecode.h | 12 +- src/lib/lttng-ctl/filter/filter-ir.h | 16 +- .../filter/filter-visitor-generate-bytecode.c | 31 +- .../filter/filter-visitor-generate-ir.c | 24 +- ...ilter-visitor-ir-normalize-glob-patterns.c | 94 ++++++ .../filter-visitor-ir-validate-globbing.c | 122 +++++++ .../filter-visitor-ir-validate-string.c | 22 +- src/lib/lttng-ctl/lttng-ctl.c | 18 +- 14 files changed, 499 insertions(+), 214 deletions(-) create mode 100644 src/lib/lttng-ctl/filter/filter-visitor-ir-normalize-glob-patterns.c create mode 100644 src/lib/lttng-ctl/filter/filter-visitor-ir-validate-globbing.c diff --git a/src/bin/lttng-sessiond/Makefile.am b/src/bin/lttng-sessiond/Makefile.am index 755c2aa0f..4b4e7ec99 100644 --- a/src/bin/lttng-sessiond/Makefile.am +++ b/src/bin/lttng-sessiond/Makefile.am @@ -59,7 +59,8 @@ lttng_sessiond_LDADD = -lurcu-common -lurcu \ $(top_builddir)/src/common/relayd/librelayd.la \ $(top_builddir)/src/common/testpoint/libtestpoint.la \ $(top_builddir)/src/common/health/libhealth.la \ - $(top_builddir)/src/common/config/libconfig.la + $(top_builddir)/src/common/config/libconfig.la \ + $(top_builddir)/src/common/string-utils/libstring-utils.la if HAVE_LIBLTTNG_UST_CTL diff --git a/src/bin/lttng-sessiond/cmd.c b/src/bin/lttng-sessiond/cmd.c index 7b9647436..f3ff25642 100644 --- a/src/bin/lttng-sessiond/cmd.c +++ b/src/bin/lttng-sessiond/cmd.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "channel.h" #include "consumer.h" @@ -61,7 +62,6 @@ static pthread_mutex_t relayd_net_seq_idx_lock = PTHREAD_MUTEX_INITIALIZER; static uint64_t relayd_net_seq_idx; -static int validate_event_name(const char *); static int validate_ust_event_name(const char *); static int cmd_enable_event_internal(struct ltt_session *session, struct lttng_domain *domain, @@ -1444,10 +1444,6 @@ int cmd_disable_event(struct ltt_session *session, DBG("Disable event command for event \'%s\'", event->name); event_name = event->name; - if (validate_event_name(event_name)) { - ret = LTTNG_ERR_INVALID_EVENT_NAME; - goto error; - } /* Error out on unhandled search criteria */ if (event->loglevel_type || event->loglevel != -1 || event->enabled @@ -1739,43 +1735,6 @@ end: return ret; } -static int validate_event_name(const char *name) -{ - int ret = 0; - const char *c = name; - const char *event_name_end = c + LTTNG_SYMBOL_NAME_LEN; - bool null_terminated = false; - - /* - * Make sure that unescaped wildcards are only used as the last - * character of the event name. - */ - while (c < event_name_end) { - switch (*c) { - case '\0': - null_terminated = true; - goto end; - case '\\': - c++; - break; - case '*': - if ((c + 1) < event_name_end && *(c + 1)) { - /* Wildcard is not the last character */ - ret = LTTNG_ERR_INVALID_EVENT_NAME; - goto end; - } - default: - break; - } - c++; - } -end: - if (!ret && !null_terminated) { - ret = LTTNG_ERR_INVALID_EVENT_NAME; - } - return ret; -} - static inline bool name_starts_with(const char *name, const char *prefix) { const size_t max_cmp_len = min(strlen(prefix), LTTNG_SYMBOL_NAME_LEN); @@ -1821,7 +1780,7 @@ static int _cmd_enable_event(struct ltt_session *session, struct lttng_event_exclusion *exclusion, int wpipe, bool internal_event) { - int ret, channel_created = 0; + int ret = 0, channel_created = 0; struct lttng_channel *attr = NULL; assert(session); @@ -1831,15 +1790,24 @@ static int _cmd_enable_event(struct ltt_session *session, /* If we have a filter, we must have its filter expression */ assert(!(!!filter_expression ^ !!filter)); - DBG("Enable event command for event \'%s\'", event->name); + /* Normalize event name as a globbing pattern */ + strutils_normalize_star_glob_pattern(event->name); - rcu_read_lock(); + /* Normalize exclusion names as globbing patterns */ + if (exclusion) { + size_t i; - ret = validate_event_name(event->name); - if (ret) { - goto error; + for (i = 0; i < exclusion->count; i++) { + char *name = LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, i); + + strutils_normalize_star_glob_pattern(name); + } } + DBG("Enable event command for event \'%s\'", event->name); + + rcu_read_lock(); + switch (domain->type) { case LTTNG_DOMAIN_KERNEL: { diff --git a/src/bin/lttng/Makefile.am b/src/bin/lttng/Makefile.am index 23fff77a7..c60c0ee6d 100644 --- a/src/bin/lttng/Makefile.am +++ b/src/bin/lttng/Makefile.am @@ -28,4 +28,5 @@ lttng_SOURCES = command.h conf.c conf.h commands/start.c \ lttng_LDADD = $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la \ $(top_builddir)/src/common/libcommon.la \ - $(top_builddir)/src/common/config/libconfig.la + $(top_builddir)/src/common/config/libconfig.la \ + $(top_builddir)/src/common/string-utils/libstring-utils.la diff --git a/src/bin/lttng/commands/enable_events.c b/src/bin/lttng/commands/enable_events.c index 1ed3a0ab7..cc79b0b74 100644 --- a/src/bin/lttng/commands/enable_events.c +++ b/src/bin/lttng/commands/enable_events.c @@ -28,6 +28,7 @@ #include #include +#include /* Mi dependancy */ #include @@ -372,9 +373,10 @@ const char *print_raw_channel_name(const char *name) * Mi print exlcusion list */ static -int mi_print_exclusion(int count, char **names) +int mi_print_exclusion(char **names) { int i, ret; + int count = names ? strutils_array_of_strings_len(names) : 0; assert(writer); @@ -406,12 +408,13 @@ end: * Return allocated string for pretty-printing exclusion names. */ static -char *print_exclusions(int count, char **names) +char *print_exclusions(char **names) { int length = 0; int i; const char *preamble = " excluding "; char *ret; + int count = names ? strutils_array_of_strings_len(names) : 0; if (count == 0) { return strdup(""); @@ -419,141 +422,168 @@ char *print_exclusions(int count, char **names) /* calculate total required length */ for (i = 0; i < count; i++) { - length += strlen(names[i]) + 1; + length += strlen(names[i]) + 4; } /* add length of preamble + one for NUL - one for last (missing) comma */ length += strlen(preamble); - ret = zmalloc(length); + ret = zmalloc(length + 1); if (!ret) { return NULL; } strncpy(ret, preamble, length); for (i = 0; i < count; i++) { + strcat(ret, "\""); strcat(ret, names[i]); + strcat(ret, "\""); if (i != count - 1) { - strcat(ret, ","); + strcat(ret, ", "); } } return ret; } -/* - * Compare list of exclusions against an event name. - * Return a list of legal exclusion names. - * Produce an error or a warning about others (depending on the situation) - */ static -int check_exclusion_subsets(const char *event_name, - const char *exclusions, - int *exclusion_count_ptr, - char ***exclusion_list_ptr) +int check_exclusion_subsets(const char *event_name, const char *exclusion) { - const char *excluder_ptr; - const char *event_ptr; - const char *next_excluder; - int excluder_length; - int exclusion_count = 0; - char **exclusion_list = NULL; - int ret = CMD_SUCCESS; + bool warn = false; + int ret = 0; + const char *e = event_name; + const char *x = exclusion; + + /* Scan both the excluder and the event letter by letter */ + while (true) { + if (*e == '\\') { + if (*x != *e) { + warn = true; + goto end; + } - if (event_name[strlen(event_name) - 1] != '*') { - ERR("Event %s: Excluders can only be used with wildcarded events", event_name); - goto error; + e++; + x++; + goto cmp_chars; + } + + if (*x == '*') { + /* Event is a subset of the excluder */ + ERR("Event %s: %s excludes all events from %s", + event_name, exclusion, event_name); + goto error; + } + + if (*e == '*') { + /* + * Reached the end of the event name before the + * end of the exclusion: this is valid. + */ + goto end; + } + +cmp_chars: + if (*x != *e) { + warn = true; + break; + } + + x++; + e++; } - next_excluder = exclusions; - while (*next_excluder != 0) { - event_ptr = event_name; - excluder_ptr = next_excluder; - excluder_length = strcspn(next_excluder, ","); + goto end; - /* Scan both the excluder and the event letter by letter */ - while (1) { - char e, x; +error: + ret = -1; - e = *event_ptr; - x = *excluder_ptr; +end: + if (warn) { + WARN("Event %s: %s does not exclude any events from %s", + event_name, exclusion, event_name); + } - if (x == '*') { - /* Event is a subset of the excluder */ - ERR("Event %s: %.*s excludes all events from %s", - event_name, - excluder_length, - next_excluder, - event_name); - goto error; - } - if (e == '*') { - char *string; - char **new_exclusion_list; - - /* Excluder is a proper subset of event */ - string = lttng_strndup(next_excluder, excluder_length); - if (!string) { - PERROR("lttng_strndup error"); - goto error; - } - new_exclusion_list = realloc(exclusion_list, - sizeof(char *) * (exclusion_count + 1)); - if (!new_exclusion_list) { - PERROR("realloc"); - free(string); + return ret; +} + +static +int check_exclusions_subsets(const char *event_name, + char * const *exclusions) +{ + int ret = 0; + char * const *item; + + for (item = exclusions; *item; item++) { + ret = check_exclusion_subsets(event_name, *item); + if (ret) { + goto end; + } + } + +end: + return ret; +} + +static +int create_exclusion_list_and_validate(const char *event_name, + const char *exclusions_arg, + char ***exclusion_list) +{ + int ret = 0; + char **exclusions = NULL; + + /* Event name must be a valid globbing pattern to allow exclusions. */ + if (!strutils_is_star_glob_pattern(event_name)) { + ERR("Event %s: Exclusions can only be used with a globbing pattern", + event_name); + goto error; + } + + /* Split exclusions. */ + exclusions = strutils_split(exclusions_arg, ',', true); + if (!exclusions) { + goto error; + } + + /* + * If the event name is a star-at-end only globbing pattern, + * then we can validate the individual exclusions. Otherwise + * all exclusions are passed to the session daemon. + */ + if (strutils_is_star_at_the_end_only_glob_pattern(event_name)) { + char * const *exclusion; + + for (exclusion = exclusions; *exclusion; exclusion++) { + if (!strutils_is_star_glob_pattern(*exclusion) || + strutils_is_star_at_the_end_only_glob_pattern(*exclusion)) { + ret = check_exclusions_subsets( + event_name, exclusion); + if (ret) { goto error; } - exclusion_list = new_exclusion_list; - exclusion_count++; - exclusion_list[exclusion_count - 1] = string; - break; } - if (x != e) { - /* Excluder and event sets have no common elements */ - WARN("Event %s: %.*s does not exclude any events from %s", - event_name, - excluder_length, - next_excluder, - event_name); - break; - } - excluder_ptr++; - event_ptr++; - } - /* next excluder */ - next_excluder += excluder_length; - if (*next_excluder == ',') { - next_excluder++; } } + + *exclusion_list = exclusions; + goto end; + error: - while (exclusion_count--) { - free(exclusion_list[exclusion_count]); - } - if (exclusion_list != NULL) { - free(exclusion_list); - } - exclusion_list = NULL; - exclusion_count = 0; - ret = CMD_ERROR; + ret = -1; + strutils_free_null_terminated_array_of_strings(exclusions); + end: - *exclusion_count_ptr = exclusion_count; - *exclusion_list_ptr = exclusion_list; return ret; } -static void warn_on_truncated_exclusion_names(char **exclusion_list, - int exclusion_count, int *warn) +static void warn_on_truncated_exclusion_names(char * const *exclusion_list, + int *warn) { - size_t i = 0; - - for (i = 0; i < exclusion_count; ++i) { - const char *name = exclusion_list[i]; - size_t len = strlen(name); + char * const *exclusion; - if (len >= LTTNG_SYMBOL_NAME_LEN) { + for (exclusion = exclusion_list; *exclusion; exclusion++) { + if (strlen(*exclusion) >= LTTNG_SYMBOL_NAME_LEN) { WARN("Event exclusion \"%s\" will be truncated", - name); + *exclusion); *warn = 1; } } @@ -570,7 +600,6 @@ static int enable_events(char *session_name) char *event_name, *channel_name = NULL; struct lttng_event ev; struct lttng_domain dom; - int exclusion_count = 0; char **exclusion_list = NULL; memset(&ev, 0, sizeof(ev)); @@ -685,21 +714,23 @@ static int enable_events(char *session_name) } if (opt_exclude) { - ret = check_exclusion_subsets("*", opt_exclude, - &exclusion_count, &exclusion_list); - if (ret == CMD_ERROR) { + ret = create_exclusion_list_and_validate("*", + opt_exclude, &exclusion_list); + if (ret) { + ret = CMD_ERROR; goto error; } - ev.exclusion = 1; + ev.exclusion = 1; warn_on_truncated_exclusion_names(exclusion_list, - exclusion_count, &warn); + &warn); } if (!opt_filter) { ret = lttng_enable_event_with_exclusions(handle, &ev, channel_name, NULL, - exclusion_count, exclusion_list); + exclusion_list ? strutils_array_of_strings_len(exclusion_list) : 0, + exclusion_list); if (ret < 0) { switch (-ret) { case LTTNG_ERR_KERN_EVENT_EXIST: @@ -733,7 +764,7 @@ static int enable_events(char *session_name) switch (opt_event_type) { case LTTNG_EVENT_TRACEPOINT: if (opt_loglevel && dom.type != LTTNG_DOMAIN_KERNEL) { - char *exclusion_string = print_exclusions(exclusion_count, exclusion_list); + char *exclusion_string = print_exclusions(exclusion_list); if (!exclusion_string) { PERROR("Cannot allocate exclusion_string"); @@ -747,7 +778,7 @@ static int enable_events(char *session_name) opt_loglevel); free(exclusion_string); } else { - char *exclusion_string = print_exclusions(exclusion_count, exclusion_list); + char *exclusion_string = print_exclusions(exclusion_list); if (!exclusion_string) { PERROR("Cannot allocate exclusion_string"); @@ -770,7 +801,7 @@ static int enable_events(char *session_name) break; case LTTNG_EVENT_ALL: if (opt_loglevel && dom.type != LTTNG_DOMAIN_KERNEL) { - char *exclusion_string = print_exclusions(exclusion_count, exclusion_list); + char *exclusion_string = print_exclusions(exclusion_list); if (!exclusion_string) { PERROR("Cannot allocate exclusion_string"); @@ -784,7 +815,7 @@ static int enable_events(char *session_name) opt_loglevel); free(exclusion_string); } else { - char *exclusion_string = print_exclusions(exclusion_count, exclusion_list); + char *exclusion_string = print_exclusions(exclusion_list); if (!exclusion_string) { PERROR("Cannot allocate exclusion_string"); @@ -809,7 +840,9 @@ static int enable_events(char *session_name) if (opt_filter) { command_ret = lttng_enable_event_with_exclusions(handle, &ev, channel_name, - opt_filter, exclusion_count, exclusion_list); + opt_filter, + exclusion_list ? strutils_array_of_strings_len(exclusion_list) : 0, + exclusion_list); if (command_ret < 0) { switch (-command_ret) { case LTTNG_ERR_FILTER_EXIST: @@ -869,7 +902,7 @@ static int enable_events(char *session_name) } /* print exclusion */ - ret = mi_print_exclusion(exclusion_count, exclusion_list); + ret = mi_print_exclusion(exclusion_list); if (ret) { ret = CMD_ERROR; goto error; @@ -973,22 +1006,19 @@ static int enable_events(char *session_name) goto error; } /* Free previously allocated items */ - if (exclusion_list != NULL) { - while (exclusion_count--) { - free(exclusion_list[exclusion_count]); - } - free(exclusion_list); - exclusion_list = NULL; - } - /* Check for proper subsets */ - ret = check_exclusion_subsets(event_name, opt_exclude, - &exclusion_count, &exclusion_list); - if (ret == CMD_ERROR) { + strutils_free_null_terminated_array_of_strings( + exclusion_list); + exclusion_list = NULL; + ret = create_exclusion_list_and_validate( + event_name, opt_exclude, + &exclusion_list); + if (ret) { + ret = CMD_ERROR; goto error; } warn_on_truncated_exclusion_names( - exclusion_list, exclusion_count, &warn); + exclusion_list, &warn); } ev.loglevel_type = opt_loglevel_type; @@ -1045,8 +1075,10 @@ static int enable_events(char *session_name) command_ret = lttng_enable_event_with_exclusions(handle, &ev, channel_name, - NULL, exclusion_count, exclusion_list); - exclusion_string = print_exclusions(exclusion_count, exclusion_list); + NULL, + exclusion_list ? strutils_array_of_strings_len(exclusion_list) : 0, + exclusion_list); + exclusion_string = print_exclusions(exclusion_list); if (!exclusion_string) { PERROR("Cannot allocate exclusion_string"); error = 1; @@ -1121,8 +1153,10 @@ static int enable_events(char *session_name) ev.filter = 1; command_ret = lttng_enable_event_with_exclusions(handle, &ev, channel_name, - opt_filter, exclusion_count, exclusion_list); - exclusion_string = print_exclusions(exclusion_count, exclusion_list); + opt_filter, + exclusion_list ? strutils_array_of_strings_len(exclusion_list) : 0, + exclusion_list); + exclusion_string = print_exclusions(exclusion_list); if (!exclusion_string) { PERROR("Cannot allocate exclusion_string"); error = 1; @@ -1185,7 +1219,7 @@ static int enable_events(char *session_name) } /* print exclusion */ - ret = mi_print_exclusion(exclusion_count, exclusion_list); + ret = mi_print_exclusion(exclusion_list); if (ret) { ret = CMD_ERROR; goto error; @@ -1231,13 +1265,7 @@ error: ret = CMD_ERROR; } lttng_destroy_handle(handle); - - if (exclusion_list != NULL) { - while (exclusion_count--) { - free(exclusion_list[exclusion_count]); - } - free(exclusion_list); - } + strutils_free_null_terminated_array_of_strings(exclusion_list); /* Overwrite ret with error_holder if there was an actual error with * enabling an event. @@ -1413,3 +1441,4 @@ end: poptFreeContext(pc); return ret; } + diff --git a/src/lib/lttng-ctl/filter/Makefile.am b/src/lib/lttng-ctl/filter/Makefile.am index 13a9e195a..2c0d74880 100644 --- a/src/lib/lttng-ctl/filter/Makefile.am +++ b/src/lib/lttng-ctl/filter/Makefile.am @@ -15,12 +15,15 @@ libfilter_la_SOURCES = \ filter-visitor-generate-ir.c \ filter-visitor-ir-check-binary-op-nesting.c \ filter-visitor-ir-validate-string.c \ + filter-visitor-ir-validate-globbing.c \ + filter-visitor-ir-normalize-glob-patterns.c \ filter-visitor-generate-bytecode.c \ filter-ast.h \ filter-bytecode.h \ filter-ir.h \ memstream.h libfilter_la_CFLAGS = -include filter-symbols.h +libfilter_la_LIBADD = $(top_builddir)/src/common/string-utils/libstring-utils.la AM_YFLAGS = -t -d -v diff --git a/src/lib/lttng-ctl/filter/filter-ast.h b/src/lib/lttng-ctl/filter/filter-ast.h index 7f1883f29..73d8d44e6 100644 --- a/src/lib/lttng-ctl/filter/filter-ast.h +++ b/src/lib/lttng-ctl/filter/filter-ast.h @@ -187,5 +187,7 @@ void filter_bytecode_free(struct filter_parser_ctx *ctx); int filter_visitor_ir_check_binary_op_nesting(struct filter_parser_ctx *ctx); int filter_visitor_ir_check_binary_comparator(struct filter_parser_ctx *ctx); int filter_visitor_ir_validate_string(struct filter_parser_ctx *ctx); +int filter_visitor_ir_normalize_glob_patterns(struct filter_parser_ctx *ctx); +int filter_visitor_ir_validate_globbing(struct filter_parser_ctx *ctx); #endif /* _FILTER_AST_H */ diff --git a/src/lib/lttng-ctl/filter/filter-bytecode.h b/src/lib/lttng-ctl/filter/filter-bytecode.h index cdc334555..627d0d6f0 100644 --- a/src/lib/lttng-ctl/filter/filter-bytecode.h +++ b/src/lib/lttng-ctl/filter/filter-bytecode.h @@ -72,7 +72,7 @@ enum filter_op { FILTER_OP_GE = 16, FILTER_OP_LE = 17, - /* string binary comparator */ + /* string binary comparator: apply to */ FILTER_OP_EQ_STRING = 18, FILTER_OP_NE_STRING = 19, FILTER_OP_GT_STRING = 20, @@ -153,6 +153,16 @@ enum filter_op { FILTER_OP_LOAD_FIELD_REF_USER_STRING = 74, FILTER_OP_LOAD_FIELD_REF_USER_SEQUENCE = 75, + /* + * load immediate star globbing pattern (literal string) + * from immediate + */ + FILTER_OP_LOAD_STAR_GLOB_STRING = 76, + + /* globbing pattern binary operator: apply to */ + FILTER_OP_EQ_STAR_GLOB_STRING = 77, + FILTER_OP_NE_STAR_GLOB_STRING = 78, + NR_FILTER_OPS, }; diff --git a/src/lib/lttng-ctl/filter/filter-ir.h b/src/lib/lttng-ctl/filter/filter-ir.h index 5c3fa4ab5..10f339e6b 100644 --- a/src/lib/lttng-ctl/filter/filter-ir.h +++ b/src/lib/lttng-ctl/filter/filter-ir.h @@ -56,13 +56,27 @@ enum ir_side { IR_RIGHT, }; +enum ir_load_string_type { + /* Plain, no globbing at all: `hello world`. */ + IR_LOAD_STRING_TYPE_PLAIN = 0, + + /* Star at the end only: `hello *`. */ + IR_LOAD_STRING_TYPE_GLOB_STAR_END, + + /* At least one star, anywhere, but not at the end only: `he*wor*`. */ + IR_LOAD_STRING_TYPE_GLOB_STAR, +}; + struct ir_op_root { struct ir_op *child; }; struct ir_op_load { union { - char *string; + struct { + enum ir_load_string_type type; + char *value; + } string; int64_t num; double flt; char *ref; diff --git a/src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c b/src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c index e28abd572..d0cc49556 100644 --- a/src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c +++ b/src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c @@ -173,13 +173,38 @@ int visit_node_load(struct filter_parser_ctx *ctx, struct ir_op *node) { struct load_op *insn; uint32_t insn_len = sizeof(struct load_op) - + strlen(node->u.load.u.string) + 1; + + strlen(node->u.load.u.string.value) + 1; insn = calloc(insn_len, 1); if (!insn) return -ENOMEM; - insn->op = FILTER_OP_LOAD_STRING; - strcpy(insn->data, node->u.load.u.string); + + switch (node->u.load.u.string.type) { + case IR_LOAD_STRING_TYPE_GLOB_STAR: + /* + * We explicitly tell the interpreter here that + * this load is a full star globbing pattern so + * that the appropriate matching function can be + * called. Also, see comment below. + */ + insn->op = FILTER_OP_LOAD_STAR_GLOB_STRING; + break; + default: + /* + * This is the "legacy" string, which includes + * star globbing patterns with a star only at + * the end. Both "plain" and "star at the end" + * literal strings are handled at the same place + * by the tracer's filter bytecode interpreter, + * whereas full star globbing patterns (stars + * can be anywhere in the string) is a special + * case. + */ + insn->op = FILTER_OP_LOAD_STRING; + break; + } + + strcpy(insn->data, node->u.load.u.string.value); ret = bytecode_push(&ctx->bytecode, insn, 1, insn_len); free(insn); return ret; diff --git a/src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c b/src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c index f734b56e3..e3dc1aab9 100644 --- a/src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c +++ b/src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c @@ -31,6 +31,7 @@ #include "filter-ir.h" #include +#include static struct ir_op *generate_ir_recursive(struct filter_parser_ctx *ctx, @@ -68,6 +69,22 @@ struct ir_op *make_op_root(struct ir_op *child, enum ir_side side) return op; } +static +enum ir_load_string_type get_literal_string_type(const char *string) +{ + assert(string); + + if (strutils_is_star_glob_pattern(string)) { + if (strutils_is_star_at_the_end_only_glob_pattern(string)) { + return IR_LOAD_STRING_TYPE_GLOB_STAR_END; + } + + return IR_LOAD_STRING_TYPE_GLOB_STAR; + } + + return IR_LOAD_STRING_TYPE_PLAIN; +} + static struct ir_op *make_op_load_string(char *string, enum ir_side side) { @@ -80,8 +97,9 @@ struct ir_op *make_op_load_string(char *string, enum ir_side side) op->data_type = IR_DATA_STRING; op->signedness = IR_SIGN_UNKNOWN; op->side = side; - op->u.load.u.string = strdup(string); - if (!op->u.load.u.string) { + op->u.load.u.string.type = get_literal_string_type(string); + op->u.load.u.string.value = strdup(string); + if (!op->u.load.u.string.value) { free(op); return NULL; } @@ -366,7 +384,7 @@ void filter_free_ir_recursive(struct ir_op *op) case IR_OP_LOAD: switch (op->data_type) { case IR_DATA_STRING: - free(op->u.load.u.string); + free(op->u.load.u.string.value); break; case IR_DATA_FIELD_REF: /* fall-through */ case IR_DATA_GET_CONTEXT_REF: diff --git a/src/lib/lttng-ctl/filter/filter-visitor-ir-normalize-glob-patterns.c b/src/lib/lttng-ctl/filter/filter-visitor-ir-normalize-glob-patterns.c new file mode 100644 index 000000000..3b1069558 --- /dev/null +++ b/src/lib/lttng-ctl/filter/filter-visitor-ir-normalize-glob-patterns.c @@ -0,0 +1,94 @@ +/* + * filter-visitor-ir-normalize-glob-patterns.c + * + * LTTng filter IR normalize string + * + * Copyright 2017 - Philippe Proulx + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "filter-ast.h" +#include "filter-parser.h" +#include "filter-ir.h" + +static +int normalize_glob_patterns(struct ir_op *node) +{ + switch (node->op) { + case IR_OP_UNKNOWN: + default: + fprintf(stderr, "[error] %s: unknown op type\n", __func__); + return -EINVAL; + + case IR_OP_ROOT: + return normalize_glob_patterns(node->u.root.child); + case IR_OP_LOAD: + { + if (node->data_type == IR_DATA_STRING) { + enum ir_load_string_type type = + node->u.load.u.string.type; + if (type == IR_LOAD_STRING_TYPE_GLOB_STAR_END || + type == IR_LOAD_STRING_TYPE_GLOB_STAR) { + assert(node->u.load.u.string.value); + strutils_normalize_star_glob_pattern( + node->u.load.u.string.value); + } + } + + return 0; + } + case IR_OP_UNARY: + return normalize_glob_patterns(node->u.unary.child); + case IR_OP_BINARY: + { + int ret = normalize_glob_patterns(node->u.binary.left); + + if (ret) + return ret; + return normalize_glob_patterns(node->u.binary.right); + } + case IR_OP_LOGICAL: + { + int ret; + + ret = normalize_glob_patterns(node->u.logical.left); + if (ret) + return ret; + return normalize_glob_patterns(node->u.logical.right); + } + } +} + +/* + * This function normalizes all the globbing literal strings with + * utils_normalize_glob_pattern(). See the documentation of + * utils_normalize_glob_pattern() for more details. + */ +LTTNG_HIDDEN +int filter_visitor_ir_normalize_glob_patterns(struct filter_parser_ctx *ctx) +{ + return normalize_glob_patterns(ctx->ir_root); +} diff --git a/src/lib/lttng-ctl/filter/filter-visitor-ir-validate-globbing.c b/src/lib/lttng-ctl/filter/filter-visitor-ir-validate-globbing.c new file mode 100644 index 000000000..4f825cbc4 --- /dev/null +++ b/src/lib/lttng-ctl/filter/filter-visitor-ir-validate-globbing.c @@ -0,0 +1,122 @@ +/* + * filter-visitor-ir-validate-globbing.c + * + * LTTng filter IR validate globbing + * + * Copyright 2017 - Philippe Proulx + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "filter-ast.h" +#include "filter-parser.h" +#include "filter-ir.h" + +static +int validate_globbing(struct ir_op *node) +{ + int ret; + + switch (node->op) { + case IR_OP_UNKNOWN: + default: + fprintf(stderr, "[error] %s: unknown op type\n", __func__); + return -EINVAL; + + case IR_OP_ROOT: + return validate_globbing(node->u.root.child); + case IR_OP_LOAD: + return 0; + case IR_OP_UNARY: + return validate_globbing(node->u.unary.child); + case IR_OP_BINARY: + { + struct ir_op *left = node->u.binary.left; + struct ir_op *right = node->u.binary.right; + + if (left->op == IR_OP_LOAD && right->op == IR_OP_LOAD && + left->data_type == IR_DATA_STRING && + right->data_type == IR_DATA_STRING) { + /* Test 1. */ + if (left->u.load.u.string.type == IR_LOAD_STRING_TYPE_GLOB_STAR && + right->u.load.u.string.type != IR_LOAD_STRING_TYPE_PLAIN) { + fprintf(stderr, "[error] Cannot compare two globbing patterns\n"); + return -1; + } + + if (right->u.load.u.string.type == IR_LOAD_STRING_TYPE_GLOB_STAR && + left->u.load.u.string.type != IR_LOAD_STRING_TYPE_PLAIN) { + fprintf(stderr, "[error] Cannot compare two globbing patterns\n"); + return -1; + } + } + + if ((left->op == IR_OP_LOAD && left->data_type == IR_DATA_STRING) || + (right->op == IR_OP_LOAD && right->data_type == IR_DATA_STRING)) { + if ((left->op == IR_OP_LOAD && left->u.load.u.string.type == IR_LOAD_STRING_TYPE_GLOB_STAR) || + (right->op == IR_OP_LOAD && right->u.load.u.string.type == IR_LOAD_STRING_TYPE_GLOB_STAR)) { + /* Test 2. */ + if (node->u.binary.type != AST_OP_EQ && + node->u.binary.type != AST_OP_NE) { + fprintf(stderr, "[error] Only the `==` and `!=` operators are allowed with a globbing pattern\n"); + return -1; + } + } + } + + ret = validate_globbing(left); + if (ret) { + return ret; + } + + return validate_globbing(right); + } + case IR_OP_LOGICAL: + ret = validate_globbing(node->u.logical.left); + if (ret) + return ret; + return validate_globbing(node->u.logical.right); + } +} + +/* + * This function recursively validates that: + * + * 1. When there's a binary operation between two literal strings, + * if one of them has the IR_LOAD_STRING_TYPE_GLOB_STAR type, + * the other one has the IR_LOAD_STRING_TYPE_PLAIN type. + * + * In other words, you cannot compare two globbing patterns, except + * for two globbing patterns with only a star at the end for backward + * compatibility reasons. + * + * 2. When there's a binary operation between two literal strings, if + * one of them is a (full) star globbing pattern, the binary + * operation is either == or !=. + */ +LTTNG_HIDDEN +int filter_visitor_ir_validate_globbing(struct filter_parser_ctx *ctx) +{ + return validate_globbing(ctx->ir_root); +} diff --git a/src/lib/lttng-ctl/filter/filter-visitor-ir-validate-string.c b/src/lib/lttng-ctl/filter/filter-visitor-ir-validate-string.c index 30b0b5dc3..5c0a58efb 100644 --- a/src/lib/lttng-ctl/filter/filter-visitor-ir-validate-string.c +++ b/src/lib/lttng-ctl/filter/filter-visitor-ir-validate-string.c @@ -77,13 +77,9 @@ int validate_string(struct ir_op *node) if (node->data_type == IR_DATA_STRING) { const char *str; - assert(node->u.load.u.string); - str = node->u.load.u.string; + assert(node->u.load.u.string.value); + str = node->u.load.u.string.value; - /* - * Make sure that if a non-escaped wildcard is - * present, it is the last character of the string. - */ for (;;) { enum parse_char_result res; @@ -95,20 +91,6 @@ int validate_string(struct ir_op *node) str++; switch (res) { - case PARSE_CHAR_WILDCARD: - { - if (*str) { - /* - * Found a wildcard followed by non-null - * character; unsupported. - */ - ret = -EINVAL; - fprintf(stderr, - "Wildcards may only be used as the last character of a string in a filter.\n"); - goto end_load; - } - break; - } case PARSE_CHAR_UNKNOWN: ret = -EINVAL; fprintf(stderr, diff --git a/src/lib/lttng-ctl/lttng-ctl.c b/src/lib/lttng-ctl/lttng-ctl.c index 0f9378c2c..469fffa98 100644 --- a/src/lib/lttng-ctl/lttng-ctl.c +++ b/src/lib/lttng-ctl/lttng-ctl.c @@ -797,7 +797,7 @@ static char *set_agent_filter(const char *filter, struct lttng_event *ev) assert(ev); /* Don't add filter for the '*' event. */ - if (ev->name[0] != '*') { + if (strcmp(ev->name, "*") != 0) { if (filter) { err = asprintf(&agent_filter, "(%s) && (logger_name == \"%s\")", filter, ev->name); @@ -920,12 +920,28 @@ static int generate_filter(char *filter_expression, ret = -LTTNG_ERR_FILTER_INVAL; goto parse_error; } + + /* Normalize globbing patterns in the expression. */ + ret = filter_visitor_ir_normalize_glob_patterns(ctx); + if (ret) { + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + /* Validate strings used as literals in the expression. */ ret = filter_visitor_ir_validate_string(ctx); if (ret) { ret = -LTTNG_ERR_FILTER_INVAL; goto parse_error; } + + /* Validate globbing patterns in the expression. */ + ret = filter_visitor_ir_validate_globbing(ctx); + if (ret) { + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + dbg_printf("done\n"); dbg_printf("Generating bytecode... "); -- 2.34.1