//! Stage 21: boot contract v2 -- which program becomes pid 1. //! //! If `fs::mount_disks` found a GPT "Linux root (x86-64)" partition with //! ext2 and mounted it at "/" ([`fs::root_on_disk`]), the kernel starts the //! first of [`INIT_PATHS`] that runs, the same order Linux tries, as pid 1: //! //! | program | argv | //! |--------------|----------------------------------------| //! | /sbin/init | `init` (+ `selftest` when headless) | //! | /bin/init | `init` (+ `selftest` when headless) | //! | /bin/sh | `sh` (+ `/scripts/selftest.sh` when headless) | //! //! The environment is [`V2_ENV`] (+ `RUSTBELT_HEADLESS=1` when headless). //! There's no boot loader command line (bootloader 0.9 has none), so the //! knobs are build time, like the `headless` feature itself: //! * `cargo build --features headless`: the headless argv/env above; //! * `RUSTBELT_INIT_ARGS="a b"`: extra arguments after those; //! * `RUSTBELT_INIT_ENV="K=V K2=V2"`: extra environment variables (one //! with a name already there replaces it, e.g. `PATH=...`). //! (Both are read with `option_env!`, so changing them rebuilds the kernel.) //! //! The kernel does not restart pid 1. When it exits, its status ends the //! QEMU run through isa-debug-exit: exit(n) writes 0x10 + n, so QEMU exits //! with 33 + 2n (33 = exit 0 = the tests' "pass", 35 = exit 1, ...). A //! status above 111 (it wouldn't fit in QEMU's 8-bit exit code) or death //! by a signal writes 0x11 (35). //! //! Without a root partition nothing here is used: main.rs runs the old //! shell supervisor (boot contract v1) exactly as before. use crate::errno::Errno; use crate::fs; use crate::process::{self, ExitStatus, Process, SpawnArgs}; use alloc::{string::String, sync::Arc, vec::Vec}; /// Tried in this order; the first that starts becomes pid 1. pub const INIT_PATHS: [&str; 3] = ["/sbin/init", "/bin/init", "/bin/sh"]; /// pid 1's environment under contract v2. pub const V2_ENV: [&str; 3] = ["PATH=/sbin:/bin:/usr/bin:/mnt/disk0/bin", "HOME=/", "TERM=linux"]; /// argv and environment for `path` (one of [`INIT_PATHS`]). pub fn init_args(path: &str, headless: bool) -> SpawnArgs { let sh = path.ends_with("/sh"); let mut args = SpawnArgs::kernel(if sh { "sh" } else { "init" }); if headless { args.args.push(String::from(if sh { "/scripts/selftest.sh" } else { "selftest" })); } args.args.extend(option_env!("RUSTBELT_INIT_ARGS").unwrap_or("").split_whitespace().map(String::from)); args.env = V2_ENV.iter().map(|s| String::from(*s)).collect(); if headless { args.env.push(String::from("RUSTBELT_HEADLESS=1")); } for var in option_env!("RUSTBELT_INIT_ENV").unwrap_or("").split_whitespace() { let key = var.split('=').next().unwrap_or(var); args.env.retain(|e| e.split('=').next() != Some(key)); args.env.push(String::from(var)); } args } /// Start pid 1: the first of [`INIT_PATHS`] that starts. Returns its path /// and process, or the last error if none did. `log` gets a line for every /// one that was tried and failed. pub fn start_init(headless: bool, log: &mut Vec) -> Result<(&'static str, Arc), Errno> { let mut last = Errno::ENOENT; for path in INIT_PATHS { match process::spawn_path(path, init_args(path, headless)) { Ok(p) => return Ok((path, p)), Err(e) => { if e != Errno::ENOENT { log.push(alloc::format!("{}: can't start it: {:?}", path, e)); } last = e; } } } Err(last) } /// What to write to isa-debug-exit for pid 1's exit status (see above). pub fn qemu_exit_value(status: ExitStatus) -> u32 { match status { ExitStatus::Exited(n) if (0..=111).contains(&n) => 0x10 + n as u32, _ => 0x11, } } /// End the QEMU run with pid 1's exit status. pub fn exit_qemu_with(status: ExitStatus) { use x86_64::instructions::port::Port; unsafe { Port::new(0xf4).write(qemu_exit_value(status)) }; } /// Run contract v2's pid 1 and wait for it. Returns its status (Err: no /// init could be started). pub fn run_init(headless: bool, log: &mut Vec) -> Result<(&'static str, ExitStatus), Errno> { let (path, p) = start_init(headless, log)?; let status = process::wait(&p); Ok((path, status)) } /// Is contract v2 in use (a root partition was mounted at "/")? pub fn v2() -> bool { fs::root_on_disk() }