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"
35 #include "bus-message.h"
36 #include "bus-internal.h"
37 #include "logs-show.h"
38 #include "microhttpd-util.h"
42 typedef struct RequestMeta {
55 int argument_parse_error;
64 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
65 [OUTPUT_SHORT] = "text/plain",
66 [OUTPUT_JSON] = "application/json",
67 [OUTPUT_JSON_SSE] = "text/event-stream",
68 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
71 static RequestMeta *request_meta(void **connection_cls) {
75 return *connection_cls;
77 m = new0(RequestMeta, 1);
85 static void request_meta_free(
87 struct MHD_Connection *connection,
88 void **connection_cls,
89 enum MHD_RequestTerminationCode toe) {
91 RequestMeta *m = *connection_cls;
97 sd_journal_close(m->journal);
106 static int open_journal(RequestMeta *m) {
112 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
115 static int respond_oom_internal(struct MHD_Connection *connection) {
116 struct MHD_Response *response;
117 const char m[] = "Out of memory.\n";
122 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
126 MHD_add_response_header(response, "Content-Type", "text/plain");
127 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
128 MHD_destroy_response(response);
133 #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);
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 char _cleanup_free_ *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_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
747 _cleanup_bus_unref_ sd_bus *bus = NULL;
752 r = sd_bus_open_system(&bus);
756 r = sd_bus_message_new_method_call(
758 "org.freedesktop.systemd1",
759 "/org/freedesktop/systemd1",
760 "org.freedesktop.DBus.Properties",
766 r = sd_bus_message_append(m, "ss", "org.freedesktop.systemd1.Manager", "Virtualization");
770 r = sd_bus_send_with_reply_and_block(bus, m, 0, NULL, &reply);
774 r = sd_bus_message_read(reply, "v", "s", &t);
791 static int request_handler_machine(
792 struct MHD_Connection *connection,
793 void *connection_cls) {
795 struct MHD_Response *response;
796 RequestMeta *m = connection_cls;
798 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
799 uint64_t cutoff_from, cutoff_to, usage;
802 _cleanup_free_ char *v = NULL;
809 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
811 r = sd_id128_get_machine(&mid);
813 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
815 r = sd_id128_get_boot(&bid);
817 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
819 hostname = gethostname_malloc();
821 return respond_oom(connection);
823 r = sd_journal_get_usage(m->journal, &usage);
825 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
827 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
829 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
831 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
833 get_virtualization(&v);
836 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
837 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
838 "\"hostname\" : \"%s\","
839 "\"os_pretty_name\" : \"%s\","
840 "\"virtualization\" : \"%s\","
841 "\"usage\" : \"%llu\","
842 "\"cutoff_from_realtime\" : \"%llu\","
843 "\"cutoff_to_realtime\" : \"%llu\" }\n",
844 SD_ID128_FORMAT_VAL(mid),
845 SD_ID128_FORMAT_VAL(bid),
846 hostname_cleanup(hostname),
847 os_name ? os_name : "Linux",
849 (unsigned long long) usage,
850 (unsigned long long) cutoff_from,
851 (unsigned long long) cutoff_to);
854 return respond_oom(connection);
856 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
859 return respond_oom(connection);
862 MHD_add_response_header(response, "Content-Type", "application/json");
863 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
864 MHD_destroy_response(response);
869 static int request_handler(
871 struct MHD_Connection *connection,
875 const char *upload_data,
876 size_t *upload_data_size,
877 void **connection_cls) {
880 assert(connection_cls);
884 if (!streq(method, "GET"))
885 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
886 "Unsupported method.\n");
889 if (!*connection_cls) {
890 if (!request_meta(connection_cls))
891 return respond_oom(connection);
896 return request_handler_redirect(connection, "/browse");
898 if (streq(url, "/entries"))
899 return request_handler_entries(connection, *connection_cls);
901 if (startswith(url, "/fields/"))
902 return request_handler_fields(connection, url + 8, *connection_cls);
904 if (streq(url, "/browse"))
905 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
907 if (streq(url, "/machine"))
908 return request_handler_machine(connection, *connection_cls);
910 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
913 static int help(void) {
915 printf("%s [OPTIONS...] ...\n\n"
916 "HTTP server for journal events.\n\n"
917 " -h --help Show this help\n"
918 " --version Show package version\n"
919 " --cert=CERT.PEM Specify server certificate in PEM format\n"
920 " --key=KEY.PEM Specify server key in PEM format\n",
921 program_invocation_short_name);
926 static char *key_pem = NULL;
927 static char *cert_pem = NULL;
929 static int parse_argv(int argc, char *argv[]) {
938 static const struct option options[] = {
939 { "help", no_argument, NULL, 'h' },
940 { "version", no_argument, NULL, ARG_VERSION },
941 { "key", required_argument, NULL, ARG_KEY },
942 { "cert", required_argument, NULL, ARG_CERT },
949 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
952 puts(PACKAGE_STRING);
953 puts(SYSTEMD_FEATURES);
961 log_error("Key file specified twice");
964 r = read_full_file(optarg, &key_pem, NULL);
966 log_error("Failed to read key file: %s", strerror(-r));
974 log_error("Certificate file specified twice");
977 r = read_full_file(optarg, &cert_pem, NULL);
979 log_error("Failed to read certificate file: %s", strerror(-r));
989 log_error("Unknown option code %c", c);
994 log_error("This program does not take arguments.");
998 if (!!key_pem != !!cert_pem) {
999 log_error("Certificate and key files must be specified together");
1006 int main(int argc, char *argv[]) {
1007 struct MHD_Daemon *d = NULL;
1010 log_set_target(LOG_TARGET_AUTO);
1011 log_parse_environment();
1014 r = parse_argv(argc, argv);
1016 return EXIT_FAILURE;
1018 return EXIT_SUCCESS;
1020 n = sd_listen_fds(1);
1022 log_error("Failed to determine passed sockets: %s", strerror(-n));
1025 log_error("Can't listen on more than one socket.");
1028 struct MHD_OptionItem opts[] = {
1029 { MHD_OPTION_NOTIFY_COMPLETED,
1030 (intptr_t) request_meta_free, NULL },
1031 { MHD_OPTION_EXTERNAL_LOGGER,
1032 (intptr_t) microhttpd_logger, NULL },
1033 { MHD_OPTION_END, 0, NULL },
1034 { MHD_OPTION_END, 0, NULL },
1035 { MHD_OPTION_END, 0, NULL },
1036 { MHD_OPTION_END, 0, NULL }};
1038 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1041 opts[opts_pos++] = (struct MHD_OptionItem)
1042 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1045 opts[opts_pos++] = (struct MHD_OptionItem)
1046 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1047 opts[opts_pos++] = (struct MHD_OptionItem)
1048 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1049 flags |= MHD_USE_SSL;
1052 d = MHD_start_daemon(flags, 19531,
1054 request_handler, NULL,
1055 MHD_OPTION_ARRAY, opts,
1060 log_error("Failed to start daemon!");