diff --git a/extensions/reaper/.gitignore b/extensions/reaper/.gitignore new file mode 100644 index 00000000..54b02da4 --- /dev/null +++ b/extensions/reaper/.gitignore @@ -0,0 +1,2 @@ +/target +*.so diff --git a/extensions/reaper/Cargo.lock b/extensions/reaper/Cargo.lock new file mode 100644 index 00000000..29069232 --- /dev/null +++ b/extensions/reaper/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "reaper" +version = "0.1.0" +dependencies = [ + "nix", +] diff --git a/extensions/reaper/Cargo.toml b/extensions/reaper/Cargo.toml new file mode 100644 index 00000000..ca049424 --- /dev/null +++ b/extensions/reaper/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "reaper" +version = "0.1.0" +edition = "2021" + +[dependencies] +nix = { version = "0.29.0", features = ["process"] } diff --git a/extensions/reaper/src/main.rs b/extensions/reaper/src/main.rs new file mode 100644 index 00000000..44bcca7c --- /dev/null +++ b/extensions/reaper/src/main.rs @@ -0,0 +1,87 @@ +use std::{env, ffi::CString, process::exit}; + +use nix::{ + errno::Errno, + sys::{prctl, wait::wait}, + unistd::{execvp, fork, ForkResult}, +}; + +fn main() { + // Parse the arguments that should be passed to the child process + let args = env::args(); + let mut child_args = vec![]; + let mut end_of_args = false; + for arg in args { + if !end_of_args && arg.as_str() == "--" { + end_of_args = true; + continue; + } + if !end_of_args { + continue; + } + child_args.push(arg); + } + + // If "--" was not included as an argument, return an error. + if !end_of_args { + panic!("reaper: no '--' argument was found"); + } + if child_args.is_empty() { + panic!("reaper: no sub-command!"); + } + + // Set the current process to be a subreaper. A subreaper MUST wait + // for all child processes to exit to clean up. + if let Err(e) = prctl::set_child_subreaper(true) { + panic!("reaper: failed to set child subreaper: {e:?}"); + } + + // Fork to spawn the requested process and wait for the subprocesses to exit + match unsafe { fork() } { + // Parent process of the fork should wait for all child processes to exit + Ok(ForkResult::Parent { child }) => { + println!("reaper: got child PID: {child}"); + + // Wait for all child processes to exit + loop { + match wait() { + Ok(_) => (), + Err(e) => { + if e == Errno::ECHILD { + break; + } + println!("reaper: got unexpected error: {e}") + } + }; + } + + println!("reaper: no more children exist; exiting"); + exit(0); + } + + // Child process of the fork should execute the requested command + Ok(ForkResult::Child) => { + println!("reaper: executing command: {child_args:?}"); + + // Convert the command to a CString + let cmd = CString::new(child_args.first().unwrap().as_str()).unwrap(); + + // Build the arguments list + let mut c_args = vec![]; + for arg in child_args { + let c_arg = CString::new(arg.as_str()).unwrap(); + c_args.push(c_arg); + } + + // Execute the command + if let Err(e) = execvp(cmd.as_c_str(), c_args.as_slice()) { + panic!("reaper: failed executing command: {e:?}"); + } + } + + // Panic if forking fails + Err(e) => { + panic!("reaper: failed to create fork: {e:?}"); + } + } +}