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 _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_message_unref_ sd_bus_message *reply = NULL;
747 _cleanup_bus_unref_ sd_bus *bus = NULL;
752 r = sd_bus_open_system(&bus);
756 r = sd_bus_call_method(
758 "org.freedesktop.systemd1",
759 "/org/freedesktop/systemd1",
760 "org.freedesktop.DBus.Properties",
765 "org.freedesktop.systemd1.Manager",
770 r = sd_bus_message_read(reply, "v", "s", &t);
787 static int request_handler_machine(
788 struct MHD_Connection *connection,
789 void *connection_cls) {
791 struct MHD_Response *response;
792 RequestMeta *m = connection_cls;
794 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
795 uint64_t cutoff_from, cutoff_to, usage;
798 _cleanup_free_ char *v = NULL;
805 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
807 r = sd_id128_get_machine(&mid);
809 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
811 r = sd_id128_get_boot(&bid);
813 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
815 hostname = gethostname_malloc();
817 return respond_oom(connection);
819 r = sd_journal_get_usage(m->journal, &usage);
821 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
823 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
825 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
827 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
829 get_virtualization(&v);
832 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
833 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
834 "\"hostname\" : \"%s\","
835 "\"os_pretty_name\" : \"%s\","
836 "\"virtualization\" : \"%s\","
837 "\"usage\" : \"%llu\","
838 "\"cutoff_from_realtime\" : \"%llu\","
839 "\"cutoff_to_realtime\" : \"%llu\" }\n",
840 SD_ID128_FORMAT_VAL(mid),
841 SD_ID128_FORMAT_VAL(bid),
842 hostname_cleanup(hostname),
843 os_name ? os_name : "Linux",
845 (unsigned long long) usage,
846 (unsigned long long) cutoff_from,
847 (unsigned long long) cutoff_to);
850 return respond_oom(connection);
852 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
855 return respond_oom(connection);
858 MHD_add_response_header(response, "Content-Type", "application/json");
859 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
860 MHD_destroy_response(response);
865 static int request_handler(
867 struct MHD_Connection *connection,
871 const char *upload_data,
872 size_t *upload_data_size,
873 void **connection_cls) {
876 assert(connection_cls);
880 if (!streq(method, "GET"))
881 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
882 "Unsupported method.\n");
885 if (!*connection_cls) {
886 if (!request_meta(connection_cls))
887 return respond_oom(connection);
892 return request_handler_redirect(connection, "/browse");
894 if (streq(url, "/entries"))
895 return request_handler_entries(connection, *connection_cls);
897 if (startswith(url, "/fields/"))
898 return request_handler_fields(connection, url + 8, *connection_cls);
900 if (streq(url, "/browse"))
901 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
903 if (streq(url, "/machine"))
904 return request_handler_machine(connection, *connection_cls);
906 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
909 static int help(void) {
911 printf("%s [OPTIONS...] ...\n\n"
912 "HTTP server for journal events.\n\n"
913 " -h --help Show this help\n"
914 " --version Show package version\n"
915 " --cert=CERT.PEM Specify server certificate in PEM format\n"
916 " --key=KEY.PEM Specify server key in PEM format\n",
917 program_invocation_short_name);
922 static char *key_pem = NULL;
923 static char *cert_pem = NULL;
925 static int parse_argv(int argc, char *argv[]) {
934 static const struct option options[] = {
935 { "help", no_argument, NULL, 'h' },
936 { "version", no_argument, NULL, ARG_VERSION },
937 { "key", required_argument, NULL, ARG_KEY },
938 { "cert", required_argument, NULL, ARG_CERT },
945 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
948 puts(PACKAGE_STRING);
949 puts(SYSTEMD_FEATURES);
957 log_error("Key file specified twice");
960 r = read_full_file(optarg, &key_pem, NULL);
962 log_error("Failed to read key file: %s", strerror(-r));
970 log_error("Certificate file specified twice");
973 r = read_full_file(optarg, &cert_pem, NULL);
975 log_error("Failed to read certificate file: %s", strerror(-r));
985 log_error("Unknown option code %c", c);
990 log_error("This program does not take arguments.");
994 if (!!key_pem != !!cert_pem) {
995 log_error("Certificate and key files must be specified together");
1002 int main(int argc, char *argv[]) {
1003 struct MHD_Daemon *d = NULL;
1006 log_set_target(LOG_TARGET_AUTO);
1007 log_parse_environment();
1010 r = parse_argv(argc, argv);
1012 return EXIT_FAILURE;
1014 return EXIT_SUCCESS;
1016 n = sd_listen_fds(1);
1018 log_error("Failed to determine passed sockets: %s", strerror(-n));
1021 log_error("Can't listen on more than one socket.");
1024 struct MHD_OptionItem opts[] = {
1025 { MHD_OPTION_NOTIFY_COMPLETED,
1026 (intptr_t) request_meta_free, NULL },
1027 { MHD_OPTION_EXTERNAL_LOGGER,
1028 (intptr_t) microhttpd_logger, NULL },
1029 { MHD_OPTION_END, 0, NULL },
1030 { MHD_OPTION_END, 0, NULL },
1031 { MHD_OPTION_END, 0, NULL },
1032 { MHD_OPTION_END, 0, NULL }};
1034 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1037 opts[opts_pos++] = (struct MHD_OptionItem)
1038 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1041 opts[opts_pos++] = (struct MHD_OptionItem)
1042 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1043 opts[opts_pos++] = (struct MHD_OptionItem)
1044 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1045 flags |= MHD_USE_SSL;
1048 d = MHD_start_daemon(flags, 19531,
1050 request_handler, NULL,
1051 MHD_OPTION_ARRAY, opts,
1056 log_error("Failed to start daemon!");