chiark / gitweb /
journal: fix seeking backwards in gateway daemon
[elogind.git] / src / journal / journal-gatewayd.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
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 <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26
27 #include <microhttpd.h>
28
29 #include "log.h"
30 #include "util.h"
31 #include "sd-journal.h"
32 #include "sd-daemon.h"
33 #include "logs-show.h"
34 #include "virt.h"
35
36 typedef struct RequestMeta {
37         sd_journal *journal;
38
39         OutputMode mode;
40
41         char *cursor;
42         int64_t n_skip;
43         uint64_t n_entries;
44         bool n_entries_set;
45
46         FILE *tmp;
47         uint64_t delta, size;
48
49         int argument_parse_error;
50
51         bool follow;
52 } RequestMeta;
53
54 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
55         [OUTPUT_SHORT] = "text/plain",
56         [OUTPUT_JSON] = "application/json",
57         [OUTPUT_EXPORT] = "application/vnd.fdo.journal"
58 };
59
60 static RequestMeta *request_meta(void **connection_cls) {
61         RequestMeta *m;
62
63         if (*connection_cls)
64                 return *connection_cls;
65
66         m = new0(RequestMeta, 1);
67         if (!m)
68                 return NULL;
69
70         *connection_cls = m;
71         return m;
72 }
73
74 static void request_meta_free(
75                 void *cls,
76                 struct MHD_Connection *connection,
77                 void **connection_cls,
78                 enum MHD_RequestTerminationCode toe) {
79
80         RequestMeta *m = *connection_cls;
81
82         if (!m)
83                 return;
84
85         if (m->journal)
86                 sd_journal_close(m->journal);
87
88         if (m->tmp)
89                 fclose(m->tmp);
90
91         free(m->cursor);
92         free(m);
93 }
94
95 static int open_journal(RequestMeta *m) {
96         assert(m);
97
98         if (m->journal)
99                 return 0;
100
101         return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
102 }
103
104
105 static int respond_oom(struct MHD_Connection *connection) {
106         struct MHD_Response *response;
107         const char m[] = "Out of memory.\n";
108         int ret;
109
110         assert(connection);
111
112         response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
113         if (!response)
114                 return MHD_NO;
115
116         MHD_add_response_header(response, "Content-Type", "text/plain");
117         ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
118         MHD_destroy_response(response);
119
120         return ret;
121 }
122
123 static int respond_error(
124                 struct MHD_Connection *connection,
125                 unsigned code,
126                 const char *format, ...) {
127
128         struct MHD_Response *response;
129         char *m;
130         int r;
131         va_list ap;
132
133         assert(connection);
134         assert(format);
135
136         va_start(ap, format);
137         r = vasprintf(&m, format, ap);
138         va_end(ap);
139
140         if (r < 0)
141                 return respond_oom(connection);
142
143         response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
144         if (!response) {
145                 free(m);
146                 return respond_oom(connection);
147         }
148
149         MHD_add_response_header(response, "Content-Type", "text/plain");
150         r = MHD_queue_response(connection, code, response);
151         MHD_destroy_response(response);
152
153         return r;
154 }
155
156 static ssize_t request_reader_entries(
157                 void *cls,
158                 uint64_t pos,
159                 char *buf,
160                 size_t max) {
161
162         RequestMeta *m = cls;
163         int r;
164         size_t n, k;
165
166         assert(m);
167         assert(buf);
168         assert(max > 0);
169         assert(pos >= m->delta);
170
171         pos -= m->delta;
172
173         while (pos >= m->size) {
174                 off_t sz;
175
176                 /* End of this entry, so let's serialize the next
177                  * one */
178
179                 if (m->n_entries_set &&
180                     m->n_entries <= 0)
181                         return MHD_CONTENT_READER_END_OF_STREAM;
182
183                 if (m->n_skip < 0)
184                         r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
185                 else if (m->n_skip > 0)
186                         r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
187                 else
188                         r = sd_journal_next(m->journal);
189
190                 if (r < 0) {
191                         log_error("Failed to advance journal pointer: %s", strerror(-r));
192                         return MHD_CONTENT_READER_END_WITH_ERROR;
193                 } else if (r == 0) {
194
195                         if (m->follow) {
196                                 r = sd_journal_wait(m->journal, (uint64_t) -1);
197                                 if (r < 0) {
198                                         log_error("Couldn't wait for journal event: %s", strerror(-r));
199                                         return MHD_CONTENT_READER_END_WITH_ERROR;
200                                 }
201
202                                 continue;
203                         }
204
205                         return MHD_CONTENT_READER_END_OF_STREAM;
206                 }
207
208                 pos -= m->size;
209                 m->delta += m->size;
210
211                 if (m->n_entries_set)
212                         m->n_entries -= 1;
213
214                 m->n_skip = 0;
215
216                 if (m->tmp)
217                         rewind(m->tmp);
218                 else {
219                         m->tmp = tmpfile();
220                         if (!m->tmp) {
221                                 log_error("Failed to create temporary file: %m");
222                                 return MHD_CONTENT_READER_END_WITH_ERROR;;
223                         }
224                 }
225
226                 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
227                 if (r < 0) {
228                         log_error("Failed to serialize item: %s", strerror(-r));
229                         return MHD_CONTENT_READER_END_WITH_ERROR;
230                 }
231
232                 sz = ftello(m->tmp);
233                 if (sz == (off_t) -1) {
234                         log_error("Failed to retrieve file position: %m");
235                         return MHD_CONTENT_READER_END_WITH_ERROR;
236                 }
237
238                 m->size = (uint64_t) sz;
239         }
240
241         if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
242                 log_error("Failed to seek to position: %m");
243                 return MHD_CONTENT_READER_END_WITH_ERROR;
244         }
245
246         n = m->size - pos;
247         if (n > max)
248                 n = max;
249
250         errno = 0;
251         k = fread(buf, 1, n, m->tmp);
252         if (k != n) {
253                 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
254                 return MHD_CONTENT_READER_END_WITH_ERROR;
255         }
256
257         return (ssize_t) k;
258 }
259
260 static int request_parse_accept(
261                 RequestMeta *m,
262                 struct MHD_Connection *connection) {
263
264         const char *accept;
265
266         assert(m);
267         assert(connection);
268
269         accept = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
270         if (!accept)
271                 return 0;
272
273         if (streq(accept, mime_types[OUTPUT_JSON]))
274                 m->mode = OUTPUT_JSON;
275         else if (streq(accept, mime_types[OUTPUT_EXPORT]))
276                 m->mode = OUTPUT_EXPORT;
277         else
278                 m->mode = OUTPUT_SHORT;
279
280         return 0;
281 }
282
283 static int request_parse_range(
284                 RequestMeta *m,
285                 struct MHD_Connection *connection) {
286
287         const char *range, *colon, *colon2;
288         int r;
289
290         assert(m);
291         assert(connection);
292
293         range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
294         if (!range)
295                 return 0;
296
297         if (!startswith(range, "entries="))
298                 return 0;
299
300         range += 8;
301         range += strspn(range, WHITESPACE);
302
303         colon = strchr(range, ':');
304         if (!colon)
305                 m->cursor = strdup(range);
306         else {
307                 const char *p;
308
309                 colon2 = strchr(colon + 1, ':');
310                 if (colon2) {
311                         char *t;
312
313                         t = strndup(colon + 1, colon2 - colon - 1);
314                         if (!t)
315                                 return -ENOMEM;
316
317                         r = safe_atoi64(t, &m->n_skip);
318                         free(t);
319                         if (r < 0)
320                                 return r;
321                 }
322
323                 p = (colon2 ? colon2 : colon) + 1;
324                 if (*p) {
325                         r = safe_atou64(p, &m->n_entries);
326                         if (r < 0)
327                                 return r;
328
329                         if (m->n_entries <= 0)
330                                 return -EINVAL;
331
332                         m->n_entries_set = true;
333                 }
334
335                 m->cursor = strndup(range, colon - range);
336         }
337
338         if (!m->cursor)
339                 return -ENOMEM;
340
341         m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
342         if (isempty(m->cursor)) {
343                 free(m->cursor);
344                 m->cursor = NULL;
345         }
346
347         return 0;
348 }
349
350 static int request_parse_arguments_iterator(
351                 void *cls,
352                 enum MHD_ValueKind kind,
353                 const char *key,
354                 const char *value) {
355
356         RequestMeta *m = cls;
357         _cleanup_free_ char *p = NULL;
358         int r;
359
360         assert(m);
361
362         if (isempty(key)) {
363                 m->argument_parse_error = -EINVAL;
364                 return MHD_NO;
365         }
366
367         if (streq(key, "follow")) {
368                 if (isempty(value)) {
369                         m->follow = true;
370                         return MHD_YES;
371                 }
372
373                 r = parse_boolean(value);
374                 if (r < 0) {
375                         m->argument_parse_error = r;
376                         return MHD_NO;
377                 }
378
379                 m->follow = r;
380                 return MHD_YES;
381         }
382
383         p = strjoin(key, "=", strempty(value), NULL);
384         if (!p) {
385                 m->argument_parse_error = log_oom();
386                 return MHD_NO;
387         }
388
389         r = sd_journal_add_match(m->journal, p, 0);
390         if (r < 0) {
391                 m->argument_parse_error = r;
392                 return MHD_NO;
393         }
394
395         return MHD_YES;
396 }
397
398 static int request_parse_arguments(
399                 RequestMeta *m,
400                 struct MHD_Connection *connection) {
401
402         assert(m);
403         assert(connection);
404
405         m->argument_parse_error = 0;
406         MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
407
408         return m->argument_parse_error;
409 }
410
411 static int request_handler_entries(
412                 struct MHD_Connection *connection,
413                 void **connection_cls) {
414
415         struct MHD_Response *response;
416         RequestMeta *m;
417         int r;
418
419         assert(connection);
420         assert(connection_cls);
421
422         m = request_meta(connection_cls);
423         if (!m)
424                 return respond_oom(connection);
425
426         r = open_journal(m);
427         if (r < 0)
428                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
429
430         if (request_parse_accept(m, connection) < 0)
431                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
432
433         if (request_parse_range(m, connection) < 0)
434                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
435
436         if (request_parse_arguments(m, connection) < 0)
437                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
438
439         if (m->cursor)
440                 r = sd_journal_seek_cursor(m->journal, m->cursor);
441         else if (m->n_skip >= 0)
442                 r = sd_journal_seek_head(m->journal);
443         else if (m->n_skip < 0)
444                 r = sd_journal_seek_tail(m->journal);
445         if (r < 0)
446                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
447
448         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
449         if (!response)
450                 return respond_oom(connection);
451
452         MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
453
454         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
455         MHD_destroy_response(response);
456
457         return r;
458 }
459
460 static int request_handler_redirect(
461                 struct MHD_Connection *connection,
462                 const char *target) {
463
464         char *page;
465         struct MHD_Response *response;
466         int ret;
467
468         assert(connection);
469         assert(target);
470
471         if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
472                 return respond_oom(connection);
473
474         response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
475         if (!response) {
476                 free(page);
477                 return respond_oom(connection);
478         }
479
480         MHD_add_response_header(response, "Content-Type", "text/html");
481         MHD_add_response_header(response, "Location", target);
482
483         ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
484         MHD_destroy_response(response);
485
486         return ret;
487 }
488
489 static int request_handler_file(
490                 struct MHD_Connection *connection,
491                 const char *path,
492                 const char *mime_type) {
493
494         struct MHD_Response *response;
495         int ret;
496         _cleanup_close_ int fd = -1;
497         struct stat st;
498
499         assert(connection);
500         assert(path);
501         assert(mime_type);
502
503         fd = open(path, O_RDONLY|O_CLOEXEC);
504         if (fd < 0)
505                 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
506
507         if (fstat(fd, &st) < 0)
508                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
509
510         response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
511         if (!response)
512                 return respond_oom(connection);
513
514         fd = -1;
515
516         MHD_add_response_header(response, "Content-Type", mime_type);
517
518         ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
519         MHD_destroy_response(response);
520
521         return ret;
522 }
523
524 static int request_handler_machine(
525                 struct MHD_Connection *connection,
526                 void **connection_cls) {
527
528         struct MHD_Response *response;
529         RequestMeta *m;
530         int r;
531         _cleanup_free_ char* hostname = NULL, *os_name = NULL;
532         uint64_t cutoff_from, cutoff_to, usage;
533         char *json;
534         sd_id128_t mid, bid;
535         const char *v = "bare";
536
537         assert(connection);
538
539         m = request_meta(connection_cls);
540         if (!m)
541                 return respond_oom(connection);
542
543         r = open_journal(m);
544         if (r < 0)
545                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
546
547         r = sd_id128_get_machine(&mid);
548         if (r < 0)
549                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
550
551         r = sd_id128_get_boot(&bid);
552         if (r < 0)
553                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
554
555         hostname = gethostname_malloc();
556         if (!hostname)
557                 return respond_oom(connection);
558
559         r = sd_journal_get_usage(m->journal, &usage);
560         if (r < 0)
561                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
562
563         r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
564         if (r < 0)
565                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
566
567         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
568
569         detect_virtualization(&v);
570
571         r = asprintf(&json,
572                      "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
573                      "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
574                      "\"hostname\" : \"%s\","
575                      "\"os_pretty_name\" : \"%s\","
576                      "\"virtualization\" : \"%s\","
577                      "\"usage\" : \"%llu\","
578                      "\"cutoff_from_realtime\" : \"%llu\","
579                      "\"cutoff_to_realtime\" : \"%llu\" }\n",
580                      SD_ID128_FORMAT_VAL(mid),
581                      SD_ID128_FORMAT_VAL(bid),
582                      hostname_cleanup(hostname),
583                      os_name ? os_name : "Linux",
584                      v,
585                      (unsigned long long) usage,
586                      (unsigned long long) cutoff_from,
587                      (unsigned long long) cutoff_to);
588
589         if (r < 0)
590                 return respond_oom(connection);
591
592         response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
593         if (!response) {
594                 free(json);
595                 return respond_oom(connection);
596         }
597
598         MHD_add_response_header(response, "Content-Type", "application/json");
599         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
600         MHD_destroy_response(response);
601
602         return r;
603 }
604
605 static int request_handler(
606                 void *cls,
607                 struct MHD_Connection *connection,
608                 const char *url,
609                 const char *method,
610                 const char *version,
611                 const char *upload_data,
612                 size_t *upload_data_size,
613                 void **connection_cls) {
614
615         assert(connection);
616         assert(url);
617         assert(method);
618
619         if (!streq(method, "GET"))
620                 return MHD_NO;
621
622         if (streq(url, "/"))
623                 return request_handler_redirect(connection, "/browse");
624
625         if (streq(url, "/entries"))
626                 return request_handler_entries(connection, connection_cls);
627
628         if (streq(url, "/browse"))
629                 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
630
631         if (streq(url, "/machine"))
632                 return request_handler_machine(connection, connection_cls);
633
634         return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
635 }
636
637 int main(int argc, char *argv[]) {
638         struct MHD_Daemon *daemon = NULL;
639         int r = EXIT_FAILURE, n;
640
641         if (argc > 1) {
642                 log_error("This program does not take arguments.");
643                 goto finish;
644         }
645
646         log_set_target(LOG_TARGET_AUTO);
647         log_parse_environment();
648         log_open();
649
650         n = sd_listen_fds(1);
651         if (n < 0) {
652                 log_error("Failed to determine passed sockets: %s", strerror(-n));
653                 goto finish;
654         } else if (n > 1) {
655                 log_error("Can't listen on more than one socket.");
656                 goto finish;
657         } else if (n > 0) {
658                 daemon = MHD_start_daemon(
659                                 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
660                                 19531,
661                                 NULL, NULL,
662                                 request_handler, NULL,
663                                 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
664                                 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
665                                 MHD_OPTION_END);
666         } else {
667                 daemon = MHD_start_daemon(
668                                 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
669                                 19531,
670                                 NULL, NULL,
671                                 request_handler, NULL,
672                                 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
673                                 MHD_OPTION_END);
674         }
675
676         if (!daemon) {
677                 log_error("Failed to start daemon!");
678                 goto finish;
679         }
680
681         pause();
682
683         r = EXIT_SUCCESS;
684
685 finish:
686         if (daemon)
687                 MHD_stop_daemon(daemon);
688
689         return r;
690 }