Coverage Report

Created: 2026-10-28 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/lib-event.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "lib-event-private.h"
5
#include "event-filter.h"
6
#include "array.h"
7
#include "hash.h"
8
#include "llist.h"
9
#include "time-util.h"
10
#include "str.h"
11
#include "strescape.h"
12
#include "ioloop-private.h"
13
14
#include <ctype.h>
15
16
HASH_TABLE_DEFINE_TYPE(category_set, void *, const struct event_category *);
17
18
enum event_code {
19
  EVENT_CODE_ALWAYS_LOG_SOURCE  = 'a',
20
  EVENT_CODE_CATEGORY   = 'c',
21
  EVENT_CODE_TV_LAST_SENT   = 'l',
22
  EVENT_CODE_SENDING_NAME   = 'n',
23
  EVENT_CODE_SOURCE   = 's',
24
25
  EVENT_CODE_FIELD_INTMAX   = 'I',
26
  EVENT_CODE_FIELD_STR    = 'S',
27
  EVENT_CODE_FIELD_TIMEVAL  = 'T',
28
  EVENT_CODE_FIELD_IP   = 'P',
29
  EVENT_CODE_FIELD_STRLIST  = 'L',
30
};
31
32
/* Internal event category state.
33
34
   Each (unique) event category maps to one internal category.  (I.e., if
35
   two places attempt to register the same category, they will share the
36
   internal state.)
37
38
   This is required in order to support multiple registrations of the same
39
   category.  Currently, the only situation in which this occurs is the
40
   stats process receiving categories from other processes and also using
41
   the same categories internally.
42
43
   During registration, we look up the internal state based on the new
44
   category's name.  If found, we use it after sanity checking that the two
45
   are identical (i.e., they both have the same name and parent).  If not
46
   found, we allocate a new internal state and use it.
47
48
   We stash a pointer to the internal state in struct event_category (the
49
   "internal" member).  As a result, all category structs for the same
50
   category point to the same internal state. */
51
struct event_internal_category {
52
  /* More than one category can be represented by the internal state.
53
     To give consumers a unique but consistent category pointer, we
54
     return a pointer to this 'representative' category structure.
55
     Because we allocated it, we know that it will live exactly as
56
     long as we need it to. */
57
  struct event_category representative;
58
59
  struct event_internal_category *parent;
60
  char *name;
61
  int refcount;
62
};
63
64
struct event_reason {
65
  struct event *event;
66
};
67
68
struct event_category_iterator {
69
  HASH_TABLE_TYPE(category_set) hash;
70
  struct hash_iterate_context *iter;
71
};
72
73
extern struct event_passthrough event_passthrough_vfuncs;
74
75
static struct event *events = NULL;
76
static struct event *current_global_event = NULL;
77
static struct event *event_last_passthrough = NULL;
78
static ARRAY(event_callback_t *) event_handlers;
79
static ARRAY(event_category_callback_t *) event_category_callbacks;
80
static ARRAY(struct event_internal_category *) event_registered_categories_internal;
81
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
82
static ARRAY(struct event_category *) event_registered_categories;
83
#endif
84
static ARRAY(struct event_category *) event_registered_categories_representative;
85
static ARRAY(struct event *) global_event_stack;
86
static uint64_t event_id_counter = 0;
87
88
static void get_self_rusage(struct rusage *ru_r)
89
0
{
90
0
  if (getrusage(RUSAGE_SELF, ru_r) < 0)
91
0
    i_fatal("getrusage() failed: %m");
92
0
}
93
94
static struct event *
95
event_create_internal(struct event *parent, const char *source_filename,
96
          unsigned int source_linenum);
97
static struct event_internal_category *
98
event_category_find_internal(const char *name);
99
100
static struct event *last_passthrough_event(void)
101
0
{
102
0
  i_assert(event_last_passthrough != NULL);
103
0
  return event_last_passthrough;
104
0
}
105
106
static void event_copy_parent_defaults(struct event *event,
107
               const struct event *parent)
108
0
{
109
0
  event->always_log_source = parent->always_log_source;
110
0
  event->passthrough = parent->passthrough;
111
0
  event->min_log_level = parent->min_log_level;
112
0
  event->forced_debug = parent->forced_debug;
113
0
  event->forced_never_debug = parent->forced_never_debug;
114
0
  event->disable_callbacks = parent->disable_callbacks;
115
0
}
116
117
static bool
118
event_find_category(const struct event *event,
119
        const struct event_category *category);
120
121
static void event_set_changed(struct event *event)
122
330
{
123
330
  event->change_id++;
124
  /* It's unlikely that change_id will ever wrap, but lets be safe
125
     anyway. */
126
330
  if (event->change_id == 0 ||
127
330
      event->change_id == event->sent_to_stats_id)
128
0
    event->change_id++;
129
330
}
130
131
static bool
132
event_call_callbacks(struct event *event, enum event_callback_type type,
133
         struct failure_context *ctx, const char *fmt, va_list args)
134
132
{
135
132
  event_callback_t *callback;
136
137
132
  if (event->disable_callbacks)
138
0
    return TRUE;
139
132
  if (!array_is_created(&event_handlers))
140
0
    return TRUE;
141
142
132
  array_foreach_elem(&event_handlers, callback) {
143
0
    bool ret;
144
145
0
    T_BEGIN {
146
0
      ret = callback(event, type, ctx, fmt, args);
147
0
    } T_END;
148
0
    if (!ret) {
149
      /* event sending was stopped */
150
0
      return FALSE;
151
0
    }
152
0
  }
153
132
  return TRUE;
154
132
}
155
156
static void
157
event_call_callbacks_noargs(struct event *event,
158
          enum event_callback_type type, ...)
159
132
{
160
132
  va_list args;
161
162
  /* the args are empty and not used for anything, but there doesn't seem
163
     to be any nice and standard way of passing an initialized va_list
164
     as a parameter without va_start(). */
165
132
  va_start(args, type);
166
132
  (void)event_call_callbacks(event, type, NULL, NULL, args);
167
132
  va_end(args);
168
132
}
169
170
void event_copy_categories(struct event *to, struct event *from)
171
0
{
172
0
  unsigned int cat_count;
173
0
  struct event_category *const *categories =
174
0
    event_get_categories(from, &cat_count);
175
0
  for (unsigned int i = 1; i <= cat_count; i++)
176
0
    event_add_category(to, categories[cat_count-i]);
177
0
}
178
179
void event_copy_fields(struct event *to, struct event *from)
180
0
{
181
0
  const struct event_field *fld;
182
0
  unsigned int count;
183
0
  const char *const *values;
184
185
0
  if (!array_is_created(&from->fields))
186
0
    return;
187
0
  array_foreach(&from->fields, fld) {
188
0
    switch (fld->value_type) {
189
0
    case EVENT_FIELD_VALUE_TYPE_STR:
190
0
      event_add_str(to, fld->key, fld->value.str);
191
0
      break;
192
0
    case EVENT_FIELD_VALUE_TYPE_INTMAX:
193
0
      event_add_int(to, fld->key, fld->value.intmax);
194
0
      break;
195
0
    case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
196
0
      event_add_timeval(to, fld->key, &fld->value.timeval);
197
0
      break;
198
0
    case EVENT_FIELD_VALUE_TYPE_IP:
199
0
      event_add_ip(to, fld->key, &fld->value.ip);
200
0
      break;
201
0
    case EVENT_FIELD_VALUE_TYPE_STRLIST:
202
0
      values = array_get(&fld->value.strlist, &count);
203
0
      for (unsigned int i = 0; i < count; i++)
204
0
        event_strlist_append(to, fld->key, values[i]);
205
0
      break;
206
0
    default:
207
0
      break;
208
0
    }
209
0
  }
210
0
}
211
212
bool event_has_all_categories(struct event *event, const struct event *other)
213
0
{
214
0
  struct event_category **cat;
215
0
  if (!array_is_created(&other->categories))
216
0
    return TRUE;
217
0
  if (!array_is_created(&event->categories))
218
0
    return FALSE;
219
0
  array_foreach_modifiable(&other->categories, cat) {
220
0
    if (!event_find_category(event, *cat))
221
0
      return FALSE;
222
0
  }
223
0
  return TRUE;
224
0
}
225
226
bool event_has_all_fields(struct event *event, const struct event *other)
227
0
{
228
0
  struct event_field *fld;
229
0
  if (!array_is_created(&other->fields))
230
0
    return TRUE;
231
0
  array_foreach_modifiable(&other->fields, fld) {
232
0
    if (event_find_field_nonrecursive(event, fld->key) == NULL)
233
0
      return FALSE;
234
0
  }
235
0
  return TRUE;
236
0
}
237
238
struct event *event_dup(const struct event *source)
239
0
{
240
0
  struct event *ret =
241
0
    event_create_internal(source->parent, source->source_filename,
242
0
              source->source_linenum);
243
0
  string_t *str = t_str_new(256);
244
0
  const char *err;
245
0
  event_export(source, str);
246
0
  if (!event_import(ret, str_c(str), &err))
247
0
    i_panic("event_import(%s) failed: %s", str_c(str), err);
248
0
  ret->tv_created_ioloop = source->tv_created_ioloop;
249
0
  return ret;
250
0
}
251
252
/*
253
 * Copy the source's categories and fields recursively.
254
 *
255
 * We recurse to the parent before copying this event's data because we may
256
 * be overriding a field.
257
 */
