]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal-remote/journal-upload.c
46a58e228b43f7ce975238dd27990890a746b689
[thirdparty/systemd.git] / src / journal-remote / journal-upload.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <getopt.h>
5 #include <stdio.h>
6 #include <unistd.h>
7
8 #include "sd-daemon.h"
9 #include "sd-event.h"
10
11 #include "alloc-util.h"
12 #include "build.h"
13 #include "conf-parser.h"
14 #include "daemon-util.h"
15 #include "env-file.h"
16 #include "extract-word.h"
17 #include "fd-util.h"
18 #include "fileio.h"
19 #include "format-util.h"
20 #include "fs-util.h"
21 #include "glob-util.h"
22 #include "hashmap.h"
23 #include "journal-header-util.h"
24 #include "journal-upload.h"
25 #include "journal-util.h"
26 #include "log.h"
27 #include "logs-show.h"
28 #include "main-func.h"
29 #include "mkdir.h"
30 #include "parse-argument.h"
31 #include "parse-helpers.h"
32 #include "pretty-print.h"
33 #include "process-util.h"
34 #include "string-util.h"
35 #include "strv.h"
36 #include "time-util.h"
37 #include "tmpfile-util.h"
38 #include "version.h"
39
40 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
41 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
42 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
43 #define DEFAULT_PORT 19532
44
45 static char *arg_url = NULL;
46 static char *arg_key = NULL;
47 static char *arg_cert = NULL;
48 static char *arg_trust = NULL;
49 static char *arg_directory = NULL;
50 static char **arg_file = NULL;
51 static char *arg_cursor = NULL;
52 static bool arg_after_cursor = false;
53 static int arg_journal_type = 0;
54 static int arg_namespace_flags = 0;
55 static char *arg_machine = NULL;
56 static char *arg_namespace = NULL;
57 static bool arg_merge = false;
58 static int arg_follow = -1;
59 static char *arg_save_state = NULL;
60 static usec_t arg_network_timeout_usec = USEC_INFINITY;
61 static OrderedHashmap *arg_compression = NULL;
62 static OrderedHashmap *arg_headers = NULL;
63 static bool arg_force_compression = false;
64
65 STATIC_DESTRUCTOR_REGISTER(arg_url, freep);
66 STATIC_DESTRUCTOR_REGISTER(arg_key, freep);
67 STATIC_DESTRUCTOR_REGISTER(arg_cert, freep);
68 STATIC_DESTRUCTOR_REGISTER(arg_trust, freep);
69 STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
70 STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
71 STATIC_DESTRUCTOR_REGISTER(arg_cursor, freep);
72 STATIC_DESTRUCTOR_REGISTER(arg_machine, freep);
73 STATIC_DESTRUCTOR_REGISTER(arg_namespace, freep);
74 STATIC_DESTRUCTOR_REGISTER(arg_save_state, freep);
75 STATIC_DESTRUCTOR_REGISTER(arg_compression, ordered_hashmap_freep);
76 STATIC_DESTRUCTOR_REGISTER(arg_headers, ordered_hashmap_freep);
77
78 static void close_fd_input(Uploader *u);
79
80 #define SERVER_ANSWER_KEEP 2048
81
82 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
83
84 #define easy_setopt(curl, opt, value, level, cmd) \
85 do { \
86 code = curl_easy_setopt(curl, opt, value); \
87 if (code) { \
88 log_full(level, \
89 "curl_easy_setopt " #opt " failed: %s", \
90 curl_easy_strerror(code)); \
91 cmd; \
92 } \
93 } while (0)
94
95 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL);
96 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL);
97
98 static size_t output_callback(char *buf,
99 size_t size,
100 size_t nmemb,
101 void *userp) {
102 Uploader *u = ASSERT_PTR(userp);
103
104 log_debug("The server answers (%zu bytes): %.*s",
105 size*nmemb, (int)(size*nmemb), buf);
106
107 if (nmemb && !u->answer) {
108 u->answer = strndup(buf, size*nmemb);
109 if (!u->answer)
110 log_warning("Failed to store server answer (%zu bytes): out of memory", size*nmemb);
111 }
112
113 return size * nmemb;
114 }
115
116 static int check_cursor_updating(Uploader *u) {
117 _cleanup_free_ char *temp_path = NULL;
118 _cleanup_fclose_ FILE *f = NULL;
119 int r;
120
121 if (!u->state_file)
122 return 0;
123
124 r = mkdir_parents(u->state_file, 0755);
125 if (r < 0)
126 return log_error_errno(r, "Cannot create parent directory of state file %s: %m",
127 u->state_file);
128
129 r = fopen_temporary(u->state_file, &f, &temp_path);
130 if (r < 0)
131 return log_error_errno(r, "Cannot save state to %s: %m",
132 u->state_file);
133 (void) unlink(temp_path);
134
135 return 0;
136 }
137
138 static int update_cursor_state(Uploader *u) {
139 _cleanup_(unlink_and_freep) char *temp_path = NULL;
140 _cleanup_fclose_ FILE *f = NULL;
141 int r;
142
143 if (!u->state_file || !u->last_cursor)
144 return 0;
145
146 r = fopen_temporary(u->state_file, &f, &temp_path);
147 if (r < 0)
148 goto fail;
149
150 fprintf(f,
151 "# This is private data. Do not parse.\n"
152 "LAST_CURSOR=%s\n",
153 u->last_cursor);
154
155 r = fflush_and_check(f);
156 if (r < 0)
157 goto fail;
158
159 if (rename(temp_path, u->state_file) < 0) {
160 r = -errno;
161 goto fail;
162 }
163
164 temp_path = mfree(temp_path);
165 return 0;
166
167 fail:
168 (void) unlink(u->state_file);
169
170 return log_error_errno(r, "Failed to save state %s: %m", u->state_file);
171 }
172
173 static int load_cursor_state(Uploader *u) {
174 int r;
175
176 if (!u->state_file)
177 return 0;
178
179 r = parse_env_file(NULL, u->state_file, "LAST_CURSOR", &u->last_cursor);
180 if (r == -ENOENT)
181 log_debug("State file %s is not present.", u->state_file);
182 else if (r < 0)
183 return log_error_errno(r, "Failed to read state file %s: %m",
184 u->state_file);
185 else
186 log_debug("Last cursor was %s", u->last_cursor);
187
188 return 0;
189 }
190
191 int start_upload(Uploader *u,
192 size_t (*input_callback)(void *ptr,
193 size_t size,
194 size_t nmemb,
195 void *userdata),
196 void *data) {
197 CURLcode code;
198
199 assert(u);
200 assert(input_callback);
201
202 if (!u->header) {
203 _cleanup_(curl_slist_free_allp) struct curl_slist *h = NULL;
204 struct curl_slist *l;
205
206 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
207 if (!h)
208 return log_oom();
209
210 l = curl_slist_append(h, "Transfer-Encoding: chunked");
211 if (!l)
212 return log_oom();
213 h = l;
214
215 l = curl_slist_append(h, "Accept: text/plain");
216 if (!l)
217 return log_oom();
218 h = l;
219
220 if (u->compression) {
221 _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_lowercase_to_string(u->compression->algorithm));
222 if (!header)
223 return log_oom();
224
225 l = curl_slist_append(h, header);
226 if (!l)
227 return log_oom();
228 h = l;
229 }
230
231 char **values;
232 const char *name;
233 ORDERED_HASHMAP_FOREACH_KEY(values, name, arg_headers) {
234 _cleanup_free_ char *joined = strv_join(values, ", ");
235 if (!joined)
236 return log_oom();
237
238 if (!header_value_is_valid(joined)) {
239 log_warning("Concatenated header value for %s is invalid, ignoring", name);
240 continue;
241 }
242
243 _cleanup_free_ char *header = strjoin(name, ": ", joined);
244 if (!header)
245 return log_oom();
246
247 l = curl_slist_append(h, header);
248 if (!l)
249 return log_oom();
250 h = l;
251 }
252
253 u->header = TAKE_PTR(h);
254 }
255
256 if (!u->easy) {
257 _cleanup_(curl_easy_cleanupp) CURL *curl = NULL;
258
259 curl = curl_easy_init();
260 if (!curl)
261 return log_error_errno(SYNTHETIC_ERRNO(ENOSR),
262 "Call to curl_easy_init failed.");
263
264 /* If configured, set a timeout for the curl operation. */
265 if (arg_network_timeout_usec != USEC_INFINITY)
266 easy_setopt(curl, CURLOPT_TIMEOUT,
267 (long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC),
268 LOG_ERR, return -EXFULL);
269
270 /* tell it to POST to the URL */
271 easy_setopt(curl, CURLOPT_POST, 1L,
272 LOG_ERR, return -EXFULL);
273
274 easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error,
275 LOG_ERR, return -EXFULL);
276
277 /* set where to write to */
278 easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
279 LOG_ERR, return -EXFULL);
280
281 easy_setopt(curl, CURLOPT_WRITEDATA, data,
282 LOG_ERR, return -EXFULL);
283
284 /* set where to read from */
285 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
286 LOG_ERR, return -EXFULL);
287
288 easy_setopt(curl, CURLOPT_READDATA, data,
289 LOG_ERR, return -EXFULL);
290
291 /* use our special own mime type and chunked transfer */
292 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
293 LOG_ERR, return -EXFULL);
294
295 if (DEBUG_LOGGING)
296 /* enable verbose for easier tracing */
297 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
298
299 easy_setopt(curl, CURLOPT_USERAGENT,
300 "systemd-journal-upload " GIT_VERSION,
301 LOG_WARNING, );
302
303 if (!streq_ptr(arg_key, "-") && (arg_key || startswith(u->url, "https://"))) {
304 easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE,
305 LOG_ERR, return -EXFULL);
306 easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE,
307 LOG_ERR, return -EXFULL);
308 }
309
310 if (STRPTR_IN_SET(arg_trust, "-", "all"))
311 easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0,
312 LOG_ERR, return -EUCLEAN);
313 else if (arg_trust || startswith(u->url, "https://"))
314 easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE,
315 LOG_ERR, return -EXFULL);
316
317 if (arg_key || arg_trust)
318 easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
319 LOG_WARNING, );
320
321 u->easy = TAKE_PTR(curl);
322 } else {
323 /* truncate the potential old error message */
324 u->error[0] = '\0';
325
326 u->answer = mfree(u->answer);
327 }
328
329 /* upload to this place */
330 code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
331 if (code)
332 return log_error_errno(SYNTHETIC_ERRNO(EXFULL),
333 "curl_easy_setopt CURLOPT_URL failed: %s",
334 curl_easy_strerror(code));
335
336 u->uploading = true;
337
338 return 0;
339 }
340
341 static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
342 _cleanup_free_ char *compression_buffer = NULL;
343 Uploader *u = ASSERT_PTR(userp);
344 ssize_t n;
345 int r;
346
347 assert(nmemb < SSIZE_MAX / size);
348
349 if (u->input < 0)
350 return 0;
351
352 assert(!size_multiply_overflow(size, nmemb));
353
354 if (u->compression) {
355 compression_buffer = malloc_multiply(nmemb, size);
356 if (!compression_buffer) {
357 log_oom();
358 return CURL_READFUNC_ABORT;
359 }
360 }
361
362 n = read(u->input, compression_buffer ?: buf, size * nmemb);
363 if (n > 0) {
364 log_debug("%s: allowed %zu, read %zd", __func__, size * nmemb, n);
365 if (!u->compression)
366 return n;
367
368 size_t compressed_size;
369 r = compress_blob(u->compression->algorithm, compression_buffer, n, buf, size * nmemb, &compressed_size, u->compression->level);
370 if (r < 0) {
371 log_error_errno(r, "Failed to compress %zd bytes by %s with level %i: %m",
372 n, compression_lowercase_to_string(u->compression->algorithm), u->compression->level);
373 return CURL_READFUNC_ABORT;
374 }
375 assert(compressed_size <= size * nmemb);
376 return compressed_size;
377 } else if (n < 0) {
378 log_error_errno(errno, "Aborting transfer after read error on input: %m.");
379 return CURL_READFUNC_ABORT;
380 }
381
382 u->uploading = false;
383 log_debug("Reached EOF");
384 close_fd_input(u);
385 return 0;
386 }
387
388 static void close_fd_input(Uploader *u) {
389 assert(u);
390
391 u->input = safe_close(u->input);
392 u->timeout = 0;
393 }
394
395 static int dispatch_fd_input(sd_event_source *event,
396 int fd,
397 uint32_t revents,
398 void *userp) {
399 Uploader *u = ASSERT_PTR(userp);
400
401 assert(fd >= 0);
402
403 if (revents & EPOLLHUP) {
404 log_debug("Received HUP");
405 close_fd_input(u);
406 return 0;
407 }
408
409 if (!(revents & EPOLLIN)) {
410 log_warning("Unexpected poll event %"PRIu32".", revents);
411 return -EINVAL;
412 }
413
414 if (u->uploading) {
415 log_warning("dispatch_fd_input called when uploading, ignoring.");
416 return 0;
417 }
418
419 return start_upload(u, fd_input_callback, u);
420 }
421
422 static int open_file_for_upload(Uploader *u, const char *filename) {
423 int fd, r = 0;
424
425 if (streq(filename, "-"))
426 fd = STDIN_FILENO;
427 else {
428 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
429 if (fd < 0)
430 return log_error_errno(errno, "Failed to open %s: %m", filename);
431 }
432
433 u->input = fd;
434
435 if (arg_follow != 0) {
436 r = sd_event_add_io(u->event, &u->input_event,
437 fd, EPOLLIN, dispatch_fd_input, u);
438 if (r < 0) {
439 if (r != -EPERM || arg_follow > 0)
440 return log_error_errno(r, "Failed to register input event: %m");
441
442 /* Normal files should just be consumed without polling. */
443 r = start_upload(u, fd_input_callback, u);
444 }
445 }
446
447 return r;
448 }
449
450 static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
451 int r;
452 const char *host, *proto = "";
453
454 assert(u);
455 assert(url);
456
457 *u = (Uploader) {
458 .input = -1,
459 };
460
461 if (arg_force_compression)
462 u->compression = ordered_hashmap_first(arg_compression);
463
464 host = STARTSWITH_SET(url, "http://", "https://");
465 if (!host) {
466 host = url;
467 proto = "https://";
468 }
469
470 if (strchr(host, ':'))
471 u->url = strjoin(proto, url, "/upload");
472 else {
473 char *t;
474 size_t x;
475
476 t = strdupa_safe(url);
477 x = strlen(t);
478 while (x > 0 && t[x - 1] == '/')
479 t[x - 1] = '\0';
480
481 u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload");
482 }
483 if (!u->url)
484 return log_oom();
485
486 u->state_file = state_file;
487
488 r = sd_event_default(&u->event);
489 if (r < 0)
490 return log_error_errno(r, "sd_event_default failed: %m");
491
492 r = sd_event_set_signal_exit(u->event, true);
493 if (r < 0)
494 return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
495
496 (void) sd_watchdog_enabled(false, &u->watchdog_usec);
497
498 return load_cursor_state(u);
499 }
500
501 static void destroy_uploader(Uploader *u) {
502 assert(u);
503
504 curl_easy_cleanup(u->easy);
505 curl_slist_free_all(u->header);
506 free(u->answer);
507
508 free(u->last_cursor);
509 free(u->current_cursor);
510
511 free(u->url);
512
513 u->input_event = sd_event_source_unref(u->input_event);
514
515 close_fd_input(u);
516 close_journal_input(u);
517
518 sd_event_unref(u->event);
519 }
520
521 #if LIBCURL_VERSION_NUM >= 0x075300
522 static int update_content_encoding_header(Uploader *u, const CompressionConfig *cc) {
523 bool update_header = false;
524
525 assert(u);
526
527 if (cc == u->compression)
528 return 0; /* Already picked the algorithm. Let's shortcut. */
529
530 if (cc) {
531 _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_lowercase_to_string(cc->algorithm));
532 if (!header)
533 return log_oom();
534
535 /* First, try to update existing Content-Encoding header. */
536 bool found = false;
537 for (struct curl_slist *l = u->header; l; l = l->next)
538 if (startswith(l->data, "Content-Encoding:")) {
539 free_and_replace(l->data, header);
540 found = true;
541 break;
542 }
543
544 /* If Content-Encoding header is not found, append new one. */
545 if (!found) {
546 struct curl_slist *l = curl_slist_append(u->header, header);
547 if (!l)
548 return log_oom();
549 u->header = l;
550 }
551
552 update_header = true;
553 } else
554 /* Remove Content-Encoding header. */
555 for (struct curl_slist *l = u->header, *prev = NULL; l; prev = l, l = l->next)
556 if (startswith(l->data, "Content-Encoding:")) {
557 if (prev)
558 prev->next = TAKE_PTR(l->next);
559 else
560 u->header = TAKE_PTR(l->next);
561
562 curl_slist_free_all(l);
563 update_header = true;
564 break;
565 }
566
567 if (update_header) {
568 CURLcode code;
569 easy_setopt(u->easy, CURLOPT_HTTPHEADER, u->header, LOG_WARNING, return -EXFULL);
570 }
571
572 u->compression = cc;
573
574 if (cc)
575 log_debug("Using compression algorithm %s with compression level %i.", compression_lowercase_to_string(cc->algorithm), cc->level);
576 else
577 log_debug("Disabled compression algorithm.");
578 return 0;
579 }
580 #endif
581
582 static int parse_accept_encoding_header(Uploader *u) {
583 #if LIBCURL_VERSION_NUM >= 0x075300
584 int r;
585
586 assert(u);
587
588 if (ordered_hashmap_isempty(arg_compression))
589 return update_content_encoding_header(u, NULL);
590
591 struct curl_header *header;
592 CURLHcode hcode = curl_easy_header(u->easy, "Accept-Encoding", 0, CURLH_HEADER, -1, &header);
593 if (hcode != CURLHE_OK)
594 goto not_found;
595
596 for (const char *p = header->value;;) {
597 _cleanup_free_ char *word = NULL;
598
599 r = extract_first_word(&p, &word, ",", 0);
600 if (r < 0)
601 return log_warning_errno(r, "Failed to parse Accept-Encoding header value, ignoring: %m");
602 if (r == 0)
603 break;
604
605 /* Cut the quality value waiting. */
606 char *q = strchr(word, ';');
607 if (q)
608 *q = '\0';
609
610 if (streq(word, "*"))
611 return update_content_encoding_header(u, ordered_hashmap_first(arg_compression));
612
613 Compression c = compression_lowercase_from_string(word);
614 if (c <= 0 || !compression_supported(c))
615 continue; /* unsupported or invalid algorithm. */
616
617 const CompressionConfig *cc = ordered_hashmap_get(arg_compression, INT_TO_PTR(c));
618 if (!cc)
619 continue; /* The specified algorithm is not enabled. */
620
621 return update_content_encoding_header(u, cc);
622 }
623
624 not_found:
625 if (arg_force_compression)
626 return update_content_encoding_header(u, ordered_hashmap_first(arg_compression));
627
628 return update_content_encoding_header(u, NULL);
629 #else
630 return 0;
631 #endif
632 }
633
634 static int perform_upload(Uploader *u) {
635 CURLcode code;
636 long status;
637
638 assert(u);
639
640 u->watchdog_timestamp = now(CLOCK_MONOTONIC);
641 code = curl_easy_perform(u->easy);
642 if (code) {
643 if (u->error[0])
644 log_error("Upload to %s failed: %.*s",
645 u->url, (int) sizeof(u->error), u->error);
646 else
647 log_error("Upload to %s failed: %s",
648 u->url, curl_easy_strerror(code));
649 return -EIO;
650 }
651
652 code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
653 if (code)
654 return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN),
655 "Failed to retrieve response code: %s",
656 curl_easy_strerror(code));
657
658 if (status >= 300)
659 return log_error_errno(SYNTHETIC_ERRNO(EIO),
660 "Upload to %s failed with code %ld: %s",
661 u->url, status, strna(u->answer));
662 if (status < 200)
663 return log_error_errno(SYNTHETIC_ERRNO(EIO),
664 "Upload to %s finished with unexpected code %ld: %s",
665 u->url, status, strna(u->answer));
666
667 (void) parse_accept_encoding_header(u);
668
669 log_debug("Upload finished successfully with code %ld: %s",
670 status, strna(u->answer));
671
672 free_and_replace(u->last_cursor, u->current_cursor);
673
674 return update_cursor_state(u);
675 }
676
677 static int parse_config(void) {
678 const ConfigTableItem items[] = {
679 { "Upload", "URL", config_parse_string, CONFIG_PARSE_STRING_SAFE, &arg_url },
680 { "Upload", "ServerKeyFile", config_parse_path_or_ignore, 0, &arg_key },
681 { "Upload", "ServerCertificateFile", config_parse_path_or_ignore, 0, &arg_cert },
682 { "Upload", "TrustedCertificateFile", config_parse_path_or_ignore, 0, &arg_trust },
683 { "Upload", "NetworkTimeoutSec", config_parse_sec, 0, &arg_network_timeout_usec },
684 { "Upload", "Header", config_parse_header, 0, &arg_headers },
685 { "Upload", "Compression", config_parse_compression, /* with_level */ true, &arg_compression },
686 { "Upload", "ForceCompression", config_parse_bool, 0, &arg_force_compression },
687 {}
688 };
689
690 return config_parse_standard_file_with_dropins(
691 "systemd/journal-upload.conf",
692 "Upload\0",
693 config_item_table_lookup, items,
694 CONFIG_PARSE_WARN,
695 /* userdata= */ NULL);
696 }
697
698 static int help(void) {
699 _cleanup_free_ char *link = NULL;
700 int r;
701
702 r = terminal_urlify_man("systemd-journal-upload.service", "8", &link);
703 if (r < 0)
704 return log_oom();
705
706 printf("%s -u URL {FILE|-}...\n\n"
707 "Upload journal events to a remote server.\n\n"
708 " -h --help Show this help\n"
709 " --version Show package version\n"
710 " -u --url=URL Upload to this address (default port "
711 STRINGIFY(DEFAULT_PORT) ")\n"
712 " --key=FILENAME Specify key in PEM format (default:\n"
713 " \"" PRIV_KEY_FILE "\")\n"
714 " --cert=FILENAME Specify certificate in PEM format (default:\n"
715 " \"" CERT_FILE "\")\n"
716 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
717 " \"" TRUST_FILE "\")\n"
718 " --system Use the system journal\n"
719 " --user Use the user journal for the current user\n"
720 " -m --merge Use all available journals\n"
721 " -M --machine=CONTAINER Operate on local container\n"
722 " --namespace=NAMESPACE Use journal files from namespace\n"
723 " -D --directory=PATH Use journal files from directory\n"
724 " --file=PATH Use this journal file\n"
725 " --cursor=CURSOR Start at the specified cursor\n"
726 " --after-cursor=CURSOR Start after the specified cursor\n"
727 " --follow[=BOOL] Do [not] wait for input\n"
728 " --save-state[=FILE] Save uploaded cursors (default \n"
729 " " STATE_FILE ")\n"
730 "\nSee the %s for details.\n",
731 program_invocation_short_name,
732 link);
733
734 return 0;
735 }
736
737 static int parse_argv(int argc, char *argv[]) {
738 enum {
739 ARG_VERSION = 0x100,
740 ARG_KEY,
741 ARG_CERT,
742 ARG_TRUST,
743 ARG_USER,
744 ARG_SYSTEM,
745 ARG_FILE,
746 ARG_CURSOR,
747 ARG_AFTER_CURSOR,
748 ARG_FOLLOW,
749 ARG_SAVE_STATE,
750 ARG_NAMESPACE,
751 };
752
753 static const struct option options[] = {
754 { "help", no_argument, NULL, 'h' },
755 { "version", no_argument, NULL, ARG_VERSION },
756 { "url", required_argument, NULL, 'u' },
757 { "key", required_argument, NULL, ARG_KEY },
758 { "cert", required_argument, NULL, ARG_CERT },
759 { "trust", required_argument, NULL, ARG_TRUST },
760 { "system", no_argument, NULL, ARG_SYSTEM },
761 { "user", no_argument, NULL, ARG_USER },
762 { "merge", no_argument, NULL, 'm' },
763 { "machine", required_argument, NULL, 'M' },
764 { "namespace", required_argument, NULL, ARG_NAMESPACE },
765 { "directory", required_argument, NULL, 'D' },
766 { "file", required_argument, NULL, ARG_FILE },
767 { "cursor", required_argument, NULL, ARG_CURSOR },
768 { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
769 { "follow", optional_argument, NULL, ARG_FOLLOW },
770 { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
771 {}
772 };
773
774 int c, r;
775
776 assert(argc >= 0);
777 assert(argv);
778
779 opterr = 0;
780
781 while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
782 switch (c) {
783 case 'h':
784 return help();
785
786 case ARG_VERSION:
787 return version();
788
789 case 'u':
790 r = free_and_strdup_warn(&arg_url, optarg);
791 if (r < 0)
792 return r;
793 break;
794
795 case ARG_KEY:
796 r = free_and_strdup_warn(&arg_key, optarg);
797 if (r < 0)
798 return r;
799 break;
800
801 case ARG_CERT:
802 r = free_and_strdup_warn(&arg_cert, optarg);
803 if (r < 0)
804 return r;
805 break;
806
807 case ARG_TRUST:
808 r = free_and_strdup_warn(&arg_trust, optarg);
809 if (r < 0)
810 return r;
811 break;
812
813 case ARG_SYSTEM:
814 arg_journal_type |= SD_JOURNAL_SYSTEM;
815 break;
816
817 case ARG_USER:
818 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
819 break;
820
821 case 'm':
822 arg_merge = true;
823 break;
824
825 case 'M':
826 r = free_and_strdup_warn(&arg_machine, optarg);
827 if (r < 0)
828 return r;
829 break;
830
831 case ARG_NAMESPACE:
832 if (streq(optarg, "*")) {
833 arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES;
834 arg_namespace = mfree(arg_namespace);
835 r = 0;
836 } else if (startswith(optarg, "+")) {
837 arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE;
838 r = free_and_strdup_warn(&arg_namespace, optarg + 1);
839 } else if (isempty(optarg)) {
840 arg_namespace_flags = 0;
841 arg_namespace = mfree(arg_namespace);
842 r = 0;
843 } else {
844 arg_namespace_flags = 0;
845 r = free_and_strdup_warn(&arg_namespace, optarg);
846 }
847 if (r < 0)
848 return r;
849 break;
850
851 case 'D':
852 r = free_and_strdup_warn(&arg_directory, optarg);
853 if (r < 0)
854 return r;
855 break;
856
857 case ARG_FILE:
858 r = glob_extend(&arg_file, optarg, GLOB_NOCHECK);
859 if (r < 0)
860 return log_error_errno(r, "Failed to add paths: %m");
861 break;
862
863 case ARG_CURSOR:
864 case ARG_AFTER_CURSOR:
865 r = free_and_strdup_warn(&arg_cursor, optarg);
866 if (r < 0)
867 return r;
868 arg_after_cursor = c == ARG_AFTER_CURSOR;
869 break;
870
871 case ARG_FOLLOW:
872 r = parse_boolean_argument("--follow", optarg, NULL);
873 if (r < 0)
874 return r;
875 arg_follow = r;
876 break;
877
878 case ARG_SAVE_STATE:
879 r = free_and_strdup_warn(&arg_save_state, optarg ?: STATE_FILE);
880 if (r < 0)
881 return r;
882 break;
883
884 case '?':
885 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
886 "Unknown option %s.",
887 argv[optind - 1]);
888
889 case ':':
890 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
891 "Missing argument to %s.",
892 argv[optind - 1]);
893
894 default:
895 assert_not_reached();
896 }
897
898 if (!arg_url)
899 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
900 "Required --url=/-u option missing.");
901
902 if (!!arg_key != !!arg_cert)
903 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
904 "Options --key= and --cert= must be used together.");
905
906 if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type))
907 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
908 "Input arguments make no sense with journal input.");
909
910 return 1;
911 }
912
913 static int open_journal(sd_journal **j) {
914 int r;
915
916 assert(j);
917
918 if (arg_directory)
919 r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
920 else if (arg_file)
921 r = sd_journal_open_files(j, (const char**) arg_file, 0);
922 else if (arg_machine)
923 r = journal_open_machine(j, arg_machine, 0);
924 else
925 r = sd_journal_open_namespace(j, arg_namespace,
926 (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) | arg_namespace_flags | arg_journal_type);
927 if (r < 0)
928 log_error_errno(r, "Failed to open %s: %m",
929 arg_directory ?: (arg_file ? "files" : "journal"));
930 return r;
931 }
932
933 static int run(int argc, char **argv) {
934 _cleanup_(destroy_uploader) Uploader u = {};
935 _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = NULL;
936 bool use_journal;
937 int r;
938
939 log_setup();
940
941 r = parse_config();
942 if (r < 0)
943 return r;
944
945 r = parse_argv(argc, argv);
946 if (r <= 0)
947 return r;
948
949 r = compression_configs_mangle(&arg_compression);
950 if (r < 0)
951 return r;
952
953 journal_browse_prepare();
954
955 r = setup_uploader(&u, arg_url, arg_save_state);
956 if (r < 0)
957 return r;
958
959 sd_event_set_watchdog(u.event, true);
960
961 r = check_cursor_updating(&u);
962 if (r < 0)
963 return r;
964
965 log_debug("%s running as pid "PID_FMT,
966 program_invocation_short_name, getpid_cached());
967
968 use_journal = optind >= argc;
969 if (use_journal) {
970 sd_journal *j;
971 r = open_journal(&j);
972 if (r < 0)
973 return r;
974 r = open_journal_for_upload(&u, j,
975 arg_cursor ?: u.last_cursor,
976 arg_cursor ? arg_after_cursor : true,
977 arg_follow != 0);
978 if (r < 0)
979 return r;
980 }
981
982 notify_message = notify_start("READY=1\n"
983 "STATUS=Processing input...",
984 NOTIFY_STOPPING_MESSAGE);
985
986 for (;;) {
987 r = sd_event_get_state(u.event);
988 if (r < 0)
989 return r;
990 if (r == SD_EVENT_FINISHED)
991 return 0;
992
993 if (use_journal) {
994 if (!u.journal)
995 return 0;
996
997 r = check_journal_input(&u);
998 } else if (u.input < 0 && !use_journal) {
999 if (optind >= argc)
1000 return 0;
1001
1002 log_debug("Using %s as input.", argv[optind]);
1003 r = open_file_for_upload(&u, argv[optind++]);
1004 }
1005 if (r < 0)
1006 return r;
1007
1008 if (u.uploading) {
1009 r = perform_upload(&u);
1010 if (r < 0)
1011 return r;
1012 }
1013
1014 r = sd_event_run(u.event, u.timeout);
1015 if (r < 0)
1016 return log_error_errno(r, "Failed to run event loop: %m");
1017 }
1018 }
1019
1020 DEFINE_MAIN_FUNCTION(run);