923a60
From 99ea430346f3f8ffba504cd3de1a269ab4eac8e6 Mon Sep 17 00:00:00 2001
923a60
From: Jan Janssen <medhefgo@web.de>
923a60
Date: Fri, 1 May 2015 15:15:16 +0200
923a60
Subject: [PATCH] journalctl: Improve boot ID lookup
923a60
923a60
This method should greatly improve offset based lookup, by simply jumping
923a60
from one boot to the next boot. It starts at the journal head to get the
923a60
a boot ID, makes a _BOOT_ID match and then comes from the opposite
923a60
journal direction (tail) to get to the end that boot. After flushing the matches
923a60
and advancing the journal from that exact position, we arrive at the start
923a60
of next boot. Rinse and repeat.
923a60
923a60
This is faster than the old method of aggregating the full boot listing just
923a60
so we can jump to a specific boot, which can be a real pain on big journals
923a60
just for a mere "-b -1" case.
923a60
923a60
As an additional benefit --list-boots should improve slightly too, because
923a60
it does less seeking.
923a60
923a60
Note that there can be a change in boot order with this lookup method
923a60
because it will use the order of boots in the journal, not the realtime stamp
923a60
stored in them. That's arguably better, though.
923a60
Another deficiency is that it will get confused with boots interleaving in the
923a60
journal, therefore, it will refuse operation in --merge, --file and --directory mode.
923a60
923a60
https://bugs.freedesktop.org/show_bug.cgi?id=72601
923a60
(cherry picked from commit 596a23293d28f93843aef86721b90043e74d3081)
923a60
923a60
Cherry-picked from: 596a232
923a60
Resolves: #1222517
923a60
---
923a60
 src/journal/journalctl.c | 275 +++++++++++++++++++++++++--------------
923a60
 1 file changed, 174 insertions(+), 101 deletions(-)
923a60
923a60
diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
923a60
index 12c869f5af..c26cc00f51 100644
923a60
--- a/src/journal/journalctl.c
923a60
+++ b/src/journal/journalctl.c
923a60
@@ -131,6 +131,7 @@ typedef struct boot_id_t {
923a60
         sd_id128_t id;
923a60
         uint64_t first;
923a60
         uint64_t last;
923a60
+        LIST_FIELDS(struct boot_id_t, boot_list);
923a60
 } boot_id_t;
923a60
 
923a60
 static void pager_open_if_enabled(void) {
923a60
@@ -735,6 +736,11 @@ static int parse_argv(int argc, char *argv[]) {
923a60
                 return -EINVAL;
923a60
         }
923a60
 
923a60
+        if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && (arg_file || arg_directory || arg_merge)) {
923a60
+                log_error("Using --boot or --list-boots with --file, --directory or --merge is not supported.");
923a60
+                return -EINVAL;
923a60
+        }
923a60
+
923a60
         return 1;
923a60
 }
923a60
 
923a60
@@ -854,111 +860,203 @@ static int add_matches(sd_journal *j, char **args) {
923a60
         return 0;
923a60
 }
923a60
 
