at 24.11-pre 268 lines 8.9 kB view raw
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}