elb_dl/
glibc.rs

1use std::collections::VecDeque;
2use std::io::BufRead;
3use std::io::BufReader;
4use std::io::ErrorKind;
5use std::path::Path;
6use std::path::PathBuf;
7use std::process::Command;
8use std::process::Stdio;
9
10use crate::fs::File;
11use glob::glob;
12use log::log_enabled;
13use log::trace;
14use log::warn;
15use log::Level::Trace;
16
17/// Get default library search directories plus the paths from `<rootfs_dir>/etc/ld.so.conf`.
18///
19/// Default search directories: `/lib:/usr/local/lib:/usr/lib`.
20pub fn get_search_dirs<P: AsRef<Path>>(rootfs_dir: P) -> Result<Vec<PathBuf>, std::io::Error> {
21    let rootfs_dir = rootfs_dir.as_ref();
22    let mut paths = Vec::new();
23    paths.extend([
24        rootfs_dir.join("lib"),
25        rootfs_dir.join("usr/local/lib"),
26        rootfs_dir.join("usr/lib"),
27    ]);
28    parse_ld_so_conf(rootfs_dir.join("etc/ld.so.conf"), rootfs_dir, &mut paths)?;
29    if log_enabled!(Trace) {
30        for path in paths.iter() {
31            trace!("Found system library path {:?}", path);
32        }
33    }
34    Ok(paths)
35}
36
37fn parse_ld_so_conf(
38    path: PathBuf,
39    rootfs_dir: &Path,
40    paths: &mut Vec<PathBuf>,
41) -> Result<(), std::io::Error> {
42    let mut conf_files = Vec::new();
43    let mut queue = VecDeque::new();
44    queue.push_back(path);
45    while let Some(path) = queue.pop_front() {
46        let file = match File::open(&path) {
47            Ok(file) => file,
48            Err(ref e) if e.kind() == ErrorKind::NotFound => continue,
49            Err(e) => {
50                warn!("Failed to open {path:?}: {e}");
51                continue;
52            }
53        };
54        conf_files.push(path);
55        let reader = BufReader::new(file);
56        for line in reader.lines() {
57            let line = line?;
58            let line = match line.find('#') {
59                Some(i) => &line[..i],
60                None => &line[..],
61            }
62            .trim();
63            if line.is_empty() {
64                continue;
65            }
66            if line.starts_with("include") {
67                let Some(i) = line.find(char::is_whitespace) else {
68                    // Malformed "include" directive.
69                    continue;
70                };
71                let pattern = if line.as_bytes().get(i + 1).copied() == Some(b'/') {
72                    &line[i + 2..]
73                } else {
74                    &line[i + 1..]
75                };
76                let pattern = rootfs_dir.join(pattern);
77                let Some(pattern) = pattern.to_str() else {
78                    // Not a valid UTF-8 string.
79                    continue;
80                };
81                let Ok(more_paths) = glob(pattern) else {
82                    // Unparsable glob pattern.
83                    continue;
84                };
85                for path in more_paths {
86                    let Ok(path) = path else {
87                        continue;
88                    };
89                    if !conf_files.contains(&path) {
90                        queue.push_back(path);
91                    }
92                }
93            }
94            if let Some(path) = line.strip_prefix("/") {
95                let path = rootfs_dir.join(path);
96                if !paths.contains(&path) {
97                    paths.push(path);
98                }
99            }
100        }
101    }
102    Ok(())
103}
104
105/// Get library search directories from via `ld.so --list-diagnostics`.
106///
107/// Useful for Nix and Guix.
108pub fn get_hard_coded_search_dirs(
109    ld_so_exe: Option<Command>,
110) -> Result<Vec<PathBuf>, std::io::Error> {
111    let mut child = ld_so_exe
112        .unwrap_or_else(|| Command::new("ld.so"))
113        .arg("--list-diagnostics")
114        .stdin(Stdio::null())
115        .stdout(Stdio::piped())
116        .stderr(Stdio::null())
117        .spawn()?;
118    let mut paths = Vec::new();
119    if let Some(stdout) = child.stdout.take() {
120        let reader = BufReader::new(stdout);
121        for line in reader.lines() {
122            let line = line?;
123            let line = line.trim();
124            if !line.starts_with("path.system_dirs") {
125                continue;
126            }
127            let Some(i) = line.find('=') else {
128                continue;
129            };
130            let mut start = i + 1;
131            let mut end = line.len() - 1;
132            // Remove quotes.
133            if line.as_bytes().get(i + 1) == Some(&b'"') {
134                start += 1;
135            }
136            if line.as_bytes().last() == Some(&b'"') {
137                end -= 1;
138            }
139            let path = &line[start..end];
140            paths.push(Path::new(path).to_path_buf());
141        }
142    }
143    Ok(paths)
144}