master
c 499 lines 14.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "systemd-internals.h"
4
5 #define ND_SD_JOURNAL_WORKER_THREADS 5
6 #define ND_SD_JOURNAL_TEST_TIMEOUT_DISABLED_SECONDS (100ULL * 365ULL * 24ULL * 60ULL * 60ULL)
7 #define ND_SD_JOURNAL_TEST_MAX_REQUEST_BYTES (16ULL * 1024ULL * 1024ULL)
8
9 netdata_mutex_t stdout_mutex;
10
11 static void __attribute__((constructor)) init_mutex(void) {
12 netdata_mutex_init(&stdout_mutex);
13 }
14
15 static void __attribute__((destructor)) destroy_mutex(void) {
16 netdata_mutex_destroy(&stdout_mutex);
17 }
18
19 static bool plugin_should_exit = false;
20
21 struct systemd_journal_test_command {
22 bool enabled;
23 const char *function_name;
24 const char *backend_dir;
25 uint64_t timeout_seconds;
26 bool timeout_seconds_set;
27 };
28
29 static void systemd_journal_test_usage(FILE *stream)
30 {
31 fprintf(
32 stream,
33 "usage: systemd-journal.plugin --test systemd-journal --dir <journal-dir> [--timeout <seconds>] < payload.json\n");
34 }
35
36 static bool test_option_present(int argc, char **argv)
37 {
38 for (int i = 1; i < argc; i++) {
39 if (strcmp(argv[i], "--test") == 0 || strncmp(argv[i], "--test=", strlen("--test=")) == 0)
40 return true;
41 }
42
43 return false;
44 }
45
46 static int set_required_option_once(const char **slot, const char *value, const char *option)
47 {
48 if (*slot) {
49 fprintf(stderr, "duplicate %s\n", option);
50 systemd_journal_test_usage(stderr);
51 return 2;
52 }
53
54 if (!value || !*value) {
55 fprintf(stderr, "missing value for %s\n", option);
56 systemd_journal_test_usage(stderr);
57 return 2;
58 }
59
60 *slot = value;
61 return 0;
62 }
63
64 static int set_timeout_option_once(uint64_t *slot, bool *slot_set, const char *value)
65 {
66 if (*slot_set) {
67 fprintf(stderr, "duplicate --timeout\n");
68 systemd_journal_test_usage(stderr);
69 return 2;
70 }
71
72 if (!value || !*value) {
73 fprintf(stderr, "missing value for --timeout\n");
74 systemd_journal_test_usage(stderr);
75 return 2;
76 }
77
78 for (const char *s = value; *s; s++) {
79 if (*s < '0' || *s > '9') {
80 fprintf(stderr, "invalid value for --timeout '%s'; expected seconds\n", value);
81 systemd_journal_test_usage(stderr);
82 return 2;
83 }
84 }
85
86 errno = 0;
87 unsigned long long parsed = strtoull(value, NULL, 10);
88 if (errno == ERANGE) {
89 fprintf(stderr, "invalid value for --timeout '%s'; expected seconds\n", value);
90 systemd_journal_test_usage(stderr);
91 return 2;
92 }
93
94 #if ULLONG_MAX > UINT64_MAX
95 if (parsed > UINT64_MAX) {
96 fprintf(stderr, "invalid value for --timeout '%s'; expected seconds\n", value);
97 systemd_journal_test_usage(stderr);
98 return 2;
99 }
100 #endif
101
102 *slot = (uint64_t)parsed;
103 *slot_set = true;
104 return 0;
105 }
106
107 static int reject_request_option(void)
108 {
109 fprintf(stderr, "--request is no longer supported; pass the request payload on stdin\n");
110 systemd_journal_test_usage(stderr);
111 return 2;
112 }
113
114 static int parse_systemd_journal_test_command(int argc, char **argv, struct systemd_journal_test_command *cmd)
115 {
116 *cmd = (struct systemd_journal_test_command){0};
117 if (!test_option_present(argc, argv))
118 return 0;
119
120 cmd->enabled = true;
121
122 for (int i = 1; i < argc; i++) {
123 const char *arg = argv[i];
124
125 if (strcmp(arg, "--test") == 0) {
126 if (++i >= argc)
127 return set_required_option_once(&cmd->function_name, NULL, "--test");
128
129 int rc = set_required_option_once(&cmd->function_name, argv[i], "--test");
130 if (rc)
131 return rc;
132 }
133 else if (strncmp(arg, "--test=", strlen("--test=")) == 0) {
134 int rc = set_required_option_once(&cmd->function_name, arg + strlen("--test="), "--test");
135 if (rc)
136 return rc;
137 }
138 else if (strcmp(arg, "--dir") == 0) {
139 if (++i >= argc)
140 return set_required_option_once(&cmd->backend_dir, NULL, "--dir");
141
142 int rc = set_required_option_once(&cmd->backend_dir, argv[i], "--dir");
143 if (rc)
144 return rc;
145 }
146 else if (strncmp(arg, "--dir=", strlen("--dir=")) == 0) {
147 int rc = set_required_option_once(&cmd->backend_dir, arg + strlen("--dir="), "--dir");
148 if (rc)
149 return rc;
150 }
151 else if (strcmp(arg, "--request") == 0) {
152 return reject_request_option();
153 }
154 else if (strncmp(arg, "--request=", strlen("--request=")) == 0) {
155 return reject_request_option();
156 }
157 else if (strcmp(arg, "--timeout") == 0) {
158 if (++i >= argc)
159 return set_timeout_option_once(&cmd->timeout_seconds, &cmd->timeout_seconds_set, NULL);
160
161 int rc = set_timeout_option_once(&cmd->timeout_seconds, &cmd->timeout_seconds_set, argv[i]);
162 if (rc)
163 return rc;
164 }
165 else if (strncmp(arg, "--timeout=", strlen("--timeout=")) == 0) {
166 int rc = set_timeout_option_once(
167 &cmd->timeout_seconds, &cmd->timeout_seconds_set, arg + strlen("--timeout="));
168 if (rc)
169 return rc;
170 }
171 else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
172 systemd_journal_test_usage(stderr);
173 return 2;
174 }
175 else {
176 fprintf(stderr, "unsupported systemd journal test option '%s'\n", arg);
177 systemd_journal_test_usage(stderr);
178 return 2;
179 }
180 }
181
182 if (!cmd->function_name) {
183 fprintf(stderr, "missing required --test\n");
184 systemd_journal_test_usage(stderr);
185 return 2;
186 }
187
188 if (!cmd->backend_dir) {
189 fprintf(stderr, "missing required --dir\n");
190 systemd_journal_test_usage(stderr);
191 return 2;
192 }
193
194 if (!cmd->timeout_seconds_set)
195 cmd->timeout_seconds = ND_SD_JOURNAL_DEFAULT_TIMEOUT;
196
197 return 0;
198 }
199
200 static uint64_t systemd_journal_effective_timeout_seconds(uint64_t timeout_seconds)
201 {
202 return timeout_seconds ? timeout_seconds : ND_SD_JOURNAL_TEST_TIMEOUT_DISABLED_SECONDS;
203 }
204
205 static usec_t systemd_journal_test_stop_monotonic_usec(uint64_t timeout_seconds)
206 {
207 usec_t now_ut = now_monotonic_usec();
208 uint64_t effective_timeout_seconds = systemd_journal_effective_timeout_seconds(timeout_seconds);
209 uint64_t max_timeout_seconds = (UINT64_MAX - now_ut) / USEC_PER_SEC;
210
211 if (effective_timeout_seconds > max_timeout_seconds)
212 return UINT64_MAX;
213
214 return now_ut + effective_timeout_seconds * USEC_PER_SEC;
215 }
216
217 static DIR *open_systemd_journal_test_backend_directory(const char *path, char *fd_path, size_t fd_path_size)
218 {
219 struct stat path_st, dir_st;
220
221 // Pin the explicit --dir backend root; symlinked journal trees are handled by the shared scanner.
222 if (!path || !*path) {
223 errno = EINVAL;
224 return NULL;
225 }
226
227 if (lstat(path, &path_st) == -1)
228 return NULL;
229
230 if (S_ISLNK(path_st.st_mode)) {
231 errno = ELOOP;
232 return NULL;
233 }
234
235 if (!S_ISDIR(path_st.st_mode)) {
236 errno = ENOTDIR;
237 return NULL;
238 }
239
240 DIR *dir = opendir(path);
241 if (!dir)
242 return NULL;
243
244 int fd = dirfd(dir);
245 if (fd == -1) {
246 int saved_errno = errno;
247 closedir(dir);
248 errno = saved_errno;
249 return NULL;
250 }
251
252 if (fstat(fd, &dir_st) == -1) {
253 int saved_errno = errno;
254 closedir(dir);
255 errno = saved_errno;
256 return NULL;
257 }
258
259 if (!S_ISDIR(dir_st.st_mode)) {
260 closedir(dir);
261 errno = ENOTDIR;
262 return NULL;
263 }
264
265 if (path_st.st_dev != dir_st.st_dev || path_st.st_ino != dir_st.st_ino) {
266 closedir(dir);
267 errno = EAGAIN;
268 return NULL;
269 }
270
271 int written = snprintfz(fd_path, fd_path_size, "/proc/self/fd/%d", fd);
272 if (written < 0 || (size_t)written >= fd_path_size) {
273 closedir(dir);
274 errno = ENAMETOOLONG;
275 return NULL;
276 }
277
278 return dir;
279 }
280
281 static BUFFER *read_request_payload_from_stdin(void)
282 {
283 BUFFER *payload = buffer_create(8192, NULL);
284 size_t total = 0;
285 while (true) {
286 char buffer[8192];
287 ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
288 if (bytes_read == -1) {
289 if (errno == EINTR)
290 continue;
291
292 fprintf(stderr, "failed to read request payload from stdin: %s\n", strerror(errno));
293 buffer_free(payload);
294 return NULL;
295 }
296
297 if (bytes_read == 0)
298 break;
299
300 if ((uint64_t)total + (uint64_t)bytes_read > ND_SD_JOURNAL_TEST_MAX_REQUEST_BYTES) {
301 fprintf(
302 stderr,
303 "request payload from stdin is too large: max %llu bytes\n",
304 (unsigned long long)ND_SD_JOURNAL_TEST_MAX_REQUEST_BYTES);
305 buffer_free(payload);
306 return NULL;
307 }
308
309 buffer_memcat(payload, buffer, (size_t)bytes_read);
310 total += (size_t)bytes_read;
311 }
312
313 if (total == 0) {
314 fprintf(stderr, "request payload from stdin is empty\n");
315 buffer_free(payload);
316 return NULL;
317 }
318
319 payload->content_type = CT_APPLICATION_JSON;
320
321 return payload;
322 }
323
324 static int run_systemd_journal_test_command(const struct systemd_journal_test_command *cmd)
325 {
326 if (strcmp(cmd->function_name, ND_SD_JOURNAL_FUNCTION_NAME) != 0) {
327 fprintf(
328 stderr,
329 "unsupported systemd journal test function '%s' (expected '%s')\n",
330 cmd->function_name,
331 ND_SD_JOURNAL_FUNCTION_NAME);
332 return 2;
333 }
334
335 char backend_dir_path[FILENAME_MAX];
336 DIR *backend_dir =
337 open_systemd_journal_test_backend_directory(cmd->backend_dir, backend_dir_path, sizeof(backend_dir_path));
338 if (!backend_dir) {
339 fprintf(
340 stderr,
341 "systemd journal backend directory '%s' cannot be opened: %s\n",
342 cmd->backend_dir,
343 strerror(errno));
344 return 1;
345 }
346
347 CLEAN_BUFFER *payload = read_request_payload_from_stdin();
348 if (!payload) {
349 closedir(backend_dir);
350 return 1;
351 }
352
353 bool cancelled = false;
354 usec_t stop_monotonic_ut = systemd_journal_test_stop_monotonic_usec(cmd->timeout_seconds);
355
356 nd_journal_set_scan_progress_enabled(false);
357 nd_journal_use_single_directory(backend_dir_path);
358 nd_journal_files_registry_update();
359
360 char *function = strdupz(cmd->function_name);
361 BUFFER *result = function_systemd_journal_result(
362 "test", function, &stop_monotonic_ut, &cancelled, payload, HTTP_ACCESS_ALL, "test-cli", NULL);
363 freez(function);
364
365 int rc = 1;
366 if (result) {
367 if (buffer_strlen(result))
368 fwrite(buffer_tostring(result), buffer_strlen(result), 1, stdout);
369 fprintf(stdout, "\n");
370 fflush(stdout);
371
372 if (result->response_code >= HTTP_RESP_OK && result->response_code < 300)
373 rc = 0;
374
375 buffer_free(result);
376 }
377 else {
378 fprintf(stderr, "systemd journal test function returned no result\n");
379 }
380
381 closedir(backend_dir);
382 return rc;
383 }
384
385 static bool journal_data_directories_exist()
386 {
387 struct stat st;
388 for (unsigned i = 0; i < MAX_JOURNAL_DIRECTORIES && journal_directories[i].path; i++) {
389 if ((stat(string2str(journal_directories[i].path), &st) == 0) && S_ISDIR(st.st_mode))
390 return true;
391 }
392 return false;
393 }
394
395 int main(int argc, char **argv)
396 {
397 struct systemd_journal_test_command test_command = {0};
398 int test_parse_rc = parse_systemd_journal_test_command(argc, argv, &test_command);
399 if (test_parse_rc)
400 exit(test_parse_rc);
401
402 nd_thread_tag_set("sd-jrnl.plugin");
403 nd_log_initialize_for_external_plugins("systemd-journal.plugin");
404 netdata_threads_init_for_external_plugins(0);
405
406 netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX");
407 if (verify_netdata_host_prefix(true) == -1)
408 exit(1);
409
410 // ------------------------------------------------------------------------
411 // initialization
412
413 nd_sd_journal_annotations_init();
414 nd_journal_init_files_and_directories();
415
416 if (test_command.enabled)
417 exit(run_systemd_journal_test_command(&test_command));
418
419 if (!journal_data_directories_exist()) {
420 nd_log_collector(NDLP_INFO, "unable to locate journal data directories. Exiting...");
421 fprintf(stdout, "DISABLE\n");
422 fflush(stdout);
423 exit(0);
424 }
425
426 // ------------------------------------------------------------------------
427 // debug
428
429 if (argc == 2 && strcmp(argv[1], "debug") == 0) {
430 nd_journal_files_registry_update();
431
432 bool cancelled = false;
433 usec_t stop_monotonic_ut = now_monotonic_usec() + 600 * USEC_PER_SEC;
434 char buf[] =
435 "systemd-journal after:-8640000 before:0 direction:backward last:200 data_only:false slice:true facets: source:all";
436 function_systemd_journal("123", buf, &stop_monotonic_ut, &cancelled, NULL, HTTP_ACCESS_ALL, NULL, NULL);
437 exit(1);
438 }
439
440 // ------------------------------------------------------------------------
441 // watcher thread
442
443 nd_thread_create("SDWATCH", NETDATA_THREAD_OPTION_DONT_LOG, nd_journal_watcher_main, NULL);
444
445 // ------------------------------------------------------------------------
446 // the event loop for functions
447
448 struct functions_evloop_globals *wg =
449 functions_evloop_init(ND_SD_JOURNAL_WORKER_THREADS, "SDJ", &stdout_mutex, &plugin_should_exit, NULL);
450
451 functions_evloop_add_function(
452 wg, ND_SD_JOURNAL_FUNCTION_NAME, function_systemd_journal, ND_SD_JOURNAL_DEFAULT_TIMEOUT, NULL);
453
454 nd_systemd_journal_dyncfg_init(wg);
455
456 // ------------------------------------------------------------------------
457 // register functions to netdata
458
459 netdata_mutex_lock(&stdout_mutex);
460
461 fprintf(
462 stdout,
463 PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"logs\" " HTTP_ACCESS_FORMAT " %d\n",
464 ND_SD_JOURNAL_FUNCTION_NAME,
465 ND_SD_JOURNAL_DEFAULT_TIMEOUT,
466 ND_SD_JOURNAL_FUNCTION_DESCRIPTION,
467 (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
468 RRDFUNCTIONS_PRIORITY_DEFAULT);
469
470 fflush(stdout);
471 netdata_mutex_unlock(&stdout_mutex);
472
473 // ------------------------------------------------------------------------
474
475 usec_t send_newline_ut = 0;
476 usec_t since_last_scan_ut =
477 ND_SD_JOURNAL_ALL_FILES_SCAN_EVERY_USEC * 2; // something big to trigger scanning at start
478 const bool tty = isatty(fileno(stdout)) == 1;
479
480 heartbeat_t hb;
481 heartbeat_init(&hb, USEC_PER_SEC);
482 while (!__atomic_load_n(&plugin_should_exit, __ATOMIC_ACQUIRE)) {
483 if (since_last_scan_ut > ND_SD_JOURNAL_ALL_FILES_SCAN_EVERY_USEC) {
484 nd_journal_files_registry_update();
485 since_last_scan_ut = 0;
486 }
487
488 usec_t dt_ut = heartbeat_next(&hb);
489 since_last_scan_ut += dt_ut;
490 send_newline_ut += dt_ut;
491
492 if (!tty && send_newline_ut > USEC_PER_SEC) {
493 send_newline_and_flush(&stdout_mutex);
494 send_newline_ut = 0;
495 }
496 }
497
498 exit(0);
499 }