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"
34 #include "logs-show.h"
35 #include "microhttpd-util.h"
40 typedef struct RequestMeta {
53 int argument_parse_error;
62 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
63 [OUTPUT_SHORT] = "text/plain",
64 [OUTPUT_JSON] = "application/json",
65 [OUTPUT_JSON_SSE] = "text/event-stream",
66 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
69 static RequestMeta *request_meta(void **connection_cls) {
73 return *connection_cls;
75 m = new0(RequestMeta, 1);
83 static void request_meta_free(
85 struct MHD_Connection *connection,
86 void **connection_cls,
87 enum MHD_RequestTerminationCode toe) {
89 RequestMeta *m = *connection_cls;
95 sd_journal_close(m->journal);
104 static int open_journal(RequestMeta *m) {
110 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
113 static int respond_oom_internal(struct MHD_Connection *connection) {
114 struct MHD_Response *response;
115 const char m[] = "Out of memory.\n";
120 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
124 MHD_add_response_header(response, "Content-Type", "text/plain");
125 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
126 MHD_destroy_response(response);
131 #define respond_oom(connection) log_oom(), respond_oom_internal(connection)
133 static int respond_error(
134 struct MHD_Connection *connection,
136 const char *format, ...) {
138 struct MHD_Response *response;
146 va_start(ap, format);
147 r = vasprintf(&m, format, ap);
151 return respond_oom(connection);
153 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
156 return respond_oom(connection);
159 MHD_add_response_header(response, "Content-Type", "text/plain");
160 r = MHD_queue_response(connection, code, response);
161 MHD_destroy_response(response);
166 static ssize_t request_reader_entries(
172 RequestMeta *m = cls;
179 assert(pos >= m->delta);
183 while (pos >= m->size) {
186 /* End of this entry, so let's serialize the next
189 if (m->n_entries_set &&
191 return MHD_CONTENT_READER_END_OF_STREAM;
194 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
195 else if (m->n_skip > 0)
196 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
198 r = sd_journal_next(m->journal);
201 log_error("Failed to advance journal pointer: %s", strerror(-r));
202 return MHD_CONTENT_READER_END_WITH_ERROR;
206 r = sd_journal_wait(m->journal, (uint64_t) -1);
208 log_error("Couldn't wait for journal event: %s", strerror(-r));
209 return MHD_CONTENT_READER_END_WITH_ERROR;
215 return MHD_CONTENT_READER_END_OF_STREAM;
221 r = sd_journal_test_cursor(m->journal, m->cursor);
223 log_error("Failed to test cursor: %s", strerror(-r));
224 return MHD_CONTENT_READER_END_WITH_ERROR;
228 return MHD_CONTENT_READER_END_OF_STREAM;
234 if (m->n_entries_set)
244 log_error("Failed to create temporary file: %m");
245 return MHD_CONTENT_READER_END_WITH_ERROR;
249 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
251 log_error("Failed to serialize item: %s", strerror(-r));
252 return MHD_CONTENT_READER_END_WITH_ERROR;
256 if (sz == (off_t) -1) {
257 log_error("Failed to retrieve file position: %m");
258 return MHD_CONTENT_READER_END_WITH_ERROR;
261 m->size = (uint64_t) sz;
264 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
265 log_error("Failed to seek to position: %m");
266 return MHD_CONTENT_READER_END_WITH_ERROR;
274 k = fread(buf, 1, n, m->tmp);
276 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
277 return MHD_CONTENT_READER_END_WITH_ERROR;
283 static int request_parse_accept(
285 struct MHD_Connection *connection) {
292 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
296 if (streq(header, mime_types[OUTPUT_JSON]))
297 m->mode = OUTPUT_JSON;
298 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
299 m->mode = OUTPUT_JSON_SSE;
300 else if (streq(header, mime_types[OUTPUT_EXPORT]))
301 m->mode = OUTPUT_EXPORT;
303 m->mode = OUTPUT_SHORT;
308 static int request_parse_range(
310 struct MHD_Connection *connection) {
312 const char *range, *colon, *colon2;
318 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
322 if (!startswith(range, "entries="))
326 range += strspn(range, WHITESPACE);
328 colon = strchr(range, ':');
330 m->cursor = strdup(range);
334 colon2 = strchr(colon + 1, ':');
336 char _cleanup_free_ *t;
338 t = strndup(colon + 1, colon2 - colon - 1);
342 r = safe_atoi64(t, &m->n_skip);
347 p = (colon2 ? colon2 : colon) + 1;
349 r = safe_atou64(p, &m->n_entries);
353 if (m->n_entries <= 0)
356 m->n_entries_set = true;
359 m->cursor = strndup(range, colon - range);
365 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
366 if (isempty(m->cursor)) {
374 static int request_parse_arguments_iterator(
376 enum MHD_ValueKind kind,
380 RequestMeta *m = cls;
381 _cleanup_free_ char *p = NULL;
387 m->argument_parse_error = -EINVAL;
391 if (streq(key, "follow")) {
392 if (isempty(value)) {
397 r = parse_boolean(value);
399 m->argument_parse_error = r;
407 if (streq(key, "discrete")) {
408 if (isempty(value)) {
413 r = parse_boolean(value);
415 m->argument_parse_error = r;
423 if (streq(key, "boot")) {
427 r = parse_boolean(value);
429 m->argument_parse_error = r;
435 char match[9 + 32 + 1] = "_BOOT_ID=";
438 r = sd_id128_get_boot(&bid);
440 log_error("Failed to get boot ID: %s", strerror(-r));
444 sd_id128_to_string(bid, match + 9);
445 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
447 m->argument_parse_error = r;
455 p = strjoin(key, "=", strempty(value), NULL);
457 m->argument_parse_error = log_oom();
461 r = sd_journal_add_match(m->journal, p, 0);
463 m->argument_parse_error = r;
470 static int request_parse_arguments(
472 struct MHD_Connection *connection) {
477 m->argument_parse_error = 0;
478 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
480 return m->argument_parse_error;
483 static int request_handler_entries(
484 struct MHD_Connection *connection,
485 void *connection_cls) {
487 struct MHD_Response *response;
488 RequestMeta *m = connection_cls;
496 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
498 if (request_parse_accept(m, connection) < 0)
499 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
501 if (request_parse_range(m, connection) < 0)
502 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
504 if (request_parse_arguments(m, connection) < 0)
505 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
509 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
512 m->n_entries_set = true;
516 r = sd_journal_seek_cursor(m->journal, m->cursor);
517 else if (m->n_skip >= 0)
518 r = sd_journal_seek_head(m->journal);
519 else if (m->n_skip < 0)
520 r = sd_journal_seek_tail(m->journal);
522 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
524 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
526 return respond_oom(connection);
528 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
530 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
531 MHD_destroy_response(response);
536 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
540 eq = memchr(d, '=', l);
544 j = l - (eq - d + 1);
546 if (m == OUTPUT_JSON) {
547 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
548 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
551 fwrite(eq+1, 1, j, f);
558 static ssize_t request_reader_fields(
564 RequestMeta *m = cls;
571 assert(pos >= m->delta);
575 while (pos >= m->size) {
580 /* End of this field, so let's serialize the next
583 if (m->n_fields_set &&
585 return MHD_CONTENT_READER_END_OF_STREAM;
587 r = sd_journal_enumerate_unique(m->journal, &d, &l);
589 log_error("Failed to advance field index: %s", strerror(-r));
590 return MHD_CONTENT_READER_END_WITH_ERROR;
592 return MHD_CONTENT_READER_END_OF_STREAM;
605 log_error("Failed to create temporary file: %m");
606 return MHD_CONTENT_READER_END_WITH_ERROR;
610 r = output_field(m->tmp, m->mode, d, l);
612 log_error("Failed to serialize item: %s", strerror(-r));
613 return MHD_CONTENT_READER_END_WITH_ERROR;
617 if (sz == (off_t) -1) {
618 log_error("Failed to retrieve file position: %m");
619 return MHD_CONTENT_READER_END_WITH_ERROR;
622 m->size = (uint64_t) sz;
625 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
626 log_error("Failed to seek to position: %m");
627 return MHD_CONTENT_READER_END_WITH_ERROR;
635 k = fread(buf, 1, n, m->tmp);
637 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
638 return MHD_CONTENT_READER_END_WITH_ERROR;
644 static int request_handler_fields(
645 struct MHD_Connection *connection,
647 void *connection_cls) {
649 struct MHD_Response *response;
650 RequestMeta *m = connection_cls;
658 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
660 if (request_parse_accept(m, connection) < 0)
661 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
663 r = sd_journal_query_unique(m->journal, field);
665 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
667 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
669 return respond_oom(connection);
671 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
673 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
674 MHD_destroy_response(response);
679 static int request_handler_redirect(
680 struct MHD_Connection *connection,
681 const char *target) {
684 struct MHD_Response *response;
690 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
691 return respond_oom(connection);
693 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
696 return respond_oom(connection);
699 MHD_add_response_header(response, "Content-Type", "text/html");
700 MHD_add_response_header(response, "Location", target);
702 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
703 MHD_destroy_response(response);
708 static int request_handler_file(
709 struct MHD_Connection *connection,
711 const char *mime_type) {
713 struct MHD_Response *response;
715 _cleanup_close_ int fd = -1;
722 fd = open(path, O_RDONLY|O_CLOEXEC);
724 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
726 if (fstat(fd, &st) < 0)
727 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
729 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
731 return respond_oom(connection);
735 MHD_add_response_header(response, "Content-Type", mime_type);
737 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
738 MHD_destroy_response(response);
743 static int request_handler_machine(
744 struct MHD_Connection *connection,
745 void *connection_cls) {
747 struct MHD_Response *response;
748 RequestMeta *m = connection_cls;
750 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
751 uint64_t cutoff_from, cutoff_to, usage;
754 const char *v = "bare";
761 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
763 r = sd_id128_get_machine(&mid);
765 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
767 r = sd_id128_get_boot(&bid);
769 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
771 hostname = gethostname_malloc();
773 return respond_oom(connection);
775 r = sd_journal_get_usage(m->journal, &usage);
777 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
779 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
781 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
783 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
785 detect_virtualization(&v);
788 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
789 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
790 "\"hostname\" : \"%s\","
791 "\"os_pretty_name\" : \"%s\","
792 "\"virtualization\" : \"%s\","
793 "\"usage\" : \"%llu\","
794 "\"cutoff_from_realtime\" : \"%llu\","
795 "\"cutoff_to_realtime\" : \"%llu\" }\n",
796 SD_ID128_FORMAT_VAL(mid),
797 SD_ID128_FORMAT_VAL(bid),
798 hostname_cleanup(hostname),
799 os_name ? os_name : "Linux",
801 (unsigned long long) usage,
802 (unsigned long long) cutoff_from,
803 (unsigned long long) cutoff_to);
806 return respond_oom(connection);
808 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
811 return respond_oom(connection);
814 MHD_add_response_header(response, "Content-Type", "application/json");
815 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
816 MHD_destroy_response(response);
821 static int request_handler(
823 struct MHD_Connection *connection,
827 const char *upload_data,
828 size_t *upload_data_size,
829 void **connection_cls) {
832 assert(connection_cls);
836 if (!streq(method, "GET"))
837 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
838 "Unsupported method.\n");
841 if (!*connection_cls) {
842 if (!request_meta(connection_cls))
843 return respond_oom(connection);
848 return request_handler_redirect(connection, "/browse");
850 if (streq(url, "/entries"))
851 return request_handler_entries(connection, *connection_cls);
853 if (startswith(url, "/fields/"))
854 return request_handler_fields(connection, url + 8, *connection_cls);
856 if (streq(url, "/browse"))
857 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
859 if (streq(url, "/machine"))
860 return request_handler_machine(connection, *connection_cls);
862 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
865 static int help(void) {
867 printf("%s [OPTIONS...] ...\n\n"
868 "HTTP server for journal events.\n\n"
869 " -h --help Show this help\n"
870 " --version Show package version\n"
871 " --cert=CERT.PEM Specify server certificate in PEM format\n"
872 " --key=KEY.PEM Specify server key in PEM format\n",
873 program_invocation_short_name);
878 static char *key_pem = NULL;
879 static char *cert_pem = NULL;
881 static int parse_argv(int argc, char *argv[]) {
890 static const struct option options[] = {
891 { "help", no_argument, NULL, 'h' },
892 { "version", no_argument, NULL, ARG_VERSION },
893 { "key", required_argument, NULL, ARG_KEY },
894 { "cert", required_argument, NULL, ARG_CERT },
901 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
904 puts(PACKAGE_STRING);
905 puts(SYSTEMD_FEATURES);
913 log_error("Key file specified twice");
916 r = read_full_file(optarg, &key_pem, NULL);
918 log_error("Failed to read key file: %s", strerror(-r));
926 log_error("Certificate file specified twice");
929 r = read_full_file(optarg, &cert_pem, NULL);
931 log_error("Failed to read certificate file: %s", strerror(-r));
941 log_error("Unknown option code %c", c);
946 log_error("This program does not take arguments.");
950 if (!!key_pem != !!cert_pem) {
951 log_error("Certificate and key files must be specified together");
958 int main(int argc, char *argv[]) {
959 struct MHD_Daemon *d = NULL;
962 log_set_target(LOG_TARGET_AUTO);
963 log_parse_environment();
966 r = parse_argv(argc, argv);
972 n = sd_listen_fds(1);
974 log_error("Failed to determine passed sockets: %s", strerror(-n));
977 log_error("Can't listen on more than one socket.");
980 struct MHD_OptionItem opts[] = {
981 { MHD_OPTION_NOTIFY_COMPLETED,
982 (intptr_t) request_meta_free, NULL },
983 { MHD_OPTION_EXTERNAL_LOGGER,
984 (intptr_t) microhttpd_logger, NULL },
985 { MHD_OPTION_END, 0, NULL },
986 { MHD_OPTION_END, 0, NULL },
987 { MHD_OPTION_END, 0, NULL },
988 { MHD_OPTION_END, 0, NULL }};
990 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
993 opts[opts_pos++] = (struct MHD_OptionItem)
994 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
997 opts[opts_pos++] = (struct MHD_OptionItem)
998 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
999 opts[opts_pos++] = (struct MHD_OptionItem)
1000 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1001 flags |= MHD_USE_SSL;
1004 d = MHD_start_daemon(flags, 19531,
1006 request_handler, NULL,
1007 MHD_OPTION_ARRAY, opts,
1012 log_error("Failed to start daemon!");