File: current-exe-mismatch.rs

package info (click to toggle)
rustc 1.85.0%2Bdfsg2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 893,176 kB
  • sloc: xml: 158,127; python: 35,830; javascript: 19,497; cpp: 19,002; sh: 17,245; ansic: 13,127; asm: 4,376; makefile: 1,051; lisp: 29; perl: 29; ruby: 19; sql: 11
file content (129 lines) | stat: -rw-r--r-- 3,906 bytes parent folder | download | duplicates (14)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 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 encountered IoError: {:?}", file!(), e);
                panic!();
            }
        }
    } else {
        // println!("{} running child", file!());
        child().unwrap();
    }
}

const VAR: &str = "__THE_TEST_YOU_ARE_LUKE";

#[derive(Debug)]
enum EarlyExit {
    IgnoreTest,
    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);
    }

    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(|_| EarlyExit::IgnoreTest)?;
    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)
}