chiark / gitweb /
timedated: use libsystemd-bus instead of libdbus for bus communication
[elogind.git] / src / journal / journal-gatewayd.c
index eea3530733b8a3bc193200df5d7999c7ed0e31c3..d4d4b7ef043d17401650b4a5b4759a1e71c78df9 100644 (file)
@@ -23,6 +23,7 @@
 #include <string.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <getopt.h>
 
 #include <microhttpd.h>
 
 #include "util.h"
 #include "sd-journal.h"
 #include "sd-daemon.h"
+#include "sd-bus.h"
+#include "bus-util.h"
 #include "logs-show.h"
-#include "virt.h"
+#include "microhttpd-util.h"
+#include "build.h"
+#include "fileio.h"
 
 typedef struct RequestMeta {
         sd_journal *journal;
@@ -50,6 +55,9 @@ typedef struct RequestMeta {
 
         bool follow;
         bool discrete;
+
+        uint64_t n_fields;
+        bool n_fields_set;
 } RequestMeta;
 
 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
@@ -100,11 +108,10 @@ static int open_journal(RequestMeta *m) {
         if (m->journal)
                 return 0;
 
-        return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
+        return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
 }
 
-
-static int respond_oom(struct MHD_Connection *connection) {
+static int respond_oom_internal(struct MHD_Connection *connection) {
         struct MHD_Response *response;
         const char m[] = "Out of memory.\n";
         int ret;
@@ -122,6 +129,8 @@ static int respond_oom(struct MHD_Connection *connection) {
         return ret;
 }
 
+#define respond_oom(connection) log_oom(), respond_oom_internal(connection)
+
 static int respond_error(
                 struct MHD_Connection *connection,
                 unsigned code,
@@ -234,11 +243,11 @@ static ssize_t request_reader_entries(
                         m->tmp = tmpfile();
                         if (!m->tmp) {
                                 log_error("Failed to create temporary file: %m");
-                                return MHD_CONTENT_READER_END_WITH_ERROR;;
+                                return MHD_CONTENT_READER_END_WITH_ERROR;
                         }
                 }
 
-                r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
+                r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
                 if (r < 0) {
                         log_error("Failed to serialize item: %s", strerror(-r));
                         return MHD_CONTENT_READER_END_WITH_ERROR;
@@ -325,14 +334,13 @@ static int request_parse_range(
 
                 colon2 = strchr(colon + 1, ':');
                 if (colon2) {
-                        char *t;
+                        _cleanup_free_ char *t;
 
                         t = strndup(colon + 1, colon2 - colon - 1);
                         if (!t)
                                 return -ENOMEM;
 
                         r = safe_atoi64(t, &m->n_skip);
-                        free(t);
                         if (r < 0)
                                 return r;
                 }
@@ -413,6 +421,38 @@ static int request_parse_arguments_iterator(
                 return MHD_YES;
         }
 
+        if (streq(key, "boot")) {
+                if (isempty(value))
+                        r = true;
+                else {
+                        r = parse_boolean(value);
+                        if (r < 0) {
+                                m->argument_parse_error = r;
+                                return MHD_NO;
+                        }
+                }
+
+                if (r) {
+                        char match[9 + 32 + 1] = "_BOOT_ID=";
+                        sd_id128_t bid;
+
+                        r = sd_id128_get_boot(&bid);
+                        if (r < 0) {
+                                log_error("Failed to get boot ID: %s", strerror(-r));
+                                return MHD_NO;
+                        }
+
+                        sd_id128_to_string(bid, match + 9);
+                        r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
+                        if (r < 0) {
+                                m->argument_parse_error = r;
+                                return MHD_NO;
+                        }
+                }
+
+                return MHD_YES;
+        }
+
         p = strjoin(key, "=", strempty(value), NULL);
         if (!p) {
                 m->argument_parse_error = log_oom();
@@ -443,18 +483,14 @@ static int request_parse_arguments(
 
 static int request_handler_entries(
                 struct MHD_Connection *connection,
-                void **connection_cls) {
+                void *connection_cls) {
 
         struct MHD_Response *response;
-        RequestMeta *m;
+        RequestMeta *m = connection_cls;
         int r;
 
         assert(connection);
-        assert(connection_cls);
-
-        m = request_meta(connection_cls);
-        if (!m)
-                return respond_oom(connection);
+        assert(m);
 
         r = open_journal(m);
         if (r < 0)
@@ -498,6 +534,149 @@ static int request_handler_entries(
         return r;
 }
 
+static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
+        const char *eq;
+        size_t j;
+
+        eq = memchr(d, '=', l);
+        if (!eq)
+                return -EINVAL;
+
+        j = l - (eq - d + 1);
+
+        if (m == OUTPUT_JSON) {
+                fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
+                json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
+                fputs(" }\n", f);
+        } else {
+                fwrite(eq+1, 1, j, f);
+                fputc('\n', f);
+        }
+
+        return 0;
+}
+
+static ssize_t request_reader_fields(
+                void *cls,
+                uint64_t pos,
+                char *buf,
+                size_t max) {
+
+        RequestMeta *m = cls;
+        int r;
+        size_t n, k;
+
+        assert(m);
+        assert(buf);
+        assert(max > 0);
+        assert(pos >= m->delta);
+
+        pos -= m->delta;
+
+        while (pos >= m->size) {
+                off_t sz;
+                const void *d;
+                size_t l;
+
+                /* End of this field, so let's serialize the next
+                 * one */
+
+                if (m->n_fields_set &&
+                    m->n_fields <= 0)
+                        return MHD_CONTENT_READER_END_OF_STREAM;
+
+                r = sd_journal_enumerate_unique(m->journal, &d, &l);
+                if (r < 0) {
+                        log_error("Failed to advance field index: %s", strerror(-r));
+                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                } else if (r == 0)
+                        return MHD_CONTENT_READER_END_OF_STREAM;
+
+                pos -= m->size;
+                m->delta += m->size;
+
+                if (m->n_fields_set)
+                        m->n_fields -= 1;
+
+                if (m->tmp)
+                        rewind(m->tmp);
+                else {
+                        m->tmp = tmpfile();
+                        if (!m->tmp) {
+                                log_error("Failed to create temporary file: %m");
+                                return MHD_CONTENT_READER_END_WITH_ERROR;
+                        }
+                }
+
+                r = output_field(m->tmp, m->mode, d, l);
+                if (r < 0) {
+                        log_error("Failed to serialize item: %s", strerror(-r));
+                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                }
+
+                sz = ftello(m->tmp);
+                if (sz == (off_t) -1) {
+                        log_error("Failed to retrieve file position: %m");
+                        return MHD_CONTENT_READER_END_WITH_ERROR;
+                }
+
+                m->size = (uint64_t) sz;
+        }
+
+        if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
+                log_error("Failed to seek to position: %m");
+                return MHD_CONTENT_READER_END_WITH_ERROR;
+        }
+
+        n = m->size - pos;
+        if (n > max)
+                n = max;
+
+        errno = 0;
+        k = fread(buf, 1, n, m->tmp);
+        if (k != n) {
+                log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
+                return MHD_CONTENT_READER_END_WITH_ERROR;
+        }
+
+        return (ssize_t) k;
+}
+
+static int request_handler_fields(
+                struct MHD_Connection *connection,
+                const char *field,
+                void *connection_cls) {
+
+        struct MHD_Response *response;
+        RequestMeta *m = connection_cls;
+        int r;
+
+        assert(connection);
+        assert(m);
+
+        r = open_journal(m);
+        if (r < 0)
+                return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+        if (request_parse_accept(m, connection) < 0)
+                return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
+
+        r = sd_journal_query_unique(m->journal, field);
+        if (r < 0)
+                return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
+
+        response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
+        if (!response)
+                return respond_oom(connection);
+
+        MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
+
+        r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+        MHD_destroy_response(response);
+
+        return r;
+}
+
 static int request_handler_redirect(
                 struct MHD_Connection *connection,
                 const char *target) {
@@ -562,24 +741,63 @@ static int request_handler_file(
         return ret;
 }
 
+static int get_virtualization(char **v) {
+        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+        _cleanup_bus_unref_ sd_bus *bus = NULL;
+        const char *t;
+        char *b;
+        int r;
+
+        r = sd_bus_open_system(&bus);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_call_method(
+                        bus,
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.DBus.Properties",
+                        "Get",
+                        NULL,
+                        &reply,
+                        "ss",
+                        "org.freedesktop.systemd1.Manager",
+                        "Virtualization");
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read(reply, "v", "s", &t);
+        if (r < 0)
+                return r;
+
+        if (isempty(t)) {
+                *v = NULL;
+                return 0;
+        }
+
+        b = strdup(t);
+        if (!b)
+                return -ENOMEM;
+
+        *v = b;
+        return 1;
+}
+
 static int request_handler_machine(
                 struct MHD_Connection *connection,
-                void **connection_cls) {
+                void *connection_cls) {
 
         struct MHD_Response *response;
-        RequestMeta *m;
+        RequestMeta *m = connection_cls;
         int r;
         _cleanup_free_ char* hostname = NULL, *os_name = NULL;
         uint64_t cutoff_from, cutoff_to, usage;
         char *json;
         sd_id128_t mid, bid;
-        const char *v = "bare";
+        _cleanup_free_ char *v = NULL;
 
         assert(connection);
-
-        m = request_meta(connection_cls);
-        if (!m)
-                return respond_oom(connection);
+        assert(m);
 
         r = open_journal(m);
         if (r < 0)
@@ -607,7 +825,7 @@ static int request_handler_machine(
 
         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
 
-        detect_virtualization(&v);
+        get_virtualization(&v);
 
         r = asprintf(&json,
                      "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
@@ -615,17 +833,17 @@ static int request_handler_machine(
                      "\"hostname\" : \"%s\","
                      "\"os_pretty_name\" : \"%s\","
                      "\"virtualization\" : \"%s\","
-                     "\"usage\" : \"%llu\","
-                     "\"cutoff_from_realtime\" : \"%llu\","
-                     "\"cutoff_to_realtime\" : \"%llu\" }\n",
+                     "\"usage\" : \"%"PRIu64"\","
+                     "\"cutoff_from_realtime\" : \"%"PRIu64"\","
+                     "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
                      SD_ID128_FORMAT_VAL(mid),
                      SD_ID128_FORMAT_VAL(bid),
-                     hostname_cleanup(hostname),
+                     hostname_cleanup(hostname, false),
                      os_name ? os_name : "Linux",
-                     v,
-                     (unsigned long long) usage,
-                     (unsigned long long) cutoff_from,
-                     (unsigned long long) cutoff_to);
+                     v ? v : "bare",
+                     usage,
+                     cutoff_from,
+                     cutoff_to);
 
         if (r < 0)
                 return respond_oom(connection);
@@ -654,40 +872,146 @@ static int request_handler(
                 void **connection_cls) {
 
         assert(connection);
+        assert(connection_cls);
         assert(url);
         assert(method);
 
         if (!streq(method, "GET"))
-                return MHD_NO;
+                return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+                                     "Unsupported method.\n");
+
+
+        if (!*connection_cls) {
+                if (!request_meta(connection_cls))
+                        return respond_oom(connection);
+                return MHD_YES;
+        }
 
         if (streq(url, "/"))
                 return request_handler_redirect(connection, "/browse");
 
         if (streq(url, "/entries"))
-                return request_handler_entries(connection, connection_cls);
+                return request_handler_entries(connection, *connection_cls);
+
+        if (startswith(url, "/fields/"))
+                return request_handler_fields(connection, url + 8, *connection_cls);
 
         if (streq(url, "/browse"))
                 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
 
         if (streq(url, "/machine"))
-                return request_handler_machine(connection, connection_cls);
+                return request_handler_machine(connection, *connection_cls);
 
         return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
 }
 
-int main(int argc, char *argv[]) {
-        struct MHD_Daemon *d = NULL;
-        int r = EXIT_FAILURE, n;
+static int help(void) {
+
+        printf("%s [OPTIONS...] ...\n\n"
+               "HTTP server for journal events.\n\n"
+               "  -h --help           Show this help\n"
+               "     --version        Show package version\n"
+               "     --cert=CERT.PEM  Specify server certificate in PEM format\n"
+               "     --key=KEY.PEM    Specify server key in PEM format\n",
+               program_invocation_short_name);
 
-        if (argc > 1) {
+        return 0;
+}
+
+static char *key_pem = NULL;
+static char *cert_pem = NULL;
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_KEY,
+                ARG_CERT,
+        };
+
+        int r, c;
+
+        static const struct option options[] = {
+                { "help",    no_argument,       NULL, 'h'         },
+                { "version", no_argument,       NULL, ARG_VERSION },
+                { "key",     required_argument, NULL, ARG_KEY     },
+                { "cert",    required_argument, NULL, ARG_CERT    },
+                { NULL,      0,                 NULL, 0           }
+        };
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+                switch(c) {
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case 'h':
+                        return help();
+
+                case ARG_KEY:
+                        if (key_pem) {
+                                log_error("Key file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &key_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read key file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(key_pem);
+                        break;
+
+                case ARG_CERT:
+                        if (cert_pem) {
+                                log_error("Certificate file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &cert_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read certificate file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(cert_pem);
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+
+        if (optind < argc) {
                 log_error("This program does not take arguments.");
-                goto finish;
+                return -EINVAL;
+        }
+
+        if (!!key_pem != !!cert_pem) {
+                log_error("Certificate and key files must be specified together");
+                return -EINVAL;
         }
 
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        struct MHD_Daemon *d = NULL;
+        int r, n;
+
         log_set_target(LOG_TARGET_AUTO);
         log_parse_environment();
         log_open();
 
+        r = parse_argv(argc, argv);
+        if (r < 0)
+                return EXIT_FAILURE;
+        if (r == 0)
+                return EXIT_SUCCESS;
+
         n = sd_listen_fds(1);
         if (n < 0) {
                 log_error("Failed to determine passed sockets: %s", strerror(-n));
@@ -695,23 +1019,36 @@ int main(int argc, char *argv[]) {
         } else if (n > 1) {
                 log_error("Can't listen on more than one socket.");
                 goto finish;
-        } else if (n > 0) {
-                d = MHD_start_daemon(
-                                MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
-                                19531,
-                                NULL, NULL,
-                                request_handler, NULL,
-                                MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
-                                MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
-                                MHD_OPTION_END);
         } else {
-                d = MHD_start_daemon(
-                                MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
-                                19531,
-                                NULL, NULL,
-                                request_handler, NULL,
-                                MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
-                                MHD_OPTION_END);
+                struct MHD_OptionItem opts[] = {
+                        { MHD_OPTION_NOTIFY_COMPLETED,
+                          (intptr_t) request_meta_free, NULL },
+                        { MHD_OPTION_EXTERNAL_LOGGER,
+                          (intptr_t) microhttpd_logger, NULL },
+                        { MHD_OPTION_END, 0, NULL },
+                        { MHD_OPTION_END, 0, NULL },
+                        { MHD_OPTION_END, 0, NULL },
+                        { MHD_OPTION_END, 0, NULL }};
+                int opts_pos = 2;
+                int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
+
+                if (n > 0)
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
+                if (key_pem) {
+                        assert(cert_pem);
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
+                        flags |= MHD_USE_SSL;
+                }
+
+                d = MHD_start_daemon(flags, 19531,
+                                     NULL, NULL,
+                                     request_handler, NULL,
+                                     MHD_OPTION_ARRAY, opts,
+                                     MHD_OPTION_END);
         }
 
         if (!d) {