-
-
Notifications
You must be signed in to change notification settings - Fork 50
/
linux.rs
159 lines (138 loc) · 4.57 KB
/
linux.rs
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use std::{
process::{Output, Stdio},
str,
};
use tokio::{process::Command, task};
#[cfg(feature = "tracing")]
use tracing::debug;
pub(super) async fn detect_targets(target: String) -> Vec<String> {
let (prefix, postfix) = target
.rsplit_once('-')
.expect("unwrap: target always has a -");
let (abi, libc) = if let Some(abi) = postfix.strip_prefix("musl") {
(abi, Libc::Musl)
} else if let Some(abi) = postfix.strip_prefix("gnu") {
(abi, Libc::Gnu)
} else if let Some(abi) = postfix.strip_prefix("android") {
(abi, Libc::Android)
} else {
(postfix, Libc::Unknown)
};
let musl_fallback_target = || format!("{prefix}-{}{abi}", "musl");
match libc {
// guess_host_triple cannot detect whether the system is using glibc,
// musl libc or other libc.
//
// On Alpine, you can use `apk add gcompat` to install glibc
// and run glibc programs.
//
// As such, we need to launch the test ourselves.
Libc::Gnu | Libc::Musl => {
let cpu_arch = target
.split_once('-')
.expect("unwrap: target always has a - for cpu_arch")
.0;
let cpu_arch_suffix = cpu_arch.replace('_', "-");
let handles: Vec<_> = [
format!("/lib/ld-linux-{cpu_arch_suffix}.so.2"),
format!("/lib/{cpu_arch}-linux-gnu/ld-linux-{cpu_arch_suffix}.so.2"),
format!("/usr/lib/{cpu_arch}-linux-gnu/ld-linux-{cpu_arch_suffix}.so.2"),
]
.into_iter()
.map(|p| AutoAbortHandle(tokio::spawn(is_gnu_ld(p))))
.collect();
let has_glibc = async move {
for mut handle in handles {
if let Ok(true) = (&mut handle.0).await {
return true;
}
}
false
}
.await;
[
has_glibc.then(|| format!("{cpu_arch}-unknown-linux-gnu{abi}")),
Some(musl_fallback_target()),
]
}
Libc::Android | Libc::Unknown => [Some(target.clone()), Some(musl_fallback_target())],
}
.into_iter()
.flatten()
.collect()
}
async fn is_gnu_ld(cmd: String) -> bool {
get_ld_flavor(&cmd).await == Some(Libc::Gnu)
}
async fn get_ld_flavor(cmd: &str) -> Option<Libc> {
let Output {
status,
stdout,
stderr,
} = match Command::new(cmd)
.arg("--version")
.stdin(Stdio::null())
.output()
.await
{
Ok(output) => output,
Err(_err) => {
#[cfg(feature = "tracing")]
debug!("Running `{cmd} --version`: err={_err:?}");
return None;
}
};
let stdout = String::from_utf8_lossy(&stdout);
let stderr = String::from_utf8_lossy(&stderr);
#[cfg(feature = "tracing")]
debug!("`{cmd} --version`: status={status}, stdout='{stdout}', stderr='{stderr}'");
const ALPINE_GCOMPAT: &str = r#"This is the gcompat ELF interpreter stub.
You are not meant to run this directly.
"#;
if status.success() {
// Executing glibc ldd or /lib/ld-linux-{cpu_arch}.so.1 will always
// succeeds.
stdout.contains("GLIBC").then_some(Libc::Gnu)
} else if status.code() == Some(1) {
// On Alpine, executing both the gcompat glibc and the ldd and
// /lib/ld-musl-{cpu_arch}.so.1 will fail with exit status 1.
if stdout == ALPINE_GCOMPAT {
// Alpine's gcompat package will output ALPINE_GCOMPAT to stdout
Some(Libc::Gnu)
} else if stderr.contains("musl libc") {
// Alpine/s ldd and musl dynlib will output to stderr
Some(Libc::Musl)
} else {
None
}
} else if status.code() == Some(127) {
// On Ubuntu 20.04 (glibc 2.31), the `--version` flag is not supported
// and it will exit with status 127.
let status = Command::new(cmd)
.arg("/bin/true")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.await
.ok()?;
#[cfg(feature = "tracing")]
debug!("`{cmd} --version`: status={status}");
status.success().then_some(Libc::Gnu)
} else {
None
}
}
#[derive(Eq, PartialEq)]
enum Libc {
Gnu,
Musl,
Android,
Unknown,
}
struct AutoAbortHandle<T>(task::JoinHandle<T>);
impl<T> Drop for AutoAbortHandle<T> {
fn drop(&mut self) {
self.0.abort();
}
}