1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2011 Lennart Poettering
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.
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.
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/>.
26 #include <sys/statvfs.h>
30 #include "journal-def.h"
31 #include "journal-file.h"
36 #define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem))
37 #define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem))
39 #define COMPRESSION_SIZE_THRESHOLD (512ULL)
41 /* This is the minimum journal file size */
42 #define JOURNAL_FILE_SIZE_MIN (64ULL*1024ULL) /* 64 KiB */
44 /* These are the lower and upper bounds if we deduce the max_use value
45 * from the file system size */
46 #define DEFAULT_MAX_USE_LOWER (1ULL*1024ULL*1024ULL) /* 1 MiB */
47 #define DEFAULT_MAX_USE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
49 /* This is the upper bound if we deduce max_size from max_use */
50 #define DEFAULT_MAX_SIZE_UPPER (128ULL*1024ULL*1024ULL) /* 128 MiB */
52 /* This is the upper bound if we deduce the keep_free value from the
54 #define DEFAULT_KEEP_FREE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
56 /* This is the keep_free value when we can't determine the system
58 #define DEFAULT_KEEP_FREE (1024ULL*1024ULL) /* 1 MB */
60 /* n_data was the first entry we added after the initial file format design */
61 #define HEADER_SIZE_MIN ALIGN64(offsetof(Header, n_data))
63 #define ALIGN64(x) (((x) + 7ULL) & ~7ULL)
65 #define JOURNAL_HEADER_CONTAINS(h, field) \
66 (le64toh((h)->header_size) >= offsetof(Header, field) + sizeof((h)->field))
68 static int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime);
69 static int journal_file_hmac_put_object(JournalFile *f, int type, uint64_t p);
71 void journal_file_close(JournalFile *f) {
74 /* Write the final tag */
76 journal_file_append_tag(f);
78 /* Sync everything to disk, before we mark the file offline */
79 if (f->mmap && f->fd >= 0)
80 mmap_cache_close_fd(f->mmap, f->fd);
82 if (f->writable && f->fd >= 0)
86 /* Mark the file offline. Don't override the archived state if it already is set */
87 if (f->writable && f->header->state == STATE_ONLINE)
88 f->header->state = STATE_OFFLINE;
90 munmap(f->header, PAGE_ALIGN(sizeof(Header)));
94 close_nointr_nofail(f->fd);
99 mmap_cache_unref(f->mmap);
102 free(f->compress_buffer);
107 munmap(f->fsprg_header, PAGE_ALIGN(f->fsprg_size));
110 gcry_md_close(f->hmac);
116 static int journal_file_init_header(JournalFile *f, JournalFile *template) {
124 memcpy(h.signature, HEADER_SIGNATURE, 8);
125 h.header_size = htole64(ALIGN64(sizeof(h)));
127 h.incompatible_flags =
128 htole32(f->compress ? HEADER_INCOMPATIBLE_COMPRESSED : 0);
131 htole32(f->authenticate ? HEADER_COMPATIBLE_AUTHENTICATED : 0);
133 r = sd_id128_randomize(&h.file_id);
138 h.seqnum_id = template->header->seqnum_id;
139 h.tail_entry_seqnum = template->header->tail_entry_seqnum;
141 h.seqnum_id = h.file_id;
143 k = pwrite(f->fd, &h, sizeof(h), 0);
153 static int journal_file_refresh_header(JournalFile *f) {
159 r = sd_id128_get_machine(&f->header->machine_id);
163 r = sd_id128_get_boot(&boot_id);
167 if (sd_id128_equal(boot_id, f->header->boot_id))
168 f->tail_entry_monotonic_valid = true;
170 f->header->boot_id = boot_id;
172 f->header->state = STATE_ONLINE;
174 /* Sync the online state to disk */
175 msync(f->header, PAGE_ALIGN(sizeof(Header)), MS_SYNC);
181 static int journal_file_verify_header(JournalFile *f) {
184 if (memcmp(f->header->signature, HEADER_SIGNATURE, 8))
187 /* In both read and write mode we refuse to open files with
188 * incompatible flags we don't know */
190 if ((le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) != 0)
191 return -EPROTONOSUPPORT;
193 if (f->header->incompatible_flags != 0)
194 return -EPROTONOSUPPORT;
197 /* When open for writing we refuse to open files with
198 * compatible flags, too */
201 if ((le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_AUTHENTICATED) != 0)
202 return -EPROTONOSUPPORT;
204 if (f->header->compatible_flags != 0)
205 return -EPROTONOSUPPORT;
209 /* The first addition was n_data, so check that we are at least this large */
210 if (le64toh(f->header->header_size) < HEADER_SIZE_MIN)
213 if ((le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED) &&
214 !JOURNAL_HEADER_CONTAINS(f->header, n_tags))
217 if ((uint64_t) f->last_stat.st_size < (le64toh(f->header->header_size) + le64toh(f->header->arena_size)))
222 sd_id128_t machine_id;
225 r = sd_id128_get_machine(&machine_id);
229 if (!sd_id128_equal(machine_id, f->header->machine_id))
232 state = f->header->state;
234 if (state == STATE_ONLINE) {
235 log_debug("Journal file %s is already online. Assuming unclean closing.", f->path);
237 } else if (state == STATE_ARCHIVED)
239 else if (state != STATE_OFFLINE) {
240 log_debug("Journal file %s has unknown state %u.", f->path, state);
245 f->compress = !!(le32toh(f->header->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED);
246 f->authenticate = !!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED);
251 static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
252 uint64_t old_size, new_size;
257 /* We assume that this file is not sparse, and we know that
258 * for sure, since we always call posix_fallocate()
262 le64toh(f->header->header_size) +
263 le64toh(f->header->arena_size);
265 new_size = PAGE_ALIGN(offset + size);
266 if (new_size < le64toh(f->header->header_size))
267 new_size = le64toh(f->header->header_size);
269 if (new_size <= old_size)
272 if (f->metrics.max_size > 0 &&
273 new_size > f->metrics.max_size)
276 if (new_size > f->metrics.min_size &&
277 f->metrics.keep_free > 0) {
280 if (fstatvfs(f->fd, &svfs) >= 0) {
283 available = svfs.f_bfree * svfs.f_bsize;
285 if (available >= f->metrics.keep_free)
286 available -= f->metrics.keep_free;
290 if (new_size - old_size > available)
295 /* Note that the glibc fallocate() fallback is very
296 inefficient, hence we try to minimize the allocation area
298 r = posix_fallocate(f->fd, old_size, new_size - old_size);
302 if (fstat(f->fd, &f->last_stat) < 0)
305 f->header->arena_size = htole64(new_size - le64toh(f->header->header_size));
310 static int journal_file_move_to(JournalFile *f, int context, uint64_t offset, uint64_t size, void **ret) {
314 /* Avoid SIGBUS on invalid accesses */
315 if (offset + size > (uint64_t) f->last_stat.st_size) {
316 /* Hmm, out of range? Let's refresh the fstat() data
317 * first, before we trust that check. */
319 if (fstat(f->fd, &f->last_stat) < 0 ||
320 offset + size > (uint64_t) f->last_stat.st_size)
321 return -EADDRNOTAVAIL;
324 return mmap_cache_get(f->mmap, f->fd, f->prot, context, offset, size, ret);
327 static bool verify_hash(Object *o) {
332 if (o->object.type == OBJECT_DATA && !(o->object.flags & OBJECT_COMPRESSED)) {
333 h1 = le64toh(o->data.hash);
334 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
335 } else if (o->object.type == OBJECT_FIELD) {
336 h1 = le64toh(o->field.hash);
337 h2 = hash64(o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload));
344 static uint64_t minimum_header_size(Object *o) {
346 static uint64_t table[] = {
347 [OBJECT_DATA] = sizeof(DataObject),
348 [OBJECT_FIELD] = sizeof(FieldObject),
349 [OBJECT_ENTRY] = sizeof(EntryObject),
350 [OBJECT_DATA_HASH_TABLE] = sizeof(HashTableObject),
351 [OBJECT_FIELD_HASH_TABLE] = sizeof(HashTableObject),
352 [OBJECT_ENTRY_ARRAY] = sizeof(EntryArrayObject),
353 [OBJECT_TAG] = sizeof(TagObject),
356 if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0)
357 return sizeof(ObjectHeader);
359 return table[o->object.type];
362 int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret) {
372 /* One context for each type, plus one catch-all for the rest */
373 context = type > 0 && type < _OBJECT_TYPE_MAX ? type : 0;
375 r = journal_file_move_to(f, context, offset, sizeof(ObjectHeader), &t);
380 s = le64toh(o->object.size);
382 if (s < sizeof(ObjectHeader))
385 if (o->object.type <= OBJECT_UNUSED)
388 if (s < minimum_header_size(o))
391 if (type >= 0 && o->object.type != type)
394 if (s > sizeof(ObjectHeader)) {
395 r = journal_file_move_to(f, o->object.type, offset, s, &t);
409 static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) {
414 r = le64toh(f->header->tail_entry_seqnum) + 1;
417 /* If an external seqnum counter was passed, we update
418 * both the local and the external one, and set it to
419 * the maximum of both */
427 f->header->tail_entry_seqnum = htole64(r);
429 if (f->header->head_entry_seqnum == 0)
430 f->header->head_entry_seqnum = htole64(r);
435 static int journal_file_append_object(JournalFile *f, int type, uint64_t size, Object **ret, uint64_t *offset) {
442 assert(type > 0 && type < _OBJECT_TYPE_MAX);
443 assert(size >= sizeof(ObjectHeader));
447 p = le64toh(f->header->tail_object_offset);
449 p = le64toh(f->header->header_size);
451 r = journal_file_move_to_object(f, -1, p, &tail);
455 p += ALIGN64(le64toh(tail->object.size));
458 r = journal_file_allocate(f, p, size);
462 r = journal_file_move_to(f, type, p, size, &t);
469 o->object.type = type;
470 o->object.size = htole64(size);
472 f->header->tail_object_offset = htole64(p);
473 f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1);
481 static int journal_file_setup_data_hash_table(JournalFile *f) {
488 /* We estimate that we need 1 hash table entry per 768 of
489 journal file and we want to make sure we never get beyond
490 75% fill level. Calculate the hash table size for the
491 maximum file size based on these metrics. */
493 s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem);
494 if (s < DEFAULT_DATA_HASH_TABLE_SIZE)
495 s = DEFAULT_DATA_HASH_TABLE_SIZE;
497 log_info("Reserving %llu entries in hash table.", (unsigned long long) (s / sizeof(HashItem)));
499 r = journal_file_append_object(f,
500 OBJECT_DATA_HASH_TABLE,
501 offsetof(Object, hash_table.items) + s,
506 memset(o->hash_table.items, 0, s);
508 f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
509 f->header->data_hash_table_size = htole64(s);
514 static int journal_file_setup_field_hash_table(JournalFile *f) {
521 s = DEFAULT_FIELD_HASH_TABLE_SIZE;
522 r = journal_file_append_object(f,
523 OBJECT_FIELD_HASH_TABLE,
524 offsetof(Object, hash_table.items) + s,
529 memset(o->hash_table.items, 0, s);
531 f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
532 f->header->field_hash_table_size = htole64(s);
537 static int journal_file_map_data_hash_table(JournalFile *f) {
544 p = le64toh(f->header->data_hash_table_offset);
545 s = le64toh(f->header->data_hash_table_size);
547 r = journal_file_move_to(f,
548 OBJECT_DATA_HASH_TABLE,
554 f->data_hash_table = t;
558 static int journal_file_map_field_hash_table(JournalFile *f) {
565 p = le64toh(f->header->field_hash_table_offset);
566 s = le64toh(f->header->field_hash_table_size);
568 r = journal_file_move_to(f,
569 OBJECT_FIELD_HASH_TABLE,
575 f->field_hash_table = t;
579 static int journal_file_link_data(JournalFile *f, Object *o, uint64_t offset, uint64_t hash) {
586 assert(o->object.type == OBJECT_DATA);
588 /* This might alter the window we are looking at */
590 o->data.next_hash_offset = o->data.next_field_offset = 0;
591 o->data.entry_offset = o->data.entry_array_offset = 0;
592 o->data.n_entries = 0;
594 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
595 p = le64toh(f->data_hash_table[h].tail_hash_offset);
597 /* Only entry in the hash table is easy */
598 f->data_hash_table[h].head_hash_offset = htole64(offset);
600 /* Move back to the previous data object, to patch in
603 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
607 o->data.next_hash_offset = htole64(offset);
610 f->data_hash_table[h].tail_hash_offset = htole64(offset);
612 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
613 f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
618 int journal_file_find_data_object_with_hash(
620 const void *data, uint64_t size, uint64_t hash,
621 Object **ret, uint64_t *offset) {
623 uint64_t p, osize, h;
627 assert(data || size == 0);
629 osize = offsetof(Object, data.payload) + size;
631 if (f->header->data_hash_table_size == 0)
634 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
635 p = le64toh(f->data_hash_table[h].head_hash_offset);
640 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
644 if (le64toh(o->data.hash) != hash)
647 if (o->object.flags & OBJECT_COMPRESSED) {
651 l = le64toh(o->object.size);
652 if (l <= offsetof(Object, data.payload))
655 l -= offsetof(Object, data.payload);
657 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
661 memcmp(f->compress_buffer, data, size) == 0) {
672 return -EPROTONOSUPPORT;
675 } else if (le64toh(o->object.size) == osize &&
676 memcmp(o->data.payload, data, size) == 0) {
688 p = le64toh(o->data.next_hash_offset);
694 int journal_file_find_data_object(
696 const void *data, uint64_t size,
697 Object **ret, uint64_t *offset) {
702 assert(data || size == 0);
704 hash = hash64(data, size);
706 return journal_file_find_data_object_with_hash(f,
711 static int journal_file_append_data(
713 const void *data, uint64_t size,
714 Object **ret, uint64_t *offset) {
720 bool compressed = false;
723 assert(data || size == 0);
725 hash = hash64(data, size);
727 r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
741 osize = offsetof(Object, data.payload) + size;
742 r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
746 o->data.hash = htole64(hash);
750 size >= COMPRESSION_SIZE_THRESHOLD) {
753 compressed = compress_blob(data, size, o->data.payload, &rsize);
756 o->object.size = htole64(offsetof(Object, data.payload) + rsize);
757 o->object.flags |= OBJECT_COMPRESSED;
759 log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize);
764 if (!compressed && size > 0)
765 memcpy(o->data.payload, data, size);
767 r = journal_file_link_data(f, o, p, hash);
771 r = journal_file_hmac_put_object(f, OBJECT_DATA, p);
775 /* The linking might have altered the window, so let's
776 * refresh our pointer */
777 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
790 uint64_t journal_file_entry_n_items(Object *o) {
792 assert(o->object.type == OBJECT_ENTRY);
794 return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
797 static uint64_t journal_file_entry_array_n_items(Object *o) {
799 assert(o->object.type == OBJECT_ENTRY_ARRAY);
801 return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
804 static int link_entry_into_array(JournalFile *f,
809 uint64_t n = 0, ap = 0, q, i, a, hidx;
818 i = hidx = le64toh(*idx);
821 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
825 n = journal_file_entry_array_n_items(o);
827 o->entry_array.items[i] = htole64(p);
828 *idx = htole64(hidx + 1);
834 a = le64toh(o->entry_array.next_entry_array_offset);
845 r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
846 offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
851 r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, q);
855 o->entry_array.items[i] = htole64(p);
860 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
864 o->entry_array.next_entry_array_offset = htole64(q);
867 *idx = htole64(hidx + 1);
872 static int link_entry_into_array_plus_one(JournalFile *f,
891 i = htole64(le64toh(*idx) - 1);
892 r = link_entry_into_array(f, first, &i, p);
897 *idx = htole64(le64toh(*idx) + 1);
901 static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
908 p = le64toh(o->entry.items[i].object_offset);
912 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
916 return link_entry_into_array_plus_one(f,
917 &o->data.entry_offset,
918 &o->data.entry_array_offset,
923 static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
930 assert(o->object.type == OBJECT_ENTRY);
932 __sync_synchronize();
934 /* Link up the entry itself */
935 r = link_entry_into_array(f,
936 &f->header->entry_array_offset,
937 &f->header->n_entries,
942 /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */
944 if (f->header->head_entry_realtime == 0)
945 f->header->head_entry_realtime = o->entry.realtime;
947 f->header->tail_entry_realtime = o->entry.realtime;
948 f->header->tail_entry_monotonic = o->entry.monotonic;
950 f->tail_entry_monotonic_valid = true;
952 /* Link up the items */
953 n = journal_file_entry_n_items(o);
954 for (i = 0; i < n; i++) {
955 r = journal_file_link_entry_item(f, o, offset, i);
963 static int journal_file_append_entry_internal(
965 const dual_timestamp *ts,
967 const EntryItem items[], unsigned n_items,
969 Object **ret, uint64_t *offset) {
976 assert(items || n_items == 0);
979 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
981 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
985 o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum));
986 memcpy(o->entry.items, items, n_items * sizeof(EntryItem));
987 o->entry.realtime = htole64(ts->realtime);
988 o->entry.monotonic = htole64(ts->monotonic);
989 o->entry.xor_hash = htole64(xor_hash);
990 o->entry.boot_id = f->header->boot_id;
992 r = journal_file_hmac_put_object(f, OBJECT_ENTRY, np);
996 r = journal_file_link_entry(f, o, np);
1009 void journal_file_post_change(JournalFile *f) {
1012 /* inotify() does not receive IN_MODIFY events from file
1013 * accesses done via mmap(). After each access we hence
1014 * trigger IN_MODIFY by truncating the journal file to its
1015 * current size which triggers IN_MODIFY. */
1017 __sync_synchronize();
1019 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
1020 log_error("Failed to to truncate file to its own size: %m");
1023 int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const struct iovec iovec[], unsigned n_iovec, uint64_t *seqnum, Object **ret, uint64_t *offset) {
1027 uint64_t xor_hash = 0;
1028 struct dual_timestamp _ts;
1031 assert(iovec || n_iovec == 0);
1037 dual_timestamp_get(&_ts);
1041 if (f->tail_entry_monotonic_valid &&
1042 ts->monotonic < le64toh(f->header->tail_entry_monotonic))
1045 r = journal_file_maybe_append_tag(f, ts->realtime);
1049 /* alloca() can't take 0, hence let's allocate at least one */
1050 items = alloca(sizeof(EntryItem) * MAX(1, n_iovec));
1052 for (i = 0; i < n_iovec; i++) {
1056 r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
1060 xor_hash ^= le64toh(o->data.hash);
1061 items[i].object_offset = htole64(p);
1062 items[i].hash = o->data.hash;
1065 r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
1067 journal_file_post_change(f);
1072 static int generic_array_get(JournalFile *f,
1075 Object **ret, uint64_t *offset) {
1087 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1091 n = journal_file_entry_array_n_items(o);
1093 p = le64toh(o->entry_array.items[i]);
1098 a = le64toh(o->entry_array.next_entry_array_offset);
1101 if (a <= 0 || p <= 0)
1104 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1117 static int generic_array_get_plus_one(JournalFile *f,
1121 Object **ret, uint64_t *offset) {
1130 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1143 return generic_array_get(f, first, i-1, ret, offset);
1152 static int generic_array_bisect(JournalFile *f,
1156 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1157 direction_t direction,
1162 uint64_t a, p, t = 0, i = 0, last_p = 0;
1163 bool subtract_one = false;
1164 Object *o, *array = NULL;
1168 assert(test_object);
1172 uint64_t left, right, k, lp;
1174 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
1178 k = journal_file_entry_array_n_items(array);
1184 lp = p = le64toh(array->entry_array.items[i]);
1188 r = test_object(f, p, needle);
1192 if (r == TEST_FOUND)
1193 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1195 if (r == TEST_RIGHT) {
1199 if (left == right) {
1200 if (direction == DIRECTION_UP)
1201 subtract_one = true;
1207 assert(left < right);
1209 i = (left + right) / 2;
1210 p = le64toh(array->entry_array.items[i]);
1214 r = test_object(f, p, needle);
1218 if (r == TEST_FOUND)
1219 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1221 if (r == TEST_RIGHT)
1229 if (direction == DIRECTION_UP) {
1231 subtract_one = true;
1242 a = le64toh(array->entry_array.next_entry_array_offset);
1248 if (subtract_one && t == 0 && i == 0)
1251 if (subtract_one && i == 0)
1253 else if (subtract_one)
1254 p = le64toh(array->entry_array.items[i-1]);
1256 p = le64toh(array->entry_array.items[i]);
1258 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1269 *idx = t + i + (subtract_one ? -1 : 0);
1274 static int generic_array_bisect_plus_one(JournalFile *f,
1279 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1280 direction_t direction,
1286 bool step_back = false;
1290 assert(test_object);
1295 /* This bisects the array in object 'first', but first checks
1297 r = test_object(f, extra, needle);
1301 if (r == TEST_FOUND)
1302 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1304 /* if we are looking with DIRECTION_UP then we need to first
1305 see if in the actual array there is a matching entry, and
1306 return the last one of that. But if there isn't any we need
1307 to return this one. Hence remember this, and return it
1310 step_back = direction == DIRECTION_UP;
1312 if (r == TEST_RIGHT) {
1313 if (direction == DIRECTION_DOWN)
1319 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
1321 if (r == 0 && step_back)
1330 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1346 static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
1352 else if (p < needle)
1358 int journal_file_move_to_entry_by_offset(
1361 direction_t direction,
1365 return generic_array_bisect(f,
1366 le64toh(f->header->entry_array_offset),
1367 le64toh(f->header->n_entries),
1375 static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
1382 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1386 if (le64toh(o->entry.seqnum) == needle)
1388 else if (le64toh(o->entry.seqnum) < needle)
1394 int journal_file_move_to_entry_by_seqnum(
1397 direction_t direction,
1401 return generic_array_bisect(f,
1402 le64toh(f->header->entry_array_offset),
1403 le64toh(f->header->n_entries),
1410 static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
1417 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1421 if (le64toh(o->entry.realtime) == needle)
1423 else if (le64toh(o->entry.realtime) < needle)
1429 int journal_file_move_to_entry_by_realtime(
1432 direction_t direction,
1436 return generic_array_bisect(f,
1437 le64toh(f->header->entry_array_offset),
1438 le64toh(f->header->n_entries),
1440 test_object_realtime,
1445 static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
1452 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1456 if (le64toh(o->entry.monotonic) == needle)
1458 else if (le64toh(o->entry.monotonic) < needle)
1464 int journal_file_move_to_entry_by_monotonic(
1468 direction_t direction,
1472 char t[9+32+1] = "_BOOT_ID=";
1478 sd_id128_to_string(boot_id, t + 9);
1479 r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
1485 return generic_array_bisect_plus_one(f,
1486 le64toh(o->data.entry_offset),
1487 le64toh(o->data.entry_array_offset),
1488 le64toh(o->data.n_entries),
1490 test_object_monotonic,
1495 int journal_file_next_entry(
1497 Object *o, uint64_t p,
1498 direction_t direction,
1499 Object **ret, uint64_t *offset) {
1505 assert(p > 0 || !o);
1507 n = le64toh(f->header->n_entries);
1512 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1514 if (o->object.type != OBJECT_ENTRY)
1517 r = generic_array_bisect(f,
1518 le64toh(f->header->entry_array_offset),
1519 le64toh(f->header->n_entries),
1528 if (direction == DIRECTION_DOWN) {
1541 /* And jump to it */
1542 return generic_array_get(f,
1543 le64toh(f->header->entry_array_offset),
1548 int journal_file_skip_entry(
1550 Object *o, uint64_t p,
1552 Object **ret, uint64_t *offset) {
1561 if (o->object.type != OBJECT_ENTRY)
1564 r = generic_array_bisect(f,
1565 le64toh(f->header->entry_array_offset),
1566 le64toh(f->header->n_entries),
1575 /* Calculate new index */
1577 if ((uint64_t) -skip >= i)
1580 i = i - (uint64_t) -skip;
1582 i += (uint64_t) skip;
1584 n = le64toh(f->header->n_entries);
1591 return generic_array_get(f,
1592 le64toh(f->header->entry_array_offset),
1597 int journal_file_next_entry_for_data(
1599 Object *o, uint64_t p,
1600 uint64_t data_offset,
1601 direction_t direction,
1602 Object **ret, uint64_t *offset) {
1609 assert(p > 0 || !o);
1611 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1615 n = le64toh(d->data.n_entries);
1620 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1622 if (o->object.type != OBJECT_ENTRY)
1625 r = generic_array_bisect_plus_one(f,
1626 le64toh(d->data.entry_offset),
1627 le64toh(d->data.entry_array_offset),
1628 le64toh(d->data.n_entries),
1638 if (direction == DIRECTION_DOWN) {
1652 return generic_array_get_plus_one(f,
1653 le64toh(d->data.entry_offset),
1654 le64toh(d->data.entry_array_offset),
1659 int journal_file_move_to_entry_by_offset_for_data(
1661 uint64_t data_offset,
1663 direction_t direction,
1664 Object **ret, uint64_t *offset) {
1671 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1675 return generic_array_bisect_plus_one(f,
1676 le64toh(d->data.entry_offset),
1677 le64toh(d->data.entry_array_offset),
1678 le64toh(d->data.n_entries),
1685 int journal_file_move_to_entry_by_monotonic_for_data(
1687 uint64_t data_offset,
1690 direction_t direction,
1691 Object **ret, uint64_t *offset) {
1693 char t[9+32+1] = "_BOOT_ID=";
1700 /* First, seek by time */
1701 sd_id128_to_string(boot_id, t + 9);
1702 r = journal_file_find_data_object(f, t, strlen(t), &o, &b);
1708 r = generic_array_bisect_plus_one(f,
1709 le64toh(o->data.entry_offset),
1710 le64toh(o->data.entry_array_offset),
1711 le64toh(o->data.n_entries),
1713 test_object_monotonic,
1719 /* And now, continue seeking until we find an entry that
1720 * exists in both bisection arrays */
1726 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1730 r = generic_array_bisect_plus_one(f,
1731 le64toh(d->data.entry_offset),
1732 le64toh(d->data.entry_array_offset),
1733 le64toh(d->data.n_entries),
1741 r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
1745 r = generic_array_bisect_plus_one(f,
1746 le64toh(o->data.entry_offset),
1747 le64toh(o->data.entry_array_offset),
1748 le64toh(o->data.n_entries),
1772 int journal_file_move_to_entry_by_seqnum_for_data(
1774 uint64_t data_offset,
1776 direction_t direction,
1777 Object **ret, uint64_t *offset) {
1784 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1788 return generic_array_bisect_plus_one(f,
1789 le64toh(d->data.entry_offset),
1790 le64toh(d->data.entry_array_offset),
1791 le64toh(d->data.n_entries),
1798 int journal_file_move_to_entry_by_realtime_for_data(
1800 uint64_t data_offset,
1802 direction_t direction,
1803 Object **ret, uint64_t *offset) {
1810 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1814 return generic_array_bisect_plus_one(f,
1815 le64toh(d->data.entry_offset),
1816 le64toh(d->data.entry_array_offset),
1817 le64toh(d->data.n_entries),
1819 test_object_realtime,
1824 static void *fsprg_state(JournalFile *f) {
1828 if (!f->authenticate)
1831 a = le64toh(f->fsprg_header->header_size);
1832 b = le64toh(f->fsprg_header->state_size);
1834 if (a + b > f->fsprg_size)
1837 return (uint8_t*) f->fsprg_header + a;
1840 static uint64_t journal_file_tag_seqnum(JournalFile *f) {
1845 r = le64toh(f->header->n_tags) + 1;
1846 f->header->n_tags = htole64(r);
1851 int journal_file_append_tag(JournalFile *f) {
1858 if (!f->authenticate)
1861 if (!f->hmac_running)
1864 log_debug("Writing tag for epoch %llu\n", (unsigned long long) FSPRG_GetEpoch(fsprg_state(f)));
1868 r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p);
1872 o->tag.seqnum = htole64(journal_file_tag_seqnum(f));
1874 /* Add the tag object itself, so that we can protect its
1875 * header. This will exclude the actual hash value in it */
1876 r = journal_file_hmac_put_object(f, OBJECT_TAG, p);
1880 /* Get the HMAC tag and store it in the object */
1881 memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH);
1882 f->hmac_running = false;
1887 static int journal_file_hmac_start(JournalFile *f) {
1888 uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */
1892 if (!f->authenticate)
1895 if (f->hmac_running)
1898 /* Prepare HMAC for next cycle */
1899 gcry_md_reset(f->hmac);
1900 FSPRG_GetKey(fsprg_state(f), key, sizeof(key), 0);
1901 gcry_md_setkey(f->hmac, key, sizeof(key));
1903 f->hmac_running = true;
1908 static int journal_file_get_epoch(JournalFile *f, uint64_t realtime, uint64_t *epoch) {
1913 assert(f->authenticate);
1915 if (le64toh(f->fsprg_header->fsprg_start_usec) == 0 ||
1916 le64toh(f->fsprg_header->fsprg_interval_usec) == 0)
1919 if (realtime < le64toh(f->fsprg_header->fsprg_start_usec))
1922 t = realtime - le64toh(f->fsprg_header->fsprg_start_usec);
1923 t = t / le64toh(f->fsprg_header->fsprg_interval_usec);
1929 static int journal_file_need_evolve(JournalFile *f, uint64_t realtime) {
1930 uint64_t goal, epoch;
1934 if (!f->authenticate)
1937 r = journal_file_get_epoch(f, realtime, &goal);
1941 epoch = FSPRG_GetEpoch(fsprg_state(f));
1945 return epoch != goal;
1948 static int journal_file_evolve(JournalFile *f, uint64_t realtime) {
1949 uint64_t goal, epoch;
1954 if (!f->authenticate)
1957 r = journal_file_get_epoch(f, realtime, &goal);
1961 epoch = FSPRG_GetEpoch(fsprg_state(f));
1963 log_debug("Evolving FSPRG key from epoch %llu to %llu.", (unsigned long long) epoch, (unsigned long long) goal);
1971 FSPRG_Evolve(fsprg_state(f));
1972 epoch = FSPRG_GetEpoch(fsprg_state(f));
1976 static int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) {
1981 if (!f->authenticate)
1984 r = journal_file_need_evolve(f, realtime);
1988 r = journal_file_append_tag(f);
1992 r = journal_file_evolve(f, realtime);
1996 r = journal_file_hmac_start(f);
2003 static int journal_file_hmac_put_object(JournalFile *f, int type, uint64_t p) {
2009 if (!f->authenticate)
2012 r = journal_file_hmac_start(f);
2016 r = journal_file_move_to_object(f, type, p, &o);
2020 gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload));
2022 switch (o->object.type) {
2025 /* All but: hash and payload are mutable */
2026 gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash));
2027 gcry_md_write(f->hmac, o->data.payload, le64toh(o->object.size) - offsetof(DataObject, payload));
2032 gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(EntryObject, seqnum));
2035 case OBJECT_FIELD_HASH_TABLE:
2036 case OBJECT_DATA_HASH_TABLE:
2037 case OBJECT_ENTRY_ARRAY:
2038 /* Nothing: everything is mutable */
2042 /* All but the tag itself */
2043 gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum));
2052 static int journal_file_hmac_put_header(JournalFile *f) {
2057 if (!f->authenticate)
2060 r = journal_file_hmac_start(f);
2064 /* All but state+reserved, boot_id, arena_size,
2065 * tail_object_offset, n_objects, n_entries, tail_seqnum,
2066 * head_entry_realtime, tail_entry_realtime,
2067 * tail_entry_monotonic, n_data, n_fields, header_tag */
2069 gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature));
2070 gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, boot_id) - offsetof(Header, file_id));
2071 gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id));
2072 gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset));
2073 gcry_md_write(f->hmac, &f->header->head_entry_seqnum, offsetof(Header, head_entry_realtime) - offsetof(Header, head_entry_seqnum));
2078 static int journal_file_load_fsprg(JournalFile *f) {
2082 FSPRGHeader *m = NULL;
2087 if (!f->authenticate)
2090 r = sd_id128_get_machine(&machine);
2094 if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fsprg",
2095 SD_ID128_FORMAT_VAL(machine)) < 0)
2098 fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
2100 log_error("Failed to open %s: %m", p);
2105 if (fstat(fd, &st) < 0) {
2110 if (st.st_size < (off_t) sizeof(FSPRGHeader)) {
2115 m = mmap(NULL, PAGE_ALIGN(sizeof(FSPRGHeader)), PROT_READ, MAP_SHARED, fd, 0);
2116 if (m == MAP_FAILED) {
2122 if (memcmp(m->signature, FSPRG_HEADER_SIGNATURE, 8) != 0) {
2127 if (m->incompatible_flags != 0) {
2128 r = -EPROTONOSUPPORT;
2132 if (le64toh(m->header_size) < sizeof(FSPRGHeader)) {
2137 if (le64toh(m->state_size) != FSPRG_stateinbytes(m->secpar)) {
2142 f->fsprg_size = le64toh(m->header_size) + le64toh(m->state_size);
2143 if ((uint64_t) st.st_size < f->fsprg_size) {
2148 if (!sd_id128_equal(machine, m->machine_id)) {
2153 if (le64toh(m->fsprg_start_usec) <= 0 ||
2154 le64toh(m->fsprg_interval_usec) <= 0) {
2159 f->fsprg_header = mmap(NULL, PAGE_ALIGN(f->fsprg_size), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
2160 if (f->fsprg_header == MAP_FAILED) {
2161 f->fsprg_header = NULL;
2170 munmap(m, PAGE_ALIGN(sizeof(FSPRGHeader)));
2173 close_nointr_nofail(fd);
2179 static int journal_file_setup_hmac(JournalFile *f) {
2182 if (!f->authenticate)
2185 e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC);
2192 static int journal_file_append_first_tag(JournalFile *f) {
2196 if (!f->authenticate)
2199 log_debug("Calculating first tag...");
2201 r = journal_file_hmac_put_header(f);
2205 p = le64toh(f->header->field_hash_table_offset);
2206 if (p < offsetof(Object, hash_table.items))
2208 p -= offsetof(Object, hash_table.items);
2210 r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, p);
2214 p = le64toh(f->header->data_hash_table_offset);
2215 if (p < offsetof(Object, hash_table.items))
2217 p -= offsetof(Object, hash_table.items);
2219 r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, p);
2223 r = journal_file_append_tag(f);
2230 static int journal_file_object_verify(JournalFile *f, Object *o) {
2234 /* This does various superficial tests about the length an
2235 * possible field values. It does not follow any references to
2238 switch (o->object.type) {
2240 if (le64toh(o->data.entry_offset) <= 0 ||
2241 le64toh(o->data.n_entries) <= 0)
2244 if (le64toh(o->object.size) - offsetof(DataObject, payload) <= 0)
2249 if (le64toh(o->object.size) - offsetof(FieldObject, payload) <= 0)
2254 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0)
2257 if ((le64toh(o->object.size) - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0)
2260 if (le64toh(o->entry.seqnum) <= 0 ||
2261 le64toh(o->entry.realtime) <= 0)
2266 case OBJECT_DATA_HASH_TABLE:
2267 case OBJECT_FIELD_HASH_TABLE:
2268 if ((le64toh(o->object.size) - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0)
2273 case OBJECT_ENTRY_ARRAY:
2274 if ((le64toh(o->object.size) - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0)
2280 if (le64toh(o->object.size) != sizeof(TagObject))
2288 static void draw_progress(uint64_t p, usec_t *last_usec) {
2289 unsigned n, i, j, k;
2292 if (!isatty(STDOUT_FILENO))
2295 z = now(CLOCK_MONOTONIC);
2298 if (x != 0 && x + 40 * USEC_PER_MSEC > z)
2303 n = (3 * columns()) / 4;
2304 j = (n * (unsigned) p) / 65535ULL;
2307 fputs("\r\x1B[?25l", stdout);
2309 for (i = 0; i < j; i++)
2310 fputs("\xe2\x96\x88", stdout);
2312 for (i = 0; i < k; i++)
2313 fputs("\xe2\x96\x91", stdout);
2315 printf(" %3lu%%", 100LU * (unsigned long) p / 65535LU);
2317 fputs("\r\x1B[?25h", stdout);
2321 static void flush_progress(void) {
2324 if (!isatty(STDOUT_FILENO))
2327 n = (3 * columns()) / 4;
2331 for (i = 0; i < n + 5; i++)
2338 int journal_file_verify(JournalFile *f, const char *key) {
2341 uint64_t p = 0, q = 0, e;
2342 uint64_t tag_seqnum = 0, entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0;
2343 sd_id128_t entry_boot_id;
2344 bool entry_seqnum_set = false, entry_monotonic_set = false, entry_realtime_set = false, found_main_entry_array = false;
2345 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;
2346 usec_t last_usec = 0;
2350 /* First iteration: we go through all objects, verify the
2351 * superficial structure, headers, hashes. */
2353 r = journal_file_hmac_put_header(f);
2355 log_error("Failed to calculate HMAC of header.");
2359 p = le64toh(f->header->header_size);
2361 draw_progress((65535ULL * p / le64toh(f->header->tail_object_offset)), &last_usec);
2363 r = journal_file_move_to_object(f, -1, p, &o);
2365 log_error("Invalid object at %llu", (unsigned long long) p);
2369 if (le64toh(f->header->tail_object_offset) < p) {
2370 log_error("Invalid tail object pointer.");
2377 r = journal_file_object_verify(f, o);
2379 log_error("Invalid object contents at %llu", (unsigned long long) p);
2383 r = journal_file_hmac_put_object(f, -1, p);
2385 log_error("Failed to calculate HMAC at %llu", (unsigned long long) p);
2389 if (o->object.flags & OBJECT_COMPRESSED &&
2390 !(le32toh(f->header->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED)) {
2391 log_error("Compressed object without compression at %llu", (unsigned long long) p);
2396 if (o->object.flags & OBJECT_COMPRESSED &&
2397 o->object.type != OBJECT_DATA) {
2398 log_error("Compressed non-data object at %llu", (unsigned long long) p);
2403 if (o->object.type == OBJECT_TAG) {
2405 if (!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED)) {
2406 log_error("Tag object without authentication at %llu", (unsigned long long) p);
2411 if (le64toh(o->tag.seqnum) != tag_seqnum) {
2412 log_error("Tag sequence number out of synchronization at %llu", (unsigned long long) p);
2417 } else if (o->object.type == OBJECT_ENTRY) {
2419 if (!entry_seqnum_set &&
2420 le64toh(o->entry.seqnum) != le64toh(f->header->head_entry_seqnum)) {
2421 log_error("Head entry sequence number incorrect");
2426 if (entry_seqnum_set &&
2427 entry_seqnum >= le64toh(o->entry.seqnum)) {
2428 log_error("Entry sequence number out of synchronization at %llu", (unsigned long long) p);
2433 entry_seqnum = le64toh(o->entry.seqnum);
2434 entry_seqnum_set = true;
2436 if (entry_monotonic_set &&
2437 sd_id128_equal(entry_boot_id, o->entry.boot_id) &&
2438 entry_monotonic > le64toh(o->entry.monotonic)) {
2439 log_error("Entry timestamp out of synchronization at %llu", (unsigned long long) p);
2444 entry_monotonic = le64toh(o->entry.monotonic);
2445 entry_boot_id = o->entry.boot_id;
2446 entry_monotonic_set = true;
2448 if (!entry_realtime_set &&
2449 le64toh(o->entry.realtime) != le64toh(f->header->head_entry_realtime)) {
2450 log_error("Head entry realtime timestamp incorrect");
2455 entry_realtime = le64toh(o->entry.realtime);
2456 entry_realtime_set = true;
2459 } else if (o->object.type == OBJECT_ENTRY_ARRAY) {
2461 if (p == le64toh(f->header->entry_array_offset)) {
2462 if (found_main_entry_array) {
2463 log_error("More than one main entry array at %llu", (unsigned long long) p);
2468 found_main_entry_array = true;
2471 } else if (o->object.type == OBJECT_DATA)
2473 else if (o->object.type == OBJECT_FIELD)
2475 else if (o->object.type == OBJECT_DATA_HASH_TABLE) {
2476 n_data_hash_tables++;
2478 if (n_data_hash_tables > 1) {
2479 log_error("More than one data hash table at %llu", (unsigned long long) p);
2484 if (le64toh(f->header->data_hash_table_offset) != p + offsetof(HashTableObject, items) ||
2485 le64toh(f->header->data_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
2486 log_error("Header fields for data hash table invalid.");
2490 } else if (o->object.type == OBJECT_FIELD_HASH_TABLE) {
2491 n_field_hash_tables++;
2493 if (n_field_hash_tables > 1) {
2494 log_error("More than one field hash table at %llu", (unsigned long long) p);
2499 if (le64toh(f->header->field_hash_table_offset) != p + offsetof(HashTableObject, items) ||
2500 le64toh(f->header->field_hash_table_size) != le64toh(o->object.size) - offsetof(HashTableObject, items)) {
2501 log_error("Header fields for field hash table invalid.");
2507 if (o->object.type >= _OBJECT_TYPE_MAX)
2510 /* Write address to file... */
2514 if (p == le64toh(f->header->tail_object_offset))
2517 p = p + ALIGN64(le64toh(o->object.size));
2520 if (n_objects != le64toh(f->header->n_objects)) {
2521 log_error("Object number mismatch");
2526 if (n_entries != le64toh(f->header->n_entries)) {
2527 log_error("Entry number mismatch");
2532 if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
2533 n_data != le64toh(f->header->n_data)) {
2534 log_error("Data number mismatch");
2539 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
2540 n_fields != le64toh(f->header->n_fields)) {
2541 log_error("Field number mismatch");
2546 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags) &&
2547 tag_seqnum != le64toh(f->header->n_tags)) {
2548 log_error("Tag number mismatch");
2553 if (n_data_hash_tables != 1) {
2554 log_error("Missing data hash table");
2559 if (n_field_hash_tables != 1) {
2560 log_error("Missing field hash table");
2565 if (!found_main_entry_array) {
2566 log_error("Missing entry array");
2571 if (entry_seqnum_set &&
2572 entry_seqnum != le64toh(f->header->tail_entry_seqnum)) {
2573 log_error("Invalid tail seqnum");
2578 if (entry_monotonic_set &&
2579 (!sd_id128_equal(entry_boot_id, f->header->boot_id) ||
2580 entry_monotonic != le64toh(f->header->tail_entry_monotonic))) {
2581 log_error("Invalid tail monotonic timestamp");
2586 if (entry_realtime_set && entry_realtime != le64toh(f->header->tail_entry_realtime)) {
2587 log_error("Invalid tail realtime timestamp");
2592 /* Second iteration: we go through all objects again, this
2593 * time verify all pointers. */
2595 /* q = le64toh(f->header->header_size); */
2596 /* while (q != 0) { */
2597 /* r = journal_file_move_to_object(f, -1, q, &o); */
2599 /* log_error("Invalid object at %llu", (unsigned long long) q); */
2603 /* if (q == le64toh(f->header->tail_object_offset)) */
2606 /* q = q + ALIGN64(le64toh(o->object.size)); */
2620 log_error("File corruption detected at %s:%llu (of %llu, %llu%%).",
2622 (unsigned long long) e,
2623 (unsigned long long) f->last_stat.st_size,
2624 (unsigned long long) (100 * e / f->last_stat.st_size));
2629 void journal_file_dump(JournalFile *f) {
2636 journal_file_print_header(f);
2638 p = le64toh(f->header->header_size);
2640 r = journal_file_move_to_object(f, -1, p, &o);
2644 switch (o->object.type) {
2647 printf("Type: OBJECT_UNUSED\n");
2651 printf("Type: OBJECT_DATA\n");
2655 printf("Type: OBJECT_ENTRY %llu %llu %llu\n",
2656 (unsigned long long) le64toh(o->entry.seqnum),
2657 (unsigned long long) le64toh(o->entry.monotonic),
2658 (unsigned long long) le64toh(o->entry.realtime));
2661 case OBJECT_FIELD_HASH_TABLE:
2662 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
2665 case OBJECT_DATA_HASH_TABLE:
2666 printf("Type: OBJECT_DATA_HASH_TABLE\n");
2669 case OBJECT_ENTRY_ARRAY:
2670 printf("Type: OBJECT_ENTRY_ARRAY\n");
2674 printf("Type: OBJECT_TAG %llu\n",
2675 (unsigned long long) le64toh(o->tag.seqnum));
2679 if (o->object.flags & OBJECT_COMPRESSED)
2680 printf("Flags: COMPRESSED\n");
2682 if (p == le64toh(f->header->tail_object_offset))
2685 p = p + ALIGN64(le64toh(o->object.size));
2690 log_error("File corrupt");
2693 void journal_file_print_header(JournalFile *f) {
2694 char a[33], b[33], c[33];
2695 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX];
2699 printf("File Path: %s\n"
2703 "Sequential Number ID: %s\n"
2705 "Compatible Flags:%s%s\n"
2706 "Incompatible Flags:%s%s\n"
2707 "Header size: %llu\n"
2708 "Arena size: %llu\n"
2709 "Data Hash Table Size: %llu\n"
2710 "Field Hash Table Size: %llu\n"
2712 "Entry Objects: %llu\n"
2713 "Rotate Suggested: %s\n"
2714 "Head Sequential Number: %llu\n"
2715 "Tail Sequential Number: %llu\n"
2716 "Head Realtime Timestamp: %s\n"
2717 "Tail Realtime Timestamp: %s\n",
2719 sd_id128_to_string(f->header->file_id, a),
2720 sd_id128_to_string(f->header->machine_id, b),
2721 sd_id128_to_string(f->header->boot_id, c),
2722 sd_id128_to_string(f->header->seqnum_id, c),
2723 f->header->state == STATE_OFFLINE ? "offline" :
2724 f->header->state == STATE_ONLINE ? "online" :
2725 f->header->state == STATE_ARCHIVED ? "archived" : "unknown",
2726 (f->header->compatible_flags & HEADER_COMPATIBLE_AUTHENTICATED) ? " AUTHENTICATED" : "",
2727 (f->header->compatible_flags & ~HEADER_COMPATIBLE_AUTHENTICATED) ? " ???" : "",
2728 (f->header->incompatible_flags & HEADER_INCOMPATIBLE_COMPRESSED) ? " COMPRESSED" : "",
2729 (f->header->incompatible_flags & ~HEADER_INCOMPATIBLE_COMPRESSED) ? " ???" : "",
2730 (unsigned long long) le64toh(f->header->header_size),
2731 (unsigned long long) le64toh(f->header->arena_size),
2732 (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
2733 (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
2734 (unsigned long long) le64toh(f->header->n_objects),
2735 (unsigned long long) le64toh(f->header->n_entries),
2736 yes_no(journal_file_rotate_suggested(f)),
2737 (unsigned long long) le64toh(f->header->head_entry_seqnum),
2738 (unsigned long long) le64toh(f->header->tail_entry_seqnum),
2739 format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
2740 format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)));
2742 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2743 printf("Data Objects: %llu\n"
2744 "Data Hash Table Fill: %.1f%%\n",
2745 (unsigned long long) le64toh(f->header->n_data),
2746 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
2748 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2749 printf("Field Objects: %llu\n"
2750 "Field Hash Table Fill: %.1f%%\n",
2751 (unsigned long long) le64toh(f->header->n_fields),
2752 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
2755 int journal_file_open(
2761 JournalMetrics *metrics,
2762 MMapCache *mmap_cache,
2763 JournalFile *template,
2764 JournalFile **ret) {
2768 bool newly_created = false;
2772 if ((flags & O_ACCMODE) != O_RDONLY &&
2773 (flags & O_ACCMODE) != O_RDWR)
2776 if (!endswith(fname, ".journal"))
2779 f = new0(JournalFile, 1);
2787 f->prot = prot_from_flags(flags);
2788 f->writable = (flags & O_ACCMODE) != O_RDONLY;
2789 f->compress = compress;
2790 f->authenticate = authenticate;
2793 f->mmap = mmap_cache_ref(mmap_cache);
2795 /* One context for each type, plus the zeroth catchall
2796 * context. One fd for the file plus one for each type
2797 * (which we need during verification */
2798 f->mmap = mmap_cache_new(_OBJECT_TYPE_MAX, 1 + _OBJECT_TYPE_MAX);
2805 f->path = strdup(fname);
2811 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
2817 if (fstat(f->fd, &f->last_stat) < 0) {
2822 if (f->last_stat.st_size == 0 && f->writable) {
2823 newly_created = true;
2825 /* Try to load the FSPRG state, and if we can't, then
2826 * just don't do authentication */
2827 r = journal_file_load_fsprg(f);
2829 f->authenticate = false;
2831 r = journal_file_init_header(f, template);
2835 if (fstat(f->fd, &f->last_stat) < 0) {
2841 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
2846 f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
2847 if (f->header == MAP_FAILED) {
2853 if (!newly_created) {
2854 r = journal_file_verify_header(f);
2859 if (!newly_created && f->writable) {
2860 r = journal_file_load_fsprg(f);
2867 journal_default_metrics(metrics, f->fd);
2868 f->metrics = *metrics;
2869 } else if (template)
2870 f->metrics = template->metrics;
2872 r = journal_file_refresh_header(f);
2876 r = journal_file_setup_hmac(f);
2881 if (newly_created) {
2882 r = journal_file_setup_field_hash_table(f);
2886 r = journal_file_setup_data_hash_table(f);
2890 r = journal_file_append_first_tag(f);
2895 r = journal_file_map_field_hash_table(f);
2899 r = journal_file_map_data_hash_table(f);
2909 journal_file_close(f);
2914 int journal_file_rotate(JournalFile **f, bool compress, bool authenticate) {
2917 JournalFile *old_file, *new_file = NULL;
2925 if (!old_file->writable)
2928 if (!endswith(old_file->path, ".journal"))
2931 l = strlen(old_file->path);
2933 p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
2937 memcpy(p, old_file->path, l - 8);
2939 sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
2940 snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
2941 "-%016llx-%016llx.journal",
2942 (unsigned long long) le64toh((*f)->header->tail_entry_seqnum),
2943 (unsigned long long) le64toh((*f)->header->tail_entry_realtime));
2945 r = rename(old_file->path, p);
2951 old_file->header->state = STATE_ARCHIVED;
2953 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, compress, authenticate, NULL, old_file->mmap, old_file, &new_file);
2954 journal_file_close(old_file);
2960 int journal_file_open_reliably(
2966 JournalMetrics *metrics,
2968 JournalFile *template,
2969 JournalFile **ret) {
2975 r = journal_file_open(fname, flags, mode, compress, authenticate, metrics, mmap, template, ret);
2976 if (r != -EBADMSG && /* corrupted */
2977 r != -ENODATA && /* truncated */
2978 r != -EHOSTDOWN && /* other machine */
2979 r != -EPROTONOSUPPORT && /* incompatible feature */
2980 r != -EBUSY && /* unclean shutdown */
2981 r != -ESHUTDOWN /* already archived */)
2984 if ((flags & O_ACCMODE) == O_RDONLY)
2987 if (!(flags & O_CREAT))
2990 if (!endswith(fname, ".journal"))
2993 /* The file is corrupted. Rotate it away and try it again (but only once) */
2996 if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
2998 (unsigned long long) now(CLOCK_REALTIME),
3002 r = rename(fname, p);
3007 log_warning("File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
3009 return journal_file_open(fname, flags, mode, compress, authenticate, metrics, mmap, template, ret);
3012 struct vacuum_info {
3017 sd_id128_t seqnum_id;
3023 static int vacuum_compare(const void *_a, const void *_b) {
3024 const struct vacuum_info *a, *b;
3029 if (a->have_seqnum && b->have_seqnum &&
3030 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
3031 if (a->seqnum < b->seqnum)
3033 else if (a->seqnum > b->seqnum)
3039 if (a->realtime < b->realtime)
3041 else if (a->realtime > b->realtime)
3043 else if (a->have_seqnum && b->have_seqnum)
3044 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
3046 return strcmp(a->filename, b->filename);
3049 int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free) {
3052 struct vacuum_info *list = NULL;
3053 unsigned n_list = 0, n_allocated = 0, i;
3061 d = opendir(directory);
3067 struct dirent buf, *de;
3071 unsigned long long seqnum = 0, realtime;
3072 sd_id128_t seqnum_id;
3075 k = readdir_r(d, &buf, &de);
3084 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
3087 if (!S_ISREG(st.st_mode))
3090 q = strlen(de->d_name);
3092 if (endswith(de->d_name, ".journal")) {
3094 /* Vacuum archived files */
3096 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
3099 if (de->d_name[q-8-16-1] != '-' ||
3100 de->d_name[q-8-16-1-16-1] != '-' ||
3101 de->d_name[q-8-16-1-16-1-32-1] != '@')
3104 p = strdup(de->d_name);
3110 de->d_name[q-8-16-1-16-1] = 0;
3111 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
3116 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
3123 } else if (endswith(de->d_name, ".journal~")) {
3124 unsigned long long tmp;
3126 /* Vacuum corrupted files */
3128 if (q < 1 + 16 + 1 + 16 + 8 + 1)
3131 if (de->d_name[q-1-8-16-1] != '-' ||
3132 de->d_name[q-1-8-16-1-16-1] != '@')
3135 p = strdup(de->d_name);
3141 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
3146 have_seqnum = false;
3150 if (n_list >= n_allocated) {
3151 struct vacuum_info *j;
3153 n_allocated = MAX(n_allocated * 2U, 8U);
3154 j = realloc(list, n_allocated * sizeof(struct vacuum_info));
3164 list[n_list].filename = p;
3165 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
3166 list[n_list].seqnum = seqnum;
3167 list[n_list].realtime = realtime;
3168 list[n_list].seqnum_id = seqnum_id;
3169 list[n_list].have_seqnum = have_seqnum;
3171 sum += list[n_list].usage;
3177 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
3179 for(i = 0; i < n_list; i++) {
3182 if (fstatvfs(dirfd(d), &ss) < 0) {
3187 if (sum <= max_use &&
3188 (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free)
3191 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
3192 log_info("Deleted archived journal %s/%s.", directory, list[i].filename);
3193 sum -= list[i].usage;
3194 } else if (errno != ENOENT)
3195 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
3199 for (i = 0; i < n_list; i++)
3200 free(list[i].filename);
3210 int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
3212 uint64_t q, xor_hash = 0;
3225 ts.monotonic = le64toh(o->entry.monotonic);
3226 ts.realtime = le64toh(o->entry.realtime);
3228 if (to->tail_entry_monotonic_valid &&
3229 ts.monotonic < le64toh(to->header->tail_entry_monotonic))
3232 n = journal_file_entry_n_items(o);
3233 items = alloca(sizeof(EntryItem) * n);
3235 for (i = 0; i < n; i++) {
3242 q = le64toh(o->entry.items[i].object_offset);
3243 le_hash = o->entry.items[i].hash;
3245 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
3249 if (le_hash != o->data.hash)
3252 l = le64toh(o->object.size) - offsetof(Object, data.payload);
3255 /* We hit the limit on 32bit machines */
3256 if ((uint64_t) t != l)
3259 if (o->object.flags & OBJECT_COMPRESSED) {
3263 if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
3266 data = from->compress_buffer;
3269 return -EPROTONOSUPPORT;
3272 data = o->data.payload;
3274 r = journal_file_append_data(to, data, l, &u, &h);
3278 xor_hash ^= le64toh(u->data.hash);
3279 items[i].object_offset = htole64(h);
3280 items[i].hash = u->data.hash;
3282 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
3287 return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
3290 void journal_default_metrics(JournalMetrics *m, int fd) {
3291 uint64_t fs_size = 0;
3293 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
3298 if (fstatvfs(fd, &ss) >= 0)
3299 fs_size = ss.f_frsize * ss.f_blocks;
3301 if (m->max_use == (uint64_t) -1) {
3304 m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
3306 if (m->max_use > DEFAULT_MAX_USE_UPPER)
3307 m->max_use = DEFAULT_MAX_USE_UPPER;
3309 if (m->max_use < DEFAULT_MAX_USE_LOWER)
3310 m->max_use = DEFAULT_MAX_USE_LOWER;
3312 m->max_use = DEFAULT_MAX_USE_LOWER;
3314 m->max_use = PAGE_ALIGN(m->max_use);
3316 if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
3317 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
3320 if (m->max_size == (uint64_t) -1) {
3321 m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
3323 if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
3324 m->max_size = DEFAULT_MAX_SIZE_UPPER;
3326 m->max_size = PAGE_ALIGN(m->max_size);
3328 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
3329 m->max_size = JOURNAL_FILE_SIZE_MIN;
3331 if (m->max_size*2 > m->max_use)
3332 m->max_use = m->max_size*2;
3334 if (m->min_size == (uint64_t) -1)
3335 m->min_size = JOURNAL_FILE_SIZE_MIN;
3337 m->min_size = PAGE_ALIGN(m->min_size);
3339 if (m->min_size < JOURNAL_FILE_SIZE_MIN)
3340 m->min_size = JOURNAL_FILE_SIZE_MIN;
3342 if (m->min_size > m->max_size)
3343 m->max_size = m->min_size;
3346 if (m->keep_free == (uint64_t) -1) {
3349 m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
3351 if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
3352 m->keep_free = DEFAULT_KEEP_FREE_UPPER;
3355 m->keep_free = DEFAULT_KEEP_FREE;
3358 log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
3359 format_bytes(a, sizeof(a), m->max_use),
3360 format_bytes(b, sizeof(b), m->max_size),
3361 format_bytes(c, sizeof(c), m->min_size),
3362 format_bytes(d, sizeof(d), m->keep_free));
3365 int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
3370 if (f->header->head_entry_realtime == 0)
3373 *from = le64toh(f->header->head_entry_realtime);
3377 if (f->header->tail_entry_realtime == 0)
3380 *to = le64toh(f->header->tail_entry_realtime);
3386 int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
3387 char t[9+32+1] = "_BOOT_ID=";
3395 sd_id128_to_string(boot_id, t + 9);
3397 r = journal_file_find_data_object(f, t, strlen(t), &o, &p);
3401 if (le64toh(o->data.n_entries) <= 0)
3405 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
3409 *from = le64toh(o->entry.monotonic);
3413 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
3417 r = generic_array_get_plus_one(f,
3418 le64toh(o->data.entry_offset),
3419 le64toh(o->data.entry_array_offset),
3420 le64toh(o->data.n_entries)-1,
3425 *to = le64toh(o->entry.monotonic);
3431 bool journal_file_rotate_suggested(JournalFile *f) {
3434 /* If we gained new header fields we gained new features,
3435 * hence suggest a rotation */
3436 if (le64toh(f->header->header_size) < sizeof(Header)) {
3437 log_debug("%s uses an outdated header, suggesting rotation.", f->path);
3441 /* Let's check if the hash tables grew over a certain fill
3442 * level (75%, borrowing this value from Java's hash table
3443 * implementation), and if so suggest a rotation. To calculate
3444 * the fill level we need the n_data field, which only exists
3445 * in newer versions. */
3447 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
3448 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
3449 log_debug("Data hash table of %s has a fill level at %.1f (%llu of %llu items, %llu file size, %llu bytes per hash table item), suggesting rotation.",
3451 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
3452 (unsigned long long) le64toh(f->header->n_data),
3453 (unsigned long long) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)),
3454 (unsigned long long) (f->last_stat.st_size),
3455 (unsigned long long) (f->last_stat.st_size / le64toh(f->header->n_data)));
3459 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
3460 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
3461 log_debug("Field hash table of %s has a fill level at %.1f (%llu of %llu items), suggesting rotation.",
3463 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
3464 (unsigned long long) le64toh(f->header->n_fields),
3465 (unsigned long long) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)));