chiark / gitweb /
journal: add ability to list unique fields to gatewayd
[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         p = strjoin(key, "=", strempty(value), NULL);
420         if (!p) {
421                 m->argument_parse_error = log_oom();
422                 return MHD_NO;
423         }
424
425         r = sd_journal_add_match(m->journal, p, 0);
426         if (r < 0) {
427                 m->argument_parse_error = r;
428                 return MHD_NO;
429         }
430
431         return MHD_YES;
432 }
433
434 static int request_parse_arguments(
435                 RequestMeta *m,
436                 struct MHD_Connection *connection) {
437
438         assert(m);
439         assert(connection);
440
441         m->argument_parse_error = 0;
442         MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
443
444         return m->argument_parse_error;
445 }
446
447 static int request_handler_entries(
448                 struct MHD_Connection *connection,
449                 void **connection_cls) {
450
451         struct MHD_Response *response;
452         RequestMeta *m;
453         int r;
454
455         assert(connection);
456         assert(connection_cls);
457
458         m = request_meta(connection_cls);
459         if (!m)
460                 return respond_oom(connection);
461
462         r = open_journal(m);
463         if (r < 0)
464                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
465
466         if (request_parse_accept(m, connection) < 0)
467                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
468
469         if (request_parse_range(m, connection) < 0)
470                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
471
472         if (request_parse_arguments(m, connection) < 0)
473                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
474
475         if (m->discrete) {
476                 if (!m->cursor)
477                         return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
478
479                 m->n_entries = 1;
480                 m->n_entries_set = true;
481         }
482
483         if (m->cursor)
484                 r = sd_journal_seek_cursor(m->journal, m->cursor);
485         else if (m->n_skip >= 0)
486                 r = sd_journal_seek_head(m->journal);
487         else if (m->n_skip < 0)
488                 r = sd_journal_seek_tail(m->journal);
489         if (r < 0)
490                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
491
492         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
493         if (!response)
494                 return respond_oom(connection);
495
496         MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
497
498         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
499         MHD_destroy_response(response);
500
501         return r;
502 }
503
504 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
505         const char *eq;
506         size_t j;
507
508         eq = memchr(d, '=', l);
509         if (!eq)
510                 return -EINVAL;
511
512         j = l - (eq - d + 1);
513
514         if (m == OUTPUT_JSON) {
515                 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
516                 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
517                 fputs(" }\n", f);
518         } else {
519                 fwrite(eq+1, 1, j, f);
520                 fputc('\n', f);
521         }
522
523         return 0;
524 }
525
526 static ssize_t request_reader_fields(
527                 void *cls,
528                 uint64_t pos,
529                 char *buf,
530                 size_t max) {
531
532         RequestMeta *m = cls;
533         int r;
534         size_t n, k;
535
536         assert(m);
537         assert(buf);
538         assert(max > 0);
539         assert(pos >= m->delta);
540
541         pos -= m->delta;
542
543         while (pos >= m->size) {
544                 off_t sz;
545                 const void *d;
546                 size_t l;
547
548                 /* End of this field, so let's serialize the next
549                  * one */
550
551                 if (m->n_fields_set &&
552                     m->n_fields <= 0)
553                         return MHD_CONTENT_READER_END_OF_STREAM;
554
555                 r = sd_journal_enumerate_unique(m->journal, &d, &l);
556                 if (r < 0) {
557                         log_error("Failed to advance field index: %s", strerror(-r));
558                         return MHD_CONTENT_READER_END_WITH_ERROR;
559                 } else if (r == 0)
560                         return MHD_CONTENT_READER_END_OF_STREAM;
561
562                 pos -= m->size;
563                 m->delta += m->size;
564
565                 if (m->n_fields_set)
566                         m->n_fields -= 1;
567
568                 if (m->tmp)
569                         rewind(m->tmp);
570                 else {
571                         m->tmp = tmpfile();
572                         if (!m->tmp) {
573                                 log_error("Failed to create temporary file: %m");
574                                 return MHD_CONTENT_READER_END_WITH_ERROR;;
575                         }
576                 }
577
578                 r = output_field(m->tmp, m->mode, d, l);
579                 if (r < 0) {
580                         log_error("Failed to serialize item: %s", strerror(-r));
581                         return MHD_CONTENT_READER_END_WITH_ERROR;
582                 }
583
584                 sz = ftello(m->tmp);
585                 if (sz == (off_t) -1) {
586                         log_error("Failed to retrieve file position: %m");
587                         return MHD_CONTENT_READER_END_WITH_ERROR;
588                 }
589
590                 m->size = (uint64_t) sz;
591         }
592
593         if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
594                 log_error("Failed to seek to position: %m");
595                 return MHD_CONTENT_READER_END_WITH_ERROR;
596         }
597
598         n = m->size - pos;
599         if (n > max)
600                 n = max;
601
602         errno = 0;
603         k = fread(buf, 1, n, m->tmp);
604         if (k != n) {
605                 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
606                 return MHD_CONTENT_READER_END_WITH_ERROR;
607         }
608
609         return (ssize_t) k;
610 }
611
612 static int request_handler_fields(
613                 struct MHD_Connection *connection,
614                 const char *field,
615                 void *connection_cls) {
616
617         struct MHD_Response *response;
618         RequestMeta *m;
619         int r;
620
621         assert(connection);
622         assert(connection_cls);
623
624         m = request_meta(connection_cls);
625         if (!m)
626                 return respond_oom(connection);
627
628         r = open_journal(m);
629         if (r < 0)
630                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
631
632         if (request_parse_accept(m, connection) < 0)
633                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
634
635         r = sd_journal_query_unique(m->journal, field);
636         if (r < 0)
637                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
638
639         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
640         if (!response)
641                 return respond_oom(connection);
642
643         MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
644
645         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
646         MHD_destroy_response(response);
647
648         return r;
649 }
650
651 static int request_handler_redirect(
652                 struct MHD_Connection *connection,
653                 const char *target) {
654
655         char *page;
656         struct MHD_Response *response;
657         int ret;
658
659         assert(connection);
660         assert(target);
661
662         if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
663                 return respond_oom(connection);
664
665         response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
666         if (!response) {
667                 free(page);
668                 return respond_oom(connection);
669         }
670
671         MHD_add_response_header(response, "Content-Type", "text/html");
672         MHD_add_response_header(response, "Location", target);
673
674         ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
675         MHD_destroy_response(response);
676
677         return ret;
678 }
679
680 static int request_handler_file(
681                 struct MHD_Connection *connection,
682                 const char *path,
683                 const char *mime_type) {
684
685         struct MHD_Response *response;
686         int ret;
687         _cleanup_close_ int fd = -1;
688         struct stat st;
689
690         assert(connection);
691         assert(path);
692         assert(mime_type);
693
694         fd = open(path, O_RDONLY|O_CLOEXEC);
695         if (fd < 0)
696                 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
697
698         if (fstat(fd, &st) < 0)
699                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
700
701         response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
702         if (!response)
703                 return respond_oom(connection);
704
705         fd = -1;
706
707         MHD_add_response_header(response, "Content-Type", mime_type);
708
709         ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
710         MHD_destroy_response(response);
711
712         return ret;
713 }
714
715 static int request_handler_machine(
716                 struct MHD_Connection *connection,
717                 void **connection_cls) {
718
719         struct MHD_Response *response;
720         RequestMeta *m;
721         int r;
722         _cleanup_free_ char* hostname = NULL, *os_name = NULL;
723         uint64_t cutoff_from, cutoff_to, usage;
724         char *json;
725         sd_id128_t mid, bid;
726         const char *v = "bare";
727
728         assert(connection);
729
730         m = request_meta(connection_cls);
731         if (!m)
732                 return respond_oom(connection);
733
734         r = open_journal(m);
735         if (r < 0)
736                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
737
738         r = sd_id128_get_machine(&mid);
739         if (r < 0)
740                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
741
742         r = sd_id128_get_boot(&bid);
743         if (r < 0)
744                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
745
746         hostname = gethostname_malloc();
747         if (!hostname)
748                 return respond_oom(connection);
749
750         r = sd_journal_get_usage(m->journal, &usage);
751         if (r < 0)
752                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
753
754         r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
755         if (r < 0)
756                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
757
758         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
759
760         detect_virtualization(&v);
761
762         r = asprintf(&json,
763                      "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
764                      "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
765                      "\"hostname\" : \"%s\","
766                      "\"os_pretty_name\" : \"%s\","
767                      "\"virtualization\" : \"%s\","
768                      "\"usage\" : \"%llu\","
769                      "\"cutoff_from_realtime\" : \"%llu\","
770                      "\"cutoff_to_realtime\" : \"%llu\" }\n",
771                      SD_ID128_FORMAT_VAL(mid),
772                      SD_ID128_FORMAT_VAL(bid),
773                      hostname_cleanup(hostname),
774                      os_name ? os_name : "Linux",
775                      v,
776                      (unsigned long long) usage,
777                      (unsigned long long) cutoff_from,
778                      (unsigned long long) cutoff_to);
779
780         if (r < 0)
781                 return respond_oom(connection);
782
783         response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
784         if (!response) {
785                 free(json);
786                 return respond_oom(connection);
787         }
788
789         MHD_add_response_header(response, "Content-Type", "application/json");
790         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
791         MHD_destroy_response(response);
792
793         return r;
794 }
795
796 static int request_handler(
797                 void *cls,
798                 struct MHD_Connection *connection,
799                 const char *url,
800                 const char *method,
801                 const char *version,
802                 const char *upload_data,
803                 size_t *upload_data_size,
804                 void **connection_cls) {
805
806         assert(connection);
807         assert(url);
808         assert(method);
809
810         if (!streq(method, "GET"))
811                 return MHD_NO;
812
813         if (streq(url, "/"))
814                 return request_handler_redirect(connection, "/browse");
815
816         if (streq(url, "/entries"))
817                 return request_handler_entries(connection, connection_cls);
818
819         if (startswith(url, "/fields/"))
820                 return request_handler_fields(connection, url + 8, connection_cls);
821
822         if (streq(url, "/browse"))
823                 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
824
825         if (streq(url, "/machine"))
826                 return request_handler_machine(connection, connection_cls);
827
828         return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
829 }
830
831 int main(int argc, char *argv[]) {
832         struct MHD_Daemon *d = NULL;
833         int r = EXIT_FAILURE, n;
834
835         if (argc > 1) {
836                 log_error("This program does not take arguments.");
837                 goto finish;
838         }
839
840         log_set_target(LOG_TARGET_AUTO);
841         log_parse_environment();
842         log_open();
843
844         n = sd_listen_fds(1);
845         if (n < 0) {
846                 log_error("Failed to determine passed sockets: %s", strerror(-n));
847                 goto finish;
848         } else if (n > 1) {
849                 log_error("Can't listen on more than one socket.");
850                 goto finish;
851         } else if (n > 0) {
852                 d = MHD_start_daemon(
853                                 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
854                                 19531,
855                                 NULL, NULL,
856                                 request_handler, NULL,
857                                 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
858                                 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
859                                 MHD_OPTION_END);
860         } else {
861                 d = MHD_start_daemon(
862                                 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
863                                 19531,
864                                 NULL, NULL,
865                                 request_handler, NULL,
866                                 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
867                                 MHD_OPTION_END);
868         }
869
870         if (!d) {
871                 log_error("Failed to start daemon!");
872                 goto finish;
873         }
874
875         pause();
876
877         r = EXIT_SUCCESS;
878
879 finish:
880         if (d)
881                 MHD_stop_daemon(d);
882
883         return r;
884 }