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"
32 #include "journal-authenticate.h"
37 #define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem))
38 #define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem))
40 #define COMPRESSION_SIZE_THRESHOLD (512ULL)
42 /* This is the minimum journal file size */
43 #define JOURNAL_FILE_SIZE_MIN (64ULL*1024ULL) /* 64 KiB */
45 /* These are the lower and upper bounds if we deduce the max_use value
46 * from the file system size */
47 #define DEFAULT_MAX_USE_LOWER (1ULL*1024ULL*1024ULL) /* 1 MiB */
48 #define DEFAULT_MAX_USE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
50 /* This is the upper bound if we deduce max_size from max_use */
51 #define DEFAULT_MAX_SIZE_UPPER (128ULL*1024ULL*1024ULL) /* 128 MiB */
53 /* This is the upper bound if we deduce the keep_free value from the
55 #define DEFAULT_KEEP_FREE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
57 /* This is the keep_free value when we can't determine the system
59 #define DEFAULT_KEEP_FREE (1024ULL*1024ULL) /* 1 MB */
61 /* n_data was the first entry we added after the initial file format design */
62 #define HEADER_SIZE_MIN ALIGN64(offsetof(Header, n_data))
64 void journal_file_close(JournalFile *f) {
67 /* Write the final tag */
69 journal_file_append_tag(f);
71 /* Sync everything to disk, before we mark the file offline */
72 if (f->mmap && f->fd >= 0)
73 mmap_cache_close_fd(f->mmap, f->fd);
75 if (f->writable && f->fd >= 0)
79 /* Mark the file offline. Don't override the archived state if it already is set */
80 if (f->writable && f->header->state == STATE_ONLINE)
81 f->header->state = STATE_OFFLINE;
83 munmap(f->header, PAGE_ALIGN(sizeof(Header)));
87 close_nointr_nofail(f->fd);
92 mmap_cache_unref(f->mmap);
95 free(f->compress_buffer);
100 munmap(f->fss_file, PAGE_ALIGN(f->fss_file_size));
101 else if (f->fsprg_state)
102 free(f->fsprg_state);
107 gcry_md_close(f->hmac);
113 static int journal_file_init_header(JournalFile *f, JournalFile *template) {
121 memcpy(h.signature, HEADER_SIGNATURE, 8);
122 h.header_size = htole64(ALIGN64(sizeof(h)));
124 h.incompatible_flags =
125 htole32(f->compress ? HEADER_INCOMPATIBLE_COMPRESSED : 0);
128 htole32(f->seal ? HEADER_COMPATIBLE_SEALED : 0);
130 r = sd_id128_randomize(&h.file_id);
135 h.seqnum_id = template->header->seqnum_id;
136 h.tail_entry_seqnum = template->header->tail_entry_seqnum;
138 h.seqnum_id = h.file_id;
140 k = pwrite(f->fd, &h, sizeof(h), 0);
150 static int journal_file_refresh_header(JournalFile *f) {
156 r = sd_id128_get_machine(&f->header->machine_id);
160 r = sd_id128_get_boot(&boot_id);
164 if (sd_id128_equal(boot_id, f->header->boot_id))
165 f->tail_entry_monotonic_valid = true;
167 f->header->boot_id = boot_id;
169 f->header->state = STATE_ONLINE;
171 /* Sync the online state to disk */
172 msync(f->header, PAGE_ALIGN(sizeof(Header)), MS_SYNC);
178 static int journal_file_verify_header(JournalFile *f) {
181 if (memcmp(f->header->signature, HEADER_SIGNATURE, 8))
184 /* In both read and write mode we refuse to open files with
185 * incompatible flags we don't know */
187 if ((le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) != 0)
188 return -EPROTONOSUPPORT;
190 if (f->header->incompatible_flags != 0)
191 return -EPROTONOSUPPORT;
194 /* When open for writing we refuse to open files with
195 * compatible flags, too */
198 if ((le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_SEALED) != 0)
199 return -EPROTONOSUPPORT;
201 if (f->header->compatible_flags != 0)
202 return -EPROTONOSUPPORT;
206 /* The first addition was n_data, so check that we are at least this large */
207 if (le64toh(f->header->header_size) < HEADER_SIZE_MIN)
210 if ((le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_SEALED) &&
211 !JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
214 if ((uint64_t) f->last_stat.st_size < (le64toh(f->header->header_size) + le64toh(f->header->arena_size)))
219 sd_id128_t machine_id;
222 r = sd_id128_get_machine(&machine_id);
226 if (!sd_id128_equal(machine_id, f->header->machine_id))
229 state = f->header->state;
231 if (state == STATE_ONLINE) {
232 log_debug("Journal file %s is already online. Assuming unclean closing.", f->path);
234 } else if (state == STATE_ARCHIVED)
236 else if (state != STATE_OFFLINE) {
237 log_debug("Journal file %s has unknown state %u.", f->path, state);
242 f->compress = !!(le32toh(f->header->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED);
243 f->seal = !!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_SEALED);
248 static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
249 uint64_t old_size, new_size;
254 /* We assume that this file is not sparse, and we know that
255 * for sure, since we always call posix_fallocate()
259 le64toh(f->header->header_size) +
260 le64toh(f->header->arena_size);
262 new_size = PAGE_ALIGN(offset + size);
263 if (new_size < le64toh(f->header->header_size))
264 new_size = le64toh(f->header->header_size);
266 if (new_size <= old_size)
269 if (f->metrics.max_size > 0 &&
270 new_size > f->metrics.max_size)
273 if (new_size > f->metrics.min_size &&
274 f->metrics.keep_free > 0) {
277 if (fstatvfs(f->fd, &svfs) >= 0) {
280 available = svfs.f_bfree * svfs.f_bsize;
282 if (available >= f->metrics.keep_free)
283 available -= f->metrics.keep_free;
287 if (new_size - old_size > available)
292 /* Note that the glibc fallocate() fallback is very
293 inefficient, hence we try to minimize the allocation area
295 r = posix_fallocate(f->fd, old_size, new_size - old_size);
299 mmap_cache_close_fd_range(f->mmap, f->fd, old_size);
301 if (fstat(f->fd, &f->last_stat) < 0)
304 f->header->arena_size = htole64(new_size - le64toh(f->header->header_size));
309 static int journal_file_move_to(JournalFile *f, int context, uint64_t offset, uint64_t size, void **ret) {
313 /* Avoid SIGBUS on invalid accesses */
314 if (offset + size > (uint64_t) f->last_stat.st_size) {
315 /* Hmm, out of range? Let's refresh the fstat() data
316 * first, before we trust that check. */
318 if (fstat(f->fd, &f->last_stat) < 0 ||
319 offset + size > (uint64_t) f->last_stat.st_size)
320 return -EADDRNOTAVAIL;
323 return mmap_cache_get(f->mmap, f->fd, f->prot, context, offset, size, ret);
326 static uint64_t minimum_header_size(Object *o) {
328 static uint64_t table[] = {
329 [OBJECT_DATA] = sizeof(DataObject),
330 [OBJECT_FIELD] = sizeof(FieldObject),
331 [OBJECT_ENTRY] = sizeof(EntryObject),
332 [OBJECT_DATA_HASH_TABLE] = sizeof(HashTableObject),
333 [OBJECT_FIELD_HASH_TABLE] = sizeof(HashTableObject),
334 [OBJECT_ENTRY_ARRAY] = sizeof(EntryArrayObject),
335 [OBJECT_TAG] = sizeof(TagObject),
338 if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0)
339 return sizeof(ObjectHeader);
341 return table[o->object.type];
344 int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret) {
354 /* One context for each type, plus one catch-all for the rest */
355 context = type > 0 && type < _OBJECT_TYPE_MAX ? type : 0;
357 r = journal_file_move_to(f, context, offset, sizeof(ObjectHeader), &t);
362 s = le64toh(o->object.size);
364 if (s < sizeof(ObjectHeader))
367 if (o->object.type <= OBJECT_UNUSED)
370 if (s < minimum_header_size(o))
373 if (type >= 0 && o->object.type != type)
376 if (s > sizeof(ObjectHeader)) {
377 r = journal_file_move_to(f, o->object.type, offset, s, &t);
388 static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) {
393 r = le64toh(f->header->tail_entry_seqnum) + 1;
396 /* If an external seqnum counter was passed, we update
397 * both the local and the external one, and set it to
398 * the maximum of both */
406 f->header->tail_entry_seqnum = htole64(r);
408 if (f->header->head_entry_seqnum == 0)
409 f->header->head_entry_seqnum = htole64(r);
414 int journal_file_append_object(JournalFile *f, int type, uint64_t size, Object **ret, uint64_t *offset) {
421 assert(type > 0 && type < _OBJECT_TYPE_MAX);
422 assert(size >= sizeof(ObjectHeader));
426 p = le64toh(f->header->tail_object_offset);
428 p = le64toh(f->header->header_size);
430 r = journal_file_move_to_object(f, -1, p, &tail);
434 p += ALIGN64(le64toh(tail->object.size));
437 r = journal_file_allocate(f, p, size);
441 r = journal_file_move_to(f, type, p, size, &t);
448 o->object.type = type;
449 o->object.size = htole64(size);
451 f->header->tail_object_offset = htole64(p);
452 f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1);
460 static int journal_file_setup_data_hash_table(JournalFile *f) {
467 /* We estimate that we need 1 hash table entry per 768 of
468 journal file and we want to make sure we never get beyond
469 75% fill level. Calculate the hash table size for the
470 maximum file size based on these metrics. */
472 s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem);
473 if (s < DEFAULT_DATA_HASH_TABLE_SIZE)
474 s = DEFAULT_DATA_HASH_TABLE_SIZE;
476 log_info("Reserving %llu entries in hash table.", (unsigned long long) (s / sizeof(HashItem)));
478 r = journal_file_append_object(f,
479 OBJECT_DATA_HASH_TABLE,
480 offsetof(Object, hash_table.items) + s,
485 memset(o->hash_table.items, 0, s);
487 f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
488 f->header->data_hash_table_size = htole64(s);
493 static int journal_file_setup_field_hash_table(JournalFile *f) {
500 s = DEFAULT_FIELD_HASH_TABLE_SIZE;
501 r = journal_file_append_object(f,
502 OBJECT_FIELD_HASH_TABLE,
503 offsetof(Object, hash_table.items) + s,
508 memset(o->hash_table.items, 0, s);
510 f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
511 f->header->field_hash_table_size = htole64(s);
516 static int journal_file_map_data_hash_table(JournalFile *f) {
523 p = le64toh(f->header->data_hash_table_offset);
524 s = le64toh(f->header->data_hash_table_size);
526 r = journal_file_move_to(f,
527 OBJECT_DATA_HASH_TABLE,
533 f->data_hash_table = t;
537 static int journal_file_map_field_hash_table(JournalFile *f) {
544 p = le64toh(f->header->field_hash_table_offset);
545 s = le64toh(f->header->field_hash_table_size);
547 r = journal_file_move_to(f,
548 OBJECT_FIELD_HASH_TABLE,
554 f->field_hash_table = t;
558 static int journal_file_link_data(JournalFile *f, Object *o, uint64_t offset, uint64_t hash) {
565 assert(o->object.type == OBJECT_DATA);
567 /* This might alter the window we are looking at */
569 o->data.next_hash_offset = o->data.next_field_offset = 0;
570 o->data.entry_offset = o->data.entry_array_offset = 0;
571 o->data.n_entries = 0;
573 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
574 p = le64toh(f->data_hash_table[h].tail_hash_offset);
576 /* Only entry in the hash table is easy */
577 f->data_hash_table[h].head_hash_offset = htole64(offset);
579 /* Move back to the previous data object, to patch in
582 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
586 o->data.next_hash_offset = htole64(offset);
589 f->data_hash_table[h].tail_hash_offset = htole64(offset);
591 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
592 f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
597 int journal_file_find_data_object_with_hash(
599 const void *data, uint64_t size, uint64_t hash,
600 Object **ret, uint64_t *offset) {
602 uint64_t p, osize, h;
606 assert(data || size == 0);
608 osize = offsetof(Object, data.payload) + size;
610 if (f->header->data_hash_table_size == 0)
613 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
614 p = le64toh(f->data_hash_table[h].head_hash_offset);
619 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
623 if (le64toh(o->data.hash) != hash)
626 if (o->object.flags & OBJECT_COMPRESSED) {
630 l = le64toh(o->object.size);
631 if (l <= offsetof(Object, data.payload))
634 l -= offsetof(Object, data.payload);
636 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
640 memcmp(f->compress_buffer, data, size) == 0) {
651 return -EPROTONOSUPPORT;
654 } else if (le64toh(o->object.size) == osize &&
655 memcmp(o->data.payload, data, size) == 0) {
667 p = le64toh(o->data.next_hash_offset);
673 int journal_file_find_data_object(
675 const void *data, uint64_t size,
676 Object **ret, uint64_t *offset) {
681 assert(data || size == 0);
683 hash = hash64(data, size);
685 return journal_file_find_data_object_with_hash(f,
690 static int journal_file_append_data(
692 const void *data, uint64_t size,
693 Object **ret, uint64_t *offset) {
699 bool compressed = false;
702 assert(data || size == 0);
704 hash = hash64(data, size);
706 r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
720 osize = offsetof(Object, data.payload) + size;
721 r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
725 o->data.hash = htole64(hash);
729 size >= COMPRESSION_SIZE_THRESHOLD) {
732 compressed = compress_blob(data, size, o->data.payload, &rsize);
735 o->object.size = htole64(offsetof(Object, data.payload) + rsize);
736 o->object.flags |= OBJECT_COMPRESSED;
738 log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize);
743 if (!compressed && size > 0)
744 memcpy(o->data.payload, data, size);
746 r = journal_file_link_data(f, o, p, hash);
750 r = journal_file_hmac_put_object(f, OBJECT_DATA, p);
754 /* The linking might have altered the window, so let's
755 * refresh our pointer */
756 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
769 uint64_t journal_file_entry_n_items(Object *o) {
771 assert(o->object.type == OBJECT_ENTRY);
773 return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
776 uint64_t journal_file_entry_array_n_items(Object *o) {
778 assert(o->object.type == OBJECT_ENTRY_ARRAY);
780 return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
783 static int link_entry_into_array(JournalFile *f,
788 uint64_t n = 0, ap = 0, q, i, a, hidx;
797 i = hidx = le64toh(*idx);
800 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
804 n = journal_file_entry_array_n_items(o);
806 o->entry_array.items[i] = htole64(p);
807 *idx = htole64(hidx + 1);
813 a = le64toh(o->entry_array.next_entry_array_offset);
824 r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
825 offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
830 r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, q);
834 o->entry_array.items[i] = htole64(p);
839 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
843 o->entry_array.next_entry_array_offset = htole64(q);
846 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
847 f->header->n_entry_arrays = htole64(le64toh(f->header->n_entry_arrays) + 1);
849 *idx = htole64(hidx + 1);
854 static int link_entry_into_array_plus_one(JournalFile *f,
873 i = htole64(le64toh(*idx) - 1);
874 r = link_entry_into_array(f, first, &i, p);
879 *idx = htole64(le64toh(*idx) + 1);
883 static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
890 p = le64toh(o->entry.items[i].object_offset);
894 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
898 return link_entry_into_array_plus_one(f,
899 &o->data.entry_offset,
900 &o->data.entry_array_offset,
905 static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
912 assert(o->object.type == OBJECT_ENTRY);
914 __sync_synchronize();
916 /* Link up the entry itself */
917 r = link_entry_into_array(f,
918 &f->header->entry_array_offset,
919 &f->header->n_entries,
924 /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */
926 if (f->header->head_entry_realtime == 0)
927 f->header->head_entry_realtime = o->entry.realtime;
929 f->header->tail_entry_realtime = o->entry.realtime;
930 f->header->tail_entry_monotonic = o->entry.monotonic;
932 f->tail_entry_monotonic_valid = true;
934 /* Link up the items */
935 n = journal_file_entry_n_items(o);
936 for (i = 0; i < n; i++) {
937 r = journal_file_link_entry_item(f, o, offset, i);
945 static int journal_file_append_entry_internal(
947 const dual_timestamp *ts,
949 const EntryItem items[], unsigned n_items,
951 Object **ret, uint64_t *offset) {
958 assert(items || n_items == 0);
961 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
963 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
967 o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum));
968 memcpy(o->entry.items, items, n_items * sizeof(EntryItem));
969 o->entry.realtime = htole64(ts->realtime);
970 o->entry.monotonic = htole64(ts->monotonic);
971 o->entry.xor_hash = htole64(xor_hash);
972 o->entry.boot_id = f->header->boot_id;
974 r = journal_file_hmac_put_object(f, OBJECT_ENTRY, np);
978 r = journal_file_link_entry(f, o, np);
991 void journal_file_post_change(JournalFile *f) {
994 /* inotify() does not receive IN_MODIFY events from file
995 * accesses done via mmap(). After each access we hence
996 * trigger IN_MODIFY by truncating the journal file to its
997 * current size which triggers IN_MODIFY. */
999 __sync_synchronize();
1001 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
1002 log_error("Failed to to truncate file to its own size: %m");
1005 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) {
1009 uint64_t xor_hash = 0;
1010 struct dual_timestamp _ts;
1013 assert(iovec || n_iovec == 0);
1019 dual_timestamp_get(&_ts);
1023 if (f->tail_entry_monotonic_valid &&
1024 ts->monotonic < le64toh(f->header->tail_entry_monotonic))
1027 r = journal_file_maybe_append_tag(f, ts->realtime);
1031 /* alloca() can't take 0, hence let's allocate at least one */
1032 items = alloca(sizeof(EntryItem) * MAX(1, n_iovec));
1034 for (i = 0; i < n_iovec; i++) {
1038 r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
1042 xor_hash ^= le64toh(o->data.hash);
1043 items[i].object_offset = htole64(p);
1044 items[i].hash = o->data.hash;
1047 r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
1049 journal_file_post_change(f);
1054 static int generic_array_get(JournalFile *f,
1057 Object **ret, uint64_t *offset) {
1069 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1073 n = journal_file_entry_array_n_items(o);
1075 p = le64toh(o->entry_array.items[i]);
1080 a = le64toh(o->entry_array.next_entry_array_offset);
1083 if (a <= 0 || p <= 0)
1086 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1099 static int generic_array_get_plus_one(JournalFile *f,
1103 Object **ret, uint64_t *offset) {
1112 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1125 return generic_array_get(f, first, i-1, ret, offset);
1134 static int generic_array_bisect(JournalFile *f,
1138 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1139 direction_t direction,
1144 uint64_t a, p, t = 0, i = 0, last_p = 0;
1145 bool subtract_one = false;
1146 Object *o, *array = NULL;
1150 assert(test_object);
1154 uint64_t left, right, k, lp;
1156 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
1160 k = journal_file_entry_array_n_items(array);
1166 lp = p = le64toh(array->entry_array.items[i]);
1170 r = test_object(f, p, needle);
1174 if (r == TEST_FOUND)
1175 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1177 if (r == TEST_RIGHT) {
1181 if (left == right) {
1182 if (direction == DIRECTION_UP)
1183 subtract_one = true;
1189 assert(left < right);
1191 i = (left + right) / 2;
1192 p = le64toh(array->entry_array.items[i]);
1196 r = test_object(f, p, needle);
1200 if (r == TEST_FOUND)
1201 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1203 if (r == TEST_RIGHT)
1211 if (direction == DIRECTION_UP) {
1213 subtract_one = true;
1224 a = le64toh(array->entry_array.next_entry_array_offset);
1230 if (subtract_one && t == 0 && i == 0)
1233 if (subtract_one && i == 0)
1235 else if (subtract_one)
1236 p = le64toh(array->entry_array.items[i-1]);
1238 p = le64toh(array->entry_array.items[i]);
1240 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1251 *idx = t + i + (subtract_one ? -1 : 0);
1256 static int generic_array_bisect_plus_one(JournalFile *f,
1261 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1262 direction_t direction,
1268 bool step_back = false;
1272 assert(test_object);
1277 /* This bisects the array in object 'first', but first checks
1279 r = test_object(f, extra, needle);
1283 if (r == TEST_FOUND)
1284 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1286 /* if we are looking with DIRECTION_UP then we need to first
1287 see if in the actual array there is a matching entry, and
1288 return the last one of that. But if there isn't any we need
1289 to return this one. Hence remember this, and return it
1292 step_back = direction == DIRECTION_UP;
1294 if (r == TEST_RIGHT) {
1295 if (direction == DIRECTION_DOWN)
1301 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
1303 if (r == 0 && step_back)
1312 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1328 static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
1334 else if (p < needle)
1340 int journal_file_move_to_entry_by_offset(
1343 direction_t direction,
1347 return generic_array_bisect(f,
1348 le64toh(f->header->entry_array_offset),
1349 le64toh(f->header->n_entries),
1357 static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
1364 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1368 if (le64toh(o->entry.seqnum) == needle)
1370 else if (le64toh(o->entry.seqnum) < needle)
1376 int journal_file_move_to_entry_by_seqnum(
1379 direction_t direction,
1383 return generic_array_bisect(f,
1384 le64toh(f->header->entry_array_offset),
1385 le64toh(f->header->n_entries),
1392 static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
1399 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1403 if (le64toh(o->entry.realtime) == needle)
1405 else if (le64toh(o->entry.realtime) < needle)
1411 int journal_file_move_to_entry_by_realtime(
1414 direction_t direction,
1418 return generic_array_bisect(f,
1419 le64toh(f->header->entry_array_offset),
1420 le64toh(f->header->n_entries),
1422 test_object_realtime,
1427 static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
1434 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1438 if (le64toh(o->entry.monotonic) == needle)
1440 else if (le64toh(o->entry.monotonic) < needle)
1446 int journal_file_move_to_entry_by_monotonic(
1450 direction_t direction,
1454 char t[9+32+1] = "_BOOT_ID=";
1460 sd_id128_to_string(boot_id, t + 9);
1461 r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
1467 return generic_array_bisect_plus_one(f,
1468 le64toh(o->data.entry_offset),
1469 le64toh(o->data.entry_array_offset),
1470 le64toh(o->data.n_entries),
1472 test_object_monotonic,
1477 int journal_file_next_entry(
1479 Object *o, uint64_t p,
1480 direction_t direction,
1481 Object **ret, uint64_t *offset) {
1487 assert(p > 0 || !o);
1489 n = le64toh(f->header->n_entries);
1494 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1496 if (o->object.type != OBJECT_ENTRY)
1499 r = generic_array_bisect(f,
1500 le64toh(f->header->entry_array_offset),
1501 le64toh(f->header->n_entries),
1510 if (direction == DIRECTION_DOWN) {
1523 /* And jump to it */
1524 return generic_array_get(f,
1525 le64toh(f->header->entry_array_offset),
1530 int journal_file_skip_entry(
1532 Object *o, uint64_t p,
1534 Object **ret, uint64_t *offset) {
1543 if (o->object.type != OBJECT_ENTRY)
1546 r = generic_array_bisect(f,
1547 le64toh(f->header->entry_array_offset),
1548 le64toh(f->header->n_entries),
1557 /* Calculate new index */
1559 if ((uint64_t) -skip >= i)
1562 i = i - (uint64_t) -skip;
1564 i += (uint64_t) skip;
1566 n = le64toh(f->header->n_entries);
1573 return generic_array_get(f,
1574 le64toh(f->header->entry_array_offset),
1579 int journal_file_next_entry_for_data(
1581 Object *o, uint64_t p,
1582 uint64_t data_offset,
1583 direction_t direction,
1584 Object **ret, uint64_t *offset) {
1591 assert(p > 0 || !o);
1593 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1597 n = le64toh(d->data.n_entries);
1602 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1604 if (o->object.type != OBJECT_ENTRY)
1607 r = generic_array_bisect_plus_one(f,
1608 le64toh(d->data.entry_offset),
1609 le64toh(d->data.entry_array_offset),
1610 le64toh(d->data.n_entries),
1620 if (direction == DIRECTION_DOWN) {
1634 return generic_array_get_plus_one(f,
1635 le64toh(d->data.entry_offset),
1636 le64toh(d->data.entry_array_offset),
1641 int journal_file_move_to_entry_by_offset_for_data(
1643 uint64_t data_offset,
1645 direction_t direction,
1646 Object **ret, uint64_t *offset) {
1653 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1657 return generic_array_bisect_plus_one(f,
1658 le64toh(d->data.entry_offset),
1659 le64toh(d->data.entry_array_offset),
1660 le64toh(d->data.n_entries),
1667 int journal_file_move_to_entry_by_monotonic_for_data(
1669 uint64_t data_offset,
1672 direction_t direction,
1673 Object **ret, uint64_t *offset) {
1675 char t[9+32+1] = "_BOOT_ID=";
1682 /* First, seek by time */
1683 sd_id128_to_string(boot_id, t + 9);
1684 r = journal_file_find_data_object(f, t, strlen(t), &o, &b);
1690 r = generic_array_bisect_plus_one(f,
1691 le64toh(o->data.entry_offset),
1692 le64toh(o->data.entry_array_offset),
1693 le64toh(o->data.n_entries),
1695 test_object_monotonic,
1701 /* And now, continue seeking until we find an entry that
1702 * exists in both bisection arrays */
1708 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1712 r = generic_array_bisect_plus_one(f,
1713 le64toh(d->data.entry_offset),
1714 le64toh(d->data.entry_array_offset),
1715 le64toh(d->data.n_entries),
1723 r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
1727 r = generic_array_bisect_plus_one(f,
1728 le64toh(o->data.entry_offset),
1729 le64toh(o->data.entry_array_offset),
1730 le64toh(o->data.n_entries),
1754 int journal_file_move_to_entry_by_seqnum_for_data(
1756 uint64_t data_offset,
1758 direction_t direction,
1759 Object **ret, uint64_t *offset) {
1766 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1770 return generic_array_bisect_plus_one(f,
1771 le64toh(d->data.entry_offset),
1772 le64toh(d->data.entry_array_offset),
1773 le64toh(d->data.n_entries),
1780 int journal_file_move_to_entry_by_realtime_for_data(
1782 uint64_t data_offset,
1784 direction_t direction,
1785 Object **ret, uint64_t *offset) {
1792 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1796 return generic_array_bisect_plus_one(f,
1797 le64toh(d->data.entry_offset),
1798 le64toh(d->data.entry_array_offset),
1799 le64toh(d->data.n_entries),
1801 test_object_realtime,
1806 void journal_file_dump(JournalFile *f) {
1813 journal_file_print_header(f);
1815 p = le64toh(f->header->header_size);
1817 r = journal_file_move_to_object(f, -1, p, &o);
1821 switch (o->object.type) {
1824 printf("Type: OBJECT_UNUSED\n");
1828 printf("Type: OBJECT_DATA\n");
1832 printf("Type: OBJECT_ENTRY %llu %llu %llu\n",
1833 (unsigned long long) le64toh(o->entry.seqnum),
1834 (unsigned long long) le64toh(o->entry.monotonic),
1835 (unsigned long long) le64toh(o->entry.realtime));
1838 case OBJECT_FIELD_HASH_TABLE:
1839 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
1842 case OBJECT_DATA_HASH_TABLE:
1843 printf("Type: OBJECT_DATA_HASH_TABLE\n");
1846 case OBJECT_ENTRY_ARRAY:
1847 printf("Type: OBJECT_ENTRY_ARRAY\n");
1851 printf("Type: OBJECT_TAG %llu\n",
1852 (unsigned long long) le64toh(o->tag.seqnum));
1856 if (o->object.flags & OBJECT_COMPRESSED)
1857 printf("Flags: COMPRESSED\n");
1859 if (p == le64toh(f->header->tail_object_offset))
1862 p = p + ALIGN64(le64toh(o->object.size));
1867 log_error("File corrupt");
1870 void journal_file_print_header(JournalFile *f) {
1871 char a[33], b[33], c[33];
1872 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX];
1876 printf("File Path: %s\n"
1880 "Sequential Number ID: %s\n"
1882 "Compatible Flags:%s%s\n"
1883 "Incompatible Flags:%s%s\n"
1884 "Header size: %llu\n"
1885 "Arena size: %llu\n"
1886 "Data Hash Table Size: %llu\n"
1887 "Field Hash Table Size: %llu\n"
1888 "Rotate Suggested: %s\n"
1889 "Head Sequential Number: %llu\n"
1890 "Tail Sequential Number: %llu\n"
1891 "Head Realtime Timestamp: %s\n"
1892 "Tail Realtime Timestamp: %s\n"
1894 "Entry Objects: %llu\n",
1896 sd_id128_to_string(f->header->file_id, a),
1897 sd_id128_to_string(f->header->machine_id, b),
1898 sd_id128_to_string(f->header->boot_id, c),
1899 sd_id128_to_string(f->header->seqnum_id, c),
1900 f->header->state == STATE_OFFLINE ? "OFFLINE" :
1901 f->header->state == STATE_ONLINE ? "ONLINE" :
1902 f->header->state == STATE_ARCHIVED ? "ARCHIVED" : "UNKNOWN",
1903 (f->header->compatible_flags & HEADER_COMPATIBLE_SEALED) ? " SEALED" : "",
1904 (f->header->compatible_flags & ~HEADER_COMPATIBLE_SEALED) ? " ???" : "",
1905 (f->header->incompatible_flags & HEADER_INCOMPATIBLE_COMPRESSED) ? " COMPRESSED" : "",
1906 (f->header->incompatible_flags & ~HEADER_INCOMPATIBLE_COMPRESSED) ? " ???" : "",
1907 (unsigned long long) le64toh(f->header->header_size),
1908 (unsigned long long) le64toh(f->header->arena_size),
1909 (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
1910 (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
1911 yes_no(journal_file_rotate_suggested(f)),
1912 (unsigned long long) le64toh(f->header->head_entry_seqnum),
1913 (unsigned long long) le64toh(f->header->tail_entry_seqnum),
1914 format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
1915 format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)),
1916 (unsigned long long) le64toh(f->header->n_objects),
1917 (unsigned long long) le64toh(f->header->n_entries));
1919 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
1920 printf("Data Objects: %llu\n"
1921 "Data Hash Table Fill: %.1f%%\n",
1922 (unsigned long long) le64toh(f->header->n_data),
1923 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
1925 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
1926 printf("Field Objects: %llu\n"
1927 "Field Hash Table Fill: %.1f%%\n",
1928 (unsigned long long) le64toh(f->header->n_fields),
1929 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
1931 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags))
1932 printf("Tag Objects: %llu\n",
1933 (unsigned long long) le64toh(f->header->n_tags));
1934 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
1935 printf("Entry Array Objects: %llu\n",
1936 (unsigned long long) le64toh(f->header->n_entry_arrays));
1939 int journal_file_open(
1945 JournalMetrics *metrics,
1946 MMapCache *mmap_cache,
1947 JournalFile *template,
1948 JournalFile **ret) {
1952 bool newly_created = false;
1956 if ((flags & O_ACCMODE) != O_RDONLY &&
1957 (flags & O_ACCMODE) != O_RDWR)
1960 if (!endswith(fname, ".journal") &&
1961 !endswith(fname, ".journal~"))
1964 f = new0(JournalFile, 1);
1972 f->prot = prot_from_flags(flags);
1973 f->writable = (flags & O_ACCMODE) != O_RDONLY;
1974 f->compress = compress;
1978 f->mmap = mmap_cache_ref(mmap_cache);
1980 /* One context for each type, plus the zeroth catchall
1981 * context. One fd for the file plus one for each type
1982 * (which we need during verification */
1983 f->mmap = mmap_cache_new(_OBJECT_TYPE_MAX, 1 + _OBJECT_TYPE_MAX);
1990 f->path = strdup(fname);
1996 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
2002 if (fstat(f->fd, &f->last_stat) < 0) {
2007 if (f->last_stat.st_size == 0 && f->writable) {
2008 newly_created = true;
2010 /* Try to load the FSPRG state, and if we can't, then
2011 * just don't do sealing */
2012 r = journal_file_fss_load(f);
2016 r = journal_file_init_header(f, template);
2020 if (fstat(f->fd, &f->last_stat) < 0) {
2026 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
2031 f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
2032 if (f->header == MAP_FAILED) {
2038 if (!newly_created) {
2039 r = journal_file_verify_header(f);
2044 if (!newly_created && f->writable) {
2045 r = journal_file_fss_load(f);
2052 journal_default_metrics(metrics, f->fd);
2053 f->metrics = *metrics;
2054 } else if (template)
2055 f->metrics = template->metrics;
2057 r = journal_file_refresh_header(f);
2062 r = journal_file_hmac_setup(f);
2066 if (newly_created) {
2067 r = journal_file_setup_field_hash_table(f);
2071 r = journal_file_setup_data_hash_table(f);
2075 r = journal_file_append_first_tag(f);
2080 r = journal_file_map_field_hash_table(f);
2084 r = journal_file_map_data_hash_table(f);
2094 journal_file_close(f);
2099 int journal_file_rotate(JournalFile **f, bool compress, bool seal) {
2102 JournalFile *old_file, *new_file = NULL;
2110 if (!old_file->writable)
2113 if (!endswith(old_file->path, ".journal"))
2116 l = strlen(old_file->path);
2118 p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
2122 memcpy(p, old_file->path, l - 8);
2124 sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
2125 snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
2126 "-%016llx-%016llx.journal",
2127 (unsigned long long) le64toh((*f)->header->tail_entry_seqnum),
2128 (unsigned long long) le64toh((*f)->header->tail_entry_realtime));
2130 r = rename(old_file->path, p);
2136 old_file->header->state = STATE_ARCHIVED;
2138 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, compress, seal, NULL, old_file->mmap, old_file, &new_file);
2139 journal_file_close(old_file);
2145 int journal_file_open_reliably(
2151 JournalMetrics *metrics,
2152 MMapCache *mmap_cache,
2153 JournalFile *template,
2154 JournalFile **ret) {
2160 r = journal_file_open(fname, flags, mode, compress, seal,
2161 metrics, mmap_cache, template, ret);
2162 if (r != -EBADMSG && /* corrupted */
2163 r != -ENODATA && /* truncated */
2164 r != -EHOSTDOWN && /* other machine */
2165 r != -EPROTONOSUPPORT && /* incompatible feature */
2166 r != -EBUSY && /* unclean shutdown */
2167 r != -ESHUTDOWN /* already archived */)
2170 if ((flags & O_ACCMODE) == O_RDONLY)
2173 if (!(flags & O_CREAT))
2176 if (!endswith(fname, ".journal"))
2179 /* The file is corrupted. Rotate it away and try it again (but only once) */
2182 if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
2184 (unsigned long long) now(CLOCK_REALTIME),
2188 r = rename(fname, p);
2193 log_warning("File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
2195 return journal_file_open(fname, flags, mode, compress, seal,
2196 metrics, mmap_cache, template, ret);
2200 int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
2202 uint64_t q, xor_hash = 0;
2215 ts.monotonic = le64toh(o->entry.monotonic);
2216 ts.realtime = le64toh(o->entry.realtime);
2218 if (to->tail_entry_monotonic_valid &&
2219 ts.monotonic < le64toh(to->header->tail_entry_monotonic))
2222 n = journal_file_entry_n_items(o);
2223 items = alloca(sizeof(EntryItem) * n);
2225 for (i = 0; i < n; i++) {
2232 q = le64toh(o->entry.items[i].object_offset);
2233 le_hash = o->entry.items[i].hash;
2235 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
2239 if (le_hash != o->data.hash)
2242 l = le64toh(o->object.size) - offsetof(Object, data.payload);
2245 /* We hit the limit on 32bit machines */
2246 if ((uint64_t) t != l)
2249 if (o->object.flags & OBJECT_COMPRESSED) {
2253 if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
2256 data = from->compress_buffer;
2259 return -EPROTONOSUPPORT;
2262 data = o->data.payload;
2264 r = journal_file_append_data(to, data, l, &u, &h);
2268 xor_hash ^= le64toh(u->data.hash);
2269 items[i].object_offset = htole64(h);
2270 items[i].hash = u->data.hash;
2272 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
2277 return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
2280 void journal_default_metrics(JournalMetrics *m, int fd) {
2281 uint64_t fs_size = 0;
2283 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
2288 if (fstatvfs(fd, &ss) >= 0)
2289 fs_size = ss.f_frsize * ss.f_blocks;
2291 if (m->max_use == (uint64_t) -1) {
2294 m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
2296 if (m->max_use > DEFAULT_MAX_USE_UPPER)
2297 m->max_use = DEFAULT_MAX_USE_UPPER;
2299 if (m->max_use < DEFAULT_MAX_USE_LOWER)
2300 m->max_use = DEFAULT_MAX_USE_LOWER;
2302 m->max_use = DEFAULT_MAX_USE_LOWER;
2304 m->max_use = PAGE_ALIGN(m->max_use);
2306 if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
2307 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
2310 if (m->max_size == (uint64_t) -1) {
2311 m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
2313 if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
2314 m->max_size = DEFAULT_MAX_SIZE_UPPER;
2316 m->max_size = PAGE_ALIGN(m->max_size);
2318 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
2319 m->max_size = JOURNAL_FILE_SIZE_MIN;
2321 if (m->max_size*2 > m->max_use)
2322 m->max_use = m->max_size*2;
2324 if (m->min_size == (uint64_t) -1)
2325 m->min_size = JOURNAL_FILE_SIZE_MIN;
2327 m->min_size = PAGE_ALIGN(m->min_size);
2329 if (m->min_size < JOURNAL_FILE_SIZE_MIN)
2330 m->min_size = JOURNAL_FILE_SIZE_MIN;
2332 if (m->min_size > m->max_size)
2333 m->max_size = m->min_size;
2336 if (m->keep_free == (uint64_t) -1) {
2339 m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
2341 if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
2342 m->keep_free = DEFAULT_KEEP_FREE_UPPER;
2345 m->keep_free = DEFAULT_KEEP_FREE;
2348 log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
2349 format_bytes(a, sizeof(a), m->max_use),
2350 format_bytes(b, sizeof(b), m->max_size),
2351 format_bytes(c, sizeof(c), m->min_size),
2352 format_bytes(d, sizeof(d), m->keep_free));
2355 int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
2360 if (f->header->head_entry_realtime == 0)
2363 *from = le64toh(f->header->head_entry_realtime);
2367 if (f->header->tail_entry_realtime == 0)
2370 *to = le64toh(f->header->tail_entry_realtime);
2376 int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
2377 char t[9+32+1] = "_BOOT_ID=";
2385 sd_id128_to_string(boot_id, t + 9);
2387 r = journal_file_find_data_object(f, t, strlen(t), &o, &p);
2391 if (le64toh(o->data.n_entries) <= 0)
2395 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
2399 *from = le64toh(o->entry.monotonic);
2403 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
2407 r = generic_array_get_plus_one(f,
2408 le64toh(o->data.entry_offset),
2409 le64toh(o->data.entry_array_offset),
2410 le64toh(o->data.n_entries)-1,
2415 *to = le64toh(o->entry.monotonic);
2421 bool journal_file_rotate_suggested(JournalFile *f) {
2424 /* If we gained new header fields we gained new features,
2425 * hence suggest a rotation */
2426 if (le64toh(f->header->header_size) < sizeof(Header)) {
2427 log_debug("%s uses an outdated header, suggesting rotation.", f->path);
2431 /* Let's check if the hash tables grew over a certain fill
2432 * level (75%, borrowing this value from Java's hash table
2433 * implementation), and if so suggest a rotation. To calculate
2434 * the fill level we need the n_data field, which only exists
2435 * in newer versions. */
2437 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2438 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
2439 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.",
2441 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
2442 (unsigned long long) le64toh(f->header->n_data),
2443 (unsigned long long) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)),
2444 (unsigned long long) (f->last_stat.st_size),
2445 (unsigned long long) (f->last_stat.st_size / le64toh(f->header->n_data)));
2449 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2450 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
2451 log_debug("Field hash table of %s has a fill level at %.1f (%llu of %llu items), suggesting rotation.",
2453 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
2454 (unsigned long long) le64toh(f->header->n_fields),
2455 (unsigned long long) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)));