fix incomplete implementation of journal watcher (#19592)
* fix incomplete implementation for journal watcher * debug messages logged as info to verify operation * detect symlinks to directories * added error handling to all inotify calls * fix for amazon linux not supporting IN_MASK_CREATE * fix cleanup when removing directories and symlinks to directories
Costa Tsaousis committed
Feb 7, 2025 at 18:13 UTC
3a2dd87697d6fa724dc2be2d1baf43fff191096d
2 files changed
+269
-73
src/collectors/systemd-journal.plugin/systemd-journal-watcher.c
+265
-71
@@ -3,11 +3,53 @@
3
#include "systemd-internals.h"
4
#include <sys/inotify.h>
5
6
-#define EVENT_SIZE (sizeof(struct inotify_event))
6
#define INITIAL_WATCHES 256
7
8
#define WATCH_FOR (IN_CREATE | IN_MODIFY | IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM | IN_MOVED_TO | IN_UNMOUNT)
9
10
+typedef uint32_t INOTIFY_MASK;
11
+
12
+ENUM_STR_MAP_DEFINE(INOTIFY_MASK) = {
13
+ // helpers (combine multiple flags)
14
+ // must be first in the list
15
+ {.id = IN_ALL_EVENTS, .name = "IN_ALL_EVENTS"},
16
+ {.id = IN_CLOSE, .name = "IN_CLOSE"},
17
+ {.id = IN_MOVE, .name = "IN_MOVE"},
18
+
19
+ // individual flags
20
+ {.id = IN_ACCESS, .name = "IN_ACCESS"},
21
+ {.id = IN_MODIFY, .name = "IN_MODIFY"},
22
+ {.id = IN_ATTRIB, .name = "IN_ATTRIB"},
23
+ {.id = IN_CLOSE_WRITE, .name = "IN_CLOSE_WRITE"},
24
+ {.id = IN_CLOSE_NOWRITE, .name = "IN_CLOSE_NOWRITE"},
25
+ {.id = IN_OPEN, .name = "IN_OPEN"},
26
+ {.id = IN_MOVED_FROM, .name = "IN_MOVED_FROM"},
27
+ {.id = IN_MOVED_TO, .name = "IN_MOVED_TO"},
28
+ {.id = IN_CREATE, .name = "IN_CREATE"},
29
+ {.id = IN_DELETE, .name = "IN_DELETE"},
30
+ {.id = IN_DELETE_SELF, .name = "IN_DELETE_SELF"},
31
+ {.id = IN_MOVE_SELF, .name = "IN_MOVE_SELF"},
32
+ {.id = IN_UNMOUNT, .name = "IN_UNMOUNT"},
33
+ {.id = IN_Q_OVERFLOW, .name = "IN_Q_OVERFLOW"},
34
+ {.id = IN_IGNORED, .name = "IN_IGNORED"},
35
+ {.id = IN_ONLYDIR, .name = "IN_ONLYDIR"},
36
+ {.id = IN_DONT_FOLLOW, .name = "IN_DONT_FOLLOW"},
37
+ {.id = IN_EXCL_UNLINK, .name = "IN_EXCL_UNLINK"},
38
+#ifdef IN_MASK_CREATE
39
+ {.id = IN_MASK_CREATE, .name = "IN_MASK_CREATE"},
40
+#endif
41
+ {.id = IN_MASK_ADD, .name = "IN_MASK_ADD"},
42
+ {.id = IN_ISDIR, .name = "IN_ISDIR"},
43
+ {.id = IN_ONESHOT, .name = "IN_ONESHOT"},
44
+
45
+ // terminator
46
+ {.id = 0, .name = NULL}
47
+};
48
+
49
+BITMAP_STR_DEFINE_FUNCTIONS(INOTIFY_MASK, 0, "UNKNOWN");
50
+
51
+DEFINE_JUDYL_TYPED(SYMLINKED_DIRS, STRING *);
52
+
53
typedef struct watch_entry {
54
int slot;
55
@@ -25,6 +67,7 @@ typedef struct {
67
68
size_t errors;
69
70
+ SYMLINKED_DIRS_JudyLSet symlinkedDirs;
71
DICTIONARY *pending;
72
} Watcher;
73
@@ -68,6 +111,7 @@ static void free_slot(Watcher *watcher, WatchEntry *t) {
111
static int add_watch(Watcher *watcher, int inotifyFd, const char *path) {
112
WatchEntry *t = get_slot(watcher);
113
114
+ errno_clear();
115
t->wd = inotify_add_watch(inotifyFd, path, WATCH_FOR);
116
if (t->wd == -1) {
117
nd_log(NDLS_COLLECTORS, NDLP_ERR,
@@ -95,6 +139,8 @@ static int add_watch(Watcher *watcher, int inotifyFd, const char *path) {
139
}
140
141
static void remove_watch(Watcher *watcher, int inotifyFd, int wd) {
142
+ errno_clear();
143
+
144
int i;
145
for (i = 0; i < watcher->watchCount; ++i) {
146
if (watcher->watchList[i].wd == wd) {
@@ -103,7 +149,9 @@ static void remove_watch(Watcher *watcher, int inotifyFd, int wd) {
149
"JOURNAL WATCHER: removing watch from directory: '%s'",
150
watcher->watchList[i].path);
151
106
- inotify_rm_watch(inotifyFd, watcher->watchList[i].wd);
152
+ if(inotify_rm_watch(inotifyFd, watcher->watchList[i].wd) == -1)
153
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "JOURNAL WATCHER: inotify_rm_watch() returned -1");
154
+
155
free_slot(watcher, &watcher->watchList[i]);
156
return;
157
}
@@ -117,7 +165,8 @@ static void remove_watch(Watcher *watcher, int inotifyFd, int wd) {
165
static void free_watches(Watcher *watcher, int inotifyFd) {
166
for (int i = 0; i < watcher->watchCount; ++i) {
167
if (watcher->watchList[i].wd != -1) {
120
- inotify_rm_watch(inotifyFd, watcher->watchList[i].wd);
168
+ if(inotify_rm_watch(inotifyFd, watcher->watchList[i].wd) == -1)
169
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "JOURNAL WATCHER: inotify_rm_watch() returned -1");
170
free_slot(watcher, &watcher->watchList[i]);
171
}
172
}
@@ -128,6 +177,17 @@ static void free_watches(Watcher *watcher, int inotifyFd) {
177
watcher->pending = NULL;
178
}
179
180
+static void free_symlinked_dirs(Watcher *watcher) {
181
+ Word_t idx = 0;
182
+ STRING *value;
183
+ while((value = SYMLINKED_DIRS_FIRST(&watcher->symlinkedDirs, &idx))) {
184
+ SYMLINKED_DIRS_DEL(&watcher->symlinkedDirs, idx);
185
+ STRING *key = (STRING *)idx;
186
+ string_freez(key);
187
+ string_freez(value);
188
+ }
189
+}
190
+
191
static char* get_path_from_wd(Watcher *watcher, int wd) {
192
for (int i = 0; i < watcher->watchCount; ++i) {
193
if (watcher->watchList[i].wd == wd)
@@ -148,14 +208,31 @@ static bool is_directory_watched(Watcher *watcher, const char *path) {
208
static void watch_directory_and_subdirectories(Watcher *watcher, int inotifyFd, const char *basePath) {
209
DICTIONARY *dirs = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
210
151
- journal_directory_scan_recursively(NULL, dirs, basePath, 0);
211
+ // First resolve any symlinks in the base path
212
+ char real_path[PATH_MAX];
213
+ if (realpath(basePath, real_path) == NULL) {
214
+ // If realpath fails, try using the original path
215
+ strncpyz(real_path, basePath, sizeof(real_path));
216
+ }
217
+
218
+ journal_directory_scan_recursively(NULL, dirs, real_path, 0);
219
220
void *x;
221
dfe_start_read(dirs, x) {
222
const char *dirname = x_dfe.name;
156
- // Check if this directory is already being watched
157
- if (!is_directory_watched(watcher, dirname)) {
158
- add_watch(watcher, inotifyFd, dirname);
223
+ char resolved_path[PATH_MAX];
224
+
225
+ // Resolve symlinks for each subdirectory
226
+ if (realpath(dirname, resolved_path) != NULL) {
227
+ // Check if this directory is already being watched
228
+ if (!is_directory_watched(watcher, resolved_path)) {
229
+ add_watch(watcher, inotifyFd, resolved_path);
230
+ }
231
+ } else {
232
+ // If realpath fails, try with original path
233
+ if (!is_directory_watched(watcher, dirname)) {
234
+ add_watch(watcher, inotifyFd, dirname);
235
+ }
236
}
237
}
238
dfe_done(x);
@@ -177,15 +254,18 @@ static bool is_subpath(const char *path, const char *subpath) {
254
void remove_directory_watch(Watcher *watcher, int inotifyFd, const char *dirPath) {
255
for (int i = 0; i < watcher->watchCount; ++i) {
256
WatchEntry *t = &watcher->watchList[i];
180
- if (t->wd != -1 && is_subpath(t->path, dirPath)) {
181
- inotify_rm_watch(inotifyFd, t->wd);
257
+ if (t->wd != -1 && is_subpath(dirPath, t->path)) {
258
+ if(inotify_rm_watch(inotifyFd, t->wd) == -1)
259
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "JOURNAL WATCHER: inotify_rm_watch() on path '%s' returned -1", t->path);
260
+ else
261
+ nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "JOURNAL WATCHER: stopped watching directory '%s'", t->path);
262
free_slot(watcher, t);
263
}
264
}
265
266
struct journal_file *jf;
267
dfe_start_write(journal_files_registry, jf) {
188
- if(is_subpath(jf->filename, dirPath))
268
+ if(is_subpath(dirPath, jf->filename))
269
dictionary_del(journal_files_registry, jf->filename);
270
}
271
dfe_done(jf);
@@ -194,22 +274,38 @@ void remove_directory_watch(Watcher *watcher, int inotifyFd, const char *dirPath
274
}
275
276
void process_event(Watcher *watcher, int inotifyFd, struct inotify_event *event) {
277
+ errno_clear();
278
+
279
if(!event->len) {
198
- nd_log(NDLS_COLLECTORS, NDLP_NOTICE
199
- , "JOURNAL WATCHER: received event with mask %u and len %u (this is zero) for path: '%s' - ignoring it."
200
- , event->mask, event->len, event->name);
280
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
281
+ INOTIFY_MASK_2buffer(wb, event->mask, ", ");
282
+ nd_log(NDLS_COLLECTORS, NDLP_NOTICE,
283
+ "JOURNAL WATCHER: received event with mask %u (%s) and len %u (this is zero) - ignoring it.",
284
+ event->mask, buffer_tostring(wb), event->len);
285
return;
286
}
287
288
char *dirPath = get_path_from_wd(watcher, event->wd);
289
if(!dirPath) {
290
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
291
+ INOTIFY_MASK_2buffer(wb, event->mask, ", ");
292
nd_log(NDLS_COLLECTORS, NDLP_NOTICE,
207
- "JOURNAL WATCHER: received event with mask %u and len %u for path: '%s' - "
208
- "but we can't find its watch descriptor - ignoring it."
209
- , event->mask, event->len, event->name);
293
+ "JOURNAL WATCHER: received event with mask %u (%s) and len %u for path: '%s' - "
294
+ "but we can't find its watch descriptor - ignoring it.",
295
+ event->mask, buffer_tostring(wb), event->len, event->name);
296
return;
297
}
298
299
+#ifdef NETDATA_INTERNAL_CHECKS
300
+ {
301
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
302
+ INOTIFY_MASK_2buffer(wb, event->mask, ", ");
303
+ nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
304
+ "JOURNAL WATCHER: received event with mask %u (%s) for path: '%s' inside '%s'",
305
+ event->mask, buffer_tostring(wb), event->name, dirPath);
306
+ }
307
+#endif
308
+
309
if(event->mask & IN_DELETE_SELF) {
310
remove_watch(watcher, inotifyFd, event->wd);
311
return;
@@ -217,46 +313,115 @@ void process_event(Watcher *watcher, int inotifyFd, struct inotify_event *event)
313
314
static __thread char fullPath[PATH_MAX];
315
snprintfz(fullPath, sizeof(fullPath), "%s/%s", dirPath, event->name);
220
- // fullPath contains the full path to the file
316
222
- size_t len = strlen(event->name);
317
+ bool is_dir = event->mask & IN_ISDIR;
318
+ char resolved_path[PATH_MAX];
319
+ const char *path_to_use = fullPath;
320
+
321
+ if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
322
+ // Give the system a moment to establish the symlink
323
+ sleep_usec(1000); // 1ms sleep
324
+
325
+ struct stat st;
326
+ if (lstat(fullPath, &st) == 0) {
327
+ if (S_ISLNK(st.st_mode)) {
328
+ // It's a symlink - resolve it
329
+ if (realpath(fullPath, resolved_path) != NULL) {
330
+ path_to_use = resolved_path;
331
+
332
+ // Check if it points to a directory
333
+ if (stat(resolved_path, &st) == 0 && S_ISDIR(st.st_mode)) {
334
+ is_dir = true;
335
+
336
+ STRING *fullPathString = string_strdupz(fullPath);
337
+ STRING *symlinked = SYMLINKED_DIRS_GET(&watcher->symlinkedDirs, (uintptr_t)fullPathString);
338
+ if (!symlinked) {
339
+ SYMLINKED_DIRS_SET(
340
+ &watcher->symlinkedDirs, (uintptr_t)fullPathString, string_strdupz(resolved_path));
341
+
342
+ // we leave fullPathString allocated, as it's now in the JudyL set
343
+
344
+ nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
345
+ "JOURNAL WATCHER: New symlinked directory created: '%s' -> '%s'",
346
+ fullPath, resolved_path);
347
+ }
348
+ else if (string_strcmp(symlinked, resolved_path) != 0) {
349
+ SYMLINKED_DIRS_SET(
350
+ &watcher->symlinkedDirs, (uintptr_t)fullPathString, string_strdupz(resolved_path));
351
+
352
+ nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
353
+ "JOURNAL WATCHER: Updated symlinked directory: '%s' -> '%s' (was '%s')",
354
+ fullPath, resolved_path, string2str(symlinked));
355
+
356
+ string_freez(symlinked);
357
+
358
+ // we need to free this, since it was already in the JudyL set
359
+ string_freez(fullPathString);
360
+ }
361
+ else
362
+ string_freez(fullPathString); // we don't need it anymore
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+ else if(event->mask & IN_DELETE) {
369
+ // Check if it was a symlink
370
+ STRING *fullPathString = string_strdupz(fullPath);
371
+ STRING *symlinked = SYMLINKED_DIRS_GET(&watcher->symlinkedDirs, (uintptr_t)fullPathString);
372
+ if (symlinked) {
373
+ strncpyz(resolved_path, string2str(symlinked), sizeof(resolved_path) - 1);
374
+ path_to_use = resolved_path;
375
+ SYMLINKED_DIRS_DEL(&watcher->symlinkedDirs, (uintptr_t)fullPathString);
376
+ string_freez(fullPathString); // to remove also the one referenced in the JudyL set
377
+ string_freez(symlinked);
378
+ is_dir = true;
379
224
- if(event->mask & IN_ISDIR) {
380
+ nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
381
+ "JOURNAL WATCHER: Deleted symlinked directory: '%s' -> '%s'",
382
+ fullPath, resolved_path);
383
+ }
384
+ string_freez(fullPathString); // the one we allocated above
385
+ }
386
+
387
+ if(is_dir) {
388
if (event->mask & (IN_DELETE | IN_MOVED_FROM)) {
226
- // A directory is deleted or moved out
389
nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
228
- "JOURNAL WATCHER: Directory deleted or moved out: '%s'",
229
- fullPath);
390
+ "JOURNAL WATCHER: Directory deleted or moved out: '%s'",
391
+ path_to_use);
392
231
- // Remove the watch - implement this function based on how you manage your watches
232
- remove_directory_watch(watcher, inotifyFd, fullPath);
393
+ remove_directory_watch(watcher, inotifyFd, path_to_use);
394
}
395
else if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
235
- // A new directory is created or moved in
396
nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
237
- "JOURNAL WATCHER: New directory created or moved in: '%s'",
238
- fullPath);
397
+ "JOURNAL WATCHER: New directory created or moved in: '%s'",
398
+ path_to_use);
399
240
- // Start watching the new directory - recursive watch
241
- watch_directory_and_subdirectories(watcher, inotifyFd, fullPath);
400
+ watch_directory_and_subdirectories(watcher, inotifyFd, path_to_use);
401
}
243
- else
402
+ else {
403
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
404
+ INOTIFY_MASK_2buffer(wb, event->mask, ", ");
405
nd_log(NDLS_COLLECTORS, NDLP_WARNING,
245
- "JOURNAL WATCHER: Received unhandled event with mask %u for directory '%s'",
246
- event->mask, fullPath);
406
+ "JOURNAL WATCHER: Received unhandled event with mask %u (%s) for directory '%s'",
407
+ event->mask, buffer_tostring(wb), path_to_use);
408
+ }
409
}
248
- else if(is_journal_file(event->name, (ssize_t)len, NULL)) {
249
- // It is a file that ends in .journal
250
- // add it to our pending list
251
- dictionary_set(watcher->pending, fullPath, NULL, 0);
410
+ else if(is_journal_file(event->name, (ssize_t)strlen(event->name), NULL)) {
411
+ dictionary_set(watcher->pending, path_to_use, NULL, 0);
412
}
253
- else
413
+ else {
414
+ CLEAN_BUFFER *wb = buffer_create(0, NULL);
415
+ INOTIFY_MASK_2buffer(wb, event->mask, ", ");
416
nd_log(NDLS_COLLECTORS, NDLP_DEBUG,
255
- "JOURNAL WATCHER: ignoring event with mask %u for file '%s'",
256
- event->mask, fullPath);
417
+ "JOURNAL WATCHER: ignoring event with mask %u (%s) for file '%s' ('%s')",
418
+ event->mask, buffer_tostring(wb), path_to_use, fullPath);
419
+ }
420
}
421
422
static void process_pending(Watcher *watcher) {
423
+ errno_clear();
424
+
425
void *x;
426
dfe_start_write(watcher->pending, x) {
427
struct stat info;
@@ -298,17 +463,63 @@ void journal_watcher_restart(void) {
463
__atomic_add_fetch(&journal_watcher_wanted_session_id, 1, __ATOMIC_RELAXED);
464
}
465
466
+static bool process_inotify_events(struct buffered_reader *reader, Watcher *watcher, int inotifyFd) {
467
+ errno_clear();
468
+
469
+ bool unmount_event = false;
470
+ ssize_t processed = 0;
471
+
472
+ // Process as many complete events as we can
473
+ while (processed + (ssize_t)sizeof(struct inotify_event) <= reader->read_len) {
474
+ struct inotify_event *event = (struct inotify_event *)(reader->read_buffer + processed);
475
+
476
+ if(event->len > NAME_MAX + 1) {
477
+ // The event length is impossibly large
478
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
479
+ "JOURNAL WATCHER: received impossibly large event length %u - restarting",
480
+ event->len);
481
+ return true; // force a restart
482
+ }
483
+
484
+ // Check if we have the complete event including the name
485
+ ssize_t total_size = (ssize_t)sizeof(struct inotify_event) + event->len;
486
+ if (processed + total_size > reader->read_len)
487
+ break; // Wait for more data
488
+
489
+ if(event->mask & IN_UNMOUNT) {
490
+ unmount_event = true;
491
+ break;
492
+ }
493
+
494
+ process_event(watcher, inotifyFd, event);
495
+ processed += total_size;
496
+ }
497
+
498
+ // If we have unprocessed data, move it to the start
499
+ if (processed < reader->read_len) {
500
+ memmove(reader->read_buffer,
501
+ reader->read_buffer + processed,
502
+ reader->read_len - processed);
503
+ reader->read_len -= processed;
504
+ }
505
+ else
506
+ reader->read_len = 0;
507
+
508
+ reader->read_buffer[reader->read_len] = '\0';
509
+ return unmount_event;
510
+}
511
+
512
void *journal_watcher_main(void *arg __maybe_unused) {
513
while(1) {
514
size_t journal_watcher_session_id = __atomic_load_n(&journal_watcher_wanted_session_id, __ATOMIC_RELAXED);
515
516
Watcher watcher = {
306
- .watchList = mallocz(INITIAL_WATCHES * sizeof(WatchEntry)),
307
- .freeList = NULL,
308
- .watchCount = 0,
309
- .watchListSize = INITIAL_WATCHES,
310
- .pending = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE|DICT_OPTION_SINGLE_THREADED),
311
- .errors = 0,
517
+ .watchList = mallocz(INITIAL_WATCHES * sizeof(WatchEntry)),
518
+ .freeList = NULL,
519
+ .watchCount = 0,
520
+ .watchListSize = INITIAL_WATCHES,
521
+ .pending = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE|DICT_OPTION_SINGLE_THREADED),
522
+ .errors = 0,
523
};
524
525
int inotifyFd = inotify_init();
@@ -325,42 +536,24 @@ void *journal_watcher_main(void *arg __maybe_unused) {
536
537
usec_t last_headers_update_ut = now_monotonic_usec();
538
struct buffered_reader reader;
539
+ buffered_reader_init(&reader);
540
+
541
while (journal_watcher_session_id == __atomic_load_n(&journal_watcher_wanted_session_id, __ATOMIC_RELAXED)) {
542
buffered_reader_ret_t rc = buffered_reader_read_timeout(
543
&reader, inotifyFd, SYSTEMD_JOURNAL_EXECUTE_WATCHER_PENDING_EVERY_MS, false);
544
332
- if (rc != BUFFERED_READER_READ_OK && rc != BUFFERED_READER_READ_POLL_TIMEOUT) {
333
- nd_log(NDLS_COLLECTORS, NDLP_CRIT,
545
+ if(rc == BUFFERED_READER_READ_OK || rc == BUFFERED_READER_READ_BUFFER_FULL) {
546
+ if (process_inotify_events(&reader, &watcher, inotifyFd))
547
+ break;
548
+ }
549
+ else if (rc != BUFFERED_READER_READ_POLL_TIMEOUT) {
550
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
551
"JOURNAL WATCHER: cannot read inotify events, buffered_reader_read_timeout() returned %d - "
552
"restarting the watcher.",
553
rc);
554
break;
555
}
556
340
- if(rc == BUFFERED_READER_READ_OK) {
341
- bool unmount_event = false;
342
-
343
- ssize_t i = 0;
344
- while (i < reader.read_len) {
345
- struct inotify_event *event = (struct inotify_event *) &reader.read_buffer[i];
346
-
347
- if(event->mask & IN_UNMOUNT) {
348
- unmount_event = true;
349
- break;
350
- }
351
-
352
- process_event(&watcher, inotifyFd, event);
353
- i += (ssize_t)EVENT_SIZE + event->len;
354
- }
355
-
356
- reader.read_buffer[0] = '\0';
357
- reader.read_len = 0;
358
- reader.pos = 0;
359
-
360
- if(unmount_event)
361
- break;
362
- }
363
-
557
usec_t ut = now_monotonic_usec();
558
if (dictionary_entries(watcher.pending) && (rc == BUFFERED_READER_READ_POLL_TIMEOUT ||
559
last_headers_update_ut + (SYSTEMD_JOURNAL_EXECUTE_WATCHER_PENDING_EVERY_MS * USEC_PER_MS) <= ut)) {
@@ -376,6 +569,7 @@ void *journal_watcher_main(void *arg __maybe_unused) {
569
570
close(inotifyFd);
571
free_watches(&watcher, inotifyFd);
572
+ free_symlinked_dirs(&watcher);
573
574
// this will scan the directories and cleanup the registry
575
journal_files_registry_update();
src/libnetdata/dictionary/dictionary-item.h
+4
-2
@@ -216,8 +216,6 @@ static inline size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM
216
size_t item_size = 0, key_size = 0, value_size = 0;
217
218
key_size += item->key_len;
219
- if(unlikely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
220
- item_free_name(dict, item);
219
220
if(item_shared_release_and_check_if_it_can_be_freed(dict, item)) {
221
dictionary_execute_delete_callback(dict, item);
@@ -234,6 +232,10 @@ static inline size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM
232
item_size += sizeof(DICTIONARY_ITEM_SHARED);
233
}
234
235
+ // free the name after calling the delete callback
236
+ if(unlikely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
237
+ item_free_name(dict, item);
238
+
239
aral_freez(dict_items_aral, item);
240
241
item_size += sizeof(DICTIONARY_ITEM);