chiark / gitweb /
added some missing include for a5c32cff1f56afe6f0c6c70d91a88a7a8238b2d7
[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 "logs-show.h"
35 #include "microhttpd-util.h"
36 #include "virt.h"
37 #include "build.h"
38 #include "fileio.h"
39
40 typedef struct RequestMeta {
41         sd_journal *journal;
42
43         OutputMode mode;
44
45         char *cursor;
46         int64_t n_skip;
47         uint64_t n_entries;
48         bool n_entries_set;
49
50         FILE *tmp;
51         uint64_t delta, size;
52
53         int argument_parse_error;
54
55         bool follow;
56         bool discrete;
57
58         uint64_t n_fields;
59         bool n_fields_set;
60 } RequestMeta;
61
62 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
63         [OUTPUT_SHORT] = "text/plain",
64         [OUTPUT_JSON] = "application/json",
65         [OUTPUT_JSON_SSE] = "text/event-stream",
66         [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
67 };
68
69 static RequestMeta *request_meta(void **connection_cls) {
70         RequestMeta *m;
71
72         if (*connection_cls)
73                 return *connection_cls;
74
75         m = new0(RequestMeta, 1);
76         if (!m)
77                 return NULL;
78
79         *connection_cls = m;
80         return m;
81 }
82
83 static void request_meta_free(
84                 void *cls,
85                 struct MHD_Connection *connection,
86                 void **connection_cls,
87                 enum MHD_RequestTerminationCode toe) {
88
89         RequestMeta *m = *connection_cls;
90
91         if (!m)
92                 return;
93
94         if (m->journal)
95                 sd_journal_close(m->journal);
96
97         if (m->tmp)
98                 fclose(m->tmp);
99
100         free(m->cursor);
101         free(m);
102 }
103
104 static int open_journal(RequestMeta *m) {
105         assert(m);
106
107         if (m->journal)
108                 return 0;
109
110         return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
111 }
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);
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                         char _cleanup_free_ *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 request_handler_machine(
745                 struct MHD_Connection *connection,
746                 void *connection_cls) {
747
748         struct MHD_Response *response;
749         RequestMeta *m = connection_cls;
750         int r;
751         _cleanup_free_ char* hostname = NULL, *os_name = NULL;
752         uint64_t cutoff_from, cutoff_to, usage;
753         char *json;
754         sd_id128_t mid, bid;
755         const char *v = "bare";
756
757         assert(connection);
758         assert(m);
759
760         r = open_journal(m);
761         if (r < 0)
762                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
763
764         r = sd_id128_get_machine(&mid);
765         if (r < 0)
766                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
767
768         r = sd_id128_get_boot(&bid);
769         if (r < 0)
770                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
771
772         hostname = gethostname_malloc();
773         if (!hostname)
774                 return respond_oom(connection);
775
776         r = sd_journal_get_usage(m->journal, &usage);
777         if (r < 0)
778                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
779
780         r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
781         if (r < 0)
782                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
783
784         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
785
786         detect_virtualization(&v);
787
788         r = asprintf(&json,
789                      "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
790                      "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
791                      "\"hostname\" : \"%s\","
792                      "\"os_pretty_name\" : \"%s\","
793                      "\"virtualization\" : \"%s\","
794                      "\"usage\" : \"%llu\","
795                      "\"cutoff_from_realtime\" : \"%llu\","
796                      "\"cutoff_to_realtime\" : \"%llu\" }\n",
797                      SD_ID128_FORMAT_VAL(mid),
798                      SD_ID128_FORMAT_VAL(bid),
799                      hostname_cleanup(hostname),
800                      os_name ? os_name : "Linux",
801                      v,
802                      (unsigned long long) usage,
803                      (unsigned long long) cutoff_from,
804                      (unsigned long long) cutoff_to);
805
806         if (r < 0)
807                 return respond_oom(connection);
808
809         response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
810         if (!response) {
811                 free(json);
812                 return respond_oom(connection);
813         }
814
815         MHD_add_response_header(response, "Content-Type", "application/json");
816         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
817         MHD_destroy_response(response);
818
819         return r;
820 }
821
822 static int request_handler(
823                 void *cls,
824                 struct MHD_Connection *connection,
825                 const char *url,
826                 const char *method,
827                 const char *version,
828                 const char *upload_data,
829                 size_t *upload_data_size,
830                 void **connection_cls) {
831
832         assert(connection);
833         assert(connection_cls);
834         assert(url);
835         assert(method);
836
837         if (!streq(method, "GET"))
838                 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
839                                      "Unsupported method.\n");
840
841
842         if (!*connection_cls) {
843                 if (!request_meta(connection_cls))
844                         return respond_oom(connection);
845                 return MHD_YES;
846         }
847
848         if (streq(url, "/"))
849                 return request_handler_redirect(connection, "/browse");
850
851         if (streq(url, "/entries"))
852                 return request_handler_entries(connection, *connection_cls);
853
854         if (startswith(url, "/fields/"))
855                 return request_handler_fields(connection, url + 8, *connection_cls);
856
857         if (streq(url, "/browse"))
858                 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
859
860         if (streq(url, "/machine"))
861                 return request_handler_machine(connection, *connection_cls);
862
863         return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
864 }
865
866 static int help(void) {
867
868         printf("%s [OPTIONS...] ...\n\n"
869                "HTTP server for journal events.\n\n"
870                "  -h --help           Show this help\n"
871                "     --version        Show package version\n"
872                "     --cert=CERT.PEM  Specify server certificate in PEM format\n"
873                "     --key=KEY.PEM    Specify server key in PEM format\n",
874                program_invocation_short_name);
875
876         return 0;
877 }
878
879 static char *key_pem = NULL;
880 static char *cert_pem = NULL;
881
882 static int parse_argv(int argc, char *argv[]) {
883         enum {
884                 ARG_VERSION = 0x100,
885                 ARG_KEY,
886                 ARG_CERT,
887         };
888
889         int r, c;
890
891         static const struct option options[] = {
892                 { "help",    no_argument,       NULL, 'h'         },
893                 { "version", no_argument,       NULL, ARG_VERSION },
894                 { "key",     required_argument, NULL, ARG_KEY     },
895                 { "cert",    required_argument, NULL, ARG_CERT    },
896                 { NULL,      0,                 NULL, 0           }
897         };
898
899         assert(argc >= 0);
900         assert(argv);
901
902         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
903                 switch(c) {
904                 case ARG_VERSION:
905                         puts(PACKAGE_STRING);
906                         puts(SYSTEMD_FEATURES);
907                         return 0;
908
909                 case 'h':
910                         return help();
911
912                 case ARG_KEY:
913                         if (key_pem) {
914                                 log_error("Key file specified twice");
915                                 return -EINVAL;
916                         }
917                         r = read_full_file(optarg, &key_pem, NULL);
918                         if (r < 0) {
919                                 log_error("Failed to read key file: %s", strerror(-r));
920                                 return r;
921                         }
922                         assert(key_pem);
923                         break;
924
925                 case ARG_CERT:
926                         if (cert_pem) {
927                                 log_error("Certificate file specified twice");
928                                 return -EINVAL;
929                         }
930                         r = read_full_file(optarg, &cert_pem, NULL);
931                         if (r < 0) {
932                                 log_error("Failed to read certificate file: %s", strerror(-r));
933                                 return r;
934                         }
935                         assert(cert_pem);
936                         break;
937
938                 case '?':
939                         return -EINVAL;
940
941                 default:
942                         log_error("Unknown option code %c", c);
943                         return -EINVAL;
944                 }
945
946         if (optind < argc) {
947                 log_error("This program does not take arguments.");
948                 return -EINVAL;
949         }
950
951         if (!!key_pem != !!cert_pem) {
952                 log_error("Certificate and key files must be specified together");
953                 return -EINVAL;
954         }
955
956         return 1;
957 }
958
959 int main(int argc, char *argv[]) {
960         struct MHD_Daemon *d = NULL;
961         int r, n;
962
963         log_set_target(LOG_TARGET_AUTO);
964         log_parse_environment();
965         log_open();
966
967         r = parse_argv(argc, argv);
968         if (r < 0)
969                 return EXIT_FAILURE;
970         if (r == 0)
971                 return EXIT_SUCCESS;
972
973         n = sd_listen_fds(1);
974         if (n < 0) {
975                 log_error("Failed to determine passed sockets: %s", strerror(-n));
976                 goto finish;
977         } else if (n > 1) {
978                 log_error("Can't listen on more than one socket.");
979                 goto finish;
980         } else {
981                 struct MHD_OptionItem opts[] = {
982                         { MHD_OPTION_NOTIFY_COMPLETED,
983                           (intptr_t) request_meta_free, NULL },
984                         { MHD_OPTION_EXTERNAL_LOGGER,
985                           (intptr_t) microhttpd_logger, NULL },
986                         { MHD_OPTION_END, 0, NULL },
987                         { MHD_OPTION_END, 0, NULL },
988                         { MHD_OPTION_END, 0, NULL },
989                         { MHD_OPTION_END, 0, NULL }};
990                 int opts_pos = 2;
991                 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
992
993                 if (n > 0)
994                         opts[opts_pos++] = (struct MHD_OptionItem)
995                                 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
996                 if (key_pem) {
997                         assert(cert_pem);
998                         opts[opts_pos++] = (struct MHD_OptionItem)
999                                 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1000                         opts[opts_pos++] = (struct MHD_OptionItem)
1001                                 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1002                         flags |= MHD_USE_SSL;
1003                 }
1004
1005                 d = MHD_start_daemon(flags, 19531,
1006                                      NULL, NULL,
1007                                      request_handler, NULL,
1008                                      MHD_OPTION_ARRAY, opts,
1009                                      MHD_OPTION_END);
1010         }
1011
1012         if (!d) {
1013                 log_error("Failed to start daemon!");
1014                 goto finish;
1015         }
1016
1017         pause();
1018
1019         r = EXIT_SUCCESS;
1020
1021 finish:
1022         if (d)
1023                 MHD_stop_daemon(d);
1024
1025         return r;
1026 }