]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-gatewayd.c
bus: properly detect and handle if a callback is installed/removed from within a...
[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"
a7edaadd
LP
34#include "sd-bus.h"
35#include "bus-message.h"
36#include "bus-internal.h"
7b17a7d7 37#include "logs-show.h"
e64690a8 38#include "microhttpd-util.h"
858634ff 39#include "build.h"
a860325e 40#include "fileio.h"
7b17a7d7
LP
41
42typedef 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;
98206c93
LP
54
55 int argument_parse_error;
7a69007a
LP
56
57 bool follow;
c6511e85 58 bool discrete;
240a5fe8
LP
59
60 uint64_t n_fields;
61 bool n_fields_set;
7b17a7d7
LP
62} RequestMeta;
63
64static const char* const mime_types[_OUTPUT_MODE_MAX] = {
65 [OUTPUT_SHORT] = "text/plain",
66 [OUTPUT_JSON] = "application/json",
48383c25
LP
67 [OUTPUT_JSON_SSE] = "text/event-stream",
68 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
7b17a7d7
LP
69};
70
71static 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
85static 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
106static 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
9775033d 115static int respond_oom_internal(struct MHD_Connection *connection) {
7b17a7d7
LP
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
9775033d
ZJS
133#define respond_oom(connection) log_oom(), respond_oom_internal(connection)
134
7b17a7d7
LP
135static 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
168static 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
77ad3b93
LP
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)
7b17a7d7
LP
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;
7a69007a
LP
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
7b17a7d7 217 return MHD_CONTENT_READER_END_OF_STREAM;
7a69007a 218 }
7b17a7d7 219
c6511e85
LP
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
7b17a7d7
LP
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");
33b40551 247 return MHD_CONTENT_READER_END_WITH_ERROR;
7b17a7d7
LP
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
285static int request_parse_accept(
286 RequestMeta *m,
287 struct MHD_Connection *connection) {
288
6374a73b 289 const char *header;
7b17a7d7
LP
290
291 assert(m);
292 assert(connection);
293
6374a73b
ZJS
294 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
295 if (!header)
7b17a7d7
LP
296 return 0;
297
6374a73b 298 if (streq(header, mime_types[OUTPUT_JSON]))
7b17a7d7 299 m->mode = OUTPUT_JSON;
6374a73b 300 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
48383c25 301 m->mode = OUTPUT_JSON_SSE;
6374a73b 302 else if (streq(header, mime_types[OUTPUT_EXPORT]))
7b17a7d7
LP
303 m->mode = OUTPUT_EXPORT;
304 else
305 m->mode = OUTPUT_SHORT;
306
307 return 0;
308}
309
310static 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) {
4a1909bb 338 char _cleanup_free_ *t;
7b17a7d7
LP
339
340 t = strndup(colon + 1, colon2 - colon - 1);
341 if (!t)
342 return -ENOMEM;
343
344 r = safe_atoi64(t, &m->n_skip);
7b17a7d7
LP
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
98206c93
LP
376static 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
7a69007a
LP
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
c6511e85
LP
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
082d0180
LP
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
98206c93
LP
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
472static 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
7b17a7d7
LP
485static int request_handler_entries(
486 struct MHD_Connection *connection,
8530a143 487 void *connection_cls) {
7b17a7d7
LP
488
489 struct MHD_Response *response;
8530a143 490 RequestMeta *m = connection_cls;
7b17a7d7
LP
491 int r;
492
493 assert(connection);
8530a143 494 assert(m);
7b17a7d7
LP
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
98206c93
LP
506 if (request_parse_arguments(m, connection) < 0)
507 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
508
c6511e85
LP
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
7b17a7d7
LP
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
240a5fe8
LP
538static 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
560static 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");
33b40551 608 return MHD_CONTENT_READER_END_WITH_ERROR;
240a5fe8
LP
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
646static int request_handler_fields(
647 struct MHD_Connection *connection,
648 const char *field,
649 void *connection_cls) {
650
651 struct MHD_Response *response;
8530a143 652 RequestMeta *m = connection_cls;
240a5fe8
LP
653 int r;
654
655 assert(connection);
8530a143 656 assert(m);
240a5fe8
LP
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
7b17a7d7
LP
681static 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);
fadd79d2 690 assert(target);
7b17a7d7
LP
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
710static 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
a7edaadd
LP
745static 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
7b17a7d7
LP
791static int request_handler_machine(
792 struct MHD_Connection *connection,
8530a143 793 void *connection_cls) {
7b17a7d7
LP
794
795 struct MHD_Response *response;
8530a143 796 RequestMeta *m = connection_cls;
7b17a7d7
LP
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;
a7edaadd 802 _cleanup_free_ char *v = NULL;
7b17a7d7
LP
803
804 assert(connection);
8530a143 805 assert(m);
7b17a7d7
LP
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
a7edaadd 833 get_virtualization(&v);
7b17a7d7
LP
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",
a7edaadd 848 v ? v : "bare",
7b17a7d7
LP
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
869static 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);
8530a143 880 assert(connection_cls);
7b17a7d7
LP
881 assert(url);
882 assert(method);
883
884 if (!streq(method, "GET"))
a93035ce
ZJS
885 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
886 "Unsupported method.\n");
887
7b17a7d7 888
8530a143
ZJS
889 if (!*connection_cls) {
890 if (!request_meta(connection_cls))
891 return respond_oom(connection);
892 return MHD_YES;
893 }
894
7b17a7d7
LP
895 if (streq(url, "/"))
896 return request_handler_redirect(connection, "/browse");
897
898 if (streq(url, "/entries"))
8530a143 899 return request_handler_entries(connection, *connection_cls);
7b17a7d7 900
240a5fe8 901 if (startswith(url, "/fields/"))
8530a143 902 return request_handler_fields(connection, url + 8, *connection_cls);
240a5fe8 903
7b17a7d7
LP
904 if (streq(url, "/browse"))
905 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
906
907 if (streq(url, "/machine"))
8530a143 908 return request_handler_machine(connection, *connection_cls);
7b17a7d7
LP
909
910 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
911}
912
c3a7cfb7
ZJS
913static 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
858634ff
ZJS
926static char *key_pem = NULL;
927static char *cert_pem = NULL;
928
929static 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[] = {
c3a7cfb7 939 { "help", no_argument, NULL, 'h' },
858634ff
ZJS
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
c3a7cfb7 949 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
858634ff
ZJS
950 switch(c) {
951 case ARG_VERSION:
952 puts(PACKAGE_STRING);
953 puts(SYSTEMD_FEATURES);
954 return 0;
955
c3a7cfb7
ZJS
956 case 'h':
957 return help();
958
858634ff
ZJS
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;
7b17a7d7 971
858634ff
ZJS
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) {
7b17a7d7 994 log_error("This program does not take arguments.");
858634ff
ZJS
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;
7b17a7d7
LP
1001 }
1002
858634ff
ZJS
1003 return 1;
1004}
1005
1006int main(int argc, char *argv[]) {
1007 struct MHD_Daemon *d = NULL;
1008 int r, n;
1009
77ad3b93 1010 log_set_target(LOG_TARGET_AUTO);
7b17a7d7
LP
1011 log_parse_environment();
1012 log_open();
1013
858634ff
ZJS
1014 r = parse_argv(argc, argv);
1015 if (r < 0)
1016 return EXIT_FAILURE;
1017 if (r == 0)
1018 return EXIT_SUCCESS;
1019
7b17a7d7
LP
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;
7b17a7d7 1027 } else {
c54ff8e3
ZJS
1028 struct MHD_OptionItem opts[] = {
1029 { MHD_OPTION_NOTIFY_COMPLETED,
1030 (intptr_t) request_meta_free, NULL },
e64690a8
ZJS
1031 { MHD_OPTION_EXTERNAL_LOGGER,
1032 (intptr_t) microhttpd_logger, NULL },
c54ff8e3 1033 { MHD_OPTION_END, 0, NULL },
858634ff
ZJS
1034 { MHD_OPTION_END, 0, NULL },
1035 { MHD_OPTION_END, 0, NULL },
c54ff8e3 1036 { MHD_OPTION_END, 0, NULL }};
e64690a8 1037 int opts_pos = 2;
858634ff
ZJS
1038 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1039
c54ff8e3 1040 if (n > 0)
858634ff
ZJS
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);
7b17a7d7
LP
1057 }
1058
6374a73b 1059 if (!d) {
7b17a7d7
LP
1060 log_error("Failed to start daemon!");
1061 goto finish;
1062 }
1063
1064 pause();
1065
1066 r = EXIT_SUCCESS;
1067
1068finish:
6374a73b
ZJS
1069 if (d)
1070 MHD_stop_daemon(d);
7b17a7d7
LP
1071
1072 return r;
1073}