I'm writing an init system in Rust, and I have logical entities known as 'units' which basically describe commands to start. Currently, I've defined it like this:
use std::cell::RefCell;
use std::process::{Child, Command};
#[derive(Debug, PartialEq)]
pub enum UnitKind {
Task,
Daemon,
}
#[derive(Debug)]
pub struct Unit<'a> {
pub name: &'a str,
pub cmds: &'a [&'a [&'a str]],
pub kind: UnitKind,
pub active: RefCell<bool>,
}
impl<'a> Unit<'a> {
pub fn load(&self) {
let mut running: Vec<Child> = Vec::new();
for x in self.cmds.iter() {
match Command::new(x[0]).args(&x[1..]).spawn() {
Ok(x) => running.push(x),
Err(e) => eprintln!(
"error starting some command in unit {}, error is:\n{}",
self.name, e
),
}
}
self.active.replace(true);
if self.kind == UnitKind::Task {
// we want to block in this case
for x in &mut running {
x.wait().unwrap();
}
self.active.replace(false);
}
}
}
This works fine, but I'd like to implement an unload
method, which will attempt to gracefully kill every process in the unit. To do that, I need to move running
into the struct, and then I can implement it. However, if I do that, I'm responsible for cleaning it up in the case I use UnitKind::Task
when it exits. On Unix I can tell when a process exits by handling SIGCHLD, so I'm doing that with the nix
crate:
unsafe {
signal::sigaction(
signal::SIGCHLD,
&signal::SigAction::new(
signal::SigHandler::Handler(sigchld),
signal::SaFlags::empty(),
signal::SigSet::empty(),
),
)
.unwrap();
}
where sigchld is defined as:
extern "C" fn sigchld(_: c_int) {
thread::spawn(|| {
// we don't want to waste time in the main thread reaping, instead let's just ask the process scheduler to do this for us via thread::spawn
println!("sigchld recieved");
loop {
match wait::waitpid(
nix::unistd::Pid::from_raw(-1),
Some(wait::WaitPidFlag::WNOHANG),
) {
Ok(_) => {}
Err(e) => match e.as_errno() {
Some(nix::errno::Errno::ECHILD) => break, // nothing more to reap
None => panic!("todo figure out why as_errno returns none here"),
_ => eprintln!("waitpid: unknown error {}", e), // some other error, report a bug, you can get the error code with `errno -l`
},
}
}
});
}
Now here, I want to remove the Child
from whatever Vec it's in in whatever Unit it's in. My first thought is to use global variables but that requires more unsafe, is there an alternative?