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_message_unref_ sd_bus_message *reply = NULL;
746 _cleanup_bus_unref_ sd_bus *bus = NULL;
751 r = sd_bus_open_system(&bus);
755 r = sd_bus_call_method(
757 "org.freedesktop.systemd1",
758 "/org/freedesktop/systemd1",
759 "org.freedesktop.DBus.Properties",
764 "org.freedesktop.systemd1.Manager",
769 r = sd_bus_message_read(reply, "v", "s", &t);
786 static int request_handler_machine(
787 struct MHD_Connection *connection,
788 void *connection_cls) {
790 struct MHD_Response *response;
791 RequestMeta *m = connection_cls;
793 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
794 uint64_t cutoff_from, cutoff_to, usage;
797 _cleanup_free_ char *v = NULL;
804 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
806 r = sd_id128_get_machine(&mid);
808 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
810 r = sd_id128_get_boot(&bid);
812 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
814 hostname = gethostname_malloc();
816 return respond_oom(connection);
818 r = sd_journal_get_usage(m->journal, &usage);
820 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
822 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
824 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
826 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
828 get_virtualization(&v);
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\","
836 "\"usage\" : \"%"PRIu64"\","
837 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
838 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
839 SD_ID128_FORMAT_VAL(mid),
840 SD_ID128_FORMAT_VAL(bid),
841 hostname_cleanup(hostname, false),
842 os_name ? os_name : "Linux",
849 return respond_oom(connection);
851 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
854 return respond_oom(connection);
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);
864 static int request_handler(
866 struct MHD_Connection *connection,
870 const char *upload_data,
871 size_t *upload_data_size,
872 void **connection_cls) {
875 assert(connection_cls);
879 if (!streq(method, "GET"))
880 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
881 "Unsupported method.\n");
884 if (!*connection_cls) {
885 if (!request_meta(connection_cls))
886 return respond_oom(connection);
891 return request_handler_redirect(connection, "/browse");
893 if (streq(url, "/entries"))
894 return request_handler_entries(connection, *connection_cls);
896 if (startswith(url, "/fields/"))
897 return request_handler_fields(connection, url + 8, *connection_cls);
899 if (streq(url, "/browse"))
900 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
902 if (streq(url, "/machine"))
903 return request_handler_machine(connection, *connection_cls);
905 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
908 static int help(void) {
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);
921 static char *key_pem = NULL;
922 static char *cert_pem = NULL;
924 static int parse_argv(int argc, char *argv[]) {
933 static const struct option options[] = {
934 { "help", no_argument, NULL, 'h' },
935 { "version", no_argument, NULL, ARG_VERSION },
936 { "key", required_argument, NULL, ARG_KEY },
937 { "cert", required_argument, NULL, ARG_CERT },
944 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
947 puts(PACKAGE_STRING);
948 puts(SYSTEMD_FEATURES);
956 log_error("Key file specified twice");
959 r = read_full_file(optarg, &key_pem, NULL);
961 log_error("Failed to read key file: %s", strerror(-r));
969 log_error("Certificate file specified twice");
972 r = read_full_file(optarg, &cert_pem, NULL);
974 log_error("Failed to read certificate file: %s", strerror(-r));
984 log_error("Unknown option code %c", c);
989 log_error("This program does not take arguments.");
993 if (!!key_pem != !!cert_pem) {
994 log_error("Certificate and key files must be specified together");
1001 int main(int argc, char *argv[]) {
1002 struct MHD_Daemon *d = NULL;
1005 log_set_target(LOG_TARGET_AUTO);
1006 log_parse_environment();
1009 r = parse_argv(argc, argv);
1011 return EXIT_FAILURE;
1013 return EXIT_SUCCESS;
1015 n = sd_listen_fds(1);
1017 log_error("Failed to determine passed sockets: %s", strerror(-n));
1020 log_error("Can't listen on more than one socket.");
1023 struct MHD_OptionItem opts[] = {
1024 { MHD_OPTION_NOTIFY_COMPLETED,
1025 (intptr_t) request_meta_free, NULL },
1026 { MHD_OPTION_EXTERNAL_LOGGER,
1027 (intptr_t) microhttpd_logger, NULL },
1028 { MHD_OPTION_END, 0, NULL },
1029 { MHD_OPTION_END, 0, NULL },
1030 { MHD_OPTION_END, 0, NULL },
1031 { MHD_OPTION_END, 0, NULL }};
1033 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1036 opts[opts_pos++] = (struct MHD_OptionItem)
1037 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1040 opts[opts_pos++] = (struct MHD_OptionItem)
1041 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1042 opts[opts_pos++] = (struct MHD_OptionItem)
1043 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1044 flags |= MHD_USE_SSL;
1047 d = MHD_start_daemon(flags, 19531,
1049 request_handler, NULL,
1050 MHD_OPTION_ARRAY, opts,
1055 log_error("Failed to start daemon!");