Skip to content

Commit 61dd278

Browse files
committed
Move download functions from native to builder.rs
This has no logic changes, just a move.
1 parent 1b5e121 commit 61dd278

File tree

3 files changed

+222
-222
lines changed

3 files changed

+222
-222
lines changed

src/bootstrap/builder.rs

Lines changed: 207 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ use std::any::{type_name, Any};
22
use std::cell::{Cell, RefCell};
33
use std::collections::BTreeSet;
44
use std::env;
5-
use std::ffi::OsStr;
5+
use std::ffi::{OsStr, OsString};
66
use std::fmt::{Debug, Write};
7-
use std::fs;
7+
use std::fs::{self, File};
88
use std::hash::Hash;
9+
use std::io::{BufRead, BufReader, ErrorKind};
910
use std::ops::Deref;
1011
use std::path::{Component, Path, PathBuf};
11-
use std::process::Command;
12+
use std::process::{Command, Stdio};
1213
use std::time::{Duration, Instant};
1314

1415
use crate::cache::{Cache, Interned, INTERNER};
@@ -29,7 +30,8 @@ use crate::{Build, CLang, DocTests, GitRepo, Mode};
2930

3031
pub use crate::Compiler;
3132
// FIXME: replace with std::lazy after it gets stabilized and reaches beta
32-
use once_cell::sync::Lazy;
33+
use once_cell::sync::{Lazy, OnceCell};
34+
use xz2::bufread::XzDecoder;
3335

3436
pub struct Builder<'a> {
3537
pub build: &'a Build,
@@ -758,6 +760,207 @@ impl<'a> Builder<'a> {
758760
StepDescription::run(v, self, paths);
759761
}
760762

