sd-journal: prefer exact cursor match after seek (#43297)

Fixes #31516.
This commit is contained in:
Yu Watanabe
2026-08-06 05:48:05 +09:00
committed by GitHub
2 changed files with 106 additions and 1 deletions
+16 -1
View File
@@ -1127,7 +1127,7 @@ static int compare_locations(sd_journal *j, JournalFile *af, JournalFile *bf) {
}
static int real_journal_next(sd_journal *j, direction_t direction) {
JournalFile *new_file = NULL;
JournalFile *new_file = NULL, *exact_match = NULL;
unsigned n_files;
const void **files;
Object *o;
@@ -1163,8 +1163,23 @@ static int real_journal_next(sd_journal *j, direction_t direction) {
if (found)
new_file = f;
/* Track the file that holds the cursor's exact entry (matching seqnum_id and seqnum). On
* systems without a reliable (or missing) RTC, compare_boot_ids() can produce incorrect
* cross-boot ordering causing compare_locations() above to prefer a wrong file. We detect
* this after the loop and override the choice if needed.
*
* See https://github.com/systemd/systemd/issues/31516 */
if (j->current_location.type == LOCATION_SEEK &&
j->current_location.seqnum_set &&
sd_id128_equal(f->header->seqnum_id, j->current_location.seqnum_id) &&
f->current_seqnum == j->current_location.seqnum)
exact_match = f;
}
if (exact_match)
new_file = exact_match;
if (!new_file)
return 0;
@@ -262,6 +262,18 @@ static void test_cursor(sd_journal *j) {
ASSERT_OK(sd_journal_next(j));
ASSERT_OK_POSITIVE(sd_journal_test_cursor(j, *c));
}
STRV_FOREACH(c, cursors) {
ASSERT_OK(sd_journal_seek_cursor(j, *c));
ASSERT_OK(sd_journal_previous(j));
ASSERT_OK_POSITIVE(sd_journal_test_cursor(j, *c));
}
ASSERT_OK(sd_journal_seek_tail(j));
STRV_FOREACH_BACKWARDS(c, cursors) {
ASSERT_OK(sd_journal_previous(j));
ASSERT_OK_POSITIVE(sd_journal_test_cursor(j, *c));
}
}
static void test_skip_one(void (*setup)(void)) {
@@ -1328,6 +1340,84 @@ TEST(seek_time) {
}
}
static void append_number_at(
JournalFile *f,
unsigned n,
const sd_id128_t *boot_id,
uint64_t realtime,
uint64_t monotonic) {
_cleanup_free_ char *p = NULL, *q = NULL;
dual_timestamp ts = { .realtime = realtime, .monotonic = monotonic };
struct iovec iovec[2];
size_t n_iov = 0;
ASSERT_OK(asprintf(&p, "NUMBER=%u", n));
iovec[n_iov++] = IOVEC_MAKE_STRING(p);
if (boot_id) {
ASSERT_NOT_NULL((q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id))));
iovec[n_iov++] = IOVEC_MAKE_STRING(q);
}
ASSERT_OK(journal_file_append_entry(f, &ts, boot_id, iovec, n_iov, NULL, NULL, NULL, NULL));
}
TEST(cursor_broken_rtc) {
/* Reproducer for https://github.com/systemd/systemd/issues/31516.
*
* On systems without a reliable (or missing) RTC, the realtime clock can go backwards across
* reboots. This creates journal files where a later boot's first entries have lower realtime
* timestamps than the previous boot's last entries.
*
* compare_boot_ids() orders boots by their newest realtime timestamp, so it considers boot 1
* "earlier" than boot 2 (since boot 2 eventually catches up). But when seeking to a cursor pointing
* at boot 2's first entry (which has low realtime), the realtime-based positioning in boot 1's file
* finds a nearby entry, and compare_locations() (via compare_boot_ids()) incorrectly picks that
* entry because boot 1 is globally "earlier".
*
* A minimal setup for such scenario - 2 files with 3 entries:
*
* one.journal (boot 1): entry at realtime=2s
* two.journal (boot 2): entry at realtime=1s (earlier than boot 1), then entry at realtime=3s */
_cleanup_(test_donep) char *t = NULL;
_cleanup_(sd_journal_closep) sd_journal *j = NULL;
mkdtemp_chdir_chattr("/var/tmp/journal-cursor-rtc-XXXXXX", &t);
{
_cleanup_(journal_file_offline_closep) JournalFile *f1 = NULL, *f2 = NULL;
sd_id128_t boot1, boot2;
f1 = test_open("one.journal");
f2 = test_open("two.journal");
ASSERT_OK(sd_id128_randomize(&boot1));
ASSERT_OK(sd_id128_randomize(&boot2));
log_info("boot1: %s, boot2: %s",
SD_ID128_TO_STRING(boot1), SD_ID128_TO_STRING(boot2));
append_number_at(f1, 1, &boot1, 2 * USEC_PER_SEC, 100 * USEC_PER_MSEC);
append_number_at(f2, 2, &boot2, 1 * USEC_PER_SEC, 100 * USEC_PER_MSEC);
append_number_at(f2, 3, &boot2, 3 * USEC_PER_SEC, 200 * USEC_PER_MSEC);
}
ASSERT_OK(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE));
/* Test cursor seeking */
test_cursor(j);
/* Also verify forward and backward iterations */
ASSERT_OK(sd_journal_seek_head(j));
ASSERT_OK_POSITIVE(sd_journal_next(j));
test_check_numbers_down(j, 3);
ASSERT_OK(sd_journal_seek_tail(j));
ASSERT_OK_POSITIVE(sd_journal_previous(j));
test_check_numbers_up(j, 3);
}
static int intro(void) {
/* journal_file_open() requires a valid machine id */
if (access("/etc/machine-id", F_OK) != 0)