// rust-lang/rust#101913: when you run your program explicitly via `ld.so`, // `std::env::current_exe` will return the path of *that* program, and not // the Rust program itself. use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::process::Command; mod common; fn main() { if std::env::var(VAR).is_err() { // the parent waits for the child; then we then handle either printing // "test result: ok", "test result: ignored", or panicking. match parent() { Ok(()) => { println!("test result: ok"); } Err(EarlyExit::IgnoreTest(_)) => { println!("test result: ignored"); } Err(EarlyExit::IoError(e)) => { println!("{} parent encoutered IoError: {:?}", file!(), e); panic!(); } } } else { // println!("{} running child", file!()); child().unwrap(); } } const VAR: &str = "__THE_TEST_YOU_ARE_LUKE"; #[derive(Debug)] enum EarlyExit { IgnoreTest(String), IoError(std::io::Error), } impl From for EarlyExit { fn from(e: std::io::Error) -> Self { EarlyExit::IoError(e) } } fn parent() -> Result<(), EarlyExit> { // If we cannot re-exec this test, there's no point in trying to do it. if common::cannot_reexec_the_test() { return Err(EarlyExit::IgnoreTest("(cannot reexec)".into())); } let me = std::env::current_exe().unwrap(); let ld_so = find_interpreter(&me)?; // use interp to invoke current exe, yielding child test. // // (if you're curious what you might compare this against, you can try // swapping in the below definition for `result`, which is the easy case of // not using the ld.so interpreter directly that Rust handled fine even // prior to resolution of rust-lang/rust#101913.) // // let result = Command::new(me).env(VAR, "1").output()?; let result = Command::new(ld_so).env(VAR, "1").arg(&me).output().unwrap(); if result.status.success() { return Ok(()); } println!("stdout:\n{}", String::from_utf8_lossy(&result.stdout)); println!("stderr:\n{}", String::from_utf8_lossy(&result.stderr)); println!("code: {}", result.status); panic!(); } fn child() -> Result<(), EarlyExit> { let bt = backtrace::Backtrace::new(); println!("{:?}", bt); let mut found_my_name = false; let my_filename = file!(); 'frames: for frame in bt.frames() { let symbols = frame.symbols(); if symbols.is_empty() { continue; } for sym in symbols { if let Some(filename) = sym.filename() { if filename.ends_with(my_filename) { // huzzah! found_my_name = true; break 'frames; } } } } assert!(found_my_name); Ok(()) } // we use the `readelf` command to extract the path to the interpreter requested // by our binary. // // if we cannot `readelf` for some reason, or if we fail to parse its output, // then we will just give up on this test (and not treat it as a test failure). fn find_interpreter(me: &Path) -> Result { let result = Command::new("readelf") .arg("-l") .arg(me) .output() .map_err(|_err| EarlyExit::IgnoreTest("readelf invocation failed".into()))?; if result.status.success() { let r = BufReader::new(&result.stdout[..]); for line in r.lines() { let line = line?; let line = line.trim(); let prefix = "[Requesting program interpreter: "; // This could use `line.split_once` and `suffix.rsplit_once` once the MSRV passes 1.52 if let Some(idx) = line.find(prefix) { let (_, suffix) = line.split_at(idx + prefix.len()); if let Some(idx) = suffix.rfind("]") { let (found_path, _ignore_remainder) = suffix.split_at(idx); return Ok(found_path.into()); } } } Err(EarlyExit::IgnoreTest( "could not find interpreter from readelf output".into(), )) } else { Err(EarlyExit::IgnoreTest("readelf returned non-success".into())) } }