Let init handle daemonization

This way init can track the lifecycle of services in the future. It may
also help with parallel booting in the future.
This commit is contained in:
bjorn3
2026-01-26 21:51:14 +01:00
parent db78254203
commit 6096fffe5d
12 changed files with 92 additions and 72 deletions
Generated
+2
View File
@@ -1109,6 +1109,7 @@ dependencies = [
name = "init"
version = "0.1.0"
dependencies = [
"daemon",
"libredox",
]
@@ -1490,6 +1491,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"common",
"daemon",
"log",
"pcid",
"pico-args",
+1 -1
View File
@@ -1,2 +1,2 @@
hwd
notify hwd
pcid-spawner /etc/pcid/initfs.toml
@@ -1 +1 @@
bcm2835-sdhcid
notify bcm2835-sdhcid
+41 -28
View File
@@ -1,46 +1,59 @@
#![feature(never_type)]
use std::io::{self, PipeWriter, Read, Write};
use std::os::fd::{AsRawFd, FromRawFd};
use std::process::Command;
#[must_use = "Daemon::ready must be called"]
pub struct Daemon {
write_pipe: PipeWriter,
}
fn errno() -> io::Error {
io::Error::last_os_error()
}
impl Daemon {
pub fn new<F: FnOnce(Daemon) -> !>(f: F) -> ! {
let (mut read_pipe, write_pipe) = std::io::pipe().unwrap();
let write_pipe = unsafe {
io::PipeWriter::from_raw_fd(std::env::var("INIT_NOTIFY").unwrap().parse().unwrap())
};
match unsafe { libc::fork() } {
0 => {
drop(read_pipe);
f(Daemon { write_pipe })
}
-1 => return Err(errno()).unwrap(),
_pid => {
drop(write_pipe);
let mut data = [0];
match read_pipe.read_exact(&mut data) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
unsafe { libc::_exit(101) };
}
Err(err) => return Err(err).unwrap(),
};
unsafe { libc::_exit(data[0].into()) };
}
}
f(Daemon { write_pipe })
}
pub fn ready(mut self) {
self.write_pipe.write_all(&[0]).unwrap();
}
pub fn spawn(mut cmd: Command) {
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
// Pass 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 pipe: {}",
io::Error::last_os_error()
);
return;
}
cmd.env("INIT_NOTIFY", format!("{}", write_pipe.as_raw_fd()));
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) {
Ok(()) => {
if data[0] != 0 {
eprintln!("daemon: {cmd:?} failed with {}", data[0]);
}
}
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
eprintln!("daemon: {cmd:?} exited without notifying readiness");
}
Err(err) => {
eprintln!("daemon: failed to wait for {cmd:?}: {err}");
}
}
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ impl Backend for AcpiBackend {
// Spawn acpid
//TODO: pass rxsdt data to acpid?
Command::new("acpid").spawn()?.wait()?;
daemon::Daemon::spawn(Command::new("acpid"));
Ok(Self { rxsdt })
}
+1 -15
View File
@@ -37,21 +37,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
//TODO: launch pcid based on backend information?
// Must launch after acpid but before probe calls /scheme/acpi/symbols
match process::Command::new("pcid").spawn() {
Ok(mut child) => match child.wait() {
Ok(status) => {
if !status.success() {
log::error!("pcid exited with status {}", status);
}
}
Err(err) => {
log::error!("failed to wait for pcid: {}", err);
}
},
Err(err) => {
log::error!("failed to spawn pcid: {}", err);
}
}
daemon::Daemon::spawn(process::Command::new("pcid"));
daemon.ready();
+1
View File
@@ -14,4 +14,5 @@ serde = { version = "1", features = ["derive"] }
toml = "0.5"
common = { path = "../common" }
daemon = { path = "../../daemon" }
pcid = { path = "../pcid" }
+1 -7
View File
@@ -88,13 +88,7 @@ fn main() -> Result<()> {
let channel_fd = handle.into_inner_fd();
command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string());
match command.status() {
Ok(status) if !status.success() => {
log::error!("pcid-spawner: driver {command:?} failed with {status}")
}
Ok(_) => {}
Err(err) => log::error!("pcid-spawner: failed to execute {command:?}: {err}"),
}
daemon::Daemon::spawn(command);
syscall::close(channel_fd as usize).unwrap();
}
+10 -10
View File
@@ -3,26 +3,26 @@
export PATH /scheme/initfs/bin
export RUST_BACKTRACE 1
rtcd
nulld
zerod
randd
notify nulld
notify zerod
notify randd
# Logging
logd
notify logd
stdio /scheme/log
ramfs logging
notify ramfs logging
# Graphics infrastructure
inputd
vesad
notify inputd
notify vesad
unset FRAMEBUFFER_ADDR FRAMEBUFFER_VIRT FRAMEBUFFER_WIDTH FRAMEBUFFER_HEIGHT FRAMEBUFFER_STRIDE
#TODO: unset FRAMEBUFFER1 and beyond?
fbbootlogd
fbcond 2
notify fbbootlogd
notify fbcond 2
# Live disk
# Note: Needs to start before drivers to ensure it gets priority when redoxfs searches for disks
lived
notify lived
# Drivers
run /scheme/initfs/etc/init_drivers.rc
+2
View File
@@ -6,3 +6,5 @@ license = "MIT"
[dependencies]
libredox.workspace = true
daemon = { path = "../daemon" }
+29 -7
View File
@@ -20,12 +20,12 @@ fn switch_stdio(stdio: &str) -> Result<()> {
}
struct InitConfig {
pub log_debug: bool,
pub skip_cmd: Vec<String>,
log_debug: bool,
skip_cmd: Vec<String>,
}
impl InitConfig {
pub fn new() -> Self {
fn new() -> Self {
let log_level = env::var("INIT_LOG_LEVEL").unwrap_or("INFO".into());
let log_debug = matches!(log_level.as_str(), "DEBUG" | "TRACE");
let skip_cmd: Vec<String> = match env::var("INIT_SKIP") {
@@ -40,7 +40,7 @@ impl InitConfig {
}
}
pub fn run(file: &Path, config: &InitConfig) -> Result<()> {
fn run(file: &Path, config: &InitConfig) -> Result<()> {
for line_raw in fs::read_to_string(file)?.lines() {
let line = line_raw.trim();
if line.is_empty() || line.starts_with('#') {
@@ -167,6 +167,11 @@ fn run_command(line: &str, config: &InitConfig) {
eprintln!("init: failed to run nowait: no argument");
return;
};
if config.skip_cmd.contains(&cmd) {
eprintln!("init: skipping '{}'", line);
return;
}
let mut command = Command::new(cmd);
for arg in args {
@@ -178,17 +183,34 @@ fn run_command(line: &str, config: &InitConfig) {
Err(err) => eprintln!("init: failed to execute '{}': {}", line, err),
}
}
_ => {
let mut command = Command::new(cmd.clone());
"notify" => {
let Some(cmd) = args.next() else {
eprintln!("init: failed to run nowait: no argument");
return;
};
if config.skip_cmd.contains(&cmd) {
eprintln!("init: skipping '{}'", line);
return;
}
let mut command = Command::new(&cmd);
for arg in args {
command.arg(arg);
}
daemon::Daemon::spawn(command);
}
_ => {
if config.skip_cmd.contains(&cmd) {
eprintln!("init: skipping '{}'", line);
return;
}
let mut command = Command::new(cmd.clone());
for arg in args {
command.arg(arg);
}
let mut child = match command.spawn() {
Ok(child) => child,
Err(err) => {
@@ -211,7 +233,7 @@ fn run_command(line: &str, config: &InitConfig) {
}
}
pub fn main() {
fn main() {
let init_path = Path::new("/scheme/initfs/etc/init.rc");
let init_config = InitConfig::new();
if let Err(err) = run(&init_path, &init_config) {
+2 -2
View File
@@ -1,3 +1,3 @@
ps2d us
hwd
notify ps2d us
notify hwd
pcid-spawner /scheme/initfs/etc/pcid/initfs.toml