1use crate::{
2 cargo_output,
3 report::{self, ScanOptions},
4 simulate::{self, Simulation},
5 version::Version,
6};
7use std::{
8 env,
9 ffi::OsString,
10 fs,
11 io::{BufRead, BufReader, Read},
12 path::{Path, PathBuf},
13 process::{Command, ExitStatus, Stdio},
14};
15
16pub const TARGET: &str = "x86_64-unknown-linux-gnu";
17pub const LOADER: &str = "/lib64/ld-linux-x86-64.so.2";
18const SYSROOT_ENV: &str = "CARGO_LIBC_SYSROOT";
19const GCC_ENV: &str = "CARGO_LIBC_GCC_RUNTIME";
20const CC_ENV: &str = "CARGO_LIBC_CC";
21const LD_ENV: &str = "CARGO_LIBC_LD";
22const DESCRIPTOR_ENV: &str = "CARGO_LIBC_PROVIDER_DESCRIPTOR";
23
24#[derive(Clone, Debug)]
25pub struct Toolchain {
26 pub provider: Option<crate::provider::Provider>,
27 pub sysroot: PathBuf,
28 pub gcc_runtime: PathBuf,
29 pub cc: PathBuf,
30 pub ld: PathBuf,
31 pub ceiling: Version,
32}
33
34#[derive(Debug)]
35pub struct Diagnostics {
36 pub header_version: Version,
37 pub compiler_version: String,
38 pub linker_version: String,
39 pub files: Vec<(String, PathBuf)>,
40}
41
42fn canonical(path: &Path, label: &str) -> Result<PathBuf, String> {
43 fs::canonicalize(path).map_err(|e| format!("cannot resolve {label} {}: {e}", path.display()))
44}
45
46fn inside_file(path: &Path, root: &Path) -> Result<PathBuf, String> {
47 let actual = canonical(path, "target file")?;
48 if !actual.starts_with(root) {
49 return Err(format!(
50 "target file {} resolves outside sysroot {}",
51 path.display(),
52 root.display()
53 ));
54 }
55 if !actual.is_file() {
56 return Err(format!(
57 "target input is not a regular file: {}",
58 actual.display()
59 ));
60 }
61 Ok(actual)
62}
63
64pub fn text_path(path: &Path) -> Result<&str, String> {
65 let text = path
66 .to_str()
67 .ok_or("toolchain paths must be UTF-8 for Cargo's encoded flags")?;
68 if text.contains('\u{1f}') {
69 return Err("toolchain paths cannot contain the Cargo flag separator U+001F".into());
70 }
71 Ok(text)
72}
73
74impl Toolchain {
75 pub fn from_provider(
76 path: &Path,
77 cc: PathBuf,
78 ld: PathBuf,
79 ceiling: Version,
80 ) -> Result<Self, String> {
81 if !cfg!(all(target_os = "linux", target_arch = "x86_64")) {
82 return Err("provider builds require x86-64 Linux".into());
83 }
84
85 let provider = crate::provider::Provider::load(path)?;
86 provider.validate_selection(ceiling)?;
87 provider.validate_files()?;
88 let sysroot = canonical(&provider.sysroot, "provider sysroot")?;
89 let gcc_runtime = canonical(&provider.gcc_startup, "provider GCC startup")?;
90 let cc = canonical(&cc, "Clang driver")?;
91 let ld = canonical(&ld, "linker")?;
92 for path in [
93 &sysroot,
94 &gcc_runtime,
95 &provider.stubs,
96 &provider.compiler_runtime,
97 &cc,
98 &ld,
99 ] {
100 text_path(path)?;
101 }
102 Ok(Self {
103 provider: Some(provider),
104 sysroot,
105 gcc_runtime,
106 cc,
107 ld,
108 ceiling,
109 })
110 }
111 pub fn new(
112 sysroot: PathBuf,
113 gcc_runtime: PathBuf,
114 cc: PathBuf,
115 ld: PathBuf,
116 ceiling: Version,
117 ) -> Result<Self, String> {
118 if !cfg!(all(target_os = "linux", target_arch = "x86_64")) {
119 return Err("the build coordinator currently supports x86-64 Linux hosts only".into());
120 }
121 ceiling.validate_glibc_target()?;
122 let sysroot = canonical(&sysroot, "sysroot")?;
123 let gcc_runtime = canonical(&gcc_runtime, "GCC runtime directory")?;
124 if !sysroot.is_dir() || !gcc_runtime.is_dir() {
125 return Err("sysroot and GCC runtime must be directories".into());
126 }
127 if !gcc_runtime.starts_with(&sysroot) {
128 return Err("the GCC runtime directory must be inside the supplied sysroot".into());
129 }
130 let cc = canonical(&cc, "Clang driver")?;
131 let ld = canonical(&ld, "linker")?;
132 if !cc.is_file() || !ld.is_file() {
133 return Err("compiler and linker paths must identify files".into());
134 }
135 for path in [&sysroot, &gcc_runtime, &cc, &ld] {
136 text_path(path)?;
137 }
138 Ok(Self {
139 provider: None,
140 sysroot,
141 gcc_runtime,
142 cc,
143 ld,
144 ceiling,
145 })
146 }
147
148 pub fn driver_args(&self) -> Vec<OsString> {
149 vec![
150 format!("--target={TARGET}").into(),
151 prefixed_path("--sysroot=", &self.sysroot),
152 prefixed_path("--gcc-install-dir=", &self.gcc_runtime),
153 "--rtlib=libgcc".into(),
154 prefixed_path("-fuse-ld=", &self.ld),
155 ]
156 }
157
158 fn command(&self) -> Command {
159 let mut cmd = Command::new(&self.cc);
160 cmd.args(self.driver_args());
161 remove_search_overrides(&mut cmd);
162 cmd
163 }
164
165 pub fn diagnose(&self) -> Result<Diagnostics, String> {
166 let header = inside_file(&self.sysroot.join("usr/include/features.h"), &self.sysroot)?;
167 let text =
168 fs::read_to_string(&header).map_err(|e| format!("cannot read features.h: {e}"))?;
169 let header_version = header_version(&text)?;
170 if header_version > self.ceiling {
171 return Err(format!("sysroot headers are glibc {header_version}, newer than requested ceiling {}; supply older target development files", self.ceiling));
172 }
173 let compiler_version = tool_version(&self.cc)?;
174 if !compiler_version.to_ascii_lowercase().contains("clang") {
175 return Err(format!("--cc must be a Clang driver supporting --gcc-install-dir; got {compiler_version:?}"));
176 }
177 let linker_version = tool_version(&self.ld)?;
178 let mut files = Vec::new();
179 if let Some(provider) = &self.provider {
180 provider.validate_files()?;
181 if header_version != self.ceiling {
182 return Err(format!(
183 "provider headers must match glibc {} exactly",
184 self.ceiling
185 ));
186 }
187 for name in ["crt1.o", "crti.o", "crtn.o"] {
188 let path = inside_file(&self.sysroot.join("usr/lib64").join(name), &self.sysroot)?;
189 validate_x86_object(&path)?;
190 files.push((name.into(), path));
191 }
192 for name in ["crtbegin.o", "crtend.o"] {
193 let path = inside_file(&self.gcc_runtime.join(name), &self.gcc_runtime)?;
194 validate_x86_object(&path)?;
195 files.push((name.into(), path));
196 }
197 for name in crate::provider::SONAMES {
198 let path = provider.stubs.join(name);
199 validate_x86_object(&path)?;
200 files.push((name.into(), path));
201 }
202 return Ok(Diagnostics {
203 header_version,
204 compiler_version,
205 linker_version,
206 files,
207 });
208 }
209 for name in [
210 "Scrt1.o",
211 "crt1.o",
212 "crti.o",
213 "crtn.o",
214 "crtbeginS.o",
215 "crtendS.o",
216 "libc.so",
217 "libc_nonshared.a",
218 "libgcc_s.so",
219 ] {
220 let output = self
221 .command()
222 .arg(format!("-print-file-name={name}"))
223 .output()
224 .map_err(|e| format!("cannot probe target input {name}: {e}"))?;
225 if !output.status.success() {
226 return Err(format!(
227 "Clang failed while finding {name}: {}",
228 String::from_utf8_lossy(&output.stderr).trim()
229 ));
230 }
231 let value = std::str::from_utf8(&output.stdout)
232 .map_err(|_| "Clang returned a non-UTF-8 input path")?
233 .trim();
234 if value == name || value.is_empty() || !Path::new(value).is_absolute() {
235 return Err(format!(
236 "Clang did not locate target input {name}; returned {value:?}"
237 ));
238 }
239 let path = inside_file(Path::new(value), &self.sysroot)?;
240 if name.ends_with(".o") {
241 validate_x86_object(&path)?;
242 }
243 files.push((name.into(), path));
244 }
245 for name in ["crtbeginS.o", "crtendS.o"] {
247 let expected = inside_file(&self.gcc_runtime.join(name), &self.sysroot)?;
248 let selected = files.iter().find(|(n, _)| n == name).unwrap();
249 if selected.1 != expected {
250 return Err(format!("Clang selected an unexpected {name}"));
251 }
252 }
253 inside_file(
254 &self.sysroot.join(LOADER.trim_start_matches('/')),
255 &self.sysroot,
256 )?;
257 Ok(Diagnostics {
258 header_version,
259 compiler_version,
260 linker_version,
261 files,
262 })
263 }
264
265 pub fn print_diagnostics(&self, info: &Diagnostics) {
266 println!(
267 "Target: {TARGET}; requested glibc ceiling: {}",
268 self.ceiling
269 );
270 println!(
271 "Sysroot: {}; headers: glibc {}",
272 self.sysroot.display(),
273 info.header_version
274 );
275 println!("Compiler: {}", info.compiler_version);
276 println!("Linker: {}", info.linker_version);
277 for (name, path) in &info.files {
278 println!(" {name}: {}", path.display());
279 }
280 println!("Target input probes passed. Full dependency resolution and target runtime behavior remain separate checks.");
281 }
282
283 pub fn rustflags(&self, driver: &Path) -> Result<OsString, String> {
284 Ok(self.rustflag_list(driver)?.join("\u{1f}").into())
285 }
286
287 pub fn rustflag_list(&self, driver: &Path) -> Result<Vec<String>, String> {
288 let mut flags = vec![
291 "-C".to_string(),
292 format!("linker={}", text_path(driver)?),
293 "-C".into(),
294 "linker-flavor=gcc".into(),
295 "-C".to_string(),
296 "linker-features=-lld".into(),
297 "-C".into(),
298 "target-cpu=x86-64".into(),
299 "-C".into(),
300 format!("link-arg=--sysroot={}", text_path(&self.sysroot)?),
301 "-C".into(),
302 format!(
303 "link-arg=--gcc-install-dir={}",
304 text_path(&self.gcc_runtime)?
305 ),
306 "-C".into(),
307 format!("link-arg=-fuse-ld={}", text_path(&self.ld)?),
308 "--cfg".into(),
309 format!("cargo_libc_profile=\"{}\"", self.profile_fingerprint()?),
310 ];
311 if self.provider.is_some() {
312 flags.extend(["-C".into(), "relocation-model=static".into()]);
313 }
314 Ok(flags)
315 }
316
317 fn profile_fingerprint(&self) -> Result<String, String> {
318 let mut hash = 0xcbf29ce484222325u64;
321 let ceiling = self.ceiling.to_string();
322 for item in [
323 text_path(&self.sysroot)?,
324 text_path(&self.gcc_runtime)?,
325 text_path(&self.cc)?,
326 text_path(&self.ld)?,
327 &ceiling,
328 ] {
329 for byte in item.bytes().chain(std::iter::once(0)) {
330 hash = (hash ^ u64::from(byte)).wrapping_mul(0x100000001b3);
331 }
332 }
333 if let Some(provider) = &self.provider {
334 for byte in provider.descriptor.bytes() {
335 hash = (hash ^ u64::from(byte)).wrapping_mul(0x100000001b3);
336 }
337 }
338 Ok(format!("{hash:016x}"))
339 }
340
341 pub fn variables(&self) -> Vec<(&'static str, OsString)> {
343 let mut variables = vec![
344 (SYSROOT_ENV, self.sysroot.clone().into_os_string()),
345 (GCC_ENV, self.gcc_runtime.clone().into_os_string()),
346 (CC_ENV, self.cc.clone().into_os_string()),
347 (LD_ENV, self.ld.clone().into_os_string()),
348 ];
349 if let Some(provider) = &self.provider {
350 variables.push((DESCRIPTOR_ENV, provider.descriptor.clone().into()));
351 }
352 variables
353 }
354
355 pub fn set_environment(&self, cmd: &mut Command) {
356 cmd.env_remove(DESCRIPTOR_ENV);
357 cmd.envs(self.variables());
358 }
359}
360
361fn has_tool_arguments(subcommand: &str) -> bool {
363 matches!(subcommand, "clippy" | "rustc")
364}
365
366pub fn validate_cargo_args(subcommand: &str, args: &[OsString]) -> Result<(), String> {
367 for (i, arg) in args.iter().enumerate() {
368 let text = arg.to_string_lossy();
369 if text == "--" && has_tool_arguments(subcommand) {
370 return validate_tool_args(&args[i + 1..]);
371 }
372 if text == "--target"
373 || text.starts_with("--target=")
374 || text == "--config"
375 || text.starts_with("--config=")
376 || text == "--message-format"
377 || text.starts_with("--message-format=")
378 || text == "--"
379 || text.starts_with('+')
380 {
381 return Err(format!("Cargo argument {text:?} conflicts with this prototype's controlled target configuration"));
382 }
383 }
384 Ok(())
385}
386
387fn validate_tool_args(args: &[OsString]) -> Result<(), String> {
390 for arg in args {
391 let text = arg.to_string_lossy();
392 if text.starts_with("--target")
393 || text.contains("linker")
394 || text.contains("relocation-model")
395 || text.contains("link-arg")
396 {
397 return Err(format!(
398 "compiler argument {text:?} conflicts with the managed target configuration"
399 ));
400 }
401 }
402 Ok(())
403}
404
405pub fn sibling(name: &str) -> Result<PathBuf, String> {
406 let executable = env::current_exe().map_err(|e| e.to_string())?;
407 let path = executable
408 .parent()
409 .ok_or_else(|| format!("cannot locate sibling helper {name}"))?
410 .join(name);
411 if !path.is_file() {
412 return Err(format!(
413 "missing Rust helper {}; install/build all cargo-libc binaries",
414 path.display()
415 ));
416 }
417 Ok(path)
418}
419
420fn toml_string(text: &str) -> String {
422 let mut out = String::from("\"");
423 for c in text.chars() {
424 match c {
425 '"' => out.push_str("\\\""),
426 '\\' => out.push_str("\\\\"),
427 c if c.is_control() => out.push_str(&format!("\\u{:04X}", c as u32)),
428 c => out.push(c),
429 }
430 }
431 out.push('"');
432 out
433}
434
435fn cargo_command(
438 config: &Toolchain,
439 subcommand: &str,
440 cargo_args: &[OsString],
441) -> Result<(Command, PathBuf), String> {
442 validate_cargo_args(subcommand, cargo_args)?;
443 let doctests = matches!(subcommand, "test" | "doc");
446 let mut managed = vec!["RUSTFLAGS", "CARGO_ENCODED_RUSTFLAGS"];
447 if doctests {
448 managed.extend(["RUSTDOCFLAGS", "CARGO_ENCODED_RUSTDOCFLAGS"]);
449 }
450 for key in managed {
451 if env::var_os(key).is_some_and(|value| !value.is_empty()) {
452 return Err(format!("{key} is already set; use a shell without that override so the target flags are explicit"));
453 }
454 }
455 let diagnostic = config.diagnose()?;
456 config.print_diagnostics(&diagnostic);
457 let driver = sibling("libc-linker")?;
458 let (cc, cxx) = crate::cc::wrappers()?;
459 let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
460 let mut cmd = Command::new(cargo);
461 cmd.arg(subcommand).arg("--target").arg(TARGET);
462 config.set_environment(&mut cmd);
463 crate::cc::set_environment(&mut cmd, &cc, &cxx)?;
466 let flags = config.rustflags(&driver)?;
469 println!(
470 "Target C compiler (cc crate, CMake): {} (C only; host units keep the host compiler)",
471 cc.display()
472 );
473 if doctests {
474 cmd.env("CARGO_ENCODED_RUSTDOCFLAGS", &flags);
475 }
476 cmd.env("CARGO_ENCODED_RUSTFLAGS", flags);
477 cmd.env_remove("CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS");
478 Ok((cmd, driver))
479}
480
481fn print_command(cmd: &Command) {
482 println!(
483 "Cargo command: {:?} {:?}",
484 cmd.get_program(),
485 cmd.get_args().collect::<Vec<_>>()
486 );
487}
488
489pub fn build(
490 config: &Toolchain,
491 cargo_args: &[OsString],
492 artifact: &Path,
493 scan: Option<&ScanOptions>,
494 dry_run: bool,
495) -> Result<i32, String> {
496 let (mut cmd, driver) = cargo_command(config, "build", cargo_args)?;
497 cmd.arg("--message-format=json-render-diagnostics")
498 .args(cargo_args);
499 print_command(&cmd);
500 println!("Rust linker driver: {}", driver.display());
501 println!(
502 "Artifact selected for direct ELF verification: {}",
503 artifact.display()
504 );
505 if dry_run {
506 return Ok(0);
507 }
508 let (status, artifacts) = run_cargo(&mut cmd)?;
509 if !status.success() {
510 return Ok(status.code().unwrap_or(1));
511 }
512 let selected = canonical(artifact, "selected executable")?;
513 if !artifacts
514 .iter()
515 .any(|path| fs::canonicalize(path).is_ok_and(|actual| actual == selected))
516 {
517 return Err(format!("{} was not reported as an executable by this Cargo invocation; select an executable it actually built or found fresh", artifact.display()));
518 }
519 let passed = verify_executable(config, artifact, scan)?;
520 Ok(if passed { 0 } else { 2 })
521}
522
523fn verify_executable(
526 config: &Toolchain,
527 artifact: &Path,
528 scan: Option<&ScanOptions>,
529) -> Result<bool, String> {
530 let bytes = report::read_bytes(artifact)?;
531 let info = crate::elf::inspect(&bytes)?;
532 let mut assessment = report::assess(&info, Some(config.ceiling));
533 if info.interpreter.as_deref() != Some(LOADER) {
534 assessment.errors.push(format!(
535 "expected dynamically linked executable with interpreter {LOADER:?}; got {:?}",
536 info.interpreter
537 ));
538 }
539 if config.sysroot != Path::new("/") {
540 for paths in info.rpath.iter().chain(&info.runpath) {
541 for entry in paths.split(':') {
542 if Path::new(entry).starts_with(&config.sysroot) {
543 assessment.errors.push(format!(
544 "build-time sysroot leaked into runtime library search path {entry:?}"
545 ));
546 }
547 }
548 }
549 }
550 let scan = scan
552 .map(|options| {
553 let ceiling = options.distro.is_none().then_some(config.ceiling);
554 report::scan(&bytes, ceiling, options).map(|(_, scan)| scan)
555 })
556 .transpose()?;
557 println!(
558 "{}",
559 report::render(
560 artifact,
561 &info,
562 &assessment,
563 Some(config.ceiling),
564 scan.as_ref(),
565 false
566 )
567 );
568 Ok(assessment.errors.is_empty() && scan.as_ref().is_none_or(report::Scan::passed))
569}
570
571pub fn cargo_passthrough(
575 config: &Toolchain,
576 subcommand: &str,
577 cargo_args: &[OsString],
578 dry_run: bool,
579) -> Result<i32, String> {
580 let (mut cmd, driver) = cargo_command(config, subcommand, cargo_args)?;
581 cmd.args(cargo_args);
582 print_command(&cmd);
583 let links = subcommand == "rustc";
584 println!(
585 "Rust linker driver: {}{}",
586 driver.display(),
587 if links {
588 ""
589 } else {
590 " (configured; this command does not link)"
591 }
592 );
593 if dry_run {
594 return Ok(0);
595 }
596 let status = cmd.status().map_err(|e| format!("cannot run Cargo: {e}"))?;
597 if status.success() {
598 if subcommand == "check" {
599 println!(
600 "Type check passed for glibc {} target flags. Nothing was linked: build and inspect the executable to check its requirements.",
601 config.ceiling
602 );
603 } else if links {
604 println!(
605 "Built with glibc {} target flags. The output was not checked: run cargo libc inspect on it.",
606 config.ceiling
607 );
608 }
609 }
610 Ok(status.code().unwrap_or(1))
611}
612
613pub fn install(
617 config: &Toolchain,
618 cargo_args: &[OsString],
619 scan: Option<&ScanOptions>,
620 dry_run: bool,
621) -> Result<i32, String> {
622 let (mut cmd, driver) = cargo_command(config, "install", cargo_args)?;
623 cmd.arg("--message-format=json-render-diagnostics");
624 let explicit = env::var_os("CARGO_TARGET_DIR").is_some_and(|v| !v.is_empty())
627 || cargo_args.iter().any(|arg| {
628 let text = arg.to_string_lossy();
629 text == "--target-dir" || text.starts_with("--target-dir=")
630 });
631 let temporary = (!explicit).then(|| {
632 let nanos = std::time::SystemTime::now()
633 .duration_since(std::time::UNIX_EPOCH)
634 .map_or(0, |d| d.as_nanos());
635 env::temp_dir().join(format!("cargo-libc-install-{}-{nanos}", std::process::id()))
636 });
637 if let Some(dir) = &temporary {
638 cmd.arg("--target-dir").arg(dir);
639 }
640 cmd.args(cargo_args);
641 print_command(&cmd);
642 println!("Rust linker driver: {}", driver.display());
643 println!(
644 "Every executable this install builds is checked against glibc {}.",
645 config.ceiling
646 );
647 if dry_run {
648 return Ok(0);
649 }
650 let result = (|| {
651 let (status, artifacts) = run_cargo(&mut cmd)?;
652 if !status.success() {
653 return Ok(status.code().unwrap_or(1));
654 }
655 if artifacts.is_empty() {
656 println!("Cargo built no executable (already installed?); nothing was checked.");
657 return Ok(0);
658 }
659 let mut passed = true;
660 for artifact in &artifacts {
661 passed &= verify_executable(config, artifact, scan)?;
662 }
663 if !passed {
664 println!("An installed executable failed the check above; remove it or rebuild for a newer target.");
665 }
666 Ok(if passed { 0 } else { 2 })
667 })();
668 if let Some(dir) = &temporary {
669 let _ = fs::remove_dir_all(dir);
670 }
671 result
672}
673
674#[derive(Clone, Copy, Debug, PartialEq, Eq)]
676pub enum EnvFormat {
677 Shell,
679 Cargo,
681}
682
683fn shell_quote(text: &str) -> String {
684 format!("'{}'", text.replace('\'', "'\\''"))
685}
686
687pub fn plain_cargo(
690 config: &Toolchain,
691 simulation: &Simulation,
692 format: EnvFormat,
693) -> Result<String, String> {
694 simulation.plan()?;
695 config.diagnose()?;
696 let driver = sibling("libc-linker")?;
697 let runner = sibling("libc-runner")?;
698 let (cc, cxx) = crate::cc::wrappers()?;
699 let flags = config.rustflag_list(&driver)?;
700 let runner = text_path(&runner)?.to_string();
701 let mut variables: Vec<(String, String)> = Vec::new();
702 let utf8 = |key: &str, value: OsString| {
703 value
704 .into_string()
705 .map_err(|_| format!("{key} must be UTF-8 for plain Cargo configuration"))
706 };
707 for (key, value) in config.variables() {
708 variables.push((key.to_string(), utf8(key, value)?));
709 }
710 for (key, value) in crate::cc::variables(&cc, &cxx, format == EnvFormat::Shell)? {
711 let value = utf8(&key, value)?;
712 variables.push((key, value));
713 }
714 for (key, value) in simulation.variables(config.ceiling) {
715 if let Some(value) = value {
716 variables.push((key.to_string(), value));
717 }
718 }
719 let triple_key = TARGET.to_uppercase().replace('-', "_");
720 let mut out = format!(
721 "# cargo-libc {}: glibc {} for {TARGET}, generated for this machine.\n\
722 # Use with: cargo build --target {TARGET} (always pass --target, so build\n\
723 # scripts and proc macros stay native). RUSTFLAGS or CARGO_ENCODED_RUSTFLAGS in\n\
724 # the environment replace these flags. Nothing checks the output: run\n\
725 # cargo libc inspect FILE --max-glibc {}. Regenerate after updating\n\
726 # cargo-libc or the provider.\n",
727 env!("CARGO_PKG_VERSION"),
728 config.ceiling,
729 config.ceiling
730 );
731 match format {
732 EnvFormat::Shell => {
733 if let Some(flag) = flags
734 .iter()
735 .chain([&runner])
736 .find(|f| f.chars().any(char::is_whitespace))
737 {
738 return Err(format!("{flag:?} contains whitespace, which Cargo's flag variables cannot hold; use --format cargo"));
739 }
740 let joined = flags.join(" ");
741 out.push_str(&format!(
742 "export CARGO_TARGET_{triple_key}_RUSTFLAGS={}\n",
743 shell_quote(&joined)
744 ));
745 out.push_str(&format!("export RUSTDOCFLAGS={}\n", shell_quote(&joined)));
746 out.push_str(&format!(
747 "export CARGO_TARGET_{triple_key}_RUNNER={}\n",
748 shell_quote(&runner)
749 ));
750 for (key, value) in variables {
751 out.push_str(&format!("export {key}={}\n", shell_quote(&value)));
752 }
753 }
754 EnvFormat::Cargo => {
755 let list = |items: &[String]| {
756 items
757 .iter()
758 .map(|f| toml_string(f))
759 .collect::<Vec<_>>()
760 .join(", ")
761 };
762 out.push_str(&format!(
763 "\n[target.{TARGET}]\nrustflags = [{}]\nrunner = [{}]\n\n[build]\nrustdocflags = [{}]\n\n[env]\n",
764 list(&flags),
765 toml_string(&runner),
766 list(&flags)
767 ));
768 for (key, value) in variables {
769 out.push_str(&format!("{key} = {}\n", toml_string(&value)));
770 }
771 }
772 }
773 Ok(out)
774}
775
776pub fn test_or_run(
779 config: &Toolchain,
780 subcommand: &str,
781 cargo_args: &[OsString],
782 simulation: &Simulation,
783 dry_run: bool,
784) -> Result<i32, String> {
785 let (plan, _) = simulation.plan()?;
786 let (mut cmd, driver) = cargo_command(config, subcommand, cargo_args)?;
787 let runner = sibling("libc-runner")?;
788 cmd.arg("--config").arg(format!(
790 "target.{TARGET}.runner=[{}]",
791 toml_string(text_path(&runner)?)
792 ));
793 cmd.args(cargo_args);
794 simulation.set_environment(&mut cmd, config.ceiling);
795 print_command(&cmd);
796 println!("Rust linker driver: {}", driver.display());
797 println!("Target runner: {}", runner.display());
798 println!("Kernel simulation: {}", simulate::describe(&plan));
799 println!(
800 "Each binary is checked against glibc {} before it runs.",
801 config.ceiling
802 );
803 if dry_run {
804 return Ok(0);
805 }
806 let status = cmd.status().map_err(|e| format!("cannot run Cargo: {e}"))?;
807 Ok(status.code().unwrap_or(1))
808}
809
810fn run_cargo(cmd: &mut Command) -> Result<(ExitStatus, Vec<PathBuf>), String> {
811 let mut child = cmd
812 .stdout(Stdio::piped())
813 .spawn()
814 .map_err(|e| format!("cannot run Cargo: {e}"))?;
815 let stdout = child.stdout.take().expect("piped Cargo stdout");
816 let parsed = (|| {
817 const LINE_LIMIT: u64 = 16 * 1024 * 1024;
818 let mut reader = BufReader::new(stdout);
819 let mut artifacts = Vec::new();
820 loop {
821 let mut line = Vec::new();
822 let count = (&mut reader)
823 .take(LINE_LIMIT + 1)
824 .read_until(b'\n', &mut line)
825 .map_err(|e| format!("cannot read Cargo messages: {e}"))?;
826 if count == 0 {
827 break;
828 }
829 if count as u64 > LINE_LIMIT {
830 return Err("Cargo output line exceeds the 16 MiB limit".to_string());
831 }
832 let line =
833 std::str::from_utf8(&line).map_err(|_| "Cargo output is not UTF-8".to_string())?;
834 if let Some(path) = cargo_output::executable_from_message(line)? {
835 if artifacts.len() >= 100_000 {
836 return Err("Cargo executable count exceeds the prototype limit".into());
837 }
838 artifacts.push(path);
839 } else if !line.trim_start().starts_with('{') {
840 print!("{line}");
841 }
842 }
843 Ok(artifacts)
844 })();
845 match parsed {
846 Ok(artifacts) => Ok((
847 child
848 .wait()
849 .map_err(|e| format!("cannot wait for Cargo: {e}"))?,
850 artifacts,
851 )),
852 Err(error) => {
853 let _ = child.kill();
854 let _ = child.wait();
855 Err(error)
856 }
857 }
858}
859
860fn prefixed_path(prefix: &str, path: &Path) -> OsString {
861 let mut arg = OsString::from(prefix);
862 arg.push(path.as_os_str());
863 arg
864}
865
866fn tool_version(path: &Path) -> Result<String, String> {
867 let output = Command::new(path)
868 .arg("--version")
869 .output()
870 .map_err(|e| format!("cannot run {}: {e}", path.display()))?;
871 if !output.status.success() {
872 return Err(format!("{} --version failed", path.display()));
873 }
874 Ok(String::from_utf8_lossy(&output.stdout)
875 .lines()
876 .next()
877 .unwrap_or("")
878 .into())
879}
880
881fn remove_search_overrides(cmd: &mut Command) {
882 for key in [
883 "LIBRARY_PATH",
884 "CPATH",
885 "C_INCLUDE_PATH",
886 "CPLUS_INCLUDE_PATH",
887 "OBJC_INCLUDE_PATH",
888 "GCC_EXEC_PREFIX",
889 "COMPILER_PATH",
890 ] {
891 cmd.env_remove(key);
892 }
893}
894
895struct Selection {
897 cc: OsString,
898 sysroot: OsString,
899 gcc: OsString,
900 ld: OsString,
901 provider: Option<crate::provider::Provider>,
902}
903
904impl Selection {
905 fn from_environment(helper: &str) -> Result<Self, String> {
906 let required = |key: &str| {
907 env::var_os(key).ok_or_else(|| {
908 format!("{key} is missing; {helper} is launched by cargo libc build")
909 })
910 };
911 let provider = env::var_os("CARGO_LIBC_PROVIDER_DESCRIPTOR")
912 .map(|descriptor| {
913 let provider = crate::provider::Provider::parse(
914 descriptor
915 .to_str()
916 .ok_or("provider descriptor must be UTF-8")?,
917 )?;
918 provider.validate_files()?;
919 Ok::<_, String>(provider)
920 })
921 .transpose()?;
922 Ok(Self {
923 cc: required(CC_ENV)?,
924 sysroot: required(SYSROOT_ENV)?,
925 gcc: required(GCC_ENV)?,
926 ld: required(LD_ENV)?,
927 provider,
928 })
929 }
930
931 fn finish(&self, cmd: &mut Command, link: bool) {
933 cmd.arg(format!("--target={TARGET}"));
934 cmd.arg(prefixed_path("--sysroot=", Path::new(&self.sysroot)));
935 cmd.arg(prefixed_path("--gcc-install-dir=", Path::new(&self.gcc)));
936 if link {
937 if self.provider.is_none() {
938 cmd.arg("--rtlib=libgcc");
939 }
940 cmd.arg(prefixed_path("-fuse-ld=", Path::new(&self.ld)));
941 }
942 remove_search_overrides(cmd);
943 }
944}
945
946fn provider_link(
949 cmd: &mut Command,
950 provider: &crate::provider::Provider,
951 args: impl IntoIterator<Item = OsString>,
952 default_libraries: bool,
953 shared: bool,
954) {
955 let (begin, end) = if shared {
956 ("crtbeginS.o", "crtendS.o")
957 } else {
958 ("crtbegin.o", "crtend.o")
959 };
960 cmd.arg("-nostdlib");
961 if !shared {
962 cmd.arg("-no-pie");
963 }
964 if let Some(path) = env::var_os("CARGO_LIBC_PROVIDER_LINK_MAP") {
965 cmd.arg(prefixed_path("-Wl,-Map=", Path::new(&path)));
966 }
967 cmd.arg(prefixed_path("-L", &provider.stubs));
968 cmd.arg(prefixed_path("-L", &provider.compiler_runtime));
969 if !shared {
970 cmd.arg(provider.sysroot.join("usr/lib64/crt1.o"));
971 }
972 cmd.arg(provider.sysroot.join("usr/lib64/crti.o"));
973 cmd.arg(provider.gcc_startup.join(begin));
974 cmd.args(args);
975 let as_needed_gcc_s = ["-Wl,--push-state,--as-needed", "-lgcc_s", "-Wl,--pop-state"];
977 if default_libraries {
978 cmd.arg("-lgcc").args(as_needed_gcc_s).arg("-lc");
979 }
980 for archive in provider.nonshared_archives() {
981 cmd.arg(provider.sysroot.join(archive));
982 }
983 if default_libraries {
984 cmd.arg("-lgcc").args(as_needed_gcc_s);
985 }
986 cmd.arg(provider.gcc_startup.join(end));
987 cmd.arg(provider.sysroot.join("usr/lib64/crtn.o"));
988 if !shared {
989 cmd.arg(format!("-Wl,--dynamic-linker={LOADER}"));
990 }
991}
992
993pub fn run_linker(args: impl Iterator<Item = OsString>) -> Result<i32, String> {
994 let selection = Selection::from_environment("this helper")?;
995 let mut cmd = Command::new(&selection.cc);
996 match &selection.provider {
997 Some(provider) => provider_link(&mut cmd, provider, args, false, false),
998 None => {
999 cmd.args(args);
1000 }
1001 }
1002 selection.finish(&mut cmd, true);
1003 let status = cmd
1004 .status()
1005 .map_err(|e| format!("cannot launch Clang driver: {e}"))?;
1006 Ok(status.code().unwrap_or(1))
1007}
1008
1009pub fn target_compiler(action: crate::cc::Action, args: Vec<OsString>) -> Result<Command, String> {
1012 use crate::cc::Action;
1013 let selection = Selection::from_environment("this compiler wrapper")?;
1014 let mut cmd = Command::new(&selection.cc);
1015 let link = matches!(action, Action::Link { .. } | Action::Relocatable);
1016 match (&selection.provider, action) {
1017 (Some(provider), Action::Link { shared }) => {
1018 provider_link(&mut cmd, provider, args, true, shared)
1019 }
1020 _ => {
1021 cmd.args(args);
1022 }
1023 }
1024 selection.finish(&mut cmd, link);
1025 Ok(cmd)
1026}
1027
1028fn validate_x86_object(path: &Path) -> Result<(), String> {
1029 use std::io::Read;
1030 let mut bytes = [0u8; 20];
1031 fs::File::open(path)
1032 .and_then(|mut f| f.read_exact(&mut bytes))
1033 .map_err(|e| format!("cannot read object {}: {e}", path.display()))?;
1034 if &bytes[0..4] != b"\x7fELF"
1035 || bytes[4] != 2
1036 || bytes[5] != 1
1037 || u16::from_le_bytes([bytes[18], bytes[19]]) != 62
1038 {
1039 return Err(format!(
1040 "{} is not an x86-64 little-endian ELF object",
1041 path.display()
1042 ));
1043 }
1044 Ok(())
1045}
1046
1047pub fn header_version(text: &str) -> Result<Version, String> {
1048 fn number(text: &str, name: &str) -> Result<u32, String> {
1049 for line in text.lines() {
1050 let Some(rest) = line.trim_start().strip_prefix('#') else {
1051 continue;
1052 };
1053 let mut words = rest.split_whitespace();
1054 if words.next() == Some("define") && words.next() == Some(name) {
1055 return words
1056 .next()
1057 .ok_or_else(|| format!("missing {name} value"))?
1058 .parse()
1059 .map_err(|_| format!("invalid {name} value"));
1060 }
1061 }
1062 Err(format!("features.h does not define {name}"))
1063 }
1064 Ok(Version {
1065 major: number(text, "__GLIBC__")?,
1066 minor: number(text, "__GLIBC_MINOR__")?,
1067 patch: 0,
1068 })
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074
1075 #[test]
1076 fn parses_glibc_headers_without_running_preprocessor() {
1077 assert_eq!(
1078 header_version("#define __GLIBC__ 2\n# define __GLIBC_MINOR__ 22\n").unwrap(),
1079 "2.22".parse().unwrap()
1080 );
1081 assert!(header_version("#define __GLIBC__ 2").is_err());
1082 }
1083
1084 #[test]
1085 fn cargo_cannot_override_managed_target() {
1086 for value in [
1087 "--target",
1088 "--target=aarch64-unknown-linux-gnu",
1089 "--config=target.x.linker=other",
1090 "--message-format=json",
1091 "--",
1092 "+nightly",
1093 ] {
1094 assert!(validate_cargo_args("build", &[value.into()]).is_err());
1095 }
1096 assert!(validate_cargo_args(
1097 "build",
1098 &[
1099 "--release".into(),
1100 "--target-dir".into(),
1101 "target with spaces".into()
1102 ]
1103 )
1104 .is_ok());
1105 }
1106
1107 #[test]
1108 fn runner_path_is_one_toml_string() {
1109 assert_eq!(
1110 toml_string("/tool path/libc-runner"),
1111 "\"/tool path/libc-runner\""
1112 );
1113 assert_eq!(toml_string("a\"b\\c\n"), "\"a\\\"b\\\\c\\u000A\"");
1114 }
1115
1116 #[test]
1117 fn path_with_spaces_is_one_driver_argument() {
1118 let cfg = Toolchain {
1119 provider: None,
1120 sysroot: "/sdk root".into(),
1121 gcc_runtime: "/sdk root/gcc".into(),
1122 cc: "/usr/bin/clang".into(),
1123 ld: "/tools/ld.bfd".into(),
1124 ceiling: "2.22".parse().unwrap(),
1125 };
1126 assert!(cfg
1127 .driver_args()
1128 .contains(&OsString::from("--sysroot=/sdk root")));
1129 let flags = cfg.rustflags(Path::new("/tool path/libc-linker")).unwrap();
1130 assert!(flags
1131 .to_str()
1132 .unwrap()
1133 .split('\u{1f}')
1134 .any(|s| s == "link-arg=--sysroot=/sdk root"));
1135 }
1136
1137 #[test]
1138 fn shell_values_are_single_quoted() {
1139 assert_eq!(shell_quote("/a b/c"), "'/a b/c'");
1140 assert_eq!(shell_quote("it's"), "'it'\\''s'");
1141 }
1142}