1use std::path::{Path, PathBuf};
8
9use crate::path::in_root;
10
11pub 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
55fn 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 #[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}