Skip to content

Commit afbd768

Browse files
Claudeclaude
andcommitted
ipk-verify: discover libraries via bundled libs' own RUNPATH
list_libs only scanned the executable's rpath dirs and the top-level lib/ dir, and LibraryInfo::parse never read DT_RUNPATH/DT_RPATH. So a dependency bundled in a subdirectory that a library locates through its own $ORIGIN-relative runpath was reported as a missing library with all its symbols undefined — even though the loader resolves it fine. This is the libpulse case in apps-repo PR #190: libpulse.so.0 (in lib/) has RUNPATH $ORIGIN/pulseaudio and its pa_* symbols are provided by the bundled lib/pulseaudio/libpulsecommon-15.0.so, which the flat scan never collected. Capture DT_RUNPATH/DT_RPATH in LibraryInfo, and turn list_libs into a breadth-first walk that follows each bundled library's own runpath ($ORIGIN-relative) to discover further bundled directories. Adds a fixture-based parse test plus the regression coverage. Bumps bin-lib 0.1.1 -> 0.1.2, ipk-lib 0.1.3 -> 0.1.4, ipk-verify 0.1.5 -> 0.1.6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011jY7WzoWeU9TWRBhWJ2Wbq
1 parent cd038b0 commit afbd768

9 files changed

Lines changed: 72 additions & 14 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/bin/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bin-lib"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
edition = "2021"
55

66
[dependencies]
13.3 KB
Binary file not shown.

common/bin/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub struct LibraryInfo {
2222
pub names: Vec<String>,
2323
#[serde(skip_serializing_if = "Vec::is_empty", default)]
2424
pub undefined: Vec<String>,
25+
#[serde(skip_serializing, default)]
26+
pub rpath: Vec<String>,
2527
#[serde(skip_serializing, default = "LibraryPriority::default")]
2628
pub priority: LibraryPriority,
2729
}

common/bin/src/library.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ impl LibraryInfo {
4545
N: AsRef<str>,
4646
{
4747
let mut needed = Vec::<String>::new();
48+
let mut rpath = Vec::<String>::new();
4849
let mut elf = ElfStream::<AnyEndian, S>::open_stream(source)?;
4950
let mut name = String::from(name.as_ref());
5051

@@ -65,6 +66,15 @@ impl LibraryInfo {
6566
abi::DT_SONAME => {
6667
name = String::from(dynstr_table.get(entry.d_val() as usize).unwrap());
6768
}
69+
abi::DT_RPATH | abi::DT_RUNPATH => {
70+
rpath.extend(
71+
dynstr_table
72+
.get(entry.d_val() as usize)
73+
.unwrap()
74+
.split(":")
75+
.map(|s| String::from(s)),
76+
);
77+
}
6878
_ => {}
6979
}
7080
}
@@ -140,8 +150,31 @@ impl LibraryInfo {
140150
needed,
141151
symbols,
142152
undefined,
153+
rpath,
143154
names: Default::default(),
144155
priority: Default::default(),
145156
})
146157
}
147158
}
159+
160+
#[cfg(test)]
161+
mod tests {
162+
use std::io::Cursor;
163+
164+
use crate::LibraryInfo;
165+
166+
#[test]
167+
fn test_parse_runpath() {
168+
// Fixture is a shared object built with -Wl,-rpath,'$ORIGIN/pulseaudio'
169+
// -Wl,--enable-new-dtags, i.e. a DT_RUNPATH entry.
170+
let mut content = Cursor::new(include_bytes!("fixtures/lib_runpath.so"));
171+
let info = LibraryInfo::parse(&mut content, true, "lib_runpath.so")
172+
.expect("should not have any error");
173+
assert_eq!(info.name, "libfixture.so.1", "name should come from DT_SONAME");
174+
assert!(
175+
info.rpath.iter().any(|p| p == "$ORIGIN/pulseaudio"),
176+
"rpath should capture DT_RUNPATH, got {:?}",
177+
info.rpath
178+
);
179+
}
180+
}

