Add support for "full" star globbing patterns in event names and filters
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Wed, 15 Feb 2017 16:38:36 +0000 (11:38 -0500)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Fri, 5 May 2017 15:30:24 +0000 (11:30 -0400)
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 <eeppeliteloop@gmail.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
14 files changed:
src/bin/lttng-sessiond/Makefile.am
src/bin/lttng-sessiond/cmd.c
src/bin/lttng/Makefile.am
src/bin/lttng/commands/enable_events.c
src/lib/lttng-ctl/filter/Makefile.am
src/lib/lttng-ctl/filter/filter-ast.h
src/lib/lttng-ctl/filter/filter-bytecode.h
src/lib/lttng-ctl/filter/filter-ir.h
src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c
src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c
src/lib/lttng-ctl/filter/filter-visitor-ir-normalize-glob-patterns.c [new file with mode: 0644]
src/lib/lttng-ctl/filter/filter-visitor-ir-validate-globbing.c [new file with mode: 0644]
src/lib/lttng-ctl/filter/filter-visitor-ir-validate-string.c
src/lib/lttng-ctl/lttng-ctl.c

index 755c2aa0ff61153cb0151f7c45a0d70fc1933b26..4b4e7ec996e15ccde73276b1a4a51ddb58f72525 100644 (file)
@@ -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
index 7b9647436ac82e059fdc61aa7216ddbbe9ca9dfc..f3ff25642e701a0f83e78bbfd02144c1a118dfdb 100644 (file)
@@ -35,6 +35,7 @@
 #include <lttng/condition/condition.h>
 #include <lttng/action/action.h>
 #include <lttng/channel-internal.h>
+#include <common/string-utils/string-utils.h>
 
 #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:
        {
index 23fff77a7f9d144900df5df2e8b0e4071dae7dde..c60c0ee6d080661be0aec2bedc225b3620734b77 100644 (file)
@@ -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
index 1ed3a0ab77c435d5dbe80b5fbfb95778e9c0ec2c..cc79b0b749247f83123b5252e1aed833bb3fd669 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <src/common/sessiond-comm/sessiond-comm.h>
 #include <common/compat/string.h>
+#include <common/string-utils/string-utils.h>
 
 /* Mi dependancy */
 #include <common/mi-lttng.h>
@@ -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;
 }
+
index 13a9e195a5f24b11bc85a2862221c76fd9106d07..2c0d748800c14b96805cceb456e13a6050400db9 100644 (file)
@@ -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
 
index 7f1883f29656ba1836699b11bc3178d02dfe7966..73d8d44e6a189bfc72550af99776d84d5711a1dd 100644 (file)
@@ -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 */
index cdc334555de37daeb5c77d12374f3b7fbc5523c5..627d0d6f0b6f58943b7326b8c59ba019f8682f11 100644 (file)
@@ -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,
 };
 
index 5c3fa4ab52f97332095bc9923fde9c1c188a0771..10f339e6be931f6af0ca9d24683b10fad42490e6 100644 (file)
@@ -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;
index e28abd57271ecec91936d19e2918c8c1b1aa986c..d0cc49556fc11560b02a8aca54cb736d2ac4291f 100644 (file)
@@ -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;
index f734b56e3fb4f6bbbd2a9d6609d9222f85f3b379..e3dc1aab9481467ec4140341255b099b4d71ff76 100644 (file)
@@ -31,6 +31,7 @@
 #include "filter-ir.h"
 
 #include <common/macros.h>
+#include <common/string-utils/string-utils.h>
 
 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 (file)
index 0000000..3b10695
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * filter-visitor-ir-normalize-glob-patterns.c
+ *
+ * LTTng filter IR normalize string
+ *
+ * Copyright 2017 - Philippe Proulx <pproulx@efficios.com>
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <common/macros.h>
+#include <common/string-utils/string-utils.h>
+
+#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 (file)
index 0000000..4f825cb
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * filter-visitor-ir-validate-globbing.c
+ *
+ * LTTng filter IR validate globbing
+ *
+ * Copyright 2017 - Philippe Proulx <pproulx@efficios.com>
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <common/macros.h>
+
+#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);
+}
index 30b0b5dc33f4057b149984ea450460c9ccfc2c73..5c0a58efbe01ad625f04b4180744238fbf2b1a05 100644 (file)
@@ -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,
index 0f9378c2c5d82507df87d2b83a46a5d5f192d733..469fffa980f33b6c527d94ac47e6fb51a3ce6f42 100644 (file)
@@ -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... ");
This page took 0.069789 seconds and 5 git commands to generate.