test-istream-unix.c \
test-json-parser.c \
test-json-tree.c \
+ test-lib-event.c \
test-lib-signals.c \
test-llist.c \
test-log-throttle.c \
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)
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();
}
EVENT_CODE_FIELD_INTMAX = 'I',
EVENT_CODE_FIELD_STR = 'S',
EVENT_CODE_FIELD_TIMEVAL = 'T',
+ EVENT_CODE_FIELD_STRLIST = 'L',
};
/* Internal event category state.
void event_copy_fields(struct event *to, struct event *from)
{
const struct event_field *fld;
+ unsigned int count;
+ const char *const *values;
+
if (!array_is_created(&from->fields))
return;
array_foreach(&from->fields, fld) {
case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
event_add_timeval(to, fld->key, &fld->value.timeval);
break;
+ case EVENT_FIELD_VALUE_TYPE_STRLIST:
+ values = array_get(&fld->value.strlist, &count);
+ for (unsigned int i = 0; i < count; i++)
+ event_strlist_append(to, fld->key, values[i]);
+ break;
default:
break;
}
return NULL;
}
+static void
+event_get_recursive_strlist(const struct event *event, pool_t pool,
+ const char *key, ARRAY_TYPE(const_string) *dest)
+{
+ const struct event_field *field;
+ const char *str;
+
+ if (event == NULL)
+ return;
+
+ field = event_find_field_nonrecursive(event, key);
+ if (field != NULL) {
+ if (field->value_type != EVENT_FIELD_VALUE_TYPE_STRLIST) {
+ /* Value type unexpectedly changed. Stop recursing. */
+ return;
+ }
+ array_foreach_elem(&field->value.strlist, str) {
+ if (array_lsearch(dest, &str, i_strcmp_p) == NULL) {
+ if (pool != NULL)
+ str = p_strdup(pool, str);
+ array_push_back(dest, &str);
+ }
+ }
+ }
+ event_get_recursive_strlist(event->parent, pool, key, dest);
+}
+
const char *
event_find_field_recursive_str(const struct event *event, const char *key)
{
return t_strdup_printf("%"PRIdTIME_T".%u",
field->value.timeval.tv_sec,
(unsigned int)field->value.timeval.tv_usec);
+ case EVENT_FIELD_VALUE_TYPE_STRLIST: {
+ ARRAY_TYPE(const_string) list;
+ t_array_init(&list, 8);
+ /* This is a bit different, because it needs to be merging
+ all of the parent events' lists together. */
+ event_get_recursive_strlist(event, NULL, key, &list);
+ return t_array_const_string_join(&list, ",");
+ }
}
i_unreached();
}
return event;
}
+struct event *
+event_strlist_append(struct event *event, const char *key, const char *value)
+{
+ struct event_field *field = event_get_field(event, key);
+
+ if (field->value_type != EVENT_FIELD_VALUE_TYPE_STRLIST)
+ i_zero(&field->value);
+ field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST;
+
+ if (!array_is_created(&field->value.strlist))
+ p_array_init(&field->value.strlist, event->pool, 1);
+
+ /* lets not add empty values there though */
+ if (value == NULL)
+ return event;
+
+ const char *str = p_strdup(event->pool, value);
+ if (array_lsearch(&field->value.strlist, &str, i_strcmp_p) == NULL)
+ array_push_back(&field->value.strlist, &str);
+ return event;
+}
+
+struct event *
+event_strlist_replace(struct event *event, const char *key,
+ const char *const *values, unsigned int count)
+{
+ struct event_field *field = event_get_field(event, key);
+ i_zero(&field->value);
+ field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST;
+
+ for (unsigned int i = 0; i < count; i++)
+ event_strlist_append(event, key, values[i]);
+ return event;
+}
+
+struct event *
+event_strlist_copy_recursive(struct event *dest, const struct event *src,
+ const char *key)
+{
+ event_strlist_append(dest, key, NULL);
+ struct event_field *field = event_get_field(dest, key);
+ i_assert(field != NULL);
+ event_get_recursive_strlist(src, dest->pool, key,
+ &field->value.strlist);
+ return dest;
+}
+
struct event *
event_add_int(struct event *event, const char *key, intmax_t num)
{
field->value.timeval.tv_sec,
(unsigned int)field->value.timeval.tv_usec);
break;
+ case EVENT_FIELD_VALUE_TYPE_STRLIST: {
+ unsigned int count;
+ const char *const *strlist =
+ array_get(&field->value.strlist, &count);
+ str_append_c(dest, EVENT_CODE_FIELD_STRLIST);
+ str_append_tabescaped(dest, field->key);
+ str_printfa(dest, "\t%u", count);
+ for (unsigned int i = 0; i < count; i++) {
+ str_append_c(dest, '\t');
+ str_append_tabescaped(dest, strlist[i]);
+ }
+ }
}
}
return TRUE;
}
+static bool
+event_import_strlist(struct event *event, struct event_field *field,
+ const char *const **_args, const char **error_r)
+{
+ const char *const *args = *_args;
+ unsigned int count, i;
+
+ field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST;
+ if (str_to_uint(args[0], &count) < 0) {
+ *error_r = t_strdup_printf("Field '%s' has invalid count: '%s'",
+ field->key, args[0]);
+ return FALSE;
+ }
+ p_array_init(&field->value.strlist, event->pool, count);
+ for (i = 1; i <= count && args[i] != NULL; i++) {
+ const char *str = p_strdup(event->pool, args[i]);
+ array_push_back(&field->value.strlist, &str);
+ }
+ if (i < count) {
+ *error_r = t_strdup_printf("Field '%s' has too few values",
+ field->key);
+ return FALSE;
+ }
+ *_args += count;
+ return TRUE;
+}
+
static bool
event_import_field(struct event *event, enum event_code code, const char *arg,
const char *const **_args, const char **error_r)
}
args++;
break;
+ case EVENT_CODE_FIELD_STRLIST:
+ if (!event_import_strlist(event, field, &args, error_r))
+ return FALSE;
+ break;
default:
i_unreached();
}
}
case EVENT_CODE_FIELD_INTMAX:
case EVENT_CODE_FIELD_STR:
+ case EVENT_CODE_FIELD_STRLIST:
case EVENT_CODE_FIELD_TIMEVAL: {
args++;
if (!event_import_field(event, code, arg, &args, error_r))
return event_last_passthrough;
}
+static struct event_passthrough *
+event_passthrough_strlist_append(const char *key, const char *value)
+{
+ event_strlist_append(last_passthrough_event(), key, value);
+ return event_last_passthrough;
+}
+
+static struct event_passthrough *
+event_passthrough_strlist_replace(const char *key, const char *const *values,
+ unsigned int count)
+{
+ event_strlist_replace(last_passthrough_event(), key, values, count);
+ return event_last_passthrough;
+}
+
static struct event_passthrough *
event_passthrough_add_int(const char *key, intmax_t num)
{
.add_int = event_passthrough_add_int,
.add_timeval = event_passthrough_add_timeval,
.inc_int = event_passthrough_inc_int,
+ .strlist_append = event_passthrough_strlist_append,
+ .strlist_replace = event_passthrough_strlist_replace,
.clear_field = event_passthrough_clear_field,
.event = event_passthrough_event,
};
EVENT_FIELD_VALUE_TYPE_STR,
EVENT_FIELD_VALUE_TYPE_INTMAX,
EVENT_FIELD_VALUE_TYPE_TIMEVAL,
+ EVENT_FIELD_VALUE_TYPE_STRLIST,
};
struct event_field {
const char *str;
intmax_t intmax;
struct timeval timeval;
+ ARRAY_TYPE(const_string) strlist;
} value;
};
struct event_passthrough *
(*inc_int)(const char *key, intmax_t num);
+ struct event_passthrough *
+ (*strlist_append)(const char *key, const char *value);
+ struct event_passthrough *
+ (*strlist_replace)(const char *key, const char *const *value,
+ unsigned int count);
+
struct event_passthrough *
(*clear_field)(const char *key);
struct event *
event_add_timeval(struct event *event, const char *key,
const struct timeval *tv);
+/* Append new value to list. If the key is not a list, it will
+ be cleared first. NULL values are ignored. Duplicate values are ignored. */
+struct event *
+event_strlist_append(struct event *event, const char *key, const char *value);
+/* Replace value with this strlist. */
+struct event *
+event_strlist_replace(struct event *event, const char *key,
+ const char *const *value, unsigned int count);
+/* Copy the string list from src and its parents to dest. This can be especially
+ useful to copy the current global events' reason_codes to a more permanent
+ (e.g. async) event that can exist after the global events are popped out. */
+struct event *
+event_strlist_copy_recursive(struct event *dest, const struct event *src,
+ const char *key);
/* Same as event_add_str/int(), but do it via event_field struct. The fields
terminates with key=NULL. Returns the event parameter. */
struct event *
test_end();
}
+static void test_event_filter_strlist(void)
+{
+ struct event_filter *filter;
+ const struct failure_context failure_ctx = {
+ .type = LOG_TYPE_DEBUG
+ };
+
+ test_begin("event filter: match string list");
+
+ struct event *e = event_create(NULL);
+
+ filter = event_filter_create();
+ /* should match empty list */
+ event_filter_parse("abc=\"\"", filter, NULL);
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ /* should still be empty */
+ event_strlist_append(e, "abc", NULL);
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+
+ /* should not match non-empty list */
+ event_strlist_append(e, "abc", "one");
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ event_filter_unref(&filter);
+
+ /* should match non-empty list that has value 'one' */
+ filter = event_filter_create();
+ event_strlist_append(e, "abc", "two");
+ event_filter_parse("abc=one", filter, NULL);
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ event_filter_unref(&filter);
+
+ /* should match non-empty list that has no value 'three' */
+ filter = event_filter_create();
+ event_filter_parse("abc=one AND NOT abc=three", filter, NULL);
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ event_filter_unref(&filter);
+
+ event_unref(&e);
+ test_end();
+}
+
+static void test_event_filter_strlist_recursive(void)
+{
+ struct event_filter *filter;
+ const struct failure_context failure_ctx = {
+ .type = LOG_TYPE_DEBUG
+ };
+
+ test_begin("event filter: match string list - recursive");
+
+ struct event *parent = event_create(NULL);
+ struct event *e = event_create(parent);
+
+ /* empty filter: parent is non-empty */
+ filter = event_filter_create();
+ event_filter_parse("list1=\"\"", filter, NULL);
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ event_strlist_append(parent, "list1", "foo");
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ event_filter_unref(&filter);
+
+ /* matching filter: matches parent */
+ filter = event_filter_create();
+ event_filter_parse("list2=parent", filter, NULL);
+ /* empty: */
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ /* set parent but no child: */
+ event_strlist_append(parent, "list2", "parent");
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ /* set child to non-matching: */
+ event_strlist_append(e, "list2", "child");
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ event_filter_unref(&filter);
+
+ /* matching filter: matches child */
+ filter = event_filter_create();
+ event_filter_parse("list3=child", filter, NULL);
+ /* empty: */
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ /* set child but no parent: */
+ event_strlist_append(e, "list3", "child");
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ /* set parent to non-matching: */
+ event_strlist_append(e, "list3", "parent");
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ event_filter_unref(&filter);
+
+ event_unref(&e);
+ event_unref(&parent);
+ test_end();
+}
+
void test_event_filter(void)
{
test_event_filter_override_parent_fields();
test_event_filter_clear_parent_fields();
test_event_filter_inc_int();
test_event_filter_parent_category_match();
+ test_event_filter_strlist();
+ test_event_filter_strlist_recursive();
}
#include "time-util.h"
#include "lib-event-private.h"
#include "failures-private.h"
+#include "array.h"
+#include "str.h"
#define CHECK_FLATTEN_SAME(e) \
check_event_same(event_flatten(e), (e))
const struct event_field *exp, unsigned int nexp)
{
unsigned int i;
+ const char *got_str;
test_assert(ngot == nexp);
test_assert(timeval_cmp(&exp[i].value.timeval,
&got[i].value.timeval) == 0);
break;
+ case EVENT_FIELD_VALUE_TYPE_STRLIST:
+ got_str = t_array_const_string_join(&got[i].value.strlist, ",");
+ test_assert_strcmp(exp[i].value.str, got_str);
+ break;
}
}
}
}
},
};
+ static struct event_field exp_1str1int1strlist[3] = {
+ {
+ .key = "abc",
+ .value_type = EVENT_FIELD_VALUE_TYPE_STR,
+ .value = {
+ .str = "foo",
+ .intmax = 0,
+ .timeval = {0,0},
+ }
+ },
+ {
+ .key = "def",
+ .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX,
+ .value = {
+ .intmax = 49,
+ .str = NULL,
+ .timeval = {0,0},
+ }
+ },
+ {
+ .key = "cba",
+ .value_type = EVENT_FIELD_VALUE_TYPE_STRLIST,
+ .value = {
+ .str = "one,two,three",
+ },
+ },
+ };
+
struct event *parent;
struct event *e;
test_begin("event flatten: one parent");
+ t_array_init(&exp_1str1int1strlist[0].value.strlist, 3);
+ const char *str = "one";
+ array_push_back(&exp_1str1int1strlist[0].value.strlist, &str);
+ str = "two";
+ array_push_back(&exp_1str1int1strlist[0].value.strlist, &str);
+ str = "three";
+ array_push_back(&exp_1str1int1strlist[0].value.strlist, &str);
+
parent = event_create(NULL);
e = event_create(parent);
event_add_category(e, &cats[1]);
CHECK_FLATTEN_DIFF(e, exp_2cat, 2, exp_1str1int, 2);
+ event_strlist_append(e, "cba", "one");
+ event_strlist_append(e, "cba", "two");
+ event_strlist_append(e, "cba", "three");
+ CHECK_FLATTEN_DIFF(e, exp_2cat, 2, exp_1str1int1strlist, 3);
+
event_unref(&e);
event_unref(&parent);
test_end();
}
+static void test_event_strlist_flatten(void)
+{
+ test_begin("event flatten: strlist");
+ struct event *l1 = event_create(NULL);
+ event_strlist_append(l1, "test", "l3");
+ struct event *l2 = event_create(l1);
+ event_strlist_append(l2, "test", "l1");
+ struct event *l3 = event_create(l2);
+ unsigned int line = __LINE__ - 1;
+ event_strlist_append(l3, "test", "l2");
+
+ string_t *dest = t_str_new(32);
+ struct event *event = event_flatten(l3);
+
+ event_export(event, dest);
+ /* see if it matches .. */
+ const char *reference = t_strdup_printf("%"PRIdTIME_T"\t%zu"
+ "\tstest-event-flatten.c"
+ "\t%u\tLtest\t3\tl3\tl1\tl2",
+ event->tv_created.tv_sec,
+ event->tv_created.tv_usec,
+ line);
+ test_assert_strcmp(str_c(dest), reference);
+
+ /* these should not end up duplicated */
+ event_strlist_append(event, "test", "l1");
+ event_strlist_append(event, "test", "l2");
+ event_strlist_append(event, "test", "l3");
+
+ /* and export should look the same */
+ str_truncate(dest, 0);
+ event_export(event, dest);
+ test_assert_strcmp(str_c(dest), reference);
+
+ event_unref(&event);
+
+ /* export event */
+ event_unref(&l3);
+ event_unref(&l2);
+ event_unref(&l1);
+
+ test_end();
+}
+
void test_event_flatten(void)
{
test_event_flatten_no_parent();
test_event_flatten_one_parent();
test_event_flatten_override_parent_field();
+ test_event_strlist_flatten();
}
--- /dev/null
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+
+static void test_event_strlist(void)
+{
+ test_begin("event strlist");
+ struct event *e1 = event_create(NULL);
+ event_strlist_append(e1, "key", "s1");
+ event_strlist_append(e1, "key", "s2");
+ struct event *e2 = event_create(e1);
+ event_strlist_append(e2, "key", "s3");
+ event_strlist_append(e2, "key", "s2");
+
+ test_assert_strcmp(event_find_field_recursive_str(e1, "key"), "s1,s2");
+ test_assert_strcmp(event_find_field_recursive_str(e2, "key"), "s3,s2,s1");
+
+ const char *new_strlist[] = { "new1", "new2", "new2", "s2" };
+ event_strlist_replace(e2, "key", new_strlist, N_ELEMENTS(new_strlist));
+ test_assert_strcmp(event_find_field_recursive_str(e2, "key"), "new1,new2,s2,s1");
+
+ struct event *e3 = event_create(NULL);
+ event_strlist_copy_recursive(e3, e2, "key");
+ test_assert_strcmp(event_find_field_recursive_str(e3, "key"), "new1,new2,s2,s1");
+ event_unref(&e3);
+
+ event_unref(&e1);
+ event_unref(&e2);
+ test_end();
+}
+
+void test_lib_event(void)
+{
+ test_event_strlist();
+}
TEST(test_istream_unix)
TEST(test_json_parser)
TEST(test_json_tree)
+TEST(test_lib_event)
TEST(test_lib_signals)
TEST(test_llist)
TEST(test_log_throttle)
append_time(dest, &field->value.timeval,
info->exporter->time_format);
break;
+ case EVENT_FIELD_VALUE_TYPE_STRLIST:
+ break;
}
}
append_time(dest, &field->value.timeval,
info->exporter->time_format);
break;
+ case EVENT_FIELD_VALUE_TYPE_STRLIST:
+ break;
}
}
return TRUE;
case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
return FALSE;
+ case EVENT_FIELD_VALUE_TYPE_STRLIST:
+ return FALSE;
}
i_unreached();
switch (field->value_type) {
case EVENT_FIELD_VALUE_TYPE_STR:
case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
+ case EVENT_FIELD_VALUE_TYPE_STRLIST:
return FALSE;
case EVENT_FIELD_VALUE_TYPE_INTMAX:
break;
switch (field->value_type) {
case EVENT_FIELD_VALUE_TYPE_STR:
case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
+ case EVENT_FIELD_VALUE_TYPE_STRLIST:
i_unreached();
case EVENT_FIELD_VALUE_TYPE_INTMAX:
break;
switch (field->value_type) {
case EVENT_FIELD_VALUE_TYPE_STR:
+ case EVENT_FIELD_VALUE_TYPE_STRLIST:
break;
case EVENT_FIELD_VALUE_TYPE_INTMAX:
num = field->value.intmax;