You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
aya/init/src/main.rs

166 lines
5.1 KiB
Rust

//! init is the first process started by the kernel.
//!
//! This implementation creates the minimal mounts required to run BPF programs, runs all binaries
//! in /bin, prints a final message ("init: success|failure"), and powers off the machine.
use anyhow::Context as _;
#[derive(Debug)]
struct Errors(Vec<anyhow::Error>);
impl std::fmt::Display for Errors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self(errors) = self;
for (i, error) in errors.iter().enumerate() {
if i != 0 {
writeln!(f)?;
}
write!(f, "{:?}", error)?;
}
Ok(())
}
}
impl std::error::Error for Errors {}
fn run() -> anyhow::Result<()> {
const RXRXRX: nix::sys::stat::Mode = nix::sys::stat::Mode::empty()
.union(nix::sys::stat::Mode::S_IRUSR)
.union(nix::sys::stat::Mode::S_IXUSR)
.union(nix::sys::stat::Mode::S_IRGRP)
.union(nix::sys::stat::Mode::S_IXGRP)
.union(nix::sys::stat::Mode::S_IROTH)
.union(nix::sys::stat::Mode::S_IXOTH);
struct Mount {
source: &'static str,
target: &'static str,
fstype: &'static str,
flags: nix::mount::MsFlags,
data: Option<&'static str>,
target_mode: Option<nix::sys::stat::Mode>,
}
for Mount {
source,
target,
fstype,
flags,
data,
target_mode,
} in [
Mount {
source: "proc",
target: "/proc",
fstype: "proc",
flags: nix::mount::MsFlags::empty(),
data: None,
target_mode: Some(RXRXRX),
},
Mount {
source: "sysfs",
target: "/sys",
fstype: "sysfs",
flags: nix::mount::MsFlags::empty(),
data: None,
target_mode: Some(RXRXRX),
},
Mount {
source: "debugfs",
target: "/sys/kernel/debug",
fstype: "debugfs",
flags: nix::mount::MsFlags::empty(),
data: None,
target_mode: None,
},
Mount {
source: "bpffs",
target: "/sys/fs/bpf",
fstype: "bpf",
flags: nix::mount::MsFlags::empty(),
data: None,
target_mode: None,
},
] {
match target_mode {
None => {
// Must exist.
let nix::sys::stat::FileStat { st_mode, .. } = nix::sys::stat::stat(target)
.with_context(|| format!("stat({target}) failed"))?;
let s_flag = nix::sys::stat::SFlag::from_bits_truncate(st_mode);
if !s_flag.contains(nix::sys::stat::SFlag::S_IFDIR) {
anyhow::bail!("{target} is not a directory");
}
}
Some(target_mode) => {
// Must not exist.
nix::unistd::mkdir(target, target_mode)
.with_context(|| format!("mkdir({target}) failed"))?;
}
}
nix::mount::mount(Some(source), target, Some(fstype), flags, data).with_context(|| {
format!("mount({source}, {target}, {fstype}, {flags:?}, {data:?}) failed")
})?;
}
// By contract we run everything in /bin and assume they're rust test binaries.
//
// If the user requested command line arguments, they're named init.arg={}.
// Read kernel parameters from /proc/cmdline. They're space separated on a single line.
let cmdline = std::fs::read_to_string("/proc/cmdline")
.with_context(|| "read_to_string(/proc/cmdline) failed")?;
let args = cmdline
.split_whitespace()
.filter_map(|parameter| {
parameter
.strip_prefix("init.arg=")
.map(std::ffi::OsString::from)
})
.collect::<Vec<_>>();
// Iterate files in /bin.
let read_dir = std::fs::read_dir("/bin").context("read_dir(/bin) failed")?;
let errors = read_dir
.map(|entry| {
let entry = entry.context("read_dir(/bin) failed")?;
let path = entry.path();
let status = std::process::Command::new(&path)
.args(&args)
.status()
.with_context(|| format!("failed to execute {}", path.display()))?;
if status.code() == Some(0) {
Ok(())
} else {
Err(anyhow::anyhow!("{} failed: {status:?}", path.display()))
}
})
.filter_map(|result| match result {
Ok(()) => None,
Err(err) => Some(err),
})
.collect::<Vec<_>>();
if errors.is_empty() {
Ok(())
} else {
Err(Errors(errors).into())
}
}
fn main() {
match run() {
Ok(()) => {
println!("init: success");
}
Err(err) => {
println!("{err:?}");
println!("init: failure");
}
}
let how = nix::sys::reboot::RebootMode::RB_POWER_OFF;
let _: std::convert::Infallible = nix::sys::reboot::reboot(how)
.unwrap_or_else(|err| panic!("reboot({how:?}) failed: {err:?}"));
}