]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-verify.c
journal-verify: don't hit SIGFPE when determining progress
[thirdparty/systemd.git] / src / journal / journal-verify.c
CommitLineData
0284adc6
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <unistd.h>
23#include <sys/mman.h>
24#include <fcntl.h>
feb12d3e 25#include <stddef.h>
0284adc6
LP
26
27#include "util.h"
28#include "macro.h"
29#include "journal-def.h"
30#include "journal-file.h"
31#include "journal-authenticate.h"
32#include "journal-verify.h"
f59a5f6b 33#include "lookup3.h"
fd5dc320 34#include "compress.h"
288a74cc 35#include "terminal-util.h"
f59a5f6b 36
54f3ff07
ZJS
37static void draw_progress(uint64_t p, usec_t *last_usec) {
38 unsigned n, i, j, k;
39 usec_t z, x;
40
41 if (!on_tty())
42 return;
43
44 z = now(CLOCK_MONOTONIC);
45 x = *last_usec;
46
47 if (x != 0 && x + 40 * USEC_PER_MSEC > z)
48 return;
49
50 *last_usec = z;
51
52 n = (3 * columns()) / 4;
53 j = (n * (unsigned) p) / 65535ULL;
54 k = n - j;
55
56 fputs("\r\x1B[?25l" ANSI_HIGHLIGHT_GREEN_ON, stdout);
57
58 for (i = 0; i < j; i++)
59 fputs("\xe2\x96\x88", stdout);
60
61 fputs(ANSI_HIGHLIGHT_OFF, stdout);
62
63 for (i = 0; i < k; i++)
64 fputs("\xe2\x96\x91", stdout);
65
66 printf(" %3"PRIu64"%%", 100U * p / 65535U);
67
68 fputs("\r\x1B[?25h", stdout);
69 fflush(stdout);
70}
71
45c047b2
LP
72static uint64_t scale_progress(uint64_t scale, uint64_t p, uint64_t m) {
73
74 /* Calculates scale * p / m, but handles m == 0 safely, and saturates */
75
76 if (p >= m || m == 0)
77 return scale;
78
79 return scale * p / m;
80}
81
54f3ff07
ZJS
82static void flush_progress(void) {
83 unsigned n, i;
84
85 if (!on_tty())
86 return;
87
88 n = (3 * columns()) / 4;
89
90 putchar('\r');
91
92 for (i = 0; i < n + 5; i++)
93 putchar(' ');
94
95 putchar('\r');
96 fflush(stdout);
97}
98
99#define debug(_offset, _fmt, ...) do{ \
100 flush_progress(); \
101 log_debug(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \
102 } while(0)
103
104#define warning(_offset, _fmt, ...) do{ \
105 flush_progress(); \
106 log_warning(OFSfmt": " _fmt, _offset, ##__VA_ARGS__); \
107 } while(0)
108
109#define error(_offset, _fmt, ...) do{ \
110 flush_progress(); \
111 log_error(OFSfmt": " _fmt, (uint64_t)_offset, ##__VA_ARGS__); \
112 } while(0)
113
92fba83e 114static int journal_file_object_verify(JournalFile *f, uint64_t offset, Object *o) {
db11ac1a
LP
115 uint64_t i;
116
0284adc6 117 assert(f);
92fba83e 118 assert(offset);
0284adc6
LP
119 assert(o);
120
121 /* This does various superficial tests about the length an
122 * possible field values. It does not follow any references to
123 * other objects. */
124
d89c8fdf 125 if ((o->object.flags & OBJECT_COMPRESSED_XZ) &&
f59a5f6b
LP
126 o->object.type != OBJECT_DATA)
127 return -EBADMSG;
128
0284adc6 129 switch (o->object.type) {
f59a5f6b 130
fd5dc320
LP
131 case OBJECT_DATA: {
132 uint64_t h1, h2;
1ec7120e 133 int compression, r;
fd5dc320 134
92fba83e 135 if (le64toh(o->data.entry_offset) == 0)
54f3ff07 136 warning(offset, "unused data (entry_offset==0)");
92fba83e
ZJS
137
138 if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0)) {
54f3ff07 139 error(offset, "bad n_entries: %"PRIu64, o->data.n_entries);
0284adc6 140 return -EBADMSG;
92fba83e 141 }
0284adc6 142
92fba83e 143 if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0) {
54f3ff07
ZJS
144 error(offset, "bad object size (<= %zu): %"PRIu64,
145 offsetof(DataObject, payload),
146 le64toh(o->object.size));
0284adc6 147 return -EBADMSG;
92fba83e 148 }
f59a5f6b 149
fd5dc320 150 h1 = le64toh(o->data.hash);
f59a5f6b 151
1ec7120e
ZJS
152 compression = o->object.flags & OBJECT_COMPRESSION_MASK;
153 if (compression) {
d89c8fdf 154 _cleanup_free_ void *b = NULL;
fa1c4b51 155 size_t alloc = 0, b_size;
f59a5f6b 156
1ec7120e
ZJS
157 r = decompress_blob(compression,
158 o->data.payload,
159 le64toh(o->object.size) - offsetof(Object, data.payload),
160 &b, &alloc, &b_size, 0);
161 if (r < 0) {
54f3ff07
ZJS
162 error(offset, "%s decompression failed: %s",
163 object_compressed_to_string(compression), strerror(-r));
1ec7120e 164 return r;
92fba83e 165 }
fd5dc320
LP
166
167 h2 = hash64(b, b_size);
fd5dc320
LP
168 } else
169 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
170
92fba83e 171 if (h1 != h2) {
54f3ff07 172 error(offset, "invalid hash (%08"PRIx64" vs. %08"PRIx64, h1, h2);
fd5dc320 173 return -EBADMSG;
92fba83e 174 }
f59a5f6b 175
db11ac1a
LP
176 if (!VALID64(o->data.next_hash_offset) ||
177 !VALID64(o->data.next_field_offset) ||
178 !VALID64(o->data.entry_offset) ||
92fba83e 179 !VALID64(o->data.entry_array_offset)) {
54f3ff07
ZJS
180 error(offset, "invalid offset (next_hash_offset="OFSfmt", next_field_offset="OFSfmt", entry_offset="OFSfmt", entry_array_offset="OFSfmt,
181 o->data.next_hash_offset,
182 o->data.next_field_offset,
183 o->data.entry_offset,
184 o->data.entry_array_offset);
db11ac1a 185 return -EBADMSG;
92fba83e 186 }
db11ac1a 187
0284adc6 188 break;
fd5dc320 189 }
0284adc6
LP
190
191 case OBJECT_FIELD:
92fba83e 192 if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0) {
54f3ff07
ZJS
193 error(offset,
194 "bad field size (<= %zu): %"PRIu64,
195 offsetof(FieldObject, payload),
196 le64toh(o->object.size));
0284adc6 197 return -EBADMSG;
92fba83e 198 }
db11ac1a
LP
199
200 if (!VALID64(o->field.next_hash_offset) ||
92fba83e 201 !VALID64(o->field.head_data_offset)) {
54f3ff07
ZJS
202 error(offset,
203 "invalid offset (next_hash_offset="OFSfmt", head_data_offset="OFSfmt,
204 o->field.next_hash_offset,
205 o->field.head_data_offset);
db11ac1a 206 return -EBADMSG;
92fba83e 207 }
0284adc6
LP
208 break;
209
210 case OBJECT_ENTRY:
92fba83e 211 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0) {
54f3ff07
ZJS
212 error(offset,
213 "bad entry size (<= %zu): %"PRIu64,
214 offsetof(EntryObject, items),
215 le64toh(o->object.size));
0284adc6 216 return -EBADMSG;
92fba83e 217 }
0284adc6 218
92fba83e 219 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0) {
54f3ff07
ZJS
220 error(offset,
221 "invalid number items in entry: %"PRIu64,
222 (le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem));
0284adc6 223 return -EBADMSG;
92fba83e
ZJS
224 }
225
226 if (le64toh(o->entry.seqnum) <= 0) {
54f3ff07
ZJS
227 error(offset,
228 "invalid entry seqnum: %"PRIx64,
229 le64toh(o->entry.seqnum));
92fba83e
ZJS
230 return -EBADMSG;
231 }
0284adc6 232
92fba83e 233 if (!VALID_REALTIME(le64toh(o->entry.realtime))) {
54f3ff07
ZJS
234 error(offset,
235 "invalid entry realtime timestamp: %"PRIu64,
236 le64toh(o->entry.realtime));
0284adc6 237 return -EBADMSG;
92fba83e
ZJS
238 }
239
240 if (!VALID_MONOTONIC(le64toh(o->entry.monotonic))) {
54f3ff07
ZJS
241 error(offset,
242 "invalid entry monotonic timestamp: %"PRIu64,
243 le64toh(o->entry.monotonic));
92fba83e
ZJS
244 return -EBADMSG;
245 }
0284adc6 246
db11ac1a
LP
247 for (i = 0; i < journal_file_entry_n_items(o); i++) {
248 if (o->entry.items[i].object_offset == 0 ||
92fba83e 249 !VALID64(o->entry.items[i].object_offset)) {
54f3ff07
ZJS
250 error(offset,
251 "invalid entry item (%"PRIu64"/%"PRIu64" offset: "OFSfmt,
252 i, journal_file_entry_n_items(o),
253 o->entry.items[i].object_offset);
db11ac1a 254 return -EBADMSG;
92fba83e 255 }
db11ac1a
LP
256 }
257
0284adc6
LP
258 break;
259
260 case OBJECT_DATA_HASH_TABLE:
261 case OBJECT_FIELD_HASH_TABLE:
92fba83e
ZJS
262 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0 ||
263 (le64toh(o->object.size) - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0) {
54f3ff07
ZJS
264 error(offset,
265 "invalid %s hash table size: %"PRIu64,
266 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
267 le64toh(o->object.size));
86adf873 268 return -EBADMSG;
92fba83e 269 }
86adf873 270
fb9a24b6
LP
271 for (i = 0; i < journal_file_hash_table_n_items(o); i++) {
272 if (o->hash_table.items[i].head_hash_offset != 0 &&
92fba83e 273 !VALID64(le64toh(o->hash_table.items[i].head_hash_offset))) {
54f3ff07
ZJS
274 error(offset,
275 "invalid %s hash table item (%"PRIu64"/%"PRIu64") head_hash_offset: "OFSfmt,
276 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
277 i, journal_file_hash_table_n_items(o),
278 le64toh(o->hash_table.items[i].head_hash_offset));
fb9a24b6 279 return -EBADMSG;
92fba83e 280 }
fb9a24b6 281 if (o->hash_table.items[i].tail_hash_offset != 0 &&
92fba83e 282 !VALID64(le64toh(o->hash_table.items[i].tail_hash_offset))) {
54f3ff07
ZJS
283 error(offset,
284 "invalid %s hash table item (%"PRIu64"/%"PRIu64") tail_hash_offset: "OFSfmt,
285 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
286 i, journal_file_hash_table_n_items(o),
287 le64toh(o->hash_table.items[i].tail_hash_offset));
fb9a24b6 288 return -EBADMSG;
92fba83e 289 }
fb9a24b6
LP
290
291 if ((o->hash_table.items[i].head_hash_offset != 0) !=
92fba83e 292 (o->hash_table.items[i].tail_hash_offset != 0)) {
54f3ff07
ZJS
293 error(offset,
294 "invalid %s hash table item (%"PRIu64"/%"PRIu64"): head_hash_offset="OFSfmt" tail_hash_offset="OFSfmt,
295 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
296 i, journal_file_hash_table_n_items(o),
297 le64toh(o->hash_table.items[i].head_hash_offset),
298 le64toh(o->hash_table.items[i].tail_hash_offset));
fb9a24b6 299 return -EBADMSG;
92fba83e 300 }
fb9a24b6
LP
301 }
302
0284adc6
LP
303 break;
304
305 case OBJECT_ENTRY_ARRAY:
92fba83e
ZJS
306 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0 ||
307 (le64toh(o->object.size) - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0) {
54f3ff07
ZJS
308 error(offset,
309 "invalid object entry array size: %"PRIu64,
310 le64toh(o->object.size));
86adf873 311 return -EBADMSG;
92fba83e 312 }
86adf873 313
92fba83e 314 if (!VALID64(o->entry_array.next_entry_array_offset)) {
54f3ff07
ZJS
315 error(offset,
316 "invalid object entry array next_entry_array_offset: "OFSfmt,
317 o->entry_array.next_entry_array_offset);
db11ac1a 318 return -EBADMSG;
92fba83e 319 }
db11ac1a 320
fb9a24b6 321 for (i = 0; i < journal_file_entry_array_n_items(o); i++)
af13a6b0
GM
322 if (le64toh(o->entry_array.items[i]) != 0 &&
323 !VALID64(le64toh(o->entry_array.items[i]))) {
54f3ff07
ZJS
324 error(offset,
325 "invalid object entry array item (%"PRIu64"/%"PRIu64"): "OFSfmt,
326 i, journal_file_entry_array_n_items(o),
327 le64toh(o->entry_array.items[i]));
fb9a24b6 328 return -EBADMSG;
92fba83e 329 }
fb9a24b6 330
0284adc6
LP
331 break;
332
333 case OBJECT_TAG:
92fba83e 334 if (le64toh(o->object.size) != sizeof(TagObject)) {
54f3ff07
ZJS
335 error(offset,
336 "invalid object tag size: %"PRIu64,
337 le64toh(o->object.size));
0284adc6 338 return -EBADMSG;
92fba83e 339 }
fc89a139 340
92fba83e 341 if (!VALID_EPOCH(o->tag.epoch)) {
54f3ff07
ZJS
342 error(offset,
343 "invalid object tag epoch: %"PRIu64,
344 o->tag.epoch);
fc89a139 345 return -EBADMSG;
92fba83e 346 }
fc89a139 347
0284adc6
LP
348 break;
349 }
350
351 return 0;
352}
353
0284adc6
LP
354static int write_uint64(int fd, uint64_t p) {
355 ssize_t k;
356
357 k = write(fd, &p, sizeof(p));
358 if (k < 0)
359 return -errno;
360 if (k != sizeof(p))
361 return -EIO;
362
363 return 0;
364}
365
366static int contains_uint64(MMapCache *m, int fd, uint64_t n, uint64_t p) {
367 uint64_t a, b;
368 int r;
369
370 assert(m);
371 assert(fd >= 0);
372
373 /* Bisection ... */
374
375 a = 0; b = n;
376 while (a < b) {
377 uint64_t c, *z;
378
379 c = (a + b) / 2;
380
1b8951e5 381 r = mmap_cache_get(m, fd, PROT_READ|PROT_WRITE, 0, false, c * sizeof(uint64_t), sizeof(uint64_t), NULL, (void **) &z);
0284adc6
LP
382 if (r < 0)
383 return r;
384
385 if (*z == p)
386 return 1;
387
a2e99cdf
LP
388 if (a + 1 >= b)
389 return 0;
390
0284adc6
LP
391 if (p < *z)
392 b = c;
fcde2389 393 else
0284adc6
LP
394 a = c;
395 }
396
397 return 0;
398}
399
86adf873
LP
400static int entry_points_to_data(
401 JournalFile *f,
402 int entry_fd,
403 uint64_t n_entries,
404 uint64_t entry_p,
405 uint64_t data_p) {
406
407 int r;
408 uint64_t i, n, a;
409 Object *o;
410 bool found = false;
411
412 assert(f);
413 assert(entry_fd >= 0);
414
415 if (!contains_uint64(f->mmap, entry_fd, n_entries, entry_p)) {
54f3ff07
ZJS
416 error(data_p,
417 "data object references invalid entry at "OFSfmt, entry_p);
86adf873
LP
418 return -EBADMSG;
419 }
420
421 r = journal_file_move_to_object(f, OBJECT_ENTRY, entry_p, &o);
422 if (r < 0)
423 return r;
424
425 n = journal_file_entry_n_items(o);
426 for (i = 0; i < n; i++)
427 if (le64toh(o->entry.items[i].object_offset) == data_p) {
428 found = true;
429 break;
430 }
431
432 if (!found) {
54f3ff07
ZJS
433 error(entry_p,
434 "data object at "OFSfmt" not referenced by linked entry", data_p);
86adf873
LP
435 return -EBADMSG;
436 }
437
438 /* Check if this entry is also in main entry array. Since the
439 * main entry array has already been verified we can rely on
7517e174 440 * its consistency. */
86adf873 441
369f0589 442 i = 0;
86adf873
LP
443 n = le64toh(f->header->n_entries);
444 a = le64toh(f->header->entry_array_offset);
86adf873
LP
445
446 while (i < n) {
369f0589 447 uint64_t m, u;
86adf873
LP
448
449 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
450 if (r < 0)
451 return r;
452
453 m = journal_file_entry_array_n_items(o);
369f0589
LP
454 u = MIN(n - i, m);
455
456 if (entry_p <= le64toh(o->entry_array.items[u-1])) {
457 uint64_t x, y, z;
458
459 x = 0;
460 y = u;
461
462 while (x < y) {
463 z = (x + y) / 2;
464
465 if (le64toh(o->entry_array.items[z]) == entry_p)
466 return 0;
467
468 if (x + 1 >= y)
469 break;
470
471 if (entry_p < le64toh(o->entry_array.items[z]))
472 y = z;
473 else
474 x = z;
475 }
476
54f3ff07 477 error(entry_p, "entry object doesn't exist in main entry array");
369f0589
LP
478 return -EBADMSG;
479 }
86adf873 480
369f0589 481 i += u;
356fe3e6 482 a = le64toh(o->entry_array.next_entry_array_offset);
86adf873
LP
483 }
484
485 return 0;
486}
487
488static int verify_data(
489 JournalFile *f,
490 Object *o, uint64_t p,
491 int entry_fd, uint64_t n_entries,
492 int entry_array_fd, uint64_t n_entry_arrays) {
493
494 uint64_t i, n, a, last, q;
495 int r;
496
497 assert(f);
498 assert(o);
499 assert(entry_fd >= 0);
500 assert(entry_array_fd >= 0);
501
502 n = le64toh(o->data.n_entries);
503 a = le64toh(o->data.entry_array_offset);
504
92fba83e
ZJS
505 /* Entry array means at least two objects */
506 if (a && n < 2) {
54f3ff07
ZJS
507 error(p,
508 "entry array present (entry_array_offset="OFSfmt", but n_entries=%"PRIu64")",
509 a, n);
92fba83e
ZJS
510 return -EBADMSG;
511 }
512
513 if (n == 0)
514 return 0;
515
516 /* We already checked that earlier */
517 assert(o->data.entry_offset);
86adf873
LP
518
519 last = q = le64toh(o->data.entry_offset);
520 r = entry_points_to_data(f, entry_fd, n_entries, q, p);
521 if (r < 0)
522 return r;
523
2a7273ef 524 i = 1;
86adf873
LP
525 while (i < n) {
526 uint64_t next, m, j;
527
528 if (a == 0) {
54f3ff07 529 error(p, "array chain too short");
86adf873
LP
530 return -EBADMSG;
531 }
532
533 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
54f3ff07 534 error(p, "invalid array offset "OFSfmt, a);
86adf873
LP
535 return -EBADMSG;
536 }
537
538 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
539 if (r < 0)
540 return r;
541
542 next = le64toh(o->entry_array.next_entry_array_offset);
543 if (next != 0 && next <= a) {
54f3ff07
ZJS
544 error(p, "array chain has cycle (jumps back from "OFSfmt" to "OFSfmt")",
545 a, next);
86adf873
LP
546 return -EBADMSG;
547 }
548
549 m = journal_file_entry_array_n_items(o);
550 for (j = 0; i < n && j < m; i++, j++) {
551
552 q = le64toh(o->entry_array.items[j]);
553 if (q <= last) {
54f3ff07 554 error(p, "data object's entry array not sorted");
86adf873
LP
555 return -EBADMSG;
556 }
557 last = q;
558
559 r = entry_points_to_data(f, entry_fd, n_entries, q, p);
560 if (r < 0)
561 return r;
562
563 /* Pointer might have moved, reposition */
564 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
565 if (r < 0)
566 return r;
567 }
568
569 a = next;
570 }
571
572 return 0;
573}
574
575static int verify_hash_table(
576 JournalFile *f,
577 int data_fd, uint64_t n_data,
578 int entry_fd, uint64_t n_entries,
579 int entry_array_fd, uint64_t n_entry_arrays,
b72631e5
LP
580 usec_t *last_usec,
581 bool show_progress) {
86adf873
LP
582
583 uint64_t i, n;
584 int r;
585
586 assert(f);
587 assert(data_fd >= 0);
588 assert(entry_fd >= 0);
589 assert(entry_array_fd >= 0);
590 assert(last_usec);
591
592 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
593 for (i = 0; i < n; i++) {
594 uint64_t last = 0, p;
595
b72631e5 596 if (show_progress)
45c047b2 597 draw_progress(0xC000 + scale_progress(0x3FFF, i, n), last_usec);
86adf873
LP
598
599 p = le64toh(f->data_hash_table[i].head_hash_offset);
600 while (p != 0) {
601 Object *o;
602 uint64_t next;
603
604 if (!contains_uint64(f->mmap, data_fd, n_data, p)) {
54f3ff07
ZJS
605 error(p, "invalid data object at hash entry %"PRIu64" of %"PRIu64,
606 i, n);
86adf873
LP
607 return -EBADMSG;
608 }
609
610 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
611 if (r < 0)
612 return r;
613
614 next = le64toh(o->data.next_hash_offset);
615 if (next != 0 && next <= p) {
54f3ff07
ZJS
616 error(p, "hash chain has a cycle in hash entry %"PRIu64" of %"PRIu64,
617 i, n);
86adf873
LP
618 return -EBADMSG;
619 }
620
621 if (le64toh(o->data.hash) % n != i) {
54f3ff07
ZJS
622 error(p, "hash value mismatch in hash entry %"PRIu64" of %"PRIu64,
623 i, n);
86adf873
LP
624 return -EBADMSG;
625 }
626
627 r = verify_data(f, o, p, entry_fd, n_entries, entry_array_fd, n_entry_arrays);
628 if (r < 0)
629 return r;
630
631 last = p;
632 p = next;
633 }
634
635 if (last != le64toh(f->data_hash_table[i].tail_hash_offset)) {
54f3ff07 636 error(p, "tail hash pointer mismatch in hash table");
86adf873
LP
637 return -EBADMSG;
638 }
639 }
640
641 return 0;
642}
643
644static int data_object_in_hash_table(JournalFile *f, uint64_t hash, uint64_t p) {
645 uint64_t n, h, q;
646 int r;
647 assert(f);
648
649 n = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
650 h = hash % n;
651
652 q = le64toh(f->data_hash_table[h].head_hash_offset);
653 while (q != 0) {
654 Object *o;
655
656 if (p == q)
657 return 1;
658
659 r = journal_file_move_to_object(f, OBJECT_DATA, q, &o);
660 if (r < 0)
661 return r;
662
663 q = le64toh(o->data.next_hash_offset);
664 }
665
666 return 0;
667}
668
669static int verify_entry(
670 JournalFile *f,
671 Object *o, uint64_t p,
672 int data_fd, uint64_t n_data) {
673
674 uint64_t i, n;
675 int r;
676
677 assert(f);
678 assert(o);
679 assert(data_fd >= 0);
680
681 n = journal_file_entry_n_items(o);
682 for (i = 0; i < n; i++) {
683 uint64_t q, h;
684 Object *u;
685
686 q = le64toh(o->entry.items[i].object_offset);
687 h = le64toh(o->entry.items[i].hash);
688
689 if (!contains_uint64(f->mmap, data_fd, n_data, q)) {
54f3ff07 690 error(p, "invalid data object of entry");
86adf873
LP
691 return -EBADMSG;
692 }
693
694 r = journal_file_move_to_object(f, OBJECT_DATA, q, &u);
695 if (r < 0)
696 return r;
697
698 if (le64toh(u->data.hash) != h) {
54f3ff07 699 error(p, "hash mismatch for data object of entry");
86adf873
LP
700 return -EBADMSG;
701 }
702
703 r = data_object_in_hash_table(f, h, q);
704 if (r < 0)
705 return r;
706 if (r == 0) {
54f3ff07 707 error(p, "data object missing from hash table");
86adf873
LP
708 return -EBADMSG;
709 }
710 }
711
712 return 0;
713}
714
715static int verify_entry_array(
716 JournalFile *f,
717 int data_fd, uint64_t n_data,
718 int entry_fd, uint64_t n_entries,
719 int entry_array_fd, uint64_t n_entry_arrays,
b72631e5
LP
720 usec_t *last_usec,
721 bool show_progress) {
86adf873
LP
722
723 uint64_t i = 0, a, n, last = 0;
724 int r;
725
726 assert(f);
727 assert(data_fd >= 0);
728 assert(entry_fd >= 0);
729 assert(entry_array_fd >= 0);
730 assert(last_usec);
731
732 n = le64toh(f->header->n_entries);
733 a = le64toh(f->header->entry_array_offset);
734 while (i < n) {
735 uint64_t next, m, j;
736 Object *o;
737
b72631e5 738 if (show_progress)
45c047b2 739 draw_progress(0x8000 + scale_progress(0x3FFF, i, n), last_usec);
86adf873
LP
740
741 if (a == 0) {
54f3ff07 742 error(a, "array chain too short at %"PRIu64" of %"PRIu64, i, n);
86adf873
LP
743 return -EBADMSG;
744 }
745
746 if (!contains_uint64(f->mmap, entry_array_fd, n_entry_arrays, a)) {
54f3ff07 747 error(a, "invalid array %"PRIu64" of %"PRIu64, i, n);
86adf873
LP
748 return -EBADMSG;
749 }
750
751 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
752 if (r < 0)
753 return r;
754
755 next = le64toh(o->entry_array.next_entry_array_offset);
756 if (next != 0 && next <= a) {
54f3ff07
ZJS
757 error(a,
758 "array chain has cycle at %"PRIu64" of %"PRIu64" (jumps back from to "OFSfmt")",
759 i, n, next);
86adf873
LP
760 return -EBADMSG;
761 }
762
763 m = journal_file_entry_array_n_items(o);
764 for (j = 0; i < n && j < m; i++, j++) {
765 uint64_t p;
766
767 p = le64toh(o->entry_array.items[j]);
768 if (p <= last) {
54f3ff07
ZJS
769 error(a, "entry array not sorted at %"PRIu64" of %"PRIu64,
770 i, n);
86adf873
LP
771 return -EBADMSG;
772 }
773 last = p;
774
775 if (!contains_uint64(f->mmap, entry_fd, n_entries, p)) {
54f3ff07
ZJS
776 error(a, "invalid array entry at %"PRIu64" of %"PRIu64,
777 i, n);
86adf873
LP
778 return -EBADMSG;
779 }
780
781 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
782 if (r < 0)
783 return r;
784
785 r = verify_entry(f, o, p, data_fd, n_data);
786 if (r < 0)
787 return r;
788
789 /* Pointer might have moved, reposition */
790 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
791 if (r < 0)
792 return r;
793 }
794
795 a = next;
796 }
797
798 return 0;
799}
800
6c7be122
LP
801int journal_file_verify(
802 JournalFile *f,
803 const char *key,
2a7b539a 804 usec_t *first_contained, usec_t *last_validated, usec_t *last_contained,
b72631e5 805 bool show_progress) {
0284adc6
LP
806 int r;
807 Object *o;
0ab5c3ed
ZJS
808 uint64_t p = 0, last_epoch = 0, last_tag_realtime = 0, last_sealed_realtime = 0;
809
14d10188 810 uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0;
0284adc6
LP
811 sd_id128_t entry_boot_id;
812 bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false;
14d10188 813 uint64_t n_weird = 0, n_objects = 0, n_entries = 0, n_data = 0, n_fields = 0, n_data_hash_tables = 0, n_field_hash_tables = 0, n_entry_arrays = 0, n_tags = 0;
0284adc6
LP
814 usec_t last_usec = 0;
815 int data_fd = -1, entry_fd = -1, entry_array_fd = -1;
97147f8c 816 unsigned i;
e8c108ca 817 bool found_last = false;
0ab5c3ed
ZJS
818#ifdef HAVE_GCRYPT
819 uint64_t last_tag = 0;
820#endif
0284adc6
LP
821 assert(f);
822
baed47c3 823 if (key) {
feb12d3e 824#ifdef HAVE_GCRYPT
baed47c3 825 r = journal_file_parse_verification_key(f, key);
b7c9ae91
LP
826 if (r < 0) {
827 log_error("Failed to parse seed.");
56e81f7c 828 return r;
b7c9ae91 829 }
feb12d3e 830#else
15411c0c 831 return -EOPNOTSUPP;
feb12d3e 832#endif
c586dbf1
LP
833 } else if (f->seal)
834 return -ENOKEY;
b7c9ae91 835
8e33886e 836 data_fd = open_tmpfile("/var/tmp", O_RDWR | O_CLOEXEC);
0284adc6 837 if (data_fd < 0) {
56f64d95 838 log_error_errno(errno, "Failed to create data file: %m");
1137e6c7 839 r = -errno;
0284adc6
LP
840 goto fail;
841 }
0284adc6 842
8e33886e 843 entry_fd = open_tmpfile("/var/tmp", O_RDWR | O_CLOEXEC);
0284adc6 844 if (entry_fd < 0) {
56f64d95 845 log_error_errno(errno, "Failed to create entry file: %m");
1137e6c7 846 r = -errno;
0284adc6
LP
847 goto fail;
848 }
0284adc6 849
8e33886e 850 entry_array_fd = open_tmpfile("/var/tmp", O_RDWR | O_CLOEXEC);
0284adc6 851 if (entry_array_fd < 0) {
56f64d95 852 log_error_errno(errno, "Failed to create entry array file: %m");
1137e6c7 853 r = -errno;
0284adc6
LP
854 goto fail;
855 }
0284adc6 856
54f3ff07 857 if (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SUPPORTED) {
97147f8c 858 log_error("Cannot verify file with unknown extensions.");
15411c0c 859 r = -EOPNOTSUPP;
97147f8c
LP
860 goto fail;
861 }
862
863 for (i = 0; i < sizeof(f->header->reserved); i++)
864 if (f->header->reserved[i] != 0) {
54f3ff07 865 error(offsetof(Header, reserved[i]), "reserved field is non-zero");
97147f8c
LP
866 r = -EBADMSG;
867 goto fail;
868 }
869
0284adc6
LP
870 /* First iteration: we go through all objects, verify the
871 * superficial structure, headers, hashes. */
872
0284adc6
LP
873 p = le64toh(f->header->header_size);
874 while (p != 0) {
b72631e5 875 if (show_progress)
45c047b2 876 draw_progress(scale_progress(0x7FFF, p, le64toh(f->header->tail_object_offset)), &last_usec);
0284adc6 877
d05089d8 878 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o);
0284adc6 879 if (r < 0) {
54f3ff07 880 error(p, "invalid object");
0284adc6
LP
881 goto fail;
882 }
883
97147f8c 884 if (p > le64toh(f->header->tail_object_offset)) {
54f3ff07 885 error(offsetof(Header, tail_object_offset), "invalid tail object pointer");
0284adc6
LP
886 r = -EBADMSG;
887 goto fail;
888 }
889
97147f8c
LP
890 if (p == le64toh(f->header->tail_object_offset))
891 found_last = true;
892
0284adc6
LP
893 n_objects ++;
894
92fba83e 895 r = journal_file_object_verify(f, p, o);
0284adc6 896 if (r < 0) {
54f3ff07 897 error(p, "invalid object contents: %s", strerror(-r));
0284adc6
LP
898 goto fail;
899 }
900
d89c8fdf
ZJS
901 if ((o->object.flags & OBJECT_COMPRESSED_XZ) &&
902 (o->object.flags & OBJECT_COMPRESSED_LZ4)) {
54f3ff07
ZJS
903 error(p, "objected with double compression");
904 r = -EINVAL;
d89c8fdf
ZJS
905 goto fail;
906 }
907
908 if ((o->object.flags & OBJECT_COMPRESSED_XZ) && !JOURNAL_HEADER_COMPRESSED_XZ(f->header)) {
54f3ff07 909 error(p, "XZ compressed object in file without XZ compression");
d89c8fdf
ZJS
910 r = -EBADMSG;
911 goto fail;
912 }
913
914 if ((o->object.flags & OBJECT_COMPRESSED_LZ4) && !JOURNAL_HEADER_COMPRESSED_LZ4(f->header)) {
54f3ff07 915 error(p, "LZ4 compressed object in file without LZ4 compression");
0284adc6
LP
916 r = -EBADMSG;
917 goto fail;
918 }
919
a8e5f514 920 switch (o->object.type) {
0284adc6 921
a8e5f514
LP
922 case OBJECT_DATA:
923 r = write_uint64(data_fd, p);
924 if (r < 0)
0284adc6 925 goto fail;
0284adc6 926
a8e5f514
LP
927 n_data++;
928 break;
0284adc6 929
a8e5f514
LP
930 case OBJECT_FIELD:
931 n_fields++;
932 break;
0284adc6 933
a8e5f514 934 case OBJECT_ENTRY:
8088cbd3 935 if (JOURNAL_HEADER_SEALED(f->header) && n_tags <= 0) {
54f3ff07 936 error(p, "first entry before first tag");
6c7be122
LP
937 r = -EBADMSG;
938 goto fail;
939 }
940
0284adc6
LP
941 r = write_uint64(entry_fd, p);
942 if (r < 0)
943 goto fail;
944
f7fab8a5 945 if (le64toh(o->entry.realtime) < last_tag_realtime) {
54f3ff07 946 error(p, "older entry after newer tag");
7b5fd91c
LP
947 r = -EBADMSG;
948 goto fail;
949 }
950
0284adc6
LP
951 if (!entry_seqnum_set &&
952 le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
54f3ff07 953 error(p, "head entry sequence number incorrect");
0284adc6
LP
954 r = -EBADMSG;
955 goto fail;
956 }
957
958 if (entry_seqnum_set &&
959 entry_seqnum >= le64toh(o->entry.seqnum)) {
54f3ff07 960 error(p, "entry sequence number out of synchronization");
0284adc6
LP
961 r = -EBADMSG;
962 goto fail;
963 }
964
965 entry_seqnum = le64toh(o->entry.seqnum);
966 entry_seqnum_set = true;
967
968 if (entry_monotonic_set &&
969 sd_id128_equal(entry_boot_id, o->entry.boot_id) &&
970 entry_monotonic > le64toh(o->entry.monotonic)) {
54f3ff07 971 error(p, "entry timestamp out of synchronization");
0284adc6
LP
972 r = -EBADMSG;
973 goto fail;
974 }
975
976 entry_monotonic = le64toh(o->entry.monotonic);
977 entry_boot_id = o->entry.boot_id;
978 entry_monotonic_set = true;
979
980 if (!entry_realtime_set &&
981 le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) {
54f3ff07 982 error(p, "head entry realtime timestamp incorrect");
0284adc6
LP
983 r = -EBADMSG;
984 goto fail;
985 }
986
987 entry_realtime = le64toh(o->entry.realtime);
988 entry_realtime_set = true;
989
990 n_entries ++;
a8e5f514 991 break;
0284adc6 992
a8e5f514 993 case OBJECT_DATA_HASH_TABLE:
0284adc6 994 if (n_data_hash_tables > 1) {
54f3ff07 995 error(p, "more than one data hash table");
0284adc6
LP
996 r = -EBADMSG;
997 goto fail;
998 }
999
1000 if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) ||
1001 le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
54f3ff07 1002 error(p, "header fields for data hash table invalid");
0284adc6
LP
1003 r = -EBADMSG;
1004 goto fail;
1005 }
0284adc6 1006
a8e5f514
LP
1007 n_data_hash_tables++;
1008 break;
1009
1010 case OBJECT_FIELD_HASH_TABLE:
0284adc6 1011 if (n_field_hash_tables > 1) {
54f3ff07 1012 error(p, "more than one field hash table");
0284adc6
LP
1013 r = -EBADMSG;
1014 goto fail;
1015 }
1016
1017 if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) ||
1018 le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
54f3ff07 1019 error(p, "header fields for field hash table invalid");
0284adc6
LP
1020 r = -EBADMSG;
1021 goto fail;
1022 }
a8e5f514
LP
1023
1024 n_field_hash_tables++;
1025 break;
1026
1027 case OBJECT_ENTRY_ARRAY:
1028 r = write_uint64(entry_array_fd, p);
1029 if (r < 0)
1030 goto fail;
1031
1032 if (p == le64toh(f->header->entry_array_offset)) {
1033 if (found_main_entry_array) {
54f3ff07 1034 error(p, "more than one main entry array");
a8e5f514
LP
1035 r = -EBADMSG;
1036 goto fail;
1037 }
1038
1039 found_main_entry_array = true;
1040 }
1041
1042 n_entry_arrays++;
1043 break;
1044
feb12d3e 1045 case OBJECT_TAG:
8088cbd3 1046 if (!JOURNAL_HEADER_SEALED(f->header)) {
54f3ff07 1047 error(p, "tag object in file without sealing");
a8e5f514
LP
1048 r = -EBADMSG;
1049 goto fail;
1050 }
1051
1052 if (le64toh(o->tag.seqnum) != n_tags + 1) {
54f3ff07 1053 error(p, "tag sequence number out of synchronization");
a8e5f514
LP
1054 r = -EBADMSG;
1055 goto fail;
1056 }
1057
14d10188 1058 if (le64toh(o->tag.epoch) < last_epoch) {
54f3ff07 1059 error(p, "epoch sequence out of synchronization");
7b5fd91c
LP
1060 r = -EBADMSG;
1061 goto fail;
1062 }
1063
feb12d3e 1064#ifdef HAVE_GCRYPT
c586dbf1 1065 if (f->seal) {
feb12d3e
LP
1066 uint64_t q, rt;
1067
54f3ff07 1068 debug(p, "checking tag %"PRIu64"...", le64toh(o->tag.seqnum));
14d10188 1069
f7fab8a5
LP
1070 rt = f->fss_start_usec + o->tag.epoch * f->fss_interval_usec;
1071 if (entry_realtime_set && entry_realtime >= rt + f->fss_interval_usec) {
54f3ff07 1072 error(p, "tag/entry realtime timestamp out of synchronization");
c586dbf1
LP
1073 r = -EBADMSG;
1074 goto fail;
1075 }
14d10188 1076
c586dbf1
LP
1077 /* OK, now we know the epoch. So let's now set
1078 * it, and calculate the HMAC for everything
1079 * since the last tag. */
1080 r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch));
14d10188
LP
1081 if (r < 0)
1082 goto fail;
1083
c586dbf1 1084 r = journal_file_hmac_start(f);
14d10188
LP
1085 if (r < 0)
1086 goto fail;
1087
c586dbf1
LP
1088 if (last_tag == 0) {
1089 r = journal_file_hmac_put_header(f);
1090 if (r < 0)
1091 goto fail;
1092
1093 q = le64toh(f->header->header_size);
1094 } else
1095 q = last_tag;
1096
1097 while (q <= p) {
d05089d8 1098 r = journal_file_move_to_object(f, OBJECT_UNUSED, q, &o);
c586dbf1
LP
1099 if (r < 0)
1100 goto fail;
1101
d05089d8 1102 r = journal_file_hmac_put_object(f, OBJECT_UNUSED, o, q);
c586dbf1
LP
1103 if (r < 0)
1104 goto fail;
1105
1106 q = q + ALIGN64(le64toh(o->object.size));
1107 }
1108
1109 /* Position might have changed, let's reposition things */
d05089d8 1110 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o);
14d10188
LP
1111 if (r < 0)
1112 goto fail;
1113
c586dbf1 1114 if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) {
54f3ff07 1115 error(p, "tag failed verification");
c586dbf1
LP
1116 r = -EBADMSG;
1117 goto fail;
1118 }
14d10188 1119
c586dbf1
LP
1120 f->hmac_running = false;
1121 last_tag_realtime = rt;
f7fab8a5 1122 last_sealed_realtime = entry_realtime;
14d10188
LP
1123 }
1124
14d10188 1125 last_tag = p + ALIGN64(le64toh(o->object.size));
0ab5c3ed
ZJS
1126#endif
1127
c586dbf1 1128 last_epoch = le64toh(o->tag.epoch);
6c7be122 1129
a8e5f514
LP
1130 n_tags ++;
1131 break;
1132
1133 default:
0284adc6 1134 n_weird ++;
a8e5f514 1135 }
0284adc6
LP
1136
1137 if (p == le64toh(f->header->tail_object_offset))
1138 p = 0;
1139 else
1140 p = p + ALIGN64(le64toh(o->object.size));
1141 }
1142
97147f8c 1143 if (!found_last) {
54f3ff07 1144 error(le64toh(f->header->tail_object_offset), "tail object pointer dead");
97147f8c
LP
1145 r = -EBADMSG;
1146 goto fail;
1147 }
1148
0284adc6 1149 if (n_objects != le64toh(f->header->n_objects)) {
54f3ff07 1150 error(offsetof(Header, n_objects), "object number mismatch");
0284adc6
LP
1151 r = -EBADMSG;
1152 goto fail;
1153 }
1154
1155 if (n_entries != le64toh(f->header->n_entries)) {
54f3ff07 1156 error(offsetof(Header, n_entries), "entry number mismatch");
0284adc6
LP
1157 r = -EBADMSG;
1158 goto fail;
1159 }
1160
1161 if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
1162 n_data != le64toh(f->header->n_data)) {
54f3ff07 1163 error(offsetof(Header, n_data), "data number mismatch");
0284adc6
LP
1164 r = -EBADMSG;
1165 goto fail;
1166 }
1167
1168 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
1169 n_fields != le64toh(f->header->n_fields)) {
54f3ff07 1170 error(offsetof(Header, n_fields), "field number mismatch");
0284adc6
LP
1171 r = -EBADMSG;
1172 goto fail;
1173 }
1174
1175 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) &&
a8e5f514 1176 n_tags != le64toh(f->header->n_tags)) {
54f3ff07 1177 error(offsetof(Header, n_tags), "tag number mismatch");
0284adc6
LP
1178 r = -EBADMSG;
1179 goto fail;
1180 }
1181
2dee23eb
LP
1182 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays) &&
1183 n_entry_arrays != le64toh(f->header->n_entry_arrays)) {
54f3ff07 1184 error(offsetof(Header, n_entry_arrays), "entry array number mismatch");
2dee23eb
LP
1185 r = -EBADMSG;
1186 goto fail;
1187 }
1188
0284adc6 1189 if (n_data_hash_tables != 1) {
54f3ff07 1190 error(0, "missing data hash table");
0284adc6
LP
1191 r = -EBADMSG;
1192 goto fail;
1193 }
1194
1195 if (n_field_hash_tables != 1) {
54f3ff07 1196 error(0, "missing field hash table");
0284adc6
LP
1197 r = -EBADMSG;
1198 goto fail;
1199 }
1200
1201 if (!found_main_entry_array) {
54f3ff07 1202 error(0, "missing entry array");
0284adc6
LP
1203 r = -EBADMSG;
1204 goto fail;
1205 }
1206
1207 if (entry_seqnum_set &&
1208 entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
54f3ff07 1209 error(offsetof(Header, tail_entry_seqnum), "invalid tail seqnum");
0284adc6
LP
1210 r = -EBADMSG;
1211 goto fail;
1212 }
1213
1214 if (entry_monotonic_set &&
1215 (!sd_id128_equal(entry_boot_id, f->header->boot_id) ||
1216 entry_monotonic != le64toh(f->header->tail_entry_monotonic))) {
54f3ff07 1217 error(0, "invalid tail monotonic timestamp");
0284adc6
LP
1218 r = -EBADMSG;
1219 goto fail;
1220 }
1221
1222 if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
54f3ff07 1223 error(0, "invalid tail realtime timestamp");
0284adc6
LP
1224 r = -EBADMSG;
1225 goto fail;
1226 }
1227
86adf873
LP
1228 /* Second iteration: we follow all objects referenced from the
1229 * two entry points: the object hash table and the entry
1230 * array. We also check that everything referenced (directly
1231 * or indirectly) in the data hash table also exists in the
1232 * entry array, and vice versa. Note that we do not care for
1233 * unreferenced objects. We only care that everything that is
1234 * referenced is consistent. */
1235
1236 r = verify_entry_array(f,
1237 data_fd, n_data,
1238 entry_fd, n_entries,
1239 entry_array_fd, n_entry_arrays,
b72631e5
LP
1240 &last_usec,
1241 show_progress);
86adf873
LP
1242 if (r < 0)
1243 goto fail;
0284adc6 1244
86adf873
LP
1245 r = verify_hash_table(f,
1246 data_fd, n_data,
1247 entry_fd, n_entries,
1248 entry_array_fd, n_entry_arrays,
b72631e5
LP
1249 &last_usec,
1250 show_progress);
86adf873
LP
1251 if (r < 0)
1252 goto fail;
0284adc6 1253
b72631e5
LP
1254 if (show_progress)
1255 flush_progress();
0284adc6
LP
1256
1257 mmap_cache_close_fd(f->mmap, data_fd);
1258 mmap_cache_close_fd(f->mmap, entry_fd);
1259 mmap_cache_close_fd(f->mmap, entry_array_fd);
1260
03e334a1
LP
1261 safe_close(data_fd);
1262 safe_close(entry_fd);
1263 safe_close(entry_array_fd);
0284adc6 1264
2a7b539a
LP
1265 if (first_contained)
1266 *first_contained = le64toh(f->header->head_entry_realtime);
6c7be122 1267 if (last_validated)
f7fab8a5 1268 *last_validated = last_sealed_realtime;
6c7be122
LP
1269 if (last_contained)
1270 *last_contained = le64toh(f->header->tail_entry_realtime);
1271
0284adc6
LP
1272 return 0;
1273
1274fail:
b72631e5
LP
1275 if (show_progress)
1276 flush_progress();
0284adc6 1277
92fba83e 1278 log_error("File corruption detected at %s:"OFSfmt" (of %llu bytes, %"PRIu64"%%).",
0284adc6 1279 f->path,
507f22bd 1280 p,
0284adc6 1281 (unsigned long long) f->last_stat.st_size,
507f22bd 1282 100 * p / f->last_stat.st_size);
0284adc6
LP
1283
1284 if (data_fd >= 0) {
1285 mmap_cache_close_fd(f->mmap, data_fd);
03e334a1 1286 safe_close(data_fd);
0284adc6
LP
1287 }
1288
1289 if (entry_fd >= 0) {
1290 mmap_cache_close_fd(f->mmap, entry_fd);
03e334a1 1291 safe_close(entry_fd);
0284adc6
LP
1292 }
1293
1294 if (entry_array_fd >= 0) {
1295 mmap_cache_close_fd(f->mmap, entry_array_fd);
03e334a1 1296 safe_close(entry_array_fd);
0284adc6
LP
1297 }
1298
1299 return r;
1300}