135 lines
4.1 KiB
Rust
135 lines
4.1 KiB
Rust
// 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()))
|
|
}
|
|
}
|