pub const EXEC_VISIBILITY_WAIT: std::time::Duration = std::time::Duration::from_secs(10);
pub const EXEC_VISIBILITY_POLL: std::time::Duration = std::time::Duration::from_millis(2);
pub fn wait_for_exec_visibility(
pid: u32,
expected_argv0_basename: &str,
wait: std::time::Duration,
poll: std::time::Duration,
) -> bool {
let self_cmdline = std::fs::read(format!("/proc/{}/cmdline", std::process::id())).ok();
let deadline = std::time::Instant::now() + wait;
loop {
if !crate::agent::agent_running(pid) {
return false;
}
if let Ok(raw) = std::fs::read(format!("/proc/{pid}/cmdline")) {
let args: Vec<String> = raw
.split(|&byte| byte == 0)
.filter(|arg| !arg.is_empty())
.map(|arg| String::from_utf8_lossy(arg).into_owned())
.collect();
let basename_matches = args
.first()
.and_then(|argv0| crate::agent::argv_basename(argv0))
.is_some_and(|name| name == expected_argv0_basename);
let differs_from_caller = self_cmdline.as_deref() != Some(raw.as_slice());
if basename_matches && differs_from_caller {
return true;
}
}
if std::time::Instant::now() >= deadline {
return false;
}
std::thread::sleep(poll);
}
}
pub use crate::git::{
ALSO_REDIRECTING_GIT_VARS, REPO_LOCAL_GIT_VARS, git_command, hermetic_command,
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wait_for_exec_visibility_detects_a_real_child_and_leaves_it_exec_visible() {
let mut child = std::process::Command::new("sleep")
.arg("5")
.spawn()
.expect("spawn sleep fixture");
let pid = child.id();
let visible = wait_for_exec_visibility(
pid,
"sleep",
std::time::Duration::from_secs(5),
std::time::Duration::from_millis(2),
);
assert!(visible, "a real sleep child must become exec-visible");
let raw = std::fs::read(format!("/proc/{pid}/cmdline"))
.expect("must be able to read the child's cmdline after the barrier returns");
let args: Vec<String> = raw
.split(|&b| b == 0)
.filter(|a| !a.is_empty())
.map(|a| String::from_utf8_lossy(a).into_owned())
.collect();
assert_eq!(
args.first().map(String::as_str),
Some("sleep"),
"after the barrier returns, cmdline must be the child's own argv, not the caller's"
);
let _ = child.kill();
let _ = child.wait();
}
#[test]
fn wait_for_exec_visibility_times_out_bounded_when_it_never_matches() {
let start = std::time::Instant::now();
let visible = wait_for_exec_visibility(
std::process::id(),
"this-basename-can-never-match-anything",
std::time::Duration::from_millis(200),
std::time::Duration::from_millis(2),
);
let elapsed = start.elapsed();
assert!(
!visible,
"a basename that can never match must return false"
);
assert!(
elapsed < std::time::Duration::from_secs(1),
"must return within roughly `wait`, not hang indefinitely — took {elapsed:?}"
);
}
#[test]
fn wait_for_exec_visibility_returns_false_promptly_for_a_dead_pid() {
let start = std::time::Instant::now();
let visible = wait_for_exec_visibility(
0x7FFF_FFFE,
"anything",
std::time::Duration::from_secs(5),
std::time::Duration::from_millis(20),
);
let elapsed = start.elapsed();
assert!(!visible, "a dead pid must never be reported exec-visible");
assert!(
elapsed < std::time::Duration::from_secs(1),
"a dead pid must not wait out the full ceiling, took {elapsed:?}"
);
}
#[test]
fn wait_for_exec_visibility_rejects_a_self_match_on_unchanged_cmdline() {
let self_pid = std::process::id();
let raw = std::fs::read(format!("/proc/{self_pid}/cmdline"))
.expect("must be able to read this process's own cmdline");
let self_basename = raw
.split(|&b| b == 0)
.find(|a| !a.is_empty())
.map(|a| String::from_utf8_lossy(a).into_owned())
.and_then(|arg0| {
std::path::Path::new(&arg0)
.file_name()
.and_then(|n| n.to_str())
.map(str::to_string)
})
.expect("must be able to derive this process's own argv[0] basename");
let visible = wait_for_exec_visibility(
self_pid,
&self_basename,
std::time::Duration::from_millis(200),
std::time::Duration::from_millis(2),
);
assert!(
!visible,
"a self-match on unchanged cmdline must never report exec-visible, even though \
argv[0]'s basename matches by construction"
);
}
}