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 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
843 f->header->n_entry_arrays = htole64(le64toh(f->header->n_entry_arrays) + 1);
845 *idx = htole64(hidx + 1);
850 static int link_entry_into_array_plus_one(JournalFile *f,
869 i = htole64(le64toh(*idx) - 1);
870 r = link_entry_into_array(f, first, &i, p);
875 *idx = htole64(le64toh(*idx) + 1);
879 static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
886 p = le64toh(o->entry.items[i].object_offset);
890 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
894 return link_entry_into_array_plus_one(f,
895 &o->data.entry_offset,
896 &o->data.entry_array_offset,
901 static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
908 assert(o->object.type == OBJECT_ENTRY);
910 __sync_synchronize();
912 /* Link up the entry itself */
913 r = link_entry_into_array(f,
914 &f->header->entry_array_offset,
915 &f->header->n_entries,
920 /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */
922 if (f->header->head_entry_realtime == 0)
923 f->header->head_entry_realtime = o->entry.realtime;
925 f->header->tail_entry_realtime = o->entry.realtime;
926 f->header->tail_entry_monotonic = o->entry.monotonic;
928 f->tail_entry_monotonic_valid = true;
930 /* Link up the items */
931 n = journal_file_entry_n_items(o);
932 for (i = 0; i < n; i++) {
933 r = journal_file_link_entry_item(f, o, offset, i);
941 static int journal_file_append_entry_internal(
943 const dual_timestamp *ts,
945 const EntryItem items[], unsigned n_items,
947 Object **ret, uint64_t *offset) {
954 assert(items || n_items == 0);
957 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
959 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
963 o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum));
964 memcpy(o->entry.items, items, n_items * sizeof(EntryItem));
965 o->entry.realtime = htole64(ts->realtime);
966 o->entry.monotonic = htole64(ts->monotonic);
967 o->entry.xor_hash = htole64(xor_hash);
968 o->entry.boot_id = f->header->boot_id;
970 r = journal_file_hmac_put_object(f, OBJECT_ENTRY, np);
974 r = journal_file_link_entry(f, o, np);
987 void journal_file_post_change(JournalFile *f) {
990 /* inotify() does not receive IN_MODIFY events from file
991 * accesses done via mmap(). After each access we hence
992 * trigger IN_MODIFY by truncating the journal file to its
993 * current size which triggers IN_MODIFY. */
995 __sync_synchronize();
997 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
998 log_error("Failed to to truncate file to its own size: %m");
1001 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) {
1005 uint64_t xor_hash = 0;
1006 struct dual_timestamp _ts;
1009 assert(iovec || n_iovec == 0);
1015 dual_timestamp_get(&_ts);
1019 if (f->tail_entry_monotonic_valid &&
1020 ts->monotonic < le64toh(f->header->tail_entry_monotonic))
1023 r = journal_file_maybe_append_tag(f, ts->realtime);
1027 /* alloca() can't take 0, hence let's allocate at least one */
1028 items = alloca(sizeof(EntryItem) * MAX(1, n_iovec));
1030 for (i = 0; i < n_iovec; i++) {
1034 r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
1038 xor_hash ^= le64toh(o->data.hash);
1039 items[i].object_offset = htole64(p);
1040 items[i].hash = o->data.hash;
1043 r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
1045 journal_file_post_change(f);
1050 static int generic_array_get(JournalFile *f,
1053 Object **ret, uint64_t *offset) {
1065 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1069 n = journal_file_entry_array_n_items(o);
1071 p = le64toh(o->entry_array.items[i]);
1076 a = le64toh(o->entry_array.next_entry_array_offset);
1079 if (a <= 0 || p <= 0)
1082 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1095 static int generic_array_get_plus_one(JournalFile *f,
1099 Object **ret, uint64_t *offset) {
1108 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1121 return generic_array_get(f, first, i-1, ret, offset);
1130 static int generic_array_bisect(JournalFile *f,
1134 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1135 direction_t direction,
1140 uint64_t a, p, t = 0, i = 0, last_p = 0;
1141 bool subtract_one = false;
1142 Object *o, *array = NULL;
1146 assert(test_object);
1150 uint64_t left, right, k, lp;
1152 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
1156 k = journal_file_entry_array_n_items(array);
1162 lp = p = le64toh(array->entry_array.items[i]);
1166 r = test_object(f, p, needle);
1170 if (r == TEST_FOUND)
1171 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1173 if (r == TEST_RIGHT) {
1177 if (left == right) {
1178 if (direction == DIRECTION_UP)
1179 subtract_one = true;
1185 assert(left < right);
1187 i = (left + right) / 2;
1188 p = le64toh(array->entry_array.items[i]);
1192 r = test_object(f, p, needle);
1196 if (r == TEST_FOUND)
1197 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1199 if (r == TEST_RIGHT)
1207 if (direction == DIRECTION_UP) {
1209 subtract_one = true;
1220 a = le64toh(array->entry_array.next_entry_array_offset);
1226 if (subtract_one && t == 0 && i == 0)
1229 if (subtract_one && i == 0)
1231 else if (subtract_one)
1232 p = le64toh(array->entry_array.items[i-1]);
1234 p = le64toh(array->entry_array.items[i]);
1236 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1247 *idx = t + i + (subtract_one ? -1 : 0);
1252 static int generic_array_bisect_plus_one(JournalFile *f,
1257 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1258 direction_t direction,
1264 bool step_back = false;
1268 assert(test_object);
1273 /* This bisects the array in object 'first', but first checks
1275 r = test_object(f, extra, needle);
1279 if (r == TEST_FOUND)
1280 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1282 /* if we are looking with DIRECTION_UP then we need to first
1283 see if in the actual array there is a matching entry, and
1284 return the last one of that. But if there isn't any we need
1285 to return this one. Hence remember this, and return it
1288 step_back = direction == DIRECTION_UP;
1290 if (r == TEST_RIGHT) {
1291 if (direction == DIRECTION_DOWN)
1297 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
1299 if (r == 0 && step_back)
1308 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1324 static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
1330 else if (p < needle)
1336 int journal_file_move_to_entry_by_offset(
1339 direction_t direction,
1343 return generic_array_bisect(f,
1344 le64toh(f->header->entry_array_offset),
1345 le64toh(f->header->n_entries),
1353 static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
1360 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1364 if (le64toh(o->entry.seqnum) == needle)
1366 else if (le64toh(o->entry.seqnum) < needle)
1372 int journal_file_move_to_entry_by_seqnum(
1375 direction_t direction,
1379 return generic_array_bisect(f,
1380 le64toh(f->header->entry_array_offset),
1381 le64toh(f->header->n_entries),
1388 static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
1395 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1399 if (le64toh(o->entry.realtime) == needle)
1401 else if (le64toh(o->entry.realtime) < needle)
1407 int journal_file_move_to_entry_by_realtime(
1410 direction_t direction,
1414 return generic_array_bisect(f,
1415 le64toh(f->header->entry_array_offset),
1416 le64toh(f->header->n_entries),
1418 test_object_realtime,
1423 static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
1430 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1434 if (le64toh(o->entry.monotonic) == needle)
1436 else if (le64toh(o->entry.monotonic) < needle)
1442 int journal_file_move_to_entry_by_monotonic(
1446 direction_t direction,
1450 char t[9+32+1] = "_BOOT_ID=";
1456 sd_id128_to_string(boot_id, t + 9);
1457 r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
1463 return generic_array_bisect_plus_one(f,
1464 le64toh(o->data.entry_offset),
1465 le64toh(o->data.entry_array_offset),
1466 le64toh(o->data.n_entries),
1468 test_object_monotonic,
1473 int journal_file_next_entry(
1475 Object *o, uint64_t p,
1476 direction_t direction,
1477 Object **ret, uint64_t *offset) {
1483 assert(p > 0 || !o);
1485 n = le64toh(f->header->n_entries);
1490 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1492 if (o->object.type != OBJECT_ENTRY)
1495 r = generic_array_bisect(f,
1496 le64toh(f->header->entry_array_offset),
1497 le64toh(f->header->n_entries),
1506 if (direction == DIRECTION_DOWN) {
1519 /* And jump to it */
1520 return generic_array_get(f,
1521 le64toh(f->header->entry_array_offset),
1526 int journal_file_skip_entry(
1528 Object *o, uint64_t p,
1530 Object **ret, uint64_t *offset) {
1539 if (o->object.type != OBJECT_ENTRY)
1542 r = generic_array_bisect(f,
1543 le64toh(f->header->entry_array_offset),
1544 le64toh(f->header->n_entries),
1553 /* Calculate new index */
1555 if ((uint64_t) -skip >= i)
1558 i = i - (uint64_t) -skip;
1560 i += (uint64_t) skip;
1562 n = le64toh(f->header->n_entries);
1569 return generic_array_get(f,
1570 le64toh(f->header->entry_array_offset),
1575 int journal_file_next_entry_for_data(
1577 Object *o, uint64_t p,
1578 uint64_t data_offset,
1579 direction_t direction,
1580 Object **ret, uint64_t *offset) {
1587 assert(p > 0 || !o);
1589 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1593 n = le64toh(d->data.n_entries);
1598 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1600 if (o->object.type != OBJECT_ENTRY)
1603 r = generic_array_bisect_plus_one(f,
1604 le64toh(d->data.entry_offset),
1605 le64toh(d->data.entry_array_offset),
1606 le64toh(d->data.n_entries),
1616 if (direction == DIRECTION_DOWN) {
1630 return generic_array_get_plus_one(f,
1631 le64toh(d->data.entry_offset),
1632 le64toh(d->data.entry_array_offset),
1637 int journal_file_move_to_entry_by_offset_for_data(
1639 uint64_t data_offset,
1641 direction_t direction,
1642 Object **ret, uint64_t *offset) {
1649 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1653 return generic_array_bisect_plus_one(f,
1654 le64toh(d->data.entry_offset),
1655 le64toh(d->data.entry_array_offset),
1656 le64toh(d->data.n_entries),
1663 int journal_file_move_to_entry_by_monotonic_for_data(
1665 uint64_t data_offset,
1668 direction_t direction,
1669 Object **ret, uint64_t *offset) {
1671 char t[9+32+1] = "_BOOT_ID=";
1678 /* First, seek by time */
1679 sd_id128_to_string(boot_id, t + 9);
1680 r = journal_file_find_data_object(f, t, strlen(t), &o, &b);
1686 r = generic_array_bisect_plus_one(f,
1687 le64toh(o->data.entry_offset),
1688 le64toh(o->data.entry_array_offset),
1689 le64toh(o->data.n_entries),
1691 test_object_monotonic,
1697 /* And now, continue seeking until we find an entry that
1698 * exists in both bisection arrays */
1704 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1708 r = generic_array_bisect_plus_one(f,
1709 le64toh(d->data.entry_offset),
1710 le64toh(d->data.entry_array_offset),
1711 le64toh(d->data.n_entries),
1719 r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
1723 r = generic_array_bisect_plus_one(f,
1724 le64toh(o->data.entry_offset),
1725 le64toh(o->data.entry_array_offset),
1726 le64toh(o->data.n_entries),
1750 int journal_file_move_to_entry_by_seqnum_for_data(
1752 uint64_t data_offset,
1754 direction_t direction,
1755 Object **ret, uint64_t *offset) {
1762 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1766 return generic_array_bisect_plus_one(f,
1767 le64toh(d->data.entry_offset),
1768 le64toh(d->data.entry_array_offset),
1769 le64toh(d->data.n_entries),
1776 int journal_file_move_to_entry_by_realtime_for_data(
1778 uint64_t data_offset,
1780 direction_t direction,
1781 Object **ret, uint64_t *offset) {
1788 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1792 return generic_array_bisect_plus_one(f,
1793 le64toh(d->data.entry_offset),
1794 le64toh(d->data.entry_array_offset),
1795 le64toh(d->data.n_entries),
1797 test_object_realtime,
1802 void journal_file_dump(JournalFile *f) {
1809 journal_file_print_header(f);
1811 p = le64toh(f->header->header_size);
1813 r = journal_file_move_to_object(f, -1, p, &o);
1817 switch (o->object.type) {
1820 printf("Type: OBJECT_UNUSED\n");
1824 printf("Type: OBJECT_DATA\n");
1828 printf("Type: OBJECT_ENTRY %llu %llu %llu\n",
1829 (unsigned long long) le64toh(o->entry.seqnum),
1830 (unsigned long long) le64toh(o->entry.monotonic),
1831 (unsigned long long) le64toh(o->entry.realtime));
1834 case OBJECT_FIELD_HASH_TABLE:
1835 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
1838 case OBJECT_DATA_HASH_TABLE:
1839 printf("Type: OBJECT_DATA_HASH_TABLE\n");
1842 case OBJECT_ENTRY_ARRAY:
1843 printf("Type: OBJECT_ENTRY_ARRAY\n");
1847 printf("Type: OBJECT_TAG %llu\n",
1848 (unsigned long long) le64toh(o->tag.seqnum));
1852 if (o->object.flags & OBJECT_COMPRESSED)
1853 printf("Flags: COMPRESSED\n");
1855 if (p == le64toh(f->header->tail_object_offset))
1858 p = p + ALIGN64(le64toh(o->object.size));
1863 log_error("File corrupt");
1866 void journal_file_print_header(JournalFile *f) {
1867 char a[33], b[33], c[33];
1868 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX];
1872 printf("File Path: %s\n"
1876 "Sequential Number ID: %s\n"
1878 "Compatible Flags:%s%s\n"
1879 "Incompatible Flags:%s%s\n"
1880 "Header size: %llu\n"
1881 "Arena size: %llu\n"
1882 "Data Hash Table Size: %llu\n"
1883 "Field Hash Table Size: %llu\n"
1885 "Entry Objects: %llu\n"
1886 "Rotate Suggested: %s\n"
1887 "Head Sequential Number: %llu\n"
1888 "Tail Sequential Number: %llu\n"
1889 "Head Realtime Timestamp: %s\n"
1890 "Tail Realtime Timestamp: %s\n",
1892 sd_id128_to_string(f->header->file_id, a),
1893 sd_id128_to_string(f->header->machine_id, b),
1894 sd_id128_to_string(f->header->boot_id, c),
1895 sd_id128_to_string(f->header->seqnum_id, c),
1896 f->header->state == STATE_OFFLINE ? "offline" :
1897 f->header->state == STATE_ONLINE ? "online" :
1898 f->header->state == STATE_ARCHIVED ? "archived" : "unknown",
1899 (f->header->compatible_flags & HEADER_COMPATIBLE_AUTHENTICATED) ? " AUTHENTICATED" : "",
1900 (f->header->compatible_flags & ~HEADER_COMPATIBLE_AUTHENTICATED) ? " ???" : "",
1901 (f->header->incompatible_flags & HEADER_INCOMPATIBLE_COMPRESSED) ? " COMPRESSED" : "",
1902 (f->header->incompatible_flags & ~HEADER_INCOMPATIBLE_COMPRESSED) ? " ???" : "",
1903 (unsigned long long) le64toh(f->header->header_size),
1904 (unsigned long long) le64toh(f->header->arena_size),
1905 (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
1906 (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
1907 (unsigned long long) le64toh(f->header->n_objects),
1908 (unsigned long long) le64toh(f->header->n_entries),
1909 yes_no(journal_file_rotate_suggested(f)),
1910 (unsigned long long) le64toh(f->header->head_entry_seqnum),
1911 (unsigned long long) le64toh(f->header->tail_entry_seqnum),
1912 format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
1913 format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)));
1915 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
1916 printf("Data Objects: %llu\n"
1917 "Data Hash Table Fill: %.1f%%\n",
1918 (unsigned long long) le64toh(f->header->n_data),
1919 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
1921 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
1922 printf("Field Objects: %llu\n"
1923 "Field Hash Table Fill: %.1f%%\n",
1924 (unsigned long long) le64toh(f->header->n_fields),
1925 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
1928 int journal_file_open(
1934 JournalMetrics *metrics,
1935 MMapCache *mmap_cache,
1936 JournalFile *template,
1937 JournalFile **ret) {
1941 bool newly_created = false;
1945 if ((flags & O_ACCMODE) != O_RDONLY &&
1946 (flags & O_ACCMODE) != O_RDWR)
1949 if (!endswith(fname, ".journal"))
1952 f = new0(JournalFile, 1);
1960 f->prot = prot_from_flags(flags);
1961 f->writable = (flags & O_ACCMODE) != O_RDONLY;
1962 f->compress = compress;
1963 f->authenticate = authenticate;
1966 f->mmap = mmap_cache_ref(mmap_cache);
1968 /* One context for each type, plus the zeroth catchall
1969 * context. One fd for the file plus one for each type
1970 * (which we need during verification */
1971 f->mmap = mmap_cache_new(_OBJECT_TYPE_MAX, 1 + _OBJECT_TYPE_MAX);
1978 f->path = strdup(fname);
1984 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
1990 if (fstat(f->fd, &f->last_stat) < 0) {
1995 if (f->last_stat.st_size == 0 && f->writable) {
1996 newly_created = true;
1998 /* Try to load the FSPRG state, and if we can't, then
1999 * just don't do authentication */
2000 r = journal_file_load_fsprg(f);
2002 f->authenticate = false;
2004 r = journal_file_init_header(f, template);
2008 if (fstat(f->fd, &f->last_stat) < 0) {
2014 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
2019 f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
2020 if (f->header == MAP_FAILED) {
2026 if (!newly_created) {
2027 r = journal_file_verify_header(f);
2032 if (!newly_created && f->writable) {
2033 r = journal_file_load_fsprg(f);
2040 journal_default_metrics(metrics, f->fd);
2041 f->metrics = *metrics;
2042 } else if (template)
2043 f->metrics = template->metrics;
2045 r = journal_file_refresh_header(f);
2049 r = journal_file_setup_hmac(f);
2054 if (newly_created) {
2055 r = journal_file_setup_field_hash_table(f);
2059 r = journal_file_setup_data_hash_table(f);
2063 r = journal_file_append_first_tag(f);
2068 r = journal_file_map_field_hash_table(f);
2072 r = journal_file_map_data_hash_table(f);
2082 journal_file_close(f);
2087 int journal_file_rotate(JournalFile **f, bool compress, bool authenticate) {
2090 JournalFile *old_file, *new_file = NULL;
2098 if (!old_file->writable)
2101 if (!endswith(old_file->path, ".journal"))
2104 l = strlen(old_file->path);
2106 p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
2110 memcpy(p, old_file->path, l - 8);
2112 sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
2113 snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
2114 "-%016llx-%016llx.journal",
2115 (unsigned long long) le64toh((*f)->header->tail_entry_seqnum),
2116 (unsigned long long) le64toh((*f)->header->tail_entry_realtime));
2118 r = rename(old_file->path, p);
2124 old_file->header->state = STATE_ARCHIVED;
2126 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, compress, authenticate, NULL, old_file->mmap, old_file, &new_file);
2127 journal_file_close(old_file);
2133 int journal_file_open_reliably(
2139 JournalMetrics *metrics,
2140 MMapCache *mmap_cache,
2141 JournalFile *template,
2142 JournalFile **ret) {
2148 r = journal_file_open(fname, flags, mode, compress, authenticate,
2149 metrics, mmap_cache, template, ret);
2150 if (r != -EBADMSG && /* corrupted */
2151 r != -ENODATA && /* truncated */
2152 r != -EHOSTDOWN && /* other machine */
2153 r != -EPROTONOSUPPORT && /* incompatible feature */
2154 r != -EBUSY && /* unclean shutdown */
2155 r != -ESHUTDOWN /* already archived */)
2158 if ((flags & O_ACCMODE) == O_RDONLY)
2161 if (!(flags & O_CREAT))
2164 if (!endswith(fname, ".journal"))
2167 /* The file is corrupted. Rotate it away and try it again (but only once) */
2170 if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
2172 (unsigned long long) now(CLOCK_REALTIME),
2176 r = rename(fname, p);
2181 log_warning("File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
2183 return journal_file_open(fname, flags, mode, compress, authenticate,
2184 metrics, mmap_cache, template, ret);
2188 int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
2190 uint64_t q, xor_hash = 0;
2203 ts.monotonic = le64toh(o->entry.monotonic);
2204 ts.realtime = le64toh(o->entry.realtime);
2206 if (to->tail_entry_monotonic_valid &&
2207 ts.monotonic < le64toh(to->header->tail_entry_monotonic))
2210 n = journal_file_entry_n_items(o);
2211 items = alloca(sizeof(EntryItem) * n);
2213 for (i = 0; i < n; i++) {
2220 q = le64toh(o->entry.items[i].object_offset);
2221 le_hash = o->entry.items[i].hash;
2223 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
2227 if (le_hash != o->data.hash)
2230 l = le64toh(o->object.size) - offsetof(Object, data.payload);
2233 /* We hit the limit on 32bit machines */
2234 if ((uint64_t) t != l)
2237 if (o->object.flags & OBJECT_COMPRESSED) {
2241 if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
2244 data = from->compress_buffer;
2247 return -EPROTONOSUPPORT;
2250 data = o->data.payload;
2252 r = journal_file_append_data(to, data, l, &u, &h);
2256 xor_hash ^= le64toh(u->data.hash);
2257 items[i].object_offset = htole64(h);
2258 items[i].hash = u->data.hash;
2260 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
2265 return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
2268 void journal_default_metrics(JournalMetrics *m, int fd) {
2269 uint64_t fs_size = 0;
2271 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
2276 if (fstatvfs(fd, &ss) >= 0)
2277 fs_size = ss.f_frsize * ss.f_blocks;
2279 if (m->max_use == (uint64_t) -1) {
2282 m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
2284 if (m->max_use > DEFAULT_MAX_USE_UPPER)
2285 m->max_use = DEFAULT_MAX_USE_UPPER;
2287 if (m->max_use < DEFAULT_MAX_USE_LOWER)
2288 m->max_use = DEFAULT_MAX_USE_LOWER;
2290 m->max_use = DEFAULT_MAX_USE_LOWER;
2292 m->max_use = PAGE_ALIGN(m->max_use);
2294 if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
2295 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
2298 if (m->max_size == (uint64_t) -1) {
2299 m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
2301 if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
2302 m->max_size = DEFAULT_MAX_SIZE_UPPER;
2304 m->max_size = PAGE_ALIGN(m->max_size);
2306 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
2307 m->max_size = JOURNAL_FILE_SIZE_MIN;
2309 if (m->max_size*2 > m->max_use)
2310 m->max_use = m->max_size*2;
2312 if (m->min_size == (uint64_t) -1)
2313 m->min_size = JOURNAL_FILE_SIZE_MIN;
2315 m->min_size = PAGE_ALIGN(m->min_size);
2317 if (m->min_size < JOURNAL_FILE_SIZE_MIN)
2318 m->min_size = JOURNAL_FILE_SIZE_MIN;
2320 if (m->min_size > m->max_size)
2321 m->max_size = m->min_size;
2324 if (m->keep_free == (uint64_t) -1) {
2327 m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
2329 if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
2330 m->keep_free = DEFAULT_KEEP_FREE_UPPER;
2333 m->keep_free = DEFAULT_KEEP_FREE;
2336 log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
2337 format_bytes(a, sizeof(a), m->max_use),
2338 format_bytes(b, sizeof(b), m->max_size),
2339 format_bytes(c, sizeof(c), m->min_size),
2340 format_bytes(d, sizeof(d), m->keep_free));
2343 int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
2348 if (f->header->head_entry_realtime == 0)
2351 *from = le64toh(f->header->head_entry_realtime);
2355 if (f->header->tail_entry_realtime == 0)
2358 *to = le64toh(f->header->tail_entry_realtime);
2364 int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
2365 char t[9+32+1] = "_BOOT_ID=";
2373 sd_id128_to_string(boot_id, t + 9);
2375 r = journal_file_find_data_object(f, t, strlen(t), &o, &p);
2379 if (le64toh(o->data.n_entries) <= 0)
2383 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
2387 *from = le64toh(o->entry.monotonic);
2391 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
2395 r = generic_array_get_plus_one(f,
2396 le64toh(o->data.entry_offset),
2397 le64toh(o->data.entry_array_offset),
2398 le64toh(o->data.n_entries)-1,
2403 *to = le64toh(o->entry.monotonic);
2409 bool journal_file_rotate_suggested(JournalFile *f) {
2412 /* If we gained new header fields we gained new features,
2413 * hence suggest a rotation */
2414 if (le64toh(f->header->header_size) < sizeof(Header)) {
2415 log_debug("%s uses an outdated header, suggesting rotation.", f->path);
2419 /* Let's check if the hash tables grew over a certain fill
2420 * level (75%, borrowing this value from Java's hash table
2421 * implementation), and if so suggest a rotation. To calculate
2422 * the fill level we need the n_data field, which only exists
2423 * in newer versions. */
2425 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2426 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
2427 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.",
2429 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
2430 (unsigned long long) le64toh(f->header->n_data),
2431 (unsigned long long) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)),
2432 (unsigned long long) (f->last_stat.st_size),
2433 (unsigned long long) (f->last_stat.st_size / le64toh(f->header->n_data)));
2437 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2438 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
2439 log_debug("Field hash table of %s has a fill level at %.1f (%llu of %llu items), suggesting rotation.",
2441 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
2442 (unsigned long long) le64toh(f->header->n_fields),
2443 (unsigned long long) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)));