2.3 Phase 1

Phase 1 is compiled into peinit. It does not change at runtime and has no registry dependency, because its whole purpose is to reach the point where a registry exists. It does the minimum needed to make Phase 2 possible, and most of its failures are fatal to the boot.

2.3.1 Step 1: Confirm the root is writable #

The initramfs delivers the root mounted read-write. peinit does not remount it — mount flags belong to the initramfs, and a redundant remount of an already-writable or overlay root can fail for reasons that have nothing to do with the root being usable.

Instead peinit probes. It creates /.peinit/ if it is absent, writes a uniquely named file there — the name is derived from peinit's own PID and a namespace identifier, so two probes cannot collide — writes to it, and removes it. If any part of that fails, the root is not usable for Phase 2 and peinit enters recovery mode.

2.3.2 Step 2: Mount what is missing #

/proc, /sys and /dev are already mounted. peinit does not blindly mount them again: a redundant mount stacks a second filesystem over the populated one, and on some kernel and flag combinations returns EBUSY instead.

peinit reads /proc/self/mountinfo to find out what is already there, and mounts only what is not:

Mount pointFilesystemFlagsProvided by
/procprocnosuid, nodev, noexecinitramfs
/syssysfsnosuid, nodev, noexecinitramfs
/devdevtmpfsnosuidinitramfs
/dev/ptsdevptsnosuid, noexecpeinit
/dev/shmtmpfsnosuid, nodevpeinit
/runtmpfsnosuid, nodevpeinit
/sys/fs/cgroupcgroup2nosuid, nodev, noexecpeinit

Each mount(2) passes the filesystem name as both the source and the filesystem type, passes only the listed flags, and passes null mount data. Mount points that do not exist are created first.

There is a bootstrap wrinkle in reading mountinfo at all: the file lives in /proc, which is one of the things being checked for. If the read fails with ENOENT or ENOTDIR, peinit mounts /proc from the table and retries. Any other failure to read or parse mountinfo sends peinit to recovery.

For the three initramfs-provided rows, an already-mounted filesystem is success, and so is an EBUSY from an attempted mount. For the four peinit owns, a mount failure sends peinit to recovery.

2.3.2.1 Seeding descriptors on the new filesystems #

Three of the four filesystems peinit mounts are fresh and empty: /dev/shm, /run and /sys/fs/cgroup. Under KACS an inode with no Security Descriptor is denied to every caller, and there is nothing on a newly mounted tmpfs for a new inode to inherit from — so peinit stamps the mount root with a descriptor that grants SYSTEM and Administrators full control and is marked inheritable by both containers and objects:

O:SY G:SY D:(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)

Everything created underneath — the control socket, the notify socket, per-service runtime directories, the cgroup hierarchy — inherits from it. This is why peinit never sets mode bits on the sockets it creates; under KACS they would mean nothing, and the descriptor is the thing that does the work. It is the same descriptor the initramfs seeds onto the root filesystem, and the two are kept identical on purpose: this one inheritable ACL is, in practice, the access policy of everything under these mounts, so an ACE missing here is missing from every per-service directory under /run/services.

Failure to apply the descriptor sends peinit to recovery. Without it every file peinit later creates on that filesystem would be unreachable to everything, including peinit.

/proc, /sys and /dev are not stamped: they arrive from the initramfs already populated.

2.3.2.2 Device node policy #

/dev arrives with that same inheritable descriptor on its root and on every node, which is the right default — whatever the root grants, a disk hot-plugged later inherits, so the root must be acceptable on a raw block device — but it leaves /dev/null unusable by anyone who is not an administrator. A single inherited descriptor cannot say "/dev/null for everyone, the disks for administrators", so peinit enumerates the exceptions. Once the mounts are up it stamps each of these nodes with a descriptor of its own:

NodeDACL
/dev/null, /dev/zero, /dev/full, /dev/random, /dev/urandom, /dev/tty, /dev/ptmxD:(A;;GA;;;SY)(A;;GA;;;BA)(A;;FRFW;;;WD)

