1use crate::{
5 cargo_output,
6 report::{self, ScanOptions},
7 simulate::{self, Simulation},
8 version::Version,
9};
10use std::{
11 env,
12 ffi::OsString,
13 fs,
14 io::{BufRead, BufReader, Read},
15 path::{Path, PathBuf},
16 process::{Command, ExitStatus, Stdio},
17};
18
19pub(crate) const TARGET: &str = "x86_64-unknown-linux-gnu";
20pub(crate) const LOADER: &str = "/lib64/ld-linux-x86-64.so.2";
21const SYSROOT_ENV: &str = "CARGO_LIBC_SYSROOT";
22const GCC_ENV: &str = "CARGO_LIBC_GCC_RUNTIME";
23const CC_ENV: &str = "CARGO_LIBC_CC";
24const LD_ENV: &str = "CARGO_LIBC_LD";
25const DESCRIPTOR_ENV: &str = "CARGO_LIBC_PROVIDER_DESCRIPTOR";
26
27#[derive(Clone, Debug)]
28pub(crate) struct Toolchain {
29 pub(crate) provider: Option<crate::provider::Provider>,
30 pub(crate) sysroot: PathBuf,
31 pub(crate) gcc_runtime: PathBuf,
32 pub(crate) cc: PathBuf,
33 pub(crate) ld: PathBuf,
34 pub(crate) ceiling: Version,
35}
36
37#[derive(Debug)]
38pub(crate) struct Diagnostics {
39 pub(crate) header_version: Version,
40 pub(crate) compiler_version: String,
41 pub(crate) linker_version: String,
42 pub(crate) files: Vec<(String, PathBuf)>,
43}
44
45fn canonical(path: &Path, label: &str) -> Result<PathBuf, String> {
46 fs::canonicalize(path).map_err(|e| format!("cannot resolve {label} {}: {e}", path.display()))
47}
48
49fn inside_file(path: &Path, root: &Path) -> Result<PathBuf, String> {
50 let actual = canonical(path, "target file")?;
51 if !actual.starts_with(root) {
52 return Err(format!(
53 "target file {} resolves outside sysroot {}",
54 path.display(),
55 root.display()
56 ));
57 }
58 if !actual.is_file() {
59 return Err(format!(
60 "target input is not a regular file: {}",
61 actual.display()
62 ));
63 }
64 Ok(actual)
65}
66
67pub(crate) fn text_path(path: &Path) -> Result<&str, String> {
68 let text = path
69 .to_str()
70 .ok_or("toolchain paths must be UTF-8 for Cargo's encoded flags")?;
71 if text.contains('\u{1f}') {
72 return Err("toolchain paths cannot contain the Cargo flag separator U+001F".into());
73 }
74 Ok(text)
75}
76
77impl Toolchain {
78 pub(crate) fn from_provider(
79 path: &Path,
80 cc: PathBuf,
81 ld: PathBuf,
82 ceiling: Version,
83 ) -> Result<Self, String> {
84 if !cfg!(all(target_os = "linux", target_arch = "x86_64")) {
85 return Err("provider builds require x86-64 Linux".into());
86 }
87
88 let provider = crate::provider::Provider::load(path)?;
89 provider.validate_selection(ceiling)?;
90 provider.validate_files()?;
91 let sysroot = canonical(&provider.sysroot, "provider sysroot")?;
92 let gcc_runtime = canonical(&provider.gcc_startup, "provider GCC startup")?;
93 let cc = canonical(&cc, "Clang driver")?;
94 let ld = canonical(&ld, "linker")?;
95 for path in [
96 &sysroot,
97 &gcc_runtime,
98 &provider.stubs,
99 &provider.compiler_runtime,
100 &cc,
101 &ld,
102 ] {
103 text_path(path)?;
104 }
105 Ok(Self {
106 provider: Some(provider),
107 sysroot,
108 gcc_runtime,
109 cc,
110 ld,
111 ceiling,
112 })
113 }
114 pub(crate) fn new(
115 sysroot: PathBuf,
116 gcc_runtime: PathBuf,
117 cc: PathBuf,
118 ld: PathBuf,
119 ceiling: Version,
120 ) -> Result<Self, String> {
121 if !cfg!(all(target_os = "linux", target_arch = "x86_64")) {
122 return Err("the build coordinator currently supports x86-64 Linux hosts only".into());
123 }
124 ceiling.validate_glibc_target()?;
125 let sysroot = canonical(&sysroot, "sysroot")?;
126 let gcc_runtime = canonical(&gcc_runtime, "GCC runtime directory")?;
127 if !sysroot.is_dir() || !gcc_runtime.is_dir() {
128 return Err("sysroot and GCC runtime must be directories".into());
129 }
130 if !gcc_runtime.starts_with(&sysroot) {
131 return Err("the GCC runtime directory must be inside the supplied sysroot".into());
132 }
133 let cc = canonical(&cc, "Clang driver")?;
134 let ld = canonical(&ld, "linker")?;
135 if !cc.is_file() || !ld.is_file() {
136 return Err("compiler and linker paths must identify files".into());
137 }
138 for path in [&sysroot, &gcc_runtime, &cc, &ld] {
139 text_path(path)?;
140 }
141 Ok(Self {
142 provider: None,
143 sysroot,
144 gcc_runtime,
145 cc,
146 ld,
147 ceiling,
148 })
149 }
150
151 pub(crate) fn driver_args(&self) -> Vec<OsString> {
152 vec![
153 format!("--target={TARGET}").into(),
154 prefixed_path("--sysroot=", &self.sysroot),
155 prefixed_path("--gcc-install-dir=", &self.gcc_runtime),
156 "--rtlib=libgcc".into(),
157 prefixed_path("-fuse-ld=", &self.ld),
158 ]
159 }
160
161 fn command(&self) -> Command {
162 let mut cmd = Command::new(&self.cc);
163 cmd.args(self.driver_args());
164 remove_search_overrides(&mut cmd);
165 cmd
166 }
167
168 pub(crate) fn diagnose(&self) -> Result<Diagnostics, String> {
169 let header = inside_file(&self.sysroot.join("usr/include/features.h"), &self.sysroot)?;
170 let text =
171 fs::read_to_string(&header).map_err(|e| format!("cannot read features.h: {e}"))?;
172 let header_version = header_version(&text)?;
173 if header_version > self.ceiling {
174 return Err(format!("sysroot headers are glibc {header_version}, newer than requested ceiling {}; supply older target development files", self.ceiling));
175 }
176 let compiler_version = tool_version(&self.cc)?;
177 if !compiler_version.to_ascii_lowercase().contains("clang") {
178 return Err(format!("--cc must be a Clang driver supporting --gcc-install-dir; got {compiler_version:?}"));
179 }
180 let linker_version = tool_version(&self.ld)?;
181 let mut files = Vec::new();
182 if let Some(provider) = &self.provider {
183 provider.validate_files()?;
184 if header_version != self.ceiling {
185 return Err(format!(
186 "provider headers must match glibc {} exactly",
187 self.ceiling
188 ));
189 }
190 for name in ["crt1.o", "crti.o", "crtn.o"] {
191 let path = inside_file(&self.sysroot.join("usr/lib64").join(name), &self.sysroot)?;
192 validate_x86_object(&path)?;
193 files.push((name.into(), path));
194 }
195 for name in ["crtbegin.o", "crtend.o"] {
196 let path = inside_file(&self.gcc_runtime.join(name), &self.gcc_runtime)?;
197 validate_x86_object(&path)?;
198 files.push((name.into(), path));
199 }
200 for name in crate::provider::SONAMES {
201 let path = provider.stubs.join(name);
202 validate_x86_object(&path)?;
203 files.push((name.into(), path));
204 }
205 return Ok(Diagnostics {
206 header_version,
207 compiler_version,
208 linker_version,
209 files,
210 });
211 }
212 for name in [
213 "Scrt1.o",
214 "crt1.o",
215 "crti.o",
216 "crtn.o",
217 "crtbeginS.o",
218 "crtendS.o",
219 "libc.so",
220 "libc_nonshared.a",
221 "libgcc_s.so",
222 ] {
223 let output = self
224 .command()
225 .arg(format!("-print-file-name={name}"))
226 .output()
227 .map_err(|e| format!("cannot probe target input {name}: {e}"))?;
228 if !output.status.success() {
229 return Err(format!(
230 "Clang failed while finding {name}: {}",
231 String::from_utf8_lossy(&output.stderr).trim()
232 ));
233 }
234 let value = std::str::from_utf8(&output.stdout)
235 .map_err(|_| "Clang returned a non-UTF-8 input path")?
236 .trim();
237 if value == name || value.is_empty() || !Path::new(value).is_absolute() {
238 return Err(format!(
239 "Clang did not locate target input {name}; returned {value:?}"
240 ));
241 }
242 let path = inside_file(Path::new(value), &self.sysroot)?;
243 if name.ends_with(".o") {
244 validate_x86_object(&path)?;
245 }
246 files.push((name.into(), path));
247 }
248 for name in ["crtbeginS.o", "crtendS.o"] {
250 let expected = inside_file(&self.gcc_runtime.join(name), &self.sysroot)?;
251 let selected = files.iter().find(|(n, _)| n == name).unwrap();
252 if selected.1 != expected {
253 return Err(format!("Clang selected an unexpected {name}"));
254 }
255 }
256 inside_file(
257 &self.sysroot.join(LOADER.trim_start_matches('/')),
258 &self.sysroot,
259 )?;
260 Ok(Diagnostics {
261 header_version,
262 compiler_version,
263 linker_version,
264 files,
265 })
266 }
267
268 pub(crate) fn print_diagnostics(&self, info: &Diagnostics) {
269 println!(
270 "Target: {TARGET}; requested glibc ceiling: {}",
271 self.ceiling
272 );
273 println!(
274 "Sysroot: {}; headers: glibc {}",
275 self.sysroot.display(),
276 info.header_version
277 );
278 println!("Compiler: {}", info.compiler_version);
279 println!("Linker: {}", info.linker_version);
280 for (name, path) in &info.files {
281 println!(" {name}: {}", path.display());
282 }
283 println!("Target input probes passed. Full dependency resolution and target runtime behavior remain separate checks.");
284 }
285
286 pub(crate) fn rustflags(&self, driver: &Path) -> Result<OsString, String> {
287 Ok(self.rustflag_list(driver)?.join("\u{1f}").into())
288 }
289
290 pub(crate) fn rustflag_list(&self, driver: &Path) -> Result<Vec<String>, String> {
291 let flags = vec![
294 "-C".to_string(),
295 format!("linker={}", text_path(driver)?),
296 "-C".into(),
297 "linker-flavor=gcc".into(),
298 "-C".to_string(),
299 "linker-features=-lld".into(),
300 "-C".into(),
301 "target-cpu=x86-64".into(),
302 "-C".into(),
303 format!("link-arg=--sysroot={}", text_path(&self.sysroot)?),
304 "-C".into(),
305 format!(
306 "link-arg=--gcc-install-dir={}",
307 text_path(&self.gcc_runtime)?
308 ),
309 "-C".into(),
310 format!("link-arg=-fuse-ld={}", text_path(&self.ld)?),
311 "--cfg".into(),
312 format!("cargo_libc_profile=\"{}\"", self.profile_fingerprint()?),
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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) 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(crate) enum EnvFormat {
677 Shell,
679 Cargo,
681}
682
683fn shell_quote(text: &str) -> String {
684 format!("'{}'", text.replace('\'', "'\\''"))
685}
686
687pub(crate) 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(crate) 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
993fn provider_link_args(args: impl Iterator<Item = OsString>) -> (bool, Vec<OsString>) {
998 let mut shared = false;
999 let mut kept = Vec::new();
1000 for arg in args {
1001 if arg == "-shared" {
1002 shared = true;
1003 }
1004 if arg != "-pie" {
1005 kept.push(arg);
1006 }
1007 }
1008 (shared, kept)
1009}
1010
1011pub fn run_linker(args: impl Iterator<Item = OsString>) -> Result<i32, String> {
1025 let selection = Selection::from_environment("this helper")?;
1026 let mut cmd = Command::new(&selection.cc);
1027 match &selection.provider {
1028 Some(provider) => {
1029 let (shared, args) = provider_link_args(args);
1030 provider_link(&mut cmd, provider, args, false, shared)
1031 }
1032 None => {
1033 cmd.args(args);
1034 }
1035 }
1036 selection.finish(&mut cmd, true);
1037 let status = cmd
1038 .status()
1039 .map_err(|e| format!("cannot launch Clang driver: {e}"))?;
1040 Ok(status.code().unwrap_or(1))
1041}
1042
1043pub(crate) fn target_compiler(
1046 action: crate::cc::Action,
1047 args: Vec<OsString>,
1048) -> Result<Command, String> {
1049 use crate::cc::Action;
1050 let selection = Selection::from_environment("this compiler wrapper")?;
1051 let mut cmd = Command::new(&selection.cc);
1052 let link = matches!(action, Action::Link { .. } | Action::Relocatable);
1053 match (&selection.provider, action) {
1054 (Some(provider), Action::Link { shared }) => {
1055 provider_link(&mut cmd, provider, args, true, shared)
1056 }
1057 _ => {
1058 cmd.args(args);
1059 }
1060 }
1061 selection.finish(&mut cmd, link);
1062 Ok(cmd)
1063}
1064
1065fn validate_x86_object(path: &Path) -> Result<(), String> {
1066 use std::io::Read;
1067 let mut bytes = [0u8; 20];
1068 fs::File::open(path)
1069 .and_then(|mut f| f.read_exact(&mut bytes))
1070 .map_err(|e| format!("cannot read object {}: {e}", path.display()))?;
1071 if &bytes[0..4] != b"\x7fELF"
1072 || bytes[4] != 2
1073 || bytes[5] != 1
1074 || u16::from_le_bytes([bytes[18], bytes[19]]) != 62
1075 {
1076 return Err(format!(
1077 "{} is not an x86-64 little-endian ELF object",
1078 path.display()
1079 ));
1080 }
1081 Ok(())
1082}
1083
1084pub(crate) fn header_version(text: &str) -> Result<Version, String> {
1085 fn number(text: &str, name: &str) -> Result<u32, String> {
1086 for line in text.lines() {
1087 let Some(rest) = line.trim_start().strip_prefix('#') else {
1088 continue;
1089 };
1090 let mut words = rest.split_whitespace();
1091 if words.next() == Some("define") && words.next() == Some(name) {
1092 return words
1093 .next()
1094 .ok_or_else(|| format!("missing {name} value"))?
1095 .parse()
1096 .map_err(|_| format!("invalid {name} value"));
1097 }
1098 }
1099 Err(format!("features.h does not define {name}"))
1100 }
1101 Ok(Version {
1102 major: number(text, "__GLIBC__")?,
1103 minor: number(text, "__GLIBC_MINOR__")?,
1104 patch: 0,
1105 })
1106}
1107
1108#[cfg(test)]
1109mod tests {
1110 use super::*;
1111
1112 #[test]
1113 fn parses_glibc_headers_without_running_preprocessor() {
1114 assert_eq!(
1115 header_version("#define __GLIBC__ 2\n# define __GLIBC_MINOR__ 22\n").unwrap(),
1116 "2.22".parse().unwrap()
1117 );
1118 assert!(header_version("#define __GLIBC__ 2").is_err());
1119 }
1120
1121 #[test]
1122 fn provider_links_tell_shared_libraries_and_drop_pie() {
1123 let args = |list: &[&str]| list.iter().map(OsString::from).collect::<Vec<_>>();
1124 let (shared, kept) = provider_link_args(args(&["a.o", "-pie", "-o", "app"]).into_iter());
1125 assert!(!shared);
1126 assert_eq!(kept, args(&["a.o", "-o", "app"]));
1127 let (shared, kept) =
1128 provider_link_args(args(&["a.o", "-shared", "-o", "lib.so"]).into_iter());
1129 assert!(shared);
1130 assert_eq!(kept, args(&["a.o", "-shared", "-o", "lib.so"]));
1131 }
1132
1133 #[test]
1134 fn cargo_cannot_override_managed_target() {
1135 for value in [
1136 "--target",
1137 "--target=aarch64-unknown-linux-gnu",
1138 "--config=target.x.linker=other",
1139 "--message-format=json",
1140 "--",
1141 "+nightly",
1142 ] {
1143 assert!(validate_cargo_args("build", &[value.into()]).is_err());
1144 }
1145 assert!(validate_cargo_args(
1146 "build",
1147 &[
1148 "--release".into(),
1149 "--target-dir".into(),
1150 "target with spaces".into()
1151 ]
1152 )
1153 .is_ok());
1154 }
1155
1156 #[test]
1157 fn runner_path_is_one_toml_string() {
1158 assert_eq!(
1159 toml_string("/tool path/libc-runner"),
1160 "\"/tool path/libc-runner\""
1161 );
1162 assert_eq!(toml_string("a\"b\\c\n"), "\"a\\\"b\\\\c\\u000A\"");
1163 }
1164
1165 #[test]
1166 fn path_with_spaces_is_one_driver_argument() {
1167 let cfg = Toolchain {
1168 provider: None,
1169 sysroot: "/sdk root".into(),
1170 gcc_runtime: "/sdk root/gcc".into(),
1171 cc: "/usr/bin/clang".into(),
1172 ld: "/tools/ld.bfd".into(),
1173 ceiling: "2.22".parse().unwrap(),
1174 };
1175 assert!(cfg
1176 .driver_args()
1177 .contains(&OsString::from("--sysroot=/sdk root")));
1178 let flags = cfg.rustflags(Path::new("/tool path/libc-linker")).unwrap();
1179 assert!(flags
1180 .to_str()
1181 .unwrap()
1182 .split('\u{1f}')
1183 .any(|s| s == "link-arg=--sysroot=/sdk root"));
1184 }
1185
1186 #[test]
1187 fn shell_values_are_single_quoted() {
1188 assert_eq!(shell_quote("/a b/c"), "'/a b/c'");
1189 assert_eq!(shell_quote("it's"), "'it'\\''s'");
1190 }
1191}