@@ -2,13 +2,14 @@ use std::any::{type_name, Any};
22use std:: cell:: { Cell , RefCell } ;
33use std:: collections:: BTreeSet ;
44use std:: env;
5- use std:: ffi:: OsStr ;
5+ use std:: ffi:: { OsStr , OsString } ;
66use std:: fmt:: { Debug , Write } ;
7- use std:: fs;
7+ use std:: fs:: { self , File } ;
88use std:: hash:: Hash ;
9+ use std:: io:: { BufRead , BufReader , ErrorKind } ;
910use std:: ops:: Deref ;
1011use std:: path:: { Component , Path , PathBuf } ;
11- use std:: process:: Command ;
12+ use std:: process:: { Command , Stdio } ;
1213use std:: time:: { Duration , Instant } ;
1314
1415use crate :: cache:: { Cache , Interned , INTERNER } ;
@@ -29,7 +30,8 @@ use crate::{Build, CLang, DocTests, GitRepo, Mode};
2930
3031pub 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
3436pub 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 ! ( "\n spurious 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