chiark / gitweb /
coredump: make use of the cleanup macros
[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 = connection_cls;
489         int r;
490
491         assert(connection);
492         assert(m);
493
494         r = open_journal(m);
495         if (r < 0)
496                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
497
498         if (request_parse_accept(m, connection) < 0)
499                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
500
501         if (request_parse_range(m, connection) < 0)
502                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
503
504         if (request_parse_arguments(m, connection) < 0)
505                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
506
507         if (m->discrete) {
508                 if (!m->cursor)
509                         return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
510
511                 m->n_entries = 1;
512                 m->n_entries_set = true;
513         }
514
515         if (m->cursor)
516                 r = sd_journal_seek_cursor(m->journal, m->cursor);
517         else if (m->n_skip >= 0)
518                 r = sd_journal_seek_head(m->journal);
519         else if (m->n_skip < 0)
520                 r = sd_journal_seek_tail(m->journal);
521         if (r < 0)
522                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
523
524         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
525         if (!response)
526                 return respond_oom(connection);
527
528         MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
529
530         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
531         MHD_destroy_response(response);
532
533         return r;
534 }
535
536 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
537         const char *eq;
538         size_t j;
539
540         eq = memchr(d, '=', l);
541         if (!eq)
542                 return -EINVAL;
543
544         j = l - (eq - d + 1);
545
546         if (m == OUTPUT_JSON) {
547                 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
548                 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
549                 fputs(" }\n", f);
550         } else {
551                 fwrite(eq+1, 1, j, f);
552                 fputc('\n', f);
553         }
554
555         return 0;
556 }
557
558 static ssize_t request_reader_fields(
559                 void *cls,
560                 uint64_t pos,
561                 char *buf,
562                 size_t max) {
563
564         RequestMeta *m = cls;
565         int r;
566         size_t n, k;
567
568         assert(m);
569         assert(buf);
570         assert(max > 0);
571         assert(pos >= m->delta);
572
573         pos -= m->delta;
574
575         while (pos >= m->size) {
576                 off_t sz;
577                 const void *d;
578                 size_t l;
579
580                 /* End of this field, so let's serialize the next
581                  * one */
582
583                 if (m->n_fields_set &&
584                     m->n_fields <= 0)
585                         return MHD_CONTENT_READER_END_OF_STREAM;
586
587                 r = sd_journal_enumerate_unique(m->journal, &d, &l);
588                 if (r < 0) {
589                         log_error("Failed to advance field index: %s", strerror(-r));
590                         return MHD_CONTENT_READER_END_WITH_ERROR;
591                 } else if (r == 0)
592                         return MHD_CONTENT_READER_END_OF_STREAM;
593
594                 pos -= m->size;
595                 m->delta += m->size;
596
597                 if (m->n_fields_set)
598                         m->n_fields -= 1;
599
600                 if (m->tmp)
601                         rewind(m->tmp);
602                 else {
603                         m->tmp = tmpfile();
604                         if (!m->tmp) {
605                                 log_error("Failed to create temporary file: %m");
606                                 return MHD_CONTENT_READER_END_WITH_ERROR;
607                         }
608                 }
609
610                 r = output_field(m->tmp, m->mode, d, l);
611                 if (r < 0) {
612                         log_error("Failed to serialize item: %s", strerror(-r));
613                         return MHD_CONTENT_READER_END_WITH_ERROR;
614                 }
615
616                 sz = ftello(m->tmp);
617                 if (sz == (off_t) -1) {
618                         log_error("Failed to retrieve file position: %m");
619                         return MHD_CONTENT_READER_END_WITH_ERROR;
620                 }
621
622                 m->size = (uint64_t) sz;
623         }
624
625         if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
626                 log_error("Failed to seek to position: %m");
627                 return MHD_CONTENT_READER_END_WITH_ERROR;
628         }
629
630         n = m->size - pos;
631         if (n > max)
632                 n = max;
633
634         errno = 0;
635         k = fread(buf, 1, n, m->tmp);
636         if (k != n) {
637                 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
638                 return MHD_CONTENT_READER_END_WITH_ERROR;
639         }
640
641         return (ssize_t) k;
642 }
643
644 static int request_handler_fields(
645                 struct MHD_Connection *connection,
646                 const char *field,
647                 void *connection_cls) {
648
649         struct MHD_Response *response;
650         RequestMeta *m = connection_cls;
651         int r;
652
653         assert(connection);
654         assert(m);
655
656         r = open_journal(m);
657         if (r < 0)
658                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
659
660         if (request_parse_accept(m, connection) < 0)
661                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
662
663         r = sd_journal_query_unique(m->journal, field);
664         if (r < 0)
665                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
666
667         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
668         if (!response)
669                 return respond_oom(connection);
670
671         MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
672
673         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
674         MHD_destroy_response(response);
675
676         return r;
677 }
678
679 static int request_handler_redirect(
680                 struct MHD_Connection *connection,
681                 const char *target) {
682
683         char *page;
684         struct MHD_Response *response;
685         int ret;
686
687         assert(connection);
688         assert(target);
689
690         if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
691                 return respond_oom(connection);
692
693         response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
694         if (!response) {
695                 free(page);
696                 return respond_oom(connection);
697         }
698
699         MHD_add_response_header(response, "Content-Type", "text/html");
700         MHD_add_response_header(response, "Location", target);
701
702         ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
703         MHD_destroy_response(response);
704
705         return ret;
706 }
707
708 static int request_handler_file(
709                 struct MHD_Connection *connection,
710                 const char *path,
711                 const char *mime_type) {
712
713         struct MHD_Response *response;
714         int ret;
715         _cleanup_close_ int fd = -1;
716         struct stat st;
717
718         assert(connection);
719         assert(path);
720         assert(mime_type);
721
722         fd = open(path, O_RDONLY|O_CLOEXEC);
723         if (fd < 0)
724                 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
725
726         if (fstat(fd, &st) < 0)
727                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
728
729         response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
730         if (!response)
731                 return respond_oom(connection);
732
733         fd = -1;
734
735         MHD_add_response_header(response, "Content-Type", mime_type);
736
737         ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
738         MHD_destroy_response(response);
739
740         return ret;
741 }
742
743 static int request_handler_machine(
744                 struct MHD_Connection *connection,
745                 void *connection_cls) {
746
747         struct MHD_Response *response;
748         RequestMeta *m = connection_cls;
749         int r;
750         _cleanup_free_ char* hostname = NULL, *os_name = NULL;
751         uint64_t cutoff_from, cutoff_to, usage;
752         char *json;
753         sd_id128_t mid, bid;
754         const char *v = "bare";
755
756         assert(connection);
757         assert(m);
758
759         r = open_journal(m);
760         if (r < 0)
761                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
762
763         r = sd_id128_get_machine(&mid);
764         if (r < 0)
765                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
766
767         r = sd_id128_get_boot(&bid);
768         if (r < 0)
769                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
770
771         hostname = gethostname_malloc();
772         if (!hostname)
773                 return respond_oom(connection);
774
775         r = sd_journal_get_usage(m->journal, &usage);
776         if (r < 0)
777                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
778
779         r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
780         if (r < 0)
781                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
782
783         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
784
785         detect_virtualization(&v);
786
787         r = asprintf(&json,
788                      "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
789                      "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
790                      "\"hostname\" : \"%s\","
791                      "\"os_pretty_name\" : \"%s\","
792                      "\"virtualization\" : \"%s\","
793                      "\"usage\" : \"%llu\","
794                      "\"cutoff_from_realtime\" : \"%llu\","
795                      "\"cutoff_to_realtime\" : \"%llu\" }\n",
796                      SD_ID128_FORMAT_VAL(mid),
797                      SD_ID128_FORMAT_VAL(bid),
798                      hostname_cleanup(hostname),
799                      os_name ? os_name : "Linux",
800                      v,
801                      (unsigned long long) usage,
802                      (unsigned long long) cutoff_from,
803                      (unsigned long long) cutoff_to);
804
805         if (r < 0)
806                 return respond_oom(connection);
807
808         response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
809         if (!response) {
810                 free(json);
811                 return respond_oom(connection);
812         }
813
814         MHD_add_response_header(response, "Content-Type", "application/json");
815         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
816         MHD_destroy_response(response);
817
818         return r;
819 }
820
821 static int request_handler(
822                 void *cls,
823                 struct MHD_Connection *connection,
824                 const char *url,
825                 const char *method,
826                 const char *version,
827                 const char *upload_data,
828                 size_t *upload_data_size,
829                 void **connection_cls) {
830
831         assert(connection);
832         assert(connection_cls);
833         assert(url);
834         assert(method);
835
836         if (!streq(method, "GET"))
837                 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
838                                      "Unsupported method.\n");
839
840
841         if (!*connection_cls) {
842                 if (!request_meta(connection_cls))
843                         return respond_oom(connection);
844                 return MHD_YES;
845         }
846
847         if (streq(url, "/"))
848                 return request_handler_redirect(connection, "/browse");
849
850         if (streq(url, "/entries"))
851                 return request_handler_entries(connection, *connection_cls);
852
853         if (startswith(url, "/fields/"))
854                 return request_handler_fields(connection, url + 8, *connection_cls);
855
856         if (streq(url, "/browse"))
857                 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
858
859         if (streq(url, "/machine"))
860                 return request_handler_machine(connection, *connection_cls);
861
862         return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
863 }
864
865 static int help(void) {
866
867         printf("%s [OPTIONS...] ...\n\n"
868                "HTTP server for journal events.\n\n"
869                "  -h --help           Show this help\n"
870                "     --version        Show package version\n"
871                "     --cert=CERT.PEM  Specify server certificate in PEM format\n"
872                "     --key=KEY.PEM    Specify server key in PEM format\n",
873                program_invocation_short_name);
874
875         return 0;
876 }
877
878 static char *key_pem = NULL;
879 static char *cert_pem = NULL;
880
881 static int parse_argv(int argc, char *argv[]) {
882         enum {
883                 ARG_VERSION = 0x100,
884                 ARG_KEY,
885                 ARG_CERT,
886         };
887
888         int r, c;
889
890         static const struct option options[] = {
891                 { "help",    no_argument,       NULL, 'h'         },
892                 { "version", no_argument,       NULL, ARG_VERSION },
893                 { "key",     required_argument, NULL, ARG_KEY     },
894                 { "cert",    required_argument, NULL, ARG_CERT    },
895                 { NULL,      0,                 NULL, 0           }
896         };
897
898         assert(argc >= 0);
899         assert(argv);
900
901         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
902                 switch(c) {
903                 case ARG_VERSION:
904                         puts(PACKAGE_STRING);
905                         puts(SYSTEMD_FEATURES);
906                         return 0;
907
908                 case 'h':
909                         return help();
910
911                 case ARG_KEY:
912                         if (key_pem) {
913                                 log_error("Key file specified twice");
914                                 return -EINVAL;
915                         }
916                         r = read_full_file(optarg, &key_pem, NULL);
917                         if (r < 0) {
918                                 log_error("Failed to read key file: %s", strerror(-r));
919                                 return r;
920                         }
921                         assert(key_pem);
922                         break;
923
924                 case ARG_CERT:
925                         if (cert_pem) {
926                                 log_error("Certificate file specified twice");
927                                 return -EINVAL;
928                         }
929                         r = read_full_file(optarg, &cert_pem, NULL);
930                         if (r < 0) {
931                                 log_error("Failed to read certificate file: %s", strerror(-r));
932                                 return r;
933                         }
934                         assert(cert_pem);
935                         break;
936
937                 case '?':
938                         return -EINVAL;
939
940                 default:
941                         log_error("Unknown option code %c", c);
942                         return -EINVAL;
943                 }
944
945         if (optind < argc) {
946                 log_error("This program does not take arguments.");
947                 return -EINVAL;
948         }
949
950         if (!!key_pem != !!cert_pem) {
951                 log_error("Certificate and key files must be specified together");
952                 return -EINVAL;
953         }
954
955         return 1;
956 }
957
958 int main(int argc, char *argv[]) {
959         struct MHD_Daemon *d = NULL;
960         int r, n;
961
962         log_set_target(LOG_TARGET_AUTO);
963         log_parse_environment();
964         log_open();
965
966         r = parse_argv(argc, argv);
967         if (r < 0)
968                 return EXIT_FAILURE;
969         if (r == 0)
970                 return EXIT_SUCCESS;
971
972         n = sd_listen_fds(1);
973         if (n < 0) {
974                 log_error("Failed to determine passed sockets: %s", strerror(-n));
975                 goto finish;
976         } else if (n > 1) {
977                 log_error("Can't listen on more than one socket.");
978                 goto finish;
979         } else {
980                 struct MHD_OptionItem opts[] = {
981                         { MHD_OPTION_NOTIFY_COMPLETED,
982                           (intptr_t) request_meta_free, NULL },
983                         { MHD_OPTION_EXTERNAL_LOGGER,
984                           (intptr_t) microhttpd_logger, NULL },
985                         { MHD_OPTION_END, 0, NULL },
986                         { MHD_OPTION_END, 0, NULL },
987                         { MHD_OPTION_END, 0, NULL },
988                         { MHD_OPTION_END, 0, NULL }};
989                 int opts_pos = 2;
990                 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
991
992                 if (n > 0)
993                         opts[opts_pos++] = (struct MHD_OptionItem)
994                                 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
995                 if (key_pem) {
996                         assert(cert_pem);
997                         opts[opts_pos++] = (struct MHD_OptionItem)
998                                 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
999                         opts[opts_pos++] = (struct MHD_OptionItem)
1000                                 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1001                         flags |= MHD_USE_SSL;
1002                 }
1003
1004                 d = MHD_start_daemon(flags, 19531,
1005                                      NULL, NULL,
1006                                      request_handler, NULL,
1007                                      MHD_OPTION_ARRAY, opts,
1008                                      MHD_OPTION_END);
1009         }
1010
1011         if (!d) {
1012                 log_error("Failed to start daemon!");
1013                 goto finish;
1014         }
1015
1016         pause();
1017
1018         r = EXIT_SUCCESS;
1019
1020 finish:
1021         if (d)
1022                 MHD_stop_daemon(d);
1023
1024         return r;
1025 }