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