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