220f053ad8
Base patch extraction (12 new patches, 11,017 lines from the 17k monolith): - P2-acpid-core-refactor: acpi.rs, dmar, aml_physmem, ec, scheme (3,150 lines) - P2-ihdad-device-refactor: CodecTopology, ControllerPolicy, InputStream (1,022 lines) - P2-ac97d-ihdad-main: AC97 + ihdad daemon error handling (287 lines) - P2-inputd: inputd lib + main with named producers (896 lines) - P2-network-driver-mains: e1000/ixgbe/rtl8139/rtl8168d/virtio-net mains (607 lines) - P2-pcid-driver-interface: BAR, cap, config, IRQ helpers, MSI, scheme (1,463 lines) - P2-storage-driver-mains: ahcid/ided/nvmed/virtio-blk main.rs files (625 lines) - P2-xhcid-remaining: xhcid main, device_enumerator, xhci mod+scheme (2,033 lines) - P2-virtio-core-vbox: virtio-core arch/probe/transport + vboxd (413 lines) - P2-init-subsystems: scheduler, service, unit management (292 lines) - P2-logd: logd main + scheme (164 lines) - P2-hwd-misc: hwd Cargo.toml + main.rs (64 lines) Graphics drivers (ihdgd, vesad, virtio-gpud, fbcond scheme/text, graphics-ipc) already fully covered by existing P2-daemon-hardening.patch — no duplicates created. Rust toolchain: nightly-2025-10-03 → nightly-2026-04-01 (1.96.0-nightly). Cookbook builds clean, no feature gates in codebase.
293 lines
10 KiB
Diff
293 lines
10 KiB
Diff
# P2-init-subsystems.patch
|
|
# Extract init subsystem hardening: service respawn, unit store Option returns,
|
|
# scheduler PID tracking, and service spawn error handling.
|
|
#
|
|
# Files: init/src/scheduler.rs, init/src/service.rs, init/src/unit.rs
|
|
|
|
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
|
|
index d42a4e57..64e64e1e 100644
|
|
--- a/init/src/scheduler.rs
|
|
+++ b/init/src/scheduler.rs
|
|
@@ -5,6 +5,7 @@ use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
|
|
|
|
pub struct Scheduler {
|
|
pending: VecDeque<Job>,
|
|
+ pub respawn_pids: Vec<(UnitId, u32)>,
|
|
}
|
|
|
|
struct Job {
|
|
@@ -20,6 +21,7 @@ impl Scheduler {
|
|
pub fn new() -> Scheduler {
|
|
Scheduler {
|
|
pending: VecDeque::new(),
|
|
+ respawn_pids: Vec::new(),
|
|
}
|
|
}
|
|
|
|
@@ -43,7 +45,10 @@ impl Scheduler {
|
|
) {
|
|
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() {
|
|
+ let Some(unit) = unit_store.unit(&unit_id) else {
|
|
+ continue;
|
|
+ };
|
|
+ if !unit.conditions_met() {
|
|
continue;
|
|
}
|
|
|
|
@@ -62,7 +67,10 @@ impl Scheduler {
|
|
|
|
match job.kind {
|
|
JobKind::Start => {
|
|
- let unit = unit_store.unit_mut(&job.unit);
|
|
+ let Some(unit) = unit_store.unit_mut(&job.unit) else {
|
|
+ eprintln!("init: unit {} not found in store, skipping", job.unit.0);
|
|
+ continue 'a;
|
|
+ };
|
|
|
|
for dep in &unit.info.requires_weak {
|
|
for pending_job in &self.pending {
|
|
@@ -73,14 +81,17 @@ impl Scheduler {
|
|
}
|
|
}
|
|
|
|
- run(unit, init_config);
|
|
+ let pid = run(unit, init_config);
|
|
+ if let Some(pid) = pid {
|
|
+ self.respawn_pids.push((job.unit.clone(), pid));
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
-fn run(unit: &mut Unit, config: &mut InitConfig) {
|
|
+fn run(unit: &mut Unit, config: &mut InitConfig) -> Option<u32> {
|
|
match &unit.kind {
|
|
UnitKind::LegacyScript { script } => {
|
|
for cmd in script.clone() {
|
|
@@ -92,25 +103,30 @@ fn run(unit: &mut Unit, config: &mut InitConfig) {
|
|
}
|
|
UnitKind::Service { service } => {
|
|
if config.skip_cmd.contains(&service.cmd) {
|
|
- eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" "));
|
|
- return;
|
|
+ eprintln!("init: skipping {} {}", service.cmd, service.args.join(" "));
|
|
+ return None;
|
|
}
|
|
- if config.log_debug {
|
|
+ eprintln!(
|
|
+ "init: starting {} ({})",
|
|
+ unit.info.description.as_ref().unwrap_or(&unit.id.0),
|
|
+ service.cmd,
|
|
+ );
|
|
+ let pid = service.spawn(&config.envs);
|
|
+ if pid.is_some() {
|
|
eprintln!(
|
|
- "Starting {} ({})",
|
|
+ "init: started {} (pid {})",
|
|
unit.info.description.as_ref().unwrap_or(&unit.id.0),
|
|
- service.cmd,
|
|
+ pid.unwrap_or(0),
|
|
);
|
|
}
|
|
- service.spawn(&config.envs);
|
|
+ return pid;
|
|
}
|
|
UnitKind::Target {} => {
|
|
- if config.log_debug {
|
|
- eprintln!(
|
|
- "Reached target {}",
|
|
- unit.info.description.as_ref().unwrap_or(&unit.id.0),
|
|
- );
|
|
- }
|
|
+ eprintln!(
|
|
+ "init: reached target {}",
|
|
+ unit.info.description.as_ref().unwrap_or(&unit.id.0),
|
|
+ );
|
|
}
|
|
}
|
|
+ None
|
|
}
|
|
diff --git a/init/src/service.rs b/init/src/service.rs
|
|
index ed0023e9..cc95d02b 100644
|
|
--- a/init/src/service.rs
|
|
+++ b/init/src/service.rs
|
|
@@ -22,6 +22,8 @@ pub struct Service {
|
|
pub inherit_envs: BTreeSet<String>,
|
|
#[serde(rename = "type")]
|
|
pub type_: ServiceType,
|
|
+ #[serde(default)]
|
|
+ pub respawn: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize)]
|
|
@@ -35,7 +37,7 @@ pub enum ServiceType {
|
|
}
|
|
|
|
impl Service {
|
|
- pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) {
|
|
+ pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) -> Option<u32> {
|
|
let mut command = Command::new(&self.cmd);
|
|
command.args(self.args.iter().map(|arg| subst_env(arg)));
|
|
command.env_clear();
|
|
@@ -46,20 +48,28 @@ impl Service {
|
|
}
|
|
command.envs(base_envs).envs(&self.envs);
|
|
|
|
- let (mut read_pipe, write_pipe) = io::pipe().unwrap();
|
|
+ let (mut read_pipe, write_pipe) = match io::pipe() {
|
|
+ Ok(pair) => pair,
|
|
+ Err(err) => {
|
|
+ eprintln!("init: failed to create readiness pipe for {:?}: {err}", command);
|
|
+ return None;
|
|
+ }
|
|
+ };
|
|
unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_pipe.into()) };
|
|
|
|
let mut child = match command.spawn() {
|
|
Ok(child) => child,
|
|
Err(err) => {
|
|
eprintln!("init: failed to execute {:?}: {}", command, err);
|
|
- return;
|
|
+ return None;
|
|
}
|
|
};
|
|
|
|
match &self.type_ {
|
|
ServiceType::Notify => match read_pipe.read_exact(&mut [0]) {
|
|
- Ok(()) => {}
|
|
+ Ok(()) => {
|
|
+ eprintln!("init: {} ready (notify)", self.cmd);
|
|
+ }
|
|
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
|
|
eprintln!("init: {command:?} exited without notifying readiness");
|
|
}
|
|
@@ -81,23 +91,34 @@ impl Service {
|
|
}) => continue,
|
|
Ok(0) => {
|
|
eprintln!("init: {command:?} exited without notifying readiness");
|
|
- return;
|
|
+ return None;
|
|
}
|
|
Ok(1) => break,
|
|
Ok(n) => {
|
|
eprintln!("init: incorrect amount of fds {n} returned");
|
|
- return;
|
|
+ return None;
|
|
}
|
|
Err(err) => {
|
|
eprintln!("init: failed to wait for {command:?}: {err}");
|
|
- return;
|
|
+ return None;
|
|
}
|
|
}
|
|
}
|
|
|
|
- let current_namespace_fd = libredox::call::getns().expect("TODO");
|
|
- libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
|
|
- .expect("TODO");
|
|
+ let current_namespace_fd = match libredox::call::getns() {
|
|
+ Ok(fd) => fd,
|
|
+ Err(err) => {
|
|
+ eprintln!("init: failed to get current namespace for {command:?}: {err}");
|
|
+ return None;
|
|
+ }
|
|
+ };
|
|
+ if let Err(err) =
|
|
+ libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
|
|
+ {
|
|
+ eprintln!("init: failed to register scheme {scheme:?} for {command:?}: {err}");
|
|
+ } else {
|
|
+ eprintln!("init: {} ready (scheme {})", self.cmd, scheme);
|
|
+ }
|
|
}
|
|
ServiceType::Oneshot => {
|
|
drop(read_pipe);
|
|
@@ -105,6 +126,8 @@ impl Service {
|
|
Ok(exit_status) => {
|
|
if !exit_status.success() {
|
|
eprintln!("init: {command:?} failed with {exit_status}");
|
|
+ } else {
|
|
+ eprintln!("init: {} done (oneshot)", self.cmd);
|
|
}
|
|
}
|
|
Err(err) => {
|
|
@@ -112,8 +135,13 @@ impl Service {
|
|
}
|
|
}
|
|
}
|
|
- ServiceType::OneshotAsync => {}
|
|
+ ServiceType::OneshotAsync => {
|
|
+ if self.respawn {
|
|
+ return Some(child.id());
|
|
+ }
|
|
+ }
|
|
}
|
|
+ None
|
|
}
|
|
}
|
|
|
|
diff --git a/init/src/unit.rs b/init/src/unit.rs
|
|
index 98053cb2..a58bfb96 100644
|
|
--- a/init/src/unit.rs
|
|
+++ b/init/src/unit.rs
|
|
@@ -23,8 +23,14 @@ impl UnitStore {
|
|
}
|
|
|
|
pub fn set_runtime_target(&mut self, unit_id: UnitId) {
|
|
- assert!(self.runtime_target.is_none());
|
|
- assert!(self.units.contains_key(&unit_id));
|
|
+ if self.runtime_target.is_some() {
|
|
+ eprintln!("init: runtime target already set, ignoring {}", unit_id.0);
|
|
+ return;
|
|
+ }
|
|
+ if !self.units.contains_key(&unit_id) {
|
|
+ eprintln!("init: runtime target {} not found in unit store", unit_id.0);
|
|
+ return;
|
|
+ }
|
|
self.runtime_target = Some(unit_id);
|
|
}
|
|
|
|
@@ -85,8 +91,10 @@ impl UnitStore {
|
|
let unit = self.load_single_unit(unit_id, errors);
|
|
if let Some(unit) = unit {
|
|
loaded_units.push(unit.clone());
|
|
- for dep in &self.unit(&unit).info.requires_weak {
|
|
- pending_units.push(dep.clone());
|
|
+ if let Some(u) = self.unit(&unit) {
|
|
+ for dep in &u.info.requires_weak {
|
|
+ pending_units.push(dep.clone());
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
@@ -94,12 +102,12 @@ impl UnitStore {
|
|
loaded_units
|
|
}
|
|
|
|
- pub fn unit(&self, unit: &UnitId) -> &Unit {
|
|
- self.units.get(unit).unwrap()
|
|
+ pub fn unit(&self, unit: &UnitId) -> Option<&Unit> {
|
|
+ self.units.get(unit)
|
|
}
|
|
|
|
- pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit {
|
|
- self.units.get_mut(unit).unwrap()
|
|
+ pub fn unit_mut(&mut self, unit: &UnitId) -> Option<&mut Unit> {
|
|
+ self.units.get_mut(unit)
|
|
}
|
|
}
|
|
|
|
@@ -180,7 +188,7 @@ impl Unit {
|
|
) -> io::Result<Self> {
|
|
let config = fs::read_to_string(config_path)?;
|
|
|
|
- let Some(ext) = config_path.extension().map(|ext| ext.to_str().unwrap()) else {
|
|
+ let Some(ext) = config_path.extension().and_then(|ext| ext.to_str()) else {
|
|
let script = Script::from_str(&config, errors)?;
|
|
return Ok(Unit {
|
|
id,
|