From 1922297b4a67c51c8d0c4b3dccd4e51502d2a569 Mon Sep 17 00:00:00 2001 From: Matteo Settenvini Date: Mon, 2 Jun 2025 19:04:14 +0200 Subject: [PATCH] fix(dso): allow specifying extra lib folders on the cmdline Before we were relying on LD_LIBRARY_PATH, but it is misleading as it will most likely also affect the sysroot-cleaner binary itself. This instead introduces a separate command line argument, `--ld-path`, to specify a list of paths to search. Also includes some minor refactoring. --- Cargo.lock | 76 ++++++++++++++++----------------- Cargo.toml | 6 +-- src/args.rs | 18 +++++++- src/cleaners.rs | 5 ++- src/cleaners/dso.rs | 102 ++++++++++++++++++++++---------------------- 5 files changed, 112 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e396dbf..6a2bfbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,12 +73,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -107,9 +107,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -122,9 +122,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bstr" @@ -162,9 +162,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.37" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", "clap_derive", @@ -172,9 +172,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstream", "anstyle", @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "goblin" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa0a64d21a7eb230583b4c5f4e23b7e4e57974f96620f42a7e75e08ae66d745" +checksum = "0e961b33649994dcf69303af6b3a332c1228549e604d455d61ec5d2ab5e68d3a" dependencies = [ "log", "plain", @@ -359,9 +359,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "jiff" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd" +checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" dependencies = [ "jiff-static", "log", @@ -372,9 +372,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300" +checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" dependencies = [ "proc-macro2", "quote", @@ -389,9 +389,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -429,13 +429,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -460,16 +460,16 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -477,9 +477,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -606,18 +606,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scroll" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" dependencies = [ "scroll_derive", ] [[package]] name = "scroll_derive" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" +checksum = "22fc4f90c27b57691bbaf11d8ecc7cfbfe98a4da6dbe60226115d322aa80c06e" dependencies = [ "proc-macro2", "quote", @@ -661,9 +661,9 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -688,7 +688,7 @@ dependencies = [ [[package]] name = "sysroot-cleaner" -version = "0.1.0" +version = "0.9.0" dependencies = [ "anyhow", "async-trait", @@ -708,9 +708,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 7325698..6c0af43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "sysroot-cleaner" authors = ["Matteo Settenvini "] -version = "0.1.0" +version = "0.9.0" edition = "2024" license = "EUPL-1.2" readme = "README.md" @@ -16,11 +16,11 @@ clap = { version = "4.5", features = ["derive"] } env_logger = { version = "0.11" } ignore = { version = "0.4" } indoc = { version = "2.0" } -goblin = { version = "0.9" } +goblin = { version = "0.10" } log = { version = "0.4" } memmap2 = { version = "0.9" } nix = { version = "0.30", features = ["fs"] } petgraph = { version = "0.8" } tokio = { version = "1", features = ["full"] } walkdir = { version = "2" } -bytesize = { version = "2.0" } \ No newline at end of file +bytesize = { version = "2.0" } diff --git a/src/args.rs b/src/args.rs index 3e8fa5f..aa4d8ac 100644 --- a/src/args.rs +++ b/src/args.rs @@ -4,8 +4,8 @@ use std::path::PathBuf; use clap::{ - Parser, builder::{PathBufValueParser, TypedValueParser}, + Parser, }; #[derive(Clone, Default)] @@ -59,4 +59,20 @@ pub struct Args { /// The location of the sysroot to clean up pub sysroot_location: PathBuf, + + /// An additional path to consider when resolving + /// libraries, relative to the sysroot. + /// Their behavior is similar of the one of the + /// `LD_LIBRARY_PATH` environment variable when + /// specified to the dynamic linker. + #[arg(long = "ld-path", value_parser = relativize_path)] + pub extra_library_paths: Vec, +} + +fn relativize_path(arg: &str) -> anyhow::Result { + let mut p = PathBuf::from(arg); + if p.is_absolute() { + p = p.strip_prefix("/")?.into(); + } + Ok(p) } diff --git a/src/cleaners.rs b/src/cleaners.rs index 93369bc..124f6e5 100644 --- a/src/cleaners.rs +++ b/src/cleaners.rs @@ -64,7 +64,10 @@ impl Runner { cleaners.push(Box::new(ListCleaner::new(list::ListType::Block, bl))); } - cleaners.push(Box::new(DsoCleaner::new(args.output_dotfile))); + cleaners.push(Box::new(DsoCleaner::new( + args.extra_library_paths, + args.output_dotfile, + ))); Self { cleaners, diff --git a/src/cleaners/dso.rs b/src/cleaners/dso.rs index 433f500..87b8b9f 100644 --- a/src/cleaners/dso.rs +++ b/src/cleaners/dso.rs @@ -27,17 +27,22 @@ type InodeGraph = DiGraphMap; /// Cleans up unused shared libraries /// and warns about broken dependencies as well pub struct DsoCleaner { + extra_library_paths: Vec, output_dot: Option, } struct State { + ld_library_path: Vec, paths_map: InodeMap, graph: InodeGraph, } impl DsoCleaner { - pub fn new(output_dot: Option) -> Self { - Self { output_dot } + pub fn new(extra_library_paths: Vec, output_dot: Option) -> Self { + Self { + extra_library_paths, + output_dot, + } } } @@ -52,6 +57,11 @@ impl Cleaner for DsoCleaner { output: mpsc::Sender, ) -> Result<()> { let mut state = State::default(); + state.ld_library_path = self + .extra_library_paths + .iter() + .map(|p| p.to_str().unwrap().to_owned()) + .collect(); let mut inodes_to_keep = HashSet::new(); inodes_to_keep.insert(ROOT_NODE); @@ -127,7 +137,11 @@ impl Default for State { paths_map.insert(ROOT_NODE, HashSet::from([fake_root_node])); graph.add_node(ROOT_NODE); - Self { paths_map, graph } + Self { + ld_library_path: vec![], + paths_map, + graph, + } } } @@ -196,7 +210,7 @@ impl State { self.update_graph("".into(), ROOT_NODE, path.to_owned(), src.st_ino); } - let search_paths = determine_lib_search_paths(path, elf)?; + let search_paths = self.determine_lib_search_paths(path, elf)?; log::trace!("determined search paths {:#?}", search_paths); 'next_lib: for &library in elf.libraries.iter() { @@ -292,61 +306,45 @@ impl State { )?; Ok(()) } -} -fn determine_lib_search_paths(path: &Path, elf: &Elf<'_>) -> Result> { - log::trace!("elf.runpaths = {:#?}", elf.runpaths); - log::trace!("elf.rpaths = {:#?}", elf.rpaths); + // Contract: only relative paths starting from the sysroot dir should be returned from this function + fn determine_lib_search_paths(&self, path: &Path, elf: &Elf<'_>) -> Result> { + log::trace!( + "{}: elf.runpaths = {:#?}, elf.rpaths = {:#?}", + path.display(), + elf.runpaths, + elf.rpaths + ); - let mut search_paths = vec![]; + let current_dir = std::env::current_dir()?; + let origin = fs::canonicalize(path)? + .parent() + .unwrap() + .strip_prefix(current_dir)? + .to_path_buf() + .into_os_string() + .into_string() + .map_err(|s| anyhow::anyhow!("cannot represent {:?} as a UTF-8 string", s))?; - let current_dir = std::env::current_dir()?; - let origin = fs::canonicalize(path)? - .parent() - .unwrap() - .strip_prefix(current_dir)? - .to_path_buf() - .into_os_string() - .into_string() - .map_err(|s| anyhow::anyhow!("cannot represent {:?} as a UTF-8 string", s))?; + let mut search_paths = vec![]; + if elf.runpaths.is_empty() { + search_paths.extend(collect_paths(&elf.rpaths, &origin)); + } - if elf.runpaths.is_empty() { - collect_paths(&elf.rpaths, &mut search_paths, &origin); - } - - search_paths.append(&mut get_env_library_paths()); - - collect_paths(&elf.runpaths, &mut search_paths, &origin); - - search_paths.push("usr/local/lib".into()); - search_paths.push("lib".into()); - search_paths.push("usr/lib".into()); - Ok(search_paths) -} - -fn collect_paths(elf_paths: &Vec<&str>, search_paths: &mut Vec, origin: &str) { - if elf_paths.iter().any(|p| !p.is_empty()) { - let mut paths = elf_paths - .iter() - .flat_map(|p| p.split(':')) - .map(|p| p.replace("$ORIGIN", origin)) - .map(|p| p.trim_start_matches('/').to_string()) - .collect::>(); - search_paths.append(&mut paths); + search_paths.extend(self.ld_library_path.clone()); + search_paths.extend(collect_paths(&elf.runpaths, &origin)); + search_paths.extend(["usr/local/lib".into(), "lib".into(), "usr/lib".into()]); + Ok(search_paths) } } -fn get_env_library_paths() -> Vec { - let ld_config_path = std::env::var("LD_LIBRARY_PATH"); - ld_config_path - .as_ref() - .map(|env| { - env.split(':') - .filter(|s| s.is_empty()) - .map(|s| s.into()) - .collect() - }) - .unwrap_or_default() +fn collect_paths(elf_paths: &Vec<&str>, origin: &str) -> impl Iterator { + elf_paths + .iter() + .flat_map(|&p| p.split(':')) // Split multiple elements in r(un)?path separated by ':' + .filter(|&p| !p.is_empty()) // ignore empty items + .map(|p| p.replace("$ORIGIN", origin)) // replace $ORIGIN with path rel to sysroot + .map(|p| p.trim_start_matches('/').to_string()) // relativize paths from sysroot } fn is_elf(f: &mut File) -> Result {