]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal-remote/journal-upload.c
journal-upload: use journal as the source
[thirdparty/systemd.git] / src / journal-remote / journal-upload.c
CommitLineData
3d090cc6
ZJS
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Zbigniew Jędrzejewski-Szmek
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <stdio.h>
23#include <curl/curl.h>
24#include <sys/stat.h>
25#include <fcntl.h>
26#include <getopt.h>
27
28#include "sd-daemon.h"
29
30#include "log.h"
31#include "util.h"
32#include "build.h"
33#include "journal-upload.h"
34
35static const char* arg_url;
36
37static void close_fd_input(Uploader *u);
38
7449bc1f
ZJS
39static const char *arg_key = NULL;
40static const char *arg_cert = NULL;
41static const char *arg_trust = NULL;
42
eacbb4d3
ZJS
43static const char *arg_directory = NULL;
44static char **arg_file = NULL;
45static const char *arg_cursor = NULL;
46static bool arg_after_cursor = false;
47static int arg_journal_type = 0;
48static const char *arg_machine = NULL;
49static bool arg_merge = false;
50static int arg_follow = -1;
51
52#define SERVER_ANSWER_KEEP 2048
53
3d090cc6
ZJS
54#define easy_setopt(curl, opt, value, level, cmd) \
55 { \
56 code = curl_easy_setopt(curl, opt, value); \
57 if (code) { \
58 log_full(level, \
59 "curl_easy_setopt " #opt " failed: %s", \
60 curl_easy_strerror(code)); \
61 cmd; \
62 } \
63 }
64
eacbb4d3
ZJS
65static size_t output_callback(char *buf,
66 size_t size,
67 size_t nmemb,
68 void *userp) {
69 Uploader *u = userp;
70
71 assert(u);
72
73 log_debug("The server answers (%zu bytes): %.*s",
74 size*nmemb, (int)(size*nmemb), buf);
75
76 if (nmemb && !u->answer) {
77 u->answer = strndup(buf, size*nmemb);
78 if (!u->answer)
79 log_warning("Failed to store server answer (%zu bytes): %s",
80 size*nmemb, strerror(ENOMEM));
81 }
82
83 return size * nmemb;
84}
85
3d090cc6
ZJS
86int start_upload(Uploader *u,
87 size_t (*input_callback)(void *ptr,
88 size_t size,
89 size_t nmemb,
90 void *userdata),
91 void *data) {
92 CURLcode code;
93
94 assert(u);
95 assert(input_callback);
96
97 if (!u->header) {
98 struct curl_slist *h;
99
100 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
101 if (!h)
102 return log_oom();
103
104 h = curl_slist_append(h, "Transfer-Encoding: chunked");
105 if (!h) {
106 curl_slist_free_all(h);
107 return log_oom();
108 }
109
110 h = curl_slist_append(h, "Accept: text/plain");
111 if (!h) {
112 curl_slist_free_all(h);
113 return log_oom();
114 }
115
116 u->header = h;
117 }
118
119 if (!u->easy) {
120 CURL *curl;
121
122 curl = curl_easy_init();
123 if (!curl) {
124 log_error("Call to curl_easy_init failed.");
125 return -ENOSR;
126 }
127
128 /* tell it to POST to the URL */
129 easy_setopt(curl, CURLOPT_POST, 1L,
130 LOG_ERR, return -EXFULL);
131
eacbb4d3
ZJS
132 easy_setopt(curl, CURLOPT_ERRORBUFFER, &u->error,
133 LOG_ERR, return -EXFULL);
134
135 /* set where to write to */
136 easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
137 LOG_ERR, return -EXFULL);
138
139 easy_setopt(curl, CURLOPT_WRITEDATA, data,
140 LOG_ERR, return -EXFULL);
141
3d090cc6
ZJS
142 /* set where to read from */
143 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
144 LOG_ERR, return -EXFULL);
145
146 easy_setopt(curl, CURLOPT_READDATA, data,
147 LOG_ERR, return -EXFULL);
148
149 /* use our special own mime type and chunked transfer */
150 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
151 LOG_ERR, return -EXFULL);
152
153 /* enable verbose for easier tracing */
154 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
155
156 easy_setopt(curl, CURLOPT_USERAGENT,
157 "systemd-journal-upload " PACKAGE_STRING,
158 LOG_WARNING, );
159
7449bc1f
ZJS
160 if (arg_key) {
161 assert(arg_cert);
162
163 easy_setopt(curl, CURLOPT_SSLKEY, arg_key,
164 LOG_ERR, return -EXFULL);
165 easy_setopt(curl, CURLOPT_SSLCERT, arg_cert,
166 LOG_ERR, return -EXFULL);
167 }
168
169 if (arg_trust)
170 easy_setopt(curl, CURLOPT_CAINFO, arg_trust,
171 LOG_ERR, return -EXFULL);
172
173 if (arg_key || arg_trust)
174 easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
175 LOG_WARNING, );
176
3d090cc6 177 u->easy = curl;
eacbb4d3
ZJS
178 } else {
179 /* truncate the potential old error message */
180 u->error[0] = '\0';
181
182 free(u->answer);
183 u->answer = 0;
3d090cc6
ZJS
184 }
185
186 /* upload to this place */
187 code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
188 if (code) {
189 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
190 curl_easy_strerror(code));
191 return -EXFULL;
192 }
193
194 u->uploading = true;
195
196 return 0;
197}
198
199static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
200 Uploader *u = userp;
201
202 ssize_t r;
203
204 assert(u);
205 assert(nmemb <= SSIZE_MAX / size);
206
207 if (u->input < 0)
208 return 0;
209
210 r = read(u->input, buf, size * nmemb);
211 log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
212
213 if (r > 0)
214 return r;
215
216 u->uploading = false;
217 if (r == 0) {
218 log_debug("Reached EOF");
219 close_fd_input(u);
220 return 0;
221 } else {
222 log_error("Aborting transfer after read error on input: %m.");
223 return CURL_READFUNC_ABORT;
224 }
225}
226
227static void close_fd_input(Uploader *u) {
228 assert(u);
229
230 if (u->input >= 0)
231 close_nointr(u->input);
232 u->input = -1;
eacbb4d3 233 u->timeout = 0;
3d090cc6
ZJS
234}
235
236static int dispatch_fd_input(sd_event_source *event,
237 int fd,
238 uint32_t revents,
239 void *userp) {
240 Uploader *u = userp;
241
242 assert(u);
243 assert(revents & EPOLLIN);
244 assert(fd >= 0);
245
246 if (u->uploading) {
247 log_warning("dispatch_fd_input called when uploading, ignoring.");
248 return 0;
249 }
250
251 return start_upload(u, fd_input_callback, u);
252}
253
254static int open_file_for_upload(Uploader *u, const char *filename) {
255 int fd, r;
256
257 if (streq(filename, "-"))
258 fd = STDIN_FILENO;
259 else {
260 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
261 if (fd < 0) {
262 log_error("Failed to open %s: %m", filename);
263 return -errno;
264 }
265 }
266
267 u->input = fd;
268
eacbb4d3
ZJS
269 if (arg_follow) {
270 r = sd_event_add_io(u->events, &u->input_event,
271 fd, EPOLLIN, dispatch_fd_input, u);
272 if (r < 0) {
273 if (r != -EPERM || arg_follow > 0) {
274 log_error("Failed to register input event: %s", strerror(-r));
275 return r;
276 }
3d090cc6 277
eacbb4d3
ZJS
278 /* Normal files should just be consumed without polling. */
279 r = start_upload(u, fd_input_callback, u);
280 }
3d090cc6 281 }
eacbb4d3 282
3d090cc6
ZJS
283 return r;
284}
285
286static int setup_uploader(Uploader *u, const char *url) {
287 int r;
288
289 assert(u);
290 assert(url);
291
292 memzero(u, sizeof(Uploader));
293 u->input = -1;
294
295 u->url = url;
296
297 r = sd_event_default(&u->events);
298 if (r < 0) {
299 log_error("sd_event_default failed: %s", strerror(-r));
300 return r;
301 }
302
303 return 0;
304}
305
306static void destroy_uploader(Uploader *u) {
307 assert(u);
308
309 curl_easy_cleanup(u->easy);
310 curl_slist_free_all(u->header);
eacbb4d3
ZJS
311 free(u->answer);
312
313 free(u->last_cursor);
3d090cc6
ZJS
314
315 u->input_event = sd_event_source_unref(u->input_event);
316
317 close_fd_input(u);
eacbb4d3 318 close_journal_input(u);
3d090cc6
ZJS
319
320 sd_event_unref(u->events);
321}
322
eacbb4d3
ZJS
323static int perform_upload(Uploader *u) {
324 CURLcode code;
325 long status;
326
327 assert(u);
328
329 code = curl_easy_perform(u->easy);
330 if (code) {
331 log_error("Upload to %s failed: %.*s",
332 u->url,
333 u->error[0] ? (int) sizeof(u->error) : INT_MAX,
334 u->error[0] ? u->error : curl_easy_strerror(code));
335 return -EIO;
336 }
337
338 code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
339 if (code) {
340 log_error("Failed to retrieve response code: %s",
341 curl_easy_strerror(code));
342 return -EUCLEAN;
343 }
344
345 if (status >= 300) {
346 log_error("Upload to %s failed with code %lu: %s",
347 u->url, status, strna(u->answer));
348 return -EIO;
349 } else if (status < 200) {
350 log_error("Upload to %s finished with unexpected code %lu: %s",
351 u->url, status, strna(u->answer));
352 return -EIO;
353 } else
354 log_debug("Upload finished successfully with code %lu: %s",
355 status, strna(u->answer));
356 return 0;
357}
358
3d090cc6
ZJS
359static void help(void) {
360 printf("%s -u URL {FILE|-}...\n\n"
361 "Upload journal events to a remote server.\n\n"
362 "Options:\n"
363 " --url=URL Upload to this address\n"
7449bc1f
ZJS
364 " --key=FILENAME Specify key in PEM format\n"
365 " --cert=FILENAME Specify certificate in PEM format\n"
366 " --trust=FILENAME Specify CA certificate in PEM format\n"
eacbb4d3
ZJS
367 " --system Use the system journal\n"
368 " --user Use the user journal for the current user\n"
369 " -m --merge Use all available journals\n"
370 " -M --machine=CONTAINER Operate on local container\n"
371 " -D --directory=PATH Use journal files from directory\n"
372 " --file=PATH Use this journal file\n"
373 " --cursor=CURSOR Start at the specified cursor\n"
374 " --after-cursor=CURSOR Start after the specified cursor\n"
375 " --[no-]follow Do [not] wait for input\n"
3d090cc6
ZJS
376 " -h --help Show this help and exit\n"
377 " --version Print version string and exit\n"
378 , program_invocation_short_name);
379}
380
381static int parse_argv(int argc, char *argv[]) {
382 enum {
383 ARG_VERSION = 0x100,
7449bc1f
ZJS
384 ARG_KEY,
385 ARG_CERT,
386 ARG_TRUST,
eacbb4d3
ZJS
387 ARG_USER,
388 ARG_SYSTEM,
389 ARG_FILE,
390 ARG_CURSOR,
391 ARG_AFTER_CURSOR,
392 ARG_FOLLOW,
393 ARG_NO_FOLLOW,
3d090cc6
ZJS
394 };
395
396 static const struct option options[] = {
397 { "help", no_argument, NULL, 'h' },
398 { "version", no_argument, NULL, ARG_VERSION },
399 { "url", required_argument, NULL, 'u' },
7449bc1f
ZJS
400 { "key", required_argument, NULL, ARG_KEY },
401 { "cert", required_argument, NULL, ARG_CERT },
402 { "trust", required_argument, NULL, ARG_TRUST },
eacbb4d3
ZJS
403 { "system", no_argument, NULL, ARG_SYSTEM },
404 { "user", no_argument, NULL, ARG_USER },
405 { "merge", no_argument, NULL, 'm' },
406 { "machine", required_argument, NULL, 'M' },
407 { "directory", required_argument, NULL, 'D' },
408 { "file", required_argument, NULL, ARG_FILE },
409 { "cursor", required_argument, NULL, ARG_CURSOR },
410 { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
411 { "follow", no_argument, NULL, ARG_FOLLOW },
412 { "no-follow", no_argument, NULL, ARG_NO_FOLLOW },
3d090cc6
ZJS
413 {}
414 };
415
eacbb4d3 416 int c, r;
3d090cc6
ZJS
417
418 assert(argc >= 0);
419 assert(argv);
420
421 opterr = 0;
422
eacbb4d3 423 while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
3d090cc6
ZJS
424 switch(c) {
425 case 'h':
426 help();
427 return 0 /* done */;
428
429 case ARG_VERSION:
430 puts(PACKAGE_STRING);
431 puts(SYSTEMD_FEATURES);
432 return 0 /* done */;
433
434 case 'u':
435 if (arg_url) {
436 log_error("cannot use more than one --url");
437 return -EINVAL;
438 }
439
440 arg_url = optarg;
441 break;
442
7449bc1f
ZJS
443 case ARG_KEY:
444 if (arg_key) {
445 log_error("cannot use more than one --key");
446 return -EINVAL;
447 }
448
449 arg_key = optarg;
450 break;
451
452 case ARG_CERT:
453 if (arg_cert) {
454 log_error("cannot use more than one --cert");
455 return -EINVAL;
456 }
457
458 arg_cert = optarg;
459 break;
460
461 case ARG_TRUST:
462 if (arg_trust) {
463 log_error("cannot use more than one --trust");
464 return -EINVAL;
465 }
466
467 arg_trust = optarg;
468 break;
469
eacbb4d3
ZJS
470 case ARG_SYSTEM:
471 arg_journal_type |= SD_JOURNAL_SYSTEM;
472 break;
473
474 case ARG_USER:
475 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
476 break;
477
478 case 'm':
479 arg_merge = true;
480 break;
481
482 case 'M':
483 if (arg_machine) {
484 log_error("cannot use more than one --machine/-M");
485 return -EINVAL;
486 }
487
488 arg_machine = optarg;
489 break;
490
491 case 'D':
492 if (arg_directory) {
493 log_error("cannot use more than one --directory/-D");
494 return -EINVAL;
495 }
496
497 arg_directory = optarg;
498 break;
499
500 case ARG_FILE:
501 r = glob_extend(&arg_file, optarg);
502 if (r < 0) {
503 log_error("Failed to add paths: %s", strerror(-r));
504 return r;
505 };
506 break;
507
508 case ARG_CURSOR:
509 if (arg_cursor) {
510 log_error("cannot use more than one --cursor/--after-cursor");
511 return -EINVAL;
512 }
513
514 arg_cursor = optarg;
515 break;
516
517 case ARG_AFTER_CURSOR:
518 if (arg_cursor) {
519 log_error("cannot use more than one --cursor/--after-cursor");
520 return -EINVAL;
521 }
522
523 arg_cursor = optarg;
524 arg_after_cursor = true;
525 break;
526
527 case ARG_FOLLOW:
528 arg_follow = true;
529 break;
530
531 case ARG_NO_FOLLOW:
532 arg_follow = false;
533 break;
534
3d090cc6
ZJS
535 case '?':
536 log_error("Unknown option %s.", argv[optind-1]);
537 return -EINVAL;
538
539 case ':':
540 log_error("Missing argument to %s.", argv[optind-1]);
541 return -EINVAL;
542
543 default:
544 assert_not_reached("Unhandled option code.");
545 }
546
547 if (!arg_url) {
548 log_error("Required --url/-u option missing.");
549 return -EINVAL;
550 }
551
7449bc1f
ZJS
552 if (!!arg_key != !!arg_cert) {
553 log_error("Options --key and --cert must be used together.");
554 return -EINVAL;
555 }
556
eacbb4d3
ZJS
557 if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
558 log_error("Input arguments make no sense with journal input.");
3d090cc6
ZJS
559 return -EINVAL;
560 }
561
562 return 1;
563}
564
eacbb4d3
ZJS
565static int open_journal(sd_journal **j) {
566 int r;
567
568 if (arg_directory)
569 r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
570 else if (arg_file)
571 r = sd_journal_open_files(j, (const char**) arg_file, 0);
572 else if (arg_machine)
573 r = sd_journal_open_container(j, arg_machine, 0);
574 else
575 r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
576 if (r < 0)
577 log_error("Failed to open %s: %s",
578 arg_directory ? arg_directory : arg_file ? "files" : "journal",
579 strerror(-r));
580 return r;
581}
3d090cc6
ZJS
582
583int main(int argc, char **argv) {
584 Uploader u;
585 int r;
eacbb4d3 586 bool use_journal;
3d090cc6
ZJS
587
588 log_show_color(true);
589 log_parse_environment();
590
591 r = parse_argv(argc, argv);
592 if (r <= 0)
593 goto finish;
594
595 r = setup_uploader(&u, arg_url);
596 if (r < 0)
597 goto cleanup;
598
599 log_debug("%s running as pid "PID_FMT,
600 program_invocation_short_name, getpid());
eacbb4d3
ZJS
601
602 use_journal = optind >= argc;
603 if (use_journal) {
604 sd_journal *j;
605 r = open_journal(&j);
606 if (r < 0)
607 goto finish;
608 r = open_journal_for_upload(&u, j,
609 arg_cursor, arg_after_cursor,
610 !!arg_follow);
611 if (r < 0)
612 goto finish;
613 }
614
3d090cc6
ZJS
615 sd_notify(false,
616 "READY=1\n"
617 "STATUS=Processing input...");
618
619 while (true) {
eacbb4d3
ZJS
620 if (use_journal) {
621 if (!u.journal)
622 break;
623
624 r = check_journal_input(&u);
625 } else if (u.input < 0 && !use_journal) {
3d090cc6
ZJS
626 if (optind >= argc)
627 break;
628
629 log_debug("Using %s as input.", argv[optind]);
3d090cc6 630 r = open_file_for_upload(&u, argv[optind++]);
3d090cc6 631 }
eacbb4d3
ZJS
632 if (r < 0)
633 goto cleanup;
3d090cc6
ZJS
634
635 r = sd_event_get_state(u.events);
636 if (r < 0)
637 break;
638 if (r == SD_EVENT_FINISHED)
639 break;
640
641 if (u.uploading) {
eacbb4d3
ZJS
642 r = perform_upload(&u);
643 if (r < 0)
3d090cc6 644 break;
3d090cc6
ZJS
645 }
646
eacbb4d3 647 r = sd_event_run(u.events, u.timeout);
3d090cc6
ZJS
648 if (r < 0) {
649 log_error("Failed to run event loop: %s", strerror(-r));
650 break;
651 }
652 }
653
654cleanup:
655 sd_notify(false, "STATUS=Shutting down...");
656 destroy_uploader(&u);
657
658finish:
659 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
660}