258
static void event_flatten_recurse(struct event *dst, struct event *src,
259
          struct event *limit)
260
0
{
261
0
  if (src->parent != limit)
262
0
    event_flatten_recurse(dst, src->parent, limit);
263
264
0
  event_copy_categories(dst, src);
265
0
  event_copy_fields(dst, src);
266
0
}
267
268
struct event *event_flatten(struct event *src)
269
0
{
270
0
  struct event *dst;
271
272
  /* If we don't have a parent or a global event,
273
     we have nothing to flatten. */
274
0
  if (src->parent == NULL && current_global_event == NULL)
275
0
    return event_ref(src);
276
277
  /* We have to flatten the event. */
278
279
0
  dst = event_create_internal(NULL, src->source_filename,
280
0
            src->source_linenum);
281
0
  dst = event_set_name(dst, src->sending_name);
282
283
0
  if (current_global_event != NULL)
284
0
    event_flatten_recurse(dst, current_global_event, NULL);
285
0
  event_flatten_recurse(dst, src, NULL);
286
287
0
  dst->tv_created_ioloop = src->tv_created_ioloop;
288
0
  dst->tv_created = src->tv_created;
289
0
  dst->tv_last_sent = src->tv_last_sent;
290
291
0
  return dst;
292
0
}
293
294
static inline void replace_parent_ref(struct event *event, struct event *new)
295
0
{
296
0
  if (event->parent == new)
297
0
    return; /* no-op */
298
299
0
  if (new != NULL)
300
0
    event_ref(new);
301
302
0
  event_unref(&event->parent);
303
304
0
  event->parent = new;
305
0
}
306
307
/*
308
 * Minimize the event and its ancestry.
309
 *
310
 * In general, the chain of parents starting from this event can be divided
311
 * up into four consecutive ranges:
312
 *
313
 *  1. the event itself
314
 *  2. a range of events that should be flattened into the event itself
315
 *  3. a range of trivial (i.e., no categories or fields) events that should
316
 *     be skipped
317
 *  4. the rest of the chain
318
 *
319
 * Except for the first range, the event itself, the remaining ranges can
320
 * have zero events.
321
 *
322
 * As the names of these ranges imply, we want to flatten certain parts of
323
 * the ancestry, skip other parts of the ancestry and leave the remainder
324
 * untouched.
325
 *
326
 * For example, suppose that we have an event (A) with ancestors forming the
327
 * following graph:
328
 *
329
 *  A -> B -> C -> D -> E -> F
330
 *
331
 * Further, suppose that B, C, and F contain some categories or fields but
332
 * have not yet been sent to an external process that knows how to reference
333
 * previously encountered events, and D contains no fields or categories of
334
 * its own (but it inherits some from E and F).
335
 *
336
 * We can define the 4 ranges:
337
 *
338
 *  A:     the event
339
 *  B-C:   flattening
340
 *  D:     skipping
341
 *  E-end: the rest
342
 *
343
 * The output would therefore be:
344
 *
345
 *  G -> E -> F
346
 *
347
 * where G contains the fields and categories of A, B, and C (and trivially
348
 * D because D was empty).
349
 *
350
 * Note that even though F has not yet been sent out, we send it now because
351
 * it is part of the "rest" range.
352
 *
353
 * TODO: We could likely apply this function recursively on the "rest"
354
 * range, but further investigation is required to determine whether it is
355
 * worth it.
356
 */
357
struct event *event_minimize(struct event *event)
358
0
{
359
0
  struct event *flatten_bound;
360
0
  struct event *skip_bound;
361
0
  struct event *new_event;
362
0
  struct event *cur;
363
364
0
  if (event->parent == NULL)
365
0
    return event_ref(event);
366
367
  /* find the bound for field/category flattening */
368
0
  flatten_bound = NULL;
369
0
  for (cur = event->parent; cur != NULL; cur = cur->parent) {
370
0
    if (cur->sent_to_stats_id == 0 &&
371
0
        timeval_cmp(&cur->tv_created_ioloop,
372
0
        &event->tv_created_ioloop) == 0)
373
0
      continue;
374
375
0
    flatten_bound = cur;
376
0
    break;
377
0
  }
378
379
  /* continue to find the bound for empty event skipping */
380
0
  skip_bound = NULL;
381
0
  for (; cur != NULL; cur = cur->parent) {
382
0
    if (cur->sent_to_stats_id == 0 &&
383
0
        (!array_is_created(&cur->fields) ||
384
0
         array_is_empty(&cur->fields)) &&
385
0
        (!array_is_created(&cur->categories) ||
386
0
         array_is_empty(&cur->categories)))
387
0
      continue;
388
389
0
    skip_bound = cur;
390
0
    break;
391
0
  }
392
393
  /* fast path - no flattening and no skipping to do */
394
0
  if ((event->parent == flatten_bound) &&
395
0
      (event->parent == skip_bound))
396
0
    return event_ref(event);
397
398
0
  new_event = event_dup(event);
399
400
  /* flatten */
401
0
  event_flatten_recurse(new_event, event, flatten_bound);
402
0
  replace_parent_ref(new_event, flatten_bound);
403
404
  /* skip */
405
0
  replace_parent_ref(new_event, skip_bound);
406
407
0
  return new_event;
408
0
}
409
410
static struct event *
411
event_create_internal(struct event *parent, const char *source_filename,
412
          unsigned int source_linenum)
413
66
{
414
66
  struct event *event;
415
66
  pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"event", 1024);
416
417
66
  event = p_new(pool, struct event, 1);
418
66
  event->refcount = 1;
419
66
  event->id = ++event_id_counter;
420
66
  event->pool = pool;
421
66
  event->tv_created_ioloop = ioloop_timeval;
422
66
  event->min_log_level = LOG_TYPE_INFO;
423
66
  i_gettimeofday(&event->tv_created);
424
66
  event->source_filename = p_strdup(pool, source_filename);
425
66
  event->source_linenum = source_linenum;
426
66
  event->change_id = 1;
427
66
  if (parent != NULL) {
428
0
    event->parent = parent;
429
0
    event_ref(event->parent);
430
0
    event_copy_parent_defaults(event, parent);
431
0
  }
432
66
  DLLIST_PREPEND(&events, event);
433
66
  return event;
434
66
}
435
436
#undef event_create
437
struct event *event_create(struct event *parent, const char *source_filename,
438
         unsigned int source_linenum)
439
66
{
440
66
  struct event *event;
441
442
66
  event = event_create_internal(parent, source_filename, source_linenum);
443
66
  (void)event_call_callbacks_noargs(event, EVENT_CALLBACK_TYPE_CREATE);
444
66
  return event;
445
66
}
446
447
#undef event_create_passthrough
448
struct event_passthrough *
449
event_create_passthrough(struct event *parent, const char *source_filename,
450
       unsigned int source_linenum)
451
0
{
452
0
  if (!parent->passthrough) {
453
0
    if (event_last_passthrough != NULL) {
454
      /* API is being used in a wrong or dangerous way */
455
0
      i_panic("Can't create multiple passthrough events - "
456
0
        "finish the earlier event (%s:%d) with ->event()",
457
0
        event_last_passthrough->source_filename,
458
0
        event_last_passthrough->source_linenum);
459
0
    }
460
0
    struct event *event =
461
0
      event_create(parent, source_filename, source_linenum);
462
0
    event->passthrough = TRUE;
463
    /* This event only intends to extend the parent event.
464
       Use the parent's creation timestamp. */
465
0
    event->tv_created_ioloop = parent->tv_created_ioloop;
466
0
    event->tv_created = parent->tv_created;
467
0
    memcpy(&event->ru_last, &parent->ru_last, sizeof(parent->ru_last));
468
0
    event_last_passthrough = event;
469
0
  } else {
470
0
    event_last_passthrough = parent;
471
0
  }
472
0
  return &event_passthrough_vfuncs;
473
0
}
474
475
struct event *event_ref(struct event *event)
476
0
{
477
0
  i_assert(event->refcount > 0);
478
479
0
  event->refcount++;
480
0
  return event;
481
0
}
482
483
void event_unref(struct event **_event)
484
2.57k
{
485
2.57k
  struct event *event = *_event;
486
487
2.57k
  if (event == NULL)
488
2.50k
    return;
489
66
  *_event = NULL;
490
491
66
  i_assert(event->refcount > 0);
492
66
  if (--event->refcount > 0)
493
0
    return;
494
66
  i_assert(event != current_global_event);
495
496
66
  event_call_callbacks_noargs(event, EVENT_CALLBACK_TYPE_FREE);
497
498
66
  if (event_last_passthrough == event)
499
0
    event_last_passthrough = NULL;
500
66
  if (event->log_prefix_from_system_pool)
501
66
    i_free(event->log_prefix);
502
66
  i_free(event->sending_name);
503
66
  event_unref(&event->parent);
504
505
66
  DLLIST_REMOVE(&events, event);
506
66
  pool_unref(&event->pool);
507
66
}
508
509
struct event *events_get_head(void)
510
0
{
511
0
  return events;
512
0
}
513
514
struct event *event_push_global(struct event *event)
515
0
{
516
0
  i_assert(event != NULL);
517
518
0
  if (current_global_event != NULL) {
519
0
    if (!array_is_created(&global_event_stack))
520
0
      i_array_init(&global_event_stack, 4);
521
0
    array_push_back(&global_event_stack, &current_global_event);
522
0
  }
523
0
  current_global_event = event;
524
0
  return event;
525
0
}
526
527
struct event *event_pop_global(struct event *event)
528
0
{
529
0
  i_assert(event != NULL);
530
0
  i_assert(event == current_global_event);
531
  /* If the active context's root event is popped, we'll assert-crash
532
     later on when deactivating the context and the root event no longer
533
     exists. */
534
0
  i_assert(event != io_loop_get_active_global_root());
535
536
0
  if (!array_is_created(&global_event_stack) ||
537
0
      array_count(&global_event_stack) == 0)
538
0
    current_global_event = NULL;
539
0
  else {
540
0
    unsigned int event_count;
541
0
    struct event *const *events =
542
0
      array_get(&global_event_stack, &event_count);
543
544
0
    i_assert(event_count > 0);
545
0
    current_global_event = events[event_count-1];
546
0
    array_delete(&global_event_stack, event_count-1, 1);
547
0
  }
548
0
  return current_global_event;
549
0
}
550
551
struct event *event_get_global(void)
552
0
{
553
0
  return current_global_event;
554
0
}
555
556
struct event *event_get_global_root(void)
557
0
{
558
0
  if (array_is_created(&global_event_stack) &&
559
0
      array_count(&global_event_stack) > 0) {
560
0
    struct event *const *events =
561
0
      array_front(&global_event_stack);
562
0
    return events[0];
563
0
  }
564
0
  return current_global_event;
565
0
}
566
567
#undef event_reason_begin
568
struct event_reason *
569
event_reason_begin(const char *reason_code, const char *source_filename,
570
       unsigned int source_linenum)
