Skip to main content

onelf_sysroot/
ldconf.rs

1//! The loader search directories a sysroot declares in `etc/ld.so.conf`.
2//!
3//! A package installed under `/opt` reaches its libraries through a
4//! drop-in there rather than through `usr/lib`, so a closure that only
5//! searches the standard directories reports them missing.
6
7use std::path::{Path, PathBuf};
8
9use crate::path::in_root;
10
11/// The directories `etc/ld.so.conf` and its includes name, each resolved
12/// inside `root`, existing ones only, in file order without duplicates.
13pub fn search_dirs(root: &Path) -> Vec<PathBuf> {
14    let mut out = Vec::new();
15    let mut seen = std::collections::HashSet::new();
16    if let Ok(conf) = in_root(root, Path::new("etc/ld.so.conf")) {
17        read_conf(root, &conf, &mut out, &mut seen, 0);
18    }
19    out
20}
21
22fn read_conf(
23    root: &Path,
24    conf: &Path,
25    out: &mut Vec<PathBuf>,
26    seen: &mut std::collections::HashSet<PathBuf>,
27    depth: usize,
28) {
29    if depth > 8 {
30        return;
31    }
32    let Ok(text) = std::fs::read_to_string(conf) else {
33        return;
34    };
35    for line in text.lines() {
36        let line = line.split('#').next().unwrap_or("").trim();
37        if line.is_empty() {
38            continue;
39        }
40        if let Some(pattern) = line.strip_prefix("include ") {
41            for file in expand(root, pattern.trim()) {
42                read_conf(root, &file, out, seen, depth + 1);
43            }
44            continue;
45        }
46        let Ok(dir) = in_root(root, Path::new(line)) else {
47            continue;
48        };
49        if dir.is_dir() && seen.insert(dir.clone()) {
50            out.push(dir);
51        }
52    }
53}
54
55/// The files an `include` pattern matches inside `root`, sorted. Each
56/// component is matched against the directory the ones before it
57/// resolve to, so a glob never lists a directory outside the sysroot.
58fn expand(root: &Path, pattern: &str) -> Vec<PathBuf> {
59    let mut found = vec![PathBuf::new()];
60    for part in Path::new(pattern).iter() {
61        let Some(part) = part.to_str() else {
62            return Vec::new();
63        };
64        if part == "/" {
65            continue;
66        }
67        if !part.contains(['*', '?', '[']) {
68            for base in &mut found {
69                base.push(part);
70            }
71            continue;
72        }
73        let Ok(glob) = glob::Pattern::new(part) else {
74            return Vec::new();
75        };
76        found = found
77            .iter()
78            .flat_map(|base| {
79                in_root(root, base)
80                    .ok()
81                    .and_then(|dir| std::fs::read_dir(dir).ok())
82                    .into_iter()
83                    .flatten()
84                    .flatten()
85                    .map(|e| e.file_name())
86                    .filter(|n| n.to_str().is_some_and(|n| glob.matches(n)))
87                    .map(|n| base.join(n))
88                    .collect::<Vec<_>>()
89            })
90            .collect();
91    }
92    let mut files: Vec<PathBuf> = found
93        .iter()
94        .filter_map(|p| in_root(root, p).ok())
95        .filter(|p| p.is_file())
96        .collect();
97    files.sort();
98    files
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::db::fixture::temp_root;
105
106    #[test]
107    fn includes_are_followed_and_missing_dirs_skipped() {
108        let root = temp_root("ldconf");
109        std::fs::create_dir_all(root.join("etc/ld.so.conf.d")).unwrap();
110        std::fs::create_dir_all(root.join("opt/vendor/lib")).unwrap();
111        std::fs::write(
112            root.join("etc/ld.so.conf"),
113            "# comment\ninclude /etc/ld.so.conf.d/*.conf\n/nonexistent\n",
114        )
115        .unwrap();
116        std::fs::write(
117            root.join("etc/ld.so.conf.d/vendor.conf"),
118            "/opt/vendor/lib\n/opt/vendor/lib/\n",
119        )
120        .unwrap();
121        assert_eq!(search_dirs(&root), vec![root.join("opt/vendor/lib")]);
122        assert!(search_dirs(Path::new("/nonexistent-root")).is_empty());
123        let _ = std::fs::remove_dir_all(&root);
124    }
125
126    /// The configuration names the sysroot's directories, through
127    /// whatever absolute links and `..` it holds, and never the packer's.
128    #[test]
129    fn directories_resolve_inside_the_sysroot() {
130        let root = temp_root("ldconfroot");
131        std::fs::create_dir_all(root.join("etc/conf.d")).unwrap();
132        std::fs::create_dir_all(root.join("opt/vendor/lib")).unwrap();
133        std::os::unix::fs::symlink("/opt/vendor", root.join("etc/vendor")).unwrap();
134        std::os::unix::fs::symlink("/etc/conf.d", root.join("etc/ld.so.conf.d")).unwrap();
135        std::fs::write(
136            root.join("etc/ld.so.conf"),
137            "include /etc/ld.so.conf.d/*.conf\n/../../../../../../../../tmp\n/usr\n",
138        )
139        .unwrap();
140        std::fs::write(root.join("etc/conf.d/vendor.conf"), "/etc/vendor/lib\n").unwrap();
141        assert_eq!(search_dirs(&root), vec![root.join("opt/vendor/lib")]);
142    }
143}