Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1use std::collections::{BTreeSet, HashSet, VecDeque};
2use std::env;
3use std::ffi::{OsStr, OsString};
4use std::fs;
5use std::hash::Hash;
6use std::iter::FromIterator;
7use std::os::unix;
8use std::os::unix::fs::PermissionsExt;
9use std::path::{Component, Path, PathBuf};
10use std::process::Command;
11
12use libc::umask;
13
14use eyre::Context;
15use goblin::{elf::Elf, Object};
16use serde::Deserialize;
17
18#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Deserialize, Hash)]
19#[serde(rename_all = "lowercase")]
20enum DLOpenPriority {
21 Required,
22 Recommended,
23 Suggested,
24}
25
26#[derive(PartialEq, Eq, Debug, Deserialize, Clone, Hash)]
27#[serde(rename_all = "camelCase")]
28struct DLOpenConfig {
29 use_priority: DLOpenPriority,
30 features: BTreeSet<String>,
31}
32
33#[derive(Deserialize, Debug)]
34struct DLOpenNote {
35 soname: Vec<String>,
36 feature: Option<String>,
37 // description is in the spec, but we don't need it here.
38 // description: Option<String>,
39 priority: Option<DLOpenPriority>,
40}
41
42#[derive(Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
43struct StoreInput {
44 source: String,
45 target: Option<String>,
46 dlopen: Option<DLOpenConfig>,
47}
48
49#[derive(PartialEq, Eq, Hash, Clone)]
50struct StorePath {
51 path: Box<Path>,
52 dlopen: Option<DLOpenConfig>,
53}
54
55struct NonRepeatingQueue<T> {
56 queue: VecDeque<T>,
57 seen: HashSet<T>,
58}
59
60impl<T> NonRepeatingQueue<T> {
61 fn new() -> NonRepeatingQueue<T> {
62 NonRepeatingQueue {
63 queue: VecDeque::new(),
64 seen: HashSet::new(),
65 }
66 }
67}
68
69impl<T: Clone + Eq + Hash> NonRepeatingQueue<T> {
70 fn push_back(&mut self, value: T) -> bool {
71 if self.seen.contains(&value) {
72 false
73 } else {
74 self.seen.insert(value.clone());
75 self.queue.push_back(value);
76 true
77 }
78 }
79
80 fn pop_front(&mut self) -> Option<T> {
81 self.queue.pop_front()
82 }
83}
84
85fn add_dependencies<P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug>(
86 source: P,
87 elf: Elf,
88 contents: &[u8],
89 dlopen: &Option<DLOpenConfig>,
90 queue: &mut NonRepeatingQueue<StorePath>,
91) -> eyre::Result<()> {
92 if let Some(interp) = elf.interpreter {
93 queue.push_back(StorePath {
94 path: Box::from(Path::new(interp)),
95 dlopen: dlopen.clone(),
96 });
97 }
98
99 let mut dlopen_libraries = vec![];
100 if let Some(dlopen) = dlopen {
101 for n in elf
102 .iter_note_sections(&contents, Some(".note.dlopen"))
103 .into_iter()
104 .flatten()
105 {
106 let note = n.wrap_err_with(|| format!("bad note in {:?}", source))?;
107 // Payload is padded and zero terminated
108 let payload = ¬e.desc[..note
109 .desc
110 .iter()
111 .position(|x| *x == 0)
112 .unwrap_or(note.desc.len())];
113 let parsed = serde_json::from_slice::<Vec<DLOpenNote>>(payload)?;
114 for mut parsed_note in parsed {
115 if dlopen.use_priority
116 >= parsed_note.priority.unwrap_or(DLOpenPriority::Recommended)
117 || parsed_note
118 .feature
119 .map(|f| dlopen.features.contains(&f))
120 .unwrap_or(false)
121 {
122 dlopen_libraries.append(&mut parsed_note.soname);
123 }
124 }
125 }
126 }
127
128 let rpaths = if elf.runpaths.len() > 0 {
129 elf.runpaths
130 } else if elf.rpaths.len() > 0 {
131 elf.rpaths
132 } else {
133 vec![]
134 };
135
136 let rpaths_as_path = rpaths
137 .into_iter()
138 .flat_map(|p| p.split(":"))
139 .map(|p| Box::<Path>::from(Path::new(p)))
140 .collect::<Vec<_>>();
141
142 for line in elf
143 .libraries
144 .into_iter()
145 .map(|s| s.to_string())
146 .chain(dlopen_libraries)
147 {
148 let mut found = false;
149 for path in &rpaths_as_path {
150 let lib = path.join(&line);
151 if lib.exists() {
152 // No need to recurse. The queue will bring it back round.
153 queue.push_back(StorePath {
154 path: Box::from(lib.as_path()),
155 dlopen: dlopen.clone(),
156 });
157 found = true;
158 break;
159 }
160 }
161 if !found {
162 // glibc makes it tricky to make this an error because
163 // none of the files have a useful rpath.
164 println!(
165 "Warning: Couldn't satisfy dependency {} for {:?}",
166 line,
167 OsStr::new(&source)
168 );
169 }
170 }
171
172 Ok(())
173}
174
175fn copy_file<
176 P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug,
177 S: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug,
178>(
179 source: P,
180 target: S,
181 dlopen: &Option<DLOpenConfig>,
182 queue: &mut NonRepeatingQueue<StorePath>,
183) -> eyre::Result<()> {
184 fs::copy(&source, &target)
185 .wrap_err_with(|| format!("failed to copy {:?} to {:?}", source, target))?;
186
187 let contents =
188 fs::read(&source).wrap_err_with(|| format!("failed to read from {:?}", source))?;
189
190 if let Ok(Object::Elf(e)) = Object::parse(&contents) {
191 add_dependencies(source, e, &contents, &dlopen, queue)?;
192 };
193
194 Ok(())
195}
196
197fn queue_dir<P: AsRef<Path> + std::fmt::Debug>(
198 source: P,
199 dlopen: &Option<DLOpenConfig>,
200 queue: &mut NonRepeatingQueue<StorePath>,
201) -> eyre::Result<()> {
202 for entry in
203 fs::read_dir(&source).wrap_err_with(|| format!("failed to read dir {:?}", source))?
204 {
205 let entry = entry?;
206 // No need to recurse. The queue will bring us back round here on its own.
207 queue.push_back(StorePath {
208 path: Box::from(entry.path().as_path()),
209 dlopen: dlopen.clone(),
210 });
211 }
212
213 Ok(())
214}
215
216fn handle_path(
217 root: &Path,
218 p: StorePath,
219 queue: &mut NonRepeatingQueue<StorePath>,
220) -> eyre::Result<()> {
221 let mut source = PathBuf::new();
222 let mut target = Path::new(root).to_path_buf();
223 let mut iter = p.path.components().peekable();
224 while let Some(comp) = iter.next() {
225 match comp {
226 Component::Prefix(_) => panic!("This tool is not meant for Windows"),
227 Component::RootDir => {
228 target.clear();
229 target.push(root);
230 source.clear();
231 source.push("/");
232 }
233 Component::CurDir => {}
234 Component::ParentDir => {
235 // Don't over-pop the target if the path has too many ParentDirs
236 if source.pop() {
237 target.pop();
238 }
239 }
240 Component::Normal(name) => {
241 target.push(name);
242 source.push(name);
243 let typ = fs::symlink_metadata(&source)
244 .wrap_err_with(|| format!("failed to get symlink metadata for {:?}", source))?
245 .file_type();
246 if typ.is_file() && !target.exists() {
247 copy_file(&source, &target, &p.dlopen, queue)?;
248
249 if let Some(filename) = source.file_name() {
250 source.set_file_name(OsString::from_iter([
251 OsStr::new("."),
252 filename,
253 OsStr::new("-wrapped"),
254 ]));
255
256 let wrapped_path = source.as_path();
257 if wrapped_path.exists() {
258 queue.push_back(StorePath {
259 path: Box::from(wrapped_path),
260 dlopen: p.dlopen.clone(),
261 });
262 }
263 }
264 } else if typ.is_symlink() {
265 let link_target = fs::read_link(&source)
266 .wrap_err_with(|| format!("failed to resolve symlink of {:?}", source))?;
267
268 // Create the link, then push its target to the queue
269 if !target.exists() && !target.is_symlink() {
270 unix::fs::symlink(&link_target, &target).wrap_err_with(|| {
271 format!("failed to symlink {:?} to {:?}", link_target, target)
272 })?;
273 }
274 source.pop();
275 source.push(link_target);
276 while let Some(c) = iter.next() {
277 source.push(c);
278 }
279 let link_target_path = source.as_path();
280 if link_target_path.exists() {
281 queue.push_back(StorePath {
282 path: Box::from(link_target_path),
283 dlopen: p.dlopen.clone(),
284 });
285 }
286 break;
287 } else if typ.is_dir() {
288 if !target.exists() {
289 fs::create_dir(&target)
290 .wrap_err_with(|| format!("failed to create dir {:?}", target))?;
291 }
292
293 // Only recursively copy if the directory is the target object
294 if iter.peek().is_none() {
295 queue_dir(&source, &p.dlopen, queue)
296 .wrap_err_with(|| format!("failed to queue dir {:?}", source))?;
297 }
298 }
299 }
300 }
301 }
302
303 Ok(())
304}
305
306fn main() -> eyre::Result<()> {
307 let args: Vec<String> = env::args().collect();
308 let contents =
309 fs::read(&args[1]).wrap_err_with(|| format!("failed to open file {:?}", &args[1]))?;
310 let input = serde_json::from_slice::<Vec<StoreInput>>(&contents)
311 .wrap_err_with(|| {
312 let text = String::from_utf8_lossy(&contents);
313 format!("failed to parse JSON '{}' in {:?}",
314 text,
315 &args[1])
316 })?;
317 let output = &args[2];
318 let out_path = Path::new(output);
319
320 // The files we create should not be writable.
321 unsafe { umask(0o022) };
322
323 let mut queue = NonRepeatingQueue::<StorePath>::new();
324
325 for sp in input {
326 let obj_path = Path::new(&sp.source);
327 queue.push_back(StorePath {
328 path: Box::from(obj_path),
329 dlopen: sp.dlopen,
330 });
331 if let Some(target) = sp.target {
332 println!("{} -> {}", &target, &sp.source);
333 // We don't care about preserving symlink structure here
334 // nearly as much as for the actual objects.
335 let link_string = format!("{}/{}", output, target);
336 let link_path = Path::new(&link_string);
337 let mut link_parent = link_path.to_path_buf();
338 link_parent.pop();
339 fs::create_dir_all(&link_parent)
340 .wrap_err_with(|| format!("failed to create directories to {:?}", link_parent))?;
341 unix::fs::symlink(obj_path, link_path)
342 .wrap_err_with(|| format!("failed to symlink {:?} to {:?}", obj_path, link_path))?;
343 }
344 }
345 while let Some(obj) = queue.pop_front() {
346 handle_path(out_path, obj, &mut queue)?;
347 }
348
349 Ok(())
350}