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;
55 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
56 [OUTPUT_SHORT] = "text/plain",
57 [OUTPUT_JSON] = "application/json",
58 [OUTPUT_EXPORT] = "application/vnd.fdo.journal"
61 static RequestMeta *request_meta(void **connection_cls) {
65 return *connection_cls;
67 m = new0(RequestMeta, 1);
75 static void request_meta_free(
77 struct MHD_Connection *connection,
78 void **connection_cls,
79 enum MHD_RequestTerminationCode toe) {
81 RequestMeta *m = *connection_cls;
87 sd_journal_close(m->journal);
96 static int open_journal(RequestMeta *m) {
102 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
106 static int respond_oom(struct MHD_Connection *connection) {
107 struct MHD_Response *response;
108 const char m[] = "Out of memory.\n";
113 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
117 MHD_add_response_header(response, "Content-Type", "text/plain");
118 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
119 MHD_destroy_response(response);
124 static int respond_error(
125 struct MHD_Connection *connection,
127 const char *format, ...) {
129 struct MHD_Response *response;
137 va_start(ap, format);
138 r = vasprintf(&m, format, ap);
142 return respond_oom(connection);
144 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
147 return respond_oom(connection);
150 MHD_add_response_header(response, "Content-Type", "text/plain");
151 r = MHD_queue_response(connection, code, response);
152 MHD_destroy_response(response);
157 static ssize_t request_reader_entries(
163 RequestMeta *m = cls;
170 assert(pos >= m->delta);
174 while (pos >= m->size) {
177 /* End of this entry, so let's serialize the next
180 if (m->n_entries_set &&
182 return MHD_CONTENT_READER_END_OF_STREAM;
185 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
186 else if (m->n_skip > 0)
187 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
189 r = sd_journal_next(m->journal);
192 log_error("Failed to advance journal pointer: %s", strerror(-r));
193 return MHD_CONTENT_READER_END_WITH_ERROR;
197 r = sd_journal_wait(m->journal, (uint64_t) -1);
199 log_error("Couldn't wait for journal event: %s", strerror(-r));
200 return MHD_CONTENT_READER_END_WITH_ERROR;
206 return MHD_CONTENT_READER_END_OF_STREAM;
212 r = sd_journal_test_cursor(m->journal, m->cursor);
214 log_error("Failed to test cursor: %s", strerror(-r));
215 return MHD_CONTENT_READER_END_WITH_ERROR;
219 return MHD_CONTENT_READER_END_OF_STREAM;
225 if (m->n_entries_set)
235 log_error("Failed to create temporary file: %m");
236 return MHD_CONTENT_READER_END_WITH_ERROR;;
240 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
242 log_error("Failed to serialize item: %s", strerror(-r));
243 return MHD_CONTENT_READER_END_WITH_ERROR;
247 if (sz == (off_t) -1) {
248 log_error("Failed to retrieve file position: %m");
249 return MHD_CONTENT_READER_END_WITH_ERROR;
252 m->size = (uint64_t) sz;
255 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
256 log_error("Failed to seek to position: %m");
257 return MHD_CONTENT_READER_END_WITH_ERROR;
265 k = fread(buf, 1, n, m->tmp);
267 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
268 return MHD_CONTENT_READER_END_WITH_ERROR;
274 static int request_parse_accept(
276 struct MHD_Connection *connection) {
283 accept = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
287 if (streq(accept, mime_types[OUTPUT_JSON]))
288 m->mode = OUTPUT_JSON;
289 else if (streq(accept, mime_types[OUTPUT_EXPORT]))
290 m->mode = OUTPUT_EXPORT;
292 m->mode = OUTPUT_SHORT;
297 static int request_parse_range(
299 struct MHD_Connection *connection) {
301 const char *range, *colon, *colon2;
307 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
311 if (!startswith(range, "entries="))
315 range += strspn(range, WHITESPACE);
317 colon = strchr(range, ':');
319 m->cursor = strdup(range);
323 colon2 = strchr(colon + 1, ':');
327 t = strndup(colon + 1, colon2 - colon - 1);
331 r = safe_atoi64(t, &m->n_skip);
337 p = (colon2 ? colon2 : colon) + 1;
339 r = safe_atou64(p, &m->n_entries);
343 if (m->n_entries <= 0)
346 m->n_entries_set = true;
349 m->cursor = strndup(range, colon - range);
355 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
356 if (isempty(m->cursor)) {
364 static int request_parse_arguments_iterator(
366 enum MHD_ValueKind kind,
370 RequestMeta *m = cls;
371 _cleanup_free_ char *p = NULL;
377 m->argument_parse_error = -EINVAL;
381 if (streq(key, "follow")) {
382 if (isempty(value)) {
387 r = parse_boolean(value);
389 m->argument_parse_error = r;
397 if (streq(key, "discrete")) {
398 if (isempty(value)) {
403 r = parse_boolean(value);
405 m->argument_parse_error = r;
413 p = strjoin(key, "=", strempty(value), NULL);
415 m->argument_parse_error = log_oom();
419 r = sd_journal_add_match(m->journal, p, 0);
421 m->argument_parse_error = r;
428 static int request_parse_arguments(
430 struct MHD_Connection *connection) {
435 m->argument_parse_error = 0;
436 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
438 return m->argument_parse_error;
441 static int request_handler_entries(
442 struct MHD_Connection *connection,
443 void **connection_cls) {
445 struct MHD_Response *response;
450 assert(connection_cls);
452 m = request_meta(connection_cls);
454 return respond_oom(connection);
458 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
460 if (request_parse_accept(m, connection) < 0)
461 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
463 if (request_parse_range(m, connection) < 0)
464 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
466 if (request_parse_arguments(m, connection) < 0)
467 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
471 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
474 m->n_entries_set = true;
478 r = sd_journal_seek_cursor(m->journal, m->cursor);
479 else if (m->n_skip >= 0)
480 r = sd_journal_seek_head(m->journal);
481 else if (m->n_skip < 0)
482 r = sd_journal_seek_tail(m->journal);
484 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
486 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
488 return respond_oom(connection);
490 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
492 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
493 MHD_destroy_response(response);
498 static int request_handler_redirect(
499 struct MHD_Connection *connection,
500 const char *target) {
503 struct MHD_Response *response;
509 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
510 return respond_oom(connection);
512 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
515 return respond_oom(connection);
518 MHD_add_response_header(response, "Content-Type", "text/html");
519 MHD_add_response_header(response, "Location", target);
521 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
522 MHD_destroy_response(response);
527 static int request_handler_file(
528 struct MHD_Connection *connection,
530 const char *mime_type) {
532 struct MHD_Response *response;
534 _cleanup_close_ int fd = -1;
541 fd = open(path, O_RDONLY|O_CLOEXEC);
543 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
545 if (fstat(fd, &st) < 0)
546 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
548 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
550 return respond_oom(connection);
554 MHD_add_response_header(response, "Content-Type", mime_type);
556 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
557 MHD_destroy_response(response);
562 static int request_handler_machine(
563 struct MHD_Connection *connection,
564 void **connection_cls) {
566 struct MHD_Response *response;
569 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
570 uint64_t cutoff_from, cutoff_to, usage;
573 const char *v = "bare";
577 m = request_meta(connection_cls);
579 return respond_oom(connection);
583 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
585 r = sd_id128_get_machine(&mid);
587 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
589 r = sd_id128_get_boot(&bid);
591 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
593 hostname = gethostname_malloc();
595 return respond_oom(connection);
597 r = sd_journal_get_usage(m->journal, &usage);
599 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
601 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
603 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
605 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
607 detect_virtualization(&v);
610 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
611 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
612 "\"hostname\" : \"%s\","
613 "\"os_pretty_name\" : \"%s\","
614 "\"virtualization\" : \"%s\","
615 "\"usage\" : \"%llu\","
616 "\"cutoff_from_realtime\" : \"%llu\","
617 "\"cutoff_to_realtime\" : \"%llu\" }\n",
618 SD_ID128_FORMAT_VAL(mid),
619 SD_ID128_FORMAT_VAL(bid),
620 hostname_cleanup(hostname),
621 os_name ? os_name : "Linux",
623 (unsigned long long) usage,
624 (unsigned long long) cutoff_from,
625 (unsigned long long) cutoff_to);
628 return respond_oom(connection);
630 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
633 return respond_oom(connection);
636 MHD_add_response_header(response, "Content-Type", "application/json");
637 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
638 MHD_destroy_response(response);
643 static int request_handler(
645 struct MHD_Connection *connection,
649 const char *upload_data,
650 size_t *upload_data_size,
651 void **connection_cls) {
657 if (!streq(method, "GET"))
661 return request_handler_redirect(connection, "/browse");
663 if (streq(url, "/entries"))
664 return request_handler_entries(connection, connection_cls);
666 if (streq(url, "/browse"))
667 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
669 if (streq(url, "/machine"))
670 return request_handler_machine(connection, connection_cls);
672 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
675 int main(int argc, char *argv[]) {
676 struct MHD_Daemon *daemon = NULL;
677 int r = EXIT_FAILURE, n;
680 log_error("This program does not take arguments.");
684 log_set_target(LOG_TARGET_AUTO);
685 log_parse_environment();
688 n = sd_listen_fds(1);
690 log_error("Failed to determine passed sockets: %s", strerror(-n));
693 log_error("Can't listen on more than one socket.");
696 daemon = MHD_start_daemon(
697 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
700 request_handler, NULL,
701 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
702 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
705 daemon = MHD_start_daemon(
706 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
709 request_handler, NULL,
710 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
715 log_error("Failed to start daemon!");
725 MHD_stop_daemon(daemon);