1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Zbigniew Jędrzejewski-Szmek
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.
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.
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/>.
23 #include <curl/curl.h>
28 #include "sd-daemon.h"
34 #include "journal-upload.h"
36 static const char* arg_url;
38 static void close_fd_input(Uploader *u);
40 static const char *arg_key = NULL;
41 static const char *arg_cert = NULL;
42 static const char *arg_trust = NULL;
44 static const char *arg_directory = NULL;
45 static char **arg_file = NULL;
46 static const char *arg_cursor = NULL;
47 static bool arg_after_cursor = false;
48 static int arg_journal_type = 0;
49 static const char *arg_machine = NULL;
50 static bool arg_merge = false;
51 static int arg_follow = -1;
52 static const char *arg_save_state = NULL;
54 #define SERVER_ANSWER_KEEP 2048
56 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
58 #define easy_setopt(curl, opt, value, level, cmd) \
60 code = curl_easy_setopt(curl, opt, value); \
63 "curl_easy_setopt " #opt " failed: %s", \
64 curl_easy_strerror(code)); \
69 static size_t output_callback(char *buf,
77 log_debug("The server answers (%zu bytes): %.*s",
78 size*nmemb, (int)(size*nmemb), buf);
80 if (nmemb && !u->answer) {
81 u->answer = strndup(buf, size*nmemb);
83 log_warning("Failed to store server answer (%zu bytes): %s",
84 size*nmemb, strerror(ENOMEM));
90 static int update_cursor_state(Uploader *u) {
91 _cleanup_free_ char *temp_path = NULL;
92 _cleanup_fclose_ FILE *f = NULL;
95 if (!u->state_file || !u->last_cursor)
98 r = fopen_temporary(u->state_file, &f, &temp_path);
103 "# This is private data. Do not parse.\n"
109 if (ferror(f) || rename(temp_path, u->state_file) < 0) {
111 unlink(u->state_file);
117 log_error("Failed to save state %s: %s", u->state_file, strerror(-r));
122 static int load_cursor_state(Uploader *u) {
128 r = parse_env_file(u->state_file, NEWLINE,
129 "LAST_CURSOR", &u->last_cursor,
132 if (r < 0 && r != -ENOENT) {
133 log_error("Failed to read state file %s: %s",
134 u->state_file, strerror(-r));
143 int start_upload(Uploader *u,
144 size_t (*input_callback)(void *ptr,
152 assert(input_callback);
155 struct curl_slist *h;
157 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
161 h = curl_slist_append(h, "Transfer-Encoding: chunked");
163 curl_slist_free_all(h);
167 h = curl_slist_append(h, "Accept: text/plain");
169 curl_slist_free_all(h);
179 curl = curl_easy_init();
181 log_error("Call to curl_easy_init failed.");
185 /* tell it to POST to the URL */
186 easy_setopt(curl, CURLOPT_POST, 1L,
187 LOG_ERR, return -EXFULL);
189 easy_setopt(curl, CURLOPT_ERRORBUFFER, &u->error,
190 LOG_ERR, return -EXFULL);
192 /* set where to write to */
193 easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
194 LOG_ERR, return -EXFULL);
196 easy_setopt(curl, CURLOPT_WRITEDATA, data,
197 LOG_ERR, return -EXFULL);
199 /* set where to read from */
200 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
201 LOG_ERR, return -EXFULL);
203 easy_setopt(curl, CURLOPT_READDATA, data,
204 LOG_ERR, return -EXFULL);
206 /* use our special own mime type and chunked transfer */
207 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
208 LOG_ERR, return -EXFULL);
210 /* enable verbose for easier tracing */
211 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
213 easy_setopt(curl, CURLOPT_USERAGENT,
214 "systemd-journal-upload " PACKAGE_STRING,
220 easy_setopt(curl, CURLOPT_SSLKEY, arg_key,
221 LOG_ERR, return -EXFULL);
222 easy_setopt(curl, CURLOPT_SSLCERT, arg_cert,
223 LOG_ERR, return -EXFULL);
227 easy_setopt(curl, CURLOPT_CAINFO, arg_trust,
228 LOG_ERR, return -EXFULL);
230 if (arg_key || arg_trust)
231 easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
236 /* truncate the potential old error message */
243 /* upload to this place */
244 code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
246 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
247 curl_easy_strerror(code));
256 static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
262 assert(nmemb <= SSIZE_MAX / size);
267 r = read(u->input, buf, size * nmemb);
268 log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
273 u->uploading = false;
275 log_debug("Reached EOF");
279 log_error("Aborting transfer after read error on input: %m.");
280 return CURL_READFUNC_ABORT;
284 static void close_fd_input(Uploader *u) {
288 close_nointr(u->input);
293 static int dispatch_fd_input(sd_event_source *event,
302 if (revents & EPOLLHUP) {
303 log_debug("Received HUP");
308 if (!(revents & EPOLLIN)) {
309 log_warning("Unexpected poll event %"PRIu32".", revents);
314 log_warning("dispatch_fd_input called when uploading, ignoring.");
318 return start_upload(u, fd_input_callback, u);
321 static int open_file_for_upload(Uploader *u, const char *filename) {
324 if (streq(filename, "-"))
327 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
329 log_error("Failed to open %s: %m", filename);
337 r = sd_event_add_io(u->events, &u->input_event,
338 fd, EPOLLIN, dispatch_fd_input, u);
340 if (r != -EPERM || arg_follow > 0) {
341 log_error("Failed to register input event: %s", strerror(-r));
345 /* Normal files should just be consumed without polling. */
346 r = start_upload(u, fd_input_callback, u);
353 static int dispatch_sigterm(sd_event_source *event,
354 const struct signalfd_siginfo *si,
356 Uploader *u = userdata;
360 log_received_signal(LOG_INFO, si);
363 close_journal_input(u);
365 sd_event_exit(u->events, 0);
369 static int setup_signals(Uploader *u) {
375 assert_se(sigemptyset(&mask) == 0);
376 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
377 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
379 r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u);
383 r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u);
390 static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
396 memzero(u, sizeof(Uploader));
399 if (!startswith(url, "http://") && !startswith(url, "https://"))
400 url = strappenda("https://", url);
402 u->url = strappend(url, "/upload");
406 u->state_file = state_file;
408 r = sd_event_default(&u->events);
410 log_error("sd_event_default failed: %s", strerror(-r));
414 r = setup_signals(u);
416 log_error("Failed to set up signals: %s", strerror(-r));
420 return load_cursor_state(u);
423 static void destroy_uploader(Uploader *u) {
426 curl_easy_cleanup(u->easy);
427 curl_slist_free_all(u->header);
430 free(u->last_cursor);
431 free(u->current_cursor);
435 u->input_event = sd_event_source_unref(u->input_event);
438 close_journal_input(u);
440 sd_event_source_unref(u->sigterm_event);
441 sd_event_source_unref(u->sigint_event);
442 sd_event_unref(u->events);
445 static int perform_upload(Uploader *u) {
451 code = curl_easy_perform(u->easy);
453 log_error("Upload to %s failed: %.*s",
455 u->error[0] ? (int) sizeof(u->error) : INT_MAX,
456 u->error[0] ? u->error : curl_easy_strerror(code));
460 code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
462 log_error("Failed to retrieve response code: %s",
463 curl_easy_strerror(code));
468 log_error("Upload to %s failed with code %lu: %s",
469 u->url, status, strna(u->answer));
471 } else if (status < 200) {
472 log_error("Upload to %s finished with unexpected code %lu: %s",
473 u->url, status, strna(u->answer));
476 log_debug("Upload finished successfully with code %lu: %s",
477 status, strna(u->answer));
479 free(u->last_cursor);
480 u->last_cursor = u->current_cursor;
481 u->current_cursor = NULL;
483 return update_cursor_state(u);
486 static void help(void) {
487 printf("%s -u URL {FILE|-}...\n\n"
488 "Upload journal events to a remote server.\n\n"
490 " -u --url=URL Upload to this address\n"
491 " --key=FILENAME Specify key in PEM format\n"
492 " --cert=FILENAME Specify certificate in PEM format\n"
493 " --trust=FILENAME Specify CA certificate in PEM format\n"
494 " --system Use the system journal\n"
495 " --user Use the user journal for the current user\n"
496 " -m --merge Use all available journals\n"
497 " -M --machine=CONTAINER Operate on local container\n"
498 " -D --directory=PATH Use journal files from directory\n"
499 " --file=PATH Use this journal file\n"
500 " --cursor=CURSOR Start at the specified cursor\n"
501 " --after-cursor=CURSOR Start after the specified cursor\n"
502 " --[no-]follow Do [not] wait for input\n"
503 " --save-state[=FILE] Save uploaded cursors (default \n"
505 " -h --help Show this help and exit\n"
506 " --version Print version string and exit\n"
507 , program_invocation_short_name);
510 static int parse_argv(int argc, char *argv[]) {
526 static const struct option options[] = {
527 { "help", no_argument, NULL, 'h' },
528 { "version", no_argument, NULL, ARG_VERSION },
529 { "url", required_argument, NULL, 'u' },
530 { "key", required_argument, NULL, ARG_KEY },
531 { "cert", required_argument, NULL, ARG_CERT },
532 { "trust", required_argument, NULL, ARG_TRUST },
533 { "system", no_argument, NULL, ARG_SYSTEM },
534 { "user", no_argument, NULL, ARG_USER },
535 { "merge", no_argument, NULL, 'm' },
536 { "machine", required_argument, NULL, 'M' },
537 { "directory", required_argument, NULL, 'D' },
538 { "file", required_argument, NULL, ARG_FILE },
539 { "cursor", required_argument, NULL, ARG_CURSOR },
540 { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
541 { "follow", no_argument, NULL, ARG_FOLLOW },
542 { "no-follow", no_argument, NULL, ARG_NO_FOLLOW },
543 { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
554 while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
561 puts(PACKAGE_STRING);
562 puts(SYSTEMD_FEATURES);
567 log_error("cannot use more than one --url");
576 log_error("cannot use more than one --key");
585 log_error("cannot use more than one --cert");
594 log_error("cannot use more than one --trust");
602 arg_journal_type |= SD_JOURNAL_SYSTEM;
606 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
615 log_error("cannot use more than one --machine/-M");
619 arg_machine = optarg;
624 log_error("cannot use more than one --directory/-D");
628 arg_directory = optarg;
632 r = glob_extend(&arg_file, optarg);
634 log_error("Failed to add paths: %s", strerror(-r));
641 log_error("cannot use more than one --cursor/--after-cursor");
648 case ARG_AFTER_CURSOR:
650 log_error("cannot use more than one --cursor/--after-cursor");
655 arg_after_cursor = true;
667 arg_save_state = optarg ?: STATE_FILE;
671 log_error("Unknown option %s.", argv[optind-1]);
675 log_error("Missing argument to %s.", argv[optind-1]);
679 assert_not_reached("Unhandled option code.");
683 log_error("Required --url/-u option missing.");
687 if (!!arg_key != !!arg_cert) {
688 log_error("Options --key and --cert must be used together.");
692 if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
693 log_error("Input arguments make no sense with journal input.");
700 static int open_journal(sd_journal **j) {
704 r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
706 r = sd_journal_open_files(j, (const char**) arg_file, 0);
707 else if (arg_machine)
708 r = sd_journal_open_container(j, arg_machine, 0);
710 r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
712 log_error("Failed to open %s: %s",
713 arg_directory ? arg_directory : arg_file ? "files" : "journal",
718 int main(int argc, char **argv) {
723 log_show_color(true);
724 log_parse_environment();
726 r = parse_argv(argc, argv);
730 r = setup_uploader(&u, arg_url, arg_save_state);
734 sd_event_set_watchdog(u.events, true);
736 log_debug("%s running as pid "PID_FMT,
737 program_invocation_short_name, getpid());
739 use_journal = optind >= argc;
742 r = open_journal(&j);
745 r = open_journal_for_upload(&u, j,
746 arg_cursor ?: u.last_cursor,
747 arg_cursor ? arg_after_cursor : true,
755 "STATUS=Processing input...");
762 r = check_journal_input(&u);
763 } else if (u.input < 0 && !use_journal) {
767 log_debug("Using %s as input.", argv[optind]);
768 r = open_file_for_upload(&u, argv[optind++]);
773 r = sd_event_get_state(u.events);
776 if (r == SD_EVENT_FINISHED)
780 r = perform_upload(&u);
785 r = sd_event_run(u.events, u.timeout);
787 log_error("Failed to run event loop: %s", strerror(-r));
793 sd_notify(false, "STATUS=Shutting down...");
794 destroy_uploader(&u);
797 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;