2 This file is part of systemd.
4 Copyright 2014 Zbigniew Jędrzejewski-Szmek
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 #include <curl/curl.h>
26 #include "sd-daemon.h"
28 #include "alloc-util.h"
29 #include "conf-parser.h"
33 #include "format-util.h"
34 #include "glob-util.h"
35 #include "journal-upload.h"
38 #include "parse-util.h"
40 #include "signal-util.h"
41 #include "string-util.h"
44 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
45 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
46 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
47 #define DEFAULT_PORT 19532
49 static const char* arg_url
= NULL
;
50 static const char *arg_key
= NULL
;
51 static const char *arg_cert
= NULL
;
52 static const char *arg_trust
= NULL
;
53 static const char *arg_directory
= NULL
;
54 static char **arg_file
= NULL
;
55 static const char *arg_cursor
= NULL
;
56 static bool arg_after_cursor
= false;
57 static int arg_journal_type
= 0;
58 static const char *arg_machine
= NULL
;
59 static bool arg_merge
= false;
60 static int arg_follow
= -1;
61 static const char *arg_save_state
= NULL
;
63 static void close_fd_input(Uploader
*u
);
65 #define SERVER_ANSWER_KEEP 2048
67 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
69 #define easy_setopt(curl, opt, value, level, cmd) \
71 code = curl_easy_setopt(curl, opt, value); \
74 "curl_easy_setopt " #opt " failed: %s", \
75 curl_easy_strerror(code)); \
80 static size_t output_callback(char *buf
,
88 log_debug("The server answers (%zu bytes): %.*s",
89 size
*nmemb
, (int)(size
*nmemb
), buf
);
91 if (nmemb
&& !u
->answer
) {
92 u
->answer
= strndup(buf
, size
*nmemb
);
94 log_warning_errno(ENOMEM
, "Failed to store server answer (%zu bytes): %m",
101 static int check_cursor_updating(Uploader
*u
) {
102 _cleanup_free_
char *temp_path
= NULL
;
103 _cleanup_fclose_
FILE *f
= NULL
;
109 r
= mkdir_parents(u
->state_file
, 0755);
111 return log_error_errno(r
, "Cannot create parent directory of state file %s: %m",
114 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
116 return log_error_errno(r
, "Cannot save state to %s: %m",
123 static int update_cursor_state(Uploader
*u
) {
124 _cleanup_free_
char *temp_path
= NULL
;
125 _cleanup_fclose_
FILE *f
= NULL
;
128 if (!u
->state_file
|| !u
->last_cursor
)
131 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
136 "# This is private data. Do not parse.\n"
140 r
= fflush_and_check(f
);
144 if (rename(temp_path
, u
->state_file
) < 0) {
153 (void) unlink(temp_path
);
155 (void) unlink(u
->state_file
);
157 return log_error_errno(r
, "Failed to save state %s: %m", u
->state_file
);
160 static int load_cursor_state(Uploader
*u
) {
166 r
= parse_env_file(u
->state_file
, NEWLINE
,
167 "LAST_CURSOR", &u
->last_cursor
,
171 log_debug("State file %s is not present.", u
->state_file
);
173 return log_error_errno(r
, "Failed to read state file %s: %m",
176 log_debug("Last cursor was %s", u
->last_cursor
);
183 int start_upload(Uploader
*u
,
184 size_t (*input_callback
)(void *ptr
,
192 assert(input_callback
);
195 struct curl_slist
*h
;
197 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
201 h
= curl_slist_append(h
, "Transfer-Encoding: chunked");
203 curl_slist_free_all(h
);
207 h
= curl_slist_append(h
, "Accept: text/plain");
209 curl_slist_free_all(h
);
219 curl
= curl_easy_init();
221 log_error("Call to curl_easy_init failed.");
225 /* tell it to POST to the URL */
226 easy_setopt(curl
, CURLOPT_POST
, 1L,
227 LOG_ERR
, return -EXFULL
);
229 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
230 LOG_ERR
, return -EXFULL
);
232 /* set where to write to */
233 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
234 LOG_ERR
, return -EXFULL
);
236 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
237 LOG_ERR
, return -EXFULL
);
239 /* set where to read from */
240 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
241 LOG_ERR
, return -EXFULL
);
243 easy_setopt(curl
, CURLOPT_READDATA
, data
,
244 LOG_ERR
, return -EXFULL
);
246 /* use our special own mime type and chunked transfer */
247 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
248 LOG_ERR
, return -EXFULL
);
250 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
))
251 /* enable verbose for easier tracing */
252 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
254 easy_setopt(curl
, CURLOPT_USERAGENT
,
255 "systemd-journal-upload " PACKAGE_STRING
,
258 if (arg_key
|| startswith(u
->url
, "https://")) {
259 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
260 LOG_ERR
, return -EXFULL
);
261 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
262 LOG_ERR
, return -EXFULL
);
265 if (streq_ptr(arg_trust
, "all"))
266 easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 0,
267 LOG_ERR
, return -EUCLEAN
);
268 else if (arg_trust
|| startswith(u
->url
, "https://"))
269 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
270 LOG_ERR
, return -EXFULL
);
272 if (arg_key
|| arg_trust
)
273 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
278 /* truncate the potential old error message */
285 /* upload to this place */
286 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
288 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
289 curl_easy_strerror(code
));
298 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
304 assert(nmemb
<= SSIZE_MAX
/ size
);
309 r
= read(u
->input
, buf
, size
* nmemb
);
310 log_debug("%s: allowed %zu, read %zd", __func__
, size
*nmemb
, r
);
315 u
->uploading
= false;
317 log_debug("Reached EOF");
321 log_error_errno(errno
, "Aborting transfer after read error on input: %m.");
322 return CURL_READFUNC_ABORT
;
326 static void close_fd_input(Uploader
*u
) {
330 close_nointr(u
->input
);
335 static int dispatch_fd_input(sd_event_source
*event
,
344 if (revents
& EPOLLHUP
) {
345 log_debug("Received HUP");
350 if (!(revents
& EPOLLIN
)) {
351 log_warning("Unexpected poll event %"PRIu32
".", revents
);
356 log_warning("dispatch_fd_input called when uploading, ignoring.");
360 return start_upload(u
, fd_input_callback
, u
);
363 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
366 if (streq(filename
, "-"))
369 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
371 return log_error_errno(errno
, "Failed to open %s: %m", filename
);
377 r
= sd_event_add_io(u
->events
, &u
->input_event
,
378 fd
, EPOLLIN
, dispatch_fd_input
, u
);
380 if (r
!= -EPERM
|| arg_follow
> 0)
381 return log_error_errno(r
, "Failed to register input event: %m");
383 /* Normal files should just be consumed without polling. */
384 r
= start_upload(u
, fd_input_callback
, u
);
391 static int dispatch_sigterm(sd_event_source
*event
,
392 const struct signalfd_siginfo
*si
,
394 Uploader
*u
= userdata
;
398 log_received_signal(LOG_INFO
, si
);
401 close_journal_input(u
);
403 sd_event_exit(u
->events
, 0);
407 static int setup_signals(Uploader
*u
) {
412 assert_se(sigprocmask_many(SIG_SETMASK
, NULL
, SIGINT
, SIGTERM
, -1) >= 0);
414 r
= sd_event_add_signal(u
->events
, &u
->sigterm_event
, SIGTERM
, dispatch_sigterm
, u
);
418 r
= sd_event_add_signal(u
->events
, &u
->sigint_event
, SIGINT
, dispatch_sigterm
, u
);
425 static int setup_uploader(Uploader
*u
, const char *url
, const char *state_file
) {
427 const char *host
, *proto
= "";
432 memzero(u
, sizeof(Uploader
));
435 if (!(host
= startswith(url
, "http://")) && !(host
= startswith(url
, "https://"))) {
440 if (strchr(host
, ':'))
441 u
->url
= strjoin(proto
, url
, "/upload");
448 while (x
> 0 && t
[x
- 1] == '/')
451 u
->url
= strjoin(proto
, t
, ":" STRINGIFY(DEFAULT_PORT
), "/upload");
456 u
->state_file
= state_file
;
458 r
= sd_event_default(&u
->events
);
460 return log_error_errno(r
, "sd_event_default failed: %m");
462 r
= setup_signals(u
);
464 return log_error_errno(r
, "Failed to set up signals: %m");
466 (void) sd_watchdog_enabled(false, &u
->watchdog_usec
);
468 return load_cursor_state(u
);
471 static void destroy_uploader(Uploader
*u
) {
474 curl_easy_cleanup(u
->easy
);
475 curl_slist_free_all(u
->header
);
478 free(u
->last_cursor
);
479 free(u
->current_cursor
);
483 u
->input_event
= sd_event_source_unref(u
->input_event
);
486 close_journal_input(u
);
488 sd_event_source_unref(u
->sigterm_event
);
489 sd_event_source_unref(u
->sigint_event
);
490 sd_event_unref(u
->events
);
493 static int perform_upload(Uploader
*u
) {
499 u
->watchdog_timestamp
= now(CLOCK_MONOTONIC
);
500 code
= curl_easy_perform(u
->easy
);
503 log_error("Upload to %s failed: %.*s",
504 u
->url
, (int) sizeof(u
->error
), u
->error
);
506 log_error("Upload to %s failed: %s",
507 u
->url
, curl_easy_strerror(code
));
511 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
513 log_error("Failed to retrieve response code: %s",
514 curl_easy_strerror(code
));
519 log_error("Upload to %s failed with code %ld: %s",
520 u
->url
, status
, strna(u
->answer
));
522 } else if (status
< 200) {
523 log_error("Upload to %s finished with unexpected code %ld: %s",
524 u
->url
, status
, strna(u
->answer
));
527 log_debug("Upload finished successfully with code %ld: %s",
528 status
, strna(u
->answer
));
530 free_and_replace(u
->last_cursor
, u
->current_cursor
);
532 return update_cursor_state(u
);
535 static int parse_config(void) {
536 const ConfigTableItem items
[] = {
537 { "Upload", "URL", config_parse_string
, 0, &arg_url
},
538 { "Upload", "ServerKeyFile", config_parse_path
, 0, &arg_key
},
539 { "Upload", "ServerCertificateFile", config_parse_path
, 0, &arg_cert
},
540 { "Upload", "TrustedCertificateFile", config_parse_path
, 0, &arg_trust
},
543 return config_parse_many_nulstr(PKGSYSCONFDIR
"/journal-upload.conf",
544 CONF_PATHS_NULSTR("systemd/journal-upload.conf.d"),
545 "Upload\0", config_item_table_lookup
, items
,
549 static void help(void) {
550 printf("%s -u URL {FILE|-}...\n\n"
551 "Upload journal events to a remote server.\n\n"
552 " -h --help Show this help\n"
553 " --version Show package version\n"
554 " -u --url=URL Upload to this address (default port "
555 STRINGIFY(DEFAULT_PORT
) ")\n"
556 " --key=FILENAME Specify key in PEM format (default:\n"
557 " \"" PRIV_KEY_FILE
"\")\n"
558 " --cert=FILENAME Specify certificate in PEM format (default:\n"
559 " \"" CERT_FILE
"\")\n"
560 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
561 " \"" TRUST_FILE
"\")\n"
562 " --system Use the system journal\n"
563 " --user Use the user journal for the current user\n"
564 " -m --merge Use all available journals\n"
565 " -M --machine=CONTAINER Operate on local container\n"
566 " -D --directory=PATH Use journal files from directory\n"
567 " --file=PATH Use this journal file\n"
568 " --cursor=CURSOR Start at the specified cursor\n"
569 " --after-cursor=CURSOR Start after the specified cursor\n"
570 " --follow[=BOOL] Do [not] wait for input\n"
571 " --save-state[=FILE] Save uploaded cursors (default \n"
573 " -h --help Show this help and exit\n"
574 " --version Print version string and exit\n"
575 , program_invocation_short_name
);
578 static int parse_argv(int argc
, char *argv
[]) {
593 static const struct option options
[] = {
594 { "help", no_argument
, NULL
, 'h' },
595 { "version", no_argument
, NULL
, ARG_VERSION
},
596 { "url", required_argument
, NULL
, 'u' },
597 { "key", required_argument
, NULL
, ARG_KEY
},
598 { "cert", required_argument
, NULL
, ARG_CERT
},
599 { "trust", required_argument
, NULL
, ARG_TRUST
},
600 { "system", no_argument
, NULL
, ARG_SYSTEM
},
601 { "user", no_argument
, NULL
, ARG_USER
},
602 { "merge", no_argument
, NULL
, 'm' },
603 { "machine", required_argument
, NULL
, 'M' },
604 { "directory", required_argument
, NULL
, 'D' },
605 { "file", required_argument
, NULL
, ARG_FILE
},
606 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
607 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
608 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
609 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
620 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
631 log_error("cannot use more than one --url");
640 log_error("cannot use more than one --key");
649 log_error("cannot use more than one --cert");
658 log_error("cannot use more than one --trust");
666 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
670 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
679 log_error("cannot use more than one --machine/-M");
683 arg_machine
= optarg
;
688 log_error("cannot use more than one --directory/-D");
692 arg_directory
= optarg
;
696 r
= glob_extend(&arg_file
, optarg
);
698 return log_error_errno(r
, "Failed to add paths: %m");
703 log_error("cannot use more than one --cursor/--after-cursor");
710 case ARG_AFTER_CURSOR
:
712 log_error("cannot use more than one --cursor/--after-cursor");
717 arg_after_cursor
= true;
722 r
= parse_boolean(optarg
);
724 log_error("Failed to parse --follow= parameter.");
735 arg_save_state
= optarg
?: STATE_FILE
;
739 log_error("Unknown option %s.", argv
[optind
-1]);
743 log_error("Missing argument to %s.", argv
[optind
-1]);
747 assert_not_reached("Unhandled option code.");
751 log_error("Required --url/-u option missing.");
755 if (!!arg_key
!= !!arg_cert
) {
756 log_error("Options --key and --cert must be used together.");
760 if (optind
< argc
&& (arg_directory
|| arg_file
|| arg_machine
|| arg_journal_type
)) {
761 log_error("Input arguments make no sense with journal input.");
768 static int open_journal(sd_journal
**j
) {
772 r
= sd_journal_open_directory(j
, arg_directory
, arg_journal_type
);
774 r
= sd_journal_open_files(j
, (const char**) arg_file
, 0);
775 else if (arg_machine
)
776 r
= sd_journal_open_container(j
, arg_machine
, 0);
778 r
= sd_journal_open(j
, !arg_merge
*SD_JOURNAL_LOCAL_ONLY
+ arg_journal_type
);
780 log_error_errno(r
, "Failed to open %s: %m",
781 arg_directory
? arg_directory
: arg_file
? "files" : "journal");
785 int main(int argc
, char **argv
) {
790 log_show_color(true);
791 log_parse_environment();
797 r
= parse_argv(argc
, argv
);
803 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
807 sd_event_set_watchdog(u
.events
, true);
809 r
= check_cursor_updating(&u
);
813 log_debug("%s running as pid "PID_FMT
,
814 program_invocation_short_name
, getpid_cached());
816 use_journal
= optind
>= argc
;
819 r
= open_journal(&j
);
822 r
= open_journal_for_upload(&u
, j
,
823 arg_cursor
?: u
.last_cursor
,
824 arg_cursor
? arg_after_cursor
: true,
832 "STATUS=Processing input...");
835 r
= sd_event_get_state(u
.events
);
838 if (r
== SD_EVENT_FINISHED
)
845 r
= check_journal_input(&u
);
846 } else if (u
.input
< 0 && !use_journal
) {
850 log_debug("Using %s as input.", argv
[optind
]);
851 r
= open_file_for_upload(&u
, argv
[optind
++]);
857 r
= perform_upload(&u
);
862 r
= sd_event_run(u
.events
, u
.timeout
);
864 log_error_errno(r
, "Failed to run event loop: %m");
872 "STATUS=Shutting down...");
874 destroy_uploader(&u
);
877 return r
>= 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;