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