/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "llist.h"
#include "str.h"
#include "strescape.h"
#include "wildcard-match.h"
#include "lib-event-private.h"
#include "event-filter.h"
#include "event-filter-private.h"

/* Note: this has to match the regexp behavior in the event filter lexer file */
#define event_filter_append_escaped(dst, str) \
	str_append_escaped((dst), (str), strlen(str))

enum event_filter_code {
	EVENT_FILTER_CODE_NAME		= 'n',
	EVENT_FILTER_CODE_SOURCE	= 's',
	EVENT_FILTER_CODE_CATEGORY	= 'c',
	EVENT_FILTER_CODE_FIELD		= 'f',
};

/* map <log type> to <event filter log type & name> */
static const struct log_type_map {
	enum event_filter_log_type log_type;
	const char *name;
} event_filter_log_type_map[] = {
	[LOG_TYPE_DEBUG]   = { EVENT_FILTER_LOG_TYPE_DEBUG, "debug" },
	[LOG_TYPE_INFO]    = { EVENT_FILTER_LOG_TYPE_INFO, "info" },
	[LOG_TYPE_WARNING] = { EVENT_FILTER_LOG_TYPE_WARNING, "warning" },
	[LOG_TYPE_ERROR]   = { EVENT_FILTER_LOG_TYPE_ERROR, "error" },
	[LOG_TYPE_FATAL]   = { EVENT_FILTER_LOG_TYPE_FATAL, "fatal" },
	[LOG_TYPE_PANIC]   = { EVENT_FILTER_LOG_TYPE_PANIC, "panic" },
};

struct event_filter_query_internal {
	struct event_filter_node *expr;
	void *context;
};

static struct event_filter *event_filters = NULL;

static struct event_filter *event_filter_create_real(pool_t pool, bool fragment)
{
	struct event_filter *filter;

	filter = p_new(pool, struct event_filter, 1);
	filter->pool = pool;
	filter->refcount = 1;
	filter->named_queries_only = TRUE;
	filter->fragment = fragment;
	p_array_init(&filter->queries, pool, 4);
	if (!fragment)
		DLLIST_PREPEND(&event_filters, filter);
	return filter;
}

struct event_filter *event_filter_create(void)
{
	return event_filter_create_real(pool_alloconly_create("event filter", 2048), FALSE);
}

struct event_filter *event_filter_create_fragment(pool_t pool)
{
	return event_filter_create_real(pool, TRUE);
}

void event_filter_ref(struct event_filter *filter)
{
	i_assert(filter->refcount > 0);
	filter->refcount++;
}

void event_filter_unref(struct event_filter **_filter)
{
	struct event_filter *filter = *_filter;

	if (filter == NULL)
		return;
	i_assert(filter->refcount > 0);

	*_filter = NULL;
	if (--filter->refcount > 0)
		return;

	if (!filter->fragment) {
		DLLIST_REMOVE(&event_filters, filter);

		/* fragments' pools are freed by the consumer */
		pool_unref(&filter->pool);
	}
}

/*
 * Look for an existing query with the same context pointer and return it.
 *
 * If not found, allocate a new internal query and return it.
 */
static struct event_filter_query_internal *
event_filter_get_or_alloc_internal_query(struct event_filter *filter,
					 void *context)
{
	struct event_filter_query_internal *query;

	array_foreach_modifiable(&filter->queries, query) {
		if (query->context == context)
			return query;
	}

	/* no matching context, allocate a new query */
	query = array_append_space(&filter->queries);
	query->context = context;
	query->expr = NULL;

	return query;
}

static void add_node(pool_t pool, struct event_filter_node **root,
		     struct event_filter_node *new,
		     enum event_filter_node_op op)
{
	struct event_filter_node *parent;

	i_assert((op == EVENT_FILTER_OP_AND) || (op == EVENT_FILTER_OP_OR));

	if (*root == NULL) {
		*root = new;
		return;
	}

