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"
35 #define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*16ULL)
36 #define DEFAULT_FIELD_HASH_TABLE_SIZE (2047ULL*16ULL)
38 #define DEFAULT_WINDOW_SIZE (8ULL*1024ULL*1024ULL)
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 #define ALIGN64(x) (((x) + 7ULL) & ~7ULL)
66 #define JOURNAL_HEADER_CONTAINS(h, field) \
67 (le64toh((h)->header_size) >= offsetof(Header, field) + sizeof((h)->field))
69 static const char signature[] = { 'L', 'P', 'K', 'S', 'H', 'H', 'R', 'H' };
71 void journal_file_close(JournalFile *f) {
78 f->header->state = STATE_OFFLINE;
80 munmap(f->header, PAGE_ALIGN(sizeof(Header)));
83 for (t = 0; t < _WINDOW_MAX; t++)
84 if (f->windows[t].ptr)
85 munmap(f->windows[t].ptr, f->windows[t].size);
88 close_nointr_nofail(f->fd);
93 free(f->compress_buffer);
99 static int journal_file_init_header(JournalFile *f, JournalFile *template) {
107 memcpy(h.signature, signature, 8);
108 h.header_size = htole64(ALIGN64(sizeof(h)));
110 r = sd_id128_randomize(&h.file_id);
115 h.seqnum_id = template->header->seqnum_id;
116 h.tail_seqnum = template->header->tail_seqnum;
118 h.seqnum_id = h.file_id;
120 k = pwrite(f->fd, &h, sizeof(h), 0);
130 static int journal_file_refresh_header(JournalFile *f) {
136 r = sd_id128_get_machine(&f->header->machine_id);
140 r = sd_id128_get_boot(&boot_id);
144 if (sd_id128_equal(boot_id, f->header->boot_id))
145 f->tail_entry_monotonic_valid = true;
147 f->header->boot_id = boot_id;
149 f->header->state = STATE_ONLINE;
151 __sync_synchronize();
156 static int journal_file_verify_header(JournalFile *f) {
159 if (memcmp(f->header, signature, 8))
163 if ((le64toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_COMPRESSED) != 0)
164 return -EPROTONOSUPPORT;
166 if (f->header->incompatible_flags != 0)
167 return -EPROTONOSUPPORT;
170 /* The first addition was n_data, so check that we are at least this large */
171 if (le64toh(f->header->header_size) < HEADER_SIZE_MIN)
174 if ((uint64_t) f->last_stat.st_size < (le64toh(f->header->header_size) + le64toh(f->header->arena_size)))
179 sd_id128_t machine_id;
182 r = sd_id128_get_machine(&machine_id);
186 if (!sd_id128_equal(machine_id, f->header->machine_id))
189 state = f->header->state;
191 if (state == STATE_ONLINE) {
192 log_debug("Journal file %s is already online. Assuming unclean closing.", f->path);
194 } else if (state == STATE_ARCHIVED)
196 else if (state != STATE_OFFLINE) {
197 log_debug("Journal file %s has unknown state %u.", f->path, state);
205 static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
206 uint64_t old_size, new_size;
211 /* We assume that this file is not sparse, and we know that
212 * for sure, since we always call posix_fallocate()
216 le64toh(f->header->header_size) +
217 le64toh(f->header->arena_size);
219 new_size = PAGE_ALIGN(offset + size);
220 if (new_size < le64toh(f->header->header_size))
221 new_size = le64toh(f->header->header_size);
223 if (new_size <= old_size)
226 if (f->metrics.max_size > 0 &&
227 new_size > f->metrics.max_size)
230 if (new_size > f->metrics.min_size &&
231 f->metrics.keep_free > 0) {
234 if (fstatvfs(f->fd, &svfs) >= 0) {
237 available = svfs.f_bfree * svfs.f_bsize;
239 if (available >= f->metrics.keep_free)
240 available -= f->metrics.keep_free;
244 if (new_size - old_size > available)
249 /* Note that the glibc fallocate() fallback is very
250 inefficient, hence we try to minimize the allocation area
252 r = posix_fallocate(f->fd, old_size, new_size - old_size);
256 if (fstat(f->fd, &f->last_stat) < 0)
259 f->header->arena_size = htole64(new_size - le64toh(f->header->header_size));
264 static int journal_file_map(
273 uint64_t woffset, wsize;
280 woffset = offset & ~((uint64_t) page_size() - 1ULL);
281 wsize = size + (offset - woffset);
282 wsize = PAGE_ALIGN(wsize);
284 /* Avoid SIGBUS on invalid accesses */
285 if (woffset + wsize > (uint64_t) PAGE_ALIGN(f->last_stat.st_size))
286 return -EADDRNOTAVAIL;
288 window = mmap(NULL, wsize, f->prot, MAP_SHARED, f->fd, woffset);
289 if (window == MAP_FAILED)
301 *ret = (uint8_t*) window + (offset - woffset);
306 static int journal_file_move_to(JournalFile *f, int wt, uint64_t offset, uint64_t size, void **ret) {
315 assert(wt < _WINDOW_MAX);
317 if (offset + size > (uint64_t) f->last_stat.st_size) {
318 /* Hmm, out of range? Let's refresh the fstat() data
319 * first, before we trust that check. */
321 if (fstat(f->fd, &f->last_stat) < 0 ||
322 offset + size > (uint64_t) f->last_stat.st_size)
323 return -EADDRNOTAVAIL;
328 if (_likely_(w->ptr &&
329 w->offset <= offset &&
330 w->offset + w->size >= offset + size)) {
332 *ret = (uint8_t*) w->ptr + (offset - w->offset);
337 if (munmap(w->ptr, w->size) < 0)
341 w->size = w->offset = 0;
344 if (size < DEFAULT_WINDOW_SIZE) {
345 /* If the default window size is larger then what was
346 * asked for extend the mapping a bit in the hope to
347 * minimize needed remappings later on. We add half
348 * the window space before and half behind the
349 * requested mapping */
351 delta = (DEFAULT_WINDOW_SIZE - size) / 2;
357 size = DEFAULT_WINDOW_SIZE;
361 if (offset + size > (uint64_t) f->last_stat.st_size)
362 size = (uint64_t) f->last_stat.st_size - offset;
365 return -EADDRNOTAVAIL;
367 r = journal_file_map(f,
369 &w->ptr, &w->offset, &w->size,
375 *ret = (uint8_t*) p + delta;
379 static bool verify_hash(Object *o) {
384 if (o->object.type == OBJECT_DATA && !(o->object.flags & OBJECT_COMPRESSED)) {
385 h1 = le64toh(o->data.hash);
386 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
387 } else if (o->object.type == OBJECT_FIELD) {
388 h1 = le64toh(o->field.hash);
389 h2 = hash64(o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload));
396 int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret) {
404 assert(type < _OBJECT_TYPE_MAX);
406 r = journal_file_move_to(f, type >= 0 ? type : WINDOW_UNKNOWN, offset, sizeof(ObjectHeader), &t);
411 s = le64toh(o->object.size);
413 if (s < sizeof(ObjectHeader))
416 if (type >= 0 && o->object.type != type)
419 if (s > sizeof(ObjectHeader)) {
420 r = journal_file_move_to(f, o->object.type, offset, s, &t);
434 static uint64_t journal_file_seqnum(JournalFile *f, uint64_t *seqnum) {
439 r = le64toh(f->header->tail_seqnum) + 1;
442 /* If an external seqnum counter was passed, we update
443 * both the local and the external one, and set it to
444 * the maximum of both */
452 f->header->tail_seqnum = htole64(r);
454 if (f->header->head_seqnum == 0)
455 f->header->head_seqnum = htole64(r);
460 static int journal_file_append_object(JournalFile *f, int type, uint64_t size, Object **ret, uint64_t *offset) {
467 assert(size >= sizeof(ObjectHeader));
471 p = le64toh(f->header->tail_object_offset);
473 p = le64toh(f->header->header_size);
475 r = journal_file_move_to_object(f, -1, p, &tail);
479 p += ALIGN64(le64toh(tail->object.size));
482 r = journal_file_allocate(f, p, size);
486 r = journal_file_move_to(f, type, p, size, &t);
493 o->object.type = type;
494 o->object.size = htole64(size);
496 f->header->tail_object_offset = htole64(p);
497 f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1);
505 static int journal_file_setup_data_hash_table(JournalFile *f) {
512 s = DEFAULT_DATA_HASH_TABLE_SIZE;
513 r = journal_file_append_object(f,
514 OBJECT_DATA_HASH_TABLE,
515 offsetof(Object, hash_table.items) + s,
520 memset(o->hash_table.items, 0, s);
522 f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
523 f->header->data_hash_table_size = htole64(s);
528 static int journal_file_setup_field_hash_table(JournalFile *f) {
535 s = DEFAULT_FIELD_HASH_TABLE_SIZE;
536 r = journal_file_append_object(f,
537 OBJECT_FIELD_HASH_TABLE,
538 offsetof(Object, hash_table.items) + s,
543 memset(o->hash_table.items, 0, s);
545 f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
546 f->header->field_hash_table_size = htole64(s);
551 static int journal_file_map_data_hash_table(JournalFile *f) {
558 p = le64toh(f->header->data_hash_table_offset);
559 s = le64toh(f->header->data_hash_table_size);
561 r = journal_file_move_to(f,
562 WINDOW_DATA_HASH_TABLE,
568 f->data_hash_table = t;
572 static int journal_file_map_field_hash_table(JournalFile *f) {
579 p = le64toh(f->header->field_hash_table_offset);
580 s = le64toh(f->header->field_hash_table_size);
582 r = journal_file_move_to(f,
583 WINDOW_FIELD_HASH_TABLE,
589 f->field_hash_table = t;
593 static int journal_file_link_data(JournalFile *f, Object *o, uint64_t offset, uint64_t hash) {
600 assert(o->object.type == OBJECT_DATA);
602 /* This might alter the window we are looking at */
604 o->data.next_hash_offset = o->data.next_field_offset = 0;
605 o->data.entry_offset = o->data.entry_array_offset = 0;
606 o->data.n_entries = 0;
608 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
609 p = le64toh(f->data_hash_table[h].tail_hash_offset);
611 /* Only entry in the hash table is easy */
612 f->data_hash_table[h].head_hash_offset = htole64(offset);
614 /* Move back to the previous data object, to patch in
617 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
621 o->data.next_hash_offset = htole64(offset);
624 f->data_hash_table[h].tail_hash_offset = htole64(offset);
626 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
627 f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
632 int journal_file_find_data_object_with_hash(
634 const void *data, uint64_t size, uint64_t hash,
635 Object **ret, uint64_t *offset) {
637 uint64_t p, osize, h;
641 assert(data || size == 0);
643 osize = offsetof(Object, data.payload) + size;
645 if (f->header->data_hash_table_size == 0)
648 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
649 p = le64toh(f->data_hash_table[h].head_hash_offset);
654 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
658 if (le64toh(o->data.hash) != hash)
661 if (o->object.flags & OBJECT_COMPRESSED) {
665 l = le64toh(o->object.size);
666 if (l <= offsetof(Object, data.payload))
669 l -= offsetof(Object, data.payload);
671 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
675 memcmp(f->compress_buffer, data, size) == 0) {
686 return -EPROTONOSUPPORT;
689 } else if (le64toh(o->object.size) == osize &&
690 memcmp(o->data.payload, data, size) == 0) {
702 p = le64toh(o->data.next_hash_offset);
708 int journal_file_find_data_object(
710 const void *data, uint64_t size,
711 Object **ret, uint64_t *offset) {
716 assert(data || size == 0);
718 hash = hash64(data, size);
720 return journal_file_find_data_object_with_hash(f,
725 static int journal_file_append_data(
727 const void *data, uint64_t size,
728 Object **ret, uint64_t *offset) {
734 bool compressed = false;
737 assert(data || size == 0);
739 hash = hash64(data, size);
741 r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
755 osize = offsetof(Object, data.payload) + size;
756 r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
760 o->data.hash = htole64(hash);
764 size >= COMPRESSION_SIZE_THRESHOLD) {
767 compressed = compress_blob(data, size, o->data.payload, &rsize);
770 o->object.size = htole64(offsetof(Object, data.payload) + rsize);
771 o->object.flags |= OBJECT_COMPRESSED;
773 f->header->incompatible_flags = htole32(le32toh(f->header->incompatible_flags) | HEADER_INCOMPATIBLE_COMPRESSED);
775 log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize);
781 memcpy(o->data.payload, data, size);
783 r = journal_file_link_data(f, o, p, hash);
787 /* The linking might have altered the window, so let's
788 * refresh our pointer */
789 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
802 uint64_t journal_file_entry_n_items(Object *o) {
804 assert(o->object.type == OBJECT_ENTRY);
806 return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
809 static uint64_t journal_file_entry_array_n_items(Object *o) {
811 assert(o->object.type == OBJECT_ENTRY_ARRAY);
813 return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
816 static int link_entry_into_array(JournalFile *f,
821 uint64_t n = 0, ap = 0, q, i, a, hidx;
830 i = hidx = le64toh(*idx);
833 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
837 n = journal_file_entry_array_n_items(o);
839 o->entry_array.items[i] = htole64(p);
840 *idx = htole64(hidx + 1);
846 a = le64toh(o->entry_array.next_entry_array_offset);
857 r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
858 offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
863 o->entry_array.items[i] = htole64(p);
868 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
872 o->entry_array.next_entry_array_offset = htole64(q);
875 *idx = htole64(hidx + 1);
880 static int link_entry_into_array_plus_one(JournalFile *f,
899 i = htole64(le64toh(*idx) - 1);
900 r = link_entry_into_array(f, first, &i, p);
905 *idx = htole64(le64toh(*idx) + 1);
909 static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
916 p = le64toh(o->entry.items[i].object_offset);
920 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
924 return link_entry_into_array_plus_one(f,
925 &o->data.entry_offset,
926 &o->data.entry_array_offset,
931 static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
938 assert(o->object.type == OBJECT_ENTRY);
940 __sync_synchronize();
942 /* Link up the entry itself */
943 r = link_entry_into_array(f,
944 &f->header->entry_array_offset,
945 &f->header->n_entries,
950 /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */
952 if (f->header->head_entry_realtime == 0)
953 f->header->head_entry_realtime = o->entry.realtime;
955 f->header->tail_entry_realtime = o->entry.realtime;
956 f->header->tail_entry_monotonic = o->entry.monotonic;
958 f->tail_entry_monotonic_valid = true;
960 /* Link up the items */
961 n = journal_file_entry_n_items(o);
962 for (i = 0; i < n; i++) {
963 r = journal_file_link_entry_item(f, o, offset, i);
971 static int journal_file_append_entry_internal(
973 const dual_timestamp *ts,
975 const EntryItem items[], unsigned n_items,
977 Object **ret, uint64_t *offset) {
984 assert(items || n_items == 0);
987 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
989 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
993 o->entry.seqnum = htole64(journal_file_seqnum(f, seqnum));
994 memcpy(o->entry.items, items, n_items * sizeof(EntryItem));
995 o->entry.realtime = htole64(ts->realtime);
996 o->entry.monotonic = htole64(ts->monotonic);
997 o->entry.xor_hash = htole64(xor_hash);
998 o->entry.boot_id = f->header->boot_id;
1000 r = journal_file_link_entry(f, o, np);
1013 void journal_file_post_change(JournalFile *f) {
1016 /* inotify() does not receive IN_MODIFY events from file
1017 * accesses done via mmap(). After each access we hence
1018 * trigger IN_MODIFY by truncating the journal file to its
1019 * current size which triggers IN_MODIFY. */
1021 __sync_synchronize();
1023 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
1024 log_error("Failed to to truncate file to its own size: %m");
1027 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) {
1031 uint64_t xor_hash = 0;
1032 struct dual_timestamp _ts;
1035 assert(iovec || n_iovec == 0);
1041 dual_timestamp_get(&_ts);
1045 if (f->tail_entry_monotonic_valid &&
1046 ts->monotonic < le64toh(f->header->tail_entry_monotonic))
1049 items = alloca(sizeof(EntryItem) * n_iovec);
1051 for (i = 0; i < n_iovec; i++) {
1055 r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
1059 xor_hash ^= le64toh(o->data.hash);
1060 items[i].object_offset = htole64(p);
1061 items[i].hash = o->data.hash;
1064 r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
1066 journal_file_post_change(f);
1071 static int generic_array_get(JournalFile *f,
1074 Object **ret, uint64_t *offset) {
1086 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1090 n = journal_file_entry_array_n_items(o);
1092 p = le64toh(o->entry_array.items[i]);
1097 a = le64toh(o->entry_array.next_entry_array_offset);
1100 if (a <= 0 || p <= 0)
1103 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1116 static int generic_array_get_plus_one(JournalFile *f,
1120 Object **ret, uint64_t *offset) {
1129 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1142 return generic_array_get(f, first, i-1, ret, offset);
1151 static int generic_array_bisect(JournalFile *f,
1155 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1156 direction_t direction,
1161 uint64_t a, p, t = 0, i = 0, last_p = 0;
1162 bool subtract_one = false;
1163 Object *o, *array = NULL;
1167 assert(test_object);
1171 uint64_t left, right, k, lp;
1173 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
1177 k = journal_file_entry_array_n_items(array);
1183 lp = p = le64toh(array->entry_array.items[i]);
1187 r = test_object(f, p, needle);
1191 if (r == TEST_FOUND)
1192 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1194 if (r == TEST_RIGHT) {
1198 if (left == right) {
1199 if (direction == DIRECTION_UP)
1200 subtract_one = true;
1206 assert(left < right);
1208 i = (left + right) / 2;
1209 p = le64toh(array->entry_array.items[i]);
1213 r = test_object(f, p, needle);
1217 if (r == TEST_FOUND)
1218 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1220 if (r == TEST_RIGHT)
1228 if (direction == DIRECTION_UP) {
1230 subtract_one = true;
1241 a = le64toh(array->entry_array.next_entry_array_offset);
1247 if (subtract_one && t == 0 && i == 0)
1250 if (subtract_one && i == 0)
1252 else if (subtract_one)
1253 p = le64toh(array->entry_array.items[i-1]);
1255 p = le64toh(array->entry_array.items[i]);
1257 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1268 *idx = t + i + (subtract_one ? -1 : 0);
1273 static int generic_array_bisect_plus_one(JournalFile *f,
1278 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1279 direction_t direction,
1285 bool step_back = false;
1289 assert(test_object);
1294 /* This bisects the array in object 'first', but first checks
1296 r = test_object(f, extra, needle);
1300 if (r == TEST_FOUND)
1301 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1303 /* if we are looking with DIRECTION_UP then we need to first
1304 see if in the actual array there is a matching entry, and
1305 return the last one of that. But if there isn't any we need
1306 to return this one. Hence remember this, and return it
1309 step_back = direction == DIRECTION_UP;
1311 if (r == TEST_RIGHT) {
1312 if (direction == DIRECTION_DOWN)
1318 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
1320 if (r == 0 && step_back)
1329 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1345 static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
1351 else if (p < needle)
1357 int journal_file_move_to_entry_by_offset(
1360 direction_t direction,
1364 return generic_array_bisect(f,
1365 le64toh(f->header->entry_array_offset),
1366 le64toh(f->header->n_entries),
1374 static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
1381 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1385 if (le64toh(o->entry.seqnum) == needle)
1387 else if (le64toh(o->entry.seqnum) < needle)
1393 int journal_file_move_to_entry_by_seqnum(
1396 direction_t direction,
1400 return generic_array_bisect(f,
1401 le64toh(f->header->entry_array_offset),
1402 le64toh(f->header->n_entries),
1409 static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
1416 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1420 if (le64toh(o->entry.realtime) == needle)
1422 else if (le64toh(o->entry.realtime) < needle)
1428 int journal_file_move_to_entry_by_realtime(
1431 direction_t direction,
1435 return generic_array_bisect(f,
1436 le64toh(f->header->entry_array_offset),
1437 le64toh(f->header->n_entries),
1439 test_object_realtime,
1444 static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
1451 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1455 if (le64toh(o->entry.monotonic) == needle)
1457 else if (le64toh(o->entry.monotonic) < needle)
1463 int journal_file_move_to_entry_by_monotonic(
1467 direction_t direction,
1471 char t[9+32+1] = "_BOOT_ID=";
1477 sd_id128_to_string(boot_id, t + 9);
1478 r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
1484 return generic_array_bisect_plus_one(f,
1485 le64toh(o->data.entry_offset),
1486 le64toh(o->data.entry_array_offset),
1487 le64toh(o->data.n_entries),
1489 test_object_monotonic,
1494 int journal_file_next_entry(
1496 Object *o, uint64_t p,
1497 direction_t direction,
1498 Object **ret, uint64_t *offset) {
1504 assert(p > 0 || !o);
1506 n = le64toh(f->header->n_entries);
1511 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1513 if (o->object.type != OBJECT_ENTRY)
1516 r = generic_array_bisect(f,
1517 le64toh(f->header->entry_array_offset),
1518 le64toh(f->header->n_entries),
1527 if (direction == DIRECTION_DOWN) {
1540 /* And jump to it */
1541 return generic_array_get(f,
1542 le64toh(f->header->entry_array_offset),
1547 int journal_file_skip_entry(
1549 Object *o, uint64_t p,
1551 Object **ret, uint64_t *offset) {
1560 if (o->object.type != OBJECT_ENTRY)
1563 r = generic_array_bisect(f,
1564 le64toh(f->header->entry_array_offset),
1565 le64toh(f->header->n_entries),
1574 /* Calculate new index */
1576 if ((uint64_t) -skip >= i)
1579 i = i - (uint64_t) -skip;
1581 i += (uint64_t) skip;
1583 n = le64toh(f->header->n_entries);
1590 return generic_array_get(f,
1591 le64toh(f->header->entry_array_offset),
1596 int journal_file_next_entry_for_data(
1598 Object *o, uint64_t p,
1599 uint64_t data_offset,
1600 direction_t direction,
1601 Object **ret, uint64_t *offset) {
1608 assert(p > 0 || !o);
1610 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1614 n = le64toh(d->data.n_entries);
1619 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1621 if (o->object.type != OBJECT_ENTRY)
1624 r = generic_array_bisect_plus_one(f,
1625 le64toh(d->data.entry_offset),
1626 le64toh(d->data.entry_array_offset),
1627 le64toh(d->data.n_entries),
1637 if (direction == DIRECTION_DOWN) {
1651 return generic_array_get_plus_one(f,
1652 le64toh(d->data.entry_offset),
1653 le64toh(d->data.entry_array_offset),
1658 int journal_file_move_to_entry_by_offset_for_data(
1660 uint64_t data_offset,
1662 direction_t direction,
1663 Object **ret, uint64_t *offset) {
1670 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1674 return generic_array_bisect_plus_one(f,
1675 le64toh(d->data.entry_offset),
1676 le64toh(d->data.entry_array_offset),
1677 le64toh(d->data.n_entries),
1684 int journal_file_move_to_entry_by_monotonic_for_data(
1686 uint64_t data_offset,
1689 direction_t direction,
1690 Object **ret, uint64_t *offset) {
1692 char t[9+32+1] = "_BOOT_ID=";
1699 /* First, seek by time */
1700 sd_id128_to_string(boot_id, t + 9);
1701 r = journal_file_find_data_object(f, t, strlen(t), &o, &b);
1707 r = generic_array_bisect_plus_one(f,
1708 le64toh(o->data.entry_offset),
1709 le64toh(o->data.entry_array_offset),
1710 le64toh(o->data.n_entries),
1712 test_object_monotonic,
1718 /* And now, continue seeking until we find an entry that
1719 * exists in both bisection arrays */
1725 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1729 r = generic_array_bisect_plus_one(f,
1730 le64toh(d->data.entry_offset),
1731 le64toh(d->data.entry_array_offset),
1732 le64toh(d->data.n_entries),
1740 r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
1744 r = generic_array_bisect_plus_one(f,
1745 le64toh(o->data.entry_offset),
1746 le64toh(o->data.entry_array_offset),
1747 le64toh(o->data.n_entries),
1771 int journal_file_move_to_entry_by_seqnum_for_data(
1773 uint64_t data_offset,
1775 direction_t direction,
1776 Object **ret, uint64_t *offset) {
1783 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1787 return generic_array_bisect_plus_one(f,
1788 le64toh(d->data.entry_offset),
1789 le64toh(d->data.entry_array_offset),
1790 le64toh(d->data.n_entries),
1797 int journal_file_move_to_entry_by_realtime_for_data(
1799 uint64_t data_offset,
1801 direction_t direction,
1802 Object **ret, uint64_t *offset) {
1809 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1813 return generic_array_bisect_plus_one(f,
1814 le64toh(d->data.entry_offset),
1815 le64toh(d->data.entry_array_offset),
1816 le64toh(d->data.n_entries),
1818 test_object_realtime,
1823 void journal_file_dump(JournalFile *f) {
1830 journal_file_print_header(f);
1832 p = le64toh(f->header->header_size);
1834 r = journal_file_move_to_object(f, -1, p, &o);
1838 switch (o->object.type) {
1841 printf("Type: OBJECT_UNUSED\n");
1845 printf("Type: OBJECT_DATA\n");
1849 printf("Type: OBJECT_ENTRY %llu %llu %llu\n",
1850 (unsigned long long) le64toh(o->entry.seqnum),
1851 (unsigned long long) le64toh(o->entry.monotonic),
1852 (unsigned long long) le64toh(o->entry.realtime));
1855 case OBJECT_FIELD_HASH_TABLE:
1856 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
1859 case OBJECT_DATA_HASH_TABLE:
1860 printf("Type: OBJECT_DATA_HASH_TABLE\n");
1863 case OBJECT_ENTRY_ARRAY:
1864 printf("Type: OBJECT_ENTRY_ARRAY\n");
1867 case OBJECT_SIGNATURE:
1868 printf("Type: OBJECT_SIGNATURE\n");
1872 if (o->object.flags & OBJECT_COMPRESSED)
1873 printf("Flags: COMPRESSED\n");
1875 if (p == le64toh(f->header->tail_object_offset))
1878 p = p + ALIGN64(le64toh(o->object.size));
1883 log_error("File corrupt");
1886 void journal_file_print_header(JournalFile *f) {
1887 char a[33], b[33], c[33];
1888 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX];
1892 printf("File Path: %s\n"
1896 "Sequential Number ID: %s\n"
1897 "Header size: %llu\n"
1898 "Arena size: %llu\n"
1899 "Data Hash Table Size: %llu\n"
1900 "Field Hash Table Size: %llu\n"
1902 "Entry Objects: %llu\n"
1903 "Rotate Suggested: %s\n"
1904 "Head Sequential Number: %llu\n"
1905 "Tail Sequential Number: %llu\n"
1906 "Head Realtime Timestamp: %s\n"
1907 "Tail Realtime Timestamp: %s\n",
1909 sd_id128_to_string(f->header->file_id, a),
1910 sd_id128_to_string(f->header->machine_id, b),
1911 sd_id128_to_string(f->header->boot_id, c),
1912 sd_id128_to_string(f->header->seqnum_id, c),
1913 (unsigned long long) le64toh(f->header->header_size),
1914 (unsigned long long) le64toh(f->header->arena_size),
1915 (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
1916 (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
1917 (unsigned long long) le64toh(f->header->n_objects),
1918 (unsigned long long) le64toh(f->header->n_entries),
1919 yes_no(journal_file_rotate_suggested(f)),
1920 (unsigned long long) le64toh(f->header->head_seqnum),
1921 (unsigned long long) le64toh(f->header->tail_seqnum),
1922 format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
1923 format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)));
1925 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
1926 printf("Data Objects: %llu\n"
1927 "Data Hash Table Fill: %.1f%%\n",
1928 (unsigned long long) le64toh(f->header->n_data),
1929 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
1931 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
1932 printf("Field Objects: %llu\n"
1933 "Field Hash Table Fill: %.1f%%\n",
1934 (unsigned long long) le64toh(f->header->n_fields),
1935 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
1938 int journal_file_open(
1942 JournalFile *template,
1943 JournalFile **ret) {
1947 bool newly_created = false;
1951 if ((flags & O_ACCMODE) != O_RDONLY &&
1952 (flags & O_ACCMODE) != O_RDWR)
1955 if (!endswith(fname, ".journal"))
1958 f = new0(JournalFile, 1);
1965 f->writable = (flags & O_ACCMODE) != O_RDONLY;
1966 f->prot = prot_from_flags(flags);
1969 f->metrics = template->metrics;
1970 f->compress = template->compress;
1973 f->path = strdup(fname);
1979 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
1985 if (fstat(f->fd, &f->last_stat) < 0) {
1990 if (f->last_stat.st_size == 0 && f->writable) {
1991 newly_created = true;
1993 r = journal_file_init_header(f, template);
1997 if (fstat(f->fd, &f->last_stat) < 0) {
2003 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
2008 f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
2009 if (f->header == MAP_FAILED) {
2015 if (!newly_created) {
2016 r = journal_file_verify_header(f);
2022 r = journal_file_refresh_header(f);
2027 if (newly_created) {
2029 r = journal_file_setup_field_hash_table(f);
2033 r = journal_file_setup_data_hash_table(f);
2038 r = journal_file_map_field_hash_table(f);
2042 r = journal_file_map_data_hash_table(f);
2052 journal_file_close(f);
2057 int journal_file_rotate(JournalFile **f) {
2060 JournalFile *old_file, *new_file = NULL;
2068 if (!old_file->writable)
2071 if (!endswith(old_file->path, ".journal"))
2074 l = strlen(old_file->path);
2076 p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
2080 memcpy(p, old_file->path, l - 8);
2082 sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
2083 snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
2084 "-%016llx-%016llx.journal",
2085 (unsigned long long) le64toh((*f)->header->tail_seqnum),
2086 (unsigned long long) le64toh((*f)->header->tail_entry_realtime));
2088 r = rename(old_file->path, p);
2094 old_file->header->state = STATE_ARCHIVED;
2096 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, old_file, &new_file);
2097 journal_file_close(old_file);
2103 int journal_file_open_reliably(
2107 JournalFile *template,
2108 JournalFile **ret) {
2114 r = journal_file_open(fname, flags, mode, template, ret);
2115 if (r != -EBADMSG && /* corrupted */
2116 r != -ENODATA && /* truncated */
2117 r != -EHOSTDOWN && /* other machine */
2118 r != -EPROTONOSUPPORT) /* incompatible feature */
2121 if ((flags & O_ACCMODE) == O_RDONLY)
2124 if (!(flags & O_CREAT))
2127 /* The file is corrupted. Rotate it away and try it again (but only once) */
2130 if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
2132 (unsigned long long) now(CLOCK_REALTIME),
2136 r = rename(fname, p);
2141 log_warning("File %s corrupted, renaming and replacing.", fname);
2143 return journal_file_open(fname, flags, mode, template, ret);
2146 struct vacuum_info {
2151 sd_id128_t seqnum_id;
2157 static int vacuum_compare(const void *_a, const void *_b) {
2158 const struct vacuum_info *a, *b;
2163 if (a->have_seqnum && b->have_seqnum &&
2164 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
2165 if (a->seqnum < b->seqnum)
2167 else if (a->seqnum > b->seqnum)
2173 if (a->realtime < b->realtime)
2175 else if (a->realtime > b->realtime)
2177 else if (a->have_seqnum && b->have_seqnum)
2178 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
2180 return strcmp(a->filename, b->filename);
2183 int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free) {
2186 struct vacuum_info *list = NULL;
2187 unsigned n_list = 0, n_allocated = 0, i;
2195 d = opendir(directory);
2201 struct dirent buf, *de;
2205 unsigned long long seqnum = 0, realtime;
2206 sd_id128_t seqnum_id;
2209 k = readdir_r(d, &buf, &de);
2218 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
2221 if (!S_ISREG(st.st_mode))
2224 q = strlen(de->d_name);
2226 if (endswith(de->d_name, ".journal")) {
2228 /* Vacuum archived files */
2230 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
2233 if (de->d_name[q-8-16-1] != '-' ||
2234 de->d_name[q-8-16-1-16-1] != '-' ||
2235 de->d_name[q-8-16-1-16-1-32-1] != '@')
2238 p = strdup(de->d_name);
2244 de->d_name[q-8-16-1-16-1] = 0;
2245 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
2250 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
2257 } else if (endswith(de->d_name, ".journal~")) {
2258 unsigned long long tmp;
2260 /* Vacuum corrupted files */
2262 if (q < 1 + 16 + 1 + 16 + 8 + 1)
2265 if (de->d_name[q-1-8-16-1] != '-' ||
2266 de->d_name[q-1-8-16-1-16-1] != '@')
2269 p = strdup(de->d_name);
2275 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
2280 have_seqnum = false;
2284 if (n_list >= n_allocated) {
2285 struct vacuum_info *j;
2287 n_allocated = MAX(n_allocated * 2U, 8U);
2288 j = realloc(list, n_allocated * sizeof(struct vacuum_info));
2298 list[n_list].filename = p;
2299 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
2300 list[n_list].seqnum = seqnum;
2301 list[n_list].realtime = realtime;
2302 list[n_list].seqnum_id = seqnum_id;
2303 list[n_list].have_seqnum = have_seqnum;
2305 sum += list[n_list].usage;
2310 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
2312 for(i = 0; i < n_list; i++) {
2315 if (fstatvfs(dirfd(d), &ss) < 0) {
2320 if (sum <= max_use &&
2321 (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free)
2324 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
2325 log_info("Deleted archived journal %s/%s.", directory, list[i].filename);
2326 sum -= list[i].usage;
2327 } else if (errno != ENOENT)
2328 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
2332 for (i = 0; i < n_list; i++)
2333 free(list[i].filename);
2343 int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
2345 uint64_t q, xor_hash = 0;
2358 ts.monotonic = le64toh(o->entry.monotonic);
2359 ts.realtime = le64toh(o->entry.realtime);
2361 if (to->tail_entry_monotonic_valid &&
2362 ts.monotonic < le64toh(to->header->tail_entry_monotonic))
2365 n = journal_file_entry_n_items(o);
2366 items = alloca(sizeof(EntryItem) * n);
2368 for (i = 0; i < n; i++) {
2375 q = le64toh(o->entry.items[i].object_offset);
2376 le_hash = o->entry.items[i].hash;
2378 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
2382 if (le_hash != o->data.hash)
2385 l = le64toh(o->object.size) - offsetof(Object, data.payload);
2388 /* We hit the limit on 32bit machines */
2389 if ((uint64_t) t != l)
2392 if (o->object.flags & OBJECT_COMPRESSED) {
2396 if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
2399 data = from->compress_buffer;
2402 return -EPROTONOSUPPORT;
2405 data = o->data.payload;
2407 r = journal_file_append_data(to, data, l, &u, &h);
2411 xor_hash ^= le64toh(u->data.hash);
2412 items[i].object_offset = htole64(h);
2413 items[i].hash = u->data.hash;
2415 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
2420 return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
2423 void journal_default_metrics(JournalMetrics *m, int fd) {
2424 uint64_t fs_size = 0;
2426 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
2431 if (fstatvfs(fd, &ss) >= 0)
2432 fs_size = ss.f_frsize * ss.f_blocks;
2434 if (m->max_use == (uint64_t) -1) {
2437 m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
2439 if (m->max_use > DEFAULT_MAX_USE_UPPER)
2440 m->max_use = DEFAULT_MAX_USE_UPPER;
2442 if (m->max_use < DEFAULT_MAX_USE_LOWER)
2443 m->max_use = DEFAULT_MAX_USE_LOWER;
2445 m->max_use = DEFAULT_MAX_USE_LOWER;
2447 m->max_use = PAGE_ALIGN(m->max_use);
2449 if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
2450 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
2453 if (m->max_size == (uint64_t) -1) {
2454 m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
2456 if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
2457 m->max_size = DEFAULT_MAX_SIZE_UPPER;
2459 m->max_size = PAGE_ALIGN(m->max_size);
2461 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
2462 m->max_size = JOURNAL_FILE_SIZE_MIN;
2464 if (m->max_size*2 > m->max_use)
2465 m->max_use = m->max_size*2;
2467 if (m->min_size == (uint64_t) -1)
2468 m->min_size = JOURNAL_FILE_SIZE_MIN;
2470 m->min_size = PAGE_ALIGN(m->min_size);
2472 if (m->min_size < JOURNAL_FILE_SIZE_MIN)
2473 m->min_size = JOURNAL_FILE_SIZE_MIN;
2475 if (m->min_size > m->max_size)
2476 m->max_size = m->min_size;
2479 if (m->keep_free == (uint64_t) -1) {
2482 m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
2484 if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
2485 m->keep_free = DEFAULT_KEEP_FREE_UPPER;
2488 m->keep_free = DEFAULT_KEEP_FREE;
2491 log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
2492 format_bytes(a, sizeof(a), m->max_use),
2493 format_bytes(b, sizeof(b), m->max_size),
2494 format_bytes(c, sizeof(c), m->min_size),
2495 format_bytes(d, sizeof(d), m->keep_free));
2498 int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
2503 if (f->header->head_entry_realtime == 0)
2506 *from = le64toh(f->header->head_entry_realtime);
2510 if (f->header->tail_entry_realtime == 0)
2513 *to = le64toh(f->header->tail_entry_realtime);
2519 int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
2520 char t[9+32+1] = "_BOOT_ID=";
2528 sd_id128_to_string(boot_id, t + 9);
2530 r = journal_file_find_data_object(f, t, strlen(t), &o, &p);
2534 if (le64toh(o->data.n_entries) <= 0)
2538 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
2542 *from = le64toh(o->entry.monotonic);
2546 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
2550 r = generic_array_get_plus_one(f,
2551 le64toh(o->data.entry_offset),
2552 le64toh(o->data.entry_array_offset),
2553 le64toh(o->data.n_entries)-1,
2558 *to = le64toh(o->entry.monotonic);
2564 bool journal_file_rotate_suggested(JournalFile *f) {
2567 /* If we gained new header fields we gained new features,
2568 * hence suggest a rotation */
2569 if (le64toh(f->header->header_size) < sizeof(Header))
2572 /* Let's check if the hash tables grew over a certain fill
2573 * level (75%, borrowing this value from Java's hash table
2574 * implementation), and if so suggest a rotation. To calculate
2575 * the fill level we need the n_data field, which only exists
2576 * in newer versions. */
2578 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2579 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL)
2582 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2583 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL)