From 8983863ffaae9aeb62b26f1f8b837381301583c7 Mon Sep 17 00:00:00 2001 From: Arthur Pinheiro Date: Wed, 12 Mar 2025 13:26:31 +0100 Subject: [PATCH 1/6] fix: allow relative paths in allowlist and blocklist --- src/args.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/args.rs b/src/args.rs index af165ae..7a00d3b 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,7 +3,29 @@ use std::path::PathBuf; -use clap::Parser; +use clap::{builder::{PathBufValueParser, TypedValueParser}, Parser}; + +#[derive(Clone, Default)] +struct AbsolutePathBufValueParser; + +impl TypedValueParser for AbsolutePathBufValueParser { + type Value = PathBuf; + + fn parse_ref( + &self, + cmd: &clap::Command, + arg: Option<&clap::Arg>, + value: &std::ffi::OsStr, + ) -> Result { + let pathbuf_parser = PathBufValueParser::new(); + let v = pathbuf_parser.parse_ref(cmd, arg, value)?; + + v.canonicalize().map_err(|e| clap::Error::raw( + clap::error::ErrorKind::Io, + e, + )) + } +} /// A tool to clean up sysroots for Linux embedded devices to save storage space. #[derive(Parser, Debug)] @@ -20,7 +42,7 @@ pub struct Args { /// An allowlist of files to keep, in .gitignore format. /// Note: this will take precedence over all other removal decisions. - #[arg(long)] + #[arg(long, value_parser = AbsolutePathBufValueParser::default())] pub allowlist: Option, /// A blocklist of files to remove, in .gitignore format. @@ -35,3 +57,4 @@ pub struct Args { /// The location of the sysroot to clean up pub sysroot_location: PathBuf, } + From 266a00d98303b245908acbda3a0ed3d2d83f27f0 Mon Sep 17 00:00:00 2001 From: Arthur Pinheiro Date: Wed, 12 Mar 2025 14:18:40 +0100 Subject: [PATCH 2/6] feat: use vector for --allowlist and --blocklist for passing multiples --- src/args.rs | 6 ++++-- src/cleaners.rs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/args.rs b/src/args.rs index 7a00d3b..a1db7e6 100644 --- a/src/args.rs +++ b/src/args.rs @@ -41,13 +41,15 @@ pub struct Args { pub split_to: Option, /// An allowlist of files to keep, in .gitignore format. + /// Can be passed multiple times. /// Note: this will take precedence over all other removal decisions. #[arg(long, value_parser = AbsolutePathBufValueParser::default())] - pub allowlist: Option, + pub allowlist: Vec, /// A blocklist of files to remove, in .gitignore format. + /// Can be passed multiple times. #[arg(long)] - pub blocklist: Option, + pub blocklist: Vec, /// An optional path to save the file graph of the DSO cleaner /// in GraphViz format. Useful for debugging. diff --git a/src/cleaners.rs b/src/cleaners.rs index 925937e..3e2860a 100644 --- a/src/cleaners.rs +++ b/src/cleaners.rs @@ -41,11 +41,11 @@ impl Runner { let removal_fn = Self::new_removal_fn(&args); let mut cleaners: Cleaners = vec![]; - if let Some(wl) = args.allowlist { + for wl in args.allowlist { cleaners.push(Box::new(ListCleaner::new(list::ListType::Allow, wl))); } - if let Some(bl) = args.blocklist { + for bl in args.blocklist { cleaners.push(Box::new(ListCleaner::new(list::ListType::Block, bl))); } From 6825d63aaa92e3ae4dbae998dbcf44d730d6b67d Mon Sep 17 00:00:00 2001 From: Arthur Pinheiro Date: Wed, 12 Mar 2025 16:52:03 +0100 Subject: [PATCH 3/6] feat: display size of each file and total removed size --- Cargo.lock | 7 +++++ Cargo.toml | 1 + src/cleaners.rs | 70 +++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 080ae12..a5ea2e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +[[package]] +name = "bytesize" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" + [[package]] name = "cfg-if" version = "1.0.0" @@ -634,6 +640,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "bytesize", "clap", "env_logger", "goblin", diff --git a/Cargo.toml b/Cargo.toml index 53bc72a..3fe8e90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ nix = { version = "0.29", features = ["fs"] } petgraph = { version = "0.7" } tokio = { version = "1", features = ["full"] } walkdir = { version = "2" } +bytesize = { version = "2.0" } \ No newline at end of file diff --git a/src/cleaners.rs b/src/cleaners.rs index 3e2860a..d431716 100644 --- a/src/cleaners.rs +++ b/src/cleaners.rs @@ -10,10 +10,11 @@ use crate::{ }; use anyhow::{Error, Result}; use async_trait::async_trait; +use bytesize::ByteSize; use dso::DsoCleaner; use list::ListCleaner; use nix::libc::EXDEV; -use std::{collections::HashMap, io, path::Path}; +use std::{collections::HashMap, fmt, io, ops::AddAssign, path::Path}; use tokio::{sync::mpsc, task::JoinSet}; use walkdir::{DirEntry, WalkDir}; @@ -26,8 +27,22 @@ pub trait Cleaner { ) -> Result<()>; } +struct FileSize(u64); + +impl fmt::Display for FileSize { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", ByteSize(self.0)) + } +} + +impl AddAssign for FileSize { + fn add_assign(&mut self, rhs: Self) { + self.0.add_assign(rhs.0); + } +} + type Cleaners = Vec>; -type RemovalFn = Box io::Result<()>>; +type RemovalFn = Box io::Result>; pub struct Runner { cleaners: Cleaners, @@ -129,6 +144,8 @@ impl Runner { async fn final_decision(removal_fn: RemovalFn, mut output_rx: mpsc::Receiver) { let mut final_decisions = HashMap::new(); + let mut total_removed_size = FileSize(0); + while let Some(input_decision) = output_rx.recv().await { if input_decision.action == Action::Undecided { continue; @@ -147,28 +164,38 @@ impl Runner { for (file, action) in final_decisions { if action == Action::Remove { - if let Err(err) = (removal_fn)(&file) { - log::error!("{}: {}", file.display(), err); - } + let removed_size = match (removal_fn)(&file) { + Ok(size) => size, + Err(err) => { + log::error!("{}: {}", file.display(), err); + FileSize(0) + } + }; + total_removed_size += removed_size; } } + log::info!("Total space removed: {}", total_removed_size); } fn new_removal_fn(args: &Args) -> RemovalFn { if let Some(dest) = args.split_to.clone() { if args.dry_run { Box::new(move |path| { + let size = Self::get_file_size(path)?; log::info!( - "(dry-run) would move {} to {}", + "(dry-run) would move {} to {} ({})", path.display(), - dest.display() + dest.display(), + size ); - Ok(()) + Ok(size) }) } else { Box::new(move |path| { - log::info!("moving {} to {}", path.display(), dest.display()); - Self::move_preserve(&path, &dest) + let size = Self::get_file_size(path)?; + log::info!("moving {} to {} ({})", path.display(), dest.display(), size); + Self::move_preserve(&path, &dest)?; + Ok(size) }) } } else { @@ -179,18 +206,33 @@ impl Runner { } else { "regular file" }; - log::info!("(dry-run) would remove {} {}", ty, path.display()); - Ok(()) + let size = Self::get_file_size(path)?; + log::info!("(dry-run) would remove {} {} ({})", ty, path.display(), size); + Ok(size) }) } else { Box::new(move |path| { - log::info!("removing {}", path.display()); - std::fs::remove_file(&path) + let size = Self::get_file_size(path)?; + log::info!("removing {} ({})", path.display(), size); + std::fs::remove_file(&path)?; + Ok(size) }) } } } + fn get_file_size(file: &Path) -> io::Result { + let lstat = nix::sys::stat::lstat(file); + let size = match lstat { + Err(err) => { + log::error!("failed to get metadata from: {}, {}", file.display(), err); + FileSize(0) + }, + Ok(lstat) => FileSize(lstat.st_size as u64) + }; + Ok(size) + } + fn move_preserve(src: &Path, dest: &Path) -> io::Result<()> { assert!(src.is_relative()); let abs_dest = dest.join(src); From f2fc705fa0113c0f76ff2f0495b0193e9bc51069 Mon Sep 17 00:00:00 2001 From: Matteo Settenvini Date: Mon, 5 May 2025 15:47:47 +0200 Subject: [PATCH 4/6] fix: only warn on missing library, but process the rest If a library is not found for a given binary, keep processing the rest of libraries in the DSO resolver. This avoids removing other, required DSOs by still adding them to the graph. Additionally, bump deps to fix a Rust Analyzer error with newer Rust versions, and run `cargo clippy`. --- Cargo.lock | 194 ++++++++++++++++++++++++++++---------------- Cargo.toml | 4 +- src/cleaners.rs | 40 +++++---- src/cleaners/dso.rs | 14 ++-- 4 files changed, 150 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5ea2e7..e396dbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anstream" version = "0.6.18" @@ -78,15 +84,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -116,15 +122,15 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "serde", @@ -132,9 +138,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytesize" @@ -156,9 +162,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.30" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" dependencies = [ "clap_builder", "clap_derive", @@ -166,9 +172,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.30" +version = "4.5.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" dependencies = [ "anstream", "anstyle", @@ -178,9 +184,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -237,14 +243,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] @@ -260,6 +266,12 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "gimli" version = "0.31.1" @@ -268,9 +280,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "globset" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", @@ -292,9 +304,14 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "heck" @@ -302,12 +319,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "ignore" version = "0.4.23" @@ -326,9 +337,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", @@ -336,9 +347,9 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.5" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "is_terminal_polyfill" @@ -347,10 +358,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "libc" -version = "0.2.169" +name = "jiff" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "lock_api" @@ -364,9 +399,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -385,9 +420,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -405,9 +440,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", @@ -426,9 +461,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" @@ -455,12 +490,14 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06" dependencies = [ "fixedbitset", + "hashbrown", "indexmap", + "serde", ] [[package]] @@ -476,28 +513,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] -name = "proc-macro2" -version = "1.0.93" +name = "portable-atomic" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags", ] @@ -563,9 +615,9 @@ dependencies = [ [[package]] name = "scroll_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", @@ -574,18 +626,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -594,24 +646,24 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -625,9 +677,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -656,9 +708,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -685,9 +737,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "utf8parse" diff --git a/Cargo.toml b/Cargo.toml index 3fe8e90..7325698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,8 @@ indoc = { version = "2.0" } goblin = { version = "0.9" } log = { version = "0.4" } memmap2 = { version = "0.9" } -nix = { version = "0.29", features = ["fs"] } -petgraph = { version = "0.7" } +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 diff --git a/src/cleaners.rs b/src/cleaners.rs index d431716..9351d7d 100644 --- a/src/cleaners.rs +++ b/src/cleaners.rs @@ -194,30 +194,28 @@ impl Runner { Box::new(move |path| { let size = Self::get_file_size(path)?; log::info!("moving {} to {} ({})", path.display(), dest.display(), size); - Self::move_preserve(&path, &dest)?; + Self::move_preserve(path, &dest)?; Ok(size) }) } + } else if args.dry_run { + Box::new(|path| { + let ty = if path.is_symlink() { + "symlink" + } else { + "regular file" + }; + let size = Self::get_file_size(path)?; + log::info!("(dry-run) would remove {} {} ({})", ty, path.display(), size); + Ok(size) + }) } else { - if args.dry_run { - Box::new(|path| { - let ty = if path.is_symlink() { - "symlink" - } else { - "regular file" - }; - let size = Self::get_file_size(path)?; - log::info!("(dry-run) would remove {} {} ({})", ty, path.display(), size); - Ok(size) - }) - } else { - Box::new(move |path| { - let size = Self::get_file_size(path)?; - log::info!("removing {} ({})", path.display(), size); - std::fs::remove_file(&path)?; - Ok(size) - }) - } + Box::new(move |path| { + let size = Self::get_file_size(path)?; + log::info!("removing {} ({})", path.display(), size); + std::fs::remove_file(path)?; + Ok(size) + }) } } @@ -239,7 +237,7 @@ impl Runner { if let Some(parent) = abs_dest.parent() { std::fs::create_dir_all(parent)?; } - match std::fs::rename(&src, &abs_dest) { + match std::fs::rename(src, &abs_dest) { Err(err) if err.raw_os_error() == Some(EXDEV) => { log::trace!( "different filesystems, falling back to copying {} to {}", diff --git a/src/cleaners/dso.rs b/src/cleaners/dso.rs index 7aea0fe..b0a0264 100644 --- a/src/cleaners/dso.rs +++ b/src/cleaners/dso.rs @@ -62,7 +62,7 @@ impl Cleaner for DsoCleaner { // that also its dependencies will not be kept. if decision.action != Action::Remove { state.process_path(&decision.path).unwrap_or_else(|e| { - log::warn!( + log::error!( "{}: {} (this might produce wrong results!)", decision.path.display(), e @@ -92,12 +92,12 @@ impl Cleaner for DsoCleaner { } if let Some(dot) = &self.output_dot { - state.debug_print_graph(&dot)?; + state.debug_print_graph(dot)?; } let mut dfs = Dfs::empty(&state.graph); dfs.stack = inodes_to_keep.into_iter().collect(); - while let Some(_) = dfs.next(&state.graph) {} + while dfs.next(&state.graph).is_some() {} for (inode, paths) in state.paths_map.into_iter() { let action = if !dfs.discovered.contains(&inode) { @@ -219,7 +219,7 @@ impl State { continue 'next_lib; } - anyhow::bail!("{}: unable to find library {}", path.display(), library); + log::warn!("{}: unable to find library {}, ignoring (this might produce wrong results)!", path.display(), library); } Ok(()) @@ -267,10 +267,8 @@ impl State { &|_, _| { String::new() }, &|_, n| { let paths = self.paths_map.get(&n.id()).unwrap(); - let first_path = paths.iter().next().expect(&format!( - "dso: you have a path map with an empty entry for inode {}", - n.id() - )); + let first_path = paths.iter().next().unwrap_or_else(|| panic!("dso: you have a path map with an empty entry for inode {}", + n.id())); format!( "label = \"({}, {})\"", n.weight(), From 6d0637715401ab8983d141461208a5862bb7bd1d Mon Sep 17 00:00:00 2001 From: Kai Stuhlemmer Date: Mon, 2 Jun 2025 17:44:10 +0200 Subject: [PATCH 5/6] fix(dso): ensure all (r|run)paths are relative to sysroot Also make sure we split them on colons. --- src/args.rs | 12 ++++---- src/cleaners.rs | 11 ++++++-- src/cleaners/dso.rs | 69 ++++++++++++++++++++++++++------------------- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/args.rs b/src/args.rs index a1db7e6..3e8fa5f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -3,7 +3,10 @@ use std::path::PathBuf; -use clap::{builder::{PathBufValueParser, TypedValueParser}, Parser}; +use clap::{ + Parser, + builder::{PathBufValueParser, TypedValueParser}, +}; #[derive(Clone, Default)] struct AbsolutePathBufValueParser; @@ -20,10 +23,8 @@ impl TypedValueParser for AbsolutePathBufValueParser { let pathbuf_parser = PathBufValueParser::new(); let v = pathbuf_parser.parse_ref(cmd, arg, value)?; - v.canonicalize().map_err(|e| clap::Error::raw( - clap::error::ErrorKind::Io, - e, - )) + v.canonicalize() + .map_err(|e| clap::Error::raw(clap::error::ErrorKind::Io, e)) } } @@ -59,4 +60,3 @@ pub struct Args { /// The location of the sysroot to clean up pub sysroot_location: PathBuf, } - diff --git a/src/cleaners.rs b/src/cleaners.rs index 9351d7d..93369bc 100644 --- a/src/cleaners.rs +++ b/src/cleaners.rs @@ -206,7 +206,12 @@ impl Runner { "regular file" }; let size = Self::get_file_size(path)?; - log::info!("(dry-run) would remove {} {} ({})", ty, path.display(), size); + log::info!( + "(dry-run) would remove {} {} ({})", + ty, + path.display(), + size + ); Ok(size) }) } else { @@ -225,8 +230,8 @@ impl Runner { Err(err) => { log::error!("failed to get metadata from: {}, {}", file.display(), err); FileSize(0) - }, - Ok(lstat) => FileSize(lstat.st_size as u64) + } + Ok(lstat) => FileSize(lstat.st_size as u64), }; Ok(size) } diff --git a/src/cleaners/dso.rs b/src/cleaners/dso.rs index b0a0264..433f500 100644 --- a/src/cleaners/dso.rs +++ b/src/cleaners/dso.rs @@ -159,7 +159,7 @@ impl State { } let current_dir = std::env::current_dir()?; - let mut dst_path = std::fs::read_link(path)?; + let mut dst_path = fs::read_link(path)?; if dst_path.is_absolute() { dst_path = dst_path.strip_prefix("/")?.into(); } else { @@ -197,10 +197,12 @@ impl State { } let search_paths = determine_lib_search_paths(path, elf)?; + log::trace!("determined search paths {:#?}", search_paths); 'next_lib: for &library in elf.libraries.iter() { for lib_path in search_paths.iter() { - let tentative_path = PathBuf::from(lib_path).strip_prefix("/")?.join(library); + assert!(Path::new(&lib_path).is_relative()); + let tentative_path = PathBuf::from(lib_path).join(library); let dst = match nix::sys::stat::lstat(&tentative_path) { Ok(dst) => dst, Err(Errno::ENOENT) => continue, @@ -219,7 +221,11 @@ impl State { continue 'next_lib; } - log::warn!("{}: unable to find library {}, ignoring (this might produce wrong results)!", path.display(), library); + log::warn!( + "{}: unable to find library {}, ignoring (this might produce wrong results)!", + path.display(), + library + ); } Ok(()) @@ -257,7 +263,7 @@ impl State { {:?} }}" }, - petgraph::dot::Dot::with_attr_getters( + dot::Dot::with_attr_getters( &self.graph, &[ dot::Config::NodeNoLabel, @@ -267,8 +273,12 @@ impl State { &|_, _| { String::new() }, &|_, n| { let paths = self.paths_map.get(&n.id()).unwrap(); - let first_path = paths.iter().next().unwrap_or_else(|| panic!("dso: you have a path map with an empty entry for inode {}", - n.id())); + let first_path = paths.iter().next().unwrap_or_else(|| { + panic!( + "dso: you have a path map with an empty entry for inode {}", + n.id() + ) + }); format!( "label = \"({}, {})\"", n.weight(), @@ -285,10 +295,13 @@ impl State { } fn determine_lib_search_paths(path: &Path, elf: &Elf<'_>) -> Result> { + log::trace!("elf.runpaths = {:#?}", elf.runpaths); + log::trace!("elf.rpaths = {:#?}", elf.rpaths); + let mut search_paths = vec![]; let current_dir = std::env::current_dir()?; - let origin = std::fs::canonicalize(path)? + let origin = fs::canonicalize(path)? .parent() .unwrap() .strip_prefix(current_dir)? @@ -297,34 +310,32 @@ fn determine_lib_search_paths(path: &Path, elf: &Elf<'_>) -> Result> .into_string() .map_err(|s| anyhow::anyhow!("cannot represent {:?} as a UTF-8 string", s))?; - if elf.rpaths != vec![""] { - if elf.runpaths != vec![""] { - let mut rpaths = elf - .rpaths - .iter() - .map(|p| p.replace("$ORIGIN", &origin)) - .collect::>(); - search_paths.append(&mut rpaths); - } - - search_paths.append(&mut get_env_library_paths()); + if elf.runpaths.is_empty() { + collect_paths(&elf.rpaths, &mut search_paths, &origin); } - if elf.runpaths != vec![""] { - let mut runpaths = elf - .runpaths - .iter() - .map(|p| p.replace("$ORIGIN", &origin)) - .collect::>(); - search_paths.append(&mut runpaths); - } + search_paths.append(&mut get_env_library_paths()); - search_paths.push("/usr/local/lib".into()); - search_paths.push("/lib".into()); - search_paths.push("/usr/lib".into()); + 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); + } +} + fn get_env_library_paths() -> Vec { let ld_config_path = std::env::var("LD_LIBRARY_PATH"); ld_config_path From 1922297b4a67c51c8d0c4b3dccd4e51502d2a569 Mon Sep 17 00:00:00 2001 From: Matteo Settenvini Date: Mon, 2 Jun 2025 19:04:14 +0200 Subject: [PATCH 6/6] 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 {