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"
33 #include "journal-upload.h"
35 static const char* arg_url;
37 static void close_fd_input(Uploader *u);
39 static const char *arg_key = NULL;
40 static const char *arg_cert = NULL;
41 static const char *arg_trust = NULL;
43 static const char *arg_directory = NULL;
44 static char **arg_file = NULL;
45 static const char *arg_cursor = NULL;
46 static bool arg_after_cursor = false;
47 static int arg_journal_type = 0;
48 static const char *arg_machine = NULL;
49 static bool arg_merge = false;
50 static int arg_follow = -1;
52 #define SERVER_ANSWER_KEEP 2048
54 #define easy_setopt(curl, opt, value, level, cmd) \
56 code = curl_easy_setopt(curl, opt, value); \
59 "curl_easy_setopt " #opt " failed: %s", \
60 curl_easy_strerror(code)); \
65 static size_t output_callback(char *buf,
73 log_debug("The server answers (%zu bytes): %.*s",
74 size*nmemb, (int)(size*nmemb), buf);
76 if (nmemb && !u->answer) {
77 u->answer = strndup(buf, size*nmemb);
79 log_warning("Failed to store server answer (%zu bytes): %s",
80 size*nmemb, strerror(ENOMEM));
86 int start_upload(Uploader *u,
87 size_t (*input_callback)(void *ptr,
95 assert(input_callback);
100 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
104 h = curl_slist_append(h, "Transfer-Encoding: chunked");
106 curl_slist_free_all(h);
110 h = curl_slist_append(h, "Accept: text/plain");
112 curl_slist_free_all(h);
122 curl = curl_easy_init();
124 log_error("Call to curl_easy_init failed.");
128 /* tell it to POST to the URL */
129 easy_setopt(curl, CURLOPT_POST, 1L,
130 LOG_ERR, return -EXFULL);
132 easy_setopt(curl, CURLOPT_ERRORBUFFER, &u->error,
133 LOG_ERR, return -EXFULL);
135 /* set where to write to */
136 easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
137 LOG_ERR, return -EXFULL);
139 easy_setopt(curl, CURLOPT_WRITEDATA, data,
140 LOG_ERR, return -EXFULL);
142 /* set where to read from */
143 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
144 LOG_ERR, return -EXFULL);
146 easy_setopt(curl, CURLOPT_READDATA, data,
147 LOG_ERR, return -EXFULL);
149 /* use our special own mime type and chunked transfer */
150 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
151 LOG_ERR, return -EXFULL);
153 /* enable verbose for easier tracing */
154 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
156 easy_setopt(curl, CURLOPT_USERAGENT,
157 "systemd-journal-upload " PACKAGE_STRING,
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);
170 easy_setopt(curl, CURLOPT_CAINFO, arg_trust,
171 LOG_ERR, return -EXFULL);
173 if (arg_key || arg_trust)
174 easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
179 /* truncate the potential old error message */
186 /* upload to this place */
187 code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
189 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
190 curl_easy_strerror(code));
199 static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
205 assert(nmemb <= SSIZE_MAX / size);
210 r = read(u->input, buf, size * nmemb);
211 log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
216 u->uploading = false;
218 log_debug("Reached EOF");
222 log_error("Aborting transfer after read error on input: %m.");
223 return CURL_READFUNC_ABORT;
227 static void close_fd_input(Uploader *u) {
231 close_nointr(u->input);
236 static int dispatch_fd_input(sd_event_source *event,
243 assert(revents & EPOLLIN);
247 log_warning("dispatch_fd_input called when uploading, ignoring.");
251 return start_upload(u, fd_input_callback, u);
254 static int open_file_for_upload(Uploader *u, const char *filename) {
257 if (streq(filename, "-"))
260 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
262 log_error("Failed to open %s: %m", filename);
270 r = sd_event_add_io(u->events, &u->input_event,
271 fd, EPOLLIN, dispatch_fd_input, u);
273 if (r != -EPERM || arg_follow > 0) {
274 log_error("Failed to register input event: %s", strerror(-r));
278 /* Normal files should just be consumed without polling. */
279 r = start_upload(u, fd_input_callback, u);
286 static int setup_uploader(Uploader *u, const char *url) {
292 memzero(u, sizeof(Uploader));
297 r = sd_event_default(&u->events);
299 log_error("sd_event_default failed: %s", strerror(-r));
306 static void destroy_uploader(Uploader *u) {
309 curl_easy_cleanup(u->easy);
310 curl_slist_free_all(u->header);
313 free(u->last_cursor);
315 u->input_event = sd_event_source_unref(u->input_event);
318 close_journal_input(u);
320 sd_event_unref(u->events);
323 static int perform_upload(Uploader *u) {
329 code = curl_easy_perform(u->easy);
331 log_error("Upload to %s failed: %.*s",
333 u->error[0] ? (int) sizeof(u->error) : INT_MAX,
334 u->error[0] ? u->error : curl_easy_strerror(code));
338 code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
340 log_error("Failed to retrieve response code: %s",
341 curl_easy_strerror(code));
346 log_error("Upload to %s failed with code %lu: %s",
347 u->url, status, strna(u->answer));
349 } else if (status < 200) {
350 log_error("Upload to %s finished with unexpected code %lu: %s",
351 u->url, status, strna(u->answer));
354 log_debug("Upload finished successfully with code %lu: %s",
355 status, strna(u->answer));
359 static void help(void) {
360 printf("%s -u URL {FILE|-}...\n\n"
361 "Upload journal events to a remote server.\n\n"
363 " --url=URL Upload to this address\n"
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"
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"
376 " -h --help Show this help and exit\n"
377 " --version Print version string and exit\n"
378 , program_invocation_short_name);
381 static int parse_argv(int argc, char *argv[]) {
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' },
400 { "key", required_argument, NULL, ARG_KEY },
401 { "cert", required_argument, NULL, ARG_CERT },
402 { "trust", required_argument, NULL, ARG_TRUST },
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 },
423 while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
430 puts(PACKAGE_STRING);
431 puts(SYSTEMD_FEATURES);
436 log_error("cannot use more than one --url");
445 log_error("cannot use more than one --key");
454 log_error("cannot use more than one --cert");
463 log_error("cannot use more than one --trust");
471 arg_journal_type |= SD_JOURNAL_SYSTEM;
475 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
484 log_error("cannot use more than one --machine/-M");
488 arg_machine = optarg;
493 log_error("cannot use more than one --directory/-D");
497 arg_directory = optarg;
501 r = glob_extend(&arg_file, optarg);
503 log_error("Failed to add paths: %s", strerror(-r));
510 log_error("cannot use more than one --cursor/--after-cursor");
517 case ARG_AFTER_CURSOR:
519 log_error("cannot use more than one --cursor/--after-cursor");
524 arg_after_cursor = true;
536 log_error("Unknown option %s.", argv[optind-1]);
540 log_error("Missing argument to %s.", argv[optind-1]);
544 assert_not_reached("Unhandled option code.");
548 log_error("Required --url/-u option missing.");
552 if (!!arg_key != !!arg_cert) {
553 log_error("Options --key and --cert must be used together.");
557 if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
558 log_error("Input arguments make no sense with journal input.");
565 static int open_journal(sd_journal **j) {
569 r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
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);
575 r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
577 log_error("Failed to open %s: %s",
578 arg_directory ? arg_directory : arg_file ? "files" : "journal",
583 int main(int argc, char **argv) {
588 log_show_color(true);
589 log_parse_environment();
591 r = parse_argv(argc, argv);
595 r = setup_uploader(&u, arg_url);
599 log_debug("%s running as pid "PID_FMT,
600 program_invocation_short_name, getpid());
602 use_journal = optind >= argc;
605 r = open_journal(&j);
608 r = open_journal_for_upload(&u, j,
609 arg_cursor, arg_after_cursor,
617 "STATUS=Processing input...");
624 r = check_journal_input(&u);
625 } else if (u.input < 0 && !use_journal) {
629 log_debug("Using %s as input.", argv[optind]);
630 r = open_file_for_upload(&u, argv[optind++]);
635 r = sd_event_get_state(u.events);
638 if (r == SD_EVENT_FINISHED)
642 r = perform_upload(&u);
647 r = sd_event_run(u.events, u.timeout);
649 log_error("Failed to run event loop: %s", strerror(-r));
655 sd_notify(false, "STATUS=Shutting down...");
656 destroy_uploader(&u);
659 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;