571
0
{
572
0
  struct event_reason *reason;
573
574
0
  reason = i_new(struct event_reason, 1);
575
0
  reason->event = event_create(event_get_global(),
576
0
             source_filename, source_linenum);
577
0
  event_strlist_append(reason->event, EVENT_REASON_CODE, reason_code);
578
0
  event_push_global(reason->event);
579
0
  return reason;
580
0
}
581
582
void event_reason_end(struct event_reason **_reason)
583
0
{
584
0
  struct event_reason *reason = *_reason;
585
586
0
  if (reason == NULL)
587
0
    return;
588
0
  event_pop_global(reason->event);
589
  /* This event was created only for global use. It shouldn't be
590
     permanently stored anywhere. This assert could help catch bugs. */
591
0
  i_assert(reason->event->refcount == 1);
592
0
  event_unref(&reason->event);
593
0
  i_free(reason);
594
0
}
595
596
const char *event_reason_code(const char *module, const char *name)
597
0
{
598
0
  return event_reason_code_prefix(module, "", name);
599
0
}
600
601
static bool event_reason_code_module_validate(const char *module)
602
0
{
603
0
  const char *p;
604
605
0
  for (p = module; *p != '\0'; p++) {
606
0
    if (*p == ' ' || *p == '-' || *p == ':')
607
0
      return FALSE;
608
0
    if (i_isupper(*p))
609
0
      return FALSE;
610
0
  }
611
0
  return TRUE;
612
0
}
613
614
const char *event_reason_code_prefix(const char *module,
615
             const char *name_prefix, const char *name)
616
0
{
617
0
  const char *p;
618
619
0
  i_assert(module[0] != '\0');
620
0
  i_assert(name[0] != '\0');
621
622
0
  if (!event_reason_code_module_validate(module)) {
623
0
    i_panic("event_reason_code_prefix(): "
624
0
      "Invalid module '%s'", module);
625
0
  }
626
0
  if (!event_reason_code_module_validate(name_prefix)) {
627
0
    i_panic("event_reason_code_prefix(): "
628
0
      "Invalid name_prefix '%s'", name_prefix);
629
0
  }
630
631
0
  string_t *str = t_str_new(strlen(module) + 1 +
632
0
          strlen(name_prefix) + strlen(name));
633
0
  str_append(str, module);
634
0
  str_append_c(str, ':');
635
0
  str_append(str, name_prefix);
636
637
0
  for (p = name; *p != '\0'; p++) {
638
0
    switch (*p) {
639
0
    case ' ':
640
0
    case '-':
641
0
      str_append_c(str, '_');
642
0
      break;
643
0
    case ':':
644
0
      i_panic("event_reason_code_prefix(): "
645
0
        "name has ':' (%s, %s%s)",
646
0
        module, name_prefix, name);
647
0
    default:
648
0
      str_append_c(str, i_tolower(*p));
649
0
      break;
650
0
    }
651
0
  }
652
0
  return str_c(str);
653
0
}
654
655
static struct event *
656
event_set_log_prefix(struct event *event, const char *prefix, bool append)
657
0
{
658
0
  event->log_prefix_callback = NULL;
659
0
  event->log_prefix_callback_context = NULL;
660
0
  if (event->log_prefix == NULL) {
661
    /* allocate the first log prefix from the pool */
662
0
    event->log_prefix = p_strdup(event->pool, prefix);
663
0
  } else {
664
    /* log prefix is being updated multiple times -
665
       switch to system pool so we don't keep leaking memory */
666
0
    if (event->log_prefix_from_system_pool)
667
0
      i_free(event->log_prefix);
668
0
    else
669
0
      event->log_prefix_from_system_pool = TRUE;
670
0
    event->log_prefix = i_strdup(prefix);
671
0
  }
672
0
  event->log_prefix_replace = !append;
673
0
  return event;
674
0
}
675
676
struct event *
677
event_set_append_log_prefix(struct event *event, const char *prefix)
678
0
{
679
0
  return event_set_log_prefix(event, prefix, TRUE);
680
0
}
681
682
struct event *event_replace_log_prefix(struct event *event, const char *prefix)
683
0
{
684
0
  return event_set_log_prefix(event, prefix, FALSE);
685
0
}
686
687
struct event *
688
event_drop_parent_log_prefixes(struct event *event, unsigned int count)
689
0
{
690
0
  event->log_prefixes_dropped = count;
691
0
  return event;
692
0
}
693
694
#undef event_set_log_prefix_callback
695
struct event *
696
event_set_log_prefix_callback(struct event *event,
697
            bool replace,
698
            event_log_prefix_callback_t *callback,
699
            void *context)
700
0
{
701
0
  if (event->log_prefix_from_system_pool)
702
0
    i_free(event->log_prefix);
703
0
  else
704
0
    event->log_prefix = NULL;
705
0
  event->log_prefix_replace = replace;
706
0
  event->log_prefix_callback = callback;
707
0
  event->log_prefix_callback_context = context;
708
0
  return event;
709
0
}
710
711
#undef event_set_log_message_callback
712
struct event *
713
event_set_log_message_callback(struct event *event,
714
             event_log_message_callback_t *callback,
715
             void *context)
716
0
{
717
0
  event->log_message_callback = callback;
718
0
  event->log_message_callback_context = context;
719
0
  return event;
720
0
}
721
722
void event_disable_callbacks(struct event *event)
723
0
{
724
0
  event->disable_callbacks = TRUE;
725
0
}
726
727
#undef event_unset_log_message_callback
728
void event_unset_log_message_callback(struct event *event,
729
              event_log_message_callback_t *callback,
730
              void *context)
731
0
{
732
0
  i_assert(event->log_message_callback == callback);
733
0
  i_assert(event->log_message_callback_context == context);
734
735
0
  event->log_message_callback = NULL;
736
0
  event->log_message_callback_context = NULL;
737
0
}
738
739
struct event *
740
event_set_name(struct event *event, const char *name)
741
66
{
742
66
  i_free(event->sending_name);
743
66
  event->sending_name = i_strdup(name);
744
66
  return event;
745
66
}
746
747
struct event *
748
event_set_source(struct event *event, const char *filename,
749
     unsigned int linenum, bool literal_fname)
