1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "sd-journal.h"
9 #include "alloc-util.h"
10 #include "chattr-util.h"
12 #include "journal-file-util.h"
13 #include "journal-vacuum.h"
15 #include "logs-show.h"
16 #include "parse-util.h"
20 /* This program tests skipping around in a multi-file journal. */
22 static bool arg_keep
= false;
23 static dual_timestamp previous_ts
= {};
25 _noreturn_
static void log_assert_errno(const char *text
, int error
, const char *file
, unsigned line
, const char *func
) {
26 log_internal(LOG_CRIT
, error
, file
, line
, func
,
27 "'%s' failed at %s:%u (%s): %m", text
, file
, line
, func
);
31 #define assert_ret(expr) \
34 if (_unlikely_(_r_ < 0)) \
35 log_assert_errno(#expr, -_r_, PROJECT_FILE, __LINE__, __func__); \
38 static JournalFile
*test_open_internal(const char *name
, JournalFileFlags flags
) {
39 _cleanup_(mmap_cache_unrefp
) MMapCache
*m
= NULL
;
45 assert_ret(journal_file_open(-1, name
, O_RDWR
|O_CREAT
, flags
, 0644, UINT64_MAX
, NULL
, m
, NULL
, &f
));
49 static JournalFile
*test_open(const char *name
) {
50 return test_open_internal(name
, JOURNAL_COMPRESS
);
53 static JournalFile
*test_open_strict(const char *name
) {
54 return test_open_internal(name
, JOURNAL_COMPRESS
| JOURNAL_STRICT_ORDER
);
57 static void test_close(JournalFile
*f
) {
58 (void) journal_file_offline_close(f
);
61 static void append_number(JournalFile
*f
, int n
, const sd_id128_t
*boot_id
, uint64_t *seqnum
) {
62 _cleanup_free_
char *p
= NULL
, *q
= NULL
;
64 struct iovec iovec
[2];
67 dual_timestamp_get(&ts
);
69 if (ts
.monotonic
<= previous_ts
.monotonic
)
70 ts
.monotonic
= previous_ts
.monotonic
+ 1;
72 if (ts
.realtime
<= previous_ts
.realtime
)
73 ts
.realtime
= previous_ts
.realtime
+ 1;
77 assert_se(asprintf(&p
, "NUMBER=%d", n
) >= 0);
78 iovec
[n_iov
++] = IOVEC_MAKE_STRING(p
);
81 assert_se(q
= strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id
)));
82 iovec
[n_iov
++] = IOVEC_MAKE_STRING(q
);
85 assert_ret(journal_file_append_entry(f
, &ts
, boot_id
, iovec
, n_iov
, seqnum
, NULL
, NULL
, NULL
));
88 static void append_unreferenced_data(JournalFile
*f
, const sd_id128_t
*boot_id
) {
89 _cleanup_free_
char *q
= NULL
;
95 ts
.monotonic
= usec_sub_unsigned(previous_ts
.monotonic
, 10);
96 ts
.realtime
= usec_sub_unsigned(previous_ts
.realtime
, 10);
98 assert_se(q
= strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id
)));
99 iovec
= IOVEC_MAKE_STRING(q
);
101 assert_se(journal_file_append_entry(f
, &ts
, boot_id
, &iovec
, 1, NULL
, NULL
, NULL
, NULL
) == -EREMCHG
);
104 static void test_check_number(sd_journal
*j
, int n
) {
107 _cleanup_free_
char *k
= NULL
;
111 assert_se(sd_journal_get_monotonic_usec(j
, NULL
, &boot_id
) >= 0);
112 assert_ret(sd_journal_get_data(j
, "NUMBER", &d
, &l
));
113 assert_se(k
= strndup(d
, l
));
114 printf("%s %s (expected=%i)\n", SD_ID128_TO_STRING(boot_id
), k
, n
);
116 assert_se(safe_atoi(k
+ STRLEN("NUMBER="), &x
) >= 0);
120 static void test_check_numbers_down(sd_journal
*j
, int count
) {
123 for (i
= 1; i
<= count
; i
++) {
125 test_check_number(j
, i
);
126 assert_ret(r
= sd_journal_next(j
));
135 static void test_check_numbers_up(sd_journal
*j
, int count
) {
136 for (int i
= count
; i
>= 1; i
--) {
138 test_check_number(j
, i
);
139 assert_ret(r
= sd_journal_previous(j
));
148 static void setup_sequential(void) {
149 JournalFile
*f1
, *f2
, *f3
;
152 f1
= test_open("one.journal");
153 f2
= test_open("two.journal");
154 f3
= test_open("three.journal");
155 assert_se(sd_id128_randomize(&id
) >= 0);
156 log_info("boot_id: %s", SD_ID128_TO_STRING(id
));
157 append_number(f1
, 1, &id
, NULL
);
158 append_number(f1
, 2, &id
, NULL
);
159 append_number(f1
, 3, &id
, NULL
);
160 append_number(f2
, 4, &id
, NULL
);
161 assert_se(sd_id128_randomize(&id
) >= 0);
162 log_info("boot_id: %s", SD_ID128_TO_STRING(id
));
163 append_number(f2
, 5, &id
, NULL
);
164 append_number(f2
, 6, &id
, NULL
);
165 append_number(f3
, 7, &id
, NULL
);
166 append_number(f3
, 8, &id
, NULL
);
167 assert_se(sd_id128_randomize(&id
) >= 0);
168 log_info("boot_id: %s", SD_ID128_TO_STRING(id
));
169 append_number(f3
, 9, &id
, NULL
);
175 static void setup_interleaved(void) {
176 JournalFile
*f1
, *f2
, *f3
;
179 f1
= test_open("one.journal");
180 f2
= test_open("two.journal");
181 f3
= test_open("three.journal");
182 assert_se(sd_id128_randomize(&id
) >= 0);
183 log_info("boot_id: %s", SD_ID128_TO_STRING(id
));
184 append_number(f1
, 1, &id
, NULL
);
185 append_number(f2
, 2, &id
, NULL
);
186 append_number(f3
, 3, &id
, NULL
);
187 append_number(f1
, 4, &id
, NULL
);
188 append_number(f2
, 5, &id
, NULL
);
189 append_number(f3
, 6, &id
, NULL
);
190 append_number(f1
, 7, &id
, NULL
);
191 append_number(f2
, 8, &id
, NULL
);
192 append_number(f3
, 9, &id
, NULL
);
198 static void setup_unreferenced_data(void) {
199 JournalFile
*f1
, *f2
, *f3
;
202 /* For issue #29275. */
204 f1
= test_open_strict("one.journal");
205 f2
= test_open_strict("two.journal");
206 f3
= test_open_strict("three.journal");
207 assert_se(sd_id128_randomize(&id
) >= 0);
208 log_info("boot_id: %s", SD_ID128_TO_STRING(id
));
209 append_number(f1
, 1, &id
, NULL
);
210 append_number(f1
, 2, &id
, NULL
);
211 append_number(f1
, 3, &id
, NULL
);
212 assert_se(sd_id128_randomize(&id
) >= 0);
213 log_info("boot_id: %s", SD_ID128_TO_STRING(id
));
214 append_unreferenced_data(f1
, &id
);
215 append_number(f2
, 4, &id
, NULL
);
216 append_number(f2
, 5, &id
, NULL
);
217 append_number(f2
, 6, &id
, NULL
);
218 assert_se(sd_id128_randomize(&id
) >= 0);
219 log_info("boot_id: %s", SD_ID128_TO_STRING(id
));
220 append_unreferenced_data(f2
, &id
);
221 append_number(f3
, 7, &id
, NULL
);
222 append_number(f3
, 8, &id
, NULL
);
223 append_number(f3
, 9, &id
, NULL
);
229 static void mkdtemp_chdir_chattr(char *path
) {
230 assert_se(mkdtemp(path
));
231 assert_se(chdir(path
) >= 0);
233 /* Speed up things a bit on btrfs, ensuring that CoW is turned off for all files created in our
234 * directory during the test run */
235 (void) chattr_path(path
, FS_NOCOW_FL
, FS_NOCOW_FL
, NULL
);
238 static void test_skip_one(void (*setup
)(void)) {
239 char t
[] = "/var/tmp/journal-skip-XXXXXX";
243 mkdtemp_chdir_chattr(t
);
247 /* Seek to head, iterate down. */
248 assert_ret(sd_journal_open_directory(&j
, t
, 0));
249 assert_ret(sd_journal_seek_head(j
));
250 assert_se(sd_journal_next(j
) == 1); /* pointing to the first entry */
251 test_check_numbers_down(j
, 9);
254 /* Seek to head, iterate down. */
255 assert_ret(sd_journal_open_directory(&j
, t
, 0));
256 assert_ret(sd_journal_seek_head(j
));
257 assert_se(sd_journal_next(j
) == 1); /* pointing to the first entry */
258 assert_se(sd_journal_previous(j
) == 0); /* no-op */
259 test_check_numbers_down(j
, 9);
262 /* Seek to head twice, iterate down. */
263 assert_ret(sd_journal_open_directory(&j
, t
, 0));
264 assert_ret(sd_journal_seek_head(j
));
265 assert_se(sd_journal_next(j
) == 1); /* pointing to the first entry */
266 assert_ret(sd_journal_seek_head(j
));
267 assert_se(sd_journal_next(j
) == 1); /* pointing to the first entry */
268 test_check_numbers_down(j
, 9);
271 /* Seek to head, move to previous, then iterate down. */
272 assert_ret(sd_journal_open_directory(&j
, t
, 0));
273 assert_ret(sd_journal_seek_head(j
));
274 assert_se(sd_journal_previous(j
) == 0); /* no-op */
275 assert_se(sd_journal_next(j
) == 1); /* pointing to the first entry */
276 test_check_numbers_down(j
, 9);
279 /* Seek to head, walk several steps, then iterate down. */
280 assert_ret(sd_journal_open_directory(&j
, t
, 0));
281 assert_ret(sd_journal_seek_head(j
));
282 assert_se(sd_journal_previous(j
) == 0); /* no-op */
283 assert_se(sd_journal_previous(j
) == 0); /* no-op */
284 assert_se(sd_journal_previous(j
) == 0); /* no-op */
285 assert_se(sd_journal_next(j
) == 1); /* pointing to the first entry */
286 assert_se(sd_journal_previous(j
) == 0); /* no-op */
287 assert_se(sd_journal_previous(j
) == 0); /* no-op */
288 test_check_numbers_down(j
, 9);
291 /* Seek to tail, iterate up. */
292 assert_ret(sd_journal_open_directory(&j
, t
, 0));
293 assert_ret(sd_journal_seek_tail(j
));
294 assert_se(sd_journal_previous(j
) == 1); /* pointing to the last entry */
295 test_check_numbers_up(j
, 9);
298 /* Seek to tail twice, iterate up. */
299 assert_ret(sd_journal_open_directory(&j
, t
, 0));
300 assert_ret(sd_journal_seek_tail(j
));
301 assert_se(sd_journal_previous(j
) == 1); /* pointing to the last entry */
302 assert_ret(sd_journal_seek_tail(j
));
303 assert_se(sd_journal_previous(j
) == 1); /* pointing to the last entry */
304 test_check_numbers_up(j
, 9);
307 /* Seek to tail, move to next, then iterate up. */
308 assert_ret(sd_journal_open_directory(&j
, t
, 0));
309 assert_ret(sd_journal_seek_tail(j
));
310 assert_se(sd_journal_next(j
) == 0); /* no-op */
311 assert_se(sd_journal_previous(j
) == 1); /* pointing to the last entry */
312 test_check_numbers_up(j
, 9);
315 /* Seek to tail, walk several steps, then iterate up. */
316 assert_ret(sd_journal_open_directory(&j
, t
, 0));
317 assert_ret(sd_journal_seek_tail(j
));
318 assert_se(sd_journal_next(j
) == 0); /* no-op */
319 assert_se(sd_journal_next(j
) == 0); /* no-op */
320 assert_se(sd_journal_next(j
) == 0); /* no-op */
321 assert_se(sd_journal_previous(j
) == 1); /* pointing to the last entry. */
322 assert_se(sd_journal_next(j
) == 0); /* no-op */
323 assert_se(sd_journal_next(j
) == 0); /* no-op */
324 test_check_numbers_up(j
, 9);
327 /* Seek to tail, skip to head, iterate down. */
328 assert_ret(sd_journal_open_directory(&j
, t
, 0));
329 assert_ret(sd_journal_seek_tail(j
));
330 assert_se(sd_journal_previous_skip(j
, 9) == 9); /* pointing to the first entry. */
331 test_check_numbers_down(j
, 9);
334 /* Seek to tail, skip to head in a more complex way, then iterate down. */
335 assert_ret(sd_journal_open_directory(&j
, t
, 0));
336 assert_ret(sd_journal_seek_tail(j
));
337 assert_se(sd_journal_next(j
) == 0);
338 assert_se(sd_journal_previous_skip(j
, 4) == 4);
339 assert_se(sd_journal_previous_skip(j
, 5) == 5);
340 assert_se(sd_journal_previous(j
) == 0);
341 assert_se(sd_journal_previous_skip(j
, 5) == 0);
342 assert_se(sd_journal_next(j
) == 1);
343 assert_se(sd_journal_previous_skip(j
, 5) == 1);
344 assert_se(sd_journal_next(j
) == 1);
345 assert_se(sd_journal_next(j
) == 1);
346 assert_se(sd_journal_previous(j
) == 1);
347 assert_se(sd_journal_next(j
) == 1);
348 assert_se(sd_journal_next(j
) == 1);
349 assert_se(sd_journal_previous_skip(j
, 5) == 3);
350 test_check_numbers_down(j
, 9);
353 /* Seek to head, skip to tail, iterate up. */
354 assert_ret(sd_journal_open_directory(&j
, t
, 0));
355 assert_ret(sd_journal_seek_head(j
));
356 assert_se(sd_journal_next_skip(j
, 9) == 9);
357 test_check_numbers_up(j
, 9);
360 /* Seek to head, skip to tail in a more complex way, then iterate up. */
361 assert_ret(sd_journal_open_directory(&j
, t
, 0));
362 assert_ret(sd_journal_seek_head(j
));
363 assert_se(sd_journal_previous(j
) == 0);
364 assert_se(sd_journal_next_skip(j
, 4) == 4);
365 assert_se(sd_journal_next_skip(j
, 5) == 5);
366 assert_se(sd_journal_next(j
) == 0);
367 assert_se(sd_journal_next_skip(j
, 5) == 0);
368 assert_se(sd_journal_previous(j
) == 1);
369 assert_se(sd_journal_next_skip(j
, 5) == 1);
370 assert_se(sd_journal_previous(j
) == 1);
371 assert_se(sd_journal_previous(j
) == 1);
372 assert_se(sd_journal_next(j
) == 1);
373 assert_se(sd_journal_previous(j
) == 1);
374 assert_se(sd_journal_previous(j
) == 1);
375 assert_se(r
= sd_journal_next_skip(j
, 5) == 3);
376 test_check_numbers_up(j
, 9);
382 log_info("Not removing %s", t
);
384 journal_directory_vacuum(".", 3000000, 0, 0, NULL
, true);
386 assert_se(rm_rf(t
, REMOVE_ROOT
|REMOVE_PHYSICAL
) >= 0);
389 puts("------------------------------------------------------------");
393 test_skip_one(setup_sequential
);
394 test_skip_one(setup_interleaved
);
397 static void test_boot_id_one(void (*setup
)(void), size_t n_boots_expected
) {
398 char t
[] = "/var/tmp/journal-boot-id-XXXXXX";
400 _cleanup_free_ BootId
*boots
= NULL
;
403 mkdtemp_chdir_chattr(t
);
407 assert_ret(sd_journal_open_directory(&j
, t
, 0));
408 assert_se(journal_get_boots(j
, &boots
, &n_boots
) >= 0);
410 assert_se(n_boots
== n_boots_expected
);
413 FOREACH_ARRAY(b
, boots
, n_boots
) {
414 assert_ret(sd_journal_open_directory(&j
, t
, 0));
415 assert_se(journal_find_boot_by_id(j
, b
->id
) == 1);
419 for (int i
= - (int) n_boots
+ 1; i
<= (int) n_boots
; i
++) {
422 assert_ret(sd_journal_open_directory(&j
, t
, 0));
423 assert_se(journal_find_boot_by_offset(j
, i
, &id
) == 1);
425 assert_se(sd_id128_equal(id
, boots
[n_boots
+ i
- 1].id
));
427 assert_se(sd_id128_equal(id
, boots
[i
- 1].id
));
434 log_info("Not removing %s", t
);
436 journal_directory_vacuum(".", 3000000, 0, 0, NULL
, true);
438 assert_se(rm_rf(t
, REMOVE_ROOT
|REMOVE_PHYSICAL
) >= 0);
441 puts("------------------------------------------------------------");
445 test_boot_id_one(setup_sequential
, 3);
446 test_boot_id_one(setup_unreferenced_data
, 3);
449 static void test_sequence_numbers_one(void) {
450 _cleanup_(mmap_cache_unrefp
) MMapCache
*m
= NULL
;
451 char t
[] = "/var/tmp/journal-seq-XXXXXX";
452 JournalFile
*one
, *two
;
454 sd_id128_t seqnum_id
;
456 m
= mmap_cache_new();
457 assert_se(m
!= NULL
);
459 mkdtemp_chdir_chattr(t
);
461 assert_se(journal_file_open(-1, "one.journal", O_RDWR
|O_CREAT
, JOURNAL_COMPRESS
, 0644,
462 UINT64_MAX
, NULL
, m
, NULL
, &one
) == 0);
464 append_number(one
, 1, NULL
, &seqnum
);
465 printf("seqnum=%"PRIu64
"\n", seqnum
);
466 assert_se(seqnum
== 1);
467 append_number(one
, 2, NULL
, &seqnum
);
468 printf("seqnum=%"PRIu64
"\n", seqnum
);
469 assert_se(seqnum
== 2);
471 assert_se(one
->header
->state
== STATE_ONLINE
);
472 assert_se(!sd_id128_equal(one
->header
->file_id
, one
->header
->machine_id
));
473 assert_se(!sd_id128_equal(one
->header
->file_id
, one
->header
->tail_entry_boot_id
));
474 assert_se(sd_id128_equal(one
->header
->file_id
, one
->header
->seqnum_id
));
476 memcpy(&seqnum_id
, &one
->header
->seqnum_id
, sizeof(sd_id128_t
));
478 assert_se(journal_file_open(-1, "two.journal", O_RDWR
|O_CREAT
, JOURNAL_COMPRESS
, 0644,
479 UINT64_MAX
, NULL
, m
, one
, &two
) == 0);
481 assert_se(two
->header
->state
== STATE_ONLINE
);
482 assert_se(!sd_id128_equal(two
->header
->file_id
, one
->header
->file_id
));
483 assert_se(sd_id128_equal(two
->header
->machine_id
, one
->header
->machine_id
));
484 assert_se(sd_id128_is_null(two
->header
->tail_entry_boot_id
)); /* Not written yet. */
485 assert_se(sd_id128_equal(two
->header
->seqnum_id
, one
->header
->seqnum_id
));
487 append_number(two
, 3, NULL
, &seqnum
);
488 printf("seqnum=%"PRIu64
"\n", seqnum
);
489 assert_se(seqnum
== 3);
490 append_number(two
, 4, NULL
, &seqnum
);
491 printf("seqnum=%"PRIu64
"\n", seqnum
);
492 assert_se(seqnum
== 4);
494 /* Verify tail_entry_boot_id. */
495 assert_se(sd_id128_equal(two
->header
->tail_entry_boot_id
, one
->header
->tail_entry_boot_id
));
499 append_number(one
, 5, NULL
, &seqnum
);
500 printf("seqnum=%"PRIu64
"\n", seqnum
);
501 assert_se(seqnum
== 5);
503 append_number(one
, 6, NULL
, &seqnum
);
504 printf("seqnum=%"PRIu64
"\n", seqnum
);
505 assert_se(seqnum
== 6);
509 /* If the machine-id is not initialized, the header file verification
510 * (which happens when re-opening a journal file) will fail. */
511 if (sd_id128_get_machine(NULL
) >= 0) {
515 assert_se(journal_file_open(-1, "two.journal", O_RDWR
, JOURNAL_COMPRESS
, 0,
516 UINT64_MAX
, NULL
, m
, NULL
, &two
) == 0);
518 assert_se(sd_id128_equal(two
->header
->seqnum_id
, seqnum_id
));
520 append_number(two
, 7, NULL
, &seqnum
);
521 printf("seqnum=%"PRIu64
"\n", seqnum
);
522 assert_se(seqnum
== 5);
524 /* So..., here we have the same seqnum in two files with the
533 log_info("Not removing %s", t
);
535 journal_directory_vacuum(".", 3000000, 0, 0, NULL
, true);
537 assert_se(rm_rf(t
, REMOVE_ROOT
|REMOVE_PHYSICAL
) >= 0);
541 TEST(sequence_numbers
) {
542 assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0);
543 test_sequence_numbers_one();
545 assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0);
546 test_sequence_numbers_one();
549 static int intro(void) {
550 /* journal_file_open() requires a valid machine id */
551 if (access("/etc/machine-id", F_OK
) != 0)
552 return log_tests_skipped("/etc/machine-id not found");
554 arg_keep
= saved_argc
> 1;
559 DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG
, intro
);