init: Change from push to a pull for loading units

Rather than starting all services in the config dir, we only start those
which are a dependency of the target we want to reach. This is necessary
to handle unit templating, service start stop and an alternative target
for recovery or graceful shutdown.
This commit is contained in:
bjorn3
2026-03-04 21:50:32 +01:00
parent 7cea939540
commit 1424a470d3
2 changed files with 78 additions and 31 deletions
+41 -4
View File
@@ -54,6 +54,7 @@ impl InitConfig {
struct SwitchRoot {
prefix: PathBuf,
etcdir: PathBuf,
target: Option<UnitId>,
}
impl SwitchRoot {
@@ -82,11 +83,44 @@ impl SwitchRoot {
self.etcdir.join("init.d"),
];
let (loaded_units, errors) = unit_store.load_units();
let (loaded_units, errors) = unit_store.load_units(UnitId("00_runtime.target".to_owned()));
for error in errors {
eprintln!("init: {error}");
}
pending_units.extend(loaded_units);
if let Some(target) = self.target {
let (loaded_units, errors) = unit_store.load_units(target);
for error in errors {
eprintln!("init: {error}");
}
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::<Vec<_>>()
.join(", ")
);
return;
}
};
for entry in entries {
let (loaded_units, errors) = unit_store.load_units(UnitId(
entry.file_name().unwrap().to_str().unwrap().to_owned(),
));
for error in errors {
eprintln!("init: {error}");
}
pending_units.extend(loaded_units);
}
}
}
}
@@ -149,9 +183,13 @@ 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 {
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);
@@ -162,9 +200,6 @@ fn main() {
eprintln!("init: failed to switch stdio to '/scheme/log': {err}");
}
let runtime_target = UnitId("00_runtime.target".to_owned());
let initfs_target = UnitId("90_initfs.target".to_owned());
'a: while let Some(unit) = pending_units.pop_front() {
if let Some(condition_architecture) = &unit_store.unit(&unit).info.condition_architecture {
if !condition_architecture
@@ -203,6 +238,8 @@ fn main() {
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);
}
+37 -27
View File
@@ -20,36 +20,46 @@ impl UnitStore {
}
}
pub fn load_units(&mut self) -> (Vec<UnitId>, Vec<String>) {
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::<Vec<_>>()
.join(", ")
));
return (loaded_units, errors);
}
fn load_single_unit(&mut self, unit_id: UnitId) -> (Option<UnitId>, Vec<String>) {
let Some(path) = self
.config_dirs
.iter()
.rev()
.map(|dir| dir.join(&unit_id.0))
.find(|path| path.exists())
else {
return (None, vec![format!("unit {} not found", unit_id.0)]);
};
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;
}
};
let (unit, errors) = match Unit::from_file(&path) {
Ok(unit) => unit,
Err(err) => {
return (None, vec![format!("{}: {err}", path.display())]);
}
};
assert_eq!(unit_id, unit.id);
self.units.insert(unit_id.clone(), unit);
(Some(unit_id), errors)
}
pub fn load_units(&mut self, root_unit: UnitId) -> (Vec<UnitId>, Vec<String>) {
let mut loaded_units = vec![];
let mut pending_units = vec![root_unit];
let mut errors = vec![];
while let Some(unit_id) = pending_units.pop() {
if self.units.contains_key(&unit_id) {
continue;
}
let (unit, new_errors) = self.load_single_unit(unit_id);
errors.extend(new_errors);
loaded_units.push(unit.id.clone());
self.units.insert(unit.id.clone(), unit);
if let Some(unit) = unit {
loaded_units.push(unit.clone());
for dep in &self.unit(&unit).info.requires_weak {
pending_units.push(dep.clone());
}
}
}
(loaded_units, errors)