#[cfg(unix)]
use std::ffi::{OsStr, OsString};
#[cfg(unix)]
use std::io::Read;
#[cfg(unix)]
use std::os::fd::RawFd;
#[cfg(unix)]
use std::path::{Path, PathBuf};
#[cfg(unix)]
use std::sync::OnceLock;
#[cfg(unix)]
use std::sync::atomic::{AtomicBool, Ordering};
pub const ROLE_MARKER: &str = "__camel-bridge-watchdog";
#[cfg(unix)]
const ACK_BYTE: u8 = 0x01;
pub const WATCHDOG_REFUSAL_EXIT: i32 = 74;
#[cfg(unix)]
const BAD_ARGV_EXIT: i32 = 64;
#[cfg(unix)]
const ACK_WRITE_EXIT: i32 = 65;
#[cfg(unix)]
const AFTER_KILL_EXIT: i32 = 137;
#[cfg(unix)]
static HOOK_INSTALLED: AtomicBool = AtomicBool::new(false);
#[cfg(unix)]
static EXE: OnceLock<PathBuf> = OnceLock::new();
pub fn maybe_run() {
#[cfg(unix)]
{
let args: Vec<OsString> = std::env::args_os().skip(1).collect();
if let Ok(exe) = std::env::current_exe() {
let _ = EXE.set(exe);
}
HOOK_INSTALLED.store(true, Ordering::Release);
match classify_args(&args) {
RoleInvocation::NotRole => {}
RoleInvocation::Role(ack_fd) => run_role(ack_fd),
RoleInvocation::MissingAckFd | RoleInvocation::InvalidAckFd => {
std::process::exit(BAD_ARGV_EXIT);
}
}
}
}
#[cfg(unix)]
pub(crate) fn hook_executable() -> Option<&'static Path> {
if !HOOK_INSTALLED.load(Ordering::Acquire) {
return None;
}
EXE.get().map(PathBuf::as_path)
}
#[cfg(unix)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RoleInvocation {
NotRole,
Role(RawFd),
MissingAckFd,
InvalidAckFd,
}
#[cfg(unix)]
fn classify_args(args: &[OsString]) -> RoleInvocation {
if args.first().map(OsString::as_os_str) != Some(OsStr::new(ROLE_MARKER)) {
return RoleInvocation::NotRole;
}
match args.get(1).and_then(|arg| arg.to_str()) {
Some(text) => match text.parse::<RawFd>() {
Ok(fd) if fd >= 0 => RoleInvocation::Role(fd),
_ => RoleInvocation::InvalidAckFd,
},
None => RoleInvocation::MissingAckFd,
}
}
#[cfg(unix)]
fn refuses_when_not_group_leader() -> bool {
let pgid = unsafe { libc::getpgrp() };
let pid = unsafe { libc::getpid() };
pgid != pid
}
#[cfg(unix)]
fn run_role(ack_fd: RawFd) -> ! {
if refuses_when_not_group_leader() {
std::process::exit(WATCHDOG_REFUSAL_EXIT);
}
unsafe {
libc::signal(libc::SIGTERM, libc::SIG_IGN);
}
let byte = ACK_BYTE;
let written = unsafe { libc::write(ack_fd, (&byte as *const u8).cast(), 1) };
if written != 1 {
std::process::exit(ACK_WRITE_EXIT);
}
let mut sink = Vec::new();
let _ = std::io::stdin().read_to_end(&mut sink);
unsafe {
libc::kill(0, libc::SIGKILL);
}
std::process::exit(AFTER_KILL_EXIT);
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
fn args(values: &[&str]) -> Vec<OsString> {
values.iter().map(OsString::from).collect()
}
#[test]
fn non_role_argv_is_not_a_role_invocation() {
assert_eq!(
classify_args(&args(&["--test-threads", "1", "--nocapture"])),
RoleInvocation::NotRole
);
assert_eq!(classify_args(&[]), RoleInvocation::NotRole);
assert_eq!(
classify_args(&args(&["--nocapture", ROLE_MARKER, "9"])),
RoleInvocation::NotRole
);
}
#[test]
fn role_argv_parses_the_ack_fd() {
assert_eq!(
classify_args(&args(&[ROLE_MARKER, "9"])),
RoleInvocation::Role(9)
);
assert_eq!(
classify_args(&args(&[ROLE_MARKER, "0"])),
RoleInvocation::Role(0)
);
}
#[test]
fn role_marker_parsing_rejects_missing_ack_fd() {
assert_eq!(
classify_args(&args(&[ROLE_MARKER])),
RoleInvocation::MissingAckFd
);
}
#[test]
fn role_marker_parsing_rejects_unusable_ack_fd() {
assert_eq!(
classify_args(&args(&[ROLE_MARKER, "not-a-fd"])),
RoleInvocation::InvalidAckFd
);
assert_eq!(
classify_args(&args(&[ROLE_MARKER, "-1"])),
RoleInvocation::InvalidAckFd
);
}
#[test]
fn harness_is_not_a_group_leader_so_the_role_refuses() {
assert!(
refuses_when_not_group_leader(),
"test harness unexpectedly leads its own process group; \
the refuse-to-arm precondition is no longer exercised"
);
}
}