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/>.
27 #include <microhttpd.h>
31 #include "sd-journal.h"
32 #include "sd-daemon.h"
33 #include "logs-show.h"
36 typedef struct RequestMeta {
49 int argument_parse_error;
58 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
59 [OUTPUT_SHORT] = "text/plain",
60 [OUTPUT_JSON] = "application/json",
61 [OUTPUT_JSON_SSE] = "text/event-stream",
62 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
65 static RequestMeta *request_meta(void **connection_cls) {
69 return *connection_cls;
71 m = new0(RequestMeta, 1);
79 static void request_meta_free(
81 struct MHD_Connection *connection,
82 void **connection_cls,
83 enum MHD_RequestTerminationCode toe) {
85 RequestMeta *m = *connection_cls;
91 sd_journal_close(m->journal);
100 static int open_journal(RequestMeta *m) {
106 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
110 static int respond_oom(struct MHD_Connection *connection) {
111 struct MHD_Response *response;
112 const char m[] = "Out of memory.\n";
117 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
121 MHD_add_response_header(response, "Content-Type", "text/plain");
122 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
123 MHD_destroy_response(response);
128 static int respond_error(
129 struct MHD_Connection *connection,
131 const char *format, ...) {
133 struct MHD_Response *response;
141 va_start(ap, format);
142 r = vasprintf(&m, format, ap);
146 return respond_oom(connection);
148 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
151 return respond_oom(connection);
154 MHD_add_response_header(response, "Content-Type", "text/plain");
155 r = MHD_queue_response(connection, code, response);
156 MHD_destroy_response(response);
161 static ssize_t request_reader_entries(
167 RequestMeta *m = cls;
174 assert(pos >= m->delta);
178 while (pos >= m->size) {
181 /* End of this entry, so let's serialize the next
184 if (m->n_entries_set &&
186 return MHD_CONTENT_READER_END_OF_STREAM;
189 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
190 else if (m->n_skip > 0)
191 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
193 r = sd_journal_next(m->journal);
196 log_error("Failed to advance journal pointer: %s", strerror(-r));
197 return MHD_CONTENT_READER_END_WITH_ERROR;
201 r = sd_journal_wait(m->journal, (uint64_t) -1);
203 log_error("Couldn't wait for journal event: %s", strerror(-r));
204 return MHD_CONTENT_READER_END_WITH_ERROR;
210 return MHD_CONTENT_READER_END_OF_STREAM;
216 r = sd_journal_test_cursor(m->journal, m->cursor);
218 log_error("Failed to test cursor: %s", strerror(-r));
219 return MHD_CONTENT_READER_END_WITH_ERROR;
223 return MHD_CONTENT_READER_END_OF_STREAM;
229 if (m->n_entries_set)
239 log_error("Failed to create temporary file: %m");
240 return MHD_CONTENT_READER_END_WITH_ERROR;
244 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
246 log_error("Failed to serialize item: %s", strerror(-r));
247 return MHD_CONTENT_READER_END_WITH_ERROR;
251 if (sz == (off_t) -1) {
252 log_error("Failed to retrieve file position: %m");
253 return MHD_CONTENT_READER_END_WITH_ERROR;
256 m->size = (uint64_t) sz;
259 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
260 log_error("Failed to seek to position: %m");
261 return MHD_CONTENT_READER_END_WITH_ERROR;
269 k = fread(buf, 1, n, m->tmp);
271 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
272 return MHD_CONTENT_READER_END_WITH_ERROR;
278 static int request_parse_accept(
280 struct MHD_Connection *connection) {
287 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
291 if (streq(header, mime_types[OUTPUT_JSON]))
292 m->mode = OUTPUT_JSON;
293 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
294 m->mode = OUTPUT_JSON_SSE;
295 else if (streq(header, mime_types[OUTPUT_EXPORT]))
296 m->mode = OUTPUT_EXPORT;
298 m->mode = OUTPUT_SHORT;
303 static int request_parse_range(
305 struct MHD_Connection *connection) {
307 const char *range, *colon, *colon2;
313 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
317 if (!startswith(range, "entries="))
321 range += strspn(range, WHITESPACE);
323 colon = strchr(range, ':');
325 m->cursor = strdup(range);
329 colon2 = strchr(colon + 1, ':');
331 char _cleanup_free_ *t;
333 t = strndup(colon + 1, colon2 - colon - 1);
337 r = safe_atoi64(t, &m->n_skip);
342 p = (colon2 ? colon2 : colon) + 1;
344 r = safe_atou64(p, &m->n_entries);
348 if (m->n_entries <= 0)
351 m->n_entries_set = true;
354 m->cursor = strndup(range, colon - range);
360 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
361 if (isempty(m->cursor)) {
369 static int request_parse_arguments_iterator(
371 enum MHD_ValueKind kind,
375 RequestMeta *m = cls;
376 _cleanup_free_ char *p = NULL;
382 m->argument_parse_error = -EINVAL;
386 if (streq(key, "follow")) {
387 if (isempty(value)) {
392 r = parse_boolean(value);
394 m->argument_parse_error = r;
402 if (streq(key, "discrete")) {
403 if (isempty(value)) {
408 r = parse_boolean(value);
410 m->argument_parse_error = r;
418 if (streq(key, "boot")) {
422 r = parse_boolean(value);
424 m->argument_parse_error = r;
430 char match[9 + 32 + 1] = "_BOOT_ID=";
433 r = sd_id128_get_boot(&bid);
435 log_error("Failed to get boot ID: %s", strerror(-r));
439 sd_id128_to_string(bid, match + 9);
440 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
442 m->argument_parse_error = r;
450 p = strjoin(key, "=", strempty(value), NULL);
452 m->argument_parse_error = log_oom();
456 r = sd_journal_add_match(m->journal, p, 0);
458 m->argument_parse_error = r;
465 static int request_parse_arguments(
467 struct MHD_Connection *connection) {
472 m->argument_parse_error = 0;
473 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
475 return m->argument_parse_error;
478 static int request_handler_entries(
479 struct MHD_Connection *connection,
480 void **connection_cls) {
482 struct MHD_Response *response;
487 assert(connection_cls);
489 m = request_meta(connection_cls);
491 return respond_oom(connection);
495 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
497 if (request_parse_accept(m, connection) < 0)
498 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
500 if (request_parse_range(m, connection) < 0)
501 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
503 if (request_parse_arguments(m, connection) < 0)
504 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
508 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
511 m->n_entries_set = true;
515 r = sd_journal_seek_cursor(m->journal, m->cursor);
516 else if (m->n_skip >= 0)
517 r = sd_journal_seek_head(m->journal);
518 else if (m->n_skip < 0)
519 r = sd_journal_seek_tail(m->journal);
521 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
523 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
525 return respond_oom(connection);
527 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
529 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
530 MHD_destroy_response(response);
535 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
539 eq = memchr(d, '=', l);
543 j = l - (eq - d + 1);
545 if (m == OUTPUT_JSON) {
546 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
547 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
550 fwrite(eq+1, 1, j, f);
557 static ssize_t request_reader_fields(
563 RequestMeta *m = cls;
570 assert(pos >= m->delta);
574 while (pos >= m->size) {
579 /* End of this field, so let's serialize the next
582 if (m->n_fields_set &&
584 return MHD_CONTENT_READER_END_OF_STREAM;
586 r = sd_journal_enumerate_unique(m->journal, &d, &l);
588 log_error("Failed to advance field index: %s", strerror(-r));
589 return MHD_CONTENT_READER_END_WITH_ERROR;
591 return MHD_CONTENT_READER_END_OF_STREAM;
604 log_error("Failed to create temporary file: %m");
605 return MHD_CONTENT_READER_END_WITH_ERROR;
609 r = output_field(m->tmp, m->mode, d, l);
611 log_error("Failed to serialize item: %s", strerror(-r));
612 return MHD_CONTENT_READER_END_WITH_ERROR;
616 if (sz == (off_t) -1) {
617 log_error("Failed to retrieve file position: %m");
618 return MHD_CONTENT_READER_END_WITH_ERROR;
621 m->size = (uint64_t) sz;
624 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
625 log_error("Failed to seek to position: %m");
626 return MHD_CONTENT_READER_END_WITH_ERROR;
634 k = fread(buf, 1, n, m->tmp);
636 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
637 return MHD_CONTENT_READER_END_WITH_ERROR;
643 static int request_handler_fields(
644 struct MHD_Connection *connection,
646 void *connection_cls) {
648 struct MHD_Response *response;
653 assert(connection_cls);
655 m = request_meta(connection_cls);
657 return respond_oom(connection);
661 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
663 if (request_parse_accept(m, connection) < 0)
664 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
666 r = sd_journal_query_unique(m->journal, field);
668 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
670 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
672 return respond_oom(connection);
674 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
676 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
677 MHD_destroy_response(response);
682 static int request_handler_redirect(
683 struct MHD_Connection *connection,
684 const char *target) {
687 struct MHD_Response *response;
693 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
694 return respond_oom(connection);
696 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
699 return respond_oom(connection);
702 MHD_add_response_header(response, "Content-Type", "text/html");
703 MHD_add_response_header(response, "Location", target);
705 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
706 MHD_destroy_response(response);
711 static int request_handler_file(
712 struct MHD_Connection *connection,
714 const char *mime_type) {
716 struct MHD_Response *response;
718 _cleanup_close_ int fd = -1;
725 fd = open(path, O_RDONLY|O_CLOEXEC);
727 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
729 if (fstat(fd, &st) < 0)
730 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
732 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
734 return respond_oom(connection);
738 MHD_add_response_header(response, "Content-Type", mime_type);
740 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
741 MHD_destroy_response(response);
746 static int request_handler_machine(
747 struct MHD_Connection *connection,
748 void **connection_cls) {
750 struct MHD_Response *response;
753 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
754 uint64_t cutoff_from, cutoff_to, usage;
757 const char *v = "bare";
761 m = request_meta(connection_cls);
763 return respond_oom(connection);
767 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
769 r = sd_id128_get_machine(&mid);
771 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
773 r = sd_id128_get_boot(&bid);
775 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
777 hostname = gethostname_malloc();
779 return respond_oom(connection);
781 r = sd_journal_get_usage(m->journal, &usage);
783 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
785 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
787 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
789 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
791 detect_virtualization(&v);
794 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
795 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
796 "\"hostname\" : \"%s\","
797 "\"os_pretty_name\" : \"%s\","
798 "\"virtualization\" : \"%s\","
799 "\"usage\" : \"%llu\","
800 "\"cutoff_from_realtime\" : \"%llu\","
801 "\"cutoff_to_realtime\" : \"%llu\" }\n",
802 SD_ID128_FORMAT_VAL(mid),
803 SD_ID128_FORMAT_VAL(bid),
804 hostname_cleanup(hostname),
805 os_name ? os_name : "Linux",
807 (unsigned long long) usage,
808 (unsigned long long) cutoff_from,
809 (unsigned long long) cutoff_to);
812 return respond_oom(connection);
814 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
817 return respond_oom(connection);
820 MHD_add_response_header(response, "Content-Type", "application/json");
821 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
822 MHD_destroy_response(response);
827 static int request_handler(
829 struct MHD_Connection *connection,
833 const char *upload_data,
834 size_t *upload_data_size,
835 void **connection_cls) {
841 if (!streq(method, "GET"))
845 return request_handler_redirect(connection, "/browse");
847 if (streq(url, "/entries"))
848 return request_handler_entries(connection, connection_cls);
850 if (startswith(url, "/fields/"))
851 return request_handler_fields(connection, url + 8, connection_cls);
853 if (streq(url, "/browse"))
854 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
856 if (streq(url, "/machine"))
857 return request_handler_machine(connection, connection_cls);
859 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
862 int main(int argc, char *argv[]) {
863 struct MHD_Daemon *d = NULL;
864 int r = EXIT_FAILURE, n;
867 log_error("This program does not take arguments.");
871 log_set_target(LOG_TARGET_AUTO);
872 log_parse_environment();
875 n = sd_listen_fds(1);
877 log_error("Failed to determine passed sockets: %s", strerror(-n));
880 log_error("Can't listen on more than one socket.");
883 d = MHD_start_daemon(
884 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
887 request_handler, NULL,
888 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
889 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
892 d = MHD_start_daemon(
893 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
896 request_handler, NULL,
897 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
902 log_error("Failed to start daemon!");