Everyone may open the node for reading and writing and may stat it; nobody but SYSTEM and Administrators may change its descriptor. Only the DACL is replaced — owner and group stay as the seed left them — and the ACEs carry no inheritance flags, because a device node has no children. /dev/console is deliberately not on the list: it is the SYSTEM console.

The step is advisory. A node that cannot be stamped is reported as a warning and stays on the inherited default, usable by administrators and denied to everyone else; a node that does not exist is noted and skipped. Neither sends peinit to recovery.

2.3.3 Step 3: Restore the persisted random seed #

peinit restores the seed at /var/state/peinit/random-seed once /dev is available and before registryd starts. The seed is a machine-local entropy cache for the kernel CSPRNG. It is not configuration, and shipping one in a packaged image, live ISO or VM template would hand every instance of that image the same starting entropy.

If the file is absent, that is an ordinary first boot or a stateless live boot, and peinit continues silently. When a seed is present peinit mixes it into the kernel pool, preferring the interface that credits entropy for a locally persisted seed; if that fails it mixes the bytes without crediting and records the failure. A seed file that is empty, or larger than 4096 bytes, is treated as an error.

Nothing in this step can send peinit to recovery. A system with no entropy cache still boots; it just starts with less entropy, which is a problem for the image builder to solve with a hardware or virtio RNG rather than with a seed baked into the image.

The initramfs may perform the same restore earlier, once the persistent root is mounted. peinit's restore stays as the fallback for initramfs images that do not participate and for boots that have no initramfs.

2.3.4 Step 4: Ensure the local machine ID #

/lcl/etc/machine-id holds a stable local install identifier used for software compatibility, log correlation and instance identity. It is not a security principal: not a credential, not a SID, not an account, and not an input to any authorisation decision.

The format is 128 bits as exactly 32 lowercase hexadecimal characters followed by one newline. A valid existing file is left alone. A file that is absent, empty, all zeroes, the wrong length, not hexadecimal, or missing its trailing newline is replaced: peinit draws 128 bits from the kernel CSPRNG and writes a valid file atomically, through a temporary file and a rename, with the result flushed.

Any failure of this step — an unreadable file, a CSPRNG failure, or an unwritable path — sends peinit to recovery. The write does not create parent directories, so an image that ships without /lcl/etc/ present fails here rather than at first use.

Images and templates are expected to ship with no machine ID, or with an empty file as a reset marker. Clone tooling that wants a new identity removes or truncates the file and lets the next boot generate one. Stateless live boots without a persistent overlay get an ephemeral ID for that boot.

2.3.5 Step 5: Set the clock from the hardware RTC #

peinit reads the hardware clock and calls clock_settime() before registryd starts, so that timestamps on registry operations, log entries and the boot attempt counter mean something.

It opens /dev/rtc, falling back to /dev/rtc0 if that device is absent, and reads it with RTC_RD_TIME. The returned struct rtc_time is interpreted as UTC and converted to CLOCK_REALTIME seconds with zero nanoseconds.

Every failure in this step sends peinit to recovery: no openable RTC device, a failed read, a value that is invalid or before the Unix epoch, a failed clock_settime, and — deliberately — a failure to close the descriptor after a successful read. Leaking a descriptor in PID 1 during bootstrap is a symptom of something being badly wrong, not a detail to swallow.

2.3.6 Step 6: Start registryd #

peinit holds a compiled-in definition for registryd — the only compiled-in service definition there is:

FieldValue
ImagePath/sbin/registryd
ArgumentsMachine=/var/state/loregd/Machine.hive, Users=/var/state/loregd/Users.hive
IdentitySYSTEM
ReadinessNotify
ErrorControlCritical

The hive paths are peinit's choice, not registryd's: where the machine registry lives is a boot-policy decision, and it is made here because this is the one service start that cannot consult configuration.

peinit mints a SYSTEM token including registryd's per-service SID, creates the cgroup tree, forks with the token installed, and execs /sbin/registryd through the runtime StrataFS view. Two separate timeouts bound the start, both 30 seconds: one on process setup, driven synchronously because there is no event loop yet, and one on readiness.