763+
/// Modifies the interpreter section of 'fname' to fix the dynamic linker,
764+
/// or the RPATH section, to fix the dynamic library search path
765+
///
766+
/// This is only required on NixOS and uses the PatchELF utility to
767+
/// change the interpreter/RPATH of ELF executables.
768+
///
769+
/// Please see https://nixos.org/patchelf.html for more information
770+
pub(crate) fn fix_bin_or_dylib(&self, fname: &Path) {
771+
// FIXME: cache NixOS detection?
772+
match Command::new("uname").arg("-s").stderr(Stdio::inherit()).output() {
773+
Err(_) => return,
774+
Ok(output) if !output.status.success() => return,
775+
Ok(output) => {
776+
let mut s = output.stdout;
777+
if s.last() == Some(&b'\n') {
778+
s.pop();
779+
}
780+
if s != b"Linux" {
781+
return;
782+
}
783+
}
784+
}
785+
786+
// If the user has asked binaries to be patched for Nix, then
787+
// don't check for NixOS or `/lib`, just continue to the patching.
788+
// FIXME: shouldn't this take precedence over the `uname` check above?
789+
if !self.config.patch_binaries_for_nix {
790+
// Use `/etc/os-release` instead of `/etc/NIXOS`.
791+
// The latter one does not exist on NixOS when using tmpfs as root.
792+
const NIX_IDS: &[&str] = &["ID=nixos", "ID='nixos'", "ID=\"nixos\""];
793+
let os_release = match File::open("/etc/os-release") {
794+
Err(e) if e.kind() == ErrorKind::NotFound => return,
795+
Err(e) => panic!("failed to access /etc/os-release: {}", e),
796+
Ok(f) => f,
797+
};
798+
if !BufReader::new(os_release).lines().any(|l| NIX_IDS.contains(&t!(l).trim())) {
799+
return;
800+
}
801+
if Path::new("/lib").exists() {
802+
return;
803+
}
804+
}
805+
806+
// At this point we're pretty sure the user is running NixOS or using Nix
807+
println!("info: you seem to be using Nix. Attempting to patch {}", fname.display());
808+
809+
// Only build `.nix-deps` once.
810+
static NIX_DEPS_DIR: OnceCell<PathBuf> = OnceCell::new();
811+
let mut nix_build_succeeded = true;
812+
let nix_deps_dir = NIX_DEPS_DIR.get_or_init(|| {
813+
// Run `nix-build` to "build" each dependency (which will likely reuse
814+
// the existing `/nix/store` copy, or at most download a pre-built copy).
815+
//
816+
// Importantly, we create a gc-root called `.nix-deps` in the `build/`
817+
// directory, but still reference the actual `/nix/store` path in the rpath
818+
// as it makes it significantly more robust against changes to the location of
819+
// the `.nix-deps` location.
820+
//
821+
// bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
822+
// zlib: Needed as a system dependency of `libLLVM-*.so`.
823+
// patchelf: Needed for patching ELF binaries (see doc comment above).
824+
let nix_deps_dir = self.out.join(".nix-deps");
825+
const NIX_EXPR: &str = "
826+
with (import <nixpkgs> {});
827+
symlinkJoin {
828+
name = \"rust-stage0-dependencies\";
829+
paths = [
830+
zlib
831+
patchelf
832+
stdenv.cc.bintools
833+
];
834+
}
835+
";
836+
nix_build_succeeded = self.try_run(Command::new("nix-build").args(&[
837+
Path::new("-E"),
838+
Path::new(NIX_EXPR),
839+
Path::new("-o"),
840+
&nix_deps_dir,
841+
]));
842+
nix_deps_dir
843+
});
844+
if !nix_build_succeeded {
845+
return;
846+
}
847+
848+
let mut patchelf = Command::new(nix_deps_dir.join("bin/patchelf"));
849+
let rpath_entries = {
850+
// ORIGIN is a relative default, all binary and dynamic libraries we ship
851+
// appear to have this (even when `../lib` is redundant).
852+
// NOTE: there are only two paths here, delimited by a `:`
853+
let mut entries = OsString::from("$ORIGIN/../lib:");
854+
entries.push(t!(fs::canonicalize(nix_deps_dir)));
855+
entries.push("/lib");
856+
entries
857+
};
858+
patchelf.args(&[OsString::from("--set-rpath"), rpath_entries]);
859+
if !fname.extension().map_or(false, |ext| ext == "so") {
860+
// Finally, set the corret .interp for binaries
861+
let dynamic_linker_path = nix_deps_dir.join("nix-support/dynamic-linker");
862+
// FIXME: can we support utf8 here? `args` doesn't accept Vec<u8>, only OsString ...
863+
let dynamic_linker = t!(String::from_utf8(t!(fs::read(dynamic_linker_path))));
864+
patchelf.args(&["--set-interpreter", dynamic_linker.trim_end()]);
865+
}
866+
867+
self.try_run(patchelf.arg(fname));
868+
}
869+
870+
pub(crate) fn download_component(&self, base: &str, url: &str, dest_path: &Path) {
871+
// Use a temporary file in case we crash while downloading, to avoid a corrupt download in cache/.
872+
let tempfile = self.tempdir().join(dest_path.file_name().unwrap());
873+
// FIXME: support `do_verify` (only really needed for nightly rustfmt)
874+
// FIXME: support non-utf8 paths?
875+
self.download_with_retries(tempfile.to_str().unwrap(), &format!("{}/{}", base, url));
876+
t!(std::fs::rename(&tempfile, dest_path));
877+
}
878+
879+
fn download_with_retries(&self, tempfile: &str, url: &str) {
880+
println!("downloading {}", url);
881+
// Try curl. If that fails and we are on windows, fallback to PowerShell.
882+
if !self.check_run(Command::new("curl").args(&[
883+
"-#",
884+
"-y",
885+
"30",
886+
"-Y",
887+
"10", // timeout if speed is < 10 bytes/sec for > 30 seconds
888+
"--connect-timeout",
889+
"30", // timeout if cannot connect within 30 seconds
890+
"--retry",
891+
"3",
892+
"-Sf",
893+
"-o",
894+
tempfile,
895+
url,
896+
])) {
897+
if self.build.build.contains("windows-msvc") {
898+
println!("Fallback to PowerShell");
899+
for _ in 0..3 {
900+
if self.try_run(Command::new("PowerShell.exe").args(&[
901+
"/nologo",
902+
"-Command",
903+
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
904+
&format!(
905+
"(New-Object System.Net.WebClient).DownloadFile('{}', '{}')",
906+
url, tempfile
907+
),
908+
])) {
909+
return;
910+
}
911+
println!("\nspurious failure, trying again");
912+
}
913+
}
914+
std::process::exit(1);
915+
}
916+
}
917+
918+
pub(crate) fn unpack(&self, tarball: &Path, dst: &Path) {
919+
println!("extracting {} to {}", tarball.display(), dst.display());
920+
if !dst.exists() {
921+
t!(fs::create_dir_all(dst));
922+
}
923+
924+
// FIXME: will need to be a parameter once `download-rustc` is moved to rustbuild
925+
const MATCH: &str = "rust-dev";
926+
927+
// `tarball` ends with `.tar.xz`; strip that suffix
928+
// example: `rust-dev-nightly-x86_64-unknown-linux-gnu`
929+
let uncompressed_filename =
930+
Path::new(tarball.file_name().expect("missing tarball filename")).file_stem().unwrap();
931+
let directory_prefix = Path::new(Path::new(uncompressed_filename).file_stem().unwrap());
932+
933+
// decompress the file
934+
let data = t!(File::open(tarball));
935+
let decompressor = XzDecoder::new(BufReader::new(data));
936+
937+
let mut tar = tar::Archive::new(decompressor);
938+
for member in t!(tar.entries()) {
939+
let mut member = t!(member);
940+
let original_path = t!(member.path()).into_owned();
941+
// skip the top-level directory
942+
if original_path == directory_prefix {
943+
continue;
944+
}
945+
let mut short_path = t!(original_path.strip_prefix(directory_prefix));
946+
if !short_path.starts_with(MATCH) {
947+
continue;
948+
}
949+
short_path = t!(short_path.strip_prefix(MATCH));
950+
let dst_path = dst.join(short_path);
951+
self.verbose(&format!("extracting {} to {}", original_path.display(), dst.display()));
952+
if !t!(member.unpack_in(dst)) {
953+
panic!("path traversal attack ??");
954+
}
955+
let src_path = dst.join(original_path);
956+
if src_path.is_dir() && dst_path.exists() {
957+
continue;
958+
}
959+
t!(fs::rename(src_path, dst_path));
960+
}
961+
t!(fs::remove_dir_all(dst.join(directory_prefix)));
962+
}
963+
761964
/// Obtain a compiler at a given stage and for a given host. Explicitly does
762965
/// not take `Compiler` since all `Compiler` instances are meant to be
763966
/// obtained through this function, since it ensures that they are valid

0 commit comments

Comments
 (0)