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_JSON_SSE] = "text/event-stream",
59 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
62 static RequestMeta *request_meta(void **connection_cls) {
66 return *connection_cls;
68 m = new0(RequestMeta, 1);
76 static void request_meta_free(
78 struct MHD_Connection *connection,
79 void **connection_cls,
80 enum MHD_RequestTerminationCode toe) {
82 RequestMeta *m = *connection_cls;
88 sd_journal_close(m->journal);
97 static int open_journal(RequestMeta *m) {
103 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
107 static int respond_oom(struct MHD_Connection *connection) {
108 struct MHD_Response *response;
109 const char m[] = "Out of memory.\n";
114 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
118 MHD_add_response_header(response, "Content-Type", "text/plain");
119 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
120 MHD_destroy_response(response);
125 static int respond_error(
126 struct MHD_Connection *connection,
128 const char *format, ...) {
130 struct MHD_Response *response;
138 va_start(ap, format);
139 r = vasprintf(&m, format, ap);
143 return respond_oom(connection);
145 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
148 return respond_oom(connection);
151 MHD_add_response_header(response, "Content-Type", "text/plain");
152 r = MHD_queue_response(connection, code, response);
153 MHD_destroy_response(response);
158 static ssize_t request_reader_entries(
164 RequestMeta *m = cls;
171 assert(pos >= m->delta);
175 while (pos >= m->size) {
178 /* End of this entry, so let's serialize the next
181 if (m->n_entries_set &&
183 return MHD_CONTENT_READER_END_OF_STREAM;
186 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
187 else if (m->n_skip > 0)
188 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
190 r = sd_journal_next(m->journal);
193 log_error("Failed to advance journal pointer: %s", strerror(-r));
194 return MHD_CONTENT_READER_END_WITH_ERROR;
198 r = sd_journal_wait(m->journal, (uint64_t) -1);
200 log_error("Couldn't wait for journal event: %s", strerror(-r));
201 return MHD_CONTENT_READER_END_WITH_ERROR;
207 return MHD_CONTENT_READER_END_OF_STREAM;
213 r = sd_journal_test_cursor(m->journal, m->cursor);
215 log_error("Failed to test cursor: %s", strerror(-r));
216 return MHD_CONTENT_READER_END_WITH_ERROR;
220 return MHD_CONTENT_READER_END_OF_STREAM;
226 if (m->n_entries_set)
236 log_error("Failed to create temporary file: %m");
237 return MHD_CONTENT_READER_END_WITH_ERROR;;
241 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
243 log_error("Failed to serialize item: %s", strerror(-r));
244 return MHD_CONTENT_READER_END_WITH_ERROR;
248 if (sz == (off_t) -1) {
249 log_error("Failed to retrieve file position: %m");
250 return MHD_CONTENT_READER_END_WITH_ERROR;
253 m->size = (uint64_t) sz;
256 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
257 log_error("Failed to seek to position: %m");
258 return MHD_CONTENT_READER_END_WITH_ERROR;
266 k = fread(buf, 1, n, m->tmp);
268 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
269 return MHD_CONTENT_READER_END_WITH_ERROR;
275 static int request_parse_accept(
277 struct MHD_Connection *connection) {
284 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
288 if (streq(header, mime_types[OUTPUT_JSON]))
289 m->mode = OUTPUT_JSON;
290 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
291 m->mode = OUTPUT_JSON_SSE;
292 else if (streq(header, mime_types[OUTPUT_EXPORT]))
293 m->mode = OUTPUT_EXPORT;
295 m->mode = OUTPUT_SHORT;
300 static int request_parse_range(
302 struct MHD_Connection *connection) {
304 const char *range, *colon, *colon2;
310 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
314 if (!startswith(range, "entries="))
318 range += strspn(range, WHITESPACE);
320 colon = strchr(range, ':');
322 m->cursor = strdup(range);
326 colon2 = strchr(colon + 1, ':');
330 t = strndup(colon + 1, colon2 - colon - 1);
334 r = safe_atoi64(t, &m->n_skip);
340 p = (colon2 ? colon2 : colon) + 1;
342 r = safe_atou64(p, &m->n_entries);
346 if (m->n_entries <= 0)
349 m->n_entries_set = true;
352 m->cursor = strndup(range, colon - range);
358 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
359 if (isempty(m->cursor)) {
367 static int request_parse_arguments_iterator(
369 enum MHD_ValueKind kind,
373 RequestMeta *m = cls;
374 _cleanup_free_ char *p = NULL;
380 m->argument_parse_error = -EINVAL;
384 if (streq(key, "follow")) {
385 if (isempty(value)) {
390 r = parse_boolean(value);
392 m->argument_parse_error = r;
400 if (streq(key, "discrete")) {
401 if (isempty(value)) {
406 r = parse_boolean(value);
408 m->argument_parse_error = r;
416 p = strjoin(key, "=", strempty(value), NULL);
418 m->argument_parse_error = log_oom();
422 r = sd_journal_add_match(m->journal, p, 0);
424 m->argument_parse_error = r;
431 static int request_parse_arguments(
433 struct MHD_Connection *connection) {
438 m->argument_parse_error = 0;
439 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
441 return m->argument_parse_error;
444 static int request_handler_entries(
445 struct MHD_Connection *connection,
446 void **connection_cls) {
448 struct MHD_Response *response;
453 assert(connection_cls);
455 m = request_meta(connection_cls);
457 return respond_oom(connection);
461 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
463 if (request_parse_accept(m, connection) < 0)
464 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
466 if (request_parse_range(m, connection) < 0)
467 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
469 if (request_parse_arguments(m, connection) < 0)
470 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
474 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
477 m->n_entries_set = true;
481 r = sd_journal_seek_cursor(m->journal, m->cursor);
482 else if (m->n_skip >= 0)
483 r = sd_journal_seek_head(m->journal);
484 else if (m->n_skip < 0)
485 r = sd_journal_seek_tail(m->journal);
487 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
489 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
491 return respond_oom(connection);
493 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
495 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
496 MHD_destroy_response(response);
501 static int request_handler_redirect(
502 struct MHD_Connection *connection,
503 const char *target) {
506 struct MHD_Response *response;
512 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
513 return respond_oom(connection);
515 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
518 return respond_oom(connection);
521 MHD_add_response_header(response, "Content-Type", "text/html");
522 MHD_add_response_header(response, "Location", target);
524 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
525 MHD_destroy_response(response);
530 static int request_handler_file(
531 struct MHD_Connection *connection,
533 const char *mime_type) {
535 struct MHD_Response *response;
537 _cleanup_close_ int fd = -1;
544 fd = open(path, O_RDONLY|O_CLOEXEC);
546 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
548 if (fstat(fd, &st) < 0)
549 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
551 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
553 return respond_oom(connection);
557 MHD_add_response_header(response, "Content-Type", mime_type);
559 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
560 MHD_destroy_response(response);
565 static int request_handler_machine(
566 struct MHD_Connection *connection,
567 void **connection_cls) {
569 struct MHD_Response *response;
572 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
573 uint64_t cutoff_from, cutoff_to, usage;
576 const char *v = "bare";
580 m = request_meta(connection_cls);
582 return respond_oom(connection);
586 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
588 r = sd_id128_get_machine(&mid);
590 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
592 r = sd_id128_get_boot(&bid);
594 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
596 hostname = gethostname_malloc();
598 return respond_oom(connection);
600 r = sd_journal_get_usage(m->journal, &usage);
602 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
604 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
606 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
608 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
610 detect_virtualization(&v);
613 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
614 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
615 "\"hostname\" : \"%s\","
616 "\"os_pretty_name\" : \"%s\","
617 "\"virtualization\" : \"%s\","
618 "\"usage\" : \"%llu\","
619 "\"cutoff_from_realtime\" : \"%llu\","
620 "\"cutoff_to_realtime\" : \"%llu\" }\n",
621 SD_ID128_FORMAT_VAL(mid),
622 SD_ID128_FORMAT_VAL(bid),
623 hostname_cleanup(hostname),
624 os_name ? os_name : "Linux",
626 (unsigned long long) usage,
627 (unsigned long long) cutoff_from,
628 (unsigned long long) cutoff_to);
631 return respond_oom(connection);
633 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
636 return respond_oom(connection);
639 MHD_add_response_header(response, "Content-Type", "application/json");
640 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
641 MHD_destroy_response(response);
646 static int request_handler(
648 struct MHD_Connection *connection,
652 const char *upload_data,
653 size_t *upload_data_size,
654 void **connection_cls) {
660 if (!streq(method, "GET"))
664 return request_handler_redirect(connection, "/browse");
666 if (streq(url, "/entries"))
667 return request_handler_entries(connection, connection_cls);
669 if (streq(url, "/browse"))
670 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
672 if (streq(url, "/machine"))
673 return request_handler_machine(connection, connection_cls);
675 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
678 int main(int argc, char *argv[]) {
679 struct MHD_Daemon *d = NULL;
680 int r = EXIT_FAILURE, n;
683 log_error("This program does not take arguments.");
687 log_set_target(LOG_TARGET_AUTO);
688 log_parse_environment();
691 n = sd_listen_fds(1);
693 log_error("Failed to determine passed sockets: %s", strerror(-n));
696 log_error("Can't listen on more than one socket.");
699 d = MHD_start_daemon(
700 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
703 request_handler, NULL,
704 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
705 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
708 d = MHD_start_daemon(
709 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
712 request_handler, NULL,
713 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
718 log_error("Failed to start daemon!");