	parent = p_new(pool, struct event_filter_node, 1);
	parent->type = EVENT_FILTER_NODE_TYPE_LOGIC;
	parent->op = op;
	parent->children[0] = *root;
	parent->children[1] = new;

	*root = parent;
}

static bool filter_node_requires_event_name(struct event_filter_node *node)
{
	switch (node->op) {
	case EVENT_FILTER_OP_NOT:
		return filter_node_requires_event_name(node->children[0]);
	case EVENT_FILTER_OP_AND:
		return filter_node_requires_event_name(node->children[0]) ||
			filter_node_requires_event_name(node->children[1]);
	case EVENT_FILTER_OP_OR:
		return filter_node_requires_event_name(node->children[0]) &&
			filter_node_requires_event_name(node->children[1]);
	default:
		return node->type == EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD ||
			node->type == EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT;
	}
}

int event_filter_parse(const char *str, struct event_filter *filter,
		       const char **error_r)
{
	struct event_filter_query_internal *int_query;
	struct event_filter_parser_state state;
	int ret;

	i_zero(&state);
	state.input = str;
	state.len = strlen(str);
	state.pos = 0;
	state.pool = filter->pool;

	event_filter_parser_lex_init(&state.scanner);
	event_filter_parser_set_extra(&state, state.scanner);

	ret = event_filter_parser_parse(&state);

	event_filter_parser_lex_destroy(state.scanner);

	if ((ret == 0) && (state.output != NULL)) {
		/* success - non-NULL expression */
		i_assert(state.error == NULL);

		int_query = event_filter_get_or_alloc_internal_query(filter, NULL);

		add_node(filter->pool, &int_query->expr, state.output,
			 EVENT_FILTER_OP_OR);

		filter->named_queries_only = filter->named_queries_only &&
			filter_node_requires_event_name(state.output);
	} else if (ret != 0) {
		/* error */
		i_assert(state.error != NULL);

		*error_r = state.error;
	}

	/*
	 * Note that success with a NULL expression output is possible, but
	 * turns into a no-op.
	 */

	return (ret != 0) ? -1 : 0;
}

bool event_filter_category_to_log_type(const char *name,
				       enum event_filter_log_type *log_type_r)
{
	unsigned int i;

	for (i = 0; i < N_ELEMENTS(event_filter_log_type_map); i++) {
		if (strcmp(name, event_filter_log_type_map[i].name) == 0) {
			*log_type_r = event_filter_log_type_map[i].log_type;
			return TRUE;
		}
	}
	return FALSE;
}

const char *
event_filter_category_from_log_type(enum event_filter_log_type log_type)
{
	unsigned int i;

	for (i = 0; i < N_ELEMENTS(event_filter_log_type_map); i++) {
		if (event_filter_log_type_map[i].log_type == log_type)
			return event_filter_log_type_map[i].name;
	}
	i_unreached();
}

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(pool, old->str);
	new->intmax = old->intmax;
	new->category.log_type = old->category.log_type;
	new->category.name = p_strdup(pool, old->category.name);
	new->category.ptr = old->category.ptr;
	new->field.key = p_strdup(pool, old->field.key);
	new->field.value_type = old->field.value_type;
	new->field.value.str = p_strdup(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;

	array_foreach(&src->queries, int_query) T_BEGIN {
		void *context = with_context ? new_context : int_query->context;
		struct event_filter_query_internal *new;

		new = event_filter_get_or_alloc_internal_query(dest, context);

		add_node(dest->pool, &new->expr,
			 clone_expr(dest->pool, int_query->expr),
			 EVENT_FILTER_OP_OR);
	} T_END;
}

bool event_filter_remove_queries_with_context(struct event_filter *filter,
					      void *context)
{
	const struct event_filter_query_internal *int_query;
	unsigned int idx;

	array_foreach(&filter->queries, int_query) {
		if (int_query->context == context) {
			idx = array_foreach_idx(&filter->queries, int_query);
			array_delete(&filter->queries, idx, 1);
			return TRUE;
		}
	}
	return FALSE;
}

