]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: Rewrite event filter internals to use an abstract-syntax-tree
authorJosef 'Jeff' Sipek <jeff.sipek@open-xchange.com>
Fri, 22 May 2020 17:08:24 +0000 (13:08 -0400)
committerJosef 'Jeff' Sipek <jeff.sipek@open-xchange.com>
Fri, 22 May 2020 17:08:24 +0000 (13:08 -0400)
src/lib/Makefile.am
src/lib/event-filter-private.h [new file with mode: 0644]
src/lib/event-filter.c

index 3b7a23dec7d5c8f39fbe9d5ddda0a0e8c9a2830a..584f502c06c4ce2b79491855111ee5b587cae443 100644 (file)
@@ -192,6 +192,7 @@ headers = \
        eacces-error.h \
        env-util.h \
        event-filter.h \
+       event-filter-private.h \
        event-log.h \
        execv-const.h \
        failures.h \
diff --git a/src/lib/event-filter-private.h b/src/lib/event-filter-private.h
new file mode 100644 (file)
index 0000000..9f566da
--- /dev/null
@@ -0,0 +1,56 @@
+#ifndef EVENT_FILTER_PRIVATE_H
+#define EVENT_FILTER_PRIVATE_H
+
+enum event_filter_node_op {
+       /* leaf nodes */
+       EVENT_FILTER_OP_CMP_EQ = 1,
+       EVENT_FILTER_OP_CMP_GT,
+       EVENT_FILTER_OP_CMP_LT,
+       EVENT_FILTER_OP_CMP_GE,
+       EVENT_FILTER_OP_CMP_LE,
+
+       /* internal nodes */
+       EVENT_FILTER_OP_AND,
+       EVENT_FILTER_OP_OR,
+       EVENT_FILTER_OP_NOT,
+};
+
+enum event_filter_node_type {
+       /* internal nodes */
+       EVENT_FILTER_NODE_TYPE_LOGIC = 1, /* children */
+
+       /* leaf nodes */
+       EVENT_FILTER_NODE_TYPE_EVENT_NAME, /* str */
+       EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION, /* str + int */
+       EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY, /* cat */
+       EVENT_FILTER_NODE_TYPE_EVENT_FIELD, /* field */
+};
+
+struct event_filter_node {
+       enum event_filter_node_type type;
+       enum event_filter_node_op op;
+
+       /* internal node */
+       struct event_filter_node *children[2];
+
+       /* leaf node */
+       const char *str;
+       uintmax_t intmax;
+       struct {
+               /*
+                * We may be dealing with one of two situations:
+                *
+                * (1) the category is registered
+                * (2) the category is not registered
+                *
+                * Regardless of which of the two cases we're dealing with,
+                * we have a name for it.  Additionally, if a category is
+                * registered, the category pointer is non-NULL.
+                */
+               const char *name;
+               struct event_category *ptr;
+       } category;
+       struct event_field field;
+};
+
+#endif
index b51dcc94d0bff46f869c4192da73cb18a0f1266e..cde1e188e5d3147263bd61fa87397182f566a46d 100644 (file)
@@ -8,6 +8,7 @@
 #include "wildcard-match.h"
 #include "lib-event-private.h"
 #include "event-filter.h"
