about summary refs log tree commit diff
path: root/nixpkgs/pkgs/development/tools/rust/rustup/0001-dynamically-patchelf-binaries.patch
blob: c3d3b5f8d7b19b0062496e03fd9fa1c09e8cd2f4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
diff --git a/src/dist/component/package.rs b/src/dist/component/package.rs
index dfccc661..85233f3b 100644
--- a/src/dist/component/package.rs
+++ b/src/dist/component/package.rs
@@ -113,6 +113,7 @@ impl Package for DirectoryPackage {
                     } else {
                         builder.move_file(path.clone(), &src_path)?
                     }
+                    nix_patchelf_if_needed(&target.prefix().path().join(path.clone()))
                 }
                 "dir" => {
                     if self.copy {
@@ -135,6 +136,175 @@ impl Package for DirectoryPackage {
     }
 }
 
+fn nix_wrap_lld(dest_lld_path: &Path) -> Result<()> {
+    use std::fs;
+    use std::io::Write;
+    use std::os::unix::fs::PermissionsExt;
+
+    let path = dest_lld_path.parent().unwrap();
+    let mut unwrapped_name = path.file_name().unwrap().to_string_lossy().to_string();
+    unwrapped_name.push_str("-unwrapped");
+    let unwrapped_dir = path.with_file_name(unwrapped_name);
+    fs::create_dir(&unwrapped_dir).context("failed to create unwrapped directory")?;
+    let mut unwrapped_lld = unwrapped_dir;
+    unwrapped_lld.push(dest_lld_path.file_name().unwrap());
+    fs::rename(dest_lld_path, &unwrapped_lld).context("failed to move file")?;
+    let mut ld_wrapper_path = std::env::current_exe()?
+        .parent()
+        .ok_or(anyhow!("failed to get parent directory"))?
+        .with_file_name("nix-support");
+    let mut file = std::fs::File::create(dest_lld_path)?;
+    ld_wrapper_path.push("ld-wrapper.sh");
+
+    let wrapped_script = format!(
+        "#!/usr/bin/env bash
+set -eu -o pipefail +o posix
+shopt -s nullglob
+export PROG=\"{}\"
+\"{}\" $@",
+        unwrapped_lld.to_string_lossy().to_string(),
+        ld_wrapper_path.to_string_lossy().to_string(),
+    );
+    file.write_all(wrapped_script.as_bytes())?;
+    let mut permissions = file.metadata()?.permissions();
+    permissions.set_mode(0o755);
+    file.set_permissions(permissions)?;
+    Ok(())
+}
+
+fn nix_patchelf_if_needed(dest_path: &Path) {
+    use std::fs::File;
+    use std::os::unix::fs::FileExt;
+
+    struct ELFReader<'a> {
+        file: &'a mut File,
+        is_32bit: bool,
+        is_little_end: bool,
+    }
+
+    impl<'a> ELFReader<'a> {
+        const MAGIC_NUMBER: &'static [u8] = &[0x7F, 0x45, 0x4c, 0x46];
+        const ET_EXEC: u16 = 0x2;
+        const ET_DYN: u16 = 0x3;
+        const PT_INTERP: u32 = 0x3;
+
+        fn new(file: &'a mut File) -> Option<Self> {
+            let mut magic_number = [0; 4];
+            file.read_exact(&mut magic_number).ok()?;
+            if Self::MAGIC_NUMBER != magic_number {
+                return None;
+            }
+            let mut ei_class = [0; 1];
+            file.read_exact_at(&mut ei_class, 0x4).ok()?;
+            let is_32bit = ei_class[0] == 1;
+            let mut ei_data = [0; 1];
+            file.read_exact_at(&mut ei_data, 0x5).ok()?;
+            let is_little_end = ei_data[0] == 1;
+            Some(Self {
+                file,
+                is_32bit,
+                is_little_end,
+            })
+        }
+
+        fn is_exec_or_dyn(&self) -> bool {
+            let e_type = self.read_u16_at(0x10);
+            e_type == Self::ET_EXEC || e_type == Self::ET_DYN
+        }
+
+        fn e_phoff(&self) -> u64 {
+            if self.is_32bit {
+                self.read_u32_at(0x1C) as u64
+            } else {
+                self.read_u64_at(0x20)
+            }
+        }
+
+        fn e_phentsize(&self) -> u64 {
+            let offset = if self.is_32bit { 0x2A } else { 0x36 };
+            self.read_u16_at(offset) as u64
+        }
+
+        fn e_phnum(&self) -> u64 {
+            let offset = if self.is_32bit { 0x2C } else { 0x38 };
+            self.read_u16_at(offset) as u64
+        }
+
+        fn has_interp(&self) -> bool {
+            let e_phoff = self.e_phoff();
+            let e_phentsize = self.e_phentsize();
+            let e_phnum = self.e_phnum();
+            for i in 0..e_phnum {
+                let p_type = self.read_u32_at(e_phoff + i * e_phentsize);
+                if p_type == Self::PT_INTERP {
+                    return true;
+                }
+            }
+            false
+        }
+
+        fn read_u16_at(&self, offset: u64) -> u16 {
+            let mut data = [0; 2];
+            self.file.read_exact_at(&mut data, offset).unwrap();
+            if self.is_little_end {
+                u16::from_le_bytes(data)
+            } else {
+                u16::from_be_bytes(data)
+            }
+        }
+
+        fn read_u32_at(&self, offset: u64) -> u32 {
+            let mut data = [0; 4];
+            self.file.read_exact_at(&mut data, offset).unwrap();
+            if self.is_little_end {
+                u32::from_le_bytes(data)
+            } else {
+                u32::from_be_bytes(data)
+            }
+        }
+
+        fn read_u64_at(&self, offset: u64) -> u64 {
+            let mut data = [0; 8];
+            self.file.read_exact_at(&mut data, offset).unwrap();
+            if self.is_little_end {
+                u64::from_le_bytes(data)
+            } else {
+                u64::from_be_bytes(data)
+            }
+        }
+    }
+
+    let Some(mut dest_file) = File::open(dest_path).ok() else {
+        return;
+    };
+    let Some(elf) = ELFReader::new(&mut dest_file) else {
+        return;
+    };
+    if !elf.is_exec_or_dyn() {
+        return;
+    }
+    let mut patch_command = std::process::Command::new("@patchelf@/bin/patchelf");
+    if elf.has_interp() {
+        patch_command
+            .arg("--set-interpreter")
+            .arg("@dynamicLinker@");
+    }
+    if Some(std::ffi::OsStr::new("rust-lld")) == dest_path.file_name() || !elf.has_interp() {
+        patch_command.arg("--add-rpath").arg("@libPath@");
+    }
+
+    debug!("patching {dest_path:?} using patchelf");
+    if let Err(err) = patch_command.arg(dest_path).output() {
+        warn!("failed to execute patchelf: {err:?}");
+    }
+
+    if Some(std::ffi::OsStr::new("ld.lld")) == dest_path.file_name() {
+        if let Err(err) = nix_wrap_lld(dest_path) {
+            warn!("failed to wrap `ld.lld`: {err:?}");
+        }
+    }
+}
+
 #[derive(Debug)]
 pub(crate) struct TarPackage<'a>(DirectoryPackage, temp::Dir<'a>);