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