chiark / gitweb /
264f915a789d825b76b3814253f0a0cc00c0b02f
[elogind.git] / src / journal-remote / journal-upload.c
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 "fileio.h"
34 #include "journal-upload.h"
35
36 static const char* arg_url;
37
38 static void close_fd_input(Uploader *u);
39
40 static const char *arg_key = NULL;
41 static const char *arg_cert = NULL;
42 static const char *arg_trust = NULL;
43
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;
53
54 #define SERVER_ANSWER_KEEP 2048
55
56 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
57
58 #define easy_setopt(curl, opt, value, level, cmd)                       \
59         {                                                               \
60                 code = curl_easy_setopt(curl, opt, value);              \
61                 if (code) {                                             \
62                         log_full(level,                                 \
63                                  "curl_easy_setopt " #opt " failed: %s", \
64                                   curl_easy_strerror(code));            \
65                         cmd;                                            \
66                 }                                                       \
67         }
68
69 static size_t output_callback(char *buf,
70                               size_t size,
71                               size_t nmemb,
72                               void *userp) {
73         Uploader *u = userp;
74
75         assert(u);
76
77         log_debug("The server answers (%zu bytes): %.*s",
78                   size*nmemb, (int)(size*nmemb), buf);
79
80         if (nmemb && !u->answer) {
81                 u->answer = strndup(buf, size*nmemb);
82                 if (!u->answer)
83                         log_warning("Failed to store server answer (%zu bytes): %s",
84                                     size*nmemb, strerror(ENOMEM));
85         }
86
87         return size * nmemb;
88 }
89
90 static int update_cursor_state(Uploader *u) {
91         _cleanup_free_ char *temp_path = NULL;
92         _cleanup_fclose_ FILE *f = NULL;
93         int r;
94
95         if (!u->state_file || !u->last_cursor)
96                 return 0;
97
98         r = fopen_temporary(u->state_file, &f, &temp_path);
99         if (r < 0)
100                 goto finish;
101
102         fprintf(f,
103                 "# This is private data. Do not parse.\n"
104                 "LAST_CURSOR=%s\n",
105                 u->last_cursor);
106
107         fflush(f);
108
109         if (ferror(f) || rename(temp_path, u->state_file) < 0) {
110                 r = -errno;
111                 unlink(u->state_file);
112                 unlink(temp_path);
113         }
114
115 finish:
116         if (r < 0)
117                 log_error("Failed to save state %s: %s", u->state_file, strerror(-r));
118
119         return r;
120 }
121
122 static int load_cursor_state(Uploader *u) {
123         int r;
124
125         if (!u->state_file)
126                 return 0;
127
128         r = parse_env_file(u->state_file, NEWLINE,
129                            "LAST_CURSOR",  &u->last_cursor,
130                            NULL);
131
132         if (r < 0 && r != -ENOENT) {
133                 log_error("Failed to read state file %s: %s",
134                           u->state_file, strerror(-r));
135                 return r;
136         }
137
138         return 0;
139 }
140
141
142
143 int start_upload(Uploader *u,
144                  size_t (*input_callback)(void *ptr,
145                                           size_t size,
146                                           size_t nmemb,
147                                           void *userdata),
148                  void *data) {
149         CURLcode code;
150
151         assert(u);
152         assert(input_callback);
153
154         if (!u->header) {
155                 struct curl_slist *h;
156
157                 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
158                 if (!h)
159                         return log_oom();
160
161                 h = curl_slist_append(h, "Transfer-Encoding: chunked");
162                 if (!h) {
163                         curl_slist_free_all(h);
164                         return log_oom();
165                 }
166
167                 h = curl_slist_append(h, "Accept: text/plain");
168                 if (!h) {
169                         curl_slist_free_all(h);
170                         return log_oom();
171                 }
172
173                 u->header = h;
174         }
175
176         if (!u->easy) {
177                 CURL *curl;
178
179                 curl = curl_easy_init();
180                 if (!curl) {
181                         log_error("Call to curl_easy_init failed.");
182                         return -ENOSR;
183                 }
184
185                 /* tell it to POST to the URL */
186                 easy_setopt(curl, CURLOPT_POST, 1L,
187                             LOG_ERR, return -EXFULL);
188
189                 easy_setopt(curl, CURLOPT_ERRORBUFFER, &u->error,
190                             LOG_ERR, return -EXFULL);
191
192                 /* set where to write to */
193                 easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
194                             LOG_ERR, return -EXFULL);
195
196                 easy_setopt(curl, CURLOPT_WRITEDATA, data,
197                             LOG_ERR, return -EXFULL);
198
199                 /* set where to read from */
200                 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
201                             LOG_ERR, return -EXFULL);
202
203                 easy_setopt(curl, CURLOPT_READDATA, data,
204                             LOG_ERR, return -EXFULL);
205
206                 /* use our special own mime type and chunked transfer */
207                 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
208                             LOG_ERR, return -EXFULL);
209
210                 /* enable verbose for easier tracing */
211                 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
212
213                 easy_setopt(curl, CURLOPT_USERAGENT,
214                             "systemd-journal-upload " PACKAGE_STRING,
215                             LOG_WARNING, );
216
217                 if (arg_key) {
218                         assert(arg_cert);
219
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);
224                 }
225
226                 if (arg_trust)
227                         easy_setopt(curl, CURLOPT_CAINFO, arg_trust,
228                                     LOG_ERR, return -EXFULL);
229
230                 if (arg_key || arg_trust)
231                         easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
232                                     LOG_WARNING, );
233
234                 u->easy = curl;
235         } else {
236                 /* truncate the potential old error message */
237                 u->error[0] = '\0';
238
239                 free(u->answer);
240                 u->answer = 0;
241         }
242
243         /* upload to this place */
244         code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
245         if (code) {
246                 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
247                           curl_easy_strerror(code));
248                 return -EXFULL;
249         }
250
251         u->uploading = true;
252
253         return 0;
254 }
255
256 static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
257         Uploader *u = userp;
258
259         ssize_t r;
260
261         assert(u);
262         assert(nmemb <= SSIZE_MAX / size);
263
264         if (u->input < 0)
265                 return 0;
266
267         r = read(u->input, buf, size * nmemb);
268         log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
269
270         if (r > 0)
271                 return r;
272
273         u->uploading = false;
274         if (r == 0) {
275                 log_debug("Reached EOF");
276                 close_fd_input(u);
277                 return 0;
278         } else {
279                 log_error("Aborting transfer after read error on input: %m.");
280                 return CURL_READFUNC_ABORT;
281         }
282 }
283
284 static void close_fd_input(Uploader *u) {
285         assert(u);
286
287         if (u->input >= 0)
288                 close_nointr(u->input);
289         u->input = -1;
290         u->timeout = 0;
291 }
292
293 static int dispatch_fd_input(sd_event_source *event,
294                              int fd,
295                              uint32_t revents,
296                              void *userp) {
297         Uploader *u = userp;
298
299         assert(u);
300         assert(revents & EPOLLIN);
301         assert(fd >= 0);
302
303         if (u->uploading) {
304                 log_warning("dispatch_fd_input called when uploading, ignoring.");
305                 return 0;
306         }
307
308         return start_upload(u, fd_input_callback, u);
309 }
310
311 static int open_file_for_upload(Uploader *u, const char *filename) {
312         int fd, r;
313
314         if (streq(filename, "-"))
315                 fd = STDIN_FILENO;
316         else {
317                 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
318                 if (fd < 0) {
319                         log_error("Failed to open %s: %m", filename);
320                         return -errno;
321                 }
322         }
323
324         u->input = fd;
325
326         if (arg_follow) {
327                 r = sd_event_add_io(u->events, &u->input_event,
328                                     fd, EPOLLIN, dispatch_fd_input, u);
329                 if (r < 0) {
330                         if (r != -EPERM || arg_follow > 0) {
331                                 log_error("Failed to register input event: %s", strerror(-r));
332                                 return r;
333                         }
334
335                         /* Normal files should just be consumed without polling. */
336                         r = start_upload(u, fd_input_callback, u);
337                 }
338         }
339
340         return r;
341 }
342
343 static int dispatch_sigterm(sd_event_source *event,
344                             const struct signalfd_siginfo *si,
345                             void *userdata) {
346         Uploader *u = userdata;
347
348         assert(u);
349
350         log_received_signal(LOG_INFO, si);
351
352         close_fd_input(u);
353         close_journal_input(u);
354
355         sd_event_exit(u->events, 0);
356         return 0;
357 }
358
359 static int setup_signals(Uploader *u) {
360         sigset_t mask;
361         int r;
362
363         assert(u);
364
365         assert_se(sigemptyset(&mask) == 0);
366         sigset_add_many(&mask, SIGINT, SIGTERM, -1);
367         assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
368
369         r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u);
370         if (r < 0)
371                 return r;
372
373         r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u);
374         if (r < 0)
375                 return r;
376
377         return 0;
378 }
379
380 static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
381         int r;
382
383         assert(u);
384         assert(url);
385
386         memzero(u, sizeof(Uploader));
387         u->input = -1;
388
389         u->url = url;
390         u->state_file = state_file;
391
392         r = sd_event_default(&u->events);
393         if (r < 0) {
394                 log_error("sd_event_default failed: %s", strerror(-r));
395                 return r;
396         }
397
398         r = setup_signals(u);
399         if (r < 0) {
400                 log_error("Failed to set up signals: %s", strerror(-r));
401                 return r;
402         }
403
404         return load_cursor_state(u);
405 }
406
407 static void destroy_uploader(Uploader *u) {
408         assert(u);
409
410         curl_easy_cleanup(u->easy);
411         curl_slist_free_all(u->header);
412         free(u->answer);
413
414         free(u->last_cursor);
415         free(u->current_cursor);
416
417         u->input_event = sd_event_source_unref(u->input_event);
418
419         close_fd_input(u);
420         close_journal_input(u);
421
422         sd_event_source_unref(u->sigterm_event);
423         sd_event_source_unref(u->sigint_event);
424         sd_event_unref(u->events);
425 }
426
427 static int perform_upload(Uploader *u) {
428         CURLcode code;
429         long status;
430
431         assert(u);
432
433         code = curl_easy_perform(u->easy);
434         if (code) {
435                 log_error("Upload to %s failed: %.*s",
436                           u->url,
437                           u->error[0] ? (int) sizeof(u->error) : INT_MAX,
438                           u->error[0] ? u->error : curl_easy_strerror(code));
439                 return -EIO;
440         }
441
442         code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
443         if (code) {
444                 log_error("Failed to retrieve response code: %s",
445                           curl_easy_strerror(code));
446                 return -EUCLEAN;
447         }
448
449         if (status >= 300) {
450                 log_error("Upload to %s failed with code %lu: %s",
451                           u->url, status, strna(u->answer));
452                 return -EIO;
453         } else if (status < 200) {
454                 log_error("Upload to %s finished with unexpected code %lu: %s",
455                           u->url, status, strna(u->answer));
456                 return -EIO;
457         } else
458                 log_debug("Upload finished successfully with code %lu: %s",
459                           status, strna(u->answer));
460
461         free(u->last_cursor);
462         u->last_cursor = u->current_cursor;
463         u->current_cursor = NULL;
464
465         return update_cursor_state(u);
466 }
467
468 static void help(void) {
469         printf("%s -u URL {FILE|-}...\n\n"
470                "Upload journal events to a remote server.\n\n"
471                "Options:\n"
472                "  --url=URL                Upload to this address\n"
473                "  --key=FILENAME           Specify key in PEM format\n"
474                "  --cert=FILENAME          Specify certificate in PEM format\n"
475                "  --trust=FILENAME         Specify CA certificate in PEM format\n"
476                "     --system              Use the system journal\n"
477                "     --user                Use the user journal for the current user\n"
478                "  -m --merge               Use  all available journals\n"
479                "  -M --machine=CONTAINER   Operate on local container\n"
480                "  -D --directory=PATH      Use journal files from directory\n"
481                "     --file=PATH           Use this journal file\n"
482                "  --cursor=CURSOR          Start at the specified cursor\n"
483                "  --after-cursor=CURSOR    Start after the specified cursor\n"
484                "  --[no-]follow            Do [not] wait for input\n"
485                "  --save-state[=FILE]      Save uploaded cursors (default \n"
486                "                           " STATE_FILE ")\n"
487                "  -h --help                Show this help and exit\n"
488                "  --version                Print version string and exit\n"
489                , program_invocation_short_name);
490 }
491
492 static int parse_argv(int argc, char *argv[]) {
493         enum {
494                 ARG_VERSION = 0x100,
495                 ARG_KEY,
496                 ARG_CERT,
497                 ARG_TRUST,
498                 ARG_USER,
499                 ARG_SYSTEM,
500                 ARG_FILE,
501                 ARG_CURSOR,
502                 ARG_AFTER_CURSOR,
503                 ARG_FOLLOW,
504                 ARG_NO_FOLLOW,
505                 ARG_SAVE_STATE,
506         };
507
508         static const struct option options[] = {
509                 { "help",         no_argument,       NULL, 'h'                },
510                 { "version",      no_argument,       NULL, ARG_VERSION        },
511                 { "url",          required_argument, NULL, 'u'                },
512                 { "key",          required_argument, NULL, ARG_KEY            },
513                 { "cert",         required_argument, NULL, ARG_CERT           },
514                 { "trust",        required_argument, NULL, ARG_TRUST          },
515                 { "system",       no_argument,       NULL, ARG_SYSTEM         },
516                 { "user",         no_argument,       NULL, ARG_USER           },
517                 { "merge",        no_argument,       NULL, 'm'                },
518                 { "machine",      required_argument, NULL, 'M'                },
519                 { "directory",    required_argument, NULL, 'D'                },
520                 { "file",         required_argument, NULL, ARG_FILE           },
521                 { "cursor",       required_argument, NULL, ARG_CURSOR         },
522                 { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR   },
523                 { "follow",       no_argument,       NULL, ARG_FOLLOW         },
524                 { "no-follow",    no_argument,       NULL, ARG_NO_FOLLOW      },
525                 { "save-state",   optional_argument, NULL, ARG_SAVE_STATE     },
526                 {}
527         };
528
529         int c, r;
530
531         assert(argc >= 0);
532         assert(argv);
533
534         opterr = 0;
535
536         while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
537                 switch(c) {
538                 case 'h':
539                         help();
540                         return 0 /* done */;
541
542                 case ARG_VERSION:
543                         puts(PACKAGE_STRING);
544                         puts(SYSTEMD_FEATURES);
545                         return 0 /* done */;
546
547                 case 'u':
548                         if (arg_url) {
549                                 log_error("cannot use more than one --url");
550                                 return -EINVAL;
551                         }
552
553                         arg_url = optarg;
554                         break;
555
556                 case ARG_KEY:
557                         if (arg_key) {
558                                 log_error("cannot use more than one --key");
559                                 return -EINVAL;
560                         }
561
562                         arg_key = optarg;
563                         break;
564
565                 case ARG_CERT:
566                         if (arg_cert) {
567                                 log_error("cannot use more than one --cert");
568                                 return -EINVAL;
569                         }
570
571                         arg_cert = optarg;
572                         break;
573
574                 case ARG_TRUST:
575                         if (arg_trust) {
576                                 log_error("cannot use more than one --trust");
577                                 return -EINVAL;
578                         }
579
580                         arg_trust = optarg;
581                         break;
582
583                 case ARG_SYSTEM:
584                         arg_journal_type |= SD_JOURNAL_SYSTEM;
585                         break;
586
587                 case ARG_USER:
588                         arg_journal_type |= SD_JOURNAL_CURRENT_USER;
589                         break;
590
591                 case 'm':
592                         arg_merge = true;
593                         break;
594
595                 case 'M':
596                         if (arg_machine) {
597                                 log_error("cannot use more than one --machine/-M");
598                                 return -EINVAL;
599                         }
600
601                         arg_machine = optarg;
602                         break;
603
604                 case 'D':
605                         if (arg_directory) {
606                                 log_error("cannot use more than one --directory/-D");
607                                 return -EINVAL;
608                         }
609
610                         arg_directory = optarg;
611                         break;
612
613                 case ARG_FILE:
614                         r = glob_extend(&arg_file, optarg);
615                         if (r < 0) {
616                                 log_error("Failed to add paths: %s", strerror(-r));
617                                 return r;
618                         };
619                         break;
620
621                 case ARG_CURSOR:
622                         if (arg_cursor) {
623                                 log_error("cannot use more than one --cursor/--after-cursor");
624                                 return -EINVAL;
625                         }
626
627                         arg_cursor = optarg;
628                         break;
629
630                 case ARG_AFTER_CURSOR:
631                         if (arg_cursor) {
632                                 log_error("cannot use more than one --cursor/--after-cursor");
633                                 return -EINVAL;
634                         }
635
636                         arg_cursor = optarg;
637                         arg_after_cursor = true;
638                         break;
639
640                 case ARG_FOLLOW:
641                         arg_follow = true;
642                         break;
643
644                 case ARG_NO_FOLLOW:
645                         arg_follow = false;
646                         break;
647
648                 case ARG_SAVE_STATE:
649                         arg_save_state = optarg ?: STATE_FILE;
650                         break;
651
652                 case '?':
653                         log_error("Unknown option %s.", argv[optind-1]);
654                         return -EINVAL;
655
656                 case ':':
657                         log_error("Missing argument to %s.", argv[optind-1]);
658                         return -EINVAL;
659
660                 default:
661                         assert_not_reached("Unhandled option code.");
662                 }
663
664         if (!arg_url) {
665                 log_error("Required --url/-u option missing.");
666                 return -EINVAL;
667         }
668
669         if (!!arg_key != !!arg_cert) {
670                 log_error("Options --key and --cert must be used together.");
671                 return -EINVAL;
672         }
673
674         if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
675                 log_error("Input arguments make no sense with journal input.");
676                 return -EINVAL;
677         }
678
679         return 1;
680 }
681
682 static int open_journal(sd_journal **j) {
683         int r;
684
685         if (arg_directory)
686                 r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
687         else if (arg_file)
688                 r = sd_journal_open_files(j, (const char**) arg_file, 0);
689         else if (arg_machine)
690                 r = sd_journal_open_container(j, arg_machine, 0);
691         else
692                 r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
693         if (r < 0)
694                 log_error("Failed to open %s: %s",
695                           arg_directory ? arg_directory : arg_file ? "files" : "journal",
696                           strerror(-r));
697         return r;
698 }
699
700 int main(int argc, char **argv) {
701         Uploader u;
702         int r;
703         bool use_journal;
704
705         log_show_color(true);
706         log_parse_environment();
707
708         r = parse_argv(argc, argv);
709         if (r <= 0)
710                 goto finish;
711
712         r = setup_uploader(&u, arg_url, arg_save_state);
713         if (r < 0)
714                 goto cleanup;
715
716         sd_event_set_watchdog(u.events, true);
717
718         log_debug("%s running as pid "PID_FMT,
719                   program_invocation_short_name, getpid());
720
721         use_journal = optind >= argc;
722         if (use_journal) {
723                 sd_journal *j;
724                 r = open_journal(&j);
725                 if (r < 0)
726                         goto finish;
727                 r = open_journal_for_upload(&u, j,
728                                             arg_cursor ?: u.last_cursor,
729                                             arg_cursor ? arg_after_cursor : true,
730                                             !!arg_follow);
731                 if (r < 0)
732                         goto finish;
733         }
734
735         sd_notify(false,
736                   "READY=1\n"
737                   "STATUS=Processing input...");
738
739         while (true) {
740                 if (use_journal) {
741                         if (!u.journal)
742                                 break;
743
744                         r = check_journal_input(&u);
745                 } else if (u.input < 0 && !use_journal) {
746                         if (optind >= argc)
747                                 break;
748
749                         log_debug("Using %s as input.", argv[optind]);
750                         r = open_file_for_upload(&u, argv[optind++]);
751                 }
752                 if (r < 0)
753                         goto cleanup;
754
755                 r = sd_event_get_state(u.events);
756                 if (r < 0)
757                         break;
758                 if (r == SD_EVENT_FINISHED)
759                         break;
760
761                 if (u.uploading) {
762                         r = perform_upload(&u);
763                         if (r < 0)
764                                 break;
765                 }
766
767                 r = sd_event_run(u.events, u.timeout);
768                 if (r < 0) {
769                         log_error("Failed to run event loop: %s", strerror(-r));
770                         break;
771                 }
772         }
773
774 cleanup:
775         sd_notify(false, "STATUS=Shutting down...");
776         destroy_uploader(&u);
777
778 finish:
779         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
780 }