750
0
{
751
0
  if (strcmp(event->source_filename, filename) != 0) {
752
0
    event->source_filename = literal_fname ? filename :
753
0
      p_strdup(event->pool, filename);
754
0
  }
755
0
  event->source_linenum = linenum;
756
0
  return event;
757
0
}
758
759
struct event *event_set_always_log_source(struct event *event)
760
0
{
761
0
  event->always_log_source = TRUE;
762
0
  return event;
763
0
}
764
765
struct event *event_set_min_log_level(struct event *event, enum log_type level)
766
0
{
767
0
  event->min_log_level = level;
768
0
  event_recalculate_debug_level(event);
769
0
  return event;
770
0
}
771
772
enum log_type event_get_min_log_level(const struct event *event)
773
0
{
774
0
  return event->min_log_level;
775
0
}
776
777
struct event *event_set_ptr(struct event *event, const char *key, void *value)
778
0
{
779
0
  struct event_pointer *p;
780
781
0
  if (!array_is_created(&event->pointers))
782
0
    p_array_init(&event->pointers, event->pool, 4);
783
0
  else {
784
    /* replace existing pointer if the key already exists */
785
0
    array_foreach_modifiable(&event->pointers, p) {
786
0
      if (strcmp(p->key, key) == 0) {
787
0
        p->value = value;
788
0
        return event;
789
0
      }
790
0
    }
791
0
  }
792
0
  p = array_append_space(&event->pointers);
793
0
  p->key = p_strdup(event->pool, key);
794
0
  p->value = value;
795
0
  return event;
796
0
}
797
798
void *event_get_ptr(const struct event *event, const char *key)
799
0
{
800
0
  const struct event_pointer *p;
801
802
0
  if (!array_is_created(&event->pointers))
803
0
    return NULL;
804
0
  array_foreach(&event->pointers, p) {
805
0
    if (strcmp(p->key, key) == 0)
806
0
      return p->value;
807
0
  }
808
0
  return NULL;
809
0
}
810
811
struct event_category *event_category_find_registered(const char *name)
812
0
{
813
0
  struct event_category *cat;
814
815
0
  array_foreach_elem(&event_registered_categories_representative, cat) {
816
0
    if (strcmp(cat->name, name) == 0)
817
0
      return cat;
818
0
  }
819
0
  return NULL;
820
0
}
821
822
static struct event_internal_category *
823
event_category_find_internal(const char *name)
824
0
{
825
0
  struct event_internal_category *internal;
826
827
0
  array_foreach_elem(&event_registered_categories_internal, internal) {
828
0
    if (strcmp(internal->name, name) == 0)
829
0
      return internal;
830
0
  }
831
832
0
  return NULL;
833
0
}
834
835
struct event_category *const *
836
event_get_registered_categories(unsigned int *count_r)
837
0
{
838
0
  return array_get(&event_registered_categories_representative, count_r);
839
0
}
840
841
static void
842
event_category_add_to_array(struct event_internal_category *internal)
843
0
{
844
0
  struct event_category *representative = &internal->representative;
845
846
0
  array_push_back(&event_registered_categories_internal, &internal);
847
0
  array_push_back(&event_registered_categories_representative,
848
0
      &representative);
849
0
}
850
851
static struct event_category *
852
event_category_register(struct event_category *category)
853
0
{
854
0
  struct event_internal_category *internal = category->internal;
855
0
  event_category_callback_t *callback;
856
0
  bool allocated;
857
858
0
  if (internal != NULL)
859
0
    return &internal->representative; /* case 2 - see below */
860
861
  /* register parent categories first */
862
0
  if (category->parent != NULL)
863
0
    (void) event_category_register(category->parent);
864
865
  /* There are four cases we need to handle:
866
867
     1) a new category is registered
868
     2) same category struct is re-registered - already handled above
869
        internal NULL check
870
     3) different category struct is registered, but it is identical
871
        to the previously registered one
872
     4) different category struct is registered, and it is different
873
        from the previously registered one - a programming error */
874
0
  internal = event_category_find_internal(category->name);
875
0
  if (internal == NULL) {
876
    /* case 1: first time we saw this name - allocate new */
877
0
    internal = i_new(struct event_internal_category, 1);
878
0
    if (category->parent != NULL)
879
0
      internal->parent = category->parent->internal;
880
0
    internal->name = i_strdup(category->name);
881
0
    internal->refcount = 1;
882
0
    internal->representative.name = internal->name;
883
    /* Point to the parent's representative rather than to the
884
       caller's struct, which may be freed while this
885
       representative still exists. */
886
0
    internal->representative.parent = internal->parent == NULL ?
887
0
      NULL : &internal->parent->representative;
888
0
    internal->representative.internal = internal;
889
890
0
    event_category_add_to_array(internal);
891
892
0
    allocated = TRUE;
893
0
  } else {
894
    /* case 3 or 4: someone registered this name before - share */
895
0
    if ((category->parent != NULL) &&
896
0
        (internal->parent != category->parent->internal)) {
897
      /* case 4 */
898
0
      struct event_internal_category *other =
899
0
        category->parent->internal;
900
901
0
      i_panic("event category parent mismatch detected: "
902
0
        "category %p internal %p (%s), "
903
0
        "internal parent %p (%s), public parent %p (%s)",
904
0
        category, internal, internal->name,
905
0
        internal->parent, internal->parent->name,
906
0
        other, other->name);
907
0
    }
908
909
0
    internal->refcount++;
910
911
0
    allocated = FALSE;
912
0
  }
913
914
0
  category->internal = internal;
915
0
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
916
  /* Record the registered category for later cleanup upon lib_deinit().
917
     For normal operation, calling lib_init() a second time never occurs,
918
     so this cleanup (clearing state) is not necessary normally. However,
919
     some fuzzers rely on fuzzing some sub-system from the very inception
920
     of the Dovecot library, so cycling lib_init() and lib_deinit()
921
     becomes a concern. Sadly, clearing event category state will only
922
     work when that is not located in some loaded plugin module, which
923
     would be unloaded long before lib_deinit(). Since Dovecot supports
924
     loading modules at many subsystems, this would cause crashes during
925
     normal operation. Therefore, the cleanup of category state is omitted
926
     entirely for normal operation and is only compiled in for the benefit
927
     of fuzzing where loading such modules is either omitted or handled
928
     specially. */
929
0
  array_push_back(&event_registered_categories, &category);
930
0
#endif
931
932
0
  if (!allocated) {
933
    /* not the first registration of this category */
934
0
    return &internal->representative;
935
0
  }
936
937
0
  array_foreach_elem(&event_category_callbacks, callback) T_BEGIN {
938
0
    callback(&internal->representative);
939
0
  } T_END;
940
941
0
  return &internal->representative;
942
0
}
943
944
void event_category_unregister(struct event_category *category)
945
0
{
946
0
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
947
0
  unsigned int idx;
948
0
#endif
949
950
0
  if (category->internal == NULL) {
951
    /* not registered */
952
0
    return;
953
0
  }
954
0
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
955
  /* Refer to earlier comment in event_category_register(). */
956
0
  if (array_lsearch_ptr_idx(&event_registered_categories, category, &idx))
957
0
    array_delete(&event_registered_categories, idx, 1);
958
0
#endif
959
0
  category->internal = NULL;
960
0
}
961
962
static bool
963
event_find_category(const struct event *event,
964
        const struct event_category *category)
965
0
{
966
0
  struct event_internal_category *internal = category->internal;
967
968
  /* make sure we're always looking for a representative */
969
0
  i_assert(category == &internal->representative);
970
971
0
  return array_lsearch_ptr(&event->categories, category) != NULL;
972
0
}
973
974
struct event *
975
event_add_categories(struct event *event,
976
         struct event_category *const *categories)
977
0
{
978
0
  struct event_category *representative;
979
980
0
  if (!array_is_created(&event->categories))
981
0
    p_array_init(&event->categories, event->pool, 4);
982
983
0
  for (unsigned int i = 0; categories[i] != NULL; i++) {
984
0
    representative = event_category_register(categories[i]);
985
0
    if (!event_find_category(event, representative))
986
0
      array_push_back(&event->categories, &representative);
987
0
  }
988
0
  event_set_changed(event);
989
0
  event_recalculate_debug_level(event);
990
0
  return event;
991
0
}
992
993
struct event *
994
event_add_category(struct event *event, struct event_category *category)
995
0
{
996
0
  struct event_category *const categories[] = { category, NULL };
997
0
  return event_add_categories(event, categories);
998
0
}
999
1000
struct event_field *
1001
event_find_field_nonrecursive(const struct event *event, const char *key)
1002
330
{
1003
330
  struct event_field *field;
1004
1005
330
  if (!array_is_created(&event->fields))
1006
66
    return NULL;
1007
1008
660
  array_foreach_modifiable(&event->fields, field) {
1009
660
    if (strcmp(field->key, key) == 0)
1010
0
      return field;
1011
660
  }
1012
264
  return NULL;
1013
264
}
1014
1015
const struct event_field *
1016
event_find_field_recursive(const struct event *event, const char *key)
1017
0
{
1018
0
  const struct event_field *field;
1019
1020
0
  do {
1021
0
    if ((field = event_find_field_nonrecursive(event, key)) != NULL)
1022
0
      return field;
1023
0
    event = event->parent;
1024
0
  } while (event != NULL);
1025
1026
  /* check also the global event and its parents */
1027
0
  event = event_get_global();
1028
0
  while (event != NULL) {
1029
0
    if ((field = event_find_field_nonrecursive(event, key)) != NULL)
1030
0
      return field;
1031
0
    event = event->parent;
1032
0
  }
1033
0
  return NULL;
1034
0
}
1035
1036
static void
1037
event_get_recursive_strlist(const struct event *event, pool_t pool,
1038
          const char *key, ARRAY_TYPE(const_string) *dest)
