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