f31522130f
Build system (5 gaps hardened): - COOKBOOK_OFFLINE defaults to true (fork-mode) - normalize_patch handles diff -ruN format - New 'repo validate-patches' command (25/25 relibc patches) - 14 patched Qt/Wayland/display recipes added to protected list - relibc archive regenerated with current patch chain Boot fixes (fixable): - Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset) - D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped) - redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped) - daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch) - udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async) - relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs - greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait) - greeter-ui: built and linked (header guard unification, sem_compat stubs removed) - mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps - greeter config: removed stale keymapd dependency from display/greeter services - prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified Unfixable (diagnosed, upstream): - i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort - kded6/greeter-ui: page fault 0x8 — Qt library null deref - Thread panics fd != -1 — Rust std library on Redox - DHCP timeout / eth0 MAC — QEMU user-mode networking - hwrngd/thermald — no hardware RNG/thermal in VM - live preload allocation — BIOS memory fragmentation, continues on demand
351 lines
14 KiB
Diff
351 lines
14 KiB
Diff
--- a/init/src/scheduler.rs 2026-05-04 23:20:24.735668104 +0100
|
|
+++ b/init/src/scheduler.rs 2026-05-05 09:36:09.955622123 +0100
|
|
@@ -1,11 +1,14 @@
|
|
-use std::collections::VecDeque;
|
|
+use std::collections::{BTreeSet, VecDeque};
|
|
|
|
use crate::InitConfig;
|
|
-use crate::color::{init_debug, init_error, init_info, status_ok, status_skip};
|
|
+use crate::color::{init_error, status_ok, status_skip};
|
|
use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
|
|
|
|
+const SPAWN_BATCH_SIZE: usize = 50;
|
|
+
|
|
pub struct Scheduler {
|
|
pending: VecDeque<Job>,
|
|
+ completed: BTreeSet<UnitId>,
|
|
}
|
|
|
|
struct Job {
|
|
@@ -21,6 +24,7 @@
|
|
pub fn new() -> Scheduler {
|
|
Scheduler {
|
|
pending: VecDeque::new(),
|
|
+ completed: BTreeSet::new(),
|
|
}
|
|
}
|
|
|
|
@@ -29,10 +33,11 @@
|
|
unit_store: &mut UnitStore,
|
|
unit_id: UnitId,
|
|
) {
|
|
+ let id_str = unit_id.0.clone();
|
|
let mut errors = vec![];
|
|
self.schedule_start(unit_store, unit_id, &mut errors);
|
|
- for error in errors {
|
|
- init_error(&format!("{}", error));
|
|
+ for error in &errors {
|
|
+ init_error(&format!("{}: {}", id_str, error));
|
|
}
|
|
}
|
|
|
|
@@ -55,7 +60,14 @@
|
|
}
|
|
}
|
|
|
|
+ pub fn has_pending(&self) -> bool {
|
|
+ !self.pending.is_empty()
|
|
+ }
|
|
+
|
|
pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) {
|
|
+ let mut defer_count: usize = 0;
|
|
+ let mut spawned_this_step: usize = 0;
|
|
+
|
|
'a: loop {
|
|
let Some(job) = self.pending.pop_front() else {
|
|
return;
|
|
@@ -63,18 +75,44 @@
|
|
|
|
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;
|
|
+ let deps_ok = {
|
|
+ let unit = unit_store.unit(&job.unit);
|
|
+ let mut ok = true;
|
|
+ for dep in &unit.info.requires_weak {
|
|
+ if self.completed.contains(dep) {
|
|
+ continue;
|
|
+ }
|
|
+ if !unit_store.has_unit(dep) {
|
|
+ continue;
|
|
}
|
|
+ let in_pending = self.pending.iter().any(|pj| &pj.unit == dep);
|
|
+ if in_pending {
|
|
+ ok = false;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ ok
|
|
+ };
|
|
+
|
|
+ if !deps_ok {
|
|
+ defer_count += 1;
|
|
+ self.pending.push_back(job);
|
|
+ if defer_count > self.pending.len() + self.completed.len() {
|
|
+ return;
|
|
}
|
|
+ continue 'a;
|
|
}
|
|
|
|
+ defer_count = 0;
|
|
+
|
|
+ let unit = unit_store.unit_mut(&job.unit);
|
|
run(unit, init_config);
|
|
+ self.completed.insert(job.unit);
|
|
+ spawned_this_step += 1;
|
|
+
|
|
+ if spawned_this_step >= SPAWN_BATCH_SIZE {
|
|
+ return;
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
@@ -85,9 +123,6 @@
|
|
match &unit.kind {
|
|
UnitKind::LegacyScript { script } => {
|
|
for cmd in script.clone() {
|
|
- if config.log_debug {
|
|
- init_debug(&format!("running: {cmd:?}"));
|
|
- }
|
|
cmd.run(config);
|
|
}
|
|
}
|
|
@@ -97,18 +132,9 @@
|
|
status_skip(&format!("Skipping {} ({})", desc, service.cmd));
|
|
return;
|
|
}
|
|
- if config.log_debug {
|
|
- init_info(&format!("Starting {} ({})", desc, service.cmd));
|
|
- } else {
|
|
- status_ok(&format!("Started {}", desc));
|
|
- }
|
|
+ status_ok(&format!("Started {}", desc));
|
|
service.spawn(&config.envs);
|
|
}
|
|
- UnitKind::Target {} => {
|
|
- if config.log_debug {
|
|
- init_info(&format!("Reached target {}",
|
|
- unit.info.description.as_ref().unwrap_or(&unit.id.0)));
|
|
- }
|
|
- }
|
|
+ UnitKind::Target {} => {}
|
|
}
|
|
}
|
|
--- a/init/src/service.rs 2026-05-04 23:20:24.736497941 +0100
|
|
+++ b/init/src/service.rs 2026-05-05 08:47:30.832599161 +0100
|
|
@@ -47,73 +47,91 @@
|
|
}
|
|
command.envs(base_envs).envs(&self.envs);
|
|
|
|
- let (mut read_pipe, write_pipe) = io::pipe().unwrap();
|
|
- unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_pipe.into()) };
|
|
-
|
|
- let mut child = match command.spawn() {
|
|
- Ok(child) => child,
|
|
- Err(err) => {
|
|
- status_fail(&format!("failed to execute {:?}: {}", command, err));
|
|
- return;
|
|
- }
|
|
- };
|
|
-
|
|
match &self.type_ {
|
|
- ServiceType::Notify => match read_pipe.read_exact(&mut [0]) {
|
|
- Ok(()) => {}
|
|
- Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
|
|
- init_warn(&format!("{:?} exited without notifying readiness", command));
|
|
- }
|
|
- Err(err) => {
|
|
- init_error(&format!("failed to wait for {:?}: {}", command, err));
|
|
+ ServiceType::OneshotAsync => {
|
|
+ match command.spawn() {
|
|
+ Ok(_child) => {}
|
|
+ Err(err) => {
|
|
+ status_fail(&format!("failed to execute {:?}: {}", command, err));
|
|
+ }
|
|
}
|
|
- },
|
|
- ServiceType::Scheme(scheme) => {
|
|
- let mut new_fd = usize::MAX;
|
|
- loop {
|
|
- match syscall::call_ro(
|
|
- read_pipe.as_raw_fd() as usize,
|
|
- unsafe { plain::as_mut_bytes(&mut new_fd) },
|
|
- syscall::CallFlags::FD | syscall::CallFlags::FD_UPPER,
|
|
- &[],
|
|
- ) {
|
|
- Err(syscall::Error {
|
|
- errno: syscall::EINTR,
|
|
- }) => continue,
|
|
- Ok(0) => {
|
|
+ }
|
|
+ _ => {
|
|
+ let (mut read_pipe, write_pipe) = io::pipe().unwrap();
|
|
+ let write_fd: OwnedFd = write_pipe.into();
|
|
+ let write_raw = write_fd.as_raw_fd();
|
|
+ unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_fd) };
|
|
+
|
|
+ let mut child = match command.spawn() {
|
|
+ Ok(child) => child,
|
|
+ Err(err) => {
|
|
+ let _ = unsafe { libc::close(write_raw) };
|
|
+ drop(read_pipe);
|
|
+ status_fail(&format!("failed to execute {:?}: {}", command, err));
|
|
+ return;
|
|
+ }
|
|
+ };
|
|
+
|
|
+ let _ = unsafe { libc::close(write_raw) };
|
|
+
|
|
+ match &self.type_ {
|
|
+ ServiceType::Notify => match read_pipe.read_exact(&mut [0]) {
|
|
+ Ok(()) => {}
|
|
+ Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
|
|
init_warn(&format!("{:?} exited without notifying readiness", command));
|
|
- return;
|
|
- }
|
|
- Ok(1) => break,
|
|
- Ok(n) => {
|
|
- init_error(&format!("incorrect amount of fds {} returned", n));
|
|
- return;
|
|
}
|
|
Err(err) => {
|
|
init_error(&format!("failed to wait for {:?}: {}", command, err));
|
|
- return;
|
|
}
|
|
- }
|
|
- }
|
|
-
|
|
- let current_namespace_fd = libredox::call::getns().expect("TODO");
|
|
- libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
|
|
- .expect("TODO");
|
|
- }
|
|
- ServiceType::Oneshot => {
|
|
- drop(read_pipe);
|
|
- match child.wait() {
|
|
- Ok(exit_status) => {
|
|
- if !exit_status.success() {
|
|
- status_fail(&format!("{:?} failed with {}", command, exit_status));
|
|
+ },
|
|
+ ServiceType::Scheme(scheme) => {
|
|
+ let mut new_fd = usize::MAX;
|
|
+ loop {
|
|
+ match syscall::call_ro(
|
|
+ read_pipe.as_raw_fd() as usize,
|
|
+ unsafe { plain::as_mut_bytes(&mut new_fd) },
|
|
+ syscall::CallFlags::FD | syscall::CallFlags::FD_UPPER,
|
|
+ &[],
|
|
+ ) {
|
|
+ Err(syscall::Error {
|
|
+ errno: syscall::EINTR,
|
|
+ }) => continue,
|
|
+ Ok(0) => {
|
|
+ init_warn(&format!("{:?} exited without notifying readiness", command));
|
|
+ return;
|
|
+ }
|
|
+ Ok(1) => break,
|
|
+ Ok(n) => {
|
|
+ init_error(&format!("incorrect amount of fds {} returned", n));
|
|
+ return;
|
|
+ }
|
|
+ Err(err) => {
|
|
+ init_error(&format!("failed to wait for {:?}: {}", command, err));
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
}
|
|
+
|
|
+ let current_namespace_fd = libredox::call::getns().expect("TODO");
|
|
+ libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
|
|
+ .expect("TODO");
|
|
}
|
|
- Err(err) => {
|
|
- init_error(&format!("failed to wait for {:?}: {}", command, err))
|
|
+ ServiceType::Oneshot => {
|
|
+ drop(read_pipe);
|
|
+ match child.wait() {
|
|
+ Ok(exit_status) => {
|
|
+ if !exit_status.success() {
|
|
+ status_fail(&format!("{:?} failed with {}", command, exit_status));
|
|
+ }
|
|
+ }
|
|
+ Err(err) => {
|
|
+ init_error(&format!("failed to wait for {:?}: {}", command, err))
|
|
+ }
|
|
+ }
|
|
}
|
|
+ ServiceType::OneshotAsync => unreachable!(),
|
|
}
|
|
}
|
|
- ServiceType::OneshotAsync => {}
|
|
}
|
|
}
|
|
}
|
|
@@ -122,7 +140,6 @@
|
|
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 {
|
|
--- a/init/src/unit.rs 2026-05-04 23:40:38.259604979 +0100
|
|
+++ b/init/src/unit.rs 2026-05-05 08:47:30.832676105 +0100
|
|
@@ -101,6 +101,10 @@
|
|
pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit {
|
|
self.units.get_mut(unit).unwrap()
|
|
}
|
|
+
|
|
+ pub fn has_unit(&self, unit: &UnitId) -> bool {
|
|
+ self.units.contains_key(unit)
|
|
+ }
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
|
|
--- a/init/src/main.rs 2026-05-04 23:20:24.737380326 +0100
|
|
+++ b/init/src/main.rs 2026-05-05 09:09:22.005434517 +0100
|
|
@@ -137,10 +137,16 @@
|
|
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 let Some(skip_val) = init_config.envs.get("INIT_SKIP") {
|
|
+ if let Some(skip_str) = skip_val.to_str() {
|
|
+ if !skip_str.is_empty() {
|
|
+ init_config.skip_cmd = skip_str.split(',').map(|s| s.to_string()).collect();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ {
|
|
let entries = match config::config_for_dirs(&unit_store.config_dirs) {
|
|
Ok(entries) => entries,
|
|
Err(err) => {
|
|
@@ -167,11 +173,14 @@
|
|
libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
|
|
|
|
loop {
|
|
- // Process any pending jobs whose dependencies may now be satisfied.
|
|
- scheduler.step(&mut unit_store, &mut init_config);
|
|
+ if scheduler.has_pending() {
|
|
+ // Reap exited children before processing more services.
|
|
+ let mut status = 0;
|
|
+ while libredox::call::waitpid(0, &mut status, 1).is_ok() {}
|
|
+
|
|
+ scheduler.step(&mut unit_store, &mut init_config);
|
|
+ }
|
|
|
|
- // Non-blocking wait: WNOHANG = 1. Reap any exited children without
|
|
- // blocking, then loop back to process any newly-unblocked pending jobs.
|
|
let mut status = 0;
|
|
match libredox::call::waitpid(0, &mut status, 1) {
|
|
Ok(_pid) => {}
|