From 6fcb07e7dd96ac5af050e2b1116904ebb358cb1e Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 25 Feb 2026 22:06:25 +0100 Subject: [PATCH] init: Introduce UnitStore This will be necessary to store persistent information about units like if they are currently started and which process is associated with them. --- init.d/00_rtcd.service | 4 +- init.d/90_exit_initfs | 1 - init.d/90_exit_initfs.switchroot | 7 ++ init/src/main.rs | 104 ++++++++++++++++++----------- init/src/script.rs | 15 +---- init/src/service.rs | 14 ++-- init/src/unit.rs | 111 +++++++++++++++++++++++++++---- 7 files changed, 179 insertions(+), 77 deletions(-) delete mode 100644 init.d/90_exit_initfs create mode 100644 init.d/90_exit_initfs.switchroot diff --git a/init.d/00_rtcd.service b/init.d/00_rtcd.service index c7c781ba46..5395f03ed1 100644 --- a/init.d/00_rtcd.service +++ b/init.d/00_rtcd.service @@ -1,5 +1,7 @@ { - "unit": {}, + "unit": { + "description": "Set time from realtime clock" + }, "service": { "cmd": "rtcd", "type": "oneshot" diff --git a/init.d/90_exit_initfs b/init.d/90_exit_initfs deleted file mode 100644 index dea1136bd9..0000000000 --- a/init.d/90_exit_initfs +++ /dev/null @@ -1 +0,0 @@ -switchroot /usr /etc diff --git a/init.d/90_exit_initfs.switchroot b/init.d/90_exit_initfs.switchroot new file mode 100644 index 0000000000..68a2bc311b --- /dev/null +++ b/init.d/90_exit_initfs.switchroot @@ -0,0 +1,7 @@ +{ + "unit": {}, + "switchroot": { + "prefix": "/usr", + "etcdir": "/etc" + } +} diff --git a/init/src/main.rs b/init/src/main.rs index a836075a3c..158761b3a2 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -1,13 +1,14 @@ use std::collections::BTreeMap; -use std::env; use std::ffi::OsString; use std::io::Result; -use std::path::Path; +use std::path::{Path, PathBuf}; +use std::{env, mem}; use libredox::flag::{O_RDONLY, O_WRONLY}; +use serde::Deserialize; use crate::script::Command; -use crate::unit::Unit; +use crate::unit::{UnitId, UnitStore}; mod script; mod service; @@ -48,42 +49,51 @@ impl InitConfig { } } -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(), - ); +#[derive(Clone, Deserialize)] +struct SwitchRoot { + prefix: PathBuf, + etcdir: PathBuf, +} - 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; - } - }; +impl SwitchRoot { + fn apply( + self, + pending_units: &mut Vec, + unit_store: &mut UnitStore, + config: &mut InitConfig, + ) { + 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(), + ); - for entry_path in entries { - if let Err(err) = run(&entry_path, config) { - eprintln!("init: failed to run '{}': {}", entry_path.display(), err); + unit_store.config_dirs = vec![ + self.prefix.join("lib").join("init.d"), + self.etcdir.join("init.d"), + ]; + + let (loaded_units, errors) = unit_store.load_units(); + for error in errors { + eprintln!("init: {error}"); } + pending_units.extend(loaded_units); } } -fn run(file: &Path, config: &mut InitConfig) -> Result<()> { - let (unit, errors) = Unit::from_file(file)?; - for error in errors { - eprintln!("init: {}: {error}", file.display()); - } +fn run( + unit: &UnitId, + pending_units: &mut Vec, + unit_store: &mut UnitStore, + config: &mut InitConfig, +) -> Result<()> { + let unit = unit_store.unit_mut(unit); - match unit.kind { + match &unit.kind { unit::UnitKind::LegacyScript { script } => { - for cmd in script.0 { + for cmd in script.0.clone() { if config.log_debug { eprintln!("init: running: {cmd:?}"); } @@ -92,10 +102,16 @@ fn run(file: &Path, config: &mut InitConfig) -> Result<()> { } unit::UnitKind::Service { service } => { if config.log_debug { - eprintln!("init: running: {} {}", service.cmd, service.args.join(" ")); + eprintln!( + "Starting {}", + unit.info.description.as_ref().unwrap_or(&unit.id.0) + ); } service.spawn(&config.envs); } + unit::UnitKind::SwitchRoot { switchroot } => { + switchroot.clone().apply(pending_units, unit_store, config); + } } Ok(()) @@ -105,9 +121,6 @@ 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); @@ -130,11 +143,22 @@ fn run_command(cmd: Command, config: &mut InitConfig) { fn main() { let mut init_config = InitConfig::new(); - switch_root( - Path::new("/scheme/initfs"), - Path::new("/scheme/initfs/etc"), - &mut init_config, - ); + let mut unit_store = UnitStore::new(); + let mut pending_units = vec![]; + + SwitchRoot { + prefix: Path::new("/scheme/initfs").to_owned(), + etcdir: Path::new("/scheme/initfs/etc").to_owned(), + } + .apply(&mut pending_units, &mut unit_store, &mut init_config); + + while !pending_units.is_empty() { + for unit in mem::take(&mut pending_units) { + if let Err(err) = run(&unit, &mut pending_units, &mut unit_store, &mut init_config) { + eprintln!("init: failed to run {}: {}", unit.0, err); + } + } + } libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); diff --git a/init/src/script.rs b/init/src/script.rs index 862436f4c6..0da2ac3183 100644 --- a/init/src/script.rs +++ b/init/src/script.rs @@ -37,7 +37,7 @@ impl Script { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Command { // Service Service(Service), @@ -47,7 +47,6 @@ pub enum Command { // Misc Echo(String), - SwitchRoot(PathBuf, PathBuf), Nothing, } @@ -59,18 +58,6 @@ impl Command { match cmd.as_str() { "echo" => Ok(Command::Echo(args.collect::>().join(" "))), - "switchroot" => { - let Some(prefix) = args.next() else { - return Err("init: failed to switchroot: no argument".to_owned()); - }; - let Some(etcdir) = args.next() else { - return Err("init: failed to switchroot: missing etcdir".to_owned()); - }; - Ok(Command::SwitchRoot( - PathBuf::from(prefix), - PathBuf::from(etcdir), - )) - } "stdio" => { let Some(stdio) = args.next() else { return Err("init: failed to set stdio: no argument".to_owned()); diff --git a/init/src/service.rs b/init/src/service.rs index 618f9e1e22..049be3839c 100644 --- a/init/src/service.rs +++ b/init/src/service.rs @@ -4,7 +4,7 @@ use std::process::Command; use serde::Deserialize; -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct Service { pub cmd: String, #[serde(default)] @@ -15,7 +15,7 @@ pub struct Service { pub type_: ServiceType, } -#[derive(Debug, Default, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] #[serde(rename_all = "snake_case")] pub enum ServiceType { #[default] @@ -26,12 +26,12 @@ pub enum ServiceType { } impl Service { - pub fn spawn(self, base_envs: &BTreeMap) { - let mut command = Command::new(self.cmd); - command.args(self.args); - command.env_clear().envs(base_envs).envs(self.envs); + pub fn spawn(&self, base_envs: &BTreeMap) { + let mut command = Command::new(&self.cmd); + command.args(&self.args); + command.env_clear().envs(base_envs).envs(&self.envs); - match self.type_ { + match &self.type_ { ServiceType::Notify => { daemon::Daemon::spawn(command); } diff --git a/init/src/unit.rs b/init/src/unit.rs index f74544eddc..377ceedf60 100644 --- a/init/src/unit.rs +++ b/init/src/unit.rs @@ -1,13 +1,71 @@ -use std::path::Path; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; use std::{fs, io}; use serde::Deserialize; +use crate::SwitchRoot; use crate::script::Script; use crate::service::Service; +pub struct UnitStore { + pub config_dirs: Vec, + units: BTreeMap, +} + +impl UnitStore { + pub fn new() -> Self { + UnitStore { + config_dirs: vec![], + units: BTreeMap::new(), + } + } + + pub fn load_units(&mut self) -> (Vec, Vec) { + let mut loaded_units = vec![]; + let mut errors = vec![]; + + let entries = match config::config_for_dirs(&self.config_dirs) { + Ok(entries) => entries, + Err(err) => { + errors.push(format!( + "failed to read configs from {}: {err}", + self.config_dirs + .iter() + .map(|dir| dir.display().to_string()) + .collect::>() + .join(", ") + )); + return (loaded_units, errors); + } + }; + + for entry in entries { + let (unit, new_errors) = match Unit::from_file(&entry) { + Ok(unit) => unit, + Err(err) => { + errors.push(format!("{}: {err}", entry.display())); + continue; + } + }; + errors.extend(new_errors); + loaded_units.push(unit.id.clone()); + self.units.insert(unit.id.clone(), unit); + } + + (loaded_units, errors) + } + + pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit { + self.units.get_mut(unit).unwrap() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct UnitId(pub String); + pub struct Unit { - pub name: String, + pub id: UnitId, pub info: UnitInfo, pub kind: UnitKind, @@ -15,17 +73,17 @@ pub struct Unit { #[derive(Deserialize)] pub struct UnitInfo { + pub description: Option, #[serde(default)] - description: String, + pub requires: Vec, #[serde(default)] - requires: Vec, - #[serde(default)] - requires_weak: Vec, + pub requires_weak: Vec, } pub enum UnitKind { LegacyScript { script: Script }, Service { service: Service }, + SwitchRoot { switchroot: SwitchRoot }, } #[derive(Deserialize)] @@ -34,14 +92,22 @@ struct SerializedService { service: Service, } +#[derive(Deserialize)] +struct SerializedSwitchRoot { + unit: UnitInfo, + switchroot: SwitchRoot, +} + impl Unit { pub fn from_file(config_path: &Path) -> io::Result<(Self, Vec)> { - let name = config_path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_owned(); + let name = UnitId( + config_path + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_owned(), + ); let config = fs::read_to_string(config_path)?; @@ -50,7 +116,7 @@ impl Unit { let (script, warnings) = Script::from_str(&config)?; ( UnitInfo { - description: "".to_owned(), + description: None, requires: vec![], requires_weak: vec![], }, @@ -68,9 +134,26 @@ impl Unit { vec![], ) } + Some("switchroot") => { + let switchroot: SerializedSwitchRoot = serde_json::from_str(&config)?; + ( + switchroot.unit, + UnitKind::SwitchRoot { + switchroot: switchroot.switchroot, + }, + vec![], + ) + } Some(_) => return Err(io::Error::other("invalid file extension")), }; - Ok((Unit { name, info, kind }, errors)) + Ok(( + Unit { + id: name, + info, + kind, + }, + errors, + )) } }