void event_filter_merge(struct event_filter *dest,
			const struct event_filter *src)
{
	event_filter_merge_with_context_internal(dest, src, NULL, FALSE);
}

void event_filter_merge_with_context(struct event_filter *dest,
				     const struct event_filter *src,
				     void *new_context)
{
	event_filter_merge_with_context_internal(dest, src, new_context, TRUE);
}

static const char *
event_filter_export_query_expr_op(enum event_filter_node_op op)
{
	switch (op) {
	case EVENT_FILTER_OP_AND:
	case EVENT_FILTER_OP_OR:
	case EVENT_FILTER_OP_NOT:
		i_unreached();
	case EVENT_FILTER_OP_CMP_EQ:
		return "=";
	case EVENT_FILTER_OP_CMP_GT:
		return ">";
	case EVENT_FILTER_OP_CMP_LT:
		return "<";
	case EVENT_FILTER_OP_CMP_GE:
		return ">=";
	case EVENT_FILTER_OP_CMP_LE:
		return "<=";
	}

	i_unreached();
}

static void
event_filter_export_query_expr(const struct event_filter_query_internal *query,
			       struct event_filter_node *node,
			       string_t *dest)
{
	switch (node->type) {
	case EVENT_FILTER_NODE_TYPE_LOGIC:
		str_append_c(dest, '(');
		switch (node->op) {
		case EVENT_FILTER_OP_AND:
			event_filter_export_query_expr(query, node->children[0], dest);
			str_append(dest, " AND ");
			event_filter_export_query_expr(query, node->children[1], dest);
			break;
		case EVENT_FILTER_OP_OR:
			event_filter_export_query_expr(query, node->children[0], dest);
			str_append(dest, " OR ");
			event_filter_export_query_expr(query, node->children[1], dest);
			break;
		case EVENT_FILTER_OP_NOT:
			str_append(dest, "NOT ");
			event_filter_export_query_expr(query, node->children[0], dest);
			break;
		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:
			i_unreached();
		}
		str_append_c(dest, ')');
		break;
	case EVENT_FILTER_NODE_TYPE_EVENT_NAME_EXACT:
	case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD:
		str_append(dest, "event");
		str_append(dest, event_filter_export_query_expr_op(node->op));
		str_append_c(dest, '"');
		event_filter_append_escaped(dest, node->str);
		str_append_c(dest, '"');
		break;
	case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION:
		str_append(dest, "source_location");
		str_append(dest, event_filter_export_query_expr_op(node->op));
		str_append_c(dest, '"');
		event_filter_append_escaped(dest, node->str);
		if (node->intmax != 0)
			str_printfa(dest, ":%ju", node->intmax);
		str_append_c(dest, '"');
		break;
	case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY:
		str_append(dest, "category");
		str_append(dest, event_filter_export_query_expr_op(node->op));
		if (node->category.name != NULL) {
			str_append_c(dest, '"');
			event_filter_append_escaped(dest, node->category.name);
			str_append_c(dest, '"');
		} else
			str_append(dest, event_filter_category_from_log_type(node->category.log_type));
		break;
	case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT:
	case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD:
		str_append_c(dest, '"');
		event_filter_append_escaped(dest, node->field.key);
		str_append_c(dest, '"');
		str_append(dest, event_filter_export_query_expr_op(node->op));
		str_append_c(dest, '"');
		event_filter_append_escaped(dest, node->field.value.str);
		str_append_c(dest, '"');
		break;
	}
}

static void
event_filter_export_query(const struct event_filter_query_internal *query,
			  string_t *dest)
{
	str_append_c(dest, '(');
	event_filter_export_query_expr(query, query->expr, dest);
	str_append_c(dest, ')');
}