1039
0
{
1040
0
  const struct event_field *field;
1041
0
  const char *str;
1042
1043
0
  if (event == NULL)
1044
0
    return;
1045
1046
0
  field = event_find_field_nonrecursive(event, key);
1047
0
  if (field != NULL) {
1048
0
    if (field->value_type != EVENT_FIELD_VALUE_TYPE_STRLIST) {
1049
      /* Value type unexpectedly changed. Stop recursing. */
1050
0
      return;
1051
0
    }
1052
0
    array_foreach_elem(&field->value.strlist, str) {
1053
0
      if (array_lsearch(dest, &str, i_strcmp_p) == NULL) {
1054
0
        if (pool != NULL)
1055
0
          str = p_strdup(pool, str);
1056
0
        array_push_back(dest, &str);
1057
0
      }
1058
0
    }
1059
0
  }
1060
0
  event_get_recursive_strlist(event->parent, pool, key, dest);
1061
0
}
1062
1063
const char *
1064
event_find_field_recursive_str(const struct event *event, const char *key)
1065
0
{
1066
0
  const struct event_field *field;
1067
1068
0
  field = event_find_field_recursive(event, key);
1069
0
  if (field == NULL)
1070
0
    return NULL;
1071
1072
0
  switch (field->value_type) {
1073
0
  case EVENT_FIELD_VALUE_TYPE_STR:
1074
0
    return field->value.str;
1075
0
  case EVENT_FIELD_VALUE_TYPE_INTMAX:
1076
0
    return t_strdup_printf("%jd", field->value.intmax);
1077
0
  case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
1078
0
    return t_strdup_printf("%"PRIdTIME_T".%u",
1079
0
      field->value.timeval.tv_sec,
1080
0
      (unsigned int)field->value.timeval.tv_usec);
1081
0
  case EVENT_FIELD_VALUE_TYPE_IP:
1082
0
    return net_ip2addr(&field->value.ip);
1083
0
  case EVENT_FIELD_VALUE_TYPE_STRLIST: {
1084
0
    ARRAY_TYPE(const_string) list;
1085
0
    t_array_init(&list, 8);
1086
    /* This is a bit different, because it needs to be merging
1087
       all of the parent events' and global events' lists
1088
       together. */
1089
0
    event_get_recursive_strlist(event, NULL, key, &list);
1090
0
    event_get_recursive_strlist(event_get_global(), NULL,
1091
0
              key, &list);
1092
0
    return t_array_const_string_join(&list, ",");
1093
0
  }
1094
0
  }
1095
0
  i_unreached();
1096
0
}
1097
1098
static struct event_field *
1099
event_get_field(struct event *event, const char *key, bool clear)
1100
330
{
1101
330
  struct event_field *field;
1102
1103
330
  field = event_find_field_nonrecursive(event, key);
1104
330
  if (field == NULL) {
1105
330
    if (!array_is_created(&event->fields))
1106
330
      p_array_init(&event->fields, event->pool, 8);
1107
330
    field = array_append_space(&event->fields);
1108
330
    field->key = p_strdup(event->pool, key);
1109
330
  } else if (clear) {
1110
0
    i_zero(&field->value);
1111
0
  }
1112
330
  event_set_changed(event);
1113
330
  return field;
1114
330
}
1115
1116
struct event *
1117
event_add_str(struct event *event, const char *key, const char *value)
1118
66
{
1119
66
  struct event_field *field;
1120
1121
66
  if (value == NULL) {
1122
    /* Silently ignoring is perhaps better than assert-crashing?
1123
       However, if the field already exists, this should be the
1124
       same as event_field_clear() */
1125
0
    if (event_find_field_recursive(event, key) == NULL)
1126
0
      return event;
1127
0
    value = "";
1128
0
  }
1129
1130
66
  field = event_get_field(event, key, TRUE);
1131
66
  field->value_type = EVENT_FIELD_VALUE_TYPE_STR;
1132
66
  field->value.str = p_strdup(event->pool, value);
1133
66
  return event;
1134
66
}
1135
1136
struct event *
1137
event_strlist_append(struct event *event, const char *key, const char *value)
1138
0
{
1139
0
  struct event_field *field = event_get_field(event, key, FALSE);
1140
1141
0
  if (field->value_type != EVENT_FIELD_VALUE_TYPE_STRLIST ||
1142
0
      !array_is_created(&field->value.strlist)) {
1143
0
    field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST;
1144
0
    p_array_init(&field->value.strlist, event->pool, 1);
1145
0
  }
1146
1147
  /* lets not add empty values there though */
1148
0
  if (value == NULL)
1149
0
    return event;
1150
1151
0
  const char *str = p_strdup(event->pool, value);
1152
0
  if (array_lsearch(&field->value.strlist, &str, i_strcmp_p) == NULL)
1153
0
    array_push_back(&field->value.strlist, &str);
1154
0
  return event;
1155
0
}
1156
1157
struct event *
1158
event_strlist_replace(struct event *event, const char *key,
1159
          const char *const *values, unsigned int count)
1160
0
{
1161
0
  struct event_field *field = event_get_field(event, key, TRUE);
1162
0
  field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST;
1163
1164
0
  for (unsigned int i = 0; i < count; i++)
1165
0
    event_strlist_append(event, key, values[i]);
1166
0
  return event;
1167
0
}
1168
1169
struct event *
1170
event_strlist_copy_recursive(struct event *dest, const struct event *src,
1171
           const char *key)
1172
0
{
1173
0
  event_strlist_append(dest, key, NULL);
1174
0
  struct event_field *field = event_get_field(dest, key, FALSE);
1175
0
  i_assert(field != NULL);
1176
0
  event_get_recursive_strlist(src, dest->pool, key,
1177
0
            &field->value.strlist);
1178
0
  return dest;
1179
0
}
1180
1181
struct event *
1182
event_add_int(struct event *event, const char *key, intmax_t num)
1183
264
{
1184
264
  struct event_field *field;
1185
1186
264
  field = event_get_field(event, key, TRUE);
1187
264
  field->value_type = EVENT_FIELD_VALUE_TYPE_INTMAX;
1188
264
  field->value.intmax = num;
1189
264
  return event;
1190
264
}
1191
1192
struct event *
1193
event_add_int_nonzero(struct event *event, const char *key, intmax_t num)
1194
0
{
1195
0
  if (num != 0)
1196
0
    return event_add_int(event, key, num);
1197
0
  return event;
1198
0
}
1199
1200
struct event *
1201
event_inc_int(struct event *event, const char *key, intmax_t num)
1202
0
{
1203
0
  struct event_field *field;
1204
1205
0
  field = event_find_field_nonrecursive(event, key);
1206
0
  if (field == NULL || field->value_type != EVENT_FIELD_VALUE_TYPE_INTMAX)
1207
0
    return event_add_int(event, key, num);
1208
1209
0
  field->value.intmax += num;
1210
0
  event_set_changed(event);
1211
0
  return event;
1212
0
}
1213
1214
struct event *
1215
event_add_timeval(struct event *event, const char *key,
1216
      const struct timeval *tv)
1217
0
{
1218
0
  struct event_field *field;
1219
1220
0
  field = event_get_field(event, key, TRUE);
1221
0
  field->value_type = EVENT_FIELD_VALUE_TYPE_TIMEVAL;
1222
0
  field->value.timeval = *tv;
1223
0
  return event;
1224
0
}
1225
1226
struct event *
1227
event_add_ip(struct event *event, const char *key, const struct ip_addr *ip)
1228
0
{
1229
0
  struct event_field *field;
1230
1231
0
  if (ip->family == 0) {
1232
    /* ignore nonexistent IP (similar to
1233
       event_add_str(value=NULL)) */
1234
0
    if (event_find_field_recursive(event, key) != NULL)
1235
0
      event_field_clear(event, key);
1236
0
    return event;
1237
0
  }
1238
1239
0
  field = event_get_field(event, key, TRUE);
1240
0
  field->value_type = EVENT_FIELD_VALUE_TYPE_IP;
1241
0
  field->value.ip = *ip;
1242
0
  return event;
1243
0
}
1244
1245
struct event *
1246
event_add_fields(struct event *event,
1247
     const struct event_add_field *fields)
1248
0
{
1249
0
  for (unsigned int i = 0; fields[i].key != NULL; i++) {
1250
0
    if (fields[i].value != NULL)
1251
0
      event_add_str(event, fields[i].key, fields[i].value);
1252
0
    else if (fields[i].value_timeval.tv_sec != 0) {
1253
0
      event_add_timeval(event, fields[i].key,
1254
0
            &fields[i].value_timeval);
1255
0
    } else if (fields[i].value_ip.family != 0) {
1256
0
      event_add_ip(event, fields[i].key, &fields[i].value_ip);
1257
0
    } else {
1258
0
      event_add_int(event, fields[i].key,
1259
0
              fields[i].value_intmax);
1260
0
    }
1261
0
  }
1262
0
  return event;
1263
0
}
1264
1265
void event_field_clear(struct event *event, const char *key)
1266
0
{
1267
0
  event_add_str(event, key, "");
1268
0
}
1269
1270
struct event *event_get_parent(const struct event *event)
1271
0
{
1272
0
  return event->parent;
1273
0
}
1274
1275
pool_t event_get_pool(const struct event *event)
1276
0
{
1277
0
  return event->pool;
1278
0
}
1279
1280
void event_get_create_time(const struct event *event, struct timeval *tv_r)
1281
0
{
1282
0
  *tv_r = event->tv_created;
1283
0
}
1284
1285
bool event_get_last_send_time(const struct event *event, struct timeval *tv_r)
1286
0
{
1287
0
  *tv_r = event->tv_last_sent;
1288
0
  return tv_r->tv_sec != 0;
1289
0
}
1290
1291
void event_get_last_duration(const struct event *event,
1292
           uintmax_t *duration_usecs_r)
