Skip to main content

systemprompt_loader/subprocess/
linux.rs

1//! Linux backend for child supervision: the `prctl` parent-death signal plus
2//! the `/proc`-backed identity and zombie checks.
3//!
4//! Copyright (c) systemprompt.io — Business Source License 1.1.
5//! See <https://systemprompt.io> for licensing details.
6
7use std::process::Command;
8
9#[expect(
10    unsafe_code,
11    reason = "std::os::unix::process::CommandExt::pre_exec is an unsafe fn; there is no safe way \
12              to run code in the forked child before exec, and the parent-death signal must be \
13              armed there to cover children that never opt in"
14)]
15pub(super) fn arm_parent_death_signal(cmd: &mut Command) {
16    use std::os::unix::process::CommandExt;
17
18    let supervisor = std::process::id();
19
20    // SAFETY: the closure runs in the forked child between `fork` and `execve`,
21    // where only async-signal-safe calls are permitted. `prctl`, `getppid`, and
22    // `_exit` are all on that list; nothing here allocates, locks, or logs.
23    unsafe {
24        cmd.pre_exec(move || {
25            if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) != 0 {
26                return Err(std::io::Error::last_os_error());
27            }
28            // Why: Linux does not deliver the parent-death signal if the parent died before
29            // `PR_SET_PDEATHSIG` was installed.
30            if libc::getppid() != supervisor as libc::pid_t {
31                libc::_exit(0);
32            }
33            Ok(())
34        });
35    }
36}
37
38#[must_use]
39pub fn live_pid_is_subprocess(pid: u32, name_key: &str, service_name: &str) -> bool {
40    match std::fs::read(format!("/proc/{pid}/environ")) {
41        Ok(environ) => systemprompt_models::subprocess::environ_identifies_child(
42            &environ,
43            name_key,
44            service_name,
45        ),
46        Err(e) => {
47            tracing::warn!(pid, error = %e, "Could not read process environ to verify child identity");
48            false
49        },
50    }
51}
52
53#[must_use]
54pub fn is_zombie(pid: u32) -> bool {
55    let Ok(stat) = std::fs::read_to_string(format!("/proc/{pid}/stat")) else {
56        return false;
57    };
58    // Why: Linux `/proc/<pid>/stat` permits spaces and `)` inside the parenthesised
59    // comm field.
60    let Some((_, after_comm)) = stat.rsplit_once(')') else {
61        return false;
62    };
63    after_comm.split_whitespace().next() == Some("Z")
64}