chiark / gitweb /
6a5db968e398e0928e873e1a7105c1126bde513b
[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 *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                         free(t);
339                         if (r < 0)
340                                 return r;
341                 }
342
343                 p = (colon2 ? colon2 : colon) + 1;
344                 if (*p) {
345                         r = safe_atou64(p, &m->n_entries);
346                         if (r < 0)
347                                 return r;
348
349                         if (m->n_entries <= 0)
350                                 return -EINVAL;
351
352                         m->n_entries_set = true;
353                 }
354
355                 m->cursor = strndup(range, colon - range);
356         }
357
358         if (!m->cursor)
359                 return -ENOMEM;
360
361         m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
362         if (isempty(m->cursor)) {
363                 free(m->cursor);
364                 m->cursor = NULL;
365         }
366
367         return 0;
368 }
369
370 static int request_parse_arguments_iterator(
371                 void *cls,
372                 enum MHD_ValueKind kind,
373                 const char *key,
374                 const char *value) {
375
376         RequestMeta *m = cls;
377         _cleanup_free_ char *p = NULL;
378         int r;
379
380         assert(m);
381
382         if (isempty(key)) {
383                 m->argument_parse_error = -EINVAL;
384                 return MHD_NO;
385         }
386
387         if (streq(key, "follow")) {
388                 if (isempty(value)) {
389                         m->follow = true;
390                         return MHD_YES;
391                 }
392
393                 r = parse_boolean(value);
394                 if (r < 0) {
395                         m->argument_parse_error = r;
396                         return MHD_NO;
397                 }
398
399                 m->follow = r;
400                 return MHD_YES;
401         }
402
403         if (streq(key, "discrete")) {
404                 if (isempty(value)) {
405                         m->discrete = true;
406                         return MHD_YES;
407                 }
408
409                 r = parse_boolean(value);
410                 if (r < 0) {
411                         m->argument_parse_error = r;
412                         return MHD_NO;
413                 }
414
415                 m->discrete = r;
416                 return MHD_YES;
417         }
418
419         if (streq(key, "boot")) {
420                 if (isempty(value))
421                         r = true;
422                 else {
423                         r = parse_boolean(value);
424                         if (r < 0) {
425                                 m->argument_parse_error = r;
426                                 return MHD_NO;
427                         }
428                 }
429
430                 if (r) {
431                         char match[9 + 32 + 1] = "_BOOT_ID=";
432                         sd_id128_t bid;
433
434                         r = sd_id128_get_boot(&bid);
435                         if (r < 0) {
436                                 log_error("Failed to get boot ID: %s", strerror(-r));
437                                 return MHD_NO;
438                         }
439
440                         sd_id128_to_string(bid, match + 9);
441                         r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
442                         if (r < 0) {
443                                 m->argument_parse_error = r;
444                                 return MHD_NO;
445                         }
446                 }
447
448                 return MHD_YES;
449         }
450
451         p = strjoin(key, "=", strempty(value), NULL);
452         if (!p) {
453                 m->argument_parse_error = log_oom();
454                 return MHD_NO;
455         }
456
457         r = sd_journal_add_match(m->journal, p, 0);
458         if (r < 0) {
459                 m->argument_parse_error = r;
460                 return MHD_NO;
461         }
462
463         return MHD_YES;
464 }
465
466 static int request_parse_arguments(
467                 RequestMeta *m,
468                 struct MHD_Connection *connection) {
469
470         assert(m);
471         assert(connection);
472
473         m->argument_parse_error = 0;
474         MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
475
476         return m->argument_parse_error;
477 }
478
479 static int request_handler_entries(
480                 struct MHD_Connection *connection,
481                 void **connection_cls) {
482
483         struct MHD_Response *response;
484         RequestMeta *m;
485         int r;
486
487         assert(connection);
488         assert(connection_cls);
489
490         m = request_meta(connection_cls);
491         if (!m)
492                 return respond_oom(connection);
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;
651         int r;
652
653         assert(connection);
654         assert(connection_cls);
655
656         m = request_meta(connection_cls);
657         if (!m)
658                 return respond_oom(connection);
659
660         r = open_journal(m);
661         if (r < 0)
662                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
663
664         if (request_parse_accept(m, connection) < 0)
665                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
666
667         r = sd_journal_query_unique(m->journal, field);
668         if (r < 0)
669                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
670
671         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
672         if (!response)
673                 return respond_oom(connection);
674
675         MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
676
677         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
678         MHD_destroy_response(response);
679
680         return r;
681 }
682
683 static int request_handler_redirect(
684                 struct MHD_Connection *connection,
685                 const char *target) {
686
687         char *page;
688         struct MHD_Response *response;
689         int ret;
690
691         assert(connection);
692         assert(target);
693
694         if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
695                 return respond_oom(connection);
696
697         response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
698         if (!response) {
699                 free(page);
700                 return respond_oom(connection);
701         }
702
703         MHD_add_response_header(response, "Content-Type", "text/html");
704         MHD_add_response_header(response, "Location", target);
705
706         ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
707         MHD_destroy_response(response);
708
709         return ret;
710 }
711
712 static int request_handler_file(
713                 struct MHD_Connection *connection,
714                 const char *path,
715                 const char *mime_type) {
716
717         struct MHD_Response *response;
718         int ret;
719         _cleanup_close_ int fd = -1;
720         struct stat st;
721
722         assert(connection);
723         assert(path);
724         assert(mime_type);
725
726         fd = open(path, O_RDONLY|O_CLOEXEC);
727         if (fd < 0)
728                 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
729
730         if (fstat(fd, &st) < 0)
731                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
732
733         response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
734         if (!response)
735                 return respond_oom(connection);
736
737         fd = -1;
738
739         MHD_add_response_header(response, "Content-Type", mime_type);
740
741         ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
742         MHD_destroy_response(response);
743
744         return ret;
745 }
746
747 static int request_handler_machine(
748                 struct MHD_Connection *connection,
749                 void **connection_cls) {
750
751         struct MHD_Response *response;
752         RequestMeta *m;
753         int r;
754         _cleanup_free_ char* hostname = NULL, *os_name = NULL;
755         uint64_t cutoff_from, cutoff_to, usage;
756         char *json;
757         sd_id128_t mid, bid;
758         const char *v = "bare";
759
760         assert(connection);
761
762         m = request_meta(connection_cls);
763         if (!m)
764                 return respond_oom(connection);
765
766         r = open_journal(m);
767         if (r < 0)
768                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
769
770         r = sd_id128_get_machine(&mid);
771         if (r < 0)
772                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
773
774         r = sd_id128_get_boot(&bid);
775         if (r < 0)
776                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
777
778         hostname = gethostname_malloc();
779         if (!hostname)
780                 return respond_oom(connection);
781
782         r = sd_journal_get_usage(m->journal, &usage);
783         if (r < 0)
784                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
785
786         r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
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         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
791
792         detect_virtualization(&v);
793
794         r = asprintf(&json,
795                      "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
796                      "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
797                      "\"hostname\" : \"%s\","
798                      "\"os_pretty_name\" : \"%s\","
799                      "\"virtualization\" : \"%s\","
800                      "\"usage\" : \"%llu\","
801                      "\"cutoff_from_realtime\" : \"%llu\","
802                      "\"cutoff_to_realtime\" : \"%llu\" }\n",
803                      SD_ID128_FORMAT_VAL(mid),
804                      SD_ID128_FORMAT_VAL(bid),
805                      hostname_cleanup(hostname),
806                      os_name ? os_name : "Linux",
807                      v,
808                      (unsigned long long) usage,
809                      (unsigned long long) cutoff_from,
810                      (unsigned long long) cutoff_to);
811
812         if (r < 0)
813                 return respond_oom(connection);
814
815         response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
816         if (!response) {
817                 free(json);
818                 return respond_oom(connection);
819         }
820
821         MHD_add_response_header(response, "Content-Type", "application/json");
822         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
823         MHD_destroy_response(response);
824
825         return r;
826 }
827
828 static int request_handler(
829                 void *cls,
830                 struct MHD_Connection *connection,
831                 const char *url,
832                 const char *method,
833                 const char *version,
834                 const char *upload_data,
835                 size_t *upload_data_size,
836                 void **connection_cls) {
837
838         assert(connection);
839         assert(url);
840         assert(method);
841
842         if (!streq(method, "GET"))
843                 return MHD_NO;
844
845         if (streq(url, "/"))
846                 return request_handler_redirect(connection, "/browse");
847
848         if (streq(url, "/entries"))
849                 return request_handler_entries(connection, connection_cls);
850
851         if (startswith(url, "/fields/"))
852                 return request_handler_fields(connection, url + 8, connection_cls);
853
854         if (streq(url, "/browse"))
855                 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
856
857         if (streq(url, "/machine"))
858                 return request_handler_machine(connection, connection_cls);
859
860         return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
861 }
862
863 int main(int argc, char *argv[]) {
864         struct MHD_Daemon *d = NULL;
865         int r = EXIT_FAILURE, n;
866
867         if (argc > 1) {
868                 log_error("This program does not take arguments.");
869                 goto finish;
870         }
871
872         log_set_target(LOG_TARGET_AUTO);
873         log_parse_environment();
874         log_open();
875
876         n = sd_listen_fds(1);
877         if (n < 0) {
878                 log_error("Failed to determine passed sockets: %s", strerror(-n));
879                 goto finish;
880         } else if (n > 1) {
881                 log_error("Can't listen on more than one socket.");
882                 goto finish;
883         } else if (n > 0) {
884                 d = MHD_start_daemon(
885                                 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
886                                 19531,
887                                 NULL, NULL,
888                                 request_handler, NULL,
889                                 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
890                                 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
891                                 MHD_OPTION_END);
892         } else {
893                 d = MHD_start_daemon(
894                                 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
895                                 19531,
896                                 NULL, NULL,
897                                 request_handler, NULL,
898                                 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
899                                 MHD_OPTION_END);
900         }
901
902         if (!d) {
903                 log_error("Failed to start daemon!");
904                 goto finish;
905         }
906
907         pause();
908
909         r = EXIT_SUCCESS;
910
911 finish:
912         if (d)
913                 MHD_stop_daemon(d);
914
915         return r;
916 }