]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/test-journal-interleaving.c
test: add test case that journal file with unreferenced _BOOT_ID data
[thirdparty/systemd.git] / src / journal / test-journal-interleaving.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <unistd.h>
5
6 #include "sd-id128.h"
7 #include "sd-journal.h"
8
9 #include "alloc-util.h"
10 #include "chattr-util.h"
11 #include "io-util.h"
12 #include "journal-vacuum.h"
13 #include "log.h"
14 #include "logs-show.h"
15 #include "managed-journal-file.h"
16 #include "parse-util.h"
17 #include "rm-rf.h"
18 #include "tests.h"
19
20 /* This program tests skipping around in a multi-file journal. */
21
22 static bool arg_keep = false;
23 static dual_timestamp previous_ts = {};
24
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);
28 abort();
29 }
30
31 #define assert_ret(expr) \
32 do { \
33 int _r_ = (expr); \
34 if (_unlikely_(_r_ < 0)) \
35 log_assert_errno(#expr, -_r_, PROJECT_FILE, __LINE__, __func__); \
36 } while (false)
37
38 static ManagedJournalFile *test_open_internal(const char *name, JournalFileFlags flags) {
39 _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL;
40 ManagedJournalFile *f;
41
42 m = mmap_cache_new();
43 assert_se(m != NULL);
44
45 assert_ret(managed_journal_file_open(-1, name, O_RDWR|O_CREAT, flags, 0644, UINT64_MAX, NULL, m, NULL, NULL, &f));
46 return f;
47 }
48
49 static ManagedJournalFile *test_open(const char *name) {
50 return test_open_internal(name, JOURNAL_COMPRESS);
51 }
52
53 static ManagedJournalFile *test_open_strict(const char *name) {
54 return test_open_internal(name, JOURNAL_COMPRESS | JOURNAL_STRICT_ORDER);
55 }
56
57 static void test_close(ManagedJournalFile *f) {
58 (void) managed_journal_file_close(f);
59 }
60
61 static void append_number(ManagedJournalFile *f, int n, const sd_id128_t *boot_id, uint64_t *seqnum) {
62 _cleanup_free_ char *p = NULL, *q = NULL;
63 dual_timestamp ts;
64 struct iovec iovec[2];
65 size_t n_iov = 0;
66
67 dual_timestamp_get(&ts);
68
69 if (ts.monotonic <= previous_ts.monotonic)
70 ts.monotonic = previous_ts.monotonic + 1;
71
72 if (ts.realtime <= previous_ts.realtime)
73 ts.realtime = previous_ts.realtime + 1;
74
75 previous_ts = ts;
76
77 assert_se(asprintf(&p, "NUMBER=%d", n) >= 0);
78 iovec[n_iov++] = IOVEC_MAKE_STRING(p);
79
80 if (boot_id) {
81 assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id)));
82 iovec[n_iov++] = IOVEC_MAKE_STRING(q);
83 }
84
85 assert_ret(journal_file_append_entry(f->file, &ts, boot_id, iovec, n_iov, seqnum, NULL, NULL, NULL));
86 }
87
88 static void append_unreferenced_data(ManagedJournalFile *f, const sd_id128_t *boot_id) {
89 _cleanup_free_ char *q = NULL;
90 dual_timestamp ts;
91 struct iovec iovec;
92
93 assert(boot_id);
94
95 ts.monotonic = usec_sub_unsigned(previous_ts.monotonic, 10);
96 ts.realtime = usec_sub_unsigned(previous_ts.realtime, 10);
97
98 assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id)));
99 iovec = IOVEC_MAKE_STRING(q);
100
101 assert_se(journal_file_append_entry(f->file, &ts, boot_id, &iovec, 1, NULL, NULL, NULL, NULL) == -EREMCHG);
102 }
103
104 static void test_check_number(sd_journal *j, int n) {
105 sd_id128_t boot_id;
106 const void *d;
107 _cleanup_free_ char *k = NULL;
108 size_t l;
109 int x;
110
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);
115
116 assert_se(safe_atoi(k + STRLEN("NUMBER="), &x) >= 0);
117 assert_se(n == x);
118 }
119
120 static void test_check_numbers_down(sd_journal *j, int count) {
121 int i;
122
123 for (i = 1; i <= count; i++) {
124 int r;
125 test_check_number(j, i);
126 assert_ret(r = sd_journal_next(j));
127 if (i == count)
128 assert_se(r == 0);
129 else
130 assert_se(r == 1);
131 }
132
133 }
134
135 static void test_check_numbers_up(sd_journal *j, int count) {
136 for (int i = count; i >= 1; i--) {
137 int r;
138 test_check_number(j, i);
139 assert_ret(r = sd_journal_previous(j));
140 if (i == 1)
141 assert_se(r == 0);
142 else
143 assert_se(r == 1);
144 }
145
146 }
147
148 static void setup_sequential(void) {
149 ManagedJournalFile *f1, *f2, *f3;
150 sd_id128_t id;
151
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);
170 test_close(f1);
171 test_close(f2);
172 test_close(f3);
173 }
174
175 static void setup_interleaved(void) {
176 ManagedJournalFile *f1, *f2, *f3;
177 sd_id128_t id;
178
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);
193 test_close(f1);
194 test_close(f2);
195 test_close(f3);
196 }
197
198 static void setup_unreferenced_data(void) {
199 ManagedJournalFile *f1, *f2, *f3;
200 sd_id128_t id;
201
202 /* For issue #29275. */
203
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);
224 test_close(f1);
225 test_close(f2);
226 test_close(f3);
227 }
228
229 static void mkdtemp_chdir_chattr(char *path) {
230 assert_se(mkdtemp(path));
231 assert_se(chdir(path) >= 0);
232
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);
236 }
237
238 static void test_skip_one(void (*setup)(void)) {
239 char t[] = "/var/tmp/journal-skip-XXXXXX";
240 sd_journal *j;
241 int r;
242
243 mkdtemp_chdir_chattr(t);
244
245 setup();
246
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);
252 sd_journal_close(j);
253
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);
260 sd_journal_close(j);
261
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);
269 sd_journal_close(j);
270
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);
277 sd_journal_close(j);
278
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);
289 sd_journal_close(j);
290
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);
296 sd_journal_close(j);
297
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);
305 sd_journal_close(j);
306
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);
313 sd_journal_close(j);
314
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);
325 sd_journal_close(j);
326
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);
332 sd_journal_close(j);
333
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);
351 sd_journal_close(j);
352
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);
358 sd_journal_close(j);
359
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);
377 sd_journal_close(j);
378
379 log_info("Done...");
380
381 if (arg_keep)
382 log_info("Not removing %s", t);
383 else {
384 journal_directory_vacuum(".", 3000000, 0, 0, NULL, true);
385
386 assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
387 }
388
389 puts("------------------------------------------------------------");
390 }
391
392 TEST(skip) {
393 test_skip_one(setup_sequential);
394 test_skip_one(setup_interleaved);
395 }
396
397 static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) {
398 char t[] = "/var/tmp/journal-boot-id-XXXXXX";
399 sd_journal *j;
400 _cleanup_free_ BootId *boots = NULL;
401 size_t n_boots;
402
403 mkdtemp_chdir_chattr(t);
404
405 setup();
406
407 assert_ret(sd_journal_open_directory(&j, t, 0));
408 assert_se(journal_get_boots(j, &boots, &n_boots) >= 0);
409 assert_se(boots);
410 assert_se(n_boots == n_boots_expected);
411 sd_journal_close(j);
412
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);
416 sd_journal_close(j);
417 }
418
419 for (int i = - (int) n_boots + 1; i <= (int) n_boots; i++) {
420 sd_id128_t id;
421
422 assert_ret(sd_journal_open_directory(&j, t, 0));
423 assert_se(journal_find_boot_by_offset(j, i, &id) == 1);
424 if (i <= 0)
425 assert_se(sd_id128_equal(id, boots[n_boots + i - 1].id));
426 else
427 assert_se(sd_id128_equal(id, boots[i - 1].id));
428 sd_journal_close(j);
429 }
430
431 log_info("Done...");
432
433 if (arg_keep)
434 log_info("Not removing %s", t);
435 else {
436 journal_directory_vacuum(".", 3000000, 0, 0, NULL, true);
437
438 assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
439 }
440
441 puts("------------------------------------------------------------");
442 }
443
444 TEST(boot_id) {
445 test_boot_id_one(setup_sequential, 3);
446 test_boot_id_one(setup_unreferenced_data, 3);
447 }
448
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 ManagedJournalFile *one, *two;
453 uint64_t seqnum = 0;
454 sd_id128_t seqnum_id;
455
456 m = mmap_cache_new();
457 assert_se(m != NULL);
458
459 mkdtemp_chdir_chattr(t);
460
461 assert_se(managed_journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644,
462 UINT64_MAX, NULL, m, NULL, NULL, &one) == 0);
463
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);
470
471 assert_se(one->file->header->state == STATE_ONLINE);
472 assert_se(!sd_id128_equal(one->file->header->file_id, one->file->header->machine_id));
473 assert_se(!sd_id128_equal(one->file->header->file_id, one->file->header->tail_entry_boot_id));
474 assert_se(sd_id128_equal(one->file->header->file_id, one->file->header->seqnum_id));
475
476 memcpy(&seqnum_id, &one->file->header->seqnum_id, sizeof(sd_id128_t));
477
478 assert_se(managed_journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644,
479 UINT64_MAX, NULL, m, NULL, one, &two) == 0);
480
481 assert_se(two->file->header->state == STATE_ONLINE);
482 assert_se(!sd_id128_equal(two->file->header->file_id, one->file->header->file_id));
483 assert_se(sd_id128_equal(two->file->header->machine_id, one->file->header->machine_id));
484 assert_se(sd_id128_is_null(two->file->header->tail_entry_boot_id)); /* Not written yet. */
485 assert_se(sd_id128_equal(two->file->header->seqnum_id, one->file->header->seqnum_id));
486
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);
493
494 /* Verify tail_entry_boot_id. */
495 assert_se(sd_id128_equal(two->file->header->tail_entry_boot_id, one->file->header->tail_entry_boot_id));
496
497 test_close(two);
498
499 append_number(one, 5, NULL, &seqnum);
500 printf("seqnum=%"PRIu64"\n", seqnum);
501 assert_se(seqnum == 5);
502
503 append_number(one, 6, NULL, &seqnum);
504 printf("seqnum=%"PRIu64"\n", seqnum);
505 assert_se(seqnum == 6);
506
507 test_close(one);
508
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) {
512 /* restart server */
513 seqnum = 0;
514
515 assert_se(managed_journal_file_open(-1, "two.journal", O_RDWR, JOURNAL_COMPRESS, 0,
516 UINT64_MAX, NULL, m, NULL, NULL, &two) == 0);
517
518 assert_se(sd_id128_equal(two->file->header->seqnum_id, seqnum_id));
519
520 append_number(two, 7, NULL, &seqnum);
521 printf("seqnum=%"PRIu64"\n", seqnum);
522 assert_se(seqnum == 5);
523
524 /* So..., here we have the same seqnum in two files with the
525 * same seqnum_id. */
526
527 test_close(two);
528 }
529
530 log_info("Done...");
531
532 if (arg_keep)
533 log_info("Not removing %s", t);
534 else {
535 journal_directory_vacuum(".", 3000000, 0, 0, NULL, true);
536
537 assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
538 }
539 }
540
541 TEST(sequence_numbers) {
542 assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0);
543 test_sequence_numbers_one();
544
545 assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0);
546 test_sequence_numbers_one();
547 }
548
549 static int intro(void) {
550 /* managed_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");
553
554 arg_keep = saved_argc > 1;
555
556 return EXIT_SUCCESS;
557 }
558
559 DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro);