1 use std::collections::{BTreeSet, HashSet, VecDeque};
3 use std::ffi::{OsStr, OsString};
6 use std::iter::FromIterator;
8 use std::path::{Component, Path, PathBuf};
9 use std::process::Command;
12 use goblin::{elf::Elf, Object};
13 use serde::Deserialize;
15 #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Deserialize, Hash)]
16 #[serde(rename_all = "lowercase")]
23 #[derive(PartialEq, Eq, Debug, Deserialize, Clone, Hash)]
24 #[serde(rename_all = "camelCase")]
26 use_priority: DLOpenPriority,
27 features: BTreeSet<String>,
30 #[derive(Deserialize, Debug)]
33 feature: Option<String>,
34 // description is in the spec, but we don't need it here.
35 // description: Option<String>,
36 priority: Option<DLOpenPriority>,
39 #[derive(Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
42 target: Option<String>,
43 dlopen: Option<DLOpenConfig>,
46 #[derive(PartialEq, Eq, Hash, Clone)]
49 dlopen: Option<DLOpenConfig>,
52 struct NonRepeatingQueue<T> {
57 impl<T> NonRepeatingQueue<T> {
58 fn new() -> NonRepeatingQueue<T> {
60 queue: VecDeque::new(),
66 impl<T: Clone + Eq + Hash> NonRepeatingQueue<T> {
67 fn push_back(&mut self, value: T) -> bool {
68 if self.seen.contains(&value) {
71 self.seen.insert(value.clone());
72 self.queue.push_back(value);
77 fn pop_front(&mut self) -> Option<T> {
78 self.queue.pop_front()
82 fn add_dependencies<P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug>(
86 dlopen: &Option<DLOpenConfig>,
87 queue: &mut NonRepeatingQueue<StorePath>,
88 ) -> eyre::Result<()> {
89 if let Some(interp) = elf.interpreter {
90 queue.push_back(StorePath {
91 path: Box::from(Path::new(interp)),
92 dlopen: dlopen.clone(),
96 let mut dlopen_libraries = vec![];
97 if let Some(dlopen) = dlopen {
99 .iter_note_sections(&contents, Some(".note.dlopen"))
103 let note = n.wrap_err_with(|| format!("bad note in {:?}", source))?;
104 // Payload is padded and zero terminated
105 let payload = ¬e.desc[..note
108 .position(|x| *x == 0)
109 .unwrap_or(note.desc.len())];
110 let parsed = serde_json::from_slice::<Vec<DLOpenNote>>(payload)?;
111 for mut parsed_note in parsed {
112 if dlopen.use_priority
113 >= parsed_note.priority.unwrap_or(DLOpenPriority::Recommended)
116 .map(|f| dlopen.features.contains(&f))
119 dlopen_libraries.append(&mut parsed_note.soname);
125 let rpaths = if elf.runpaths.len() > 0 {
127 } else if elf.rpaths.len() > 0 {
133 let rpaths_as_path = rpaths
135 .flat_map(|p| p.split(":"))
136 .map(|p| Box::<Path>::from(Path::new(p)))
137 .collect::<Vec<_>>();
142 .map(|s| s.to_string())
143 .chain(dlopen_libraries)
145 let mut found = false;
146 for path in &rpaths_as_path {
147 let lib = path.join(&line);
149 // No need to recurse. The queue will bring it back round.
150 queue.push_back(StorePath {
151 path: Box::from(lib.as_path()),
152 dlopen: dlopen.clone(),
159 // glibc makes it tricky to make this an error because
160 // none of the files have a useful rpath.
162 "Warning: Couldn't satisfy dependency {} for {:?}",
173 P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug,
174 S: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug,
178 dlopen: &Option<DLOpenConfig>,
179 queue: &mut NonRepeatingQueue<StorePath>,
180 ) -> eyre::Result<()> {
181 fs::copy(&source, &target)
182 .wrap_err_with(|| format!("failed to copy {:?} to {:?}", source, target))?;
185 fs::read(&source).wrap_err_with(|| format!("failed to read from {:?}", source))?;
187 if let Ok(Object::Elf(e)) = Object::parse(&contents) {
188 add_dependencies(source, e, &contents, &dlopen, queue)?;
190 // Make file writable to strip it
191 let mut permissions = fs::metadata(&target)
192 .wrap_err_with(|| format!("failed to get metadata for {:?}", target))?
194 permissions.set_readonly(false);
195 fs::set_permissions(&target, permissions)
196 .wrap_err_with(|| format!("failed to set readonly flag to false for {:?}", target))?;
198 // Strip further than normal
199 if let Ok(strip) = env::var("STRIP") {
200 if !Command::new(strip)
202 .arg(OsStr::new(&target))
207 println!("{:?} was not successfully stripped.", OsStr::new(&target));
215 fn queue_dir<P: AsRef<Path> + std::fmt::Debug>(
217 dlopen: &Option<DLOpenConfig>,
218 queue: &mut NonRepeatingQueue<StorePath>,
219 ) -> eyre::Result<()> {
221 fs::read_dir(&source).wrap_err_with(|| format!("failed to read dir {:?}", source))?
224 // No need to recurse. The queue will bring us back round here on its own.
225 queue.push_back(StorePath {
226 path: Box::from(entry.path().as_path()),
227 dlopen: dlopen.clone(),
237 queue: &mut NonRepeatingQueue<StorePath>,
238 ) -> eyre::Result<()> {
239 let mut source = PathBuf::new();
240 let mut target = Path::new(root).to_path_buf();
241 let mut iter = p.path.components().peekable();
242 while let Some(comp) = iter.next() {
244 Component::Prefix(_) => panic!("This tool is not meant for Windows"),
245 Component::RootDir => {
251 Component::CurDir => {}
252 Component::ParentDir => {
253 // Don't over-pop the target if the path has too many ParentDirs
258 Component::Normal(name) => {
261 let typ = fs::symlink_metadata(&source)
262 .wrap_err_with(|| format!("failed to get symlink metadata for {:?}", source))?
264 if typ.is_file() && !target.exists() {
265 copy_file(&source, &target, &p.dlopen, queue)?;
267 if let Some(filename) = source.file_name() {
268 source.set_file_name(OsString::from_iter([
271 OsStr::new("-wrapped"),
274 let wrapped_path = source.as_path();
275 if wrapped_path.exists() {
276 queue.push_back(StorePath {
277 path: Box::from(wrapped_path),
278 dlopen: p.dlopen.clone(),
282 } else if typ.is_symlink() {
283 let link_target = fs::read_link(&source)
284 .wrap_err_with(|| format!("failed to resolve symlink of {:?}", source))?;
286 // Create the link, then push its target to the queue
287 if !target.exists() && !target.is_symlink() {
288 unix::fs::symlink(&link_target, &target).wrap_err_with(|| {
289 format!("failed to symlink {:?} to {:?}", link_target, target)
293 source.push(link_target);
294 while let Some(c) = iter.next() {
297 let link_target_path = source.as_path();
298 if link_target_path.exists() {
299 queue.push_back(StorePath {
300 path: Box::from(link_target_path),
301 dlopen: p.dlopen.clone(),
305 } else if typ.is_dir() {
306 if !target.exists() {
307 fs::create_dir(&target)
308 .wrap_err_with(|| format!("failed to create dir {:?}", target))?;
311 // Only recursively copy if the directory is the target object
312 if iter.peek().is_none() {
313 queue_dir(&source, &p.dlopen, queue)
314 .wrap_err_with(|| format!("failed to queue dir {:?}", source))?;
324 fn main() -> eyre::Result<()> {
325 let args: Vec<String> = env::args().collect();
327 fs::read(&args[1]).wrap_err_with(|| format!("failed to open file {:?}", &args[1]))?;
328 let input = serde_json::from_slice::<Vec<StoreInput>>(&contents)
330 let text = String::from_utf8_lossy(&contents);
331 format!("failed to parse JSON '{}' in {:?}",
335 let output = &args[2];
336 let out_path = Path::new(output);
338 let mut queue = NonRepeatingQueue::<StorePath>::new();
341 let obj_path = Path::new(&sp.source);
342 queue.push_back(StorePath {
343 path: Box::from(obj_path),
346 if let Some(target) = sp.target {
347 println!("{} -> {}", &target, &sp.source);
348 // We don't care about preserving symlink structure here
349 // nearly as much as for the actual objects.
350 let link_string = format!("{}/{}", output, target);
351 let link_path = Path::new(&link_string);
352 let mut link_parent = link_path.to_path_buf();
354 fs::create_dir_all(&link_parent)
355 .wrap_err_with(|| format!("failed to create directories to {:?}", link_parent))?;
356 unix::fs::symlink(obj_path, link_path)
357 .wrap_err_with(|| format!("failed to symlink {:?} to {:?}", obj_path, link_path))?;
360 while let Some(obj) = queue.pop_front() {
361 handle_path(out_path, obj, &mut queue)?;