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_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;
492 assert(connection_cls);
494 m = request_meta(connection_cls);
496 return respond_oom(connection);
500 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
502 if (request_parse_accept(m, connection) < 0)
503 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
505 if (request_parse_range(m, connection) < 0)
506 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
508 if (request_parse_arguments(m, connection) < 0)
509 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
513 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
516 m->n_entries_set = true;
520 r = sd_journal_seek_cursor(m->journal, m->cursor);
521 else if (m->n_skip >= 0)
522 r = sd_journal_seek_head(m->journal);
523 else if (m->n_skip < 0)
524 r = sd_journal_seek_tail(m->journal);
526 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
528 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
530 return respond_oom(connection);
532 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
534 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
535 MHD_destroy_response(response);
540 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
544 eq = memchr(d, '=', l);
548 j = l - (eq - d + 1);
550 if (m == OUTPUT_JSON) {
551 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
552 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
555 fwrite(eq+1, 1, j, f);
562 static ssize_t request_reader_fields(
568 RequestMeta *m = cls;
575 assert(pos >= m->delta);
579 while (pos >= m->size) {
584 /* End of this field, so let's serialize the next
587 if (m->n_fields_set &&
589 return MHD_CONTENT_READER_END_OF_STREAM;
591 r = sd_journal_enumerate_unique(m->journal, &d, &l);
593 log_error("Failed to advance field index: %s", strerror(-r));
594 return MHD_CONTENT_READER_END_WITH_ERROR;
596 return MHD_CONTENT_READER_END_OF_STREAM;
609 log_error("Failed to create temporary file: %m");
610 return MHD_CONTENT_READER_END_WITH_ERROR;
614 r = output_field(m->tmp, m->mode, d, l);
616 log_error("Failed to serialize item: %s", strerror(-r));
617 return MHD_CONTENT_READER_END_WITH_ERROR;
621 if (sz == (off_t) -1) {
622 log_error("Failed to retrieve file position: %m");
623 return MHD_CONTENT_READER_END_WITH_ERROR;
626 m->size = (uint64_t) sz;
629 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
630 log_error("Failed to seek to position: %m");
631 return MHD_CONTENT_READER_END_WITH_ERROR;
639 k = fread(buf, 1, n, m->tmp);
641 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
642 return MHD_CONTENT_READER_END_WITH_ERROR;
648 static int request_handler_fields(
649 struct MHD_Connection *connection,
651 void *connection_cls) {
653 struct MHD_Response *response;
658 assert(connection_cls);
660 m = request_meta(connection_cls);
662 return respond_oom(connection);
666 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
668 if (request_parse_accept(m, connection) < 0)
669 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
671 r = sd_journal_query_unique(m->journal, field);
673 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
675 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
677 return respond_oom(connection);
679 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
681 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
682 MHD_destroy_response(response);
687 static int request_handler_redirect(
688 struct MHD_Connection *connection,
689 const char *target) {
692 struct MHD_Response *response;
698 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
699 return respond_oom(connection);
701 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
704 return respond_oom(connection);
707 MHD_add_response_header(response, "Content-Type", "text/html");
708 MHD_add_response_header(response, "Location", target);
710 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
711 MHD_destroy_response(response);
716 static int request_handler_file(
717 struct MHD_Connection *connection,
719 const char *mime_type) {
721 struct MHD_Response *response;
723 _cleanup_close_ int fd = -1;
730 fd = open(path, O_RDONLY|O_CLOEXEC);
732 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
734 if (fstat(fd, &st) < 0)
735 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
737 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
739 return respond_oom(connection);
743 MHD_add_response_header(response, "Content-Type", mime_type);
745 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
746 MHD_destroy_response(response);
751 static int request_handler_machine(
752 struct MHD_Connection *connection,
753 void **connection_cls) {
755 struct MHD_Response *response;
758 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
759 uint64_t cutoff_from, cutoff_to, usage;
762 const char *v = "bare";
766 m = request_meta(connection_cls);
768 return respond_oom(connection);
772 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
774 r = sd_id128_get_machine(&mid);
776 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
778 r = sd_id128_get_boot(&bid);
780 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
782 hostname = gethostname_malloc();
784 return respond_oom(connection);
786 r = sd_journal_get_usage(m->journal, &usage);
788 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
790 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
792 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
794 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
796 detect_virtualization(&v);
799 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
800 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
801 "\"hostname\" : \"%s\","
802 "\"os_pretty_name\" : \"%s\","
803 "\"virtualization\" : \"%s\","
804 "\"usage\" : \"%llu\","
805 "\"cutoff_from_realtime\" : \"%llu\","
806 "\"cutoff_to_realtime\" : \"%llu\" }\n",
807 SD_ID128_FORMAT_VAL(mid),
808 SD_ID128_FORMAT_VAL(bid),
809 hostname_cleanup(hostname),
810 os_name ? os_name : "Linux",
812 (unsigned long long) usage,
813 (unsigned long long) cutoff_from,
814 (unsigned long long) cutoff_to);
817 return respond_oom(connection);
819 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
822 return respond_oom(connection);
825 MHD_add_response_header(response, "Content-Type", "application/json");
826 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
827 MHD_destroy_response(response);
832 static int request_handler(
834 struct MHD_Connection *connection,
838 const char *upload_data,
839 size_t *upload_data_size,
840 void **connection_cls) {
846 if (!streq(method, "GET"))
850 return request_handler_redirect(connection, "/browse");
852 if (streq(url, "/entries"))
853 return request_handler_entries(connection, connection_cls);
855 if (startswith(url, "/fields/"))
856 return request_handler_fields(connection, url + 8, connection_cls);
858 if (streq(url, "/browse"))
859 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
861 if (streq(url, "/machine"))
862 return request_handler_machine(connection, connection_cls);
864 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
867 static char *key_pem = NULL;
868 static char *cert_pem = NULL;
870 static int parse_argv(int argc, char *argv[]) {
879 static const struct option options[] = {
880 { "version", no_argument, NULL, ARG_VERSION },
881 { "key", required_argument, NULL, ARG_KEY },
882 { "cert", required_argument, NULL, ARG_CERT },
889 while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
892 puts(PACKAGE_STRING);
893 puts(SYSTEMD_FEATURES);
898 log_error("Key file specified twice");
901 r = read_full_file(optarg, &key_pem, NULL);
903 log_error("Failed to read key file: %s", strerror(-r));
911 log_error("Certificate file specified twice");
914 r = read_full_file(optarg, &cert_pem, NULL);
916 log_error("Failed to read certificate file: %s", strerror(-r));
926 log_error("Unknown option code %c", c);
931 log_error("This program does not take arguments.");
935 if (!!key_pem != !!cert_pem) {
936 log_error("Certificate and key files must be specified together");
943 int main(int argc, char *argv[]) {
944 struct MHD_Daemon *d = NULL;
947 log_set_target(LOG_TARGET_AUTO);
948 log_parse_environment();
951 r = parse_argv(argc, argv);
957 n = sd_listen_fds(1);
959 log_error("Failed to determine passed sockets: %s", strerror(-n));
962 log_error("Can't listen on more than one socket.");
965 struct MHD_OptionItem opts[] = {
966 { MHD_OPTION_NOTIFY_COMPLETED,
967 (intptr_t) request_meta_free, NULL },
968 { MHD_OPTION_EXTERNAL_LOGGER,
969 (intptr_t) microhttpd_logger, NULL },
970 { MHD_OPTION_END, 0, NULL },
971 { MHD_OPTION_END, 0, NULL },
972 { MHD_OPTION_END, 0, NULL },
973 { MHD_OPTION_END, 0, NULL }};
975 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
978 opts[opts_pos++] = (struct MHD_OptionItem)
979 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
982 opts[opts_pos++] = (struct MHD_OptionItem)
983 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
984 opts[opts_pos++] = (struct MHD_OptionItem)
985 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
986 flags |= MHD_USE_SSL;
989 d = MHD_start_daemon(flags, 19531,
991 request_handler, NULL,
992 MHD_OPTION_ARRAY, opts,
997 log_error("Failed to start daemon!");