use anyhow::Context as _;
pub(crate) fn invoked_literally(argv: &[String]) -> bool {
argv.get(1).map(String::as_str)
== Some(trusty_mpm::core::spawn_disclaim::PANE_DISCLAIM_SUBCOMMAND)
}
pub(crate) fn run(argv: Vec<String>) -> anyhow::Result<()> {
let mut it = argv.into_iter();
let program = it
.next()
.context("internal-spawn-disclaimed requires a program to launch")?;
let rest: Vec<String> = it.collect();
let mut cmd = std::process::Command::new(&program);
cmd.args(&rest);
let status = spawn_and_wait(&mut cmd, &program)?.0;
std::process::exit(status);
}
pub(crate) fn run_launch_spec(path: &std::path::Path) -> anyhow::Result<()> {
let spec = trusty_mpm::runtime::launch_spec::LaunchSpec::consume(path)
.with_context(|| "managed launch aborted: its launch spec could not be used")?;
trusty_mpm::runtime::launch_spec::LaunchSpec::mark_started(path, &spec.launch_id);
let mut cmd = spec.to_command();
let started = std::time::Instant::now();
let (status, code) = spawn_and_wait(&mut cmd, &spec.program)?;
println!(
"{}",
trusty_mpm::runtime::launch_report::report_exit(code, started.elapsed())
);
std::process::exit(status);
}
fn spawn_and_wait(
cmd: &mut std::process::Command,
program: &str,
) -> anyhow::Result<(i32, Option<i32>)> {
#[cfg(target_os = "macos")]
unsafe {
libc::signal(libc::SIGINT, libc::SIG_IGN);
libc::signal(libc::SIGQUIT, libc::SIG_IGN);
}
let status = trusty_mpm::core::spawn_disclaim::disclaimed_status(cmd)
.with_context(|| format!("failed to spawn `{program}` (disclaimed)"))?;
Ok((status.code().unwrap_or(127), status.code()))
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_LAUNCH_ID: &str = "aaaabbbbccccddddeeeeffff00001111";
fn spec_naming(dir: &std::path::Path, program: &str) -> std::path::PathBuf {
let spec = trusty_mpm::runtime::launch_spec::LaunchSpec {
session_id: "11111111-2222-3333-4444-555555555555".to_owned(),
launch_id: TEST_LAUNCH_ID.to_owned(),
cwd: dir.to_path_buf(),
program: program.to_owned(),
args: Vec::new(),
env_unset: Vec::new(),
env_set: Vec::new(),
};
spec.write_in(dir).expect("write the spec")
}
#[test]
fn run_launch_spec_reports_a_program_that_cannot_be_spawned() {
let dir = tempfile::tempdir().expect("tempdir");
let path = spec_naming(
dir.path(),
&dir.path().join("no-such-claude").to_string_lossy(),
);
let err = run_launch_spec(&path).expect_err("an unspawnable program must error");
assert!(
err.to_string().contains("failed to spawn"),
"the pane must be told the program could not start: {err:#}"
);
}
#[test]
fn run_launch_spec_marks_started_before_it_spawns() {
let dir = tempfile::tempdir().expect("tempdir");
let path = spec_naming(
dir.path(),
&dir.path().join("no-such-claude").to_string_lossy(),
);
let _ = run_launch_spec(&path);
let marker = trusty_mpm::runtime::launch_spec::LaunchSpec::started_marker_in(
dir.path(),
TEST_LAUNCH_ID,
);
assert!(
marker.exists(),
"the sentinel must record that the shell reached the shim: {}",
marker.display()
);
}
#[test]
fn run_rejects_empty_argv() {
let err = run(Vec::new()).expect_err("empty argv must error");
assert!(
err.to_string().contains("requires a program"),
"unexpected error: {err}"
);
}
#[test]
fn invoked_literally_accepts_exact_name() {
let argv = vec![
"tm".to_string(),
"internal-spawn-disclaimed".to_string(),
"claude".to_string(),
"-p".to_string(),
];
assert!(invoked_literally(&argv));
}
#[test]
fn invoked_literally_rejects_abbreviated_prefixes() {
for prefix in ["int", "inte", "internal"] {
let argv = vec!["tm".to_string(), prefix.to_string(), "claude".to_string()];
assert!(
!invoked_literally(&argv),
"abbreviated prefix {prefix:?} must not pass the literal-invocation guard"
);
}
}
#[test]
fn invoked_literally_rejects_missing_token() {
let argv = vec!["tm".to_string()];
assert!(!invoked_literally(&argv));
}
#[test]
fn run_launch_spec_rejects_a_missing_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let err = run_launch_spec(&dir.path().join("absent.json"))
.expect_err("a missing spec must abort the launch");
assert!(
format!("{err:#}").contains("managed launch aborted"),
"the pane must be told what happened: {err:#}"
);
}
#[test]
fn run_launch_spec_rejects_a_corrupt_spec() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("corrupt.json");
std::fs::write(&path, b"{\"session_id\":").expect("seed");
let err = run_launch_spec(&path).expect_err("a corrupt spec must abort the launch");
assert!(
format!("{err:#}").contains("managed launch aborted"),
"the pane must be told what happened: {err:#}"
);
}
}