1use std::collections::BTreeMap;
12use std::path::{Path, PathBuf};
13use std::sync::Arc;
14
15use crate::platform::SystemTime;
16
17use parking_lot::RwLock;
18
19use super::{DirEntry, Metadata, NodeType, VirtualFs};
20use crate::error::VfsError;
21use crate::interpreter::pattern::glob_match;
22
23struct MountPair {
25 src_fs: Arc<dyn VirtualFs>,
26 src_rel: PathBuf,
27 dst_fs: Arc<dyn VirtualFs>,
28 dst_rel: PathBuf,
29 same: bool,
30}
31
32pub struct MountableFs {
52 mounts: Arc<RwLock<BTreeMap<PathBuf, Arc<dyn VirtualFs>>>>,
53}
54
55impl MountableFs {
56 pub fn new() -> Self {
58 Self {
59 mounts: Arc::new(RwLock::new(BTreeMap::new())),
60 }
61 }
62
63 pub fn mount(self, path: impl Into<PathBuf>, fs: Arc<dyn VirtualFs>) -> Self {
68 let path = path.into();
69 assert!(
70 super::vfs_path_is_absolute(&path),
71 "mount path must be absolute: {path:?}"
72 );
73 self.mounts.write().insert(path, fs);
74 self
75 }
76
77 fn resolve_mount(&self, path: &Path) -> Result<(Arc<dyn VirtualFs>, PathBuf), VfsError> {
85 let mounts = self.mounts.read();
86 for (mount_point, fs) in mounts.iter().rev() {
87 if path.starts_with(mount_point) {
88 let relative = path.strip_prefix(mount_point).unwrap_or(Path::new(""));
89 let resolved = if relative.as_os_str().is_empty() {
90 PathBuf::from("/")
91 } else {
92 PathBuf::from("/").join(relative)
93 };
94 return Ok((Arc::clone(fs), resolved));
95 }
96 }
97 Err(VfsError::NotFound(path.to_path_buf()))
98 }
99
100 fn resolve_two(&self, src: &Path, dst: &Path) -> Result<MountPair, VfsError> {
102 let mounts = self.mounts.read();
103 let resolve_one =
104 |path: &Path| -> Result<(Arc<dyn VirtualFs>, PathBuf, PathBuf), VfsError> {
105 for (mount_point, fs) in mounts.iter().rev() {
106 if path.starts_with(mount_point) {
107 let relative = path.strip_prefix(mount_point).unwrap_or(Path::new(""));
108 let resolved = if relative.as_os_str().is_empty() {
109 PathBuf::from("/")
110 } else {
111 PathBuf::from("/").join(relative)
112 };
113 return Ok((Arc::clone(fs), resolved, mount_point.clone()));
114 }
115 }
116 Err(VfsError::NotFound(path.to_path_buf()))
117 };
118
119 let (src_fs, src_rel, src_mount) = resolve_one(src)?;
120 let (dst_fs, dst_rel, dst_mount) = resolve_one(dst)?;
121 let same = src_mount == dst_mount;
122 Ok(MountPair {
123 src_fs,
124 src_rel,
125 dst_fs,
126 dst_rel,
127 same,
128 })
129 }
130
131 fn synthetic_mount_entries(&self, dir_path: &Path) -> Vec<DirEntry> {
136 let mounts = self.mounts.read();
137 let mut entries = Vec::new();
138 let dir_str = dir_path.to_string_lossy();
139 let prefix = if dir_str == "/" {
140 "/".to_string()
141 } else {
142 format!("{}/", dir_str.trim_end_matches('/'))
143 };
144
145 for mount_point in mounts.keys() {
146 if mount_point == dir_path {
148 continue;
149 }
150 let mp_str = mount_point.to_string_lossy();
151 if let Some(rest) = mp_str.strip_prefix(&prefix)
152 && !rest.is_empty()
153 {
154 let first_component = rest.split('/').next().unwrap();
157 if !entries.iter().any(|e: &DirEntry| e.name == first_component) {
158 entries.push(DirEntry {
159 name: first_component.to_string(),
160 node_type: NodeType::Directory,
161 });
162 }
163 }
164 }
165 entries
166 }
167
168 fn glob_walk(
170 &self,
171 dir: &Path,
172 components: &[&str],
173 current_path: PathBuf,
174 results: &mut Vec<PathBuf>,
175 max: usize,
176 ) {
177 if results.len() >= max || components.is_empty() {
178 if components.is_empty() {
179 results.push(current_path);
180 }
181 return;
182 }
183
184 let pattern = components[0];
185 let rest = &components[1..];
186
187 let entries = self.merged_readdir_for_glob(dir);
189
190 if pattern == "**" {
191 self.glob_walk(dir, rest, current_path.clone(), results, max);
193
194 for entry in &entries {
195 if results.len() >= max {
196 return;
197 }
198 if entry.name.starts_with('.') {
199 continue;
200 }
201 let child_path = current_path.join(&entry.name);
202 let child_dir = dir.join(&entry.name);
203 if entry.node_type == NodeType::Directory || entry.node_type == NodeType::Symlink {
204 self.glob_walk(&child_dir, components, child_path, results, max);
205 }
206 }
207 } else {
208 for entry in &entries {
209 if results.len() >= max {
210 return;
211 }
212 if entry.name.starts_with('.') && !pattern.starts_with('.') {
213 continue;
214 }
215 if glob_match(pattern, &entry.name) {
216 let child_path = current_path.join(&entry.name);
217 let child_dir = dir.join(&entry.name);
218 if rest.is_empty() {
219 results.push(child_path);
220 } else if entry.node_type == NodeType::Directory
221 || entry.node_type == NodeType::Symlink
222 {
223 self.glob_walk(&child_dir, rest, child_path, results, max);
224 }
225 }
226 }
227 }
228 }
229
230 fn merged_readdir_for_glob(&self, dir: &Path) -> Vec<DirEntry> {
233 let mut entries = match self.resolve_mount(dir) {
234 Ok((fs, rel)) => fs.readdir(&rel).unwrap_or_default(),
235 Err(_) => Vec::new(),
236 };
237
238 let synthetics = self.synthetic_mount_entries(dir);
240 let existing_names: std::collections::HashSet<String> =
241 entries.iter().map(|e| e.name.clone()).collect();
242 for s in synthetics {
243 if !existing_names.contains(&s.name) {
244 entries.push(s);
245 }
246 }
247 entries
248 }
249}
250
251impl Default for MountableFs {
252 fn default() -> Self {
253 Self::new()
254 }
255}
256
257impl VirtualFs for MountableFs {
262 fn read_file(&self, path: &Path) -> Result<Vec<u8>, VfsError> {
263 let (fs, rel) = self.resolve_mount(path)?;
264 fs.read_file(&rel)
265 }
266
267 fn write_file(&self, path: &Path, content: &[u8]) -> Result<(), VfsError> {
268 let (fs, rel) = self.resolve_mount(path)?;
269 fs.write_file(&rel, content)
270 }
271
272 fn append_file(&self, path: &Path, content: &[u8]) -> Result<(), VfsError> {
273 let (fs, rel) = self.resolve_mount(path)?;
274 fs.append_file(&rel, content)
275 }
276
277 fn remove_file(&self, path: &Path) -> Result<(), VfsError> {
278 let (fs, rel) = self.resolve_mount(path)?;
279 fs.remove_file(&rel)
280 }
281
282 fn mkdir(&self, path: &Path) -> Result<(), VfsError> {
283 let (fs, rel) = self.resolve_mount(path)?;
284 fs.mkdir(&rel)
285 }
286
287 fn mkdir_p(&self, path: &Path) -> Result<(), VfsError> {
288 let (fs, rel) = self.resolve_mount(path)?;
289 fs.mkdir_p(&rel)
290 }
291
292 fn readdir(&self, path: &Path) -> Result<Vec<DirEntry>, VfsError> {
293 let (mut entries, mount_ok) = match self.resolve_mount(path) {
295 Ok((fs, rel)) => match fs.readdir(&rel) {
296 Ok(e) => (e, true),
297 Err(_) => (Vec::new(), false),
298 },
299 Err(_) => (Vec::new(), false),
300 };
301
302 let synthetics = self.synthetic_mount_entries(path);
304 let existing_names: std::collections::HashSet<String> =
305 entries.iter().map(|e| e.name.clone()).collect();
306 for s in synthetics {
307 if !existing_names.contains(&s.name) {
308 entries.push(s);
309 }
310 }
311
312 if !mount_ok && entries.is_empty() {
316 return Err(VfsError::NotFound(path.to_path_buf()));
317 }
318 Ok(entries)
319 }
320
321 fn remove_dir(&self, path: &Path) -> Result<(), VfsError> {
322 let (fs, rel) = self.resolve_mount(path)?;
323 fs.remove_dir(&rel)
324 }
325
326 fn remove_dir_all(&self, path: &Path) -> Result<(), VfsError> {
327 let (fs, rel) = self.resolve_mount(path)?;
328 fs.remove_dir_all(&rel)
329 }
330
331 fn exists(&self, path: &Path) -> bool {
332 if let Ok((fs, rel)) = self.resolve_mount(path)
335 && fs.exists(&rel)
336 {
337 return true;
338 }
339 let mounts = self.mounts.read();
341 if mounts.contains_key(path) {
342 return true;
343 }
344 let prefix = if path == Path::new("/") {
346 "/".to_string()
347 } else {
348 format!("{}/", path.to_string_lossy().trim_end_matches('/'))
349 };
350 mounts
351 .keys()
352 .any(|mp| mp.to_string_lossy().starts_with(&prefix))
353 }
354
355 fn stat(&self, path: &Path) -> Result<Metadata, VfsError> {
356 if let Ok((fs, rel)) = self.resolve_mount(path)
358 && let Ok(m) = fs.stat(&rel)
359 {
360 return Ok(m);
361 }
362 if self.is_mount_point_or_ancestor(path) {
365 return Ok(Metadata {
366 node_type: NodeType::Directory,
367 size: 0,
368 mode: 0o755,
369 mtime: SystemTime::UNIX_EPOCH,
370 });
371 }
372 Err(VfsError::NotFound(path.to_path_buf()))
373 }
374
375 fn lstat(&self, path: &Path) -> Result<Metadata, VfsError> {
376 if let Ok((fs, rel)) = self.resolve_mount(path)
377 && let Ok(m) = fs.lstat(&rel)
378 {
379 return Ok(m);
380 }
381 if self.is_mount_point_or_ancestor(path) {
382 return Ok(Metadata {
383 node_type: NodeType::Directory,
384 size: 0,
385 mode: 0o755,
386 mtime: SystemTime::UNIX_EPOCH,
387 });
388 }
389 Err(VfsError::NotFound(path.to_path_buf()))
390 }
391
392 fn chmod(&self, path: &Path, mode: u32) -> Result<(), VfsError> {
393 let (fs, rel) = self.resolve_mount(path)?;
394 fs.chmod(&rel, mode)
395 }
396
397 fn utimes(&self, path: &Path, mtime: SystemTime) -> Result<(), VfsError> {
398 let (fs, rel) = self.resolve_mount(path)?;
399 fs.utimes(&rel, mtime)
400 }
401
402 fn symlink(&self, target: &Path, link: &Path) -> Result<(), VfsError> {
403 let (link_fs, link_rel) = self.resolve_mount(link)?;
404 let remapped_target = if target.is_absolute() {
407 if let Ok((_, target_rel)) = self.resolve_mount(target) {
408 let link_mount = self.mount_point_for(link);
410 let target_mount = self.mount_point_for(target);
411 if link_mount == target_mount {
412 target_rel
413 } else {
414 target.to_path_buf()
415 }
416 } else {
417 target.to_path_buf()
418 }
419 } else {
420 target.to_path_buf()
421 };
422 link_fs.symlink(&remapped_target, &link_rel)
423 }
424
425 fn hardlink(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
426 let pair = self.resolve_two(src, dst)?;
427 if !pair.same {
428 return Err(VfsError::IoError(
429 "hard links across mount boundaries are not supported".to_string(),
430 ));
431 }
432 pair.src_fs.hardlink(&pair.src_rel, &pair.dst_rel)
433 }
434
435 fn readlink(&self, path: &Path) -> Result<PathBuf, VfsError> {
436 let (fs, rel) = self.resolve_mount(path)?;
437 let target = fs.readlink(&rel)?;
438 if target.is_absolute() {
441 let mount_point = self.mount_point_for(path);
442 if mount_point != Path::new("/") {
443 let inner_rel = target.strip_prefix("/").unwrap_or(&target);
444 if inner_rel.as_os_str().is_empty() {
445 return Ok(mount_point);
446 }
447 return Ok(mount_point.join(inner_rel));
448 }
449 }
450 Ok(target)
451 }
452
453 fn canonicalize(&self, path: &Path) -> Result<PathBuf, VfsError> {
454 let (fs, rel) = self.resolve_mount(path)?;
455 let canonical_in_mount = fs.canonicalize(&rel)?;
456 let mounts = self.mounts.read();
459 for (mount_point, _) in mounts.iter().rev() {
460 if path.starts_with(mount_point) {
461 if mount_point == Path::new("/") {
462 return Ok(canonical_in_mount);
463 }
464 let inner_rel = canonical_in_mount
465 .strip_prefix("/")
466 .unwrap_or(&canonical_in_mount);
467 if inner_rel.as_os_str().is_empty() {
468 return Ok(mount_point.clone());
469 }
470 return Ok(mount_point.join(inner_rel));
471 }
472 }
473 Ok(canonical_in_mount)
474 }
475
476 fn copy(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
477 let pair = self.resolve_two(src, dst)?;
478 if pair.same {
479 pair.src_fs.copy(&pair.src_rel, &pair.dst_rel)
480 } else {
481 let content = pair.src_fs.read_file(&pair.src_rel)?;
482 pair.dst_fs.write_file(&pair.dst_rel, &content)
483 }
484 }
485
486 fn rename(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
487 let pair = self.resolve_two(src, dst)?;
488 if pair.same {
489 pair.src_fs.rename(&pair.src_rel, &pair.dst_rel)
490 } else {
491 if let Ok(m) = pair.src_fs.stat(&pair.src_rel)
494 && m.node_type == NodeType::Directory
495 {
496 return Err(VfsError::IoError(
497 "rename of directories across mount boundaries is not supported".to_string(),
498 ));
499 }
500 let content = pair.src_fs.read_file(&pair.src_rel)?;
501 pair.dst_fs.write_file(&pair.dst_rel, &content)?;
502 pair.src_fs.remove_file(&pair.src_rel)
503 }
504 }
505
506 fn glob(&self, pattern: &str, cwd: &Path) -> Result<Vec<PathBuf>, VfsError> {
509 let is_absolute = pattern.starts_with('/');
510 let abs_pattern = if is_absolute {
511 pattern.to_string()
512 } else {
513 let cwd_str = cwd.to_str().unwrap_or("/").trim_end_matches('/');
514 format!("{cwd_str}/{pattern}")
515 };
516
517 let components: Vec<&str> = abs_pattern.split('/').filter(|s| !s.is_empty()).collect();
518 let mut results = Vec::new();
519 let max = 100_000;
520 self.glob_walk(
521 Path::new("/"),
522 &components,
523 PathBuf::from("/"),
524 &mut results,
525 max,
526 );
527
528 results.sort();
529 results.dedup();
530
531 if !is_absolute {
532 results = results
533 .into_iter()
534 .filter_map(|p| p.strip_prefix(cwd).ok().map(|r| r.to_path_buf()))
535 .collect();
536 }
537
538 Ok(results)
539 }
540
541 fn deep_clone(&self) -> Arc<dyn VirtualFs> {
542 let mounts = self.mounts.read();
543 let cloned_mounts: BTreeMap<PathBuf, Arc<dyn VirtualFs>> = mounts
544 .iter()
545 .map(|(path, fs)| (path.clone(), fs.deep_clone()))
546 .collect();
547 Arc::new(MountableFs {
548 mounts: Arc::new(RwLock::new(cloned_mounts)),
549 })
550 }
551}
552
553impl MountableFs {
558 fn is_mount_point_or_ancestor(&self, path: &Path) -> bool {
560 let mounts = self.mounts.read();
561 if mounts.contains_key(path) {
562 return true;
563 }
564 let prefix = if path == Path::new("/") {
565 "/".to_string()
566 } else {
567 format!("{}/", path.to_string_lossy().trim_end_matches('/'))
568 };
569 mounts
570 .keys()
571 .any(|mp| mp.to_string_lossy().starts_with(&prefix))
572 }
573
574 fn mount_point_for(&self, path: &Path) -> PathBuf {
576 let mounts = self.mounts.read();
577 for mount_point in mounts.keys().rev() {
578 if path.starts_with(mount_point) {
579 return mount_point.clone();
580 }
581 }
582 PathBuf::from("/")
583 }
584}