chiark / gitweb /
journal: pipe journalctl help output into a pager
[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 #include <getopt.h>
27
28 #include <microhttpd.h>
29
30 #include "log.h"
31 #include "util.h"
32 #include "sd-journal.h"
33 #include "sd-daemon.h"
34 #include "sd-bus.h"
35 #include "bus-util.h"
36 #include "logs-show.h"
37 #include "microhttpd-util.h"
38 #include "build.h"
39 #include "fileio.h"
40
41 typedef struct RequestMeta {
42         sd_journal *journal;
43
44         OutputMode mode;
45
46         char *cursor;
47         int64_t n_skip;
48         uint64_t n_entries;
49         bool n_entries_set;
50
51         FILE *tmp;
52         uint64_t delta, size;
53
54         int argument_parse_error;
55
56         bool follow;
57         bool discrete;
58
59         uint64_t n_fields;
60         bool n_fields_set;
61 } RequestMeta;
62
63 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
64         [OUTPUT_SHORT] = "text/plain",
65         [OUTPUT_JSON] = "application/json",
66         [OUTPUT_JSON_SSE] = "text/event-stream",
67         [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
68 };
69
70 static RequestMeta *request_meta(void **connection_cls) {
71         RequestMeta *m;
72
73         if (*connection_cls)
74                 return *connection_cls;
75
76         m = new0(RequestMeta, 1);
77         if (!m)
78                 return NULL;
79
80         *connection_cls = m;
81         return m;
82 }
83
84 static void request_meta_free(
85                 void *cls,
86                 struct MHD_Connection *connection,
87                 void **connection_cls,
88                 enum MHD_RequestTerminationCode toe) {
89
90         RequestMeta *m = *connection_cls;
91
92         if (!m)
93                 return;
94
95         if (m->journal)
96                 sd_journal_close(m->journal);
97
98         if (m->tmp)
99                 fclose(m->tmp);
100
101         free(m->cursor);
102         free(m);
103 }
104
105 static int open_journal(RequestMeta *m) {
106         assert(m);
107
108         if (m->journal)
109                 return 0;
110
111         return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
112 }
113
114 static int respond_oom_internal(struct MHD_Connection *connection) {
115         struct MHD_Response *response;
116         const char m[] = "Out of memory.\n";
117         int ret;
118
119         assert(connection);
120
121         response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
122         if (!response)
123                 return MHD_NO;
124
125         MHD_add_response_header(response, "Content-Type", "text/plain");
126         ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
127         MHD_destroy_response(response);
128
129         return ret;
130 }
131
132 #define respond_oom(connection) log_oom(), respond_oom_internal(connection)
133
134 static int respond_error(
135                 struct MHD_Connection *connection,
136                 unsigned code,
137                 const char *format, ...) {
138
139         struct MHD_Response *response;
140         char *m;
141         int r;
142         va_list ap;
143
144         assert(connection);
145         assert(format);
146
147         va_start(ap, format);
148         r = vasprintf(&m, format, ap);
149         va_end(ap);
150
151         if (r < 0)
152                 return respond_oom(connection);
153
154         response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
155         if (!response) {
156                 free(m);
157                 return respond_oom(connection);
158         }
159
160         MHD_add_response_header(response, "Content-Type", "text/plain");
161         r = MHD_queue_response(connection, code, response);
162         MHD_destroy_response(response);
163
164         return r;
165 }
166
167 static ssize_t request_reader_entries(
168                 void *cls,
169                 uint64_t pos,
170                 char *buf,
171                 size_t max) {
172
173         RequestMeta *m = cls;
174         int r;
175         size_t n, k;
176
177         assert(m);
178         assert(buf);
179         assert(max > 0);
180         assert(pos >= m->delta);
181
182         pos -= m->delta;
183
184         while (pos >= m->size) {
185                 off_t sz;
186
187                 /* End of this entry, so let's serialize the next
188                  * one */
189
190                 if (m->n_entries_set &&
191                     m->n_entries <= 0)
192                         return MHD_CONTENT_READER_END_OF_STREAM;
193
194                 if (m->n_skip < 0)
195                         r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
196                 else if (m->n_skip > 0)
197                         r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
198                 else
199                         r = sd_journal_next(m->journal);
200
201                 if (r < 0) {
202                         log_error("Failed to advance journal pointer: %s", strerror(-r));
203                         return MHD_CONTENT_READER_END_WITH_ERROR;
204                 } else if (r == 0) {
205
206                         if (m->follow) {
207                                 r = sd_journal_wait(m->journal, (uint64_t) -1);
208                                 if (r < 0) {
209                                         log_error("Couldn't wait for journal event: %s", strerror(-r));
210                                         return MHD_CONTENT_READER_END_WITH_ERROR;
211                                 }
212
213                                 continue;
214                         }
215
216                         return MHD_CONTENT_READER_END_OF_STREAM;
217                 }
218
219                 if (m->discrete) {
220                         assert(m->cursor);
221
222                         r = sd_journal_test_cursor(m->journal, m->cursor);
223                         if (r < 0) {
224                                 log_error("Failed to test cursor: %s", strerror(-r));
225                                 return MHD_CONTENT_READER_END_WITH_ERROR;
226                         }
227
228                         if (r == 0)
229                                 return MHD_CONTENT_READER_END_OF_STREAM;
230                 }
231
232                 pos -= m->size;
233                 m->delta += m->size;
234
235                 if (m->n_entries_set)
236                         m->n_entries -= 1;
237
238                 m->n_skip = 0;
239
240                 if (m->tmp)
241                         rewind(m->tmp);
242                 else {
243                         m->tmp = tmpfile();
244                         if (!m->tmp) {
245                                 log_error("Failed to create temporary file: %m");
246                                 return MHD_CONTENT_READER_END_WITH_ERROR;
247                         }
248                 }
249
250                 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
251                 if (r < 0) {
252                         log_error("Failed to serialize item: %s", strerror(-r));
253                         return MHD_CONTENT_READER_END_WITH_ERROR;
254                 }
255
256                 sz = ftello(m->tmp);
257                 if (sz == (off_t) -1) {
258                         log_error("Failed to retrieve file position: %m");
259                         return MHD_CONTENT_READER_END_WITH_ERROR;
260                 }
261
262                 m->size = (uint64_t) sz;
263         }
264
265         if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
266                 log_error("Failed to seek to position: %m");
267                 return MHD_CONTENT_READER_END_WITH_ERROR;
268         }
269
270         n = m->size - pos;
271         if (n > max)
272                 n = max;
273
274         errno = 0;
275         k = fread(buf, 1, n, m->tmp);
276         if (k != n) {
277                 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
278                 return MHD_CONTENT_READER_END_WITH_ERROR;
279         }
280
281         return (ssize_t) k;
282 }
283
284 static int request_parse_accept(
285                 RequestMeta *m,
286                 struct MHD_Connection *connection) {
287
288         const char *header;
289
290         assert(m);
291         assert(connection);
292
293         header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
294         if (!header)
295                 return 0;
296
297         if (streq(header, mime_types[OUTPUT_JSON]))
298                 m->mode = OUTPUT_JSON;
299         else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
300                 m->mode = OUTPUT_JSON_SSE;
301         else if (streq(header, mime_types[OUTPUT_EXPORT]))
302                 m->mode = OUTPUT_EXPORT;
303         else
304                 m->mode = OUTPUT_SHORT;
305
306         return 0;
307 }
308
309 static int request_parse_range(
310                 RequestMeta *m,
311                 struct MHD_Connection *connection) {
312
313         const char *range, *colon, *colon2;
314         int r;
315
316         assert(m);
317         assert(connection);
318
319         range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
320         if (!range)
321                 return 0;
322
323         if (!startswith(range, "entries="))
324                 return 0;
325
326         range += 8;
327         range += strspn(range, WHITESPACE);
328
329         colon = strchr(range, ':');
330         if (!colon)
331                 m->cursor = strdup(range);
332         else {
333                 const char *p;
334
335                 colon2 = strchr(colon + 1, ':');
336                 if (colon2) {
337                         _cleanup_free_ char *t;
338
339                         t = strndup(colon + 1, colon2 - colon - 1);
340                         if (!t)
341                                 return -ENOMEM;
342
343                         r = safe_atoi64(t, &m->n_skip);
344                         if (r < 0)
345                                 return r;
346                 }
347
348                 p = (colon2 ? colon2 : colon) + 1;
349                 if (*p) {
350                         r = safe_atou64(p, &m->n_entries);
351                         if (r < 0)
352                                 return r;
353
354                         if (m->n_entries <= 0)
355                                 return -EINVAL;
356
357                         m->n_entries_set = true;
358                 }
359
360                 m->cursor = strndup(range, colon - range);
361         }
362
363         if (!m->cursor)
364                 return -ENOMEM;
365
366         m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
367         if (isempty(m->cursor)) {
368                 free(m->cursor);
369                 m->cursor = NULL;
370         }
371
372         return 0;
373 }
374
375 static int request_parse_arguments_iterator(
376                 void *cls,
377                 enum MHD_ValueKind kind,
378                 const char *key,
379                 const char *value) {
380
381         RequestMeta *m = cls;
382         _cleanup_free_ char *p = NULL;
383         int r;
384
385         assert(m);
386
387         if (isempty(key)) {
388                 m->argument_parse_error = -EINVAL;
389                 return MHD_NO;
390         }
391
392         if (streq(key, "follow")) {
393                 if (isempty(value)) {
394                         m->follow = true;
395                         return MHD_YES;
396                 }
397
398                 r = parse_boolean(value);
399                 if (r < 0) {
400                         m->argument_parse_error = r;
401                         return MHD_NO;
402                 }
403
404                 m->follow = r;
405                 return MHD_YES;
406         }
407
408         if (streq(key, "discrete")) {
409                 if (isempty(value)) {
410                         m->discrete = true;
411                         return MHD_YES;
412                 }
413
414                 r = parse_boolean(value);
415                 if (r < 0) {
416                         m->argument_parse_error = r;
417                         return MHD_NO;
418                 }
419
420                 m->discrete = r;
421                 return MHD_YES;
422         }
423
424         if (streq(key, "boot")) {
425                 if (isempty(value))
426                         r = true;
427                 else {
428                         r = parse_boolean(value);
429                         if (r < 0) {
430                                 m->argument_parse_error = r;
431                                 return MHD_NO;
432                         }
433                 }
434
435                 if (r) {
436                         char match[9 + 32 + 1] = "_BOOT_ID=";
437                         sd_id128_t bid;
438
439                         r = sd_id128_get_boot(&bid);
440                         if (r < 0) {
441                                 log_error("Failed to get boot ID: %s", strerror(-r));
442                                 return MHD_NO;
443                         }
444
445                         sd_id128_to_string(bid, match + 9);
446                         r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
447                         if (r < 0) {
448                                 m->argument_parse_error = r;
449                                 return MHD_NO;
450                         }
451                 }
452
453                 return MHD_YES;
454         }
455
456         p = strjoin(key, "=", strempty(value), NULL);
457         if (!p) {
458                 m->argument_parse_error = log_oom();
459                 return MHD_NO;
460         }
461
462         r = sd_journal_add_match(m->journal, p, 0);
463         if (r < 0) {
464                 m->argument_parse_error = r;
465                 return MHD_NO;
466         }
467
468         return MHD_YES;
469 }
470
471 static int request_parse_arguments(
472                 RequestMeta *m,
473                 struct MHD_Connection *connection) {
474
475         assert(m);
476         assert(connection);
477
478         m->argument_parse_error = 0;
479         MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
480
481         return m->argument_parse_error;
482 }
483
484 static int request_handler_entries(
485                 struct MHD_Connection *connection,
486                 void *connection_cls) {
487
488         struct MHD_Response *response;
489         RequestMeta *m = connection_cls;
490         int r;
491
492         assert(connection);
493         assert(m);
494
495         r = open_journal(m);
496         if (r < 0)
497                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
498
499         if (request_parse_accept(m, connection) < 0)
500                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
501
502         if (request_parse_range(m, connection) < 0)
503                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
504
505         if (request_parse_arguments(m, connection) < 0)
506                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
507
508         if (m->discrete) {
509                 if (!m->cursor)
510                         return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
511
512                 m->n_entries = 1;
513                 m->n_entries_set = true;
514         }
515
516         if (m->cursor)
517                 r = sd_journal_seek_cursor(m->journal, m->cursor);
518         else if (m->n_skip >= 0)
519                 r = sd_journal_seek_head(m->journal);
520         else if (m->n_skip < 0)
521                 r = sd_journal_seek_tail(m->journal);
522         if (r < 0)
523                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
524
525         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
526         if (!response)
527                 return respond_oom(connection);
528
529         MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
530
531         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
532         MHD_destroy_response(response);
533
534         return r;
535 }
536
537 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
538         const char *eq;
539         size_t j;
540
541         eq = memchr(d, '=', l);
542         if (!eq)
543                 return -EINVAL;
544
545         j = l - (eq - d + 1);
546
547         if (m == OUTPUT_JSON) {
548                 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
549                 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
550                 fputs(" }\n", f);
551         } else {
552                 fwrite(eq+1, 1, j, f);
553                 fputc('\n', f);
554         }
555
556         return 0;
557 }
558
559 static ssize_t request_reader_fields(
560                 void *cls,
561                 uint64_t pos,
562                 char *buf,
563                 size_t max) {
564
565         RequestMeta *m = cls;
566         int r;
567         size_t n, k;
568
569         assert(m);
570         assert(buf);
571         assert(max > 0);
572         assert(pos >= m->delta);
573
574         pos -= m->delta;
575
576         while (pos >= m->size) {
577                 off_t sz;
578                 const void *d;
579                 size_t l;
580
581                 /* End of this field, so let's serialize the next
582                  * one */
583
584                 if (m->n_fields_set &&
585                     m->n_fields <= 0)
586                         return MHD_CONTENT_READER_END_OF_STREAM;
587
588                 r = sd_journal_enumerate_unique(m->journal, &d, &l);
589                 if (r < 0) {
590                         log_error("Failed to advance field index: %s", strerror(-r));
591                         return MHD_CONTENT_READER_END_WITH_ERROR;
592                 } else if (r == 0)
593                         return MHD_CONTENT_READER_END_OF_STREAM;
594
595                 pos -= m->size;
596                 m->delta += m->size;
597
598                 if (m->n_fields_set)
599                         m->n_fields -= 1;
600
601                 if (m->tmp)
602                         rewind(m->tmp);
603                 else {
604                         m->tmp = tmpfile();
605                         if (!m->tmp) {
606                                 log_error("Failed to create temporary file: %m");
607                                 return MHD_CONTENT_READER_END_WITH_ERROR;
608                         }
609                 }
610
611                 r = output_field(m->tmp, m->mode, d, l);
612                 if (r < 0) {
613                         log_error("Failed to serialize item: %s", strerror(-r));
614                         return MHD_CONTENT_READER_END_WITH_ERROR;
615                 }
616
617                 sz = ftello(m->tmp);
618                 if (sz == (off_t) -1) {
619                         log_error("Failed to retrieve file position: %m");
620                         return MHD_CONTENT_READER_END_WITH_ERROR;
621                 }
622
623                 m->size = (uint64_t) sz;
624         }
625
626         if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
627                 log_error("Failed to seek to position: %m");
628                 return MHD_CONTENT_READER_END_WITH_ERROR;
629         }
630
631         n = m->size - pos;
632         if (n > max)
633                 n = max;
634
635         errno = 0;
636         k = fread(buf, 1, n, m->tmp);
637         if (k != n) {
638                 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
639                 return MHD_CONTENT_READER_END_WITH_ERROR;
640         }
641
642         return (ssize_t) k;
643 }
644
645 static int request_handler_fields(
646                 struct MHD_Connection *connection,
647                 const char *field,
648                 void *connection_cls) {
649
650         struct MHD_Response *response;
651         RequestMeta *m = connection_cls;
652         int r;
653
654         assert(connection);
655         assert(m);
656
657         r = open_journal(m);
658         if (r < 0)
659                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
660
661         if (request_parse_accept(m, connection) < 0)
662                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
663
664         r = sd_journal_query_unique(m->journal, field);
665         if (r < 0)
666                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
667
668         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
669         if (!response)
670                 return respond_oom(connection);
671
672         MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
673
674         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
675         MHD_destroy_response(response);
676
677         return r;
678 }
679
680 static int request_handler_redirect(
681                 struct MHD_Connection *connection,
682                 const char *target) {
683
684         char *page;
685         struct MHD_Response *response;
686         int ret;
687
688         assert(connection);
689         assert(target);
690
691         if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
692                 return respond_oom(connection);
693
694         response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
695         if (!response) {
696                 free(page);
697                 return respond_oom(connection);
698         }
699
700         MHD_add_response_header(response, "Content-Type", "text/html");
701         MHD_add_response_header(response, "Location", target);
702
703         ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
704         MHD_destroy_response(response);
705
706         return ret;
707 }
708
709 static int request_handler_file(
710                 struct MHD_Connection *connection,
711                 const char *path,
712                 const char *mime_type) {
713
714         struct MHD_Response *response;
715         int ret;
716         _cleanup_close_ int fd = -1;
717         struct stat st;
718
719         assert(connection);
720         assert(path);
721         assert(mime_type);
722
723         fd = open(path, O_RDONLY|O_CLOEXEC);
724         if (fd < 0)
725                 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
726
727         if (fstat(fd, &st) < 0)
728                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
729
730         response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
731         if (!response)
732                 return respond_oom(connection);
733
734         fd = -1;
735
736         MHD_add_response_header(response, "Content-Type", mime_type);
737
738         ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
739         MHD_destroy_response(response);
740
741         return ret;
742 }
743
744 static int get_virtualization(char **v) {
745         _cleanup_bus_unref_ sd_bus *bus = NULL;
746         char *b;
747         int r;
748
749         r = sd_bus_default_system(&bus);
750         if (r < 0)
751                 return r;
752
753         r = sd_bus_get_property_string(
754                         bus,
755                         "org.freedesktop.systemd1",
756                         "/org/freedesktop/systemd1",
757                         "org.freedesktop.systemd1.Manager",
758                         "Virtualization",
759                         NULL,
760                         &b);
761         if (r < 0)
762                 return r;
763
764         if (isempty(b)) {
765                 free(b);
766                 *v = NULL;
767                 return 0;
768         }
769
770         *v = b;
771         return 1;
772 }
773
774 static int request_handler_machine(
775                 struct MHD_Connection *connection,
776                 void *connection_cls) {
777
778         struct MHD_Response *response;
779         RequestMeta *m = connection_cls;
780         int r;
781         _cleanup_free_ char* hostname = NULL, *os_name = NULL;
782         uint64_t cutoff_from, cutoff_to, usage;
783         char *json;
784         sd_id128_t mid, bid;
785         _cleanup_free_ char *v = NULL;
786
787         assert(connection);
788         assert(m);
789
790         r = open_journal(m);
791         if (r < 0)
792                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
793
794         r = sd_id128_get_machine(&mid);
795         if (r < 0)
796                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
797
798         r = sd_id128_get_boot(&bid);
799         if (r < 0)
800                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
801
802         hostname = gethostname_malloc();
803         if (!hostname)
804                 return respond_oom(connection);
805
806         r = sd_journal_get_usage(m->journal, &usage);
807         if (r < 0)
808                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
809
810         r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
811         if (r < 0)
812                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
813
814         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
815
816         get_virtualization(&v);
817
818         r = asprintf(&json,
819                      "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
820                      "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
821                      "\"hostname\" : \"%s\","
822                      "\"os_pretty_name\" : \"%s\","
823                      "\"virtualization\" : \"%s\","
824                      "\"usage\" : \"%"PRIu64"\","
825                      "\"cutoff_from_realtime\" : \"%"PRIu64"\","
826                      "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
827                      SD_ID128_FORMAT_VAL(mid),
828                      SD_ID128_FORMAT_VAL(bid),
829                      hostname_cleanup(hostname, false),
830                      os_name ? os_name : "Linux",
831                      v ? v : "bare",
832                      usage,
833                      cutoff_from,
834                      cutoff_to);
835
836         if (r < 0)
837                 return respond_oom(connection);
838
839         response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
840         if (!response) {
841                 free(json);
842                 return respond_oom(connection);
843         }
844
845         MHD_add_response_header(response, "Content-Type", "application/json");
846         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
847         MHD_destroy_response(response);
848
849         return r;
850 }
851
852 static int request_handler(
853                 void *cls,
854                 struct MHD_Connection *connection,
855                 const char *url,
856                 const char *method,
857                 const char *version,
858                 const char *upload_data,
859                 size_t *upload_data_size,
860                 void **connection_cls) {
861
862         assert(connection);
863         assert(connection_cls);
864         assert(url);
865         assert(method);
866
867         if (!streq(method, "GET"))
868                 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
869                                      "Unsupported method.\n");
870
871
872         if (!*connection_cls) {
873                 if (!request_meta(connection_cls))
874                         return respond_oom(connection);
875                 return MHD_YES;
876         }
877
878         if (streq(url, "/"))
879                 return request_handler_redirect(connection, "/browse");
880
881         if (streq(url, "/entries"))
882                 return request_handler_entries(connection, *connection_cls);
883
884         if (startswith(url, "/fields/"))
885                 return request_handler_fields(connection, url + 8, *connection_cls);
886
887         if (streq(url, "/browse"))
888                 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
889
890         if (streq(url, "/machine"))
891                 return request_handler_machine(connection, *connection_cls);
892
893         return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
894 }
895
896 static int help(void) {
897
898         printf("%s [OPTIONS...] ...\n\n"
899                "HTTP server for journal events.\n\n"
900                "  -h --help           Show this help\n"
901                "     --version        Show package version\n"
902                "     --cert=CERT.PEM  Specify server certificate in PEM format\n"
903                "     --key=KEY.PEM    Specify server key in PEM format\n",
904                program_invocation_short_name);
905
906         return 0;
907 }
908
909 static char *key_pem = NULL;
910 static char *cert_pem = NULL;
911
912 static int parse_argv(int argc, char *argv[]) {
913         enum {
914                 ARG_VERSION = 0x100,
915                 ARG_KEY,
916                 ARG_CERT,
917         };
918
919         int r, c;
920
921         static const struct option options[] = {
922                 { "help",    no_argument,       NULL, 'h'         },
923                 { "version", no_argument,       NULL, ARG_VERSION },
924                 { "key",     required_argument, NULL, ARG_KEY     },
925                 { "cert",    required_argument, NULL, ARG_CERT    },
926                 {}
927         };
928
929         assert(argc >= 0);
930         assert(argv);
931
932         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
933
934                 switch(c) {
935
936                 case 'h':
937                         return help();
938
939                 case ARG_VERSION:
940                         puts(PACKAGE_STRING);
941                         puts(SYSTEMD_FEATURES);
942                         return 0;
943
944                 case ARG_KEY:
945                         if (key_pem) {
946                                 log_error("Key file specified twice");
947                                 return -EINVAL;
948                         }
949                         r = read_full_file(optarg, &key_pem, NULL);
950                         if (r < 0) {
951                                 log_error("Failed to read key file: %s", strerror(-r));
952                                 return r;
953                         }
954                         assert(key_pem);
955                         break;
956
957                 case ARG_CERT:
958                         if (cert_pem) {
959                                 log_error("Certificate file specified twice");
960                                 return -EINVAL;
961                         }
962                         r = read_full_file(optarg, &cert_pem, NULL);
963                         if (r < 0) {
964                                 log_error("Failed to read certificate file: %s", strerror(-r));
965                                 return r;
966                         }
967                         assert(cert_pem);
968                         break;
969
970                 case '?':
971                         return -EINVAL;
972
973                 default:
974                         assert_not_reached("Unhandled option");
975                 }
976
977         if (optind < argc) {
978                 log_error("This program does not take arguments.");
979                 return -EINVAL;
980         }
981
982         if (!!key_pem != !!cert_pem) {
983                 log_error("Certificate and key files must be specified together");
984                 return -EINVAL;
985         }
986
987         return 1;
988 }
989
990 int main(int argc, char *argv[]) {
991         struct MHD_Daemon *d = NULL;
992         int r, n;
993
994         log_set_target(LOG_TARGET_AUTO);
995         log_parse_environment();
996         log_open();
997
998         r = parse_argv(argc, argv);
999         if (r < 0)
1000                 return EXIT_FAILURE;
1001         if (r == 0)
1002                 return EXIT_SUCCESS;
1003
1004         n = sd_listen_fds(1);
1005         if (n < 0) {
1006                 log_error("Failed to determine passed sockets: %s", strerror(-n));
1007                 goto finish;
1008         } else if (n > 1) {
1009                 log_error("Can't listen on more than one socket.");
1010                 goto finish;
1011         } else {
1012                 struct MHD_OptionItem opts[] = {
1013                         { MHD_OPTION_NOTIFY_COMPLETED,
1014                           (intptr_t) request_meta_free, NULL },
1015                         { MHD_OPTION_EXTERNAL_LOGGER,
1016                           (intptr_t) microhttpd_logger, NULL },
1017                         { MHD_OPTION_END, 0, NULL },
1018                         { MHD_OPTION_END, 0, NULL },
1019                         { MHD_OPTION_END, 0, NULL },
1020                         { MHD_OPTION_END, 0, NULL }};
1021                 int opts_pos = 2;
1022                 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1023
1024                 if (n > 0)
1025                         opts[opts_pos++] = (struct MHD_OptionItem)
1026                                 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1027                 if (key_pem) {
1028                         assert(cert_pem);
1029                         opts[opts_pos++] = (struct MHD_OptionItem)
1030                                 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1031                         opts[opts_pos++] = (struct MHD_OptionItem)
1032                                 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1033                         flags |= MHD_USE_SSL;
1034                 }
1035
1036                 d = MHD_start_daemon(flags, 19531,
1037                                      NULL, NULL,
1038                                      request_handler, NULL,
1039                                      MHD_OPTION_ARRAY, opts,
1040                                      MHD_OPTION_END);
1041         }
1042
1043         if (!d) {
1044                 log_error("Failed to start daemon!");
1045                 goto finish;
1046         }
1047
1048         pause();
1049
1050         r = EXIT_SUCCESS;
1051
1052 finish:
1053         if (d)
1054                 MHD_stop_daemon(d);
1055
1056         return r;
1057 }