Corium is a minimal Linux initramfs PID 1. It establishes a verified Carapace
root and optional writable state, then executes /sbin/init. It also retains
its static executable in /run/initramfs/shutdown for systemd’s shutdown
handoff.
- Supply Linux 7.0 or newer, with device-mapper, devtmpfs, procfs, sysfs,
bootconfig support (
CONFIG_BOOT_CONFIG=y), and the drivers needed to discover and mount the root. Drivers may be built in or supplied as modules. - Install the static Corium binary as
/initin an initramfs. - Put the required modules and their dependencies under
/usr/lib/modules/<running-kernel-release>/, preserving subdirectories. Corium loads regular.ko,.ko.xz,.ko.gz, and.ko.zstfiles. Compressed files require the matching kernel decompressor. No module index, userspace decompressor, udev, shell, or device nodes are needed. - Activate bootconfig with
bootconfigon the kernel command line and supplycorium.rootin the bootconfig document. The value is the Carapace tip hash: nonempty, even-length hexadecimal, in either case, with no0xprefix. Carapace validates the digest length against the chain. - Expose the Carapace's GPT partitions to the guest. The assembled Carapace may contain a filesystem directly or a GPT with one eligible root partition for the guest architecture. Corium detects the layout from the GPT signature.
- Include an executable
/sbin/init, its interpreter/libraries, and the directories and files the real init requires in that filesystem, including real/dev,/proc,/sysand/runmountpoints. Corium does not create runtime mountpoints inside the read-only root.
- Mount devtmpfs and reopen the console. Require Linux 7.0+ before mounting temporary procfs and sysfs.
- Read and validate Corium's settings from
/proc/bootconfig. - Read the selected plain
.kofiles' ELF.modinfohard dependencies and load dependencies first. Arma decompresses modules when building the initramfs; compressed files are rejected with a packaging diagnostic. Already-loaded modules count as available. Missing dependencies, cycles and malformed metadata fail before loading; kernel rejection is fatal. Module metadata reads are limited to 64 MiB per file. An absent or empty module directory is valid. - Discover the root chain, allowing up to five seconds for missing partitions. Validation and other I/O failures are fatal immediately. Assemble it using Carapace's file-based API.
- Create a read-only dm-linear view named
corium-rootover the assembled device or its selected GPT root partition, leaving the shared chain available as another consumer's origin. Recognize ext-family, XFS and Btrfs primary superblock signatures and use the matching registered driver directly. Prefer ext4 for the ext family (it also supports ext2/ext3), avoiding incompatible older alias probes. A missing driver or rejected recognized mount is fatal; conflicting signatures are rejected. Otherwise try registered non-nodevdrivers until the view mounts read-only. Unknown formats retain driver discovery rather than being rejected by a fixed filesystem allowlist. - Make mount propagation recursively private, then move the temporary API
mounts into the new root. Use
pivot_root(".", ".")and detach the old initramfs, relying on Linux 7.0+'s rootfs semantics. There is no recursive deletion, writable old-root directory, or legacy-kernel fallback. - Mount tmpfs at
/runand copy only the running static executable to/run/initramfs/shutdown; the boot initramfs and module files are not retained. Detach the temporary API mounts, then execute/sbin/initwith every received process argument afterargv[0], preserving their bytes, order, and boundaries. Setargv[0]to/sbin/initand inherit the environment. Console descriptors close on successful exec, but remain available for a fatal diagnostic if the handoff fails.
On fatal errors, Corium logs at error level, syncs, and attempts to power off.
Systemd stops services and flushes storage, switches into /run/initramfs,
and executes /shutdown with the shutdown action as its first argument.
Corium normally unmounts the /oldroot subtree, children first, then syncs
and performs the requested poweroff, reboot, halt or kexec. Unmount failures
are reported before the requested action is attempted. An unsupported action
is reported and halts instead. No dracut restoration service is needed.
Carapace and Corium mappings already use deferred removal. Corium does not wait for their kernel objects to disappear after unmounting, issue additional device-removal commands, or reload modules. Writable state must be cleanly unmounted; read-only mapping disappearance is not a shutdown gate.
It never returns from PID 1. Invoking it as an ordinary process fails without performing init duties. Kernel and real-init output are outside Corium's logging policy. Failures before the console is established may have no Corium diagnostic if the kernel supplied no usable stderr.
Corium does not create /run, leave API mounts for the real init, enforce
a JSON boot contract, configure networking, provide a
writable root layer, or run as a post-boot service. It has no systemd generator,
init-path override, directory listing, or success-marker mode.
The pinned Carapace API currently wraps missing-partition errors as
InvalidData. The discovery adapter recognizes that specific diagnostic;
other validation errors are not retried. Recheck this adapter when updating
Carapace.
A writable whole virtio disk provides persistent /var, /home, /root and
/srv on the same state filesystem. Without a writable disk, Corium boots
without persistent state. Root, /etc and /usr remain read-only.
List image-owned files or directories in /var/.bind, one relative path beneath
/var per line (for example lib/dpkg). These paths remain read-only and come
from the current image on every boot. An absent or empty file makes all of /var
mutable when a state disk is attached. Blank lines and lines beginning with #
are ignored. Symlinks, missing sources, escaping paths, overlapping entries and
conflicting state types are rejected. Declared paths hide existing
state without deleting it; removing a declaration exposes that state again.
Attach exactly one writable whole virtio disk, either without a GPT or
containing a GPT state partition of type
32a2f055-1a43-44b2-ab7a-483ff5f13481. Its filesystem must match the root
filesystem. Corium accepts 512- and 4096-byte logical sectors,
validates both GPT copies and requires one partition aligned to 1 MiB. It
rejects unsupported or damaged GPT storage without formatting or repairing it.
For a disk without a GPT signature in either header location, Corium creates
the state GPT and runs
/usr/sbin/mkfs.<root-filesystem> from the verified image after pivot. Include
that formatter and its libraries in the root image. Initialization completes
within the first boot. Existing non-GPT data is replaced. Once a GPT exists,
interrupted initialization is not retried by formatting;
existing filesystem failures are fatal.
The image must provide real /tmp, /var, /home, /root and /srv directories.
Corium temporarily mounts state at /tmp, binds each matching state directory
onto its image mountpoint, and detaches staging before init. Init remains
responsible for runtime mounts and tmpfiles initialization. Image contents of
these four directories are hidden except declared /var bindings.
New state directories inherit their image directory’s ownership and permissions;
existing instance permissions and contents are retained. Initialize other
required contents with tmpfiles. This does not authenticate state or hide the
disk from guest root.
Corium consumes the top-level corium namespace from /proc/bootconfig:
corium {
root = "<Carapace tip hash in hexadecimal>";
log = error;
# halt = assembled;
}
corium.root is required. All Corium settings take one scalar value.
Unknown keys within corium are fatal; other namespaces are ignored.
Linux parses the document and resolves grouping and assignment operators;
Corium reads the resulting flattened, quoted key/value records.
Two optional settings control diagnostics:
corium.log = off|error|warn|info|debug|tracesets the maximum verbosity. The default iserror; successful boots and shutdowns are silent.offsuppresses all Corium records, including fatal errors.corium.halt = started|modules|discovered|assembled|mounted|pivoted|statesyncs and powers off after the selected phase completes successfully. Without it, Corium executes/sbin/init. It does not change the log level.
Diagnostic values are case-sensitive. Empty values and arrays are fatal.
Until the log setting can be read and validated, the filter is error.
A valid log setting applies before validation of the root and shutdown
settings, regardless of entry order. Invalid log settings leave the bootstrap
error filter in effect.
At info level, each completed phase produces a record:
INFO corium: message="phase completed" phase="assembled" elapsed_us=28000 duration_us=8000
INFO corium: message="shutdown requested" phase="assembled"
The second record appears only at a requested shutdown boundary. Phase meanings:
| Phase | Work completed |
|---|---|
started |
Temporary mounts, console setup, and configuration validation |
modules |
All supplied modules loaded |
discovered |
Root chain discovered and validated |
assembled |
Carapace device activated |
mounted |
Root filesystem mounted read-only |
pivoted |
Root pivot and old-root detachment; API mounts remain until handoff |
state |
Optional persistent directories and image bindings ready; staging and temporary API mounts detached, before init |
The state milestone also occurs when persistence is not enabled. State setup
failures never report it as completed.
Both times are monotonic microseconds. elapsed_us measures time since entry
to the boot routine; duration_us measures this phase, including retries.
The preceding phase-completion record's write is excluded from the next
phase's duration, but included in total elapsed time.
debug adds module and filesystem selections and assembly details; trace
adds individual module, discovery, and mount attempts. warn is available
for noteworthy recoverable conditions; there are currently no warning records.
Normal dependency retries are trace events. Fatal errors use
message="boot failed" with a separate error field.
Records use Rust's log facade with structured key/value fields and a small
synchronous console logger. The format is LEVEL target: message="..." key=value.
Messages and string fields use JSON-style quoting and escaping; numbers and
booleans are unquoted. Each record occupies one line. Targets are corium
or its module paths (for example, corium::modules). Logging retains no extra
file descriptor and remains available after the pivot until successful exec.
Kernel and real-init output are unaffected by these settings.
Rust 1.89 or newer is required; all build settings work on stable Rust. Release builds optimize for size with LTO, stripped symbols, and abort-on-panic. Run tests as an ordinary Linux user.
cargo build --locked --release --target x86_64-unknown-linux-musl
cargo test --locked
cargo fmt --all -- --check
RUSTFLAGS='-D warnings' cargo clippy --locked --all-targets --all-features
cargo deny checkUse aarch64-unknown-linux-musl for an ARM64 guest.
The rootless x86-64 VM tests are ignored Rust integration tests. They require
read/write access to /dev/kvm, a running Linux 7.0+ kernel with its readable
/boot/vmlinuz-$(uname -r) and matching installed modules/configuration,
stable Rust with the x86-64 musl target,
QEMU, host systemd, cpio, sfdisk, mkfs.ext4, mkfs.xfs, mkfs.btrfs,
matching ext4/XFS/Btrfs kernel drivers, debugfs, e2fsck,
modprobe, veritysetup, dmsetup, losetup, mount, umount, mkdir,
truncate, sh, ldd, ldconfig, sha256sum, df, and lsblk.
Installed compressed modules also require the corresponding xz, gzip, or
zstd tool. No root, host mounts, loop devices, or host device-mapper changes
are needed. Required prerequisites fail explicitly; optional decompressor and
squashfs cases report OMIT with a reason, separately from Cargo's ignored
count. The suite requires at least 8 GiB free artifact storage per invocation.
cargo +stable test --locked --test boot -- --ignored --test-threads=1
cargo +stable test --locked --test benchmark -- --ignored --nocapture --test-threads=1The guest binary is built once per invocation using stable Rust's musl release profile, then copied into the artifact directory. Host test code stays in the ordinary test profile. Run VM tests and benchmarks sequentially on an otherwise idle host; do not run separate invocations concurrently when comparing timings.
The shared large fixture has 50 scutes on one read-only virtio GPT disk with
100 partitions. A bootable ext4 base plus 44 small 4 MiB increments and five
160 MiB increments produces roughly 1 GiB of changed-block payload. Offline
filesystem edits, a Rust snapshot-store writer, and veritysetup produce the
chain without mounting it. Systemd runs sha256sum to check a surviving
sentinel from every layer and the final overwritten value before printing its
success marker and powering off. The bulk payload is not read in full during
validation. This is a synthetic demanding workload, not a full Fedora image.
Small fixtures exercise bootconfig validation, module failure/compression, discovery timeout, corrupt metadata/data, missing intermediate scutes, failed exec, halt boundaries, log filtering, and filesystem selection. Every expected shutdown requires QMP's explicit guest-shutdown event. A deliberate panic negative control ensures QEMU exit alone is rejected.
The informational benchmark uses the same large recipe and validation service:
two warm-up boots and ten measured boots, each in a fresh VM with
corium.log = info in bootconfig.
The primary metric is state.elapsed_us, from entry to Corium's boot routine
through state setup and temporary mount cleanup. This benchmark has no state
manifest, so it measures the ordinary immutable boot path. It excludes startup
in main, the final log write, and exec; successful systemd validation
independently proves handoff.
It includes earlier logging overhead. Phase durations explain the total but
must not be summed to reconstruct it, because they exclude completion-record
writes. Fixture generation, QEMU/kernel startup, and systemd/shutdown are not
part of the metric. Host caches are warmed by construction and previous boots;
these are not cold-storage measurements. No host cache flushing is performed.
Report median/minimum/maximum and raw samples, with no timing pass/fail limit.
Artifacts remain under target/boot-tests-*: GPT/initramfs images, console and
QMP logs, case summaries, fixture sizes/roots, input hashes, command lines,
compiler/kernel/tool/host metadata, and benchmark CSV/summary. A failed run
retains its directory. Each invocation builds fresh fixtures, so tool and host
systemd changes are recorded rather than hidden by cross-run caching. Remove
obsolete target/boot-tests-* directories when their evidence is no longer
needed; they can occupy several GiB each. See CONTRIBUTING.md for the required
pre-merge correctness gate and runner setup.
Image builders install Corium as /init, enable bootconfig, and supply
corium.root with the Carapace tip hash. Fedora's module-tree layout matches
this contract. Carapace manages the assembled device's identity.
Version 0.2 removes the former library items CORIUM_ROOT_OK, CORIUM_PIVOT,
parse_root_carapace, and decode_root_carapace. The library now exposes
boot and init_logging for the PID 1 binary. External boot tests must
provide a real init and verify its handoff, rather than expect Corium to list
files and power off successfully when init is absent.
Pushes to main trigger the existing COPR webhook for
npmccallum/pichi.
COPR prepares one vendored SRPM and builds the native RPMs. Its build ID
is included in the RPM release so later builds of the same version supersede
earlier packages. Until the first release, the RPM version is 0.1.0~dev.
The packaged executable is static and can be used as an initramfs init.
Launchpad builds main snapshots into ppa:nmccallum/pichi.
See debian/README.source for the PPA publication process and setup.