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