diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs index e3828bbfab..36376a05bc 100644 --- a/daemon/src/lib.rs +++ b/daemon/src/lib.rs @@ -2,7 +2,8 @@ #![feature(never_type)] use std::io::{self, PipeWriter, Read, Write}; -use std::os::fd::{AsRawFd, FromRawFd, RawFd}; +use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}; +use std::os::unix::process::CommandExt; use std::process::Command; use libredox::Fd; @@ -20,6 +21,20 @@ unsafe fn get_fd(var: &str) -> RawFd { fd } +unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) { + cmd.env(env, format!("{}", fd.as_raw_fd())); + unsafe { + cmd.pre_exec(move || { + // Pass notify pipe to child + if libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, 0) == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + }); + } +} + /// A long running background process that handles requests. #[must_use = "Daemon::ready must be called"] pub struct Daemon { @@ -43,29 +58,16 @@ impl Daemon { pub fn spawn(mut cmd: Command) { let (mut read_pipe, write_pipe) = io::pipe().unwrap(); - // Pass notify pipe to child - if unsafe { libc::fcntl(write_pipe.as_raw_fd(), libc::F_SETFD, 0) } == -1 { - eprintln!( - "daemon: failed to unset CLOEXEC flag for notify pipe: {}", - io::Error::last_os_error() - ); - return; - } - cmd.env("INIT_NOTIFY", format!("{}", write_pipe.as_raw_fd())); + unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) }; if let Err(err) = cmd.spawn() { eprintln!("daemon: failed to execute {cmd:?}: {err}"); return; } - drop(write_pipe); let mut data = [0]; match read_pipe.read_exact(&mut data) { - Ok(()) => { - if data[0] != 0 { - eprintln!("daemon: {cmd:?} failed with {}", data[0]); - } - } + Ok(()) => {} Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { eprintln!("daemon: {cmd:?} exited without notifying readiness"); } @@ -127,21 +129,12 @@ impl SchemeDaemon { pub fn spawn(mut cmd: Command, scheme_name: &str) { let (read_pipe, write_pipe) = io::pipe().unwrap(); - // Pass notify pipe to child - if unsafe { libc::fcntl(write_pipe.as_raw_fd(), libc::F_SETFD, 0) } == -1 { - eprintln!( - "daemon: failed to unset CLOEXEC flag for notify pipe: {}", - io::Error::last_os_error() - ); - return; - } - cmd.env("INIT_NOTIFY", format!("{}", write_pipe.as_raw_fd())); + unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) }; if let Err(err) = cmd.spawn() { eprintln!("daemon: failed to execute {cmd:?}: {err}"); return; } - drop(write_pipe); let mut new_fd = usize::MAX; let fd_bytes = unsafe { diff --git a/init/src/main.rs b/init/src/main.rs index a5e22710d7..16c2b3a7ca 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -1,15 +1,16 @@ -use std::collections::{BTreeMap, VecDeque}; +use std::collections::BTreeMap; use std::env; use std::ffi::OsString; use std::io::Result; -use std::path::{Path, PathBuf}; +use std::path::Path; use libredox::flag::{O_RDONLY, O_WRONLY}; -use serde::Deserialize; +use crate::scheduler::Scheduler; use crate::script::Command; -use crate::unit::{UnitId, UnitStore}; +use crate::unit::{Unit, UnitId, UnitStore}; +mod scheduler; mod script; mod service; mod unit; @@ -49,83 +50,25 @@ impl InitConfig { } } -#[derive(Clone, Deserialize)] -#[serde(deny_unknown_fields)] -struct SwitchRoot { - prefix: PathBuf, - etcdir: PathBuf, - target: Option, +fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Path, etcdir: &Path) { + eprintln!( + "init: switchroot to {} {}", + prefix.display(), + etcdir.display() + ); + + 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(), + ); + + unit_store.config_dirs = vec![prefix.join("lib").join("init.d"), etcdir.join("init.d")]; } -impl SwitchRoot { - fn apply( - self, - pending_units: &mut VecDeque, - unit_store: &mut UnitStore, - config: &mut InitConfig, - ) { - eprintln!( - "init: switchroot to {} {}", - self.prefix.display(), - self.etcdir.display() - ); - - config - .envs - .insert("PATH".to_owned(), self.prefix.join("bin").into_os_string()); - config.envs.insert( - "LD_LIBRARY_PATH".to_owned(), - self.prefix.join("lib").into_os_string(), - ); - - unit_store.config_dirs = vec![ - self.prefix.join("lib").join("init.d"), - self.etcdir.join("init.d"), - ]; - - let mut errors = vec![]; - - let loaded_units = - unit_store.load_units(UnitId("00_runtime.target".to_owned()), &mut errors); - pending_units.extend(loaded_units); - - if let Some(target) = self.target { - let loaded_units = unit_store.load_units(target, &mut errors); - pending_units.extend(loaded_units); - } else { - let entries = match config::config_for_dirs(&unit_store.config_dirs) { - Ok(entries) => entries, - Err(err) => { - eprintln!( - "init: failed to read configs from {}: {err}", - unit_store - .config_dirs - .iter() - .map(|dir| dir.display().to_string()) - .collect::>() - .join(", ") - ); - return; - } - }; - for entry in entries { - let loaded_units = unit_store.load_units( - UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), - &mut errors, - ); - pending_units.extend(loaded_units); - } - } - - for error in errors { - eprintln!("init: {error}"); - } - } -} - -fn run(unit: &UnitId, unit_store: &mut UnitStore, config: &mut InitConfig) -> Result<()> { - let unit = unit_store.unit_mut(unit); - +fn run(unit: &mut Unit, config: &mut InitConfig) -> Result<()> { match &unit.kind { unit::UnitKind::LegacyScript { script } => { for cmd in script.0.clone() { @@ -185,17 +128,21 @@ fn run_command(cmd: Command, config: &mut InitConfig) { fn main() { let mut init_config = InitConfig::new(); let mut unit_store = UnitStore::new(); - let mut pending_units = VecDeque::new(); + let mut scheduler = Scheduler::new(); + + switch_root( + &mut unit_store, + &mut init_config, + Path::new("/scheme/initfs"), + Path::new("/scheme/initfs/etc"), + ); let runtime_target = UnitId("00_runtime.target".to_owned()); - let initfs_target = UnitId("90_initfs.target".to_owned()); + scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone()); + unit_store.set_runtime_target(runtime_target); - SwitchRoot { - prefix: Path::new("/scheme/initfs").to_owned(), - etcdir: Path::new("/scheme/initfs/etc").to_owned(), - target: Some(initfs_target.clone()), - } - .apply(&mut pending_units, &mut unit_store, &mut init_config); + scheduler + .schedule_start_and_report_errors(&mut unit_store, UnitId("90_initfs.target".to_owned())); let mut command = std::process::Command::new("logd"); command.env_clear().envs(&init_config.envs); @@ -204,50 +151,42 @@ fn main() { eprintln!("init: failed to switch stdio to '/scheme/log': {err}"); } - 'a: while let Some(unit) = pending_units.pop_front() { - if let Some(condition_architecture) = &unit_store.unit(&unit).info.condition_architecture { - if !condition_architecture - .iter() - .any(|arch| arch == std::env::consts::ARCH) - { - continue 'a; - } - } - if let Some(condition_board) = &unit_store.unit(&unit).info.condition_board { - if !condition_board - .iter() - .any(|board| Some(&**board) == option_env!("BOARD")) - { - continue 'a; - } - } - if unit_store.unit(&unit).info.default_dependencies { - if pending_units.contains(&runtime_target) { - pending_units.push_back(unit); - continue 'a; - } - } - for dep in &unit_store.unit(&unit).info.requires_weak { - if pending_units.contains(dep) { - pending_units.push_back(unit); - continue 'a; - } - } + scheduler.step(&mut unit_store, &mut init_config); - if let Err(err) = run(&unit, &mut unit_store, &mut init_config) { - eprintln!("init: failed to run {}: {}", unit.0, err); - } + switch_root( + &mut unit_store, + &mut init_config, + Path::new("/usr"), + Path::new("/etc"), + ); + { + // FIXME introduce multi-user.target unit and replace the config dir iteration + // scheduler.schedule_start_and_report_errors(&mut unit_store, UnitId("multi-user.target".to_owned())); - if unit == initfs_target { - SwitchRoot { - prefix: PathBuf::from("/usr"), - etcdir: PathBuf::from("/etc"), - // FIXME make target non-optional once there is a multi-user.target unit - target: None, //UnitId("multi-user.target".to_owned()), + let entries = match config::config_for_dirs(&unit_store.config_dirs) { + Ok(entries) => entries, + Err(err) => { + eprintln!( + "init: failed to read configs from {}: {err}", + unit_store + .config_dirs + .iter() + .map(|dir| dir.display().to_string()) + .collect::>() + .join(", ") + ); + return; } - .apply(&mut pending_units, &mut unit_store, &mut init_config); + }; + for entry in entries { + scheduler.schedule_start_and_report_errors( + &mut unit_store, + UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), + ); } - } + }; + + scheduler.step(&mut unit_store, &mut init_config); libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs new file mode 100644 index 0000000000..04918aff25 --- /dev/null +++ b/init/src/scheduler.rs @@ -0,0 +1,83 @@ +use std::collections::VecDeque; + +use crate::InitConfig; +use crate::unit::{UnitId, UnitStore}; + +pub struct Scheduler { + pending: VecDeque, +} + +struct Job { + unit: UnitId, + kind: JobKind, +} + +enum JobKind { + Start, +} + +impl Scheduler { + pub fn new() -> Scheduler { + Scheduler { + pending: VecDeque::new(), + } + } + + pub fn schedule_start_and_report_errors( + &mut self, + unit_store: &mut UnitStore, + unit_id: UnitId, + ) { + let mut errors = vec![]; + self.schedule_start(unit_store, unit_id, &mut errors); + for error in errors { + eprintln!("init: {error}"); + } + } + + pub fn schedule_start( + &mut self, + unit_store: &mut UnitStore, + unit_id: UnitId, + errors: &mut Vec, + ) { + let loaded_units = unit_store.load_units(unit_id.clone(), errors); + for unit_id in loaded_units { + if !unit_store.unit(&unit_id).conditions_met() { + continue; + } + + self.pending.push_back(Job { + unit: unit_id, + kind: JobKind::Start, + }); + } + } + + pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) { + 'a: loop { + let Some(job) = self.pending.pop_front() else { + return; + }; + + match job.kind { + JobKind::Start => { + let unit = unit_store.unit_mut(&job.unit); + + for dep in &unit.info.requires_weak { + for pending_job in &self.pending { + if &pending_job.unit == dep { + self.pending.push_back(job); + continue 'a; + } + } + } + + if let Err(err) = crate::run(unit, init_config) { + eprintln!("init: failed to run {}: {}", job.unit.0, err); + } + } + } + } + } +} diff --git a/init/src/unit.rs b/init/src/unit.rs index 9be292e5d6..54e5057461 100644 --- a/init/src/unit.rs +++ b/init/src/unit.rs @@ -10,6 +10,7 @@ use crate::service::Service; pub struct UnitStore { pub config_dirs: Vec, units: BTreeMap, + runtime_target: Option, } impl UnitStore { @@ -17,9 +18,16 @@ impl UnitStore { UnitStore { config_dirs: vec![], units: BTreeMap::new(), + runtime_target: None, } } + pub fn set_runtime_target(&mut self, unit_id: UnitId) { + assert!(self.runtime_target.is_none()); + assert!(self.units.contains_key(&unit_id)); + self.runtime_target = Some(unit_id); + } + fn load_single_unit(&mut self, unit_id: UnitId, errors: &mut Vec) -> Option { let (filename, instance) = if let Some((base_service, rest)) = unit_id.0.split_once('@') { let Some((instance, ext)) = rest.rsplit_once('.') else { @@ -42,13 +50,25 @@ impl UnitStore { return None; }; - let unit = match Unit::from_file(unit_id.clone(), &path, instance, errors) { + let mut unit = match Unit::from_file(unit_id.clone(), &path, instance, errors) { Ok(unit) => unit, Err(err) => { errors.push(format!("{}: {err}", path.display())); return None; } }; + + if unit.info.default_dependencies { + if let Some(runtime_target) = self.runtime_target.clone() { + unit.info.requires_weak.push(runtime_target); + } else { + errors.push(format!( + "{}: dependency of the runtime target must have default dependencies disabled", + path.display(), + )); + } + } + self.units.insert(unit_id.clone(), unit); Some(unit_id) @@ -213,4 +233,26 @@ impl Unit { Ok(Unit { id, info, kind }) } + + pub fn conditions_met(&self) -> bool { + if let Some(condition_architecture) = &self.info.condition_architecture { + if !condition_architecture + .iter() + .any(|arch| arch == std::env::consts::ARCH) + { + return false; + } + } + + if let Some(condition_board) = &self.info.condition_board { + if !condition_board + .iter() + .any(|board| Some(&**board) == option_env!("BOARD")) + { + return false; + } + } + + true + } }