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;
52 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
53 [OUTPUT_SHORT] = "text/plain",
54 [OUTPUT_JSON] = "application/json",
55 [OUTPUT_EXPORT] = "application/vnd.fdo.journal"
58 static RequestMeta *request_meta(void **connection_cls) {
62 return *connection_cls;
64 m = new0(RequestMeta, 1);
72 static void request_meta_free(
74 struct MHD_Connection *connection,
75 void **connection_cls,
76 enum MHD_RequestTerminationCode toe) {
78 RequestMeta *m = *connection_cls;
84 sd_journal_close(m->journal);
93 static int open_journal(RequestMeta *m) {
99 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
103 static int respond_oom(struct MHD_Connection *connection) {
104 struct MHD_Response *response;
105 const char m[] = "Out of memory.\n";
110 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
114 MHD_add_response_header(response, "Content-Type", "text/plain");
115 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
116 MHD_destroy_response(response);
121 static int respond_error(
122 struct MHD_Connection *connection,
124 const char *format, ...) {
126 struct MHD_Response *response;
134 va_start(ap, format);
135 r = vasprintf(&m, format, ap);
139 return respond_oom(connection);
141 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
144 return respond_oom(connection);
147 MHD_add_response_header(response, "Content-Type", "text/plain");
148 r = MHD_queue_response(connection, code, response);
149 MHD_destroy_response(response);
154 static ssize_t request_reader_entries(
160 RequestMeta *m = cls;
167 assert(pos >= m->delta);
171 while (pos >= m->size) {
174 /* End of this entry, so let's serialize the next
177 if (m->n_entries_set &&
179 return MHD_CONTENT_READER_END_OF_STREAM;
182 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip);
184 /* We couldn't seek this far backwards? Then
185 * let's try to look forward... */
187 r = sd_journal_next(m->journal);
189 } else if (m->n_skip > 0)
190 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
192 r = sd_journal_next(m->journal);
195 log_error("Failed to advance journal pointer: %s", strerror(-r));
196 return MHD_CONTENT_READER_END_WITH_ERROR;
198 return MHD_CONTENT_READER_END_OF_STREAM;
203 if (m->n_entries_set)
213 log_error("Failed to create temporary file: %m");
214 return MHD_CONTENT_READER_END_WITH_ERROR;;
218 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
220 log_error("Failed to serialize item: %s", strerror(-r));
221 return MHD_CONTENT_READER_END_WITH_ERROR;
225 if (sz == (off_t) -1) {
226 log_error("Failed to retrieve file position: %m");
227 return MHD_CONTENT_READER_END_WITH_ERROR;
230 m->size = (uint64_t) sz;
233 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
234 log_error("Failed to seek to position: %m");
235 return MHD_CONTENT_READER_END_WITH_ERROR;
243 k = fread(buf, 1, n, m->tmp);
245 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
246 return MHD_CONTENT_READER_END_WITH_ERROR;
252 static int request_parse_accept(
254 struct MHD_Connection *connection) {
261 accept = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
265 if (streq(accept, mime_types[OUTPUT_JSON]))
266 m->mode = OUTPUT_JSON;
267 else if (streq(accept, mime_types[OUTPUT_EXPORT]))
268 m->mode = OUTPUT_EXPORT;
270 m->mode = OUTPUT_SHORT;
275 static int request_parse_range(
277 struct MHD_Connection *connection) {
279 const char *range, *colon, *colon2;
285 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
289 if (!startswith(range, "entries="))
293 range += strspn(range, WHITESPACE);
295 colon = strchr(range, ':');
297 m->cursor = strdup(range);
301 colon2 = strchr(colon + 1, ':');
305 t = strndup(colon + 1, colon2 - colon - 1);
309 r = safe_atoi64(t, &m->n_skip);
315 p = (colon2 ? colon2 : colon) + 1;
317 r = safe_atou64(p, &m->n_entries);
321 if (m->n_entries <= 0)
324 m->n_entries_set = true;
327 m->cursor = strndup(range, colon - range);
333 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
334 if (isempty(m->cursor)) {
342 static int request_parse_arguments_iterator(
344 enum MHD_ValueKind kind,
348 RequestMeta *m = cls;
349 _cleanup_free_ char *p = NULL;
355 m->argument_parse_error = -EINVAL;
359 p = strjoin(key, "=", strempty(value), NULL);
361 m->argument_parse_error = log_oom();
365 r = sd_journal_add_match(m->journal, p, 0);
367 m->argument_parse_error = r;
374 static int request_parse_arguments(
376 struct MHD_Connection *connection) {
381 m->argument_parse_error = 0;
382 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
384 return m->argument_parse_error;
387 static int request_handler_entries(
388 struct MHD_Connection *connection,
389 void **connection_cls) {
391 struct MHD_Response *response;
396 assert(connection_cls);
398 m = request_meta(connection_cls);
400 return respond_oom(connection);
404 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
406 if (request_parse_accept(m, connection) < 0)
407 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
409 if (request_parse_range(m, connection) < 0)
410 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
412 if (request_parse_arguments(m, connection) < 0)
413 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
415 /* log_info("cursor = %s", m->cursor); */
416 /* log_info("skip = %lli", m->n_skip); */
417 /* if (!m->n_entries_set) */
418 /* log_info("n_entries not set!"); */
420 /* log_info("n_entries = %llu", m->n_entries); */
423 r = sd_journal_seek_cursor(m->journal, m->cursor);
424 else if (m->n_skip >= 0)
425 r = sd_journal_seek_head(m->journal);
426 else if (m->n_skip < 0)
427 r = sd_journal_seek_tail(m->journal);
429 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
431 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
433 return respond_oom(connection);
435 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
437 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
438 MHD_destroy_response(response);
443 static int request_handler_redirect(
444 struct MHD_Connection *connection,
445 const char *target) {
448 struct MHD_Response *response;
454 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
455 return respond_oom(connection);
457 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
460 return respond_oom(connection);
463 MHD_add_response_header(response, "Content-Type", "text/html");
464 MHD_add_response_header(response, "Location", target);
466 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
467 MHD_destroy_response(response);
472 static int request_handler_file(
473 struct MHD_Connection *connection,
475 const char *mime_type) {
477 struct MHD_Response *response;
479 _cleanup_close_ int fd = -1;
486 fd = open(path, O_RDONLY|O_CLOEXEC);
488 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
490 if (fstat(fd, &st) < 0)
491 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
493 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
495 return respond_oom(connection);
499 MHD_add_response_header(response, "Content-Type", mime_type);
501 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
502 MHD_destroy_response(response);
507 static int request_handler_machine(
508 struct MHD_Connection *connection,
509 void **connection_cls) {
511 struct MHD_Response *response;
514 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
515 uint64_t cutoff_from, cutoff_to, usage;
518 const char *v = "bare";
522 m = request_meta(connection_cls);
524 return respond_oom(connection);
528 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
530 r = sd_id128_get_machine(&mid);
532 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
534 r = sd_id128_get_boot(&bid);
536 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
538 hostname = gethostname_malloc();
540 return respond_oom(connection);
542 r = sd_journal_get_usage(m->journal, &usage);
544 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
546 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
548 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
550 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
552 detect_virtualization(&v);
555 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
556 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
557 "\"hostname\" : \"%s\","
558 "\"os_pretty_name\" : \"%s\","
559 "\"virtualization\" : \"%s\","
560 "\"usage\" : \"%llu\","
561 "\"cutoff_from_realtime\" : \"%llu\","
562 "\"cutoff_to_realtime\" : \"%llu\" }\n",
563 SD_ID128_FORMAT_VAL(mid),
564 SD_ID128_FORMAT_VAL(bid),
565 hostname_cleanup(hostname),
566 os_name ? os_name : "Linux",
568 (unsigned long long) usage,
569 (unsigned long long) cutoff_from,
570 (unsigned long long) cutoff_to);
573 return respond_oom(connection);
575 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
578 return respond_oom(connection);
581 MHD_add_response_header(response, "Content-Type", "application/json");
582 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
583 MHD_destroy_response(response);
588 static int request_handler(
590 struct MHD_Connection *connection,
594 const char *upload_data,
595 size_t *upload_data_size,
596 void **connection_cls) {
602 if (!streq(method, "GET"))
606 return request_handler_redirect(connection, "/browse");
608 if (streq(url, "/entries"))
609 return request_handler_entries(connection, connection_cls);
611 if (streq(url, "/browse"))
612 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
614 if (streq(url, "/machine"))
615 return request_handler_machine(connection, connection_cls);
617 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
620 int main(int argc, char *argv[]) {
621 struct MHD_Daemon *daemon = NULL;
622 int r = EXIT_FAILURE, n;
625 log_error("This program does not take arguments.");
629 log_set_target(LOG_TARGET_KMSG);
630 log_parse_environment();
633 n = sd_listen_fds(1);
635 log_error("Failed to determine passed sockets: %s", strerror(-n));
638 log_error("Can't listen on more than one socket.");
641 daemon = MHD_start_daemon(
642 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
645 request_handler, NULL,
646 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
647 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
650 daemon = MHD_start_daemon(
651 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
654 request_handler, NULL,
655 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
660 log_error("Failed to start daemon!");
670 MHD_stop_daemon(daemon);