1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-25 01:34:28 +03:00

Merge pull request #29296 from yuwata/sd-journal-several-cleanups-for-boot-id

This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2023-09-27 14:56:48 +02:00
commit e071384dc5
13 changed files with 563 additions and 272 deletions

View File

@ -175,12 +175,6 @@ static enum {
ACTION_LIST_FIELD_NAMES,
} arg_action = ACTION_SHOW;
typedef struct BootId {
sd_id128_t id;
usec_t first_usec;
usec_t last_usec;
} BootId;
static int add_matches_for_device(sd_journal *j, const char *devpath) {
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
sd_device *d = NULL;
@ -1225,207 +1219,6 @@ static int add_matches(sd_journal *j, char **args) {
return 0;
}
static int discover_next_boot(
sd_journal *j,
sd_id128_t previous_boot_id,
bool advance_older,
BootId *ret) {
BootId boot;
int r;
assert(j);
assert(ret);
/* 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. */
/* 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);
do {
if (advance_older)
r = sd_journal_previous(j);
else
r = sd_journal_next(j);
if (r < 0)
return r;
else if (r == 0) {
*ret = (BootId) {};
return 0; /* End of journal, yay. */
}
r = sd_journal_get_monotonic_usec(j, NULL, &boot.id);
if (r < 0)
return r;
/* We iterate through this in a loop, until the boot ID differs from the previous one. Note that
* normally, this will only require a single iteration, as we moved to the last entry of the previous
* boot entry already. However, it might happen that the per-journal-field entry arrays are less
* complete than the main entry array, and hence might reference an entry that's not actually the last
* one of the boot ID as last one. Let's hence use the per-field array is initial seek position to
* speed things up, but let's not trust that it is complete, and hence, manually advance as
* necessary. */
} while (sd_id128_equal(boot.id, previous_boot_id));
r = sd_journal_get_realtime_usec(j, &boot.first_usec);
if (r < 0)
return r;
/* Now seek to the last occurrence of this boot ID. */
r = add_match_boot_id(j, boot.id);
if (r < 0)
return r;
if (advance_older)
r = sd_journal_seek_head(j);
else
r = sd_journal_seek_tail(j);
if (r < 0)
return r;
if (advance_older)
r = sd_journal_next(j);
else
r = sd_journal_previous(j);
if (r < 0)
return r;
else if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA),
"Whoopsie! We found a boot ID but can't read its last entry."); /* This shouldn't happen. We just came from this very boot ID. */
r = sd_journal_get_realtime_usec(j, &boot.last_usec);
if (r < 0)
return r;
sd_journal_flush_matches(j);
*ret = boot;
return 1;
}
static int find_boot_by_id(sd_journal *j) {
int r;
assert(j);
sd_journal_flush_matches(j);
r = add_match_boot_id(j, arg_boot_id);
if (r < 0)
return r;
r = sd_journal_seek_head(j); /* seek to oldest */
if (r < 0)
return r;
r = sd_journal_next(j); /* read the oldest entry */
if (r < 0)
return r;
/* At this point the read pointer is positioned at the oldest occurrence of the reference boot ID.
* After flushing the matches, one more invocation of _previous() will hence place us at the
* following entry, which must then have an older boot ID */
sd_journal_flush_matches(j);
return r > 0;
}
static int find_boot_by_offset(sd_journal *j) {
bool advance_older, skip_once;
int r;
/* Adjust for the asymmetry that offset 0 is the last (and current) boot, while 1 is considered the
* (chronological) first boot in the journal. */
advance_older = skip_once = arg_boot_offset <= 0;
if (advance_older)
r = sd_journal_seek_tail(j); /* seek to newest */
else
r = sd_journal_seek_head(j); /* seek to oldest */
if (r < 0)
return r;
/* No sd_journal_next()/_previous() here.
*
* At this point the read pointer is positioned after the newest/before the oldest entry in the whole
* journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest
* entry we have. */
int offset = arg_boot_offset;
sd_id128_t previous_boot_id = SD_ID128_NULL;
for (;;) {
BootId boot;
r = discover_next_boot(j, previous_boot_id, advance_older, &boot);
if (r <= 0)
return r;
previous_boot_id = boot.id;
if (!skip_once)
offset += advance_older ? 1 : -1;
skip_once = false;
if (offset == 0) {
arg_boot_id = boot.id;
return true;
}
}
}
static int get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) {
_cleanup_free_ BootId *boots = NULL;
size_t n_boots = 0;
int r;
assert(j);
assert(ret_boots);
assert(ret_n_boots);
r = sd_journal_seek_head(j); /* seek to oldest */
if (r < 0)
return r;
/* No sd_journal_next()/_previous() here.
*
* At this point the read pointer is positioned before the oldest entry in the whole journal. The
* next invocation of _next() will hence position us at the oldest entry we have. */
sd_id128_t previous_boot_id = SD_ID128_NULL;
for (;;) {
BootId boot;
r = discover_next_boot(j, previous_boot_id, /* advance_older = */ false, &boot);
if (r < 0)
return r;
if (r == 0)
break;
previous_boot_id = boot.id;
FOREACH_ARRAY(i, boots, n_boots)
if (sd_id128_equal(i->id, boot.id))
/* The boot id is already stored, something wrong with the journal files.
* Exiting as otherwise this problem would cause an infinite loop. */
break;
if (!GREEDY_REALLOC(boots, n_boots + 1))
return -ENOMEM;
boots[n_boots++] = boot;
}
*ret_boots = TAKE_PTR(boots);
*ret_n_boots = n_boots;
return n_boots > 0;
}
static int list_boots(sd_journal *j) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ BootId *boots = NULL;
@ -1434,7 +1227,7 @@ static int list_boots(sd_journal *j) {
assert(j);
r = get_boots(j, &boots, &n_boots);
r = journal_get_boots(j, &boots, &n_boots);
if (r < 0)
return log_error_errno(r, "Failed to determine boots: %m");
if (r == 0)
@ -1488,7 +1281,7 @@ static int add_boot(sd_journal *j) {
return add_match_this_boot(j, arg_machine);
if (sd_id128_is_null(arg_boot_id)) {
r = find_boot_by_offset(j);
r = journal_find_boot_by_offset(j, arg_boot_offset, &arg_boot_id);
if (r < 0)
return log_error_errno(r, "Failed to find journal entry from the specified boot offset (%+i): %m",
arg_boot_offset);
@ -1497,7 +1290,7 @@ static int add_boot(sd_journal *j) {
"No journal boot entry found from the specified boot offset (%+i).",
arg_boot_offset);
} else {
r = find_boot_by_id(j);
r = journal_find_boot_by_id(j, arg_boot_id);
if (r < 0)
return log_error_errno(r, "Failed to find journal entry from the specified boot ID (%s): %m",
SD_ID128_TO_STRING(arg_boot_id));

View File

@ -11,6 +11,7 @@
#include "io-util.h"
#include "journal-vacuum.h"
#include "log.h"
#include "logs-show.h"
#include "managed-journal-file.h"
#include "parse-util.h"
#include "rm-rf.h"
@ -19,6 +20,7 @@
/* This program tests skipping around in a multi-file journal. */
static bool arg_keep = false;
static dual_timestamp previous_ts = {};
_noreturn_ static void log_assert_errno(const char *text, int error, const char *file, unsigned line, const char *func) {
log_internal(LOG_CRIT, error, file, line, func,
@ -33,26 +35,34 @@ _noreturn_ static void log_assert_errno(const char *text, int error, const char
log_assert_errno(#expr, -_r_, PROJECT_FILE, __LINE__, __func__); \
} while (false)
static ManagedJournalFile *test_open(const char *name) {
static ManagedJournalFile *test_open_internal(const char *name, JournalFileFlags flags) {
_cleanup_(mmap_cache_unrefp) MMapCache *m = NULL;
ManagedJournalFile *f;
m = mmap_cache_new();
assert_se(m != NULL);
assert_ret(managed_journal_file_open(-1, name, O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, UINT64_MAX, NULL, m, NULL, NULL, &f));
assert_ret(managed_journal_file_open(-1, name, O_RDWR|O_CREAT, flags, 0644, UINT64_MAX, NULL, m, NULL, NULL, &f));
return f;
}
static ManagedJournalFile *test_open(const char *name) {
return test_open_internal(name, JOURNAL_COMPRESS);
}
static ManagedJournalFile *test_open_strict(const char *name) {
return test_open_internal(name, JOURNAL_COMPRESS | JOURNAL_STRICT_ORDER);
}
static void test_close(ManagedJournalFile *f) {
(void) managed_journal_file_close(f);
}
static void append_number(ManagedJournalFile *f, int n, uint64_t *seqnum) {
char *p;
static void append_number(ManagedJournalFile *f, int n, const sd_id128_t *boot_id, uint64_t *seqnum) {
_cleanup_free_ char *p = NULL, *q = NULL;
dual_timestamp ts;
static dual_timestamp previous_ts = {};
struct iovec iovec[1];
struct iovec iovec[2];
size_t n_iov = 0;
dual_timestamp_get(&ts);
@ -65,20 +75,43 @@ static void append_number(ManagedJournalFile *f, int n, uint64_t *seqnum) {
previous_ts = ts;
assert_se(asprintf(&p, "NUMBER=%d", n) >= 0);
iovec[0] = IOVEC_MAKE_STRING(p);
assert_ret(journal_file_append_entry(f->file, &ts, NULL, iovec, 1, seqnum, NULL, NULL, NULL));
free(p);
iovec[n_iov++] = IOVEC_MAKE_STRING(p);
if (boot_id) {
assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id)));
iovec[n_iov++] = IOVEC_MAKE_STRING(q);
}
assert_ret(journal_file_append_entry(f->file, &ts, boot_id, iovec, n_iov, seqnum, NULL, NULL, NULL));
}
static void append_unreferenced_data(ManagedJournalFile *f, const sd_id128_t *boot_id) {
_cleanup_free_ char *q = NULL;
dual_timestamp ts;
struct iovec iovec;
assert(boot_id);
ts.monotonic = usec_sub_unsigned(previous_ts.monotonic, 10);
ts.realtime = usec_sub_unsigned(previous_ts.realtime, 10);
assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id)));
iovec = IOVEC_MAKE_STRING(q);
assert_se(journal_file_append_entry(f->file, &ts, boot_id, &iovec, 1, NULL, NULL, NULL, NULL) == -EREMCHG);
}
static void test_check_number(sd_journal *j, int n) {
sd_id128_t boot_id;
const void *d;
_cleanup_free_ char *k = NULL;
size_t l;
int x;
assert_se(sd_journal_get_monotonic_usec(j, NULL, &boot_id) >= 0);
assert_ret(sd_journal_get_data(j, "NUMBER", &d, &l));
assert_se(k = strndup(d, l));
printf("%s (expected=%i)\n", k, n);
printf("%s %s (expected=%i)\n", SD_ID128_TO_STRING(boot_id), k, n);
assert_se(safe_atoi(k + STRLEN("NUMBER="), &x) >= 0);
assert_se(n == x);
@ -114,19 +147,26 @@ static void test_check_numbers_up(sd_journal *j, int count) {
static void setup_sequential(void) {
ManagedJournalFile *f1, *f2, *f3;
sd_id128_t id;
f1 = test_open("one.journal");
f2 = test_open("two.journal");
f3 = test_open("three.journal");
append_number(f1, 1, NULL);
append_number(f1, 2, NULL);
append_number(f1, 3, NULL);
append_number(f2, 4, NULL);
append_number(f2, 5, NULL);
append_number(f2, 6, NULL);
append_number(f3, 7, NULL);
append_number(f3, 8, NULL);
append_number(f3, 9, NULL);
assert_se(sd_id128_randomize(&id) >= 0);
log_info("boot_id: %s", SD_ID128_TO_STRING(id));
append_number(f1, 1, &id, NULL);
append_number(f1, 2, &id, NULL);
append_number(f1, 3, &id, NULL);
append_number(f2, 4, &id, NULL);
assert_se(sd_id128_randomize(&id) >= 0);
log_info("boot_id: %s", SD_ID128_TO_STRING(id));
append_number(f2, 5, &id, NULL);
append_number(f2, 6, &id, NULL);
append_number(f3, 7, &id, NULL);
append_number(f3, 8, &id, NULL);
assert_se(sd_id128_randomize(&id) >= 0);
log_info("boot_id: %s", SD_ID128_TO_STRING(id));
append_number(f3, 9, &id, NULL);
test_close(f1);
test_close(f2);
test_close(f3);
@ -134,19 +174,53 @@ static void setup_sequential(void) {
static void setup_interleaved(void) {
ManagedJournalFile *f1, *f2, *f3;
sd_id128_t id;
f1 = test_open("one.journal");
f2 = test_open("two.journal");
f3 = test_open("three.journal");
append_number(f1, 1, NULL);
append_number(f2, 2, NULL);
append_number(f3, 3, NULL);
append_number(f1, 4, NULL);
append_number(f2, 5, NULL);
append_number(f3, 6, NULL);
append_number(f1, 7, NULL);
append_number(f2, 8, NULL);
append_number(f3, 9, NULL);
assert_se(sd_id128_randomize(&id) >= 0);
log_info("boot_id: %s", SD_ID128_TO_STRING(id));
append_number(f1, 1, &id, NULL);
append_number(f2, 2, &id, NULL);
append_number(f3, 3, &id, NULL);
append_number(f1, 4, &id, NULL);
append_number(f2, 5, &id, NULL);
append_number(f3, 6, &id, NULL);
append_number(f1, 7, &id, NULL);
append_number(f2, 8, &id, NULL);
append_number(f3, 9, &id, NULL);
test_close(f1);
test_close(f2);
test_close(f3);
}
static void setup_unreferenced_data(void) {
ManagedJournalFile *f1, *f2, *f3;
sd_id128_t id;
/* For issue #29275. */
f1 = test_open_strict("one.journal");
f2 = test_open_strict("two.journal");
f3 = test_open_strict("three.journal");
assert_se(sd_id128_randomize(&id) >= 0);
log_info("boot_id: %s", SD_ID128_TO_STRING(id));
append_number(f1, 1, &id, NULL);
append_number(f1, 2, &id, NULL);
append_number(f1, 3, &id, NULL);
assert_se(sd_id128_randomize(&id) >= 0);
log_info("boot_id: %s", SD_ID128_TO_STRING(id));
append_unreferenced_data(f1, &id);
append_number(f2, 4, &id, NULL);
append_number(f2, 5, &id, NULL);
append_number(f2, 6, &id, NULL);
assert_se(sd_id128_randomize(&id) >= 0);
log_info("boot_id: %s", SD_ID128_TO_STRING(id));
append_unreferenced_data(f2, &id);
append_number(f3, 7, &id, NULL);
append_number(f3, 8, &id, NULL);
append_number(f3, 9, &id, NULL);
test_close(f1);
test_close(f2);
test_close(f3);
@ -320,6 +394,58 @@ TEST(skip) {
test_skip_one(setup_interleaved);
}
static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) {
char t[] = "/var/tmp/journal-boot-id-XXXXXX";
sd_journal *j;
_cleanup_free_ BootId *boots = NULL;
size_t n_boots;
mkdtemp_chdir_chattr(t);
setup();
assert_ret(sd_journal_open_directory(&j, t, 0));
assert_se(journal_get_boots(j, &boots, &n_boots) >= 0);
assert_se(boots);
assert_se(n_boots == n_boots_expected);
sd_journal_close(j);
FOREACH_ARRAY(b, boots, n_boots) {
assert_ret(sd_journal_open_directory(&j, t, 0));
assert_se(journal_find_boot_by_id(j, b->id) == 1);
sd_journal_close(j);
}
for (int i = - (int) n_boots + 1; i <= (int) n_boots; i++) {
sd_id128_t id;
assert_ret(sd_journal_open_directory(&j, t, 0));
assert_se(journal_find_boot_by_offset(j, i, &id) == 1);
if (i <= 0)
assert_se(sd_id128_equal(id, boots[n_boots + i - 1].id));
else
assert_se(sd_id128_equal(id, boots[i - 1].id));
sd_journal_close(j);
}
log_info("Done...");
if (arg_keep)
log_info("Not removing %s", t);
else {
journal_directory_vacuum(".", 3000000, 0, 0, NULL, true);
assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
}
puts("------------------------------------------------------------");
}
TEST(boot_id) {
test_boot_id_one(setup_sequential, 3);
test_boot_id_one(setup_unreferenced_data, 3);
}
static void test_sequence_numbers_one(void) {
_cleanup_(mmap_cache_unrefp) MMapCache *m = NULL;
char t[] = "/var/tmp/journal-seq-XXXXXX";
@ -335,10 +461,10 @@ static void test_sequence_numbers_one(void) {
assert_se(managed_journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644,
UINT64_MAX, NULL, m, NULL, NULL, &one) == 0);
append_number(one, 1, &seqnum);
append_number(one, 1, NULL, &seqnum);
printf("seqnum=%"PRIu64"\n", seqnum);
assert_se(seqnum == 1);
append_number(one, 2, &seqnum);
append_number(one, 2, NULL, &seqnum);
printf("seqnum=%"PRIu64"\n", seqnum);
assert_se(seqnum == 2);
@ -354,24 +480,27 @@ static void test_sequence_numbers_one(void) {
assert_se(two->file->header->state == STATE_ONLINE);
assert_se(!sd_id128_equal(two->file->header->file_id, one->file->header->file_id));
assert_se(sd_id128_equal(one->file->header->machine_id, one->file->header->machine_id));
assert_se(sd_id128_equal(one->file->header->tail_entry_boot_id, one->file->header->tail_entry_boot_id));
assert_se(sd_id128_equal(one->file->header->seqnum_id, one->file->header->seqnum_id));
assert_se(sd_id128_equal(two->file->header->machine_id, one->file->header->machine_id));
assert_se(sd_id128_is_null(two->file->header->tail_entry_boot_id)); /* Not written yet. */
assert_se(sd_id128_equal(two->file->header->seqnum_id, one->file->header->seqnum_id));
append_number(two, 3, &seqnum);
append_number(two, 3, NULL, &seqnum);
printf("seqnum=%"PRIu64"\n", seqnum);
assert_se(seqnum == 3);
append_number(two, 4, &seqnum);
append_number(two, 4, NULL, &seqnum);
printf("seqnum=%"PRIu64"\n", seqnum);
assert_se(seqnum == 4);
/* Verify tail_entry_boot_id. */
assert_se(sd_id128_equal(two->file->header->tail_entry_boot_id, one->file->header->tail_entry_boot_id));
test_close(two);
append_number(one, 5, &seqnum);
append_number(one, 5, NULL, &seqnum);
printf("seqnum=%"PRIu64"\n", seqnum);
assert_se(seqnum == 5);
append_number(one, 6, &seqnum);
append_number(one, 6, NULL, &seqnum);
printf("seqnum=%"PRIu64"\n", seqnum);
assert_se(seqnum == 6);
@ -388,7 +517,7 @@ static void test_sequence_numbers_one(void) {
assert_se(sd_id128_equal(two->file->header->seqnum_id, seqnum_id));
append_number(two, 7, &seqnum);
append_number(two, 7, NULL, &seqnum);
printf("seqnum=%"PRIu64"\n", seqnum);
assert_se(seqnum == 5);

View File

@ -623,10 +623,36 @@ static int journal_file_verify_header(JournalFile *f) {
return -ENODATA;
}
if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset))
if (!offset_is_valid(le64toh(f->header->tail_entry_offset), header_size, tail_object_offset))
if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) {
uint64_t offset = le64toh(f->header->tail_entry_offset);
if (!offset_is_valid(offset, header_size, tail_object_offset))
return -ENODATA;
if (offset > 0) {
/* When there is an entry object, then these fields must be filled. */
if (sd_id128_is_null(f->header->tail_entry_boot_id))
return -ENODATA;
if (!VALID_REALTIME(le64toh(f->header->head_entry_realtime)))
return -ENODATA;
if (!VALID_REALTIME(le64toh(f->header->tail_entry_realtime)))
return -ENODATA;
if (!VALID_MONOTONIC(le64toh(f->header->tail_entry_realtime)))
return -ENODATA;
} else {
/* Otherwise, the fields must be zero. */
if (JOURNAL_HEADER_TAIL_ENTRY_BOOT_ID(f->header) &&
!sd_id128_is_null(f->header->tail_entry_boot_id))
return -ENODATA;
if (f->header->head_entry_realtime != 0)
return -ENODATA;
if (f->header->tail_entry_realtime != 0)
return -ENODATA;
if (f->header->tail_entry_realtime != 0)
return -ENODATA;
}
}
/* Verify number of objects */
uint64_t n_objects = le64toh(f->header->n_objects);
if (n_objects > arena_size / sizeof(ObjectHeader))
@ -2272,6 +2298,8 @@ static int journal_file_append_entry_internal(
assert(f);
assert(f->header);
assert(ts);
assert(boot_id);
assert(!sd_id128_is_null(*boot_id));
assert(items || n_items == 0);
if (f->strict_order) {
@ -2292,7 +2320,7 @@ static int journal_file_append_entry_internal(
"timestamp %" PRIu64 ", refusing entry.",
ts->realtime, le64toh(f->header->tail_entry_realtime));
if ((!boot_id || sd_id128_equal(*boot_id, f->header->tail_entry_boot_id)) &&
if (sd_id128_equal(*boot_id, f->header->tail_entry_boot_id) &&
ts->monotonic < le64toh(f->header->tail_entry_monotonic))
return log_debug_errno(
SYNTHETIC_ERRNO(ENOTNAM),
@ -2332,9 +2360,7 @@ static int journal_file_append_entry_internal(
o->entry.realtime = htole64(ts->realtime);
o->entry.monotonic = htole64(ts->monotonic);
o->entry.xor_hash = htole64(xor_hash);
if (boot_id)
f->header->tail_entry_boot_id = *boot_id;
o->entry.boot_id = f->header->tail_entry_boot_id;
o->entry.boot_id = f->header->tail_entry_boot_id = *boot_id;
for (size_t i = 0; i < n_items; i++)
write_entry_item(f, o, i, &items[i]);
@ -2503,7 +2529,10 @@ int journal_file_append_entry(
ts = &_ts;
}
if (!boot_id) {
if (boot_id) {
if (sd_id128_is_null(*boot_id))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Empty boot ID, refusing entry.");
} else {
r = sd_id128_get_boot(&_boot_id);
if (r < 0)
return r;
@ -3322,10 +3351,8 @@ int journal_file_move_to_entry_by_monotonic(
assert(f);
r = find_data_object_by_boot_id(f, boot_id, &o, NULL);
if (r < 0)
if (r <= 0)
return r;
if (r == 0)
return -ENOENT;
return generic_array_bisect_plus_one(
f,
@ -3517,10 +3544,8 @@ int journal_file_move_to_entry_by_monotonic_for_data(
/* First, seek by time */
r = find_data_object_by_boot_id(f, boot_id, &o, &b);
if (r < 0)
if (r <= 0)
return r;
if (r == 0)
return -ENOENT;
r = generic_array_bisect_plus_one(f,
le64toh(o->data.entry_offset),

View File

@ -667,7 +667,7 @@ static int find_location_for_match(
return journal_file_move_to_entry_by_seqnum_for_data(f, d, j->current_location.seqnum, direction, ret, offset);
if (j->current_location.monotonic_set) {
r = journal_file_move_to_entry_by_monotonic_for_data(f, d, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset);
if (r != -ENOENT)
if (r != 0)
return r;
/* The data object might have been invalidated. */
@ -762,7 +762,7 @@ static int find_location_with_matches(
return journal_file_move_to_entry_by_seqnum(f, j->current_location.seqnum, direction, ret, offset);
if (j->current_location.monotonic_set) {
r = journal_file_move_to_entry_by_monotonic(f, j->current_location.boot_id, j->current_location.monotonic, direction, ret, offset);
if (r != -ENOENT)
if (r != 0)
return r;
}
if (j->current_location.realtime_set)
@ -2445,14 +2445,6 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) {
mo = le64toh(f->header->tail_entry_monotonic);
rt = le64toh(f->header->tail_entry_realtime);
id = f->header->tail_entry_boot_id;
/* Some superficial checking if what we read makes sense. Note that we only do this
* when reading the timestamps from the Header object, but not when reading them from
* the most recent entry object, because in that case journal_file_move_to_object()
* already validated them. */
if (!VALID_MONOTONIC(mo) || !VALID_REALTIME(rt))
return -ENODATA;
} else {
/* Otherwise let's find the last entry manually (this possibly means traversing the
* chain of entry arrays, till the end */

View File

@ -1827,3 +1827,203 @@ int show_journal_by_unit(
return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized);
}
static int discover_next_boot(
sd_journal *j,
sd_id128_t previous_boot_id,
bool advance_older,
BootId *ret) {
BootId boot;
int r;
assert(j);
assert(ret);
/* 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. */
/* 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);
do {
r = sd_journal_step_one(j, !advance_older);
if (r < 0)
return r;
if (r == 0) {
*ret = (BootId) {};
return 0; /* End of journal, yay. */
}
r = sd_journal_get_monotonic_usec(j, NULL, &boot.id);
if (r < 0)
return r;
/* We iterate through this in a loop, until the boot ID differs from the previous one. Note that
* normally, this will only require a single iteration, as we moved to the last entry of the previous
* boot entry already. However, it might happen that the per-journal-field entry arrays are less
* complete than the main entry array, and hence might reference an entry that's not actually the last
* one of the boot ID as last one. Let's hence use the per-field array is initial seek position to
* speed things up, but let's not trust that it is complete, and hence, manually advance as
* necessary. */
} while (sd_id128_equal(boot.id, previous_boot_id));
r = sd_journal_get_realtime_usec(j, &boot.first_usec);
if (r < 0)
return r;
/* Now seek to the last occurrence of this boot ID. */
r = add_match_boot_id(j, boot.id);
if (r < 0)
return r;
if (advance_older)
r = sd_journal_seek_head(j);
else
r = sd_journal_seek_tail(j);
if (r < 0)
return r;
r = sd_journal_step_one(j, advance_older);
if (r < 0)
return r;
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA),
"Whoopsie! We found a boot ID but can't read its last entry."); /* This shouldn't happen. We just came from this very boot ID. */
r = sd_journal_get_realtime_usec(j, &boot.last_usec);
if (r < 0)
return r;
sd_journal_flush_matches(j);
*ret = boot;
return 1;
}
int journal_find_boot_by_id(sd_journal *j, sd_id128_t boot_id) {
int r;
assert(j);
assert(!sd_id128_is_null(boot_id));
sd_journal_flush_matches(j);
r = add_match_boot_id(j, boot_id);
if (r < 0)
return r;
r = sd_journal_seek_head(j); /* seek to oldest */
if (r < 0)
return r;
r = sd_journal_next(j); /* read the oldest entry */
if (r < 0)
return r;
/* At this point the read pointer is positioned at the oldest occurrence of the reference boot ID.
* After flushing the matches, one more invocation of _previous() will hence place us at the
* following entry, which must then have an older boot ID */
sd_journal_flush_matches(j);
return r > 0;
}
int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) {
bool advance_older;
int r;
assert(j);
assert(ret);
/* Adjust for the asymmetry that offset 0 is the last (and current) boot, while 1 is considered the
* (chronological) first boot in the journal. */
advance_older = offset <= 0;
if (advance_older)
r = sd_journal_seek_tail(j); /* seek to newest */
else
r = sd_journal_seek_head(j); /* seek to oldest */
if (r < 0)
return r;
/* No sd_journal_next()/_previous() here.
*
* At this point the read pointer is positioned after the newest/before the oldest entry in the whole
* journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest
* entry we have. */
sd_id128_t boot_id = SD_ID128_NULL;
for (int off = !advance_older; ; off += advance_older ? -1 : 1) {
BootId boot;
r = discover_next_boot(j, boot_id, advance_older, &boot);
if (r < 0)
return r;
if (r == 0) {
*ret = SD_ID128_NULL;
return false;
}
boot_id = boot.id;
log_debug("Found boot ID %s by offset %i", SD_ID128_TO_STRING(boot_id), off);
if (off == offset)
break;
}
*ret = boot_id;
return true;
}
int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) {
_cleanup_free_ BootId *boots = NULL;
size_t n_boots = 0;
int r;
assert(j);
assert(ret_boots);
assert(ret_n_boots);
r = sd_journal_seek_head(j); /* seek to oldest */
if (r < 0)
return r;
/* No sd_journal_next()/_previous() here.
*
* At this point the read pointer is positioned before the oldest entry in the whole journal. The
* next invocation of _next() will hence position us at the oldest entry we have. */
sd_id128_t previous_boot_id = SD_ID128_NULL;
for (;;) {
BootId boot;
r = discover_next_boot(j, previous_boot_id, /* advance_older = */ false, &boot);
if (r < 0)
return r;
if (r == 0)
break;
previous_boot_id = boot.id;
FOREACH_ARRAY(i, boots, n_boots)
if (sd_id128_equal(i->id, boot.id))
/* The boot id is already stored, something wrong with the journal files.
* Exiting as otherwise this problem would cause an infinite loop. */
break;
if (!GREEDY_REALLOC(boots, n_boots + 1))
return -ENOMEM;
boots[n_boots++] = boot;
}
*ret_boots = TAKE_PTR(boots);
*ret_n_boots = n_boots;
return n_boots > 0;
}

View File

@ -13,6 +13,12 @@
#include "output-mode.h"
#include "time-util.h"
typedef struct BootId {
sd_id128_t id;
usec_t first_usec;
usec_t last_usec;
} BootId;
int show_journal_entry(
FILE *f,
sd_journal *j,
@ -65,3 +71,7 @@ void json_escape(
const char* p,
size_t l,
OutputFlags flags);
int journal_find_boot_by_id(sd_journal *j, sd_id128_t boot_id);
int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret);
int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots);

View File

@ -0,0 +1 @@
../TEST-01-BASIC/Makefile

10
test/TEST-09-REBOOT/test.sh Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -e
TEST_DESCRIPTION="Test various scenarios involving (multiple) machine reboots"
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
do_test "$@"

View File

@ -3311,6 +3311,9 @@ test_run() {
local test_id="${1:?}"
mount_initdir
# Reset the boot counter, if present
rm -f "${initdir:?}/var/tmp/.systemd_reboot_count"
if ! get_bool "${TEST_NO_QEMU:=}"; then
if run_qemu "$test_id"; then
check_result_qemu || { echo "qemu test failed"; return 1; }

View File

@ -0,0 +1,72 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
get_first_boot_id() {
journalctl -b "${1:?}" -o json | jq -sr '.[0]._BOOT_ID'
}
get_last_boot_id() {
journalctl -b "${1:?}" -o json -n 1 | jq -r '._BOOT_ID'
}
get_first_timestamp() {
journalctl -b "${1:?}" -o json | jq -sr '.[0].__REALTIME_TIMESTAMP'
}
get_last_timestamp() {
journalctl -b "${1:?}" -o json -n 1 | jq -r '.__REALTIME_TIMESTAMP'
}
# Issue: #29275, second part
# Now let's check if the boot entries are in the correct/expected order
index=0
SYSTEMD_LOG_LEVEL=debug journalctl --list-boots
journalctl --list-boots -o json | jq -r '.[] | [.index, .boot_id, .first_entry, .last_entry] | @tsv' |
while read -r offset boot_id first_ts last_ts; do
: "Boot #$((++index)) ($offset) with ID $boot_id"
# Try the "regular" (non-json) variants first, as they provide a helpful
# error message if something is not right
SYSTEMD_LOG_LEVEL=debug journalctl -q -n 0 -b "$index"
SYSTEMD_LOG_LEVEL=debug journalctl -q -n 0 -b "$offset"
SYSTEMD_LOG_LEVEL=debug journalctl -q -n 0 -b "$boot_id"
# Check the boot ID of the first entry
entry_boot_id="$(get_first_boot_id "$index")"
assert_eq "$entry_boot_id" "$boot_id"
entry_boot_id="$(get_first_boot_id "$offset")"
assert_eq "$entry_boot_id" "$boot_id"
entry_boot_id="$(get_first_boot_id "$boot_id")"
assert_eq "$entry_boot_id" "$boot_id"
# Check the timestamp of the first entry
entry_ts="$(get_first_timestamp "$index")"
assert_eq "$entry_ts" "$first_ts"
entry_ts="$(get_first_timestamp "$offset")"
assert_eq "$entry_ts" "$first_ts"
entry_ts="$(get_first_timestamp "$boot_id")"
assert_eq "$entry_ts" "$first_ts"
# Check the boot ID of the last entry
entry_boot_id="$(get_last_boot_id "$index")"
assert_eq "$entry_boot_id" "$boot_id"
entry_boot_id="$(get_last_boot_id "$offset")"
assert_eq "$entry_boot_id" "$boot_id"
entry_boot_id="$(get_last_boot_id "$boot_id")"
assert_eq "$entry_boot_id" "$boot_id"
# Check the timestamp of the last entry
if [[ "$offset" != "0" ]]; then
entry_ts="$(get_last_timestamp "$index")"
assert_eq "$entry_ts" "$last_ts"
entry_ts="$(get_last_timestamp "$offset")"
assert_eq "$entry_ts" "$last_ts"
entry_ts="$(get_last_timestamp "$boot_id")"
assert_eq "$entry_ts" "$last_ts"
fi
done

View File

@ -0,0 +1,9 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Unit]
Description=TEST-09-REBOOT
After=multi-user.target
[Service]
ExecStartPre=rm -f /failed /testok
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
Type=oneshot

25
test/units/testsuite-09.sh Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
NUM_REBOOT=4
# shellcheck source=test/units/test-control.sh
. "$(dirname "$0")"/test-control.sh
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
systemd-cat echo "Reboot count: $REBOOT_COUNT"
systemd-cat journalctl --list-boots
run_subtests
if [[ "$REBOOT_COUNT" -lt "$NUM_REBOOT" ]]; then
systemctl_final reboot
elif [[ "$REBOOT_COUNT" -gt "$NUM_REBOOT" ]]; then
assert_not_reached
fi
touch /testok

View File

@ -3,6 +3,9 @@
# Utility functions for shell tests
# shellcheck disable=SC2034
[[ -e /var/tmp/.systemd_reboot_count ]] && REBOOT_COUNT="$(</var/tmp/.systemd_reboot_count)" || REBOOT_COUNT=0
assert_true() {(
set +ex
@ -16,7 +19,6 @@ assert_true() {(
fi
)}
assert_eq() {(
set +ex
@ -57,6 +59,11 @@ assert_rc() {(
assert_eq "$rc" "$exp"
)}
assert_not_reached() {
echo >&2 "Code should not be reached at ${BASH_SOURCE[1]}:${BASH_LINENO[1]}, function ${FUNCNAME[1]}()"
exit 1
}
run_and_grep() {(
set +ex
@ -149,3 +156,18 @@ create_dummy_container() {
cp -a /testsuite-13-container-template/* "$root"
coverage_create_nspawn_dropin "$root"
}
# Bump the reboot counter and call systemctl with the given arguments
systemctl_final() {
local counter
if [[ $# -eq 0 ]]; then
echo >&2 "Missing arguments"
exit 1
fi
[[ -e /var/tmp/.systemd_reboot_count ]] && counter="$(</var/tmp/.systemd_reboot_count)" || counter=0
echo "$((counter + 1))" >/var/tmp/.systemd_reboot_count
systemctl "$@"
}