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.
This commit is contained in:
Matteo Settenvini 2025-06-02 19:04:14 +02:00
parent 6d06377154
commit 1922297b4a
Signed by: matteo
GPG key ID: 1C1B12600D81DE05
5 changed files with 112 additions and 95 deletions

76
Cargo.lock generated
View file

@ -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",

View file

@ -4,7 +4,7 @@
[package]
name = "sysroot-cleaner"
authors = ["Matteo Settenvini <matteo.settenvini@montecristosoftware.eu>"]
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" }
bytesize = { version = "2.0" }

View file

@ -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<PathBuf>,
}
fn relativize_path(arg: &str) -> anyhow::Result<PathBuf> {
let mut p = PathBuf::from(arg);
if p.is_absolute() {
p = p.strip_prefix("/")?.into();
}
Ok(p)
}

View file

@ -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,

View file

@ -27,17 +27,22 @@ type InodeGraph = DiGraphMap<ino_t, ()>;
/// Cleans up unused shared libraries
/// and warns about broken dependencies as well
pub struct DsoCleaner {
extra_library_paths: Vec<PathBuf>,
output_dot: Option<PathBuf>,
}
struct State {
ld_library_path: Vec<String>,
paths_map: InodeMap,
graph: InodeGraph,
}
impl DsoCleaner {
pub fn new(output_dot: Option<PathBuf>) -> Self {
Self { output_dot }
pub fn new(extra_library_paths: Vec<PathBuf>, output_dot: Option<PathBuf>) -> Self {
Self {
extra_library_paths,
output_dot,
}
}
}
@ -52,6 +57,11 @@ impl Cleaner for DsoCleaner {
output: mpsc::Sender<Decision>,
) -> 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<Vec<String>> {
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<Vec<String>> {
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<String>, 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::<Vec<_>>();
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<String> {
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<Item = String> {
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<bool> {