common/ipk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ipk-lib"
3-
version = "0.1.3"
3+
version = "0.1.4"
44
edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

common/ipk/src/component.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use common_path::common_path;
22
use std::borrow::Cow;
3-
use std::collections::HashMap;
3+
use std::collections::{HashMap, HashSet, VecDeque};
44
use std::fs;
55
use std::fs::File;
66
use std::io::{Error, ErrorKind};
@@ -160,16 +160,30 @@ impl<T> Component<T> {
160160
rpath: &Vec<PathBuf>,
161161
links: &Symlinks,
162162
) -> Result<Vec<LibraryInfo>, Error> {
163-
let mut libs = HashMap::new();
164-
let lib_dir = dir.join("lib").canonicalize();
165-
let mut lib_dirs: Vec<(&Path, bool)> = rpath.iter().map(|p| (p.as_path(), true)).collect();
166-
if let Ok(lib_dir) = lib_dir.as_ref() {
163+
let mut libs: HashMap<PathBuf, LibraryInfo> = HashMap::new();
164+
let mut visited_dirs: HashSet<PathBuf> = HashSet::new();
165+
let mut queue: VecDeque<(PathBuf, bool)> = VecDeque::new();
166+
167+
for p in rpath {
168+
queue.push_back((p.clone(), true));
169+
}
170+
if let Ok(lib_dir) = dir.join("lib").canonicalize() {
167171
if !rpath.contains(&lib_dir) {
168-
lib_dirs.push((lib_dir.as_path(), false));
172+
queue.push_back((lib_dir, false));
169173
}
170174
}
171-
for (lib_dir, is_rpath) in lib_dirs {
172-
let Ok(entries) = fs::read_dir(lib_dir) else {
175+
176+
// Discover libraries by walking the executable's rpath directories and,
177+
// transitively, each bundled library's own DT_RUNPATH/DT_RPATH
178+
// ($ORIGIN-relative). This mirrors the dynamic loader: e.g. a bundled
179+
// libpulse.so.0 with RUNPATH $ORIGIN/pulseaudio pulls in
180+
// lib/pulseaudio/libpulsecommon-15.0.so, which a flat scan of lib/
181+
// would miss.
182+
while let Some((lib_dir, is_rpath)) = queue.pop_front() {
183+
if !visited_dirs.insert(lib_dir.clone()) {
184+
continue;
185+
}
186+
let Ok(entries) = fs::read_dir(&lib_dir) else {
173187
continue;
174188
};
175189
for entry in entries {
@@ -190,9 +204,17 @@ impl<T> Component<T> {
190204
} else {
191205
LibraryPriority::Package
192206
};
207+
// A bundled library's own runpath can point at further bundled
208+
// directories; queue them for discovery too.
209+
for sub_dir in Self::rpath(&lib.rpath, &path) {
210+
if !visited_dirs.contains(&sub_dir) {
211+
queue.push_back((sub_dir, true));
212+
}
213+
}
193214
libs.insert(path, lib);
194215
}
195216
}
217+
196218
for (path, lib) in &mut libs {
197219
lib.names
198220
.push(String::from(path.file_name().unwrap().to_string_lossy()));

packages/ipk-verify/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ipk-verify"
3-
version = "0.1.5"
3+
version = "0.1.6"
44
edition = "2021"
55
description = "Command line tool for checking symbols in an exectuable and libraries in an IPK file"
66
authors = ["Mariotaku Lee <mariotaku.lee@gmail.com>"]

packages/ipk-verify/tests/global_scope.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ fn bundled_lib(name: &str, needed: &[&str], symbols: &[&str], undefined: &[&str]
2222
symbols,
2323
names: vec![name.to_string()],
2424
undefined: undefined.iter().map(|s| s.to_string()).collect(),
25+
rpath: vec![],
2526
priority: LibraryPriority::Rpath,
2627
}
2728
}

0 commit comments

Comments
 (0)