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 if (streq(key, "boot")) {
423 r = parse_boolean(value);
425 m->argument_parse_error = r;
431 char match[9 + 32 + 1] = "_BOOT_ID=";
434 r = sd_id128_get_boot(&bid);
436 log_error("Failed to get boot ID: %s", strerror(-r));
440 sd_id128_to_string(bid, match + 9);
441 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
443 m->argument_parse_error = r;
451 p = strjoin(key, "=", strempty(value), NULL);
453 m->argument_parse_error = log_oom();
457 r = sd_journal_add_match(m->journal, p, 0);
459 m->argument_parse_error = r;
466 static int request_parse_arguments(
468 struct MHD_Connection *connection) {
473 m->argument_parse_error = 0;
474 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
476 return m->argument_parse_error;
479 static int request_handler_entries(
480 struct MHD_Connection *connection,
481 void **connection_cls) {
483 struct MHD_Response *response;
488 assert(connection_cls);
490 m = request_meta(connection_cls);
492 return respond_oom(connection);
496 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
498 if (request_parse_accept(m, connection) < 0)
499 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
501 if (request_parse_range(m, connection) < 0)
502 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
504 if (request_parse_arguments(m, connection) < 0)
505 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
509 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
512 m->n_entries_set = true;
516 r = sd_journal_seek_cursor(m->journal, m->cursor);
517 else if (m->n_skip >= 0)
518 r = sd_journal_seek_head(m->journal);
519 else if (m->n_skip < 0)
520 r = sd_journal_seek_tail(m->journal);
522 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
524 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
526 return respond_oom(connection);
528 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
530 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
531 MHD_destroy_response(response);
536 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
540 eq = memchr(d, '=', l);
544 j = l - (eq - d + 1);
546 if (m == OUTPUT_JSON) {
547 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
548 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
551 fwrite(eq+1, 1, j, f);
558 static ssize_t request_reader_fields(
564 RequestMeta *m = cls;
571 assert(pos >= m->delta);
575 while (pos >= m->size) {
580 /* End of this field, so let's serialize the next
583 if (m->n_fields_set &&
585 return MHD_CONTENT_READER_END_OF_STREAM;
587 r = sd_journal_enumerate_unique(m->journal, &d, &l);
589 log_error("Failed to advance field index: %s", strerror(-r));
590 return MHD_CONTENT_READER_END_WITH_ERROR;
592 return MHD_CONTENT_READER_END_OF_STREAM;
605 log_error("Failed to create temporary file: %m");
606 return MHD_CONTENT_READER_END_WITH_ERROR;
610 r = output_field(m->tmp, m->mode, d, l);
612 log_error("Failed to serialize item: %s", strerror(-r));
613 return MHD_CONTENT_READER_END_WITH_ERROR;
617 if (sz == (off_t) -1) {
618 log_error("Failed to retrieve file position: %m");
619 return MHD_CONTENT_READER_END_WITH_ERROR;
622 m->size = (uint64_t) sz;
625 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
626 log_error("Failed to seek to position: %m");
627 return MHD_CONTENT_READER_END_WITH_ERROR;
635 k = fread(buf, 1, n, m->tmp);
637 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
638 return MHD_CONTENT_READER_END_WITH_ERROR;
644 static int request_handler_fields(
645 struct MHD_Connection *connection,
647 void *connection_cls) {
649 struct MHD_Response *response;
654 assert(connection_cls);
656 m = request_meta(connection_cls);
658 return respond_oom(connection);
662 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
664 if (request_parse_accept(m, connection) < 0)
665 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
667 r = sd_journal_query_unique(m->journal, field);
669 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
671 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
673 return respond_oom(connection);
675 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
677 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
678 MHD_destroy_response(response);
683 static int request_handler_redirect(
684 struct MHD_Connection *connection,
685 const char *target) {
688 struct MHD_Response *response;
694 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
695 return respond_oom(connection);
697 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
700 return respond_oom(connection);
703 MHD_add_response_header(response, "Content-Type", "text/html");
704 MHD_add_response_header(response, "Location", target);
706 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
707 MHD_destroy_response(response);
712 static int request_handler_file(
713 struct MHD_Connection *connection,
715 const char *mime_type) {
717 struct MHD_Response *response;
719 _cleanup_close_ int fd = -1;
726 fd = open(path, O_RDONLY|O_CLOEXEC);
728 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
730 if (fstat(fd, &st) < 0)
731 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
733 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
735 return respond_oom(connection);
739 MHD_add_response_header(response, "Content-Type", mime_type);
741 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
742 MHD_destroy_response(response);
747 static int request_handler_machine(
748 struct MHD_Connection *connection,
749 void **connection_cls) {
751 struct MHD_Response *response;
754 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
755 uint64_t cutoff_from, cutoff_to, usage;
758 const char *v = "bare";
762 m = request_meta(connection_cls);
764 return respond_oom(connection);
768 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
770 r = sd_id128_get_machine(&mid);
772 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
774 r = sd_id128_get_boot(&bid);
776 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
778 hostname = gethostname_malloc();
780 return respond_oom(connection);
782 r = sd_journal_get_usage(m->journal, &usage);
784 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
786 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
788 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
790 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
792 detect_virtualization(&v);
795 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
796 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
797 "\"hostname\" : \"%s\","
798 "\"os_pretty_name\" : \"%s\","
799 "\"virtualization\" : \"%s\","
800 "\"usage\" : \"%llu\","
801 "\"cutoff_from_realtime\" : \"%llu\","
802 "\"cutoff_to_realtime\" : \"%llu\" }\n",
803 SD_ID128_FORMAT_VAL(mid),
804 SD_ID128_FORMAT_VAL(bid),
805 hostname_cleanup(hostname),
806 os_name ? os_name : "Linux",
808 (unsigned long long) usage,
809 (unsigned long long) cutoff_from,
810 (unsigned long long) cutoff_to);
813 return respond_oom(connection);
815 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
818 return respond_oom(connection);
821 MHD_add_response_header(response, "Content-Type", "application/json");
822 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
823 MHD_destroy_response(response);
828 static int request_handler(
830 struct MHD_Connection *connection,
834 const char *upload_data,
835 size_t *upload_data_size,
836 void **connection_cls) {
842 if (!streq(method, "GET"))
846 return request_handler_redirect(connection, "/browse");
848 if (streq(url, "/entries"))
849 return request_handler_entries(connection, connection_cls);
851 if (startswith(url, "/fields/"))
852 return request_handler_fields(connection, url + 8, connection_cls);
854 if (streq(url, "/browse"))
855 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
857 if (streq(url, "/machine"))
858 return request_handler_machine(connection, connection_cls);
860 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
863 int main(int argc, char *argv[]) {
864 struct MHD_Daemon *d = NULL;
865 int r = EXIT_FAILURE, n;
868 log_error("This program does not take arguments.");
872 log_set_target(LOG_TARGET_AUTO);
873 log_parse_environment();
876 n = sd_listen_fds(1);
878 log_error("Failed to determine passed sockets: %s", strerror(-n));
881 log_error("Can't listen on more than one socket.");
884 d = MHD_start_daemon(
885 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
888 request_handler, NULL,
889 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
890 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
893 d = MHD_start_daemon(
894 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
897 request_handler, NULL,
898 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
903 log_error("Failed to start daemon!");