Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/util/prereqs.rs
3314 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
use std::cmp::Ordering;
6
7
use crate::constants::QUALITYLESS_SERVER_NAME;
8
use crate::update_service::Platform;
9
use lazy_static::lazy_static;
10
use regex::bytes::Regex as BinRegex;
11
use regex::Regex;
12
use tokio::fs;
13
14
use super::errors::CodeError;
15
16
lazy_static! {
17
static ref LDCONFIG_STDC_RE: Regex = Regex::new(r"libstdc\+\+.* => (.+)").unwrap();
18
static ref LDD_VERSION_RE: BinRegex = BinRegex::new(r"^ldd.*\s(\d+)\.(\d+)(?:\.(\d+))?\s").unwrap();
19
static ref GENERIC_VERSION_RE: Regex = Regex::new(r"^([0-9]+)\.([0-9]+)$").unwrap();
20
static ref LIBSTD_CXX_VERSION_RE: BinRegex =
21
BinRegex::new(r"GLIBCXX_([0-9]+)\.([0-9]+)(?:\.([0-9]+))?").unwrap();
22
static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 28, 0);
23
}
24
25
#[cfg(target_arch = "arm")]
26
lazy_static! {
27
static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 26);
28
}
29
30
#[cfg(not(target_arch = "arm"))]
31
lazy_static! {
32
static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 25);
33
}
34
35
const NIXOS_TEST_PATH: &str = "/etc/NIXOS";
36
37
pub struct PreReqChecker {}
38
39
impl Default for PreReqChecker {
40
fn default() -> Self {
41
Self::new()
42
}
43
}
44
45
impl PreReqChecker {
46
pub fn new() -> PreReqChecker {
47
PreReqChecker {}
48
}
49
50
#[cfg(not(target_os = "linux"))]
51
pub async fn verify(&self) -> Result<Platform, CodeError> {
52
Platform::env_default().ok_or_else(|| {
53
CodeError::UnsupportedPlatform(format!(
54
"{} {}",
55
std::env::consts::OS,
56
std::env::consts::ARCH
57
))
58
})
59
}
60
61
#[cfg(target_os = "linux")]
62
pub async fn verify(&self) -> Result<Platform, CodeError> {
63
let (is_nixos, skip_glibc_checks, or_musl) = tokio::join!(
64
check_is_nixos(),
65
skip_requirements_check(),
66
check_musl_interpreter()
67
);
68
69
let (gnu_a, gnu_b) = if !skip_glibc_checks {
70
tokio::join!(check_glibc_version(), check_glibcxx_version())
71
} else {
72
println!("!!! WARNING: Skipping server pre-requisite check !!!");
73
println!("!!! Server stability is not guaranteed. Proceed at your own risk. !!!");
74
(Ok(true), Ok(true))
75
};
76
77
match (&gnu_a, &gnu_b, is_nixos) {
78
(Ok(true), Ok(true), _) | (_, _, true) => {
79
return Ok(if cfg!(target_arch = "x86_64") {
80
Platform::LinuxX64
81
} else if cfg!(target_arch = "arm") {
82
Platform::LinuxARM32
83
} else {
84
Platform::LinuxARM64
85
});
86
}
87
_ => {}
88
};
89
90
if or_musl.is_ok() {
91
return Ok(if cfg!(target_arch = "x86_64") {
92
Platform::LinuxAlpineX64
93
} else {
94
Platform::LinuxAlpineARM64
95
});
96
}
97
98
let mut errors: Vec<String> = vec![];
99
if let Err(e) = gnu_a {
100
errors.push(e);
101
} else if let Err(e) = gnu_b {
102
errors.push(e);
103
}
104
105
if let Err(e) = or_musl {
106
errors.push(e);
107
}
108
109
let bullets = errors
110
.iter()
111
.map(|e| format!(" - {e}"))
112
.collect::<Vec<String>>()
113
.join("\n");
114
115
Err(CodeError::PrerequisitesFailed {
116
bullets,
117
name: QUALITYLESS_SERVER_NAME,
118
})
119
}
120
}
121
122
#[allow(dead_code)]
123
async fn check_musl_interpreter() -> Result<(), String> {
124
const MUSL_PATH: &str = if cfg!(target_arch = "aarch64") {
125
"/lib/ld-musl-aarch64.so.1"
126
} else {
127
"/lib/ld-musl-x86_64.so.1"
128
};
129
130
if fs::metadata(MUSL_PATH).await.is_err() {
131
return Err(format!(
132
"find {MUSL_PATH}, which is required to run the {QUALITYLESS_SERVER_NAME} in musl environments"
133
));
134
}
135
136
Ok(())
137
}
138
139
/// Checks the glibc version, returns "true" if the default server is required.
140
#[cfg(target_os = "linux")]
141
async fn check_glibc_version() -> Result<bool, String> {
142
#[cfg(target_env = "gnu")]
143
let version = {
144
let v = unsafe { libc::gnu_get_libc_version() };
145
let v = unsafe { std::ffi::CStr::from_ptr(v) };
146
let v = v.to_str().unwrap();
147
extract_generic_version(v)
148
};
149
#[cfg(not(target_env = "gnu"))]
150
let version = {
151
super::command::capture_command("ldd", ["--version"])
152
.await
153
.ok()
154
.and_then(|o| extract_ldd_version(&o.stdout))
155
};
156
157
if let Some(v) = version {
158
return if v >= *MIN_LDD_VERSION {
159
Ok(true)
160
} else {
161
Err(format!(
162
"find GLIBC >= {} (but found {} instead) for GNU environments",
163
*MIN_LDD_VERSION, v
164
))
165
};
166
}
167
168
Ok(false)
169
}
170
171
/// Check for nixos to avoid mandating glibc versions. See:
172
/// https://github.com/microsoft/vscode-remote-release/issues/7129
173
#[allow(dead_code)]
174
async fn check_is_nixos() -> bool {
175
fs::metadata(NIXOS_TEST_PATH).await.is_ok()
176
}
177
178
/// Do not remove this check.
179
/// Provides a way to skip the server glibc requirements check from
180
/// outside the install flow.
181
///
182
/// 1) A system process can create this
183
/// file before the server is downloaded and installed.
184
///
185
/// 2) An environment variable declared in host
186
/// that contains path to a glibc sysroot satisfying the
187
/// minimum requirements.
188
#[cfg(not(windows))]
189
pub async fn skip_requirements_check() -> bool {
190
std::env::var("VSCODE_SERVER_CUSTOM_GLIBC_LINKER").is_ok() ||
191
fs::metadata("/tmp/vscode-skip-server-requirements-check")
192
.await
193
.is_ok()
194
}
195
196
#[cfg(windows)]
197
pub async fn skip_requirements_check() -> bool {
198
false
199
}
200
201
/// Checks the glibc++ version, returns "true" if the default server is required.
202
#[cfg(target_os = "linux")]
203
async fn check_glibcxx_version() -> Result<bool, String> {
204
let mut libstdc_path: Option<String> = None;
205
206
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
207
const DEFAULT_LIB_PATH: &str = "/usr/lib64/libstdc++.so.6";
208
#[cfg(any(target_arch = "x86", target_arch = "arm"))]
209
const DEFAULT_LIB_PATH: &str = "/usr/lib/libstdc++.so.6";
210
const LDCONFIG_PATH: &str = "/sbin/ldconfig";
211
212
if fs::metadata(DEFAULT_LIB_PATH).await.is_ok() {
213
libstdc_path = Some(DEFAULT_LIB_PATH.to_owned());
214
} else if fs::metadata(LDCONFIG_PATH).await.is_ok() {
215
libstdc_path = super::command::capture_command(LDCONFIG_PATH, ["-p"])
216
.await
217
.ok()
218
.and_then(|o| extract_libstd_from_ldconfig(&o.stdout));
219
}
220
221
match libstdc_path {
222
Some(path) => match fs::read(&path).await {
223
Ok(contents) => check_for_sufficient_glibcxx_versions(contents),
224
Err(e) => Err(format!(
225
"validate GLIBCXX version for GNU environments, but could not: {e}"
226
)),
227
},
228
None => Err("find libstdc++.so or ldconfig for GNU environments".to_owned()),
229
}
230
}
231
232
#[cfg(target_os = "linux")]
233
fn check_for_sufficient_glibcxx_versions(contents: Vec<u8>) -> Result<bool, String> {
234
let max_version = LIBSTD_CXX_VERSION_RE
235
.captures_iter(&contents)
236
.map(|m| SimpleSemver {
237
major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())),
238
minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())),
239
patch: m.get(3).map_or(0, |s| u32_from_bytes(s.as_bytes())),
240
})
241
.max();
242
243
if let Some(max_version) = &max_version {
244
if max_version >= &*MIN_CXX_VERSION {
245
return Ok(true);
246
}
247
}
248
249
Err(format!(
250
"find GLIBCXX >= {} (but found {} instead) for GNU environments",
251
*MIN_CXX_VERSION,
252
max_version
253
.as_ref()
254
.map(String::from)
255
.unwrap_or("none".to_string())
256
))
257
}
258
259
#[allow(dead_code)]
260
fn extract_ldd_version(output: &[u8]) -> Option<SimpleSemver> {
261
LDD_VERSION_RE.captures(output).map(|m| SimpleSemver {
262
major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())),
263
minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())),
264
patch: 0,
265
})
266
}
267
268
#[allow(dead_code)]
269
fn extract_generic_version(output: &str) -> Option<SimpleSemver> {
270
GENERIC_VERSION_RE.captures(output).map(|m| SimpleSemver {
271
major: m.get(1).map_or(0, |s| s.as_str().parse().unwrap()),
272
minor: m.get(2).map_or(0, |s| s.as_str().parse().unwrap()),
273
patch: 0,
274
})
275
}
276
277
#[allow(dead_code)]
278
fn extract_libstd_from_ldconfig(output: &[u8]) -> Option<String> {
279
String::from_utf8_lossy(output)
280
.lines()
281
.find_map(|l| LDCONFIG_STDC_RE.captures(l))
282
.and_then(|cap| cap.get(1))
283
.map(|cap| cap.as_str().to_owned())
284
}
285
286
fn u32_from_bytes(b: &[u8]) -> u32 {
287
String::from_utf8_lossy(b).parse::<u32>().unwrap_or(0)
288
}
289
290
#[derive(Debug, Default, PartialEq, Eq)]
291
struct SimpleSemver {
292
major: u32,
293
minor: u32,
294
patch: u32,
295
}
296
297
impl PartialOrd for SimpleSemver {
298
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
299
Some(self.cmp(other))
300
}
301
}
302
303
impl Ord for SimpleSemver {
304
fn cmp(&self, other: &Self) -> Ordering {
305
let major = self.major.cmp(&other.major);
306
if major != Ordering::Equal {
307
return major;
308
}
309
310
let minor = self.minor.cmp(&other.minor);
311
if minor != Ordering::Equal {
312
return minor;
313
}
314
315
self.patch.cmp(&other.patch)
316
}
317
}
318
319
impl From<&SimpleSemver> for String {
320
fn from(s: &SimpleSemver) -> Self {
321
format!("v{}.{}.{}", s.major, s.minor, s.patch)
322
}
323
}
324
325
impl std::fmt::Display for SimpleSemver {
326
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
327
write!(f, "{}", String::from(self))
328
}
329
}
330
331
#[allow(dead_code)]
332
impl SimpleSemver {
333
fn new(major: u32, minor: u32, patch: u32) -> SimpleSemver {
334
SimpleSemver {
335
major,
336
minor,
337
patch,
338
}
339
}
340
}
341
342
#[cfg(test)]
343
mod tests {
344
use super::*;
345
346
#[test]
347
fn test_extract_libstd_from_ldconfig() {
348
let actual = "
349
libstoken.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstoken.so.1
350
libstemmer.so.0d (libc6,x86-64) => /lib/x86_64-linux-gnu/libstemmer.so.0d
351
libstdc++.so.6 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstdc++.so.6
352
libstartup-notification-1.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstartup-notification-1.so.0
353
libssl3.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libssl3.so
354
".to_owned().into_bytes();
355
356
assert_eq!(
357
extract_libstd_from_ldconfig(&actual),
358
Some("/lib/x86_64-linux-gnu/libstdc++.so.6".to_owned()),
359
);
360
361
assert_eq!(
362
extract_libstd_from_ldconfig(&"nothing here!".to_owned().into_bytes()),
363
None,
364
);
365
}
366
367
#[test]
368
fn test_gte() {
369
assert!(SimpleSemver::new(1, 2, 3) >= SimpleSemver::new(1, 2, 3));
370
assert!(SimpleSemver::new(1, 2, 3) >= SimpleSemver::new(0, 10, 10));
371
assert!(SimpleSemver::new(1, 2, 3) >= SimpleSemver::new(1, 1, 10));
372
373
assert!(SimpleSemver::new(1, 2, 3) < SimpleSemver::new(1, 2, 10));
374
assert!(SimpleSemver::new(1, 2, 3) < SimpleSemver::new(1, 3, 1));
375
assert!(SimpleSemver::new(1, 2, 3) < SimpleSemver::new(2, 2, 1));
376
}
377
378
#[test]
379
fn check_for_sufficient_glibcxx_versions() {
380
let actual = "ldd (Ubuntu GLIBC 2.31-0ubuntu9.7) 2.31
381
Copyright (C) 2020 Free Software Foundation, Inc.
382
This is free software; see the source for copying conditions. There is NO
383
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
384
Written by Roland McGrath and Ulrich Drepper."
385
.to_owned()
386
.into_bytes();
387
388
assert_eq!(
389
extract_ldd_version(&actual),
390
Some(SimpleSemver::new(2, 31, 0)),
391
);
392
393
let actual2 = "ldd (GNU libc) 2.40.9000
394
Copyright (C) 2024 Free Software Foundation, Inc.
395
This is free software; see the source for copying conditions. There is NO
396
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
397
Written by Roland McGrath and Ulrich Drepper."
398
.to_owned()
399
.into_bytes();
400
assert_eq!(
401
extract_ldd_version(&actual2),
402
Some(SimpleSemver::new(2, 40, 0)),
403
);
404
}
405
406
}
407
408