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/>.
28 #include <microhttpd.h>
32 #include "sd-journal.h"
33 #include "sd-daemon.h"
34 #include "logs-show.h"
38 typedef struct RequestMeta {
51 int argument_parse_error;
60 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
61 [OUTPUT_SHORT] = "text/plain",
62 [OUTPUT_JSON] = "application/json",
63 [OUTPUT_JSON_SSE] = "text/event-stream",
64 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
67 static RequestMeta *request_meta(void **connection_cls) {
71 return *connection_cls;
73 m = new0(RequestMeta, 1);
81 static void request_meta_free(
83 struct MHD_Connection *connection,
84 void **connection_cls,
85 enum MHD_RequestTerminationCode toe) {
87 RequestMeta *m = *connection_cls;
93 sd_journal_close(m->journal);
102 static int open_journal(RequestMeta *m) {
108 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
112 static int respond_oom(struct MHD_Connection *connection) {
113 struct MHD_Response *response;
114 const char m[] = "Out of memory.\n";
119 response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
123 MHD_add_response_header(response, "Content-Type", "text/plain");
124 ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
125 MHD_destroy_response(response);
130 static int respond_error(
131 struct MHD_Connection *connection,
133 const char *format, ...) {
135 struct MHD_Response *response;
143 va_start(ap, format);
144 r = vasprintf(&m, format, ap);
148 return respond_oom(connection);
150 response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
153 return respond_oom(connection);
156 MHD_add_response_header(response, "Content-Type", "text/plain");
157 r = MHD_queue_response(connection, code, response);
158 MHD_destroy_response(response);
163 static ssize_t request_reader_entries(
169 RequestMeta *m = cls;
176 assert(pos >= m->delta);
180 while (pos >= m->size) {
183 /* End of this entry, so let's serialize the next
186 if (m->n_entries_set &&
188 return MHD_CONTENT_READER_END_OF_STREAM;
191 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
192 else if (m->n_skip > 0)
193 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
195 r = sd_journal_next(m->journal);
198 log_error("Failed to advance journal pointer: %s", strerror(-r));
199 return MHD_CONTENT_READER_END_WITH_ERROR;
203 r = sd_journal_wait(m->journal, (uint64_t) -1);
205 log_error("Couldn't wait for journal event: %s", strerror(-r));
206 return MHD_CONTENT_READER_END_WITH_ERROR;
212 return MHD_CONTENT_READER_END_OF_STREAM;
218 r = sd_journal_test_cursor(m->journal, m->cursor);
220 log_error("Failed to test cursor: %s", strerror(-r));
221 return MHD_CONTENT_READER_END_WITH_ERROR;
225 return MHD_CONTENT_READER_END_OF_STREAM;
231 if (m->n_entries_set)
241 log_error("Failed to create temporary file: %m");
242 return MHD_CONTENT_READER_END_WITH_ERROR;
246 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
248 log_error("Failed to serialize item: %s", strerror(-r));
249 return MHD_CONTENT_READER_END_WITH_ERROR;
253 if (sz == (off_t) -1) {
254 log_error("Failed to retrieve file position: %m");
255 return MHD_CONTENT_READER_END_WITH_ERROR;
258 m->size = (uint64_t) sz;
261 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
262 log_error("Failed to seek to position: %m");
263 return MHD_CONTENT_READER_END_WITH_ERROR;
271 k = fread(buf, 1, n, m->tmp);
273 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
274 return MHD_CONTENT_READER_END_WITH_ERROR;
280 static int request_parse_accept(
282 struct MHD_Connection *connection) {
289 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
293 if (streq(header, mime_types[OUTPUT_JSON]))
294 m->mode = OUTPUT_JSON;
295 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
296 m->mode = OUTPUT_JSON_SSE;
297 else if (streq(header, mime_types[OUTPUT_EXPORT]))
298 m->mode = OUTPUT_EXPORT;
300 m->mode = OUTPUT_SHORT;
305 static int request_parse_range(
307 struct MHD_Connection *connection) {
309 const char *range, *colon, *colon2;
315 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
319 if (!startswith(range, "entries="))
323 range += strspn(range, WHITESPACE);
325 colon = strchr(range, ':');
327 m->cursor = strdup(range);
331 colon2 = strchr(colon + 1, ':');
333 char _cleanup_free_ *t;
335 t = strndup(colon + 1, colon2 - colon - 1);
339 r = safe_atoi64(t, &m->n_skip);
344 p = (colon2 ? colon2 : colon) + 1;
346 r = safe_atou64(p, &m->n_entries);
350 if (m->n_entries <= 0)
353 m->n_entries_set = true;
356 m->cursor = strndup(range, colon - range);
362 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
363 if (isempty(m->cursor)) {
371 static int request_parse_arguments_iterator(
373 enum MHD_ValueKind kind,
377 RequestMeta *m = cls;
378 _cleanup_free_ char *p = NULL;
384 m->argument_parse_error = -EINVAL;
388 if (streq(key, "follow")) {
389 if (isempty(value)) {
394 r = parse_boolean(value);
396 m->argument_parse_error = r;
404 if (streq(key, "discrete")) {
405 if (isempty(value)) {
410 r = parse_boolean(value);
412 m->argument_parse_error = r;
420 if (streq(key, "boot")) {
424 r = parse_boolean(value);
426 m->argument_parse_error = r;
432 char match[9 + 32 + 1] = "_BOOT_ID=";
435 r = sd_id128_get_boot(&bid);
437 log_error("Failed to get boot ID: %s", strerror(-r));
441 sd_id128_to_string(bid, match + 9);
442 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
444 m->argument_parse_error = r;
452 p = strjoin(key, "=", strempty(value), NULL);
454 m->argument_parse_error = log_oom();
458 r = sd_journal_add_match(m->journal, p, 0);
460 m->argument_parse_error = r;
467 static int request_parse_arguments(
469 struct MHD_Connection *connection) {
474 m->argument_parse_error = 0;
475 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
477 return m->argument_parse_error;
480 static int request_handler_entries(
481 struct MHD_Connection *connection,
482 void **connection_cls) {
484 struct MHD_Response *response;
489 assert(connection_cls);
491 m = request_meta(connection_cls);
493 return respond_oom(connection);
497 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
499 if (request_parse_accept(m, connection) < 0)
500 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
502 if (request_parse_range(m, connection) < 0)
503 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
505 if (request_parse_arguments(m, connection) < 0)
506 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
510 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
513 m->n_entries_set = true;
517 r = sd_journal_seek_cursor(m->journal, m->cursor);
518 else if (m->n_skip >= 0)
519 r = sd_journal_seek_head(m->journal);
520 else if (m->n_skip < 0)
521 r = sd_journal_seek_tail(m->journal);
523 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
525 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
527 return respond_oom(connection);
529 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
531 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
532 MHD_destroy_response(response);
537 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
541 eq = memchr(d, '=', l);
545 j = l - (eq - d + 1);
547 if (m == OUTPUT_JSON) {
548 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
549 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
552 fwrite(eq+1, 1, j, f);
559 static ssize_t request_reader_fields(
565 RequestMeta *m = cls;
572 assert(pos >= m->delta);
576 while (pos >= m->size) {
581 /* End of this field, so let's serialize the next
584 if (m->n_fields_set &&
586 return MHD_CONTENT_READER_END_OF_STREAM;
588 r = sd_journal_enumerate_unique(m->journal, &d, &l);
590 log_error("Failed to advance field index: %s", strerror(-r));
591 return MHD_CONTENT_READER_END_WITH_ERROR;
593 return MHD_CONTENT_READER_END_OF_STREAM;
606 log_error("Failed to create temporary file: %m");
607 return MHD_CONTENT_READER_END_WITH_ERROR;
611 r = output_field(m->tmp, m->mode, d, l);
613 log_error("Failed to serialize item: %s", strerror(-r));
614 return MHD_CONTENT_READER_END_WITH_ERROR;
618 if (sz == (off_t) -1) {
619 log_error("Failed to retrieve file position: %m");
620 return MHD_CONTENT_READER_END_WITH_ERROR;
623 m->size = (uint64_t) sz;
626 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
627 log_error("Failed to seek to position: %m");
628 return MHD_CONTENT_READER_END_WITH_ERROR;
636 k = fread(buf, 1, n, m->tmp);
638 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
639 return MHD_CONTENT_READER_END_WITH_ERROR;
645 static int request_handler_fields(
646 struct MHD_Connection *connection,
648 void *connection_cls) {
650 struct MHD_Response *response;
655 assert(connection_cls);
657 m = request_meta(connection_cls);
659 return respond_oom(connection);
663 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
665 if (request_parse_accept(m, connection) < 0)
666 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
668 r = sd_journal_query_unique(m->journal, field);
670 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
672 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
674 return respond_oom(connection);
676 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
678 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
679 MHD_destroy_response(response);
684 static int request_handler_redirect(
685 struct MHD_Connection *connection,
686 const char *target) {
689 struct MHD_Response *response;
695 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
696 return respond_oom(connection);
698 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
701 return respond_oom(connection);
704 MHD_add_response_header(response, "Content-Type", "text/html");
705 MHD_add_response_header(response, "Location", target);
707 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
708 MHD_destroy_response(response);
713 static int request_handler_file(
714 struct MHD_Connection *connection,
716 const char *mime_type) {
718 struct MHD_Response *response;
720 _cleanup_close_ int fd = -1;
727 fd = open(path, O_RDONLY|O_CLOEXEC);
729 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
731 if (fstat(fd, &st) < 0)
732 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
734 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
736 return respond_oom(connection);
740 MHD_add_response_header(response, "Content-Type", mime_type);
742 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
743 MHD_destroy_response(response);
748 static int request_handler_machine(
749 struct MHD_Connection *connection,
750 void **connection_cls) {
752 struct MHD_Response *response;
755 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
756 uint64_t cutoff_from, cutoff_to, usage;
759 const char *v = "bare";
763 m = request_meta(connection_cls);
765 return respond_oom(connection);
769 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
771 r = sd_id128_get_machine(&mid);
773 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
775 r = sd_id128_get_boot(&bid);
777 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
779 hostname = gethostname_malloc();
781 return respond_oom(connection);
783 r = sd_journal_get_usage(m->journal, &usage);
785 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
787 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
789 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
791 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
793 detect_virtualization(&v);
796 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
797 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
798 "\"hostname\" : \"%s\","
799 "\"os_pretty_name\" : \"%s\","
800 "\"virtualization\" : \"%s\","
801 "\"usage\" : \"%llu\","
802 "\"cutoff_from_realtime\" : \"%llu\","
803 "\"cutoff_to_realtime\" : \"%llu\" }\n",
804 SD_ID128_FORMAT_VAL(mid),
805 SD_ID128_FORMAT_VAL(bid),
806 hostname_cleanup(hostname),
807 os_name ? os_name : "Linux",
809 (unsigned long long) usage,
810 (unsigned long long) cutoff_from,
811 (unsigned long long) cutoff_to);
814 return respond_oom(connection);
816 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
819 return respond_oom(connection);
822 MHD_add_response_header(response, "Content-Type", "application/json");
823 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
824 MHD_destroy_response(response);
829 static int request_handler(
831 struct MHD_Connection *connection,
835 const char *upload_data,
836 size_t *upload_data_size,
837 void **connection_cls) {
843 if (!streq(method, "GET"))
847 return request_handler_redirect(connection, "/browse");
849 if (streq(url, "/entries"))
850 return request_handler_entries(connection, connection_cls);
852 if (startswith(url, "/fields/"))
853 return request_handler_fields(connection, url + 8, connection_cls);
855 if (streq(url, "/browse"))
856 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
858 if (streq(url, "/machine"))
859 return request_handler_machine(connection, connection_cls);
861 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
864 static char *key_pem = NULL;
865 static char *cert_pem = NULL;
867 static int parse_argv(int argc, char *argv[]) {
876 static const struct option options[] = {
877 { "version", no_argument, NULL, ARG_VERSION },
878 { "key", required_argument, NULL, ARG_KEY },
879 { "cert", required_argument, NULL, ARG_CERT },
886 while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0)
889 puts(PACKAGE_STRING);
890 puts(SYSTEMD_FEATURES);
895 log_error("Key file specified twice");
898 r = read_full_file(optarg, &key_pem, NULL);
900 log_error("Failed to read key file: %s", strerror(-r));
908 log_error("Certificate file specified twice");
911 r = read_full_file(optarg, &cert_pem, NULL);
913 log_error("Failed to read certificate file: %s", strerror(-r));
923 log_error("Unknown option code %c", c);
928 log_error("This program does not take arguments.");
932 if (!!key_pem != !!cert_pem) {
933 log_error("Certificate and key files must be specified together");
940 int main(int argc, char *argv[]) {
941 struct MHD_Daemon *d = NULL;
944 log_set_target(LOG_TARGET_AUTO);
945 log_parse_environment();
948 r = parse_argv(argc, argv);
954 n = sd_listen_fds(1);
956 log_error("Failed to determine passed sockets: %s", strerror(-n));
959 log_error("Can't listen on more than one socket.");
962 struct MHD_OptionItem opts[] = {
963 { MHD_OPTION_NOTIFY_COMPLETED,
964 (intptr_t) request_meta_free, NULL },
965 { MHD_OPTION_END, 0, NULL },
966 { MHD_OPTION_END, 0, NULL },
967 { MHD_OPTION_END, 0, NULL },
968 { MHD_OPTION_END, 0, NULL }};
970 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
973 opts[opts_pos++] = (struct MHD_OptionItem)
974 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
977 opts[opts_pos++] = (struct MHD_OptionItem)
978 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
979 opts[opts_pos++] = (struct MHD_OptionItem)
980 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
981 flags |= MHD_USE_SSL;
984 d = MHD_start_daemon(flags, 19531,
986 request_handler, NULL,
987 MHD_OPTION_ARRAY, opts,
992 log_error("Failed to start daemon!");