]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-gatewayd.c
journal-gatewayd: return nice error on unsupported methods
[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"
34#include "logs-show.h"
e64690a8 35#include "microhttpd-util.h"
7b17a7d7 36#include "virt.h"
858634ff 37#include "build.h"
7b17a7d7
LP
38
39typedef struct RequestMeta {
40 sd_journal *journal;
41
42 OutputMode mode;
43
44 char *cursor;
45 int64_t n_skip;
46 uint64_t n_entries;
47 bool n_entries_set;
48
49 FILE *tmp;
50 uint64_t delta, size;
98206c93
LP
51
52 int argument_parse_error;
7a69007a
LP
53
54 bool follow;
c6511e85 55 bool discrete;
240a5fe8
LP
56
57 uint64_t n_fields;
58 bool n_fields_set;
7b17a7d7
LP
59} RequestMeta;
60
61static const char* const mime_types[_OUTPUT_MODE_MAX] = {
62 [OUTPUT_SHORT] = "text/plain",
63 [OUTPUT_JSON] = "application/json",
48383c25
LP
64 [OUTPUT_JSON_SSE] = "text/event-stream",
65 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
7b17a7d7
LP
66};
67
68static RequestMeta *request_meta(void **connection_cls) {
69 RequestMeta *m;
70
71 if (*connection_cls)
72 return *connection_cls;
73
74 m = new0(RequestMeta, 1);
75 if (!m)
76 return NULL;
77
78 *connection_cls = m;
79 return m;
80}
81
82static void request_meta_free(
83 void *cls,
84 struct MHD_Connection *connection,
85 void **connection_cls,
86 enum MHD_RequestTerminationCode toe) {
87
88 RequestMeta *m = *connection_cls;
89
90 if (!m)
91 return;
92
93 if (m->journal)
94 sd_journal_close(m->journal);
95
96 if (m->tmp)
97 fclose(m->tmp);
98
99 free(m->cursor);
100 free(m);
101}
102
103static int open_journal(RequestMeta *m) {
104 assert(m);
105
106 if (m->journal)
107 return 0;
108
109 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
110}
111
112
9775033d 113static int respond_oom_internal(struct MHD_Connection *connection) {
7b17a7d7
LP
114 struct MHD_Response *response;
115 const char m[] = "Out of memory.\n";
116 int ret;
117
118 assert(connection);
119
120 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
121 if (!response)
122 return MHD_NO;
123
124 MHD_add_response_header(response, "Content-Type", "text/plain");
125 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
126 MHD_destroy_response(response);
127
128 return ret;
129}
130
9775033d
ZJS
131#define respond_oom(connection) log_oom(), respond_oom_internal(connection)
132
7b17a7d7
LP
133static int respond_error(
134 struct MHD_Connection *connection,
135 unsigned code,
136 const char *format, ...) {
137
138 struct MHD_Response *response;
139 char *m;
140 int r;
141 va_list ap;
142
143 assert(connection);
144 assert(format);
145
146 va_start(ap, format);
147 r = vasprintf(&m, format, ap);
148 va_end(ap);
149
150 if (r < 0)
151 return respond_oom(connection);
152
153 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
154 if (!response) {
155 free(m);
156 return respond_oom(connection);
157 }
158
159 MHD_add_response_header(response, "Content-Type", "text/plain");
160 r = MHD_queue_response(connection, code, response);
161 MHD_destroy_response(response);
162
163 return r;
164}
165
166static ssize_t request_reader_entries(
167 void *cls,
168 uint64_t pos,
169 char *buf,
170 size_t max) {
171
172 RequestMeta *m = cls;
173 int r;
174 size_t n, k;
175
176 assert(m);
177 assert(buf);
178 assert(max > 0);
179 assert(pos >= m->delta);
180
181 pos -= m->delta;
182
183 while (pos >= m->size) {
184 off_t sz;
185
186 /* End of this entry, so let's serialize the next
187 * one */
188
189 if (m->n_entries_set &&
190 m->n_entries <= 0)
191 return MHD_CONTENT_READER_END_OF_STREAM;
192
77ad3b93
LP
193 if (m->n_skip < 0)
194 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
195 else if (m->n_skip > 0)
7b17a7d7
LP
196 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
197 else
198 r = sd_journal_next(m->journal);
199
200 if (r < 0) {
201 log_error("Failed to advance journal pointer: %s", strerror(-r));
202 return MHD_CONTENT_READER_END_WITH_ERROR;
7a69007a
LP
203 } else if (r == 0) {
204
205 if (m->follow) {
206 r = sd_journal_wait(m->journal, (uint64_t) -1);
207 if (r < 0) {
208 log_error("Couldn't wait for journal event: %s", strerror(-r));
209 return MHD_CONTENT_READER_END_WITH_ERROR;
210 }
211
212 continue;
213 }
214
7b17a7d7 215 return MHD_CONTENT_READER_END_OF_STREAM;
7a69007a 216 }
7b17a7d7 217
c6511e85
LP
218 if (m->discrete) {
219 assert(m->cursor);
220
221 r = sd_journal_test_cursor(m->journal, m->cursor);
222 if (r < 0) {
223 log_error("Failed to test cursor: %s", strerror(-r));
224 return MHD_CONTENT_READER_END_WITH_ERROR;
225 }
226
227 if (r == 0)
228 return MHD_CONTENT_READER_END_OF_STREAM;
229 }
230
7b17a7d7
LP
231 pos -= m->size;
232 m->delta += m->size;
233
234 if (m->n_entries_set)
235 m->n_entries -= 1;
236
237 m->n_skip = 0;
238
239 if (m->tmp)
240 rewind(m->tmp);
241 else {
242 m->tmp = tmpfile();
243 if (!m->tmp) {
244 log_error("Failed to create temporary file: %m");
33b40551 245 return MHD_CONTENT_READER_END_WITH_ERROR;
7b17a7d7
LP
246 }
247 }
248
249 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
250 if (r < 0) {
251 log_error("Failed to serialize item: %s", strerror(-r));
252 return MHD_CONTENT_READER_END_WITH_ERROR;
253 }
254
255 sz = ftello(m->tmp);
256 if (sz == (off_t) -1) {
257 log_error("Failed to retrieve file position: %m");
258 return MHD_CONTENT_READER_END_WITH_ERROR;
259 }
260
261 m->size = (uint64_t) sz;
262 }
263
264 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
265 log_error("Failed to seek to position: %m");
266 return MHD_CONTENT_READER_END_WITH_ERROR;
267 }
268
269 n = m->size - pos;
270 if (n > max)
271 n = max;
272
273 errno = 0;
274 k = fread(buf, 1, n, m->tmp);
275 if (k != n) {
276 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
277 return MHD_CONTENT_READER_END_WITH_ERROR;
278 }
279
280 return (ssize_t) k;
281}
282
283static int request_parse_accept(
284 RequestMeta *m,
285 struct MHD_Connection *connection) {
286
6374a73b 287 const char *header;
7b17a7d7
LP
288
289 assert(m);
290 assert(connection);
291
6374a73b
ZJS
292 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
293 if (!header)
7b17a7d7
LP
294 return 0;
295
6374a73b 296 if (streq(header, mime_types[OUTPUT_JSON]))
7b17a7d7 297 m->mode = OUTPUT_JSON;
6374a73b 298 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
48383c25 299 m->mode = OUTPUT_JSON_SSE;
6374a73b 300 else if (streq(header, mime_types[OUTPUT_EXPORT]))
7b17a7d7
LP
301 m->mode = OUTPUT_EXPORT;
302 else
303 m->mode = OUTPUT_SHORT;
304
305 return 0;
306}
307
308static int request_parse_range(
309 RequestMeta *m,
310 struct MHD_Connection *connection) {
311
312 const char *range, *colon, *colon2;
313 int r;
314
315 assert(m);
316 assert(connection);
317
318 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
319 if (!range)
320 return 0;
321
322 if (!startswith(range, "entries="))
323 return 0;
324
325 range += 8;
326 range += strspn(range, WHITESPACE);
327
328 colon = strchr(range, ':');
329 if (!colon)
330 m->cursor = strdup(range);
331 else {
332 const char *p;
333
334 colon2 = strchr(colon + 1, ':');
335 if (colon2) {
4a1909bb 336 char _cleanup_free_ *t;
7b17a7d7
LP
337
338 t = strndup(colon + 1, colon2 - colon - 1);
339 if (!t)
340 return -ENOMEM;
341
342 r = safe_atoi64(t, &m->n_skip);
7b17a7d7
LP
343 if (r < 0)
344 return r;
345 }
346
347 p = (colon2 ? colon2 : colon) + 1;
348 if (*p) {
349 r = safe_atou64(p, &m->n_entries);
350 if (r < 0)
351 return r;
352
353 if (m->n_entries <= 0)
354 return -EINVAL;
355
356 m->n_entries_set = true;
357 }
358
359 m->cursor = strndup(range, colon - range);
360 }
361
362 if (!m->cursor)
363 return -ENOMEM;
364
365 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
366 if (isempty(m->cursor)) {
367 free(m->cursor);
368 m->cursor = NULL;
369 }
370
371 return 0;
372}
373
98206c93
LP
374static int request_parse_arguments_iterator(
375 void *cls,
376 enum MHD_ValueKind kind,
377 const char *key,
378 const char *value) {
379
380 RequestMeta *m = cls;
381 _cleanup_free_ char *p = NULL;
382 int r;
383
384 assert(m);
385
386 if (isempty(key)) {
387 m->argument_parse_error = -EINVAL;
388 return MHD_NO;
389 }
390
7a69007a
LP
391 if (streq(key, "follow")) {
392 if (isempty(value)) {
393 m->follow = true;
394 return MHD_YES;
395 }
396
397 r = parse_boolean(value);
398 if (r < 0) {
399 m->argument_parse_error = r;
400 return MHD_NO;
401 }
402
403 m->follow = r;
404 return MHD_YES;
405 }
406
c6511e85
LP
407 if (streq(key, "discrete")) {
408 if (isempty(value)) {
409 m->discrete = true;
410 return MHD_YES;
411 }
412
413 r = parse_boolean(value);
414 if (r < 0) {
415 m->argument_parse_error = r;
416 return MHD_NO;
417 }
418
419 m->discrete = r;
420 return MHD_YES;
421 }
422
082d0180
LP
423 if (streq(key, "boot")) {
424 if (isempty(value))
425 r = true;
426 else {
427 r = parse_boolean(value);
428 if (r < 0) {
429 m->argument_parse_error = r;
430 return MHD_NO;
431 }
432 }
433
434 if (r) {
435 char match[9 + 32 + 1] = "_BOOT_ID=";
436 sd_id128_t bid;
437
438 r = sd_id128_get_boot(&bid);
439 if (r < 0) {
440 log_error("Failed to get boot ID: %s", strerror(-r));
441 return MHD_NO;
442 }
443
444 sd_id128_to_string(bid, match + 9);
445 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
446 if (r < 0) {
447 m->argument_parse_error = r;
448 return MHD_NO;
449 }
450 }
451
452 return MHD_YES;
453 }
454
98206c93
LP
455 p = strjoin(key, "=", strempty(value), NULL);
456 if (!p) {
457 m->argument_parse_error = log_oom();
458 return MHD_NO;
459 }
460
461 r = sd_journal_add_match(m->journal, p, 0);
462 if (r < 0) {
463 m->argument_parse_error = r;
464 return MHD_NO;
465 }
466
467 return MHD_YES;
468}
469
470static int request_parse_arguments(
471 RequestMeta *m,
472 struct MHD_Connection *connection) {
473
474 assert(m);
475 assert(connection);
476
477 m->argument_parse_error = 0;
478 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
479
480 return m->argument_parse_error;
481}
482
7b17a7d7
LP
483static int request_handler_entries(
484 struct MHD_Connection *connection,
8530a143 485 void *connection_cls) {
7b17a7d7
LP
486
487 struct MHD_Response *response;
8530a143 488 RequestMeta *m = connection_cls;
7b17a7d7
LP
489 int r;
490
491 assert(connection);
8530a143 492 assert(m);
7b17a7d7
LP
493
494 r = open_journal(m);
495 if (r < 0)
496 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
497
498 if (request_parse_accept(m, connection) < 0)
499 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
500
501 if (request_parse_range(m, connection) < 0)
502 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
503
98206c93
LP
504 if (request_parse_arguments(m, connection) < 0)
505 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
506
c6511e85
LP
507 if (m->discrete) {
508 if (!m->cursor)
509 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
510
511 m->n_entries = 1;
512 m->n_entries_set = true;
513 }
514
7b17a7d7
LP
515 if (m->cursor)
516 r = sd_journal_seek_cursor(m->journal, m->cursor);
517 else if (m->n_skip >= 0)
518 r = sd_journal_seek_head(m->journal);
519 else if (m->n_skip < 0)
520 r = sd_journal_seek_tail(m->journal);
521 if (r < 0)
522 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
523
524 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
525 if (!response)
526 return respond_oom(connection);
527
528 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
529
530 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
531 MHD_destroy_response(response);
532
533 return r;
534}
535
240a5fe8
LP
536static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
537 const char *eq;
538 size_t j;
539
540 eq = memchr(d, '=', l);
541 if (!eq)
542 return -EINVAL;
543
544 j = l - (eq - d + 1);
545
546 if (m == OUTPUT_JSON) {
547 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
548 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
549 fputs(" }\n", f);
550 } else {
551 fwrite(eq+1, 1, j, f);
552 fputc('\n', f);
553 }
554
555 return 0;
556}
557
558static ssize_t request_reader_fields(
559 void *cls,
560 uint64_t pos,
561 char *buf,
562 size_t max) {
563
564 RequestMeta *m = cls;
565 int r;
566 size_t n, k;
567
568 assert(m);
569 assert(buf);
570 assert(max > 0);
571 assert(pos >= m->delta);
572
573 pos -= m->delta;
574
575 while (pos >= m->size) {
576 off_t sz;
577 const void *d;
578 size_t l;
579
580 /* End of this field, so let's serialize the next
581 * one */
582
583 if (m->n_fields_set &&
584 m->n_fields <= 0)
585 return MHD_CONTENT_READER_END_OF_STREAM;
586
587 r = sd_journal_enumerate_unique(m->journal, &d, &l);
588 if (r < 0) {
589 log_error("Failed to advance field index: %s", strerror(-r));
590 return MHD_CONTENT_READER_END_WITH_ERROR;
591 } else if (r == 0)
592 return MHD_CONTENT_READER_END_OF_STREAM;
593
594 pos -= m->size;
595 m->delta += m->size;
596
597 if (m->n_fields_set)
598 m->n_fields -= 1;
599
600 if (m->tmp)
601 rewind(m->tmp);
602 else {
603 m->tmp = tmpfile();
604 if (!m->tmp) {
605 log_error("Failed to create temporary file: %m");
33b40551 606 return MHD_CONTENT_READER_END_WITH_ERROR;
240a5fe8
LP
607 }
608 }
609
610 r = output_field(m->tmp, m->mode, d, l);
611 if (r < 0) {
612 log_error("Failed to serialize item: %s", strerror(-r));
613 return MHD_CONTENT_READER_END_WITH_ERROR;
614 }
615
616 sz = ftello(m->tmp);
617 if (sz == (off_t) -1) {
618 log_error("Failed to retrieve file position: %m");
619 return MHD_CONTENT_READER_END_WITH_ERROR;
620 }
621
622 m->size = (uint64_t) sz;
623 }
624
625 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
626 log_error("Failed to seek to position: %m");
627 return MHD_CONTENT_READER_END_WITH_ERROR;
628 }
629
630 n = m->size - pos;
631 if (n > max)
632 n = max;
633
634 errno = 0;
635 k = fread(buf, 1, n, m->tmp);
636 if (k != n) {
637 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
638 return MHD_CONTENT_READER_END_WITH_ERROR;
639 }
640
641 return (ssize_t) k;
642}
643
644static int request_handler_fields(
645 struct MHD_Connection *connection,
646 const char *field,
647 void *connection_cls) {
648
649 struct MHD_Response *response;
8530a143 650 RequestMeta *m = connection_cls;
240a5fe8
LP
651 int r;
652
653 assert(connection);
8530a143 654 assert(m);
240a5fe8
LP
655
656 r = open_journal(m);
657 if (r < 0)
658 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
659
660 if (request_parse_accept(m, connection) < 0)
661 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
662
663 r = sd_journal_query_unique(m->journal, field);
664 if (r < 0)
665 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
666
667 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
668 if (!response)
669 return respond_oom(connection);
670
671 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
672
673 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
674 MHD_destroy_response(response);
675
676 return r;
677}
678
7b17a7d7
LP
679static int request_handler_redirect(
680 struct MHD_Connection *connection,
681 const char *target) {
682
683 char *page;
684 struct MHD_Response *response;
685 int ret;
686
687 assert(connection);
fadd79d2 688 assert(target);
7b17a7d7
LP
689
690 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
691 return respond_oom(connection);
692
693 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
694 if (!response) {
695 free(page);
696 return respond_oom(connection);
697 }
698
699 MHD_add_response_header(response, "Content-Type", "text/html");
700 MHD_add_response_header(response, "Location", target);
701
702 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
703 MHD_destroy_response(response);
704
705 return ret;
706}
707
708static int request_handler_file(
709 struct MHD_Connection *connection,
710 const char *path,
711 const char *mime_type) {
712
713 struct MHD_Response *response;
714 int ret;
715 _cleanup_close_ int fd = -1;
716 struct stat st;
717
718 assert(connection);
719 assert(path);
720 assert(mime_type);
721
722 fd = open(path, O_RDONLY|O_CLOEXEC);
723 if (fd < 0)
724 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
725
726 if (fstat(fd, &st) < 0)
727 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
728
729 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
730 if (!response)
731 return respond_oom(connection);
732
733 fd = -1;
734
735 MHD_add_response_header(response, "Content-Type", mime_type);
736
737 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
738 MHD_destroy_response(response);
739
740 return ret;
741}
742
743static int request_handler_machine(
744 struct MHD_Connection *connection,
8530a143 745 void *connection_cls) {
7b17a7d7
LP
746
747 struct MHD_Response *response;
8530a143 748 RequestMeta *m = connection_cls;
7b17a7d7
LP
749 int r;
750 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
751 uint64_t cutoff_from, cutoff_to, usage;
752 char *json;
753 sd_id128_t mid, bid;
754 const char *v = "bare";
755
756 assert(connection);
8530a143 757 assert(m);
7b17a7d7
LP
758
759 r = open_journal(m);
760 if (r < 0)
761 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
762
763 r = sd_id128_get_machine(&mid);
764 if (r < 0)
765 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
766
767 r = sd_id128_get_boot(&bid);
768 if (r < 0)
769 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
770
771 hostname = gethostname_malloc();
772 if (!hostname)
773 return respond_oom(connection);
774
775 r = sd_journal_get_usage(m->journal, &usage);
776 if (r < 0)
777 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
778
779 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
780 if (r < 0)
781 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
782
783 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
784
785 detect_virtualization(&v);
786
787 r = asprintf(&json,
788 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
789 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
790 "\"hostname\" : \"%s\","
791 "\"os_pretty_name\" : \"%s\","
792 "\"virtualization\" : \"%s\","
793 "\"usage\" : \"%llu\","
794 "\"cutoff_from_realtime\" : \"%llu\","
795 "\"cutoff_to_realtime\" : \"%llu\" }\n",
796 SD_ID128_FORMAT_VAL(mid),
797 SD_ID128_FORMAT_VAL(bid),
798 hostname_cleanup(hostname),
799 os_name ? os_name : "Linux",
800 v,
801 (unsigned long long) usage,
802 (unsigned long long) cutoff_from,
803 (unsigned long long) cutoff_to);
804
805 if (r < 0)
806 return respond_oom(connection);
807
808 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
809 if (!response) {
810 free(json);
811 return respond_oom(connection);
812 }
813
814 MHD_add_response_header(response, "Content-Type", "application/json");
815 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
816 MHD_destroy_response(response);
817
818 return r;
819}
820
821static int request_handler(
822 void *cls,
823 struct MHD_Connection *connection,
824 const char *url,
825 const char *method,
826 const char *version,
827 const char *upload_data,
828 size_t *upload_data_size,
829 void **connection_cls) {
830
831 assert(connection);
8530a143 832 assert(connection_cls);
7b17a7d7
LP
833 assert(url);
834 assert(method);
835
836 if (!streq(method, "GET"))
a93035ce
ZJS
837 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
838 "Unsupported method.\n");
839
7b17a7d7 840
8530a143
ZJS
841 if (!*connection_cls) {
842 if (!request_meta(connection_cls))
843 return respond_oom(connection);
844 return MHD_YES;
845 }
846
7b17a7d7
LP
847 if (streq(url, "/"))
848 return request_handler_redirect(connection, "/browse");
849
850 if (streq(url, "/entries"))
8530a143 851 return request_handler_entries(connection, *connection_cls);
7b17a7d7 852
240a5fe8 853 if (startswith(url, "/fields/"))
8530a143 854 return request_handler_fields(connection, url + 8, *connection_cls);
240a5fe8 855
7b17a7d7
LP
856 if (streq(url, "/browse"))
857 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
858
859 if (streq(url, "/machine"))
8530a143 860 return request_handler_machine(connection, *connection_cls);
7b17a7d7
LP
861
862 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
863}
864
858634ff
ZJS
865static char *key_pem = NULL;
866static char *cert_pem = NULL;
867
868static int parse_argv(int argc, char *argv[]) {
869 enum {
870 ARG_VERSION = 0x100,
871 ARG_KEY,
872 ARG_CERT,
873 };
874
875 int r, c;
876
877 static const struct option options[] = {
878 { "version", no_argument, NULL, ARG_VERSION },
879 { "key", required_argument, NULL, ARG_KEY },
880 { "cert", required_argument, NULL, ARG_CERT },
881 { NULL, 0, NULL, 0 }
882 };
883
884 assert(argc >= 0);
885 assert(argv);
886
887 while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
888 switch(c) {
889 case ARG_VERSION:
890 puts(PACKAGE_STRING);
891 puts(SYSTEMD_FEATURES);
892 return 0;
893
894 case ARG_KEY:
895 if (key_pem) {
896 log_error("Key file specified twice");
897 return -EINVAL;
898 }
899 r = read_full_file(optarg, &key_pem, NULL);
900 if (r < 0) {
901 log_error("Failed to read key file: %s", strerror(-r));
902 return r;
903 }
904 assert(key_pem);
905 break;
7b17a7d7 906
858634ff
ZJS
907 case ARG_CERT:
908 if (cert_pem) {
909 log_error("Certificate file specified twice");
910 return -EINVAL;
911 }
912 r = read_full_file(optarg, &cert_pem, NULL);
913 if (r < 0) {
914 log_error("Failed to read certificate file: %s", strerror(-r));
915 return r;
916 }
917 assert(cert_pem);
918 break;
919
920 case '?':
921 return -EINVAL;
922
923 default:
924 log_error("Unknown option code %c", c);
925 return -EINVAL;
926 }
927
928 if (optind < argc) {
7b17a7d7 929 log_error("This program does not take arguments.");
858634ff
ZJS
930 return -EINVAL;
931 }
932
933 if (!!key_pem != !!cert_pem) {
934 log_error("Certificate and key files must be specified together");
935 return -EINVAL;
7b17a7d7
LP
936 }
937
858634ff
ZJS
938 return 1;
939}
940
941int main(int argc, char *argv[]) {
942 struct MHD_Daemon *d = NULL;
943 int r, n;
944
77ad3b93 945 log_set_target(LOG_TARGET_AUTO);
7b17a7d7
LP
946 log_parse_environment();
947 log_open();
948
858634ff
ZJS
949 r = parse_argv(argc, argv);
950 if (r < 0)
951 return EXIT_FAILURE;
952 if (r == 0)
953 return EXIT_SUCCESS;
954
7b17a7d7
LP
955 n = sd_listen_fds(1);
956 if (n < 0) {
957 log_error("Failed to determine passed sockets: %s", strerror(-n));
958 goto finish;
959 } else if (n > 1) {
960 log_error("Can't listen on more than one socket.");
961 goto finish;
7b17a7d7 962 } else {
c54ff8e3
ZJS
963 struct MHD_OptionItem opts[] = {
964 { MHD_OPTION_NOTIFY_COMPLETED,
965 (intptr_t) request_meta_free, NULL },
e64690a8
ZJS
966 { MHD_OPTION_EXTERNAL_LOGGER,
967 (intptr_t) microhttpd_logger, NULL },
c54ff8e3 968 { MHD_OPTION_END, 0, NULL },
858634ff
ZJS
969 { MHD_OPTION_END, 0, NULL },
970 { MHD_OPTION_END, 0, NULL },
c54ff8e3 971 { MHD_OPTION_END, 0, NULL }};
e64690a8 972 int opts_pos = 2;
858634ff
ZJS
973 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
974
c54ff8e3 975 if (n > 0)
858634ff
ZJS
976 opts[opts_pos++] = (struct MHD_OptionItem)
977 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
978 if (key_pem) {
979 assert(cert_pem);
980 opts[opts_pos++] = (struct MHD_OptionItem)
981 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
982 opts[opts_pos++] = (struct MHD_OptionItem)
983 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
984 flags |= MHD_USE_SSL;
985 }
986
987 d = MHD_start_daemon(flags, 19531,
988 NULL, NULL,
989 request_handler, NULL,
990 MHD_OPTION_ARRAY, opts,
991 MHD_OPTION_END);
7b17a7d7
LP
992 }
993
6374a73b 994 if (!d) {
7b17a7d7
LP
995 log_error("Failed to start daemon!");
996 goto finish;
997 }
998
999 pause();
1000
1001 r = EXIT_SUCCESS;
1002
1003finish:
6374a73b
ZJS
1004 if (d)
1005 MHD_stop_daemon(d);
7b17a7d7
LP
1006
1007 return r;
1008}