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 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 Ok(flags)
312 }
313
314 fn profile_fingerprint(&self) -> Result<String, String> {
315 let mut hash = 0xcbf29ce484222325u64;
318 let ceiling = self.ceiling.to_string();
319 for item in [
320 text_path(&self.sysroot)?,
321 text_path(&self.gcc_runtime)?,
322 text_path(&self.cc)?,
323 text_path(&self.ld)?,
324 &ceiling,
325 ] {
326 for byte in item.bytes().chain(std::iter::once(0)) {
327 hash = (hash ^ u64::from(byte)).wrapping_mul(0x100000001b3);
328 }
329 }
330 if let Some(provider) = &self.provider {
331 for byte in provider.descriptor.bytes() {
332 hash = (hash ^ u64::from(byte)).wrapping_mul(0x100000001b3);
333 }
334 }
335 Ok(format!("{hash:016x}"))
336 }
337
338 pub fn variables(&self) -> Vec<(&'static str, OsString)> {
340 let mut variables = vec![
341 (SYSROOT_ENV, self.sysroot.clone().into_os_string()),
342 (GCC_ENV, self.gcc_runtime.clone().into_os_string()),
343 (CC_ENV, self.cc.clone().into_os_string()),
344 (LD_ENV, self.ld.clone().into_os_string()),
345 ];
346 if let Some(provider) = &self.provider {
347 variables.push((DESCRIPTOR_ENV, provider.descriptor.clone().into()));
348 }
349 variables
350 }
351
352 pub fn set_environment(&self, cmd: &mut Command) {
353 cmd.env_remove(DESCRIPTOR_ENV);
354 cmd.envs(self.variables());
355 }
356}
357
358fn has_tool_arguments(subcommand: &str) -> bool {
360 matches!(subcommand, "clippy" | "rustc")
361}
362
363pub fn validate_cargo_args(subcommand: &str, args: &[OsString]) -> Result<(), String> {
364 for (i, arg) in args.iter().enumerate() {
365 let text = arg.to_string_lossy();
366 if text == "--" && has_tool_arguments(subcommand) {
367 return validate_tool_args(&args[i + 1..]);
368 }
369 if text == "--target"
370 || text.starts_with("--target=")
371 || text == "--config"
372 || text.starts_with("--config=")
373 || text == "--message-format"
374 || text.starts_with("--message-format=")
375 || text == "--"
376 || text.starts_with('+')
377 {
378 return Err(format!("Cargo argument {text:?} conflicts with this prototype's controlled target configuration"));
379 }
380 }
381 Ok(())
382}
383
384fn validate_tool_args(args: &[OsString]) -> Result<(), String> {
387 for arg in args {
388 let text = arg.to_string_lossy();
389 if text.starts_with("--target")
390 || text.contains("linker")
391 || text.contains("relocation-model")
392 || text.contains("link-arg")
393 {
394 return Err(format!(
395 "compiler argument {text:?} conflicts with the managed target configuration"
396 ));
397 }
398 }
399 Ok(())
400}
401
402pub fn sibling(name: &str) -> Result<PathBuf, String> {
403 let executable = env::current_exe().map_err(|e| e.to_string())?;
404 let path = executable
405 .parent()
406 .ok_or_else(|| format!("cannot locate sibling helper {name}"))?
407 .join(name);
408 if !path.is_file() {
409 return Err(format!(
410 "missing Rust helper {}; install/build all cargo-libc binaries",
411 path.display()
412 ));
413 }
414 Ok(path)
415}
416
417fn toml_string(text: &str) -> String {
419 let mut out = String::from("\"");
420 for c in text.chars() {
421 match c {
422 '"' => out.push_str("\\\""),
423 '\\' => out.push_str("\\\\"),
424 c if c.is_control() => out.push_str(&format!("\\u{:04X}", c as u32)),
425 c => out.push(c),
426 }
427 }
428 out.push('"');
429 out
430}
431
432fn cargo_command(
435 config: &Toolchain,
436 subcommand: &str,
437 cargo_args: &[OsString],
438) -> Result<(Command, PathBuf), String> {
439 validate_cargo_args(subcommand, cargo_args)?;
440 let doctests = matches!(subcommand, "test" | "doc");
443 let mut managed = vec!["RUSTFLAGS", "CARGO_ENCODED_RUSTFLAGS"];
444 if doctests {
445 managed.extend(["RUSTDOCFLAGS", "CARGO_ENCODED_RUSTDOCFLAGS"]);
446 }
447 for key in managed {
448 if env::var_os(key).is_some_and(|value| !value.is_empty()) {
449 return Err(format!("{key} is already set; use a shell without that override so the target flags are explicit"));
450 }
451 }
452 let diagnostic = config.diagnose()?;
453 config.print_diagnostics(&diagnostic);
454 let driver = sibling("libc-linker")?;
455 let (cc, cxx) = crate::cc::wrappers()?;
456 let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
457 let mut cmd = Command::new(cargo);
458 cmd.arg(subcommand).arg("--target").arg(TARGET);
459 config.set_environment(&mut cmd);
460 crate::cc::set_environment(&mut cmd, &cc, &cxx)?;
463 let flags = config.rustflags(&driver)?;
466 println!(
467 "Target C compiler (cc crate, CMake): {} (C only; host units keep the host compiler)",
468 cc.display()
469 );
470 if doctests {
471 cmd.env("CARGO_ENCODED_RUSTDOCFLAGS", &flags);
472 }
473 cmd.env("CARGO_ENCODED_RUSTFLAGS", flags);
474 cmd.env_remove("CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS");
475 Ok((cmd, driver))
476}
477
478fn print_command(cmd: &Command) {
479 println!(
480 "Cargo command: {:?} {:?}",
481 cmd.get_program(),
482 cmd.get_args().collect::<Vec<_>>()
483 );
484}
485
486pub fn build(
487 config: &Toolchain,
488 cargo_args: &[OsString],
489 artifact: &Path,
490 scan: Option<&ScanOptions>,
491 dry_run: bool,
492) -> Result<i32, String> {
493 let (mut cmd, driver) = cargo_command(config, "build", cargo_args)?;
494 cmd.arg("--message-format=json-render-diagnostics")
495 .args(cargo_args);
496 print_command(&cmd);
497 println!("Rust linker driver: {}", driver.display());
498 println!(
499 "Artifact selected for direct ELF verification: {}",
500 artifact.display()
501 );
502 if dry_run {
503 return Ok(0);
504 }
505 let (status, artifacts) = run_cargo(&mut cmd)?;
506 if !status.success() {
507 return Ok(status.code().unwrap_or(1));
508 }
509 let selected = canonical(artifact, "selected executable")?;
510 if !artifacts
511 .iter()
512 .any(|path| fs::canonicalize(path).is_ok_and(|actual| actual == selected))
513 {
514 return Err(format!("{} was not reported as an executable by this Cargo invocation; select an executable it actually built or found fresh", artifact.display()));
515 }
516 let passed = verify_executable(config, artifact, scan)?;
517 Ok(if passed { 0 } else { 2 })
518}
519
520fn verify_executable(
523 config: &Toolchain,
524 artifact: &Path,
525 scan: Option<&ScanOptions>,
526) -> Result<bool, String> {
527 let bytes = report::read_bytes(artifact)?;
528 let info = crate::elf::inspect(&bytes)?;
529 let mut assessment = report::assess(&info, Some(config.ceiling));
530 if info.interpreter.as_deref() != Some(LOADER) {
531 assessment.errors.push(format!(
532 "expected dynamically linked executable with interpreter {LOADER:?}; got {:?}",
533 info.interpreter
534 ));
535 }
536 if config.sysroot != Path::new("/") {
537 for paths in info.rpath.iter().chain(&info.runpath) {
538 for entry in paths.split(':') {
539 if Path::new(entry).starts_with(&config.sysroot) {
540 assessment.errors.push(format!(
541 "build-time sysroot leaked into runtime library search path {entry:?}"
542 ));
543 }
544 }
545 }
546 }
547 let scan = scan
549 .map(|options| {
550 let ceiling = options.distro.is_none().then_some(config.ceiling);
551 report::scan(&bytes, ceiling, options).map(|(_, scan)| scan)
552 })
553 .transpose()?;
554 println!(
555 "{}",
556 report::render(
557 artifact,
558 &info,
559 &assessment,
560 Some(config.ceiling),
561 scan.as_ref(),
562 false
563 )
564 );
565 Ok(assessment.errors.is_empty() && scan.as_ref().is_none_or(report::Scan::passed))
566}
567
568pub fn cargo_passthrough(
572 config: &Toolchain,
573 subcommand: &str,
574 cargo_args: &[OsString],
575 dry_run: bool,
576) -> Result<i32, String> {
577 let (mut cmd, driver) = cargo_command(config, subcommand, cargo_args)?;
578 cmd.args(cargo_args);
579 print_command(&cmd);
580 let links = subcommand == "rustc";
581 println!(
582 "Rust linker driver: {}{}",
583 driver.display(),
584 if links {
585 ""
586 } else {
587 " (configured; this command does not link)"
588 }
589 );
590 if dry_run {
591 return Ok(0);
592 }
593 let status = cmd.status().map_err(|e| format!("cannot run Cargo: {e}"))?;
594 if status.success() {
595 if subcommand == "check" {
596 println!(
597 "Type check passed for glibc {} target flags. Nothing was linked: build and inspect the executable to check its requirements.",
598 config.ceiling
599 );
600 } else if links {
601 println!(
602 "Built with glibc {} target flags. The output was not checked: run cargo libc inspect on it.",
603 config.ceiling
604 );
605 }
606 }
607 Ok(status.code().unwrap_or(1))
608}
609
610pub fn install(
614 config: &Toolchain,
615 cargo_args: &[OsString],
616 scan: Option<&ScanOptions>,
617 dry_run: bool,
618) -> Result<i32, String> {
619 let (mut cmd, driver) = cargo_command(config, "install", cargo_args)?;
620 cmd.arg("--message-format=json-render-diagnostics");
621 let explicit = env::var_os("CARGO_TARGET_DIR").is_some_and(|v| !v.is_empty())
624 || cargo_args.iter().any(|arg| {
625 let text = arg.to_string_lossy();
626 text == "--target-dir" || text.starts_with("--target-dir=")
627 });
628 let temporary = (!explicit).then(|| {
629 let nanos = std::time::SystemTime::now()
630 .duration_since(std::time::UNIX_EPOCH)
631 .map_or(0, |d| d.as_nanos());
632 env::temp_dir().join(format!("cargo-libc-install-{}-{nanos}", std::process::id()))
633 });
634 if let Some(dir) = &temporary {
635 cmd.arg("--target-dir").arg(dir);
636 }
637 cmd.args(cargo_args);
638 print_command(&cmd);
639 println!("Rust linker driver: {}", driver.display());
640 println!(
641 "Every executable this install builds is checked against glibc {}.",
642 config.ceiling
643 );
644 if dry_run {
645 return Ok(0);
646 }
647 let result = (|| {
648 let (status, artifacts) = run_cargo(&mut cmd)?;
649 if !status.success() {
650 return Ok(status.code().unwrap_or(1));
651 }
652 if artifacts.is_empty() {
653 println!("Cargo built no executable (already installed?); nothing was checked.");
654 return Ok(0);
655 }
656 let mut passed = true;
657 for artifact in &artifacts {
658 passed &= verify_executable(config, artifact, scan)?;
659 }
660 if !passed {
661 println!("An installed executable failed the check above; remove it or rebuild for a newer target.");
662 }
663 Ok(if passed { 0 } else { 2 })
664 })();
665 if let Some(dir) = &temporary {
666 let _ = fs::remove_dir_all(dir);
667 }
668 result
669}
670
671#[derive(Clone, Copy, Debug, PartialEq, Eq)]
673pub enum EnvFormat {
674 Shell,
676 Cargo,
678}
679
680fn shell_quote(text: &str) -> String {
681 format!("'{}'", text.replace('\'', "'\\''"))
682}
683
684pub fn plain_cargo(
687 config: &Toolchain,
688 simulation: &Simulation,
689 format: EnvFormat,
690) -> Result<String, String> {
691 simulation.plan()?;
692 config.diagnose()?;
693 let driver = sibling("libc-linker")?;
694 let runner = sibling("libc-runner")?;
695 let (cc, cxx) = crate::cc::wrappers()?;
696 let flags = config.rustflag_list(&driver)?;
697 let runner = text_path(&runner)?.to_string();
698 let mut variables: Vec<(String, String)> = Vec::new();
699 let utf8 = |key: &str, value: OsString| {
700 value
701 .into_string()
702 .map_err(|_| format!("{key} must be UTF-8 for plain Cargo configuration"))
703 };
704 for (key, value) in config.variables() {
705 variables.push((key.to_string(), utf8(key, value)?));
706 }
707 for (key, value) in crate::cc::variables(&cc, &cxx, format == EnvFormat::Shell)? {
708 let value = utf8(&key, value)?;
709 variables.push((key, value));
710 }
711 for (key, value) in simulation.variables(config.ceiling) {
712 if let Some(value) = value {
713 variables.push((key.to_string(), value));
714 }
715 }
716 let triple_key = TARGET.to_uppercase().replace('-', "_");
717 let mut out = format!(
718 "# cargo-libc {}: glibc {} for {TARGET}, generated for this machine.\n\
719 # Use with: cargo build --target {TARGET} (always pass --target, so build\n\
720 # scripts and proc macros stay native). RUSTFLAGS or CARGO_ENCODED_RUSTFLAGS in\n\
721 # the environment replace these flags. Nothing checks the output: run\n\
722 # cargo libc inspect FILE --max-glibc {}. Regenerate after updating\n\
723 # cargo-libc or the provider.\n",
724 env!("CARGO_PKG_VERSION"),
725 config.ceiling,
726 config.ceiling
727 );
728 match format {
729 EnvFormat::Shell => {
730 if let Some(flag) = flags
731 .iter()
732 .chain([&runner])
733 .find(|f| f.chars().any(char::is_whitespace))
734 {
735 return Err(format!("{flag:?} contains whitespace, which Cargo's flag variables cannot hold; use --format cargo"));
736 }
737 let joined = flags.join(" ");
738 out.push_str(&format!(
739 "export CARGO_TARGET_{triple_key}_RUSTFLAGS={}\n",
740 shell_quote(&joined)
741 ));
742 out.push_str(&format!("export RUSTDOCFLAGS={}\n", shell_quote(&joined)));
743 out.push_str(&format!(
744 "export CARGO_TARGET_{triple_key}_RUNNER={}\n",
745 shell_quote(&runner)
746 ));
747 for (key, value) in variables {
748 out.push_str(&format!("export {key}={}\n", shell_quote(&value)));
749 }
750 }
751 EnvFormat::Cargo => {
752 let list = |items: &[String]| {
753 items
754 .iter()
755 .map(|f| toml_string(f))
756 .collect::<Vec<_>>()
757 .join(", ")
758 };
759 out.push_str(&format!(
760 "\n[target.{TARGET}]\nrustflags = [{}]\nrunner = [{}]\n\n[build]\nrustdocflags = [{}]\n\n[env]\n",
761 list(&flags),
762 toml_string(&runner),
763 list(&flags)
764 ));
765 for (key, value) in variables {
766 out.push_str(&format!("{key} = {}\n", toml_string(&value)));
767 }
768 }
769 }
770 Ok(out)
771}
772
773pub fn test_or_run(
776 config: &Toolchain,
777 subcommand: &str,
778 cargo_args: &[OsString],
779 simulation: &Simulation,
780 dry_run: bool,
781) -> Result<i32, String> {
782 let (plan, _) = simulation.plan()?;
783 let (mut cmd, driver) = cargo_command(config, subcommand, cargo_args)?;
784 let runner = sibling("libc-runner")?;
785 cmd.arg("--config").arg(format!(
787 "target.{TARGET}.runner=[{}]",
788 toml_string(text_path(&runner)?)
789 ));
790 cmd.args(cargo_args);
791 simulation.set_environment(&mut cmd, config.ceiling);
792 print_command(&cmd);
793 println!("Rust linker driver: {}", driver.display());
794 println!("Target runner: {}", runner.display());
795 println!("Kernel simulation: {}", simulate::describe(&plan));
796 println!(
797 "Each binary is checked against glibc {} before it runs.",
798 config.ceiling
799 );
800 if dry_run {
801 return Ok(0);
802 }
803 let status = cmd.status().map_err(|e| format!("cannot run Cargo: {e}"))?;
804 Ok(status.code().unwrap_or(1))
805}
806
807fn run_cargo(cmd: &mut Command) -> Result<(ExitStatus, Vec<PathBuf>), String> {
808 let mut child = cmd
809 .stdout(Stdio::piped())
810 .spawn()
811 .map_err(|e| format!("cannot run Cargo: {e}"))?;
812 let stdout = child.stdout.take().expect("piped Cargo stdout");
813 let parsed = (|| {
814 const LINE_LIMIT: u64 = 16 * 1024 * 1024;
815 let mut reader = BufReader::new(stdout);
816 let mut artifacts = Vec::new();
817 loop {
818 let mut line = Vec::new();
819 let count = (&mut reader)
820 .take(LINE_LIMIT + 1)
821 .read_until(b'\n', &mut line)
822 .map_err(|e| format!("cannot read Cargo messages: {e}"))?;
823 if count == 0 {
824 break;
825 }
826 if count as u64 > LINE_LIMIT {
827 return Err("Cargo output line exceeds the 16 MiB limit".to_string());
828 }
829 let line =
830 std::str::from_utf8(&line).map_err(|_| "Cargo output is not UTF-8".to_string())?;
831 if let Some(path) = cargo_output::executable_from_message(line)? {
832 if artifacts.len() >= 100_000 {
833 return Err("Cargo executable count exceeds the prototype limit".into());
834 }
835 artifacts.push(path);
836 } else if !line.trim_start().starts_with('{') {
837 print!("{line}");
838 }
839 }
840 Ok(artifacts)
841 })();
842 match parsed {
843 Ok(artifacts) => Ok((
844 child
845 .wait()
846 .map_err(|e| format!("cannot wait for Cargo: {e}"))?,
847 artifacts,
848 )),
849 Err(error) => {
850 let _ = child.kill();
851 let _ = child.wait();
852 Err(error)
853 }
854 }
855}
856
857fn prefixed_path(prefix: &str, path: &Path) -> OsString {
858 let mut arg = OsString::from(prefix);
859 arg.push(path.as_os_str());
860 arg
861}
862
863fn tool_version(path: &Path) -> Result<String, String> {
864 let output = Command::new(path)
865 .arg("--version")
866 .output()
867 .map_err(|e| format!("cannot run {}: {e}", path.display()))?;
868 if !output.status.success() {
869 return Err(format!("{} --version failed", path.display()));
870 }
871 Ok(String::from_utf8_lossy(&output.stdout)
872 .lines()
873 .next()
874 .unwrap_or("")
875 .into())
876}
877
878fn remove_search_overrides(cmd: &mut Command) {
879 for key in [
880 "LIBRARY_PATH",
881 "CPATH",
882 "C_INCLUDE_PATH",
883 "CPLUS_INCLUDE_PATH",
884 "OBJC_INCLUDE_PATH",
885 "GCC_EXEC_PREFIX",
886 "COMPILER_PATH",
887 ] {
888 cmd.env_remove(key);
889 }
890}
891
892struct Selection {
894 cc: OsString,
895 sysroot: OsString,
896 gcc: OsString,
897 ld: OsString,
898 provider: Option<crate::provider::Provider>,
899}
900
901impl Selection {
902 fn from_environment(helper: &str) -> Result<Self, String> {
903 let required = |key: &str| {
904 env::var_os(key).ok_or_else(|| {
905 format!("{key} is missing; {helper} is launched by cargo libc build")
906 })
907 };
908 let provider = env::var_os("CARGO_LIBC_PROVIDER_DESCRIPTOR")
909 .map(|descriptor| {
910 let provider = crate::provider::Provider::parse(
911 descriptor
912 .to_str()
913 .ok_or("provider descriptor must be UTF-8")?,
914 )?;
915 provider.validate_files()?;
916 Ok::<_, String>(provider)
917 })
918 .transpose()?;
919 Ok(Self {
920 cc: required(CC_ENV)?,
921 sysroot: required(SYSROOT_ENV)?,
922 gcc: required(GCC_ENV)?,
923 ld: required(LD_ENV)?,
924 provider,
925 })
926 }
927
928 fn finish(&self, cmd: &mut Command, link: bool) {
930 cmd.arg(format!("--target={TARGET}"));
931 cmd.arg(prefixed_path("--sysroot=", Path::new(&self.sysroot)));
932 cmd.arg(prefixed_path("--gcc-install-dir=", Path::new(&self.gcc)));
933 if link {
934 if self.provider.is_none() {
935 cmd.arg("--rtlib=libgcc");
936 }
937 cmd.arg(prefixed_path("-fuse-ld=", Path::new(&self.ld)));
938 }
939 remove_search_overrides(cmd);
940 }
941}
942
943fn provider_link(
946 cmd: &mut Command,
947 provider: &crate::provider::Provider,
948 args: impl IntoIterator<Item = OsString>,
949 default_libraries: bool,
950 shared: bool,
951) {
952 let (begin, end) = if shared {
953 ("crtbeginS.o", "crtendS.o")
954 } else {
955 ("crtbegin.o", "crtend.o")
956 };
957 cmd.arg("-nostdlib");
958 if !shared {
959 cmd.arg("-no-pie");
960 }
961 if let Some(path) = env::var_os("CARGO_LIBC_PROVIDER_LINK_MAP") {
962 cmd.arg(prefixed_path("-Wl,-Map=", Path::new(&path)));
963 }
964 cmd.arg(prefixed_path("-L", &provider.stubs));
965 cmd.arg(prefixed_path("-L", &provider.compiler_runtime));
966 if !shared {
967 cmd.arg(provider.sysroot.join("usr/lib64/crt1.o"));
968 }
969 cmd.arg(provider.sysroot.join("usr/lib64/crti.o"));
970 cmd.arg(provider.gcc_startup.join(begin));
971 cmd.args(args);
972 let as_needed_gcc_s = ["-Wl,--push-state,--as-needed", "-lgcc_s", "-Wl,--pop-state"];
974 if default_libraries {
975 cmd.arg("-lgcc").args(as_needed_gcc_s).arg("-lc");
976 }
977 for archive in provider.nonshared_archives() {
978 cmd.arg(provider.sysroot.join(archive));
979 }
980 if default_libraries {
981 cmd.arg("-lgcc").args(as_needed_gcc_s);
982 }
983 cmd.arg(provider.gcc_startup.join(end));
984 cmd.arg(provider.sysroot.join("usr/lib64/crtn.o"));
985 if !shared {
986 cmd.arg(format!("-Wl,--dynamic-linker={LOADER}"));
987 }
988}
989
990fn provider_link_args(args: impl Iterator<Item = OsString>) -> (bool, Vec<OsString>) {
995 let mut shared = false;
996 let mut kept = Vec::new();
997 for arg in args {
998 if arg == "-shared" {
999 shared = true;
1000 }
1001 if arg != "-pie" {
1002 kept.push(arg);
1003 }
1004 }
1005 (shared, kept)
1006}
1007
1008pub fn run_linker(args: impl Iterator<Item = OsString>) -> Result<i32, String> {
1009 let selection = Selection::from_environment("this helper")?;
1010 let mut cmd = Command::new(&selection.cc);
1011 match &selection.provider {
1012 Some(provider) => {
1013 let (shared, args) = provider_link_args(args);
1014 provider_link(&mut cmd, provider, args, false, shared)
1015 }
1016 None => {
1017 cmd.args(args);
1018 }
1019 }
1020 selection.finish(&mut cmd, true);
1021 let status = cmd
1022 .status()
1023 .map_err(|e| format!("cannot launch Clang driver: {e}"))?;
1024 Ok(status.code().unwrap_or(1))
1025}
1026
1027pub fn target_compiler(action: crate::cc::Action, args: Vec<OsString>) -> Result<Command, String> {
1030 use crate::cc::Action;
1031 let selection = Selection::from_environment("this compiler wrapper")?;
1032 let mut cmd = Command::new(&selection.cc);
1033 let link = matches!(action, Action::Link { .. } | Action::Relocatable);
1034 match (&selection.provider, action) {
1035 (Some(provider), Action::Link { shared }) => {
1036 provider_link(&mut cmd, provider, args, true, shared)
1037 }
1038 _ => {
1039 cmd.args(args);
1040 }
1041 }
1042 selection.finish(&mut cmd, link);
1043 Ok(cmd)
1044}
1045
1046fn validate_x86_object(path: &Path) -> Result<(), String> {
1047 use std::io::Read;
1048 let mut bytes = [0u8; 20];
1049 fs::File::open(path)
1050 .and_then(|mut f| f.read_exact(&mut bytes))
1051 .map_err(|e| format!("cannot read object {}: {e}", path.display()))?;
1052 if &bytes[0..4] != b"\x7fELF"
1053 || bytes[4] != 2
1054 || bytes[5] != 1
1055 || u16::from_le_bytes([bytes[18], bytes[19]]) != 62
1056 {
1057 return Err(format!(
1058 "{} is not an x86-64 little-endian ELF object",
1059 path.display()
1060 ));
1061 }
1062 Ok(())
1063}
1064
1065pub fn header_version(text: &str) -> Result<Version, String> {
1066 fn number(text: &str, name: &str) -> Result<u32, String> {
1067 for line in text.lines() {
1068 let Some(rest) = line.trim_start().strip_prefix('#') else {
1069 continue;
1070 };
1071 let mut words = rest.split_whitespace();
1072 if words.next() == Some("define") && words.next() == Some(name) {
1073 return words
1074 .next()
1075 .ok_or_else(|| format!("missing {name} value"))?
1076 .parse()
1077 .map_err(|_| format!("invalid {name} value"));
1078 }
1079 }
1080 Err(format!("features.h does not define {name}"))
1081 }
1082 Ok(Version {
1083 major: number(text, "__GLIBC__")?,
1084 minor: number(text, "__GLIBC_MINOR__")?,
1085 patch: 0,
1086 })
1087}
1088
1089#[cfg(test)]
1090mod tests {
1091 use super::*;
1092
1093 #[test]
1094 fn parses_glibc_headers_without_running_preprocessor() {
1095 assert_eq!(
1096 header_version("#define __GLIBC__ 2\n# define __GLIBC_MINOR__ 22\n").unwrap(),
1097 "2.22".parse().unwrap()
1098 );
1099 assert!(header_version("#define __GLIBC__ 2").is_err());
1100 }
1101
1102 #[test]
1103 fn provider_links_tell_shared_libraries_and_drop_pie() {
1104 let args = |list: &[&str]| list.iter().map(OsString::from).collect::<Vec<_>>();
1105 let (shared, kept) = provider_link_args(args(&["a.o", "-pie", "-o", "app"]).into_iter());
1106 assert!(!shared);
1107 assert_eq!(kept, args(&["a.o", "-o", "app"]));
1108 let (shared, kept) =
1109 provider_link_args(args(&["a.o", "-shared", "-o", "lib.so"]).into_iter());
1110 assert!(shared);
1111 assert_eq!(kept, args(&["a.o", "-shared", "-o", "lib.so"]));
1112 }
1113
1114 #[test]
1115 fn cargo_cannot_override_managed_target() {
1116 for value in [
1117 "--target",
1118 "--target=aarch64-unknown-linux-gnu",
1119 "--config=target.x.linker=other",
1120 "--message-format=json",
1121 "--",
1122 "+nightly",
1123 ] {
1124 assert!(validate_cargo_args("build", &[value.into()]).is_err());
1125 }
1126 assert!(validate_cargo_args(
1127 "build",
1128 &[
1129 "--release".into(),
1130 "--target-dir".into(),
1131 "target with spaces".into()
1132 ]
1133 )
1134 .is_ok());
1135 }
1136
1137 #[test]
1138 fn runner_path_is_one_toml_string() {
1139 assert_eq!(
1140 toml_string("/tool path/libc-runner"),
1141 "\"/tool path/libc-runner\""
1142 );
1143 assert_eq!(toml_string("a\"b\\c\n"), "\"a\\\"b\\\\c\\u000A\"");
1144 }
1145
1146 #[test]
1147 fn path_with_spaces_is_one_driver_argument() {
1148 let cfg = Toolchain {
1149 provider: None,
1150 sysroot: "/sdk root".into(),
1151 gcc_runtime: "/sdk root/gcc".into(),
1152 cc: "/usr/bin/clang".into(),
1153 ld: "/tools/ld.bfd".into(),
1154 ceiling: "2.22".parse().unwrap(),
1155 };
1156 assert!(cfg
1157 .driver_args()
1158 .contains(&OsString::from("--sysroot=/sdk root")));
1159 let flags = cfg.rustflags(Path::new("/tool path/libc-linker")).unwrap();
1160 assert!(flags
1161 .to_str()
1162 .unwrap()
1163 .split('\u{1f}')
1164 .any(|s| s == "link-arg=--sysroot=/sdk root"));
1165 }
1166
1167 #[test]
1168 fn shell_values_are_single_quoted() {
1169 assert_eq!(shell_quote("/a b/c"), "'/a b/c'");
1170 assert_eq!(shell_quote("it's"), "'it'\\''s'");
1171 }
1172}