]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-file.c
journal: add void cast to journal_file_close() calls
[thirdparty/systemd.git] / src / journal / journal-file.c
CommitLineData
cec736d2
LP
1/***
2 This file is part of systemd.
3
4 Copyright 2011 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
cec736d2
LP
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 14 Lesser General Public License for more details.
cec736d2 15
5430f7f2 16 You should have received a copy of the GNU Lesser General Public License
cec736d2
LP
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18***/
19
cec736d2 20#include <errno.h>
cec736d2 21#include <fcntl.h>
11689d2a 22#include <linux/fs.h>
07630cea
LP
23#include <stddef.h>
24#include <sys/mman.h>
25#include <sys/statvfs.h>
26#include <sys/uio.h>
27#include <unistd.h>
fb0951b0 28
b5efdb8a 29#include "alloc-util.h"
f27a3864 30#include "btrfs-util.h"
c8b3094d 31#include "chattr-util.h"
07630cea 32#include "compress.h"
3ffd4af2 33#include "fd-util.h"
0284adc6 34#include "journal-authenticate.h"
cec736d2
LP
35#include "journal-def.h"
36#include "journal-file.h"
37#include "lookup3.h"
6bedfcbb 38#include "parse-util.h"
3df3e884 39#include "random-util.h"
7a24f3bf 40#include "sd-event.h"
07630cea 41#include "string-util.h"
89a5a90c 42#include "xattr-util.h"
cec736d2 43
4a92baf3
LP
44#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem))
45#define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem))
cec736d2 46
be19b7df 47#define COMPRESSION_SIZE_THRESHOLD (512ULL)
807e17f0 48
babfc091 49/* This is the minimum journal file size */
16098e93 50#define JOURNAL_FILE_SIZE_MIN (512ULL*1024ULL) /* 512 KiB */
babfc091
LP
51
52/* These are the lower and upper bounds if we deduce the max_use value
53 * from the file system size */
54#define DEFAULT_MAX_USE_LOWER (1ULL*1024ULL*1024ULL) /* 1 MiB */
55#define DEFAULT_MAX_USE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
56
8580d1f7
LP
57/* This is the default minimal use limit, how much we'll use even if keep_free suggests otherwise. */
58#define DEFAULT_MIN_USE (1ULL*1024ULL*1024ULL) /* 1 MiB */
59
babfc091 60/* This is the upper bound if we deduce max_size from max_use */
71100051 61#define DEFAULT_MAX_SIZE_UPPER (128ULL*1024ULL*1024ULL) /* 128 MiB */
babfc091
LP
62
63/* This is the upper bound if we deduce the keep_free value from the
64 * file system size */
65#define DEFAULT_KEEP_FREE_UPPER (4ULL*1024ULL*1024ULL*1024ULL) /* 4 GiB */
66
67/* This is the keep_free value when we can't determine the system
68 * size */
69#define DEFAULT_KEEP_FREE (1024ULL*1024ULL) /* 1 MB */
70
8580d1f7
LP
71/* This is the default maximum number of journal files to keep around. */
72#define DEFAULT_N_MAX_FILES (100)
73
dca6219e
LP
74/* n_data was the first entry we added after the initial file format design */
75#define HEADER_SIZE_MIN ALIGN64(offsetof(Header, n_data))
cec736d2 76
a4bcff5b
LP
77/* How many entries to keep in the entry array chain cache at max */
78#define CHAIN_CACHE_MAX 20
79
a676e665
LP
80/* How much to increase the journal file size at once each time we allocate something new. */
81#define FILE_SIZE_INCREASE (8ULL*1024ULL*1024ULL) /* 8MB */
82
2678031a
LP
83/* Reread fstat() of the file for detecting deletions at least this often */
84#define LAST_STAT_REFRESH_USEC (5*USEC_PER_SEC)
85
fa6ac760
LP
86/* The mmap context to use for the header we pick as one above the last defined typed */
87#define CONTEXT_HEADER _OBJECT_TYPE_MAX
88
9588bc32 89static int journal_file_set_online(JournalFile *f) {
26687bf8
OS
90 assert(f);
91
92 if (!f->writable)
93 return -EPERM;
94
95 if (!(f->fd >= 0 && f->header))
96 return -EINVAL;
97
fa6ac760
LP
98 if (mmap_cache_got_sigbus(f->mmap, f->fd))
99 return -EIO;
100
31981791 101 switch (f->header->state) {
26687bf8
OS
102 case STATE_ONLINE:
103 return 0;
104
105 case STATE_OFFLINE:
106 f->header->state = STATE_ONLINE;
fb426037 107 (void) fsync(f->fd);
26687bf8
OS
108 return 0;
109
110 default:
111 return -EINVAL;
112 }
113}
114
115int journal_file_set_offline(JournalFile *f) {
116 assert(f);
117
118 if (!f->writable)
119 return -EPERM;
120
121 if (!(f->fd >= 0 && f->header))
122 return -EINVAL;
123
124 if (f->header->state != STATE_ONLINE)
125 return 0;
126
fb426037 127 (void) fsync(f->fd);
26687bf8 128
fa6ac760
LP
129 if (mmap_cache_got_sigbus(f->mmap, f->fd))
130 return -EIO;
131
26687bf8
OS
132 f->header->state = STATE_OFFLINE;
133
fa6ac760
LP
134 if (mmap_cache_got_sigbus(f->mmap, f->fd))
135 return -EIO;
136
fb426037 137 (void) fsync(f->fd);
26687bf8
OS
138
139 return 0;
140}
141
804ae586 142JournalFile* journal_file_close(JournalFile *f) {
de190aef 143 assert(f);
cec736d2 144
feb12d3e 145#ifdef HAVE_GCRYPT
b0af6f41 146 /* Write the final tag */
c586dbf1 147 if (f->seal && f->writable)
b0af6f41 148 journal_file_append_tag(f);
feb12d3e 149#endif
b0af6f41 150
7a24f3bf
VC
151 if (f->post_change_timer) {
152 int enabled;
153
154 if (sd_event_source_get_enabled(f->post_change_timer, &enabled) >= 0)
155 if (enabled == SD_EVENT_ONESHOT)
156 journal_file_post_change(f);
157
e167d7fd 158 (void) sd_event_source_set_enabled(f->post_change_timer, SD_EVENT_OFF);
7a24f3bf
VC
159 sd_event_source_unref(f->post_change_timer);
160 }
161
26687bf8 162 journal_file_set_offline(f);
cec736d2 163
fa6ac760
LP
164 if (f->mmap && f->fd >= 0)
165 mmap_cache_close_fd(f->mmap, f->fd);
cec736d2 166
11689d2a
LP
167 if (f->fd >= 0 && f->defrag_on_close) {
168
169 /* Be friendly to btrfs: turn COW back on again now,
170 * and defragment the file. We won't write to the file
171 * ever again, hence remove all fragmentation, and
172 * reenable all the good bits COW usually provides
173 * (such as data checksumming). */
174
1ed8f8c1 175 (void) chattr_fd(f->fd, 0, FS_NOCOW_FL);
11689d2a
LP
176 (void) btrfs_defrag_fd(f->fd);
177 }
f27a3864 178
03e334a1 179 safe_close(f->fd);
cec736d2 180 free(f->path);
807e17f0 181
f649045c 182 mmap_cache_unref(f->mmap);
16e9f408 183
4743015d 184 ordered_hashmap_free_free(f->chain_cache);
a4bcff5b 185
d89c8fdf 186#if defined(HAVE_XZ) || defined(HAVE_LZ4)
807e17f0
LP
187 free(f->compress_buffer);
188#endif
189
7560fffc 190#ifdef HAVE_GCRYPT
baed47c3
LP
191 if (f->fss_file)
192 munmap(f->fss_file, PAGE_ALIGN(f->fss_file_size));
dc4ebc07 193 else
b7c9ae91
LP
194 free(f->fsprg_state);
195
196 free(f->fsprg_seed);
7560fffc
LP
197
198 if (f->hmac)
199 gcry_md_close(f->hmac);
200#endif
201
cec736d2 202 free(f);
804ae586 203 return NULL;
cec736d2
LP
204}
205
0ac38b70 206static int journal_file_init_header(JournalFile *f, JournalFile *template) {
d89c8fdf 207 Header h = {};
cec736d2
LP
208 ssize_t k;
209 int r;
210
211 assert(f);
212
7560fffc 213 memcpy(h.signature, HEADER_SIGNATURE, 8);
23b0b2b2 214 h.header_size = htole64(ALIGN64(sizeof(h)));
cec736d2 215
d89c8fdf
ZJS
216 h.incompatible_flags |= htole32(
217 f->compress_xz * HEADER_INCOMPATIBLE_COMPRESSED_XZ |
218 f->compress_lz4 * HEADER_INCOMPATIBLE_COMPRESSED_LZ4);
7560fffc 219
d89c8fdf
ZJS
220 h.compatible_flags = htole32(
221 f->seal * HEADER_COMPATIBLE_SEALED);
7560fffc 222
cec736d2
LP
223 r = sd_id128_randomize(&h.file_id);
224 if (r < 0)
225 return r;
226
0ac38b70
LP
227 if (template) {
228 h.seqnum_id = template->header->seqnum_id;
beec0085 229 h.tail_entry_seqnum = template->header->tail_entry_seqnum;
0ac38b70
LP
230 } else
231 h.seqnum_id = h.file_id;
cec736d2
LP
232
233 k = pwrite(f->fd, &h, sizeof(h), 0);
234 if (k < 0)
235 return -errno;
236
237 if (k != sizeof(h))
238 return -EIO;
239
240 return 0;
241}
242
243static int journal_file_refresh_header(JournalFile *f) {
de190aef 244 sd_id128_t boot_id;
fa6ac760 245 int r;
cec736d2
LP
246
247 assert(f);
c88cc6af 248 assert(f->header);
cec736d2
LP
249
250 r = sd_id128_get_machine(&f->header->machine_id);
251 if (r < 0)
252 return r;
253
de190aef 254 r = sd_id128_get_boot(&boot_id);
cec736d2
LP
255 if (r < 0)
256 return r;
257
de190aef
LP
258 if (sd_id128_equal(boot_id, f->header->boot_id))
259 f->tail_entry_monotonic_valid = true;
260
261 f->header->boot_id = boot_id;
262
fa6ac760 263 r = journal_file_set_online(f);
b788cc23 264
7560fffc 265 /* Sync the online state to disk */
fb426037 266 (void) fsync(f->fd);
b788cc23 267
fa6ac760 268 return r;
cec736d2
LP
269}
270
271static int journal_file_verify_header(JournalFile *f) {
d89c8fdf
ZJS
272 uint32_t flags;
273
cec736d2 274 assert(f);
c88cc6af 275 assert(f->header);
cec736d2 276
7560fffc 277 if (memcmp(f->header->signature, HEADER_SIGNATURE, 8))
cec736d2
LP
278 return -EBADMSG;
279
7560fffc
LP
280 /* In both read and write mode we refuse to open files with
281 * incompatible flags we don't know */
d89c8fdf
ZJS
282 flags = le32toh(f->header->incompatible_flags);
283 if (flags & ~HEADER_INCOMPATIBLE_SUPPORTED) {
284 if (flags & ~HEADER_INCOMPATIBLE_ANY)
285 log_debug("Journal file %s has unknown incompatible flags %"PRIx32,
286 f->path, flags & ~HEADER_INCOMPATIBLE_ANY);
287 flags = (flags & HEADER_INCOMPATIBLE_ANY) & ~HEADER_INCOMPATIBLE_SUPPORTED;
288 if (flags)
289 log_debug("Journal file %s uses incompatible flags %"PRIx32
290 " disabled at compilation time.", f->path, flags);
cec736d2 291 return -EPROTONOSUPPORT;
d89c8fdf 292 }
cec736d2 293
7560fffc
LP
294 /* When open for writing we refuse to open files with
295 * compatible flags, too */
d89c8fdf
ZJS
296 flags = le32toh(f->header->compatible_flags);
297 if (f->writable && (flags & ~HEADER_COMPATIBLE_SUPPORTED)) {
298 if (flags & ~HEADER_COMPATIBLE_ANY)
299 log_debug("Journal file %s has unknown compatible flags %"PRIx32,
300 f->path, flags & ~HEADER_COMPATIBLE_ANY);
301 flags = (flags & HEADER_COMPATIBLE_ANY) & ~HEADER_COMPATIBLE_SUPPORTED;
302 if (flags)
303 log_debug("Journal file %s uses compatible flags %"PRIx32
304 " disabled at compilation time.", f->path, flags);
305 return -EPROTONOSUPPORT;
7560fffc
LP
306 }
307
db11ac1a
LP
308 if (f->header->state >= _STATE_MAX)
309 return -EBADMSG;
310
dca6219e
LP
311 /* The first addition was n_data, so check that we are at least this large */
312 if (le64toh(f->header->header_size) < HEADER_SIZE_MIN)
23b0b2b2
LP
313 return -EBADMSG;
314
8088cbd3 315 if (JOURNAL_HEADER_SEALED(f->header) && !JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
beec0085
LP
316 return -EBADMSG;
317
db11ac1a
LP
318 if ((le64toh(f->header->header_size) + le64toh(f->header->arena_size)) > (uint64_t) f->last_stat.st_size)
319 return -ENODATA;
320
321 if (le64toh(f->header->tail_object_offset) > (le64toh(f->header->header_size) + le64toh(f->header->arena_size)))
322 return -ENODATA;
323
7762e02b
LP
324 if (!VALID64(le64toh(f->header->data_hash_table_offset)) ||
325 !VALID64(le64toh(f->header->field_hash_table_offset)) ||
326 !VALID64(le64toh(f->header->tail_object_offset)) ||
327 !VALID64(le64toh(f->header->entry_array_offset)))
328 return -ENODATA;
329
cec736d2 330 if (f->writable) {
ccdbaf91 331 uint8_t state;
cec736d2
LP
332 sd_id128_t machine_id;
333 int r;
334
335 r = sd_id128_get_machine(&machine_id);
336 if (r < 0)
337 return r;
338
339 if (!sd_id128_equal(machine_id, f->header->machine_id))
340 return -EHOSTDOWN;
341
de190aef 342 state = f->header->state;
cec736d2 343
71fa6f00
LP
344 if (state == STATE_ONLINE) {
345 log_debug("Journal file %s is already online. Assuming unclean closing.", f->path);
346 return -EBUSY;
347 } else if (state == STATE_ARCHIVED)
cec736d2 348 return -ESHUTDOWN;
71fa6f00 349 else if (state != STATE_OFFLINE) {
8facc349 350 log_debug("Journal file %s has unknown state %i.", f->path, state);
71fa6f00
LP
351 return -EBUSY;
352 }
cec736d2
LP
353 }
354
d89c8fdf
ZJS
355 f->compress_xz = JOURNAL_HEADER_COMPRESSED_XZ(f->header);
356 f->compress_lz4 = JOURNAL_HEADER_COMPRESSED_LZ4(f->header);
c586dbf1 357
f1889c91 358 f->seal = JOURNAL_HEADER_SEALED(f->header);
7560fffc 359
cec736d2
LP
360 return 0;
361}
362
2678031a
LP
363static int journal_file_fstat(JournalFile *f) {
364 assert(f);
365 assert(f->fd >= 0);
366
367 if (fstat(f->fd, &f->last_stat) < 0)
368 return -errno;
369
370 f->last_stat_usec = now(CLOCK_MONOTONIC);
371
372 /* Refuse appending to files that are already deleted */
373 if (f->last_stat.st_nlink <= 0)
374 return -EIDRM;
375
376 return 0;
377}
378
cec736d2 379static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
eda4b58b 380 uint64_t old_size, new_size;
fec2aa2f 381 int r;
cec736d2
LP
382
383 assert(f);
c88cc6af 384 assert(f->header);
cec736d2 385
cec736d2 386 /* We assume that this file is not sparse, and we know that
38ac38b2 387 * for sure, since we always call posix_fallocate()
cec736d2
LP
388 * ourselves */
389
fa6ac760
LP
390 if (mmap_cache_got_sigbus(f->mmap, f->fd))
391 return -EIO;
392
cec736d2 393 old_size =
23b0b2b2 394 le64toh(f->header->header_size) +
cec736d2
LP
395 le64toh(f->header->arena_size);
396
bc85bfee 397 new_size = PAGE_ALIGN(offset + size);
23b0b2b2
LP
398 if (new_size < le64toh(f->header->header_size))
399 new_size = le64toh(f->header->header_size);
bc85bfee 400
2678031a
LP
401 if (new_size <= old_size) {
402
403 /* We already pre-allocated enough space, but before
404 * we write to it, let's check with fstat() if the
405 * file got deleted, in order make sure we don't throw
406 * away the data immediately. Don't check fstat() for
407 * all writes though, but only once ever 10s. */
408
409 if (f->last_stat_usec + LAST_STAT_REFRESH_USEC > now(CLOCK_MONOTONIC))
410 return 0;
411
412 return journal_file_fstat(f);
413 }
414
415 /* Allocate more space. */
cec736d2 416
a676e665 417 if (f->metrics.max_size > 0 && new_size > f->metrics.max_size)
bc85bfee 418 return -E2BIG;
cec736d2 419
a676e665 420 if (new_size > f->metrics.min_size && f->metrics.keep_free > 0) {
cec736d2
LP
421 struct statvfs svfs;
422
423 if (fstatvfs(f->fd, &svfs) >= 0) {
424 uint64_t available;
425
070052ab 426 available = LESS_BY((uint64_t) svfs.f_bfree * (uint64_t) svfs.f_bsize, f->metrics.keep_free);
cec736d2
LP
427
428 if (new_size - old_size > available)
429 return -E2BIG;
430 }
431 }
432
eda4b58b
LP
433 /* Increase by larger blocks at once */
434 new_size = ((new_size+FILE_SIZE_INCREASE-1) / FILE_SIZE_INCREASE) * FILE_SIZE_INCREASE;
435 if (f->metrics.max_size > 0 && new_size > f->metrics.max_size)
436 new_size = f->metrics.max_size;
437
bc85bfee
LP
438 /* Note that the glibc fallocate() fallback is very
439 inefficient, hence we try to minimize the allocation area
440 as we can. */
fec2aa2f
GV
441 r = posix_fallocate(f->fd, old_size, new_size - old_size);
442 if (r != 0)
443 return -r;
cec736d2 444
23b0b2b2 445 f->header->arena_size = htole64(new_size - le64toh(f->header->header_size));
cec736d2 446
2678031a 447 return journal_file_fstat(f);
cec736d2
LP
448}
449
78519831 450static unsigned type_to_context(ObjectType type) {
d3d3208f 451 /* One context for each type, plus one catch-all for the rest */
69adae51 452 assert_cc(_OBJECT_TYPE_MAX <= MMAP_CACHE_MAX_CONTEXTS);
fa6ac760 453 assert_cc(CONTEXT_HEADER < MMAP_CACHE_MAX_CONTEXTS);
d05089d8 454 return type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX ? type : 0;
d3d3208f
MS
455}
456
7a9dabea 457static int journal_file_move_to(JournalFile *f, ObjectType type, bool keep_always, uint64_t offset, uint64_t size, void **ret) {
2678031a
LP
458 int r;
459
cec736d2 460 assert(f);
cec736d2
LP
461 assert(ret);
462
7762e02b
LP
463 if (size <= 0)
464 return -EINVAL;
465
2a59ea54 466 /* Avoid SIGBUS on invalid accesses */
4bbdcdb3
LP
467 if (offset + size > (uint64_t) f->last_stat.st_size) {
468 /* Hmm, out of range? Let's refresh the fstat() data
469 * first, before we trust that check. */
470
2678031a
LP
471 r = journal_file_fstat(f);
472 if (r < 0)
473 return r;
474
475 if (offset + size > (uint64_t) f->last_stat.st_size)
4bbdcdb3
LP
476 return -EADDRNOTAVAIL;
477 }
478
7a9dabea 479 return mmap_cache_get(f->mmap, f->fd, f->prot, type_to_context(type), keep_always, offset, size, &f->last_stat, ret);
cec736d2
LP
480}
481
16e9f408
LP
482static uint64_t minimum_header_size(Object *o) {
483
b8e891e6 484 static const uint64_t table[] = {
16e9f408
LP
485 [OBJECT_DATA] = sizeof(DataObject),
486 [OBJECT_FIELD] = sizeof(FieldObject),
487 [OBJECT_ENTRY] = sizeof(EntryObject),
488 [OBJECT_DATA_HASH_TABLE] = sizeof(HashTableObject),
489 [OBJECT_FIELD_HASH_TABLE] = sizeof(HashTableObject),
490 [OBJECT_ENTRY_ARRAY] = sizeof(EntryArrayObject),
491 [OBJECT_TAG] = sizeof(TagObject),
492 };
493
494 if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0)
495 return sizeof(ObjectHeader);
496
497 return table[o->object.type];
498}
499
78519831 500int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset, Object **ret) {
cec736d2
LP
501 int r;
502 void *t;
503 Object *o;
504 uint64_t s;
505
506 assert(f);
507 assert(ret);
508
db11ac1a
LP
509 /* Objects may only be located at multiple of 64 bit */
510 if (!VALID64(offset))
511 return -EFAULT;
512
7a9dabea 513 r = journal_file_move_to(f, type, false, offset, sizeof(ObjectHeader), &t);
cec736d2
LP
514 if (r < 0)
515 return r;
516
517 o = (Object*) t;
518 s = le64toh(o->object.size);
519
520 if (s < sizeof(ObjectHeader))
521 return -EBADMSG;
522
16e9f408
LP
523 if (o->object.type <= OBJECT_UNUSED)
524 return -EBADMSG;
525
526 if (s < minimum_header_size(o))
527 return -EBADMSG;
528
d05089d8 529 if (type > OBJECT_UNUSED && o->object.type != type)
cec736d2
LP
530 return -EBADMSG;
531
532 if (s > sizeof(ObjectHeader)) {
7a9dabea 533 r = journal_file_move_to(f, type, false, offset, s, &t);
cec736d2
LP
534 if (r < 0)
535 return r;
536
537 o = (Object*) t;
538 }
539
cec736d2
LP
540 *ret = o;
541 return 0;
542}
543
d98cc1f2 544static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) {
cec736d2
LP
545 uint64_t r;
546
547 assert(f);
c88cc6af 548 assert(f->header);
cec736d2 549
beec0085 550 r = le64toh(f->header->tail_entry_seqnum) + 1;
c2373f84
LP
551
552 if (seqnum) {
de190aef 553 /* If an external seqnum counter was passed, we update
c2373f84
LP
554 * both the local and the external one, and set it to
555 * the maximum of both */
556
557 if (*seqnum + 1 > r)
558 r = *seqnum + 1;
559
560 *seqnum = r;
561 }
562
beec0085 563 f->header->tail_entry_seqnum = htole64(r);
cec736d2 564
beec0085
LP
565 if (f->header->head_entry_seqnum == 0)
566 f->header->head_entry_seqnum = htole64(r);
de190aef 567
cec736d2
LP
568 return r;
569}
570
78519831 571int journal_file_append_object(JournalFile *f, ObjectType type, uint64_t size, Object **ret, uint64_t *offset) {
cec736d2
LP
572 int r;
573 uint64_t p;
574 Object *tail, *o;
575 void *t;
576
577 assert(f);
c88cc6af 578 assert(f->header);
d05089d8 579 assert(type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX);
cec736d2
LP
580 assert(size >= sizeof(ObjectHeader));
581 assert(offset);
582 assert(ret);
583
26687bf8
OS
584 r = journal_file_set_online(f);
585 if (r < 0)
586 return r;
587
cec736d2 588 p = le64toh(f->header->tail_object_offset);
cec736d2 589 if (p == 0)
23b0b2b2 590 p = le64toh(f->header->header_size);
cec736d2 591 else {
d05089d8 592 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &tail);
cec736d2
LP
593 if (r < 0)
594 return r;
595
596 p += ALIGN64(le64toh(tail->object.size));
597 }
598
599 r = journal_file_allocate(f, p, size);
600 if (r < 0)
601 return r;
602
fcde2389 603 r = journal_file_move_to(f, type, false, p, size, &t);
cec736d2
LP
604 if (r < 0)
605 return r;
606
607 o = (Object*) t;
608
609 zero(o->object);
de190aef 610 o->object.type = type;
cec736d2
LP
611 o->object.size = htole64(size);
612
613 f->header->tail_object_offset = htole64(p);
cec736d2
LP
614 f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1);
615
616 *ret = o;
617 *offset = p;
618
619 return 0;
620}
621
de190aef 622static int journal_file_setup_data_hash_table(JournalFile *f) {
cec736d2
LP
623 uint64_t s, p;
624 Object *o;
625 int r;
626
627 assert(f);
c88cc6af 628 assert(f->header);
cec736d2 629
070052ab
LP
630 /* We estimate that we need 1 hash table entry per 768 bytes
631 of journal file and we want to make sure we never get
632 beyond 75% fill level. Calculate the hash table size for
633 the maximum file size based on these metrics. */
4a92baf3 634
dfabe643 635 s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem);
4a92baf3
LP
636 if (s < DEFAULT_DATA_HASH_TABLE_SIZE)
637 s = DEFAULT_DATA_HASH_TABLE_SIZE;
638
507f22bd 639 log_debug("Reserving %"PRIu64" entries in hash table.", s / sizeof(HashItem));
4a92baf3 640
de190aef
LP
641 r = journal_file_append_object(f,
642 OBJECT_DATA_HASH_TABLE,
643 offsetof(Object, hash_table.items) + s,
644 &o, &p);
cec736d2
LP
645 if (r < 0)
646 return r;
647
29804cc1 648 memzero(o->hash_table.items, s);
cec736d2 649
de190aef
LP
650 f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
651 f->header->data_hash_table_size = htole64(s);
cec736d2
LP
652
653 return 0;
654}
655
de190aef 656static int journal_file_setup_field_hash_table(JournalFile *f) {
cec736d2
LP
657 uint64_t s, p;
658 Object *o;
659 int r;
660
661 assert(f);
c88cc6af 662 assert(f->header);
cec736d2 663
3c1668da
LP
664 /* We use a fixed size hash table for the fields as this
665 * number should grow very slowly only */
666
de190aef
LP
667 s = DEFAULT_FIELD_HASH_TABLE_SIZE;
668 r = journal_file_append_object(f,
669 OBJECT_FIELD_HASH_TABLE,
670 offsetof(Object, hash_table.items) + s,
671 &o, &p);
cec736d2
LP
672 if (r < 0)
673 return r;
674
29804cc1 675 memzero(o->hash_table.items, s);
cec736d2 676
de190aef
LP
677 f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
678 f->header->field_hash_table_size = htole64(s);
cec736d2
LP
679
680 return 0;
681}
682
dade37d4 683int journal_file_map_data_hash_table(JournalFile *f) {
cec736d2
LP
684 uint64_t s, p;
685 void *t;
686 int r;
687
688 assert(f);
c88cc6af 689 assert(f->header);
cec736d2 690
dade37d4
LP
691 if (f->data_hash_table)
692 return 0;
693
de190aef
LP
694 p = le64toh(f->header->data_hash_table_offset);
695 s = le64toh(f->header->data_hash_table_size);
cec736d2 696
de190aef 697 r = journal_file_move_to(f,
16e9f408 698 OBJECT_DATA_HASH_TABLE,
fcde2389 699 true,
de190aef
LP
700 p, s,
701 &t);
cec736d2
LP
702 if (r < 0)
703 return r;
704
de190aef 705 f->data_hash_table = t;
cec736d2
LP
706 return 0;
707}
708
dade37d4 709int journal_file_map_field_hash_table(JournalFile *f) {
cec736d2
LP
710 uint64_t s, p;
711 void *t;
712 int r;
713
714 assert(f);
c88cc6af 715 assert(f->header);
cec736d2 716
dade37d4
LP
717 if (f->field_hash_table)
718 return 0;
719
de190aef
LP
720 p = le64toh(f->header->field_hash_table_offset);
721 s = le64toh(f->header->field_hash_table_size);
cec736d2 722
de190aef 723 r = journal_file_move_to(f,
16e9f408 724 OBJECT_FIELD_HASH_TABLE,
fcde2389 725 true,
de190aef
LP
726 p, s,
727 &t);
cec736d2
LP
728 if (r < 0)
729 return r;
730
de190aef 731 f->field_hash_table = t;
cec736d2
LP
732 return 0;
733}
734
3c1668da
LP
735static int journal_file_link_field(
736 JournalFile *f,
737 Object *o,
738 uint64_t offset,
739 uint64_t hash) {
740
805d1486 741 uint64_t p, h, m;
3c1668da
LP
742 int r;
743
744 assert(f);
c88cc6af 745 assert(f->header);
90d222c1 746 assert(f->field_hash_table);
3c1668da
LP
747 assert(o);
748 assert(offset > 0);
749
750 if (o->object.type != OBJECT_FIELD)
751 return -EINVAL;
752
805d1486
LP
753 m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem);
754 if (m <= 0)
755 return -EBADMSG;
3c1668da 756
805d1486 757 /* This might alter the window we are looking at */
3c1668da
LP
758 o->field.next_hash_offset = o->field.head_data_offset = 0;
759
805d1486 760 h = hash % m;
3c1668da
LP
761 p = le64toh(f->field_hash_table[h].tail_hash_offset);
762 if (p == 0)
763 f->field_hash_table[h].head_hash_offset = htole64(offset);
764 else {
765 r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o);
766 if (r < 0)
767 return r;
768
769 o->field.next_hash_offset = htole64(offset);
770 }
771
772 f->field_hash_table[h].tail_hash_offset = htole64(offset);
773
774 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
775 f->header->n_fields = htole64(le64toh(f->header->n_fields) + 1);
776
777 return 0;
778}
779
780static int journal_file_link_data(
781 JournalFile *f,
782 Object *o,
783 uint64_t offset,
784 uint64_t hash) {
785
805d1486 786 uint64_t p, h, m;
cec736d2
LP
787 int r;
788
789 assert(f);
c88cc6af 790 assert(f->header);
90d222c1 791 assert(f->data_hash_table);
cec736d2
LP
792 assert(o);
793 assert(offset > 0);
b588975f
LP
794
795 if (o->object.type != OBJECT_DATA)
796 return -EINVAL;
cec736d2 797
805d1486
LP
798 m = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
799 if (m <= 0)
800 return -EBADMSG;
48496df6 801
805d1486 802 /* This might alter the window we are looking at */
de190aef
LP
803 o->data.next_hash_offset = o->data.next_field_offset = 0;
804 o->data.entry_offset = o->data.entry_array_offset = 0;
805 o->data.n_entries = 0;
cec736d2 806
805d1486 807 h = hash % m;
8db4213e 808 p = le64toh(f->data_hash_table[h].tail_hash_offset);
3c1668da 809 if (p == 0)
cec736d2 810 /* Only entry in the hash table is easy */
de190aef 811 f->data_hash_table[h].head_hash_offset = htole64(offset);
3c1668da 812 else {
48496df6
LP
813 /* Move back to the previous data object, to patch in
814 * pointer */
cec736d2 815
de190aef 816 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
cec736d2
LP
817 if (r < 0)
818 return r;
819
de190aef 820 o->data.next_hash_offset = htole64(offset);
cec736d2
LP
821 }
822
de190aef 823 f->data_hash_table[h].tail_hash_offset = htole64(offset);
cec736d2 824
dca6219e
LP
825 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
826 f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
827
cec736d2
LP
828 return 0;
829}
830
3c1668da
LP
831int journal_file_find_field_object_with_hash(
832 JournalFile *f,
833 const void *field, uint64_t size, uint64_t hash,
834 Object **ret, uint64_t *offset) {
835
805d1486 836 uint64_t p, osize, h, m;
3c1668da
LP
837 int r;
838
839 assert(f);
c88cc6af 840 assert(f->header);
3c1668da
LP
841 assert(field && size > 0);
842
dade37d4
LP
843 /* If the field hash table is empty, we can't find anything */
844 if (le64toh(f->header->field_hash_table_size) <= 0)
845 return 0;
846
847 /* Map the field hash table, if it isn't mapped yet. */
848 r = journal_file_map_field_hash_table(f);
849 if (r < 0)
850 return r;
851
3c1668da
LP
852 osize = offsetof(Object, field.payload) + size;
853
805d1486 854 m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem);
805d1486 855 if (m <= 0)
3c1668da
LP
856 return -EBADMSG;
857
805d1486 858 h = hash % m;
3c1668da
LP
859 p = le64toh(f->field_hash_table[h].head_hash_offset);
860
861 while (p > 0) {
862 Object *o;
863
864 r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o);
865 if (r < 0)
866 return r;
867
868 if (le64toh(o->field.hash) == hash &&
869 le64toh(o->object.size) == osize &&
870 memcmp(o->field.payload, field, size) == 0) {
871
872 if (ret)
873 *ret = o;
874 if (offset)
875 *offset = p;
876
877 return 1;
878 }
879
880 p = le64toh(o->field.next_hash_offset);
881 }
882
883 return 0;
884}
885
886int journal_file_find_field_object(
887 JournalFile *f,
888 const void *field, uint64_t size,
889 Object **ret, uint64_t *offset) {
890
891 uint64_t hash;
892
893 assert(f);
894 assert(field && size > 0);
895
896 hash = hash64(field, size);
897
898 return journal_file_find_field_object_with_hash(f,
899 field, size, hash,
900 ret, offset);
901}
902
de190aef
LP
903int journal_file_find_data_object_with_hash(
904 JournalFile *f,
905 const void *data, uint64_t size, uint64_t hash,
906 Object **ret, uint64_t *offset) {
48496df6 907
805d1486 908 uint64_t p, osize, h, m;
cec736d2
LP
909 int r;
910
911 assert(f);
c88cc6af 912 assert(f->header);
cec736d2
LP
913 assert(data || size == 0);
914
dade37d4
LP
915 /* If there's no data hash table, then there's no entry. */
916 if (le64toh(f->header->data_hash_table_size) <= 0)
917 return 0;
918
919 /* Map the data hash table, if it isn't mapped yet. */
920 r = journal_file_map_data_hash_table(f);
921 if (r < 0)
922 return r;
923
cec736d2
LP
924 osize = offsetof(Object, data.payload) + size;
925
805d1486
LP
926 m = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
927 if (m <= 0)
bc85bfee
LP
928 return -EBADMSG;
929
805d1486 930 h = hash % m;
de190aef 931 p = le64toh(f->data_hash_table[h].head_hash_offset);
cec736d2 932
de190aef
LP
933 while (p > 0) {
934 Object *o;
cec736d2 935
de190aef 936 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
cec736d2
LP
937 if (r < 0)
938 return r;
939
807e17f0 940 if (le64toh(o->data.hash) != hash)
85a131e8 941 goto next;
807e17f0 942
d89c8fdf 943 if (o->object.flags & OBJECT_COMPRESSION_MASK) {
3b1a55e1 944#if defined(HAVE_XZ) || defined(HAVE_LZ4)
fa1c4b51 945 uint64_t l;
a7f7d1bd 946 size_t rsize = 0;
cec736d2 947
807e17f0
LP
948 l = le64toh(o->object.size);
949 if (l <= offsetof(Object, data.payload))
cec736d2
LP
950 return -EBADMSG;
951
807e17f0
LP
952 l -= offsetof(Object, data.payload);
953
d89c8fdf
ZJS
954 r = decompress_blob(o->object.flags & OBJECT_COMPRESSION_MASK,
955 o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize, 0);
956 if (r < 0)
957 return r;
807e17f0 958
b785c858 959 if (rsize == size &&
807e17f0
LP
960 memcmp(f->compress_buffer, data, size) == 0) {
961
962 if (ret)
963 *ret = o;
964
965 if (offset)
966 *offset = p;
967
968 return 1;
969 }
3b1a55e1
ZJS
970#else
971 return -EPROTONOSUPPORT;
972#endif
807e17f0
LP
973 } else if (le64toh(o->object.size) == osize &&
974 memcmp(o->data.payload, data, size) == 0) {
975
cec736d2
LP
976 if (ret)
977 *ret = o;
978
979 if (offset)
980 *offset = p;
981
de190aef 982 return 1;
cec736d2
LP
983 }
984
85a131e8 985 next:
cec736d2
LP
986 p = le64toh(o->data.next_hash_offset);
987 }
988
de190aef
LP
989 return 0;
990}
991
992int journal_file_find_data_object(
993 JournalFile *f,
994 const void *data, uint64_t size,
995 Object **ret, uint64_t *offset) {
996
997 uint64_t hash;
998
999 assert(f);
1000 assert(data || size == 0);
1001
1002 hash = hash64(data, size);
1003
1004 return journal_file_find_data_object_with_hash(f,
1005 data, size, hash,
1006 ret, offset);
1007}
1008
3c1668da
LP
1009static int journal_file_append_field(
1010 JournalFile *f,
1011 const void *field, uint64_t size,
1012 Object **ret, uint64_t *offset) {
1013
1014 uint64_t hash, p;
1015 uint64_t osize;
1016 Object *o;
1017 int r;
1018
1019 assert(f);
1020 assert(field && size > 0);
1021
1022 hash = hash64(field, size);
1023
1024 r = journal_file_find_field_object_with_hash(f, field, size, hash, &o, &p);
1025 if (r < 0)
1026 return r;
1027 else if (r > 0) {
1028
1029 if (ret)
1030 *ret = o;
1031
1032 if (offset)
1033 *offset = p;
1034
1035 return 0;
1036 }
1037
1038 osize = offsetof(Object, field.payload) + size;
1039 r = journal_file_append_object(f, OBJECT_FIELD, osize, &o, &p);
8c92d4bb
LP
1040 if (r < 0)
1041 return r;
3c1668da
LP
1042
1043 o->field.hash = htole64(hash);
1044 memcpy(o->field.payload, field, size);
1045
1046 r = journal_file_link_field(f, o, p, hash);
1047 if (r < 0)
1048 return r;
1049
1050 /* The linking might have altered the window, so let's
1051 * refresh our pointer */
1052 r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o);
1053 if (r < 0)
1054 return r;
1055
1056#ifdef HAVE_GCRYPT
1057 r = journal_file_hmac_put_object(f, OBJECT_FIELD, o, p);
1058 if (r < 0)
1059 return r;
1060#endif
1061
1062 if (ret)
1063 *ret = o;
1064
1065 if (offset)
1066 *offset = p;
1067
1068 return 0;
1069}
1070
48496df6
LP
1071static int journal_file_append_data(
1072 JournalFile *f,
1073 const void *data, uint64_t size,
1074 Object **ret, uint64_t *offset) {
1075
de190aef
LP
1076 uint64_t hash, p;
1077 uint64_t osize;
1078 Object *o;
d89c8fdf 1079 int r, compression = 0;
3c1668da 1080 const void *eq;
de190aef
LP
1081
1082 assert(f);
1083 assert(data || size == 0);
1084
1085 hash = hash64(data, size);
1086
1087 r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
1088 if (r < 0)
1089 return r;
0240c603 1090 if (r > 0) {
de190aef
LP
1091
1092 if (ret)
1093 *ret = o;
1094
1095 if (offset)
1096 *offset = p;
1097
1098 return 0;
1099 }
1100
1101 osize = offsetof(Object, data.payload) + size;
1102 r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
cec736d2
LP
1103 if (r < 0)
1104 return r;
1105
cec736d2 1106 o->data.hash = htole64(hash);
807e17f0 1107
d89c8fdf 1108#if defined(HAVE_XZ) || defined(HAVE_LZ4)
d1afbcd2 1109 if (JOURNAL_FILE_COMPRESS(f) && size >= COMPRESSION_SIZE_THRESHOLD) {
a7f7d1bd 1110 size_t rsize = 0;
807e17f0 1111
5d6f46b6 1112 compression = compress_blob(data, size, o->data.payload, size - 1, &rsize);
807e17f0 1113
d1afbcd2 1114 if (compression >= 0) {
807e17f0 1115 o->object.size = htole64(offsetof(Object, data.payload) + rsize);
d89c8fdf 1116 o->object.flags |= compression;
807e17f0 1117
fa1c4b51 1118 log_debug("Compressed data object %"PRIu64" -> %zu using %s",
d89c8fdf 1119 size, rsize, object_compressed_to_string(compression));
d1afbcd2
LP
1120 } else
1121 /* Compression didn't work, we don't really care why, let's continue without compression */
1122 compression = 0;
807e17f0
LP
1123 }
1124#endif
1125
75f32f04
ZJS
1126 if (compression == 0)
1127 memcpy_safe(o->data.payload, data, size);
cec736d2 1128
de190aef 1129 r = journal_file_link_data(f, o, p, hash);
cec736d2
LP
1130 if (r < 0)
1131 return r;
1132
48496df6
LP
1133 /* The linking might have altered the window, so let's
1134 * refresh our pointer */
1135 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1136 if (r < 0)
1137 return r;
1138
08c6f819
SL
1139 if (!data)
1140 eq = NULL;
1141 else
1142 eq = memchr(data, '=', size);
3c1668da 1143 if (eq && eq > data) {
748db592 1144 Object *fo = NULL;
3c1668da 1145 uint64_t fp;
3c1668da
LP
1146
1147 /* Create field object ... */
1148 r = journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, &fo, &fp);
1149 if (r < 0)
1150 return r;
1151
1152 /* ... and link it in. */
1153 o->data.next_field_offset = fo->field.head_data_offset;
1154 fo->field.head_data_offset = le64toh(p);
1155 }
1156
5996c7c2
LP
1157#ifdef HAVE_GCRYPT
1158 r = journal_file_hmac_put_object(f, OBJECT_DATA, o, p);
1159 if (r < 0)
1160 return r;
1161#endif
1162
cec736d2
LP
1163 if (ret)
1164 *ret = o;
1165
1166 if (offset)
de190aef 1167 *offset = p;
cec736d2
LP
1168
1169 return 0;
1170}
1171
1172uint64_t journal_file_entry_n_items(Object *o) {
1173 assert(o);
b588975f
LP
1174
1175 if (o->object.type != OBJECT_ENTRY)
1176 return 0;
cec736d2
LP
1177
1178 return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
1179}
1180
0284adc6 1181uint64_t journal_file_entry_array_n_items(Object *o) {
de190aef 1182 assert(o);
b588975f
LP
1183
1184 if (o->object.type != OBJECT_ENTRY_ARRAY)
1185 return 0;
de190aef
LP
1186
1187 return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
1188}
1189
fb9a24b6
LP
1190uint64_t journal_file_hash_table_n_items(Object *o) {
1191 assert(o);
b588975f
LP
1192
1193 if (o->object.type != OBJECT_DATA_HASH_TABLE &&
1194 o->object.type != OBJECT_FIELD_HASH_TABLE)
1195 return 0;
fb9a24b6
LP
1196
1197 return (le64toh(o->object.size) - offsetof(Object, hash_table.items)) / sizeof(HashItem);
1198}
1199
de190aef 1200static int link_entry_into_array(JournalFile *f,
4fd052ae
FC
1201 le64_t *first,
1202 le64_t *idx,
de190aef 1203 uint64_t p) {
cec736d2 1204 int r;
de190aef
LP
1205 uint64_t n = 0, ap = 0, q, i, a, hidx;
1206 Object *o;
1207
cec736d2 1208 assert(f);
c88cc6af 1209 assert(f->header);
de190aef
LP
1210 assert(first);
1211 assert(idx);
1212 assert(p > 0);
cec736d2 1213
de190aef
LP
1214 a = le64toh(*first);
1215 i = hidx = le64toh(*idx);
1216 while (a > 0) {
1217
1218 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1219 if (r < 0)
1220 return r;
cec736d2 1221
de190aef
LP
1222 n = journal_file_entry_array_n_items(o);
1223 if (i < n) {
1224 o->entry_array.items[i] = htole64(p);
1225 *idx = htole64(hidx + 1);
1226 return 0;
1227 }
cec736d2 1228
de190aef
LP
1229 i -= n;
1230 ap = a;
1231 a = le64toh(o->entry_array.next_entry_array_offset);
1232 }
1233
1234 if (hidx > n)
1235 n = (hidx+1) * 2;
1236 else
1237 n = n * 2;
1238
1239 if (n < 4)
1240 n = 4;
1241
1242 r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
1243 offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
1244 &o, &q);
cec736d2
LP
1245 if (r < 0)
1246 return r;
1247
feb12d3e 1248#ifdef HAVE_GCRYPT
5996c7c2 1249 r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, o, q);
b0af6f41
LP
1250 if (r < 0)
1251 return r;
feb12d3e 1252#endif
b0af6f41 1253
de190aef 1254 o->entry_array.items[i] = htole64(p);
cec736d2 1255
de190aef 1256 if (ap == 0)
7be3aa17 1257 *first = htole64(q);
cec736d2 1258 else {
de190aef 1259 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
cec736d2
LP
1260 if (r < 0)
1261 return r;
1262
de190aef
LP
1263 o->entry_array.next_entry_array_offset = htole64(q);
1264 }
cec736d2 1265
2dee23eb
LP
1266 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
1267 f->header->n_entry_arrays = htole64(le64toh(f->header->n_entry_arrays) + 1);
1268
de190aef
LP
1269 *idx = htole64(hidx + 1);
1270
1271 return 0;
1272}
cec736d2 1273
de190aef 1274static int link_entry_into_array_plus_one(JournalFile *f,
4fd052ae
FC
1275 le64_t *extra,
1276 le64_t *first,
1277 le64_t *idx,
de190aef
LP
1278 uint64_t p) {
1279
1280 int r;
1281
1282 assert(f);
1283 assert(extra);
1284 assert(first);
1285 assert(idx);
1286 assert(p > 0);
1287
1288 if (*idx == 0)
1289 *extra = htole64(p);
1290 else {
4fd052ae 1291 le64_t i;
de190aef 1292
7be3aa17 1293 i = htole64(le64toh(*idx) - 1);
de190aef
LP
1294 r = link_entry_into_array(f, first, &i, p);
1295 if (r < 0)
1296 return r;
cec736d2
LP
1297 }
1298
de190aef
LP
1299 *idx = htole64(le64toh(*idx) + 1);
1300 return 0;
1301}
1302
1303static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
1304 uint64_t p;
1305 int r;
1306 assert(f);
1307 assert(o);
1308 assert(offset > 0);
1309
1310 p = le64toh(o->entry.items[i].object_offset);
1311 if (p == 0)
1312 return -EINVAL;
1313
1314 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
cec736d2
LP
1315 if (r < 0)
1316 return r;
1317
de190aef
LP
1318 return link_entry_into_array_plus_one(f,
1319 &o->data.entry_offset,
1320 &o->data.entry_array_offset,
1321 &o->data.n_entries,
1322 offset);
cec736d2
LP
1323}
1324
1325static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
de190aef 1326 uint64_t n, i;
cec736d2
LP
1327 int r;
1328
1329 assert(f);
c88cc6af 1330 assert(f->header);
cec736d2
LP
1331 assert(o);
1332 assert(offset > 0);
b588975f
LP
1333
1334 if (o->object.type != OBJECT_ENTRY)
1335 return -EINVAL;
cec736d2 1336
b788cc23
LP
1337 __sync_synchronize();
1338
cec736d2 1339 /* Link up the entry itself */
de190aef
LP
1340 r = link_entry_into_array(f,
1341 &f->header->entry_array_offset,
1342 &f->header->n_entries,
1343 offset);
1344 if (r < 0)
1345 return r;
cec736d2 1346
507f22bd 1347 /* log_debug("=> %s seqnr=%"PRIu64" n_entries=%"PRIu64, f->path, o->entry.seqnum, f->header->n_entries); */
cec736d2 1348
de190aef 1349 if (f->header->head_entry_realtime == 0)
0ac38b70 1350 f->header->head_entry_realtime = o->entry.realtime;
cec736d2 1351
0ac38b70 1352 f->header->tail_entry_realtime = o->entry.realtime;
de190aef
LP
1353 f->header->tail_entry_monotonic = o->entry.monotonic;
1354
1355 f->tail_entry_monotonic_valid = true;
cec736d2
LP
1356
1357 /* Link up the items */
1358 n = journal_file_entry_n_items(o);
1359 for (i = 0; i < n; i++) {
1360 r = journal_file_link_entry_item(f, o, offset, i);
1361 if (r < 0)
1362 return r;
1363 }
1364
cec736d2
LP
1365 return 0;
1366}
1367
1368static int journal_file_append_entry_internal(
1369 JournalFile *f,
1370 const dual_timestamp *ts,
1371 uint64_t xor_hash,
1372 const EntryItem items[], unsigned n_items,
de190aef 1373 uint64_t *seqnum,
cec736d2
LP
1374 Object **ret, uint64_t *offset) {
1375 uint64_t np;
1376 uint64_t osize;
1377 Object *o;
1378 int r;
1379
1380 assert(f);
c88cc6af 1381 assert(f->header);
cec736d2 1382 assert(items || n_items == 0);
de190aef 1383 assert(ts);
cec736d2
LP
1384
1385 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
1386
de190aef 1387 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
cec736d2
LP
1388 if (r < 0)
1389 return r;
1390
d98cc1f2 1391 o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum));
75f32f04 1392 memcpy_safe(o->entry.items, items, n_items * sizeof(EntryItem));
de190aef
LP
1393 o->entry.realtime = htole64(ts->realtime);
1394 o->entry.monotonic = htole64(ts->monotonic);
cec736d2
LP
1395 o->entry.xor_hash = htole64(xor_hash);
1396 o->entry.boot_id = f->header->boot_id;
1397
feb12d3e 1398#ifdef HAVE_GCRYPT
5996c7c2 1399 r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np);
b0af6f41
LP
1400 if (r < 0)
1401 return r;
feb12d3e 1402#endif
b0af6f41 1403
cec736d2
LP
1404 r = journal_file_link_entry(f, o, np);
1405 if (r < 0)
1406 return r;
1407
1408 if (ret)
1409 *ret = o;
1410
1411 if (offset)
1412 *offset = np;
1413
1414 return 0;
1415}
1416
cf244689 1417void journal_file_post_change(JournalFile *f) {
50f20cfd
LP
1418 assert(f);
1419
1420 /* inotify() does not receive IN_MODIFY events from file
1421 * accesses done via mmap(). After each access we hence
1422 * trigger IN_MODIFY by truncating the journal file to its
1423 * current size which triggers IN_MODIFY. */
1424
bc85bfee
LP
1425 __sync_synchronize();
1426
50f20cfd 1427 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
e167d7fd 1428 log_debug_errno(errno, "Failed to truncate file to its own size: %m");
50f20cfd
LP
1429}
1430
7a24f3bf
VC
1431static int post_change_thunk(sd_event_source *timer, uint64_t usec, void *userdata) {
1432 assert(userdata);
1433
1434 journal_file_post_change(userdata);
1435
1436 return 1;
1437}
1438
1439static void schedule_post_change(JournalFile *f) {
1440 sd_event_source *timer;
1441 int enabled, r;
1442 uint64_t now;
1443
1444 assert(f);
1445 assert(f->post_change_timer);
1446
1447 timer = f->post_change_timer;
1448
1449 r = sd_event_source_get_enabled(timer, &enabled);
1450 if (r < 0) {
e167d7fd
LP
1451 log_debug_errno(r, "Failed to get ftruncate timer state: %m");
1452 goto fail;
7a24f3bf
VC
1453 }
1454
1455 if (enabled == SD_EVENT_ONESHOT)
1456 return;
1457
1458 r = sd_event_now(sd_event_source_get_event(timer), CLOCK_MONOTONIC, &now);
1459 if (r < 0) {
e167d7fd
LP
1460 log_debug_errno(r, "Failed to get clock's now for scheduling ftruncate: %m");
1461 goto fail;
7a24f3bf
VC
1462 }
1463
1464 r = sd_event_source_set_time(timer, now+f->post_change_timer_period);
1465 if (r < 0) {
e167d7fd
LP
1466 log_debug_errno(r, "Failed to set time for scheduling ftruncate: %m");
1467 goto fail;
7a24f3bf
VC
1468 }
1469
1470 r = sd_event_source_set_enabled(timer, SD_EVENT_ONESHOT);
1471 if (r < 0) {
e167d7fd
LP
1472 log_debug_errno(r, "Failed to enable scheduled ftruncate: %m");
1473 goto fail;
7a24f3bf 1474 }
e167d7fd
LP
1475
1476 return;
1477
1478fail:
1479 /* On failure, let's simply post the change immediately. */
1480 journal_file_post_change(f);
7a24f3bf
VC
1481}
1482
1483/* Enable coalesced change posting in a timer on the provided sd_event instance */
1484int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t) {
1485 _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL;
1486 int r;
1487
1488 assert(f);
1489 assert_return(!f->post_change_timer, -EINVAL);
1490 assert(e);
1491 assert(t);
1492
1493 r = sd_event_add_time(e, &timer, CLOCK_MONOTONIC, 0, 0, post_change_thunk, f);
1494 if (r < 0)
1495 return r;
1496
1497 r = sd_event_source_set_enabled(timer, SD_EVENT_OFF);
1498 if (r < 0)
1499 return r;
1500
1501 f->post_change_timer = timer;
1502 timer = NULL;
1503 f->post_change_timer_period = t;
1504
1505 return r;
1506}
1507
1f2da9ec
LP
1508static int entry_item_cmp(const void *_a, const void *_b) {
1509 const EntryItem *a = _a, *b = _b;
1510
1511 if (le64toh(a->object_offset) < le64toh(b->object_offset))
1512 return -1;
1513 if (le64toh(a->object_offset) > le64toh(b->object_offset))
1514 return 1;
1515 return 0;
1516}
1517
de190aef 1518int 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) {
cec736d2
LP
1519 unsigned i;
1520 EntryItem *items;
1521 int r;
1522 uint64_t xor_hash = 0;
de190aef 1523 struct dual_timestamp _ts;
cec736d2
LP
1524
1525 assert(f);
c88cc6af 1526 assert(f->header);
cec736d2
LP
1527 assert(iovec || n_iovec == 0);
1528
de190aef
LP
1529 if (!ts) {
1530 dual_timestamp_get(&_ts);
1531 ts = &_ts;
1532 }
1533
feb12d3e 1534#ifdef HAVE_GCRYPT
7560fffc
LP
1535 r = journal_file_maybe_append_tag(f, ts->realtime);
1536 if (r < 0)
1537 return r;
feb12d3e 1538#endif
7560fffc 1539
64825d3c 1540 /* alloca() can't take 0, hence let's allocate at least one */
9607d947 1541 items = alloca(sizeof(EntryItem) * MAX(1u, n_iovec));
cec736d2
LP
1542
1543 for (i = 0; i < n_iovec; i++) {
1544 uint64_t p;
1545 Object *o;
1546
1547 r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
1548 if (r < 0)
cf244689 1549 return r;
cec736d2
LP
1550
1551 xor_hash ^= le64toh(o->data.hash);
1552 items[i].object_offset = htole64(p);
de7b95cd 1553 items[i].hash = o->data.hash;
cec736d2
LP
1554 }
1555
1f2da9ec
LP
1556 /* Order by the position on disk, in order to improve seek
1557 * times for rotating media. */
7ff7394d 1558 qsort_safe(items, n_iovec, sizeof(EntryItem), entry_item_cmp);
1f2da9ec 1559
de190aef 1560 r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
cec736d2 1561
fa6ac760
LP
1562 /* If the memory mapping triggered a SIGBUS then we return an
1563 * IO error and ignore the error code passed down to us, since
1564 * it is very likely just an effect of a nullified replacement
1565 * mapping page */
1566
1567 if (mmap_cache_got_sigbus(f->mmap, f->fd))
1568 r = -EIO;
1569
7a24f3bf
VC
1570 if (f->post_change_timer)
1571 schedule_post_change(f);
1572 else
1573 journal_file_post_change(f);
50f20cfd 1574
cec736d2
LP
1575 return r;
1576}
1577
a4bcff5b 1578typedef struct ChainCacheItem {
fb099c8d 1579 uint64_t first; /* the array at the beginning of the chain */
a4bcff5b
LP
1580 uint64_t array; /* the cached array */
1581 uint64_t begin; /* the first item in the cached array */
1582 uint64_t total; /* the total number of items in all arrays before this one in the chain */
f268980d 1583 uint64_t last_index; /* the last index we looked at, to optimize locality when bisecting */
a4bcff5b
LP
1584} ChainCacheItem;
1585
1586static void chain_cache_put(
4743015d 1587 OrderedHashmap *h,
a4bcff5b
LP
1588 ChainCacheItem *ci,
1589 uint64_t first,
1590 uint64_t array,
1591 uint64_t begin,
f268980d
LP
1592 uint64_t total,
1593 uint64_t last_index) {
a4bcff5b
LP
1594
1595 if (!ci) {
34741aa3
LP
1596 /* If the chain item to cache for this chain is the
1597 * first one it's not worth caching anything */
1598 if (array == first)
1599 return;
1600
29433089 1601 if (ordered_hashmap_size(h) >= CHAIN_CACHE_MAX) {
4743015d 1602 ci = ordered_hashmap_steal_first(h);
29433089
LP
1603 assert(ci);
1604 } else {
a4bcff5b
LP
1605 ci = new(ChainCacheItem, 1);
1606 if (!ci)
1607 return;
1608 }
1609
1610 ci->first = first;
1611
4743015d 1612 if (ordered_hashmap_put(h, &ci->first, ci) < 0) {
a4bcff5b
LP
1613 free(ci);
1614 return;
1615 }
1616 } else
1617 assert(ci->first == first);
1618
1619 ci->array = array;
1620 ci->begin = begin;
1621 ci->total = total;
f268980d 1622 ci->last_index = last_index;
a4bcff5b
LP
1623}
1624
f268980d
LP
1625static int generic_array_get(
1626 JournalFile *f,
1627 uint64_t first,
1628 uint64_t i,
1629 Object **ret, uint64_t *offset) {
de190aef 1630
cec736d2 1631 Object *o;
a4bcff5b 1632 uint64_t p = 0, a, t = 0;
cec736d2 1633 int r;
a4bcff5b 1634 ChainCacheItem *ci;
cec736d2
LP
1635
1636 assert(f);
1637
de190aef 1638 a = first;
a4bcff5b
LP
1639
1640 /* Try the chain cache first */
4743015d 1641 ci = ordered_hashmap_get(f->chain_cache, &first);
a4bcff5b
LP
1642 if (ci && i > ci->total) {
1643 a = ci->array;
1644 i -= ci->total;
1645 t = ci->total;
1646 }
1647
de190aef 1648 while (a > 0) {
a4bcff5b 1649 uint64_t k;
cec736d2 1650
de190aef
LP
1651 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1652 if (r < 0)
1653 return r;
cec736d2 1654
a4bcff5b
LP
1655 k = journal_file_entry_array_n_items(o);
1656 if (i < k) {
de190aef 1657 p = le64toh(o->entry_array.items[i]);
a4bcff5b 1658 goto found;
cec736d2
LP
1659 }
1660
a4bcff5b
LP
1661 i -= k;
1662 t += k;
de190aef
LP
1663 a = le64toh(o->entry_array.next_entry_array_offset);
1664 }
1665
a4bcff5b
LP
1666 return 0;
1667
1668found:
1669 /* Let's cache this item for the next invocation */
af13a6b0 1670 chain_cache_put(f->chain_cache, ci, first, a, le64toh(o->entry_array.items[0]), t, i);
de190aef
LP
1671
1672 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1673 if (r < 0)
1674 return r;
1675
1676 if (ret)
1677 *ret = o;
1678
1679 if (offset)
1680 *offset = p;
1681
1682 return 1;
1683}
1684
f268980d
LP
1685static int generic_array_get_plus_one(
1686 JournalFile *f,
1687 uint64_t extra,
1688 uint64_t first,
1689 uint64_t i,
1690 Object **ret, uint64_t *offset) {
de190aef
LP
1691
1692 Object *o;
1693
1694 assert(f);
1695
1696 if (i == 0) {
1697 int r;
1698
1699 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
cec736d2
LP
1700 if (r < 0)
1701 return r;
1702
de190aef
LP
1703 if (ret)
1704 *ret = o;
cec736d2 1705
de190aef
LP
1706 if (offset)
1707 *offset = extra;
cec736d2 1708
de190aef 1709 return 1;
cec736d2
LP
1710 }
1711
de190aef
LP
1712 return generic_array_get(f, first, i-1, ret, offset);
1713}
cec736d2 1714
de190aef
LP
1715enum {
1716 TEST_FOUND,
1717 TEST_LEFT,
1718 TEST_RIGHT
1719};
cec736d2 1720
f268980d
LP
1721static int generic_array_bisect(
1722 JournalFile *f,
1723 uint64_t first,
1724 uint64_t n,
1725 uint64_t needle,
1726 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1727 direction_t direction,
1728 Object **ret,
1729 uint64_t *offset,
1730 uint64_t *idx) {
1731
1732 uint64_t a, p, t = 0, i = 0, last_p = 0, last_index = (uint64_t) -1;
de190aef
LP
1733 bool subtract_one = false;
1734 Object *o, *array = NULL;
1735 int r;
a4bcff5b 1736 ChainCacheItem *ci;
cec736d2 1737
de190aef
LP
1738 assert(f);
1739 assert(test_object);
cec736d2 1740
a4bcff5b 1741 /* Start with the first array in the chain */
de190aef 1742 a = first;
a4bcff5b 1743
4743015d 1744 ci = ordered_hashmap_get(f->chain_cache, &first);
a4bcff5b
LP
1745 if (ci && n > ci->total) {
1746 /* Ah, we have iterated this bisection array chain
1747 * previously! Let's see if we can skip ahead in the
1748 * chain, as far as the last time. But we can't jump
1749 * backwards in the chain, so let's check that
1750 * first. */
1751
1752 r = test_object(f, ci->begin, needle);
1753 if (r < 0)
1754 return r;
1755
1756 if (r == TEST_LEFT) {
f268980d 1757 /* OK, what we are looking for is right of the
a4bcff5b
LP
1758 * begin of this EntryArray, so let's jump
1759 * straight to previously cached array in the
1760 * chain */
1761
1762 a = ci->array;
1763 n -= ci->total;
1764 t = ci->total;
f268980d 1765 last_index = ci->last_index;
a4bcff5b
LP
1766 }
1767 }
1768
de190aef
LP
1769 while (a > 0) {
1770 uint64_t left, right, k, lp;
1771
1772 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
cec736d2
LP
1773 if (r < 0)
1774 return r;
1775
de190aef
LP
1776 k = journal_file_entry_array_n_items(array);
1777 right = MIN(k, n);
1778 if (right <= 0)
1779 return 0;
cec736d2 1780
de190aef
LP
1781 i = right - 1;
1782 lp = p = le64toh(array->entry_array.items[i]);
1783 if (p <= 0)
1784 return -EBADMSG;
cec736d2 1785
de190aef
LP
1786 r = test_object(f, p, needle);
1787 if (r < 0)
1788 return r;
cec736d2 1789
de190aef
LP
1790 if (r == TEST_FOUND)
1791 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1792
1793 if (r == TEST_RIGHT) {
1794 left = 0;
1795 right -= 1;
f268980d
LP
1796
1797 if (last_index != (uint64_t) -1) {
1798 assert(last_index <= right);
1799
1800 /* If we cached the last index we
1801 * looked at, let's try to not to jump
1802 * too wildly around and see if we can
1803 * limit the range to look at early to
1804 * the immediate neighbors of the last
1805 * index we looked at. */
1806
1807 if (last_index > 0) {
1808 uint64_t x = last_index - 1;
1809
1810 p = le64toh(array->entry_array.items[x]);
1811 if (p <= 0)
1812 return -EBADMSG;
1813
1814 r = test_object(f, p, needle);
1815 if (r < 0)
1816 return r;
1817
1818 if (r == TEST_FOUND)
1819 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1820
1821 if (r == TEST_RIGHT)
1822 right = x;
1823 else
1824 left = x + 1;
1825 }
1826
1827 if (last_index < right) {
1828 uint64_t y = last_index + 1;
1829
1830 p = le64toh(array->entry_array.items[y]);
1831 if (p <= 0)
1832 return -EBADMSG;
1833
1834 r = test_object(f, p, needle);
1835 if (r < 0)
1836 return r;
1837
1838 if (r == TEST_FOUND)
1839 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1840
1841 if (r == TEST_RIGHT)
1842 right = y;
1843 else
1844 left = y + 1;
1845 }
f268980d
LP
1846 }
1847
de190aef
LP
1848 for (;;) {
1849 if (left == right) {
1850 if (direction == DIRECTION_UP)
1851 subtract_one = true;
1852
1853 i = left;
1854 goto found;
1855 }
1856
1857 assert(left < right);
de190aef 1858 i = (left + right) / 2;
f268980d 1859
de190aef
LP
1860 p = le64toh(array->entry_array.items[i]);
1861 if (p <= 0)
1862 return -EBADMSG;
1863
1864 r = test_object(f, p, needle);
1865 if (r < 0)
1866 return r;
cec736d2 1867
de190aef
LP
1868 if (r == TEST_FOUND)
1869 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1870
1871 if (r == TEST_RIGHT)
1872 right = i;
1873 else
1874 left = i + 1;
1875 }
1876 }
1877
2173cbf8 1878 if (k >= n) {
cbdca852
LP
1879 if (direction == DIRECTION_UP) {
1880 i = n;
1881 subtract_one = true;
1882 goto found;
1883 }
1884
cec736d2 1885 return 0;
cbdca852 1886 }
cec736d2 1887
de190aef
LP
1888 last_p = lp;
1889
1890 n -= k;
1891 t += k;
f268980d 1892 last_index = (uint64_t) -1;
de190aef 1893 a = le64toh(array->entry_array.next_entry_array_offset);
cec736d2
LP
1894 }
1895
1896 return 0;
de190aef
LP
1897
1898found:
1899 if (subtract_one && t == 0 && i == 0)
1900 return 0;
1901
a4bcff5b 1902 /* Let's cache this item for the next invocation */
af13a6b0 1903 chain_cache_put(f->chain_cache, ci, first, a, le64toh(array->entry_array.items[0]), t, subtract_one ? (i > 0 ? i-1 : (uint64_t) -1) : i);
a4bcff5b 1904
de190aef
LP
1905 if (subtract_one && i == 0)
1906 p = last_p;
1907 else if (subtract_one)
1908 p = le64toh(array->entry_array.items[i-1]);
1909 else
1910 p = le64toh(array->entry_array.items[i]);
1911
1912 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
1913 if (r < 0)
1914 return r;
1915
1916 if (ret)
1917 *ret = o;
1918
1919 if (offset)
1920 *offset = p;
1921
1922 if (idx)
cbdca852 1923 *idx = t + i + (subtract_one ? -1 : 0);
de190aef
LP
1924
1925 return 1;
cec736d2
LP
1926}
1927
f268980d
LP
1928static int generic_array_bisect_plus_one(
1929 JournalFile *f,
1930 uint64_t extra,
1931 uint64_t first,
1932 uint64_t n,
1933 uint64_t needle,
1934 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
1935 direction_t direction,
1936 Object **ret,
1937 uint64_t *offset,
1938 uint64_t *idx) {
de190aef 1939
cec736d2 1940 int r;
cbdca852
LP
1941 bool step_back = false;
1942 Object *o;
cec736d2
LP
1943
1944 assert(f);
de190aef 1945 assert(test_object);
cec736d2 1946
de190aef
LP
1947 if (n <= 0)
1948 return 0;
cec736d2 1949
de190aef
LP
1950 /* This bisects the array in object 'first', but first checks
1951 * an extra */
de190aef
LP
1952 r = test_object(f, extra, needle);
1953 if (r < 0)
1954 return r;
a536e261
LP
1955
1956 if (r == TEST_FOUND)
1957 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1958
cbdca852
LP
1959 /* if we are looking with DIRECTION_UP then we need to first
1960 see if in the actual array there is a matching entry, and
1961 return the last one of that. But if there isn't any we need
1962 to return this one. Hence remember this, and return it
1963 below. */
1964 if (r == TEST_LEFT)
1965 step_back = direction == DIRECTION_UP;
de190aef 1966
cbdca852
LP
1967 if (r == TEST_RIGHT) {
1968 if (direction == DIRECTION_DOWN)
1969 goto found;
1970 else
1971 return 0;
a536e261 1972 }
cec736d2 1973
de190aef
LP
1974 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
1975
cbdca852
LP
1976 if (r == 0 && step_back)
1977 goto found;
1978
ecf68b1d 1979 if (r > 0 && idx)
de190aef
LP
1980 (*idx) ++;
1981
1982 return r;
cbdca852
LP
1983
1984found:
1985 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
1986 if (r < 0)
1987 return r;
1988
1989 if (ret)
1990 *ret = o;
1991
1992 if (offset)
1993 *offset = extra;
1994
1995 if (idx)
1996 *idx = 0;
1997
1998 return 1;
1999}
2000
44a6b1b6 2001_pure_ static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
cbdca852
LP
2002 assert(f);
2003 assert(p > 0);
2004
2005 if (p == needle)
2006 return TEST_FOUND;
2007 else if (p < needle)
2008 return TEST_LEFT;
2009 else
2010 return TEST_RIGHT;
2011}
2012
de190aef
LP
2013static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
2014 Object *o;
2015 int r;
2016
2017 assert(f);
2018 assert(p > 0);
2019
2020 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
cec736d2
LP
2021 if (r < 0)
2022 return r;
2023
de190aef
LP
2024 if (le64toh(o->entry.seqnum) == needle)
2025 return TEST_FOUND;
2026 else if (le64toh(o->entry.seqnum) < needle)
2027 return TEST_LEFT;
2028 else
2029 return TEST_RIGHT;
2030}
cec736d2 2031
de190aef
LP
2032int journal_file_move_to_entry_by_seqnum(
2033 JournalFile *f,
2034 uint64_t seqnum,
2035 direction_t direction,
2036 Object **ret,
2037 uint64_t *offset) {
c88cc6af
VC
2038 assert(f);
2039 assert(f->header);
de190aef
LP
2040
2041 return generic_array_bisect(f,
2042 le64toh(f->header->entry_array_offset),
2043 le64toh(f->header->n_entries),
2044 seqnum,
2045 test_object_seqnum,
2046 direction,
2047 ret, offset, NULL);
2048}
cec736d2 2049
de190aef
LP
2050static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
2051 Object *o;
2052 int r;
2053
2054 assert(f);
2055 assert(p > 0);
2056
2057 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
2058 if (r < 0)
2059 return r;
2060
2061 if (le64toh(o->entry.realtime) == needle)
2062 return TEST_FOUND;
2063 else if (le64toh(o->entry.realtime) < needle)
2064 return TEST_LEFT;
2065 else
2066 return TEST_RIGHT;
cec736d2
LP
2067}
2068
de190aef
LP
2069int journal_file_move_to_entry_by_realtime(
2070 JournalFile *f,
2071 uint64_t realtime,
2072 direction_t direction,
2073 Object **ret,
2074 uint64_t *offset) {
c88cc6af
VC
2075 assert(f);
2076 assert(f->header);
de190aef
LP
2077
2078 return generic_array_bisect(f,
2079 le64toh(f->header->entry_array_offset),
2080 le64toh(f->header->n_entries),
2081 realtime,
2082 test_object_realtime,
2083 direction,
2084 ret, offset, NULL);
2085}
2086
2087static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
2088 Object *o;
2089 int r;
2090
2091 assert(f);
2092 assert(p > 0);
2093
2094 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
2095 if (r < 0)
2096 return r;
2097
2098 if (le64toh(o->entry.monotonic) == needle)
2099 return TEST_FOUND;
2100 else if (le64toh(o->entry.monotonic) < needle)
2101 return TEST_LEFT;
2102 else
2103 return TEST_RIGHT;
2104}
2105
2a560338 2106static int find_data_object_by_boot_id(
47838ab3
ZJS
2107 JournalFile *f,
2108 sd_id128_t boot_id,
2109 Object **o,
2110 uint64_t *b) {
2a560338 2111
47838ab3
ZJS
2112 char t[sizeof("_BOOT_ID=")-1 + 32 + 1] = "_BOOT_ID=";
2113
2114 sd_id128_to_string(boot_id, t + 9);
2115 return journal_file_find_data_object(f, t, sizeof(t) - 1, o, b);
2116}
2117
de190aef
LP
2118int journal_file_move_to_entry_by_monotonic(
2119 JournalFile *f,
2120 sd_id128_t boot_id,
2121 uint64_t monotonic,
2122 direction_t direction,
2123 Object **ret,
2124 uint64_t *offset) {
2125
de190aef
LP
2126 Object *o;
2127 int r;
2128
cbdca852 2129 assert(f);
de190aef 2130
47838ab3 2131 r = find_data_object_by_boot_id(f, boot_id, &o, NULL);
de190aef
LP
2132 if (r < 0)
2133 return r;
cbdca852 2134 if (r == 0)
de190aef
LP
2135 return -ENOENT;
2136
2137 return generic_array_bisect_plus_one(f,
2138 le64toh(o->data.entry_offset),
2139 le64toh(o->data.entry_array_offset),
2140 le64toh(o->data.n_entries),
2141 monotonic,
2142 test_object_monotonic,
2143 direction,
2144 ret, offset, NULL);
2145}
2146
1fc605b0 2147void journal_file_reset_location(JournalFile *f) {
6573ef05 2148 f->location_type = LOCATION_HEAD;
1fc605b0 2149 f->current_offset = 0;
6573ef05
MS
2150 f->current_seqnum = 0;
2151 f->current_realtime = 0;
2152 f->current_monotonic = 0;
2153 zero(f->current_boot_id);
2154 f->current_xor_hash = 0;
2155}
2156
950c07d4 2157void journal_file_save_location(JournalFile *f, Object *o, uint64_t offset) {
6573ef05
MS
2158 f->location_type = LOCATION_SEEK;
2159 f->current_offset = offset;
2160 f->current_seqnum = le64toh(o->entry.seqnum);
2161 f->current_realtime = le64toh(o->entry.realtime);
2162 f->current_monotonic = le64toh(o->entry.monotonic);
2163 f->current_boot_id = o->entry.boot_id;
2164 f->current_xor_hash = le64toh(o->entry.xor_hash);
1fc605b0
MS
2165}
2166
d8ae66d7
MS
2167int journal_file_compare_locations(JournalFile *af, JournalFile *bf) {
2168 assert(af);
c88cc6af 2169 assert(af->header);
d8ae66d7 2170 assert(bf);
c88cc6af 2171 assert(bf->header);
d8ae66d7
MS
2172 assert(af->location_type == LOCATION_SEEK);
2173 assert(bf->location_type == LOCATION_SEEK);
2174
2175 /* If contents and timestamps match, these entries are
2176 * identical, even if the seqnum does not match */
2177 if (sd_id128_equal(af->current_boot_id, bf->current_boot_id) &&
2178 af->current_monotonic == bf->current_monotonic &&
2179 af->current_realtime == bf->current_realtime &&
2180 af->current_xor_hash == bf->current_xor_hash)
2181 return 0;
2182
2183 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
2184
2185 /* If this is from the same seqnum source, compare
2186 * seqnums */
2187 if (af->current_seqnum < bf->current_seqnum)
2188 return -1;
2189 if (af->current_seqnum > bf->current_seqnum)
2190 return 1;
2191
2192 /* Wow! This is weird, different data but the same
2193 * seqnums? Something is borked, but let's make the
2194 * best of it and compare by time. */
2195 }
2196
2197 if (sd_id128_equal(af->current_boot_id, bf->current_boot_id)) {
2198
2199 /* If the boot id matches, compare monotonic time */
2200 if (af->current_monotonic < bf->current_monotonic)
2201 return -1;
2202 if (af->current_monotonic > bf->current_monotonic)
2203 return 1;
2204 }
2205
2206 /* Otherwise, compare UTC time */
2207 if (af->current_realtime < bf->current_realtime)
2208 return -1;
2209 if (af->current_realtime > bf->current_realtime)
2210 return 1;
2211
2212 /* Finally, compare by contents */
2213 if (af->current_xor_hash < bf->current_xor_hash)
2214 return -1;
2215 if (af->current_xor_hash > bf->current_xor_hash)
2216 return 1;
2217
2218 return 0;
2219}
2220
de190aef
LP
2221int journal_file_next_entry(
2222 JournalFile *f,
f534928a 2223 uint64_t p,
de190aef
LP
2224 direction_t direction,
2225 Object **ret, uint64_t *offset) {
2226
fb099c8d 2227 uint64_t i, n, ofs;
cec736d2
LP
2228 int r;
2229
2230 assert(f);
c88cc6af 2231 assert(f->header);
de190aef
LP
2232
2233 n = le64toh(f->header->n_entries);
2234 if (n <= 0)
2235 return 0;
cec736d2 2236
f534928a 2237 if (p == 0)
de190aef 2238 i = direction == DIRECTION_DOWN ? 0 : n - 1;
cec736d2 2239 else {
de190aef
LP
2240 r = generic_array_bisect(f,
2241 le64toh(f->header->entry_array_offset),
2242 le64toh(f->header->n_entries),
2243 p,
2244 test_object_offset,
2245 DIRECTION_DOWN,
2246 NULL, NULL,
2247 &i);
2248 if (r <= 0)
2249 return r;
2250
2251 if (direction == DIRECTION_DOWN) {
2252 if (i >= n - 1)
2253 return 0;
2254
2255 i++;
2256 } else {
2257 if (i <= 0)
2258 return 0;
2259
2260 i--;
2261 }
cec736d2
LP
2262 }
2263
de190aef 2264 /* And jump to it */
fb099c8d
ZJS
2265 r = generic_array_get(f,
2266 le64toh(f->header->entry_array_offset),
2267 i,
2268 ret, &ofs);
2269 if (r <= 0)
2270 return r;
2271
2272 if (p > 0 &&
2273 (direction == DIRECTION_DOWN ? ofs <= p : ofs >= p)) {
2274 log_debug("%s: entry array corrupted at entry %"PRIu64,
2275 f->path, i);
2276 return -EBADMSG;
2277 }
2278
2279 if (offset)
2280 *offset = ofs;
2281
2282 return 1;
de190aef 2283}
cec736d2 2284
de190aef
LP
2285int journal_file_next_entry_for_data(
2286 JournalFile *f,
2287 Object *o, uint64_t p,
2288 uint64_t data_offset,
2289 direction_t direction,
2290 Object **ret, uint64_t *offset) {
2291
2292 uint64_t n, i;
cec736d2 2293 int r;
de190aef 2294 Object *d;
cec736d2
LP
2295
2296 assert(f);
de190aef 2297 assert(p > 0 || !o);
cec736d2 2298
de190aef 2299 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
466ccd92 2300 if (r < 0)
de190aef 2301 return r;
cec736d2 2302
de190aef
LP
2303 n = le64toh(d->data.n_entries);
2304 if (n <= 0)
2305 return n;
cec736d2 2306
de190aef
LP
2307 if (!o)
2308 i = direction == DIRECTION_DOWN ? 0 : n - 1;
2309 else {
2310 if (o->object.type != OBJECT_ENTRY)
2311 return -EINVAL;
cec736d2 2312
de190aef
LP
2313 r = generic_array_bisect_plus_one(f,
2314 le64toh(d->data.entry_offset),
2315 le64toh(d->data.entry_array_offset),
2316 le64toh(d->data.n_entries),
2317 p,
2318 test_object_offset,
2319 DIRECTION_DOWN,
2320 NULL, NULL,
2321 &i);
2322
2323 if (r <= 0)
cec736d2
LP
2324 return r;
2325
de190aef
LP
2326 if (direction == DIRECTION_DOWN) {
2327 if (i >= n - 1)
2328 return 0;
cec736d2 2329
de190aef
LP
2330 i++;
2331 } else {
2332 if (i <= 0)
2333 return 0;
cec736d2 2334
de190aef
LP
2335 i--;
2336 }
cec736d2 2337
de190aef 2338 }
cec736d2 2339
de190aef
LP
2340 return generic_array_get_plus_one(f,
2341 le64toh(d->data.entry_offset),
2342 le64toh(d->data.entry_array_offset),
2343 i,
2344 ret, offset);
2345}
cec736d2 2346
cbdca852
LP
2347int journal_file_move_to_entry_by_offset_for_data(
2348 JournalFile *f,
2349 uint64_t data_offset,
2350 uint64_t p,
2351 direction_t direction,
2352 Object **ret, uint64_t *offset) {
2353
2354 int r;
2355 Object *d;
2356
2357 assert(f);
2358
2359 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
2360 if (r < 0)
2361 return r;
2362
2363 return generic_array_bisect_plus_one(f,
2364 le64toh(d->data.entry_offset),
2365 le64toh(d->data.entry_array_offset),
2366 le64toh(d->data.n_entries),
2367 p,
2368 test_object_offset,
2369 direction,
2370 ret, offset, NULL);
2371}
2372
2373int journal_file_move_to_entry_by_monotonic_for_data(
2374 JournalFile *f,
2375 uint64_t data_offset,
2376 sd_id128_t boot_id,
2377 uint64_t monotonic,
2378 direction_t direction,
2379 Object **ret, uint64_t *offset) {
2380
cbdca852
LP
2381 Object *o, *d;
2382 int r;
2383 uint64_t b, z;
2384
2385 assert(f);
2386
2387 /* First, seek by time */
47838ab3 2388 r = find_data_object_by_boot_id(f, boot_id, &o, &b);
cbdca852
LP
2389 if (r < 0)
2390 return r;
2391 if (r == 0)
2392 return -ENOENT;
2393
2394 r = generic_array_bisect_plus_one(f,
2395 le64toh(o->data.entry_offset),
2396 le64toh(o->data.entry_array_offset),
2397 le64toh(o->data.n_entries),
2398 monotonic,
2399 test_object_monotonic,
2400 direction,
2401 NULL, &z, NULL);
2402 if (r <= 0)
2403 return r;
2404
2405 /* And now, continue seeking until we find an entry that
2406 * exists in both bisection arrays */
2407
2408 for (;;) {
2409 Object *qo;
2410 uint64_t p, q;
2411
2412 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
2413 if (r < 0)
2414 return r;
2415
2416 r = generic_array_bisect_plus_one(f,
2417 le64toh(d->data.entry_offset),
2418 le64toh(d->data.entry_array_offset),
2419 le64toh(d->data.n_entries),
2420 z,
2421 test_object_offset,
2422 direction,
2423 NULL, &p, NULL);
2424 if (r <= 0)
2425 return r;
2426
2427 r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
2428 if (r < 0)
2429 return r;
2430
2431 r = generic_array_bisect_plus_one(f,
2432 le64toh(o->data.entry_offset),
2433 le64toh(o->data.entry_array_offset),
2434 le64toh(o->data.n_entries),
2435 p,
2436 test_object_offset,
2437 direction,
2438 &qo, &q, NULL);
2439
2440 if (r <= 0)
2441 return r;
2442
2443 if (p == q) {
2444 if (ret)
2445 *ret = qo;
2446 if (offset)
2447 *offset = q;
2448
2449 return 1;
2450 }
2451
2452 z = q;
2453 }
cbdca852
LP
2454}
2455
de190aef
LP
2456int journal_file_move_to_entry_by_seqnum_for_data(
2457 JournalFile *f,
2458 uint64_t data_offset,
2459 uint64_t seqnum,
2460 direction_t direction,
2461 Object **ret, uint64_t *offset) {
cec736d2 2462
de190aef
LP
2463 Object *d;
2464 int r;
cec736d2 2465
91a31dde
LP
2466 assert(f);
2467
de190aef 2468 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
91a31dde 2469 if (r < 0)
de190aef 2470 return r;
cec736d2 2471
de190aef
LP
2472 return generic_array_bisect_plus_one(f,
2473 le64toh(d->data.entry_offset),
2474 le64toh(d->data.entry_array_offset),
2475 le64toh(d->data.n_entries),
2476 seqnum,
2477 test_object_seqnum,
2478 direction,
2479 ret, offset, NULL);
2480}
cec736d2 2481
de190aef
LP
2482int journal_file_move_to_entry_by_realtime_for_data(
2483 JournalFile *f,
2484 uint64_t data_offset,
2485 uint64_t realtime,
2486 direction_t direction,
2487 Object **ret, uint64_t *offset) {
2488
2489 Object *d;
2490 int r;
2491
91a31dde
LP
2492 assert(f);
2493
de190aef 2494 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
91a31dde 2495 if (r < 0)
de190aef
LP
2496 return r;
2497
2498 return generic_array_bisect_plus_one(f,
2499 le64toh(d->data.entry_offset),
2500 le64toh(d->data.entry_array_offset),
2501 le64toh(d->data.n_entries),
2502 realtime,
2503 test_object_realtime,
2504 direction,
2505 ret, offset, NULL);
cec736d2
LP
2506}
2507
0284adc6 2508void journal_file_dump(JournalFile *f) {
7560fffc 2509 Object *o;
7560fffc 2510 int r;
0284adc6 2511 uint64_t p;
7560fffc
LP
2512
2513 assert(f);
c88cc6af 2514 assert(f->header);
7560fffc 2515
0284adc6 2516 journal_file_print_header(f);
7560fffc 2517
0284adc6
LP
2518 p = le64toh(f->header->header_size);
2519 while (p != 0) {
d05089d8 2520 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o);
0284adc6
LP
2521 if (r < 0)
2522 goto fail;
7560fffc 2523
0284adc6 2524 switch (o->object.type) {
d98cc1f2 2525
0284adc6
LP
2526 case OBJECT_UNUSED:
2527 printf("Type: OBJECT_UNUSED\n");
2528 break;
d98cc1f2 2529
0284adc6
LP
2530 case OBJECT_DATA:
2531 printf("Type: OBJECT_DATA\n");
2532 break;
7560fffc 2533
3c1668da
LP
2534 case OBJECT_FIELD:
2535 printf("Type: OBJECT_FIELD\n");
2536 break;
2537
0284adc6 2538 case OBJECT_ENTRY:
507f22bd
ZJS
2539 printf("Type: OBJECT_ENTRY seqnum=%"PRIu64" monotonic=%"PRIu64" realtime=%"PRIu64"\n",
2540 le64toh(o->entry.seqnum),
2541 le64toh(o->entry.monotonic),
2542 le64toh(o->entry.realtime));
0284adc6 2543 break;
7560fffc 2544
0284adc6
LP
2545 case OBJECT_FIELD_HASH_TABLE:
2546 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
2547 break;
7560fffc 2548
0284adc6
LP
2549 case OBJECT_DATA_HASH_TABLE:
2550 printf("Type: OBJECT_DATA_HASH_TABLE\n");
2551 break;
7560fffc 2552
0284adc6
LP
2553 case OBJECT_ENTRY_ARRAY:
2554 printf("Type: OBJECT_ENTRY_ARRAY\n");
2555 break;
7560fffc 2556
0284adc6 2557 case OBJECT_TAG:
507f22bd
ZJS
2558 printf("Type: OBJECT_TAG seqnum=%"PRIu64" epoch=%"PRIu64"\n",
2559 le64toh(o->tag.seqnum),
2560 le64toh(o->tag.epoch));
0284adc6 2561 break;
3c1668da
LP
2562
2563 default:
8facc349 2564 printf("Type: unknown (%i)\n", o->object.type);
3c1668da 2565 break;
0284adc6 2566 }
7560fffc 2567
d89c8fdf
ZJS
2568 if (o->object.flags & OBJECT_COMPRESSION_MASK)
2569 printf("Flags: %s\n",
2570 object_compressed_to_string(o->object.flags & OBJECT_COMPRESSION_MASK));
7560fffc 2571
0284adc6
LP
2572 if (p == le64toh(f->header->tail_object_offset))
2573 p = 0;
2574 else
2575 p = p + ALIGN64(le64toh(o->object.size));
2576 }
7560fffc 2577
0284adc6
LP
2578 return;
2579fail:
2580 log_error("File corrupt");
7560fffc
LP
2581}
2582
718fe4b1
ZJS
2583static const char* format_timestamp_safe(char *buf, size_t l, usec_t t) {
2584 const char *x;
2585
2586 x = format_timestamp(buf, l, t);
2587 if (x)
2588 return x;
2589 return " --- ";
2590}
2591
0284adc6 2592void journal_file_print_header(JournalFile *f) {
2765b7bb 2593 char a[33], b[33], c[33], d[33];
ed375beb 2594 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX], z[FORMAT_TIMESTAMP_MAX];
a1a03e30
LP
2595 struct stat st;
2596 char bytes[FORMAT_BYTES_MAX];
7560fffc
LP
2597
2598 assert(f);
c88cc6af 2599 assert(f->header);
7560fffc 2600
0284adc6
LP
2601 printf("File Path: %s\n"
2602 "File ID: %s\n"
2603 "Machine ID: %s\n"
2604 "Boot ID: %s\n"
2605 "Sequential Number ID: %s\n"
2606 "State: %s\n"
2607 "Compatible Flags:%s%s\n"
d89c8fdf 2608 "Incompatible Flags:%s%s%s\n"
507f22bd
ZJS
2609 "Header size: %"PRIu64"\n"
2610 "Arena size: %"PRIu64"\n"
2611 "Data Hash Table Size: %"PRIu64"\n"
2612 "Field Hash Table Size: %"PRIu64"\n"
0284adc6 2613 "Rotate Suggested: %s\n"
507f22bd
ZJS
2614 "Head Sequential Number: %"PRIu64"\n"
2615 "Tail Sequential Number: %"PRIu64"\n"
0284adc6 2616 "Head Realtime Timestamp: %s\n"
3223f44f 2617 "Tail Realtime Timestamp: %s\n"
ed375beb 2618 "Tail Monotonic Timestamp: %s\n"
507f22bd
ZJS
2619 "Objects: %"PRIu64"\n"
2620 "Entry Objects: %"PRIu64"\n",
0284adc6
LP
2621 f->path,
2622 sd_id128_to_string(f->header->file_id, a),
2623 sd_id128_to_string(f->header->machine_id, b),
2624 sd_id128_to_string(f->header->boot_id, c),
2765b7bb 2625 sd_id128_to_string(f->header->seqnum_id, d),
3223f44f
LP
2626 f->header->state == STATE_OFFLINE ? "OFFLINE" :
2627 f->header->state == STATE_ONLINE ? "ONLINE" :
2628 f->header->state == STATE_ARCHIVED ? "ARCHIVED" : "UNKNOWN",
8088cbd3 2629 JOURNAL_HEADER_SEALED(f->header) ? " SEALED" : "",
d89c8fdf
ZJS
2630 (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_ANY) ? " ???" : "",
2631 JOURNAL_HEADER_COMPRESSED_XZ(f->header) ? " COMPRESSED-XZ" : "",
2632 JOURNAL_HEADER_COMPRESSED_LZ4(f->header) ? " COMPRESSED-LZ4" : "",
2633 (le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_ANY) ? " ???" : "",
507f22bd
ZJS
2634 le64toh(f->header->header_size),
2635 le64toh(f->header->arena_size),
2636 le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
2637 le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
fb0951b0 2638 yes_no(journal_file_rotate_suggested(f, 0)),
507f22bd
ZJS
2639 le64toh(f->header->head_entry_seqnum),
2640 le64toh(f->header->tail_entry_seqnum),
718fe4b1
ZJS
2641 format_timestamp_safe(x, sizeof(x), le64toh(f->header->head_entry_realtime)),
2642 format_timestamp_safe(y, sizeof(y), le64toh(f->header->tail_entry_realtime)),
ed375beb 2643 format_timespan(z, sizeof(z), le64toh(f->header->tail_entry_monotonic), USEC_PER_MSEC),
507f22bd
ZJS
2644 le64toh(f->header->n_objects),
2645 le64toh(f->header->n_entries));
7560fffc 2646
0284adc6 2647 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
507f22bd 2648 printf("Data Objects: %"PRIu64"\n"
0284adc6 2649 "Data Hash Table Fill: %.1f%%\n",
507f22bd 2650 le64toh(f->header->n_data),
0284adc6 2651 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
7560fffc 2652
0284adc6 2653 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
507f22bd 2654 printf("Field Objects: %"PRIu64"\n"
0284adc6 2655 "Field Hash Table Fill: %.1f%%\n",
507f22bd 2656 le64toh(f->header->n_fields),
0284adc6 2657 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
3223f44f
LP
2658
2659 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags))
507f22bd
ZJS
2660 printf("Tag Objects: %"PRIu64"\n",
2661 le64toh(f->header->n_tags));
3223f44f 2662 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
507f22bd
ZJS
2663 printf("Entry Array Objects: %"PRIu64"\n",
2664 le64toh(f->header->n_entry_arrays));
a1a03e30
LP
2665
2666 if (fstat(f->fd, &st) >= 0)
59f448cf 2667 printf("Disk usage: %s\n", format_bytes(bytes, sizeof(bytes), (uint64_t) st.st_blocks * 512ULL));
7560fffc
LP
2668}
2669
fc68c929
LP
2670static int journal_file_warn_btrfs(JournalFile *f) {
2671 unsigned attrs;
2672 int r;
2673
2674 assert(f);
2675
2676 /* Before we write anything, check if the COW logic is turned
2677 * off on btrfs. Given our write pattern that is quite
2678 * unfriendly to COW file systems this should greatly improve
2679 * performance on COW file systems, such as btrfs, at the
2680 * expense of data integrity features (which shouldn't be too
2681 * bad, given that we do our own checksumming). */
2682
2683 r = btrfs_is_filesystem(f->fd);
2684 if (r < 0)
2685 return log_warning_errno(r, "Failed to determine if journal is on btrfs: %m");
2686 if (!r)
2687 return 0;
2688
2689 r = read_attr_fd(f->fd, &attrs);
2690 if (r < 0)
2691 return log_warning_errno(r, "Failed to read file attributes: %m");
2692
2693 if (attrs & FS_NOCOW_FL) {
2694 log_debug("Detected btrfs file system with copy-on-write disabled, all is good.");
2695 return 0;
2696 }
2697
2698 log_notice("Creating journal file %s on a btrfs file system, and copy-on-write is enabled. "
2699 "This is likely to slow down journal access substantially, please consider turning "
2700 "off the copy-on-write file attribute on the journal directory, using chattr +C.", f->path);
2701
2702 return 1;
2703}
2704
0284adc6
LP
2705int journal_file_open(
2706 const char *fname,
2707 int flags,
2708 mode_t mode,
2709 bool compress,
baed47c3 2710 bool seal,
0284adc6
LP
2711 JournalMetrics *metrics,
2712 MMapCache *mmap_cache,
2713 JournalFile *template,
2714 JournalFile **ret) {
7560fffc 2715
fa6ac760 2716 bool newly_created = false;
0284adc6 2717 JournalFile *f;
fa6ac760 2718 void *h;
0284adc6 2719 int r;
7560fffc 2720
0284adc6 2721 assert(fname);
0559d3a5 2722 assert(ret);
7560fffc 2723
0284adc6
LP
2724 if ((flags & O_ACCMODE) != O_RDONLY &&
2725 (flags & O_ACCMODE) != O_RDWR)
2726 return -EINVAL;
7560fffc 2727
a0108012
LP
2728 if (!endswith(fname, ".journal") &&
2729 !endswith(fname, ".journal~"))
0284adc6 2730 return -EINVAL;
7560fffc 2731
0284adc6
LP
2732 f = new0(JournalFile, 1);
2733 if (!f)
2734 return -ENOMEM;
7560fffc 2735
0284adc6
LP
2736 f->fd = -1;
2737 f->mode = mode;
7560fffc 2738
0284adc6
LP
2739 f->flags = flags;
2740 f->prot = prot_from_flags(flags);
2741 f->writable = (flags & O_ACCMODE) != O_RDONLY;
92261977 2742#if defined(HAVE_LZ4)
d89c8fdf
ZJS
2743 f->compress_lz4 = compress;
2744#elif defined(HAVE_XZ)
2745 f->compress_xz = compress;
48b61739 2746#endif
49a32d43 2747#ifdef HAVE_GCRYPT
baed47c3 2748 f->seal = seal;
49a32d43 2749#endif
7560fffc 2750
0284adc6
LP
2751 if (mmap_cache)
2752 f->mmap = mmap_cache_ref(mmap_cache);
2753 else {
84168d80 2754 f->mmap = mmap_cache_new();
0284adc6
LP
2755 if (!f->mmap) {
2756 r = -ENOMEM;
2757 goto fail;
2758 }
2759 }
7560fffc 2760
0284adc6
LP
2761 f->path = strdup(fname);
2762 if (!f->path) {
2763 r = -ENOMEM;
2764 goto fail;
2765 }
7560fffc 2766
4743015d 2767 f->chain_cache = ordered_hashmap_new(&uint64_hash_ops);
a4bcff5b
LP
2768 if (!f->chain_cache) {
2769 r = -ENOMEM;
2770 goto fail;
2771 }
2772
0284adc6
LP
2773 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
2774 if (f->fd < 0) {
2775 r = -errno;
2776 goto fail;
7560fffc 2777 }
7560fffc 2778
2678031a
LP
2779 r = journal_file_fstat(f);
2780 if (r < 0)
0284adc6 2781 goto fail;
7560fffc 2782
0284adc6 2783 if (f->last_stat.st_size == 0 && f->writable) {
11689d2a 2784
fc68c929 2785 (void) journal_file_warn_btrfs(f);
11689d2a 2786
fb0951b0
LP
2787 /* Let's attach the creation time to the journal file,
2788 * so that the vacuuming code knows the age of this
2789 * file even if the file might end up corrupted one
2790 * day... Ideally we'd just use the creation time many
2791 * file systems maintain for each file, but there is
2792 * currently no usable API to query this, hence let's
2793 * emulate this via extended attributes. If extended
2794 * attributes are not supported we'll just skip this,
7517e174 2795 * and rely solely on mtime/atime/ctime of the file. */
fb0951b0 2796
d61b600d 2797 fd_setcrtime(f->fd, 0);
7560fffc 2798
feb12d3e 2799#ifdef HAVE_GCRYPT
0284adc6 2800 /* Try to load the FSPRG state, and if we can't, then
baed47c3 2801 * just don't do sealing */
49a32d43
LP
2802 if (f->seal) {
2803 r = journal_file_fss_load(f);
2804 if (r < 0)
2805 f->seal = false;
2806 }
feb12d3e 2807#endif
7560fffc 2808
0284adc6
LP
2809 r = journal_file_init_header(f, template);
2810 if (r < 0)
2811 goto fail;
7560fffc 2812
2678031a
LP
2813 r = journal_file_fstat(f);
2814 if (r < 0)
0284adc6 2815 goto fail;
fb0951b0
LP
2816
2817 newly_created = true;
0284adc6 2818 }
7560fffc 2819
0284adc6 2820 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
cfb571f3 2821 r = -ENODATA;
0284adc6
LP
2822 goto fail;
2823 }
7560fffc 2824
fa6ac760 2825 r = mmap_cache_get(f->mmap, f->fd, f->prot, CONTEXT_HEADER, true, 0, PAGE_ALIGN(sizeof(Header)), &f->last_stat, &h);
977eaa1e 2826 if (r < 0)
0284adc6 2827 goto fail;
7560fffc 2828
fa6ac760
LP
2829 f->header = h;
2830
0284adc6
LP
2831 if (!newly_created) {
2832 r = journal_file_verify_header(f);
2833 if (r < 0)
2834 goto fail;
2835 }
7560fffc 2836
feb12d3e 2837#ifdef HAVE_GCRYPT
0284adc6 2838 if (!newly_created && f->writable) {
baed47c3 2839 r = journal_file_fss_load(f);
0284adc6
LP
2840 if (r < 0)
2841 goto fail;
2842 }
feb12d3e 2843#endif
cec736d2
LP
2844
2845 if (f->writable) {
4a92baf3
LP
2846 if (metrics) {
2847 journal_default_metrics(metrics, f->fd);
2848 f->metrics = *metrics;
2849 } else if (template)
2850 f->metrics = template->metrics;
2851
cec736d2
LP
2852 r = journal_file_refresh_header(f);
2853 if (r < 0)
2854 goto fail;
2855 }
2856
feb12d3e 2857#ifdef HAVE_GCRYPT
baed47c3 2858 r = journal_file_hmac_setup(f);
14d10188
LP
2859 if (r < 0)
2860 goto fail;
feb12d3e 2861#endif
14d10188 2862
cec736d2 2863 if (newly_created) {
de190aef 2864 r = journal_file_setup_field_hash_table(f);
cec736d2
LP
2865 if (r < 0)
2866 goto fail;
2867
de190aef 2868 r = journal_file_setup_data_hash_table(f);
cec736d2
LP
2869 if (r < 0)
2870 goto fail;
7560fffc 2871
feb12d3e 2872#ifdef HAVE_GCRYPT
7560fffc
LP
2873 r = journal_file_append_first_tag(f);
2874 if (r < 0)
2875 goto fail;
feb12d3e 2876#endif
cec736d2
LP
2877 }
2878
fa6ac760
LP
2879 if (mmap_cache_got_sigbus(f->mmap, f->fd)) {
2880 r = -EIO;
2881 goto fail;
2882 }
2883
7a24f3bf 2884 if (template && template->post_change_timer) {
e167d7fd
LP
2885 r = journal_file_enable_post_change_timer(
2886 f,
2887 sd_event_source_get_event(template->post_change_timer),
2888 template->post_change_timer_period);
7a24f3bf 2889
7a24f3bf
VC
2890 if (r < 0)
2891 goto fail;
2892 }
2893
0559d3a5 2894 *ret = f;
cec736d2
LP
2895 return 0;
2896
2897fail:
fa6ac760
LP
2898 if (f->fd >= 0 && mmap_cache_got_sigbus(f->mmap, f->fd))
2899 r = -EIO;
2900
69a3a6fd 2901 (void) journal_file_close(f);
cec736d2
LP
2902
2903 return r;
2904}
0ac38b70 2905
baed47c3 2906int journal_file_rotate(JournalFile **f, bool compress, bool seal) {
57535f47 2907 _cleanup_free_ char *p = NULL;
0ac38b70
LP
2908 size_t l;
2909 JournalFile *old_file, *new_file = NULL;
2910 int r;
2911
2912 assert(f);
2913 assert(*f);
2914
2915 old_file = *f;
2916
2917 if (!old_file->writable)
2918 return -EINVAL;
2919
2920 if (!endswith(old_file->path, ".journal"))
2921 return -EINVAL;
2922
2923 l = strlen(old_file->path);
57535f47
ZJS
2924 r = asprintf(&p, "%.*s@" SD_ID128_FORMAT_STR "-%016"PRIx64"-%016"PRIx64".journal",
2925 (int) l - 8, old_file->path,
2926 SD_ID128_FORMAT_VAL(old_file->header->seqnum_id),
2927 le64toh((*f)->header->head_entry_seqnum),
2928 le64toh((*f)->header->head_entry_realtime));
2929 if (r < 0)
0ac38b70
LP
2930 return -ENOMEM;
2931
2678031a
LP
2932 /* Try to rename the file to the archived version. If the file
2933 * already was deleted, we'll get ENOENT, let's ignore that
2934 * case. */
0ac38b70 2935 r = rename(old_file->path, p);
2678031a 2936 if (r < 0 && errno != ENOENT)
0ac38b70
LP
2937 return -errno;
2938
ccdbaf91 2939 old_file->header->state = STATE_ARCHIVED;
0ac38b70 2940
f27a3864
LP
2941 /* Currently, btrfs is not very good with out write patterns
2942 * and fragments heavily. Let's defrag our journal files when
2943 * we archive them */
2944 old_file->defrag_on_close = true;
2945
baed47c3 2946 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, compress, seal, NULL, old_file->mmap, old_file, &new_file);
0ac38b70
LP
2947 journal_file_close(old_file);
2948
2949 *f = new_file;
2950 return r;
2951}
2952
9447a7f1
LP
2953int journal_file_open_reliably(
2954 const char *fname,
2955 int flags,
2956 mode_t mode,
7560fffc 2957 bool compress,
baed47c3 2958 bool seal,
4a92baf3 2959 JournalMetrics *metrics,
27370278 2960 MMapCache *mmap_cache,
9447a7f1
LP
2961 JournalFile *template,
2962 JournalFile **ret) {
2963
2964 int r;
2965 size_t l;
ed375beb 2966 _cleanup_free_ char *p = NULL;
9447a7f1 2967
070052ab 2968 r = journal_file_open(fname, flags, mode, compress, seal, metrics, mmap_cache, template, ret);
288359db
ZJS
2969 if (!IN_SET(r,
2970 -EBADMSG, /* corrupted */
2971 -ENODATA, /* truncated */
2972 -EHOSTDOWN, /* other machine */
2973 -EPROTONOSUPPORT, /* incompatible feature */
2974 -EBUSY, /* unclean shutdown */
2975 -ESHUTDOWN, /* already archived */
2976 -EIO, /* IO error, including SIGBUS on mmap */
2977 -EIDRM /* File has been deleted */))
9447a7f1
LP
2978 return r;
2979
2980 if ((flags & O_ACCMODE) == O_RDONLY)
2981 return r;
2982
2983 if (!(flags & O_CREAT))
2984 return r;
2985
7560fffc
LP
2986 if (!endswith(fname, ".journal"))
2987 return r;
2988
5c70eab4
LP
2989 /* The file is corrupted. Rotate it away and try it again (but only once) */
2990
9447a7f1 2991 l = strlen(fname);
d587eca5 2992 if (asprintf(&p, "%.*s@%016"PRIx64 "-%016"PRIx64 ".journal~",
57535f47 2993 (int) l - 8, fname,
d587eca5 2994 now(CLOCK_REALTIME),
9bf3b535 2995 random_u64()) < 0)
9447a7f1
LP
2996 return -ENOMEM;
2997
65089b82 2998 if (rename(fname, p) < 0)
9447a7f1
LP
2999 return -errno;
3000
f27a3864
LP
3001 /* btrfs doesn't cope well with our write pattern and
3002 * fragments heavily. Let's defrag all files we rotate */
11689d2a
LP
3003
3004 (void) chattr_path(p, false, FS_NOCOW_FL);
f27a3864
LP
3005 (void) btrfs_defrag(p);
3006
65089b82 3007 log_warning_errno(r, "File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
9447a7f1 3008
070052ab 3009 return journal_file_open(fname, flags, mode, compress, seal, metrics, mmap_cache, template, ret);
9447a7f1
LP
3010}
3011
cf244689
LP
3012int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p, uint64_t *seqnum, Object **ret, uint64_t *offset) {
3013 uint64_t i, n;
3014 uint64_t q, xor_hash = 0;
3015 int r;
3016 EntryItem *items;
3017 dual_timestamp ts;
3018
3019 assert(from);
3020 assert(to);
3021 assert(o);
3022 assert(p);
3023
3024 if (!to->writable)
3025 return -EPERM;
3026
3027 ts.monotonic = le64toh(o->entry.monotonic);
3028 ts.realtime = le64toh(o->entry.realtime);
3029
cf244689 3030 n = journal_file_entry_n_items(o);
4faa7004
TA
3031 /* alloca() can't take 0, hence let's allocate at least one */
3032 items = alloca(sizeof(EntryItem) * MAX(1u, n));
cf244689
LP
3033
3034 for (i = 0; i < n; i++) {
4fd052ae
FC
3035 uint64_t l, h;
3036 le64_t le_hash;
cf244689
LP
3037 size_t t;
3038 void *data;
3039 Object *u;
3040
3041 q = le64toh(o->entry.items[i].object_offset);
3042 le_hash = o->entry.items[i].hash;
3043
3044 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
3045 if (r < 0)
3046 return r;
3047
3048 if (le_hash != o->data.hash)
3049 return -EBADMSG;
3050
3051 l = le64toh(o->object.size) - offsetof(Object, data.payload);
3052 t = (size_t) l;
3053
3054 /* We hit the limit on 32bit machines */
3055 if ((uint64_t) t != l)
3056 return -E2BIG;
3057
d89c8fdf 3058 if (o->object.flags & OBJECT_COMPRESSION_MASK) {
3b1a55e1 3059#if defined(HAVE_XZ) || defined(HAVE_LZ4)
a7f7d1bd 3060 size_t rsize = 0;
cf244689 3061
d89c8fdf
ZJS
3062 r = decompress_blob(o->object.flags & OBJECT_COMPRESSION_MASK,
3063 o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize, 0);
3064 if (r < 0)
3065 return r;
cf244689
LP
3066
3067 data = from->compress_buffer;
3068 l = rsize;
3b1a55e1
ZJS
3069#else
3070 return -EPROTONOSUPPORT;
3071#endif
cf244689
LP
3072 } else
3073 data = o->data.payload;
3074
3075 r = journal_file_append_data(to, data, l, &u, &h);
3076 if (r < 0)
3077 return r;
3078
3079 xor_hash ^= le64toh(u->data.hash);
3080 items[i].object_offset = htole64(h);
3081 items[i].hash = u->data.hash;
3082
3083 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
3084 if (r < 0)
3085 return r;
3086 }
3087
fa6ac760
LP
3088 r = journal_file_append_entry_internal(to, &ts, xor_hash, items, n, seqnum, ret, offset);
3089
3090 if (mmap_cache_got_sigbus(to->mmap, to->fd))
3091 return -EIO;
3092
3093 return r;
cf244689 3094}
babfc091 3095
8580d1f7
LP
3096void journal_reset_metrics(JournalMetrics *m) {
3097 assert(m);
3098
3099 /* Set everything to "pick automatic values". */
3100
3101 *m = (JournalMetrics) {
3102 .min_use = (uint64_t) -1,
3103 .max_use = (uint64_t) -1,
3104 .min_size = (uint64_t) -1,
3105 .max_size = (uint64_t) -1,
3106 .keep_free = (uint64_t) -1,
3107 .n_max_files = (uint64_t) -1,
3108 };
3109}
3110
babfc091 3111void journal_default_metrics(JournalMetrics *m, int fd) {
8580d1f7 3112 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX], e[FORMAT_BYTES_MAX];
babfc091 3113 struct statvfs ss;
8580d1f7 3114 uint64_t fs_size;
babfc091
LP
3115
3116 assert(m);
3117 assert(fd >= 0);
3118
3119 if (fstatvfs(fd, &ss) >= 0)
3120 fs_size = ss.f_frsize * ss.f_blocks;
8580d1f7
LP
3121 else {
3122 log_debug_errno(errno, "Failed to detremine disk size: %m");
3123 fs_size = 0;
3124 }
babfc091
LP
3125
3126 if (m->max_use == (uint64_t) -1) {
3127
3128 if (fs_size > 0) {
3129 m->max_use = PAGE_ALIGN(fs_size / 10); /* 10% of file system size */
3130
3131 if (m->max_use > DEFAULT_MAX_USE_UPPER)
3132 m->max_use = DEFAULT_MAX_USE_UPPER;
3133
3134 if (m->max_use < DEFAULT_MAX_USE_LOWER)
3135 m->max_use = DEFAULT_MAX_USE_LOWER;
3136 } else
3137 m->max_use = DEFAULT_MAX_USE_LOWER;
3138 } else {
3139 m->max_use = PAGE_ALIGN(m->max_use);
3140
8580d1f7 3141 if (m->max_use != 0 && m->max_use < JOURNAL_FILE_SIZE_MIN*2)
babfc091
LP
3142 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
3143 }
3144
8580d1f7
LP
3145 if (m->min_use == (uint64_t) -1)
3146 m->min_use = DEFAULT_MIN_USE;
3147
3148 if (m->min_use > m->max_use)
3149 m->min_use = m->max_use;
3150
babfc091
LP
3151 if (m->max_size == (uint64_t) -1) {
3152 m->max_size = PAGE_ALIGN(m->max_use / 8); /* 8 chunks */
3153
3154 if (m->max_size > DEFAULT_MAX_SIZE_UPPER)
3155 m->max_size = DEFAULT_MAX_SIZE_UPPER;
3156 } else
3157 m->max_size = PAGE_ALIGN(m->max_size);
3158
8580d1f7
LP
3159 if (m->max_size != 0) {
3160 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
3161 m->max_size = JOURNAL_FILE_SIZE_MIN;
babfc091 3162
8580d1f7
LP
3163 if (m->max_use != 0 && m->max_size*2 > m->max_use)
3164 m->max_use = m->max_size*2;
3165 }
babfc091
LP
3166
3167 if (m->min_size == (uint64_t) -1)
3168 m->min_size = JOURNAL_FILE_SIZE_MIN;
3169 else {
3170 m->min_size = PAGE_ALIGN(m->min_size);
3171
3172 if (m->min_size < JOURNAL_FILE_SIZE_MIN)
3173 m->min_size = JOURNAL_FILE_SIZE_MIN;
3174
8580d1f7 3175 if (m->max_size != 0 && m->min_size > m->max_size)
babfc091
LP
3176 m->max_size = m->min_size;
3177 }
3178
3179 if (m->keep_free == (uint64_t) -1) {
3180
3181 if (fs_size > 0) {
8621b110 3182 m->keep_free = PAGE_ALIGN(fs_size * 3 / 20); /* 15% of file system size */
babfc091
LP
3183
3184 if (m->keep_free > DEFAULT_KEEP_FREE_UPPER)
3185 m->keep_free = DEFAULT_KEEP_FREE_UPPER;
3186
3187 } else
3188 m->keep_free = DEFAULT_KEEP_FREE;
3189 }
3190
8580d1f7
LP
3191 if (m->n_max_files == (uint64_t) -1)
3192 m->n_max_files = DEFAULT_N_MAX_FILES;
3193
3194 log_debug("Fixed min_use=%s max_use=%s max_size=%s min_size=%s keep_free=%s n_max_files=%" PRIu64,
3195 format_bytes(a, sizeof(a), m->min_use),
3196 format_bytes(b, sizeof(b), m->max_use),
3197 format_bytes(c, sizeof(c), m->max_size),
3198 format_bytes(d, sizeof(d), m->min_size),
3199 format_bytes(e, sizeof(e), m->keep_free),
3200 m->n_max_files);
babfc091 3201}
08984293
LP
3202
3203int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
08984293 3204 assert(f);
c88cc6af 3205 assert(f->header);
08984293
LP
3206 assert(from || to);
3207
3208 if (from) {
162566a4
LP
3209 if (f->header->head_entry_realtime == 0)
3210 return -ENOENT;
08984293 3211
162566a4 3212 *from = le64toh(f->header->head_entry_realtime);
08984293
LP
3213 }
3214
3215 if (to) {
162566a4
LP
3216 if (f->header->tail_entry_realtime == 0)
3217 return -ENOENT;
08984293 3218
162566a4 3219 *to = le64toh(f->header->tail_entry_realtime);
08984293
LP
3220 }
3221
3222 return 1;
3223}
3224
3225int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
08984293
LP
3226 Object *o;
3227 uint64_t p;
3228 int r;
3229
3230 assert(f);
3231 assert(from || to);
3232
47838ab3 3233 r = find_data_object_by_boot_id(f, boot_id, &o, &p);
08984293
LP
3234 if (r <= 0)
3235 return r;
3236
3237 if (le64toh(o->data.n_entries) <= 0)
3238 return 0;
3239
3240 if (from) {
3241 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
3242 if (r < 0)
3243 return r;
3244
3245 *from = le64toh(o->entry.monotonic);
3246 }
3247
3248 if (to) {
3249 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
3250 if (r < 0)
3251 return r;
3252
3253 r = generic_array_get_plus_one(f,
3254 le64toh(o->data.entry_offset),
3255 le64toh(o->data.entry_array_offset),
3256 le64toh(o->data.n_entries)-1,
3257 &o, NULL);
3258 if (r <= 0)
3259 return r;
3260
3261 *to = le64toh(o->entry.monotonic);
3262 }
3263
3264 return 1;
3265}
dca6219e 3266
fb0951b0 3267bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec) {
dca6219e 3268 assert(f);
c88cc6af 3269 assert(f->header);
dca6219e
LP
3270
3271 /* If we gained new header fields we gained new features,
3272 * hence suggest a rotation */
361f9cbc
LP
3273 if (le64toh(f->header->header_size) < sizeof(Header)) {
3274 log_debug("%s uses an outdated header, suggesting rotation.", f->path);
dca6219e 3275 return true;
361f9cbc 3276 }
dca6219e
LP
3277
3278 /* Let's check if the hash tables grew over a certain fill
3279 * level (75%, borrowing this value from Java's hash table
3280 * implementation), and if so suggest a rotation. To calculate
3281 * the fill level we need the n_data field, which only exists
3282 * in newer versions. */
3283
3284 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
361f9cbc 3285 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
507f22bd 3286 log_debug("Data hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items, %llu file size, %"PRIu64" bytes per hash table item), suggesting rotation.",
361f9cbc
LP
3287 f->path,
3288 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
507f22bd
ZJS
3289 le64toh(f->header->n_data),
3290 le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
3291 (unsigned long long) f->last_stat.st_size,
3292 f->last_stat.st_size / le64toh(f->header->n_data));
dca6219e 3293 return true;
361f9cbc 3294 }
dca6219e
LP
3295
3296 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
361f9cbc 3297 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
507f22bd 3298 log_debug("Field hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items), suggesting rotation.",
361f9cbc
LP
3299 f->path,
3300 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
507f22bd
ZJS
3301 le64toh(f->header->n_fields),
3302 le64toh(f->header->field_hash_table_size) / sizeof(HashItem));
dca6219e 3303 return true;
361f9cbc 3304 }
dca6219e 3305
0598fd4a
LP
3306 /* Are the data objects properly indexed by field objects? */
3307 if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
3308 JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
3309 le64toh(f->header->n_data) > 0 &&
3310 le64toh(f->header->n_fields) == 0)
3311 return true;
3312
fb0951b0
LP
3313 if (max_file_usec > 0) {
3314 usec_t t, h;
3315
3316 h = le64toh(f->header->head_entry_realtime);
3317 t = now(CLOCK_REALTIME);
3318
3319 if (h > 0 && t > h + max_file_usec)
3320 return true;
3321 }
3322
dca6219e
LP
3323 return false;
3324}