Blame SOURCES/0210-journalctl-Improve-boot-ID-lookup.patch

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