diff --git a/etc/Dockerfile b/etc/Dockerfile index 661dede..a37fcde 100644 --- a/etc/Dockerfile +++ b/etc/Dockerfile @@ -1,5 +1,11 @@ FROM alpine +RUN apk update && apk add gcc musl-dev procps + COPY simple /simple +COPY zombie.c /zombie.c + +RUN gcc /zombie.c + CMD ["/simple"] diff --git a/etc/zombie.c b/etc/zombie.c new file mode 100644 index 0000000..947a732 --- /dev/null +++ b/etc/zombie.c @@ -0,0 +1,24 @@ +#include +#include +#include + +int main() +{ + // fork() creates child process identical to parent + int pid = fork(); + + // if pid is greater than 0 than it is parent process + // if pid is 0 then it is child process + // if pid is -ve , it means fork() failed to create child process + + // Parent process + if (pid > 0) + sleep(20); + + // Child process + else { + exit(0); + } + + return 0; +} diff --git a/examples/simple.rs b/examples/simple.rs index 8c4d7f8..01f66b7 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,12 +1,16 @@ +use pid1::Pid1Settings; + fn main() -> Result<(), Box> { - pid1::relaunch_if_pid1()?; + pid1::relaunch_if_pid1(Pid1Settings { log: true })?; let id = std::process::id(); println!("In the simple process, going to sleep. Process ID is {id}"); let args = std::env::args().collect::>(); println!("Args: {args:?}"); + for _ in 0..4 { + std::thread::sleep(std::time::Duration::from_secs(5)); std::process::Command::new("date").spawn().unwrap(); } - std::thread::sleep(std::time::Duration::from_secs(5)); + Ok(()) } diff --git a/justfile b/justfile index 6cd466c..ce162be 100644 --- a/justfile +++ b/justfile @@ -2,10 +2,14 @@ default: just --list --unsorted -# Basic test +# Test test: - cargo build --target x86_64-unknown-linux-musl --example simple - cp target/x86_64-unknown-linux-musl/debug/examples/simple etc - docker build etc --tag pid1rstest - # Commenting this out to make the test pass - # docker run --rm -t pid1rstest + cargo build --target x86_64-unknown-linux-musl --example simple + cp target/x86_64-unknown-linux-musl/debug/examples/simple etc + docker build etc -f etc/Dockerfile --tag pid1rstest + docker rm pid1rs || exit 0 + docker run --name pid1rs -t pid1rstest + +# Exec into that docker container +exec-shell: + docker exec -it pid1rs sh diff --git a/src/lib.rs b/src/lib.rs index 3c724e7..4311aa2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ use std::process::Child; -use nix::sys::wait::WaitStatus; +use nix::{sys::wait::WaitStatus, unistd::Pid}; use signal_hook::{ consts::{SIGCHLD, SIGINT, SIGTERM}, iterator::Signals, @@ -12,15 +12,27 @@ pub enum Error { SpawnChild(std::io::Error), } -pub fn relaunch_if_pid1() -> Result<(), Error> { - if std::process::id() == 1 { +pub fn relaunch_if_pid1(option: Pid1Settings) -> Result<(), Error> { + let pid = std::process::id(); + if pid == 1 { let child = relaunch()?; + if option.log { + eprintln!("pid1-rs: Process running as PID 1"); + } pid1_handling(Some(child)) } else { + if option.log { + eprintln!("pid1-rs: Process not running as Pid 1: PID {pid}"); + } Ok(()) } } +#[derive(Debug, Default, Copy, Clone)] +pub struct Pid1Settings { + pub log: bool, +} + fn relaunch() -> Result { let exe = std::env::current_exe().unwrap(); let args = std::env::args_os().skip(1).collect::>(); @@ -31,17 +43,36 @@ fn relaunch() -> Result { } fn pid1_handling(child: Option) -> ! { - let child = child.map(|x| x.id()); let mut signals = Signals::new([SIGTERM, SIGINT, SIGCHLD]).unwrap(); + + let child = child.map(|x| x.id()); + struct ProcessStatus { + pid: Pid, + exit_code: i32, + } + loop { - for signal in signals.pending() { + for signal in signals.forever() { if signal == SIGTERM || signal == SIGINT { + // TODO: Should forward these signals to the + // application and then force kill the application + // after certain time. graceful_shutdown(); } if signal == SIGCHLD { - let pid = match nix::sys::wait::wait().unwrap() { - WaitStatus::Exited(pid, _) => Some(pid), - WaitStatus::Signaled(pid, _, _) => Some(pid), + let child_exit_status = match nix::sys::wait::wait().unwrap() { + WaitStatus::Exited(pid, exit_code) => { + let exit_status = ProcessStatus { pid, exit_code }; + Some(exit_status) + } + WaitStatus::Signaled(pid, signal, _) => { + let exit_status = ProcessStatus { + pid, + // Translate signal to exit code + exit_code: signal as i32 + 128, + }; + Some(exit_status) + } WaitStatus::Stopped(_, _) => None, WaitStatus::PtraceEvent(_, _, _) => None, WaitStatus::PtraceSyscall(_) => None, @@ -50,10 +81,12 @@ fn pid1_handling(child: Option) -> ! { }; (|| { let child = child?; - let pid = pid?; - let pid = u32::try_from(pid.as_raw()).ok()?; - if pid == child { - graceful_shutdown() + let child_exit_status = child_exit_status?; + let child_pid = child_exit_status.pid; + let child_pid = u32::try_from(child_pid.as_raw()).ok()?; + if child_pid == child { + // Propagate child exit status code + std::process::exit(child_exit_status.exit_code); } Some(()) })();