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;
54 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
55 [OUTPUT_SHORT] = "text/plain",
56 [OUTPUT_JSON] = "application/json",
57 [OUTPUT_EXPORT] = "application/vnd.fdo.journal"
60 static RequestMeta *request_meta(void **connection_cls) {
64 return *connection_cls;
66 m = new0(RequestMeta, 1);
74 static void request_meta_free(
76 struct MHD_Connection *connection,
77 void **connection_cls,
78 enum MHD_RequestTerminationCode toe) {
80 RequestMeta *m = *connection_cls;
86 sd_journal_close(m->journal);
95 static int open_journal(RequestMeta *m) {
101 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
105 static int respond_oom(struct MHD_Connection *connection) {
106 struct MHD_Response *response;
107 const char m[] = "Out of memory.\n";
112 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
116 MHD_add_response_header(response, "Content-Type", "text/plain");
117 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
118 MHD_destroy_response(response);
123 static int respond_error(
124 struct MHD_Connection *connection,
126 const char *format, ...) {
128 struct MHD_Response *response;
136 va_start(ap, format);
137 r = vasprintf(&m, format, ap);
141 return respond_oom(connection);
143 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
146 return respond_oom(connection);
149 MHD_add_response_header(response, "Content-Type", "text/plain");
150 r = MHD_queue_response(connection, code, response);
151 MHD_destroy_response(response);
156 static ssize_t request_reader_entries(
162 RequestMeta *m = cls;
169 assert(pos >= m->delta);
173 while (pos >= m->size) {
176 /* End of this entry, so let's serialize the next
179 if (m->n_entries_set &&
181 return MHD_CONTENT_READER_END_OF_STREAM;
184 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip);
186 /* We couldn't seek this far backwards? Then
187 * let's try to look forward... */
189 r = sd_journal_next(m->journal);
191 } else if (m->n_skip > 0)
192 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
194 r = sd_journal_next(m->journal);
197 log_error("Failed to advance journal pointer: %s", strerror(-r));
198 return MHD_CONTENT_READER_END_WITH_ERROR;
202 r = sd_journal_wait(m->journal, (uint64_t) -1);
204 log_error("Couldn't wait for journal event: %s", strerror(-r));
205 return MHD_CONTENT_READER_END_WITH_ERROR;
211 return MHD_CONTENT_READER_END_OF_STREAM;
217 if (m->n_entries_set)
227 log_error("Failed to create temporary file: %m");
228 return MHD_CONTENT_READER_END_WITH_ERROR;;
232 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
234 log_error("Failed to serialize item: %s", strerror(-r));
235 return MHD_CONTENT_READER_END_WITH_ERROR;
239 if (sz == (off_t) -1) {
240 log_error("Failed to retrieve file position: %m");
241 return MHD_CONTENT_READER_END_WITH_ERROR;
244 m->size = (uint64_t) sz;
247 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
248 log_error("Failed to seek to position: %m");
249 return MHD_CONTENT_READER_END_WITH_ERROR;
257 k = fread(buf, 1, n, m->tmp);
259 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
260 return MHD_CONTENT_READER_END_WITH_ERROR;
266 static int request_parse_accept(
268 struct MHD_Connection *connection) {
275 accept = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
279 if (streq(accept, mime_types[OUTPUT_JSON]))
280 m->mode = OUTPUT_JSON;
281 else if (streq(accept, mime_types[OUTPUT_EXPORT]))
282 m->mode = OUTPUT_EXPORT;
284 m->mode = OUTPUT_SHORT;
289 static int request_parse_range(
291 struct MHD_Connection *connection) {
293 const char *range, *colon, *colon2;
299 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
303 if (!startswith(range, "entries="))
307 range += strspn(range, WHITESPACE);
309 colon = strchr(range, ':');
311 m->cursor = strdup(range);
315 colon2 = strchr(colon + 1, ':');
319 t = strndup(colon + 1, colon2 - colon - 1);
323 r = safe_atoi64(t, &m->n_skip);
329 p = (colon2 ? colon2 : colon) + 1;
331 r = safe_atou64(p, &m->n_entries);
335 if (m->n_entries <= 0)
338 m->n_entries_set = true;
341 m->cursor = strndup(range, colon - range);
347 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
348 if (isempty(m->cursor)) {
356 static int request_parse_arguments_iterator(
358 enum MHD_ValueKind kind,
362 RequestMeta *m = cls;
363 _cleanup_free_ char *p = NULL;
369 m->argument_parse_error = -EINVAL;
373 if (streq(key, "follow")) {
374 if (isempty(value)) {
379 r = parse_boolean(value);
381 m->argument_parse_error = r;
389 p = strjoin(key, "=", strempty(value), NULL);
391 m->argument_parse_error = log_oom();
395 r = sd_journal_add_match(m->journal, p, 0);
397 m->argument_parse_error = r;
404 static int request_parse_arguments(
406 struct MHD_Connection *connection) {
411 m->argument_parse_error = 0;
412 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
414 return m->argument_parse_error;
417 static int request_handler_entries(
418 struct MHD_Connection *connection,
419 void **connection_cls) {
421 struct MHD_Response *response;
426 assert(connection_cls);
428 m = request_meta(connection_cls);
430 return respond_oom(connection);
434 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
436 if (request_parse_accept(m, connection) < 0)
437 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
439 if (request_parse_range(m, connection) < 0)
440 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
442 if (request_parse_arguments(m, connection) < 0)
443 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
445 /* log_info("cursor = %s", m->cursor); */
446 /* log_info("skip = %lli", m->n_skip); */
447 /* if (!m->n_entries_set) */
448 /* log_info("n_entries not set!"); */
450 /* log_info("n_entries = %llu", m->n_entries); */
453 r = sd_journal_seek_cursor(m->journal, m->cursor);
454 else if (m->n_skip >= 0)
455 r = sd_journal_seek_head(m->journal);
456 else if (m->n_skip < 0)
457 r = sd_journal_seek_tail(m->journal);
459 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
461 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
463 return respond_oom(connection);
465 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
467 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
468 MHD_destroy_response(response);
473 static int request_handler_redirect(
474 struct MHD_Connection *connection,
475 const char *target) {
478 struct MHD_Response *response;
484 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
485 return respond_oom(connection);
487 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
490 return respond_oom(connection);
493 MHD_add_response_header(response, "Content-Type", "text/html");
494 MHD_add_response_header(response, "Location", target);
496 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
497 MHD_destroy_response(response);
502 static int request_handler_file(
503 struct MHD_Connection *connection,
505 const char *mime_type) {
507 struct MHD_Response *response;
509 _cleanup_close_ int fd = -1;
516 fd = open(path, O_RDONLY|O_CLOEXEC);
518 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
520 if (fstat(fd, &st) < 0)
521 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
523 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
525 return respond_oom(connection);
529 MHD_add_response_header(response, "Content-Type", mime_type);
531 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
532 MHD_destroy_response(response);
537 static int request_handler_machine(
538 struct MHD_Connection *connection,
539 void **connection_cls) {
541 struct MHD_Response *response;
544 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
545 uint64_t cutoff_from, cutoff_to, usage;
548 const char *v = "bare";
552 m = request_meta(connection_cls);
554 return respond_oom(connection);
558 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
560 r = sd_id128_get_machine(&mid);
562 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
564 r = sd_id128_get_boot(&bid);
566 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
568 hostname = gethostname_malloc();
570 return respond_oom(connection);
572 r = sd_journal_get_usage(m->journal, &usage);
574 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
576 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
578 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
580 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
582 detect_virtualization(&v);
585 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
586 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
587 "\"hostname\" : \"%s\","
588 "\"os_pretty_name\" : \"%s\","
589 "\"virtualization\" : \"%s\","
590 "\"usage\" : \"%llu\","
591 "\"cutoff_from_realtime\" : \"%llu\","
592 "\"cutoff_to_realtime\" : \"%llu\" }\n",
593 SD_ID128_FORMAT_VAL(mid),
594 SD_ID128_FORMAT_VAL(bid),
595 hostname_cleanup(hostname),
596 os_name ? os_name : "Linux",
598 (unsigned long long) usage,
599 (unsigned long long) cutoff_from,
600 (unsigned long long) cutoff_to);
603 return respond_oom(connection);
605 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
608 return respond_oom(connection);
611 MHD_add_response_header(response, "Content-Type", "application/json");
612 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
613 MHD_destroy_response(response);
618 static int request_handler(
620 struct MHD_Connection *connection,
624 const char *upload_data,
625 size_t *upload_data_size,
626 void **connection_cls) {
632 if (!streq(method, "GET"))
636 return request_handler_redirect(connection, "/browse");
638 if (streq(url, "/entries"))
639 return request_handler_entries(connection, connection_cls);
641 if (streq(url, "/browse"))
642 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
644 if (streq(url, "/machine"))
645 return request_handler_machine(connection, connection_cls);
647 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
650 int main(int argc, char *argv[]) {
651 struct MHD_Daemon *daemon = NULL;
652 int r = EXIT_FAILURE, n;
655 log_error("This program does not take arguments.");
659 log_set_target(LOG_TARGET_KMSG);
660 log_parse_environment();
663 n = sd_listen_fds(1);
665 log_error("Failed to determine passed sockets: %s", strerror(-n));
668 log_error("Can't listen on more than one socket.");
671 daemon = MHD_start_daemon(
672 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
675 request_handler, NULL,
676 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
677 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
680 daemon = MHD_start_daemon(
681 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
684 request_handler, NULL,
685 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
690 log_error("Failed to start daemon!");
700 MHD_stop_daemon(daemon);