biome: 1.9.2 -> 1.9.3
[NixPkgs.git] / pkgs / build-support / kernel / make-initrd-ng / src / main.rs
blob934c2faebed8f4c355117999d9563c2b64b277d6
1 use std::collections::{BTreeSet, HashSet, VecDeque};
2 use std::env;
3 use std::ffi::{OsStr, OsString};
4 use std::fs;
5 use std::hash::Hash;
6 use std::iter::FromIterator;
7 use std::os::unix;
8 use std::path::{Component, Path, PathBuf};
9 use std::process::Command;
11 use eyre::Context;
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")]
17 enum DLOpenPriority {
18     Required,
19     Recommended,
20     Suggested,
23 #[derive(PartialEq, Eq, Debug, Deserialize, Clone, Hash)]
24 #[serde(rename_all = "camelCase")]
25 struct DLOpenConfig {
26     use_priority: DLOpenPriority,
27     features: BTreeSet<String>,
30 #[derive(Deserialize, Debug)]
31 struct DLOpenNote {
32     soname: Vec<String>,
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)]
40 struct StoreInput {
41     source: String,
42     target: Option<String>,
43     dlopen: Option<DLOpenConfig>,
46 #[derive(PartialEq, Eq, Hash, Clone)]
47 struct StorePath {
48     path: Box<Path>,
49     dlopen: Option<DLOpenConfig>,
52 struct NonRepeatingQueue<T> {
53     queue: VecDeque<T>,
54     seen: HashSet<T>,
57 impl<T> NonRepeatingQueue<T> {
58     fn new() -> NonRepeatingQueue<T> {
59         NonRepeatingQueue {
60             queue: VecDeque::new(),
61             seen: HashSet::new(),
62         }
63     }
66 impl<T: Clone + Eq + Hash> NonRepeatingQueue<T> {
67     fn push_back(&mut self, value: T) -> bool {
68         if self.seen.contains(&value) {
69             false
70         } else {
71             self.seen.insert(value.clone());
72             self.queue.push_back(value);
73             true
74         }
75     }
77     fn pop_front(&mut self) -> Option<T> {
78         self.queue.pop_front()
79     }
82 fn add_dependencies<P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug>(
83     source: P,
84     elf: Elf,
85     contents: &[u8],
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(),
93         });
94     }
96     let mut dlopen_libraries = vec![];
97     if let Some(dlopen) = dlopen {
98         for n in elf
99             .iter_note_sections(&contents, Some(".note.dlopen"))
100             .into_iter()
101             .flatten()
102         {
103             let note = n.wrap_err_with(|| format!("bad note in {:?}", source))?;
104             // Payload is padded and zero terminated
105             let payload = &note.desc[..note
106                 .desc
107                 .iter()
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)
114                     || parsed_note
115                         .feature
116                         .map(|f| dlopen.features.contains(&f))
117                         .unwrap_or(false)
118                 {
119                     dlopen_libraries.append(&mut parsed_note.soname);
120                 }
121             }
122         }
123     }
125     let rpaths = if elf.runpaths.len() > 0 {
126         elf.runpaths
127     } else if elf.rpaths.len() > 0 {
128         elf.rpaths
129     } else {
130         vec![]
131     };
133     let rpaths_as_path = rpaths
134         .into_iter()
135         .flat_map(|p| p.split(":"))
136         .map(|p| Box::<Path>::from(Path::new(p)))
137         .collect::<Vec<_>>();
139     for line in elf
140         .libraries
141         .into_iter()
142         .map(|s| s.to_string())
143         .chain(dlopen_libraries)
144     {
145         let mut found = false;
146         for path in &rpaths_as_path {
147             let lib = path.join(&line);
148             if lib.exists() {
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(),
153                 });
154                 found = true;
155                 break;
156             }
157         }
158         if !found {
159             // glibc makes it tricky to make this an error because
160             // none of the files have a useful rpath.
161             println!(
162                 "Warning: Couldn't satisfy dependency {} for {:?}",
163                 line,
164                 OsStr::new(&source)
165             );
166         }
167     }
169     Ok(())
172 fn copy_file<
173     P: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug,
174     S: AsRef<Path> + AsRef<OsStr> + std::fmt::Debug,
176     source: P,
177     target: S,
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))?;
184     let contents =
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))?
193             .permissions();
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)
201                 .arg("--strip-all")
202                 .arg(OsStr::new(&target))
203                 .output()?
204                 .status
205                 .success()
206             {
207                 println!("{:?} was not successfully stripped.", OsStr::new(&target));
208             }
209         }
210     };
212     Ok(())
215 fn queue_dir<P: AsRef<Path> + std::fmt::Debug>(
216     source: P,
217     dlopen: &Option<DLOpenConfig>,
218     queue: &mut NonRepeatingQueue<StorePath>,
219 ) -> eyre::Result<()> {
220     for entry in
221         fs::read_dir(&source).wrap_err_with(|| format!("failed to read dir {:?}", source))?
222     {
223         let entry = entry?;
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(),
228         });
229     }
231     Ok(())
234 fn handle_path(
235     root: &Path,
236     p: StorePath,
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() {
243         match comp {
244             Component::Prefix(_) => panic!("This tool is not meant for Windows"),
245             Component::RootDir => {
246                 target.clear();
247                 target.push(root);
248                 source.clear();
249                 source.push("/");
250             }
251             Component::CurDir => {}
252             Component::ParentDir => {
253                 // Don't over-pop the target if the path has too many ParentDirs
254                 if source.pop() {
255                     target.pop();
256                 }
257             }
258             Component::Normal(name) => {
259                 target.push(name);
260                 source.push(name);
261                 let typ = fs::symlink_metadata(&source)
262                     .wrap_err_with(|| format!("failed to get symlink metadata for {:?}", source))?
263                     .file_type();
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([
269                             OsStr::new("."),
270                             filename,
271                             OsStr::new("-wrapped"),
272                         ]));
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(),
279                             });
280                         }
281                     }
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)
290                         })?;
291                     }
292                     source.pop();
293                     source.push(link_target);
294                     while let Some(c) = iter.next() {
295                         source.push(c);
296                     }
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(),
302                         });
303                     }
304                     break;
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))?;
309                     }
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))?;
315                     }
316                 }
317             }
318         }
319     }
321     Ok(())
324 fn main() -> eyre::Result<()> {
325     let args: Vec<String> = env::args().collect();
326     let contents =
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)
329         .wrap_err_with(|| {
330             let text = String::from_utf8_lossy(&contents);
331             format!("failed to parse JSON '{}' in {:?}",
332                 text,
333                 &args[1])
334         })?;
335     let output = &args[2];
336     let out_path = Path::new(output);
338     let mut queue = NonRepeatingQueue::<StorePath>::new();
340     for sp in input {
341         let obj_path = Path::new(&sp.source);
342         queue.push_back(StorePath {
343             path: Box::from(obj_path),
344             dlopen: sp.dlopen,
345         });
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();
353             link_parent.pop();
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))?;
358         }
359     }
360     while let Some(obj) = queue.pop_front() {
361         handle_path(out_path, obj, &mut queue)?;
362     }
364     Ok(())