Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at python-updates 350 lines 11 kB view raw
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 = &note.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}