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, ':');
333 t = strndup(colon + 1, colon2 - colon - 1);
337 r = safe_atoi64(t, &m->n_skip);
343 p = (colon2 ? colon2 : colon) + 1;
345 r = safe_atou64(p, &m->n_entries);
349 if (m->n_entries <= 0)
352 m->n_entries_set = true;
355 m->cursor = strndup(range, colon - range);
361 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
362 if (isempty(m->cursor)) {
370 static int request_parse_arguments_iterator(
372 enum MHD_ValueKind kind,
376 RequestMeta *m = cls;
377 _cleanup_free_ char *p = NULL;
383 m->argument_parse_error = -EINVAL;
387 if (streq(key, "follow")) {
388 if (isempty(value)) {
393 r = parse_boolean(value);
395 m->argument_parse_error = r;
403 if (streq(key, "discrete")) {
404 if (isempty(value)) {
409 r = parse_boolean(value);
411 m->argument_parse_error = r;
419 p = strjoin(key, "=", strempty(value), NULL);
421 m->argument_parse_error = log_oom();
425 r = sd_journal_add_match(m->journal, p, 0);
427 m->argument_parse_error = r;
434 static int request_parse_arguments(
436 struct MHD_Connection *connection) {
441 m->argument_parse_error = 0;
442 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
444 return m->argument_parse_error;
447 static int request_handler_entries(
448 struct MHD_Connection *connection,
449 void **connection_cls) {
451 struct MHD_Response *response;
456 assert(connection_cls);
458 m = request_meta(connection_cls);
460 return respond_oom(connection);
464 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
466 if (request_parse_accept(m, connection) < 0)
467 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
469 if (request_parse_range(m, connection) < 0)
470 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
472 if (request_parse_arguments(m, connection) < 0)
473 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
477 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
480 m->n_entries_set = true;
484 r = sd_journal_seek_cursor(m->journal, m->cursor);
485 else if (m->n_skip >= 0)
486 r = sd_journal_seek_head(m->journal);
487 else if (m->n_skip < 0)
488 r = sd_journal_seek_tail(m->journal);
490 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
492 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
494 return respond_oom(connection);
496 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
498 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
499 MHD_destroy_response(response);
504 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
508 eq = memchr(d, '=', l);
512 j = l - (eq - d + 1);
514 if (m == OUTPUT_JSON) {
515 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
516 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
519 fwrite(eq+1, 1, j, f);
526 static ssize_t request_reader_fields(
532 RequestMeta *m = cls;
539 assert(pos >= m->delta);
543 while (pos >= m->size) {
548 /* End of this field, so let's serialize the next
551 if (m->n_fields_set &&
553 return MHD_CONTENT_READER_END_OF_STREAM;
555 r = sd_journal_enumerate_unique(m->journal, &d, &l);
557 log_error("Failed to advance field index: %s", strerror(-r));
558 return MHD_CONTENT_READER_END_WITH_ERROR;
560 return MHD_CONTENT_READER_END_OF_STREAM;
573 log_error("Failed to create temporary file: %m");
574 return MHD_CONTENT_READER_END_WITH_ERROR;;
578 r = output_field(m->tmp, m->mode, d, l);
580 log_error("Failed to serialize item: %s", strerror(-r));
581 return MHD_CONTENT_READER_END_WITH_ERROR;
585 if (sz == (off_t) -1) {
586 log_error("Failed to retrieve file position: %m");
587 return MHD_CONTENT_READER_END_WITH_ERROR;
590 m->size = (uint64_t) sz;
593 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
594 log_error("Failed to seek to position: %m");
595 return MHD_CONTENT_READER_END_WITH_ERROR;
603 k = fread(buf, 1, n, m->tmp);
605 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
606 return MHD_CONTENT_READER_END_WITH_ERROR;
612 static int request_handler_fields(
613 struct MHD_Connection *connection,
615 void *connection_cls) {
617 struct MHD_Response *response;
622 assert(connection_cls);
624 m = request_meta(connection_cls);
626 return respond_oom(connection);
630 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
632 if (request_parse_accept(m, connection) < 0)
633 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
635 r = sd_journal_query_unique(m->journal, field);
637 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
639 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
641 return respond_oom(connection);
643 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
645 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
646 MHD_destroy_response(response);
651 static int request_handler_redirect(
652 struct MHD_Connection *connection,
653 const char *target) {
656 struct MHD_Response *response;
662 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
663 return respond_oom(connection);
665 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
668 return respond_oom(connection);
671 MHD_add_response_header(response, "Content-Type", "text/html");
672 MHD_add_response_header(response, "Location", target);
674 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
675 MHD_destroy_response(response);
680 static int request_handler_file(
681 struct MHD_Connection *connection,
683 const char *mime_type) {
685 struct MHD_Response *response;
687 _cleanup_close_ int fd = -1;
694 fd = open(path, O_RDONLY|O_CLOEXEC);
696 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
698 if (fstat(fd, &st) < 0)
699 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
701 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
703 return respond_oom(connection);
707 MHD_add_response_header(response, "Content-Type", mime_type);
709 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
710 MHD_destroy_response(response);
715 static int request_handler_machine(
716 struct MHD_Connection *connection,
717 void **connection_cls) {
719 struct MHD_Response *response;
722 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
723 uint64_t cutoff_from, cutoff_to, usage;
726 const char *v = "bare";
730 m = request_meta(connection_cls);
732 return respond_oom(connection);
736 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
738 r = sd_id128_get_machine(&mid);
740 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
742 r = sd_id128_get_boot(&bid);
744 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
746 hostname = gethostname_malloc();
748 return respond_oom(connection);
750 r = sd_journal_get_usage(m->journal, &usage);
752 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
754 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
756 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
758 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
760 detect_virtualization(&v);
763 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
764 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
765 "\"hostname\" : \"%s\","
766 "\"os_pretty_name\" : \"%s\","
767 "\"virtualization\" : \"%s\","
768 "\"usage\" : \"%llu\","
769 "\"cutoff_from_realtime\" : \"%llu\","
770 "\"cutoff_to_realtime\" : \"%llu\" }\n",
771 SD_ID128_FORMAT_VAL(mid),
772 SD_ID128_FORMAT_VAL(bid),
773 hostname_cleanup(hostname),
774 os_name ? os_name : "Linux",
776 (unsigned long long) usage,
777 (unsigned long long) cutoff_from,
778 (unsigned long long) cutoff_to);
781 return respond_oom(connection);
783 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
786 return respond_oom(connection);
789 MHD_add_response_header(response, "Content-Type", "application/json");
790 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
791 MHD_destroy_response(response);
796 static int request_handler(
798 struct MHD_Connection *connection,
802 const char *upload_data,
803 size_t *upload_data_size,
804 void **connection_cls) {
810 if (!streq(method, "GET"))
814 return request_handler_redirect(connection, "/browse");
816 if (streq(url, "/entries"))
817 return request_handler_entries(connection, connection_cls);
819 if (startswith(url, "/fields/"))
820 return request_handler_fields(connection, url + 8, connection_cls);
822 if (streq(url, "/browse"))
823 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
825 if (streq(url, "/machine"))
826 return request_handler_machine(connection, connection_cls);
828 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
831 int main(int argc, char *argv[]) {
832 struct MHD_Daemon *d = NULL;
833 int r = EXIT_FAILURE, n;
836 log_error("This program does not take arguments.");
840 log_set_target(LOG_TARGET_AUTO);
841 log_parse_environment();
844 n = sd_listen_fds(1);
846 log_error("Failed to determine passed sockets: %s", strerror(-n));
849 log_error("Can't listen on more than one socket.");
852 d = MHD_start_daemon(
853 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
856 request_handler, NULL,
857 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
858 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
861 d = MHD_start_daemon(
862 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
865 request_handler, NULL,
866 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
871 log_error("Failed to start daemon!");