void event_filter_export(struct event_filter *filter, string_t *dest)
{
	const struct event_filter_query_internal *query;
	bool first = TRUE;

	array_foreach(&filter->queries, query) {
		if (!first)
			str_append(dest, " OR ");
		first = FALSE;
		event_filter_export_query(query, dest);
	}
}

struct event_filter_node *
event_filter_get_expr_for_testing(struct event_filter *filter,
				  unsigned int *count_r)
{
	const struct event_filter_query_internal *queries;

	queries = array_get(&filter->queries, count_r);

	return (*count_r == 0) ? NULL : queries[0].expr;
}

static bool
event_category_match(const struct event_category *category,
		     const struct event_category *wanted_category)
{
	for (; category != NULL; category = category->parent) {
		if (category->internal == wanted_category->internal)
			return TRUE;
	}
	return FALSE;
}

static bool
event_has_category_nonrecursive(struct event *event,
				struct event_category *wanted_category)
{
	struct event_category *cat;

	if (array_is_created(&event->categories)) {
		array_foreach_elem(&event->categories, cat) {
			if (event_category_match(cat, wanted_category))
				return TRUE;
		}
	}
	return FALSE;
}

static bool
event_has_category(struct event *event, struct event_filter_node *node,
		   enum event_filter_log_type log_type)
{
	struct event_category *wanted_category = node->category.ptr;

	/* category is a log type */
	if (node->category.name == NULL)
		return (node->category.log_type & log_type) != 0;

	/* category not registered, therefore the event cannot have it */
	if (wanted_category == NULL)
		return FALSE;

	while (event != NULL) {
		if (event_has_category_nonrecursive(event, wanted_category))
			return TRUE;
		/* try also the parent events */
		event = event_get_parent(event);
	}
	/* check also the global event and its parents */
	event = event_get_global();
	while (event != NULL) {
		if (event_has_category_nonrecursive(event, wanted_category))
			return TRUE;
		event = event_get_parent(event);
	}
	return FALSE;
}

static bool
event_match_strlist_recursive(struct event *event,
			      const struct event_field *wanted_field,
			      bool use_strcmp, bool *seen)
{
	const char *wanted_value = wanted_field->value.str;
	const struct event_field *field;
	const char *value;
	bool match;

	if (event == NULL)
		return FALSE;

	field = event_find_field_nonrecursive(event, wanted_field->key);
	if (field != NULL) {
		i_assert(field->value_type == EVENT_FIELD_VALUE_TYPE_STRLIST);
		array_foreach_elem(&field->value.strlist, value) {
			*seen = TRUE;
			match = use_strcmp ? strcmp(value, wanted_value) == 0 :
				wildcard_match_icase(value, wanted_value);
			if (match)
				return TRUE;
		}
	}
	return event_match_strlist_recursive(event->parent, wanted_field,
					     use_strcmp, seen);
}

static bool
event_match_strlist(struct event *event, const struct event_field *wanted_field,
		    bool use_strcmp)
{
	bool seen = FALSE;

	if (event_match_strlist_recursive(event, wanted_field,
					  use_strcmp, &seen))
		return TRUE;
	if (event_match_strlist_recursive(event_get_global(),
					  wanted_field, use_strcmp, &seen))
		return TRUE;
	if (wanted_field->value.str[0] == '\0' && !seen) {
		/* strlist="" matches nonexistent strlist */
		return TRUE;
	}
	return FALSE;

}

static bool
event_match_field(struct event *event, const struct event_field *wanted_field,
		  enum event_filter_node_op op, bool use_strcmp)
{
	const struct event_field *field;

	/* wanted_field has the value in all available formats */
	field = event_find_field_recursive(event, wanted_field->key);
	if (field == NULL) {
		/* field="" matches nonexistent field */
		return wanted_field->value.str[0] == '\0';
	}

