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*sizeof(HashItem))
36 #define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem))
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 /* We estimate that we need 1 hash table entry per 2K of
513 journal file and we want to make sure we never get beyond
514 75% fill level. Calculate the hash table size for the
515 maximum file size based on these metrics. */
517 s = (f->metrics.max_size * 4 / 2048 / 3) * sizeof(HashItem);
518 if (s < DEFAULT_DATA_HASH_TABLE_SIZE)
519 s = DEFAULT_DATA_HASH_TABLE_SIZE;
521 log_info("Reserving %llu entries in hash table.", (unsigned long long) s);
523 r = journal_file_append_object(f,
524 OBJECT_DATA_HASH_TABLE,
525 offsetof(Object, hash_table.items) + s,
530 memset(o->hash_table.items, 0, s);
532 f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
533 f->header->data_hash_table_size = htole64(s);
538 static int journal_file_setup_field_hash_table(JournalFile *f) {
545 s = DEFAULT_FIELD_HASH_TABLE_SIZE;
546 r = journal_file_append_object(f,
547 OBJECT_FIELD_HASH_TABLE,
548 offsetof(Object, hash_table.items) + s,
553 memset(o->hash_table.items, 0, s);
555 f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
556 f->header->field_hash_table_size = htole64(s);
561 static int journal_file_map_data_hash_table(JournalFile *f) {
568 p = le64toh(f->header->data_hash_table_offset);
569 s = le64toh(f->header->data_hash_table_size);
571 r = journal_file_move_to(f,
572 WINDOW_DATA_HASH_TABLE,
578 f->data_hash_table = t;
582 static int journal_file_map_field_hash_table(JournalFile *f) {
589 p = le64toh(f->header->field_hash_table_offset);
590 s = le64toh(f->header->field_hash_table_size);
592 r = journal_file_move_to(f,
593 WINDOW_FIELD_HASH_TABLE,
599 f->field_hash_table = t;
603 static int journal_file_link_data(JournalFile *f, Object *o, uint64_t offset, uint64_t hash) {
610 assert(o->object.type == OBJECT_DATA);
612 /* This might alter the window we are looking at */
614 o->data.next_hash_offset = o->data.next_field_offset = 0;
615 o->data.entry_offset = o->data.entry_array_offset = 0;
616 o->data.n_entries = 0;
618 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
619 p = le64toh(f->data_hash_table[h].tail_hash_offset);
621 /* Only entry in the hash table is easy */
622 f->data_hash_table[h].head_hash_offset = htole64(offset);
624 /* Move back to the previous data object, to patch in
627 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
631 o->data.next_hash_offset = htole64(offset);
634 f->data_hash_table[h].tail_hash_offset = htole64(offset);
636 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
637 f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
642 int journal_file_find_data_object_with_hash(
644 const void *data, uint64_t size, uint64_t hash,
645 Object **ret, uint64_t *offset) {
647 uint64_t p, osize, h;
651 assert(data || size == 0);
653 osize = offsetof(Object, data.payload) + size;
655 if (f->header->data_hash_table_size == 0)
658 h = hash % (le64toh(f->header->data_hash_table_size) / sizeof(HashItem));
659 p = le64toh(f->data_hash_table[h].head_hash_offset);
664 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
668 if (le64toh(o->data.hash) != hash)
671 if (o->object.flags & OBJECT_COMPRESSED) {
675 l = le64toh(o->object.size);
676 if (l <= offsetof(Object, data.payload))
679 l -= offsetof(Object, data.payload);
681 if (!uncompress_blob(o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize))
685 memcmp(f->compress_buffer, data, size) == 0) {
696 return -EPROTONOSUPPORT;
699 } else if (le64toh(o->object.size) == osize &&
700 memcmp(o->data.payload, data, size) == 0) {
712 p = le64toh(o->data.next_hash_offset);
718 int journal_file_find_data_object(
720 const void *data, uint64_t size,
721 Object **ret, uint64_t *offset) {
726 assert(data || size == 0);
728 hash = hash64(data, size);
730 return journal_file_find_data_object_with_hash(f,
735 static int journal_file_append_data(
737 const void *data, uint64_t size,
738 Object **ret, uint64_t *offset) {
744 bool compressed = false;
747 assert(data || size == 0);
749 hash = hash64(data, size);
751 r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
765 osize = offsetof(Object, data.payload) + size;
766 r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
770 o->data.hash = htole64(hash);
774 size >= COMPRESSION_SIZE_THRESHOLD) {
777 compressed = compress_blob(data, size, o->data.payload, &rsize);
780 o->object.size = htole64(offsetof(Object, data.payload) + rsize);
781 o->object.flags |= OBJECT_COMPRESSED;
783 f->header->incompatible_flags = htole32(le32toh(f->header->incompatible_flags) | HEADER_INCOMPATIBLE_COMPRESSED);
785 log_debug("Compressed data object %lu -> %lu", (unsigned long) size, (unsigned long) rsize);
791 memcpy(o->data.payload, data, size);
793 r = journal_file_link_data(f, o, p, hash);
797 /* The linking might have altered the window, so let's
798 * refresh our pointer */
799 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
812 uint64_t journal_file_entry_n_items(Object *o) {
814 assert(o->object.type == OBJECT_ENTRY);
816 return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
819 static uint64_t journal_file_entry_array_n_items(Object *o) {
821 assert(o->object.type == OBJECT_ENTRY_ARRAY);
823 return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
826 static int link_entry_into_array(JournalFile *f,
831 uint64_t n = 0, ap = 0, q, i, a, hidx;
840 i = hidx = le64toh(*idx);
843 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
847 n = journal_file_entry_array_n_items(o);
849 o->entry_array.items[i] = htole64(p);
850 *idx = htole64(hidx + 1);
856 a = le64toh(o->entry_array.next_entry_array_offset);
867 r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
868 offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
873 o->entry_array.items[i] = htole64(p);
878 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
882 o->entry_array.next_entry_array_offset = htole64(q);
885 *idx = htole64(hidx + 1);
890 static int link_entry_into_array_plus_one(JournalFile *f,
909 i = htole64(le64toh(*idx) - 1);
910 r = link_entry_into_array(f, first, &i, p);
915 *idx = htole64(le64toh(*idx) + 1);
919 static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
926 p = le64toh(o->entry.items[i].object_offset);
930 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
934 return link_entry_into_array_plus_one(f,
935 &o->data.entry_offset,
936 &o->data.entry_array_offset,
941 static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
948 assert(o->object.type == OBJECT_ENTRY);
950 __sync_synchronize();
952 /* Link up the entry itself */
953 r = link_entry_into_array(f,
954 &f->header->entry_array_offset,
955 &f->header->n_entries,
960 /* log_debug("=> %s seqnr=%lu n_entries=%lu", f->path, (unsigned long) o->entry.seqnum, (unsigned long) f->header->n_entries); */
962 if (f->header->head_entry_realtime == 0)
963 f->header->head_entry_realtime = o->entry.realtime;
965 f->header->tail_entry_realtime = o->entry.realtime;
966 f->header->tail_entry_monotonic = o->entry.monotonic;
968 f->tail_entry_monotonic_valid = true;
970 /* Link up the items */
971 n = journal_file_entry_n_items(o);
972 for (i = 0; i < n; i++) {
973 r = journal_file_link_entry_item(f, o, offset, i);
981 static int journal_file_append_entry_internal(
983 const dual_timestamp *ts,
985 const EntryItem items[], unsigned n_items,
987 Object **ret, uint64_t *offset) {
994 assert(items || n_items == 0);
997 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
999 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
1003 o->entry.seqnum = htole64(journal_file_seqnum(f, seqnum));
1004 memcpy(o->entry.items, items, n_items * sizeof(EntryItem));
1005 o->entry.realtime = htole64(ts->realtime);
1006 o->entry.monotonic = htole64(ts->monotonic);
1007 o->entry.xor_hash = htole64(xor_hash);
1008 o->entry.boot_id = f->header->boot_id;
1010 r = journal_file_link_entry(f, o, np);
1023 void journal_file_post_change(JournalFile *f) {
1026 /* inotify() does not receive IN_MODIFY events from file
1027 * accesses done via mmap(). After each access we hence
1028 * trigger IN_MODIFY by truncating the journal file to its
1029 * current size which triggers IN_MODIFY. */
1031 __sync_synchronize();
1033 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
1034 log_error("Failed to to truncate file to its own size: %m");
1037 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) {
1041 uint64_t xor_hash = 0;
1042 struct dual_timestamp _ts;
1045 assert(iovec || n_iovec == 0);
1051 dual_timestamp_get(&_ts);
1055 if (f->tail_entry_monotonic_valid &&
1056 ts->monotonic < le64toh(f->header->tail_entry_monotonic))
1059 items = alloca(sizeof(EntryItem) * n_iovec);
1061 for (i = 0; i < n_iovec; i++) {
1065 r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
1069 xor_hash ^= le64toh(o->data.hash);
1070 items[i].object_offset = htole64(p);
1071 items[i].hash = o->data.hash;
1074 r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
1076 journal_file_post_change(f);
1081 static int generic_array_get(JournalFile *f,
1084 Object **ret, uint64_t *offset) {
1096 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1100 n = journal_file_entry_array_n_items(o);
1102 p = le64toh(o->entry_array.items[i]);
1107 a = le64toh(o->entry_array.next_entry_array_offset);
1110 if (a <= 0 || p <= 0)
1113 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1126 static int generic_array_get_plus_one(JournalFile *f,
1130 Object **ret, uint64_t *offset) {
1139 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1152 return generic_array_get(f, first, i-1, ret, offset);
1161 static int generic_array_bisect(JournalFile *f,
1165 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1166 direction_t direction,
1171 uint64_t a, p, t = 0, i = 0, last_p = 0;
1172 bool subtract_one = false;
1173 Object *o, *array = NULL;
1177 assert(test_object);
1181 uint64_t left, right, k, lp;
1183 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
1187 k = journal_file_entry_array_n_items(array);
1193 lp = p = le64toh(array->entry_array.items[i]);
1197 r = test_object(f, p, needle);
1201 if (r == TEST_FOUND)
1202 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1204 if (r == TEST_RIGHT) {
1208 if (left == right) {
1209 if (direction == DIRECTION_UP)
1210 subtract_one = true;
1216 assert(left < right);
1218 i = (left + right) / 2;
1219 p = le64toh(array->entry_array.items[i]);
1223 r = test_object(f, p, needle);
1227 if (r == TEST_FOUND)
1228 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1230 if (r == TEST_RIGHT)
1238 if (direction == DIRECTION_UP) {
1240 subtract_one = true;
1251 a = le64toh(array->entry_array.next_entry_array_offset);
1257 if (subtract_one && t == 0 && i == 0)
1260 if (subtract_one && i == 0)
1262 else if (subtract_one)
1263 p = le64toh(array->entry_array.items[i-1]);
1265 p = le64toh(array->entry_array.items[i]);
1267 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1278 *idx = t + i + (subtract_one ? -1 : 0);
1283 static int generic_array_bisect_plus_one(JournalFile *f,
1288 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1289 direction_t direction,
1295 bool step_back = false;
1299 assert(test_object);
1304 /* This bisects the array in object 'first', but first checks
1306 r = test_object(f, extra, needle);
1310 if (r == TEST_FOUND)
1311 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1313 /* if we are looking with DIRECTION_UP then we need to first
1314 see if in the actual array there is a matching entry, and
1315 return the last one of that. But if there isn't any we need
1316 to return this one. Hence remember this, and return it
1319 step_back = direction == DIRECTION_UP;
1321 if (r == TEST_RIGHT) {
1322 if (direction == DIRECTION_DOWN)
1328 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
1330 if (r == 0 && step_back)
1339 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1355 static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
1361 else if (p < needle)
1367 int journal_file_move_to_entry_by_offset(
1370 direction_t direction,
1374 return generic_array_bisect(f,
1375 le64toh(f->header->entry_array_offset),
1376 le64toh(f->header->n_entries),
1384 static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
1391 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1395 if (le64toh(o->entry.seqnum) == needle)
1397 else if (le64toh(o->entry.seqnum) < needle)
1403 int journal_file_move_to_entry_by_seqnum(
1406 direction_t direction,
1410 return generic_array_bisect(f,
1411 le64toh(f->header->entry_array_offset),
1412 le64toh(f->header->n_entries),
1419 static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
1426 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1430 if (le64toh(o->entry.realtime) == needle)
1432 else if (le64toh(o->entry.realtime) < needle)
1438 int journal_file_move_to_entry_by_realtime(
1441 direction_t direction,
1445 return generic_array_bisect(f,
1446 le64toh(f->header->entry_array_offset),
1447 le64toh(f->header->n_entries),
1449 test_object_realtime,
1454 static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
1461 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1465 if (le64toh(o->entry.monotonic) == needle)
1467 else if (le64toh(o->entry.monotonic) < needle)
1473 int journal_file_move_to_entry_by_monotonic(
1477 direction_t direction,
1481 char t[9+32+1] = "_BOOT_ID=";
1487 sd_id128_to_string(boot_id, t + 9);
1488 r = journal_file_find_data_object(f, t, strlen(t), &o, NULL);
1494 return generic_array_bisect_plus_one(f,
1495 le64toh(o->data.entry_offset),
1496 le64toh(o->data.entry_array_offset),
1497 le64toh(o->data.n_entries),
1499 test_object_monotonic,
1504 int journal_file_next_entry(
1506 Object *o, uint64_t p,
1507 direction_t direction,
1508 Object **ret, uint64_t *offset) {
1514 assert(p > 0 || !o);
1516 n = le64toh(f->header->n_entries);
1521 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1523 if (o->object.type != OBJECT_ENTRY)
1526 r = generic_array_bisect(f,
1527 le64toh(f->header->entry_array_offset),
1528 le64toh(f->header->n_entries),
1537 if (direction == DIRECTION_DOWN) {
1550 /* And jump to it */
1551 return generic_array_get(f,
1552 le64toh(f->header->entry_array_offset),
1557 int journal_file_skip_entry(
1559 Object *o, uint64_t p,
1561 Object **ret, uint64_t *offset) {
1570 if (o->object.type != OBJECT_ENTRY)
1573 r = generic_array_bisect(f,
1574 le64toh(f->header->entry_array_offset),
1575 le64toh(f->header->n_entries),
1584 /* Calculate new index */
1586 if ((uint64_t) -skip >= i)
1589 i = i - (uint64_t) -skip;
1591 i += (uint64_t) skip;
1593 n = le64toh(f->header->n_entries);
1600 return generic_array_get(f,
1601 le64toh(f->header->entry_array_offset),
1606 int journal_file_next_entry_for_data(
1608 Object *o, uint64_t p,
1609 uint64_t data_offset,
1610 direction_t direction,
1611 Object **ret, uint64_t *offset) {
1618 assert(p > 0 || !o);
1620 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1624 n = le64toh(d->data.n_entries);
1629 i = direction == DIRECTION_DOWN ? 0 : n - 1;
1631 if (o->object.type != OBJECT_ENTRY)
1634 r = generic_array_bisect_plus_one(f,
1635 le64toh(d->data.entry_offset),
1636 le64toh(d->data.entry_array_offset),
1637 le64toh(d->data.n_entries),
1647 if (direction == DIRECTION_DOWN) {
1661 return generic_array_get_plus_one(f,
1662 le64toh(d->data.entry_offset),
1663 le64toh(d->data.entry_array_offset),
1668 int journal_file_move_to_entry_by_offset_for_data(
1670 uint64_t data_offset,
1672 direction_t direction,
1673 Object **ret, uint64_t *offset) {
1680 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1684 return generic_array_bisect_plus_one(f,
1685 le64toh(d->data.entry_offset),
1686 le64toh(d->data.entry_array_offset),
1687 le64toh(d->data.n_entries),
1694 int journal_file_move_to_entry_by_monotonic_for_data(
1696 uint64_t data_offset,
1699 direction_t direction,
1700 Object **ret, uint64_t *offset) {
1702 char t[9+32+1] = "_BOOT_ID=";
1709 /* First, seek by time */
1710 sd_id128_to_string(boot_id, t + 9);
1711 r = journal_file_find_data_object(f, t, strlen(t), &o, &b);
1717 r = generic_array_bisect_plus_one(f,
1718 le64toh(o->data.entry_offset),
1719 le64toh(o->data.entry_array_offset),
1720 le64toh(o->data.n_entries),
1722 test_object_monotonic,
1728 /* And now, continue seeking until we find an entry that
1729 * exists in both bisection arrays */
1735 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1739 r = generic_array_bisect_plus_one(f,
1740 le64toh(d->data.entry_offset),
1741 le64toh(d->data.entry_array_offset),
1742 le64toh(d->data.n_entries),
1750 r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
1754 r = generic_array_bisect_plus_one(f,
1755 le64toh(o->data.entry_offset),
1756 le64toh(o->data.entry_array_offset),
1757 le64toh(o->data.n_entries),
1781 int journal_file_move_to_entry_by_seqnum_for_data(
1783 uint64_t data_offset,
1785 direction_t direction,
1786 Object **ret, uint64_t *offset) {
1793 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1797 return generic_array_bisect_plus_one(f,
1798 le64toh(d->data.entry_offset),
1799 le64toh(d->data.entry_array_offset),
1800 le64toh(d->data.n_entries),
1807 int journal_file_move_to_entry_by_realtime_for_data(
1809 uint64_t data_offset,
1811 direction_t direction,
1812 Object **ret, uint64_t *offset) {
1819 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
1823 return generic_array_bisect_plus_one(f,
1824 le64toh(d->data.entry_offset),
1825 le64toh(d->data.entry_array_offset),
1826 le64toh(d->data.n_entries),
1828 test_object_realtime,
1833 void journal_file_dump(JournalFile *f) {
1840 journal_file_print_header(f);
1842 p = le64toh(f->header->header_size);
1844 r = journal_file_move_to_object(f, -1, p, &o);
1848 switch (o->object.type) {
1851 printf("Type: OBJECT_UNUSED\n");
1855 printf("Type: OBJECT_DATA\n");
1859 printf("Type: OBJECT_ENTRY %llu %llu %llu\n",
1860 (unsigned long long) le64toh(o->entry.seqnum),
1861 (unsigned long long) le64toh(o->entry.monotonic),
1862 (unsigned long long) le64toh(o->entry.realtime));
1865 case OBJECT_FIELD_HASH_TABLE:
1866 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
1869 case OBJECT_DATA_HASH_TABLE:
1870 printf("Type: OBJECT_DATA_HASH_TABLE\n");
1873 case OBJECT_ENTRY_ARRAY:
1874 printf("Type: OBJECT_ENTRY_ARRAY\n");
1877 case OBJECT_SIGNATURE:
1878 printf("Type: OBJECT_SIGNATURE\n");
1882 if (o->object.flags & OBJECT_COMPRESSED)
1883 printf("Flags: COMPRESSED\n");
1885 if (p == le64toh(f->header->tail_object_offset))
1888 p = p + ALIGN64(le64toh(o->object.size));
1893 log_error("File corrupt");
1896 void journal_file_print_header(JournalFile *f) {
1897 char a[33], b[33], c[33];
1898 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX];
1902 printf("File Path: %s\n"
1906 "Sequential Number ID: %s\n"
1907 "Header size: %llu\n"
1908 "Arena size: %llu\n"
1909 "Data Hash Table Size: %llu\n"
1910 "Field Hash Table Size: %llu\n"
1912 "Entry Objects: %llu\n"
1913 "Rotate Suggested: %s\n"
1914 "Head Sequential Number: %llu\n"
1915 "Tail Sequential Number: %llu\n"
1916 "Head Realtime Timestamp: %s\n"
1917 "Tail Realtime Timestamp: %s\n",
1919 sd_id128_to_string(f->header->file_id, a),
1920 sd_id128_to_string(f->header->machine_id, b),
1921 sd_id128_to_string(f->header->boot_id, c),
1922 sd_id128_to_string(f->header->seqnum_id, c),
1923 (unsigned long long) le64toh(f->header->header_size),
1924 (unsigned long long) le64toh(f->header->arena_size),
1925 (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
1926 (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
1927 (unsigned long long) le64toh(f->header->n_objects),
1928 (unsigned long long) le64toh(f->header->n_entries),
1929 yes_no(journal_file_rotate_suggested(f)),
1930 (unsigned long long) le64toh(f->header->head_seqnum),
1931 (unsigned long long) le64toh(f->header->tail_seqnum),
1932 format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
1933 format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)));
1935 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
1936 printf("Data Objects: %llu\n"
1937 "Data Hash Table Fill: %.1f%%\n",
1938 (unsigned long long) le64toh(f->header->n_data),
1939 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
1941 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
1942 printf("Field Objects: %llu\n"
1943 "Field Hash Table Fill: %.1f%%\n",
1944 (unsigned long long) le64toh(f->header->n_fields),
1945 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
1948 int journal_file_open(
1952 JournalMetrics *metrics,
1953 JournalFile *template,
1954 JournalFile **ret) {
1958 bool newly_created = false;
1962 if ((flags & O_ACCMODE) != O_RDONLY &&
1963 (flags & O_ACCMODE) != O_RDWR)
1966 if (!endswith(fname, ".journal"))
1969 f = new0(JournalFile, 1);
1976 f->writable = (flags & O_ACCMODE) != O_RDONLY;
1977 f->prot = prot_from_flags(flags);
1980 f->compress = template->compress;
1982 f->path = strdup(fname);
1988 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
1994 if (fstat(f->fd, &f->last_stat) < 0) {
1999 if (f->last_stat.st_size == 0 && f->writable) {
2000 newly_created = true;
2002 r = journal_file_init_header(f, template);
2006 if (fstat(f->fd, &f->last_stat) < 0) {
2012 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
2017 f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
2018 if (f->header == MAP_FAILED) {
2024 if (!newly_created) {
2025 r = journal_file_verify_header(f);
2032 journal_default_metrics(metrics, f->fd);
2033 f->metrics = *metrics;
2034 } else if (template)
2035 f->metrics = template->metrics;
2037 r = journal_file_refresh_header(f);
2042 if (newly_created) {
2044 r = journal_file_setup_field_hash_table(f);
2048 r = journal_file_setup_data_hash_table(f);
2053 r = journal_file_map_field_hash_table(f);
2057 r = journal_file_map_data_hash_table(f);
2067 journal_file_close(f);
2072 int journal_file_rotate(JournalFile **f) {
2075 JournalFile *old_file, *new_file = NULL;
2083 if (!old_file->writable)
2086 if (!endswith(old_file->path, ".journal"))
2089 l = strlen(old_file->path);
2091 p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
2095 memcpy(p, old_file->path, l - 8);
2097 sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
2098 snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
2099 "-%016llx-%016llx.journal",
2100 (unsigned long long) le64toh((*f)->header->tail_seqnum),
2101 (unsigned long long) le64toh((*f)->header->tail_entry_realtime));
2103 r = rename(old_file->path, p);
2109 old_file->header->state = STATE_ARCHIVED;
2111 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, NULL, old_file, &new_file);
2112 journal_file_close(old_file);
2118 int journal_file_open_reliably(
2122 JournalMetrics *metrics,
2123 JournalFile *template,
2124 JournalFile **ret) {
2130 r = journal_file_open(fname, flags, mode, metrics, template, ret);
2131 if (r != -EBADMSG && /* corrupted */
2132 r != -ENODATA && /* truncated */
2133 r != -EHOSTDOWN && /* other machine */
2134 r != -EPROTONOSUPPORT) /* incompatible feature */
2137 if ((flags & O_ACCMODE) == O_RDONLY)
2140 if (!(flags & O_CREAT))
2143 /* The file is corrupted. Rotate it away and try it again (but only once) */
2146 if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
2148 (unsigned long long) now(CLOCK_REALTIME),
2152 r = rename(fname, p);
2157 log_warning("File %s corrupted, renaming and replacing.", fname);
2159 return journal_file_open(fname, flags, mode, metrics, template, ret);
2162 struct vacuum_info {
2167 sd_id128_t seqnum_id;
2173 static int vacuum_compare(const void *_a, const void *_b) {
2174 const struct vacuum_info *a, *b;
2179 if (a->have_seqnum && b->have_seqnum &&
2180 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
2181 if (a->seqnum < b->seqnum)
2183 else if (a->seqnum > b->seqnum)
2189 if (a->realtime < b->realtime)
2191 else if (a->realtime > b->realtime)
2193 else if (a->have_seqnum && b->have_seqnum)
2194 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
2196 return strcmp(a->filename, b->filename);
2199 int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free) {
2202 struct vacuum_info *list = NULL;
2203 unsigned n_list = 0, n_allocated = 0, i;
2211 d = opendir(directory);
2217 struct dirent buf, *de;
2221 unsigned long long seqnum = 0, realtime;
2222 sd_id128_t seqnum_id;
2225 k = readdir_r(d, &buf, &de);
2234 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
2237 if (!S_ISREG(st.st_mode))
2240 q = strlen(de->d_name);
2242 if (endswith(de->d_name, ".journal")) {
2244 /* Vacuum archived files */
2246 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
2249 if (de->d_name[q-8-16-1] != '-' ||
2250 de->d_name[q-8-16-1-16-1] != '-' ||
2251 de->d_name[q-8-16-1-16-1-32-1] != '@')
2254 p = strdup(de->d_name);
2260 de->d_name[q-8-16-1-16-1] = 0;
2261 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
2266 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
2273 } else if (endswith(de->d_name, ".journal~")) {
2274 unsigned long long tmp;
2276 /* Vacuum corrupted files */
2278 if (q < 1 + 16 + 1 + 16 + 8 + 1)
2281 if (de->d_name[q-1-8-16-1] != '-' ||
2282 de->d_name[q-1-8-16-1-16-1] != '@')
2285 p = strdup(de->d_name);
2291 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
2296 have_seqnum = false;
2300 if (n_list >= n_allocated) {
2301 struct vacuum_info *j;
2303 n_allocated = MAX(n_allocated * 2U, 8U);
2304 j = realloc(list, n_allocated * sizeof(struct vacuum_info));
2314 list[n_list].filename = p;
2315 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
2316 list[n_list].seqnum = seqnum;
2317 list[n_list].realtime = realtime;
2318 list[n_list].seqnum_id = seqnum_id;
2319 list[n_list].have_seqnum = have_seqnum;
2321 sum += list[n_list].usage;
2326 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
2328 for(i = 0; i < n_list; i++) {
2331 if (fstatvfs(dirfd(d), &ss) < 0) {
2336 if (sum <= max_use &&
2337 (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free)
2340 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
2341 log_info("Deleted archived journal %s/%s.", directory, list[i].filename);
2342 sum -= list[i].usage;
2343 } else if (errno != ENOENT)
2344 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
2348 for (i = 0; i < n_list; i++)
2349 free(list[i].filename);
2359 int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
2361 uint64_t q, xor_hash = 0;
2374 ts.monotonic = le64toh(o->entry.monotonic);
2375 ts.realtime = le64toh(o->entry.realtime);
2377 if (to->tail_entry_monotonic_valid &&
2378 ts.monotonic < le64toh(to->header->tail_entry_monotonic))
2381 n = journal_file_entry_n_items(o);
2382 items = alloca(sizeof(EntryItem) * n);
2384 for (i = 0; i < n; i++) {
2391 q = le64toh(o->entry.items[i].object_offset);
2392 le_hash = o->entry.items[i].hash;
2394 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
2398 if (le_hash != o->data.hash)
2401 l = le64toh(o->object.size) - offsetof(Object, data.payload);
2404 /* We hit the limit on 32bit machines */
2405 if ((uint64_t) t != l)
2408 if (o->object.flags & OBJECT_COMPRESSED) {
2412 if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
2415 data = from->compress_buffer;
2418 return -EPROTONOSUPPORT;
2421 data = o->data.payload;
2423 r = journal_file_append_data(to, data, l, &u, &h);
2427 xor_hash ^= le64toh(u->data.hash);
2428 items[i].object_offset = htole64(h);
2429 items[i].hash = u->data.hash;
2431 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
2436 return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
2439 void journal_default_metrics(JournalMetrics *m, int fd) {
2440 uint64_t fs_size = 0;
2442 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
2447 if (fstatvfs(fd, &ss) >= 0)
2448 fs_size = ss.f_frsize * ss.f_blocks;
2450 if (m->max_use == (uint64_t) -1) {
2453 m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
2455 if (m->max_use > DEFAULT_MAX_USE_UPPER)
2456 m->max_use = DEFAULT_MAX_USE_UPPER;
2458 if (m->max_use < DEFAULT_MAX_USE_LOWER)
2459 m->max_use = DEFAULT_MAX_USE_LOWER;
2461 m->max_use = DEFAULT_MAX_USE_LOWER;
2463 m->max_use = PAGE_ALIGN(m->max_use);
2465 if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
2466 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
2469 if (m->max_size == (uint64_t) -1) {
2470 m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
2472 if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
2473 m->max_size = DEFAULT_MAX_SIZE_UPPER;
2475 m->max_size = PAGE_ALIGN(m->max_size);
2477 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
2478 m->max_size = JOURNAL_FILE_SIZE_MIN;
2480 if (m->max_size*2 > m->max_use)
2481 m->max_use = m->max_size*2;
2483 if (m->min_size == (uint64_t) -1)
2484 m->min_size = JOURNAL_FILE_SIZE_MIN;
2486 m->min_size = PAGE_ALIGN(m->min_size);
2488 if (m->min_size < JOURNAL_FILE_SIZE_MIN)
2489 m->min_size = JOURNAL_FILE_SIZE_MIN;
2491 if (m->min_size > m->max_size)
2492 m->max_size = m->min_size;
2495 if (m->keep_free == (uint64_t) -1) {
2498 m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
2500 if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
2501 m->keep_free = DEFAULT_KEEP_FREE_UPPER;
2504 m->keep_free = DEFAULT_KEEP_FREE;
2507 log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
2508 format_bytes(a, sizeof(a), m->max_use),
2509 format_bytes(b, sizeof(b), m->max_size),
2510 format_bytes(c, sizeof(c), m->min_size),
2511 format_bytes(d, sizeof(d), m->keep_free));
2514 int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
2519 if (f->header->head_entry_realtime == 0)
2522 *from = le64toh(f->header->head_entry_realtime);
2526 if (f->header->tail_entry_realtime == 0)
2529 *to = le64toh(f->header->tail_entry_realtime);
2535 int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
2536 char t[9+32+1] = "_BOOT_ID=";
2544 sd_id128_to_string(boot_id, t + 9);
2546 r = journal_file_find_data_object(f, t, strlen(t), &o, &p);
2550 if (le64toh(o->data.n_entries) <= 0)
2554 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
2558 *from = le64toh(o->entry.monotonic);
2562 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
2566 r = generic_array_get_plus_one(f,
2567 le64toh(o->data.entry_offset),
2568 le64toh(o->data.entry_array_offset),
2569 le64toh(o->data.n_entries)-1,
2574 *to = le64toh(o->entry.monotonic);
2580 bool journal_file_rotate_suggested(JournalFile *f) {
2583 /* If we gained new header fields we gained new features,
2584 * hence suggest a rotation */
2585 if (le64toh(f->header->header_size) < sizeof(Header))
2588 /* Let's check if the hash tables grew over a certain fill
2589 * level (75%, borrowing this value from Java's hash table
2590 * implementation), and if so suggest a rotation. To calculate
2591 * the fill level we need the n_data field, which only exists
2592 * in newer versions. */
2594 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2595 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL)
2598 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2599 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL)