1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
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.
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.
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/>.
28 #include <microhttpd.h>
32 #include "sd-journal.h"
33 #include "sd-daemon.h"
36 #include "logs-show.h"
37 #include "microhttpd-util.h"
41 typedef struct RequestMeta {
54 int argument_parse_error;
63 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
64 [OUTPUT_SHORT] = "text/plain",
65 [OUTPUT_JSON] = "application/json",
66 [OUTPUT_JSON_SSE] = "text/event-stream",
67 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
70 static RequestMeta *request_meta(void **connection_cls) {
74 return *connection_cls;
76 m = new0(RequestMeta, 1);
84 static void request_meta_free(
86 struct MHD_Connection *connection,
87 void **connection_cls,
88 enum MHD_RequestTerminationCode toe) {
90 RequestMeta *m = *connection_cls;
96 sd_journal_close(m->journal);
105 static int open_journal(RequestMeta *m) {
111 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
114 static int respond_oom_internal(struct MHD_Connection *connection) {
115 struct MHD_Response *response;
116 const char m[] = "Out of memory.\n";
121 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
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);
132 #define respond_oom(connection) log_oom(), respond_oom_internal(connection)
135 static int respond_error(
136 struct MHD_Connection *connection,
138 const char *format, ...) {
140 struct MHD_Response *response;
148 va_start(ap, format);
149 r = vasprintf(&m, format, ap);
153 return respond_oom(connection);
155 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
158 return respond_oom(connection);
161 MHD_add_response_header(response, "Content-Type", "text/plain");
162 r = MHD_queue_response(connection, code, response);
163 MHD_destroy_response(response);
168 static ssize_t request_reader_entries(
174 RequestMeta *m = cls;
181 assert(pos >= m->delta);
185 while (pos >= m->size) {
188 /* End of this entry, so let's serialize the next
191 if (m->n_entries_set &&
193 return MHD_CONTENT_READER_END_OF_STREAM;
196 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
197 else if (m->n_skip > 0)
198 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
200 r = sd_journal_next(m->journal);
203 log_error("Failed to advance journal pointer: %s", strerror(-r));
204 return MHD_CONTENT_READER_END_WITH_ERROR;
208 r = sd_journal_wait(m->journal, (uint64_t) -1);
210 log_error("Couldn't wait for journal event: %s", strerror(-r));
211 return MHD_CONTENT_READER_END_WITH_ERROR;
217 return MHD_CONTENT_READER_END_OF_STREAM;
223 r = sd_journal_test_cursor(m->journal, m->cursor);
225 log_error("Failed to test cursor: %s", strerror(-r));
226 return MHD_CONTENT_READER_END_WITH_ERROR;
230 return MHD_CONTENT_READER_END_OF_STREAM;
236 if (m->n_entries_set)
246 log_error("Failed to create temporary file: %m");
247 return MHD_CONTENT_READER_END_WITH_ERROR;
251 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
253 log_error("Failed to serialize item: %s", strerror(-r));
254 return MHD_CONTENT_READER_END_WITH_ERROR;
258 if (sz == (off_t) -1) {
259 log_error("Failed to retrieve file position: %m");
260 return MHD_CONTENT_READER_END_WITH_ERROR;
263 m->size = (uint64_t) sz;
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;
276 k = fread(buf, 1, n, m->tmp);
278 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
279 return MHD_CONTENT_READER_END_WITH_ERROR;
285 static int request_parse_accept(
287 struct MHD_Connection *connection) {
294 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
298 if (streq(header, mime_types[OUTPUT_JSON]))
299 m->mode = OUTPUT_JSON;
300 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
301 m->mode = OUTPUT_JSON_SSE;
302 else if (streq(header, mime_types[OUTPUT_EXPORT]))
303 m->mode = OUTPUT_EXPORT;
305 m->mode = OUTPUT_SHORT;
310 static int request_parse_range(
312 struct MHD_Connection *connection) {
314 const char *range, *colon, *colon2;
320 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
324 if (!startswith(range, "entries="))
328 range += strspn(range, WHITESPACE);
330 colon = strchr(range, ':');
332 m->cursor = strdup(range);
336 colon2 = strchr(colon + 1, ':');
338 _cleanup_free_ char *t;
340 t = strndup(colon + 1, colon2 - colon - 1);
344 r = safe_atoi64(t, &m->n_skip);
349 p = (colon2 ? colon2 : colon) + 1;
351 r = safe_atou64(p, &m->n_entries);
355 if (m->n_entries <= 0)
358 m->n_entries_set = true;
361 m->cursor = strndup(range, colon - range);
367 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
368 if (isempty(m->cursor)) {
376 static int request_parse_arguments_iterator(
378 enum MHD_ValueKind kind,
382 RequestMeta *m = cls;
383 _cleanup_free_ char *p = NULL;
389 m->argument_parse_error = -EINVAL;
393 if (streq(key, "follow")) {
394 if (isempty(value)) {
399 r = parse_boolean(value);
401 m->argument_parse_error = r;
409 if (streq(key, "discrete")) {
410 if (isempty(value)) {
415 r = parse_boolean(value);
417 m->argument_parse_error = r;
425 if (streq(key, "boot")) {
429 r = parse_boolean(value);
431 m->argument_parse_error = r;
437 char match[9 + 32 + 1] = "_BOOT_ID=";
440 r = sd_id128_get_boot(&bid);
442 log_error("Failed to get boot ID: %s", strerror(-r));
446 sd_id128_to_string(bid, match + 9);
447 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
449 m->argument_parse_error = r;
457 p = strjoin(key, "=", strempty(value), NULL);
459 m->argument_parse_error = log_oom();
463 r = sd_journal_add_match(m->journal, p, 0);
465 m->argument_parse_error = r;
472 static int request_parse_arguments(
474 struct MHD_Connection *connection) {
479 m->argument_parse_error = 0;
480 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
482 return m->argument_parse_error;
485 static int request_handler_entries(
486 struct MHD_Connection *connection,
487 void *connection_cls) {
489 struct MHD_Response *response;
490 RequestMeta *m = connection_cls;
498 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
500 if (request_parse_accept(m, connection) < 0)
501 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
503 if (request_parse_range(m, connection) < 0)
504 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
506 if (request_parse_arguments(m, connection) < 0)
507 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
511 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
514 m->n_entries_set = true;
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);
524 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
526 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
528 return respond_oom(connection);
530 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
532 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
533 MHD_destroy_response(response);
538 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
542 eq = memchr(d, '=', l);
546 j = l - (eq - d + 1);
548 if (m == OUTPUT_JSON) {
549 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
550 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
553 fwrite(eq+1, 1, j, f);
560 static ssize_t request_reader_fields(
566 RequestMeta *m = cls;
573 assert(pos >= m->delta);
577 while (pos >= m->size) {
582 /* End of this field, so let's serialize the next
585 if (m->n_fields_set &&
587 return MHD_CONTENT_READER_END_OF_STREAM;
589 r = sd_journal_enumerate_unique(m->journal, &d, &l);
591 log_error("Failed to advance field index: %s", strerror(-r));
592 return MHD_CONTENT_READER_END_WITH_ERROR;
594 return MHD_CONTENT_READER_END_OF_STREAM;
607 log_error("Failed to create temporary file: %m");
608 return MHD_CONTENT_READER_END_WITH_ERROR;
612 r = output_field(m->tmp, m->mode, d, l);
614 log_error("Failed to serialize item: %s", strerror(-r));
615 return MHD_CONTENT_READER_END_WITH_ERROR;
619 if (sz == (off_t) -1) {
620 log_error("Failed to retrieve file position: %m");
621 return MHD_CONTENT_READER_END_WITH_ERROR;
624 m->size = (uint64_t) sz;
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;
637 k = fread(buf, 1, n, m->tmp);
639 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
640 return MHD_CONTENT_READER_END_WITH_ERROR;
646 static int request_handler_fields(
647 struct MHD_Connection *connection,
649 void *connection_cls) {
651 struct MHD_Response *response;
652 RequestMeta *m = connection_cls;
660 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
662 if (request_parse_accept(m, connection) < 0)
663 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
665 r = sd_journal_query_unique(m->journal, field);
667 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
669 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
671 return respond_oom(connection);
673 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
675 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
676 MHD_destroy_response(response);
681 static int request_handler_redirect(
682 struct MHD_Connection *connection,
683 const char *target) {
686 struct MHD_Response *response;
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);
695 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
698 return respond_oom(connection);
701 MHD_add_response_header(response, "Content-Type", "text/html");
702 MHD_add_response_header(response, "Location", target);
704 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
705 MHD_destroy_response(response);
710 static int request_handler_file(
711 struct MHD_Connection *connection,
713 const char *mime_type) {
715 struct MHD_Response *response;
717 _cleanup_close_ int fd = -1;
724 fd = open(path, O_RDONLY|O_CLOEXEC);
726 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
728 if (fstat(fd, &st) < 0)
729 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
731 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
733 return respond_oom(connection);
737 MHD_add_response_header(response, "Content-Type", mime_type);
739 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
740 MHD_destroy_response(response);
745 static int get_virtualization(char **v) {
746 _cleanup_bus_unref_ sd_bus *bus = NULL;
750 r = sd_bus_default_system(&bus);
754 r = sd_bus_get_property_string(
756 "org.freedesktop.systemd1",
757 "/org/freedesktop/systemd1",
758 "org.freedesktop.systemd1.Manager",
775 static int request_handler_machine(
776 struct MHD_Connection *connection,
777 void *connection_cls) {
779 struct MHD_Response *response;
780 RequestMeta *m = connection_cls;
782 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
783 uint64_t cutoff_from, cutoff_to, usage;
786 _cleanup_free_ char *v = NULL;
793 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
795 r = sd_id128_get_machine(&mid);
797 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
799 r = sd_id128_get_boot(&bid);
801 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
803 hostname = gethostname_malloc();
805 return respond_oom(connection);
807 r = sd_journal_get_usage(m->journal, &usage);
809 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
811 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
813 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
815 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
817 get_virtualization(&v);
820 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
821 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
822 "\"hostname\" : \"%s\","
823 "\"os_pretty_name\" : \"%s\","
824 "\"virtualization\" : \"%s\","
825 "\"usage\" : \"%"PRIu64"\","
826 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
827 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
828 SD_ID128_FORMAT_VAL(mid),
829 SD_ID128_FORMAT_VAL(bid),
830 hostname_cleanup(hostname, false),
831 os_name ? os_name : "Linux",
838 return respond_oom(connection);
840 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
843 return respond_oom(connection);
846 MHD_add_response_header(response, "Content-Type", "application/json");
847 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
848 MHD_destroy_response(response);
853 static int request_handler(
855 struct MHD_Connection *connection,
859 const char *upload_data,
860 size_t *upload_data_size,
861 void **connection_cls) {
864 assert(connection_cls);
868 if (!streq(method, "GET"))
869 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
870 "Unsupported method.\n");
873 if (!*connection_cls) {
874 if (!request_meta(connection_cls))
875 return respond_oom(connection);
880 return request_handler_redirect(connection, "/browse");
882 if (streq(url, "/entries"))
883 return request_handler_entries(connection, *connection_cls);
885 if (startswith(url, "/fields/"))
886 return request_handler_fields(connection, url + 8, *connection_cls);
888 if (streq(url, "/browse"))
889 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
891 if (streq(url, "/machine"))
892 return request_handler_machine(connection, *connection_cls);
894 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
897 static int help(void) {
899 printf("%s [OPTIONS...] ...\n\n"
900 "HTTP server for journal events.\n\n"
901 " -h --help Show this help\n"
902 " --version Show package version\n"
903 " --cert=CERT.PEM Specify server certificate in PEM format\n"
904 " --key=KEY.PEM Specify server key in PEM format\n",
905 program_invocation_short_name);
910 static char *key_pem = NULL;
911 static char *cert_pem = NULL;
913 static int parse_argv(int argc, char *argv[]) {
922 static const struct option options[] = {
923 { "help", no_argument, NULL, 'h' },
924 { "version", no_argument, NULL, ARG_VERSION },
925 { "key", required_argument, NULL, ARG_KEY },
926 { "cert", required_argument, NULL, ARG_CERT },
933 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
941 puts(PACKAGE_STRING);
942 puts(SYSTEMD_FEATURES);
947 log_error("Key file specified twice");
950 r = read_full_file(optarg, &key_pem, NULL);
952 log_error("Failed to read key file: %s", strerror(-r));
960 log_error("Certificate file specified twice");
963 r = read_full_file(optarg, &cert_pem, NULL);
965 log_error("Failed to read certificate file: %s", strerror(-r));
975 assert_not_reached("Unhandled option");
979 log_error("This program does not take arguments.");
983 if (!!key_pem != !!cert_pem) {
984 log_error("Certificate and key files must be specified together");
991 int main(int argc, char *argv[]) {
992 struct MHD_Daemon *d = NULL;
995 log_set_target(LOG_TARGET_AUTO);
996 log_parse_environment();
999 r = parse_argv(argc, argv);
1001 return EXIT_FAILURE;
1003 return EXIT_SUCCESS;
1005 n = sd_listen_fds(1);
1007 log_error("Failed to determine passed sockets: %s", strerror(-n));
1010 log_error("Can't listen on more than one socket.");
1013 struct MHD_OptionItem opts[] = {
1014 { MHD_OPTION_NOTIFY_COMPLETED,
1015 (intptr_t) request_meta_free, NULL },
1016 { MHD_OPTION_EXTERNAL_LOGGER,
1017 (intptr_t) microhttpd_logger, NULL },
1018 { MHD_OPTION_END, 0, NULL },
1019 { MHD_OPTION_END, 0, NULL },
1020 { MHD_OPTION_END, 0, NULL },
1021 { MHD_OPTION_END, 0, NULL }};
1023 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1026 opts[opts_pos++] = (struct MHD_OptionItem)
1027 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1030 opts[opts_pos++] = (struct MHD_OptionItem)
1031 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1032 opts[opts_pos++] = (struct MHD_OptionItem)
1033 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1034 flags |= MHD_USE_SSL;
1037 d = MHD_start_daemon(flags, 19531,
1039 request_handler, NULL,
1040 MHD_OPTION_ARRAY, opts,
1045 log_error("Failed to start daemon!");