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 + 1);
185 else if (m->n_skip > 0)
186 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
188 r = sd_journal_next(m->journal);
191 log_error("Failed to advance journal pointer: %s", strerror(-r));
192 return MHD_CONTENT_READER_END_WITH_ERROR;
196 r = sd_journal_wait(m->journal, (uint64_t) -1);
198 log_error("Couldn't wait for journal event: %s", strerror(-r));
199 return MHD_CONTENT_READER_END_WITH_ERROR;
205 return MHD_CONTENT_READER_END_OF_STREAM;
211 if (m->n_entries_set)
221 log_error("Failed to create temporary file: %m");
222 return MHD_CONTENT_READER_END_WITH_ERROR;;
226 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
228 log_error("Failed to serialize item: %s", strerror(-r));
229 return MHD_CONTENT_READER_END_WITH_ERROR;
233 if (sz == (off_t) -1) {
234 log_error("Failed to retrieve file position: %m");
235 return MHD_CONTENT_READER_END_WITH_ERROR;
238 m->size = (uint64_t) sz;
241 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
242 log_error("Failed to seek to position: %m");
243 return MHD_CONTENT_READER_END_WITH_ERROR;
251 k = fread(buf, 1, n, m->tmp);
253 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
254 return MHD_CONTENT_READER_END_WITH_ERROR;
260 static int request_parse_accept(
262 struct MHD_Connection *connection) {
269 accept = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
273 if (streq(accept, mime_types[OUTPUT_JSON]))
274 m->mode = OUTPUT_JSON;
275 else if (streq(accept, mime_types[OUTPUT_EXPORT]))
276 m->mode = OUTPUT_EXPORT;
278 m->mode = OUTPUT_SHORT;
283 static int request_parse_range(
285 struct MHD_Connection *connection) {
287 const char *range, *colon, *colon2;
293 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
297 if (!startswith(range, "entries="))
301 range += strspn(range, WHITESPACE);
303 colon = strchr(range, ':');
305 m->cursor = strdup(range);
309 colon2 = strchr(colon + 1, ':');
313 t = strndup(colon + 1, colon2 - colon - 1);
317 r = safe_atoi64(t, &m->n_skip);
323 p = (colon2 ? colon2 : colon) + 1;
325 r = safe_atou64(p, &m->n_entries);
329 if (m->n_entries <= 0)
332 m->n_entries_set = true;
335 m->cursor = strndup(range, colon - range);
341 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
342 if (isempty(m->cursor)) {
350 static int request_parse_arguments_iterator(
352 enum MHD_ValueKind kind,
356 RequestMeta *m = cls;
357 _cleanup_free_ char *p = NULL;
363 m->argument_parse_error = -EINVAL;
367 if (streq(key, "follow")) {
368 if (isempty(value)) {
373 r = parse_boolean(value);
375 m->argument_parse_error = r;
383 p = strjoin(key, "=", strempty(value), NULL);
385 m->argument_parse_error = log_oom();
389 r = sd_journal_add_match(m->journal, p, 0);
391 m->argument_parse_error = r;
398 static int request_parse_arguments(
400 struct MHD_Connection *connection) {
405 m->argument_parse_error = 0;
406 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
408 return m->argument_parse_error;
411 static int request_handler_entries(
412 struct MHD_Connection *connection,
413 void **connection_cls) {
415 struct MHD_Response *response;
420 assert(connection_cls);
422 m = request_meta(connection_cls);
424 return respond_oom(connection);
428 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
430 if (request_parse_accept(m, connection) < 0)
431 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
433 if (request_parse_range(m, connection) < 0)
434 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
436 if (request_parse_arguments(m, connection) < 0)
437 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
440 r = sd_journal_seek_cursor(m->journal, m->cursor);
441 else if (m->n_skip >= 0)
442 r = sd_journal_seek_head(m->journal);
443 else if (m->n_skip < 0)
444 r = sd_journal_seek_tail(m->journal);
446 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
448 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
450 return respond_oom(connection);
452 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
454 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
455 MHD_destroy_response(response);
460 static int request_handler_redirect(
461 struct MHD_Connection *connection,
462 const char *target) {
465 struct MHD_Response *response;
471 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
472 return respond_oom(connection);
474 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
477 return respond_oom(connection);
480 MHD_add_response_header(response, "Content-Type", "text/html");
481 MHD_add_response_header(response, "Location", target);
483 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
484 MHD_destroy_response(response);
489 static int request_handler_file(
490 struct MHD_Connection *connection,
492 const char *mime_type) {
494 struct MHD_Response *response;
496 _cleanup_close_ int fd = -1;
503 fd = open(path, O_RDONLY|O_CLOEXEC);
505 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
507 if (fstat(fd, &st) < 0)
508 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
510 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
512 return respond_oom(connection);
516 MHD_add_response_header(response, "Content-Type", mime_type);
518 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
519 MHD_destroy_response(response);
524 static int request_handler_machine(
525 struct MHD_Connection *connection,
526 void **connection_cls) {
528 struct MHD_Response *response;
531 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
532 uint64_t cutoff_from, cutoff_to, usage;
535 const char *v = "bare";
539 m = request_meta(connection_cls);
541 return respond_oom(connection);
545 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
547 r = sd_id128_get_machine(&mid);
549 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
551 r = sd_id128_get_boot(&bid);
553 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
555 hostname = gethostname_malloc();
557 return respond_oom(connection);
559 r = sd_journal_get_usage(m->journal, &usage);
561 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
563 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
565 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
567 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
569 detect_virtualization(&v);
572 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
573 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
574 "\"hostname\" : \"%s\","
575 "\"os_pretty_name\" : \"%s\","
576 "\"virtualization\" : \"%s\","
577 "\"usage\" : \"%llu\","
578 "\"cutoff_from_realtime\" : \"%llu\","
579 "\"cutoff_to_realtime\" : \"%llu\" }\n",
580 SD_ID128_FORMAT_VAL(mid),
581 SD_ID128_FORMAT_VAL(bid),
582 hostname_cleanup(hostname),
583 os_name ? os_name : "Linux",
585 (unsigned long long) usage,
586 (unsigned long long) cutoff_from,
587 (unsigned long long) cutoff_to);
590 return respond_oom(connection);
592 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
595 return respond_oom(connection);
598 MHD_add_response_header(response, "Content-Type", "application/json");
599 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
600 MHD_destroy_response(response);
605 static int request_handler(
607 struct MHD_Connection *connection,
611 const char *upload_data,
612 size_t *upload_data_size,
613 void **connection_cls) {
619 if (!streq(method, "GET"))
623 return request_handler_redirect(connection, "/browse");
625 if (streq(url, "/entries"))
626 return request_handler_entries(connection, connection_cls);
628 if (streq(url, "/browse"))
629 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
631 if (streq(url, "/machine"))
632 return request_handler_machine(connection, connection_cls);
634 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
637 int main(int argc, char *argv[]) {
638 struct MHD_Daemon *daemon = NULL;
639 int r = EXIT_FAILURE, n;
642 log_error("This program does not take arguments.");
646 log_set_target(LOG_TARGET_AUTO);
647 log_parse_environment();
650 n = sd_listen_fds(1);
652 log_error("Failed to determine passed sockets: %s", strerror(-n));
655 log_error("Can't listen on more than one socket.");
658 daemon = MHD_start_daemon(
659 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
662 request_handler, NULL,
663 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
664 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
667 daemon = MHD_start_daemon(
668 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
671 request_handler, NULL,
672 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
677 log_error("Failed to start daemon!");
687 MHD_stop_daemon(daemon);