From 57d74e8e17f0194e535de025f4fc63a54c2f282d Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:30:07 +0200 Subject: [PATCH 1/8] init: Turn default dependencies into regular dependencies at load time --- init/src/main.rs | 22 +++++++++++----------- init/src/unit.rs | 22 +++++++++++++++++++++- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/init/src/main.rs b/init/src/main.rs index a5e22710d7..1243be1cbb 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -83,11 +83,18 @@ impl SwitchRoot { self.etcdir.join("init.d"), ]; - let mut errors = vec![]; + if self.prefix == Path::new("/scheme/initfs") { + let unit_id = UnitId("00_runtime.target".to_owned()); + let mut errors = vec![]; + let loaded_units = unit_store.load_units(unit_id.clone(), &mut errors); + pending_units.extend(loaded_units); + for error in errors { + eprintln!("init: {error}"); + } + unit_store.set_runtime_target(unit_id); + } - let loaded_units = - unit_store.load_units(UnitId("00_runtime.target".to_owned()), &mut errors); - pending_units.extend(loaded_units); + let mut errors = vec![]; if let Some(target) = self.target { let loaded_units = unit_store.load_units(target, &mut errors); @@ -187,7 +194,6 @@ fn main() { let mut unit_store = UnitStore::new(); let mut pending_units = VecDeque::new(); - let runtime_target = UnitId("00_runtime.target".to_owned()); let initfs_target = UnitId("90_initfs.target".to_owned()); SwitchRoot { @@ -221,12 +227,6 @@ fn main() { 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); diff --git a/init/src/unit.rs b/init/src/unit.rs index 9be292e5d6..753fbe8e97 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) From e09f28f1b03d1e674060e8ff27aa7898ef25455d Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:36:17 +0200 Subject: [PATCH 2/8] init: Pass reference to Unit to run --- init/src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/init/src/main.rs b/init/src/main.rs index 1243be1cbb..2e34a98d72 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -8,7 +8,7 @@ use libredox::flag::{O_RDONLY, O_WRONLY}; use serde::Deserialize; use crate::script::Command; -use crate::unit::{UnitId, UnitStore}; +use crate::unit::{Unit, UnitId, UnitStore}; mod script; mod service; @@ -130,9 +130,7 @@ impl SwitchRoot { } } -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() { @@ -234,7 +232,7 @@ fn main() { } } - if let Err(err) = run(&unit, &mut unit_store, &mut init_config) { + if let Err(err) = run(unit_store.unit_mut(&unit), &mut init_config) { eprintln!("init: failed to run {}: {}", unit.0, err); } From a4cb95bd61794b976614619eedb9698d2435be4f Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:43:48 +0200 Subject: [PATCH 3/8] init: Extract Unit::conditions_met method --- init/src/main.rs | 17 ++--------------- init/src/unit.rs | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/init/src/main.rs b/init/src/main.rs index 2e34a98d72..9f797095bc 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -209,21 +209,8 @@ fn main() { } '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).conditions_met() { + continue 'a; } for dep in &unit_store.unit(&unit).info.requires_weak { if pending_units.contains(dep) { diff --git a/init/src/unit.rs b/init/src/unit.rs index 753fbe8e97..54e5057461 100644 --- a/init/src/unit.rs +++ b/init/src/unit.rs @@ -233,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 + } } From a93e4c5fccbb79f85326d760b90803371f64a9b9 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 5 Apr 2026 17:26:03 +0200 Subject: [PATCH 4/8] init: Remove unnecessary derives for SwitchRoot --- init/src/main.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/init/src/main.rs b/init/src/main.rs index 9f797095bc..4f4499cefa 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -5,7 +5,6 @@ use std::io::Result; use std::path::{Path, PathBuf}; use libredox::flag::{O_RDONLY, O_WRONLY}; -use serde::Deserialize; use crate::script::Command; use crate::unit::{Unit, UnitId, UnitStore}; @@ -49,8 +48,6 @@ impl InitConfig { } } -#[derive(Clone, Deserialize)] -#[serde(deny_unknown_fields)] struct SwitchRoot { prefix: PathBuf, etcdir: PathBuf, From 6a59d6f2d49ae6fc129798fcef3579a7ebff9133 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:54:15 +0200 Subject: [PATCH 5/8] daemon: Introduce pass_fd helper --- daemon/src/lib.rs | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs index e3828bbfab..5a19ea961d 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,21 +58,12 @@ 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) { @@ -127,21 +133,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 { From ec0d29285ee17ef9f49333aa65356d91542cc399 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:55:28 +0200 Subject: [PATCH 6/8] daemon: Remove check that can never fail --- daemon/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs index 5a19ea961d..36376a05bc 100644 --- a/daemon/src/lib.rs +++ b/daemon/src/lib.rs @@ -67,11 +67,7 @@ impl Daemon { 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"); } From 8e6b1f8643f89101b3834bce324c0e90b0460252 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:19:19 +0100 Subject: [PATCH 7/8] init: Introduce a basic job scheduler --- init/src/main.rs | 72 +++++++++++-------------------------- init/src/scheduler.rs | 83 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 52 deletions(-) create mode 100644 init/src/scheduler.rs diff --git a/init/src/main.rs b/init/src/main.rs index 4f4499cefa..ae4ec0f7bd 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, VecDeque}; +use std::collections::BTreeMap; use std::env; use std::ffi::OsString; use std::io::Result; @@ -6,9 +6,11 @@ use std::path::{Path, PathBuf}; use libredox::flag::{O_RDONLY, O_WRONLY}; +use crate::scheduler::Scheduler; use crate::script::Command; use crate::unit::{Unit, UnitId, UnitStore}; +mod scheduler; mod script; mod service; mod unit; @@ -55,12 +57,7 @@ struct SwitchRoot { } impl SwitchRoot { - fn apply( - self, - pending_units: &mut VecDeque, - unit_store: &mut UnitStore, - config: &mut InitConfig, - ) { + fn apply(self, scheduler: &mut Scheduler, unit_store: &mut UnitStore, config: &mut InitConfig) { eprintln!( "init: switchroot to {} {}", self.prefix.display(), @@ -82,20 +79,12 @@ impl SwitchRoot { if self.prefix == Path::new("/scheme/initfs") { let unit_id = UnitId("00_runtime.target".to_owned()); - let mut errors = vec![]; - let loaded_units = unit_store.load_units(unit_id.clone(), &mut errors); - pending_units.extend(loaded_units); - for error in errors { - eprintln!("init: {error}"); - } + scheduler.schedule_start_and_report_errors(unit_store, unit_id.clone()); unit_store.set_runtime_target(unit_id); } - let mut errors = vec![]; - if let Some(target) = self.target { - let loaded_units = unit_store.load_units(target, &mut errors); - pending_units.extend(loaded_units); + scheduler.schedule_start_and_report_errors(unit_store, target); } else { let entries = match config::config_for_dirs(&unit_store.config_dirs) { Ok(entries) => entries, @@ -113,17 +102,12 @@ impl SwitchRoot { } }; for entry in entries { - let loaded_units = unit_store.load_units( + scheduler.schedule_start_and_report_errors( + unit_store, UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), - &mut errors, ); - pending_units.extend(loaded_units); } } - - for error in errors { - eprintln!("init: {error}"); - } } } @@ -187,16 +171,14 @@ 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 initfs_target = UnitId("90_initfs.target".to_owned()); + let mut scheduler = Scheduler::new(); SwitchRoot { prefix: Path::new("/scheme/initfs").to_owned(), etcdir: Path::new("/scheme/initfs/etc").to_owned(), - target: Some(initfs_target.clone()), + target: Some(UnitId("90_initfs.target".to_owned())), } - .apply(&mut pending_units, &mut unit_store, &mut init_config); + .apply(&mut scheduler, &mut unit_store, &mut init_config); let mut command = std::process::Command::new("logd"); command.env_clear().envs(&init_config.envs); @@ -205,31 +187,17 @@ fn main() { eprintln!("init: failed to switch stdio to '/scheme/log': {err}"); } - 'a: while let Some(unit) = pending_units.pop_front() { - if !unit_store.unit(&unit).conditions_met() { - 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_store.unit_mut(&unit), &mut init_config) { - eprintln!("init: failed to run {}: {}", unit.0, err); - } - - 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()), - } - .apply(&mut pending_units, &mut unit_store, &mut init_config); - } + 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()), } + .apply(&mut scheduler, &mut unit_store, &mut init_config); + + 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); + } + } + } + } + } +} From 1b50d1b5432f9505ebf9d32f7d6228bf24314264 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 5 Apr 2026 17:31:16 +0200 Subject: [PATCH 8/8] init: Inline SwitchRoot --- init/src/main.rs | 131 ++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 71 deletions(-) diff --git a/init/src/main.rs b/init/src/main.rs index ae4ec0f7bd..16c2b3a7ca 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -2,7 +2,7 @@ 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}; @@ -50,65 +50,22 @@ impl InitConfig { } } -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() + ); -impl SwitchRoot { - fn apply(self, scheduler: &mut Scheduler, unit_store: &mut UnitStore, config: &mut InitConfig) { - eprintln!( - "init: switchroot to {} {}", - self.prefix.display(), - self.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(), + ); - 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"), - ]; - - if self.prefix == Path::new("/scheme/initfs") { - let unit_id = UnitId("00_runtime.target".to_owned()); - scheduler.schedule_start_and_report_errors(unit_store, unit_id.clone()); - unit_store.set_runtime_target(unit_id); - } - - if let Some(target) = self.target { - scheduler.schedule_start_and_report_errors(unit_store, target); - } 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 { - scheduler.schedule_start_and_report_errors( - unit_store, - UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), - ); - } - } - } + unit_store.config_dirs = vec![prefix.join("lib").join("init.d"), etcdir.join("init.d")]; } fn run(unit: &mut Unit, config: &mut InitConfig) -> Result<()> { @@ -173,12 +130,19 @@ fn main() { let mut unit_store = UnitStore::new(); let mut scheduler = Scheduler::new(); - SwitchRoot { - prefix: Path::new("/scheme/initfs").to_owned(), - etcdir: Path::new("/scheme/initfs/etc").to_owned(), - target: Some(UnitId("90_initfs.target".to_owned())), - } - .apply(&mut scheduler, &mut unit_store, &mut init_config); + 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()); + scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone()); + unit_store.set_runtime_target(runtime_target); + + 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); @@ -189,13 +153,38 @@ fn main() { scheduler.step(&mut unit_store, &mut init_config); - 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()), - } - .apply(&mut scheduler, &mut unit_store, &mut init_config); + 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())); + + 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 { + 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);