1use std::collections::BTreeMap;
2use std::path::{Component, Path, PathBuf};
3use std::sync::Arc;
4
5use crate::platform::SystemTime;
6
7use parking_lot::RwLock;
8
9use super::{DirEntry, FsNode, GlobOptions, Metadata, NodeType, VirtualFs};
10use crate::error::VfsError;
11
12const MAX_SYMLINK_DEPTH: u32 = 40;
13
14#[derive(Debug, Clone)]
19pub struct InMemoryFs {
20 root: Arc<RwLock<FsNode>>,
21}
22
23impl Default for InMemoryFs {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl InMemoryFs {
30 pub fn new() -> Self {
31 Self {
32 root: Arc::new(RwLock::new(FsNode::Directory {
33 children: BTreeMap::new(),
34 mode: 0o755,
35 mtime: SystemTime::now(),
36 })),
37 }
38 }
39
40 pub fn deep_clone(&self) -> Self {
46 let tree = self.root.read();
47 Self {
48 root: Arc::new(RwLock::new(tree.clone())),
49 }
50 }
51}
52
53fn normalize(path: &Path) -> Result<PathBuf, VfsError> {
60 let s = path.to_str().unwrap_or("");
61 if s.is_empty() {
62 return Err(VfsError::InvalidPath("empty path".into()));
63 }
64 if !super::vfs_path_is_absolute(path) {
65 return Err(VfsError::InvalidPath(format!(
66 "path must be absolute: {}",
67 path.display()
68 )));
69 }
70
71 let mut parts: Vec<String> = Vec::new();
72 for comp in path.components() {
73 match comp {
74 Component::RootDir | Component::Prefix(_) => {}
75 Component::CurDir => {}
76 Component::ParentDir => {
77 parts.pop();
78 }
79 Component::Normal(seg) => {
80 if let Some(s) = seg.to_str() {
81 parts.push(s.to_owned());
82 } else {
83 return Err(VfsError::InvalidPath(format!(
84 "non-UTF-8 component in: {}",
85 path.display()
86 )));
87 }
88 }
89 }
90 }
91
92 let mut result = PathBuf::from("/");
93 for p in &parts {
94 result.push(p);
95 }
96 Ok(result)
97}
98
99fn components(path: &Path) -> Vec<&str> {
101 path.components()
102 .filter_map(|c| match c {
103 Component::Normal(s) => s.to_str(),
104 _ => None,
105 })
106 .collect()
107}
108
109impl InMemoryFs {
114 fn with_node<F, T>(&self, path: &Path, f: F) -> Result<T, VfsError>
116 where
117 F: FnOnce(&FsNode) -> Result<T, VfsError>,
118 {
119 let norm = normalize(path)?;
120 let tree = self.root.read();
121 let node = navigate(&tree, &norm, true, MAX_SYMLINK_DEPTH, &tree)?;
122 f(node)
123 }
124
125 fn with_node_no_follow<F, T>(&self, path: &Path, f: F) -> Result<T, VfsError>
128 where
129 F: FnOnce(&FsNode) -> Result<T, VfsError>,
130 {
131 let norm = normalize(path)?;
132 let tree = self.root.read();
133 let node = navigate(&tree, &norm, false, MAX_SYMLINK_DEPTH, &tree)?;
134 f(node)
135 }
136
137 fn with_parent_mut<F, T>(&self, path: &Path, f: F) -> Result<T, VfsError>
139 where
140 F: FnOnce(&mut FsNode, &str) -> Result<T, VfsError>,
141 {
142 let norm = normalize(path)?;
143 let parts = components(&norm);
144 if parts.is_empty() {
145 return Err(VfsError::InvalidPath(
146 "cannot operate on root itself".into(),
147 ));
148 }
149 let child_name = parts.last().unwrap();
150 let parent_path: PathBuf = if parts.len() == 1 {
151 PathBuf::from("/")
152 } else {
153 let mut p = PathBuf::from("/");
154 for seg in &parts[..parts.len() - 1] {
155 p.push(seg);
156 }
157 p
158 };
159
160 let mut tree = self.root.write();
161 let parent = navigate_mut(&mut tree, &parent_path, true, MAX_SYMLINK_DEPTH)?;
162 f(parent, child_name)
163 }
164
165 fn with_node_mut<F, T>(&self, path: &Path, f: F) -> Result<T, VfsError>
167 where
168 F: FnOnce(&mut FsNode) -> Result<T, VfsError>,
169 {
170 let norm = normalize(path)?;
171 let mut tree = self.root.write();
172 let node = navigate_mut(&mut tree, &norm, true, MAX_SYMLINK_DEPTH)?;
173 f(node)
174 }
175
176 fn resolve_path(&self, path: &Path) -> Result<PathBuf, VfsError> {
178 let norm = normalize(path)?;
179 let tree = self.root.read();
180 resolve_canonical(&tree, &norm, MAX_SYMLINK_DEPTH, &tree)
181 }
182}
183
184fn navigate<'a>(
191 root: &'a FsNode,
192 path: &Path,
193 follow_final: bool,
194 depth: u32,
195 tree_root: &'a FsNode,
196) -> Result<&'a FsNode, VfsError> {
197 if depth == 0 {
198 return Err(VfsError::SymlinkLoop(path.to_path_buf()));
199 }
200
201 let parts = components(path);
202 if parts.is_empty() {
203 return Ok(root);
204 }
205
206 let mut current = root;
207 for (i, name) in parts.iter().enumerate() {
208 let is_last = i == parts.len() - 1;
209 current = resolve_if_symlink(current, path, depth, tree_root)?;
211
212 match current {
213 FsNode::Directory { children, .. } => {
214 let child = children
215 .get(*name)
216 .ok_or_else(|| VfsError::NotFound(path.to_path_buf()))?;
217 if is_last && !follow_final {
218 current = child;
219 } else {
220 current = resolve_if_symlink(child, path, depth - 1, tree_root)?;
221 }
222 }
223 _ => return Err(VfsError::NotADirectory(path.to_path_buf())),
224 }
225 }
226 Ok(current)
227}
228
229fn resolve_if_symlink<'a>(
231 node: &'a FsNode,
232 original_path: &Path,
233 depth: u32,
234 tree_root: &'a FsNode,
235) -> Result<&'a FsNode, VfsError> {
236 if depth == 0 {
237 return Err(VfsError::SymlinkLoop(original_path.to_path_buf()));
238 }
239 match node {
240 FsNode::Symlink { target, .. } => {
241 let target_norm = normalize(target)?;
242 navigate(tree_root, &target_norm, true, depth - 1, tree_root)
243 }
244 other => Ok(other),
245 }
246}
247
248fn navigate_mut<'a>(
252 root: &'a mut FsNode,
253 path: &Path,
254 follow_final: bool,
255 depth: u32,
256) -> Result<&'a mut FsNode, VfsError> {
257 if depth == 0 {
258 return Err(VfsError::SymlinkLoop(path.to_path_buf()));
259 }
260
261 let parts = components(path);
262 if parts.is_empty() {
263 return Ok(root);
264 }
265
266 let canonical = resolve_canonical_from_root(root, path, follow_final, depth)?;
271 let canon_parts = components(&canonical);
272
273 let mut current = root as &mut FsNode;
274 for name in &canon_parts {
275 match current {
276 FsNode::Directory { children, .. } => {
277 current = children
278 .get_mut(*name)
279 .ok_or_else(|| VfsError::NotFound(path.to_path_buf()))?;
280 }
281 _ => return Err(VfsError::NotADirectory(path.to_path_buf())),
282 }
283 }
284 Ok(current)
285}
286
287fn resolve_canonical(
289 root: &FsNode,
290 path: &Path,
291 depth: u32,
292 tree_root: &FsNode,
293) -> Result<PathBuf, VfsError> {
294 if depth == 0 {
295 return Err(VfsError::SymlinkLoop(path.to_path_buf()));
296 }
297
298 let parts = components(path);
299 let mut resolved = PathBuf::from("/");
300 let mut current = root;
301
302 for name in &parts {
303 current = resolve_if_symlink(current, path, depth, tree_root)?;
305 match current {
306 FsNode::Directory { children, .. } => {
307 let child = children
308 .get(*name)
309 .ok_or_else(|| VfsError::NotFound(path.to_path_buf()))?;
310 match child {
311 FsNode::Symlink { target, .. } => {
312 let target_norm = normalize(target)?;
313 resolved =
315 resolve_canonical(tree_root, &target_norm, depth - 1, tree_root)?;
316 current = navigate(tree_root, &resolved, true, depth - 1, tree_root)?;
317 }
318 _ => {
319 resolved.push(name);
320 current = child;
321 }
322 }
323 }
324 _ => return Err(VfsError::NotADirectory(path.to_path_buf())),
325 }
326 }
327 Ok(resolved)
328}
329
330fn resolve_canonical_from_root(
333 root: &FsNode,
334 path: &Path,
335 follow_final: bool,
336 depth: u32,
337) -> Result<PathBuf, VfsError> {
338 if depth == 0 {
339 return Err(VfsError::SymlinkLoop(path.to_path_buf()));
340 }
341
342 let parts = components(path);
343 let mut resolved = PathBuf::from("/");
344 let mut current: &FsNode = root;
345
346 for (i, name) in parts.iter().enumerate() {
347 let is_last = i == parts.len() - 1;
348 current = resolve_if_symlink_from_root(current, path, depth, root)?;
350 match current {
351 FsNode::Directory { children, .. } => {
352 let child = children
353 .get(*name)
354 .ok_or_else(|| VfsError::NotFound(path.to_path_buf()))?;
355 if is_last && !follow_final {
356 resolved.push(name);
357 break;
358 }
359 match child {
360 FsNode::Symlink { target, .. } => {
361 let target_norm = normalize(target)?;
362 resolved =
363 resolve_canonical_from_root(root, &target_norm, true, depth - 1)?;
364 current = navigate_readonly(root, &resolved, true, depth - 1, root)?;
365 }
366 _ => {
367 resolved.push(name);
368 current = child;
369 }
370 }
371 }
372 _ => return Err(VfsError::NotADirectory(path.to_path_buf())),
373 }
374 }
375 Ok(resolved)
376}
377
378fn resolve_if_symlink_from_root<'a>(
379 node: &'a FsNode,
380 original_path: &Path,
381 depth: u32,
382 root: &'a FsNode,
383) -> Result<&'a FsNode, VfsError> {
384 if depth == 0 {
385 return Err(VfsError::SymlinkLoop(original_path.to_path_buf()));
386 }
387 match node {
388 FsNode::Symlink { target, .. } => {
389 let target_norm = normalize(target)?;
390 navigate_readonly(root, &target_norm, true, depth - 1, root)
391 }
392 other => Ok(other),
393 }
394}
395
396fn navigate_readonly<'a>(
397 root: &'a FsNode,
398 path: &Path,
399 follow_final: bool,
400 depth: u32,
401 tree_root: &'a FsNode,
402) -> Result<&'a FsNode, VfsError> {
403 navigate(root, path, follow_final, depth, tree_root)
404}
405
406fn navigate_to_mut<'a>(node: &'a mut FsNode, parts: &[&str]) -> Option<&'a mut FsNode> {
409 let mut current = node;
410 for name in parts {
411 match current {
412 FsNode::Directory { children, .. } => {
413 current = children.get_mut(*name)?;
414 }
415 _ => return None,
416 }
417 }
418 Some(current)
419}
420
421impl VirtualFs for InMemoryFs {
426 fn read_file(&self, path: &Path) -> Result<Vec<u8>, VfsError> {
427 self.with_node(path, |node| match node {
428 FsNode::File { content, .. } => Ok(content.clone()),
429 FsNode::Directory { .. } => Err(VfsError::IsADirectory(path.to_path_buf())),
430 FsNode::Symlink { .. } => Err(VfsError::IoError(
431 "unexpected symlink after resolution".into(),
432 )),
433 })
434 }
435
436 fn write_file(&self, path: &Path, content: &[u8]) -> Result<(), VfsError> {
437 let norm = normalize(path)?;
438
439 {
441 let mut tree = self.root.write();
442 let canon_result = resolve_canonical_from_root(&tree, &norm, true, MAX_SYMLINK_DEPTH);
443 if let Ok(canon) = canon_result {
444 let canon_parts = components(&canon);
445 let node = navigate_to_mut(&mut tree, &canon_parts);
446 if let Some(node) = node {
447 match node {
448 FsNode::File {
449 content: c,
450 mtime: m,
451 ..
452 } => {
453 *c = content.to_vec();
454 *m = SystemTime::now();
455 return Ok(());
456 }
457 FsNode::Directory { .. } => {
458 return Err(VfsError::IsADirectory(path.to_path_buf()));
459 }
460 FsNode::Symlink { .. } => {}
461 }
462 }
463 }
464 }
465
466 self.with_parent_mut(path, |parent, child_name| match parent {
468 FsNode::Directory { children, .. } => {
469 children.insert(
470 child_name.to_string(),
471 FsNode::File {
472 content: content.to_vec(),
473 mode: 0o644,
474 mtime: SystemTime::now(),
475 },
476 );
477 Ok(())
478 }
479 _ => Err(VfsError::NotADirectory(path.to_path_buf())),
480 })
481 }
482
483 fn append_file(&self, path: &Path, content: &[u8]) -> Result<(), VfsError> {
484 self.with_node_mut(path, |node| match node {
485 FsNode::File {
486 content: c,
487 mtime: m,
488 ..
489 } => {
490 c.extend_from_slice(content);
491 *m = SystemTime::now();
492 Ok(())
493 }
494 FsNode::Directory { .. } => Err(VfsError::IsADirectory(path.to_path_buf())),
495 FsNode::Symlink { .. } => Err(VfsError::IoError(
496 "unexpected symlink after resolution".into(),
497 )),
498 })
499 }
500
501 fn remove_file(&self, path: &Path) -> Result<(), VfsError> {
502 let norm = normalize(path)?;
504
505 self.with_parent_mut(path, |parent, child_name| match parent {
507 FsNode::Directory { children, .. } => match children.get(child_name) {
508 Some(FsNode::File { .. } | FsNode::Symlink { .. }) => {
509 children.remove(child_name);
510 Ok(())
511 }
512 Some(FsNode::Directory { .. }) => Err(VfsError::IsADirectory(norm.clone())),
513 None => Err(VfsError::NotFound(norm.clone())),
514 },
515 _ => Err(VfsError::NotADirectory(norm.clone())),
516 })
517 }
518
519 fn mkdir(&self, path: &Path) -> Result<(), VfsError> {
520 self.with_parent_mut(path, |parent, child_name| match parent {
521 FsNode::Directory { children, .. } => {
522 if children.contains_key(child_name) {
523 return Err(VfsError::AlreadyExists(path.to_path_buf()));
524 }
525 children.insert(
526 child_name.to_string(),
527 FsNode::Directory {
528 children: BTreeMap::new(),
529 mode: 0o755,
530 mtime: SystemTime::now(),
531 },
532 );
533 Ok(())
534 }
535 _ => Err(VfsError::NotADirectory(path.to_path_buf())),
536 })
537 }
538
539 fn mkdir_p(&self, path: &Path) -> Result<(), VfsError> {
540 let norm = normalize(path)?;
541 let parts = components(&norm);
542 if parts.is_empty() {
543 return Ok(()); }
545
546 let mut tree = self.root.write();
547 let mut current: &mut FsNode = &mut tree;
548 for name in &parts {
549 match current {
550 FsNode::Directory { children, .. } => {
551 current =
552 children
553 .entry((*name).to_string())
554 .or_insert_with(|| FsNode::Directory {
555 children: BTreeMap::new(),
556 mode: 0o755,
557 mtime: SystemTime::now(),
558 });
559 match current {
561 FsNode::Directory { .. } => {}
562 FsNode::File { .. } => {
563 return Err(VfsError::NotADirectory(path.to_path_buf()));
564 }
565 FsNode::Symlink { .. } => {
566 return Err(VfsError::NotADirectory(path.to_path_buf()));
568 }
569 }
570 }
571 _ => return Err(VfsError::NotADirectory(path.to_path_buf())),
572 }
573 }
574 Ok(())
575 }
576
577 fn readdir(&self, path: &Path) -> Result<Vec<DirEntry>, VfsError> {
578 self.with_node(path, |node| match node {
579 FsNode::Directory { children, .. } => {
580 let entries = children
581 .iter()
582 .map(|(name, child)| DirEntry {
583 name: name.clone(),
584 node_type: match child {
585 FsNode::File { .. } => NodeType::File,
586 FsNode::Directory { .. } => NodeType::Directory,
587 FsNode::Symlink { .. } => NodeType::Symlink,
588 },
589 })
590 .collect();
591 Ok(entries)
592 }
593 _ => Err(VfsError::NotADirectory(path.to_path_buf())),
594 })
595 }
596
597 fn remove_dir(&self, path: &Path) -> Result<(), VfsError> {
598 self.with_parent_mut(path, |parent, child_name| match parent {
599 FsNode::Directory { children, .. } => match children.get(child_name) {
600 Some(FsNode::Directory { children: ch, .. }) => {
601 if ch.is_empty() {
602 children.remove(child_name);
603 Ok(())
604 } else {
605 Err(VfsError::DirectoryNotEmpty(path.to_path_buf()))
606 }
607 }
608 Some(FsNode::File { .. }) => Err(VfsError::NotADirectory(path.to_path_buf())),
609 Some(FsNode::Symlink { .. }) => Err(VfsError::NotADirectory(path.to_path_buf())),
610 None => Err(VfsError::NotFound(path.to_path_buf())),
611 },
612 _ => Err(VfsError::NotADirectory(path.to_path_buf())),
613 })
614 }
615
616 fn remove_dir_all(&self, path: &Path) -> Result<(), VfsError> {
617 self.with_parent_mut(path, |parent, child_name| match parent {
618 FsNode::Directory { children, .. } => match children.get(child_name) {
619 Some(FsNode::Directory { .. }) => {
620 children.remove(child_name);
621 Ok(())
622 }
623 Some(FsNode::File { .. }) => Err(VfsError::NotADirectory(path.to_path_buf())),
624 Some(FsNode::Symlink { .. }) => Err(VfsError::NotADirectory(path.to_path_buf())),
625 None => Err(VfsError::NotFound(path.to_path_buf())),
626 },
627 _ => Err(VfsError::NotADirectory(path.to_path_buf())),
628 })
629 }
630
631 fn exists(&self, path: &Path) -> bool {
632 let norm = match normalize(path) {
633 Ok(p) => p,
634 Err(_) => return false,
635 };
636 let tree = self.root.read();
637 navigate(&tree, &norm, true, MAX_SYMLINK_DEPTH, &tree).is_ok()
638 }
639
640 fn stat(&self, path: &Path) -> Result<Metadata, VfsError> {
641 self.with_node(path, |node| Ok(node_metadata(node)))
642 }
643
644 fn lstat(&self, path: &Path) -> Result<Metadata, VfsError> {
645 self.with_node_no_follow(path, |node| Ok(node_metadata(node)))
646 }
647
648 fn chmod(&self, path: &Path, mode: u32) -> Result<(), VfsError> {
649 self.with_node_mut(path, |node| {
650 match node {
651 FsNode::File { mode: m, .. } | FsNode::Directory { mode: m, .. } => {
652 *m = mode;
653 }
654 FsNode::Symlink { .. } => {
655 return Err(VfsError::IoError("cannot chmod a symlink directly".into()));
657 }
658 }
659 Ok(())
660 })
661 }
662
663 fn utimes(&self, path: &Path, mtime: SystemTime) -> Result<(), VfsError> {
664 self.with_node_mut(path, |node| {
665 match node {
666 FsNode::File { mtime: m, .. }
667 | FsNode::Directory { mtime: m, .. }
668 | FsNode::Symlink { mtime: m, .. } => {
669 *m = mtime;
670 }
671 }
672 Ok(())
673 })
674 }
675
676 fn symlink(&self, target: &Path, link: &Path) -> Result<(), VfsError> {
677 self.with_parent_mut(link, |parent, child_name| match parent {
678 FsNode::Directory { children, .. } => {
679 if children.contains_key(child_name) {
680 return Err(VfsError::AlreadyExists(link.to_path_buf()));
681 }
682 children.insert(
683 child_name.to_string(),
684 FsNode::Symlink {
685 target: target.to_path_buf(),
686 mtime: SystemTime::now(),
687 },
688 );
689 Ok(())
690 }
691 _ => Err(VfsError::NotADirectory(link.to_path_buf())),
692 })
693 }
694
695 fn hardlink(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
696 let content = self.read_file(src)?;
698 let meta = self.stat(src)?;
699 self.with_parent_mut(dst, |parent, child_name| match parent {
700 FsNode::Directory { children, .. } => {
701 if children.contains_key(child_name) {
702 return Err(VfsError::AlreadyExists(dst.to_path_buf()));
703 }
704 children.insert(
705 child_name.to_string(),
706 FsNode::File {
707 content: content.clone(),
708 mode: meta.mode,
709 mtime: meta.mtime,
710 },
711 );
712 Ok(())
713 }
714 _ => Err(VfsError::NotADirectory(dst.to_path_buf())),
715 })
716 }
717
718 fn readlink(&self, path: &Path) -> Result<PathBuf, VfsError> {
719 self.with_node_no_follow(path, |node| match node {
720 FsNode::Symlink { target, .. } => Ok(target.clone()),
721 _ => Err(VfsError::InvalidPath(format!(
722 "not a symlink: {}",
723 path.display()
724 ))),
725 })
726 }
727
728 fn canonicalize(&self, path: &Path) -> Result<PathBuf, VfsError> {
729 self.resolve_path(path)
730 }
731
732 fn copy(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
733 let content = self.read_file(src)?;
734 let meta = self.stat(src)?;
735 self.write_file(dst, &content)?;
736 self.chmod(dst, meta.mode)?;
737 Ok(())
738 }
739
740 fn rename(&self, src: &Path, dst: &Path) -> Result<(), VfsError> {
741 let src_norm = normalize(src)?;
742 let dst_norm = normalize(dst)?;
743
744 let src_parts = components(&src_norm);
745 let dst_parts = components(&dst_norm);
746 if src_parts.is_empty() || dst_parts.is_empty() {
747 return Err(VfsError::InvalidPath("cannot rename root".into()));
748 }
749
750 let mut tree = self.root.write();
751
752 let node = {
754 let src_parent_parts = &src_parts[..src_parts.len() - 1];
755 let src_child = src_parts.last().unwrap();
756
757 let mut parent: &mut FsNode = &mut tree;
758 for name in src_parent_parts {
759 match parent {
760 FsNode::Directory { children, .. } => {
761 parent = children
762 .get_mut(*name)
763 .ok_or_else(|| VfsError::NotFound(src.to_path_buf()))?;
764 }
765 _ => return Err(VfsError::NotADirectory(src.to_path_buf())),
766 }
767 }
768 match parent {
769 FsNode::Directory { children, .. } => children
770 .remove(*src_child)
771 .ok_or_else(|| VfsError::NotFound(src.to_path_buf()))?,
772 _ => return Err(VfsError::NotADirectory(src.to_path_buf())),
773 }
774 };
775
776 {
778 let dst_parent_parts = &dst_parts[..dst_parts.len() - 1];
779 let dst_child = dst_parts.last().unwrap();
780
781 let mut parent: &mut FsNode = &mut tree;
782 for name in dst_parent_parts {
783 match parent {
784 FsNode::Directory { children, .. } => {
785 parent = children
786 .get_mut(*name)
787 .ok_or_else(|| VfsError::NotFound(dst.to_path_buf()))?;
788 }
789 _ => return Err(VfsError::NotADirectory(dst.to_path_buf())),
790 }
791 }
792 match parent {
793 FsNode::Directory { children, .. } => {
794 children.insert((*dst_child).to_string(), node);
795 }
796 _ => return Err(VfsError::NotADirectory(dst.to_path_buf())),
797 }
798 }
799
800 Ok(())
801 }
802
803 fn glob(&self, pattern: &str, cwd: &Path) -> Result<Vec<PathBuf>, VfsError> {
804 self.glob_with_opts(
807 pattern,
808 cwd,
809 &GlobOptions {
810 globstar: true,
811 ..GlobOptions::default()
812 },
813 )
814 }
815
816 fn glob_with_opts(
817 &self,
818 pattern: &str,
819 cwd: &Path,
820 opts: &GlobOptions,
821 ) -> Result<Vec<PathBuf>, VfsError> {
822 let is_absolute = pattern.starts_with('/');
823 let abs_pattern = if is_absolute {
824 pattern.to_string()
825 } else {
826 let cwd_str = cwd.to_str().unwrap_or("/").trim_end_matches('/');
827 format!("{cwd_str}/{pattern}")
828 };
829
830 let components: Vec<&str> = abs_pattern.split('/').filter(|s| !s.is_empty()).collect();
831 let tree = self.root.read();
832 let mut results = Vec::new();
833 let max = 100_000;
834 glob_collect(
835 &tree,
836 &components,
837 PathBuf::from("/"),
838 &mut results,
839 &tree,
840 max,
841 opts,
842 );
843 results.sort();
844 results.dedup();
845
846 if !is_absolute {
847 results = results
848 .into_iter()
849 .filter_map(|p| p.strip_prefix(cwd).ok().map(|r| r.to_path_buf()))
850 .collect();
851 }
852
853 Ok(results)
854 }
855
856 fn deep_clone(&self) -> Arc<dyn VirtualFs> {
857 Arc::new(InMemoryFs::deep_clone(self))
858 }
859}
860
861use crate::interpreter::pattern::{
866 extglob_match, extglob_match_nocase, glob_match, glob_match_nocase,
867};
868
869fn glob_collect(
875 node: &FsNode,
876 components: &[&str],
877 current_path: PathBuf,
878 results: &mut Vec<PathBuf>,
879 tree_root: &FsNode,
880 max: usize,
881 opts: &GlobOptions,
882) {
883 if results.len() >= max {
884 return;
885 }
886
887 if components.is_empty() {
888 results.push(current_path);
889 return;
890 }
891
892 let resolved =
894 resolve_if_symlink(node, ¤t_path, MAX_SYMLINK_DEPTH, tree_root).unwrap_or(node);
895
896 let pattern = components[0];
897 let rest = &components[1..];
898
899 if pattern == "**" && opts.globstar {
900 glob_collect(
902 resolved,
903 rest,
904 current_path.clone(),
905 results,
906 tree_root,
907 max,
908 opts,
909 );
910
911 if let FsNode::Directory { children, .. } = resolved {
913 for (name, child) in children {
914 if results.len() >= max {
915 return;
916 }
917 if name.starts_with('.') && !opts.dotglob {
918 continue;
919 }
920 let child_path = current_path.join(name);
921 glob_collect(child, components, child_path, results, tree_root, max, opts);
922 }
923 }
924 } else {
925 let effective_pattern = if pattern == "**" { "*" } else { pattern };
927
928 if let FsNode::Directory { children, .. } = resolved {
929 for (name, child) in children {
930 if results.len() >= max {
931 return;
932 }
933 if name.starts_with('.') && !effective_pattern.starts_with('.') && !opts.dotglob {
935 continue;
936 }
937 let matched = if opts.extglob && opts.nocaseglob {
938 extglob_match_nocase(effective_pattern, name)
939 } else if opts.extglob {
940 extglob_match(effective_pattern, name)
941 } else if opts.nocaseglob {
942 glob_match_nocase(effective_pattern, name)
943 } else {
944 glob_match(effective_pattern, name)
945 };
946 if matched {
947 let child_path = current_path.join(name);
948 if rest.is_empty() {
949 results.push(child_path);
950 } else {
951 glob_collect(child, rest, child_path, results, tree_root, max, opts);
952 }
953 }
954 }
955 }
956 }
957}
958
959fn node_metadata(node: &FsNode) -> Metadata {
961 match node {
962 FsNode::File {
963 content,
964 mode,
965 mtime,
966 ..
967 } => Metadata {
968 node_type: NodeType::File,
969 size: content.len() as u64,
970 mode: *mode,
971 mtime: *mtime,
972 },
973 FsNode::Directory { mode, mtime, .. } => Metadata {
974 node_type: NodeType::Directory,
975 size: 0,
976 mode: *mode,
977 mtime: *mtime,
978 },
979 FsNode::Symlink { target, mtime, .. } => Metadata {
980 node_type: NodeType::Symlink,
981 size: target.to_string_lossy().len() as u64,
982 mode: 0o777,
983 mtime: *mtime,
984 },
985 }
986}