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