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. Ignoring.", f->path);
193 /* FIXME: immediately rotate */
194 else if (state == STATE_ARCHIVED)
196 else if (state != STATE_OFFLINE)
197 log_debug("Journal file %s has unknown state %u. Ignoring.", f->path, state);
203 static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
204 uint64_t old_size, new_size;
209 /* We assume that this file is not sparse, and we know that
210 * for sure, since we always call posix_fallocate()
214 le64toh(f->header->header_size) +
215 le64toh(f->header->arena_size);
217 new_size = PAGE_ALIGN(offset + size);
218 if (new_size < le64toh(f->header->header_size))
219 new_size = le64toh(f->header->header_size);
221 if (new_size <= old_size)
224 if (f->metrics.max_size > 0 &&
225 new_size > f->metrics.max_size)
228 if (new_size > f->metrics.min_size &&
229 f->metrics.keep_free > 0) {
232 if (fstatvfs(f->fd, &svfs) >= 0) {
235 available = svfs.f_bfree * svfs.f_bsize;
237 if (available >= f->metrics.keep_free)
238 available -= f->metrics.keep_free;
242 if (new_size - old_size > available)
247 /* Note that the glibc fallocate() fallback is very
248 inefficient, hence we try to minimize the allocation area
250 r = posix_fallocate(f->fd, old_size, new_size - old_size);
254 if (fstat(f->fd, &f->last_stat) < 0)
257 f->header->arena_size = htole64(new_size - le64toh(f->header->header_size));
262 static int journal_file_map(
271 uint64_t woffset, wsize;
278 woffset = offset & ~((uint64_t) page_size() - 1ULL);
279 wsize = size + (offset - woffset);
280 wsize = PAGE_ALIGN(wsize);
282 /* Avoid SIGBUS on invalid accesses */
283 if (woffset + wsize > (uint64_t) PAGE_ALIGN(f->last_stat.st_size))
284 return -EADDRNOTAVAIL;
286 window = mmap(NULL, wsize, f->prot, MAP_SHARED, f->fd, woffset);
287 if (window == MAP_FAILED)
299 *ret = (uint8_t*) window + (offset - woffset);
304 static int journal_file_move_to(JournalFile *f, int wt, uint64_t offset, uint64_t size, void **ret) {
313 assert(wt < _WINDOW_MAX);
315 if (offset + size > (uint64_t) f->last_stat.st_size) {
316 /* Hmm, out of range? Let's refresh the fstat() data
317 * first, before we trust that check. */
319 if (fstat(f->fd, &f->last_stat) < 0 ||
320 offset + size > (uint64_t) f->last_stat.st_size)
321 return -EADDRNOTAVAIL;
326 if (_likely_(w->ptr &&
327 w->offset <= offset &&
328 w->offset + w->size >= offset + size)) {
330 *ret = (uint8_t*) w->ptr + (offset - w->offset);
335 if (munmap(w->ptr, w->size) < 0)
339 w->size = w->offset = 0;
342 if (size < DEFAULT_WINDOW_SIZE) {
343 /* If the default window size is larger then what was
344 * asked for extend the mapping a bit in the hope to
345 * minimize needed remappings later on. We add half
346 * the window space before and half behind the
347 * requested mapping */
349 delta = (DEFAULT_WINDOW_SIZE - size) / 2;
355 size = DEFAULT_WINDOW_SIZE;
359 if (offset + size > (uint64_t) f->last_stat.st_size)
360 size = (uint64_t) f->last_stat.st_size - offset;
363 return -EADDRNOTAVAIL;
365 r = journal_file_map(f,
367 &w->ptr, &w->offset, &w->size,
373 *ret = (uint8_t*) p + delta;
377 static bool verify_hash(Object *o) {
382 if (o->object.type == OBJECT_DATA && !(o->object.flags & OBJECT_COMPRESSED)) {
383 h1 = le64toh(o->data.hash);
384 h2 = hash64(o->data.payload, le64toh(o->object.size) - offsetof(Object, data.payload));
385 } else if (o->object.type == OBJECT_FIELD) {
386 h1 = le64toh(o->field.hash);
387 h2 = hash64(o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload));
394 int journal_file_move_to_object(JournalFile *f, int type, uint64_t offset, Object **ret) {
402 assert(type < _OBJECT_TYPE_MAX);
404 r = journal_file_move_to(f, type >= 0 ? type : WINDOW_UNKNOWN, offset, sizeof(ObjectHeader), &t);
409 s = le64toh(o->object.size);
411 if (s < sizeof(ObjectHeader))
414 if (type >= 0 && o->object.type != type)
417 if (s > sizeof(ObjectHeader)) {
418 r = journal_file_move_to(f, o->object.type, offset, s, &t);
432 static uint64_t journal_file_seqnum(JournalFile *f, uint64_t *seqnum) {
437 r = le64toh(f->header->tail_seqnum) + 1;
440 /* If an external seqnum counter was passed, we update
441 * both the local and the external one, and set it to
442 * the maximum of both */
450 f->header->tail_seqnum = htole64(r);
452 if (f->header->head_seqnum == 0)
453 f->header->head_seqnum = htole64(r);
458 static int journal_file_append_object(JournalFile *f, int type, uint64_t size, Object **ret, uint64_t *offset) {
465 assert(size >= sizeof(ObjectHeader));
469 p = le64toh(f->header->tail_object_offset);
471 p = le64toh(f->header->header_size);
473 r = journal_file_move_to_object(f, -1, p, &tail);
477 p += ALIGN64(le64toh(tail->object.size));
480 r = journal_file_allocate(f, p, size);
484 r = journal_file_move_to(f, type, p, size, &t);
491 o->object.type = type;
492 o->object.size = htole64(size);
494 f->header->tail_object_offset = htole64(p);
495 f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1);
503 static int journal_file_setup_data_hash_table(JournalFile *f) {
510 s = DEFAULT_DATA_HASH_TABLE_SIZE;
511 r = journal_file_append_object(f,
512 OBJECT_DATA_HASH_TABLE,
513 offsetof(Object, hash_table.items) + s,
518 memset(o->hash_table.items, 0, s);
520 f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
521 f->header->data_hash_table_size = htole64(s);
526 static int journal_file_setup_field_hash_table(JournalFile *f) {
533 s = DEFAULT_FIELD_HASH_TABLE_SIZE;
534 r = journal_file_append_object(f,
535 OBJECT_FIELD_HASH_TABLE,
536 offsetof(Object, hash_table.items) + s,
541 memset(o->hash_table.items, 0, s);
543 f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
544 f->header->field_hash_table_size = htole64(s);
549 static int journal_file_map_data_hash_table(JournalFile *f) {
556 p = le64toh(f->header->data_hash_table_offset);
557 s = le64toh(f->header->data_hash_table_size);
559 r = journal_file_move_to(f,
560 WINDOW_DATA_HASH_TABLE,
566 f->data_hash_table = t;
570 static int journal_file_map_field_hash_table(JournalFile *f) {
577 p = le64toh(f->header->field_hash_table_offset);
578 s = le64toh(f->header->field_hash_table_size);
580 r = journal_file_move_to(f,
581 WINDOW_FIELD_HASH_TABLE,
587 f->field_hash_table = t;
591 static int journal_file_link_data(JournalFile *f, Object *o, uint64_t offset, uint64_t hash) {
598 assert(o->object.type == OBJECT_DATA);
600 /* This might alter the window we are looking at */
602 o->data.next_hash_offset = o->data.next_field_offset = 0;
603 o->data.entry_offset = o->data.entry_array_offset = 0;
604 o->data.n_entries = 0;
606 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
607 p = le64toh(f->data_hash_table[h].tail_hash_offset);
609 /* Only entry in the hash table is easy */
610 f->data_hash_table[h].head_hash_offset = htole64(offset);
612 /* Move back to the previous data object, to patch in
615 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
619 o->data.next_hash_offset = htole64(offset);
622 f->data_hash_table[h].tail_hash_offset = htole64(offset);
624 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
625 f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
630 int journal_file_find_data_object_with_hash(
632 const void *data, uint64_t size, uint64_t hash,
633 Object **ret, uint64_t *offset) {
635 uint64_t p, osize, h;
639 assert(data || size == 0);
641 osize = offsetof(Object, data.payload) + size;
643 if (f->header->data_hash_table_size == 0)
646 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
647 p = le64toh(f->data_hash_table[h].head_hash_offset);
652 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
656 if (le64toh(o->data.hash) != hash)
659 if (o->object.flags & OBJECT_COMPRESSED) {
663 l = le64toh(o->object.size);
664 if (l <= offsetof(Object, data.payload))
667 l -= offsetof(Object, data.payload);
669 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
673 memcmp(f->compress_buffer, data, size) == 0) {
684 return -EPROTONOSUPPORT;
687 } else if (le64toh(o->object.size) == osize &&
688 memcmp(o->data.payload, data, size) == 0) {
700 p = le64toh(o->data.next_hash_offset);
706 int journal_file_find_data_object(
708 const void *data, uint64_t size,
709 Object **ret, uint64_t *offset) {
714 assert(data || size == 0);
716 hash = hash64(data, size);
718 return journal_file_find_data_object_with_hash(f,
723 static int journal_file_append_data(
725 const void *data, uint64_t size,
726 Object **ret, uint64_t *offset) {
732 bool compressed = false;
735 assert(data || size == 0);
737 hash = hash64(data, size);
739 r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
753 osize = offsetof(Object, data.payload) + size;
754 r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
758 o->data.hash = htole64(hash);
762 size >= COMPRESSION_SIZE_THRESHOLD) {
765 compressed = compress_blob(data, size, o->data.payload, &rsize);
768 o->object.size = htole64(offsetof(Object, data.payload) + rsize);
769 o->object.flags |= OBJECT_COMPRESSED;
771 f->header->incompatible_flags = htole32(le32toh(f->header->incompatible_flags) | HEADER_INCOMPATIBLE_COMPRESSED);
773 log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize);
779 memcpy(o->data.payload, data, size);
781 r = journal_file_link_data(f, o, p, hash);
785 /* The linking might have altered the window, so let's
786 * refresh our pointer */
787 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
800 uint64_t journal_file_entry_n_items(Object *o) {
802 assert(o->object.type == OBJECT_ENTRY);
804 return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
807 static uint64_t journal_file_entry_array_n_items(Object *o) {
809 assert(o->object.type == OBJECT_ENTRY_ARRAY);
811 return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
814 static int link_entry_into_array(JournalFile *f,
819 uint64_t n = 0, ap = 0, q, i, a, hidx;
828 i = hidx = le64toh(*idx);
831 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
835 n = journal_file_entry_array_n_items(o);
837 o->entry_array.items[i] = htole64(p);
838 *idx = htole64(hidx + 1);
844 a = le64toh(o->entry_array.next_entry_array_offset);
855 r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
856 offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
861 o->entry_array.items[i] = htole64(p);
866 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
870 o->entry_array.next_entry_array_offset = htole64(q);
873 *idx = htole64(hidx + 1);
878 static int link_entry_into_array_plus_one(JournalFile *f,
897 i = htole64(le64toh(*idx) - 1);
898 r = link_entry_into_array(f, first, &i, p);
903 *idx = htole64(le64toh(*idx) + 1);
907 static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
914 p = le64toh(o->entry.items[i].object_offset);
918 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
922 return link_entry_into_array_plus_one(f,
923 &o->data.entry_offset,
924 &o->data.entry_array_offset,
929 static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
936 assert(o->object.type == OBJECT_ENTRY);
938 __sync_synchronize();
940 /* Link up the entry itself */
941 r = link_entry_into_array(f,
942 &f->header->entry_array_offset,
943 &f->header->n_entries,
948 /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */
950 if (f->header->head_entry_realtime == 0)
951 f->header->head_entry_realtime = o->entry.realtime;
953 f->header->tail_entry_realtime = o->entry.realtime;
954 f->header->tail_entry_monotonic = o->entry.monotonic;
956 f->tail_entry_monotonic_valid = true;
958 /* Link up the items */
959 n = journal_file_entry_n_items(o);
960 for (i = 0; i < n; i++) {
961 r = journal_file_link_entry_item(f, o, offset, i);
969 static int journal_file_append_entry_internal(
971 const dual_timestamp *ts,
973 const EntryItem items[], unsigned n_items,
975 Object **ret, uint64_t *offset) {
982 assert(items || n_items == 0);
985 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
987 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
991 o->entry.seqnum = htole64(journal_file_seqnum(f, seqnum));
992 memcpy(o->entry.items, items, n_items * sizeof(EntryItem));
993 o->entry.realtime = htole64(ts->realtime);
994 o->entry.monotonic = htole64(ts->monotonic);
995 o->entry.xor_hash = htole64(xor_hash);
996 o->entry.boot_id = f->header->boot_id;
998 r = journal_file_link_entry(f, o, np);
1011 void journal_file_post_change(JournalFile *f) {
1014 /* inotify() does not receive IN_MODIFY events from file
1015 * accesses done via mmap(). After each access we hence
1016 * trigger IN_MODIFY by truncating the journal file to its
1017 * current size which triggers IN_MODIFY. */
1019 __sync_synchronize();
1021 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
1022 log_error("Failed to to truncate file to its own size: %m");
1025 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) {
1029 uint64_t xor_hash = 0;
1030 struct dual_timestamp _ts;
1033 assert(iovec || n_iovec == 0);
1039 dual_timestamp_get(&_ts);
1043 if (f->tail_entry_monotonic_valid &&
1044 ts->monotonic < le64toh(f->header->tail_entry_monotonic))
1047 items = alloca(sizeof(EntryItem) * n_iovec);
1049 for (i = 0; i < n_iovec; i++) {
1053 r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
1057 xor_hash ^= le64toh(o->data.hash);
1058 items[i].object_offset = htole64(p);
1059 items[i].hash = o->data.hash;
1062 r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
1064 journal_file_post_change(f);
1069 static int generic_array_get(JournalFile *f,
1072 Object **ret, uint64_t *offset) {
1084 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1088 n = journal_file_entry_array_n_items(o);
1090 p = le64toh(o->entry_array.items[i]);
1095 a = le64toh(o->entry_array.next_entry_array_offset);
1098 if (a <= 0 || p <= 0)
1101 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1114 static int generic_array_get_plus_one(JournalFile *f,
1118 Object **ret, uint64_t *offset) {
1127 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1140 return generic_array_get(f, first, i-1, ret, offset);
1149 static int generic_array_bisect(JournalFile *f,
1153 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1154 direction_t direction,
1159 uint64_t a, p, t = 0, i = 0, last_p = 0;
1160 bool subtract_one = false;
1161 Object *o, *array = NULL;
1165 assert(test_object);
1169 uint64_t left, right, k, lp;
1171 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
1175 k = journal_file_entry_array_n_items(array);
1181 lp = p = le64toh(array->entry_array.items[i]);
1185 r = test_object(f, p, needle);
1189 if (r == TEST_FOUND)
1190 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1192 if (r == TEST_RIGHT) {
1196 if (left == right) {
1197 if (direction == DIRECTION_UP)
1198 subtract_one = true;
1204 assert(left < right);
1206 i = (left + right) / 2;
1207 p = le64toh(array->entry_array.items[i]);
1211 r = test_object(f, p, needle);
1215 if (r == TEST_FOUND)
1216 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1218 if (r == TEST_RIGHT)
1226 if (direction == DIRECTION_UP) {
1228 subtract_one = true;
1239 a = le64toh(array->entry_array.next_entry_array_offset);
1245 if (subtract_one && t == 0 && i == 0)
1248 if (subtract_one && i == 0)
1250 else if (subtract_one)
1251 p = le64toh(array->entry_array.items[i-1]);
1253 p = le64toh(array->entry_array.items[i]);
1255 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1266 *idx = t + i + (subtract_one ? -1 : 0);
1271 static int generic_array_bisect_plus_one(JournalFile *f,
1276 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1277 direction_t direction,
1283 bool step_back = false;
1287 assert(test_object);
1292 /* This bisects the array in object 'first', but first checks
1294 r = test_object(f, extra, needle);
1298 if (r == TEST_FOUND)
1299 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1301 /* if we are looking with DIRECTION_UP then we need to first
1302 see if in the actual array there is a matching entry, and
1303 return the last one of that. But if there isn't any we need
1304 to return this one. Hence remember this, and return it
1307 step_back = direction == DIRECTION_UP;
1309 if (r == TEST_RIGHT) {
1310 if (direction == DIRECTION_DOWN)
1316 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
1318 if (r == 0 && step_back)
1327 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1343 static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
1349 else if (p < needle)
1355 int journal_file_move_to_entry_by_offset(
1358 direction_t direction,
1362 return generic_array_bisect(f,
1363 le64toh(f->header->entry_array_offset),
1364 le64toh(f->header->n_entries),
1372 static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
1379 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1383 if (le64toh(o->entry.seqnum) == needle)
1385 else if (le64toh(o->entry.seqnum) < needle)
1391 int journal_file_move_to_entry_by_seqnum(
1394 direction_t direction,
1398 return generic_array_bisect(f,
1399 le64toh(f->header->entry_array_offset),
1400 le64toh(f->header->n_entries),
1407 static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
1414 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1418 if (le64toh(o->entry.realtime) == needle)
1420 else if (le64toh(o->entry.realtime) < needle)
1426 int journal_file_move_to_entry_by_realtime(
1429 direction_t direction,
1433 return generic_array_bisect(f,
1434 le64toh(f->header->entry_array_offset),
1435 le64toh(f->header->n_entries),
1437 test_object_realtime,
1442 static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
1449 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1453 if (le64toh(o->entry.monotonic) == needle)
1455 else if (le64toh(o->entry.monotonic) < needle)
1461 int journal_file_move_to_entry_by_monotonic(
1465 direction_t direction,
1469 char t[9+32+1] = "_BOOT_ID=";
1475 sd_id128_to_string(boot_id, t + 9);
1476 r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
1482 return generic_array_bisect_plus_one(f,
1483 le64toh(o->data.entry_offset),
1484 le64toh(o->data.entry_array_offset),
1485 le64toh(o->data.n_entries),
1487 test_object_monotonic,
1492 int journal_file_next_entry(
1494 Object *o, uint64_t p,
1495 direction_t direction,
1496 Object **ret, uint64_t *offset) {
1502 assert(p > 0 || !o);
1504 n = le64toh(f->header->n_entries);
1509 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1511 if (o->object.type != OBJECT_ENTRY)
1514 r = generic_array_bisect(f,
1515 le64toh(f->header->entry_array_offset),
1516 le64toh(f->header->n_entries),
1525 if (direction == DIRECTION_DOWN) {
1538 /* And jump to it */
1539 return generic_array_get(f,
1540 le64toh(f->header->entry_array_offset),
1545 int journal_file_skip_entry(
1547 Object *o, uint64_t p,
1549 Object **ret, uint64_t *offset) {
1558 if (o->object.type != OBJECT_ENTRY)
1561 r = generic_array_bisect(f,
1562 le64toh(f->header->entry_array_offset),
1563 le64toh(f->header->n_entries),
1572 /* Calculate new index */
1574 if ((uint64_t) -skip >= i)
1577 i = i - (uint64_t) -skip;
1579 i += (uint64_t) skip;
1581 n = le64toh(f->header->n_entries);
1588 return generic_array_get(f,
1589 le64toh(f->header->entry_array_offset),
1594 int journal_file_next_entry_for_data(
1596 Object *o, uint64_t p,
1597 uint64_t data_offset,
1598 direction_t direction,
1599 Object **ret, uint64_t *offset) {
1606 assert(p > 0 || !o);
1608 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1612 n = le64toh(d->data.n_entries);
1617 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1619 if (o->object.type != OBJECT_ENTRY)
1622 r = generic_array_bisect_plus_one(f,
1623 le64toh(d->data.entry_offset),
1624 le64toh(d->data.entry_array_offset),
1625 le64toh(d->data.n_entries),
1635 if (direction == DIRECTION_DOWN) {
1649 return generic_array_get_plus_one(f,
1650 le64toh(d->data.entry_offset),
1651 le64toh(d->data.entry_array_offset),
1656 int journal_file_move_to_entry_by_offset_for_data(
1658 uint64_t data_offset,
1660 direction_t direction,
1661 Object **ret, uint64_t *offset) {
1668 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1672 return generic_array_bisect_plus_one(f,
1673 le64toh(d->data.entry_offset),
1674 le64toh(d->data.entry_array_offset),
1675 le64toh(d->data.n_entries),
1682 int journal_file_move_to_entry_by_monotonic_for_data(
1684 uint64_t data_offset,
1687 direction_t direction,
1688 Object **ret, uint64_t *offset) {
1690 char t[9+32+1] = "_BOOT_ID=";
1697 /* First, seek by time */
1698 sd_id128_to_string(boot_id, t + 9);
1699 r = journal_file_find_data_object(f, t, strlen(t), &o, &b);
1705 r = generic_array_bisect_plus_one(f,
1706 le64toh(o->data.entry_offset),
1707 le64toh(o->data.entry_array_offset),
1708 le64toh(o->data.n_entries),
1710 test_object_monotonic,
1716 /* And now, continue seeking until we find an entry that
1717 * exists in both bisection arrays */
1723 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1727 r = generic_array_bisect_plus_one(f,
1728 le64toh(d->data.entry_offset),
1729 le64toh(d->data.entry_array_offset),
1730 le64toh(d->data.n_entries),
1738 r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
1742 r = generic_array_bisect_plus_one(f,
1743 le64toh(o->data.entry_offset),
1744 le64toh(o->data.entry_array_offset),
1745 le64toh(o->data.n_entries),
1769 int journal_file_move_to_entry_by_seqnum_for_data(
1771 uint64_t data_offset,
1773 direction_t direction,
1774 Object **ret, uint64_t *offset) {
1781 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1785 return generic_array_bisect_plus_one(f,
1786 le64toh(d->data.entry_offset),
1787 le64toh(d->data.entry_array_offset),
1788 le64toh(d->data.n_entries),
1795 int journal_file_move_to_entry_by_realtime_for_data(
1797 uint64_t data_offset,
1799 direction_t direction,
1800 Object **ret, uint64_t *offset) {
1807 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1811 return generic_array_bisect_plus_one(f,
1812 le64toh(d->data.entry_offset),
1813 le64toh(d->data.entry_array_offset),
1814 le64toh(d->data.n_entries),
1816 test_object_realtime,
1821 void journal_file_dump(JournalFile *f) {
1828 journal_file_print_header(f);
1830 p = le64toh(f->header->header_size);
1832 r = journal_file_move_to_object(f, -1, p, &o);
1836 switch (o->object.type) {
1839 printf("Type: OBJECT_UNUSED\n");
1843 printf("Type: OBJECT_DATA\n");
1847 printf("Type: OBJECT_ENTRY %llu %llu %llu\n",
1848 (unsigned long long) le64toh(o->entry.seqnum),
1849 (unsigned long long) le64toh(o->entry.monotonic),
1850 (unsigned long long) le64toh(o->entry.realtime));
1853 case OBJECT_FIELD_HASH_TABLE:
1854 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
1857 case OBJECT_DATA_HASH_TABLE:
1858 printf("Type: OBJECT_DATA_HASH_TABLE\n");
1861 case OBJECT_ENTRY_ARRAY:
1862 printf("Type: OBJECT_ENTRY_ARRAY\n");
1865 case OBJECT_SIGNATURE:
1866 printf("Type: OBJECT_SIGNATURE\n");
1870 if (o->object.flags & OBJECT_COMPRESSED)
1871 printf("Flags: COMPRESSED\n");
1873 if (p == le64toh(f->header->tail_object_offset))
1876 p = p + ALIGN64(le64toh(o->object.size));
1881 log_error("File corrupt");
1884 void journal_file_print_header(JournalFile *f) {
1885 char a[33], b[33], c[33];
1886 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX];
1890 printf("File Path: %s\n"
1894 "Sequential Number ID: %s\n"
1895 "Header size: %llu\n"
1896 "Arena size: %llu\n"
1897 "Data Hash Table Size: %llu\n"
1898 "Field Hash Table Size: %llu\n"
1900 "Entry Objects: %llu\n"
1901 "Rotate Suggested: %s\n"
1902 "Head Sequential Number: %llu\n"
1903 "Tail Sequential Number: %llu\n"
1904 "Head Realtime Timestamp: %s\n"
1905 "Tail Realtime Timestamp: %s\n",
1907 sd_id128_to_string(f->header->file_id, a),
1908 sd_id128_to_string(f->header->machine_id, b),
1909 sd_id128_to_string(f->header->boot_id, c),
1910 sd_id128_to_string(f->header->seqnum_id, c),
1911 (unsigned long long) le64toh(f->header->header_size),
1912 (unsigned long long) le64toh(f->header->arena_size),
1913 (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
1914 (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
1915 (unsigned long long) le64toh(f->header->n_objects),
1916 (unsigned long long) le64toh(f->header->n_entries),
1917 yes_no(journal_file_rotate_suggested(f)),
1918 (unsigned long long) le64toh(f->header->head_seqnum),
1919 (unsigned long long) le64toh(f->header->tail_seqnum),
1920 format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
1921 format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)));
1923 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
1924 printf("Data Objects: %llu\n"
1925 "Data Hash Table Fill: %.1f%%\n",
1926 (unsigned long long) le64toh(f->header->n_data),
1927 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
1929 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
1930 printf("Field Objects: %llu\n"
1931 "Field Hash Table Fill: %.1f%%\n",
1932 (unsigned long long) le64toh(f->header->n_fields),
1933 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
1936 int journal_file_open(
1940 JournalFile *template,
1941 JournalFile **ret) {
1945 bool newly_created = false;
1949 if ((flags & O_ACCMODE) != O_RDONLY &&
1950 (flags & O_ACCMODE) != O_RDWR)
1953 if (!endswith(fname, ".journal"))
1956 f = new0(JournalFile, 1);
1963 f->writable = (flags & O_ACCMODE) != O_RDONLY;
1964 f->prot = prot_from_flags(flags);
1967 f->metrics = template->metrics;
1968 f->compress = template->compress;
1971 f->path = strdup(fname);
1977 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
1983 if (fstat(f->fd, &f->last_stat) < 0) {
1988 if (f->last_stat.st_size == 0 && f->writable) {
1989 newly_created = true;
1991 r = journal_file_init_header(f, template);
1995 if (fstat(f->fd, &f->last_stat) < 0) {
2001 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
2006 f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
2007 if (f->header == MAP_FAILED) {
2013 if (!newly_created) {
2014 r = journal_file_verify_header(f);
2020 r = journal_file_refresh_header(f);
2025 if (newly_created) {
2027 r = journal_file_setup_field_hash_table(f);
2031 r = journal_file_setup_data_hash_table(f);
2036 r = journal_file_map_field_hash_table(f);
2040 r = journal_file_map_data_hash_table(f);
2050 journal_file_close(f);
2055 int journal_file_rotate(JournalFile **f) {
2058 JournalFile *old_file, *new_file = NULL;
2066 if (!old_file->writable)
2069 if (!endswith(old_file->path, ".journal"))
2072 l = strlen(old_file->path);
2074 p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
2078 memcpy(p, old_file->path, l - 8);
2080 sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
2081 snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
2082 "-%016llx-%016llx.journal",
2083 (unsigned long long) le64toh((*f)->header->tail_seqnum),
2084 (unsigned long long) le64toh((*f)->header->tail_entry_realtime));
2086 r = rename(old_file->path, p);
2092 old_file->header->state = STATE_ARCHIVED;
2094 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, old_file, &new_file);
2095 journal_file_close(old_file);
2101 int journal_file_open_reliably(
2105 JournalFile *template,
2106 JournalFile **ret) {
2112 r = journal_file_open(fname, flags, mode, template, ret);
2113 if (r != -EBADMSG && /* corrupted */
2114 r != -ENODATA && /* truncated */
2115 r != -EHOSTDOWN && /* other machine */
2116 r != -EPROTONOSUPPORT) /* incompatible feature */
2119 if ((flags & O_ACCMODE) == O_RDONLY)
2122 if (!(flags & O_CREAT))
2125 /* The file is corrupted. Rotate it away and try it again (but only once) */
2128 if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
2130 (unsigned long long) now(CLOCK_REALTIME),
2134 r = rename(fname, p);
2139 log_warning("File %s corrupted, renaming and replacing.", fname);
2141 return journal_file_open(fname, flags, mode, template, ret);
2144 struct vacuum_info {
2149 sd_id128_t seqnum_id;
2155 static int vacuum_compare(const void *_a, const void *_b) {
2156 const struct vacuum_info *a, *b;
2161 if (a->have_seqnum && b->have_seqnum &&
2162 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
2163 if (a->seqnum < b->seqnum)
2165 else if (a->seqnum > b->seqnum)
2171 if (a->realtime < b->realtime)
2173 else if (a->realtime > b->realtime)
2175 else if (a->have_seqnum && b->have_seqnum)
2176 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
2178 return strcmp(a->filename, b->filename);
2181 int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free) {
2184 struct vacuum_info *list = NULL;
2185 unsigned n_list = 0, n_allocated = 0, i;
2193 d = opendir(directory);
2199 struct dirent buf, *de;
2203 unsigned long long seqnum = 0, realtime;
2204 sd_id128_t seqnum_id;
2207 k = readdir_r(d, &buf, &de);
2216 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
2219 if (!S_ISREG(st.st_mode))
2222 q = strlen(de->d_name);
2224 if (endswith(de->d_name, ".journal")) {
2226 /* Vacuum archived files */
2228 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
2231 if (de->d_name[q-8-16-1] != '-' ||
2232 de->d_name[q-8-16-1-16-1] != '-' ||
2233 de->d_name[q-8-16-1-16-1-32-1] != '@')
2236 p = strdup(de->d_name);
2242 de->d_name[q-8-16-1-16-1] = 0;
2243 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
2248 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
2255 } else if (endswith(de->d_name, ".journal~")) {
2256 unsigned long long tmp;
2258 /* Vacuum corrupted files */
2260 if (q < 1 + 16 + 1 + 16 + 8 + 1)
2263 if (de->d_name[q-1-8-16-1] != '-' ||
2264 de->d_name[q-1-8-16-1-16-1] != '@')
2267 p = strdup(de->d_name);
2273 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
2278 have_seqnum = false;
2282 if (n_list >= n_allocated) {
2283 struct vacuum_info *j;
2285 n_allocated = MAX(n_allocated * 2U, 8U);
2286 j = realloc(list, n_allocated * sizeof(struct vacuum_info));
2296 list[n_list].filename = p;
2297 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
2298 list[n_list].seqnum = seqnum;
2299 list[n_list].realtime = realtime;
2300 list[n_list].seqnum_id = seqnum_id;
2301 list[n_list].have_seqnum = have_seqnum;
2303 sum += list[n_list].usage;
2308 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
2310 for(i = 0; i < n_list; i++) {
2313 if (fstatvfs(dirfd(d), &ss) < 0) {
2318 if (sum <= max_use &&
2319 (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free)
2322 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
2323 log_info("Deleted archived journal %s/%s.", directory, list[i].filename);
2324 sum -= list[i].usage;
2325 } else if (errno != ENOENT)
2326 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
2330 for (i = 0; i < n_list; i++)
2331 free(list[i].filename);
2341 int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
2343 uint64_t q, xor_hash = 0;
2356 ts.monotonic = le64toh(o->entry.monotonic);
2357 ts.realtime = le64toh(o->entry.realtime);
2359 if (to->tail_entry_monotonic_valid &&
2360 ts.monotonic < le64toh(to->header->tail_entry_monotonic))
2363 n = journal_file_entry_n_items(o);
2364 items = alloca(sizeof(EntryItem) * n);
2366 for (i = 0; i < n; i++) {
2373 q = le64toh(o->entry.items[i].object_offset);
2374 le_hash = o->entry.items[i].hash;
2376 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
2380 if (le_hash != o->data.hash)
2383 l = le64toh(o->object.size) - offsetof(Object, data.payload);
2386 /* We hit the limit on 32bit machines */
2387 if ((uint64_t) t != l)
2390 if (o->object.flags & OBJECT_COMPRESSED) {
2394 if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
2397 data = from->compress_buffer;
2400 return -EPROTONOSUPPORT;
2403 data = o->data.payload;
2405 r = journal_file_append_data(to, data, l, &u, &h);
2409 xor_hash ^= le64toh(u->data.hash);
2410 items[i].object_offset = htole64(h);
2411 items[i].hash = u->data.hash;
2413 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
2418 return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
2421 void journal_default_metrics(JournalMetrics *m, int fd) {
2422 uint64_t fs_size = 0;
2424 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
2429 if (fstatvfs(fd, &ss) >= 0)
2430 fs_size = ss.f_frsize * ss.f_blocks;
2432 if (m->max_use == (uint64_t) -1) {
2435 m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
2437 if (m->max_use > DEFAULT_MAX_USE_UPPER)
2438 m->max_use = DEFAULT_MAX_USE_UPPER;
2440 if (m->max_use < DEFAULT_MAX_USE_LOWER)
2441 m->max_use = DEFAULT_MAX_USE_LOWER;
2443 m->max_use = DEFAULT_MAX_USE_LOWER;
2445 m->max_use = PAGE_ALIGN(m->max_use);
2447 if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
2448 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
2451 if (m->max_size == (uint64_t) -1) {
2452 m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
2454 if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
2455 m->max_size = DEFAULT_MAX_SIZE_UPPER;
2457 m->max_size = PAGE_ALIGN(m->max_size);
2459 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
2460 m->max_size = JOURNAL_FILE_SIZE_MIN;
2462 if (m->max_size*2 > m->max_use)
2463 m->max_use = m->max_size*2;
2465 if (m->min_size == (uint64_t) -1)
2466 m->min_size = JOURNAL_FILE_SIZE_MIN;
2468 m->min_size = PAGE_ALIGN(m->min_size);
2470 if (m->min_size < JOURNAL_FILE_SIZE_MIN)
2471 m->min_size = JOURNAL_FILE_SIZE_MIN;
2473 if (m->min_size > m->max_size)
2474 m->max_size = m->min_size;
2477 if (m->keep_free == (uint64_t) -1) {
2480 m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
2482 if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
2483 m->keep_free = DEFAULT_KEEP_FREE_UPPER;
2486 m->keep_free = DEFAULT_KEEP_FREE;
2489 log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
2490 format_bytes(a, sizeof(a), m->max_use),
2491 format_bytes(b, sizeof(b), m->max_size),
2492 format_bytes(c, sizeof(c), m->min_size),
2493 format_bytes(d, sizeof(d), m->keep_free));
2496 int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
2501 if (f->header->head_entry_realtime == 0)
2504 *from = le64toh(f->header->head_entry_realtime);
2508 if (f->header->tail_entry_realtime == 0)
2511 *to = le64toh(f->header->tail_entry_realtime);
2517 int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
2518 char t[9+32+1] = "_BOOT_ID=";
2526 sd_id128_to_string(boot_id, t + 9);
2528 r = journal_file_find_data_object(f, t, strlen(t), &o, &p);
2532 if (le64toh(o->data.n_entries) <= 0)
2536 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
2540 *from = le64toh(o->entry.monotonic);
2544 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
2548 r = generic_array_get_plus_one(f,
2549 le64toh(o->data.entry_offset),
2550 le64toh(o->data.entry_array_offset),
2551 le64toh(o->data.n_entries)-1,
2556 *to = le64toh(o->entry.monotonic);
2562 bool journal_file_rotate_suggested(JournalFile *f) {
2565 /* If we gained new header fields we gained new features,
2566 * hence suggest a rotation */
2567 if (le64toh(f->header->header_size) < sizeof(Header))
2570 /* Let's check if the hash tables grew over a certain fill
2571 * level (75%, borrowing this value from Java's hash table
2572 * implementation), and if so suggest a rotation. To calculate
2573 * the fill level we need the n_data field, which only exists
2574 * in newer versions. */
2576 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2577 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL)
2580 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2581 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL)