blob: b655827fb758a9abd0374cdbf6e8ddb99a43e628 [file] [log] [blame]
// 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<std::io::Error> 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<PathBuf, EarlyExit> {
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: ";
if let Some((_, suffix)) = line.split_once(prefix) {
if let Some((found_path, _)) = suffix.rsplit_once("]") {
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()))
}
}