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);
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);
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 char _cleanup_free_ *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 request_handler_machine(
745 struct MHD_Connection *connection,
746 void *connection_cls) {
748 struct MHD_Response *response;
749 RequestMeta *m = connection_cls;
751 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
752 uint64_t cutoff_from, cutoff_to, usage;
755 const char *v = "bare";
762 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
764 r = sd_id128_get_machine(&mid);
766 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
768 r = sd_id128_get_boot(&bid);
770 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
772 hostname = gethostname_malloc();
774 return respond_oom(connection);
776 r = sd_journal_get_usage(m->journal, &usage);
778 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
780 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
782 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
784 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
786 detect_virtualization(&v);
789 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
790 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
791 "\"hostname\" : \"%s\","
792 "\"os_pretty_name\" : \"%s\","
793 "\"virtualization\" : \"%s\","
794 "\"usage\" : \"%llu\","
795 "\"cutoff_from_realtime\" : \"%llu\","
796 "\"cutoff_to_realtime\" : \"%llu\" }\n",
797 SD_ID128_FORMAT_VAL(mid),
798 SD_ID128_FORMAT_VAL(bid),
799 hostname_cleanup(hostname),
800 os_name ? os_name : "Linux",
802 (unsigned long long) usage,
803 (unsigned long long) cutoff_from,
804 (unsigned long long) cutoff_to);
807 return respond_oom(connection);
809 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
812 return respond_oom(connection);
815 MHD_add_response_header(response, "Content-Type", "application/json");
816 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
817 MHD_destroy_response(response);
822 static int request_handler(
824 struct MHD_Connection *connection,
828 const char *upload_data,
829 size_t *upload_data_size,
830 void **connection_cls) {
833 assert(connection_cls);
837 if (!streq(method, "GET"))
838 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
839 "Unsupported method.\n");
842 if (!*connection_cls) {
843 if (!request_meta(connection_cls))
844 return respond_oom(connection);
849 return request_handler_redirect(connection, "/browse");
851 if (streq(url, "/entries"))
852 return request_handler_entries(connection, *connection_cls);
854 if (startswith(url, "/fields/"))
855 return request_handler_fields(connection, url + 8, *connection_cls);
857 if (streq(url, "/browse"))
858 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
860 if (streq(url, "/machine"))
861 return request_handler_machine(connection, *connection_cls);
863 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
866 static int help(void) {
868 printf("%s [OPTIONS...] ...\n\n"
869 "HTTP server for journal events.\n\n"
870 " -h --help Show this help\n"
871 " --version Show package version\n"
872 " --cert=CERT.PEM Specify server certificate in PEM format\n"
873 " --key=KEY.PEM Specify server key in PEM format\n",
874 program_invocation_short_name);
879 static char *key_pem = NULL;
880 static char *cert_pem = NULL;
882 static int parse_argv(int argc, char *argv[]) {
891 static const struct option options[] = {
892 { "help", no_argument, NULL, 'h' },
893 { "version", no_argument, NULL, ARG_VERSION },
894 { "key", required_argument, NULL, ARG_KEY },
895 { "cert", required_argument, NULL, ARG_CERT },
902 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
905 puts(PACKAGE_STRING);
906 puts(SYSTEMD_FEATURES);
914 log_error("Key file specified twice");
917 r = read_full_file(optarg, &key_pem, NULL);
919 log_error("Failed to read key file: %s", strerror(-r));
927 log_error("Certificate file specified twice");
930 r = read_full_file(optarg, &cert_pem, NULL);
932 log_error("Failed to read certificate file: %s", strerror(-r));
942 log_error("Unknown option code %c", c);
947 log_error("This program does not take arguments.");
951 if (!!key_pem != !!cert_pem) {
952 log_error("Certificate and key files must be specified together");
959 int main(int argc, char *argv[]) {
960 struct MHD_Daemon *d = NULL;
963 log_set_target(LOG_TARGET_AUTO);
964 log_parse_environment();
967 r = parse_argv(argc, argv);
973 n = sd_listen_fds(1);
975 log_error("Failed to determine passed sockets: %s", strerror(-n));
978 log_error("Can't listen on more than one socket.");
981 struct MHD_OptionItem opts[] = {
982 { MHD_OPTION_NOTIFY_COMPLETED,
983 (intptr_t) request_meta_free, NULL },
984 { MHD_OPTION_EXTERNAL_LOGGER,
985 (intptr_t) microhttpd_logger, NULL },
986 { MHD_OPTION_END, 0, NULL },
987 { MHD_OPTION_END, 0, NULL },
988 { MHD_OPTION_END, 0, NULL },
989 { MHD_OPTION_END, 0, NULL }};
991 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
994 opts[opts_pos++] = (struct MHD_OptionItem)
995 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
998 opts[opts_pos++] = (struct MHD_OptionItem)
999 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1000 opts[opts_pos++] = (struct MHD_OptionItem)
1001 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1002 flags |= MHD_USE_SSL;
1005 d = MHD_start_daemon(flags, 19531,
1007 request_handler, NULL,
1008 MHD_OPTION_ARRAY, opts,
1013 log_error("Failed to start daemon!");