1293
0
{
1294
0
  if (event->tv_last_sent.tv_sec == 0) {
1295
0
    *duration_usecs_r = 0;
1296
0
    return;
1297
0
  }
1298
0
  long long diff = timeval_diff_usecs(&event->tv_last_sent,
1299
0
              &event->tv_created);
1300
0
  i_assert(diff >= 0);
1301
0
  *duration_usecs_r = diff;
1302
0
}
1303
1304
const struct event_field *
1305
event_get_fields(const struct event *event, unsigned int *count_r)
1306
0
{
1307
0
  if (!array_is_created(&event->fields)) {
1308
0
    *count_r = 0;
1309
0
    return NULL;
1310
0
  }
1311
0
  return array_get(&event->fields, count_r);
1312
0
}
1313
1314
struct event_category *const *
1315
event_get_categories(const struct event *event, unsigned int *count_r)
1316
0
{
1317
0
  if (!array_is_created(&event->categories)) {
1318
0
    *count_r = 0;
1319
0
    return NULL;
1320
0
  }
1321
0
  return array_get(&event->categories, count_r);
1322
0
}
1323
1324
static void
1325
insert_category(HASH_TABLE_TYPE(category_set) hash,
1326
    const struct event_category *const cat)
1327
0
{
1328
  /* insert this category (key == the unique internal pointer) */
1329
0
  hash_table_update(hash, cat->internal, cat);
1330
1331
  /* insert parent's categories */
1332
0
  if (cat->parent != NULL)
1333
0
    insert_category(hash, cat->parent);
1334
0
}
1335
1336
struct event_category_iterator *
1337
event_categories_iterate_init(const struct event *event)
1338
0
{
1339
0
  struct event_category_iterator *iter;
1340
0
  struct event_category *const *cats;
1341
0
  unsigned int count, i;
1342
1343
0
  cats = event_get_categories(event, &count);
1344
0
  if (count == 0)
1345
0
    return NULL;
1346
1347
0
  iter = i_new(struct event_category_iterator, 1);
1348
1349
0
  hash_table_create_direct(&iter->hash, default_pool,
1350
0
         3 * count /* estimate */);
1351
1352
  /* Insert all the categories into the hash table */
1353
0
  for (i = 0; i < count; i++)
1354
0
    insert_category(iter->hash, cats[i]);
1355
1356
0
  iter->iter = hash_table_iterate_init(iter->hash);
1357
1358
0
  return iter;
1359
0
}
1360
1361
bool event_categories_iterate(struct event_category_iterator *iter,
1362
            const struct event_category **cat_r)
1363
0
{
1364
0
  void *key ATTR_UNUSED;
1365
1366
0
  if (iter == NULL) {
1367
0
    *cat_r = NULL;
1368
0
    return FALSE;
1369
0
  }
1370
0
  return hash_table_iterate(iter->iter, iter->hash, &key, cat_r);
1371
0
}
1372
1373
void event_categories_iterate_deinit(struct event_category_iterator **_iter)
1374
0
{
1375
0
  struct event_category_iterator *iter = *_iter;
1376
1377
0
  if (iter == NULL)
1378
0
    return;
1379
0
  *_iter = NULL;
1380
1381
0
  hash_table_iterate_deinit(&iter->iter);
1382
0
  hash_table_destroy(&iter->hash);
1383
0
  i_free(iter);
1384
0
}
1385
1386
void event_send(struct event *event, struct failure_context *ctx,
1387
    const char *fmt, ...)
1388
0
{
1389
0
  va_list args;
1390
1391
0
  va_start(args, fmt);
1392
0
  event_vsend(event, ctx, fmt, args);
1393
0
  va_end(args);
1394
0
}
1395
1396
void event_vsend(struct event *event, struct failure_context *ctx,
1397
     const char *fmt, va_list args)
1398
0
{
1399
0
  i_gettimeofday(&event->tv_last_sent);
1400
1401
  /* Skip adding user_cpu_usecs if not enabled. */
1402
0
  if (event->ru_last.ru_utime.tv_sec != 0 ||
1403
0
      event->ru_last.ru_utime.tv_usec != 0) {
1404
0
    struct rusage ru_current;
1405
0
    get_self_rusage(&ru_current);
1406
0
    long long udiff = timeval_diff_usecs(&ru_current.ru_utime,
1407
0
                 &event->ru_last.ru_utime);
1408
0
    event_add_int(event, "user_cpu_usecs", udiff > 0 ? udiff : 0);
1409
0
  }
1410
0
  if (event_call_callbacks(event, EVENT_CALLBACK_TYPE_SEND,
1411
0
         ctx, fmt, args)) {
1412
0
    if (ctx->type != LOG_TYPE_DEBUG ||
1413
0
        event->sending_debug_log)
1414
0
      i_log_typev(ctx, fmt, args);
1415
0
  }
1416
0
  event_send_abort(event);
1417
0
}
1418
1419
void event_send_abort(struct event *event)
1420
0
{
1421
  /* if the event is sent again, it needs a new name */
1422
0
  i_free(event->sending_name);
1423
0
  if (event->passthrough)
1424
0
    event_unref(&event);
1425
0
}
1426
1427
static void
1428
event_export_field_value(string_t *dest, const struct event_field *field)
1429
0
{
1430
0
  switch (field->value_type) {
1431
0
  case EVENT_FIELD_VALUE_TYPE_STR:
1432
0
    str_append_c(dest, EVENT_CODE_FIELD_STR);
1433
0
    str_append_tabescaped(dest, field->key);
1434
0
    str_append_c(dest, '\t');
1435
0
    str_append_tabescaped(dest, field->value.str);
1436
0
    break;
1437
0
  case EVENT_FIELD_VALUE_TYPE_INTMAX:
1438
0
    str_append_c(dest, EVENT_CODE_FIELD_INTMAX);
1439
0
    str_append_tabescaped(dest, field->key);
1440
0
    str_printfa(dest, "\t%jd", field->value.intmax);
1441
0
    break;
1442
0
  case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
1443
0
    str_append_c(dest, EVENT_CODE_FIELD_TIMEVAL);
1444
0
    str_append_tabescaped(dest, field->key);
1445
0
    str_printfa(dest, "\t%"PRIdTIME_T"\t%u",
1446
0
          field->value.timeval.tv_sec,
1447
0
          (unsigned int)field->value.timeval.tv_usec);
1448
0
    break;
1449
0
  case EVENT_FIELD_VALUE_TYPE_IP:
1450
0
    str_append_c(dest, EVENT_CODE_FIELD_IP);
1451
0
    str_append_tabescaped(dest, field->key);
1452
0
    str_printfa(dest, "\t%s", net_ip2addr(&field->value.ip));
1453
0
    break;
1454
0
  case EVENT_FIELD_VALUE_TYPE_STRLIST: {
1455
0
    unsigned int count;
1456
0
    const char *const *strlist =
1457
0
      array_get(&field->value.strlist, &count);
1458
0
    str_append_c(dest, EVENT_CODE_FIELD_STRLIST);
1459
0
    str_append_tabescaped(dest, field->key);
1460
0
    str_printfa(dest, "\t%u", count);
1461
0
    for (unsigned int i = 0; i < count; i++) {
1462
0
      str_append_c(dest, '\t');
1463
0
      str_append_tabescaped(dest, strlist[i]);
1464
0
    }
1465
0
  }
1466
0
  }
1467
0
}
1468
1469
void event_export(const struct event *event, string_t *dest)
1470
0
{
1471
  /* required fields: */
1472
0
  str_printfa(dest, "%"PRIdTIME_T"\t%u",
1473
0
        event->tv_created.tv_sec,
1474
0
        (unsigned int)event->tv_created.tv_usec);
1475
1476
  /* optional fields: */
1477
0
  if (event->source_filename != NULL) {
1478
0
    str_append_c(dest, '\t');
1479
0
    str_append_c(dest, EVENT_CODE_SOURCE);
1480
0
    str_append_tabescaped(dest, event->source_filename);
1481
0
    str_printfa(dest, "\t%u", event->source_linenum);
1482
0
  }
1483
0
  if (event->always_log_source) {
1484
0
    str_append_c(dest, '\t');
1485
0
    str_append_c(dest, EVENT_CODE_ALWAYS_LOG_SOURCE);
1486
0
  }
1487
0
  if (event->tv_last_sent.tv_sec != 0) {
1488
0
    str_printfa(dest, "\t%c%"PRIdTIME_T"\t%u",
1489
0
          EVENT_CODE_TV_LAST_SENT,
1490
0
          event->tv_last_sent.tv_sec,
1491
0
          (unsigned int)event->tv_last_sent.tv_usec);
1492
0
  }
1493
0
  if (event->sending_name != NULL) {
1494
0
    str_append_c(dest, '\t');
1495
0
    str_append_c(dest, EVENT_CODE_SENDING_NAME);
1496
0
    str_append_tabescaped(dest, event->sending_name);
1497
0
  }
1498
1499
0
  if (array_is_created(&event->categories)) {
1500
0
    struct event_category *cat;
1501
0
    array_foreach_elem(&event->categories, cat) {
1502
0
      str_append_c(dest, '\t');
1503
0
      str_append_c(dest, EVENT_CODE_CATEGORY);
1504
0
      str_append_tabescaped(dest, cat->name);
1505
0
    }
1506
0
  }
1507
1508
0
  if (array_is_created(&event->fields)) {
1509
0
    const struct event_field *field;
1510
0
    array_foreach(&event->fields, field) {
1511
0
      str_append_c(dest, '\t');
1512
0
      event_export_field_value(dest, field);
1513
0
    }
1514
0
  }
1515
0
}
1516
1517
bool event_import(struct event *event, const char *str, const char **error_r)
1518
0
{
1519
0
  return event_import_unescaped(event, t_strsplit_tabescaped(str),
1520
0
              error_r);
1521
0
}
1522
1523
static bool event_import_tv(const char *arg_secs, const char *arg_usecs,
1524
          struct timeval *tv_r, const char **error_r)
