]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal-remote/journal-upload.c
ci: enable arm64 runner for build/unit jobs
[thirdparty/systemd.git] / src / journal-remote / journal-upload.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
3d090cc6 2
3d090cc6
ZJS
3#include <fcntl.h>
4#include <getopt.h>
3f6fd1ba 5#include <stdio.h>
3c9fbb99 6#include <unistd.h>
3d090cc6
ZJS
7
8#include "sd-daemon.h"
6c68f5d4 9#include "sd-event.h"
3f6fd1ba 10
b5efdb8a 11#include "alloc-util.h"
d6b4d1c7 12#include "build.h"
3f6fd1ba 13#include "conf-parser.h"
f36bb1e1 14#include "daemon-util.h"
686d13b9 15#include "env-file.h"
6c68f5d4 16#include "extract-word.h"
3ffd4af2 17#include "fd-util.h"
722b6795 18#include "fileio.h"
f97b34a6 19#include "format-util.h"
70f1280c 20#include "fs-util.h"
7d50b32a 21#include "glob-util.h"
6c68f5d4 22#include "hashmap.h"
5209e9cb 23#include "journal-header-util.h"
3ffd4af2 24#include "journal-upload.h"
5c6673af 25#include "journal-util.h"
3f6fd1ba 26#include "log.h"
fad3feec 27#include "logs-show.h"
f36bb1e1 28#include "main-func.h"
d71839af 29#include "mkdir.h"
9c7f2201 30#include "parse-argument.h"
c3eaba2d 31#include "parse-helpers.h"
294bf0c3 32#include "pretty-print.h"
dccca82b 33#include "process-util.h"
07630cea 34#include "string-util.h"
49fe5c09 35#include "strv.h"
6c68f5d4 36#include "time-util.h"
e4de7287 37#include "tmpfile-util.h"
47350c5f 38#include "version.h"
3d090cc6 39
799a8f39
ZJS
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"
50a0b071 43#define DEFAULT_PORT 19532
29fc0ddc 44
fc1cf91a
YW
45static char *arg_url = NULL;
46static char *arg_key = NULL;
47static char *arg_cert = NULL;
48static char *arg_trust = NULL;
49static char *arg_directory = NULL;
eacbb4d3 50static char **arg_file = NULL;
fc1cf91a 51static char *arg_cursor = NULL;
eacbb4d3
ZJS
52static bool arg_after_cursor = false;
53static int arg_journal_type = 0;
9f6e0bd4 54static int arg_namespace_flags = 0;
fc1cf91a
YW
55static char *arg_machine = NULL;
56static char *arg_namespace = NULL;
eacbb4d3
ZJS
57static bool arg_merge = false;
58static int arg_follow = -1;
fc1cf91a 59static char *arg_save_state = NULL;
279082ed 60static usec_t arg_network_timeout_usec = USEC_INFINITY;
c259c9e2 61static OrderedHashmap *arg_compression = NULL;
5209e9cb 62static OrderedHashmap *arg_headers = NULL;
cfaf7800 63static bool arg_force_compression = false;
eacbb4d3 64
fc1cf91a
YW
65STATIC_DESTRUCTOR_REGISTER(arg_url, freep);
66STATIC_DESTRUCTOR_REGISTER(arg_key, freep);
67STATIC_DESTRUCTOR_REGISTER(arg_cert, freep);
68STATIC_DESTRUCTOR_REGISTER(arg_trust, freep);
69STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
3ec49af9 70STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep);
fc1cf91a
YW
71STATIC_DESTRUCTOR_REGISTER(arg_cursor, freep);
72STATIC_DESTRUCTOR_REGISTER(arg_machine, freep);
73STATIC_DESTRUCTOR_REGISTER(arg_namespace, freep);
74STATIC_DESTRUCTOR_REGISTER(arg_save_state, freep);
c259c9e2 75STATIC_DESTRUCTOR_REGISTER(arg_compression, ordered_hashmap_freep);
5209e9cb 76STATIC_DESTRUCTOR_REGISTER(arg_headers, ordered_hashmap_freep);
3ec49af9 77
2cf4172a
LP
78static void close_fd_input(Uploader *u);
79
eacbb4d3
ZJS
80#define SERVER_ANSWER_KEEP 2048
81
722b6795
ZJS
82#define STATE_FILE "/var/lib/systemd/journal-upload/state"
83
3d090cc6 84#define easy_setopt(curl, opt, value, level, cmd) \
8847551b 85 do { \
3d090cc6
ZJS
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 } \
9ed794a3 93 } while (0)
3d090cc6 94
a6f575a1 95DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL);
c4a2d475 96DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL);
a6f575a1 97
eacbb4d3
ZJS
98static size_t output_callback(char *buf,
99 size_t size,
100 size_t nmemb,
101 void *userp) {
99534007 102 Uploader *u = ASSERT_PTR(userp);
eacbb4d3
ZJS
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)
d4e98880 110 log_warning("Failed to store server answer (%zu bytes): out of memory", size*nmemb);
eacbb4d3
ZJS
111 }
112
113 return size * nmemb;
114}
115
d71839af
ZJS
116static 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);
eb56eb9b
MS
125 if (r < 0)
126 return log_error_errno(r, "Cannot create parent directory of state file %s: %m",
127 u->state_file);
d71839af
ZJS
128
129 r = fopen_temporary(u->state_file, &f, &temp_path);
eb56eb9b
MS
130 if (r < 0)
131 return log_error_errno(r, "Cannot save state to %s: %m",
132 u->state_file);
6990fb6b 133 (void) unlink(temp_path);
d71839af
ZJS
134
135 return 0;
136}
137
722b6795 138static int update_cursor_state(Uploader *u) {
70f1280c 139 _cleanup_(unlink_and_freep) char *temp_path = NULL;
722b6795
ZJS
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)
dacd6cee 148 goto fail;
722b6795
ZJS
149
150 fprintf(f,
151 "# This is private data. Do not parse.\n"
152 "LAST_CURSOR=%s\n",
153 u->last_cursor);
154
dacd6cee
LP
155 r = fflush_and_check(f);
156 if (r < 0)
157 goto fail;
722b6795 158
dacd6cee 159 if (rename(temp_path, u->state_file) < 0) {
722b6795 160 r = -errno;
dacd6cee 161 goto fail;
722b6795
ZJS
162 }
163
70f1280c 164 temp_path = mfree(temp_path);
dacd6cee
LP
165 return 0;
166
167fail:
dacd6cee 168 (void) unlink(u->state_file);
722b6795 169
dacd6cee 170 return log_error_errno(r, "Failed to save state %s: %m", u->state_file);
722b6795
ZJS
171}
172
173static int load_cursor_state(Uploader *u) {
174 int r;
175
176 if (!u->state_file)
177 return 0;
178
13df9c39 179 r = parse_env_file(NULL, u->state_file, "LAST_CURSOR", &u->last_cursor);
36d4739a
ZJS
180 if (r == -ENOENT)
181 log_debug("State file %s is not present.", u->state_file);
eb56eb9b
MS
182 else if (r < 0)
183 return log_error_errno(r, "Failed to read state file %s: %m",
184 u->state_file);
185 else
36d4739a 186 log_debug("Last cursor was %s", u->last_cursor);
722b6795
ZJS
187
188 return 0;
189}
190
3d090cc6
ZJS
191int 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) {
c4a2d475
DT
203 _cleanup_(curl_slist_free_allp) struct curl_slist *h = NULL;
204 struct curl_slist *l;
3d090cc6
ZJS
205
206 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
207 if (!h)
208 return log_oom();
209
38510697 210 l = curl_slist_append(h, "Transfer-Encoding: chunked");
c4a2d475 211 if (!l)
3d090cc6 212 return log_oom();
38510697 213 h = l;
3d090cc6 214
38510697 215 l = curl_slist_append(h, "Accept: text/plain");
c4a2d475 216 if (!l)
3d090cc6 217 return log_oom();
38510697 218 h = l;
3d090cc6 219
c259c9e2
YW
220 if (u->compression) {
221 _cleanup_free_ char *header = strjoin("Content-Encoding: ", compression_lowercase_to_string(u->compression->algorithm));
cfaf7800
AC
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
5209e9cb
AC
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
c4a2d475 253 u->header = TAKE_PTR(h);
3d090cc6
ZJS
254 }
255
256 if (!u->easy) {
a6f575a1 257 _cleanup_(curl_easy_cleanupp) CURL *curl = NULL;
3d090cc6
ZJS
258
259 curl = curl_easy_init();
baaa35ad
ZJS
260 if (!curl)
261 return log_error_errno(SYNTHETIC_ERRNO(ENOSR),
262 "Call to curl_easy_init failed.");
3d090cc6 263
279082ed
JA
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
3d090cc6
ZJS
270 /* tell it to POST to the URL */
271 easy_setopt(curl, CURLOPT_POST, 1L,
272 LOG_ERR, return -EXFULL);
273
b88a40a7 274 easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error,
eacbb4d3
ZJS
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
3d090cc6
ZJS
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
f1d34068 295 if (DEBUG_LOGGING)
5dabb1e0
ZJS
296 /* enable verbose for easier tracing */
297 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
3d090cc6
ZJS
298
299 easy_setopt(curl, CURLOPT_USERAGENT,
681bd2c5 300 "systemd-journal-upload " GIT_VERSION,
3d090cc6
ZJS
301 LOG_WARNING, );
302
3dadb54f 303 if (!streq_ptr(arg_key, "-") && (arg_key || startswith(u->url, "https://"))) {
799a8f39 304 easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE,
7449bc1f 305 LOG_ERR, return -EXFULL);
29fc0ddc 306 easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE,
7449bc1f
ZJS
307 LOG_ERR, return -EXFULL);
308 }
309
3dadb54f 310 if (STRPTR_IN_SET(arg_trust, "-", "all"))
8847551b
ZJS
311 easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0,
312 LOG_ERR, return -EUCLEAN);
313 else if (arg_trust || startswith(u->url, "https://"))
29fc0ddc 314 easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE,
7449bc1f
ZJS
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
a6f575a1 321 u->easy = TAKE_PTR(curl);
eacbb4d3
ZJS
322 } else {
323 /* truncate the potential old error message */
324 u->error[0] = '\0';
325
5bcd12ca 326 u->answer = mfree(u->answer);
3d090cc6
ZJS
327 }
328
329 /* upload to this place */
330 code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
baaa35ad
ZJS
331 if (code)
332 return log_error_errno(SYNTHETIC_ERRNO(EXFULL),
333 "curl_easy_setopt CURLOPT_URL failed: %s",
334 curl_easy_strerror(code));
3d090cc6
ZJS
335
336 u->uploading = true;
337
338 return 0;
339}
340
341static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
cfaf7800 342 _cleanup_free_ char *compression_buffer = NULL;
99534007 343 Uploader *u = ASSERT_PTR(userp);
c504106c 344 ssize_t n;
cfaf7800 345 int r;
3d090cc6 346
ccc0ec6f 347 assert(nmemb < SSIZE_MAX / size);
3d090cc6
ZJS
348
349 if (u->input < 0)
350 return 0;
351
c504106c 352 assert(!size_multiply_overflow(size, nmemb));
3d090cc6 353
c259c9e2 354 if (u->compression) {
cfaf7800
AC
355 compression_buffer = malloc_multiply(nmemb, size);
356 if (!compression_buffer) {
357 log_oom();
358 return CURL_READFUNC_ABORT;
359 }
360 }
3d090cc6 361
cfaf7800
AC
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);
c259c9e2 365 if (!u->compression)
cfaf7800
AC
366 return n;
367
368 size_t compressed_size;
c259c9e2 369 r = compress_blob(u->compression->algorithm, compression_buffer, n, buf, size * nmemb, &compressed_size, u->compression->level);
cfaf7800 370 if (r < 0) {
c259c9e2
YW
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);
cfaf7800
AC
373 return CURL_READFUNC_ABORT;
374 }
375 assert(compressed_size <= size * nmemb);
376 return compressed_size;
377 } else if (n < 0) {
56f64d95 378 log_error_errno(errno, "Aborting transfer after read error on input: %m.");
3d090cc6
ZJS
379 return CURL_READFUNC_ABORT;
380 }
c504106c 381
cfaf7800 382 u->uploading = false;
c504106c
LP
383 log_debug("Reached EOF");
384 close_fd_input(u);
385 return 0;
3d090cc6
ZJS
386}
387
388static void close_fd_input(Uploader *u) {
389 assert(u);
390
3ccf323d 391 u->input = safe_close(u->input);
eacbb4d3 392 u->timeout = 0;
3d090cc6
ZJS
393}
394
395static int dispatch_fd_input(sd_event_source *event,
396 int fd,
397 uint32_t revents,
398 void *userp) {
99534007 399 Uploader *u = ASSERT_PTR(userp);
3d090cc6 400
3d090cc6
ZJS
401 assert(fd >= 0);
402
8201af08
ZJS
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
3d090cc6
ZJS
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
422static int open_file_for_upload(Uploader *u, const char *filename) {
e1ad6e24 423 int fd, r = 0;
3d090cc6
ZJS
424
425 if (streq(filename, "-"))
426 fd = STDIN_FILENO;
427 else {
428 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
4a62c710
MS
429 if (fd < 0)
430 return log_error_errno(errno, "Failed to open %s: %m", filename);
3d090cc6
ZJS
431 }
432
433 u->input = fd;
434
9c7f2201 435 if (arg_follow != 0) {
b50e9011 436 r = sd_event_add_io(u->event, &u->input_event,
eacbb4d3
ZJS
437 fd, EPOLLIN, dispatch_fd_input, u);
438 if (r < 0) {
eb56eb9b
MS
439 if (r != -EPERM || arg_follow > 0)
440 return log_error_errno(r, "Failed to register input event: %m");
3d090cc6 441
eacbb4d3
ZJS
442 /* Normal files should just be consumed without polling. */
443 r = start_upload(u, fd_input_callback, u);
444 }
3d090cc6 445 }
eacbb4d3 446
3d090cc6
ZJS
447 return r;
448}
449
722b6795 450static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
3d090cc6 451 int r;
50a0b071 452 const char *host, *proto = "";
3d090cc6
ZJS
453
454 assert(u);
455 assert(url);
456
b48a0e09 457 *u = (Uploader) {
61b315de 458 .input = -1,
b48a0e09 459 };
3d090cc6 460
c259c9e2
YW
461 if (arg_force_compression)
462 u->compression = ordered_hashmap_first(arg_compression);
cfaf7800 463
49fe5c09
LP
464 host = STARTSWITH_SET(url, "http://", "https://");
465 if (!host) {
50a0b071
ZJS
466 host = url;
467 proto = "https://";
468 }
469
470 if (strchr(host, ':'))
605405c6 471 u->url = strjoin(proto, url, "/upload");
50a0b071
ZJS
472 else {
473 char *t;
474 size_t x;
5bc89120 475
2f82562b 476 t = strdupa_safe(url);
50a0b071
ZJS
477 x = strlen(t);
478 while (x > 0 && t[x - 1] == '/')
479 t[x - 1] = '\0';
480
605405c6 481 u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload");
50a0b071 482 }
5bc89120
ZJS
483 if (!u->url)
484 return log_oom();
485
722b6795 486 u->state_file = state_file;
3d090cc6 487
b50e9011 488 r = sd_event_default(&u->event);
eb56eb9b
MS
489 if (r < 0)
490 return log_error_errno(r, "sd_event_default failed: %m");
3d090cc6 491
b50e9011 492 r = sd_event_set_signal_exit(u->event, true);
eb56eb9b 493 if (r < 0)
dcd332ae 494 return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
a3152e76 495
0aa176a7
ZJS
496 (void) sd_watchdog_enabled(false, &u->watchdog_usec);
497
722b6795 498 return load_cursor_state(u);
3d090cc6
ZJS
499}
500
501static void destroy_uploader(Uploader *u) {
502 assert(u);
503
504 curl_easy_cleanup(u->easy);
505 curl_slist_free_all(u->header);
eacbb4d3
ZJS
506 free(u->answer);
507
508 free(u->last_cursor);
722b6795 509 free(u->current_cursor);
3d090cc6 510
5bc89120
ZJS
511 free(u->url);
512
3d090cc6
ZJS
513 u->input_event = sd_event_source_unref(u->input_event);
514
515 close_fd_input(u);
eacbb4d3 516 close_journal_input(u);
3d090cc6 517
b50e9011 518 sd_event_unref(u->event);
3d090cc6
ZJS
519}
520
cfaf7800 521#if LIBCURL_VERSION_NUM >= 0x075300
04b09640
YW
522static 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}
56ab3ae1 580#endif
04b09640 581
56ab3ae1
YW
582static int parse_accept_encoding_header(Uploader *u) {
583#if LIBCURL_VERSION_NUM >= 0x075300
cfaf7800
AC
584 int r;
585
586 assert(u);
587
56ab3ae1
YW
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;;) {
8c7e4e0d 597 _cleanup_free_ char *word = NULL;
cfaf7800 598
8c7e4e0d 599 r = extract_first_word(&p, &word, ",", 0);
cfaf7800 600 if (r < 0)
56ab3ae1 601 return log_warning_errno(r, "Failed to parse Accept-Encoding header value, ignoring: %m");
cfaf7800 602 if (r == 0)
56ab3ae1 603 break;
cfaf7800 604
8c7e4e0d
YW
605 /* Cut the quality value waiting. */
606 char *q = strchr(word, ';');
607 if (q)
608 *q = '\0';
cfaf7800 609
56ab3ae1
YW
610 if (streq(word, "*"))
611 return update_content_encoding_header(u, ordered_hashmap_first(arg_compression));
612
8c7e4e0d
YW
613 Compression c = compression_lowercase_from_string(word);
614 if (c <= 0 || !compression_supported(c))
615 continue; /* unsupported or invalid algorithm. */
cfaf7800 616
c259c9e2
YW
617 const CompressionConfig *cc = ordered_hashmap_get(arg_compression, INT_TO_PTR(c));
618 if (!cc)
619 continue; /* The specified algorithm is not enabled. */
cfaf7800 620
04b09640 621 return update_content_encoding_header(u, cc);
cfaf7800 622 }
56ab3ae1
YW
623
624not_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;
cfaf7800 631#endif
56ab3ae1 632}
cfaf7800 633
eacbb4d3
ZJS
634static int perform_upload(Uploader *u) {
635 CURLcode code;
636 long status;
637
638 assert(u);
639
0aa176a7 640 u->watchdog_timestamp = now(CLOCK_MONOTONIC);
eacbb4d3
ZJS
641 code = curl_easy_perform(u->easy);
642 if (code) {
30776485
ZJS
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));
eacbb4d3
ZJS
649 return -EIO;
650 }
651
652 code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
baaa35ad
ZJS
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));
56ab3ae1 662 if (status < 200)
baaa35ad
ZJS
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));
cfaf7800 666
56ab3ae1
YW
667 (void) parse_accept_encoding_header(u);
668
669 log_debug("Upload finished successfully with code %ld: %s",
670 status, strna(u->answer));
722b6795 671
3b319885 672 free_and_replace(u->last_cursor, u->current_cursor);
722b6795
ZJS
673
674 return update_cursor_state(u);
eacbb4d3
ZJS
675}
676
29fc0ddc
ZJS
677static int parse_config(void) {
678 const ConfigTableItem items[] = {
3f87eaa5
YW
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 },
5209e9cb 684 { "Upload", "Header", config_parse_header, 0, &arg_headers },
c259c9e2 685 { "Upload", "Compression", config_parse_compression, /* with_level */ true, &arg_compression },
cfaf7800 686 { "Upload", "ForceCompression", config_parse_bool, 0, &arg_force_compression },
4f9ff96a
LP
687 {}
688 };
29fc0ddc 689
6378f257
ZJS
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);
29fc0ddc
ZJS
696}
697
37ec0fdd
LP
698static 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
3d090cc6
ZJS
706 printf("%s -u URL {FILE|-}...\n\n"
707 "Upload journal events to a remote server.\n\n"
dad29dff
LP
708 " -h --help Show this help\n"
709 " --version Show package version\n"
50a0b071
ZJS
710 " -u --url=URL Upload to this address (default port "
711 STRINGIFY(DEFAULT_PORT) ")\n"
1af719ed
ZJS
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"
dad29dff
LP
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"
9f6e0bd4 722 " --namespace=NAMESPACE Use journal files from namespace\n"
dad29dff
LP
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"
bc556335
DDM
730 "\nSee the %s for details.\n",
731 program_invocation_short_name,
732 link);
37ec0fdd
LP
733
734 return 0;
3d090cc6
ZJS
735}
736
737static int parse_argv(int argc, char *argv[]) {
738 enum {
739 ARG_VERSION = 0x100,
7449bc1f
ZJS
740 ARG_KEY,
741 ARG_CERT,
742 ARG_TRUST,
eacbb4d3
ZJS
743 ARG_USER,
744 ARG_SYSTEM,
745 ARG_FILE,
746 ARG_CURSOR,
747 ARG_AFTER_CURSOR,
748 ARG_FOLLOW,
722b6795 749 ARG_SAVE_STATE,
9f6e0bd4 750 ARG_NAMESPACE,
3d090cc6
ZJS
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' },
7449bc1f
ZJS
757 { "key", required_argument, NULL, ARG_KEY },
758 { "cert", required_argument, NULL, ARG_CERT },
759 { "trust", required_argument, NULL, ARG_TRUST },
eacbb4d3
ZJS
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' },
9f6e0bd4 764 { "namespace", required_argument, NULL, ARG_NAMESPACE },
eacbb4d3
ZJS
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 },
dad29dff 769 { "follow", optional_argument, NULL, ARG_FOLLOW },
722b6795 770 { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
3d090cc6
ZJS
771 {}
772 };
773
eacbb4d3 774 int c, r;
3d090cc6
ZJS
775
776 assert(argc >= 0);
777 assert(argv);
778
779 opterr = 0;
780
eacbb4d3 781 while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
79893116 782 switch (c) {
3d090cc6 783 case 'h':
37ec0fdd 784 return help();
3d090cc6
ZJS
785
786 case ARG_VERSION:
3f6fd1ba 787 return version();
3d090cc6
ZJS
788
789 case 'u':
fc1cf91a
YW
790 r = free_and_strdup_warn(&arg_url, optarg);
791 if (r < 0)
792 return r;
3d090cc6
ZJS
793 break;
794
7449bc1f 795 case ARG_KEY:
fc1cf91a
YW
796 r = free_and_strdup_warn(&arg_key, optarg);
797 if (r < 0)
798 return r;
7449bc1f
ZJS
799 break;
800
801 case ARG_CERT:
fc1cf91a
YW
802 r = free_and_strdup_warn(&arg_cert, optarg);
803 if (r < 0)
804 return r;
7449bc1f
ZJS
805 break;
806
807 case ARG_TRUST:
fc1cf91a
YW
808 r = free_and_strdup_warn(&arg_trust, optarg);
809 if (r < 0)
810 return r;
7449bc1f
ZJS
811 break;
812
eacbb4d3
ZJS
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':
fc1cf91a
YW
826 r = free_and_strdup_warn(&arg_machine, optarg);
827 if (r < 0)
828 return r;
eacbb4d3
ZJS
829 break;
830
9f6e0bd4
IT
831 case ARG_NAMESPACE:
832 if (streq(optarg, "*")) {
833 arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES;
fc1cf91a
YW
834 arg_namespace = mfree(arg_namespace);
835 r = 0;
9f6e0bd4
IT
836 } else if (startswith(optarg, "+")) {
837 arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE;
fc1cf91a 838 r = free_and_strdup_warn(&arg_namespace, optarg + 1);
9f6e0bd4
IT
839 } else if (isempty(optarg)) {
840 arg_namespace_flags = 0;
fc1cf91a
YW
841 arg_namespace = mfree(arg_namespace);
842 r = 0;
9f6e0bd4
IT
843 } else {
844 arg_namespace_flags = 0;
fc1cf91a 845 r = free_and_strdup_warn(&arg_namespace, optarg);
9f6e0bd4 846 }
fc1cf91a
YW
847 if (r < 0)
848 return r;
9f6e0bd4
IT
849 break;
850
eacbb4d3 851 case 'D':
fc1cf91a
YW
852 r = free_and_strdup_warn(&arg_directory, optarg);
853 if (r < 0)
854 return r;
eacbb4d3
ZJS
855 break;
856
857 case ARG_FILE:
544e146b 858 r = glob_extend(&arg_file, optarg, GLOB_NOCHECK);
eb56eb9b
MS
859 if (r < 0)
860 return log_error_errno(r, "Failed to add paths: %m");
eacbb4d3
ZJS
861 break;
862
863 case ARG_CURSOR:
eacbb4d3 864 case ARG_AFTER_CURSOR:
fc1cf91a
YW
865 r = free_and_strdup_warn(&arg_cursor, optarg);
866 if (r < 0)
867 return r;
868 arg_after_cursor = c == ARG_AFTER_CURSOR;
eacbb4d3
ZJS
869 break;
870
871 case ARG_FOLLOW:
9c7f2201
ZJS
872 r = parse_boolean_argument("--follow", optarg, NULL);
873 if (r < 0)
874 return r;
875 arg_follow = r;
eacbb4d3
ZJS
876 break;
877
722b6795 878 case ARG_SAVE_STATE:
fc1cf91a
YW
879 r = free_and_strdup_warn(&arg_save_state, optarg ?: STATE_FILE);
880 if (r < 0)
881 return r;
722b6795
ZJS
882 break;
883
3d090cc6 884 case '?':
baaa35ad
ZJS
885 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
886 "Unknown option %s.",
887 argv[optind - 1]);
3d090cc6
ZJS
888
889 case ':':
baaa35ad
ZJS
890 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
891 "Missing argument to %s.",
892 argv[optind - 1]);
3d090cc6
ZJS
893
894 default:
04499a70 895 assert_not_reached();
3d090cc6
ZJS
896 }
897
baaa35ad
ZJS
898 if (!arg_url)
899 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
0096917d 900 "Required --url=/-u option missing.");
3d090cc6 901
baaa35ad
ZJS
902 if (!!arg_key != !!arg_cert)
903 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
0096917d 904 "Options --key= and --cert= must be used together.");
7449bc1f 905
baaa35ad
ZJS
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.");
3d090cc6
ZJS
909
910 return 1;
911}
912
eacbb4d3
ZJS
913static int open_journal(sd_journal **j) {
914 int r;
915
7050d928
YW
916 assert(j);
917
eacbb4d3
ZJS
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);
5c6673af 922 else if (arg_machine)
f7f062bf 923 r = journal_open_machine(j, arg_machine, 0);
5c6673af 924 else
12fff85d
YW
925 r = sd_journal_open_namespace(j, arg_namespace,
926 (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) | arg_namespace_flags | arg_journal_type);
eacbb4d3 927 if (r < 0)
c33b3297 928 log_error_errno(r, "Failed to open %s: %m",
1da3cb81 929 arg_directory ?: (arg_file ? "files" : "journal"));
eacbb4d3
ZJS
930 return r;
931}
3d090cc6 932
f36bb1e1 933static int run(int argc, char **argv) {
c9ed6086 934 _cleanup_(destroy_uploader) Uploader u = {};
d7ac0952 935 _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = NULL;
eacbb4d3 936 bool use_journal;
f36bb1e1 937 int r;
3d090cc6 938
aa976d87 939 log_setup();
3d090cc6 940
29fc0ddc 941 r = parse_config();
4015ac5c 942 if (r < 0)
f36bb1e1 943 return r;
29fc0ddc 944
3d090cc6
ZJS
945 r = parse_argv(argc, argv);
946 if (r <= 0)
f36bb1e1 947 return r;
3d090cc6 948
c259c9e2
YW
949 r = compression_configs_mangle(&arg_compression);
950 if (r < 0)
951 return r;
952
fad3feec 953 journal_browse_prepare();
2cf4172a 954
722b6795 955 r = setup_uploader(&u, arg_url, arg_save_state);
3d090cc6 956 if (r < 0)
f36bb1e1 957 return r;
3d090cc6 958
b50e9011 959 sd_event_set_watchdog(u.event, true);
a3152e76 960
d71839af
ZJS
961 r = check_cursor_updating(&u);
962 if (r < 0)
f36bb1e1 963 return r;
d71839af 964
3d090cc6 965 log_debug("%s running as pid "PID_FMT,
df0ff127 966 program_invocation_short_name, getpid_cached());
eacbb4d3
ZJS
967
968 use_journal = optind >= argc;
969 if (use_journal) {
970 sd_journal *j;
971 r = open_journal(&j);
972 if (r < 0)
f36bb1e1 973 return r;
eacbb4d3 974 r = open_journal_for_upload(&u, j,
722b6795
ZJS
975 arg_cursor ?: u.last_cursor,
976 arg_cursor ? arg_after_cursor : true,
9c7f2201 977 arg_follow != 0);
eacbb4d3 978 if (r < 0)
f36bb1e1 979 return r;
eacbb4d3
ZJS
980 }
981
f36bb1e1
YW
982 notify_message = notify_start("READY=1\n"
983 "STATUS=Processing input...",
792a8bfb 984 NOTIFY_STOPPING_MESSAGE);
3d090cc6 985
57255510 986 for (;;) {
b50e9011 987 r = sd_event_get_state(u.event);
36d4739a 988 if (r < 0)
f36bb1e1 989 return r;
36d4739a 990 if (r == SD_EVENT_FINISHED)
f36bb1e1 991 return 0;
36d4739a 992
eacbb4d3
ZJS
993 if (use_journal) {
994 if (!u.journal)
f36bb1e1 995 return 0;
eacbb4d3
ZJS
996
997 r = check_journal_input(&u);
998 } else if (u.input < 0 && !use_journal) {
3d090cc6 999 if (optind >= argc)
f36bb1e1 1000 return 0;
3d090cc6
ZJS
1001
1002 log_debug("Using %s as input.", argv[optind]);
3d090cc6 1003 r = open_file_for_upload(&u, argv[optind++]);
3d090cc6 1004 }
eacbb4d3 1005 if (r < 0)
f36bb1e1 1006 return r;
3d090cc6 1007
3d090cc6 1008 if (u.uploading) {
eacbb4d3
ZJS
1009 r = perform_upload(&u);
1010 if (r < 0)
f36bb1e1 1011 return r;
3d090cc6
ZJS
1012 }
1013
b50e9011 1014 r = sd_event_run(u.event, u.timeout);
f36bb1e1
YW
1015 if (r < 0)
1016 return log_error_errno(r, "Failed to run event loop: %m");
3d090cc6 1017 }
3d090cc6 1018}
f36bb1e1
YW
1019
1020DEFINE_MAIN_FUNCTION(run);