chiark / gitweb /
journal: when show blob data show length
[elogind.git] / src / logs-show.c
index d44c50a..5a00133 100644 (file)
@@ -22,6 +22,8 @@
 #include <time.h>
 #include <assert.h>
 #include <errno.h>
+#include <sys/poll.h>
+#include <string.h>
 
 #include "logs-show.h"
 #include "log.h"
@@ -39,64 +41,230 @@ static bool contains_unprintable(const void *p, size_t l) {
         return false;
 }
 
-static int output_short(sd_journal *j, unsigned line, bool show_all) {
+static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
+        size_t fl, nl;
+        void *buf;
+
+        assert(data);
+        assert(field);
+        assert(target);
+        assert(target_size);
+
+        fl = strlen(field);
+        if (length < fl)
+                return 0;
+
+        if (memcmp(data, field, fl))
+                return 0;
+
+        nl = length - fl;
+        buf = malloc(nl+1);
+        memcpy(buf, (const char*) data + fl, nl);
+        ((char*)buf)[nl] = 0;
+        if (!buf) {
+                log_error("Out of memory");
+                return -ENOMEM;
+        }
+
+        free(*target);
+        *target = buf;
+        *target_size = nl;
+
+        return 1;
+}
+
+static bool shall_print(bool show_all, char *p, size_t l) {
+        if (show_all)
+                return true;
+
+        if (l > PRINT_THRESHOLD)
+                return false;
+
+        if (contains_unprintable(p, l))
+                return false;
+
+        return true;
+}
+
+static int output_short(sd_journal *j, unsigned line, bool show_all, bool monotonic_mode) {
         int r;
-        uint64_t realtime;
-        time_t t;
-        struct tm tm;
-        char buf[64];
         const void *data;
         size_t length;
         size_t n = 0;
+        char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL;
+        size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0;
 
         assert(j);
 
-        r = sd_journal_get_realtime_usec(j, &realtime);
-        if (r < 0) {
-                log_error("Failed to get realtime: %s", strerror(-r));
-                return r;
+        SD_JOURNAL_FOREACH_DATA(j, data, length) {
+
+                r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "_COMM=", &comm, &comm_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "_PID=", &pid, &pid_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len);
+                if (r < 0)
+                        goto finish;
+                else if (r > 0)
+                        continue;
+
+                r = parse_field(data, length, "MESSAGE=", &message, &message_len);
+                if (r < 0)
+                        goto finish;
         }
 
-        t = (time_t) (realtime / USEC_PER_SEC);
-        if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) {
-                log_error("Failed to format time.");
-                return -EINVAL;
+        if (!message) {
+                r = 0;
+                goto finish;
         }
 
-        fputs(buf, stdout);
-        n += strlen(buf);
+        if (monotonic_mode) {
+                uint64_t t;
+                sd_id128_t boot_id;
 
-        if (sd_journal_get_data(j, "_HOSTNAME", &data, &length) >= 0 &&
-            (show_all || (!contains_unprintable(data, length) &&
-                          length < PRINT_THRESHOLD))) {
-                printf(" %.*s", (int) length - 10, ((const char*) data) + 10);
-                n += length - 10 + 1;
-        }
+                r = -ENOENT;
+
+                if (monotonic)
+                        r = safe_atou64(monotonic, &t);
 
-        if (sd_journal_get_data(j, "MESSAGE", &data, &length) >= 0) {
-                if (show_all)
-                        printf(" %.*s", (int) length - 8, ((const char*) data) + 8);
-                else if (contains_unprintable(data, length))
-                        fputs(" [blob data]", stdout);
-                else if (length - 8 + n < columns())
-                        printf(" %.*s", (int) length - 8, ((const char*) data) + 8);
-                else if (n < columns()) {
-                        char *e;
+                if (r < 0)
+                        r = sd_journal_get_monotonic_usec(j, &t, &boot_id);
+
+                if (r < 0) {
+                        log_error("Failed to get monotonic: %s", strerror(-r));
+                        goto finish;
+                }
+
+                printf("[%5llu.%06llu]",
+                       (unsigned long long) (t / USEC_PER_SEC),
+                       (unsigned long long) (t % USEC_PER_SEC));
+
+                n += 1 + 5 + 1 + 6 + 1;
+
+        } else {
+                char buf[64];
+                uint64_t x;
+                time_t t;
+                struct tm tm;
 
-                        e = ellipsize_mem((const char *) data + 8, length - 8, columns() - n - 2, 90);
+                r = -ENOENT;
 
-                        if (!e)
-                                printf(" %.*s", (int) length - 8, ((const char*) data) + 8);
-                        else
-                                printf(" %s", e);
+                if (realtime)
+                        r = safe_atou64(realtime, &x);
 
-                        free(e);
+                if (r < 0)
+                        r = sd_journal_get_realtime_usec(j, &x);
+
+                if (r < 0) {
+                        log_error("Failed to get realtime: %s", strerror(-r));
+                        goto finish;
+                }
+
+                t = (time_t) (x / USEC_PER_SEC);
+                if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_r(&t, &tm)) <= 0) {
+                        log_error("Failed to format time.");
+                        goto finish;
                 }
+
+                fputs(buf, stdout);
+                n += strlen(buf);
         }
 
-        fputc('\n', stdout);
+        if (hostname && shall_print(show_all, hostname, hostname_len)) {
+                printf(" %.*s", (int) hostname_len, hostname);
+                n += hostname_len + 1;
+        }
 
-        return 0;
+        if (identifier && shall_print(show_all, identifier, identifier_len)) {
+                printf(" %.*s", (int) identifier_len, identifier);
+                n += identifier_len + 1;
+        } else if (comm && shall_print(show_all, comm, comm_len)) {
+                printf(" %.*s", (int) comm_len, comm);
+                n += comm_len + 1;
+        }
+
+        if (pid && shall_print(show_all, pid, pid_len)) {
+                printf("[%.*s]", (int) pid_len, pid);
+                n += pid_len + 2;
+        } else if (fake_pid && shall_print(show_all, fake_pid, fake_pid_len)) {
+                printf("[%.*s]", (int) fake_pid_len, fake_pid);
+                n += fake_pid_len + 2;
+        }
+
+        if (show_all)
+                printf(": %.*s\n", (int) message_len, message);
+        else if (contains_unprintable(message, message_len)) {
+                char bytes[FORMAT_BYTES_MAX];
+                printf(": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len));
+        } else if (message_len + n < columns())
+                printf(": %.*s\n", (int) message_len, message);
+        else if (n < columns()) {
+                char *e;
+
+                e = ellipsize_mem(message, message_len, columns() - n - 2, 90);
+
+                if (!e)
+                        printf(": %.*s\n", (int) message_len, message);
+                else
+                        printf(": %s\n", e);
+
+                free(e);
+        } else
+                fputs("\n", stdout);
+
+        r = 0;
+
+finish:
+        free(hostname);
+        free(identifier);
+        free(comm);
+        free(pid);
+        free(fake_pid);
+        free(message);
+        free(monotonic);
+        free(realtime);
+
+        return r;
+}
+
+static int output_short_realtime(sd_journal *j, unsigned line, bool show_all) {
+        return output_short(j, line, show_all, false);
+}
+
+static int output_short_monotonic(sd_journal *j, unsigned line, bool show_all) {
+        return output_short(j, line, show_all, true);
 }
 
 static int output_verbose(sd_journal *j, unsigned line, bool show_all) {
@@ -131,6 +299,7 @@ static int output_verbose(sd_journal *j, unsigned line, bool show_all) {
                 if (!show_all && (length > PRINT_THRESHOLD ||
                                   contains_unprintable(data, length))) {
                         const char *c;
+                        char bytes[FORMAT_BYTES_MAX];
 
                         c = memchr(data, '=', length);
                         if (!c) {
@@ -138,9 +307,10 @@ static int output_verbose(sd_journal *j, unsigned line, bool show_all) {
                                 return -EINVAL;
                         }
 
-                        printf("\t%.*s=[blob data]\n",
+                        printf("\t%.*s=[%s blob data]\n",
                                (int) (c - (const char*) data),
-                               (const char*) data);
+                               (const char*) data,
+                               format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1));
                 } else
                         printf("\t%.*s\n", (int) length, (const char*) data);
         }
@@ -321,51 +491,80 @@ static int output_json(sd_journal *j, unsigned line, bool show_all) {
         return 0;
 }
 
+static int output_cat(sd_journal *j, unsigned line, bool show_all) {
+        const void *data;
+        size_t l;
+        int r;
+
+        assert(j);
+
+        r = sd_journal_get_data(j, "MESSAGE", &data, &l);
+        if (r < 0) {
+                log_error("Failed to get data: %s", strerror(-r));
+                return r;
+        }
+
+        assert(l >= 8);
+
+        fwrite((const char*) data + 8, 1, l - 8, stdout);
+        putchar('\n');
+
+        return 0;
+}
+
 static int (*output_funcs[_OUTPUT_MODE_MAX])(sd_journal*j, unsigned line, bool show_all) = {
-        [OUTPUT_SHORT] = output_short,
+        [OUTPUT_SHORT] = output_short_realtime,
+        [OUTPUT_SHORT_MONOTONIC] = output_short_monotonic,
         [OUTPUT_VERBOSE] = output_verbose,
         [OUTPUT_EXPORT] = output_export,
-        [OUTPUT_JSON] = output_json
+        [OUTPUT_JSON] = output_json,
+        [OUTPUT_CAT] = output_cat
 };
 
-int output_journal(sd_journal *j, output_mode mode, unsigned line, bool show_all) {
+int output_journal(sd_journal *j, OutputMode mode, unsigned line, bool show_all) {
+        assert(mode >= 0);
         assert(mode < _OUTPUT_MODE_MAX);
 
         return output_funcs[mode](j, line, show_all);
 }
 
-int show_journal_by_service(
-                const char *service,
-                output_mode mode,
+int show_journal_by_unit(
+                const char *unit,
+                OutputMode mode,
                 const char *prefix,
                 unsigned n_columns,
                 usec_t not_before,
                 unsigned how_many,
-                bool show_all) {
+                bool show_all,
+                bool follow) {
 
         char *m = NULL;
         sd_journal *j;
         int r;
-        unsigned i;
+        int fd;
+        unsigned line = 0;
+        bool need_seek = false;
 
-        assert(service);
+        assert(mode >= 0);
+        assert(mode < _OUTPUT_MODE_MAX);
+        assert(unit);
+
+        if (!endswith(unit, ".service") &&
+            !endswith(unit, ".socket") &&
+            !endswith(unit, ".mount") &&
+            !endswith(unit, ".swap"))
+                return 0;
 
-        if (!endswith(service, ".service") &&
-            !endswith(service, ".socket") &&
-            !endswith(service, ".mount") &&
-            !endswith(service, ".swap"))
+        if (how_many <= 0)
                 return 0;
 
         if (n_columns <= 0)
                 n_columns = columns();
 
-        if (how_many <= 0)
-                how_many = 10;
-
         if (!prefix)
                 prefix = "";
 
-        if (asprintf(&m, "_SYSTEMD_UNIT=%s", service) < 0) {
+        if (asprintf(&m, "_SYSTEMD_UNIT=%s", unit) < 0) {
                 r = -ENOMEM;
                 goto finish;
         }
@@ -374,6 +573,10 @@ int show_journal_by_service(
         if (r < 0)
                 goto finish;
 
+        fd = sd_journal_get_fd(j);
+        if (fd < 0)
+                goto finish;
+
         r = sd_journal_add_match(j, m, strlen(m));
         if (r < 0)
                 goto finish;
@@ -382,23 +585,67 @@ int show_journal_by_service(
         if (r < 0)
                 goto finish;
 
-        for (i = 0; i < how_many; i++)
-                sd_journal_previous(j);
+        r = sd_journal_previous_skip(j, how_many);
+        if (r < 0)
+                goto finish;
 
-        for (i = 0; i < how_many; i++) {
+        if (mode == OUTPUT_JSON) {
+                fputc('[', stdout);
+                fflush(stdout);
+        }
 
-                r = sd_journal_next(j);
-                if (r < 0)
-                        goto finish;
+        for (;;) {
+                for (;;) {
+                        usec_t usec;
+
+                        if (need_seek) {
+                                r = sd_journal_next(j);
+                                if (r < 0)
+                                        goto finish;
+                        }
+
+                        if (r == 0)
+                                break;
 
-                if (r == 0)
+                        need_seek = true;
+
+                        if (not_before > 0) {
+                                r = sd_journal_get_monotonic_usec(j, &usec, NULL);
+
+                                /* -ESTALE is returned if the
+                                   timestamp is not from this boot */
+                                if (r == -ESTALE)
+                                        continue;
+                                else if (r < 0)
+                                        goto finish;
+
+                                if (usec < not_before)
+                                        continue;
+                        }
+
+                        line ++;
+
+                        r = output_journal(j, mode, line, show_all);
+                        if (r < 0)
+                                goto finish;
+                }
+
+                if (!follow)
                         break;
 
-                r = output_journal(j, mode, i+1, show_all);
+                r = fd_wait_for_event(fd, POLLIN);
                 if (r < 0)
                         goto finish;
+
+                r = sd_journal_process(j);
+                if (r < 0)
+                        goto finish;
+
         }
 
+        if (mode == OUTPUT_JSON)
+                fputs("\n]\n", stdout);
+
 finish:
         if (m)
                 free(m);
@@ -408,3 +655,14 @@ finish:
 
         return r;
 }
+
+static const char *const output_mode_table[_OUTPUT_MODE_MAX] = {
+        [OUTPUT_SHORT] = "short",
+        [OUTPUT_SHORT_MONOTONIC] = "short-monotonic",
+        [OUTPUT_VERBOSE] = "verbose",
+        [OUTPUT_EXPORT] = "export",
+        [OUTPUT_JSON] = "json",
+        [OUTPUT_CAT] = "cat"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode);