1use crate::{
8 elf::ElfMetadata,
9 error::{Error, Result, io},
10 paths::normalize_absolute,
11};
12use std::{
13 collections::HashMap,
14 path::{Component, Path, PathBuf},
15};
16
17const SYMLINK_HOPS_MAX: usize = 40;
22
23const PENDING_COMPONENTS_MAX: usize = 1024;
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct SymlinkEntry {
32 pub logical: PathBuf,
34 pub target: PathBuf,
36}
37
38#[derive(Debug, Clone)]
40pub struct Resolved {
41 pub logical: PathBuf,
43 pub host: PathBuf,
45 pub links: Vec<SymlinkEntry>,
47 pub kind: EntryKind,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum EntryKind {
52 File,
53 Directory,
54 Other,
55}
56
57#[derive(Debug, Clone)]
58pub struct SourceRoot {
59 path: PathBuf,
60}
61
62impl SourceRoot {
63 pub fn new(path: impl Into<PathBuf>) -> SourceRoot {
64 SourceRoot { path: path.into() }
65 }
66
67 pub fn path(&self) -> &Path {
68 &self.path
69 }
70
71 pub fn host_path(&self, logical: &Path) -> PathBuf {
73 crate::paths::join_under(&self.path, logical)
74 }
75
76 pub fn resolve(&self, logical: &Path) -> Result<Option<Resolved>> {
81 let mut pending = components_reversed(&normalize_absolute(logical));
82 let mut current = PathBuf::from("/");
83 let mut links: Vec<SymlinkEntry> = Vec::new();
84 let mut hops = 0usize;
85
86 while let Some(component) = pending.pop() {
87 if component == ".." {
88 current.pop();
89 continue;
90 }
91 if component == "." {
92 continue;
93 }
94
95 let next_logical = current.join(&component);
96 let host = self.host_path(&next_logical);
97 let Some(metadata) = symlink_metadata_optional(&host)? else {
98 return Ok(None);
99 };
100 if !metadata.is_symlink() {
101 current = next_logical;
102 continue;
103 }
104
105 if hops == SYMLINK_HOPS_MAX || pending.len() > PENDING_COMPONENTS_MAX {
108 return Err(Error::SymlinkLoop {
109 path: logical.to_path_buf(),
110 });
111 }
112 hops += 1;
113
114 let target = std::fs::read_link(&host).map_err(|e| io(&host, e))?;
115 links.push(SymlinkEntry {
116 logical: next_logical,
117 target: target.clone(),
118 });
119 if target.is_absolute() {
120 current = PathBuf::from("/");
121 }
122 pending.extend(components_reversed(&target));
123 }
124
125 self.describe(current, links)
126 }
127
128 fn describe(&self, logical: PathBuf, links: Vec<SymlinkEntry>) -> Result<Option<Resolved>> {
130 assert!(logical.is_absolute());
131
132 let host = self.host_path(&logical);
133 let Some(metadata) = metadata_optional(&host)? else {
134 return Ok(None);
135 };
136 let kind = if metadata.is_dir() {
137 EntryKind::Directory
138 } else if metadata.is_file() {
139 EntryKind::File
140 } else {
141 EntryKind::Other
142 };
143 Ok(Some(Resolved {
144 logical,
145 host,
146 links,
147 kind,
148 }))
149 }
150
151 pub fn read(&self, logical: &Path) -> Result<Option<Vec<u8>>> {
153 match self.resolve(logical)? {
154 Some(resolved) if resolved.kind == EntryKind::File => Ok(Some(
155 std::fs::read(&resolved.host).map_err(|e| io(&resolved.host, e))?,
156 )),
157 _ => Ok(None),
158 }
159 }
160
161 pub fn exists(&self, logical: &Path) -> bool {
162 matches!(self.resolve(logical), Ok(Some(_)))
163 }
164
165 pub fn is_dir(&self, logical: &Path) -> bool {
166 matches!(self.resolve(logical), Ok(Some(r)) if r.kind == EntryKind::Directory)
167 }
168
169 pub fn read_dir(&self, logical: &Path) -> Result<Vec<std::ffi::OsString>> {
171 let host = match self.resolve(logical)? {
172 Some(resolved) if resolved.kind == EntryKind::Directory => resolved.host,
173 _ => return Ok(Vec::new()),
174 };
175 let mut names = Vec::new();
176 for entry in std::fs::read_dir(&host).map_err(|e| io(&host, e))? {
177 let entry = entry.map_err(|e| io(&host, e))?;
178 names.push(entry.file_name());
179 }
180 names.sort();
183 Ok(names)
184 }
185}
186
187fn components_reversed(path: &Path) -> Vec<std::ffi::OsString> {
190 path.components()
191 .filter_map(|c| match c {
192 Component::Normal(part) => Some(part.to_os_string()),
193 Component::ParentDir => Some(std::ffi::OsString::from("..")),
194 Component::RootDir | Component::CurDir | Component::Prefix(_) => None,
195 })
196 .rev()
197 .collect()
198}
199
200fn symlink_metadata_optional(host: &Path) -> Result<Option<std::fs::Metadata>> {
203 match std::fs::symlink_metadata(host) {
204 Ok(metadata) => Ok(Some(metadata)),
205 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
206 Err(e) => Err(io(host, e)),
207 }
208}
209
210fn metadata_optional(host: &Path) -> Result<Option<std::fs::Metadata>> {
212 match std::fs::metadata(host) {
213 Ok(metadata) => Ok(Some(metadata)),
214 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
215 Err(e) => Err(io(host, e)),
216 }
217}
218
219#[derive(Debug, Default)]
221pub struct ElfCache {
222 entries: HashMap<PathBuf, Option<ElfMetadata>>,
223}
224
225impl ElfCache {
226 pub fn new() -> ElfCache {
227 ElfCache::default()
228 }
229
230 pub fn get(&mut self, host: &Path) -> Result<Option<ElfMetadata>> {
233 if let Some(cached) = self.entries.get(host) {
234 return Ok(cached.clone());
235 }
236 let parsed = match ElfMetadata::parse_file(host) {
237 Ok(metadata) => Some(metadata),
238 Err(Error::NotElf { .. }) | Err(Error::Elf { .. }) => None,
239 Err(e) => return Err(e),
240 };
241 self.entries.insert(host.to_path_buf(), parsed.clone());
242 Ok(parsed)
243 }
244
245 pub fn require(&mut self, host: &Path) -> Result<ElfMetadata> {
247 match self.get(host)? {
248 Some(metadata) => Ok(metadata),
249 None => ElfMetadata::parse_file(host),
250 }
251 }
252}