1525
0
{
1526
0
  unsigned int usecs;
1527
1528
0
  if (str_to_time(arg_secs, &tv_r->tv_sec) < 0) {
1529
0
    *error_r = "Invalid timeval seconds parameter";
1530
0
    return FALSE;
1531
0
  }
1532
1533
0
  if (arg_usecs == NULL) {
1534
0
    *error_r = "Timeval missing microseconds parameter";
1535
0
    return FALSE;
1536
0
  }
1537
0
  if (str_to_uint(arg_usecs, &usecs) < 0 || usecs >= 1000000) {
1538
0
    *error_r = "Invalid timeval microseconds parameter";
1539
0
    return FALSE;
1540
0
  }
1541
0
  tv_r->tv_usec = usecs;
1542
0
  return TRUE;
1543
0
}
1544
1545
static bool
1546
event_import_strlist(struct event *event, struct event_field *field,
1547
         const char *const **_args, const char **error_r)
1548
0
{
1549
0
  const char *const *args = *_args;
1550
0
  unsigned int count, i;
1551
1552
0
  field->value_type = EVENT_FIELD_VALUE_TYPE_STRLIST;
1553
0
  if (str_to_uint(args[0], &count) < 0) {
1554
0
    *error_r = t_strdup_printf("Field '%s' has invalid count: '%s'",
1555
0
             field->key, args[0]);
1556
0
    return FALSE;
1557
0
  }
1558
0
  p_array_init(&field->value.strlist, event->pool, count);
1559
0
  for (i = 1; i <= count && args[i] != NULL; i++) {
1560
0
    const char *str = p_strdup(event->pool, args[i]);
1561
0
    array_push_back(&field->value.strlist, &str);
1562
0
  }
1563
0
  if (i < count) {
1564
0
    *error_r = t_strdup_printf("Field '%s' has too few values",
1565
0
             field->key);
1566
0
    return FALSE;
1567
0
  }
1568
0
  *_args += count;
1569
0
  return TRUE;
1570
0
}
1571
1572
static bool
1573
event_import_field(struct event *event, enum event_code code, const char *arg,
1574
       const char *const **_args, const char **error_r)
1575
0
{
1576
0
  const char *const *args = *_args;
1577
0
  const char *error;
1578
1579
0
  if (*arg == '\0') {
1580
0
    *error_r = "Field name is missing";
1581
0
    return FALSE;
1582
0
  }
1583
0
  struct event_field *field = event_get_field(event, arg, TRUE);
1584
0
  if (args[0] == NULL) {
1585
0
    *error_r = "Field value is missing";
1586
0
    return FALSE;
1587
0
  }
1588
0
  switch (code) {
1589
0
  case EVENT_CODE_FIELD_INTMAX:
1590
0
    field->value_type = EVENT_FIELD_VALUE_TYPE_INTMAX;
1591
0
    if (str_to_intmax(*args, &field->value.intmax) < 0) {
1592
0
      *error_r = t_strdup_printf(
1593
0
        "Invalid field value '%s' number for '%s'",
1594
0
        *args, field->key);
1595
0
      return FALSE;
1596
0
    }
1597
0
    break;
1598
0
  case EVENT_CODE_FIELD_STR:
1599
0
    if (field->value_type == EVENT_FIELD_VALUE_TYPE_STR &&
1600
0
        null_strcmp(field->value.str, *args) == 0) {
1601
      /* already identical value */
1602
0
      break;
1603
0
    }
1604
0
    field->value_type = EVENT_FIELD_VALUE_TYPE_STR;
1605
0
    field->value.str = p_strdup(event->pool, *args);
1606
0
    break;
1607
0
  case EVENT_CODE_FIELD_TIMEVAL:
1608
0
    field->value_type = EVENT_FIELD_VALUE_TYPE_TIMEVAL;
1609
0
    if (!event_import_tv(args[0], args[1],
1610
0
             &field->value.timeval, &error)) {
1611
0
      *error_r = t_strdup_printf("Field '%s' value '%s': %s",
1612
0
               field->key, args[1], error);
1613
0
      return FALSE;
1614
0
    }
1615
0
    args++;
1616
0
    break;
1617
0
  case EVENT_CODE_FIELD_IP:
1618
0
    field->value_type = EVENT_FIELD_VALUE_TYPE_IP;
1619
0
    if (net_addr2ip(*args, &field->value.ip) < 0) {
1620
0
      *error_r = t_strdup_printf(
1621
0
        "Invalid field value '%s' IP for '%s'",
1622
0
        *args, field->key);
1623
0
      return FALSE;
1624
0
    }
1625
0
    break;
1626
0
  case EVENT_CODE_FIELD_STRLIST:
1627
0
    if (!event_import_strlist(event, field, &args, error_r))
1628
0
      return FALSE;
1629
0
    break;
1630
0
  default:
1631
0
    i_unreached();
1632
0
  }
1633
0
  *_args = args;
1634
0
  return TRUE;
1635
0
}
1636
1637
1638
static bool
1639
event_import_arg(struct event *event, const char *const **_args,
1640
     const char **error_r)
1641
0
{
1642
0
  const char *const *args = *_args;
1643
0
  const char *error, *arg = *args;
1644
0
  enum event_code code = arg[0];
1645
1646
0
  arg++;
1647
0
  switch (code) {
1648
0
  case EVENT_CODE_ALWAYS_LOG_SOURCE:
1649
0
    event->always_log_source = TRUE;
1650
0
    break;
1651
0
  case EVENT_CODE_CATEGORY: {
1652
0
    struct event_category *category =
1653
0
      event_category_find_registered(arg);
1654
0
    if (category == NULL) {
1655
0
      *error_r = t_strdup_printf(
1656
0
        "Unregistered category: '%s'", arg);
1657
0
      return FALSE;
1658
0
    }
1659
0
    if (!array_is_created(&event->categories))
1660
0
      p_array_init(&event->categories, event->pool, 4);
1661
0
    if (!event_find_category(event, category))
1662
0
      array_push_back(&event->categories, &category);
1663
0
    break;
1664
0
  }
1665
0
  case EVENT_CODE_TV_LAST_SENT:
1666
0
    if (!event_import_tv(arg, args[1], &event->tv_last_sent,
1667
0
             &error)) {
1668
0
      *error_r = t_strdup_printf(
1669
0
        "Invalid tv_last_sent: %s", error);
1670
0
      return FALSE;
1671
0
    }
1672
0
    args++;
1673
0
    break;
1674
0
  case EVENT_CODE_SENDING_NAME:
1675
0
    i_free(event->sending_name);
1676
0
    event->sending_name = i_strdup(arg);
1677
0
    break;
1678
0
  case EVENT_CODE_SOURCE: {
1679
0
    unsigned int linenum;
1680
1681
0
    if (args[1] == NULL) {
1682
0
      *error_r = "Source line number missing";
1683
0
      return FALSE;
1684
0
    }
1685
0
    if (str_to_uint(args[1], &linenum) < 0) {
1686
0
      *error_r = "Invalid Source line number";
1687
0
      return FALSE;
1688
0
    }
1689
0
    event_set_source(event, arg, linenum, FALSE);
1690
0
    args++;
1691
0
    break;
1692
0
  }
1693
0
  case EVENT_CODE_FIELD_INTMAX:
1694
0
  case EVENT_CODE_FIELD_STR:
1695
0
  case EVENT_CODE_FIELD_STRLIST:
1696
0
  case EVENT_CODE_FIELD_TIMEVAL:
1697
0
  case EVENT_CODE_FIELD_IP: {
1698
0
    args++;
1699
0
    if (!event_import_field(event, code, arg, &args, error_r))
1700
0
      return FALSE;
1701
0
    break;
1702
0
  }
1703
0
  }
1704
0
  *_args = args;
1705
0
  return TRUE;
1706
0
}
1707
1708
bool event_import_unescaped(struct event *event, const char *const *args,
1709
          const char **error_r)