923a60
-static int boot_id_cmp(const void *a, const void *b) {
923a60
-        uint64_t _a, _b;
923a60
+static int discover_next_boot(sd_journal *j,
923a60
+                              boot_id_t **boot,
923a60
+                              bool advance_older,
923a60
+                              bool read_realtime) {
923a60
+        int r;
923a60
+        char match[9+32+1] = "_BOOT_ID=";
923a60
+        _cleanup_free_ boot_id_t *next_boot = NULL;
923a60
 
923a60
-        _a = ((const boot_id_t *)a)->first;
923a60
-        _b = ((const boot_id_t *)b)->first;
923a60
+        assert(j);
923a60
+        assert(boot);
923a60
 
923a60
-        return _a < _b ? -1 : (_a > _b ? 1 : 0);
923a60
-}
923a60
+        /* We expect the journal to be on the last position of a boot
923a60
+         * (in relation to the direction we are going), so that the next
923a60
+         * invocation of sd_journal_next/previous will be from a different
923a60
+         * boot. We then collect any information we desire and then jump
923a60
+         * to the last location of the new boot by using a _BOOT_ID match
923a60
+         * coming from the other journal direction. */
923a60
 
923a60
-static int get_boots(sd_journal *j,
923a60
-                     boot_id_t **boots,
923a60
-                     unsigned int *count,
923a60
-                     boot_id_t *query_ref_boot) {
923a60
-        int r;
923a60
-        const void *data;
923a60
-        size_t length, allocated = 0;
923a60
+        /* Make sure we aren't restricted by any _BOOT_ID matches, so that
923a60
+         * we can actually advance to a *different* boot. */
923a60
+        sd_journal_flush_matches(j);
923a60
 
923a60
-        assert(j);
923a60
-        assert(boots);
923a60
-        assert(count);
923a60
+        if (advance_older)
923a60
+                r = sd_journal_previous(j);
923a60
+        else
923a60
+                r = sd_journal_next(j);
923a60
+        if (r < 0)
923a60
+                return r;
923a60
+        else if (r == 0)
923a60
+                return 0; /* End of journal, yay. */
923a60
+
923a60
+        next_boot = new0(boot_id_t, 1);
923a60
+        if (!next_boot)
923a60
+                return log_oom();
923a60
 
923a60
-        r = sd_journal_query_unique(j, "_BOOT_ID");
923a60
+        r = sd_journal_get_monotonic_usec(j, NULL, &next_boot->id);
923a60
         if (r < 0)
923a60
                 return r;
923a60
 
923a60
-        *count = 0;
923a60
-        SD_JOURNAL_FOREACH_UNIQUE(j, data, length) {
923a60
-                boot_id_t *id;
923a60
+        if (read_realtime) {
923a60
+                r = sd_journal_get_realtime_usec(j, &next_boot->first);
923a60
+                if (r < 0)
923a60
+                        return r;
923a60
+        }
923a60
 
923a60
-                assert(startswith(data, "_BOOT_ID="));
923a60
+        /* Now seek to the last occurrence of this boot ID. */
923a60
+        sd_id128_to_string(next_boot->id, match + 9);
923a60
+        r = sd_journal_add_match(j, match, sizeof(match) - 1);
923a60
+        if (r < 0)
923a60
+                return r;
923a60
 
923a60
-                if (!GREEDY_REALLOC(*boots, allocated, *count + 1))
923a60
-                        return log_oom();
923a60
+        if (advance_older)
923a60
+                r = sd_journal_seek_head(j);
923a60
+        else
923a60
+                r = sd_journal_seek_tail(j);
923a60
+        if (r < 0)
923a60
+                return r;
923a60
 
923a60
-                id = *boots + *count;
923a60
+        if (advance_older)
923a60
+                r = sd_journal_next(j);
923a60
+        else
923a60
+                r = sd_journal_previous(j);
923a60
+        if (r < 0)
923a60
+                return r;
923a60
+        else if (r == 0)
923a60
+                return -ENODATA; /* This shouldn't happen. We just came from this very boot ID. */
923a60
 
923a60
-                r = sd_id128_from_string(((const char *)data) + strlen("_BOOT_ID="), &id->id);
923a60
+        if (read_realtime) {
923a60
+                r = sd_journal_get_realtime_usec(j, &next_boot->last);
923a60
                 if (r < 0)
923a60
-                        continue;
923a60
+                        return r;
923a60
+        }
923a60
+
923a60
+        *boot = next_boot;
923a60
+        next_boot = NULL;
923a60
+        return 0;
923a60
+}
923a60
+
923a60
+static int get_boots(sd_journal *j,
923a60
+                     boot_id_t **boots,
923a60
+                     boot_id_t *query_ref_boot,
923a60
+                     int ref_boot_offset) {
923a60
+        bool skip_once;
923a60
+        int r, count = 0;
923a60
+        boot_id_t *head = NULL, *tail = NULL;
923a60
+        const bool advance_older = query_ref_boot && ref_boot_offset <= 0;
923a60
+
923a60
+        assert(j);
923a60
+
923a60
+        /* Adjust for the asymmetry that offset 0 is
923a60
+         * the last (and current) boot, while 1 is considered the
923a60
+         * (chronological) first boot in the journal. */
923a60
+        skip_once = query_ref_boot && sd_id128_is_null(query_ref_boot->id) && ref_boot_offset < 0;
923a60
+
923a60
+        /* Advance to the earliest/latest occurrence of our reference
923a60
+         * boot ID (taking our lookup direction into account), so that
923a60
+         * discover_next_boot() can do its job.
923a60
+         * If no reference is given, the journal head/tail will do,
923a60
+         * they're "virtual" boots after all. */
923a60
+        if (query_ref_boot && !sd_id128_is_null(query_ref_boot->id)) {
923a60
+                char match[9+32+1] = "_BOOT_ID=";
923a60
+
923a60
+                sd_journal_flush_matches(j);
923a60
 
923a60
-                r = sd_journal_add_match(j, data, length);
923a60
+                sd_id128_to_string(query_ref_boot->id, match + 9);
923a60
+                r = sd_journal_add_match(j, match, sizeof(match) - 1);
923a60
                 if (r < 0)
923a60
                         return r;
923a60
 
923a60
-                r = sd_journal_seek_head(j);
923a60
+                if (advance_older)
923a60
+                        r = sd_journal_seek_head(j);
923a60
+                else
923a60
+                        r = sd_journal_seek_tail(j);
923a60
                 if (r < 0)
923a60
                         return r;
923a60
 
923a60
-                r = sd_journal_next(j);
923a60
+                if (advance_older)
923a60
+                        r = sd_journal_next(j);
923a60
+                else
923a60
+                        r = sd_journal_previous(j);
923a60
                 if (r < 0)
923a60
                         return r;
923a60
                 else if (r == 0)
923a60
-                        goto flush;
923a60
-
923a60
-                r = sd_journal_get_realtime_usec(j, &id->first);
923a60
+                        goto finish;
923a60
+                else if (ref_boot_offset == 0) {
923a60
+                        count = 1;
923a60
+                        goto finish;
923a60
+                }
923a60
+        } else {
923a60
+                if (advance_older)
923a60
+                        r = sd_journal_seek_tail(j);
923a60
+                else
923a60
+                        r = sd_journal_seek_head(j);
923a60
                 if (r < 0)
923a60
                         return r;
923a60
 
923a60
-                if (query_ref_boot) {
923a60
-                        id->last = 0;
923a60
-                        if (sd_id128_equal(id->id, query_ref_boot->id))
923a60
-                                *query_ref_boot = *id;
923a60
-                } else {
923a60
-                        r = sd_journal_seek_tail(j);
923a60
-                        if (r < 0)
923a60
-                                return r;
923a60
+                /* No sd_journal_next/previous here. */
923a60
+        }
923a60
 
923a60
-                        r = sd_journal_previous(j);
923a60
-                        if (r < 0)
923a60
-                                return r;
923a60
-                        else if (r == 0)
923a60
-                                goto flush;
923a60
+        while (true) {
923a60
+                _cleanup_free_ boot_id_t *current = NULL;
923a60
 
923a60
-                        r = sd_journal_get_realtime_usec(j, &id->last);
923a60
-                        if (r < 0)
923a60
-                                return r;
923a60
+                r = discover_next_boot(j, &current, advance_older, !query_ref_boot);
923a60
+                if (r < 0) {
923a60
+                        boot_id_t *id, *id_next;
923a60
+                        LIST_FOREACH_SAFE(boot_list, id, id_next, head)
923a60
+                                free(id);
923a60
+                        return r;
923a60
                 }
923a60
 
923a60
-                (*count)++;
923a60
-        flush:
923a60
-                sd_journal_flush_matches(j);
923a60
+                if (!current)
923a60
+                        break;
923a60
+
923a60
+                if (query_ref_boot) {
923a60
+                        if (!skip_once)
923a60
+                                ref_boot_offset += advance_older ? 1 : -1;
923a60
+                        skip_once = false;
923a60
+
923a60
+                        if (ref_boot_offset == 0) {
923a60
+                                count = 1;
923a60
+                                query_ref_boot->id = current->id;
923a60
+                                break;
923a60
+                        }
923a60
+                } else {
923a60
+                        LIST_INSERT_AFTER(boot_list, head, tail, current);
923a60
+                        tail = current;
923a60
+                        current = NULL;
923a60
+                        count++;
923a60
+                }
923a60
         }
923a60
 
923a60
-        qsort_safe(*boots, *count, sizeof(boot_id_t), boot_id_cmp);
923a60
-        return 0;
923a60
+finish:
923a60
+        if (boots)
923a60
+                *boots = head;
923a60
+
923a60
+        sd_journal_flush_matches(j);
923a60
+
923a60
+        return count;
923a60
 }
923a60
 
923a60
 static int list_boots(sd_journal *j) {
923a60
-        int r, w, i;
923a60
-        unsigned int count;
923a60
-        boot_id_t *id;
923a60
-        _cleanup_free_ boot_id_t *all_ids = NULL;
923a60
+        int w, i, count;
923a60
+        boot_id_t *id, *id_next, *all_ids;
923a60
 
923a60
         assert(j);
923a60
 
923a60
-        r = get_boots(j, &all_ids, &count, NULL);
923a60
-        if (r < 0)
923a60
-                return r;
923a60
+        count = get_boots(j, &all_ids, NULL, 0);
923a60
+        if (count <= 0)
923a60
+                return count;
923a60
 
923a60
         pager_open_if_enabled();
923a60
 
923a60
         /* numbers are one less, but we need an extra char for the sign */
923a60
         w = DECIMAL_STR_WIDTH(count - 1) + 1;
923a60
 
923a60
-        for (id = all_ids, i = 0; id < all_ids + count; id++, i++) {
923a60
+        i = 0;
923a60
+        LIST_FOREACH_SAFE(boot_list, id, id_next, all_ids) {
923a60
                 char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX];
923a60
 
923a60
                 printf("% *i " SD_ID128_FORMAT_STR " %s—%s\n",
923a60
@@ -966,39 +1064,8 @@ static int list_boots(sd_journal *j) {
923a60
                        SD_ID128_FORMAT_VAL(id->id),
923a60
                        format_timestamp_maybe_utc(a, sizeof(a), id->first),
923a60
                        format_timestamp_maybe_utc(b, sizeof(b), id->last));
923a60
-        }
923a60
-
923a60
-        return 0;
923a60
-}
923a60
-
923a60
-static int get_boot_id_by_offset(sd_journal *j, sd_id128_t *boot_id, int offset) {
923a60
-        int r;
923a60
-        unsigned int count;
923a60
-        boot_id_t ref_boot_id = {}, *id;
923a60
-        _cleanup_free_ boot_id_t *all_ids = NULL;
923a60
-
923a60
-        assert(j);
923a60
-        assert(boot_id);
923a60
-
923a60
-        ref_boot_id.id = *boot_id;
923a60
-        r = get_boots(j, &all_ids, &count, &ref_boot_id);
923a60
-        if (r < 0)
923a60
-                return r;
923a60
-
923a60
-        if (sd_id128_equal(*boot_id, SD_ID128_NULL)) {
923a60
-                if (offset > (int) count || offset <= -(int)count)
923a60
-                        return -EADDRNOTAVAIL;
923a60
-
923a60
-                *boot_id = all_ids[(offset <= 0)*count + offset - 1].id;
923a60
-        } else {
923a60
-                id = bsearch(&ref_boot_id, all_ids, count, sizeof(boot_id_t), boot_id_cmp);
923a60
-
923a60
-                if (!id ||
923a60
-                    offset <= 0 ? (id - all_ids) + offset < 0 :
923a60
-                                    (id - all_ids) + offset >= (int) count)
923a60
-                        return -EADDRNOTAVAIL;
923a60
-
923a60
-                *boot_id = (id + offset)->id;
923a60
+                i++;
923a60
+                free(id);
923a60
         }
923a60
 
923a60
         return 0;
923a60
@@ -1007,6 +1074,7 @@ static int get_boot_id_by_offset(sd_journal *j, sd_id128_t *boot_id, int offset)
923a60
 static int add_boot(sd_journal *j) {
923a60
         char match[9+32+1] = "_BOOT_ID=";
923a60
         int r;
923a60
+        boot_id_t ref_boot_id = {};
923a60
 
923a60
         assert(j);
923a60
 
923a60
@@ -1016,17 +1084,22 @@ static int add_boot(sd_journal *j) {
923a60
         if (arg_boot_offset == 0 && sd_id128_equal(arg_boot_id, SD_ID128_NULL))
923a60
                 return add_match_this_boot(j, arg_machine);
923a60
 
923a60
-        r = get_boot_id_by_offset(j, &arg_boot_id, arg_boot_offset);
923a60
-        if (r < 0) {
923a60
-                if (sd_id128_equal(arg_boot_id, SD_ID128_NULL))
923a60
-                        log_error_errno(r, "Failed to look up boot %+i: %m", arg_boot_offset);
923a60
+        ref_boot_id.id = arg_boot_id;
923a60
+        r = get_boots(j, NULL, &ref_boot_id, arg_boot_offset);
923a60
+        assert(r <= 1);
923a60
+        if (r <= 0) {
923a60
+                const char *reason = (r == 0) ? "No such boot ID in journal" : strerror(-r);
923a60
+
923a60
+                if (sd_id128_is_null(arg_boot_id))
923a60
+                        log_error("Failed to look up boot %+i: %s", arg_boot_offset, reason);
923a60
                 else
923a60
                         log_error("Failed to look up boot ID "SD_ID128_FORMAT_STR"%+i: %s",
923a60
-                                  SD_ID128_FORMAT_VAL(arg_boot_id), arg_boot_offset, strerror(-r));
923a60
-                return r;
923a60
+                                  SD_ID128_FORMAT_VAL(arg_boot_id), arg_boot_offset, reason);
923a60
+
923a60
+                return r == 0 ? -ENODATA : r;
923a60
         }
923a60
 
923a60
-        sd_id128_to_string(arg_boot_id, match + 9);
923a60
+        sd_id128_to_string(ref_boot_id.id, match + 9);
923a60
 
923a60
         r = sd_journal_add_match(j, match, sizeof(match) - 1);
923a60
         if (r < 0)