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"
1908 "Compatible Flags:%s%s\n"
1909 "Incompatible Flags:%s%s\n"
1910 "Header size: %llu\n"
1911 "Arena size: %llu\n"
1912 "Data Hash Table Size: %llu\n"
1913 "Field Hash Table Size: %llu\n"
1915 "Entry Objects: %llu\n"
1916 "Rotate Suggested: %s\n"
1917 "Head Sequential Number: %llu\n"
1918 "Tail Sequential Number: %llu\n"
1919 "Head Realtime Timestamp: %s\n"
1920 "Tail Realtime Timestamp: %s\n",
1922 sd_id128_to_string(f->header->file_id, a),
1923 sd_id128_to_string(f->header->machine_id, b),
1924 sd_id128_to_string(f->header->boot_id, c),
1925 sd_id128_to_string(f->header->seqnum_id, c),
1926 f->header->state == STATE_OFFLINE ? "offline" :
1927 f->header->state == STATE_ONLINE ? "online" :
1928 f->header->state == STATE_ARCHIVED ? "archived" : "unknown",
1929 (f->header->compatible_flags & HEADER_COMPATIBLE_SIGNED) ? " SIGNED" : "",
1930 (f->header->compatible_flags & ~HEADER_COMPATIBLE_SIGNED) ? " ???" : "",
1931 (f->header->incompatible_flags & HEADER_INCOMPATIBLE_COMPRESSED) ? " COMPRESSED" : "",
1932 (f->header->incompatible_flags & ~HEADER_INCOMPATIBLE_COMPRESSED) ? " ???" : "",
1933 (unsigned long long) le64toh(f->header->header_size),
1934 (unsigned long long) le64toh(f->header->arena_size),
1935 (unsigned long long) le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
1936 (unsigned long long) le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
1937 (unsigned long long) le64toh(f->header->n_objects),
1938 (unsigned long long) le64toh(f->header->n_entries),
1939 yes_no(journal_file_rotate_suggested(f)),
1940 (unsigned long long) le64toh(f->header->head_seqnum),
1941 (unsigned long long) le64toh(f->header->tail_seqnum),
1942 format_timestamp(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
1943 format_timestamp(y, sizeof(y), le64toh(f->header->tail_entry_realtime)));
1945 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
1946 printf("Data Objects: %llu\n"
1947 "Data Hash Table Fill: %.1f%%\n",
1948 (unsigned long long) le64toh(f->header->n_data),
1949 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
1951 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
1952 printf("Field Objects: %llu\n"
1953 "Field Hash Table Fill: %.1f%%\n",
1954 (unsigned long long) le64toh(f->header->n_fields),
1955 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
1958 int journal_file_open(
1962 JournalMetrics *metrics,
1963 JournalFile *template,
1964 JournalFile **ret) {
1968 bool newly_created = false;
1972 if ((flags & O_ACCMODE) != O_RDONLY &&
1973 (flags & O_ACCMODE) != O_RDWR)
1976 if (!endswith(fname, ".journal"))
1979 f = new0(JournalFile, 1);
1986 f->writable = (flags & O_ACCMODE) != O_RDONLY;
1987 f->prot = prot_from_flags(flags);
1990 f->compress = template->compress;
1992 f->path = strdup(fname);
1998 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
2004 if (fstat(f->fd, &f->last_stat) < 0) {
2009 if (f->last_stat.st_size == 0 && f->writable) {
2010 newly_created = true;
2012 r = journal_file_init_header(f, template);
2016 if (fstat(f->fd, &f->last_stat) < 0) {
2022 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
2027 f->header = mmap(NULL, PAGE_ALIGN(sizeof(Header)), prot_from_flags(flags), MAP_SHARED, f->fd, 0);
2028 if (f->header == MAP_FAILED) {
2034 if (!newly_created) {
2035 r = journal_file_verify_header(f);
2042 journal_default_metrics(metrics, f->fd);
2043 f->metrics = *metrics;
2044 } else if (template)
2045 f->metrics = template->metrics;
2047 r = journal_file_refresh_header(f);
2052 if (newly_created) {
2054 r = journal_file_setup_field_hash_table(f);
2058 r = journal_file_setup_data_hash_table(f);
2063 r = journal_file_map_field_hash_table(f);
2067 r = journal_file_map_data_hash_table(f);
2077 journal_file_close(f);
2082 int journal_file_rotate(JournalFile **f) {
2085 JournalFile *old_file, *new_file = NULL;
2093 if (!old_file->writable)
2096 if (!endswith(old_file->path, ".journal"))
2099 l = strlen(old_file->path);
2101 p = new(char, l + 1 + 32 + 1 + 16 + 1 + 16 + 1);
2105 memcpy(p, old_file->path, l - 8);
2107 sd_id128_to_string(old_file->header->seqnum_id, p + l - 8 + 1);
2108 snprintf(p + l - 8 + 1 + 32, 1 + 16 + 1 + 16 + 8 + 1,
2109 "-%016llx-%016llx.journal",
2110 (unsigned long long) le64toh((*f)->header->tail_seqnum),
2111 (unsigned long long) le64toh((*f)->header->tail_entry_realtime));
2113 r = rename(old_file->path, p);
2119 old_file->header->state = STATE_ARCHIVED;
2121 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, NULL, old_file, &new_file);
2122 journal_file_close(old_file);
2128 int journal_file_open_reliably(
2132 JournalMetrics *metrics,
2133 JournalFile *template,
2134 JournalFile **ret) {
2140 r = journal_file_open(fname, flags, mode, metrics, template, ret);
2141 if (r != -EBADMSG && /* corrupted */
2142 r != -ENODATA && /* truncated */
2143 r != -EHOSTDOWN && /* other machine */
2144 r != -EPROTONOSUPPORT) /* incompatible feature */
2147 if ((flags & O_ACCMODE) == O_RDONLY)
2150 if (!(flags & O_CREAT))
2153 /* The file is corrupted. Rotate it away and try it again (but only once) */
2156 if (asprintf(&p, "%.*s@%016llx-%016llx.journal~",
2158 (unsigned long long) now(CLOCK_REALTIME),
2162 r = rename(fname, p);
2167 log_warning("File %s corrupted, renaming and replacing.", fname);
2169 return journal_file_open(fname, flags, mode, metrics, template, ret);
2172 struct vacuum_info {
2177 sd_id128_t seqnum_id;
2183 static int vacuum_compare(const void *_a, const void *_b) {
2184 const struct vacuum_info *a, *b;
2189 if (a->have_seqnum && b->have_seqnum &&
2190 sd_id128_equal(a->seqnum_id, b->seqnum_id)) {
2191 if (a->seqnum < b->seqnum)
2193 else if (a->seqnum > b->seqnum)
2199 if (a->realtime < b->realtime)
2201 else if (a->realtime > b->realtime)
2203 else if (a->have_seqnum && b->have_seqnum)
2204 return memcmp(&a->seqnum_id, &b->seqnum_id, 16);
2206 return strcmp(a->filename, b->filename);
2209 int journal_directory_vacuum(const char *directory, uint64_t max_use, uint64_t min_free) {
2212 struct vacuum_info *list = NULL;
2213 unsigned n_list = 0, n_allocated = 0, i;
2221 d = opendir(directory);
2227 struct dirent buf, *de;
2231 unsigned long long seqnum = 0, realtime;
2232 sd_id128_t seqnum_id;
2235 k = readdir_r(d, &buf, &de);
2244 if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0)
2247 if (!S_ISREG(st.st_mode))
2250 q = strlen(de->d_name);
2252 if (endswith(de->d_name, ".journal")) {
2254 /* Vacuum archived files */
2256 if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8)
2259 if (de->d_name[q-8-16-1] != '-' ||
2260 de->d_name[q-8-16-1-16-1] != '-' ||
2261 de->d_name[q-8-16-1-16-1-32-1] != '@')
2264 p = strdup(de->d_name);
2270 de->d_name[q-8-16-1-16-1] = 0;
2271 if (sd_id128_from_string(de->d_name + q-8-16-1-16-1-32, &seqnum_id) < 0) {
2276 if (sscanf(de->d_name + q-8-16-1-16, "%16llx-%16llx.journal", &seqnum, &realtime) != 2) {
2283 } else if (endswith(de->d_name, ".journal~")) {
2284 unsigned long long tmp;
2286 /* Vacuum corrupted files */
2288 if (q < 1 + 16 + 1 + 16 + 8 + 1)
2291 if (de->d_name[q-1-8-16-1] != '-' ||
2292 de->d_name[q-1-8-16-1-16-1] != '@')
2295 p = strdup(de->d_name);
2301 if (sscanf(de->d_name + q-1-8-16-1-16, "%16llx-%16llx.journal~", &realtime, &tmp) != 2) {
2306 have_seqnum = false;
2310 if (n_list >= n_allocated) {
2311 struct vacuum_info *j;
2313 n_allocated = MAX(n_allocated * 2U, 8U);
2314 j = realloc(list, n_allocated * sizeof(struct vacuum_info));
2324 list[n_list].filename = p;
2325 list[n_list].usage = 512UL * (uint64_t) st.st_blocks;
2326 list[n_list].seqnum = seqnum;
2327 list[n_list].realtime = realtime;
2328 list[n_list].seqnum_id = seqnum_id;
2329 list[n_list].have_seqnum = have_seqnum;
2331 sum += list[n_list].usage;
2336 qsort(list, n_list, sizeof(struct vacuum_info), vacuum_compare);
2338 for(i = 0; i < n_list; i++) {
2341 if (fstatvfs(dirfd(d), &ss) < 0) {
2346 if (sum <= max_use &&
2347 (uint64_t) ss.f_bavail * (uint64_t) ss.f_bsize >= min_free)
2350 if (unlinkat(dirfd(d), list[i].filename, 0) >= 0) {
2351 log_info("Deleted archived journal %s/%s.", directory, list[i].filename);
2352 sum -= list[i].usage;
2353 } else if (errno != ENOENT)
2354 log_warning("Failed to delete %s/%s: %m", directory, list[i].filename);
2358 for (i = 0; i < n_list; i++)
2359 free(list[i].filename);
2369 int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
2371 uint64_t q, xor_hash = 0;
2384 ts.monotonic = le64toh(o->entry.monotonic);
2385 ts.realtime = le64toh(o->entry.realtime);
2387 if (to->tail_entry_monotonic_valid &&
2388 ts.monotonic < le64toh(to->header->tail_entry_monotonic))
2391 n = journal_file_entry_n_items(o);
2392 items = alloca(sizeof(EntryItem) * n);
2394 for (i = 0; i < n; i++) {
2401 q = le64toh(o->entry.items[i].object_offset);
2402 le_hash = o->entry.items[i].hash;
2404 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
2408 if (le_hash != o->data.hash)
2411 l = le64toh(o->object.size) - offsetof(Object, data.payload);
2414 /* We hit the limit on 32bit machines */
2415 if ((uint64_t) t != l)
2418 if (o->object.flags & OBJECT_COMPRESSED) {
2422 if (!uncompress_blob(o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize))
2425 data = from->compress_buffer;
2428 return -EPROTONOSUPPORT;
2431 data = o->data.payload;
2433 r = journal_file_append_data(to, data, l, &u, &h);
2437 xor_hash ^= le64toh(u->data.hash);
2438 items[i].object_offset = htole64(h);
2439 items[i].hash = u->data.hash;
2441 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
2446 return journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
2449 void journal_default_metrics(JournalMetrics *m, int fd) {
2450 uint64_t fs_size = 0;
2452 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX];
2457 if (fstatvfs(fd, &ss) >= 0)
2458 fs_size = ss.f_frsize * ss.f_blocks;
2460 if (m->max_use == (uint64_t) -1) {
2463 m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
2465 if (m->max_use > DEFAULT_MAX_USE_UPPER)
2466 m->max_use = DEFAULT_MAX_USE_UPPER;
2468 if (m->max_use < DEFAULT_MAX_USE_LOWER)
2469 m->max_use = DEFAULT_MAX_USE_LOWER;
2471 m->max_use = DEFAULT_MAX_USE_LOWER;
2473 m->max_use = PAGE_ALIGN(m->max_use);
2475 if (m->max_use < JOURNAL_FILE_SIZE_MIN*2)
2476 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
2479 if (m->max_size == (uint64_t) -1) {
2480 m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
2482 if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
2483 m->max_size = DEFAULT_MAX_SIZE_UPPER;
2485 m->max_size = PAGE_ALIGN(m->max_size);
2487 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
2488 m->max_size = JOURNAL_FILE_SIZE_MIN;
2490 if (m->max_size*2 > m->max_use)
2491 m->max_use = m->max_size*2;
2493 if (m->min_size == (uint64_t) -1)
2494 m->min_size = JOURNAL_FILE_SIZE_MIN;
2496 m->min_size = PAGE_ALIGN(m->min_size);
2498 if (m->min_size < JOURNAL_FILE_SIZE_MIN)
2499 m->min_size = JOURNAL_FILE_SIZE_MIN;
2501 if (m->min_size > m->max_size)
2502 m->max_size = m->min_size;
2505 if (m->keep_free == (uint64_t) -1) {
2508 m->keep_free = PAGE_ALIGN(fs_size / 20); /* 5% of file system size */
2510 if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
2511 m->keep_free = DEFAULT_KEEP_FREE_UPPER;
2514 m->keep_free = DEFAULT_KEEP_FREE;
2517 log_info("Fixed max_use=%s max_size=%s min_size=%s keep_free=%s",
2518 format_bytes(a, sizeof(a), m->max_use),
2519 format_bytes(b, sizeof(b), m->max_size),
2520 format_bytes(c, sizeof(c), m->min_size),
2521 format_bytes(d, sizeof(d), m->keep_free));
2524 int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
2529 if (f->header->head_entry_realtime == 0)
2532 *from = le64toh(f->header->head_entry_realtime);
2536 if (f->header->tail_entry_realtime == 0)
2539 *to = le64toh(f->header->tail_entry_realtime);
2545 int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
2546 char t[9+32+1] = "_BOOT_ID=";
2554 sd_id128_to_string(boot_id, t + 9);
2556 r = journal_file_find_data_object(f, t, strlen(t), &o, &p);
2560 if (le64toh(o->data.n_entries) <= 0)
2564 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
2568 *from = le64toh(o->entry.monotonic);
2572 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
2576 r = generic_array_get_plus_one(f,
2577 le64toh(o->data.entry_offset),
2578 le64toh(o->data.entry_array_offset),
2579 le64toh(o->data.n_entries)-1,
2584 *to = le64toh(o->entry.monotonic);
2590 bool journal_file_rotate_suggested(JournalFile *f) {
2593 /* If we gained new header fields we gained new features,
2594 * hence suggest a rotation */
2595 if (le64toh(f->header->header_size) < sizeof(Header))
2598 /* Let's check if the hash tables grew over a certain fill
2599 * level (75%, borrowing this value from Java's hash table
2600 * implementation), and if so suggest a rotation. To calculate
2601 * the fill level we need the n_data field, which only exists
2602 * in newer versions. */
2604 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2605 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL)
2608 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2609 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL)