mouse/cheesed

Diff

6df4f01ac29ee15f2330f78d

PLAN.md

Mode 100644100644; object 25af1ae0238341499fe7db8d

@@ -235,6 +235,10 @@
 
 ## Milestones
 
+Current status: C0 and C1 are implemented in the MOUSE image. The C1 proof
+service also exercises the C2 `supervise-daemon` and runtime-control path; C2
+remains open until its failure cases are covered as a distinct milestone.
+
 ### C0: executable skeleton
 
 Create the Rust crate, cross/static build configuration, and a QEMU boot path

README.md

Mode 100644100644; object ba267da1e4547ce4d1903764

@@ -1,9 +1,11 @@
 # cheesed
 
-`cheesed` is the PID 1 and service supervisor for MOUSE. The repository is
-currently implementing milestone C0 from [`PLAN.md`](PLAN.md): a static Linux
-init which logs to the console, reaps children, provides an emergency `tcsh`,
-and handles reboot and poweroff signals without ever returning from PID 1.
+`cheesed` is MOUSE's PID 1 and boot coordinator. Milestones C0 and C1 from
+[`PLAN.md`](PLAN.md) are implemented: the static Linux init performs early
+bootstrap, strictly translates `/etc/rc.conf` into disposable OpenRC
+runlevels, runs the boot graph, reaps children during transitions and steady
+state, provides a recovery `tcsh`, and coordinates OpenRC shutdown before
+reboot or poweroff.
 
 ## Development checks
 
@@ -22,7 +24,7 @@
 scripts/build-static.sh
 ```
 
-## C0 QEMU boot
+## Standalone QEMU boot
 
 The initramfs deliberately takes an explicit static `tcsh`; it does not copy a
 host shell with unresolved dynamic-library dependencies.

src/lib.rs

Mode 100644100644; object a3dcd4ec10d08d5975b7110f

@@ -2,6 +2,9 @@
 
 use std::fmt;
 
+#[cfg(any(target_os = "linux", test))]
+mod policy;
+
 /// The first message emitted after PID 1 bootstrap succeeds.
 pub const STARTUP_BANNER: &str = concat!("Cheesed to meet you! v", env!("CARGO_PKG_VERSION"));
 

src/linux.rs

Mode 100644100644; object d15a4d24ca98c77400ea4607

@@ -1,8 +1,10 @@
+use crate::policy::{ServiceCatalog, generate_runlevels, parse_rc_conf};
 use crate::{STARTUP_BANNER, ShutdownAction, shutdown_action};
 use std::fs::{File, OpenOptions};
 use std::io::{self, Write};
 use std::os::unix::process::CommandExt;
-use std::process::{Command, Stdio};
+use std::os::unix::process::ExitStatusExt;
+use std::process::{Command, ExitStatus, Stdio};
 use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
 use std::thread;
 use std::time::{Duration, Instant};
@@ -10,9 +12,17 @@
 const CONSOLE_PATH: &str = "/dev/console";
 const EMERGENCY_SHELL: &str = "/bin/tcsh";
 const HOSTNAME_PATH: &str = "/etc/hostname";
+const OPENRC_PATH: &str = "/sbin/openrc";
+const RC_CONF_PATH: &str = "/etc/rc.conf";
+const SERVICE_DEFINITIONS: &str = "/etc/init.d";
+const RUNLEVEL_TEMPLATES: &str = "/usr/lib/mouse/runlevels";
+const RUNTIME_RUNLEVELS: &str = "/run/openrc/runlevels";
 const POLL_INTERVAL: Duration = Duration::from_millis(50);
 const SHELL_RESTART_DELAY: Duration = Duration::from_secs(1);
-const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
+const BOOT_TRANSITION_TIMEOUT: Duration = Duration::from_secs(120);
+const SHUTDOWN_TRANSITION_TIMEOUT: Duration = Duration::from_secs(30);
+const SERVICE_STATUS_TIMEOUT: Duration = Duration::from_secs(10);
+const TERMINATION_GRACE: Duration = Duration::from_secs(5);
 
 static CHILD_EXITED: AtomicBool = AtomicBool::new(false);
 static SHUTDOWN_SIGNAL: AtomicI32 = AtomicI32::new(0);
@@ -21,7 +31,7 @@
     if signal == libc::SIGCHLD {
         CHILD_EXITED.store(true, Ordering::Relaxed);
     } else {
-        SHUTDOWN_SIGNAL.store(signal, Ordering::Relaxed);
+        let _ = SHUTDOWN_SIGNAL.compare_exchange(0, signal, Ordering::Relaxed, Ordering::Relaxed);
     }
 }
 
@@ -72,7 +82,12 @@
     }
 
     console.write_line(STARTUP_BANNER);
-    console.log("C0 bootstrap complete; cheesed is PID 1");
+    console.log("C1 bootstrap complete; cheesed is PID 1");
+    match boot_services(&mut console) {
+        Ok(Some(action)) => perform_shutdown(&mut console, None, action),
+        Ok(None) => {}
+        Err(error) => console.log(&format!("{error}; entering recovery console")),
+    }
     let mut shell_pid = match spawn_emergency_shell(&mut console) {
         Ok(pid) => Some(pid),
         Err(error) => {
@@ -171,7 +186,8 @@
 fn mount_early_filesystems() -> io::Result<()> {
     mount_filesystem(c"devtmpfs".as_ptr(), c"/dev".as_ptr(), c"devtmpfs".as_ptr())?;
     mount_filesystem(c"proc".as_ptr(), c"/proc".as_ptr(), c"proc".as_ptr())?;
-    mount_filesystem(c"sysfs".as_ptr(), c"/sys".as_ptr(), c"sysfs".as_ptr())
+    mount_filesystem(c"sysfs".as_ptr(), c"/sys".as_ptr(), c"sysfs".as_ptr())?;
+    mount_filesystem(c"tmpfs".as_ptr(), c"/run".as_ptr(), c"tmpfs".as_ptr())
 }
 
 fn mount_filesystem(
@@ -216,6 +232,239 @@
         return Err(io::Error::last_os_error());
     }
     Ok(())
+}
+
+fn boot_services(console: &mut Console) -> Result<Option<ShutdownAction>, String> {
+    let catalog = ServiceCatalog::discover(
+        std::path::Path::new(SERVICE_DEFINITIONS),
+        std::path::Path::new(RUNLEVEL_TEMPLATES),
+    )
+    .map_err(|error| format!("cannot discover services: {error}"))?;
+    let contents = std::fs::read_to_string(RC_CONF_PATH)
+        .map_err(|error| format!("cannot read {RC_CONF_PATH}: {error}"))?;
+    let policy = parse_rc_conf(&contents, &catalog)
+        .map_err(|error| format!("invalid service policy: {error}"))?;
+    let enabled = policy.enabled().collect::<Vec<_>>();
+    if enabled.is_empty() {
+        console.log("enabled optional services: (none)");
+    } else {
+        console.log(&format!(
+            "enabled optional services: {}",
+            enabled.join(", ")
+        ));
+    }
+
+    let generation = generate_runlevels(&policy, &catalog, std::path::Path::new(RUNTIME_RUNLEVELS))
+        .map_err(|error| format!("cannot generate OpenRC runlevels: {error}"))?;
+    console.log(&format!(
+        "generated OpenRC runlevels at {}",
+        generation.display()
+    ));
+
+    for runlevel in ["sysinit", "boot"] {
+        if let Some(action) = run_openrc(console, runlevel, BOOT_TRANSITION_TIMEOUT, true)? {
+            return Ok(Some(action));
+        }
+    }
+    if let Some(action) = run_openrc(console, "default", BOOT_TRANSITION_TIMEOUT, false)? {
+        return Ok(Some(action));
+    }
+    for service in catalog.required() {
+        if let Some(action) = run_service_status(console, service)
+            .map_err(|error| format!("required service {service} is not started: {error}"))?
+        {
+            return Ok(Some(action));
+        }
+    }
+    for service in policy.enabled() {
+        match run_service_status(console, service) {
+            Ok(Some(action)) => return Ok(Some(action)),
+            Ok(None) => {}
+            Err(error) => console.log(&format!("optional service {service} failed: {error}")),
+        }
+    }
+    Ok(None)
+}
+
+fn run_openrc(
+    console: &mut Console,
+    runlevel: &str,
+    timeout: Duration,
+    require_success: bool,
+) -> Result<Option<ShutdownAction>, String> {
+    console.log(&format!("starting OpenRC {runlevel} transition"));
+    let status = match command_with_console(OPENRC_PATH, &[runlevel], console, timeout)? {
+        CommandResult::Exited(status) => status,
+        CommandResult::Shutdown(action) => return Ok(Some(action)),
+    };
+    if require_success && !status.success() {
+        return Err(format!(
+            "OpenRC {runlevel} transition exited with {}",
+            describe_exit_status(status)
+        ));
+    }
+    if status.success() {
+        console.log(&format!("OpenRC {runlevel} transition complete"));
+    } else {
+        console.log(&format!(
+            "OpenRC {runlevel} transition exited with {}; checking required services",
+            describe_exit_status(status)
+        ));
+    }
+    Ok(None)
+}
+
+fn run_service_status(
+    console: &mut Console,
+    service: &str,
+) -> Result<Option<ShutdownAction>, String> {
+    let result = command_with_console(
+        "/sbin/rc-service",
+        &[service, "status"],
+        console,
+        SERVICE_STATUS_TIMEOUT,
+    )?;
+    let status = match result {
+        CommandResult::Exited(status) => status,
+        CommandResult::Shutdown(action) => return Ok(Some(action)),
+    };
+    if status.success() {
+        Ok(None)
+    } else {
+        Err(format!(
+            "status command exited with {}",
+            describe_exit_status(status)
+        ))
+    }
+}
+
+fn command_with_console(
+    program: &str,
+    arguments: &[&str],
+    console: &mut Console,
+    timeout: Duration,
+) -> Result<CommandResult, String> {
+    let stdin = console
+        .clone_file()
+        .map_err(|error| format!("cannot duplicate console input: {error}"))?;
+    let stdout = console
+        .clone_file()
+        .map_err(|error| format!("cannot duplicate console output: {error}"))?;
+    let stderr = console
+        .clone_file()
+        .map_err(|error| format!("cannot duplicate console error output: {error}"))?;
+    let mut command = Command::new(program);
+    command
+        .args(arguments)
+        .stdin(Stdio::from(stdin))
+        .stdout(Stdio::from(stdout))
+        .stderr(Stdio::from(stderr));
+    // SAFETY: this closure runs between fork and exec, and setpgid is
+    // async-signal-safe. A separate group lets PID 1 terminate a whole
+    // transition, including service-script descendants.
+    unsafe {
+        command.pre_exec(|| {
+            if libc::setpgid(0, 0) == -1 {
+                return Err(io::Error::last_os_error());
+            }
+            Ok(())
+        });
+    }
+    let child = command
+        .spawn()
+        .map_err(|error| format!("cannot run {program}: {error}"))?;
+    let pid = i32::try_from(child.id()).map_err(|_| "child PID does not fit pid_t".to_owned())?;
+    let deadline = Instant::now() + timeout;
+    loop {
+        if let Some(status) = reap_during_command(console, pid)? {
+            return Ok(CommandResult::Exited(status));
+        }
+        if let Some(action) = take_shutdown_action() {
+            terminate_process_group(pid, program, console);
+            return Ok(CommandResult::Shutdown(action));
+        }
+        if Instant::now() >= deadline {
+            terminate_process_group(pid, program, console);
+            return Err(format!(
+                "{program} {} timed out after {} seconds",
+                arguments.join(" "),
+                timeout.as_secs()
+            ));
+        }
+        thread::sleep(POLL_INTERVAL);
+    }
+}
+
+enum CommandResult {
+    Exited(ExitStatus),
+    Shutdown(ShutdownAction),
+}
+
+fn take_shutdown_action() -> Option<ShutdownAction> {
+    shutdown_action(SHUTDOWN_SIGNAL.swap(0, Ordering::Relaxed))
+}
+
+fn reap_during_command(
+    console: &mut Console,
+    command_pid: libc::pid_t,
+) -> Result<Option<ExitStatus>, String> {
+    CHILD_EXITED.store(false, Ordering::Relaxed);
+    loop {
+        let mut status = 0;
+        // SAFETY: status is writable and WNOHANG makes the call nonblocking.
+        let pid = unsafe { libc::waitpid(-1, &raw mut status, libc::WNOHANG) };
+        match pid {
+            value if value == command_pid => {
+                return Ok(Some(ExitStatus::from_raw(status)));
+            }
+            value if value > 0 => {
+                console.log(&format!(
+                    "reaped orphan PID {value} ({})",
+                    describe_wait_status(status)
+                ));
+            }
+            0 => return Ok(None),
+            _ => {
+                let error = io::Error::last_os_error();
+                return if error.raw_os_error() == Some(libc::ECHILD) {
+                    Err("transition child disappeared without an exit status".to_owned())
+                } else {
+                    Err(format!("waitpid failed during transition: {error}"))
+                };
+            }
+        }
+    }
+}
+
+fn terminate_process_group(pid: libc::pid_t, program: &str, console: &mut Console) {
+    // SAFETY: negative pid addresses the process group created before exec.
+    let term_result = unsafe { libc::kill(-pid, libc::SIGTERM) };
+    if term_result == -1 && io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH) {
+        console.log(&format!("cannot terminate {program} process group {pid}"));
+    }
+    let deadline = Instant::now() + TERMINATION_GRACE;
+    while Instant::now() < deadline {
+        match reap_during_command(console, pid) {
+            Ok(Some(_)) | Err(_) => return,
+            Ok(None) => thread::sleep(POLL_INTERVAL),
+        }
+    }
+    // SAFETY: negative pid addresses the same known process group.
+    unsafe {
+        libc::kill(-pid, libc::SIGKILL);
+    }
+    loop {
+        match reap_during_command(console, pid) {
+            Ok(Some(_)) | Err(_) => return,
+            Ok(None) => thread::sleep(POLL_INTERVAL),
+        }
+    }
+}
+
+fn describe_exit_status(status: std::process::ExitStatus) -> String {
+    status
+        .code()
+        .map_or_else(|| "a signal".to_owned(), |code| format!("status {code}"))
 }
 
 fn spawn_emergency_shell(console: &mut Console) -> Result<libc::pid_t, String> {
@@ -313,6 +562,9 @@
 ) -> ! {
     console.log(&format!("received {action} request"));
     stop_shell(console, shell_pid);
+    if let Err(error) = run_openrc(console, "shutdown", SHUTDOWN_TRANSITION_TIMEOUT, true) {
+        console.log(&format!("OpenRC shutdown failed: {error}"));
+    }
 
     // SAFETY: sync has no preconditions and is required before the terminal reboot call.
     unsafe {
@@ -349,7 +601,7 @@
         return;
     }
 
-    let deadline = Instant::now() + SHUTDOWN_TIMEOUT;
+    let deadline = Instant::now() + TERMINATION_GRACE;
     while Instant::now() < deadline {
         if wait_for_pid(pid) {
             console.log(&format!("emergency shell PID {pid} stopped"));

src/policy.rs

Mode 100644; object 0a875df53ee3

@@ -1,0 +1,417 @@
+use std::collections::{BTreeMap, BTreeSet};
+use std::fmt;
+use std::fs;
+use std::io;
+use std::os::unix::fs::symlink;
+use std::path::{Path, PathBuf};
+
+const RUNLEVELS: [&str; 4] = ["sysinit", "boot", "default", "shutdown"];
+
+#[derive(Debug, Eq, PartialEq)]
+pub(crate) struct RcPolicy {
+    enabled: BTreeSet<String>,
+}
+
+impl RcPolicy {
+    pub(crate) fn enabled(&self) -> impl Iterator<Item = &str> {
+        self.enabled.iter().map(String::as_str)
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct ServiceCatalog {
+    definitions: PathBuf,
+    installed: BTreeSet<String>,
+    required_by_runlevel: BTreeMap<String, BTreeSet<String>>,
+    required: BTreeSet<String>,
+}
+
+impl ServiceCatalog {
+    pub(crate) fn discover(definitions: &Path, templates: &Path) -> Result<Self, PolicyError> {
+        let installed = read_named_entries(definitions, "service definition")?;
+        let mut required_by_runlevel = BTreeMap::new();
+        let mut required = BTreeSet::new();
+
+        for runlevel in RUNLEVELS {
+            let members = read_named_entries(
+                &templates.join(runlevel),
+                &format!("{runlevel} runlevel member"),
+            )?;
+            for service in &members {
+                if !installed.contains(service) {
+                    return Err(PolicyError::new(format!(
+                        "required {runlevel} service {service} has no definition"
+                    )));
+                }
+                required.insert(service.clone());
+            }
+            required_by_runlevel.insert(runlevel.to_owned(), members);
+        }
+
+        Ok(Self {
+            definitions: definitions.to_path_buf(),
+            installed,
+            required_by_runlevel,
+            required,
+        })
+    }
+
+    #[cfg(target_os = "linux")]
+    pub(crate) fn required(&self) -> impl Iterator<Item = &str> {
+        self.required.iter().map(String::as_str)
+    }
+}
+
+#[derive(Debug, Eq, PartialEq)]
+pub(crate) struct PolicyError {
+    message: String,
+}
+
+impl PolicyError {
+    fn new(message: impl Into<String>) -> Self {
+        Self {
+            message: message.into(),
+        }
+    }
+}
+
+impl fmt::Display for PolicyError {
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        formatter.write_str(&self.message)
+    }
+}
+
+impl From<io::Error> for PolicyError {
+    fn from(error: io::Error) -> Self {
+        Self::new(error.to_string())
+    }
+}
+
+pub(crate) fn parse_rc_conf(
+    contents: &str,
+    catalog: &ServiceCatalog,
+) -> Result<RcPolicy, PolicyError> {
+    let mut assignments = BTreeMap::new();
+
+    for (index, line) in contents.lines().enumerate() {
+        let line_number = index + 1;
+        if line.trim().is_empty() || line.trim_start().starts_with('#') {
+            continue;
+        }
+
+        let Some((key, value)) = line.split_once('=') else {
+            return Err(line_error(
+                line_number,
+                "expected enable_name=YES or enable_name=NO",
+            ));
+        };
+        if key.contains(char::is_whitespace)
+            || value.contains(char::is_whitespace)
+            || line.matches('=').count() != 1
+        {
+            return Err(line_error(
+                line_number,
+                "whitespace, quoting, comments, and extra assignments are not allowed",
+            ));
+        }
+
+        let Some(name) = key.strip_prefix("enable_") else {
+            return Err(line_error(line_number, "unknown rc.conf key"));
+        };
+        if !valid_service_name(name) {
+            return Err(line_error(line_number, "invalid service name"));
+        }
+        let enabled = match value {
+            "YES" => true,
+            "NO" => false,
+            _ => {
+                return Err(line_error(
+                    line_number,
+                    "enablement value must be exactly YES or NO",
+                ));
+            }
+        };
+        if !catalog.installed.contains(name) {
+            return Err(line_error(
+                line_number,
+                format!("service {name} has no installed definition"),
+            ));
+        }
+        if catalog.required.contains(name) {
+            return Err(line_error(
+                line_number,
+                format!("required service {name} cannot be enabled or disabled"),
+            ));
+        }
+        if assignments.insert(name.to_owned(), enabled).is_some() {
+            return Err(line_error(
+                line_number,
+                format!("duplicate assignment for service {name}"),
+            ));
+        }
+    }
+
+    Ok(RcPolicy {
+        enabled: assignments
+            .into_iter()
+            .filter_map(|(name, enabled)| enabled.then_some(name))
+            .collect(),
+    })
+}
+
+pub(crate) fn generate_runlevels(
+    policy: &RcPolicy,
+    catalog: &ServiceCatalog,
+    runtime_root: &Path,
+) -> Result<PathBuf, PolicyError> {
+    let generations = runtime_root.join("generations");
+    fs::create_dir_all(&generations)?;
+    let generation_number = next_generation(&generations)?;
+    let pending = generations.join(format!("{generation_number}.new"));
+    let generation = generations.join(generation_number.to_string());
+    fs::create_dir(&pending)?;
+
+    let result = (|| {
+        for runlevel in RUNLEVELS {
+            let runlevel_dir = pending.join(runlevel);
+            fs::create_dir(&runlevel_dir)?;
+            let required = catalog
+                .required_by_runlevel
+                .get(runlevel)
+                .expect("all fixed runlevels were discovered");
+            for service in required {
+                link_service(&catalog.definitions, &runlevel_dir, service)?;
+            }
+        }
+
+        for service in policy.enabled() {
+            link_service(&catalog.definitions, &pending.join("default"), service)?;
+        }
+
+        fs::rename(&pending, &generation)?;
+        let next_link = runtime_root.join("current.new");
+        remove_file_if_present(&next_link)?;
+        symlink(format!("generations/{generation_number}"), &next_link)?;
+        fs::rename(next_link, runtime_root.join("current"))?;
+        Ok(())
+    })();
+
+    if result.is_err() {
+        let _ = fs::remove_dir_all(&pending);
+    }
+    result.map(|()| generation)
+}
+
+fn read_named_entries(directory: &Path, kind: &str) -> Result<BTreeSet<String>, PolicyError> {
+    let mut names = BTreeSet::new();
+    let entries = fs::read_dir(directory).map_err(|error| {
+        PolicyError::new(format!("cannot read {}: {error}", directory.display()))
+    })?;
+    for entry in entries {
+        let entry = entry?;
+        let name = entry
+            .file_name()
+            .into_string()
+            .map_err(|_| PolicyError::new(format!("{kind} name is not UTF-8")))?;
+        if kind == "service definition" && name == "functions.sh" {
+            continue;
+        }
+        if !valid_service_name(&name) {
+            return Err(PolicyError::new(format!(
+                "{kind} {name:?} is not a valid MOUSE service name"
+            )));
+        }
+        if !names.insert(name.clone()) {
+            return Err(PolicyError::new(format!("duplicate {kind} {name}")));
+        }
+    }
+    Ok(names)
+}
+
+fn valid_service_name(name: &str) -> bool {
+    let mut bytes = name.bytes();
+    matches!(bytes.next(), Some(b'a'..=b'z'))
+        && bytes.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
+}
+
+fn line_error(line_number: usize, message: impl fmt::Display) -> PolicyError {
+    PolicyError::new(format!("/etc/rc.conf:{line_number}: {message}"))
+}
+
+fn next_generation(generations: &Path) -> Result<u64, PolicyError> {
+    let mut highest = 0;
+    for entry in fs::read_dir(generations)? {
+        let entry = entry?;
+        let Some(name) = entry.file_name().to_str().map(str::to_owned) else {
+            continue;
+        };
+        if let Ok(number) = name.parse::<u64>() {
+            highest = highest.max(number);
+        }
+    }
+    highest
+        .checked_add(1)
+        .ok_or_else(|| PolicyError::new("runlevel generation counter overflowed"))
+}
+
+fn link_service(definitions: &Path, runlevel: &Path, service: &str) -> Result<(), PolicyError> {
+    let definition = definitions.join(service);
+    fs::metadata(&definition).map_err(|error| {
+        PolicyError::new(format!(
+            "cannot resolve service definition {}: {error}",
+            definition.display()
+        ))
+    })?;
+    symlink(&definition, runlevel.join(service))?;
+    Ok(())
+}
+
+fn remove_file_if_present(path: &Path) -> Result<(), PolicyError> {
+    match fs::remove_file(path) {
+        Ok(()) => Ok(()),
+        Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
+        Err(error) => Err(error.into()),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{RcPolicy, ServiceCatalog, generate_runlevels, parse_rc_conf};
+    use std::collections::BTreeSet;
+    use std::fs;
+    use std::os::unix::fs::symlink;
+    use std::path::{Path, PathBuf};
+    use std::sync::atomic::{AtomicU64, Ordering};
+
+    static NEXT_TEMP: AtomicU64 = AtomicU64::new(0);
+
+    struct TestLayout {
+        root: PathBuf,
+        definitions: PathBuf,
+        templates: PathBuf,
+        runtime: PathBuf,
+    }
+
+    impl TestLayout {
+        fn new() -> Self {
+            let serial = NEXT_TEMP.fetch_add(1, Ordering::Relaxed);
+            let root = std::env::temp_dir()
+                .join(format!("cheesed-policy-{}-{serial}", std::process::id()));
+            let definitions = root.join("init.d");
+            let templates = root.join("templates");
+            let runtime = root.join("runlevels");
+            fs::create_dir_all(&definitions).unwrap();
+            for runlevel in ["sysinit", "boot", "default", "shutdown"] {
+                fs::create_dir_all(templates.join(runlevel)).unwrap();
+            }
+            fs::write(definitions.join("required"), "#!/bin/sh\n").unwrap();
+            fs::write(definitions.join("optional"), "#!/bin/sh\n").unwrap();
+            fs::write(definitions.join("functions.sh"), "# OpenRC compatibility\n").unwrap();
+            symlink(
+                definitions.join("required"),
+                templates.join("default/required"),
+            )
+            .unwrap();
+            Self {
+                root,
+                definitions,
+                templates,
+                runtime,
+            }
+        }
+
+        fn catalog(&self) -> ServiceCatalog {
+            ServiceCatalog::discover(&self.definitions, &self.templates).unwrap()
+        }
+    }
+
+    impl Drop for TestLayout {
+        fn drop(&mut self) {
+            fs::remove_dir_all(&self.root).unwrap();
+        }
+    }
+
+    fn enabled(policy: &RcPolicy) -> BTreeSet<&str> {
+        policy.enabled().collect()
+    }
+
+    #[test]
+    fn accepts_comments_empty_lines_and_strict_assignments() {
+        let layout = TestLayout::new();
+        let policy =
+            parse_rc_conf("\n  # comment\nenable_optional=YES\n", &layout.catalog()).unwrap();
+        assert_eq!(enabled(&policy), BTreeSet::from(["optional"]));
+    }
+
+    #[test]
+    fn no_and_missing_assignments_leave_optional_services_disabled() {
+        let layout = TestLayout::new();
+        let catalog = layout.catalog();
+        assert!(enabled(&parse_rc_conf("", &catalog).unwrap()).is_empty());
+        assert!(enabled(&parse_rc_conf("enable_optional=NO\n", &catalog).unwrap()).is_empty());
+    }
+
+    #[test]
+    fn rejects_shell_and_ambiguous_input() {
+        let layout = TestLayout::new();
+        let catalog = layout.catalog();
+        for invalid in [
+            "enable_optional =YES",
+            "enable_optional=\"YES\"",
+            "enable_optional=YES # comment",
+            "enable_optional=$(hostname)",
+            "enable_optional=YES=NO",
+            "rc_parallel=YES",
+            "enable_Optional=YES",
+        ] {
+            assert!(
+                parse_rc_conf(invalid, &catalog).is_err(),
+                "accepted {invalid:?}"
+            );
+        }
+    }
+
+    #[test]
+    fn rejects_duplicates_unknown_services_and_required_policy() {
+        let layout = TestLayout::new();
+        let catalog = layout.catalog();
+        for invalid in [
+            "enable_optional=YES\nenable_optional=NO\n",
+            "enable_missing=NO\n",
+            "enable_required=YES\n",
+        ] {
+            assert!(
+                parse_rc_conf(invalid, &catalog).is_err(),
+                "accepted {invalid:?}"
+            );
+        }
+    }
+
+    #[test]
+    fn generates_complete_atomic_runtime_membership() {
+        let layout = TestLayout::new();
+        let catalog = layout.catalog();
+        let policy = parse_rc_conf("enable_optional=YES\n", &catalog).unwrap();
+        let first = generate_runlevels(&policy, &catalog, &layout.runtime).unwrap();
+
+        assert_eq!(first, layout.runtime.join("generations/1"));
+        assert!(first.join("default/required").is_symlink());
+        assert!(first.join("default/optional").is_symlink());
+        for runlevel in ["sysinit", "boot", "default", "shutdown"] {
+            assert!(first.join(runlevel).is_dir());
+        }
+        assert_eq!(
+            fs::read_link(layout.runtime.join("current")).unwrap(),
+            Path::new("generations/1")
+        );
+
+        let disabled = parse_rc_conf("enable_optional=NO\n", &catalog).unwrap();
+        let second = generate_runlevels(&disabled, &catalog, &layout.runtime).unwrap();
+        assert_eq!(second, layout.runtime.join("generations/2"));
+        assert!(!second.join("default/optional").exists());
+        assert_eq!(
+            fs::read_link(layout.runtime.join("current")).unwrap(),
+            Path::new("generations/2")
+        );
+    }
+}