chiark / gitweb /
journal-upload: a tool to push messages to systemd-journal-remote
[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 #define easy_setopt(curl, opt, value, level, cmd)                       \
40         {                                                               \
41                 code = curl_easy_setopt(curl, opt, value);              \
42                 if (code) {                                             \
43                         log_full(level,                                 \
44                                  "curl_easy_setopt " #opt " failed: %s", \
45                                   curl_easy_strerror(code));            \
46                         cmd;                                            \
47                 }                                                       \
48         }
49
50 int start_upload(Uploader *u,
51                  size_t (*input_callback)(void *ptr,
52                                           size_t size,
53                                           size_t nmemb,
54                                           void *userdata),
55                  void *data) {
56         CURLcode code;
57
58         assert(u);
59         assert(input_callback);
60
61         if (!u->header) {
62                 struct curl_slist *h;
63
64                 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
65                 if (!h)
66                         return log_oom();
67
68                 h = curl_slist_append(h, "Transfer-Encoding: chunked");
69                 if (!h) {
70                         curl_slist_free_all(h);
71                         return log_oom();
72                 }
73
74                 h = curl_slist_append(h, "Accept: text/plain");
75                 if (!h) {
76                         curl_slist_free_all(h);
77                         return log_oom();
78                 }
79
80                 u->header = h;
81         }
82
83         if (!u->easy) {
84                 CURL *curl;
85
86                 curl = curl_easy_init();
87                 if (!curl) {
88                         log_error("Call to curl_easy_init failed.");
89                         return -ENOSR;
90                 }
91
92                 /* tell it to POST to the URL */
93                 easy_setopt(curl, CURLOPT_POST, 1L,
94                             LOG_ERR, return -EXFULL);
95
96                 /* set where to read from */
97                 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
98                             LOG_ERR, return -EXFULL);
99
100                 easy_setopt(curl, CURLOPT_READDATA, data,
101                             LOG_ERR, return -EXFULL);
102
103                 /* use our special own mime type and chunked transfer */
104                 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
105                             LOG_ERR, return -EXFULL);
106
107                 /* enable verbose for easier tracing */
108                 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
109
110                 easy_setopt(curl, CURLOPT_USERAGENT,
111                             "systemd-journal-upload " PACKAGE_STRING,
112                             LOG_WARNING, );
113
114                 u->easy = curl;
115         }
116
117         /* upload to this place */
118         code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
119         if (code) {
120                 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
121                           curl_easy_strerror(code));
122                 return -EXFULL;
123         }
124
125         u->uploading = true;
126
127         return 0;
128 }
129
130 static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
131         Uploader *u = userp;
132
133         ssize_t r;
134
135         assert(u);
136         assert(nmemb <= SSIZE_MAX / size);
137
138         if (u->input < 0)
139                 return 0;
140
141         r = read(u->input, buf, size * nmemb);
142         log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
143
144         if (r > 0)
145                 return r;
146
147         u->uploading = false;
148         if (r == 0) {
149                 log_debug("Reached EOF");
150                 close_fd_input(u);
151                 return 0;
152         } else {
153                 log_error("Aborting transfer after read error on input: %m.");
154                 return CURL_READFUNC_ABORT;
155         }
156 }
157
158 static void close_fd_input(Uploader *u) {
159         assert(u);
160
161         if (u->input >= 0)
162                 close_nointr(u->input);
163         u->input = -1;
164 }
165
166 static int dispatch_fd_input(sd_event_source *event,
167                              int fd,
168                              uint32_t revents,
169                              void *userp) {
170         Uploader *u = userp;
171
172         assert(u);
173         assert(revents & EPOLLIN);
174         assert(fd >= 0);
175
176         if (u->uploading) {
177                 log_warning("dispatch_fd_input called when uploading, ignoring.");
178                 return 0;
179         }
180
181         return start_upload(u, fd_input_callback, u);
182 }
183
184 static int open_file_for_upload(Uploader *u, const char *filename) {
185         int fd, r;
186
187         if (streq(filename, "-"))
188                 fd = STDIN_FILENO;
189         else {
190                 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
191                 if (fd < 0) {
192                         log_error("Failed to open %s: %m", filename);
193                         return -errno;
194                 }
195         }
196
197         u->input = fd;
198
199         r = sd_event_add_io(u->events, &u->input_event,
200                             fd, EPOLLIN, dispatch_fd_input, u);
201         if (r < 0) {
202                 if (r != -EPERM) {
203                         log_error("Failed to register input event: %s", strerror(-r));
204                         return r;
205                 }
206
207                 /* Normal files should just be consumed without polling. */
208                 r = start_upload(u, fd_input_callback, u);
209         }
210         return r;
211 }
212
213 static int setup_uploader(Uploader *u, const char *url) {
214         int r;
215
216         assert(u);
217         assert(url);
218
219         memzero(u, sizeof(Uploader));
220         u->input = -1;
221
222         u->url = url;
223
224         r = sd_event_default(&u->events);
225         if (r < 0) {
226                 log_error("sd_event_default failed: %s", strerror(-r));
227                 return r;
228         }
229
230         return 0;
231 }
232
233 static void destroy_uploader(Uploader *u) {
234         assert(u);
235
236         curl_easy_cleanup(u->easy);
237         curl_slist_free_all(u->header);
238
239         u->input_event = sd_event_source_unref(u->input_event);
240
241         close_fd_input(u);
242
243         sd_event_unref(u->events);
244 }
245
246 static void help(void) {
247         printf("%s -u URL {FILE|-}...\n\n"
248                "Upload journal events to a remote server.\n\n"
249                "Options:\n"
250                "  --url=URL                Upload to this address\n"
251                "  -h --help                Show this help and exit\n"
252                "  --version                Print version string and exit\n"
253                , program_invocation_short_name);
254 }
255
256 static int parse_argv(int argc, char *argv[]) {
257         enum {
258                 ARG_VERSION = 0x100,
259         };
260
261         static const struct option options[] = {
262                 { "help",         no_argument,       NULL, 'h'                },
263                 { "version",      no_argument,       NULL, ARG_VERSION        },
264                 { "url",          required_argument, NULL, 'u'                },
265                 {}
266         };
267
268         int c;
269
270         assert(argc >= 0);
271         assert(argv);
272
273         opterr = 0;
274
275         while ((c = getopt_long(argc, argv, "hu:", options, NULL)) >= 0)
276                 switch(c) {
277                 case 'h':
278                         help();
279                         return 0 /* done */;
280
281                 case ARG_VERSION:
282                         puts(PACKAGE_STRING);
283                         puts(SYSTEMD_FEATURES);
284                         return 0 /* done */;
285
286                 case 'u':
287                         if (arg_url) {
288                                 log_error("cannot use more than one --url");
289                                 return -EINVAL;
290                         }
291
292                         arg_url = optarg;
293                         break;
294
295                 case '?':
296                         log_error("Unknown option %s.", argv[optind-1]);
297                         return -EINVAL;
298
299                 case ':':
300                         log_error("Missing argument to %s.", argv[optind-1]);
301                         return -EINVAL;
302
303                 default:
304                         assert_not_reached("Unhandled option code.");
305                 }
306
307         if (!arg_url) {
308                 log_error("Required --url/-u option missing.");
309                 return -EINVAL;
310         }
311
312         if (optind >= argc) {
313                 log_error("Input argument missing.");
314                 return -EINVAL;
315         }
316
317         return 1;
318 }
319
320
321 int main(int argc, char **argv) {
322         Uploader u;
323         int r;
324
325         log_show_color(true);
326         log_parse_environment();
327
328         r = parse_argv(argc, argv);
329         if (r <= 0)
330                 goto finish;
331
332         r = setup_uploader(&u, arg_url);
333         if (r < 0)
334                 goto cleanup;
335
336         log_debug("%s running as pid "PID_FMT,
337                   program_invocation_short_name, getpid());
338         sd_notify(false,
339                   "READY=1\n"
340                   "STATUS=Processing input...");
341
342         while (true) {
343                 if (u.input < 0) {
344                         if (optind >= argc)
345                                 break;
346
347                         log_debug("Using %s as input.", argv[optind]);
348
349                         r = open_file_for_upload(&u, argv[optind++]);
350                         if (r < 0)
351                                 goto cleanup;
352
353                 }
354
355                 r = sd_event_get_state(u.events);
356                 if (r < 0)
357                         break;
358                 if (r == SD_EVENT_FINISHED)
359                         break;
360
361                 if (u.uploading) {
362                         CURLcode code;
363
364                         assert(u.easy);
365
366                         code = curl_easy_perform(u.easy);
367                         if (code) {
368                                 log_error("Upload to %s failed: %s",
369                                           u.url, curl_easy_strerror(code));
370                                 r = -EIO;
371                                 break;
372                         } else
373                                 log_debug("Upload finished successfully.");
374                 }
375
376                 r = sd_event_run(u.events, u.input >= 0 ? -1 : 0);
377                 if (r < 0) {
378                         log_error("Failed to run event loop: %s", strerror(-r));
379                         break;
380                 }
381         }
382
383 cleanup:
384         sd_notify(false, "STATUS=Shutting down...");
385         destroy_uploader(&u);
386
387 finish:
388         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
389 }