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"
39 typedef struct RequestMeta {
52 int argument_parse_error;
61 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
62 [OUTPUT_SHORT] = "text/plain",
63 [OUTPUT_JSON] = "application/json",
64 [OUTPUT_JSON_SSE] = "text/event-stream",
65 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
68 static RequestMeta *request_meta(void **connection_cls) {
72 return *connection_cls;
74 m = new0(RequestMeta, 1);
82 static void request_meta_free(
84 struct MHD_Connection *connection,
85 void **connection_cls,
86 enum MHD_RequestTerminationCode toe) {
88 RequestMeta *m = *connection_cls;
94 sd_journal_close(m->journal);
103 static int open_journal(RequestMeta *m) {
109 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
113 static int respond_oom(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 static int respond_error(
132 struct MHD_Connection *connection,
134 const char *format, ...) {
136 struct MHD_Response *response;
144 va_start(ap, format);
145 r = vasprintf(&m, format, ap);
149 return respond_oom(connection);
151 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
154 return respond_oom(connection);
157 MHD_add_response_header(response, "Content-Type", "text/plain");
158 r = MHD_queue_response(connection, code, response);
159 MHD_destroy_response(response);
164 static ssize_t request_reader_entries(
170 RequestMeta *m = cls;
177 assert(pos >= m->delta);
181 while (pos >= m->size) {
184 /* End of this entry, so let's serialize the next
187 if (m->n_entries_set &&
189 return MHD_CONTENT_READER_END_OF_STREAM;
192 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
193 else if (m->n_skip > 0)
194 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
196 r = sd_journal_next(m->journal);
199 log_error("Failed to advance journal pointer: %s", strerror(-r));
200 return MHD_CONTENT_READER_END_WITH_ERROR;
204 r = sd_journal_wait(m->journal, (uint64_t) -1);
206 log_error("Couldn't wait for journal event: %s", strerror(-r));
207 return MHD_CONTENT_READER_END_WITH_ERROR;
213 return MHD_CONTENT_READER_END_OF_STREAM;
219 r = sd_journal_test_cursor(m->journal, m->cursor);
221 log_error("Failed to test cursor: %s", strerror(-r));
222 return MHD_CONTENT_READER_END_WITH_ERROR;
226 return MHD_CONTENT_READER_END_OF_STREAM;
232 if (m->n_entries_set)
242 log_error("Failed to create temporary file: %m");
243 return MHD_CONTENT_READER_END_WITH_ERROR;
247 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
249 log_error("Failed to serialize item: %s", strerror(-r));
250 return MHD_CONTENT_READER_END_WITH_ERROR;
254 if (sz == (off_t) -1) {
255 log_error("Failed to retrieve file position: %m");
256 return MHD_CONTENT_READER_END_WITH_ERROR;
259 m->size = (uint64_t) sz;
262 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
263 log_error("Failed to seek to position: %m");
264 return MHD_CONTENT_READER_END_WITH_ERROR;
272 k = fread(buf, 1, n, m->tmp);
274 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
275 return MHD_CONTENT_READER_END_WITH_ERROR;
281 static int request_parse_accept(
283 struct MHD_Connection *connection) {
290 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
294 if (streq(header, mime_types[OUTPUT_JSON]))
295 m->mode = OUTPUT_JSON;
296 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
297 m->mode = OUTPUT_JSON_SSE;
298 else if (streq(header, mime_types[OUTPUT_EXPORT]))
299 m->mode = OUTPUT_EXPORT;
301 m->mode = OUTPUT_SHORT;
306 static int request_parse_range(
308 struct MHD_Connection *connection) {
310 const char *range, *colon, *colon2;
316 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
320 if (!startswith(range, "entries="))
324 range += strspn(range, WHITESPACE);
326 colon = strchr(range, ':');
328 m->cursor = strdup(range);
332 colon2 = strchr(colon + 1, ':');
334 char _cleanup_free_ *t;
336 t = strndup(colon + 1, colon2 - colon - 1);
340 r = safe_atoi64(t, &m->n_skip);
345 p = (colon2 ? colon2 : colon) + 1;
347 r = safe_atou64(p, &m->n_entries);
351 if (m->n_entries <= 0)
354 m->n_entries_set = true;
357 m->cursor = strndup(range, colon - range);
363 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
364 if (isempty(m->cursor)) {
372 static int request_parse_arguments_iterator(
374 enum MHD_ValueKind kind,
378 RequestMeta *m = cls;
379 _cleanup_free_ char *p = NULL;
385 m->argument_parse_error = -EINVAL;
389 if (streq(key, "follow")) {
390 if (isempty(value)) {
395 r = parse_boolean(value);
397 m->argument_parse_error = r;
405 if (streq(key, "discrete")) {
406 if (isempty(value)) {
411 r = parse_boolean(value);
413 m->argument_parse_error = r;
421 if (streq(key, "boot")) {
425 r = parse_boolean(value);
427 m->argument_parse_error = r;
433 char match[9 + 32 + 1] = "_BOOT_ID=";
436 r = sd_id128_get_boot(&bid);
438 log_error("Failed to get boot ID: %s", strerror(-r));
442 sd_id128_to_string(bid, match + 9);
443 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
445 m->argument_parse_error = r;
453 p = strjoin(key, "=", strempty(value), NULL);
455 m->argument_parse_error = log_oom();
459 r = sd_journal_add_match(m->journal, p, 0);
461 m->argument_parse_error = r;
468 static int request_parse_arguments(
470 struct MHD_Connection *connection) {
475 m->argument_parse_error = 0;
476 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
478 return m->argument_parse_error;
481 static int request_handler_entries(
482 struct MHD_Connection *connection,
483 void **connection_cls) {
485 struct MHD_Response *response;
490 assert(connection_cls);
492 m = request_meta(connection_cls);
494 return respond_oom(connection);
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;
656 assert(connection_cls);
658 m = request_meta(connection_cls);
660 return respond_oom(connection);
664 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
666 if (request_parse_accept(m, connection) < 0)
667 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
669 r = sd_journal_query_unique(m->journal, field);
671 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
673 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
675 return respond_oom(connection);
677 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
679 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
680 MHD_destroy_response(response);
685 static int request_handler_redirect(
686 struct MHD_Connection *connection,
687 const char *target) {
690 struct MHD_Response *response;
696 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
697 return respond_oom(connection);
699 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
702 return respond_oom(connection);
705 MHD_add_response_header(response, "Content-Type", "text/html");
706 MHD_add_response_header(response, "Location", target);
708 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
709 MHD_destroy_response(response);
714 static int request_handler_file(
715 struct MHD_Connection *connection,
717 const char *mime_type) {
719 struct MHD_Response *response;
721 _cleanup_close_ int fd = -1;
728 fd = open(path, O_RDONLY|O_CLOEXEC);
730 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
732 if (fstat(fd, &st) < 0)
733 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
735 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
737 return respond_oom(connection);
741 MHD_add_response_header(response, "Content-Type", mime_type);
743 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
744 MHD_destroy_response(response);
749 static int request_handler_machine(
750 struct MHD_Connection *connection,
751 void **connection_cls) {
753 struct MHD_Response *response;
756 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
757 uint64_t cutoff_from, cutoff_to, usage;
760 const char *v = "bare";
764 m = request_meta(connection_cls);
766 return respond_oom(connection);
770 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
772 r = sd_id128_get_machine(&mid);
774 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
776 r = sd_id128_get_boot(&bid);
778 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
780 hostname = gethostname_malloc();
782 return respond_oom(connection);
784 r = sd_journal_get_usage(m->journal, &usage);
786 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
788 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
790 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
792 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
794 detect_virtualization(&v);
797 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
798 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
799 "\"hostname\" : \"%s\","
800 "\"os_pretty_name\" : \"%s\","
801 "\"virtualization\" : \"%s\","
802 "\"usage\" : \"%llu\","
803 "\"cutoff_from_realtime\" : \"%llu\","
804 "\"cutoff_to_realtime\" : \"%llu\" }\n",
805 SD_ID128_FORMAT_VAL(mid),
806 SD_ID128_FORMAT_VAL(bid),
807 hostname_cleanup(hostname),
808 os_name ? os_name : "Linux",
810 (unsigned long long) usage,
811 (unsigned long long) cutoff_from,
812 (unsigned long long) cutoff_to);
815 return respond_oom(connection);
817 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
820 return respond_oom(connection);
823 MHD_add_response_header(response, "Content-Type", "application/json");
824 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
825 MHD_destroy_response(response);
830 static int request_handler(
832 struct MHD_Connection *connection,
836 const char *upload_data,
837 size_t *upload_data_size,
838 void **connection_cls) {
844 if (!streq(method, "GET"))
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 char *key_pem = NULL;
866 static char *cert_pem = NULL;
868 static int parse_argv(int argc, char *argv[]) {
877 static const struct option options[] = {
878 { "version", no_argument, NULL, ARG_VERSION },
879 { "key", required_argument, NULL, ARG_KEY },
880 { "cert", required_argument, NULL, ARG_CERT },
887 while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
890 puts(PACKAGE_STRING);
891 puts(SYSTEMD_FEATURES);
896 log_error("Key file specified twice");
899 r = read_full_file(optarg, &key_pem, NULL);
901 log_error("Failed to read key file: %s", strerror(-r));
909 log_error("Certificate file specified twice");
912 r = read_full_file(optarg, &cert_pem, NULL);
914 log_error("Failed to read certificate file: %s", strerror(-r));
924 log_error("Unknown option code %c", c);
929 log_error("This program does not take arguments.");
933 if (!!key_pem != !!cert_pem) {
934 log_error("Certificate and key files must be specified together");
941 int main(int argc, char *argv[]) {
942 struct MHD_Daemon *d = NULL;
945 log_set_target(LOG_TARGET_AUTO);
946 log_parse_environment();
949 r = parse_argv(argc, argv);
955 n = sd_listen_fds(1);
957 log_error("Failed to determine passed sockets: %s", strerror(-n));
960 log_error("Can't listen on more than one socket.");
963 struct MHD_OptionItem opts[] = {
964 { MHD_OPTION_NOTIFY_COMPLETED,
965 (intptr_t) request_meta_free, NULL },
966 { MHD_OPTION_EXTERNAL_LOGGER,
967 (intptr_t) microhttpd_logger, NULL },
968 { MHD_OPTION_END, 0, NULL },
969 { MHD_OPTION_END, 0, NULL },
970 { MHD_OPTION_END, 0, NULL },
971 { MHD_OPTION_END, 0, NULL }};
973 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
976 opts[opts_pos++] = (struct MHD_OptionItem)
977 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
980 opts[opts_pos++] = (struct MHD_OptionItem)
981 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
982 opts[opts_pos++] = (struct MHD_OptionItem)
983 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
984 flags |= MHD_USE_SSL;
987 d = MHD_start_daemon(flags, 19531,
989 request_handler, NULL,
990 MHD_OPTION_ARRAY, opts,
995 log_error("Failed to start daemon!");