chiark / gitweb /
journal-upload: use journal as the source
[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 "journal-upload.h"
34
35 static const char* arg_url;
36
37 static void close_fd_input(Uploader *u);
38
39 static const char *arg_key = NULL;
40 static const char *arg_cert = NULL;
41 static const char *arg_trust = NULL;
42
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;
51
52 #define SERVER_ANSWER_KEEP 2048
53
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
65 static 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
86 int 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
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
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
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
177                 u->easy = curl;
178         } else {
179                 /* truncate the potential old error message */
180                 u->error[0] = '\0';
181
182                 free(u->answer);
183                 u->answer = 0;
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
199 static 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
227 static void close_fd_input(Uploader *u) {
228         assert(u);
229
230         if (u->input >= 0)
231                 close_nointr(u->input);
232         u->input = -1;
233         u->timeout = 0;
234 }
235
236 static 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
254 static 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
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                         }
277
278                         /* Normal files should just be consumed without polling. */
279                         r = start_upload(u, fd_input_callback, u);
280                 }
281         }
282
283         return r;
284 }
285
286 static 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
306 static void destroy_uploader(Uploader *u) {
307         assert(u);
308
309         curl_easy_cleanup(u->easy);
310         curl_slist_free_all(u->header);
311         free(u->answer);
312
313         free(u->last_cursor);
314
315         u->input_event = sd_event_source_unref(u->input_event);
316
317         close_fd_input(u);
318         close_journal_input(u);
319
320         sd_event_unref(u->events);
321 }
322
323 static 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
359 static 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"
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);
379 }
380
381 static int parse_argv(int argc, char *argv[]) {
382         enum {
383                 ARG_VERSION = 0x100,
384                 ARG_KEY,
385                 ARG_CERT,
386                 ARG_TRUST,
387                 ARG_USER,
388                 ARG_SYSTEM,
389                 ARG_FILE,
390                 ARG_CURSOR,
391                 ARG_AFTER_CURSOR,
392                 ARG_FOLLOW,
393                 ARG_NO_FOLLOW,
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'                },
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      },
413                 {}
414         };
415
416         int c, r;
417
418         assert(argc >= 0);
419         assert(argv);
420
421         opterr = 0;
422
423         while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
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
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
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
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
552         if (!!arg_key != !!arg_cert) {
553                 log_error("Options --key and --cert must be used together.");
554                 return -EINVAL;
555         }
556
557         if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
558                 log_error("Input arguments make no sense with journal input.");
559                 return -EINVAL;
560         }
561
562         return 1;
563 }
564
565 static 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 }
582
583 int main(int argc, char **argv) {
584         Uploader u;
585         int r;
586         bool use_journal;
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());
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
615         sd_notify(false,
616                   "READY=1\n"
617                   "STATUS=Processing input...");
618
619         while (true) {
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) {
626                         if (optind >= argc)
627                                 break;
628
629                         log_debug("Using %s as input.", argv[optind]);
630                         r = open_file_for_upload(&u, argv[optind++]);
631                 }
632                 if (r < 0)
633                         goto cleanup;
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) {
642                         r = perform_upload(&u);
643                         if (r < 0)
644                                 break;
645                 }
646
647                 r = sd_event_run(u.events, u.timeout);
648                 if (r < 0) {
649                         log_error("Failed to run event loop: %s", strerror(-r));
650                         break;
651                 }
652         }
653
654 cleanup:
655         sd_notify(false, "STATUS=Shutting down...");
656         destroy_uploader(&u);
657
658 finish:
659         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
660 }