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