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->fsprg_header, PAGE_ALIGN(f->fsprg_size));
103 gcry_md_close(f->hmac);
109 static int journal_file_init_header(JournalFile *f, JournalFile *template) {
117 memcpy(h.signature, HEADER_SIGNATURE, 8);
118 h.header_size = htole64(ALIGN64(sizeof(h)));
120 h.incompatible_flags =
121 htole32(f->compress ? HEADER_INCOMPATIBLE_COMPRESSED : 0);
124 htole32(f->authenticate ? HEADER_COMPATIBLE_AUTHENTICATED : 0);
126 r = sd_id128_randomize(&h.file_id);
131 h.seqnum_id = template->header->seqnum_id;
132 h.tail_entry_seqnum = template->header->tail_entry_seqnum;
134 h.seqnum_id = h.file_id;
136 k = pwrite(f->fd, &h, sizeof(h), 0);
146 static int journal_file_refresh_header(JournalFile *f) {
152 r = sd_id128_get_machine(&f->header->machine_id);
156 r = sd_id128_get_boot(&boot_id);
160 if (sd_id128_equal(boot_id, f->header->boot_id))
161 f->tail_entry_monotonic_valid = true;
163 f->header->boot_id = boot_id;
165 f->header->state = STATE_ONLINE;
167 /* Sync the online state to disk */
168 msync(f->header, PAGE_ALIGN(sizeof(Header)), MS_SYNC);
174 static int journal_file_verify_header(JournalFile *f) {
177 if (memcmp(f->header->signature, HEADER_SIGNATURE, 8))
180 /* In both read and write mode we refuse to open files with
181 * incompatible flags we don't know */
183 if ((le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) != 0)
184 return -EPROTONOSUPPORT;
186 if (f->header->incompatible_flags != 0)
187 return -EPROTONOSUPPORT;
190 /* When open for writing we refuse to open files with
191 * compatible flags, too */
194 if ((le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_AUTHENTICATED) != 0)
195 return -EPROTONOSUPPORT;
197 if (f->header->compatible_flags != 0)
198 return -EPROTONOSUPPORT;
202 /* The first addition was n_data, so check that we are at least this large */
203 if (le64toh(f->header->header_size) < HEADER_SIZE_MIN)
206 if ((le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED) &&
207 !JOURNAL_HEADER_CONTAINS(f->header, n_tags))
210 if ((uint64_t) f->last_stat.st_size < (le64toh(f->header->header_size) + le64toh(f->header->arena_size)))
215 sd_id128_t machine_id;
218 r = sd_id128_get_machine(&machine_id);
222 if (!sd_id128_equal(machine_id, f->header->machine_id))
225 state = f->header->state;
227 if (state == STATE_ONLINE) {
228 log_debug("Journal file %s is already online. Assuming unclean closing.", f->path);
230 } else if (state == STATE_ARCHIVED)
232 else if (state != STATE_OFFLINE) {
233 log_debug("Journal file %s has unknown state %u.", f->path, state);
238 f->compress = !!(le32toh(f->header->incompatible_flags) & HEADER_INCOMPATIBLE_COMPRESSED);
239 f->authenticate = !!(le32toh(f->header->compatible_flags) & HEADER_COMPATIBLE_AUTHENTICATED);
244 static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
245 uint64_t old_size, new_size;
250 /* We assume that this file is not sparse, and we know that
251 * for sure, since we always call posix_fallocate()
255 le64toh(f->header->header_size) +
256 le64toh(f->header->arena_size);
258 new_size = PAGE_ALIGN(offset + size);
259 if (new_size < le64toh(f->header->header_size))
260 new_size = le64toh(f->header->header_size);
262 if (new_size <= old_size)
265 if (f->metrics.max_size > 0 &&
266 new_size > f->metrics.max_size)
269 if (new_size > f->metrics.min_size &&
270 f->metrics.keep_free > 0) {
273 if (fstatvfs(f->fd, &svfs) >= 0) {
276 available = svfs.f_bfree * svfs.f_bsize;
278 if (available >= f->metrics.keep_free)
279 available -= f->metrics.keep_free;
283 if (new_size - old_size > available)
288 /* Note that the glibc fallocate() fallback is very
289 inefficient, hence we try to minimize the allocation area
291 r = posix_fallocate(f->fd, old_size, new_size - old_size);
295 mmap_cache_close_fd_range(f->mmap, f->fd, old_size);
297 if (fstat(f->fd, &f->last_stat) < 0)
300 f->header->arena_size = htole64(new_size - le64toh(f->header->header_size));
305 static int journal_file_move_to(JournalFile *f, int context, uint64_t offset, uint64_t size, void **ret) {
309 /* Avoid SIGBUS on invalid accesses */
310 if (offset + size > (uint64_t) f->last_stat.st_size) {
311 /* Hmm, out of range? Let's refresh the fstat() data
312 * first, before we trust that check. */
314 if (fstat(f->fd, &f->last_stat) < 0 ||
315 offset + size > (uint64_t) f->last_stat.st_size)
316 return -EADDRNOTAVAIL;
319 return mmap_cache_get(f->mmap, f->fd, f->prot, context, offset, size, ret);
322 static uint64_t minimum_header_size(Object *o) {
324 static uint64_t table[] = {
325 [OBJECT_DATA] = sizeof(DataObject),
326 [OBJECT_FIELD] = sizeof(FieldObject),
327 [OBJECT_ENTRY] = sizeof(EntryObject),
328 [OBJECT_DATA_HASH_TABLE] = sizeof(HashTableObject),
329 [OBJECT_FIELD_HASH_TABLE] = sizeof(HashTableObject),
330 [OBJECT_ENTRY_ARRAY] = sizeof(EntryArrayObject),
331 [OBJECT_TAG] = sizeof(TagObject),
334 if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0)
335 return sizeof(ObjectHeader);
337 return table[o->object.type];
340 int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret) {
350 /* One context for each type, plus one catch-all for the rest */
351 context = type > 0 && type < _OBJECT_TYPE_MAX ? type : 0;
353 r = journal_file_move_to(f, context, offset, sizeof(ObjectHeader), &t);
358 s = le64toh(o->object.size);
360 if (s < sizeof(ObjectHeader))
363 if (o->object.type <= OBJECT_UNUSED)
366 if (s < minimum_header_size(o))
369 if (type >= 0 && o->object.type != type)
372 if (s > sizeof(ObjectHeader)) {
373 r = journal_file_move_to(f, o->object.type, offset, s, &t);
384 static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) {
389 r = le64toh(f->header->tail_entry_seqnum) + 1;
392 /* If an external seqnum counter was passed, we update
393 * both the local and the external one, and set it to
394 * the maximum of both */
402 f->header->tail_entry_seqnum = htole64(r);
404 if (f->header->head_entry_seqnum == 0)
405 f->header->head_entry_seqnum = htole64(r);
410 int journal_file_append_object(JournalFile *f, int type, uint64_t size, Object **ret, uint64_t *offset) {
417 assert(type > 0 && type < _OBJECT_TYPE_MAX);
418 assert(size >= sizeof(ObjectHeader));
422 p = le64toh(f->header->tail_object_offset);
424 p = le64toh(f->header->header_size);
426 r = journal_file_move_to_object(f, -1, p, &tail);
430 p += ALIGN64(le64toh(tail->object.size));
433 r = journal_file_allocate(f, p, size);
437 r = journal_file_move_to(f, type, p, size, &t);
444 o->object.type = type;
445 o->object.size = htole64(size);
447 f->header->tail_object_offset = htole64(p);
448 f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1);
456 static int journal_file_setup_data_hash_table(JournalFile *f) {
463 /* We estimate that we need 1 hash table entry per 768 of
464 journal file and we want to make sure we never get beyond
465 75% fill level. Calculate the hash table size for the
466 maximum file size based on these metrics. */
468 s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem);
469 if (s < DEFAULT_DATA_HASH_TABLE_SIZE)
470 s = DEFAULT_DATA_HASH_TABLE_SIZE;
472 log_info("Reserving %llu entries in hash table.", (unsigned long long) (s / sizeof(HashItem)));
474 r = journal_file_append_object(f,
475 OBJECT_DATA_HASH_TABLE,
476 offsetof(Object, hash_table.items) + s,
481 memset(o->hash_table.items, 0, s);
483 f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
484 f->header->data_hash_table_size = htole64(s);
489 static int journal_file_setup_field_hash_table(JournalFile *f) {
496 s = DEFAULT_FIELD_HASH_TABLE_SIZE;
497 r = journal_file_append_object(f,
498 OBJECT_FIELD_HASH_TABLE,
499 offsetof(Object, hash_table.items) + s,
504 memset(o->hash_table.items, 0, s);
506 f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
507 f->header->field_hash_table_size = htole64(s);
512 static int journal_file_map_data_hash_table(JournalFile *f) {
519 p = le64toh(f->header->data_hash_table_offset);
520 s = le64toh(f->header->data_hash_table_size);
522 r = journal_file_move_to(f,
523 OBJECT_DATA_HASH_TABLE,
529 f->data_hash_table = t;
533 static int journal_file_map_field_hash_table(JournalFile *f) {
540 p = le64toh(f->header->field_hash_table_offset);
541 s = le64toh(f->header->field_hash_table_size);
543 r = journal_file_move_to(f,
544 OBJECT_FIELD_HASH_TABLE,
550 f->field_hash_table = t;
554 static int journal_file_link_data(JournalFile *f, Object *o, uint64_t offset, uint64_t hash) {
561 assert(o->object.type == OBJECT_DATA);
563 /* This might alter the window we are looking at */
565 o->data.next_hash_offset = o->data.next_field_offset = 0;
566 o->data.entry_offset = o->data.entry_array_offset = 0;
567 o->data.n_entries = 0;
569 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
570 p = le64toh(f->data_hash_table[h].tail_hash_offset);
572 /* Only entry in the hash table is easy */
573 f->data_hash_table[h].head_hash_offset = htole64(offset);
575 /* Move back to the previous data object, to patch in
578 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
582 o->data.next_hash_offset = htole64(offset);
585 f->data_hash_table[h].tail_hash_offset = htole64(offset);
587 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
588 f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
593 int journal_file_find_data_object_with_hash(
595 const void *data, uint64_t size, uint64_t hash,
596 Object **ret, uint64_t *offset) {
598 uint64_t p, osize, h;
602 assert(data || size == 0);
604 osize = offsetof(Object, data.payload) + size;
606 if (f->header->data_hash_table_size == 0)
609 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
610 p = le64toh(f->data_hash_table[h].head_hash_offset);
615 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
619 if (le64toh(o->data.hash) != hash)
622 if (o->object.flags & OBJECT_COMPRESSED) {
626 l = le64toh(o->object.size);
627 if (l <= offsetof(Object, data.payload))
630 l -= offsetof(Object, data.payload);
632 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
636 memcmp(f->compress_buffer, data, size) == 0) {
647 return -EPROTONOSUPPORT;
650 } else if (le64toh(o->object.size) == osize &&
651 memcmp(o->data.payload, data, size) == 0) {
663 p = le64toh(o->data.next_hash_offset);
669 int journal_file_find_data_object(
671 const void *data, uint64_t size,
672 Object **ret, uint64_t *offset) {
677 assert(data || size == 0);
679 hash = hash64(data, size);
681 return journal_file_find_data_object_with_hash(f,
686 static int journal_file_append_data(
688 const void *data, uint64_t size,
689 Object **ret, uint64_t *offset) {
695 bool compressed = false;
698 assert(data || size == 0);
700 hash = hash64(data, size);
702 r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
716 osize = offsetof(Object, data.payload) + size;
717 r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
721 o->data.hash = htole64(hash);
725 size >= COMPRESSION_SIZE_THRESHOLD) {
728 compressed = compress_blob(data, size, o->data.payload, &rsize);
731 o->object.size = htole64(offsetof(Object, data.payload) + rsize);
732 o->object.flags |= OBJECT_COMPRESSED;
734 log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize);
739 if (!compressed && size > 0)
740 memcpy(o->data.payload, data, size);
742 r = journal_file_link_data(f, o, p, hash);
746 r = journal_file_hmac_put_object(f, OBJECT_DATA, p);
750 /* The linking might have altered the window, so let's
751 * refresh our pointer */
752 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
765 uint64_t journal_file_entry_n_items(Object *o) {
767 assert(o->object.type == OBJECT_ENTRY);
769 return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
772 uint64_t journal_file_entry_array_n_items(Object *o) {
774 assert(o->object.type == OBJECT_ENTRY_ARRAY);
776 return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
779 static int link_entry_into_array(JournalFile *f,
784 uint64_t n = 0, ap = 0, q, i, a, hidx;
793 i = hidx = le64toh(*idx);
796 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
800 n = journal_file_entry_array_n_items(o);
802 o->entry_array.items[i] = htole64(p);
803 *idx = htole64(hidx + 1);
809 a = le64toh(o->entry_array.next_entry_array_offset);
820 r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
821 offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
826 r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, q);
830 o->entry_array.items[i] = htole64(p);
835 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
839 o->entry_array.next_entry_array_offset = htole64(q);
842 *idx = htole64(hidx + 1);
847 static int link_entry_into_array_plus_one(JournalFile *f,
866 i = htole64(le64toh(*idx) - 1);
867 r = link_entry_into_array(f, first, &i, p);
872 *idx = htole64(le64toh(*idx) + 1);
876 static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
883 p = le64toh(o->entry.items[i].object_offset);
887 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
891 return link_entry_into_array_plus_one(f,
892 &o->data.entry_offset,
893 &o->data.entry_array_offset,
898 static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
905 assert(o->object.type == OBJECT_ENTRY);
907 __sync_synchronize();
909 /* Link up the entry itself */
910 r = link_entry_into_array(f,
911 &f->header->entry_array_offset,
912 &f->header->n_entries,
917 /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */
919 if (f->header->head_entry_realtime == 0)
920 f->header->head_entry_realtime = o->entry.realtime;
922 f->header->tail_entry_realtime = o->entry.realtime;
923 f->header->tail_entry_monotonic = o->entry.monotonic;
925 f->tail_entry_monotonic_valid = true;
927 /* Link up the items */
928 n = journal_file_entry_n_items(o);
929 for (i = 0; i < n; i++) {
930 r = journal_file_link_entry_item(f, o, offset, i);
938 static int journal_file_append_entry_internal(
940 const dual_timestamp *ts,
942 const EntryItem items[], unsigned n_items,
944 Object **ret, uint64_t *offset) {
951 assert(items || n_items == 0);
954 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
956 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
960 o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum));
961 memcpy(o->entry.items, items, n_items * sizeof(EntryItem));
962 o->entry.realtime = htole64(ts->realtime);
963 o->entry.monotonic = htole64(ts->monotonic);
964 o->entry.xor_hash = htole64(xor_hash);
965 o->entry.boot_id = f->header->boot_id;
967 r = journal_file_hmac_put_object(f, OBJECT_ENTRY, np);
971 r = journal_file_link_entry(f, o, np);
984 void journal_file_post_change(JournalFile *f) {
987 /* inotify() does not receive IN_MODIFY events from file
988 * accesses done via mmap(). After each access we hence
989 * trigger IN_MODIFY by truncating the journal file to its
990 * current size which triggers IN_MODIFY. */
992 __sync_synchronize();
994 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
995 log_error("Failed to to truncate file to its own size: %m");
998 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) {
1002 uint64_t xor_hash = 0;
1003 struct dual_timestamp _ts;
1006 assert(iovec || n_iovec == 0);
1012 dual_timestamp_get(&_ts);
1016 if (f->tail_entry_monotonic_valid &&
1017 ts->monotonic < le64toh(f->header->tail_entry_monotonic))
1020 r = journal_file_maybe_append_tag(f, ts->realtime);
1024 /* alloca() can't take 0, hence let's allocate at least one */
1025 items = alloca(sizeof(EntryItem) * MAX(1, n_iovec));
1027 for (i = 0; i < n_iovec; i++) {
1031 r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
1035 xor_hash ^= le64toh(o->data.hash);
1036 items[i].object_offset = htole64(p);
1037 items[i].hash = o->data.hash;
1040 r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
1042 journal_file_post_change(f);
1047 static int generic_array_get(JournalFile *f,
1050 Object **ret, uint64_t *offset) {
1062 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1066 n = journal_file_entry_array_n_items(o);
1068 p = le64toh(o->entry_array.items[i]);
1073 a = le64toh(o->entry_array.next_entry_array_offset);
1076 if (a <= 0 || p <= 0)
1079 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1092 static int generic_array_get_plus_one(JournalFile *f,
1096 Object **ret, uint64_t *offset) {
1105 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1118 return generic_array_get(f, first, i-1, ret, offset);
1127 static int generic_array_bisect(JournalFile *f,
1131 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1132 direction_t direction,
1137 uint64_t a, p, t = 0, i = 0, last_p = 0;
1138 bool subtract_one = false;
1139 Object *o, *array = NULL;
1143 assert(test_object);
1147 uint64_t left, right, k, lp;
1149 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
1153 k = journal_file_entry_array_n_items(array);
1159 lp = p = le64toh(array->entry_array.items[i]);
1163 r = test_object(f, p, needle);
1167 if (r == TEST_FOUND)
1168 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1170 if (r == TEST_RIGHT) {
1174 if (left == right) {
1175 if (direction == DIRECTION_UP)
1176 subtract_one = true;
1182 assert(left < right);
1184 i = (left + right) / 2;
1185 p = le64toh(array->entry_array.items[i]);
1189 r = test_object(f, p, needle);
1193 if (r == TEST_FOUND)
1194 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1196 if (r == TEST_RIGHT)
1204 if (direction == DIRECTION_UP) {
1206 subtract_one = true;
1217 a = le64toh(array->entry_array.next_entry_array_offset);
1223 if (subtract_one && t == 0 && i == 0)
1226 if (subtract_one && i == 0)
1228 else if (subtract_one)
1229 p = le64toh(array->entry_array.items[i-1]);
1231 p = le64toh(array->entry_array.items[i]);
1233 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1244 *idx = t + i + (subtract_one ? -1 : 0);
1249 static int generic_array_bisect_plus_one(JournalFile *f,
1254 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1255 direction_t direction,
1261 bool step_back = false;
1265 assert(test_object);
1270 /* This bisects the array in object 'first', but first checks
1272 r = test_object(f, extra, needle);
1276 if (r == TEST_FOUND)
1277 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1279 /* if we are looking with DIRECTION_UP then we need to first
1280 see if in the actual array there is a matching entry, and
1281 return the last one of that. But if there isn't any we need
1282 to return this one. Hence remember this, and return it
1285 step_back = direction == DIRECTION_UP;
1287 if (r == TEST_RIGHT) {
1288 if (direction == DIRECTION_DOWN)
1294 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
1296 if (r == 0 && step_back)
1305 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1321 static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
1327 else if (p < needle)
1333 int journal_file_move_to_entry_by_offset(
1336 direction_t direction,
1340 return generic_array_bisect(f,
1341 le64toh(f->header->entry_array_offset),
1342 le64toh(f->header->n_entries),
1350 static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
1357 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1361 if (le64toh(o->entry.seqnum) == needle)
1363 else if (le64toh(o->entry.seqnum) < needle)
1369 int journal_file_move_to_entry_by_seqnum(
1372 direction_t direction,
1376 return generic_array_bisect(f,
1377 le64toh(f->header->entry_array_offset),
1378 le64toh(f->header->n_entries),
1385 static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
1392 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1396 if (le64toh(o->entry.realtime) == needle)
1398 else if (le64toh(o->entry.realtime) < needle)
1404 int journal_file_move_to_entry_by_realtime(
1407 direction_t direction,
1411 return generic_array_bisect(f,
1412 le64toh(f->header->entry_array_offset),
1413 le64toh(f->header->n_entries),
1415 test_object_realtime,
1420 static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
1427 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1431 if (le64toh(o->entry.monotonic) == needle)
1433 else if (le64toh(o->entry.monotonic) < needle)
1439 int journal_file_move_to_entry_by_monotonic(
1443 direction_t direction,
1447 char t[9+32+1] = "_BOOT_ID=";
1453 sd_id128_to_string(boot_id, t + 9);
1454 r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
1460 return generic_array_bisect_plus_one(f,
1461 le64toh(o->data.entry_offset),
1462 le64toh(o->data.entry_array_offset),
1463 le64toh(o->data.n_entries),
1465 test_object_monotonic,
1470 int journal_file_next_entry(
1472 Object *o, uint64_t p,
1473 direction_t direction,
1474 Object **ret, uint64_t *offset) {
1480 assert(p > 0 || !o);
1482 n = le64toh(f->header->n_entries);
1487 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1489 if (o->object.type != OBJECT_ENTRY)
1492 r = generic_array_bisect(f,
1493 le64toh(f->header->entry_array_offset),
1494 le64toh(f->header->n_entries),
1503 if (direction == DIRECTION_DOWN) {
1516 /* And jump to it */
1517 return generic_array_get(f,
1518 le64toh(f->header->entry_array_offset),
1523 int journal_file_skip_entry(
1525 Object *o, uint64_t p,
1527 Object **ret, uint64_t *offset) {
1536 if (o->object.type != OBJECT_ENTRY)
1539 r = generic_array_bisect(f,
1540 le64toh(f->header->entry_array_offset),
1541 le64toh(f->header->n_entries),
1550 /* Calculate new index */
1552 if ((uint64_t) -skip >= i)
1555 i = i - (uint64_t) -skip;
1557 i += (uint64_t) skip;
1559 n = le64toh(f->header->n_entries);
1566 return generic_array_get(f,
1567 le64toh(f->header->entry_array_offset),
1572 int journal_file_next_entry_for_data(
1574 Object *o, uint64_t p,
1575 uint64_t data_offset,
1576 direction_t direction,
1577 Object **ret, uint64_t *offset) {
1584 assert(p > 0 || !o);
1586 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1590 n = le64toh(d->data.n_entries);
1595 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1597 if (o->object.type != OBJECT_ENTRY)
1600 r = generic_array_bisect_plus_one(f,
1601 le64toh(d->data.entry_offset),
1602 le64toh(d->data.entry_array_offset),
1603 le64toh(d->data.n_entries),
1613 if (direction == DIRECTION_DOWN) {
1627 return generic_array_get_plus_one(f,
1628 le64toh(d->data.entry_offset),
1629 le64toh(d->data.entry_array_offset),
1634 int journal_file_move_to_entry_by_offset_for_data(
1636 uint64_t data_offset,
1638 direction_t direction,
1639 Object **ret, uint64_t *offset) {
1646 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1650 return generic_array_bisect_plus_one(f,
1651 le64toh(d->data.entry_offset),
1652 le64toh(d->data.entry_array_offset),
1653 le64toh(d->data.n_entries),
1660 int journal_file_move_to_entry_by_monotonic_for_data(
1662 uint64_t data_offset,
1665 direction_t direction,
1666 Object **ret, uint64_t *offset) {
1668 char t[9+32+1] = "_BOOT_ID=";
1675 /* First, seek by time */
1676 sd_id128_to_string(boot_id, t + 9);
1677 r = journal_file_find_data_object(f, t, strlen(t), &o, &b);
1683 r = generic_array_bisect_plus_one(f,
1684 le64toh(o->data.entry_offset),
1685 le64toh(o->data.entry_array_offset),
1686 le64toh(o->data.n_entries),
1688 test_object_monotonic,
1694 /* And now, continue seeking until we find an entry that
1695 * exists in both bisection arrays */
1701 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1705 r = generic_array_bisect_plus_one(f,
1706 le64toh(d->data.entry_offset),
1707 le64toh(d->data.entry_array_offset),
1708 le64toh(d->data.n_entries),
1716 r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
1720 r = generic_array_bisect_plus_one(f,
1721 le64toh(o->data.entry_offset),
1722 le64toh(o->data.entry_array_offset),
1723 le64toh(o->data.n_entries),
1747 int journal_file_move_to_entry_by_seqnum_for_data(
1749 uint64_t data_offset,
1751 direction_t direction,
1752 Object **ret, uint64_t *offset) {
1759 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1763 return generic_array_bisect_plus_one(f,
1764 le64toh(d->data.entry_offset),
1765 le64toh(d->data.entry_array_offset),
1766 le64toh(d->data.n_entries),
1773 int journal_file_move_to_entry_by_realtime_for_data(
1775 uint64_t data_offset,
1777 direction_t direction,
1778 Object **ret, uint64_t *offset) {
1785 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1789 return generic_array_bisect_plus_one(f,
1790 le64toh(d->data.entry_offset),
1791 le64toh(d->data.entry_array_offset),
1792 le64toh(d->data.n_entries),
1794 test_object_realtime,
1799 void journal_file_dump(JournalFile *f) {
1806 journal_file_print_header(f);
1808 p = le64toh(f->header->header_size);
1810 r = journal_file_move_to_object(f, -1, p, &o);
1814 switch (o->object.type) {
1817 printf("Type: OBJECT_UNUSED\n");
1821 printf("Type: OBJECT_DATA\n");
1825 printf("Type: OBJECT_ENTRY %llu %llu %llu\n",
1826 (unsigned long long) le64toh(o->entry.seqnum),
1827 (unsigned long long) le64toh(o->entry.monotonic),
1828 (unsigned long long) le64toh(o->entry.realtime));
1831 case OBJECT_FIELD_HASH_TABLE:
1832 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
1835 case OBJECT_DATA_HASH_TABLE:
1836 printf("Type: OBJECT_DATA_HASH_TABLE\n");
1839 case OBJECT_ENTRY_ARRAY:
1840 printf("Type: OBJECT_ENTRY_ARRAY\n");
1844 printf("Type: OBJECT_TAG %llu\n",
1845 (unsigned long long) le64toh(o->tag.seqnum));
1849 if (o->object.flags & OBJECT_COMPRESSED)
1850 printf("Flags: COMPRESSED\n");
1852 if (p == le64toh(f->header->tail_object_offset))
1855 p = p + ALIGN64(le64toh(o->object.size));
1860 log_error("File corrupt");
1863 void journal_file_print_header(JournalFile *f) {
1864 char a[33], b[33], c[33];
1865 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX];
1869 printf("File Path: %s\n"
1873 "Sequential Number ID: %s\n"
1875 "Compatible Flags:%s%s\n"
1876 "Incompatible Flags:%s%s\n"
1877 "Header size: %llu\n"
1878 "Arena size: %llu\n"
1879 "Data Hash Table Size: %llu\n"
1880 "Field Hash Table Size: %llu\n"
1882 "Entry Objects: %llu\n"
1883 "Rotate Suggested: %s\n"
1884 "Head Sequential Number: %llu\n"
1885 "Tail Sequential Number: %llu\n"
1886 "Head Realtime Timestamp: %s\n"
1887 "Tail Realtime Timestamp: %s\n",
1889 sd_id128_to_string(f->header->file_id, a),
1890 sd_id128_to_string(f->header->machine_id, b),
1891 sd_id128_to_string(f->header->boot_id, c),
1892 sd_id128_to_string(f->header->seqnum_id, c),
1893 f->header->state == STATE_OFFLINE ? "offline" :
1894 f->header->state == STATE_ONLINE ? "online" :
1895 f->header->state == STATE_ARCHIVED ? "archived" : "unknown",
1896 (f->header->compatible_flags & HEADER_COMPATIBLE_AUTHENTICATED) ? " AUTHENTICATED" : "",
1897 (f->header->compatible_flags & ~HEADER_COMPATIBLE_AUTHENTICATED) ? " ???" : "",
1898 (f->header->incompatible_flags & HEADER_INCOMPATIBLE_COMPRESSED) ? " COMPRESSED" : "",
1899 (f->header->incompatible_flags & ~HEADER_INCOMPATIBLE_COMPRESSED) ? " ???" : "",
1900 (unsigned long long) le64toh(f->header->header_size),
1901 (unsigned long long) le64toh(f->header->arena_size),
1902 (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
1903 (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
1904 (unsigned long long) le64toh(f->header->n_objects),
1905 (unsigned long long) le64toh(f->header->n_entries),
1906 yes_no(journal_file_rotate_suggested(f)),
1907 (unsigned long long) le64toh(f->header->head_entry_seqnum),
1908 (unsigned long long) le64toh(f->header->tail_entry_seqnum),
1909 format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
1910 format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)));
1912 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
1913 printf("Data Objects: %llu\n"
1914 "Data Hash Table Fill: %.1f%%\n",
1915 (unsigned long long) le64toh(f->header->n_data),
1916 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
1918 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
1919 printf("Field Objects: %llu\n"
1920 "Field Hash Table Fill: %.1f%%\n",
1921 (unsigned long long) le64toh(f->header->n_fields),
1922 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
1925 int journal_file_open(
1931 JournalMetrics *metrics,
1932 MMapCache *mmap_cache,
1933 JournalFile *template,
1934 JournalFile **ret) {
1938 bool newly_created = false;
1942 if ((flags & O_ACCMODE) != O_RDONLY &&
1943 (flags & O_ACCMODE) != O_RDWR)
1946 if (!endswith(fname, ".journal"))
1949 f = new0(JournalFile, 1);
1957 f->prot = prot_from_flags(flags);
1958 f->writable = (flags & O_ACCMODE) != O_RDONLY;
1959 f->compress = compress;
1960 f->authenticate = authenticate;
1963 f->mmap = mmap_cache_ref(mmap_cache);
1965 /* One context for each type, plus the zeroth catchall
1966 * context. One fd for the file plus one for each type
1967 * (which we need during verification */
1968 f->mmap = mmap_cache_new(_OBJECT_TYPE_MAX, 1 + _OBJECT_TYPE_MAX);
1975 f->path = strdup(fname);
1981 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
1987 if (fstat(f->fd, &f->last_stat) < 0) {
1992 if (f->last_stat.st_size == 0 && f->writable) {
1993 newly_created = true;
1995 /* Try to load the FSPRG state, and if we can't, then
1996 * just don't do authentication */
1997 r = journal_file_load_fsprg(f);
1999 f->authenticate = false;
2001 r = journal_file_init_header(f, template);
2005 if (fstat(f->fd, &f->last_stat) < 0) {
2011 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
2016 f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
2017 if (f->header == MAP_FAILED) {
2023 if (!newly_created) {
2024 r = journal_file_verify_header(f);
2029 if (!newly_created && f->writable) {
2030 r = journal_file_load_fsprg(f);
2037 journal_default_metrics(metrics, f->fd);
2038 f->metrics = *metrics;
2039 } else if (template)
2040 f->metrics = template->metrics;
2042 r = journal_file_refresh_header(f);
2046 r = journal_file_setup_hmac(f);
2051 if (newly_created) {
2052 r = journal_file_setup_field_hash_table(f);
2056 r = journal_file_setup_data_hash_table(f);
2060 r = journal_file_append_first_tag(f);
2065 r = journal_file_map_field_hash_table(f);
2069 r = journal_file_map_data_hash_table(f);
2079 journal_file_close(f);
2084 int journal_file_rotate(JournalFile **f, bool compress, bool authenticate) {
2087 JournalFile *old_file, *new_file = NULL;
2095 if (!old_file->writable)
2098 if (!endswith(old_file->path, ".journal"))
2101 l = strlen(old_file->path);
2103 p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
2107 memcpy(p, old_file->path, l - 8);
2109 sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
2110 snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
2111 "-%016llx-%016llx.journal",
2112 (unsigned long long) le64toh((*f)->header->tail_entry_seqnum),
2113 (unsigned long long) le64toh((*f)->header->tail_entry_realtime));
2115 r = rename(old_file->path, p);
2121 old_file->header->state = STATE_ARCHIVED;
2123 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, compress, authenticate, NULL, old_file->mmap, old_file, &new_file);
2124 journal_file_close(old_file);
2130 int journal_file_open_reliably(
2136 JournalMetrics *metrics,
2138 JournalFile *template,
2139 JournalFile **ret) {
2145 r = journal_file_open(fname, flags, mode, compress, authenticate, metrics, mmap, template, ret);
2146 if (r != -EBADMSG && /* corrupted */
2147 r != -ENODATA && /* truncated */
2148 r != -EHOSTDOWN && /* other machine */
2149 r != -EPROTONOSUPPORT && /* incompatible feature */
2150 r != -EBUSY && /* unclean shutdown */
2151 r != -ESHUTDOWN /* already archived */)
2154 if ((flags & O_ACCMODE) == O_RDONLY)
2157 if (!(flags & O_CREAT))
2160 if (!endswith(fname, ".journal"))
2163 /* The file is corrupted. Rotate it away and try it again (but only once) */
2166 if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
2168 (unsigned long long) now(CLOCK_REALTIME),
2172 r = rename(fname, p);
2177 log_warning("File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
2179 return journal_file_open(fname, flags, mode, compress, authenticate, metrics, mmap, template, ret);
2183 int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
2185 uint64_t q, xor_hash = 0;
2198 ts.monotonic = le64toh(o->entry.monotonic);
2199 ts.realtime = le64toh(o->entry.realtime);
2201 if (to->tail_entry_monotonic_valid &&
2202 ts.monotonic < le64toh(to->header->tail_entry_monotonic))
2205 n = journal_file_entry_n_items(o);
2206 items = alloca(sizeof(EntryItem) * n);
2208 for (i = 0; i < n; i++) {
2215 q = le64toh(o->entry.items[i].object_offset);
2216 le_hash = o->entry.items[i].hash;
2218 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
2222 if (le_hash != o->data.hash)
2225 l = le64toh(o->object.size) - offsetof(Object, data.payload);
2228 /* We hit the limit on 32bit machines */
2229 if ((uint64_t) t != l)
2232 if (o->object.flags & OBJECT_COMPRESSED) {
2236 if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
2239 data = from->compress_buffer;
2242 return -EPROTONOSUPPORT;
2245 data = o->data.payload;
2247 r = journal_file_append_data(to, data, l, &u, &h);
2251 xor_hash ^= le64toh(u->data.hash);
2252 items[i].object_offset = htole64(h);
2253 items[i].hash = u->data.hash;
2255 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
2260 return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
2263 void journal_default_metrics(JournalMetrics *m, int fd) {
2264 uint64_t fs_size = 0;
2266 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
2271 if (fstatvfs(fd, &ss) >= 0)
2272 fs_size = ss.f_frsize * ss.f_blocks;
2274 if (m->max_use == (uint64_t) -1) {
2277 m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
2279 if (m->max_use > DEFAULT_MAX_USE_UPPER)
2280 m->max_use = DEFAULT_MAX_USE_UPPER;
2282 if (m->max_use < DEFAULT_MAX_USE_LOWER)
2283 m->max_use = DEFAULT_MAX_USE_LOWER;
2285 m->max_use = DEFAULT_MAX_USE_LOWER;
2287 m->max_use = PAGE_ALIGN(m->max_use);
2289 if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
2290 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
2293 if (m->max_size == (uint64_t) -1) {
2294 m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
2296 if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
2297 m->max_size = DEFAULT_MAX_SIZE_UPPER;
2299 m->max_size = PAGE_ALIGN(m->max_size);
2301 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
2302 m->max_size = JOURNAL_FILE_SIZE_MIN;
2304 if (m->max_size*2 > m->max_use)
2305 m->max_use = m->max_size*2;
2307 if (m->min_size == (uint64_t) -1)
2308 m->min_size = JOURNAL_FILE_SIZE_MIN;
2310 m->min_size = PAGE_ALIGN(m->min_size);
2312 if (m->min_size < JOURNAL_FILE_SIZE_MIN)
2313 m->min_size = JOURNAL_FILE_SIZE_MIN;
2315 if (m->min_size > m->max_size)
2316 m->max_size = m->min_size;
2319 if (m->keep_free == (uint64_t) -1) {
2322 m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
2324 if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
2325 m->keep_free = DEFAULT_KEEP_FREE_UPPER;
2328 m->keep_free = DEFAULT_KEEP_FREE;
2331 log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
2332 format_bytes(a, sizeof(a), m->max_use),
2333 format_bytes(b, sizeof(b), m->max_size),
2334 format_bytes(c, sizeof(c), m->min_size),
2335 format_bytes(d, sizeof(d), m->keep_free));
2338 int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
2343 if (f->header->head_entry_realtime == 0)
2346 *from = le64toh(f->header->head_entry_realtime);
2350 if (f->header->tail_entry_realtime == 0)
2353 *to = le64toh(f->header->tail_entry_realtime);
2359 int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
2360 char t[9+32+1] = "_BOOT_ID=";
2368 sd_id128_to_string(boot_id, t + 9);
2370 r = journal_file_find_data_object(f, t, strlen(t), &o, &p);
2374 if (le64toh(o->data.n_entries) <= 0)
2378 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
2382 *from = le64toh(o->entry.monotonic);
2386 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
2390 r = generic_array_get_plus_one(f,
2391 le64toh(o->data.entry_offset),
2392 le64toh(o->data.entry_array_offset),
2393 le64toh(o->data.n_entries)-1,
2398 *to = le64toh(o->entry.monotonic);
2404 bool journal_file_rotate_suggested(JournalFile *f) {
2407 /* If we gained new header fields we gained new features,
2408 * hence suggest a rotation */
2409 if (le64toh(f->header->header_size) < sizeof(Header)) {
2410 log_debug("%s uses an outdated header, suggesting rotation.", f->path);
2414 /* Let's check if the hash tables grew over a certain fill
2415 * level (75%, borrowing this value from Java's hash table
2416 * implementation), and if so suggest a rotation. To calculate
2417 * the fill level we need the n_data field, which only exists
2418 * in newer versions. */
2420 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2421 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
2422 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.",
2424 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
2425 (unsigned long long) le64toh(f->header->n_data),
2426 (unsigned long long) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)),
2427 (unsigned long long) (f->last_stat.st_size),
2428 (unsigned long long) (f->last_stat.st_size / le64toh(f->header->n_data)));
2432 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2433 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
2434 log_debug("Field hash table of %s has a fill level at %.1f (%llu of %llu items), suggesting rotation.",
2436 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
2437 (unsigned long long) le64toh(f->header->n_fields),
2438 (unsigned long long) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)));