	switch (field->value_type) {
	case EVENT_FIELD_VALUE_TYPE_STR:
		if (op != EVENT_FILTER_OP_CMP_EQ) {
			/* we only support string equality comparisons */
			return FALSE;
		}
		if (field->value.str[0] == '\0') {
			/* field was removed, but it matches field="" filter */
			return wanted_field->value.str[0] == '\0';
		}
		if (use_strcmp)
			return strcasecmp(field->value.str, wanted_field->value.str) == 0;
		else
			return wildcard_match_icase(field->value.str, wanted_field->value.str);
	case EVENT_FIELD_VALUE_TYPE_INTMAX:
		if (wanted_field->value.intmax > INT_MIN) {
			/* compare against an integer */
			switch (op) {
			case EVENT_FILTER_OP_CMP_EQ:
				return field->value.intmax == wanted_field->value.intmax;
			case EVENT_FILTER_OP_CMP_GT:
				return field->value.intmax > wanted_field->value.intmax;
			case EVENT_FILTER_OP_CMP_LT:
				return field->value.intmax < wanted_field->value.intmax;
			case EVENT_FILTER_OP_CMP_GE:
				return field->value.intmax >= wanted_field->value.intmax;
			case EVENT_FILTER_OP_CMP_LE:
				return field->value.intmax <= wanted_field->value.intmax;
			case EVENT_FILTER_OP_AND:
			case EVENT_FILTER_OP_OR:
			case EVENT_FILTER_OP_NOT:
				i_unreached();
			}
			i_unreached();
		} else {
			/* compare against an "integer" with wildcards */
			if (op != EVENT_FILTER_OP_CMP_EQ) {
				/* we only support string equality comparisons */
				return FALSE;
			}
			char tmp[MAX_INT_STRLEN];
			i_snprintf(tmp, sizeof(tmp), "%jd", field->value.intmax);
			if (use_strcmp)
				return strcasecmp(field->value.str, wanted_field->value.str) == 0;
			else
				return wildcard_match_icase(tmp, wanted_field->value.str);
		}
	case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
		/* there's no point to support matching exact timestamps */
		return FALSE;
	case EVENT_FIELD_VALUE_TYPE_STRLIST:
		/* check if the value is (or is not) on the list,
		   only string matching makes sense here. */
		if (op != EVENT_FILTER_OP_CMP_EQ)
			return FALSE;
		return event_match_strlist(event, wanted_field, use_strcmp);
	}
	i_unreached();
}

static bool
event_filter_query_match_cmp(struct event_filter_node *node,
			     struct event *event, const char *source_filename,
			     unsigned int source_linenum,
			     enum event_filter_log_type log_type)
{
	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_EXACT:
			return (event->sending_name != NULL) &&
			       strcmp(event->sending_name, node->str) == 0;
		case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD:
			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, log_type);
		case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT:
			return event_match_field(event, &node->field, node->op,
						 TRUE);
		case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD:
			return event_match_field(event, &node->field, node->op,
						 FALSE);
	}

	i_unreached();
}

bool
event_filter_query_match_eval(struct event_filter_node *node,
			      struct event *event, const char *source_filename,
			      unsigned int source_linenum,
			      enum event_filter_log_type log_type)
{
	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, log_type);
	case EVENT_FILTER_OP_AND:
		return event_filter_query_match_eval(node->children[0], event,
						     source_filename, source_linenum,
						     log_type) &&
		       event_filter_query_match_eval(node->children[1], event,
						     source_filename, source_linenum,
						     log_type);
	case EVENT_FILTER_OP_OR:
		return event_filter_query_match_eval(node->children[0], event,
						     source_filename, source_linenum,
						     log_type) ||
		       event_filter_query_match_eval(node->children[1], event,
						     source_filename, source_linenum,
						     log_type);
	case EVENT_FILTER_OP_NOT:
		return !event_filter_query_match_eval(node->children[0], event,
						      source_filename, source_linenum,
						      log_type);
	}

	i_unreached();
}

