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