@@ -2,13 +2,14 @@ use std::any::{type_name, Any};
2
2
use std:: cell:: { Cell , RefCell } ;
3
3
use std:: collections:: BTreeSet ;
4
4
use std:: env;
5
- use std:: ffi:: OsStr ;
5
+ use std:: ffi:: { OsStr , OsString } ;
6
6
use std:: fmt:: { Debug , Write } ;
7
- use std:: fs;
7
+ use std:: fs:: { self , File } ;
8
8
use std:: hash:: Hash ;
9
+ use std:: io:: { BufRead , BufReader , ErrorKind } ;
9
10
use std:: ops:: Deref ;
10
11
use std:: path:: { Component , Path , PathBuf } ;
11
- use std:: process:: Command ;
12
+ use std:: process:: { Command , Stdio } ;
12
13
use std:: time:: { Duration , Instant } ;
13
14
14
15
use crate :: cache:: { Cache , Interned , INTERNER } ;
@@ -29,7 +30,8 @@ use crate::{Build, CLang, DocTests, GitRepo, Mode};
29
30
30
31
pub use crate :: Compiler ;
31
32
// 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 ;
33
35
34
36
pub struct Builder < ' a > {
35
37
pub build : & ' a Build ,
@@ -758,6 +760,207 @@ impl<'a> Builder<'a> {
758
760
StepDescription :: run ( v, self , paths) ;
759
761
}
760
762
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
+
761
964
/// Obtain a compiler at a given stage and for a given host. Explicitly does
762
965
/// not take `Compiler` since all `Compiler` instances are meant to be
763
966
/// obtained through this function, since it ensures that they are valid
0 commit comments