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