Skip to main content

Module journal

Module journal 

Source
Expand description

The write-ahead journal (§12, §12c).

Frame (format 2): len: u32 | crc32c: u32 | seq: u64 | prev: [u8; 32] | payload, little-endian, where len counts prev and the payload. The CRC covers a format tag, seq, prev and the payload.

Hash chain (I18). prev is the chain head before this record, and the head after it is SHA-256(prev ‖ seq ‖ payload); the first record after an empty journal chains from zero, and the checkpoint carries the head at its seq in its own prev. A scan accepts frames only while each is intact, its seq is exactly one more than the last, and its prev is the current head. An intact frame that does not chain is an integrity failure. A frame that fails its CRC is the torn tail only if no intact record with a later seq follows it; then its header is zeroed and it is never interpreted. Otherwise a record in the middle was damaged or edited, which is an integrity failure too, and nothing is truncated: the evidence stays.

The chain has no key: it detects damage and naive edits, not an attacker with root who rewrites every later record and the checkpoint consistently (measured boot, Phase E, anchors the head outside the disk).

Compaction is checkpoint-then-truncate. The ledger is written as the checkpoint (atomic replace), then the log restarts at offset 0. Bytes left over from before compaction all carry seq ≤ the checkpoint’s seq, so they can never pass as the next record.

Several handles (the daemon and the watchdog) may share a store. Every append takes the store lock and first catches up on records others wrote.

Structs§

Frame 🔒
An intact frame: its seq, the chain head before it, its payload, and its size.
Journal
Locked
A journal handle with the store lock held (see Journal::locked).

Enums§

JournalError

Constants§

FORMAT 🔒
Prefixed to the CRC input, so a frame of another format never passes.
HEADER 🔒
PREV 🔒

Functions§

chain
The chain head after a record (I18).
could_follow 🔒
Whether bytes could start a frame written after last_seq, judged on the header alone, before any CRC (the scan’s search for a later record). Its length fits, and its seq is past last_seq by no more than the number of frames capacity holds: every record this segment can contain is within that, and leftovers from before a compaction carry seqs at or below the checkpoint’s. And its payload starts as every appended one does, with the entry’s own seq ({"seq":<seq>,, Entry’s first field): at a misaligned offset inside a leftover frame, a length and a seq can both look plausible, but not that too. Without these the search ran a CRC over the claimed length at every byte offset, quadratic in the store after a compaction (stale seqs and hashes read as plausible lengths).
crc 🔒
delete
Fault injection (tests and the gym): remove the nth record and re-frame every record after it one seq lower, each with its own prev and a valid CRC, so every frame is intact and the seqs run on; zero what is left at the end. The chain then breaks at the record after the deleted one (I18). False if there is no such record.
frame 🔒
legacy_frame 🔒
Whether bytes start with an intact frame of format 1 (no chain, plain CRC).
sentinel_txn 🔒
The transaction a sentinel names (RecoveryFailed txn=<id> …), if any.
tamper
Fault injection (tests and the gym): rewrite the payload of the nth record in the log (0-based, from offset 0) with edit, keeping its prev and recomputing its CRC, as someone editing the file would. The chain then breaks at the record after it (I18). False if there is no such record or the edit changes the payload’s length.
unframe 🔒
Parse a frame header + body from bytes; None if it is not intact.