registryd's READY=1 means "accepting and serving registry requests", not "the process is alive" — it does not signal until its storage backend is open, its schema is validated and it can answer a read.

2.3.6.1 The schema-version guard #

After readiness, peinit ensures the base registry structure exists and then probes it. Ensuring comes first: peinit creates Machine\System, Machine\System\Services and Machine\System\Init if they are absent and stamps Machine\System\Services\SchemaVersion with the current schema version, 1. Only then does it read the value back.

The read is what verifies registryd is genuinely serving. A value that is present but not a REG_DWORD, or that is a REG_DWORD of the wrong length, fails the probe. A key or value that is absent reads as zero and passes — the structure was just created, so absence at this point means the write did not take effect, and the failure that matters is the provisioning failure, which is reported directly.

The consequence is that the guard is self-healing. An unprovisioned first boot, or a registry cleared by the recovery tools, comes up with an empty Machine\System\Services\ and boots into a Phase 2 with no services rather than into recovery.

2.3.6.2 Keeping registryd #

When registryd passes readiness and the probe, peinit retains the activation as an ordinary runtime service instance: its state, its pidfd-tracked main process, its cgroup generation and paths, its output pipe ownership, its notify generation, its job identity, and any cleanup evidence. Ownership is not dropped at the Phase 2 boundary.

During Phase 2 the registry's own definition of registryd, if there is one, is merged onto the retained activation. peinit does not create a second inactive record and does not restart registryd because a definition has appeared. If the registry definition is absent or invalid, the ordinary graph validation rules apply.

If registryd fails to start, its readiness times out, or the probe fails, peinit enters recovery. There is no Phase 2 without a registry.

2.3.7 Step 7: Autorun scripts #

Between registryd starting and path provisioning, peinit runs every non-directory entry in /lcl/policy/autorun.d, in sorted order, by absolute path, with the working directory / and PATH=/sbin:/bin. Each runs under peinit's own SYSTEM token.

The step is fail-open at every point: a missing directory, an unreadable directory, a spawn failure and a non-zero exit are all console warnings and none of them stops the boot. Its console output bypasses the quiet policy (§2.6), because a script that ran this early and went wrong needs to be visible.

2.3.8 Step 8: Provision boot-time paths #

Covered in §2.4.

2.3.9 Step 9: Infrastructure setup #

Three things, before Phase 2 begins:

  1. The control socket at /run/services/peinit/control.sock, which serves every runtime command for the lifetime of the system.
  2. The JFS device: peinit opens /dev/jfs and adds the descriptor to its event loop, enabling ad-hoc job submission once Phase 2 runs.
  3. Loopback: peinit brings up lo over netlink, because services that bind 127.0.0.1 need it.

Control socket creation failing sends peinit to recovery — without it there is no way to administer the system. The other two are warnings: a JFS open failure, a JFS event-loop registration failure and a loopback bring-up failure all let Phase 2 proceed.

2.3.10 Failure summary #

FailureResponse
Root writability probe failsRecovery
A mount point cannot be createdRecovery
A peinit-owned filesystem fails to mountRecovery
A mounted filesystem cannot be stamped with its descriptorRecovery
/proc/self/mountinfo unreadable or unparseableRecovery
/proc, /sys or /dev already mounted, or EBUSYTolerated as success
A device node in the policy list cannot be stampedWarning; node keeps the inherited default
A device node in the policy list does not existNoted; boot continues
Random seed absent, oversized, empty, or unrestorableWarning; boot continues
Machine ID read, generation, or write failsRecovery
Machine ID absent, empty, or malformedRegenerated; boot continues
Any RTC or clock failureRecovery
registryd fails to start, or setup times outRecovery
registryd readiness times outRecovery
Base registry provisioning failsRecovery
Schema-version probe returns a wrong type or lengthRecovery
An autorun script is missing, unspawnable, or exits non-zeroWarning; boot continues
A provisioning entry is malformedWarning; entry skipped
An optional provisioned path failsWarning; boot continues
A required provisioned path failsRecovery
Control socket creation failsRecovery
JFS open or registration failsWarning; boot continues
Loopback bring-up failsWarning; boot continues

Edit this page