kernel/fs/vfs_v2/manager.rs
1//! VFS Manager v2 - Enhanced Virtual File System Management
2//!
3//! This module provides the next-generation VFS management system for Scarlet,
4//! built on the improved VFS v2 architecture with enhanced mount tree management,
5//! VfsEntry-based caching, and better isolation support.
6
7use alloc::{
8 string::{String, ToString},
9 sync::Arc,
10 vec,
11 vec::Vec,
12};
13use spin::{RwLock, Once};
14
15use crate::fs::{
16 FileSystemError, FileSystemErrorKind, FileMetadata, FileType,
17 DeviceFileInfo
18};
19use crate::object::KernelObject;
20
21use super::{
22 core::{VfsEntry, FileSystemOperations, DirectoryEntryInternal},
23 mount_tree::{MountTree, MountOptionsV2, MountPoint, VfsManagerId},
24};
25
26/// Filesystem ID type
27pub type FSId = u64;
28
29/// Path resolution options for VFS operations
30#[derive(Debug, Clone)]
31pub struct PathResolutionOptions {
32 /// Don't follow symbolic links in the final component (like lstat behavior)
33 pub no_follow: bool,
34}
35
36impl PathResolutionOptions {
37 /// Create options with no_follow flag set (don't follow final symlink)
38 pub fn no_follow() -> Self {
39 Self {
40 no_follow: true,
41 }
42 }
43}
44
45impl Default for PathResolutionOptions {
46 fn default() -> Self {
47 Self {
48 no_follow: false,
49 }
50 }
51}
52
53// Helper function to create FileSystemError
54fn vfs_error(kind: FileSystemErrorKind, message: &str) -> FileSystemError {
55 FileSystemError::new(kind, message)
56}
57
58/// VFS Manager v2 - Enhanced VFS architecture implementation
59///
60/// This manager provides advanced VFS functionality with proper mount tree
61/// management, enhanced caching, and better support for containerization.
62pub struct VfsManager {
63 /// Unique identifier for this VfsManager instance
64 pub id: VfsManagerId,
65 /// Mount tree for hierarchical mount point management
66 pub mount_tree: MountTree,
67 /// Current working directory
68 pub cwd: RwLock<Option<Arc<VfsEntry>>>,
69 /// Strong references to all currently mounted filesystems
70 pub mounted_filesystems: RwLock<Vec<Arc<dyn FileSystemOperations>>>,
71}
72
73static GLOBAL_VFS_MANAGER: Once<Arc<VfsManager>> = Once::new();
74
75impl VfsManager {
76 /// Create a new VFS manager instance with a dummy root
77 pub fn new() -> Self {
78 // Create a dummy root filesystem for initialization
79 use super::drivers::tmpfs::TmpFS;
80 let root_fs: Arc<dyn FileSystemOperations> = TmpFS::new(0); // 0 = unlimited memory
81 let root_node = root_fs.root_node();
82 let dummy_root_entry = VfsEntry::new(None, "/".to_string(), root_node);
83
84 let mount_tree = MountTree::new(dummy_root_entry.clone());
85
86 Self {
87 id: VfsManagerId::new(),
88 mount_tree,
89 cwd: RwLock::new(None),
90 mounted_filesystems: RwLock::new(vec![root_fs.clone()]),
91 }
92 }
93
94 /// Create a new VFS manager instance with a specified root filesystem
95 pub fn new_with_root(root_fs: Arc<dyn FileSystemOperations>) -> Self {
96 let root_node = root_fs.root_node();
97 let dummy_root_entry = VfsEntry::new(None, "/".to_string(), root_node);
98 let mount_tree = MountTree::new(dummy_root_entry.clone());
99 Self {
100 id: VfsManagerId::new(),
101 mount_tree,
102 cwd: RwLock::new(None),
103 mounted_filesystems: RwLock::new(vec![root_fs.clone()]),
104 }
105 }
106
107 /// Mount a filesystem at the specified path
108 ///
109 /// This will mount the given filesystem at the specified mount point.
110 /// If the mount point is "/", it will replace the root filesystem.
111 ///
112 /// # Arguments
113 /// * `filesystem` - The filesystem to mount.
114 /// * `mount_point_str` - The path where the filesystem should be mounted.
115 /// * `flags` - Flags for the mount operation (e.g., read-only).
116 ///
117 /// # Errors
118 /// Returns an error if the mount point is invalid, the filesystem cannot be mounted,
119 /// or if the mount operation fails.
120 ///
121 pub fn mount(
122 &self,
123 filesystem: Arc<dyn FileSystemOperations>,
124 mount_point_str: &str,
125 flags: u32,
126 ) -> Result<(), FileSystemError> {
127 if mount_point_str == "/" {
128 // Remove the existing root FS from the list
129 let old_root_fs = self.mount_tree.root_mount.read().root.node().filesystem()
130 .and_then(|w| w.upgrade());
131 if let Some(old_fs) = old_root_fs {
132 let old_ptr = Arc::as_ptr(&old_fs) as *const () as usize;
133 self.mounted_filesystems.write().retain(|fs| Arc::as_ptr(fs) as *const () as usize != old_ptr);
134 }
135 // Set the new root
136 let new_root_node = filesystem.root_node();
137 let new_root_entry = VfsEntry::new(None, "/".to_string(), new_root_node);
138 let new_root_mount = MountPoint::new_regular("/".to_string(), new_root_entry);
139 self.mount_tree.replace_root(new_root_mount);
140 // Push the new FS
141 self.mounted_filesystems.write().push(filesystem.clone());
142 return Ok(());
143 }
144 let _mount_options = MountOptionsV2 {
145 readonly: (flags & 0x01) != 0,
146 flags,
147 };
148 let (target_entry, target_mount_point) = self.mount_tree.resolve_path(mount_point_str)?;
149 self.mount_tree.mount(target_entry, target_mount_point, filesystem.clone())?;
150 self.mounted_filesystems.write().push(filesystem);
151 Ok(())
152 }
153
154 /// Unmount a mount point at the specified path
155 ///
156 /// This will remove the mount point from the mount tree and clean up any
157 /// associated resources.
158 ///
159 /// # Arguments
160 /// * `mount_point_str` - The path of the mount point to unmount.
161 ///
162 /// # Errors
163 /// Returns an error if the mount point is not valid or if the unmount operation fails.
164 ///
165 pub fn unmount(&self, mount_point_str: &str) -> Result<(), FileSystemError> {
166 let (entry, mount_point) = self.mount_tree.resolve_mount_point(mount_point_str)?;
167 if !self.mount_tree.is_mount_point(&entry, &mount_point) {
168 return Err(vfs_error(FileSystemErrorKind::InvalidPath, "Path is not a mount point"));
169 }
170 let unmounted_mount = self.mount_tree.unmount(&entry, &mount_point)?;
171 // Identify the unmounted fs and remove it from the holding list
172 // If mount_point is a bind mount, we do not remove the filesystem
173 if !unmounted_mount.is_bind_mount() {
174 if let Some(fs) = unmounted_mount.root.node().filesystem().unwrap().upgrade() {
175 let fs_ptr = Arc::as_ptr(&fs) as *const () as usize;
176 self.mounted_filesystems.write().retain(|fs| Arc::as_ptr(fs) as *const () as usize != fs_ptr);
177 }
178 }
179 Ok(())
180 }
181
182 /// Bind mount a directory from source_path to target_path
183 ///
184 /// This will create a bind mount where the source directory is mounted
185 /// at the target path.
186 ///
187 /// # Arguments
188 /// * `source_path` - The path of the source directory to bind mount.
189 /// * `target_path` - The path where the source directory should be mounted.
190 ///
191 /// # Errors
192 /// Returns an error if the source is not a directory, the target is already a mount point,
193 /// or if the source is not a valid directory.
194 ///
195 pub fn bind_mount(
196 &self,
197 source_path: &str,
198 target_path: &str
199 ) -> Result<(), FileSystemError> {
200 // Resolve the target mount point
201 let (target_entry, target_mount_point) = self.mount_tree.resolve_path(target_path)?;
202 // Resolve the source entry
203 let (source_entry, source_mount_point) = self.mount_tree.resolve_path(source_path)?;
204 // Check if source is a valid entry
205 if !source_entry.node().is_directory()? {
206 return Err(vfs_error(FileSystemErrorKind::NotADirectory, "Source path must be a directory"));
207 }
208 // Check if target is not already a mount point
209 if self.mount_tree.is_mount_point(&target_entry, &target_mount_point) {
210 return Err(vfs_error(FileSystemErrorKind::InvalidPath, "Target path is already a mount point"));
211 }
212 // Check if source is a directory (bind mounts only support directories)
213 if !source_entry.node().is_directory()? {
214 return Err(vfs_error(FileSystemErrorKind::NotADirectory, "Source path must be a directory"));
215 }
216 // Create the bind mount entry
217 self.bind_mount_entry(
218 source_entry,
219 source_mount_point,
220 target_entry,
221 target_mount_point
222 )
223 }
224
225 /// Bind mount a directory from another VFS instance
226 ///
227 /// This will create a bind mount where the source directory from another VFS
228 /// is mounted at the target path in this VFS.
229 ///
230 /// # Arguments
231 /// * `source_vfs` - The source VFS instance containing the directory to bind
232 /// * `source_path` - The path of the source directory in the source VFS.
233 /// * `target_path` - The path where the source directory should be mounted in this
234 /// VFS.
235 ///
236 /// # Errors
237 /// Returns an error if the source path does not exist, the target is already a mount point,
238 /// or if the source is not a valid directory.
239 ///
240 pub fn bind_mount_from(
241 &self,
242 source_vfs: &Arc<VfsManager>,
243 source_path: &str,
244 target_path: &str,
245 ) -> Result<(), FileSystemError> {
246 // Resolve the source and target paths
247 let (source_entry, source_mount_point) = source_vfs.mount_tree.resolve_path(source_path)?;
248 let (target_entry, target_mount_point) = self.mount_tree.resolve_path(target_path)?;
249
250 // Create the bind mount entry
251 self.bind_mount_entry(
252 source_entry,
253 source_mount_point,
254 target_entry,
255 target_mount_point
256 )
257 }
258
259 /// Create a bind mount from source_entry to target_entry
260 fn bind_mount_entry(
261 &self,
262 source_entry: Arc<VfsEntry>,
263 source_mount_point: Arc<MountPoint>,
264 target_entry: Arc<VfsEntry>,
265 target_mount_point: Arc<MountPoint>,
266 ) -> Result<(), FileSystemError> {
267 // Create a new MountPoint for the bind mount
268 let bind_mount = MountPoint::new_bind(target_entry.name().clone(), source_entry);
269 // Set parent/parent_entry
270 unsafe {
271 let mut_ptr = Arc::as_ptr(&bind_mount) as *mut MountPoint;
272 (*mut_ptr).parent = Some(Arc::downgrade(&target_mount_point));
273 (*mut_ptr).parent_entry = Some(target_entry.clone());
274 }
275 // Connect the bind mount to the source mount point
276 *(bind_mount.children.write()) = source_mount_point.children.read().clone();
277 // Add as child to target_mount_point
278 target_mount_point.children.write().insert(target_entry.node().id(), bind_mount);
279 Ok(())
280 }
281
282 /// Open a file at the specified path
283 ///
284 /// This will resolve the path using the MountTreeV2 and open the file
285 /// using the filesystem associated with the resolved VfsEntry.
286 ///
287 /// # Arguments
288 /// * `path` - The path of the file to open.
289 /// * `flags` - Flags for opening the file (e.g., read, write
290 /// * `O_CREAT`, etc.).
291 ///
292 /// # Errors
293 /// Returns an error if the path does not exist, is not a file, or if
294 /// the filesystem cannot be resolved.
295 ///
296 pub fn open(&self, path: &str, flags: u32) -> Result<KernelObject, FileSystemError> {
297 // Use MountTreeV2 to resolve filesystem and relative path, then open
298 let entry = self.mount_tree.resolve_path(path)?.0;
299 let node = entry.node();
300 let filesystem = node.filesystem()
301 .and_then(|w| w.upgrade())
302 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "No filesystem reference"))?;
303 let file_obj = filesystem.open(&node, flags)?;
304 Ok(KernelObject::File(file_obj))
305 }
306
307 /// Create a file at the specified path
308 ///
309 /// This will create a new file in the filesystem at the given path.
310 ///
311 /// # Arguments
312 /// * `path` - The path where the file should be created.
313 /// * `file_type` - The type of file to create (e.g., regular
314 /// file, directory, etc.).
315 ///
316 /// # Errors
317 /// Returns an error if the parent directory does not exist, the filesystem cannot be resolved,
318 /// or if the file cannot be created.
319 ///
320 pub fn create_file(&self, path: &str, file_type: FileType) -> Result<(), FileSystemError> {
321 // Split path into parent and filename
322 let (parent_path, filename) = self.split_parent_child(path)?;
323
324 // Resolve parent directory using MountTreeV2
325 let parent_entry = self.mount_tree.resolve_path(&parent_path)?.0;
326 let parent_node = parent_entry.node();
327 debug_assert!(parent_node.filesystem().is_some(), "VfsManager::create_file - parent_node.filesystem() is None for path '{}'", parent_path);
328
329 // Create file using filesystem
330 let filesystem = parent_node.filesystem()
331 .and_then(|w| w.upgrade())
332 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "No filesystem reference"))?;
333 let new_node = filesystem.create(
334 &parent_node,
335 &filename,
336 file_type,
337 0o644, // Default permissions
338 )?;
339
340 // Create VfsEntry and add to parent cache
341 let new_entry = VfsEntry::new(
342 Some(Arc::downgrade(&parent_entry)),
343 filename.clone(),
344 new_node,
345 );
346
347
348 parent_entry.add_child(filename, new_entry);
349
350
351 Ok(())
352 }
353
354 /// Create a directory at the specified path
355 ///
356 /// This will create a new directory in the filesystem at the given path.
357 ///
358 /// # Arguments
359 /// * `path` - The path where the directory should be created.
360 ///
361 /// # Errors
362 /// Returns an error if the parent directory does not exist, the filesystem cannot be resolved,
363 /// or if the directory cannot be created.
364 ///
365 pub fn create_dir(&self, path: &str) -> Result<(), FileSystemError> {
366 self.create_file(path, FileType::Directory)
367 }
368
369 /// Create a symbolic link at the specified path
370 ///
371 /// This will create a new symbolic link in the filesystem at the given path,
372 /// pointing to the specified target path.
373 ///
374 /// # Arguments
375 /// * `path` - The path where the symbolic link should be created.
376 /// * `target_path` - The path that the symbolic link should point to.
377 ///
378 /// # Errors
379 /// Returns an error if the parent directory does not exist, the filesystem cannot be resolved,
380 /// or if the symbolic link cannot be created.
381 ///
382 pub fn create_symlink(&self, path: &str, target_path: &str) -> Result<(), FileSystemError> {
383 self.create_file(path, FileType::SymbolicLink(target_path.to_string()))
384 }
385
386 /// Remove a file at the specified path
387 ///
388 /// This will remove the file from the filesystem and update the mount tree.
389 ///
390 /// # Arguments
391 /// * `path` - The path of the file to remove.
392 ///
393 /// # Errors
394 /// Returns an error if the path does not exist, is not a file, or if
395 /// the filesystem cannot be resolved.
396 ///
397 pub fn remove(&self, path: &str) -> Result<(), FileSystemError> {
398 // Resolve the entry to be removed - use no_follow to follow intermediate symlinks
399 // but not the final component (like POSIX rm behavior)
400 let options = PathResolutionOptions::no_follow();
401 let (entry_to_remove, mount_point) = self.mount_tree.resolve_path_with_options(path, &options)?;
402
403 // Check if the entry is involved in any mount, which would make it busy
404 if self.mount_tree.is_entry_used_in_mount(&entry_to_remove, &mount_point) {
405 return Err(vfs_error(FileSystemErrorKind::NotSupported, "Resource is busy"));
406 }
407
408 // Split path into parent and filename
409 let (parent_path, filename) = self.split_parent_child(path)?;
410
411 // Resolve parent directory using MountTreeV2 (follow all symlinks for parent path)
412 let parent_entry = self.mount_tree.resolve_path(&parent_path)?.0;
413 let parent_node = parent_entry.node();
414
415 // Remove from filesystem
416 let filesystem = parent_node.filesystem()
417 .and_then(|w| w.upgrade())
418 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "No filesystem reference"))?;
419 filesystem.remove(&parent_node, &filename)?;
420
421 // Remove from parent cache
422 let _ = parent_entry.remove_child(&filename);
423
424 Ok(())
425 }
426
427 /// Get metadata for a file at the specified path
428 ///
429 /// This will resolve the path using the MountTreeV2 and return the metadata
430 /// for the file represented by the resolved VfsEntry.
431 ///
432 /// # Arguments
433 /// * `path` - The path of the file to get metadata for.
434 ///
435 /// # Errors
436 /// Returns an error if the path does not exist, is not a file, or if
437 /// the filesystem cannot be resolved.
438 ///
439 pub fn metadata(&self, path: &str) -> Result<FileMetadata, FileSystemError> {
440 // Resolve path to VfsEntry
441 let entry = self.mount_tree.resolve_path(path)?.0;
442
443 // Get VfsNode and return metadata
444 let node = entry.node();
445
446 node.metadata()
447 }
448
449 /// Read directory entries at the specified path
450 ///
451 /// This will resolve the path using the MountTreeV2 and return a list of
452 /// directory entries for the directory represented by the resolved VfsEntry.
453 ///
454 /// # Arguments
455 /// * `path` - The path of the directory to read.
456 ///
457 /// # Errors
458 /// Returns an error if the path does not exist, is not a directory, or if
459 /// the filesystem cannot be resolved.
460 ///
461 pub fn readdir(&self, path: &str) -> Result<Vec<DirectoryEntryInternal>, FileSystemError> {
462 // Resolve path to VfsEntry
463 let entry = self.mount_tree.resolve_path(path)?.0;
464
465 // Get VfsNode
466 let node = entry.node();
467
468 // Check if it's a directory
469 if !node.is_directory()? {
470 return Err(FileSystemError::new(
471 FileSystemErrorKind::NotADirectory,
472 "Not a directory"
473 ));
474 }
475
476 // Get filesystem from node
477 let fs_ref = node.filesystem()
478 .ok_or_else(|| FileSystemError::new(
479 FileSystemErrorKind::NotSupported,
480 "Node has no filesystem reference"
481 ))?;
482
483 let filesystem = fs_ref.upgrade()
484 .ok_or_else(|| FileSystemError::new(
485 FileSystemErrorKind::NotSupported,
486 "Filesystem reference is dead"
487 ))?;
488
489 // Call filesystem's readdir
490 filesystem.readdir(&node)
491 }
492
493 /// Set current working directory
494 ///
495 /// This will change the current working directory to the specified path.
496 ///
497 /// # Arguments
498 /// * `path` - The path to set as the current working directory.
499 ///
500 /// # Errors
501 /// Returns an error if the path does not exist, is not a directory, or if
502 /// the filesystem cannot be resolved.
503 ///
504 pub fn set_cwd(&self, path: &str) -> Result<(), FileSystemError> {
505 let entry = self.mount_tree.resolve_path(path)?.0;
506
507 // Verify it's a directory
508 let node = entry.node();
509
510 if !node.is_directory()? {
511 return Err(FileSystemError::new(
512 FileSystemErrorKind::NotADirectory,
513 "Not a directory"
514 ));
515 }
516
517 *self.cwd.write() = Some(entry);
518 Ok(())
519 }
520
521 /// Get current working directory
522 ///
523 /// This returns the current working directory as an `Arc<VfsEntry>`.
524 ///
525 /// If the current working directory is not set, it returns `None`.
526 ///
527 /// # Returns
528 /// An `Option<Arc<VfsEntry>>` containing the current working directory entry,
529 /// or `None` if the current working directory is not set.
530 ///
531 pub fn get_cwd(&self) -> Option<Arc<VfsEntry>> {
532 self.cwd.read().clone()
533 }
534
535 /// Create a device file
536 ///
537 /// This will create a new device file in the filesystem at the given path.
538 ///
539 /// # Arguments
540 /// * `path` - The path where the device file should be created.
541 /// * `device_info` - Information about the device file to create (e.g.,
542 /// device type, major/minor numbers, etc.).
543 ///
544 /// # Errors
545 /// Returns an error if the parent directory does not exist, the filesystem cannot be resolved,
546 /// or if the device file cannot be created.
547 ///
548 pub fn create_device_file(
549 &self,
550 path: &str,
551 device_info: DeviceFileInfo,
552 ) -> Result<(), FileSystemError> {
553 let file_type = match device_info.device_type {
554 crate::device::DeviceType::Char => FileType::CharDevice(device_info),
555 crate::device::DeviceType::Block => FileType::BlockDevice(device_info),
556 _ => return Err(FileSystemError::new(
557 FileSystemErrorKind::NotSupported,
558 "Unsupported device type"
559 )),
560 };
561
562 self.create_file(path, file_type)
563 }
564
565 pub fn resolve_path(&self, path: &str) -> Result<Arc<VfsEntry>, FileSystemError> {
566 self.resolve_path_with_options(path, &PathResolutionOptions::default())
567 }
568
569 /// Resolve a path with specified options
570 pub fn resolve_path_with_options(&self, path: &str, options: &PathResolutionOptions) -> Result<Arc<VfsEntry>, FileSystemError> {
571 // Use MountTreeV2 to resolve the path with options
572 let (entry, _mount_point) = self.mount_tree.resolve_path_with_options(path, options)?;
573 Ok(entry)
574 }
575
576 /// Create a hard link
577 ///
578 /// This will create a hard link where the source file is linked to the target path.
579 /// Both paths will refer to the same underlying file data.
580 ///
581 /// # Arguments
582 /// * `source_path` - Path of the existing file to link to
583 /// * `target_path` - Path where the hard link should be created
584 ///
585 /// # Errors
586 /// Returns an error if the source doesn't exist, target already exists,
587 /// filesystems don't match, or hard links aren't supported.
588 ///
589 pub fn create_hardlink(
590 &self,
591 source_path: &str,
592 target_path: &str,
593 ) -> Result<(), FileSystemError> {
594 // Resolve source file
595 let (source_entry, _source_mount) = self.mount_tree.resolve_path(source_path)?;
596 let source_node = source_entry.node();
597
598 // Check that source is a regular file (most filesystems don't support directory hard links)
599 if source_node.is_directory()? {
600 return Err(vfs_error(
601 FileSystemErrorKind::InvalidOperation,
602 "Cannot create hard link to directory"
603 ));
604 }
605
606 // Split target path into parent and filename
607 let (target_parent_path, target_filename) = self.split_parent_child(target_path)?;
608
609 // Resolve target parent directory
610 let (target_parent_entry, _target_mount) = self.mount_tree.resolve_path(&target_parent_path)?;
611 let target_parent_node = target_parent_entry.node();
612
613 // Check that target parent is a directory
614 if !target_parent_node.is_directory()? {
615 return Err(vfs_error(
616 FileSystemErrorKind::NotADirectory,
617 "Target parent is not a directory"
618 ));
619 }
620
621 // Get filesystems for both source and target
622 let source_fs = source_node.filesystem()
623 .and_then(|w| w.upgrade())
624 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "No filesystem reference for source"))?;
625
626 let target_fs = target_parent_node.filesystem()
627 .and_then(|w| w.upgrade())
628 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "No filesystem reference for target"))?;
629
630 // Check that both files are on the same filesystem (hard links can't cross filesystem boundaries)
631 if !Arc::ptr_eq(&source_fs, &target_fs) {
632 return Err(vfs_error(
633 FileSystemErrorKind::CrossDevice,
634 "Hard links cannot cross filesystem boundaries"
635 ));
636 }
637
638 // Check if target already exists
639 if target_parent_entry.get_child(&target_filename).is_some() {
640 return Err(vfs_error(
641 FileSystemErrorKind::FileExists,
642 "Target file already exists"
643 ));
644 }
645
646 // Create the hard link
647 let link_node = source_fs.create_hardlink(&target_parent_node, &target_filename, &source_node)?;
648
649 // Create VfsEntry and add to parent cache
650 let link_entry = VfsEntry::new(
651 Some(Arc::downgrade(&target_parent_entry)),
652 target_filename.clone(),
653 link_node,
654 );
655 target_parent_entry.add_child(target_filename, link_entry);
656
657 Ok(())
658 }
659
660 // Helper methods
661
662 /// Split a path into parent directory and filename
663 fn split_parent_child(&self, path: &str) -> Result<(String, String), FileSystemError> {
664 // Simple path normalization: remove trailing slash except for root
665 let normalized = if path != "/" && path.ends_with('/') {
666 path.trim_end_matches('/').to_string()
667 } else {
668 path.to_string()
669 };
670
671 if normalized == "/" {
672 return Err(FileSystemError::new(
673 FileSystemErrorKind::InvalidPath,
674 "Cannot split root path"
675 ));
676 }
677
678 if let Some(last_slash) = normalized.rfind('/') {
679 let parent = if last_slash == 0 {
680 "/".to_string()
681 } else {
682 normalized[..last_slash].to_string()
683 };
684 let filename = normalized[last_slash + 1..].to_string();
685 Ok((parent, filename))
686 } else {
687 Err(FileSystemError::new(
688 FileSystemErrorKind::InvalidPath,
689 "Invalid path format"
690 ))
691 }
692 }
693}
694
695/// Initialize the global VFS manager (Arc) so it can be retrieved later
696pub fn init_global_vfs_manager() -> Arc<VfsManager> {
697 GLOBAL_VFS_MANAGER.call_once(|| Arc::new(VfsManager::new())).clone()
698}
699
700/// Retrieve the global VFS manager (Arc)
701pub fn get_global_vfs_manager() -> Arc<VfsManager> {
702 GLOBAL_VFS_MANAGER.get().expect("global VFS manager not initialized").clone()
703}
704