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