1use std::collections::{HashSet, VecDeque};
2use std::env;
3use std::ffi::{OsStr, OsString};
4use std::fs;
5use std::hash::Hash;
6use std::io::{BufRead, BufReader};
7use std::iter::FromIterator;
8use std::os::unix;
9use std::path::{Component, Path, PathBuf};
10use std::process::Command;
11
12use eyre::Context;
13use goblin::{elf::Elf, Object};
14
15struct NonRepeatingQueue<T> {
16 queue: VecDeque<T>,
17 seen: HashSet<T>,
18}
19
20impl<T> NonRepeatingQueue<T> {
21 fn new() -> NonRepeatingQueue<T> {
22 NonRepeatingQueue {
23 queue: VecDeque::new(),
24 seen: HashSet::new(),
25 }
26 }
27}
28
29impl<T: Clone + Eq + Hash> NonRepeatingQueue<T> {
30 fn push_back(&mut self, value: T) -> bool {
31 if self.seen.contains(&value) {
32 false
33 } else {
34 self.seen.insert(value.clone());
35 self.queue.push_back(value);
36 true
37 }
38 }
39
40 fn pop_front(&mut self) -> Option<T> {
41 self.queue.pop_front()
42 }
43}
44
45fn add_dependencies<P: AsRef<Path> + AsRef<OsStr>>(
46 source: P,
47 elf: Elf,
48 queue: &mut NonRepeatingQueue<Box<Path>>,
49) {
50 if let Some(interp) = elf.interpreter {
51 queue.push_back(Box::from(Path::new(interp)));
52 }
53
54 let rpaths = if elf.runpaths.len() > 0 {
55 elf.runpaths
56 } else if elf.rpaths.len() > 0 {
57 elf.rpaths
58 } else {
59 vec![]
60 };
61
62 let rpaths_as_path = rpaths
63 .into_iter()
64 .flat_map(|p| p.split(":"))
65 .map(|p| Box::<Path>::from(Path::new(p)))
66 .collect::<Vec<_>>();
67
68 for line in elf.libraries {
69 let mut found = false;
70 for path in &rpaths_as_path {
71 let lib = path.join(line);
72 if lib.exists() {
73 // No need to recurse. The queue will bring it back round.
74 queue.push_back(Box::from(lib.as_path()));
75 found = true;
76 break;
77 }
78 }
79 if !found {
80 // glibc makes it tricky to make this an error because
81 // none of the files have a useful rpath.
82 println!(
83 "Warning: Couldn't satisfy dependency {} for {:?}",
84 line,
85 OsStr::new(&source)
86 );
87 }
88 }
89}
90
91fn copy_file<
92 P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug,
93 S: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug,
94>(
95 source: P,
96 target: S,
97 queue: &mut NonRepeatingQueue<Box<Path>>,
98) -> eyre::Result<()> {
99 fs::copy(&source, &target)
100 .wrap_err_with(|| format!("failed to copy {:?} to {:?}", source, target))?;
101
102 let contents =
103 fs::read(&source).wrap_err_with(|| format!("failed to read from {:?}", source))?;
104
105 if let Ok(Object::Elf(e)) = Object::parse(&contents) {
106 add_dependencies(source, e, queue);
107
108 // Make file writable to strip it
109 let mut permissions = fs::metadata(&target)
110 .wrap_err_with(|| format!("failed to get metadata for {:?}", target))?
111 .permissions();
112 permissions.set_readonly(false);
113 fs::set_permissions(&target, permissions)
114 .wrap_err_with(|| format!("failed to set readonly flag to false for {:?}", target))?;
115
116 // Strip further than normal
117 if let Ok(strip) = env::var("STRIP") {
118 if !Command::new(strip)
119 .arg("--strip-all")
120 .arg(OsStr::new(&target))
121 .output()?
122 .status
123 .success()
124 {
125 println!("{:?} was not successfully stripped.", OsStr::new(&target));
126 }
127 }
128 };
129
130 Ok(())
131}
132
133fn queue_dir<P: AsRef<Path> + std::fmt::Debug>(
134 source: P,
135 queue: &mut NonRepeatingQueue<Box<Path>>,
136) -> eyre::Result<()> {
137 for entry in
138 fs::read_dir(&source).wrap_err_with(|| format!("failed to read dir {:?}", source))?
139 {
140 let entry = entry?;
141 // No need to recurse. The queue will bring us back round here on its own.
142 queue.push_back(Box::from(entry.path().as_path()));
143 }
144
145 Ok(())
146}
147
148fn handle_path(
149 root: &Path,
150 p: &Path,
151 queue: &mut NonRepeatingQueue<Box<Path>>,
152) -> eyre::Result<()> {
153 let mut source = PathBuf::new();
154 let mut target = Path::new(root).to_path_buf();
155 let mut iter = p.components().peekable();
156 while let Some(comp) = iter.next() {
157 match comp {
158 Component::Prefix(_) => panic!("This tool is not meant for Windows"),
159 Component::RootDir => {
160 target.clear();
161 target.push(root);
162 source.clear();
163 source.push("/");
164 }
165 Component::CurDir => {}
166 Component::ParentDir => {
167 // Don't over-pop the target if the path has too many ParentDirs
168 if source.pop() {
169 target.pop();
170 }
171 }
172 Component::Normal(name) => {
173 target.push(name);
174 source.push(name);
175 let typ = fs::symlink_metadata(&source)
176 .wrap_err_with(|| format!("failed to get symlink metadata for {:?}", source))?
177 .file_type();
178 if typ.is_file() && !target.exists() {
179 copy_file(&source, &target, queue)?;
180
181 if let Some(filename) = source.file_name() {
182 source.set_file_name(OsString::from_iter([
183 OsStr::new("."),
184 filename,
185 OsStr::new("-wrapped"),
186 ]));
187
188 let wrapped_path = source.as_path();
189 if wrapped_path.exists() {
190 queue.push_back(Box::from(wrapped_path));
191 }
192 }
193 } else if typ.is_symlink() {
194 let link_target = fs::read_link(&source)
195 .wrap_err_with(|| format!("failed to resolve symlink of {:?}", source))?;
196
197 // Create the link, then push its target to the queue
198 if !target.exists() && !target.is_symlink() {
199 unix::fs::symlink(&link_target, &target).wrap_err_with(|| {
200 format!("failed to symlink {:?} to {:?}", link_target, target)
201 })?;
202 }
203 source.pop();
204 source.push(link_target);
205 while let Some(c) = iter.next() {
206 source.push(c);
207 }
208 let link_target_path = source.as_path();
209 if link_target_path.exists() {
210 queue.push_back(Box::from(link_target_path));
211 }
212 break;
213 } else if typ.is_dir() {
214 if !target.exists() {
215 fs::create_dir(&target)
216 .wrap_err_with(|| format!("failed to create dir {:?}", target))?;
217 }
218
219 // Only recursively copy if the directory is the target object
220 if iter.peek().is_none() {
221 queue_dir(&source, queue)
222 .wrap_err_with(|| format!("failed to queue dir {:?}", source))?;
223 }
224 }
225 }
226 }
227 }
228
229 Ok(())
230}
231
232fn main() -> eyre::Result<()> {
233 let args: Vec<String> = env::args().collect();
234 let input =
235 fs::File::open(&args[1]).wrap_err_with(|| format!("failed to open file {:?}", &args[1]))?;
236 let output = &args[2];
237 let out_path = Path::new(output);
238
239 let mut queue = NonRepeatingQueue::<Box<Path>>::new();
240
241 let mut lines = BufReader::new(input).lines();
242 while let Some(obj) = lines.next() {
243 // Lines should always come in pairs
244 let obj = obj?;
245 let sym = lines.next().unwrap()?;
246
247 let obj_path = Path::new(&obj);
248 queue.push_back(Box::from(obj_path));
249 if !sym.is_empty() {
250 println!("{} -> {}", &sym, &obj);
251 // We don't care about preserving symlink structure here
252 // nearly as much as for the actual objects.
253 let link_string = format!("{}/{}", output, sym);
254 let link_path = Path::new(&link_string);
255 let mut link_parent = link_path.to_path_buf();
256 link_parent.pop();
257 fs::create_dir_all(&link_parent)
258 .wrap_err_with(|| format!("failed to create directories to {:?}", link_parent))?;
259 unix::fs::symlink(obj_path, link_path)
260 .wrap_err_with(|| format!("failed to symlink {:?} to {:?}", obj_path, link_path))?;
261 }
262 }
263 while let Some(obj) = queue.pop_front() {
264 handle_path(out_path, &*obj, &mut queue)?;
265 }
266
267 Ok(())
268}