+#include "event-filter-private.h"
 
 enum event_filter_code {
        EVENT_FILTER_CODE_NAME          = 'n',
@@ -16,14 +17,6 @@ enum event_filter_code {
        EVENT_FILTER_CODE_FIELD         = 'f',
 };
 
-struct event_filter_category {
-       const char *name;
-       /* Pointer to the event_category. NULL if the category isn't
-          registered yet. If it becomes registered, this will be filled out
-          by the category register callback. */
-       struct event_category *category;
-};
-
 enum event_filter_log_type {
        EVENT_FILTER_LOG_TYPE_DEBUG     = BIT(0),
        EVENT_FILTER_LOG_TYPE_INFO      = BIT(1),
@@ -47,18 +40,8 @@ static enum event_filter_log_type event_filter_log_types[] = {
 };
 
 struct event_filter_query_internal {
-       unsigned int categories_count;
-       unsigned int fields_count;
-
-       bool has_unregistered_categories;
-       struct event_filter_category *categories;
-       const struct event_field *fields;
        enum event_filter_log_type log_type_mask;
-
-       const char *name;
-       const char *source_filename;
-       unsigned int source_linenum;
-
+       struct event_filter_node *expr;
        void *context;
 };
 
@@ -141,63 +124,84 @@ event_filter_category_to_log_type(const char *name,
        return FALSE;
 }
 
+static void add_node(pool_t pool, struct event_filter_node **root,
+                    struct event_filter_node *new)
+{
+       struct event_filter_node *parent;
+
+       if (*root == NULL) {
+               *root = new;
+               return;
+       }
+
+       parent = p_new(pool, struct event_filter_node, 1);
+       parent->type = EVENT_FILTER_NODE_TYPE_LOGIC;
+       parent->op = EVENT_FILTER_OP_AND;
+       parent->children[0] = *root;
+       parent->children[1] = new;
+
+       *root = parent;
+}
+
 static void
-event_filter_add_categories(struct event_filter *filter,
+event_filter_add_categories(pool_t pool,
                            struct event_filter_query_internal *int_query,
                            const char *const *categories)
 {
        unsigned int categories_count = str_array_length(categories);
-       struct event_filter_category *cat;
        enum event_filter_log_type log_type;
-       unsigned int i, j;
+       unsigned int i;
 
        if (categories_count == 0)
                return;
 
-       /* copy strings */
-       cat = p_new(filter->pool, struct event_filter_category,
-                   categories_count);
-       for (i = j = 0; i < categories_count; i++) {
+       for (i = 0; i < categories_count; i++) {
+               struct event_filter_node *node;
+
                if (event_filter_category_to_log_type(categories[i], &log_type)) {
                        int_query->log_type_mask |= log_type;
                        continue;
                }
-               cat[j].name = p_strdup(filter->pool, categories[i]);
-               cat[j].category = event_category_find_registered(categories[i]);
-               if (cat[j].category == NULL)
-                       int_query->has_unregistered_categories = TRUE;
-               j++;
+
+               node = p_new(pool, struct event_filter_node, 1);
+               node->type = EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY;
+               node->op = EVENT_FILTER_OP_CMP_EQ;
+               node->category.name = p_strdup(pool, categories[i]);
+               node->category.ptr = event_category_find_registered(categories[i]);
+
+               add_node(pool, &int_query->expr, node);
        }
-       int_query->categories_count = j;
-       int_query->categories = cat;
 }
 
 static void
-event_filter_add_fields(struct event_filter *filter,
+event_filter_add_fields(pool_t pool,
                        struct event_filter_query_internal *int_query,
                        const struct event_filter_field *fields)
 {
-       struct event_field *int_fields;
-       unsigned int i, count;
+       unsigned int i;
 
-       for (count = 0; fields[count].key != NULL; count++) ;
-       if (count == 0)
+       if (fields == NULL)
                return;
 
-       int_fields = p_new(filter->pool, struct event_field, count);
-       for (i = 0; i < count; i++) {
-               int_fields[i].key = p_strdup(filter->pool, fields[i].key);
+       for (i = 0; fields[i].key != NULL; i++) {
+               struct event_filter_node *node;
+
+               node = p_new(pool, struct event_filter_node, 1);
+               node->type = EVENT_FILTER_NODE_TYPE_EVENT_FIELD;
+               node->op = EVENT_FILTER_OP_CMP_EQ;
+               node->field.key = p_strdup(pool, fields[i].key);
+               node->field.value.str = p_strdup(pool, fields[i].value);
+
                /* Filter currently supports only comparing strings
                   and numbers. */
-               int_fields[i].value.str = p_strdup(filter->pool, fields[i].value);
-               if (str_to_intmax(fields[i].value, &int_fields[i].value.intmax) < 0) {
+               if (str_to_intmax(fields[i].value, &node->field.value.intmax) < 0) {
                        /* not a number - no problem
                           Either we have a string, or a number with wildcards */
-                       int_fields[i].value.intmax = INT_MIN;
+                       node->field.value.intmax = INT_MIN;
                }
+
+               add_node(pool, &int_query->expr, node);
        }
-       int_query->fields_count = count;
-       int_query->fields = int_fields;
 }
 
 void event_filter_add(struct event_filter *filter,
@@ -207,20 +211,35 @@ void event_filter_add(struct event_filter *filter,
 
        int_query = array_append_space(&filter->queries);
        int_query->context = query->context;
+       int_query->expr = NULL;
+
+       if (query->name != NULL) {
+               struct event_filter_node *node;
+
+               node = p_new(filter->pool, struct event_filter_node, 1);
+               node->type = EVENT_FILTER_NODE_TYPE_EVENT_NAME;
+               node->op = EVENT_FILTER_OP_CMP_EQ;
+               node->str = p_strdup(filter->pool, query->name);
 
-       if (query->name != NULL)
-               int_query->name = p_strdup(filter->pool, query->name);
-       else
+               add_node(filter->pool, &int_query->expr, node);
+       } else {
                filter->named_queries_only = FALSE;
+       }
 
-       int_query->source_filename =
-               p_strdup_empty(filter->pool, query->source_filename);
-       int_query->source_linenum = query->source_linenum;
+       if ((query->source_filename != NULL) && (query->source_filename[0] != '\0')) {
+               struct event_filter_node *node;
 
-       if (query->categories != NULL)
-               event_filter_add_categories(filter, int_query, query->categories);
-       if (query->fields != NULL)
-               event_filter_add_fields(filter, int_query, query->fields);
+               node = p_new(filter->pool, struct event_filter_node, 1);
+               node->type = EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION;
+               node->op = EVENT_FILTER_OP_CMP_EQ;
+               node->str = p_strdup_empty(filter->pool, query->source_filename);
+               node->intmax = query->source_linenum;
+
+               add_node(filter->pool, &int_query->expr, node);
+       }
+
+       event_filter_add_categories(filter->pool, int_query, query->categories);
+       event_filter_add_fields(filter->pool, int_query, query->fields);
 
        if (int_query->log_type_mask == 0) {
                /* no explicit log types given. default to all. */
@@ -228,55 +247,46 @@ void event_filter_add(struct event_filter *filter,
        }
 }
 
+static struct event_filter_node *
+clone_expr(pool_t pool, struct event_filter_node *old)
+{
+       struct event_filter_node *new;
+
+       if (old == NULL)
+               return NULL;
+
+       new = p_new(pool, struct event_filter_node, 1);
+       new->type = old->type;
+       new->op = old->op;
+       new->children[0] = clone_expr(pool, old->children[0]);
+       new->children[1] = clone_expr(pool, old->children[1]);
+       new->str = p_strdup_empty(pool, old->str);
+       new->intmax = old->intmax;
+       new->category.name = p_strdup_empty(pool, old->category.name);
+       new->category.ptr = old->category.ptr;
+       new->field.key = p_strdup_empty(pool, old->field.key);
+       new->field.value_type = old->field.value_type;
+       new->field.value.str = p_strdup_empty(pool, old->field.value.str);
+       new->field.value.intmax = old->field.value.intmax;
+       new->field.value.timeval = old->field.value.timeval;
+
+       return new;
+}
+
 static void
 event_filter_merge_with_context_internal(struct event_filter *dest,
                                         const struct event_filter *src,
                                         void *new_context, bool with_context)
 {
        const struct event_filter_query_internal *int_query;
-       struct event_filter_query query;
-       unsigned int i;
 
        array_foreach(&src->queries, int_query) T_BEGIN {
-               i_zero(&query);
-               query.context = with_context ? new_context : int_query->context;
-               query.name = int_query->name;
-               query.source_filename = int_query->source_filename;
-               query.source_linenum = int_query->source_linenum;
-
-               if (int_query->categories_count > 0 ||
-                   int_query->log_type_mask != EVENT_FILTER_LOG_TYPE_ALL) {
-                       ARRAY_TYPE(const_string) categories;
-
-                       t_array_init(&categories, int_query->categories_count);
-                       for (i = 0; i < int_query->categories_count; i++) {
-                               array_push_back(&categories,
-                                               &int_query->categories[i].name);
-                       }
-                       for (i = 0; i < N_ELEMENTS(event_filter_log_type_names); i++) {
-                               if ((int_query->log_type_mask & (1 << i)) == 0)
-                                       continue;
-                               array_push_back(&categories,
-                                               &event_filter_log_type_names[i]);
-                       }
-                       array_append_zero(&categories);
-                       query.categories = array_front(&categories);
-               }
-               if (int_query->fields_count > 0) {
-                       ARRAY(struct event_filter_field) fields;
-
-                       t_array_init(&fields, int_query->fields_count);
-                       for (i = 0; i < int_query->fields_count; i++) {
-                               struct event_filter_field *field =
-                                       array_append_space(&fields);
-                               field->key = p_strdup(dest->pool, int_query->fields[i].key);
-                               field->value = p_strdup(dest->pool, int_query->fields[i].value.str);
-                       }
-                       array_append_zero(&fields);
-                       query.fields = array_front(&fields);
-               }
+               struct event_filter_query_internal *new;
 
-               event_filter_add(dest, &query);
+               new = array_append_space(&dest->queries);
+               new->log_type_mask = int_query->log_type_mask;
+               new->expr = clone_expr(dest->pool, int_query->expr);
+               new->context = with_context ? new_context : int_query->context;
        } T_END;
 }
 
@@ -294,26 +304,53 @@ void event_filter_merge_with_context(struct event_filter *dest,
 }
 
 static void
-event_filter_export_query(const struct event_filter_query_internal *query,
-                         string_t *dest)
+event_filter_export_query_expr(const struct event_filter_query_internal *query,
+                              struct event_filter_node *node,
+                              string_t *dest)
 {
-       unsigned int i;
+       if (node == NULL)
+               return;
 
-       if (query->name != NULL) {
+       switch (node->type) {
+       case EVENT_FILTER_NODE_TYPE_LOGIC:
+               /* currently only AND is supported */
+               i_assert(node->op == EVENT_FILTER_OP_AND);
+               event_filter_export_query_expr(query, node->children[0], dest);
+               event_filter_export_query_expr(query, node->children[1], dest);
+               break;
+       case EVENT_FILTER_NODE_TYPE_EVENT_NAME:
                str_append_c(dest, EVENT_FILTER_CODE_NAME);
-               str_append_tabescaped(dest, query->name);
+               str_append_tabescaped(dest, node->str);
                str_append_c(dest, '\t');
-       }
-       if (query->source_filename != NULL) {
+               break;
+       case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION:
                str_append_c(dest, EVENT_FILTER_CODE_SOURCE);
-               str_append_tabescaped(dest, query->source_filename);
-               str_printfa(dest, "\t%u\t", query->source_linenum);
-       }
-       for (i = 0; i < query->categories_count; i++) {
+               str_append_tabescaped(dest, node->str);
+               str_printfa(dest, "\t%ju\t", node->intmax);
+               break;
+       case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY:
                str_append_c(dest, EVENT_FILTER_CODE_CATEGORY);
-               str_append_tabescaped(dest, query->categories[i].name);
+               str_append_tabescaped(dest, node->category.name);
+               str_append_c(dest, '\t');
+               break;
+       case EVENT_FILTER_NODE_TYPE_EVENT_FIELD:
+               str_append_c(dest, EVENT_FILTER_CODE_FIELD);
+               str_append_tabescaped(dest, node->field.key);
+               str_append_c(dest, '\t');
+               str_append_tabescaped(dest, node->field.value.str);
                str_append_c(dest, '\t');
+               break;
        }
+}
+
+static void
+event_filter_export_query(const struct event_filter_query_internal *query,
+                         string_t *dest)
+{
+       unsigned int i;
+
+       event_filter_export_query_expr(query, query->expr, dest);
+
        if (query->log_type_mask != EVENT_FILTER_LOG_TYPE_ALL) {
                for (i = 0; i < N_ELEMENTS(event_filter_log_type_names); i++) {
                        if ((query->log_type_mask & (1 << i)) == 0)
@@ -323,14 +360,6 @@ event_filter_export_query(const struct event_filter_query_internal *query,
                        str_append_c(dest, '\t');
                }
        }
-
-       for (i = 0; i < query->fields_count; i++) {
-               str_append_c(dest, EVENT_FILTER_CODE_FIELD);
-               str_append_tabescaped(dest, query->fields[i].key);
-               str_append_c(dest, '\t');
-               str_append_tabescaped(dest, query->fields[i].value.str);
-               str_append_c(dest, '\t');
-       }
 }
 
 void event_filter_export(struct event_filter *filter, string_t *dest)
@@ -445,11 +474,14 @@ event_category_match(const struct event_category *category,
 }
 
 static bool
-event_has_category(struct event *event, struct event_category *wanted_category)
+event_has_category(struct event *event, struct event_filter_node *node)
 {
+       struct event_category *wanted_category = node->category.ptr;
        struct event_category *const *catp;
 
-       i_assert(wanted_category != NULL);
+       /* category not registered, therefore the event cannot have it */
+       if (wanted_category == NULL)
+               return FALSE;
 
        while (event != NULL) {
                if (array_is_created(&event->categories)) {
@@ -464,23 +496,6 @@ event_has_category(struct event *event, struct event_category *wanted_category)
        return FALSE;
 }
 
-static bool
-event_filter_query_match_categories(const struct event_filter_query_internal *query,
-                                   struct event *event)
-{
-       if (query->has_unregistered_categories) {
-               /* At least one of the categories in the filter hasn't even
-                  been registered yet. This filter can't match. */
-               return FALSE;
-       }
-
-       for (unsigned int i = 0; i < query->categories_count; i++) {
-               if (!event_has_category(event, query->categories[i].category))
-                       return FALSE;
-       }
-       return TRUE;
-}
-
 static bool
 event_match_field(struct event *event, const struct event_field *wanted_field)
 {
@@ -519,14 +534,65 @@ event_match_field(struct event *event, const struct event_field *wanted_field)
 }
 
 static bool
-event_filter_query_match_fields(const struct event_filter_query_internal *query,
-                               struct event *event)
-{
-       for (unsigned int i = 0; i < query->fields_count; i++) {
-               if (!event_match_field(event, &query->fields[i]))
-                       return FALSE;
+event_filter_query_match_cmp(struct event_filter_node *node,
+                            struct event *event, const char *source_filename,
+                            unsigned int source_linenum)
+{
+       i_assert((node->op == EVENT_FILTER_OP_CMP_EQ) ||
+                (node->op == EVENT_FILTER_OP_CMP_GT) ||
+                (node->op == EVENT_FILTER_OP_CMP_LT) ||
+                (node->op == EVENT_FILTER_OP_CMP_GE) ||
+                (node->op == EVENT_FILTER_OP_CMP_LE));
+
+       switch (node->type) {
+               case EVENT_FILTER_NODE_TYPE_LOGIC:
+                       i_unreached();
+               case EVENT_FILTER_NODE_TYPE_EVENT_NAME:
+                       return (event->sending_name != NULL) &&
+                              wildcard_match(event->sending_name, node->str);
+               case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION:
+                       return !((source_linenum != node->intmax &&
+                                 node->intmax != 0) ||
+                                source_filename == NULL ||
+                                strcmp(event->source_filename, node->str) != 0);
+               case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY:
+                       return event_has_category(event, node);
+               case EVENT_FILTER_NODE_TYPE_EVENT_FIELD:
+                       return event_match_field(event, &node->field);
        }
-       return TRUE;
+
+       i_unreached();
+}
+
+static bool
+event_filter_query_match_eval(struct event_filter_node *node,
+                             struct event *event, const char *source_filename,
+                             unsigned int source_linenum)
+{
+       switch (node->op) {
+       case EVENT_FILTER_OP_CMP_EQ:
+       case EVENT_FILTER_OP_CMP_GT:
+       case EVENT_FILTER_OP_CMP_LT:
+       case EVENT_FILTER_OP_CMP_GE:
+       case EVENT_FILTER_OP_CMP_LE:
+               return event_filter_query_match_cmp(node, event, source_filename,
+                                                   source_linenum);
+       case EVENT_FILTER_OP_AND:
+               return event_filter_query_match_eval(node->children[0], event,
+                                                    source_filename, source_linenum) &&
+                      event_filter_query_match_eval(node->children[1], event,
+                                                    source_filename, source_linenum);
+       case EVENT_FILTER_OP_OR:
+               return event_filter_query_match_eval(node->children[0], event,
+                                                    source_filename, source_linenum) ||
+                      event_filter_query_match_eval(node->children[1], event,
+                                                    source_filename, source_linenum);
+       case EVENT_FILTER_OP_NOT:
+               return !event_filter_query_match_eval(node->children[0], event,
+                                                     source_filename, source_linenum);
+       }
+
+       i_unreached();
 }
 
 static bool
@@ -539,23 +605,13 @@ event_filter_query_match(const struct event_filter_query_internal *query,
        if ((query->log_type_mask & event_filter_log_types[ctx->type]) == 0)
                return FALSE;
 
-       if (query->name != NULL) {
-               if (event->sending_name == NULL ||
-                   !wildcard_match(event->sending_name, query->name))
-                       return FALSE;
-       }
-       if (query->source_filename != NULL) {
-               if ((source_linenum != query->source_linenum &&
-                    query->source_linenum != 0) ||
-                   source_filename == NULL ||
-                   strcmp(event->source_filename, query->source_filename) != 0)
-                       return FALSE;
-       }
-       if (!event_filter_query_match_categories(query, event))
-               return FALSE;
-       if (!event_filter_query_match_fields(query, event))
-               return FALSE;
-       return TRUE;
+       /* Nothing to evaluate - this can happen if the filter consists
+          solely of log types (e.g., cat:debug) */
+       if (query->expr == NULL)
+               return TRUE;
+
+       return event_filter_query_match_eval(query->expr, event, source_filename,
+                                            source_linenum);
 }
 
 static bool
@@ -651,65 +707,49 @@ void event_filter_match_iter_deinit(struct event_filter_match_iter **_iter)
 }
 
 static void
-event_filter_query_remove_category(struct event_filter_query_internal *query,
-                                  struct event_category *category)
+event_filter_query_update_category(struct event_filter_query_internal *query,
+                                  struct event_filter_node *node,
+                                  struct event_category *category,
+                                  bool add)
 {
-       for (unsigned int i = 0; i < query->categories_count; i++) {
-               if (query->categories[i].category == category) {
-                       query->categories[i].category = NULL;
-                       query->has_unregistered_categories = TRUE;
-               }
-       }
-}
-
-static void
-event_filter_remove_category(struct event_filter *filter,
-                            struct event_category *category)
-{
-       struct event_filter_query_internal *query;
-
-       array_foreach_modifiable(&filter->queries, query)
-               event_filter_query_remove_category(query, category);
-}
-
-static void
-event_filter_query_add_missing_category(struct event_filter_query_internal *query,
-                                       struct event_category *category)
-{
-       if (!query->has_unregistered_categories)
+       if (node == NULL)
                return;
-       query->has_unregistered_categories = FALSE;
-
-       for (unsigned int i = 0; i < query->categories_count; i++) {
-               if (query->categories[i].category != NULL)
-                       continue;
 
-               if (strcmp(query->categories[i].name, category->name) == 0)
-                       query->categories[i].category = category;
-               else
-                       query->has_unregistered_categories = TRUE;
+       switch (node->type) {
+       case EVENT_FILTER_NODE_TYPE_LOGIC:
+               event_filter_query_update_category(query, node->children[0], category, add);
+               event_filter_query_update_category(query, node->children[1], category, add);
+               break;
+       case EVENT_FILTER_NODE_TYPE_EVENT_NAME:
+       case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION:
+       case EVENT_FILTER_NODE_TYPE_EVENT_FIELD:
+               break;
+       case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY:
+               if (add) {
+                       if (node->category.ptr != NULL)
+                               break;
+
+                       if (strcmp(node->category.name, category->name) == 0)
+                               node->category.ptr = category;
+               } else {
+                       if (node->category.ptr == category)
+                               node->category.ptr = NULL;
+               }
+               break;
        }
 }
 
-static void
-event_filter_add_missing_category(struct event_filter *filter,
-                                 struct event_category *category)
-{
-       struct event_filter_query_internal *query;
-
-       array_foreach_modifiable(&filter->queries, query)
-               event_filter_query_add_missing_category(query, category);
-}
-
 static void event_filter_category_registered(struct event_category *category)
 {
+       const bool add = category->internal != NULL;
+       struct event_filter_query_internal *query;
        struct event_filter *filter;
 
        for (filter = event_filters; filter != NULL; filter = filter->next) {
-               if (category->internal == NULL)
-                       event_filter_remove_category(filter, category);
-               else
-                       event_filter_add_missing_category(filter, category);
+               array_foreach_modifiable(&filter->queries, query) {
+                       event_filter_query_update_category(query, query->expr,
+                                                          category, add);
+               }
        }
 }