]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-gatewayd.c
inhibit: more conversions to use bus_log_parse_error()
[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 34#include "sd-bus.h"
40ca29a1 35#include "bus-util.h"
7b17a7d7 36#include "logs-show.h"
e64690a8 37#include "microhttpd-util.h"
858634ff 38#include "build.h"
a860325e 39#include "fileio.h"
7b17a7d7
LP
40
41typedef 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;
98206c93
LP
53
54 int argument_parse_error;
7a69007a
LP
55
56 bool follow;
c6511e85 57 bool discrete;
240a5fe8
LP
58
59 uint64_t n_fields;
60 bool n_fields_set;
7b17a7d7
LP
61} RequestMeta;
62
63static const char* const mime_types[_OUTPUT_MODE_MAX] = {
64 [OUTPUT_SHORT] = "text/plain",
65 [OUTPUT_JSON] = "application/json",
48383c25
LP
66 [OUTPUT_JSON_SSE] = "text/event-stream",
67 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
7b17a7d7
LP
68};
69
70static 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
84static 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
105static int open_journal(RequestMeta *m) {
106 assert(m);
107
108 if (m->journal)
109 return 0;
110
a688baa8 111 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
7b17a7d7
LP
112}
113
9775033d 114static int respond_oom_internal(struct MHD_Connection *connection) {
7b17a7d7
LP
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
9775033d
ZJS
132#define respond_oom(connection) log_oom(), respond_oom_internal(connection)
133
7b17a7d7
LP
134static 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
167static 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
77ad3b93
LP
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)
7b17a7d7
LP
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;
7a69007a
LP
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
7b17a7d7 216 return MHD_CONTENT_READER_END_OF_STREAM;
7a69007a 217 }
7b17a7d7 218
c6511e85
LP
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
7b17a7d7
LP
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");
33b40551 246 return MHD_CONTENT_READER_END_WITH_ERROR;
7b17a7d7
LP
247 }
248 }
249
94e0bd7d 250 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
7b17a7d7
LP
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
284static int request_parse_accept(
285 RequestMeta *m,
286 struct MHD_Connection *connection) {
287
6374a73b 288 const char *header;
7b17a7d7
LP
289
290 assert(m);
291 assert(connection);
292
6374a73b
ZJS
293 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
294 if (!header)
7b17a7d7
LP
295 return 0;
296
6374a73b 297 if (streq(header, mime_types[OUTPUT_JSON]))
7b17a7d7 298 m->mode = OUTPUT_JSON;
6374a73b 299 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
48383c25 300 m->mode = OUTPUT_JSON_SSE;
6374a73b 301 else if (streq(header, mime_types[OUTPUT_EXPORT]))
7b17a7d7
LP
302 m->mode = OUTPUT_EXPORT;
303 else
304 m->mode = OUTPUT_SHORT;
305
306 return 0;
307}
308
309static 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) {
7fd1b19b 337 _cleanup_free_ char *t;
7b17a7d7
LP
338
339 t = strndup(colon + 1, colon2 - colon - 1);
340 if (!t)
341 return -ENOMEM;
342
343 r = safe_atoi64(t, &m->n_skip);
7b17a7d7
LP
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
98206c93
LP
375static 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
7a69007a
LP
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
c6511e85
LP
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
082d0180
LP
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
98206c93
LP
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
471static 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
7b17a7d7
LP
484static int request_handler_entries(
485 struct MHD_Connection *connection,
8530a143 486 void *connection_cls) {
7b17a7d7
LP
487
488 struct MHD_Response *response;
8530a143 489 RequestMeta *m = connection_cls;
7b17a7d7
LP
490 int r;
491
492 assert(connection);
8530a143 493 assert(m);
7b17a7d7
LP
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
98206c93
LP
505 if (request_parse_arguments(m, connection) < 0)
506 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
507
c6511e85
LP
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
7b17a7d7
LP
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
240a5fe8
LP
537static 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
559static 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");
33b40551 607 return MHD_CONTENT_READER_END_WITH_ERROR;
240a5fe8
LP
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
645static int request_handler_fields(
646 struct MHD_Connection *connection,
647 const char *field,
648 void *connection_cls) {
649
650 struct MHD_Response *response;
8530a143 651 RequestMeta *m = connection_cls;
240a5fe8
LP
652 int r;
653
654 assert(connection);
8530a143 655 assert(m);
240a5fe8
LP
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
7b17a7d7
LP
680static 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);
fadd79d2 689 assert(target);
7b17a7d7
LP
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
709static 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
a7edaadd 744static int get_virtualization(char **v) {
917b5dc7 745 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
a7edaadd
LP
746 _cleanup_bus_unref_ sd_bus *bus = NULL;
747 const char *t;
748 char *b;
749 int r;
750
751 r = sd_bus_open_system(&bus);
752 if (r < 0)
753 return r;
754
917b5dc7 755 r = sd_bus_call_method(
a7edaadd
LP
756 bus,
757 "org.freedesktop.systemd1",
758 "/org/freedesktop/systemd1",
759 "org.freedesktop.DBus.Properties",
760 "Get",
917b5dc7
LP
761 NULL,
762 &reply,
763 "ss",
764 "org.freedesktop.systemd1.Manager",
765 "Virtualization");
a7edaadd
LP
766 if (r < 0)
767 return r;
768
769 r = sd_bus_message_read(reply, "v", "s", &t);
770 if (r < 0)
771 return r;
772
773 if (isempty(t)) {
774 *v = NULL;
775 return 0;
776 }
777
778 b = strdup(t);
779 if (!b)
780 return -ENOMEM;
781
782 *v = b;
783 return 1;
784}
785
7b17a7d7
LP
786static int request_handler_machine(
787 struct MHD_Connection *connection,
8530a143 788 void *connection_cls) {
7b17a7d7
LP
789
790 struct MHD_Response *response;
8530a143 791 RequestMeta *m = connection_cls;
7b17a7d7
LP
792 int r;
793 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
794 uint64_t cutoff_from, cutoff_to, usage;
795 char *json;
796 sd_id128_t mid, bid;
a7edaadd 797 _cleanup_free_ char *v = NULL;
7b17a7d7
LP
798
799 assert(connection);
8530a143 800 assert(m);
7b17a7d7
LP
801
802 r = open_journal(m);
803 if (r < 0)
804 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
805
806 r = sd_id128_get_machine(&mid);
807 if (r < 0)
808 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
809
810 r = sd_id128_get_boot(&bid);
811 if (r < 0)
812 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
813
814 hostname = gethostname_malloc();
815 if (!hostname)
816 return respond_oom(connection);
817
818 r = sd_journal_get_usage(m->journal, &usage);
819 if (r < 0)
820 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
821
822 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
823 if (r < 0)
824 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
825
826 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
827
a7edaadd 828 get_virtualization(&v);
7b17a7d7
LP
829
830 r = asprintf(&json,
831 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
832 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
833 "\"hostname\" : \"%s\","
834 "\"os_pretty_name\" : \"%s\","
835 "\"virtualization\" : \"%s\","
507f22bd
ZJS
836 "\"usage\" : \"%"PRIu64"\","
837 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
838 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
7b17a7d7
LP
839 SD_ID128_FORMAT_VAL(mid),
840 SD_ID128_FORMAT_VAL(bid),
e724b063 841 hostname_cleanup(hostname, false),
7b17a7d7 842 os_name ? os_name : "Linux",
a7edaadd 843 v ? v : "bare",
507f22bd
ZJS
844 usage,
845 cutoff_from,
846 cutoff_to);
7b17a7d7
LP
847
848 if (r < 0)
849 return respond_oom(connection);
850
851 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
852 if (!response) {
853 free(json);
854 return respond_oom(connection);
855 }
856
857 MHD_add_response_header(response, "Content-Type", "application/json");
858 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
859 MHD_destroy_response(response);
860
861 return r;
862}
863
864static int request_handler(
865 void *cls,
866 struct MHD_Connection *connection,
867 const char *url,
868 const char *method,
869 const char *version,
870 const char *upload_data,
871 size_t *upload_data_size,
872 void **connection_cls) {
873
874 assert(connection);
8530a143 875 assert(connection_cls);
7b17a7d7
LP
876 assert(url);
877 assert(method);
878
879 if (!streq(method, "GET"))
a93035ce
ZJS
880 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
881 "Unsupported method.\n");
882
7b17a7d7 883
8530a143
ZJS
884 if (!*connection_cls) {
885 if (!request_meta(connection_cls))
886 return respond_oom(connection);
887 return MHD_YES;
888 }
889
7b17a7d7
LP
890 if (streq(url, "/"))
891 return request_handler_redirect(connection, "/browse");
892
893 if (streq(url, "/entries"))
8530a143 894 return request_handler_entries(connection, *connection_cls);
7b17a7d7 895
240a5fe8 896 if (startswith(url, "/fields/"))
8530a143 897 return request_handler_fields(connection, url + 8, *connection_cls);
240a5fe8 898
7b17a7d7
LP
899 if (streq(url, "/browse"))
900 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
901
902 if (streq(url, "/machine"))
8530a143 903 return request_handler_machine(connection, *connection_cls);
7b17a7d7
LP
904
905 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
906}
907
c3a7cfb7
ZJS
908static int help(void) {
909
910 printf("%s [OPTIONS...] ...\n\n"
911 "HTTP server for journal events.\n\n"
912 " -h --help Show this help\n"
913 " --version Show package version\n"
914 " --cert=CERT.PEM Specify server certificate in PEM format\n"
915 " --key=KEY.PEM Specify server key in PEM format\n",
916 program_invocation_short_name);
917
918 return 0;
919}
920
858634ff
ZJS
921static char *key_pem = NULL;
922static char *cert_pem = NULL;
923
924static int parse_argv(int argc, char *argv[]) {
925 enum {
926 ARG_VERSION = 0x100,
927 ARG_KEY,
928 ARG_CERT,
929 };
930
931 int r, c;
932
933 static const struct option options[] = {
c3a7cfb7 934 { "help", no_argument, NULL, 'h' },
858634ff
ZJS
935 { "version", no_argument, NULL, ARG_VERSION },
936 { "key", required_argument, NULL, ARG_KEY },
937 { "cert", required_argument, NULL, ARG_CERT },
eb9da376 938 {}
858634ff
ZJS
939 };
940
941 assert(argc >= 0);
942 assert(argv);
943
c3a7cfb7 944 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
eb9da376 945
858634ff 946 switch(c) {
eb9da376
LP
947
948 case 'h':
949 return help();
950
858634ff
ZJS
951 case ARG_VERSION:
952 puts(PACKAGE_STRING);
953 puts(SYSTEMD_FEATURES);
954 return 0;
955
956 case ARG_KEY:
957 if (key_pem) {
958 log_error("Key file specified twice");
959 return -EINVAL;
960 }
961 r = read_full_file(optarg, &key_pem, NULL);
962 if (r < 0) {
963 log_error("Failed to read key file: %s", strerror(-r));
964 return r;
965 }
966 assert(key_pem);
967 break;
7b17a7d7 968
858634ff
ZJS
969 case ARG_CERT:
970 if (cert_pem) {
971 log_error("Certificate file specified twice");
972 return -EINVAL;
973 }
974 r = read_full_file(optarg, &cert_pem, NULL);
975 if (r < 0) {
976 log_error("Failed to read certificate file: %s", strerror(-r));
977 return r;
978 }
979 assert(cert_pem);
980 break;
981
982 case '?':
983 return -EINVAL;
984
985 default:
eb9da376 986 assert_not_reached("Unhandled option");
858634ff
ZJS
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}