1710
0
{
1711
0
  const char *error;
1712
1713
  /* Event's create callback has already added service:<name> category.
1714
     This imported event may be coming from another service process
1715
     though, so clear it out. */
1716
0
  if (array_is_created(&event->categories))
1717
0
    array_clear(&event->categories);
1718
1719
  /* required fields: */
1720
0
  if (args[0] == NULL) {
1721
0
    *error_r = "Missing required fields";
1722
0
    return FALSE;
1723
0
  }
1724
0
  if (!event_import_tv(args[0], args[1], &event->tv_created, &error)) {
1725
0
    *error_r = t_strdup_printf("Invalid tv_created: %s", error);
1726
0
    return FALSE;
1727
0
  }
1728
0
  args += 2;
1729
1730
  /* optional fields: */
1731
0
  while (*args != NULL) {
1732
0
    if (!event_import_arg(event, &args, error_r))
1733
0
      return FALSE;
1734
0
    args++;
1735
0
  }
1736
0
  return TRUE;
1737
0
}
1738
1739
void event_register_callback(event_callback_t *callback)
1740
0
{
1741
0
  array_push_back(&event_handlers, &callback);
1742
0
}
1743
1744
void event_register_callback_prepend(event_callback_t *callback)
1745
0
{
1746
0
  array_push_front(&event_handlers, &callback);
1747
0
}
1748
1749
void event_unregister_callback(event_callback_t *callback)
1750
0
{
1751
0
  unsigned int idx;
1752
1753
0
  if (!array_lsearch_ptr_idx(&event_handlers, callback, &idx))
1754
0
    i_unreached();
1755
0
  array_delete(&event_handlers, idx, 1);
1756
0
}
1757
1758
void event_category_register_callback(event_category_callback_t *callback)
1759
2.50k
{
1760
2.50k
  array_push_back(&event_category_callbacks, &callback);
1761
2.50k
}
1762
1763
void event_category_unregister_callback(event_category_callback_t *callback)
1764
2.50k
{
1765
2.50k
  unsigned int idx;
1766
1767
2.50k
  if (!array_lsearch_ptr_idx(&event_category_callbacks, callback, &idx))
1768
0
    i_unreached();
1769
2.50k
  array_delete(&event_category_callbacks, idx, 1);
1770
2.50k
}
1771
1772
static struct event_passthrough *
1773
event_passthrough_set_append_log_prefix(const char *prefix)
1774
0
{
1775
0
  event_set_append_log_prefix(last_passthrough_event(), prefix);
1776
0
  return &event_passthrough_vfuncs;
1777
0
}
1778
1779
static struct event_passthrough *
1780
event_passthrough_replace_log_prefix(const char *prefix)
1781
0
{
1782
0
  event_replace_log_prefix(last_passthrough_event(), prefix);
1783
0
  return &event_passthrough_vfuncs;
1784
0
}
1785
1786
static struct event_passthrough *
1787
event_passthrough_set_name(const char *name)
1788
0
{
1789
0
  event_set_name(last_passthrough_event(), name);
1790
0
  return &event_passthrough_vfuncs;
1791
0
}
1792
1793
static struct event_passthrough *
1794
event_passthrough_set_source(const char *filename,
1795
           unsigned int linenum, bool literal_fname)
1796
0
{
1797
0
  event_set_source(last_passthrough_event(), filename,
1798
0
       linenum, literal_fname);
1799
0
  return &event_passthrough_vfuncs;
1800
0
}
1801
1802
static struct event_passthrough *
1803
event_passthrough_set_always_log_source(void)
1804
0
{
1805
0
  event_set_always_log_source(last_passthrough_event());
1806
0
  return &event_passthrough_vfuncs;
1807
0
}
1808
1809
static struct event_passthrough *
1810
event_passthrough_add_categories(struct event_category *const *categories)
1811
0
{
1812
0
  event_add_categories(last_passthrough_event(), categories);
1813
0
  return &event_passthrough_vfuncs;
1814
0
}
1815
1816
static struct event_passthrough *
1817
event_passthrough_add_category(struct event_category *category)
1818
0
{
1819
0
  event_add_category(last_passthrough_event(), category);
1820
0
  return &event_passthrough_vfuncs;
1821
0
}
1822
1823
static struct event_passthrough *
1824
event_passthrough_add_fields(const struct event_add_field *fields)
1825
0
{
1826
0
  event_add_fields(last_passthrough_event(), fields);
1827
0
  return &event_passthrough_vfuncs;
1828
0
}
1829
1830
static struct event_passthrough *
1831
event_passthrough_add_str(const char *key, const char *value)
1832
0
{
1833
0
  event_add_str(last_passthrough_event(), key, value);
1834
0
  return &event_passthrough_vfuncs;
1835
0
}
1836
1837
static struct event_passthrough *
1838
event_passthrough_strlist_append(const char *key, const char *value)
1839
0
{
1840
0
  event_strlist_append(last_passthrough_event(), key, value);
1841
0
  return &event_passthrough_vfuncs;
1842
0
}
1843
1844
static struct event_passthrough *
1845
event_passthrough_strlist_replace(const char *key, const char *const *values,
1846
          unsigned int count)
1847
0
{
1848
0
  event_strlist_replace(last_passthrough_event(), key, values, count);
1849
0
  return &event_passthrough_vfuncs;
1850
0
}
1851
1852
static struct event_passthrough *
1853
event_passthrough_add_int(const char *key, intmax_t num)
1854
0
{
1855
0
  event_add_int(last_passthrough_event(), key, num);
1856
0
  return &event_passthrough_vfuncs;
1857
0
}
1858
1859
static struct event_passthrough *
1860
event_passthrough_add_int_nonzero(const char *key, intmax_t num)
1861
0
{
1862
0
  event_add_int_nonzero(last_passthrough_event(), key, num);
1863
0
  return &event_passthrough_vfuncs;
1864
0
}
1865
1866
static struct event_passthrough *
1867
event_passthrough_add_timeval(const char *key, const struct timeval *tv)
1868
0
{
1869
0
  event_add_timeval(last_passthrough_event(), key, tv);
1870
0
  return &event_passthrough_vfuncs;
1871
0
}
1872
1873
static struct event_passthrough *
1874
event_passthrough_add_ip(const char *key, const struct ip_addr *ip)
1875
0
{
1876
0
  event_add_ip(last_passthrough_event(), key, ip);
1877
0
  return &event_passthrough_vfuncs;
1878
0
}
1879
1880
static struct event_passthrough *
1881
event_passthrough_inc_int(const char *key, intmax_t num)
1882
0
{
1883
0
  event_inc_int(last_passthrough_event(), key, num);
1884
0
  return &event_passthrough_vfuncs;
1885
0
}
1886
1887
static struct event_passthrough *
1888
event_passthrough_clear_field(const char *key)
1889
0
{
1890
0
  event_field_clear(last_passthrough_event(), key);
1891
0
  return &event_passthrough_vfuncs;
1892
0
}
1893
1894
static struct event *event_passthrough_event(void)
1895
0
{
1896
0
  struct event *event = last_passthrough_event();
1897
0
  event_last_passthrough = NULL;
1898
0
  return event;
1899
0
}
1900
1901
struct event_passthrough event_passthrough_vfuncs = {
1902
  .append_log_prefix = event_passthrough_set_append_log_prefix,
1903
  .replace_log_prefix = event_passthrough_replace_log_prefix,
1904
  .set_name = event_passthrough_set_name,
1905
  .set_source = event_passthrough_set_source,
1906
  .set_always_log_source = event_passthrough_set_always_log_source,
1907
  .add_categories = event_passthrough_add_categories,
1908
  .add_category = event_passthrough_add_category,
1909
  .add_fields = event_passthrough_add_fields,
1910
  .add_str = event_passthrough_add_str,
1911
  .add_int = event_passthrough_add_int,
1912
  .add_int_nonzero = event_passthrough_add_int_nonzero,
1913
  .add_timeval = event_passthrough_add_timeval,
1914
  .add_ip = event_passthrough_add_ip,
1915
  .inc_int = event_passthrough_inc_int,
1916
  .strlist_append = event_passthrough_strlist_append,
1917
  .strlist_replace = event_passthrough_strlist_replace,
1918
  .clear_field = event_passthrough_clear_field,
1919
  .event = event_passthrough_event,
1920
};
1921
1922
void event_enable_user_cpu_usecs(struct event *event)
1923
0
{
1924
0
  get_self_rusage(&event->ru_last);
1925
0
}
1926
1927
void lib_event_init(void)
1928
2.50k
{
1929
2.50k
  events = NULL;
1930
2.50k
  current_global_event = NULL;
1931
2.50k
  event_last_passthrough = NULL;
1932
2.50k
  event_id_counter = 0;
1933
1934
2.50k
  i_array_init(&event_handlers, 4);
1935
2.50k
  i_array_init(&event_category_callbacks, 4);
1936
2.50k
  i_array_init(&event_registered_categories_internal, 16);
1937
2.50k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1938
  /* Refer to earlier commment in event_category_register(). */
1939
2.50k
  i_array_init(&event_registered_categories, 32);
1940
2.50k
#endif
1941
2.50k
  i_array_init(&event_registered_categories_representative, 16);
1942
2.50k
  event_log_init();
1943
2.50k
}
1944
1945
void lib_event_deinit(void)
1946
2.50k
{
1947
2.50k
  struct event_internal_category *internal;
1948
2.50k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1949
2.50k
  struct event_category *category;
1950
2.50k
#endif
1951
1952
2.50k
  event_log_deinit();
1953
2.50k
  event_unset_global_debug_log_filter();
1954
2.50k
  event_unset_global_core_log_filter();
1955
2.50k
  for (struct event *event = events; event != NULL; event = event->next) {
1956
0
    i_warning("Event %p leaked (parent=%p): %s:%u",
1957
0
        event, event->parent,
1958
0
        event->source_filename, event->source_linenum);
1959
0
  }
1960
  /* categories cannot be unregistered, so just free them here */
1961
2.50k
  array_foreach_elem(&event_registered_categories_internal, internal) {
1962
0
    i_free(internal->name);
1963
0
    i_free(internal);
1964
0
  }
1965
2.50k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1966
  /* Refer to earlier commment in event_category_register(). */
1967
2.50k
  array_foreach_elem(&event_registered_categories, category)
1968
0
    category->internal = NULL;
1969
2.50k
  array_free(&event_registered_categories);
1970
2.50k
#endif
1971
2.50k
  array_free(&event_handlers);
1972
2.50k
  array_free(&event_category_callbacks);
1973
2.50k
  array_free(&event_registered_categories_internal);
1974
2.50k
  array_free(&event_registered_categories_representative);
1975
2.50k
  array_free(&global_event_stack);
1976
2.50k
}