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)
134 static int respond_error(
135 struct MHD_Connection *connection,
137 const char *format, ...) {
139 struct MHD_Response *response;
147 va_start(ap, format);
148 r = vasprintf(&m, format, ap);
152 return respond_oom(connection);
154 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
157 return respond_oom(connection);
160 MHD_add_response_header(response, "Content-Type", "text/plain");
161 r = MHD_queue_response(connection, code, response);
162 MHD_destroy_response(response);
167 static ssize_t request_reader_entries(
173 RequestMeta *m = cls;
180 assert(pos >= m->delta);
184 while (pos >= m->size) {
187 /* End of this entry, so let's serialize the next
190 if (m->n_entries_set &&
192 return MHD_CONTENT_READER_END_OF_STREAM;
195 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
196 else if (m->n_skip > 0)
197 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
199 r = sd_journal_next(m->journal);
202 log_error("Failed to advance journal pointer: %s", strerror(-r));
203 return MHD_CONTENT_READER_END_WITH_ERROR;
207 r = sd_journal_wait(m->journal, (uint64_t) -1);
209 log_error("Couldn't wait for journal event: %s", strerror(-r));
210 return MHD_CONTENT_READER_END_WITH_ERROR;
216 return MHD_CONTENT_READER_END_OF_STREAM;
222 r = sd_journal_test_cursor(m->journal, m->cursor);
224 log_error("Failed to test cursor: %s", strerror(-r));
225 return MHD_CONTENT_READER_END_WITH_ERROR;
229 return MHD_CONTENT_READER_END_OF_STREAM;
235 if (m->n_entries_set)
245 log_error("Failed to create temporary file: %m");
246 return MHD_CONTENT_READER_END_WITH_ERROR;
250 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
252 log_error("Failed to serialize item: %s", strerror(-r));
253 return MHD_CONTENT_READER_END_WITH_ERROR;
257 if (sz == (off_t) -1) {
258 log_error("Failed to retrieve file position: %m");
259 return MHD_CONTENT_READER_END_WITH_ERROR;
262 m->size = (uint64_t) sz;
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;
275 k = fread(buf, 1, n, m->tmp);
277 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
278 return MHD_CONTENT_READER_END_WITH_ERROR;
284 static int request_parse_accept(
286 struct MHD_Connection *connection) {
293 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
297 if (streq(header, mime_types[OUTPUT_JSON]))
298 m->mode = OUTPUT_JSON;
299 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
300 m->mode = OUTPUT_JSON_SSE;
301 else if (streq(header, mime_types[OUTPUT_EXPORT]))
302 m->mode = OUTPUT_EXPORT;
304 m->mode = OUTPUT_SHORT;
309 static int request_parse_range(
311 struct MHD_Connection *connection) {
313 const char *range, *colon, *colon2;
319 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
323 if (!startswith(range, "entries="))
327 range += strspn(range, WHITESPACE);
329 colon = strchr(range, ':');
331 m->cursor = strdup(range);
335 colon2 = strchr(colon + 1, ':');
337 _cleanup_free_ char *t;
339 t = strndup(colon + 1, colon2 - colon - 1);
343 r = safe_atoi64(t, &m->n_skip);
348 p = (colon2 ? colon2 : colon) + 1;
350 r = safe_atou64(p, &m->n_entries);
354 if (m->n_entries <= 0)
357 m->n_entries_set = true;
360 m->cursor = strndup(range, colon - range);
366 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
367 if (isempty(m->cursor)) {
375 static int request_parse_arguments_iterator(
377 enum MHD_ValueKind kind,
381 RequestMeta *m = cls;
382 _cleanup_free_ char *p = NULL;
388 m->argument_parse_error = -EINVAL;
392 if (streq(key, "follow")) {
393 if (isempty(value)) {
398 r = parse_boolean(value);
400 m->argument_parse_error = r;
408 if (streq(key, "discrete")) {
409 if (isempty(value)) {
414 r = parse_boolean(value);
416 m->argument_parse_error = r;
424 if (streq(key, "boot")) {
428 r = parse_boolean(value);
430 m->argument_parse_error = r;
436 char match[9 + 32 + 1] = "_BOOT_ID=";
439 r = sd_id128_get_boot(&bid);
441 log_error("Failed to get boot ID: %s", strerror(-r));
445 sd_id128_to_string(bid, match + 9);
446 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
448 m->argument_parse_error = r;
456 p = strjoin(key, "=", strempty(value), NULL);
458 m->argument_parse_error = log_oom();
462 r = sd_journal_add_match(m->journal, p, 0);
464 m->argument_parse_error = r;
471 static int request_parse_arguments(
473 struct MHD_Connection *connection) {
478 m->argument_parse_error = 0;
479 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
481 return m->argument_parse_error;
484 static int request_handler_entries(
485 struct MHD_Connection *connection,
486 void *connection_cls) {
488 struct MHD_Response *response;
489 RequestMeta *m = connection_cls;
497 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
499 if (request_parse_accept(m, connection) < 0)
500 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
502 if (request_parse_range(m, connection) < 0)
503 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
505 if (request_parse_arguments(m, connection) < 0)
506 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
510 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
513 m->n_entries_set = true;
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);
523 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
525 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
527 return respond_oom(connection);
529 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
531 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
532 MHD_destroy_response(response);
537 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
541 eq = memchr(d, '=', l);
545 j = l - (eq - d + 1);
547 if (m == OUTPUT_JSON) {
548 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
549 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
552 fwrite(eq+1, 1, j, f);
559 static ssize_t request_reader_fields(
565 RequestMeta *m = cls;
572 assert(pos >= m->delta);
576 while (pos >= m->size) {
581 /* End of this field, so let's serialize the next
584 if (m->n_fields_set &&
586 return MHD_CONTENT_READER_END_OF_STREAM;
588 r = sd_journal_enumerate_unique(m->journal, &d, &l);
590 log_error("Failed to advance field index: %s", strerror(-r));
591 return MHD_CONTENT_READER_END_WITH_ERROR;
593 return MHD_CONTENT_READER_END_OF_STREAM;
606 log_error("Failed to create temporary file: %m");
607 return MHD_CONTENT_READER_END_WITH_ERROR;
611 r = output_field(m->tmp, m->mode, d, l);
613 log_error("Failed to serialize item: %s", strerror(-r));
614 return MHD_CONTENT_READER_END_WITH_ERROR;
618 if (sz == (off_t) -1) {
619 log_error("Failed to retrieve file position: %m");
620 return MHD_CONTENT_READER_END_WITH_ERROR;
623 m->size = (uint64_t) sz;
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;
636 k = fread(buf, 1, n, m->tmp);
638 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
639 return MHD_CONTENT_READER_END_WITH_ERROR;
645 static int request_handler_fields(
646 struct MHD_Connection *connection,
648 void *connection_cls) {
650 struct MHD_Response *response;
651 RequestMeta *m = connection_cls;
659 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
661 if (request_parse_accept(m, connection) < 0)
662 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
664 r = sd_journal_query_unique(m->journal, field);
666 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
668 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
670 return respond_oom(connection);
672 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
674 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
675 MHD_destroy_response(response);
680 static int request_handler_redirect(
681 struct MHD_Connection *connection,
682 const char *target) {
685 struct MHD_Response *response;
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);
694 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
697 return respond_oom(connection);
700 MHD_add_response_header(response, "Content-Type", "text/html");
701 MHD_add_response_header(response, "Location", target);
703 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
704 MHD_destroy_response(response);
709 static int request_handler_file(
710 struct MHD_Connection *connection,
712 const char *mime_type) {
714 struct MHD_Response *response;
716 _cleanup_close_ int fd = -1;
723 fd = open(path, O_RDONLY|O_CLOEXEC);
725 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
727 if (fstat(fd, &st) < 0)
728 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
730 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
732 return respond_oom(connection);
736 MHD_add_response_header(response, "Content-Type", mime_type);
738 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
739 MHD_destroy_response(response);
744 static int get_virtualization(char **v) {
745 _cleanup_bus_unref_ sd_bus *bus = NULL;
749 r = sd_bus_default_system(&bus);
753 r = sd_bus_get_property_string(
755 "org.freedesktop.systemd1",
756 "/org/freedesktop/systemd1",
757 "org.freedesktop.systemd1.Manager",
774 static int request_handler_machine(
775 struct MHD_Connection *connection,
776 void *connection_cls) {
778 struct MHD_Response *response;
779 RequestMeta *m = connection_cls;
781 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
782 uint64_t cutoff_from, cutoff_to, usage;
785 _cleanup_free_ char *v = NULL;
792 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
794 r = sd_id128_get_machine(&mid);
796 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
798 r = sd_id128_get_boot(&bid);
800 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
802 hostname = gethostname_malloc();
804 return respond_oom(connection);
806 r = sd_journal_get_usage(m->journal, &usage);
808 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
810 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
812 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
814 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
816 get_virtualization(&v);
819 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
820 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
821 "\"hostname\" : \"%s\","
822 "\"os_pretty_name\" : \"%s\","
823 "\"virtualization\" : \"%s\","
824 "\"usage\" : \"%"PRIu64"\","
825 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
826 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
827 SD_ID128_FORMAT_VAL(mid),
828 SD_ID128_FORMAT_VAL(bid),
829 hostname_cleanup(hostname, false),
830 os_name ? os_name : "Linux",
837 return respond_oom(connection);
839 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
842 return respond_oom(connection);
845 MHD_add_response_header(response, "Content-Type", "application/json");
846 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
847 MHD_destroy_response(response);
852 static int request_handler(
854 struct MHD_Connection *connection,
858 const char *upload_data,
859 size_t *upload_data_size,
860 void **connection_cls) {
863 assert(connection_cls);
867 if (!streq(method, "GET"))
868 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
869 "Unsupported method.\n");
872 if (!*connection_cls) {
873 if (!request_meta(connection_cls))
874 return respond_oom(connection);
879 return request_handler_redirect(connection, "/browse");
881 if (streq(url, "/entries"))
882 return request_handler_entries(connection, *connection_cls);
884 if (startswith(url, "/fields/"))
885 return request_handler_fields(connection, url + 8, *connection_cls);
887 if (streq(url, "/browse"))
888 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
890 if (streq(url, "/machine"))
891 return request_handler_machine(connection, *connection_cls);
893 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
896 static int help(void) {
898 printf("%s [OPTIONS...] ...\n\n"
899 "HTTP server for journal events.\n\n"
900 " -h --help Show this help\n"
901 " --version Show package version\n"
902 " --cert=CERT.PEM Specify server certificate in PEM format\n"
903 " --key=KEY.PEM Specify server key in PEM format\n",
904 program_invocation_short_name);
909 static char *key_pem = NULL;
910 static char *cert_pem = NULL;
912 static int parse_argv(int argc, char *argv[]) {
921 static const struct option options[] = {
922 { "help", no_argument, NULL, 'h' },
923 { "version", no_argument, NULL, ARG_VERSION },
924 { "key", required_argument, NULL, ARG_KEY },
925 { "cert", required_argument, NULL, ARG_CERT },
932 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
940 puts(PACKAGE_STRING);
941 puts(SYSTEMD_FEATURES);
946 log_error("Key file specified twice");
949 r = read_full_file(optarg, &key_pem, NULL);
951 log_error("Failed to read key file: %s", strerror(-r));
959 log_error("Certificate file specified twice");
962 r = read_full_file(optarg, &cert_pem, NULL);
964 log_error("Failed to read certificate file: %s", strerror(-r));
974 assert_not_reached("Unhandled option");
978 log_error("This program does not take arguments.");
982 if (!!key_pem != !!cert_pem) {
983 log_error("Certificate and key files must be specified together");
990 int main(int argc, char *argv[]) {
991 struct MHD_Daemon *d = NULL;
994 log_set_target(LOG_TARGET_AUTO);
995 log_parse_environment();
998 r = parse_argv(argc, argv);
1000 return EXIT_FAILURE;
1002 return EXIT_SUCCESS;
1004 n = sd_listen_fds(1);
1006 log_error("Failed to determine passed sockets: %s", strerror(-n));
1009 log_error("Can't listen on more than one socket.");
1012 struct MHD_OptionItem opts[] = {
1013 { MHD_OPTION_NOTIFY_COMPLETED,
1014 (intptr_t) request_meta_free, NULL },
1015 { MHD_OPTION_EXTERNAL_LOGGER,
1016 (intptr_t) microhttpd_logger, NULL },
1017 { MHD_OPTION_END, 0, NULL },
1018 { MHD_OPTION_END, 0, NULL },
1019 { MHD_OPTION_END, 0, NULL },
1020 { MHD_OPTION_END, 0, NULL }};
1022 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1025 opts[opts_pos++] = (struct MHD_OptionItem)
1026 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1029 opts[opts_pos++] = (struct MHD_OptionItem)
1030 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1031 opts[opts_pos++] = (struct MHD_OptionItem)
1032 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1033 flags |= MHD_USE_SSL;
1036 d = MHD_start_daemon(flags, 19531,
1038 request_handler, NULL,
1039 MHD_OPTION_ARRAY, opts,
1044 log_error("Failed to start daemon!");