use std::collections::BTreeMap; use std::env; use std::ffi::OsString; use std::io::Result; use std::path::Path; use libredox::flag::{O_RDONLY, O_WRONLY}; use crate::script::Command; use crate::unit::Unit; mod script; mod service; mod unit; fn switch_stdio(stdio: &str) -> Result<()> { let stdin = libredox::Fd::open(stdio, O_RDONLY, 0)?; let stdout = libredox::Fd::open(stdio, O_WRONLY, 0)?; let stderr = libredox::Fd::open(stdio, O_WRONLY, 0)?; stdin.dup2(0, &[])?; stdout.dup2(1, &[])?; stderr.dup2(2, &[])?; Ok(()) } struct InitConfig { log_debug: bool, skip_cmd: Vec, envs: BTreeMap, } impl InitConfig { fn new() -> Self { let log_level = env::var("INIT_LOG_LEVEL").unwrap_or("INFO".into()); let log_debug = matches!(log_level.as_str(), "DEBUG" | "TRACE"); let skip_cmd: Vec = match env::var("INIT_SKIP") { Ok(v) if v.len() > 0 => v.split(',').map(|s| s.to_string()).collect(), _ => Vec::new(), }; Self { log_debug, skip_cmd, envs: BTreeMap::from([("RUST_BACKTRACE".to_owned(), "1".into())]), } } } fn switch_root(prefix: &Path, etcdir: &Path, config: &mut InitConfig) { config .envs .insert("PATH".to_owned(), prefix.join("bin").into_os_string()); config.envs.insert( "LD_LIBRARY_PATH".to_owned(), prefix.join("lib").into_os_string(), ); let entries = match config::config_for_dirs(&[ prefix.join("lib").join("init.d"), etcdir.join("init.d"), ]) { Ok(list) => list, Err(err) => { eprintln!("init: failed to switchroot: '{prefix:?}', '{etcdir:?}': {err}"); return; } }; for entry_path in entries { if let Err(err) = run(&entry_path, config) { eprintln!("init: failed to run '{}': {}", entry_path.display(), err); } } } fn run(file: &Path, config: &mut InitConfig) -> Result<()> { let (unit, errors) = Unit::from_file(file)?; for error in errors { eprintln!("init: {}: {error}", file.display()); } match unit.kind { unit::UnitKind::LegacyScript { script } => { for cmd in script.0 { if config.log_debug { eprintln!("init: running: {cmd:?}"); } run_command(cmd, config); } } unit::UnitKind::Service { service } => { if config.log_debug { eprintln!("init: running: {} {}", service.cmd, service.args.join(" ")); } service.spawn(&config.envs); } } Ok(()) } fn run_command(cmd: Command, config: &mut InitConfig) { match cmd { Command::Nothing => {} Command::Echo(text) => println!("{text}"), Command::SwitchRoot(prefix, etcdir) => { switch_root(&prefix, &etcdir, config); } Command::Stdio(stdio) => { if let Err(err) = switch_stdio(&stdio) { eprintln!("init: failed to switch stdio to '{}': {}", stdio, err); } } Command::Service(service) => { if config.skip_cmd.contains(&service.cmd) { eprintln!( "init: skipping '{} {}'", service.cmd, service.args.join(" ") ); return; } service.spawn(&config.envs); } } } fn main() { let mut init_config = InitConfig::new(); switch_root( Path::new("/scheme/initfs"), Path::new("/scheme/initfs/etc"), &mut init_config, ); libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); loop { let mut status = 0; libredox::call::waitpid(0, &mut status, 0).unwrap(); } }