]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/journal/journal-file.c
journal: add void cast to journal_file_close() calls
[thirdparty/systemd.git] / src / journal / journal-file.c
... / ...
CommitLineData
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
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
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
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18***/
19
20#include <errno.h>
21#include <fcntl.h>
22#include <linux/fs.h>
23#include <stddef.h>
24#include <sys/mman.h>
25#include <sys/statvfs.h>
26#include <sys/uio.h>
27#include <unistd.h>
28
29#include "alloc-util.h"
30#include "btrfs-util.h"
31#include "chattr-util.h"
32#include "compress.h"
33#include "fd-util.h"
34#include "journal-authenticate.h"
35#include "journal-def.h"
36#include "journal-file.h"
37#include "lookup3.h"
38#include "parse-util.h"
39#include "random-util.h"
40#include "sd-event.h"
41#include "string-util.h"
42#include "xattr-util.h"
43
44#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem))
45#define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem))
46
47#define COMPRESSION_SIZE_THRESHOLD (512ULL)
48
49/* This is the minimum journal file size */
50#define JOURNAL_FILE_SIZE_MIN (512ULL*1024ULL) /* 512 KiB */
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
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
60/* This is the upper bound if we deduce max_size from max_use */
61#define DEFAULT_MAX_SIZE_UPPER (128ULL*1024ULL*1024ULL) /* 128 MiB */
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
71/* This is the default maximum number of journal files to keep around. */
72#define DEFAULT_N_MAX_FILES (100)
73
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))
76
77/* How many entries to keep in the entry array chain cache at max */
78#define CHAIN_CACHE_MAX 20
79
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
83/* Reread fstat() of the file for detecting deletions at least this often */
84#define LAST_STAT_REFRESH_USEC (5*USEC_PER_SEC)
85
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
89static int journal_file_set_online(JournalFile *f) {
90 assert(f);
91
92 if (!f->writable)
93 return -EPERM;
94
95 if (!(f->fd >= 0 && f->header))
96 return -EINVAL;
97
98 if (mmap_cache_got_sigbus(f->mmap, f->fd))
99 return -EIO;
100
101 switch (f->header->state) {
102 case STATE_ONLINE:
103 return 0;
104
105 case STATE_OFFLINE:
106 f->header->state = STATE_ONLINE;
107 (void) fsync(f->fd);
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
127 (void) fsync(f->fd);
128
129 if (mmap_cache_got_sigbus(f->mmap, f->fd))
130 return -EIO;
131
132 f->header->state = STATE_OFFLINE;
133
134 if (mmap_cache_got_sigbus(f->mmap, f->fd))
135 return -EIO;
136
137 (void) fsync(f->fd);
138
139 return 0;
140}
141
142JournalFile* journal_file_close(JournalFile *f) {
143 assert(f);
144
145#ifdef HAVE_GCRYPT
146 /* Write the final tag */
147 if (f->seal && f->writable)
148 journal_file_append_tag(f);
149#endif
150
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
158 (void) sd_event_source_set_enabled(f->post_change_timer, SD_EVENT_OFF);
159 sd_event_source_unref(f->post_change_timer);
160 }
161
162 journal_file_set_offline(f);
163
164 if (f->mmap && f->fd >= 0)
165 mmap_cache_close_fd(f->mmap, f->fd);
166
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
175 (void) chattr_fd(f->fd, 0, FS_NOCOW_FL);
176 (void) btrfs_defrag_fd(f->fd);
177 }
178
179 safe_close(f->fd);
180 free(f->path);
181
182 mmap_cache_unref(f->mmap);
183
184 ordered_hashmap_free_free(f->chain_cache);
185
186#if defined(HAVE_XZ) || defined(HAVE_LZ4)
187 free(f->compress_buffer);
188#endif
189
190#ifdef HAVE_GCRYPT
191 if (f->fss_file)
192 munmap(f->fss_file, PAGE_ALIGN(f->fss_file_size));
193 else
194 free(f->fsprg_state);
195
196 free(f->fsprg_seed);
197
198 if (f->hmac)
199 gcry_md_close(f->hmac);
200#endif
201
202 free(f);
203 return NULL;
204}
205
206static int journal_file_init_header(JournalFile *f, JournalFile *template) {
207 Header h = {};
208 ssize_t k;
209 int r;
210
211 assert(f);
212
213 memcpy(h.signature, HEADER_SIGNATURE, 8);
214 h.header_size = htole64(ALIGN64(sizeof(h)));
215
216 h.incompatible_flags |= htole32(
217 f->compress_xz * HEADER_INCOMPATIBLE_COMPRESSED_XZ |
218 f->compress_lz4 * HEADER_INCOMPATIBLE_COMPRESSED_LZ4);
219
220 h.compatible_flags = htole32(
221 f->seal * HEADER_COMPATIBLE_SEALED);
222
223 r = sd_id128_randomize(&h.file_id);
224 if (r < 0)
225 return r;
226
227 if (template) {
228 h.seqnum_id = template->header->seqnum_id;
229 h.tail_entry_seqnum = template->header->tail_entry_seqnum;
230 } else
231 h.seqnum_id = h.file_id;
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) {
244 sd_id128_t boot_id;
245 int r;
246
247 assert(f);
248 assert(f->header);
249
250 r = sd_id128_get_machine(&f->header->machine_id);
251 if (r < 0)
252 return r;
253
254 r = sd_id128_get_boot(&boot_id);
255 if (r < 0)
256 return r;
257
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
263 r = journal_file_set_online(f);
264
265 /* Sync the online state to disk */
266 (void) fsync(f->fd);
267
268 return r;
269}
270
271static int journal_file_verify_header(JournalFile *f) {
272 uint32_t flags;
273
274 assert(f);
275 assert(f->header);
276
277 if (memcmp(f->header->signature, HEADER_SIGNATURE, 8))
278 return -EBADMSG;
279
280 /* In both read and write mode we refuse to open files with
281 * incompatible flags we don't know */
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);
291 return -EPROTONOSUPPORT;
292 }
293
294 /* When open for writing we refuse to open files with
295 * compatible flags, too */
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;
306 }
307
308 if (f->header->state >= _STATE_MAX)
309 return -EBADMSG;
310
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)
313 return -EBADMSG;
314
315 if (JOURNAL_HEADER_SEALED(f->header) && !JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
316 return -EBADMSG;
317
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
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
330 if (f->writable) {
331 uint8_t state;
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
342 state = f->header->state;
343
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)
348 return -ESHUTDOWN;
349 else if (state != STATE_OFFLINE) {
350 log_debug("Journal file %s has unknown state %i.", f->path, state);
351 return -EBUSY;
352 }
353 }
354
355 f->compress_xz = JOURNAL_HEADER_COMPRESSED_XZ(f->header);
356 f->compress_lz4 = JOURNAL_HEADER_COMPRESSED_LZ4(f->header);
357
358 f->seal = JOURNAL_HEADER_SEALED(f->header);
359
360 return 0;
361}
362
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
379static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
380 uint64_t old_size, new_size;
381 int r;
382
383 assert(f);
384 assert(f->header);
385
386 /* We assume that this file is not sparse, and we know that
387 * for sure, since we always call posix_fallocate()
388 * ourselves */
389
390 if (mmap_cache_got_sigbus(f->mmap, f->fd))
391 return -EIO;
392
393 old_size =
394 le64toh(f->header->header_size) +
395 le64toh(f->header->arena_size);
396
397 new_size = PAGE_ALIGN(offset + size);
398 if (new_size < le64toh(f->header->header_size))
399 new_size = le64toh(f->header->header_size);
400
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. */
416
417 if (f->metrics.max_size > 0 && new_size > f->metrics.max_size)
418 return -E2BIG;
419
420 if (new_size > f->metrics.min_size && f->metrics.keep_free > 0) {
421 struct statvfs svfs;
422
423 if (fstatvfs(f->fd, &svfs) >= 0) {
424 uint64_t available;
425
426 available = LESS_BY((uint64_t) svfs.f_bfree * (uint64_t) svfs.f_bsize, f->metrics.keep_free);
427
428 if (new_size - old_size > available)
429 return -E2BIG;
430 }
431 }
432
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
438 /* Note that the glibc fallocate() fallback is very
439 inefficient, hence we try to minimize the allocation area
440 as we can. */
441 r = posix_fallocate(f->fd, old_size, new_size - old_size);
442 if (r != 0)
443 return -r;
444
445 f->header->arena_size = htole64(new_size - le64toh(f->header->header_size));
446
447 return journal_file_fstat(f);
448}
449
450static unsigned type_to_context(ObjectType type) {
451 /* One context for each type, plus one catch-all for the rest */
452 assert_cc(_OBJECT_TYPE_MAX <= MMAP_CACHE_MAX_CONTEXTS);
453 assert_cc(CONTEXT_HEADER < MMAP_CACHE_MAX_CONTEXTS);
454 return type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX ? type : 0;
455}
456
457static int journal_file_move_to(JournalFile *f, ObjectType type, bool keep_always, uint64_t offset, uint64_t size, void **ret) {
458 int r;
459
460 assert(f);
461 assert(ret);
462
463 if (size <= 0)
464 return -EINVAL;
465
466 /* Avoid SIGBUS on invalid accesses */
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
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)
476 return -EADDRNOTAVAIL;
477 }
478
479 return mmap_cache_get(f->mmap, f->fd, f->prot, type_to_context(type), keep_always, offset, size, &f->last_stat, ret);
480}
481
482static uint64_t minimum_header_size(Object *o) {
483
484 static const uint64_t table[] = {
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
500int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset, Object **ret) {
501 int r;
502 void *t;
503 Object *o;
504 uint64_t s;
505
506 assert(f);
507 assert(ret);
508
509 /* Objects may only be located at multiple of 64 bit */
510 if (!VALID64(offset))
511 return -EFAULT;
512
513 r = journal_file_move_to(f, type, false, offset, sizeof(ObjectHeader), &t);
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
523 if (o->object.type <= OBJECT_UNUSED)
524 return -EBADMSG;
525
526 if (s < minimum_header_size(o))
527 return -EBADMSG;
528
529 if (type > OBJECT_UNUSED && o->object.type != type)
530 return -EBADMSG;
531
532 if (s > sizeof(ObjectHeader)) {
533 r = journal_file_move_to(f, type, false, offset, s, &t);
534 if (r < 0)
535 return r;
536
537 o = (Object*) t;
538 }
539
540 *ret = o;
541 return 0;
542}
543
544static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) {
545 uint64_t r;
546
547 assert(f);
548 assert(f->header);
549
550 r = le64toh(f->header->tail_entry_seqnum) + 1;
551
552 if (seqnum) {
553 /* If an external seqnum counter was passed, we update
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
563 f->header->tail_entry_seqnum = htole64(r);
564
565 if (f->header->head_entry_seqnum == 0)
566 f->header->head_entry_seqnum = htole64(r);
567
568 return r;
569}
570
571int journal_file_append_object(JournalFile *f, ObjectType type, uint64_t size, Object **ret, uint64_t *offset) {
572 int r;
573 uint64_t p;
574 Object *tail, *o;
575 void *t;
576
577 assert(f);
578 assert(f->header);
579 assert(type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX);
580 assert(size >= sizeof(ObjectHeader));
581 assert(offset);
582 assert(ret);
583
584 r = journal_file_set_online(f);
585 if (r < 0)
586 return r;
587
588 p = le64toh(f->header->tail_object_offset);
589 if (p == 0)
590 p = le64toh(f->header->header_size);
591 else {
592 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &tail);
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
603 r = journal_file_move_to(f, type, false, p, size, &t);
604 if (r < 0)
605 return r;
606
607 o = (Object*) t;
608
609 zero(o->object);
610 o->object.type = type;
611 o->object.size = htole64(size);
612
613 f->header->tail_object_offset = htole64(p);
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
622static int journal_file_setup_data_hash_table(JournalFile *f) {
623 uint64_t s, p;
624 Object *o;
625 int r;
626
627 assert(f);
628 assert(f->header);
629
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. */
634
635 s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem);
636 if (s < DEFAULT_DATA_HASH_TABLE_SIZE)
637 s = DEFAULT_DATA_HASH_TABLE_SIZE;
638
639 log_debug("Reserving %"PRIu64" entries in hash table.", s / sizeof(HashItem));
640
641 r = journal_file_append_object(f,
642 OBJECT_DATA_HASH_TABLE,
643 offsetof(Object, hash_table.items) + s,
644 &o, &p);
645 if (r < 0)
646 return r;
647
648 memzero(o->hash_table.items, s);
649
650 f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
651 f->header->data_hash_table_size = htole64(s);
652
653 return 0;
654}
655
656static int journal_file_setup_field_hash_table(JournalFile *f) {
657 uint64_t s, p;
658 Object *o;
659 int r;
660
661 assert(f);
662 assert(f->header);
663
664 /* We use a fixed size hash table for the fields as this
665 * number should grow very slowly only */
666
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);
672 if (r < 0)
673 return r;
674
675 memzero(o->hash_table.items, s);
676
677 f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
678 f->header->field_hash_table_size = htole64(s);
679
680 return 0;
681}
682
683int journal_file_map_data_hash_table(JournalFile *f) {
684 uint64_t s, p;
685 void *t;
686 int r;
687
688 assert(f);
689 assert(f->header);
690
691 if (f->data_hash_table)
692 return 0;
693
694 p = le64toh(f->header->data_hash_table_offset);
695 s = le64toh(f->header->data_hash_table_size);
696
697 r = journal_file_move_to(f,
698 OBJECT_DATA_HASH_TABLE,
699 true,
700 p, s,
701 &t);
702 if (r < 0)
703 return r;
704
705 f->data_hash_table = t;
706 return 0;
707}
708
709int journal_file_map_field_hash_table(JournalFile *f) {
710 uint64_t s, p;
711 void *t;
712 int r;
713
714 assert(f);
715 assert(f->header);
716
717 if (f->field_hash_table)
718 return 0;
719
720 p = le64toh(f->header->field_hash_table_offset);
721 s = le64toh(f->header->field_hash_table_size);
722
723 r = journal_file_move_to(f,
724 OBJECT_FIELD_HASH_TABLE,
725 true,
726 p, s,
727 &t);
728 if (r < 0)
729 return r;
730
731 f->field_hash_table = t;
732 return 0;
733}
734
735static int journal_file_link_field(
736 JournalFile *f,
737 Object *o,
738 uint64_t offset,
739 uint64_t hash) {
740
741 uint64_t p, h, m;
742 int r;
743
744 assert(f);
745 assert(f->header);
746 assert(f->field_hash_table);
747 assert(o);
748 assert(offset > 0);
749
750 if (o->object.type != OBJECT_FIELD)
751 return -EINVAL;
752
753 m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem);
754 if (m <= 0)
755 return -EBADMSG;
756
757 /* This might alter the window we are looking at */
758 o->field.next_hash_offset = o->field.head_data_offset = 0;
759
760 h = hash % m;
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
786 uint64_t p, h, m;
787 int r;
788
789 assert(f);
790 assert(f->header);
791 assert(f->data_hash_table);
792 assert(o);
793 assert(offset > 0);
794
795 if (o->object.type != OBJECT_DATA)
796 return -EINVAL;
797
798 m = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
799 if (m <= 0)
800 return -EBADMSG;
801
802 /* This might alter the window we are looking at */
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;
806
807 h = hash % m;
808 p = le64toh(f->data_hash_table[h].tail_hash_offset);
809 if (p == 0)
810 /* Only entry in the hash table is easy */
811 f->data_hash_table[h].head_hash_offset = htole64(offset);
812 else {
813 /* Move back to the previous data object, to patch in
814 * pointer */
815
816 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
817 if (r < 0)
818 return r;
819
820 o->data.next_hash_offset = htole64(offset);
821 }
822
823 f->data_hash_table[h].tail_hash_offset = htole64(offset);
824
825 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
826 f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
827
828 return 0;
829}
830
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
836 uint64_t p, osize, h, m;
837 int r;
838
839 assert(f);
840 assert(f->header);
841 assert(field && size > 0);
842
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
852 osize = offsetof(Object, field.payload) + size;
853
854 m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem);
855 if (m <= 0)
856 return -EBADMSG;
857
858 h = hash % m;
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
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) {
907
908 uint64_t p, osize, h, m;
909 int r;
910
911 assert(f);
912 assert(f->header);
913 assert(data || size == 0);
914
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
924 osize = offsetof(Object, data.payload) + size;
925
926 m = le64toh(f->header->data_hash_table_size) / sizeof(HashItem);
927 if (m <= 0)
928 return -EBADMSG;
929
930 h = hash % m;
931 p = le64toh(f->data_hash_table[h].head_hash_offset);
932
933 while (p > 0) {
934 Object *o;
935
936 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
937 if (r < 0)
938 return r;
939
940 if (le64toh(o->data.hash) != hash)
941 goto next;
942
943 if (o->object.flags & OBJECT_COMPRESSION_MASK) {
944#if defined(HAVE_XZ) || defined(HAVE_LZ4)
945 uint64_t l;
946 size_t rsize = 0;
947
948 l = le64toh(o->object.size);
949 if (l <= offsetof(Object, data.payload))
950 return -EBADMSG;
951
952 l -= offsetof(Object, data.payload);
953
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;
958
959 if (rsize == size &&
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 }
970#else
971 return -EPROTONOSUPPORT;
972#endif
973 } else if (le64toh(o->object.size) == osize &&
974 memcmp(o->data.payload, data, size) == 0) {
975
976 if (ret)
977 *ret = o;
978
979 if (offset)
980 *offset = p;
981
982 return 1;
983 }
984
985 next:
986 p = le64toh(o->data.next_hash_offset);
987 }
988
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
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);
1040 if (r < 0)
1041 return r;
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
1071static int journal_file_append_data(
1072 JournalFile *f,
1073 const void *data, uint64_t size,
1074 Object **ret, uint64_t *offset) {
1075
1076 uint64_t hash, p;
1077 uint64_t osize;
1078 Object *o;
1079 int r, compression = 0;
1080 const void *eq;
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;
1090 if (r > 0) {
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);
1103 if (r < 0)
1104 return r;
1105
1106 o->data.hash = htole64(hash);
1107
1108#if defined(HAVE_XZ) || defined(HAVE_LZ4)
1109 if (JOURNAL_FILE_COMPRESS(f) && size >= COMPRESSION_SIZE_THRESHOLD) {
1110 size_t rsize = 0;
1111
1112 compression = compress_blob(data, size, o->data.payload, size - 1, &rsize);
1113
1114 if (compression >= 0) {
1115 o->object.size = htole64(offsetof(Object, data.payload) + rsize);
1116 o->object.flags |= compression;
1117
1118 log_debug("Compressed data object %"PRIu64" -> %zu using %s",
1119 size, rsize, object_compressed_to_string(compression));
1120 } else
1121 /* Compression didn't work, we don't really care why, let's continue without compression */
1122 compression = 0;
1123 }
1124#endif
1125
1126 if (compression == 0)
1127 memcpy_safe(o->data.payload, data, size);
1128
1129 r = journal_file_link_data(f, o, p, hash);
1130 if (r < 0)
1131 return r;
1132
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
1139 if (!data)
1140 eq = NULL;
1141 else
1142 eq = memchr(data, '=', size);
1143 if (eq && eq > data) {
1144 Object *fo = NULL;
1145 uint64_t fp;
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
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
1163 if (ret)
1164 *ret = o;
1165
1166 if (offset)
1167 *offset = p;
1168
1169 return 0;
1170}
1171
1172uint64_t journal_file_entry_n_items(Object *o) {
1173 assert(o);
1174
1175 if (o->object.type != OBJECT_ENTRY)
1176 return 0;
1177
1178 return (le64toh(o->object.size) - offsetof(Object, entry.items)) / sizeof(EntryItem);
1179}
1180
1181uint64_t journal_file_entry_array_n_items(Object *o) {
1182 assert(o);
1183
1184 if (o->object.type != OBJECT_ENTRY_ARRAY)
1185 return 0;
1186
1187 return (le64toh(o->object.size) - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
1188}
1189
1190uint64_t journal_file_hash_table_n_items(Object *o) {
1191 assert(o);
1192
1193 if (o->object.type != OBJECT_DATA_HASH_TABLE &&
1194 o->object.type != OBJECT_FIELD_HASH_TABLE)
1195 return 0;
1196
1197 return (le64toh(o->object.size) - offsetof(Object, hash_table.items)) / sizeof(HashItem);
1198}
1199
1200static int link_entry_into_array(JournalFile *f,
1201 le64_t *first,
1202 le64_t *idx,
1203 uint64_t p) {
1204 int r;
1205 uint64_t n = 0, ap = 0, q, i, a, hidx;
1206 Object *o;
1207
1208 assert(f);
1209 assert(f->header);
1210 assert(first);
1211 assert(idx);
1212 assert(p > 0);
1213
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;
1221
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 }
1228
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);
1245 if (r < 0)
1246 return r;
1247
1248#ifdef HAVE_GCRYPT
1249 r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, o, q);
1250 if (r < 0)
1251 return r;
1252#endif
1253
1254 o->entry_array.items[i] = htole64(p);
1255
1256 if (ap == 0)
1257 *first = htole64(q);
1258 else {
1259 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
1260 if (r < 0)
1261 return r;
1262
1263 o->entry_array.next_entry_array_offset = htole64(q);
1264 }
1265
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
1269 *idx = htole64(hidx + 1);
1270
1271 return 0;
1272}
1273
1274static int link_entry_into_array_plus_one(JournalFile *f,
1275 le64_t *extra,
1276 le64_t *first,
1277 le64_t *idx,
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 {
1291 le64_t i;
1292
1293 i = htole64(le64toh(*idx) - 1);
1294 r = link_entry_into_array(f, first, &i, p);
1295 if (r < 0)
1296 return r;
1297 }
1298
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);
1315 if (r < 0)
1316 return r;
1317
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);
1323}
1324
1325static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
1326 uint64_t n, i;
1327 int r;
1328
1329 assert(f);
1330 assert(f->header);
1331 assert(o);
1332 assert(offset > 0);
1333
1334 if (o->object.type != OBJECT_ENTRY)
1335 return -EINVAL;
1336
1337 __sync_synchronize();
1338
1339 /* Link up the entry itself */
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;
1346
1347 /* log_debug("=> %s seqnr=%"PRIu64" n_entries=%"PRIu64, f->path, o->entry.seqnum, f->header->n_entries); */
1348
1349 if (f->header->head_entry_realtime == 0)
1350 f->header->head_entry_realtime = o->entry.realtime;
1351
1352 f->header->tail_entry_realtime = o->entry.realtime;
1353 f->header->tail_entry_monotonic = o->entry.monotonic;
1354
1355 f->tail_entry_monotonic_valid = true;
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
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,
1373 uint64_t *seqnum,
1374 Object **ret, uint64_t *offset) {
1375 uint64_t np;
1376 uint64_t osize;
1377 Object *o;
1378 int r;
1379
1380 assert(f);
1381 assert(f->header);
1382 assert(items || n_items == 0);
1383 assert(ts);
1384
1385 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
1386
1387 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
1388 if (r < 0)
1389 return r;
1390
1391 o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum));
1392 memcpy_safe(o->entry.items, items, n_items * sizeof(EntryItem));
1393 o->entry.realtime = htole64(ts->realtime);
1394 o->entry.monotonic = htole64(ts->monotonic);
1395 o->entry.xor_hash = htole64(xor_hash);
1396 o->entry.boot_id = f->header->boot_id;
1397
1398#ifdef HAVE_GCRYPT
1399 r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np);
1400 if (r < 0)
1401 return r;
1402#endif
1403
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
1417void journal_file_post_change(JournalFile *f) {
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
1425 __sync_synchronize();
1426
1427 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
1428 log_debug_errno(errno, "Failed to truncate file to its own size: %m");
1429}
1430
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) {
1451 log_debug_errno(r, "Failed to get ftruncate timer state: %m");
1452 goto fail;
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) {
1460 log_debug_errno(r, "Failed to get clock's now for scheduling ftruncate: %m");
1461 goto fail;
1462 }
1463
1464 r = sd_event_source_set_time(timer, now+f->post_change_timer_period);
1465 if (r < 0) {
1466 log_debug_errno(r, "Failed to set time for scheduling ftruncate: %m");
1467 goto fail;
1468 }
1469
1470 r = sd_event_source_set_enabled(timer, SD_EVENT_ONESHOT);
1471 if (r < 0) {
1472 log_debug_errno(r, "Failed to enable scheduled ftruncate: %m");
1473 goto fail;
1474 }
1475
1476 return;
1477
1478fail:
1479 /* On failure, let's simply post the change immediately. */
1480 journal_file_post_change(f);
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
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
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) {
1519 unsigned i;
1520 EntryItem *items;
1521 int r;
1522 uint64_t xor_hash = 0;
1523 struct dual_timestamp _ts;
1524
1525 assert(f);
1526 assert(f->header);
1527 assert(iovec || n_iovec == 0);
1528
1529 if (!ts) {
1530 dual_timestamp_get(&_ts);
1531 ts = &_ts;
1532 }
1533
1534#ifdef HAVE_GCRYPT
1535 r = journal_file_maybe_append_tag(f, ts->realtime);
1536 if (r < 0)
1537 return r;
1538#endif
1539
1540 /* alloca() can't take 0, hence let's allocate at least one */
1541 items = alloca(sizeof(EntryItem) * MAX(1u, n_iovec));
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)
1549 return r;
1550
1551 xor_hash ^= le64toh(o->data.hash);
1552 items[i].object_offset = htole64(p);
1553 items[i].hash = o->data.hash;
1554 }
1555
1556 /* Order by the position on disk, in order to improve seek
1557 * times for rotating media. */
1558 qsort_safe(items, n_iovec, sizeof(EntryItem), entry_item_cmp);
1559
1560 r = journal_file_append_entry_internal(f, ts, xor_hash, items, n_iovec, seqnum, ret, offset);
1561
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
1570 if (f->post_change_timer)
1571 schedule_post_change(f);
1572 else
1573 journal_file_post_change(f);
1574
1575 return r;
1576}
1577
1578typedef struct ChainCacheItem {
1579 uint64_t first; /* the array at the beginning of the chain */
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 */
1583 uint64_t last_index; /* the last index we looked at, to optimize locality when bisecting */
1584} ChainCacheItem;
1585
1586static void chain_cache_put(
1587 OrderedHashmap *h,
1588 ChainCacheItem *ci,
1589 uint64_t first,
1590 uint64_t array,
1591 uint64_t begin,
1592 uint64_t total,
1593 uint64_t last_index) {
1594
1595 if (!ci) {
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
1601 if (ordered_hashmap_size(h) >= CHAIN_CACHE_MAX) {
1602 ci = ordered_hashmap_steal_first(h);
1603 assert(ci);
1604 } else {
1605 ci = new(ChainCacheItem, 1);
1606 if (!ci)
1607 return;
1608 }
1609
1610 ci->first = first;
1611
1612 if (ordered_hashmap_put(h, &ci->first, ci) < 0) {
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;
1622 ci->last_index = last_index;
1623}
1624
1625static int generic_array_get(
1626 JournalFile *f,
1627 uint64_t first,
1628 uint64_t i,
1629 Object **ret, uint64_t *offset) {
1630
1631 Object *o;
1632 uint64_t p = 0, a, t = 0;
1633 int r;
1634 ChainCacheItem *ci;
1635
1636 assert(f);
1637
1638 a = first;
1639
1640 /* Try the chain cache first */
1641 ci = ordered_hashmap_get(f->chain_cache, &first);
1642 if (ci && i > ci->total) {
1643 a = ci->array;
1644 i -= ci->total;
1645 t = ci->total;
1646 }
1647
1648 while (a > 0) {
1649 uint64_t k;
1650
1651 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1652 if (r < 0)
1653 return r;
1654
1655 k = journal_file_entry_array_n_items(o);
1656 if (i < k) {
1657 p = le64toh(o->entry_array.items[i]);
1658 goto found;
1659 }
1660
1661 i -= k;
1662 t += k;
1663 a = le64toh(o->entry_array.next_entry_array_offset);
1664 }
1665
1666 return 0;
1667
1668found:
1669 /* Let's cache this item for the next invocation */
1670 chain_cache_put(f->chain_cache, ci, first, a, le64toh(o->entry_array.items[0]), t, i);
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
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) {
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);
1700 if (r < 0)
1701 return r;
1702
1703 if (ret)
1704 *ret = o;
1705
1706 if (offset)
1707 *offset = extra;
1708
1709 return 1;
1710 }
1711
1712 return generic_array_get(f, first, i-1, ret, offset);
1713}
1714
1715enum {
1716 TEST_FOUND,
1717 TEST_LEFT,
1718 TEST_RIGHT
1719};
1720
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;
1733 bool subtract_one = false;
1734 Object *o, *array = NULL;
1735 int r;
1736 ChainCacheItem *ci;
1737
1738 assert(f);
1739 assert(test_object);
1740
1741 /* Start with the first array in the chain */
1742 a = first;
1743
1744 ci = ordered_hashmap_get(f->chain_cache, &first);
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) {
1757 /* OK, what we are looking for is right of the
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;
1765 last_index = ci->last_index;
1766 }
1767 }
1768
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);
1773 if (r < 0)
1774 return r;
1775
1776 k = journal_file_entry_array_n_items(array);
1777 right = MIN(k, n);
1778 if (right <= 0)
1779 return 0;
1780
1781 i = right - 1;
1782 lp = p = le64toh(array->entry_array.items[i]);
1783 if (p <= 0)
1784 return -EBADMSG;
1785
1786 r = test_object(f, p, needle);
1787 if (r < 0)
1788 return r;
1789
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;
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 }
1846 }
1847
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);
1858 i = (left + right) / 2;
1859
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;
1867
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
1878 if (k >= n) {
1879 if (direction == DIRECTION_UP) {
1880 i = n;
1881 subtract_one = true;
1882 goto found;
1883 }
1884
1885 return 0;
1886 }
1887
1888 last_p = lp;
1889
1890 n -= k;
1891 t += k;
1892 last_index = (uint64_t) -1;
1893 a = le64toh(array->entry_array.next_entry_array_offset);
1894 }
1895
1896 return 0;
1897
1898found:
1899 if (subtract_one && t == 0 && i == 0)
1900 return 0;
1901
1902 /* Let's cache this item for the next invocation */
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);
1904
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)
1923 *idx = t + i + (subtract_one ? -1 : 0);
1924
1925 return 1;
1926}
1927
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) {
1939
1940 int r;
1941 bool step_back = false;
1942 Object *o;
1943
1944 assert(f);
1945 assert(test_object);
1946
1947 if (n <= 0)
1948 return 0;
1949
1950 /* This bisects the array in object 'first', but first checks
1951 * an extra */
1952 r = test_object(f, extra, needle);
1953 if (r < 0)
1954 return r;
1955
1956 if (r == TEST_FOUND)
1957 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
1958
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;
1966
1967 if (r == TEST_RIGHT) {
1968 if (direction == DIRECTION_DOWN)
1969 goto found;
1970 else
1971 return 0;
1972 }
1973
1974 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, offset, idx);
1975
1976 if (r == 0 && step_back)
1977 goto found;
1978
1979 if (r > 0 && idx)
1980 (*idx) ++;
1981
1982 return r;
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
2001_pure_ static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
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
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);
2021 if (r < 0)
2022 return r;
2023
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}
2031
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) {
2038 assert(f);
2039 assert(f->header);
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}
2049
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;
2067}
2068
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) {
2075 assert(f);
2076 assert(f->header);
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
2106static int find_data_object_by_boot_id(
2107 JournalFile *f,
2108 sd_id128_t boot_id,
2109 Object **o,
2110 uint64_t *b) {
2111
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
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
2126 Object *o;
2127 int r;
2128
2129 assert(f);
2130
2131 r = find_data_object_by_boot_id(f, boot_id, &o, NULL);
2132 if (r < 0)
2133 return r;
2134 if (r == 0)
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
2147void journal_file_reset_location(JournalFile *f) {
2148 f->location_type = LOCATION_HEAD;
2149 f->current_offset = 0;
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
2157void journal_file_save_location(JournalFile *f, Object *o, uint64_t offset) {
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);
2165}
2166
2167int journal_file_compare_locations(JournalFile *af, JournalFile *bf) {
2168 assert(af);
2169 assert(af->header);
2170 assert(bf);
2171 assert(bf->header);
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
2221int journal_file_next_entry(
2222 JournalFile *f,
2223 uint64_t p,
2224 direction_t direction,
2225 Object **ret, uint64_t *offset) {
2226
2227 uint64_t i, n, ofs;
2228 int r;
2229
2230 assert(f);
2231 assert(f->header);
2232
2233 n = le64toh(f->header->n_entries);
2234 if (n <= 0)
2235 return 0;
2236
2237 if (p == 0)
2238 i = direction == DIRECTION_DOWN ? 0 : n - 1;
2239 else {
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 }
2262 }
2263
2264 /* And jump to it */
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;
2283}
2284
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;
2293 int r;
2294 Object *d;
2295
2296 assert(f);
2297 assert(p > 0 || !o);
2298
2299 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
2300 if (r < 0)
2301 return r;
2302
2303 n = le64toh(d->data.n_entries);
2304 if (n <= 0)
2305 return n;
2306
2307 if (!o)
2308 i = direction == DIRECTION_DOWN ? 0 : n - 1;
2309 else {
2310 if (o->object.type != OBJECT_ENTRY)
2311 return -EINVAL;
2312
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)
2324 return r;
2325
2326 if (direction == DIRECTION_DOWN) {
2327 if (i >= n - 1)
2328 return 0;
2329
2330 i++;
2331 } else {
2332 if (i <= 0)
2333 return 0;
2334
2335 i--;
2336 }
2337
2338 }
2339
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}
2346
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
2381 Object *o, *d;
2382 int r;
2383 uint64_t b, z;
2384
2385 assert(f);
2386
2387 /* First, seek by time */
2388 r = find_data_object_by_boot_id(f, boot_id, &o, &b);
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 }
2454}
2455
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) {
2462
2463 Object *d;
2464 int r;
2465
2466 assert(f);
2467
2468 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
2469 if (r < 0)
2470 return r;
2471
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}
2481
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
2492 assert(f);
2493
2494 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
2495 if (r < 0)
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);
2506}
2507
2508void journal_file_dump(JournalFile *f) {
2509 Object *o;
2510 int r;
2511 uint64_t p;
2512
2513 assert(f);
2514 assert(f->header);
2515
2516 journal_file_print_header(f);
2517
2518 p = le64toh(f->header->header_size);
2519 while (p != 0) {
2520 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o);
2521 if (r < 0)
2522 goto fail;
2523
2524 switch (o->object.type) {
2525
2526 case OBJECT_UNUSED:
2527 printf("Type: OBJECT_UNUSED\n");
2528 break;
2529
2530 case OBJECT_DATA:
2531 printf("Type: OBJECT_DATA\n");
2532 break;
2533
2534 case OBJECT_FIELD:
2535 printf("Type: OBJECT_FIELD\n");
2536 break;
2537
2538 case OBJECT_ENTRY:
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));
2543 break;
2544
2545 case OBJECT_FIELD_HASH_TABLE:
2546 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
2547 break;
2548
2549 case OBJECT_DATA_HASH_TABLE:
2550 printf("Type: OBJECT_DATA_HASH_TABLE\n");
2551 break;
2552
2553 case OBJECT_ENTRY_ARRAY:
2554 printf("Type: OBJECT_ENTRY_ARRAY\n");
2555 break;
2556
2557 case OBJECT_TAG:
2558 printf("Type: OBJECT_TAG seqnum=%"PRIu64" epoch=%"PRIu64"\n",
2559 le64toh(o->tag.seqnum),
2560 le64toh(o->tag.epoch));
2561 break;
2562
2563 default:
2564 printf("Type: unknown (%i)\n", o->object.type);
2565 break;
2566 }
2567
2568 if (o->object.flags & OBJECT_COMPRESSION_MASK)
2569 printf("Flags: %s\n",
2570 object_compressed_to_string(o->object.flags & OBJECT_COMPRESSION_MASK));
2571
2572 if (p == le64toh(f->header->tail_object_offset))
2573 p = 0;
2574 else
2575 p = p + ALIGN64(le64toh(o->object.size));
2576 }
2577
2578 return;
2579fail:
2580 log_error("File corrupt");
2581}
2582
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
2592void journal_file_print_header(JournalFile *f) {
2593 char a[33], b[33], c[33], d[33];
2594 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX], z[FORMAT_TIMESTAMP_MAX];
2595 struct stat st;
2596 char bytes[FORMAT_BYTES_MAX];
2597
2598 assert(f);
2599 assert(f->header);
2600
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"
2608 "Incompatible Flags:%s%s%s\n"
2609 "Header size: %"PRIu64"\n"
2610 "Arena size: %"PRIu64"\n"
2611 "Data Hash Table Size: %"PRIu64"\n"
2612 "Field Hash Table Size: %"PRIu64"\n"
2613 "Rotate Suggested: %s\n"
2614 "Head Sequential Number: %"PRIu64"\n"
2615 "Tail Sequential Number: %"PRIu64"\n"
2616 "Head Realtime Timestamp: %s\n"
2617 "Tail Realtime Timestamp: %s\n"
2618 "Tail Monotonic Timestamp: %s\n"
2619 "Objects: %"PRIu64"\n"
2620 "Entry Objects: %"PRIu64"\n",
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),
2625 sd_id128_to_string(f->header->seqnum_id, d),
2626 f->header->state == STATE_OFFLINE ? "OFFLINE" :
2627 f->header->state == STATE_ONLINE ? "ONLINE" :
2628 f->header->state == STATE_ARCHIVED ? "ARCHIVED" : "UNKNOWN",
2629 JOURNAL_HEADER_SEALED(f->header) ? " SEALED" : "",
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) ? " ???" : "",
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),
2638 yes_no(journal_file_rotate_suggested(f, 0)),
2639 le64toh(f->header->head_entry_seqnum),
2640 le64toh(f->header->tail_entry_seqnum),
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)),
2643 format_timespan(z, sizeof(z), le64toh(f->header->tail_entry_monotonic), USEC_PER_MSEC),
2644 le64toh(f->header->n_objects),
2645 le64toh(f->header->n_entries));
2646
2647 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2648 printf("Data Objects: %"PRIu64"\n"
2649 "Data Hash Table Fill: %.1f%%\n",
2650 le64toh(f->header->n_data),
2651 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
2652
2653 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2654 printf("Field Objects: %"PRIu64"\n"
2655 "Field Hash Table Fill: %.1f%%\n",
2656 le64toh(f->header->n_fields),
2657 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
2658
2659 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags))
2660 printf("Tag Objects: %"PRIu64"\n",
2661 le64toh(f->header->n_tags));
2662 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
2663 printf("Entry Array Objects: %"PRIu64"\n",
2664 le64toh(f->header->n_entry_arrays));
2665
2666 if (fstat(f->fd, &st) >= 0)
2667 printf("Disk usage: %s\n", format_bytes(bytes, sizeof(bytes), (uint64_t) st.st_blocks * 512ULL));
2668}
2669
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
2705int journal_file_open(
2706 const char *fname,
2707 int flags,
2708 mode_t mode,
2709 bool compress,
2710 bool seal,
2711 JournalMetrics *metrics,
2712 MMapCache *mmap_cache,
2713 JournalFile *template,
2714 JournalFile **ret) {
2715
2716 bool newly_created = false;
2717 JournalFile *f;
2718 void *h;
2719 int r;
2720
2721 assert(fname);
2722 assert(ret);
2723
2724 if ((flags & O_ACCMODE) != O_RDONLY &&
2725 (flags & O_ACCMODE) != O_RDWR)
2726 return -EINVAL;
2727
2728 if (!endswith(fname, ".journal") &&
2729 !endswith(fname, ".journal~"))
2730 return -EINVAL;
2731
2732 f = new0(JournalFile, 1);
2733 if (!f)
2734 return -ENOMEM;
2735
2736 f->fd = -1;
2737 f->mode = mode;
2738
2739 f->flags = flags;
2740 f->prot = prot_from_flags(flags);
2741 f->writable = (flags & O_ACCMODE) != O_RDONLY;
2742#if defined(HAVE_LZ4)
2743 f->compress_lz4 = compress;
2744#elif defined(HAVE_XZ)
2745 f->compress_xz = compress;
2746#endif
2747#ifdef HAVE_GCRYPT
2748 f->seal = seal;
2749#endif
2750
2751 if (mmap_cache)
2752 f->mmap = mmap_cache_ref(mmap_cache);
2753 else {
2754 f->mmap = mmap_cache_new();
2755 if (!f->mmap) {
2756 r = -ENOMEM;
2757 goto fail;
2758 }
2759 }
2760
2761 f->path = strdup(fname);
2762 if (!f->path) {
2763 r = -ENOMEM;
2764 goto fail;
2765 }
2766
2767 f->chain_cache = ordered_hashmap_new(&uint64_hash_ops);
2768 if (!f->chain_cache) {
2769 r = -ENOMEM;
2770 goto fail;
2771 }
2772
2773 f->fd = open(f->path, f->flags|O_CLOEXEC, f->mode);
2774 if (f->fd < 0) {
2775 r = -errno;
2776 goto fail;
2777 }
2778
2779 r = journal_file_fstat(f);
2780 if (r < 0)
2781 goto fail;
2782
2783 if (f->last_stat.st_size == 0 && f->writable) {
2784
2785 (void) journal_file_warn_btrfs(f);
2786
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,
2795 * and rely solely on mtime/atime/ctime of the file. */
2796
2797 fd_setcrtime(f->fd, 0);
2798
2799#ifdef HAVE_GCRYPT
2800 /* Try to load the FSPRG state, and if we can't, then
2801 * just don't do sealing */
2802 if (f->seal) {
2803 r = journal_file_fss_load(f);
2804 if (r < 0)
2805 f->seal = false;
2806 }
2807#endif
2808
2809 r = journal_file_init_header(f, template);
2810 if (r < 0)
2811 goto fail;
2812
2813 r = journal_file_fstat(f);
2814 if (r < 0)
2815 goto fail;
2816
2817 newly_created = true;
2818 }
2819
2820 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
2821 r = -ENODATA;
2822 goto fail;
2823 }
2824
2825 r = mmap_cache_get(f->mmap, f->fd, f->prot, CONTEXT_HEADER, true, 0, PAGE_ALIGN(sizeof(Header)), &f->last_stat, &h);
2826 if (r < 0)
2827 goto fail;
2828
2829 f->header = h;
2830
2831 if (!newly_created) {
2832 r = journal_file_verify_header(f);
2833 if (r < 0)
2834 goto fail;
2835 }
2836
2837#ifdef HAVE_GCRYPT
2838 if (!newly_created && f->writable) {
2839 r = journal_file_fss_load(f);
2840 if (r < 0)
2841 goto fail;
2842 }
2843#endif
2844
2845 if (f->writable) {
2846 if (metrics) {
2847 journal_default_metrics(metrics, f->fd);
2848 f->metrics = *metrics;
2849 } else if (template)
2850 f->metrics = template->metrics;
2851
2852 r = journal_file_refresh_header(f);
2853 if (r < 0)
2854 goto fail;
2855 }
2856
2857#ifdef HAVE_GCRYPT
2858 r = journal_file_hmac_setup(f);
2859 if (r < 0)
2860 goto fail;
2861#endif
2862
2863 if (newly_created) {
2864 r = journal_file_setup_field_hash_table(f);
2865 if (r < 0)
2866 goto fail;
2867
2868 r = journal_file_setup_data_hash_table(f);
2869 if (r < 0)
2870 goto fail;
2871
2872#ifdef HAVE_GCRYPT
2873 r = journal_file_append_first_tag(f);
2874 if (r < 0)
2875 goto fail;
2876#endif
2877 }
2878
2879 if (mmap_cache_got_sigbus(f->mmap, f->fd)) {
2880 r = -EIO;
2881 goto fail;
2882 }
2883
2884 if (template && template->post_change_timer) {
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);
2889
2890 if (r < 0)
2891 goto fail;
2892 }
2893
2894 *ret = f;
2895 return 0;
2896
2897fail:
2898 if (f->fd >= 0 && mmap_cache_got_sigbus(f->mmap, f->fd))
2899 r = -EIO;
2900
2901 (void) journal_file_close(f);
2902
2903 return r;
2904}
2905
2906int journal_file_rotate(JournalFile **f, bool compress, bool seal) {
2907 _cleanup_free_ char *p = NULL;
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);
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)
2930 return -ENOMEM;
2931
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. */
2935 r = rename(old_file->path, p);
2936 if (r < 0 && errno != ENOENT)
2937 return -errno;
2938
2939 old_file->header->state = STATE_ARCHIVED;
2940
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
2946 r = journal_file_open(old_file->path, old_file->flags, old_file->mode, compress, seal, NULL, old_file->mmap, old_file, &new_file);
2947 journal_file_close(old_file);
2948
2949 *f = new_file;
2950 return r;
2951}
2952
2953int journal_file_open_reliably(
2954 const char *fname,
2955 int flags,
2956 mode_t mode,
2957 bool compress,
2958 bool seal,
2959 JournalMetrics *metrics,
2960 MMapCache *mmap_cache,
2961 JournalFile *template,
2962 JournalFile **ret) {
2963
2964 int r;
2965 size_t l;
2966 _cleanup_free_ char *p = NULL;
2967
2968 r = journal_file_open(fname, flags, mode, compress, seal, metrics, mmap_cache, template, ret);
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 */))
2978 return r;
2979
2980 if ((flags & O_ACCMODE) == O_RDONLY)
2981 return r;
2982
2983 if (!(flags & O_CREAT))
2984 return r;
2985
2986 if (!endswith(fname, ".journal"))
2987 return r;
2988
2989 /* The file is corrupted. Rotate it away and try it again (but only once) */
2990
2991 l = strlen(fname);
2992 if (asprintf(&p, "%.*s@%016"PRIx64 "-%016"PRIx64 ".journal~",
2993 (int) l - 8, fname,
2994 now(CLOCK_REALTIME),
2995 random_u64()) < 0)
2996 return -ENOMEM;
2997
2998 if (rename(fname, p) < 0)
2999 return -errno;
3000
3001 /* btrfs doesn't cope well with our write pattern and
3002 * fragments heavily. Let's defrag all files we rotate */
3003
3004 (void) chattr_path(p, false, FS_NOCOW_FL);
3005 (void) btrfs_defrag(p);
3006
3007 log_warning_errno(r, "File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
3008
3009 return journal_file_open(fname, flags, mode, compress, seal, metrics, mmap_cache, template, ret);
3010}
3011
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
3030 n = journal_file_entry_n_items(o);
3031 /* alloca() can't take 0, hence let's allocate at least one */
3032 items = alloca(sizeof(EntryItem) * MAX(1u, n));
3033
3034 for (i = 0; i < n; i++) {
3035 uint64_t l, h;
3036 le64_t le_hash;
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
3058 if (o->object.flags & OBJECT_COMPRESSION_MASK) {
3059#if defined(HAVE_XZ) || defined(HAVE_LZ4)
3060 size_t rsize = 0;
3061
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;
3066
3067 data = from->compress_buffer;
3068 l = rsize;
3069#else
3070 return -EPROTONOSUPPORT;
3071#endif
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
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;
3094}
3095
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
3111void journal_default_metrics(JournalMetrics *m, int fd) {
3112 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX], e[FORMAT_BYTES_MAX];
3113 struct statvfs ss;
3114 uint64_t fs_size;
3115
3116 assert(m);
3117 assert(fd >= 0);
3118
3119 if (fstatvfs(fd, &ss) >= 0)
3120 fs_size = ss.f_frsize * ss.f_blocks;
3121 else {
3122 log_debug_errno(errno, "Failed to detremine disk size: %m");
3123 fs_size = 0;
3124 }
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
3141 if (m->max_use != 0 && m->max_use < JOURNAL_FILE_SIZE_MIN*2)
3142 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
3143 }
3144
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
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
3159 if (m->max_size != 0) {
3160 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
3161 m->max_size = JOURNAL_FILE_SIZE_MIN;
3162
3163 if (m->max_use != 0 && m->max_size*2 > m->max_use)
3164 m->max_use = m->max_size*2;
3165 }
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
3175 if (m->max_size != 0 && m->min_size > m->max_size)
3176 m->max_size = m->min_size;
3177 }
3178
3179 if (m->keep_free == (uint64_t) -1) {
3180
3181 if (fs_size > 0) {
3182 m->keep_free = PAGE_ALIGN(fs_size * 3 / 20); /* 15% of file system size */
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
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);
3201}
3202
3203int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
3204 assert(f);
3205 assert(f->header);
3206 assert(from || to);
3207
3208 if (from) {
3209 if (f->header->head_entry_realtime == 0)
3210 return -ENOENT;
3211
3212 *from = le64toh(f->header->head_entry_realtime);
3213 }
3214
3215 if (to) {
3216 if (f->header->tail_entry_realtime == 0)
3217 return -ENOENT;
3218
3219 *to = le64toh(f->header->tail_entry_realtime);
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) {
3226 Object *o;
3227 uint64_t p;
3228 int r;
3229
3230 assert(f);
3231 assert(from || to);
3232
3233 r = find_data_object_by_boot_id(f, boot_id, &o, &p);
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}
3266
3267bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec) {
3268 assert(f);
3269 assert(f->header);
3270
3271 /* If we gained new header fields we gained new features,
3272 * hence suggest a rotation */
3273 if (le64toh(f->header->header_size) < sizeof(Header)) {
3274 log_debug("%s uses an outdated header, suggesting rotation.", f->path);
3275 return true;
3276 }
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))
3285 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
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.",
3287 f->path,
3288 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
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));
3293 return true;
3294 }
3295
3296 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
3297 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
3298 log_debug("Field hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items), suggesting rotation.",
3299 f->path,
3300 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
3301 le64toh(f->header->n_fields),
3302 le64toh(f->header->field_hash_table_size) / sizeof(HashItem));
3303 return true;
3304 }
3305
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
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
3323 return false;
3324}