static bool
event_filter_query_match(const struct event_filter_query_internal *query,
			 struct event *event, const char *source_filename,
			 unsigned int source_linenum,
			 const struct failure_context *ctx)
{
	enum event_filter_log_type log_type;

	i_assert(ctx->type < N_ELEMENTS(event_filter_log_type_map));
	log_type = event_filter_log_type_map[ctx->type].log_type;

	return event_filter_query_match_eval(query->expr, event, source_filename,
					     source_linenum, log_type);
}

static bool
event_filter_match_fastpath(struct event_filter *filter, struct event *event)
{
	if (filter->named_queries_only && event->sending_name == NULL) {
		/* No debug logging is enabled. Only named events may be wanted
		   for stats. This event doesn't have a name, so we don't need
		   to check any further. */
		return FALSE;
	}
	return TRUE;
}

bool event_filter_match(struct event_filter *filter, struct event *event,
			const struct failure_context *ctx)
{
	if (filter == NULL)
		return FALSE;
	return event_filter_match_source(filter, event, event->source_filename,
					 event->source_linenum, ctx);
}

bool event_filter_match_source(struct event_filter *filter, struct event *event,
			       const char *source_filename,
			       unsigned int source_linenum,
			       const struct failure_context *ctx)
{
	const struct event_filter_query_internal *query;

	i_assert(!filter->fragment);

	if (!event_filter_match_fastpath(filter, event))
		return FALSE;

	array_foreach(&filter->queries, query) {
		if (event_filter_query_match(query, event, source_filename,
					     source_linenum, ctx))
			return TRUE;
	}
	return FALSE;
}

struct event_filter_match_iter {
	struct event_filter *filter;
	struct event *event;
	const struct failure_context *failure_ctx;
	unsigned int idx;
};

struct event_filter_match_iter *
event_filter_match_iter_init(struct event_filter *filter, struct event *event,
			     const struct failure_context *ctx)
{
	struct event_filter_match_iter *iter;

	i_assert(!filter->fragment);

	iter = i_new(struct event_filter_match_iter, 1);
	iter->filter = filter;
	iter->event = event;
	iter->failure_ctx = ctx;
	if (!event_filter_match_fastpath(filter, event))
		iter->idx = UINT_MAX;
	return iter;
}

void *event_filter_match_iter_next(struct event_filter_match_iter *iter)
{
	const struct event_filter_query_internal *queries;
	unsigned int count;

	queries = array_get(&iter->filter->queries, &count);
	while (iter->idx < count) {
		const struct event_filter_query_internal *query =
			&queries[iter->idx];

		iter->idx++;
		if (query->context != NULL &&
		    event_filter_query_match(query, iter->event,
					     iter->event->source_filename,
					     iter->event->source_linenum,
					     iter->failure_ctx))
			return query->context;
	}
	return NULL;
}

void event_filter_match_iter_deinit(struct event_filter_match_iter **_iter)
{
	struct event_filter_match_iter *iter = *_iter;

	*_iter = NULL;
	i_free(iter);
}

static void
event_filter_query_update_category(struct event_filter_query_internal *query,
				   struct event_filter_node *node,
				   struct event_category *category,
				   bool add)
{
	if (node == NULL)
		return;

	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_EXACT:
	case EVENT_FILTER_NODE_TYPE_EVENT_NAME_WILDCARD:
	case EVENT_FILTER_NODE_TYPE_EVENT_SOURCE_LOCATION:
	case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT:
	case EVENT_FILTER_NODE_TYPE_EVENT_FIELD_WILDCARD:
		break;
	case EVENT_FILTER_NODE_TYPE_EVENT_CATEGORY:
		if (node->category.name == NULL)
			break; /* log type */

		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_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) {
		array_foreach_modifiable(&filter->queries, query) {
			event_filter_query_update_category(query, query->expr,
							   category, add);
		}
	}
}

void event_filter_init(void)
{
	event_category_register_callback(event_filter_category_registered);
}

void event_filter_deinit(void)
{
	event_category_unregister_callback(event_filter_category_registered);
}
