]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal/journal-file.c
catalog: update Polish translation
[thirdparty/systemd.git] / src / journal / journal-file.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
cec736d2 2
cec736d2 3#include <errno.h>
cec736d2 4#include <fcntl.h>
11689d2a 5#include <linux/fs.h>
ac2e41f5 6#include <pthread.h>
07630cea
LP
7#include <stddef.h>
8#include <sys/mman.h>
9#include <sys/statvfs.h>
10#include <sys/uio.h>
11#include <unistd.h>
fb0951b0 12
a03d4359
ZJS
13#include "sd-event.h"
14
b5efdb8a 15#include "alloc-util.h"
f27a3864 16#include "btrfs-util.h"
c8b3094d 17#include "chattr-util.h"
07630cea 18#include "compress.h"
4ce534f4 19#include "env-util.h"
3ffd4af2 20#include "fd-util.h"
aa892669 21#include "format-util.h"
11b29a96 22#include "fs-util.h"
0284adc6 23#include "journal-authenticate.h"
cec736d2
LP
24#include "journal-def.h"
25#include "journal-file.h"
26#include "lookup3.h"
0a970718 27#include "memory-util.h"
5d1ce257 28#include "path-util.h"
3df3e884 29#include "random-util.h"
b58c888f 30#include "set.h"
760877e9 31#include "sort-util.h"
3cc44114 32#include "stat-util.h"
07630cea 33#include "string-util.h"
4761fd0f 34#include "strv.h"
89a5a90c 35#include "xattr-util.h"
cec736d2 36
4a92baf3
LP
37#define DEFAULT_DATA_HASH_TABLE_SIZE (2047ULL*sizeof(HashItem))
38#define DEFAULT_FIELD_HASH_TABLE_SIZE (333ULL*sizeof(HashItem))
cec736d2 39
57850536
AG
40#define DEFAULT_COMPRESS_THRESHOLD (512ULL)
41#define MIN_COMPRESS_THRESHOLD (8ULL)
807e17f0 42
babfc091 43/* This is the minimum journal file size */
6aae0b1a 44#define JOURNAL_FILE_SIZE_MIN (512 * 1024ULL) /* 512 KiB */
babfc091
LP
45
46/* These are the lower and upper bounds if we deduce the max_use value
47 * from the file system size */
6aae0b1a
ZJS
48#define MAX_USE_LOWER (1 * 1024 * 1024ULL) /* 1 MiB */
49#define MAX_USE_UPPER (4 * 1024 * 1024 * 1024ULL) /* 4 GiB */
babfc091 50
6aae0b1a
ZJS
51/* Those are the lower and upper bounds for the minimal use limit,
52 * i.e. how much we'll use even if keep_free suggests otherwise. */
53#define MIN_USE_LOW (1 * 1024 * 1024ULL) /* 1 MiB */
54#define MIN_USE_HIGH (16 * 1024 * 1024ULL) /* 16 MiB */
8580d1f7 55
babfc091 56/* This is the upper bound if we deduce max_size from max_use */
6aae0b1a 57#define MAX_SIZE_UPPER (128 * 1024 * 1024ULL) /* 128 MiB */
babfc091
LP
58
59/* This is the upper bound if we deduce the keep_free value from the
60 * file system size */
6aae0b1a 61#define KEEP_FREE_UPPER (4 * 1024 * 1024 * 1024ULL) /* 4 GiB */
babfc091
LP
62
63/* This is the keep_free value when we can't determine the system
64 * size */
6aae0b1a 65#define DEFAULT_KEEP_FREE (1024 * 1024ULL) /* 1 MB */
babfc091 66
8580d1f7 67/* This is the default maximum number of journal files to keep around. */
6aae0b1a 68#define DEFAULT_N_MAX_FILES 100
8580d1f7 69
dca6219e
LP
70/* n_data was the first entry we added after the initial file format design */
71#define HEADER_SIZE_MIN ALIGN64(offsetof(Header, n_data))
cec736d2 72
a4bcff5b
LP
73/* How many entries to keep in the entry array chain cache at max */
74#define CHAIN_CACHE_MAX 20
75
a676e665 76/* How much to increase the journal file size at once each time we allocate something new. */
6aae0b1a 77#define FILE_SIZE_INCREASE (8 * 1024 * 1024ULL) /* 8MB */
a676e665 78
2678031a
LP
79/* Reread fstat() of the file for detecting deletions at least this often */
80#define LAST_STAT_REFRESH_USEC (5*USEC_PER_SEC)
81
fa6ac760
LP
82/* The mmap context to use for the header we pick as one above the last defined typed */
83#define CONTEXT_HEADER _OBJECT_TYPE_MAX
84
0dbe57ee
LP
85/* Longest hash chain to rotate after */
86#define HASH_CHAIN_DEPTH_MAX 100
87
51804460
ZJS
88#ifdef __clang__
89# pragma GCC diagnostic ignored "-Waddress-of-packed-member"
90#endif
91
ac2e41f5
VC
92/* This may be called from a separate thread to prevent blocking the caller for the duration of fsync().
93 * As a result we use atomic operations on f->offline_state for inter-thread communications with
94 * journal_file_set_offline() and journal_file_set_online(). */
95static void journal_file_set_offline_internal(JournalFile *f) {
26687bf8 96 assert(f);
ac2e41f5
VC
97 assert(f->fd >= 0);
98 assert(f->header);
99
100 for (;;) {
101 switch (f->offline_state) {
102 case OFFLINE_CANCEL:
103 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_CANCEL, OFFLINE_DONE))
104 continue;
105 return;
106
107 case OFFLINE_AGAIN_FROM_SYNCING:
108 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_SYNCING, OFFLINE_SYNCING))
109 continue;
110 break;
111
112 case OFFLINE_AGAIN_FROM_OFFLINING:
113 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_OFFLINING, OFFLINE_SYNCING))
114 continue;
115 break;
116
117 case OFFLINE_SYNCING:
118 (void) fsync(f->fd);
26687bf8 119
ac2e41f5
VC
120 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_OFFLINING))
121 continue;
26687bf8 122
8eb85171 123 f->header->state = f->archive ? STATE_ARCHIVED : STATE_OFFLINE;
ac2e41f5
VC
124 (void) fsync(f->fd);
125 break;
126
127 case OFFLINE_OFFLINING:
128 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_OFFLINING, OFFLINE_DONE))
129 continue;
4831981d 130 _fallthrough_;
ac2e41f5
VC
131 case OFFLINE_DONE:
132 return;
133
134 case OFFLINE_JOINED:
135 log_debug("OFFLINE_JOINED unexpected offline state for journal_file_set_offline_internal()");
136 return;
137 }
138 }
139}
140
141static void * journal_file_set_offline_thread(void *arg) {
142 JournalFile *f = arg;
143
fa7ff4cf
LP
144 (void) pthread_setname_np(pthread_self(), "journal-offline");
145
ac2e41f5
VC
146 journal_file_set_offline_internal(f);
147
148 return NULL;
149}
150
151static int journal_file_set_offline_thread_join(JournalFile *f) {
152 int r;
153
154 assert(f);
155
156 if (f->offline_state == OFFLINE_JOINED)
157 return 0;
158
159 r = pthread_join(f->offline_thread, NULL);
160 if (r)
161 return -r;
162
163 f->offline_state = OFFLINE_JOINED;
26687bf8 164
be7cdd8e 165 if (mmap_cache_got_sigbus(f->mmap, f->cache_fd))
fa6ac760
LP
166 return -EIO;
167
ac2e41f5
VC
168 return 0;
169}
26687bf8 170
ac2e41f5
VC
171/* Trigger a restart if the offline thread is mid-flight in a restartable state. */
172static bool journal_file_set_offline_try_restart(JournalFile *f) {
173 for (;;) {
174 switch (f->offline_state) {
175 case OFFLINE_AGAIN_FROM_SYNCING:
176 case OFFLINE_AGAIN_FROM_OFFLINING:
177 return true;
178
179 case OFFLINE_CANCEL:
180 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_CANCEL, OFFLINE_AGAIN_FROM_SYNCING))
181 continue;
182 return true;
183
184 case OFFLINE_SYNCING:
185 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_AGAIN_FROM_SYNCING))
186 continue;
187 return true;
188
189 case OFFLINE_OFFLINING:
190 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_OFFLINING, OFFLINE_AGAIN_FROM_OFFLINING))
191 continue;
192 return true;
26687bf8
OS
193
194 default:
ac2e41f5
VC
195 return false;
196 }
26687bf8
OS
197 }
198}
199
ac2e41f5
VC
200/* Sets a journal offline.
201 *
202 * If wait is false then an offline is dispatched in a separate thread for a
203 * subsequent journal_file_set_offline() or journal_file_set_online() of the
204 * same journal to synchronize with.
205 *
206 * If wait is true, then either an existing offline thread will be restarted
207 * and joined, or if none exists the offline is simply performed in this
208 * context without involving another thread.
209 */
210int journal_file_set_offline(JournalFile *f, bool wait) {
211 bool restarted;
212 int r;
213
26687bf8
OS
214 assert(f);
215
216 if (!f->writable)
217 return -EPERM;
218
846e5418 219 if (f->fd < 0 || !f->header)
26687bf8
OS
220 return -EINVAL;
221
b8f99e27
VC
222 /* An offlining journal is implicitly online and may modify f->header->state,
223 * we must also join any potentially lingering offline thread when not online. */
224 if (!journal_file_is_offlining(f) && f->header->state != STATE_ONLINE)
225 return journal_file_set_offline_thread_join(f);
26687bf8 226
ac2e41f5
VC
227 /* Restart an in-flight offline thread and wait if needed, or join a lingering done one. */
228 restarted = journal_file_set_offline_try_restart(f);
229 if ((restarted && wait) || !restarted) {
230 r = journal_file_set_offline_thread_join(f);
231 if (r < 0)
232 return r;
233 }
26687bf8 234
ac2e41f5
VC
235 if (restarted)
236 return 0;
237
238 /* Initiate a new offline. */
239 f->offline_state = OFFLINE_SYNCING;
fa6ac760 240
ac2e41f5
VC
241 if (wait) /* Without using a thread if waiting. */
242 journal_file_set_offline_internal(f);
243 else {
5e9f01e8
LP
244 sigset_t ss, saved_ss;
245 int k;
246
cd2a429e 247 assert_se(sigfillset(&ss) >= 0);
08f9e80b
CM
248 /* Don't block SIGBUS since the offlining thread accesses a memory mapped file.
249 * Asynchronous SIGBUS signals can safely be handled by either thread. */
250 assert_se(sigdelset(&ss, SIGBUS) >= 0);
5e9f01e8
LP
251
252 r = pthread_sigmask(SIG_BLOCK, &ss, &saved_ss);
253 if (r > 0)
254 return -r;
255
ac2e41f5 256 r = pthread_create(&f->offline_thread, NULL, journal_file_set_offline_thread, f);
5e9f01e8
LP
257
258 k = pthread_sigmask(SIG_SETMASK, &saved_ss, NULL);
ec9ffa2c
VC
259 if (r > 0) {
260 f->offline_state = OFFLINE_JOINED;
ac2e41f5 261 return -r;
ec9ffa2c 262 }
5e9f01e8
LP
263 if (k > 0)
264 return -k;
ac2e41f5
VC
265 }
266
267 return 0;
268}
269
270static int journal_file_set_online(JournalFile *f) {
83bf6b67 271 bool wait = true;
ac2e41f5
VC
272
273 assert(f);
274
275 if (!f->writable)
276 return -EPERM;
277
846e5418 278 if (f->fd < 0 || !f->header)
ac2e41f5
VC
279 return -EINVAL;
280
83bf6b67 281 while (wait) {
ac2e41f5
VC
282 switch (f->offline_state) {
283 case OFFLINE_JOINED:
284 /* No offline thread, no need to wait. */
83bf6b67 285 wait = false;
ac2e41f5
VC
286 break;
287
288 case OFFLINE_SYNCING:
289 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_SYNCING, OFFLINE_CANCEL))
290 continue;
291 /* Canceled syncing prior to offlining, no need to wait. */
83bf6b67 292 wait = false;
ac2e41f5
VC
293 break;
294
295 case OFFLINE_AGAIN_FROM_SYNCING:
296 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_SYNCING, OFFLINE_CANCEL))
297 continue;
298 /* Canceled restart from syncing, no need to wait. */
83bf6b67 299 wait = false;
ac2e41f5
VC
300 break;
301
302 case OFFLINE_AGAIN_FROM_OFFLINING:
303 if (!__sync_bool_compare_and_swap(&f->offline_state, OFFLINE_AGAIN_FROM_OFFLINING, OFFLINE_CANCEL))
304 continue;
305 /* Canceled restart from offlining, must wait for offlining to complete however. */
4831981d 306 _fallthrough_;
ac2e41f5
VC
307 default: {
308 int r;
309
310 r = journal_file_set_offline_thread_join(f);
311 if (r < 0)
312 return r;
313
83bf6b67 314 wait = false;
ac2e41f5
VC
315 break;
316 }
317 }
318 }
26687bf8 319
be7cdd8e 320 if (mmap_cache_got_sigbus(f->mmap, f->cache_fd))
fa6ac760
LP
321 return -EIO;
322
ac2e41f5
VC
323 switch (f->header->state) {
324 case STATE_ONLINE:
325 return 0;
26687bf8 326
ac2e41f5
VC
327 case STATE_OFFLINE:
328 f->header->state = STATE_ONLINE;
329 (void) fsync(f->fd);
330 return 0;
331
332 default:
333 return -EINVAL;
334 }
26687bf8
OS
335}
336
b58c888f
VC
337bool journal_file_is_offlining(JournalFile *f) {
338 assert(f);
339
340 __sync_synchronize();
341
3742095b 342 if (IN_SET(f->offline_state, OFFLINE_DONE, OFFLINE_JOINED))
b58c888f
VC
343 return false;
344
345 return true;
346}
347
804ae586 348JournalFile* journal_file_close(JournalFile *f) {
c377a6f3
YW
349 if (!f)
350 return NULL;
cec736d2 351
349cc4a5 352#if HAVE_GCRYPT
b0af6f41 353 /* Write the final tag */
43cd8794
FB
354 if (f->seal && f->writable) {
355 int r;
356
357 r = journal_file_append_tag(f);
358 if (r < 0)
359 log_error_errno(r, "Failed to append tag when closing journal: %m");
360 }
feb12d3e 361#endif
b0af6f41 362
7a24f3bf 363 if (f->post_change_timer) {
b6cdfbe5
ZJS
364 if (sd_event_source_get_enabled(f->post_change_timer, NULL) > 0)
365 journal_file_post_change(f);
7a24f3bf 366
1d3fe304 367 sd_event_source_disable_unref(f->post_change_timer);
7a24f3bf
VC
368 }
369
ac2e41f5 370 journal_file_set_offline(f, true);
cec736d2 371
be7cdd8e
VC
372 if (f->mmap && f->cache_fd)
373 mmap_cache_free_fd(f->mmap, f->cache_fd);
cec736d2 374
11689d2a
LP
375 if (f->fd >= 0 && f->defrag_on_close) {
376
377 /* Be friendly to btrfs: turn COW back on again now,
378 * and defragment the file. We won't write to the file
379 * ever again, hence remove all fragmentation, and
380 * reenable all the good bits COW usually provides
381 * (such as data checksumming). */
382
db9a4254 383 (void) chattr_fd(f->fd, 0, FS_NOCOW_FL, NULL);
11689d2a
LP
384 (void) btrfs_defrag_fd(f->fd);
385 }
f27a3864 386
5d1ce257
LP
387 if (f->close_fd)
388 safe_close(f->fd);
cec736d2 389 free(f->path);
807e17f0 390
f649045c 391 mmap_cache_unref(f->mmap);
16e9f408 392
4743015d 393 ordered_hashmap_free_free(f->chain_cache);
a4bcff5b 394
d80b051c 395#if HAVE_COMPRESSION
807e17f0
LP
396 free(f->compress_buffer);
397#endif
398
349cc4a5 399#if HAVE_GCRYPT
baed47c3
LP
400 if (f->fss_file)
401 munmap(f->fss_file, PAGE_ALIGN(f->fss_file_size));
dc4ebc07 402 else
b7c9ae91
LP
403 free(f->fsprg_state);
404
405 free(f->fsprg_seed);
7560fffc
LP
406
407 if (f->hmac)
408 gcry_md_close(f->hmac);
409#endif
410
6b430fdb 411 return mfree(f);
cec736d2
LP
412}
413
0ac38b70 414static int journal_file_init_header(JournalFile *f, JournalFile *template) {
d89c8fdf 415 Header h = {};
cec736d2
LP
416 ssize_t k;
417 int r;
418
419 assert(f);
420
7560fffc 421 memcpy(h.signature, HEADER_SIGNATURE, 8);
23b0b2b2 422 h.header_size = htole64(ALIGN64(sizeof(h)));
cec736d2 423
d89c8fdf
ZJS
424 h.incompatible_flags |= htole32(
425 f->compress_xz * HEADER_INCOMPATIBLE_COMPRESSED_XZ |
4ce534f4 426 f->compress_lz4 * HEADER_INCOMPATIBLE_COMPRESSED_LZ4 |
8653185a 427 f->compress_zstd * HEADER_INCOMPATIBLE_COMPRESSED_ZSTD |
4ce534f4 428 f->keyed_hash * HEADER_INCOMPATIBLE_KEYED_HASH);
7560fffc 429
d89c8fdf
ZJS
430 h.compatible_flags = htole32(
431 f->seal * HEADER_COMPATIBLE_SEALED);
7560fffc 432
cec736d2
LP
433 r = sd_id128_randomize(&h.file_id);
434 if (r < 0)
435 return r;
436
0ac38b70
LP
437 if (template) {
438 h.seqnum_id = template->header->seqnum_id;
beec0085 439 h.tail_entry_seqnum = template->header->tail_entry_seqnum;
0ac38b70
LP
440 } else
441 h.seqnum_id = h.file_id;
cec736d2
LP
442
443 k = pwrite(f->fd, &h, sizeof(h), 0);
444 if (k < 0)
445 return -errno;
446
447 if (k != sizeof(h))
448 return -EIO;
449
450 return 0;
451}
452
453static int journal_file_refresh_header(JournalFile *f) {
fa6ac760 454 int r;
cec736d2
LP
455
456 assert(f);
c88cc6af 457 assert(f->header);
cec736d2
LP
458
459 r = sd_id128_get_machine(&f->header->machine_id);
fd4885df
ZJS
460 if (IN_SET(r, -ENOENT, -ENOMEDIUM))
461 /* We don't have a machine-id, let's continue without */
462 zero(f->header->machine_id);
463 else if (r < 0)
cec736d2
LP
464 return r;
465
e958c057 466 r = sd_id128_get_boot(&f->header->boot_id);
cec736d2
LP
467 if (r < 0)
468 return r;
469
fa6ac760 470 r = journal_file_set_online(f);
b788cc23 471
7560fffc 472 /* Sync the online state to disk */
fb426037 473 (void) fsync(f->fd);
b788cc23 474
a0fe2a2d
LP
475 /* We likely just created a new file, also sync the directory this file is located in. */
476 (void) fsync_directory_of_file(f->fd);
477
fa6ac760 478 return r;
cec736d2
LP
479}
480
4214009f
ZJS
481static bool warn_wrong_flags(const JournalFile *f, bool compatible) {
482 const uint32_t any = compatible ? HEADER_COMPATIBLE_ANY : HEADER_INCOMPATIBLE_ANY,
483 supported = compatible ? HEADER_COMPATIBLE_SUPPORTED : HEADER_INCOMPATIBLE_SUPPORTED;
484 const char *type = compatible ? "compatible" : "incompatible";
d89c8fdf
ZJS
485 uint32_t flags;
486
4214009f
ZJS
487 flags = le32toh(compatible ? f->header->compatible_flags : f->header->incompatible_flags);
488
489 if (flags & ~supported) {
490 if (flags & ~any)
4761fd0f 491 log_debug("Journal file %s has unknown %s flags 0x%"PRIx32,
4214009f
ZJS
492 f->path, type, flags & ~any);
493 flags = (flags & any) & ~supported;
4761fd0f 494 if (flags) {
8653185a 495 const char* strv[5];
4761fd0f
ZJS
496 unsigned n = 0;
497 _cleanup_free_ char *t = NULL;
498
4ce534f4
LP
499 if (compatible) {
500 if (flags & HEADER_COMPATIBLE_SEALED)
501 strv[n++] = "sealed";
502 } else {
503 if (flags & HEADER_INCOMPATIBLE_COMPRESSED_XZ)
504 strv[n++] = "xz-compressed";
505 if (flags & HEADER_INCOMPATIBLE_COMPRESSED_LZ4)
506 strv[n++] = "lz4-compressed";
8653185a
LP
507 if (flags & HEADER_INCOMPATIBLE_COMPRESSED_ZSTD)
508 strv[n++] = "zstd-compressed";
4ce534f4
LP
509 if (flags & HEADER_INCOMPATIBLE_KEYED_HASH)
510 strv[n++] = "keyed-hash";
511 }
4761fd0f
ZJS
512 strv[n] = NULL;
513 assert(n < ELEMENTSOF(strv));
514
515 t = strv_join((char**) strv, ", ");
516 log_debug("Journal file %s uses %s %s %s disabled at compilation time.",
517 f->path, type, n > 1 ? "flags" : "flag", strnull(t));
518 }
4214009f
ZJS
519 return true;
520 }
521
522 return false;
523}
524
525static int journal_file_verify_header(JournalFile *f) {
6f94e420
TS
526 uint64_t arena_size, header_size;
527
cec736d2 528 assert(f);
c88cc6af 529 assert(f->header);
cec736d2 530
7560fffc 531 if (memcmp(f->header->signature, HEADER_SIGNATURE, 8))
cec736d2
LP
532 return -EBADMSG;
533
4214009f
ZJS
534 /* In both read and write mode we refuse to open files with incompatible
535 * flags we don't know. */
536 if (warn_wrong_flags(f, false))
cec736d2
LP
537 return -EPROTONOSUPPORT;
538
4214009f
ZJS
539 /* When open for writing we refuse to open files with compatible flags, too. */
540 if (f->writable && warn_wrong_flags(f, true))
d89c8fdf 541 return -EPROTONOSUPPORT;
7560fffc 542
db11ac1a
LP
543 if (f->header->state >= _STATE_MAX)
544 return -EBADMSG;
545
893e0f8f 546 header_size = le64toh(READ_NOW(f->header->header_size));
6f94e420 547
dca6219e 548 /* The first addition was n_data, so check that we are at least this large */
6f94e420 549 if (header_size < HEADER_SIZE_MIN)
23b0b2b2
LP
550 return -EBADMSG;
551
8088cbd3 552 if (JOURNAL_HEADER_SEALED(f->header) && !JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
beec0085
LP
553 return -EBADMSG;
554
893e0f8f 555 arena_size = le64toh(READ_NOW(f->header->arena_size));
6f94e420
TS
556
557 if (UINT64_MAX - header_size < arena_size || header_size + arena_size > (uint64_t) f->last_stat.st_size)
db11ac1a
LP
558 return -ENODATA;
559
6f94e420 560 if (le64toh(f->header->tail_object_offset) > header_size + arena_size)
db11ac1a
LP
561 return -ENODATA;
562
7762e02b
LP
563 if (!VALID64(le64toh(f->header->data_hash_table_offset)) ||
564 !VALID64(le64toh(f->header->field_hash_table_offset)) ||
565 !VALID64(le64toh(f->header->tail_object_offset)) ||
566 !VALID64(le64toh(f->header->entry_array_offset)))
567 return -ENODATA;
568
cec736d2 569 if (f->writable) {
cec736d2 570 sd_id128_t machine_id;
ae739cc1 571 uint8_t state;
cec736d2
LP
572 int r;
573
574 r = sd_id128_get_machine(&machine_id);
575 if (r < 0)
576 return r;
577
578 if (!sd_id128_equal(machine_id, f->header->machine_id))
579 return -EHOSTDOWN;
580
de190aef 581 state = f->header->state;
cec736d2 582
b288cdeb
ZJS
583 if (state == STATE_ARCHIVED)
584 return -ESHUTDOWN; /* Already archived */
baaa35ad
ZJS
585 else if (state == STATE_ONLINE)
586 return log_debug_errno(SYNTHETIC_ERRNO(EBUSY),
587 "Journal file %s is already online. Assuming unclean closing.",
588 f->path);
589 else if (state != STATE_OFFLINE)
590 return log_debug_errno(SYNTHETIC_ERRNO(EBUSY),
591 "Journal file %s has unknown state %i.",
592 f->path, state);
ae739cc1 593
5b3cc0c8
YN
594 if (f->header->field_hash_table_size == 0 || f->header->data_hash_table_size == 0)
595 return -EBADMSG;
596
ae739cc1
LP
597 /* Don't permit appending to files from the future. Because otherwise the realtime timestamps wouldn't
598 * be strictly ordered in the entries in the file anymore, and we can't have that since it breaks
599 * bisection. */
baaa35ad
ZJS
600 if (le64toh(f->header->tail_entry_realtime) > now(CLOCK_REALTIME))
601 return log_debug_errno(SYNTHETIC_ERRNO(ETXTBSY),
602 "Journal file %s is from the future, refusing to append new data to it that'd be older.",
603 f->path);
cec736d2
LP
604 }
605
d89c8fdf
ZJS
606 f->compress_xz = JOURNAL_HEADER_COMPRESSED_XZ(f->header);
607 f->compress_lz4 = JOURNAL_HEADER_COMPRESSED_LZ4(f->header);
8653185a 608 f->compress_zstd = JOURNAL_HEADER_COMPRESSED_ZSTD(f->header);
c586dbf1 609
f1889c91 610 f->seal = JOURNAL_HEADER_SEALED(f->header);
7560fffc 611
4ce534f4
LP
612 f->keyed_hash = JOURNAL_HEADER_KEYED_HASH(f->header);
613
cec736d2
LP
614 return 0;
615}
616
28ca867a 617int journal_file_fstat(JournalFile *f) {
3cc44114
LP
618 int r;
619
2678031a
LP
620 assert(f);
621 assert(f->fd >= 0);
622
623 if (fstat(f->fd, &f->last_stat) < 0)
624 return -errno;
625
626 f->last_stat_usec = now(CLOCK_MONOTONIC);
627
8d6a4d33 628 /* Refuse dealing with with files that aren't regular */
3cc44114
LP
629 r = stat_verify_regular(&f->last_stat);
630 if (r < 0)
631 return r;
8d6a4d33 632
2678031a
LP
633 /* Refuse appending to files that are already deleted */
634 if (f->last_stat.st_nlink <= 0)
635 return -EIDRM;
636
637 return 0;
638}
639
cec736d2 640static int journal_file_allocate(JournalFile *f, uint64_t offset, uint64_t size) {
893e0f8f 641 uint64_t old_size, new_size, old_header_size, old_arena_size;
fec2aa2f 642 int r;
cec736d2
LP
643
644 assert(f);
c88cc6af 645 assert(f->header);
cec736d2 646
893e0f8f
LP
647 /* We assume that this file is not sparse, and we know that for sure, since we always call
648 * posix_fallocate() ourselves */
649
650 if (size > PAGE_ALIGN_DOWN(UINT64_MAX) - offset)
651 return -EINVAL;
cec736d2 652
be7cdd8e 653 if (mmap_cache_got_sigbus(f->mmap, f->cache_fd))
fa6ac760
LP
654 return -EIO;
655
893e0f8f
LP
656 old_header_size = le64toh(READ_NOW(f->header->header_size));
657 old_arena_size = le64toh(READ_NOW(f->header->arena_size));
658 if (old_arena_size > PAGE_ALIGN_DOWN(UINT64_MAX) - old_header_size)
659 return -EBADMSG;
660
661 old_size = old_header_size + old_arena_size;
cec736d2 662
893e0f8f 663 new_size = MAX(PAGE_ALIGN(offset + size), old_header_size);
bc85bfee 664
2678031a
LP
665 if (new_size <= old_size) {
666
667 /* We already pre-allocated enough space, but before
668 * we write to it, let's check with fstat() if the
669 * file got deleted, in order make sure we don't throw
670 * away the data immediately. Don't check fstat() for
671 * all writes though, but only once ever 10s. */
672
673 if (f->last_stat_usec + LAST_STAT_REFRESH_USEC > now(CLOCK_MONOTONIC))
674 return 0;
675
676 return journal_file_fstat(f);
677 }
678
679 /* Allocate more space. */
cec736d2 680
a676e665 681 if (f->metrics.max_size > 0 && new_size > f->metrics.max_size)
bc85bfee 682 return -E2BIG;
cec736d2 683
a676e665 684 if (new_size > f->metrics.min_size && f->metrics.keep_free > 0) {
cec736d2
LP
685 struct statvfs svfs;
686
687 if (fstatvfs(f->fd, &svfs) >= 0) {
688 uint64_t available;
689
070052ab 690 available = LESS_BY((uint64_t) svfs.f_bfree * (uint64_t) svfs.f_bsize, f->metrics.keep_free);
cec736d2
LP
691
692 if (new_size - old_size > available)
693 return -E2BIG;
694 }
695 }
696
eda4b58b 697 /* Increase by larger blocks at once */
be6b0c21 698 new_size = DIV_ROUND_UP(new_size, FILE_SIZE_INCREASE) * FILE_SIZE_INCREASE;
eda4b58b
LP
699 if (f->metrics.max_size > 0 && new_size > f->metrics.max_size)
700 new_size = f->metrics.max_size;
701
bc85bfee
LP
702 /* Note that the glibc fallocate() fallback is very
703 inefficient, hence we try to minimize the allocation area
704 as we can. */
fec2aa2f
GV
705 r = posix_fallocate(f->fd, old_size, new_size - old_size);
706 if (r != 0)
707 return -r;
cec736d2 708
893e0f8f 709 f->header->arena_size = htole64(new_size - old_header_size);
cec736d2 710
2678031a 711 return journal_file_fstat(f);
cec736d2
LP
712}
713
78519831 714static unsigned type_to_context(ObjectType type) {
d3d3208f 715 /* One context for each type, plus one catch-all for the rest */
69adae51 716 assert_cc(_OBJECT_TYPE_MAX <= MMAP_CACHE_MAX_CONTEXTS);
fa6ac760 717 assert_cc(CONTEXT_HEADER < MMAP_CACHE_MAX_CONTEXTS);
d05089d8 718 return type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX ? type : 0;
d3d3208f
MS
719}
720
71139898
LP
721static int journal_file_move_to(
722 JournalFile *f,
723 ObjectType type,
724 bool keep_always,
725 uint64_t offset,
726 uint64_t size,
727 void **ret,
728 size_t *ret_size) {
729
2678031a
LP
730 int r;
731
cec736d2 732 assert(f);
cec736d2
LP
733 assert(ret);
734
7762e02b
LP
735 if (size <= 0)
736 return -EINVAL;
737
893e0f8f
LP
738 if (size > UINT64_MAX - offset)
739 return -EBADMSG;
740
2a59ea54 741 /* Avoid SIGBUS on invalid accesses */
4bbdcdb3
LP
742 if (offset + size > (uint64_t) f->last_stat.st_size) {
743 /* Hmm, out of range? Let's refresh the fstat() data
744 * first, before we trust that check. */
745
2678031a
LP
746 r = journal_file_fstat(f);
747 if (r < 0)
748 return r;
749
750 if (offset + size > (uint64_t) f->last_stat.st_size)
4bbdcdb3
LP
751 return -EADDRNOTAVAIL;
752 }
753
b439282e 754 return mmap_cache_get(f->mmap, f->cache_fd, f->prot, type_to_context(type), keep_always, offset, size, &f->last_stat, ret, ret_size);
cec736d2
LP
755}
756
16e9f408
LP
757static uint64_t minimum_header_size(Object *o) {
758
b8e891e6 759 static const uint64_t table[] = {
16e9f408
LP
760 [OBJECT_DATA] = sizeof(DataObject),
761 [OBJECT_FIELD] = sizeof(FieldObject),
762 [OBJECT_ENTRY] = sizeof(EntryObject),
763 [OBJECT_DATA_HASH_TABLE] = sizeof(HashTableObject),
764 [OBJECT_FIELD_HASH_TABLE] = sizeof(HashTableObject),
765 [OBJECT_ENTRY_ARRAY] = sizeof(EntryArrayObject),
766 [OBJECT_TAG] = sizeof(TagObject),
767 };
768
769 if (o->object.type >= ELEMENTSOF(table) || table[o->object.type] <= 0)
770 return sizeof(ObjectHeader);
771
772 return table[o->object.type];
773}
774
24754f36
TR
775/* Lightweight object checks. We want this to be fast, so that we won't
776 * slowdown every journal_file_move_to_object() call too much. */
777static int journal_file_check_object(JournalFile *f, uint64_t offset, Object *o) {
778 assert(f);
779 assert(o);
780
781 switch (o->object.type) {
782
a602d93e 783 case OBJECT_DATA:
baaa35ad
ZJS
784 if ((le64toh(o->data.entry_offset) == 0) ^ (le64toh(o->data.n_entries) == 0))
785 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
786 "Bad n_entries: %" PRIu64 ": %" PRIu64,
787 le64toh(o->data.n_entries),
788 offset);
789
20ee282b 790 if (le64toh(o->object.size) <= offsetof(DataObject, payload))
baaa35ad
ZJS
791 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
792 "Bad object size (<= %zu): %" PRIu64 ": %" PRIu64,
793 offsetof(DataObject, payload),
794 le64toh(o->object.size),
795 offset);
24754f36 796
10e8445b
TR
797 if (!VALID64(le64toh(o->data.next_hash_offset)) ||
798 !VALID64(le64toh(o->data.next_field_offset)) ||
799 !VALID64(le64toh(o->data.entry_offset)) ||
baaa35ad
ZJS
800 !VALID64(le64toh(o->data.entry_array_offset)))
801 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
802 "Invalid offset, next_hash_offset=" OFSfmt ", next_field_offset=" OFSfmt ", entry_offset=" OFSfmt ", entry_array_offset=" OFSfmt ": %" PRIu64,
803 le64toh(o->data.next_hash_offset),
804 le64toh(o->data.next_field_offset),
805 le64toh(o->data.entry_offset),
806 le64toh(o->data.entry_array_offset),
807 offset);
24754f36
TR
808
809 break;
24754f36
TR
810
811 case OBJECT_FIELD:
20ee282b 812 if (le64toh(o->object.size) <= offsetof(FieldObject, payload))
baaa35ad
ZJS
813 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
814 "Bad field size (<= %zu): %" PRIu64 ": %" PRIu64,
815 offsetof(FieldObject, payload),
816 le64toh(o->object.size),
817 offset);
24754f36 818
10e8445b 819 if (!VALID64(le64toh(o->field.next_hash_offset)) ||
baaa35ad
ZJS
820 !VALID64(le64toh(o->field.head_data_offset)))
821 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
822 "Invalid offset, next_hash_offset=" OFSfmt ", head_data_offset=" OFSfmt ": %" PRIu64,
823 le64toh(o->field.next_hash_offset),
824 le64toh(o->field.head_data_offset),
825 offset);
24754f36
TR
826 break;
827
893e0f8f
LP
828 case OBJECT_ENTRY: {
829 uint64_t sz;
830
831 sz = le64toh(READ_NOW(o->object.size));
832 if (sz < offsetof(EntryObject, items) ||
833 (sz - offsetof(EntryObject, items)) % sizeof(EntryItem) != 0)
baaa35ad
ZJS
834 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
835 "Bad entry size (<= %zu): %" PRIu64 ": %" PRIu64,
836 offsetof(EntryObject, items),
893e0f8f 837 sz,
baaa35ad
ZJS
838 offset);
839
893e0f8f 840 if ((sz - offsetof(EntryObject, items)) / sizeof(EntryItem) <= 0)
baaa35ad
ZJS
841 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
842 "Invalid number items in entry: %" PRIu64 ": %" PRIu64,
893e0f8f 843 (sz - offsetof(EntryObject, items)) / sizeof(EntryItem),
baaa35ad
ZJS
844 offset);
845
846 if (le64toh(o->entry.seqnum) <= 0)
847 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
848 "Invalid entry seqnum: %" PRIx64 ": %" PRIu64,
849 le64toh(o->entry.seqnum),
850 offset);
851
852 if (!VALID_REALTIME(le64toh(o->entry.realtime)))
853 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
854 "Invalid entry realtime timestamp: %" PRIu64 ": %" PRIu64,
855 le64toh(o->entry.realtime),
856 offset);
857
858 if (!VALID_MONOTONIC(le64toh(o->entry.monotonic)))
859 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
860 "Invalid entry monotonic timestamp: %" PRIu64 ": %" PRIu64,
861 le64toh(o->entry.monotonic),
862 offset);
24754f36
TR
863
864 break;
893e0f8f 865 }
24754f36
TR
866
867 case OBJECT_DATA_HASH_TABLE:
893e0f8f
LP
868 case OBJECT_FIELD_HASH_TABLE: {
869 uint64_t sz;
870
871 sz = le64toh(READ_NOW(o->object.size));
872 if (sz < offsetof(HashTableObject, items) ||
873 (sz - offsetof(HashTableObject, items)) % sizeof(HashItem) != 0 ||
874 (sz - offsetof(HashTableObject, items)) / sizeof(HashItem) <= 0)
baaa35ad
ZJS
875 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
876 "Invalid %s hash table size: %" PRIu64 ": %" PRIu64,
877 o->object.type == OBJECT_DATA_HASH_TABLE ? "data" : "field",
893e0f8f 878 sz,
baaa35ad 879 offset);
24754f36
TR
880
881 break;
893e0f8f 882 }
24754f36 883
893e0f8f
LP
884 case OBJECT_ENTRY_ARRAY: {
885 uint64_t sz;
886
887 sz = le64toh(READ_NOW(o->object.size));
888 if (sz < offsetof(EntryArrayObject, items) ||
889 (sz - offsetof(EntryArrayObject, items)) % sizeof(le64_t) != 0 ||
890 (sz - offsetof(EntryArrayObject, items)) / sizeof(le64_t) <= 0)
baaa35ad
ZJS
891 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
892 "Invalid object entry array size: %" PRIu64 ": %" PRIu64,
893e0f8f 893 sz,
baaa35ad
ZJS
894 offset);
895
896 if (!VALID64(le64toh(o->entry_array.next_entry_array_offset)))
897 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
898 "Invalid object entry array next_entry_array_offset: " OFSfmt ": %" PRIu64,
899 le64toh(o->entry_array.next_entry_array_offset),
900 offset);
24754f36
TR
901
902 break;
893e0f8f 903 }
24754f36
TR
904
905 case OBJECT_TAG:
baaa35ad
ZJS
906 if (le64toh(o->object.size) != sizeof(TagObject))
907 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
908 "Invalid object tag size: %" PRIu64 ": %" PRIu64,
909 le64toh(o->object.size),
910 offset);
24754f36 911
baaa35ad
ZJS
912 if (!VALID_EPOCH(le64toh(o->tag.epoch)))
913 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
914 "Invalid object tag epoch: %" PRIu64 ": %" PRIu64,
915 le64toh(o->tag.epoch), offset);
24754f36
TR
916
917 break;
918 }
919
920 return 0;
921}
922
78519831 923int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset, Object **ret) {
cec736d2
LP
924 int r;
925 void *t;
b439282e 926 size_t tsize;
cec736d2
LP
927 Object *o;
928 uint64_t s;
929
930 assert(f);
931 assert(ret);
932
db11ac1a 933 /* Objects may only be located at multiple of 64 bit */
baaa35ad
ZJS
934 if (!VALID64(offset))
935 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
936 "Attempt to move to object at non-64bit boundary: %" PRIu64,
937 offset);
db11ac1a 938
50809d7a 939 /* Object may not be located in the file header */
baaa35ad
ZJS
940 if (offset < le64toh(f->header->header_size))
941 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
942 "Attempt to move to object located in file header: %" PRIu64,
943 offset);
50809d7a 944
b439282e 945 r = journal_file_move_to(f, type, false, offset, sizeof(ObjectHeader), &t, &tsize);
cec736d2
LP
946 if (r < 0)
947 return r;
948
949 o = (Object*) t;
893e0f8f 950 s = le64toh(READ_NOW(o->object.size));
cec736d2 951
baaa35ad
ZJS
952 if (s == 0)
953 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
954 "Attempt to move to uninitialized object: %" PRIu64,
955 offset);
956 if (s < sizeof(ObjectHeader))
957 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
958 "Attempt to move to overly short object: %" PRIu64,
959 offset);
960
961 if (o->object.type <= OBJECT_UNUSED)
962 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
963 "Attempt to move to object with invalid type: %" PRIu64,
964 offset);
965
966 if (s < minimum_header_size(o))
967 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
968 "Attempt to move to truncated object: %" PRIu64,
969 offset);
970
971 if (type > OBJECT_UNUSED && o->object.type != type)
972 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
973 "Attempt to move to object of unexpected type: %" PRIu64,
974 offset);
cec736d2 975
b439282e
VC
976 if (s > tsize) {
977 r = journal_file_move_to(f, type, false, offset, s, &t, NULL);
cec736d2
LP
978 if (r < 0)
979 return r;
980
981 o = (Object*) t;
982 }
983
24754f36
TR
984 r = journal_file_check_object(f, offset, o);
985 if (r < 0)
986 return r;
987
cec736d2
LP
988 *ret = o;
989 return 0;
990}
991
d98cc1f2 992static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) {
cec736d2
LP
993 uint64_t r;
994
995 assert(f);
c88cc6af 996 assert(f->header);
cec736d2 997
beec0085 998 r = le64toh(f->header->tail_entry_seqnum) + 1;
c2373f84
LP
999
1000 if (seqnum) {
de190aef 1001 /* If an external seqnum counter was passed, we update
c2373f84
LP
1002 * both the local and the external one, and set it to
1003 * the maximum of both */
1004
1005 if (*seqnum + 1 > r)
1006 r = *seqnum + 1;
1007
1008 *seqnum = r;
1009 }
1010
beec0085 1011 f->header->tail_entry_seqnum = htole64(r);
cec736d2 1012
beec0085
LP
1013 if (f->header->head_entry_seqnum == 0)
1014 f->header->head_entry_seqnum = htole64(r);
de190aef 1015
cec736d2
LP
1016 return r;
1017}
1018
f4474e00
LP
1019int journal_file_append_object(
1020 JournalFile *f,
1021 ObjectType type,
1022 uint64_t size,
1023 Object **ret,
1024 uint64_t *ret_offset) {
1025
cec736d2
LP
1026 int r;
1027 uint64_t p;
1028 Object *tail, *o;
1029 void *t;
1030
1031 assert(f);
c88cc6af 1032 assert(f->header);
d05089d8 1033 assert(type > OBJECT_UNUSED && type < _OBJECT_TYPE_MAX);
cec736d2 1034 assert(size >= sizeof(ObjectHeader));
cec736d2 1035
26687bf8
OS
1036 r = journal_file_set_online(f);
1037 if (r < 0)
1038 return r;
1039
cec736d2 1040 p = le64toh(f->header->tail_object_offset);
cec736d2 1041 if (p == 0)
23b0b2b2 1042 p = le64toh(f->header->header_size);
cec736d2 1043 else {
893e0f8f
LP
1044 uint64_t sz;
1045
d05089d8 1046 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &tail);
cec736d2
LP
1047 if (r < 0)
1048 return r;
1049
893e0f8f
LP
1050 sz = le64toh(READ_NOW(tail->object.size));
1051 if (sz > UINT64_MAX - sizeof(uint64_t) + 1)
1052 return -EBADMSG;
1053
1054 sz = ALIGN64(sz);
1055 if (p > UINT64_MAX - sz)
1056 return -EBADMSG;
1057
1058 p += sz;
cec736d2
LP
1059 }
1060
1061 r = journal_file_allocate(f, p, size);
1062 if (r < 0)
1063 return r;
1064
b439282e 1065 r = journal_file_move_to(f, type, false, p, size, &t, NULL);
cec736d2
LP
1066 if (r < 0)
1067 return r;
1068
1069 o = (Object*) t;
71139898
LP
1070 o->object = (ObjectHeader) {
1071 .type = type,
1072 .size = htole64(size),
1073 };
cec736d2
LP
1074
1075 f->header->tail_object_offset = htole64(p);
cec736d2
LP
1076 f->header->n_objects = htole64(le64toh(f->header->n_objects) + 1);
1077
f4474e00
LP
1078 if (ret)
1079 *ret = o;
1080
1081 if (ret_offset)
1082 *ret_offset = p;
cec736d2
LP
1083
1084 return 0;
1085}
1086
de190aef 1087static int journal_file_setup_data_hash_table(JournalFile *f) {
cec736d2
LP
1088 uint64_t s, p;
1089 Object *o;
1090 int r;
1091
1092 assert(f);
c88cc6af 1093 assert(f->header);
cec736d2 1094
070052ab
LP
1095 /* We estimate that we need 1 hash table entry per 768 bytes
1096 of journal file and we want to make sure we never get
1097 beyond 75% fill level. Calculate the hash table size for
1098 the maximum file size based on these metrics. */
4a92baf3 1099
dfabe643 1100 s = (f->metrics.max_size * 4 / 768 / 3) * sizeof(HashItem);
4a92baf3
LP
1101 if (s < DEFAULT_DATA_HASH_TABLE_SIZE)
1102 s = DEFAULT_DATA_HASH_TABLE_SIZE;
1103
5030c85a 1104 log_debug("Reserving %"PRIu64" entries in data hash table.", s / sizeof(HashItem));
4a92baf3 1105
de190aef
LP
1106 r = journal_file_append_object(f,
1107 OBJECT_DATA_HASH_TABLE,
1108 offsetof(Object, hash_table.items) + s,
1109 &o, &p);
cec736d2
LP
1110 if (r < 0)
1111 return r;
1112
29804cc1 1113 memzero(o->hash_table.items, s);
cec736d2 1114
de190aef
LP
1115 f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
1116 f->header->data_hash_table_size = htole64(s);
cec736d2
LP
1117
1118 return 0;
1119}
1120
de190aef 1121static int journal_file_setup_field_hash_table(JournalFile *f) {
cec736d2
LP
1122 uint64_t s, p;
1123 Object *o;
1124 int r;
1125
1126 assert(f);
c88cc6af 1127 assert(f->header);
cec736d2 1128
3c1668da
LP
1129 /* We use a fixed size hash table for the fields as this
1130 * number should grow very slowly only */
1131
de190aef 1132 s = DEFAULT_FIELD_HASH_TABLE_SIZE;
5030c85a
LP
1133 log_debug("Reserving %"PRIu64" entries in field hash table.", s / sizeof(HashItem));
1134
de190aef
LP
1135 r = journal_file_append_object(f,
1136 OBJECT_FIELD_HASH_TABLE,
1137 offsetof(Object, hash_table.items) + s,
1138 &o, &p);
cec736d2
LP
1139 if (r < 0)
1140 return r;
1141
29804cc1 1142 memzero(o->hash_table.items, s);
cec736d2 1143
de190aef
LP
1144 f->header->field_hash_table_offset = htole64(p + offsetof(Object, hash_table.items));
1145 f->header->field_hash_table_size = htole64(s);
cec736d2
LP
1146
1147 return 0;
1148}
1149
dade37d4 1150int journal_file_map_data_hash_table(JournalFile *f) {
cec736d2
LP
1151 uint64_t s, p;
1152 void *t;
1153 int r;
1154
1155 assert(f);
c88cc6af 1156 assert(f->header);
cec736d2 1157
dade37d4
LP
1158 if (f->data_hash_table)
1159 return 0;
1160
de190aef
LP
1161 p = le64toh(f->header->data_hash_table_offset);
1162 s = le64toh(f->header->data_hash_table_size);
cec736d2 1163
de190aef 1164 r = journal_file_move_to(f,
16e9f408 1165 OBJECT_DATA_HASH_TABLE,
fcde2389 1166 true,
de190aef 1167 p, s,
b42549ad 1168 &t, NULL);
cec736d2
LP
1169 if (r < 0)
1170 return r;
1171
de190aef 1172 f->data_hash_table = t;
cec736d2
LP
1173 return 0;
1174}
1175
dade37d4 1176int journal_file_map_field_hash_table(JournalFile *f) {
cec736d2
LP
1177 uint64_t s, p;
1178 void *t;
1179 int r;
1180
1181 assert(f);
c88cc6af 1182 assert(f->header);
cec736d2 1183
dade37d4
LP
1184 if (f->field_hash_table)
1185 return 0;
1186
de190aef
LP
1187 p = le64toh(f->header->field_hash_table_offset);
1188 s = le64toh(f->header->field_hash_table_size);
cec736d2 1189
de190aef 1190 r = journal_file_move_to(f,
16e9f408 1191 OBJECT_FIELD_HASH_TABLE,
fcde2389 1192 true,
de190aef 1193 p, s,
b42549ad 1194 &t, NULL);
cec736d2
LP
1195 if (r < 0)
1196 return r;
1197
de190aef 1198 f->field_hash_table = t;
cec736d2
LP
1199 return 0;
1200}
1201
3c1668da
LP
1202static int journal_file_link_field(
1203 JournalFile *f,
1204 Object *o,
1205 uint64_t offset,
1206 uint64_t hash) {
1207
805d1486 1208 uint64_t p, h, m;
3c1668da
LP
1209 int r;
1210
1211 assert(f);
c88cc6af 1212 assert(f->header);
90d222c1 1213 assert(f->field_hash_table);
3c1668da
LP
1214 assert(o);
1215 assert(offset > 0);
1216
1217 if (o->object.type != OBJECT_FIELD)
1218 return -EINVAL;
1219
893e0f8f 1220 m = le64toh(READ_NOW(f->header->field_hash_table_size)) / sizeof(HashItem);
805d1486
LP
1221 if (m <= 0)
1222 return -EBADMSG;
3c1668da 1223
805d1486 1224 /* This might alter the window we are looking at */
3c1668da
LP
1225 o->field.next_hash_offset = o->field.head_data_offset = 0;
1226
805d1486 1227 h = hash % m;
3c1668da
LP
1228 p = le64toh(f->field_hash_table[h].tail_hash_offset);
1229 if (p == 0)
1230 f->field_hash_table[h].head_hash_offset = htole64(offset);
1231 else {
1232 r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o);
1233 if (r < 0)
1234 return r;
1235
1236 o->field.next_hash_offset = htole64(offset);
1237 }
1238
1239 f->field_hash_table[h].tail_hash_offset = htole64(offset);
1240
1241 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
1242 f->header->n_fields = htole64(le64toh(f->header->n_fields) + 1);
1243
1244 return 0;
1245}
1246
1247static int journal_file_link_data(
1248 JournalFile *f,
1249 Object *o,
1250 uint64_t offset,
1251 uint64_t hash) {
1252
805d1486 1253 uint64_t p, h, m;
cec736d2
LP
1254 int r;
1255
1256 assert(f);
c88cc6af 1257 assert(f->header);
90d222c1 1258 assert(f->data_hash_table);
cec736d2
LP
1259 assert(o);
1260 assert(offset > 0);
b588975f
LP
1261
1262 if (o->object.type != OBJECT_DATA)
1263 return -EINVAL;
cec736d2 1264
893e0f8f 1265 m = le64toh(READ_NOW(f->header->data_hash_table_size)) / sizeof(HashItem);
805d1486
LP
1266 if (m <= 0)
1267 return -EBADMSG;
48496df6 1268
805d1486 1269 /* This might alter the window we are looking at */
de190aef
LP
1270 o->data.next_hash_offset = o->data.next_field_offset = 0;
1271 o->data.entry_offset = o->data.entry_array_offset = 0;
1272 o->data.n_entries = 0;
cec736d2 1273
805d1486 1274 h = hash % m;
8db4213e 1275 p = le64toh(f->data_hash_table[h].tail_hash_offset);
3c1668da 1276 if (p == 0)
cec736d2 1277 /* Only entry in the hash table is easy */
de190aef 1278 f->data_hash_table[h].head_hash_offset = htole64(offset);
3c1668da 1279 else {
48496df6
LP
1280 /* Move back to the previous data object, to patch in
1281 * pointer */
cec736d2 1282
de190aef 1283 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
cec736d2
LP
1284 if (r < 0)
1285 return r;
1286
de190aef 1287 o->data.next_hash_offset = htole64(offset);
cec736d2
LP
1288 }
1289
de190aef 1290 f->data_hash_table[h].tail_hash_offset = htole64(offset);
cec736d2 1291
dca6219e
LP
1292 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
1293 f->header->n_data = htole64(le64toh(f->header->n_data) + 1);
1294
cec736d2
LP
1295 return 0;
1296}
1297
0dbe57ee
LP
1298static int next_hash_offset(
1299 JournalFile *f,
1300 uint64_t *p,
1301 le64_t *next_hash_offset,
1302 uint64_t *depth,
1303 le64_t *header_max_depth) {
1304
1305 uint64_t nextp;
1306
1307 nextp = le64toh(READ_NOW(*next_hash_offset));
1308 if (nextp > 0) {
1309 if (nextp <= *p) /* Refuse going in loops */
1310 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
1311 "Detected hash item loop in %s, refusing.", f->path);
1312
1313 (*depth)++;
1314
1315 /* If the depth of this hash chain is larger than all others we have seen so far, record it */
1316 if (header_max_depth && f->writable)
1317 *header_max_depth = htole64(MAX(*depth, le64toh(*header_max_depth)));
1318 }
1319
1320 *p = nextp;
1321 return 0;
1322}
1323
3c1668da
LP
1324int journal_file_find_field_object_with_hash(
1325 JournalFile *f,
1326 const void *field, uint64_t size, uint64_t hash,
f4474e00 1327 Object **ret, uint64_t *ret_offset) {
3c1668da 1328
0dbe57ee 1329 uint64_t p, osize, h, m, depth = 0;
3c1668da
LP
1330 int r;
1331
1332 assert(f);
c88cc6af 1333 assert(f->header);
3c1668da
LP
1334 assert(field && size > 0);
1335
dade37d4
LP
1336 /* If the field hash table is empty, we can't find anything */
1337 if (le64toh(f->header->field_hash_table_size) <= 0)
1338 return 0;
1339
1340 /* Map the field hash table, if it isn't mapped yet. */
1341 r = journal_file_map_field_hash_table(f);
1342 if (r < 0)
1343 return r;
1344
3c1668da
LP
1345 osize = offsetof(Object, field.payload) + size;
1346
893e0f8f 1347 m = le64toh(READ_NOW(f->header->field_hash_table_size)) / sizeof(HashItem);
805d1486 1348 if (m <= 0)
3c1668da
LP
1349 return -EBADMSG;
1350
805d1486 1351 h = hash % m;
3c1668da 1352 p = le64toh(f->field_hash_table[h].head_hash_offset);
3c1668da
LP
1353 while (p > 0) {
1354 Object *o;
1355
1356 r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o);
1357 if (r < 0)
1358 return r;
1359
1360 if (le64toh(o->field.hash) == hash &&
1361 le64toh(o->object.size) == osize &&
1362 memcmp(o->field.payload, field, size) == 0) {
1363
1364 if (ret)
1365 *ret = o;
f4474e00
LP
1366 if (ret_offset)
1367 *ret_offset = p;
3c1668da
LP
1368
1369 return 1;
1370 }
1371
0dbe57ee
LP
1372 r = next_hash_offset(
1373 f,
1374 &p,
1375 &o->field.next_hash_offset,
1376 &depth,
1377 JOURNAL_HEADER_CONTAINS(f->header, field_hash_chain_depth) ? &f->header->field_hash_chain_depth : NULL);
1378 if (r < 0)
1379 return r;
3c1668da
LP
1380 }
1381
1382 return 0;
1383}
1384
4ce534f4
LP
1385uint64_t journal_file_hash_data(
1386 JournalFile *f,
1387 const void *data,
1388 size_t sz) {
1389
1390 assert(f);
1391 assert(data || sz == 0);
1392
1393 /* We try to unify our codebase on siphash, hence new-styled journal files utilizing the keyed hash
1394 * function use siphash. Old journal files use the Jenkins hash. */
1395
1396 if (JOURNAL_HEADER_KEYED_HASH(f->header))
1397 return siphash24(data, sz, f->header->file_id.bytes);
1398
1399 return jenkins_hash64(data, sz);
1400}
1401
3c1668da
LP
1402int journal_file_find_field_object(
1403 JournalFile *f,
1404 const void *field, uint64_t size,
f4474e00 1405 Object **ret, uint64_t *ret_offset) {
3c1668da 1406
3c1668da
LP
1407 assert(f);
1408 assert(field && size > 0);
1409
f4474e00
LP
1410 return journal_file_find_field_object_with_hash(
1411 f,
4ce534f4
LP
1412 field, size,
1413 journal_file_hash_data(f, field, size),
f4474e00 1414 ret, ret_offset);
3c1668da
LP
1415}
1416
de190aef
LP
1417int journal_file_find_data_object_with_hash(
1418 JournalFile *f,
1419 const void *data, uint64_t size, uint64_t hash,
f4474e00 1420 Object **ret, uint64_t *ret_offset) {
48496df6 1421
0dbe57ee 1422 uint64_t p, osize, h, m, depth = 0;
cec736d2
LP
1423 int r;
1424
1425 assert(f);
c88cc6af 1426 assert(f->header);
cec736d2
LP
1427 assert(data || size == 0);
1428
dade37d4
LP
1429 /* If there's no data hash table, then there's no entry. */
1430 if (le64toh(f->header->data_hash_table_size) <= 0)
1431 return 0;
1432
1433 /* Map the data hash table, if it isn't mapped yet. */
1434 r = journal_file_map_data_hash_table(f);
1435 if (r < 0)
1436 return r;
1437
cec736d2
LP
1438 osize = offsetof(Object, data.payload) + size;
1439
893e0f8f 1440 m = le64toh(READ_NOW(f->header->data_hash_table_size)) / sizeof(HashItem);
805d1486 1441 if (m <= 0)
bc85bfee
LP
1442 return -EBADMSG;
1443
805d1486 1444 h = hash % m;
de190aef 1445 p = le64toh(f->data_hash_table[h].head_hash_offset);
cec736d2 1446
de190aef
LP
1447 while (p > 0) {
1448 Object *o;
cec736d2 1449
de190aef 1450 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
cec736d2
LP
1451 if (r < 0)
1452 return r;
1453
807e17f0 1454 if (le64toh(o->data.hash) != hash)
85a131e8 1455 goto next;
807e17f0 1456
d89c8fdf 1457 if (o->object.flags & OBJECT_COMPRESSION_MASK) {
d80b051c 1458#if HAVE_COMPRESSION
fa1c4b51 1459 uint64_t l;
a7f7d1bd 1460 size_t rsize = 0;
cec736d2 1461
893e0f8f 1462 l = le64toh(READ_NOW(o->object.size));
807e17f0 1463 if (l <= offsetof(Object, data.payload))
cec736d2
LP
1464 return -EBADMSG;
1465
807e17f0
LP
1466 l -= offsetof(Object, data.payload);
1467
d89c8fdf
ZJS
1468 r = decompress_blob(o->object.flags & OBJECT_COMPRESSION_MASK,
1469 o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, &rsize, 0);
1470 if (r < 0)
1471 return r;
807e17f0 1472
b785c858 1473 if (rsize == size &&
807e17f0
LP
1474 memcmp(f->compress_buffer, data, size) == 0) {
1475
1476 if (ret)
1477 *ret = o;
1478
f4474e00
LP
1479 if (ret_offset)
1480 *ret_offset = p;
807e17f0
LP
1481
1482 return 1;
1483 }
3b1a55e1
ZJS
1484#else
1485 return -EPROTONOSUPPORT;
1486#endif
807e17f0
LP
1487 } else if (le64toh(o->object.size) == osize &&
1488 memcmp(o->data.payload, data, size) == 0) {
1489
cec736d2
LP
1490 if (ret)
1491 *ret = o;
1492
f4474e00
LP
1493 if (ret_offset)
1494 *ret_offset = p;
cec736d2 1495
de190aef 1496 return 1;
cec736d2
LP
1497 }
1498
85a131e8 1499 next:
0dbe57ee
LP
1500 r = next_hash_offset(
1501 f,
1502 &p,
1503 &o->data.next_hash_offset,
1504 &depth,
1505 JOURNAL_HEADER_CONTAINS(f->header, data_hash_chain_depth) ? &f->header->data_hash_chain_depth : NULL);
1506 if (r < 0)
1507 return r;
cec736d2
LP
1508 }
1509
de190aef
LP
1510 return 0;
1511}
1512
1513int journal_file_find_data_object(
1514 JournalFile *f,
1515 const void *data, uint64_t size,
f4474e00 1516 Object **ret, uint64_t *ret_offset) {
de190aef 1517
de190aef
LP
1518 assert(f);
1519 assert(data || size == 0);
1520
f4474e00
LP
1521 return journal_file_find_data_object_with_hash(
1522 f,
4ce534f4
LP
1523 data, size,
1524 journal_file_hash_data(f, data, size),
f4474e00 1525 ret, ret_offset);
de190aef
LP
1526}
1527
3c1668da
LP
1528static int journal_file_append_field(
1529 JournalFile *f,
1530 const void *field, uint64_t size,
f4474e00 1531 Object **ret, uint64_t *ret_offset) {
3c1668da
LP
1532
1533 uint64_t hash, p;
1534 uint64_t osize;
1535 Object *o;
1536 int r;
1537
1538 assert(f);
1539 assert(field && size > 0);
1540
4ce534f4 1541 hash = journal_file_hash_data(f, field, size);
3c1668da
LP
1542
1543 r = journal_file_find_field_object_with_hash(f, field, size, hash, &o, &p);
1544 if (r < 0)
1545 return r;
1546 else if (r > 0) {
1547
1548 if (ret)
1549 *ret = o;
1550
f4474e00
LP
1551 if (ret_offset)
1552 *ret_offset = p;
3c1668da
LP
1553
1554 return 0;
1555 }
1556
1557 osize = offsetof(Object, field.payload) + size;
1558 r = journal_file_append_object(f, OBJECT_FIELD, osize, &o, &p);
8c92d4bb
LP
1559 if (r < 0)
1560 return r;
3c1668da
LP
1561
1562 o->field.hash = htole64(hash);
1563 memcpy(o->field.payload, field, size);
1564
1565 r = journal_file_link_field(f, o, p, hash);
1566 if (r < 0)
1567 return r;
1568
1569 /* The linking might have altered the window, so let's
1570 * refresh our pointer */
1571 r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o);
1572 if (r < 0)
1573 return r;
1574
349cc4a5 1575#if HAVE_GCRYPT
3c1668da
LP
1576 r = journal_file_hmac_put_object(f, OBJECT_FIELD, o, p);
1577 if (r < 0)
1578 return r;
1579#endif
1580
1581 if (ret)
1582 *ret = o;
1583
f4474e00
LP
1584 if (ret_offset)
1585 *ret_offset = p;
3c1668da
LP
1586
1587 return 0;
1588}
1589
48496df6
LP
1590static int journal_file_append_data(
1591 JournalFile *f,
1592 const void *data, uint64_t size,
f4474e00 1593 Object **ret, uint64_t *ret_offset) {
48496df6 1594
de190aef
LP
1595 uint64_t hash, p;
1596 uint64_t osize;
1597 Object *o;
d89c8fdf 1598 int r, compression = 0;
3c1668da 1599 const void *eq;
de190aef
LP
1600
1601 assert(f);
1602 assert(data || size == 0);
1603
4ce534f4 1604 hash = journal_file_hash_data(f, data, size);
de190aef
LP
1605
1606 r = journal_file_find_data_object_with_hash(f, data, size, hash, &o, &p);
1607 if (r < 0)
1608 return r;
0240c603 1609 if (r > 0) {
de190aef
LP
1610
1611 if (ret)
1612 *ret = o;
1613
f4474e00
LP
1614 if (ret_offset)
1615 *ret_offset = p;
de190aef
LP
1616
1617 return 0;
1618 }
1619
1620 osize = offsetof(Object, data.payload) + size;
1621 r = journal_file_append_object(f, OBJECT_DATA, osize, &o, &p);
cec736d2
LP
1622 if (r < 0)
1623 return r;
1624
cec736d2 1625 o->data.hash = htole64(hash);
807e17f0 1626
d80b051c 1627#if HAVE_COMPRESSION
57850536 1628 if (JOURNAL_FILE_COMPRESS(f) && size >= f->compress_threshold_bytes) {
a7f7d1bd 1629 size_t rsize = 0;
807e17f0 1630
5d6f46b6 1631 compression = compress_blob(data, size, o->data.payload, size - 1, &rsize);
807e17f0 1632
d1afbcd2 1633 if (compression >= 0) {
807e17f0 1634 o->object.size = htole64(offsetof(Object, data.payload) + rsize);
d89c8fdf 1635 o->object.flags |= compression;
807e17f0 1636
fa1c4b51 1637 log_debug("Compressed data object %"PRIu64" -> %zu using %s",
d89c8fdf 1638 size, rsize, object_compressed_to_string(compression));
d1afbcd2
LP
1639 } else
1640 /* Compression didn't work, we don't really care why, let's continue without compression */
1641 compression = 0;
807e17f0
LP
1642 }
1643#endif
1644
75f32f04
ZJS
1645 if (compression == 0)
1646 memcpy_safe(o->data.payload, data, size);
cec736d2 1647
de190aef 1648 r = journal_file_link_data(f, o, p, hash);
cec736d2
LP
1649 if (r < 0)
1650 return r;
1651
349cc4a5 1652#if HAVE_GCRYPT
33685a5a
FB
1653 r = journal_file_hmac_put_object(f, OBJECT_DATA, o, p);
1654 if (r < 0)
1655 return r;
1656#endif
1657
48496df6
LP
1658 /* The linking might have altered the window, so let's
1659 * refresh our pointer */
1660 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
1661 if (r < 0)
1662 return r;
1663
08c6f819
SL
1664 if (!data)
1665 eq = NULL;
1666 else
1667 eq = memchr(data, '=', size);
3c1668da 1668 if (eq && eq > data) {
748db592 1669 Object *fo = NULL;
3c1668da 1670 uint64_t fp;
3c1668da
LP
1671
1672 /* Create field object ... */
1673 r = journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, &fo, &fp);
1674 if (r < 0)
1675 return r;
1676
1677 /* ... and link it in. */
1678 o->data.next_field_offset = fo->field.head_data_offset;
1679 fo->field.head_data_offset = le64toh(p);
1680 }
1681
cec736d2
LP
1682 if (ret)
1683 *ret = o;
1684
f4474e00
LP
1685 if (ret_offset)
1686 *ret_offset = p;
cec736d2
LP
1687
1688 return 0;
1689}
1690
1691uint64_t journal_file_entry_n_items(Object *o) {
893e0f8f 1692 uint64_t sz;
cec736d2 1693 assert(o);
b588975f
LP
1694
1695 if (o->object.type != OBJECT_ENTRY)
1696 return 0;
cec736d2 1697
893e0f8f
LP
1698 sz = le64toh(READ_NOW(o->object.size));
1699 if (sz < offsetof(Object, entry.items))
1700 return 0;
1701
1702 return (sz - offsetof(Object, entry.items)) / sizeof(EntryItem);
cec736d2
LP
1703}
1704
0284adc6 1705uint64_t journal_file_entry_array_n_items(Object *o) {
893e0f8f
LP
1706 uint64_t sz;
1707
de190aef 1708 assert(o);
b588975f
LP
1709
1710 if (o->object.type != OBJECT_ENTRY_ARRAY)
1711 return 0;
de190aef 1712
893e0f8f
LP
1713 sz = le64toh(READ_NOW(o->object.size));
1714 if (sz < offsetof(Object, entry_array.items))
1715 return 0;
1716
1717 return (sz - offsetof(Object, entry_array.items)) / sizeof(uint64_t);
de190aef
LP
1718}
1719
fb9a24b6 1720uint64_t journal_file_hash_table_n_items(Object *o) {
893e0f8f
LP
1721 uint64_t sz;
1722
fb9a24b6 1723 assert(o);
b588975f 1724
ec2ce0c5 1725 if (!IN_SET(o->object.type, OBJECT_DATA_HASH_TABLE, OBJECT_FIELD_HASH_TABLE))
b588975f 1726 return 0;
fb9a24b6 1727
893e0f8f
LP
1728 sz = le64toh(READ_NOW(o->object.size));
1729 if (sz < offsetof(Object, hash_table.items))
1730 return 0;
1731
1732 return (sz - offsetof(Object, hash_table.items)) / sizeof(HashItem);
fb9a24b6
LP
1733}
1734
de190aef 1735static int link_entry_into_array(JournalFile *f,
4fd052ae
FC
1736 le64_t *first,
1737 le64_t *idx,
de190aef 1738 uint64_t p) {
cec736d2 1739 int r;
de190aef
LP
1740 uint64_t n = 0, ap = 0, q, i, a, hidx;
1741 Object *o;
1742
cec736d2 1743 assert(f);
c88cc6af 1744 assert(f->header);
de190aef
LP
1745 assert(first);
1746 assert(idx);
1747 assert(p > 0);
cec736d2 1748
de190aef 1749 a = le64toh(*first);
893e0f8f 1750 i = hidx = le64toh(READ_NOW(*idx));
de190aef
LP
1751 while (a > 0) {
1752
1753 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
1754 if (r < 0)
1755 return r;
cec736d2 1756
de190aef
LP
1757 n = journal_file_entry_array_n_items(o);
1758 if (i < n) {
1759 o->entry_array.items[i] = htole64(p);
1760 *idx = htole64(hidx + 1);
1761 return 0;
1762 }
cec736d2 1763
de190aef
LP
1764 i -= n;
1765 ap = a;
1766 a = le64toh(o->entry_array.next_entry_array_offset);
1767 }
1768
1769 if (hidx > n)
1770 n = (hidx+1) * 2;
1771 else
1772 n = n * 2;
1773
1774 if (n < 4)
1775 n = 4;
1776
1777 r = journal_file_append_object(f, OBJECT_ENTRY_ARRAY,
1778 offsetof(Object, entry_array.items) + n * sizeof(uint64_t),
1779 &o, &q);
cec736d2
LP
1780 if (r < 0)
1781 return r;
1782
349cc4a5 1783#if HAVE_GCRYPT
5996c7c2 1784 r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, o, q);
b0af6f41
LP
1785 if (r < 0)
1786 return r;
feb12d3e 1787#endif
b0af6f41 1788
de190aef 1789 o->entry_array.items[i] = htole64(p);
cec736d2 1790
de190aef 1791 if (ap == 0)
7be3aa17 1792 *first = htole64(q);
cec736d2 1793 else {
de190aef 1794 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, ap, &o);
cec736d2
LP
1795 if (r < 0)
1796 return r;
1797
de190aef
LP
1798 o->entry_array.next_entry_array_offset = htole64(q);
1799 }
cec736d2 1800
2dee23eb
LP
1801 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
1802 f->header->n_entry_arrays = htole64(le64toh(f->header->n_entry_arrays) + 1);
1803
de190aef
LP
1804 *idx = htole64(hidx + 1);
1805
1806 return 0;
1807}
cec736d2 1808
de190aef 1809static int link_entry_into_array_plus_one(JournalFile *f,
4fd052ae
FC
1810 le64_t *extra,
1811 le64_t *first,
1812 le64_t *idx,
de190aef
LP
1813 uint64_t p) {
1814
893e0f8f 1815 uint64_t hidx;
de190aef
LP
1816 int r;
1817
1818 assert(f);
1819 assert(extra);
1820 assert(first);
1821 assert(idx);
1822 assert(p > 0);
1823
893e0f8f
LP
1824 hidx = le64toh(READ_NOW(*idx));
1825 if (hidx == UINT64_MAX)
1826 return -EBADMSG;
1827 if (hidx == 0)
de190aef
LP
1828 *extra = htole64(p);
1829 else {
4fd052ae 1830 le64_t i;
de190aef 1831
893e0f8f 1832 i = htole64(hidx - 1);
de190aef
LP
1833 r = link_entry_into_array(f, first, &i, p);
1834 if (r < 0)
1835 return r;
cec736d2
LP
1836 }
1837
893e0f8f 1838 *idx = htole64(hidx + 1);
de190aef
LP
1839 return 0;
1840}
1841
1842static int journal_file_link_entry_item(JournalFile *f, Object *o, uint64_t offset, uint64_t i) {
1843 uint64_t p;
1844 int r;
bfbd5be0 1845
de190aef
LP
1846 assert(f);
1847 assert(o);
1848 assert(offset > 0);
1849
1850 p = le64toh(o->entry.items[i].object_offset);
de190aef 1851 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
cec736d2
LP
1852 if (r < 0)
1853 return r;
1854
de190aef
LP
1855 return link_entry_into_array_plus_one(f,
1856 &o->data.entry_offset,
1857 &o->data.entry_array_offset,
1858 &o->data.n_entries,
1859 offset);
cec736d2
LP
1860}
1861
1862static int journal_file_link_entry(JournalFile *f, Object *o, uint64_t offset) {
de190aef 1863 uint64_t n, i;
cec736d2
LP
1864 int r;
1865
1866 assert(f);
c88cc6af 1867 assert(f->header);
cec736d2
LP
1868 assert(o);
1869 assert(offset > 0);
b588975f
LP
1870
1871 if (o->object.type != OBJECT_ENTRY)
1872 return -EINVAL;
cec736d2 1873
b788cc23
LP
1874 __sync_synchronize();
1875
cec736d2 1876 /* Link up the entry itself */
de190aef
LP
1877 r = link_entry_into_array(f,
1878 &f->header->entry_array_offset,
1879 &f->header->n_entries,
1880 offset);
1881 if (r < 0)
1882 return r;
cec736d2 1883
507f22bd 1884 /* log_debug("=> %s seqnr=%"PRIu64" n_entries=%"PRIu64, f->path, o->entry.seqnum, f->header->n_entries); */
cec736d2 1885
de190aef 1886 if (f->header->head_entry_realtime == 0)
0ac38b70 1887 f->header->head_entry_realtime = o->entry.realtime;
cec736d2 1888
0ac38b70 1889 f->header->tail_entry_realtime = o->entry.realtime;
de190aef
LP
1890 f->header->tail_entry_monotonic = o->entry.monotonic;
1891
cec736d2
LP
1892 /* Link up the items */
1893 n = journal_file_entry_n_items(o);
1894 for (i = 0; i < n; i++) {
1895 r = journal_file_link_entry_item(f, o, offset, i);
1896 if (r < 0)
1897 return r;
1898 }
1899
cec736d2
LP
1900 return 0;
1901}
1902
1903static int journal_file_append_entry_internal(
1904 JournalFile *f,
1905 const dual_timestamp *ts,
d180c349 1906 const sd_id128_t *boot_id,
cec736d2
LP
1907 uint64_t xor_hash,
1908 const EntryItem items[], unsigned n_items,
de190aef 1909 uint64_t *seqnum,
f4474e00 1910 Object **ret, uint64_t *ret_offset) {
cec736d2
LP
1911 uint64_t np;
1912 uint64_t osize;
1913 Object *o;
1914 int r;
1915
1916 assert(f);
c88cc6af 1917 assert(f->header);
cec736d2 1918 assert(items || n_items == 0);
de190aef 1919 assert(ts);
cec736d2
LP
1920
1921 osize = offsetof(Object, entry.items) + (n_items * sizeof(EntryItem));
1922
de190aef 1923 r = journal_file_append_object(f, OBJECT_ENTRY, osize, &o, &np);
cec736d2
LP
1924 if (r < 0)
1925 return r;
1926
d98cc1f2 1927 o->entry.seqnum = htole64(journal_file_entry_seqnum(f, seqnum));
75f32f04 1928 memcpy_safe(o->entry.items, items, n_items * sizeof(EntryItem));
de190aef
LP
1929 o->entry.realtime = htole64(ts->realtime);
1930 o->entry.monotonic = htole64(ts->monotonic);
cec736d2 1931 o->entry.xor_hash = htole64(xor_hash);
924426a7
CM
1932 if (boot_id)
1933 f->header->boot_id = *boot_id;
1934 o->entry.boot_id = f->header->boot_id;
cec736d2 1935
349cc4a5 1936#if HAVE_GCRYPT
5996c7c2 1937 r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np);
b0af6f41
LP
1938 if (r < 0)
1939 return r;
feb12d3e 1940#endif
b0af6f41 1941
cec736d2
LP
1942 r = journal_file_link_entry(f, o, np);
1943 if (r < 0)
1944 return r;
1945
1946 if (ret)
1947 *ret = o;
1948
f4474e00
LP
1949 if (ret_offset)
1950 *ret_offset = np;
cec736d2
LP
1951
1952 return 0;
1953}
1954
cf244689 1955void journal_file_post_change(JournalFile *f) {
50f20cfd
LP
1956 assert(f);
1957
c5236850
DT
1958 if (f->fd < 0)
1959 return;
1960
50f20cfd
LP
1961 /* inotify() does not receive IN_MODIFY events from file
1962 * accesses done via mmap(). After each access we hence
1963 * trigger IN_MODIFY by truncating the journal file to its
1964 * current size which triggers IN_MODIFY. */
1965
bc85bfee
LP
1966 __sync_synchronize();
1967
50f20cfd 1968 if (ftruncate(f->fd, f->last_stat.st_size) < 0)
e167d7fd 1969 log_debug_errno(errno, "Failed to truncate file to its own size: %m");
50f20cfd
LP
1970}
1971
7a24f3bf
VC
1972static int post_change_thunk(sd_event_source *timer, uint64_t usec, void *userdata) {
1973 assert(userdata);
1974
1975 journal_file_post_change(userdata);
1976
1977 return 1;
1978}
1979
1980static void schedule_post_change(JournalFile *f) {
7a24f3bf 1981 uint64_t now;
b6cdfbe5 1982 int r;
7a24f3bf
VC
1983
1984 assert(f);
1985 assert(f->post_change_timer);
1986
b6cdfbe5 1987 r = sd_event_source_get_enabled(f->post_change_timer, NULL);
7a24f3bf 1988 if (r < 0) {
e167d7fd
LP
1989 log_debug_errno(r, "Failed to get ftruncate timer state: %m");
1990 goto fail;
7a24f3bf 1991 }
b6cdfbe5 1992 if (r > 0)
7a24f3bf
VC
1993 return;
1994
ca5d90d4 1995 r = sd_event_now(sd_event_source_get_event(f->post_change_timer), CLOCK_MONOTONIC, &now);
7a24f3bf 1996 if (r < 0) {
e167d7fd
LP
1997 log_debug_errno(r, "Failed to get clock's now for scheduling ftruncate: %m");
1998 goto fail;
7a24f3bf
VC
1999 }
2000
ca5d90d4 2001 r = sd_event_source_set_time(f->post_change_timer, now + f->post_change_timer_period);
7a24f3bf 2002 if (r < 0) {
e167d7fd
LP
2003 log_debug_errno(r, "Failed to set time for scheduling ftruncate: %m");
2004 goto fail;
7a24f3bf
VC
2005 }
2006
ca5d90d4 2007 r = sd_event_source_set_enabled(f->post_change_timer, SD_EVENT_ONESHOT);
7a24f3bf 2008 if (r < 0) {
e167d7fd
LP
2009 log_debug_errno(r, "Failed to enable scheduled ftruncate: %m");
2010 goto fail;
7a24f3bf 2011 }
e167d7fd
LP
2012
2013 return;
2014
2015fail:
2016 /* On failure, let's simply post the change immediately. */
2017 journal_file_post_change(f);
7a24f3bf
VC
2018}
2019
2020/* Enable coalesced change posting in a timer on the provided sd_event instance */
2021int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t) {
2022 _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL;
2023 int r;
2024
2025 assert(f);
2026 assert_return(!f->post_change_timer, -EINVAL);
2027 assert(e);
2028 assert(t);
2029
2030 r = sd_event_add_time(e, &timer, CLOCK_MONOTONIC, 0, 0, post_change_thunk, f);
2031 if (r < 0)
2032 return r;
2033
2034 r = sd_event_source_set_enabled(timer, SD_EVENT_OFF);
2035 if (r < 0)
2036 return r;
2037
1cc6c93a 2038 f->post_change_timer = TAKE_PTR(timer);
7a24f3bf
VC
2039 f->post_change_timer_period = t;
2040
2041 return r;
2042}
2043
93bab288
YW
2044static int entry_item_cmp(const EntryItem *a, const EntryItem *b) {
2045 return CMP(le64toh(a->object_offset), le64toh(b->object_offset));
1f2da9ec
LP
2046}
2047
d180c349
ZJS
2048int journal_file_append_entry(
2049 JournalFile *f,
2050 const dual_timestamp *ts,
2051 const sd_id128_t *boot_id,
2052 const struct iovec iovec[], unsigned n_iovec,
2053 uint64_t *seqnum,
f4474e00 2054 Object **ret, uint64_t *ret_offset) {
d180c349 2055
cec736d2
LP
2056 unsigned i;
2057 EntryItem *items;
2058 int r;
2059 uint64_t xor_hash = 0;
de190aef 2060 struct dual_timestamp _ts;
cec736d2
LP
2061
2062 assert(f);
c88cc6af 2063 assert(f->header);
cec736d2
LP
2064 assert(iovec || n_iovec == 0);
2065
c6273953 2066 if (ts) {
baaa35ad
ZJS
2067 if (!VALID_REALTIME(ts->realtime))
2068 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
2069 "Invalid realtime timestamp %" PRIu64 ", refusing entry.",
2070 ts->realtime);
2071 if (!VALID_MONOTONIC(ts->monotonic))
2072 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
2073 "Invalid monotomic timestamp %" PRIu64 ", refusing entry.",
2074 ts->monotonic);
c6273953 2075 } else {
de190aef
LP
2076 dual_timestamp_get(&_ts);
2077 ts = &_ts;
2078 }
2079
349cc4a5 2080#if HAVE_GCRYPT
7560fffc
LP
2081 r = journal_file_maybe_append_tag(f, ts->realtime);
2082 if (r < 0)
2083 return r;
feb12d3e 2084#endif
7560fffc 2085
64825d3c 2086 /* alloca() can't take 0, hence let's allocate at least one */
cf409d15 2087 items = newa(EntryItem, MAX(1u, n_iovec));
cec736d2
LP
2088
2089 for (i = 0; i < n_iovec; i++) {
2090 uint64_t p;
2091 Object *o;
2092
2093 r = journal_file_append_data(f, iovec[i].iov_base, iovec[i].iov_len, &o, &p);
2094 if (r < 0)
cf244689 2095 return r;
cec736d2 2096
4ce534f4
LP
2097 /* When calculating the XOR hash field, we need to take special care if the "keyed-hash"
2098 * journal file flag is on. We use the XOR hash field to quickly determine the identity of a
2099 * specific record, and give records with otherwise identical position (i.e. match in seqno,
2100 * timestamp, …) a stable ordering. But for that we can't have it that the hash of the
2101 * objects in each file is different since they are keyed. Hence let's calculate the Jenkins
2102 * hash here for that. This also has the benefit that cursors for old and new journal files
2103 * are completely identical (they include the XOR hash after all). For classic Jenkins-hash
2104 * files things are easier, we can just take the value from the stored record directly. */
2105
2106 if (JOURNAL_HEADER_KEYED_HASH(f->header))
2107 xor_hash ^= jenkins_hash64(iovec[i].iov_base, iovec[i].iov_len);
2108 else
2109 xor_hash ^= le64toh(o->data.hash);
2110
cec736d2 2111 items[i].object_offset = htole64(p);
de7b95cd 2112 items[i].hash = o->data.hash;
cec736d2
LP
2113 }
2114
1f2da9ec
LP
2115 /* Order by the position on disk, in order to improve seek
2116 * times for rotating media. */
93bab288 2117 typesafe_qsort(items, n_iovec, entry_item_cmp);
1f2da9ec 2118
f4474e00 2119 r = journal_file_append_entry_internal(f, ts, boot_id, xor_hash, items, n_iovec, seqnum, ret, ret_offset);
cec736d2 2120
fa6ac760
LP
2121 /* If the memory mapping triggered a SIGBUS then we return an
2122 * IO error and ignore the error code passed down to us, since
2123 * it is very likely just an effect of a nullified replacement
2124 * mapping page */
2125
be7cdd8e 2126 if (mmap_cache_got_sigbus(f->mmap, f->cache_fd))
fa6ac760
LP
2127 r = -EIO;
2128
7a24f3bf
VC
2129 if (f->post_change_timer)
2130 schedule_post_change(f);
2131 else
2132 journal_file_post_change(f);
50f20cfd 2133
cec736d2
LP
2134 return r;
2135}
2136
a4bcff5b 2137typedef struct ChainCacheItem {
fb099c8d 2138 uint64_t first; /* the array at the beginning of the chain */
a4bcff5b
LP
2139 uint64_t array; /* the cached array */
2140 uint64_t begin; /* the first item in the cached array */
2141 uint64_t total; /* the total number of items in all arrays before this one in the chain */
f268980d 2142 uint64_t last_index; /* the last index we looked at, to optimize locality when bisecting */
a4bcff5b
LP
2143} ChainCacheItem;
2144
2145static void chain_cache_put(
4743015d 2146 OrderedHashmap *h,
a4bcff5b
LP
2147 ChainCacheItem *ci,
2148 uint64_t first,
2149 uint64_t array,
2150 uint64_t begin,
f268980d
LP
2151 uint64_t total,
2152 uint64_t last_index) {
a4bcff5b
LP
2153
2154 if (!ci) {
34741aa3
LP
2155 /* If the chain item to cache for this chain is the
2156 * first one it's not worth caching anything */
2157 if (array == first)
2158 return;
2159
29433089 2160 if (ordered_hashmap_size(h) >= CHAIN_CACHE_MAX) {
4743015d 2161 ci = ordered_hashmap_steal_first(h);
29433089
LP
2162 assert(ci);
2163 } else {
a4bcff5b
LP
2164 ci = new(ChainCacheItem, 1);
2165 if (!ci)
2166 return;
2167 }
2168
2169 ci->first = first;
2170
4743015d 2171 if (ordered_hashmap_put(h, &ci->first, ci) < 0) {
a4bcff5b
LP
2172 free(ci);
2173 return;
2174 }
2175 } else
2176 assert(ci->first == first);
2177
2178 ci->array = array;
2179 ci->begin = begin;
2180 ci->total = total;
f268980d 2181 ci->last_index = last_index;
a4bcff5b
LP
2182}
2183
f268980d
LP
2184static int generic_array_get(
2185 JournalFile *f,
2186 uint64_t first,
2187 uint64_t i,
f4474e00 2188 Object **ret, uint64_t *ret_offset) {
de190aef 2189
cec736d2 2190 Object *o;
a4bcff5b 2191 uint64_t p = 0, a, t = 0;
cec736d2 2192 int r;
a4bcff5b 2193 ChainCacheItem *ci;
cec736d2
LP
2194
2195 assert(f);
2196
de190aef 2197 a = first;
a4bcff5b
LP
2198
2199 /* Try the chain cache first */
4743015d 2200 ci = ordered_hashmap_get(f->chain_cache, &first);
a4bcff5b
LP
2201 if (ci && i > ci->total) {
2202 a = ci->array;
2203 i -= ci->total;
2204 t = ci->total;
2205 }
2206
de190aef 2207 while (a > 0) {
a4bcff5b 2208 uint64_t k;
cec736d2 2209
de190aef
LP
2210 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o);
2211 if (r < 0)
2212 return r;
cec736d2 2213
a4bcff5b
LP
2214 k = journal_file_entry_array_n_items(o);
2215 if (i < k) {
de190aef 2216 p = le64toh(o->entry_array.items[i]);
a4bcff5b 2217 goto found;
cec736d2
LP
2218 }
2219
a4bcff5b
LP
2220 i -= k;
2221 t += k;
de190aef
LP
2222 a = le64toh(o->entry_array.next_entry_array_offset);
2223 }
2224
a4bcff5b
LP
2225 return 0;
2226
2227found:
2228 /* Let's cache this item for the next invocation */
af13a6b0 2229 chain_cache_put(f->chain_cache, ci, first, a, le64toh(o->entry_array.items[0]), t, i);
de190aef
LP
2230
2231 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
2232 if (r < 0)
2233 return r;
2234
2235 if (ret)
2236 *ret = o;
2237
f4474e00
LP
2238 if (ret_offset)
2239 *ret_offset = p;
de190aef
LP
2240
2241 return 1;
2242}
2243
f268980d
LP
2244static int generic_array_get_plus_one(
2245 JournalFile *f,
2246 uint64_t extra,
2247 uint64_t first,
2248 uint64_t i,
f4474e00 2249 Object **ret, uint64_t *ret_offset) {
de190aef
LP
2250
2251 Object *o;
2252
2253 assert(f);
2254
2255 if (i == 0) {
2256 int r;
2257
2258 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
cec736d2
LP
2259 if (r < 0)
2260 return r;
2261
de190aef
LP
2262 if (ret)
2263 *ret = o;
cec736d2 2264
f4474e00
LP
2265 if (ret_offset)
2266 *ret_offset = extra;
cec736d2 2267
de190aef 2268 return 1;
cec736d2
LP
2269 }
2270
f4474e00 2271 return generic_array_get(f, first, i-1, ret, ret_offset);
de190aef 2272}
cec736d2 2273
de190aef
LP
2274enum {
2275 TEST_FOUND,
2276 TEST_LEFT,
2277 TEST_RIGHT
2278};
cec736d2 2279
f268980d
LP
2280static int generic_array_bisect(
2281 JournalFile *f,
2282 uint64_t first,
2283 uint64_t n,
2284 uint64_t needle,
2285 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
2286 direction_t direction,
2287 Object **ret,
f4474e00
LP
2288 uint64_t *ret_offset,
2289 uint64_t *ret_idx) {
f268980d
LP
2290
2291 uint64_t a, p, t = 0, i = 0, last_p = 0, last_index = (uint64_t) -1;
de190aef
LP
2292 bool subtract_one = false;
2293 Object *o, *array = NULL;
2294 int r;
a4bcff5b 2295 ChainCacheItem *ci;
cec736d2 2296
de190aef
LP
2297 assert(f);
2298 assert(test_object);
cec736d2 2299
a4bcff5b 2300 /* Start with the first array in the chain */
de190aef 2301 a = first;
a4bcff5b 2302
4743015d 2303 ci = ordered_hashmap_get(f->chain_cache, &first);
96d4d024 2304 if (ci && n > ci->total && ci->begin != 0) {
a4bcff5b
LP
2305 /* Ah, we have iterated this bisection array chain
2306 * previously! Let's see if we can skip ahead in the
2307 * chain, as far as the last time. But we can't jump
2308 * backwards in the chain, so let's check that
2309 * first. */
2310
2311 r = test_object(f, ci->begin, needle);
2312 if (r < 0)
2313 return r;
2314
2315 if (r == TEST_LEFT) {
f268980d 2316 /* OK, what we are looking for is right of the
a4bcff5b
LP
2317 * begin of this EntryArray, so let's jump
2318 * straight to previously cached array in the
2319 * chain */
2320
2321 a = ci->array;
2322 n -= ci->total;
2323 t = ci->total;
f268980d 2324 last_index = ci->last_index;
a4bcff5b
LP
2325 }
2326 }
2327
de190aef
LP
2328 while (a > 0) {
2329 uint64_t left, right, k, lp;
2330
2331 r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array);
cec736d2
LP
2332 if (r < 0)
2333 return r;
2334
de190aef
LP
2335 k = journal_file_entry_array_n_items(array);
2336 right = MIN(k, n);
2337 if (right <= 0)
2338 return 0;
cec736d2 2339
de190aef
LP
2340 i = right - 1;
2341 lp = p = le64toh(array->entry_array.items[i]);
2342 if (p <= 0)
bee6a291
LP
2343 r = -EBADMSG;
2344 else
2345 r = test_object(f, p, needle);
2346 if (r == -EBADMSG) {
2347 log_debug_errno(r, "Encountered invalid entry while bisecting, cutting algorithm short. (1)");
2348 n = i;
2349 continue;
2350 }
de190aef
LP
2351 if (r < 0)
2352 return r;
cec736d2 2353
de190aef
LP
2354 if (r == TEST_FOUND)
2355 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
2356
2357 if (r == TEST_RIGHT) {
2358 left = 0;
2359 right -= 1;
f268980d
LP
2360
2361 if (last_index != (uint64_t) -1) {
2362 assert(last_index <= right);
2363
2364 /* If we cached the last index we
2365 * looked at, let's try to not to jump
2366 * too wildly around and see if we can
2367 * limit the range to look at early to
2368 * the immediate neighbors of the last
2369 * index we looked at. */
2370
2371 if (last_index > 0) {
2372 uint64_t x = last_index - 1;
2373
2374 p = le64toh(array->entry_array.items[x]);
2375 if (p <= 0)
2376 return -EBADMSG;
2377
2378 r = test_object(f, p, needle);
2379 if (r < 0)
2380 return r;
2381
2382 if (r == TEST_FOUND)
2383 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
2384
2385 if (r == TEST_RIGHT)
2386 right = x;
2387 else
2388 left = x + 1;
2389 }
2390
2391 if (last_index < right) {
2392 uint64_t y = last_index + 1;
2393
2394 p = le64toh(array->entry_array.items[y]);
2395 if (p <= 0)
2396 return -EBADMSG;
2397
2398 r = test_object(f, p, needle);
2399 if (r < 0)
2400 return r;
2401
2402 if (r == TEST_FOUND)
2403 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
2404
2405 if (r == TEST_RIGHT)
2406 right = y;
2407 else
2408 left = y + 1;
2409 }
f268980d
LP
2410 }
2411
de190aef
LP
2412 for (;;) {
2413 if (left == right) {
2414 if (direction == DIRECTION_UP)
2415 subtract_one = true;
2416
2417 i = left;
2418 goto found;
2419 }
2420
2421 assert(left < right);
de190aef 2422 i = (left + right) / 2;
f268980d 2423
de190aef
LP
2424 p = le64toh(array->entry_array.items[i]);
2425 if (p <= 0)
bee6a291
LP
2426 r = -EBADMSG;
2427 else
2428 r = test_object(f, p, needle);
2429 if (r == -EBADMSG) {
2430 log_debug_errno(r, "Encountered invalid entry while bisecting, cutting algorithm short. (2)");
2431 right = n = i;
2432 continue;
2433 }
de190aef
LP
2434 if (r < 0)
2435 return r;
cec736d2 2436
de190aef
LP
2437 if (r == TEST_FOUND)
2438 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
2439
2440 if (r == TEST_RIGHT)
2441 right = i;
2442 else
2443 left = i + 1;
2444 }
2445 }
2446
2173cbf8 2447 if (k >= n) {
cbdca852
LP
2448 if (direction == DIRECTION_UP) {
2449 i = n;
2450 subtract_one = true;
2451 goto found;
2452 }
2453
cec736d2 2454 return 0;
cbdca852 2455 }
cec736d2 2456
de190aef
LP
2457 last_p = lp;
2458
2459 n -= k;
2460 t += k;
f268980d 2461 last_index = (uint64_t) -1;
de190aef 2462 a = le64toh(array->entry_array.next_entry_array_offset);
cec736d2
LP
2463 }
2464
2465 return 0;
de190aef
LP
2466
2467found:
2468 if (subtract_one && t == 0 && i == 0)
2469 return 0;
2470
a4bcff5b 2471 /* Let's cache this item for the next invocation */
af13a6b0 2472 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 2473
de190aef
LP
2474 if (subtract_one && i == 0)
2475 p = last_p;
2476 else if (subtract_one)
2477 p = le64toh(array->entry_array.items[i-1]);
2478 else
2479 p = le64toh(array->entry_array.items[i]);
2480
2481 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
2482 if (r < 0)
2483 return r;
2484
2485 if (ret)
2486 *ret = o;
2487
f4474e00
LP
2488 if (ret_offset)
2489 *ret_offset = p;
de190aef 2490
f4474e00
LP
2491 if (ret_idx)
2492 *ret_idx = t + i + (subtract_one ? -1 : 0);
de190aef
LP
2493
2494 return 1;
cec736d2
LP
2495}
2496
f268980d
LP
2497static int generic_array_bisect_plus_one(
2498 JournalFile *f,
2499 uint64_t extra,
2500 uint64_t first,
2501 uint64_t n,
2502 uint64_t needle,
2503 int (*test_object)(JournalFile *f, uint64_t p, uint64_t needle),
2504 direction_t direction,
2505 Object **ret,
f4474e00
LP
2506 uint64_t *ret_offset,
2507 uint64_t *ret_idx) {
de190aef 2508
cec736d2 2509 int r;
cbdca852
LP
2510 bool step_back = false;
2511 Object *o;
cec736d2
LP
2512
2513 assert(f);
de190aef 2514 assert(test_object);
cec736d2 2515
de190aef
LP
2516 if (n <= 0)
2517 return 0;
cec736d2 2518
de190aef
LP
2519 /* This bisects the array in object 'first', but first checks
2520 * an extra */
de190aef
LP
2521 r = test_object(f, extra, needle);
2522 if (r < 0)
2523 return r;
a536e261
LP
2524
2525 if (r == TEST_FOUND)
2526 r = direction == DIRECTION_DOWN ? TEST_RIGHT : TEST_LEFT;
2527
cbdca852
LP
2528 /* if we are looking with DIRECTION_UP then we need to first
2529 see if in the actual array there is a matching entry, and
2530 return the last one of that. But if there isn't any we need
2531 to return this one. Hence remember this, and return it
2532 below. */
2533 if (r == TEST_LEFT)
2534 step_back = direction == DIRECTION_UP;
de190aef 2535
cbdca852
LP
2536 if (r == TEST_RIGHT) {
2537 if (direction == DIRECTION_DOWN)
2538 goto found;
2539 else
2540 return 0;
a536e261 2541 }
cec736d2 2542
f4474e00 2543 r = generic_array_bisect(f, first, n-1, needle, test_object, direction, ret, ret_offset, ret_idx);
de190aef 2544
cbdca852
LP
2545 if (r == 0 && step_back)
2546 goto found;
2547
f4474e00
LP
2548 if (r > 0 && ret_idx)
2549 (*ret_idx)++;
de190aef
LP
2550
2551 return r;
cbdca852
LP
2552
2553found:
2554 r = journal_file_move_to_object(f, OBJECT_ENTRY, extra, &o);
2555 if (r < 0)
2556 return r;
2557
2558 if (ret)
2559 *ret = o;
2560
f4474e00
LP
2561 if (ret_offset)
2562 *ret_offset = extra;
cbdca852 2563
f4474e00
LP
2564 if (ret_idx)
2565 *ret_idx = 0;
cbdca852
LP
2566
2567 return 1;
2568}
2569
44a6b1b6 2570_pure_ static int test_object_offset(JournalFile *f, uint64_t p, uint64_t needle) {
cbdca852
LP
2571 assert(f);
2572 assert(p > 0);
2573
2574 if (p == needle)
2575 return TEST_FOUND;
2576 else if (p < needle)
2577 return TEST_LEFT;
2578 else
2579 return TEST_RIGHT;
2580}
2581
de190aef 2582static int test_object_seqnum(JournalFile *f, uint64_t p, uint64_t needle) {
893e0f8f 2583 uint64_t sq;
de190aef
LP
2584 Object *o;
2585 int r;
2586
2587 assert(f);
2588 assert(p > 0);
2589
2590 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
cec736d2
LP
2591 if (r < 0)
2592 return r;
2593
893e0f8f
LP
2594 sq = le64toh(READ_NOW(o->entry.seqnum));
2595 if (sq == needle)
de190aef 2596 return TEST_FOUND;
893e0f8f 2597 else if (sq < needle)
de190aef
LP
2598 return TEST_LEFT;
2599 else
2600 return TEST_RIGHT;
2601}
cec736d2 2602
de190aef
LP
2603int journal_file_move_to_entry_by_seqnum(
2604 JournalFile *f,
2605 uint64_t seqnum,
2606 direction_t direction,
2607 Object **ret,
f4474e00 2608 uint64_t *ret_offset) {
c88cc6af
VC
2609 assert(f);
2610 assert(f->header);
de190aef 2611
f4474e00
LP
2612 return generic_array_bisect(
2613 f,
2614 le64toh(f->header->entry_array_offset),
2615 le64toh(f->header->n_entries),
2616 seqnum,
2617 test_object_seqnum,
2618 direction,
2619 ret, ret_offset, NULL);
de190aef 2620}
cec736d2 2621
de190aef
LP
2622static int test_object_realtime(JournalFile *f, uint64_t p, uint64_t needle) {
2623 Object *o;
893e0f8f 2624 uint64_t rt;
de190aef
LP
2625 int r;
2626
2627 assert(f);
2628 assert(p > 0);
2629
2630 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
2631 if (r < 0)
2632 return r;
2633
893e0f8f
LP
2634 rt = le64toh(READ_NOW(o->entry.realtime));
2635 if (rt == needle)
de190aef 2636 return TEST_FOUND;
893e0f8f 2637 else if (rt < needle)
de190aef
LP
2638 return TEST_LEFT;
2639 else
2640 return TEST_RIGHT;
cec736d2
LP
2641}
2642
de190aef
LP
2643int journal_file_move_to_entry_by_realtime(
2644 JournalFile *f,
2645 uint64_t realtime,
2646 direction_t direction,
2647 Object **ret,
f4474e00 2648 uint64_t *ret_offset) {
c88cc6af
VC
2649 assert(f);
2650 assert(f->header);
de190aef 2651
f4474e00
LP
2652 return generic_array_bisect(
2653 f,
2654 le64toh(f->header->entry_array_offset),
2655 le64toh(f->header->n_entries),
2656 realtime,
2657 test_object_realtime,
2658 direction,
2659 ret, ret_offset, NULL);
de190aef
LP
2660}
2661
2662static int test_object_monotonic(JournalFile *f, uint64_t p, uint64_t needle) {
2663 Object *o;
893e0f8f 2664 uint64_t m;
de190aef
LP
2665 int r;
2666
2667 assert(f);
2668 assert(p > 0);
2669
2670 r = journal_file_move_to_object(f, OBJECT_ENTRY, p, &o);
2671 if (r < 0)
2672 return r;
2673
893e0f8f
LP
2674 m = le64toh(READ_NOW(o->entry.monotonic));
2675 if (m == needle)
de190aef 2676 return TEST_FOUND;
893e0f8f 2677 else if (m < needle)
de190aef
LP
2678 return TEST_LEFT;
2679 else
2680 return TEST_RIGHT;
2681}
2682
2a560338 2683static int find_data_object_by_boot_id(
47838ab3
ZJS
2684 JournalFile *f,
2685 sd_id128_t boot_id,
2686 Object **o,
2687 uint64_t *b) {
2a560338 2688
fbd0b64f 2689 char t[STRLEN("_BOOT_ID=") + 32 + 1] = "_BOOT_ID=";
47838ab3
ZJS
2690
2691 sd_id128_to_string(boot_id, t + 9);
2692 return journal_file_find_data_object(f, t, sizeof(t) - 1, o, b);
2693}
2694
de190aef
LP
2695int journal_file_move_to_entry_by_monotonic(
2696 JournalFile *f,
2697 sd_id128_t boot_id,
2698 uint64_t monotonic,
2699 direction_t direction,
2700 Object **ret,
f4474e00 2701 uint64_t *ret_offset) {
de190aef 2702
de190aef
LP
2703 Object *o;
2704 int r;
2705
cbdca852 2706 assert(f);
de190aef 2707
47838ab3 2708 r = find_data_object_by_boot_id(f, boot_id, &o, NULL);
de190aef
LP
2709 if (r < 0)
2710 return r;
cbdca852 2711 if (r == 0)
de190aef
LP
2712 return -ENOENT;
2713
f4474e00
LP
2714 return generic_array_bisect_plus_one(
2715 f,
2716 le64toh(o->data.entry_offset),
2717 le64toh(o->data.entry_array_offset),
2718 le64toh(o->data.n_entries),
2719 monotonic,
2720 test_object_monotonic,
2721 direction,
2722 ret, ret_offset, NULL);
de190aef
LP
2723}
2724
1fc605b0 2725void journal_file_reset_location(JournalFile *f) {
6573ef05 2726 f->location_type = LOCATION_HEAD;
1fc605b0 2727 f->current_offset = 0;
6573ef05
MS
2728 f->current_seqnum = 0;
2729 f->current_realtime = 0;
2730 f->current_monotonic = 0;
2731 zero(f->current_boot_id);
2732 f->current_xor_hash = 0;
2733}
2734
950c07d4 2735void journal_file_save_location(JournalFile *f, Object *o, uint64_t offset) {
6573ef05
MS
2736 f->location_type = LOCATION_SEEK;
2737 f->current_offset = offset;
2738 f->current_seqnum = le64toh(o->entry.seqnum);
2739 f->current_realtime = le64toh(o->entry.realtime);
2740 f->current_monotonic = le64toh(o->entry.monotonic);
2741 f->current_boot_id = o->entry.boot_id;
2742 f->current_xor_hash = le64toh(o->entry.xor_hash);
1fc605b0
MS
2743}
2744
d8ae66d7 2745int journal_file_compare_locations(JournalFile *af, JournalFile *bf) {
90c88092
YW
2746 int r;
2747
d8ae66d7 2748 assert(af);
c88cc6af 2749 assert(af->header);
d8ae66d7 2750 assert(bf);
c88cc6af 2751 assert(bf->header);
d8ae66d7
MS
2752 assert(af->location_type == LOCATION_SEEK);
2753 assert(bf->location_type == LOCATION_SEEK);
2754
2755 /* If contents and timestamps match, these entries are
2756 * identical, even if the seqnum does not match */
2757 if (sd_id128_equal(af->current_boot_id, bf->current_boot_id) &&
2758 af->current_monotonic == bf->current_monotonic &&
2759 af->current_realtime == bf->current_realtime &&
2760 af->current_xor_hash == bf->current_xor_hash)
2761 return 0;
2762
2763 if (sd_id128_equal(af->header->seqnum_id, bf->header->seqnum_id)) {
2764
2765 /* If this is from the same seqnum source, compare
2766 * seqnums */
90c88092
YW
2767 r = CMP(af->current_seqnum, bf->current_seqnum);
2768 if (r != 0)
2769 return r;
d8ae66d7
MS
2770
2771 /* Wow! This is weird, different data but the same
2772 * seqnums? Something is borked, but let's make the
2773 * best of it and compare by time. */
2774 }
2775
2776 if (sd_id128_equal(af->current_boot_id, bf->current_boot_id)) {
2777
2778 /* If the boot id matches, compare monotonic time */
90c88092
YW
2779 r = CMP(af->current_monotonic, bf->current_monotonic);
2780 if (r != 0)
2781 return r;
d8ae66d7
MS
2782 }
2783
2784 /* Otherwise, compare UTC time */
90c88092
YW
2785 r = CMP(af->current_realtime, bf->current_realtime);
2786 if (r != 0)
2787 return r;
d8ae66d7
MS
2788
2789 /* Finally, compare by contents */
6dd91b36 2790 return CMP(af->current_xor_hash, bf->current_xor_hash);
d8ae66d7
MS
2791}
2792
aa598ba5
LP
2793static int bump_array_index(uint64_t *i, direction_t direction, uint64_t n) {
2794
2795 /* Increase or decrease the specified index, in the right direction. */
2796
2797 if (direction == DIRECTION_DOWN) {
2798 if (*i >= n - 1)
2799 return 0;
2800
2801 (*i) ++;
2802 } else {
2803 if (*i <= 0)
2804 return 0;
2805
2806 (*i) --;
2807 }
2808
2809 return 1;
2810}
2811
b6da4ed0
LP
2812static bool check_properly_ordered(uint64_t new_offset, uint64_t old_offset, direction_t direction) {
2813
2814 /* Consider it an error if any of the two offsets is uninitialized */
2815 if (old_offset == 0 || new_offset == 0)
2816 return false;
2817
2818 /* If we go down, the new offset must be larger than the old one. */
2819 return direction == DIRECTION_DOWN ?
2820 new_offset > old_offset :
2821 new_offset < old_offset;
2822}
2823
de190aef
LP
2824int journal_file_next_entry(
2825 JournalFile *f,
f534928a 2826 uint64_t p,
de190aef 2827 direction_t direction,
f4474e00 2828 Object **ret, uint64_t *ret_offset) {
de190aef 2829
fb099c8d 2830 uint64_t i, n, ofs;
cec736d2
LP
2831 int r;
2832
2833 assert(f);
c88cc6af 2834 assert(f->header);
de190aef 2835
893e0f8f 2836 n = le64toh(READ_NOW(f->header->n_entries));
de190aef
LP
2837 if (n <= 0)
2838 return 0;
cec736d2 2839
f534928a 2840 if (p == 0)
de190aef 2841 i = direction == DIRECTION_DOWN ? 0 : n - 1;
cec736d2 2842 else {
de190aef
LP
2843 r = generic_array_bisect(f,
2844 le64toh(f->header->entry_array_offset),
2845 le64toh(f->header->n_entries),
2846 p,
2847 test_object_offset,
2848 DIRECTION_DOWN,
2849 NULL, NULL,
2850 &i);
2851 if (r <= 0)
2852 return r;
2853
aa598ba5
LP
2854 r = bump_array_index(&i, direction, n);
2855 if (r <= 0)
2856 return r;
cec736d2
LP
2857 }
2858
de190aef 2859 /* And jump to it */
989793d3
LP
2860 for (;;) {
2861 r = generic_array_get(f,
2862 le64toh(f->header->entry_array_offset),
2863 i,
2864 ret, &ofs);
2865 if (r > 0)
2866 break;
2867 if (r != -EBADMSG)
2868 return r;
2869
2870 /* OK, so this entry is borked. Most likely some entry didn't get synced to disk properly, let's see if
2871 * the next one might work for us instead. */
2872 log_debug_errno(r, "Entry item %" PRIu64 " is bad, skipping over it.", i);
2873
2874 r = bump_array_index(&i, direction, n);
2875 if (r <= 0)
2876 return r;
caeab8f6 2877 }
fb099c8d 2878
b6da4ed0 2879 /* Ensure our array is properly ordered. */
baaa35ad
ZJS
2880 if (p > 0 && !check_properly_ordered(ofs, p, direction))
2881 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
2882 "%s: entry array not properly ordered at entry %" PRIu64,
2883 f->path, i);
fb099c8d 2884
f4474e00
LP
2885 if (ret_offset)
2886 *ret_offset = ofs;
fb099c8d
ZJS
2887
2888 return 1;
de190aef 2889}
cec736d2 2890
de190aef
LP
2891int journal_file_next_entry_for_data(
2892 JournalFile *f,
2893 Object *o, uint64_t p,
2894 uint64_t data_offset,
2895 direction_t direction,
f4474e00 2896 Object **ret, uint64_t *ret_offset) {
de190aef 2897
ded5034e 2898 uint64_t i, n, ofs;
de190aef 2899 Object *d;
989793d3 2900 int r;
cec736d2
LP
2901
2902 assert(f);
de190aef 2903 assert(p > 0 || !o);
cec736d2 2904
de190aef 2905 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
466ccd92 2906 if (r < 0)
de190aef 2907 return r;
cec736d2 2908
893e0f8f 2909 n = le64toh(READ_NOW(d->data.n_entries));
de190aef
LP
2910 if (n <= 0)
2911 return n;
cec736d2 2912
de190aef
LP
2913 if (!o)
2914 i = direction == DIRECTION_DOWN ? 0 : n - 1;
2915 else {
2916 if (o->object.type != OBJECT_ENTRY)
2917 return -EINVAL;
cec736d2 2918
de190aef
LP
2919 r = generic_array_bisect_plus_one(f,
2920 le64toh(d->data.entry_offset),
2921 le64toh(d->data.entry_array_offset),
2922 le64toh(d->data.n_entries),
2923 p,
2924 test_object_offset,
2925 DIRECTION_DOWN,
2926 NULL, NULL,
2927 &i);
2928
2929 if (r <= 0)
cec736d2
LP
2930 return r;
2931
aa598ba5
LP
2932 r = bump_array_index(&i, direction, n);
2933 if (r <= 0)
2934 return r;
de190aef 2935 }
cec736d2 2936
989793d3
LP
2937 for (;;) {
2938 r = generic_array_get_plus_one(f,
2939 le64toh(d->data.entry_offset),
2940 le64toh(d->data.entry_array_offset),
2941 i,
2942 ret, &ofs);
2943 if (r > 0)
2944 break;
2945 if (r != -EBADMSG)
2946 return r;
2947
2948 log_debug_errno(r, "Data entry item %" PRIu64 " is bad, skipping over it.", i);
2949
2950 r = bump_array_index(&i, direction, n);
2951 if (r <= 0)
2952 return r;
2953 }
ded5034e
LP
2954
2955 /* Ensure our array is properly ordered. */
baaa35ad
ZJS
2956 if (p > 0 && check_properly_ordered(ofs, p, direction))
2957 return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
2958 "%s data entry array not properly ordered at entry %" PRIu64,
2959 f->path, i);
ded5034e 2960
f4474e00
LP
2961 if (ret_offset)
2962 *ret_offset = ofs;
ded5034e
LP
2963
2964 return 1;
de190aef 2965}
cec736d2 2966
cbdca852
LP
2967int journal_file_move_to_entry_by_offset_for_data(
2968 JournalFile *f,
2969 uint64_t data_offset,
2970 uint64_t p,
2971 direction_t direction,
f4474e00 2972 Object **ret, uint64_t *ret_offset) {
cbdca852
LP
2973
2974 int r;
2975 Object *d;
2976
2977 assert(f);
2978
2979 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
2980 if (r < 0)
2981 return r;
2982
f4474e00
LP
2983 return generic_array_bisect_plus_one(
2984 f,
2985 le64toh(d->data.entry_offset),
2986 le64toh(d->data.entry_array_offset),
2987 le64toh(d->data.n_entries),
2988 p,
2989 test_object_offset,
2990 direction,
2991 ret, ret_offset, NULL);
cbdca852
LP
2992}
2993
2994int journal_file_move_to_entry_by_monotonic_for_data(
2995 JournalFile *f,
2996 uint64_t data_offset,
2997 sd_id128_t boot_id,
2998 uint64_t monotonic,
2999 direction_t direction,
f4474e00 3000 Object **ret, uint64_t *ret_offset) {
cbdca852 3001
cbdca852
LP
3002 Object *o, *d;
3003 int r;
3004 uint64_t b, z;
3005
3006 assert(f);
3007
3008 /* First, seek by time */
47838ab3 3009 r = find_data_object_by_boot_id(f, boot_id, &o, &b);
cbdca852
LP
3010 if (r < 0)
3011 return r;
3012 if (r == 0)
3013 return -ENOENT;
3014
3015 r = generic_array_bisect_plus_one(f,
3016 le64toh(o->data.entry_offset),
3017 le64toh(o->data.entry_array_offset),
3018 le64toh(o->data.n_entries),
3019 monotonic,
3020 test_object_monotonic,
3021 direction,
3022 NULL, &z, NULL);
3023 if (r <= 0)
3024 return r;
3025
3026 /* And now, continue seeking until we find an entry that
3027 * exists in both bisection arrays */
3028
3029 for (;;) {
3030 Object *qo;
3031 uint64_t p, q;
3032
3033 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
3034 if (r < 0)
3035 return r;
3036
3037 r = generic_array_bisect_plus_one(f,
3038 le64toh(d->data.entry_offset),
3039 le64toh(d->data.entry_array_offset),
3040 le64toh(d->data.n_entries),
3041 z,
3042 test_object_offset,
3043 direction,
3044 NULL, &p, NULL);
3045 if (r <= 0)
3046 return r;
3047
3048 r = journal_file_move_to_object(f, OBJECT_DATA, b, &o);
3049 if (r < 0)
3050 return r;
3051
3052 r = generic_array_bisect_plus_one(f,
3053 le64toh(o->data.entry_offset),
3054 le64toh(o->data.entry_array_offset),
3055 le64toh(o->data.n_entries),
3056 p,
3057 test_object_offset,
3058 direction,
3059 &qo, &q, NULL);
3060
3061 if (r <= 0)
3062 return r;
3063
3064 if (p == q) {
3065 if (ret)
3066 *ret = qo;
f4474e00
LP
3067 if (ret_offset)
3068 *ret_offset = q;
cbdca852
LP
3069
3070 return 1;
3071 }
3072
3073 z = q;
3074 }
cbdca852
LP
3075}
3076
de190aef
LP
3077int journal_file_move_to_entry_by_seqnum_for_data(
3078 JournalFile *f,
3079 uint64_t data_offset,
3080 uint64_t seqnum,
3081 direction_t direction,
f4474e00 3082 Object **ret, uint64_t *ret_offset) {
cec736d2 3083
de190aef
LP
3084 Object *d;
3085 int r;
cec736d2 3086
91a31dde
LP
3087 assert(f);
3088
de190aef 3089 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
91a31dde 3090 if (r < 0)
de190aef 3091 return r;
cec736d2 3092
f4474e00
LP
3093 return generic_array_bisect_plus_one(
3094 f,
3095 le64toh(d->data.entry_offset),
3096 le64toh(d->data.entry_array_offset),
3097 le64toh(d->data.n_entries),
3098 seqnum,
3099 test_object_seqnum,
3100 direction,
3101 ret, ret_offset, NULL);
de190aef 3102}
cec736d2 3103
de190aef
LP
3104int journal_file_move_to_entry_by_realtime_for_data(
3105 JournalFile *f,
3106 uint64_t data_offset,
3107 uint64_t realtime,
3108 direction_t direction,
f4474e00 3109 Object **ret, uint64_t *ret_offset) {
de190aef
LP
3110
3111 Object *d;
3112 int r;
3113
91a31dde
LP
3114 assert(f);
3115
de190aef 3116 r = journal_file_move_to_object(f, OBJECT_DATA, data_offset, &d);
91a31dde 3117 if (r < 0)
de190aef
LP
3118 return r;
3119
f4474e00
LP
3120 return generic_array_bisect_plus_one(
3121 f,
3122 le64toh(d->data.entry_offset),
3123 le64toh(d->data.entry_array_offset),
3124 le64toh(d->data.n_entries),
3125 realtime,
3126 test_object_realtime,
3127 direction,
3128 ret, ret_offset, NULL);
cec736d2
LP
3129}
3130
0284adc6 3131void journal_file_dump(JournalFile *f) {
7560fffc 3132 Object *o;
7560fffc 3133 int r;
0284adc6 3134 uint64_t p;
7560fffc
LP
3135
3136 assert(f);
c88cc6af 3137 assert(f->header);
7560fffc 3138
0284adc6 3139 journal_file_print_header(f);
7560fffc 3140
893e0f8f 3141 p = le64toh(READ_NOW(f->header->header_size));
0284adc6 3142 while (p != 0) {
d05089d8 3143 r = journal_file_move_to_object(f, OBJECT_UNUSED, p, &o);
0284adc6
LP
3144 if (r < 0)
3145 goto fail;
7560fffc 3146
0284adc6 3147 switch (o->object.type) {
d98cc1f2 3148
0284adc6
LP
3149 case OBJECT_UNUSED:
3150 printf("Type: OBJECT_UNUSED\n");
3151 break;
d98cc1f2 3152
0284adc6
LP
3153 case OBJECT_DATA:
3154 printf("Type: OBJECT_DATA\n");
3155 break;
7560fffc 3156
3c1668da
LP
3157 case OBJECT_FIELD:
3158 printf("Type: OBJECT_FIELD\n");
3159 break;
3160
0284adc6 3161 case OBJECT_ENTRY:
507f22bd
ZJS
3162 printf("Type: OBJECT_ENTRY seqnum=%"PRIu64" monotonic=%"PRIu64" realtime=%"PRIu64"\n",
3163 le64toh(o->entry.seqnum),
3164 le64toh(o->entry.monotonic),
3165 le64toh(o->entry.realtime));
0284adc6 3166 break;
7560fffc 3167
0284adc6
LP
3168 case OBJECT_FIELD_HASH_TABLE:
3169 printf("Type: OBJECT_FIELD_HASH_TABLE\n");
3170 break;
7560fffc 3171
0284adc6
LP
3172 case OBJECT_DATA_HASH_TABLE:
3173 printf("Type: OBJECT_DATA_HASH_TABLE\n");
3174 break;
7560fffc 3175
0284adc6
LP
3176 case OBJECT_ENTRY_ARRAY:
3177 printf("Type: OBJECT_ENTRY_ARRAY\n");
3178 break;
7560fffc 3179
0284adc6 3180 case OBJECT_TAG:
507f22bd
ZJS
3181 printf("Type: OBJECT_TAG seqnum=%"PRIu64" epoch=%"PRIu64"\n",
3182 le64toh(o->tag.seqnum),
3183 le64toh(o->tag.epoch));
0284adc6 3184 break;
3c1668da
LP
3185
3186 default:
8facc349 3187 printf("Type: unknown (%i)\n", o->object.type);
3c1668da 3188 break;
0284adc6 3189 }
7560fffc 3190
d89c8fdf
ZJS
3191 if (o->object.flags & OBJECT_COMPRESSION_MASK)
3192 printf("Flags: %s\n",
3193 object_compressed_to_string(o->object.flags & OBJECT_COMPRESSION_MASK));
7560fffc 3194
0284adc6
LP
3195 if (p == le64toh(f->header->tail_object_offset))
3196 p = 0;
3197 else
71139898 3198 p += ALIGN64(le64toh(o->object.size));
0284adc6 3199 }
7560fffc 3200
0284adc6
LP
3201 return;
3202fail:
3203 log_error("File corrupt");
7560fffc
LP
3204}
3205
718fe4b1
ZJS
3206static const char* format_timestamp_safe(char *buf, size_t l, usec_t t) {
3207 const char *x;
3208
3209 x = format_timestamp(buf, l, t);
3210 if (x)
3211 return x;
3212 return " --- ";
3213}
3214
0284adc6 3215void journal_file_print_header(JournalFile *f) {
5905d7cf 3216 char a[SD_ID128_STRING_MAX], b[SD_ID128_STRING_MAX], c[SD_ID128_STRING_MAX], d[SD_ID128_STRING_MAX];
ed375beb 3217 char x[FORMAT_TIMESTAMP_MAX], y[FORMAT_TIMESTAMP_MAX], z[FORMAT_TIMESTAMP_MAX];
a1a03e30
LP
3218 struct stat st;
3219 char bytes[FORMAT_BYTES_MAX];
7560fffc
LP
3220
3221 assert(f);
c88cc6af 3222 assert(f->header);
7560fffc 3223
2c54acb1 3224 printf("File path: %s\n"
0284adc6
LP
3225 "File ID: %s\n"
3226 "Machine ID: %s\n"
3227 "Boot ID: %s\n"
2c54acb1 3228 "Sequential number ID: %s\n"
0284adc6 3229 "State: %s\n"
2c54acb1 3230 "Compatible flags:%s%s\n"
8653185a 3231 "Incompatible flags:%s%s%s%s%s\n"
507f22bd
ZJS
3232 "Header size: %"PRIu64"\n"
3233 "Arena size: %"PRIu64"\n"
2c54acb1
TN
3234 "Data hash table size: %"PRIu64"\n"
3235 "Field hash table size: %"PRIu64"\n"
3236 "Rotate suggested: %s\n"
3237 "Head sequential number: %"PRIu64" (%"PRIx64")\n"
3238 "Tail sequential number: %"PRIu64" (%"PRIx64")\n"
3239 "Head realtime timestamp: %s (%"PRIx64")\n"
3240 "Tail realtime timestamp: %s (%"PRIx64")\n"
3241 "Tail monotonic timestamp: %s (%"PRIx64")\n"
507f22bd 3242 "Objects: %"PRIu64"\n"
2c54acb1 3243 "Entry objects: %"PRIu64"\n",
0284adc6
LP
3244 f->path,
3245 sd_id128_to_string(f->header->file_id, a),
3246 sd_id128_to_string(f->header->machine_id, b),
3247 sd_id128_to_string(f->header->boot_id, c),
2765b7bb 3248 sd_id128_to_string(f->header->seqnum_id, d),
3223f44f
LP
3249 f->header->state == STATE_OFFLINE ? "OFFLINE" :
3250 f->header->state == STATE_ONLINE ? "ONLINE" :
3251 f->header->state == STATE_ARCHIVED ? "ARCHIVED" : "UNKNOWN",
8088cbd3 3252 JOURNAL_HEADER_SEALED(f->header) ? " SEALED" : "",
d89c8fdf
ZJS
3253 (le32toh(f->header->compatible_flags) & ~HEADER_COMPATIBLE_ANY) ? " ???" : "",
3254 JOURNAL_HEADER_COMPRESSED_XZ(f->header) ? " COMPRESSED-XZ" : "",
3255 JOURNAL_HEADER_COMPRESSED_LZ4(f->header) ? " COMPRESSED-LZ4" : "",
8653185a 3256 JOURNAL_HEADER_COMPRESSED_ZSTD(f->header) ? " COMPRESSED-ZSTD" : "",
4ce534f4 3257 JOURNAL_HEADER_KEYED_HASH(f->header) ? " KEYED-HASH" : "",
d89c8fdf 3258 (le32toh(f->header->incompatible_flags) & ~HEADER_INCOMPATIBLE_ANY) ? " ???" : "",
507f22bd
ZJS
3259 le64toh(f->header->header_size),
3260 le64toh(f->header->arena_size),
3261 le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
3262 le64toh(f->header->field_hash_table_size) / sizeof(HashItem),
fb0951b0 3263 yes_no(journal_file_rotate_suggested(f, 0)),
0808b92f
LP
3264 le64toh(f->header->head_entry_seqnum), le64toh(f->header->head_entry_seqnum),
3265 le64toh(f->header->tail_entry_seqnum), le64toh(f->header->tail_entry_seqnum),
3266 format_timestamp_safe(x, sizeof(x), le64toh(f->header->head_entry_realtime)), le64toh(f->header->head_entry_realtime),
3267 format_timestamp_safe(y, sizeof(y), le64toh(f->header->tail_entry_realtime)), le64toh(f->header->tail_entry_realtime),
3268 format_timespan(z, sizeof(z), le64toh(f->header->tail_entry_monotonic), USEC_PER_MSEC), le64toh(f->header->tail_entry_monotonic),
507f22bd
ZJS
3269 le64toh(f->header->n_objects),
3270 le64toh(f->header->n_entries));
7560fffc 3271
0284adc6 3272 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
2c54acb1
TN
3273 printf("Data objects: %"PRIu64"\n"
3274 "Data hash table fill: %.1f%%\n",
507f22bd 3275 le64toh(f->header->n_data),
0284adc6 3276 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))));
7560fffc 3277
0284adc6 3278 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
2c54acb1
TN
3279 printf("Field objects: %"PRIu64"\n"
3280 "Field hash table fill: %.1f%%\n",
507f22bd 3281 le64toh(f->header->n_fields),
0284adc6 3282 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))));
3223f44f
LP
3283
3284 if (JOURNAL_HEADER_CONTAINS(f->header, n_tags))
2c54acb1 3285 printf("Tag objects: %"PRIu64"\n",
507f22bd 3286 le64toh(f->header->n_tags));
3223f44f 3287 if (JOURNAL_HEADER_CONTAINS(f->header, n_entry_arrays))
2c54acb1 3288 printf("Entry array objects: %"PRIu64"\n",
507f22bd 3289 le64toh(f->header->n_entry_arrays));
a1a03e30 3290
0dbe57ee
LP
3291 if (JOURNAL_HEADER_CONTAINS(f->header, field_hash_chain_depth))
3292 printf("Deepest field hash chain: %" PRIu64"\n",
3293 f->header->field_hash_chain_depth);
3294
3295 if (JOURNAL_HEADER_CONTAINS(f->header, data_hash_chain_depth))
3296 printf("Deepest data hash chain: %" PRIu64"\n",
3297 f->header->data_hash_chain_depth);
3298
a1a03e30 3299 if (fstat(f->fd, &st) >= 0)
59f448cf 3300 printf("Disk usage: %s\n", format_bytes(bytes, sizeof(bytes), (uint64_t) st.st_blocks * 512ULL));
7560fffc
LP
3301}
3302
fc68c929
LP
3303static int journal_file_warn_btrfs(JournalFile *f) {
3304 unsigned attrs;
3305 int r;
3306
3307 assert(f);
3308
3309 /* Before we write anything, check if the COW logic is turned
3310 * off on btrfs. Given our write pattern that is quite
3311 * unfriendly to COW file systems this should greatly improve
3312 * performance on COW file systems, such as btrfs, at the
3313 * expense of data integrity features (which shouldn't be too
3314 * bad, given that we do our own checksumming). */
3315
3316 r = btrfs_is_filesystem(f->fd);
3317 if (r < 0)
3318 return log_warning_errno(r, "Failed to determine if journal is on btrfs: %m");
3319 if (!r)
3320 return 0;
3321
3322 r = read_attr_fd(f->fd, &attrs);
3323 if (r < 0)
3324 return log_warning_errno(r, "Failed to read file attributes: %m");
3325
3326 if (attrs & FS_NOCOW_FL) {
3327 log_debug("Detected btrfs file system with copy-on-write disabled, all is good.");
3328 return 0;
3329 }
3330
3331 log_notice("Creating journal file %s on a btrfs file system, and copy-on-write is enabled. "
3332 "This is likely to slow down journal access substantially, please consider turning "
3333 "off the copy-on-write file attribute on the journal directory, using chattr +C.", f->path);
3334
3335 return 1;
3336}
3337
0284adc6 3338int journal_file_open(
5d1ce257 3339 int fd,
0284adc6
LP
3340 const char *fname,
3341 int flags,
3342 mode_t mode,
3343 bool compress,
57850536 3344 uint64_t compress_threshold_bytes,
baed47c3 3345 bool seal,
0284adc6
LP
3346 JournalMetrics *metrics,
3347 MMapCache *mmap_cache,
b58c888f 3348 Set *deferred_closes,
0284adc6
LP
3349 JournalFile *template,
3350 JournalFile **ret) {
7560fffc 3351
fa6ac760 3352 bool newly_created = false;
0284adc6 3353 JournalFile *f;
fa6ac760 3354 void *h;
0284adc6 3355 int r;
7560fffc 3356
0559d3a5 3357 assert(ret);
5d1ce257 3358 assert(fd >= 0 || fname);
7560fffc 3359
ec2ce0c5 3360 if (!IN_SET((flags & O_ACCMODE), O_RDONLY, O_RDWR))
0284adc6 3361 return -EINVAL;
7560fffc 3362
6eda13d3
LP
3363 if (fname && (flags & O_CREAT) && !endswith(fname, ".journal"))
3364 return -EINVAL;
7560fffc 3365
971b52c4 3366 f = new(JournalFile, 1);
0284adc6
LP
3367 if (!f)
3368 return -ENOMEM;
7560fffc 3369
971b52c4
LP
3370 *f = (JournalFile) {
3371 .fd = fd,
3372 .mode = mode,
3373
3374 .flags = flags,
3375 .prot = prot_from_flags(flags),
3376 .writable = (flags & O_ACCMODE) != O_RDONLY,
7560fffc 3377
8653185a
LP
3378#if HAVE_ZSTD
3379 .compress_zstd = compress,
3380#elif HAVE_LZ4
971b52c4 3381 .compress_lz4 = compress,
349cc4a5 3382#elif HAVE_XZ
971b52c4 3383 .compress_xz = compress,
48b61739 3384#endif
971b52c4
LP
3385 .compress_threshold_bytes = compress_threshold_bytes == (uint64_t) -1 ?
3386 DEFAULT_COMPRESS_THRESHOLD :
3387 MAX(MIN_COMPRESS_THRESHOLD, compress_threshold_bytes),
349cc4a5 3388#if HAVE_GCRYPT
971b52c4 3389 .seal = seal,
49a32d43 3390#endif
971b52c4 3391 };
7560fffc 3392
4ce534f4
LP
3393 /* We turn on keyed hashes by default, but provide an environment variable to turn them off, if
3394 * people really want that */
3395 r = getenv_bool("SYSTEMD_JOURNAL_KEYED_HASH");
3396 if (r < 0) {
3397 if (r != -ENXIO)
3398 log_debug_errno(r, "Failed to parse $SYSTEMD_JOURNAL_KEYED_HASH environment variable, ignoring.");
3399 f->keyed_hash = true;
3400 } else
3401 f->keyed_hash = r;
3402
170a434c 3403 if (DEBUG_LOGGING) {
4ce534f4 3404 static int last_seal = -1, last_compress = -1, last_keyed_hash = -1;
170a434c
ZJS
3405 static uint64_t last_bytes = UINT64_MAX;
3406 char bytes[FORMAT_BYTES_MAX];
3407
3408 if (last_seal != f->seal ||
4ce534f4 3409 last_keyed_hash != f->keyed_hash ||
170a434c
ZJS
3410 last_compress != JOURNAL_FILE_COMPRESS(f) ||
3411 last_bytes != f->compress_threshold_bytes) {
3412
4ce534f4
LP
3413 log_debug("Journal effective settings seal=%s keyed_hash=%s compress=%s compress_threshold_bytes=%s",
3414 yes_no(f->seal), yes_no(f->keyed_hash), yes_no(JOURNAL_FILE_COMPRESS(f)),
170a434c
ZJS
3415 format_bytes(bytes, sizeof bytes, f->compress_threshold_bytes));
3416 last_seal = f->seal;
4ce534f4 3417 last_keyed_hash = f->keyed_hash;
170a434c
ZJS
3418 last_compress = JOURNAL_FILE_COMPRESS(f);
3419 last_bytes = f->compress_threshold_bytes;
3420 }
3421 }
57850536 3422
0284adc6
LP
3423 if (mmap_cache)
3424 f->mmap = mmap_cache_ref(mmap_cache);
3425 else {
84168d80 3426 f->mmap = mmap_cache_new();
0284adc6
LP
3427 if (!f->mmap) {
3428 r = -ENOMEM;
3429 goto fail;
3430 }
3431 }
7560fffc 3432
7645c77b 3433 if (fname) {
5d1ce257 3434 f->path = strdup(fname);
7645c77b
ZJS
3435 if (!f->path) {
3436 r = -ENOMEM;
3437 goto fail;
3438 }
3439 } else {
817b1c5b
LP
3440 assert(fd >= 0);
3441
7645c77b
ZJS
3442 /* If we don't know the path, fill in something explanatory and vaguely useful */
3443 if (asprintf(&f->path, "/proc/self/%i", fd) < 0) {
3444 r = -ENOMEM;
3445 goto fail;
3446 }
0284adc6 3447 }
7560fffc 3448
4743015d 3449 f->chain_cache = ordered_hashmap_new(&uint64_hash_ops);
a4bcff5b
LP
3450 if (!f->chain_cache) {
3451 r = -ENOMEM;
3452 goto fail;
3453 }
3454
0284adc6 3455 if (f->fd < 0) {
817b1c5b
LP
3456 /* We pass O_NONBLOCK here, so that in case somebody pointed us to some character device node or FIFO
3457 * or so, we likely fail quickly than block for long. For regular files O_NONBLOCK has no effect, hence
3458 * it doesn't hurt in that case. */
3459
3460 f->fd = open(f->path, f->flags|O_CLOEXEC|O_NONBLOCK, f->mode);
5d1ce257
LP
3461 if (f->fd < 0) {
3462 r = -errno;
3463 goto fail;
3464 }
3465
3466 /* fds we opened here by us should also be closed by us. */
3467 f->close_fd = true;
817b1c5b
LP
3468
3469 r = fd_nonblock(f->fd, false);
3470 if (r < 0)
3471 goto fail;
7560fffc 3472 }
7560fffc 3473
be7cdd8e
VC
3474 f->cache_fd = mmap_cache_add_fd(f->mmap, f->fd);
3475 if (!f->cache_fd) {
3476 r = -ENOMEM;
3477 goto fail;
3478 }
3479
2678031a
LP
3480 r = journal_file_fstat(f);
3481 if (r < 0)
0284adc6 3482 goto fail;
7560fffc 3483
0284adc6 3484 if (f->last_stat.st_size == 0 && f->writable) {
11689d2a 3485
fc68c929 3486 (void) journal_file_warn_btrfs(f);
11689d2a 3487
4c2e1b39
LP
3488 /* Let's attach the creation time to the journal file, so that the vacuuming code knows the age of this
3489 * file even if the file might end up corrupted one day... Ideally we'd just use the creation time many
3490 * file systems maintain for each file, but the API to query this is very new, hence let's emulate this
3491 * via extended attributes. If extended attributes are not supported we'll just skip this, and rely
3492 * solely on mtime/atime/ctime of the file. */
3493 (void) fd_setcrtime(f->fd, 0);
7560fffc 3494
349cc4a5 3495#if HAVE_GCRYPT
0284adc6 3496 /* Try to load the FSPRG state, and if we can't, then
baed47c3 3497 * just don't do sealing */
49a32d43
LP
3498 if (f->seal) {
3499 r = journal_file_fss_load(f);
3500 if (r < 0)
3501 f->seal = false;
3502 }
feb12d3e 3503#endif
7560fffc 3504
0284adc6
LP
3505 r = journal_file_init_header(f, template);
3506 if (r < 0)
3507 goto fail;
7560fffc 3508
2678031a
LP
3509 r = journal_file_fstat(f);
3510 if (r < 0)
0284adc6 3511 goto fail;
fb0951b0
LP
3512
3513 newly_created = true;
0284adc6 3514 }
7560fffc 3515
0284adc6 3516 if (f->last_stat.st_size < (off_t) HEADER_SIZE_MIN) {
cfb571f3 3517 r = -ENODATA;
0284adc6
LP
3518 goto fail;
3519 }
7560fffc 3520
b42549ad 3521 r = mmap_cache_get(f->mmap, f->cache_fd, f->prot, CONTEXT_HEADER, true, 0, PAGE_ALIGN(sizeof(Header)), &f->last_stat, &h, NULL);
5087825e
LP
3522 if (r == -EINVAL) {
3523 /* Some file systems (jffs2 or p9fs) don't support mmap() properly (or only read-only
3524 * mmap()), and return EINVAL in that case. Let's propagate that as a more recognizable error
3525 * code. */
3526 r = -EAFNOSUPPORT;
3527 goto fail;
3528 }
977eaa1e 3529 if (r < 0)
0284adc6 3530 goto fail;
7560fffc 3531
fa6ac760
LP
3532 f->header = h;
3533
0284adc6 3534 if (!newly_created) {
f9168190 3535 set_clear_with_destructor(deferred_closes, journal_file_close);
b58c888f 3536
0284adc6
LP
3537 r = journal_file_verify_header(f);
3538 if (r < 0)
3539 goto fail;
3540 }
7560fffc 3541
349cc4a5 3542#if HAVE_GCRYPT
0284adc6 3543 if (!newly_created && f->writable) {
baed47c3 3544 r = journal_file_fss_load(f);
0284adc6
LP
3545 if (r < 0)
3546 goto fail;
3547 }
feb12d3e 3548#endif
cec736d2
LP
3549
3550 if (f->writable) {
4a92baf3
LP
3551 if (metrics) {
3552 journal_default_metrics(metrics, f->fd);
3553 f->metrics = *metrics;
3554 } else if (template)
3555 f->metrics = template->metrics;
3556
cec736d2
LP
3557 r = journal_file_refresh_header(f);
3558 if (r < 0)
3559 goto fail;
3560 }
3561
349cc4a5 3562#if HAVE_GCRYPT
baed47c3 3563 r = journal_file_hmac_setup(f);
14d10188
LP
3564 if (r < 0)
3565 goto fail;
feb12d3e 3566#endif
14d10188 3567
cec736d2 3568 if (newly_created) {
de190aef 3569 r = journal_file_setup_field_hash_table(f);
cec736d2
LP
3570 if (r < 0)
3571 goto fail;
3572
de190aef 3573 r = journal_file_setup_data_hash_table(f);
cec736d2
LP
3574 if (r < 0)
3575 goto fail;
7560fffc 3576
349cc4a5 3577#if HAVE_GCRYPT
7560fffc
LP
3578 r = journal_file_append_first_tag(f);
3579 if (r < 0)
3580 goto fail;
feb12d3e 3581#endif
cec736d2
LP
3582 }
3583
be7cdd8e 3584 if (mmap_cache_got_sigbus(f->mmap, f->cache_fd)) {
fa6ac760
LP
3585 r = -EIO;
3586 goto fail;
3587 }
3588
7a24f3bf 3589 if (template && template->post_change_timer) {
e167d7fd
LP
3590 r = journal_file_enable_post_change_timer(
3591 f,
3592 sd_event_source_get_event(template->post_change_timer),
3593 template->post_change_timer_period);
7a24f3bf 3594
7a24f3bf
VC
3595 if (r < 0)
3596 goto fail;
3597 }
3598
f8e2f4d6 3599 /* The file is opened now successfully, thus we take possession of any passed in fd. */
5d1ce257
LP
3600 f->close_fd = true;
3601
0559d3a5 3602 *ret = f;
cec736d2
LP
3603 return 0;
3604
3605fail:
be7cdd8e 3606 if (f->cache_fd && mmap_cache_got_sigbus(f->mmap, f->cache_fd))
fa6ac760
LP
3607 r = -EIO;
3608
69a3a6fd 3609 (void) journal_file_close(f);
cec736d2
LP
3610
3611 return r;
3612}
0ac38b70 3613
7a4d21ad 3614int journal_file_archive(JournalFile *f) {
57535f47 3615 _cleanup_free_ char *p = NULL;
0ac38b70
LP
3616
3617 assert(f);
0ac38b70 3618
7a4d21ad 3619 if (!f->writable)
0ac38b70
LP
3620 return -EINVAL;
3621
5d1ce257 3622 /* Is this a journal file that was passed to us as fd? If so, we synthesized a path name for it, and we refuse
13e785f7 3623 * rotation, since we don't know the actual path, and couldn't rename the file hence. */
7a4d21ad 3624 if (path_startswith(f->path, "/proc/self/fd"))
5d1ce257
LP
3625 return -EINVAL;
3626
7a4d21ad 3627 if (!endswith(f->path, ".journal"))
0ac38b70
LP
3628 return -EINVAL;
3629
7a4d21ad
LP
3630 if (asprintf(&p, "%.*s@" SD_ID128_FORMAT_STR "-%016"PRIx64"-%016"PRIx64".journal",
3631 (int) strlen(f->path) - 8, f->path,
3632 SD_ID128_FORMAT_VAL(f->header->seqnum_id),
3633 le64toh(f->header->head_entry_seqnum),
3634 le64toh(f->header->head_entry_realtime)) < 0)
0ac38b70
LP
3635 return -ENOMEM;
3636
7a4d21ad
LP
3637 /* Try to rename the file to the archived version. If the file already was deleted, we'll get ENOENT, let's
3638 * ignore that case. */
3639 if (rename(f->path, p) < 0 && errno != ENOENT)
0ac38b70
LP
3640 return -errno;
3641
1fcefd88 3642 /* Sync the rename to disk */
7a4d21ad
LP
3643 (void) fsync_directory_of_file(f->fd);
3644
3645 /* Set as archive so offlining commits w/state=STATE_ARCHIVED. Previously we would set old_file->header->state
3646 * to STATE_ARCHIVED directly here, but journal_file_set_offline() short-circuits when state != STATE_ONLINE,
3647 * which would result in the rotated journal never getting fsync() called before closing. Now we simply queue
3648 * the archive state by setting an archive bit, leaving the state as STATE_ONLINE so proper offlining
3649 * occurs. */
3650 f->archive = true;
3651
3652 /* Currently, btrfs is not very good with out write patterns and fragments heavily. Let's defrag our journal
3653 * files when we archive them */
3654 f->defrag_on_close = true;
3655
3656 return 0;
3657}
3658
3659JournalFile* journal_initiate_close(
3660 JournalFile *f,
3661 Set *deferred_closes) {
3662
3663 int r;
3664
3665 assert(f);
3666
3667 if (deferred_closes) {
0ac38b70 3668
7a4d21ad
LP
3669 r = set_put(deferred_closes, f);
3670 if (r < 0)
3671 log_debug_errno(r, "Failed to add file to deferred close set, closing immediately.");
3672 else {
3673 (void) journal_file_set_offline(f, false);
3674 return NULL;
3675 }
3676 }
3677
3678 return journal_file_close(f);
3679}
3680
3681int journal_file_rotate(
3682 JournalFile **f,
3683 bool compress,
3684 uint64_t compress_threshold_bytes,
3685 bool seal,
3686 Set *deferred_closes) {
3687
3688 JournalFile *new_file = NULL;
3689 int r;
3690
3691 assert(f);
3692 assert(*f);
3693
3694 r = journal_file_archive(*f);
3695 if (r < 0)
3696 return r;
3697
3698 r = journal_file_open(
3699 -1,
3700 (*f)->path,
3701 (*f)->flags,
3702 (*f)->mode,
3703 compress,
3704 compress_threshold_bytes,
3705 seal,
3706 NULL, /* metrics */
3707 (*f)->mmap,
3708 deferred_closes,
3709 *f, /* template */
3710 &new_file);
3711
3712 journal_initiate_close(*f, deferred_closes);
0ac38b70 3713 *f = new_file;
7a4d21ad 3714
0ac38b70
LP
3715 return r;
3716}
3717
68127658
LP
3718int journal_file_dispose(int dir_fd, const char *fname) {
3719 _cleanup_free_ char *p = NULL;
3720 _cleanup_close_ int fd = -1;
3721
3722 assert(fname);
3723
3724 /* Renames a journal file to *.journal~, i.e. to mark it as corruped or otherwise uncleanly shutdown. Note that
3725 * this is done without looking into the file or changing any of its contents. The idea is that this is called
3726 * whenever something is suspicious and we want to move the file away and make clear that it is not accessed
3727 * for writing anymore. */
3728
3729 if (!endswith(fname, ".journal"))
3730 return -EINVAL;
3731
3732 if (asprintf(&p, "%.*s@%016" PRIx64 "-%016" PRIx64 ".journal~",
3733 (int) strlen(fname) - 8, fname,
3734 now(CLOCK_REALTIME),
3735 random_u64()) < 0)
3736 return -ENOMEM;
3737
3738 if (renameat(dir_fd, fname, dir_fd, p) < 0)
3739 return -errno;
3740
3741 /* btrfs doesn't cope well with our write pattern and fragments heavily. Let's defrag all files we rotate */
3742 fd = openat(dir_fd, p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW);
3743 if (fd < 0)
3744 log_debug_errno(errno, "Failed to open file for defragmentation/FS_NOCOW_FL, ignoring: %m");
3745 else {
3746 (void) chattr_fd(fd, 0, FS_NOCOW_FL, NULL);
3747 (void) btrfs_defrag_fd(fd);
3748 }
3749
3750 return 0;
3751}
3752
9447a7f1
LP
3753int journal_file_open_reliably(
3754 const char *fname,
3755 int flags,
3756 mode_t mode,
7560fffc 3757 bool compress,
57850536 3758 uint64_t compress_threshold_bytes,
baed47c3 3759 bool seal,
4a92baf3 3760 JournalMetrics *metrics,
27370278 3761 MMapCache *mmap_cache,
b58c888f 3762 Set *deferred_closes,
9447a7f1
LP
3763 JournalFile *template,
3764 JournalFile **ret) {
3765
68127658 3766 int r;
9447a7f1 3767
57850536
AG
3768 r = journal_file_open(-1, fname, flags, mode, compress, compress_threshold_bytes, seal, metrics, mmap_cache,
3769 deferred_closes, template, ret);
288359db 3770 if (!IN_SET(r,
b288cdeb
ZJS
3771 -EBADMSG, /* Corrupted */
3772 -ENODATA, /* Truncated */
3773 -EHOSTDOWN, /* Other machine */
3774 -EPROTONOSUPPORT, /* Incompatible feature */
3775 -EBUSY, /* Unclean shutdown */
3776 -ESHUTDOWN, /* Already archived */
288359db 3777 -EIO, /* IO error, including SIGBUS on mmap */
ae739cc1
LP
3778 -EIDRM, /* File has been deleted */
3779 -ETXTBSY)) /* File is from the future */
9447a7f1
LP
3780 return r;
3781
3782 if ((flags & O_ACCMODE) == O_RDONLY)
3783 return r;
3784
3785 if (!(flags & O_CREAT))
3786 return r;
3787
7560fffc
LP
3788 if (!endswith(fname, ".journal"))
3789 return r;
3790
5c70eab4 3791 /* The file is corrupted. Rotate it away and try it again (but only once) */
65089b82 3792 log_warning_errno(r, "File %s corrupted or uncleanly shut down, renaming and replacing.", fname);
9447a7f1 3793
68127658
LP
3794 r = journal_file_dispose(AT_FDCWD, fname);
3795 if (r < 0)
3796 return r;
3797
57850536
AG
3798 return journal_file_open(-1, fname, flags, mode, compress, compress_threshold_bytes, seal, metrics, mmap_cache,
3799 deferred_closes, template, ret);
9447a7f1
LP
3800}
3801
5a271b08 3802int journal_file_copy_entry(JournalFile *from, JournalFile *to, Object *o, uint64_t p) {
cf244689
LP
3803 uint64_t i, n;
3804 uint64_t q, xor_hash = 0;
3805 int r;
3806 EntryItem *items;
3807 dual_timestamp ts;
d180c349 3808 const sd_id128_t *boot_id;
cf244689
LP
3809
3810 assert(from);
3811 assert(to);
3812 assert(o);
3813 assert(p);
3814
3815 if (!to->writable)
3816 return -EPERM;
3817
3818 ts.monotonic = le64toh(o->entry.monotonic);
3819 ts.realtime = le64toh(o->entry.realtime);
d180c349 3820 boot_id = &o->entry.boot_id;
cf244689 3821
cf244689 3822 n = journal_file_entry_n_items(o);
4faa7004 3823 /* alloca() can't take 0, hence let's allocate at least one */
cf409d15 3824 items = newa(EntryItem, MAX(1u, n));
cf244689
LP
3825
3826 for (i = 0; i < n; i++) {
4fd052ae
FC
3827 uint64_t l, h;
3828 le64_t le_hash;
cf244689
LP
3829 size_t t;
3830 void *data;
3831 Object *u;
3832
3833 q = le64toh(o->entry.items[i].object_offset);
3834 le_hash = o->entry.items[i].hash;
3835
3836 r = journal_file_move_to_object(from, OBJECT_DATA, q, &o);
3837 if (r < 0)
3838 return r;
3839
3840 if (le_hash != o->data.hash)
3841 return -EBADMSG;
3842
893e0f8f
LP
3843 l = le64toh(READ_NOW(o->object.size));
3844 if (l < offsetof(Object, data.payload))
3845 return -EBADMSG;
3846
3847 l -= offsetof(Object, data.payload);
cf244689
LP
3848 t = (size_t) l;
3849
3850 /* We hit the limit on 32bit machines */
3851 if ((uint64_t) t != l)
3852 return -E2BIG;
3853
d89c8fdf 3854 if (o->object.flags & OBJECT_COMPRESSION_MASK) {
d80b051c 3855#if HAVE_COMPRESSION
a7f7d1bd 3856 size_t rsize = 0;
cf244689 3857
d89c8fdf
ZJS
3858 r = decompress_blob(o->object.flags & OBJECT_COMPRESSION_MASK,
3859 o->data.payload, l, &from->compress_buffer, &from->compress_buffer_size, &rsize, 0);
3860 if (r < 0)
3861 return r;
cf244689
LP
3862
3863 data = from->compress_buffer;
3864 l = rsize;
3b1a55e1
ZJS
3865#else
3866 return -EPROTONOSUPPORT;
3867#endif
cf244689
LP
3868 } else
3869 data = o->data.payload;
3870
3871 r = journal_file_append_data(to, data, l, &u, &h);
3872 if (r < 0)
3873 return r;
3874
4ce534f4
LP
3875 if (JOURNAL_HEADER_KEYED_HASH(to->header))
3876 xor_hash ^= jenkins_hash64(data, l);
3877 else
3878 xor_hash ^= le64toh(u->data.hash);
3879
cf244689
LP
3880 items[i].object_offset = htole64(h);
3881 items[i].hash = u->data.hash;
3882
3883 r = journal_file_move_to_object(from, OBJECT_ENTRY, p, &o);
3884 if (r < 0)
3885 return r;
3886 }
3887
d180c349
ZJS
3888 r = journal_file_append_entry_internal(to, &ts, boot_id, xor_hash, items, n,
3889 NULL, NULL, NULL);
fa6ac760 3890
be7cdd8e 3891 if (mmap_cache_got_sigbus(to->mmap, to->cache_fd))
fa6ac760
LP
3892 return -EIO;
3893
3894 return r;
cf244689 3895}
babfc091 3896
8580d1f7
LP
3897void journal_reset_metrics(JournalMetrics *m) {
3898 assert(m);
3899
3900 /* Set everything to "pick automatic values". */
3901
3902 *m = (JournalMetrics) {
3903 .min_use = (uint64_t) -1,
3904 .max_use = (uint64_t) -1,
3905 .min_size = (uint64_t) -1,
3906 .max_size = (uint64_t) -1,
3907 .keep_free = (uint64_t) -1,
3908 .n_max_files = (uint64_t) -1,
3909 };
3910}
3911
babfc091 3912void journal_default_metrics(JournalMetrics *m, int fd) {
8580d1f7 3913 char a[FORMAT_BYTES_MAX], b[FORMAT_BYTES_MAX], c[FORMAT_BYTES_MAX], d[FORMAT_BYTES_MAX], e[FORMAT_BYTES_MAX];
babfc091 3914 struct statvfs ss;
6aae0b1a 3915 uint64_t fs_size = 0;
babfc091
LP
3916
3917 assert(m);
3918 assert(fd >= 0);
3919
3920 if (fstatvfs(fd, &ss) >= 0)
3921 fs_size = ss.f_frsize * ss.f_blocks;
6aae0b1a 3922 else
8fc58f1a 3923 log_debug_errno(errno, "Failed to determine disk size: %m");
babfc091
LP
3924
3925 if (m->max_use == (uint64_t) -1) {
3926
6aae0b1a
ZJS
3927 if (fs_size > 0)
3928 m->max_use = CLAMP(PAGE_ALIGN(fs_size / 10), /* 10% of file system size */
3929 MAX_USE_LOWER, MAX_USE_UPPER);
3930 else
3931 m->max_use = MAX_USE_LOWER;
babfc091
LP
3932 } else {
3933 m->max_use = PAGE_ALIGN(m->max_use);
3934
8580d1f7 3935 if (m->max_use != 0 && m->max_use < JOURNAL_FILE_SIZE_MIN*2)
babfc091
LP
3936 m->max_use = JOURNAL_FILE_SIZE_MIN*2;
3937 }
3938
6aae0b1a
ZJS
3939 if (m->min_use == (uint64_t) -1) {
3940 if (fs_size > 0)
3941 m->min_use = CLAMP(PAGE_ALIGN(fs_size / 50), /* 2% of file system size */
3942 MIN_USE_LOW, MIN_USE_HIGH);
3943 else
3944 m->min_use = MIN_USE_LOW;
3945 }
8580d1f7
LP
3946
3947 if (m->min_use > m->max_use)
3948 m->min_use = m->max_use;
3949
6aae0b1a
ZJS
3950 if (m->max_size == (uint64_t) -1)
3951 m->max_size = MIN(PAGE_ALIGN(m->max_use / 8), /* 8 chunks */
3952 MAX_SIZE_UPPER);
3953 else
babfc091
LP
3954 m->max_size = PAGE_ALIGN(m->max_size);
3955
8580d1f7
LP
3956 if (m->max_size != 0) {
3957 if (m->max_size < JOURNAL_FILE_SIZE_MIN)
3958 m->max_size = JOURNAL_FILE_SIZE_MIN;
babfc091 3959
8580d1f7
LP
3960 if (m->max_use != 0 && m->max_size*2 > m->max_use)
3961 m->max_use = m->max_size*2;
3962 }
babfc091
LP
3963
3964 if (m->min_size == (uint64_t) -1)
3965 m->min_size = JOURNAL_FILE_SIZE_MIN;
6aae0b1a
ZJS
3966 else
3967 m->min_size = CLAMP(PAGE_ALIGN(m->min_size),
3968 JOURNAL_FILE_SIZE_MIN,
3969 m->max_size ?: UINT64_MAX);
babfc091
LP
3970
3971 if (m->keep_free == (uint64_t) -1) {
6aae0b1a
ZJS
3972 if (fs_size > 0)
3973 m->keep_free = MIN(PAGE_ALIGN(fs_size / 20), /* 5% of file system size */
3974 KEEP_FREE_UPPER);
3975 else
babfc091
LP
3976 m->keep_free = DEFAULT_KEEP_FREE;
3977 }
3978
8580d1f7
LP
3979 if (m->n_max_files == (uint64_t) -1)
3980 m->n_max_files = DEFAULT_N_MAX_FILES;
3981
3982 log_debug("Fixed min_use=%s max_use=%s max_size=%s min_size=%s keep_free=%s n_max_files=%" PRIu64,
3983 format_bytes(a, sizeof(a), m->min_use),
3984 format_bytes(b, sizeof(b), m->max_use),
3985 format_bytes(c, sizeof(c), m->max_size),
3986 format_bytes(d, sizeof(d), m->min_size),
3987 format_bytes(e, sizeof(e), m->keep_free),
3988 m->n_max_files);
babfc091 3989}
08984293
LP
3990
3991int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *from, usec_t *to) {
08984293 3992 assert(f);
c88cc6af 3993 assert(f->header);
08984293
LP
3994 assert(from || to);
3995
3996 if (from) {
162566a4
LP
3997 if (f->header->head_entry_realtime == 0)
3998 return -ENOENT;
08984293 3999
162566a4 4000 *from = le64toh(f->header->head_entry_realtime);
08984293
LP
4001 }
4002
4003 if (to) {
162566a4
LP
4004 if (f->header->tail_entry_realtime == 0)
4005 return -ENOENT;
08984293 4006
162566a4 4007 *to = le64toh(f->header->tail_entry_realtime);
08984293
LP
4008 }
4009
4010 return 1;
4011}
4012
4013int journal_file_get_cutoff_monotonic_usec(JournalFile *f, sd_id128_t boot_id, usec_t *from, usec_t *to) {
08984293
LP
4014 Object *o;
4015 uint64_t p;
4016 int r;
4017
4018 assert(f);
4019 assert(from || to);
4020
47838ab3 4021 r = find_data_object_by_boot_id(f, boot_id, &o, &p);
08984293
LP
4022 if (r <= 0)
4023 return r;
4024
4025 if (le64toh(o->data.n_entries) <= 0)
4026 return 0;
4027
4028 if (from) {
4029 r = journal_file_move_to_object(f, OBJECT_ENTRY, le64toh(o->data.entry_offset), &o);
4030 if (r < 0)
4031 return r;
4032
4033 *from = le64toh(o->entry.monotonic);
4034 }
4035
4036 if (to) {
4037 r = journal_file_move_to_object(f, OBJECT_DATA, p, &o);
4038 if (r < 0)
4039 return r;
4040
4041 r = generic_array_get_plus_one(f,
4042 le64toh(o->data.entry_offset),
4043 le64toh(o->data.entry_array_offset),
4044 le64toh(o->data.n_entries)-1,
4045 &o, NULL);
4046 if (r <= 0)
4047 return r;
4048
4049 *to = le64toh(o->entry.monotonic);
4050 }
4051
4052 return 1;
4053}
dca6219e 4054
fb0951b0 4055bool journal_file_rotate_suggested(JournalFile *f, usec_t max_file_usec) {
dca6219e 4056 assert(f);
c88cc6af 4057 assert(f->header);
dca6219e
LP
4058
4059 /* If we gained new header fields we gained new features,
4060 * hence suggest a rotation */
361f9cbc
LP
4061 if (le64toh(f->header->header_size) < sizeof(Header)) {
4062 log_debug("%s uses an outdated header, suggesting rotation.", f->path);
dca6219e 4063 return true;
361f9cbc 4064 }
dca6219e 4065
0dbe57ee
LP
4066 /* Let's check if the hash tables grew over a certain fill level (75%, borrowing this value from
4067 * Java's hash table implementation), and if so suggest a rotation. To calculate the fill level we
4068 * need the n_data field, which only exists in newer versions. */
dca6219e
LP
4069
4070 if (JOURNAL_HEADER_CONTAINS(f->header, n_data))
361f9cbc 4071 if (le64toh(f->header->n_data) * 4ULL > (le64toh(f->header->data_hash_table_size) / sizeof(HashItem)) * 3ULL) {
507f22bd 4072 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
4073 f->path,
4074 100.0 * (double) le64toh(f->header->n_data) / ((double) (le64toh(f->header->data_hash_table_size) / sizeof(HashItem))),
507f22bd
ZJS
4075 le64toh(f->header->n_data),
4076 le64toh(f->header->data_hash_table_size) / sizeof(HashItem),
4077 (unsigned long long) f->last_stat.st_size,
4078 f->last_stat.st_size / le64toh(f->header->n_data));
dca6219e 4079 return true;
361f9cbc 4080 }
dca6219e
LP
4081
4082 if (JOURNAL_HEADER_CONTAINS(f->header, n_fields))
361f9cbc 4083 if (le64toh(f->header->n_fields) * 4ULL > (le64toh(f->header->field_hash_table_size) / sizeof(HashItem)) * 3ULL) {
507f22bd 4084 log_debug("Field hash table of %s has a fill level at %.1f (%"PRIu64" of %"PRIu64" items), suggesting rotation.",
361f9cbc
LP
4085 f->path,
4086 100.0 * (double) le64toh(f->header->n_fields) / ((double) (le64toh(f->header->field_hash_table_size) / sizeof(HashItem))),
507f22bd
ZJS
4087 le64toh(f->header->n_fields),
4088 le64toh(f->header->field_hash_table_size) / sizeof(HashItem));
dca6219e 4089 return true;
361f9cbc 4090 }
dca6219e 4091
0dbe57ee
LP
4092 /* If there are too many hash collisions somebody is most likely playing games with us. Hence, if our
4093 * longest chain is longer than some threshold, let's suggest rotation. */
4094 if (JOURNAL_HEADER_CONTAINS(f->header, data_hash_chain_depth) &&
4095 le64toh(f->header->data_hash_chain_depth) > HASH_CHAIN_DEPTH_MAX) {
4096 log_debug("Data hash table of %s has deepest hash chain of length %" PRIu64 ", suggesting rotation.",
4097 f->path, le64toh(f->header->data_hash_chain_depth));
4098 return true;
4099 }
4100
4101 if (JOURNAL_HEADER_CONTAINS(f->header, field_hash_chain_depth) &&
4102 le64toh(f->header->field_hash_chain_depth) > HASH_CHAIN_DEPTH_MAX) {
4103 log_debug("Field hash table of %s has deepest hash chain of length at %" PRIu64 ", suggesting rotation.",
4104 f->path, le64toh(f->header->field_hash_chain_depth));
4105 return true;
4106 }
4107
0598fd4a
LP
4108 /* Are the data objects properly indexed by field objects? */
4109 if (JOURNAL_HEADER_CONTAINS(f->header, n_data) &&
4110 JOURNAL_HEADER_CONTAINS(f->header, n_fields) &&
4111 le64toh(f->header->n_data) > 0 &&
4112 le64toh(f->header->n_fields) == 0)
4113 return true;
4114
fb0951b0
LP
4115 if (max_file_usec > 0) {
4116 usec_t t, h;
4117
4118 h = le64toh(f->header->head_entry_realtime);
4119 t = now(CLOCK_REALTIME);
4120
4121 if (h > 0 && t > h + max_file_usec)
4122 return true;
4123 }
4124
dca6219e
LP
4125 return false;
4126}