]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journal-gatewayd.c
journal-gatewayd: query PID 1 for virtalization
[thirdparty/systemd.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_ONLY);
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);
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 char _cleanup_free_ *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 *m = NULL, *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_message_new_method_call(
757 bus,
758 "org.freedesktop.systemd1",
759 "/org/freedesktop/systemd1",
760 "org.freedesktop.DBus.Properties",
761 "Get",
762 &m);
763 if (r < 0)
764 return r;
765
766 r = sd_bus_message_append(m, "ss", "org.freedesktop.systemd1.Manager", "Virtualization");
767 if (r < 0)
768 return r;
769
770 r = sd_bus_send_with_reply_and_block(bus, m, 0, NULL, &reply);
771 if (r < 0)
772 return r;
773
774 r = sd_bus_message_read(reply, "v", "s", &t);
775 if (r < 0)
776 return r;
777
778 if (isempty(t)) {
779 *v = NULL;
780 return 0;
781 }
782
783 b = strdup(t);
784 if (!b)
785 return -ENOMEM;
786
787 *v = b;
788 return 1;
789 }
790
791 static int request_handler_machine(
792 struct MHD_Connection *connection,
793 void *connection_cls) {
794
795 struct MHD_Response *response;
796 RequestMeta *m = connection_cls;
797 int r;
798 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
799 uint64_t cutoff_from, cutoff_to, usage;
800 char *json;
801 sd_id128_t mid, bid;
802 _cleanup_free_ char *v = NULL;
803
804 assert(connection);
805 assert(m);
806
807 r = open_journal(m);
808 if (r < 0)
809 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
810
811 r = sd_id128_get_machine(&mid);
812 if (r < 0)
813 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
814
815 r = sd_id128_get_boot(&bid);
816 if (r < 0)
817 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
818
819 hostname = gethostname_malloc();
820 if (!hostname)
821 return respond_oom(connection);
822
823 r = sd_journal_get_usage(m->journal, &usage);
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 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
828 if (r < 0)
829 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
830
831 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
832
833 get_virtualization(&v);
834
835 r = asprintf(&json,
836 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
837 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
838 "\"hostname\" : \"%s\","
839 "\"os_pretty_name\" : \"%s\","
840 "\"virtualization\" : \"%s\","
841 "\"usage\" : \"%llu\","
842 "\"cutoff_from_realtime\" : \"%llu\","
843 "\"cutoff_to_realtime\" : \"%llu\" }\n",
844 SD_ID128_FORMAT_VAL(mid),
845 SD_ID128_FORMAT_VAL(bid),
846 hostname_cleanup(hostname),
847 os_name ? os_name : "Linux",
848 v ? v : "bare",
849 (unsigned long long) usage,
850 (unsigned long long) cutoff_from,
851 (unsigned long long) cutoff_to);
852
853 if (r < 0)
854 return respond_oom(connection);
855
856 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
857 if (!response) {
858 free(json);
859 return respond_oom(connection);
860 }
861
862 MHD_add_response_header(response, "Content-Type", "application/json");
863 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
864 MHD_destroy_response(response);
865
866 return r;
867 }
868
869 static int request_handler(
870 void *cls,
871 struct MHD_Connection *connection,
872 const char *url,
873 const char *method,
874 const char *version,
875 const char *upload_data,
876 size_t *upload_data_size,
877 void **connection_cls) {
878
879 assert(connection);
880 assert(connection_cls);
881 assert(url);
882 assert(method);
883
884 if (!streq(method, "GET"))
885 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
886 "Unsupported method.\n");
887
888
889 if (!*connection_cls) {
890 if (!request_meta(connection_cls))
891 return respond_oom(connection);
892 return MHD_YES;
893 }
894
895 if (streq(url, "/"))
896 return request_handler_redirect(connection, "/browse");
897
898 if (streq(url, "/entries"))
899 return request_handler_entries(connection, *connection_cls);
900
901 if (startswith(url, "/fields/"))
902 return request_handler_fields(connection, url + 8, *connection_cls);
903
904 if (streq(url, "/browse"))
905 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
906
907 if (streq(url, "/machine"))
908 return request_handler_machine(connection, *connection_cls);
909
910 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
911 }
912
913 static int help(void) {
914
915 printf("%s [OPTIONS...] ...\n\n"
916 "HTTP server for journal events.\n\n"
917 " -h --help Show this help\n"
918 " --version Show package version\n"
919 " --cert=CERT.PEM Specify server certificate in PEM format\n"
920 " --key=KEY.PEM Specify server key in PEM format\n",
921 program_invocation_short_name);
922
923 return 0;
924 }
925
926 static char *key_pem = NULL;
927 static char *cert_pem = NULL;
928
929 static int parse_argv(int argc, char *argv[]) {
930 enum {
931 ARG_VERSION = 0x100,
932 ARG_KEY,
933 ARG_CERT,
934 };
935
936 int r, c;
937
938 static const struct option options[] = {
939 { "help", no_argument, NULL, 'h' },
940 { "version", no_argument, NULL, ARG_VERSION },
941 { "key", required_argument, NULL, ARG_KEY },
942 { "cert", required_argument, NULL, ARG_CERT },
943 { NULL, 0, NULL, 0 }
944 };
945
946 assert(argc >= 0);
947 assert(argv);
948
949 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
950 switch(c) {
951 case ARG_VERSION:
952 puts(PACKAGE_STRING);
953 puts(SYSTEMD_FEATURES);
954 return 0;
955
956 case 'h':
957 return help();
958
959 case ARG_KEY:
960 if (key_pem) {
961 log_error("Key file specified twice");
962 return -EINVAL;
963 }
964 r = read_full_file(optarg, &key_pem, NULL);
965 if (r < 0) {
966 log_error("Failed to read key file: %s", strerror(-r));
967 return r;
968 }
969 assert(key_pem);
970 break;
971
972 case ARG_CERT:
973 if (cert_pem) {
974 log_error("Certificate file specified twice");
975 return -EINVAL;
976 }
977 r = read_full_file(optarg, &cert_pem, NULL);
978 if (r < 0) {
979 log_error("Failed to read certificate file: %s", strerror(-r));
980 return r;
981 }
982 assert(cert_pem);
983 break;
984
985 case '?':
986 return -EINVAL;
987
988 default:
989 log_error("Unknown option code %c", c);
990 return -EINVAL;
991 }
992
993 if (optind < argc) {
994 log_error("This program does not take arguments.");
995 return -EINVAL;
996 }
997
998 if (!!key_pem != !!cert_pem) {
999 log_error("Certificate and key files must be specified together");
1000 return -EINVAL;
1001 }
1002
1003 return 1;
1004 }
1005
1006 int main(int argc, char *argv[]) {
1007 struct MHD_Daemon *d = NULL;
1008 int r, n;
1009
1010 log_set_target(LOG_TARGET_AUTO);
1011 log_parse_environment();
1012 log_open();
1013
1014 r = parse_argv(argc, argv);
1015 if (r < 0)
1016 return EXIT_FAILURE;
1017 if (r == 0)
1018 return EXIT_SUCCESS;
1019
1020 n = sd_listen_fds(1);
1021 if (n < 0) {
1022 log_error("Failed to determine passed sockets: %s", strerror(-n));
1023 goto finish;
1024 } else if (n > 1) {
1025 log_error("Can't listen on more than one socket.");
1026 goto finish;
1027 } else {
1028 struct MHD_OptionItem opts[] = {
1029 { MHD_OPTION_NOTIFY_COMPLETED,
1030 (intptr_t) request_meta_free, NULL },
1031 { MHD_OPTION_EXTERNAL_LOGGER,
1032 (intptr_t) microhttpd_logger, NULL },
1033 { MHD_OPTION_END, 0, NULL },
1034 { MHD_OPTION_END, 0, NULL },
1035 { MHD_OPTION_END, 0, NULL },
1036 { MHD_OPTION_END, 0, NULL }};
1037 int opts_pos = 2;
1038 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1039
1040 if (n > 0)
1041 opts[opts_pos++] = (struct MHD_OptionItem)
1042 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1043 if (key_pem) {
1044 assert(cert_pem);
1045 opts[opts_pos++] = (struct MHD_OptionItem)
1046 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1047 opts[opts_pos++] = (struct MHD_OptionItem)
1048 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1049 flags |= MHD_USE_SSL;
1050 }
1051
1052 d = MHD_start_daemon(flags, 19531,
1053 NULL, NULL,
1054 request_handler, NULL,
1055 MHD_OPTION_ARRAY, opts,
1056 MHD_OPTION_END);
1057 }
1058
1059 if (!d) {
1060 log_error("Failed to start daemon!");
1061 goto finish;
1062 }
1063
1064 pause();
1065
1066 r = EXIT_SUCCESS;
1067
1068 finish:
1069 if (d)
1070 MHD_stop_daemon(d);
1071
1072 return r;
1073 }