kernel/fs/vfs_v2/drivers/overlayfs/mod.rs
1//! OverlayFS v2 - Overlay filesystem implementation for VFS v2
2//!
3//! This module provides a union/overlay view of multiple filesystems, allowing
4//! files and directories from multiple source filesystems to appear as a single
5//! unified filesystem hierarchy.
6//!
7//! ## Features
8//!
9//! - **Multi-layer support**: Combines an optional upper layer (read-write) with
10//! multiple lower layers (read-only) in priority order
11//! - **Copy-up semantics**: Modifications to lower layer files are copied to the
12//! upper layer before modification
13//! - **Whiteout support**: Files can be hidden or deleted from view using special
14//! whiteout entries
15//! - **Mount point aware**: Handles crossing mount boundaries correctly when
16//! resolving paths across layers
17//!
18//! ## Usage
19//!
20//! ```rust,no_run
21//! // Create overlay with upper and lower layers
22//! let overlay = OverlayFS::new(
23//! Some((upper_mount, upper_entry)), // Upper layer for writes
24//! vec![(lower_mount, lower_entry)], // Lower layers (read-only)
25//! "my_overlay".to_string()
26//! )?;
27//! ```
28//!
29//! ## Cross-VFS Support
30//!
31//! - **Cross-VFS overlays supported**: Upper and lower layers can come from
32//! different VFS managers, enabling flexible overlay configurations
33//! - **Seamless integration**: Mount points from different VFS managers are
34//! unified transparently through the overlay interface
35//!
36//! ## Limitations
37//!
38//! - Upper layer is required for write operations
39//! - Whiteout files follow the `.wh.filename` convention
40
41use alloc::boxed::Box;
42use alloc::string::ToString;
43use alloc::{sync::Arc, string::String, vec::Vec, collections::BTreeSet, format};
44use spin::RwLock;
45use core::any::Any;
46
47use crate::driver_initcall;
48use crate::fs::vfs_v2::core::{VfsNode, FileSystemOperations, DirectoryEntryInternal, VfsEntry};
49use crate::fs::{get_fs_driver_manager, FileMetadata, FileObject, FilePermission, FileSystemDriver, FileSystemError, FileSystemErrorKind, FileType, SeekFrom, VfsManager};
50use crate::object::capability::{StreamOps, StreamError};
51use crate::fs::vfs_v2::mount_tree::MountPoint;
52use crate::vm::vmem::MemoryArea;
53
54/// OverlayFS implementation for VFS v2
55///
56/// This filesystem provides a unified view of multiple underlying filesystems
57/// by layering them on top of each other. Files and directories from all layers
58/// are merged, with the upper layer taking precedence for writes and the lower
59/// layers providing fallback content.
60///
61/// ## Layer Resolution
62///
63/// When resolving files or directories:
64/// 1. Check upper layer first (if present and not whiteout)
65/// 2. Check lower layers in priority order
66/// 3. Return first match found
67///
68/// ## Write Operations
69///
70/// All write operations are performed on the upper layer. If a file exists
71/// only in lower layers, it is first copied to the upper layer (copy-up)
72/// before modification.
73#[derive(Clone)]
74pub struct OverlayFS {
75 /// Upper layer for write operations (may be None for read-only overlay)
76 upper: Option<(Arc<MountPoint>, Arc<VfsEntry>)>,
77 /// Lower layers (in priority order, highest priority first)
78 lower_layers: Vec<(Arc<MountPoint>, Arc<VfsEntry>)>,
79 /// Filesystem name
80 name: String,
81 /// Root node (composite of all layers)
82 root_node: Arc<OverlayNode>,
83}
84
85/// A composite node that represents a file/directory across overlay layers
86///
87/// OverlayNode serves as a virtual representation of a file or directory that
88/// may exist in one or more layers of the overlay filesystem. It handles the
89/// resolution of operations across these layers according to overlay semantics.
90///
91/// ## Design
92///
93/// Each OverlayNode represents a specific path in the overlay and delegates
94/// operations to the appropriate underlying filesystem layers. The node itself
95/// doesn't store file content but rather coordinates access to the real nodes
96/// in the upper and lower layers.
97pub struct OverlayNode {
98 /// Node name
99 name: String,
100 /// Reference to overlay filesystem
101 overlay_fs: RwLock<Option<Arc<OverlayFS>>>,
102 /// Path in the overlay
103 path: String,
104 /// File type (resolved from layers)
105 file_type: FileType,
106 /// File ID
107 file_id: u64,
108}
109
110impl OverlayNode {
111 pub fn new(name: String, path: String, file_type: FileType, file_id: u64) -> Arc<Self> {
112 Arc::new(Self {
113 name,
114 overlay_fs: RwLock::new(None),
115 path,
116 file_type,
117 file_id,
118 })
119 }
120
121 pub fn set_overlay_fs(&self, fs: Arc<OverlayFS>) {
122 *self.overlay_fs.write() = Some(fs);
123 }
124}
125
126impl Clone for OverlayNode {
127 fn clone(&self) -> Self {
128 let cloned = Self {
129 name: self.name.clone(),
130 overlay_fs: RwLock::new(None),
131 path: self.path.clone(),
132 file_type: self.file_type.clone(),
133 file_id: self.file_id,
134 };
135
136 // Copy the overlay_fs reference if it exists
137 if let Some(fs) = self.overlay_fs.read().as_ref() {
138 *cloned.overlay_fs.write() = Some(Arc::clone(fs));
139 }
140
141 cloned
142 }
143}
144
145impl VfsNode for OverlayNode {
146 fn id(&self) -> u64 {
147 self.file_id
148 }
149
150 fn filesystem(&self) -> Option<alloc::sync::Weak<dyn FileSystemOperations>> {
151 self.overlay_fs.read().as_ref().map(|fs| Arc::downgrade(fs) as alloc::sync::Weak<dyn FileSystemOperations>)
152 }
153
154 fn metadata(&self) -> Result<FileMetadata, FileSystemError> {
155 if let Some(ref fs) = *self.overlay_fs.read() {
156 fs.get_metadata_for_path(&self.path)
157 } else {
158 Err(FileSystemError::new(FileSystemErrorKind::NotSupported, "No filesystem reference"))
159 }
160 }
161
162 fn as_any(&self) -> &dyn Any {
163 self
164 }
165}
166
167impl OverlayFS {
168 /// Create a new OverlayFS instance with specified layers
169 ///
170 /// # Arguments
171 ///
172 /// * `upper` - Optional upper layer for write operations (mount point and entry)
173 /// * `lower_layers` - Vector of lower layers in priority order (highest priority first)
174 /// * `name` - Name identifier for this overlay filesystem
175 ///
176 /// # Returns
177 ///
178 /// Returns an Arc<OverlayFS> on success, or FileSystemError on failure
179 ///
180 /// # Example
181 ///
182 /// ```rust,no_run
183 /// let overlay = OverlayFS::new(
184 /// Some((upper_mount, upper_entry)), // Read-write upper layer
185 /// vec![
186 /// (layer1_mount, layer1_entry), // Higher priority lower layer
187 /// (layer2_mount, layer2_entry), // Lower priority layer
188 /// ],
189 /// "system_overlay".to_string()
190 /// )?;
191 /// ```
192 pub fn new(
193 upper: Option<(Arc<MountPoint>, Arc<VfsEntry>)>,
194 lower_layers: Vec<(Arc<MountPoint>, Arc<VfsEntry>)>,
195 name: String
196 ) -> Result<Arc<Self>, FileSystemError> {
197 let root_node = OverlayNode::new("/".to_string(), "/".to_string(), FileType::Directory, 1);
198 let overlay = Arc::new(Self {
199 upper,
200 lower_layers,
201 name,
202 root_node: root_node.clone(),
203 });
204 root_node.set_overlay_fs(overlay.clone());
205 Ok(overlay)
206 }
207
208 /// Create a new OverlayFS from VFS paths
209 ///
210 /// This is a convenience method that resolves VFS paths to create an overlay.
211 /// This approach follows the "normal filesystem" pattern - create the overlay
212 /// instance, then mount it like any other filesystem.
213 ///
214 /// # Arguments
215 /// * `vfs_manager` - VFS manager to resolve paths in
216 /// * `upper_path` - Optional path for the upper (writable) layer
217 /// * `lower_paths` - Vector of paths for lower (read-only) layers
218 /// * `name` - Name for the overlay instance
219 ///
220 /// # Example
221 /// ```rust,no_run
222 /// // Create overlay from paths
223 /// let overlay = OverlayFS::new_from_paths(
224 /// &vfs_manager,
225 /// Some("/tmp/overlay"), // Upper layer
226 /// vec!["/system", "/base"], // Lower layers
227 /// "container_overlay"
228 /// )?;
229 ///
230 /// // Mount like any other filesystem
231 /// vfs_manager.mount(overlay, "/merged", 0)?;
232 /// ```
233 pub fn new_from_paths(
234 vfs_manager: &crate::fs::vfs_v2::manager::VfsManager,
235 upper_path: Option<&str>,
236 lower_paths: Vec<&str>,
237 name: &str,
238 ) -> Result<Arc<Self>, FileSystemError> {
239 // Resolve upper layer if provided
240 let upper = if let Some(path) = upper_path {
241 let (entry, mount) = vfs_manager.mount_tree.resolve_path(path)?;
242 Some((mount, entry))
243 } else {
244 None
245 };
246
247 // Resolve lower layers
248 let mut lower_layers = Vec::new();
249 for path in lower_paths {
250 let (entry, mount) = vfs_manager.mount_tree.resolve_path(path)?;
251 lower_layers.push((mount, entry));
252 }
253
254 // Create overlay with resolved layers
255 Self::new(upper, lower_layers, name.to_string())
256 }
257
258 /// Create a new OverlayFS from paths across multiple VFS managers (Cross-VFS)
259 ///
260 /// This method enables true cross-VFS overlays where upper and lower layers
261 /// can come from completely different VFS manager instances. This is perfect
262 /// for container scenarios where the base system is in one VFS and the
263 /// container overlay is in another.
264 ///
265 /// # Arguments
266 /// * `upper_vfs_and_path` - Optional tuple of (vfs_manager, path) for upper layer
267 /// * `lower_vfs_and_paths` - Vector of (vfs_manager, path) tuples for lower layers
268 /// * `name` - Name for the overlay instance
269 ///
270 /// # Example
271 /// ```rust,no_run
272 /// // Cross-VFS overlay: base system from global VFS, overlay in container VFS
273 /// let base_vfs = get_global_vfs_manager();
274 /// let container_vfs = VfsManager::new();
275 ///
276 /// let overlay = OverlayFS::new_from_paths_and_vfs(
277 /// Some((&container_vfs, "/upper")), // Upper in container VFS
278 /// vec![
279 /// (&base_vfs, "/system"), // Base system from global VFS
280 /// (&container_vfs, "/config"), // Config from container VFS
281 /// ],
282 /// "cross_vfs_overlay"
283 /// )?;
284 ///
285 /// // Mount in container VFS like any other filesystem
286 /// container_vfs.mount(overlay, "/merged", 0)?;
287 /// ```
288 pub fn new_from_paths_and_vfs(
289 upper_vfs_and_path: Option<(&Arc<VfsManager>, &str)>,
290 lower_vfs_and_paths: Vec<(&Arc<VfsManager>, &str)>,
291 name: &str,
292 ) -> Result<Arc<Self>, FileSystemError> {
293 // Resolve upper layer from its VFS
294 let upper = if let Some((upper_vfs, upper_path)) = upper_vfs_and_path {
295 let (entry, mount) = upper_vfs.mount_tree.resolve_path(upper_path)?;
296 Some((mount, entry))
297 } else {
298 None
299 };
300
301 // Resolve lower layers from their respective VFS managers
302 let mut lower_layers = Vec::new();
303 for (lower_vfs, lower_path) in lower_vfs_and_paths {
304 let (entry, mount) = lower_vfs.mount_tree.resolve_path(lower_path)?;
305 lower_layers.push((mount, entry));
306 }
307
308 // Create overlay - the internal implementation already supports cross-VFS!
309 Self::new(upper, lower_layers, name.to_string())
310 }
311
312 /// Get FileSystemOperations from MountPoint
313 ///
314 /// Helper method to extract the filesystem operations from a mount point.
315 /// This is used internally to access the underlying filesystem operations
316 /// for each layer.
317 fn fs_from_mount(mount: &Arc<MountPoint>) -> Result<Arc<dyn FileSystemOperations>, FileSystemError> {
318 let filesystem = mount.root.node().filesystem()
319 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::BrokenFileSystem, "Mount point has no filesystem"))?;
320
321 let fs_ops = filesystem.upgrade()
322 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::BrokenFileSystem, "Filesystem operations are no longer available"))?;
323
324 Ok(fs_ops)
325 }
326
327 /// Get metadata for a path by checking layers in priority order
328 ///
329 /// This method implements the core overlay resolution logic:
330 /// 1. Check if the path is hidden by a whiteout file
331 /// 2. Check the upper layer first (if present)
332 /// 3. Fall back to lower layers in priority order
333 ///
334 /// # Arguments
335 ///
336 /// * `path` - The path to resolve within the overlay
337 ///
338 /// # Returns
339 ///
340 /// Returns FileMetadata for the first matching file found, or NotFound error
341 /// if the file doesn't exist in any layer or is hidden by whiteout.
342 fn get_metadata_for_path(&self, path: &str) -> Result<FileMetadata, FileSystemError> {
343 // Check for whiteout first
344 if self.is_whiteout(path) {
345 return Err(FileSystemError::new(FileSystemErrorKind::NotFound, "File is hidden by whiteout"));
346 }
347
348 // Check upper layer first
349 if let Some((ref upper_fs, ref upper_node)) = self.upper {
350 if let Ok(node) = self.resolve_in_layer(upper_fs, upper_node, path) {
351 return node.metadata();
352 }
353 }
354
355 // Check lower layers
356 for (lower_fs, lower_node) in &self.lower_layers {
357 if let Ok(node) = self.resolve_in_layer(lower_fs, lower_node, path) {
358 return node.metadata();
359 }
360 }
361
362 Err(FileSystemError::new(FileSystemErrorKind::NotFound, "File not found in any layer"))
363 }
364
365 /// Resolve a path in a specific layer, starting from the given node
366 ///
367 /// This method performs path resolution within a single overlay layer,
368 /// handling mount boundary crossings correctly. It walks down the path
369 /// components, following mount points as needed.
370 ///
371 /// # Arguments
372 ///
373 /// * `mount` - The mount point to start resolution from
374 /// * `entry` - The VFS entry to start resolution from
375 /// * `path` - The path to resolve (relative to the entry)
376 ///
377 /// # Returns
378 ///
379 /// Returns the resolved VfsNode, or an error if the path cannot be resolved
380 /// in this layer.
381 fn resolve_in_layer(&self, mount: &Arc<MountPoint>, entry: &Arc<VfsEntry>, path: &str) -> Result<Arc<dyn VfsNode>, FileSystemError> {
382 let mut current_mount = mount.clone();
383 let mut current_node = entry.node();
384
385 let parts: Vec<&str> = path.trim_start_matches('/').split('/').filter(|s| !s.is_empty()).collect();
386 if parts.is_empty() {
387 return Ok(current_node);
388 }
389
390 for part in parts {
391 let current_fs = current_node.filesystem()
392 .and_then(|w| w.upgrade())
393 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "Node has no filesystem"))?;
394
395 let next_node = current_fs.lookup(¤t_node, &part.to_string())?;
396
397 let child_mount_opt = current_mount.children.read().get(&next_node.id()).cloned();
398
399 if let Some(child_mount) = child_mount_opt {
400 current_mount = child_mount.clone();
401 current_node = child_mount.root.node();
402 } else {
403 current_node = next_node;
404 }
405 }
406
407 Ok(current_node)
408 }
409
410 /// Check if a file is hidden by a whiteout file
411 ///
412 /// Whiteout files are special files in the upper layer that indicate
413 /// a file from a lower layer should be hidden. They follow the naming
414 /// convention `.wh.filename` where `filename` is the name of the file
415 /// to be hidden.
416 ///
417 /// # Arguments
418 ///
419 /// * `path` - The path to check for whiteout
420 ///
421 /// # Returns
422 ///
423 /// Returns true if the file is hidden by a whiteout, false otherwise.
424 fn is_whiteout(&self, path: &str) -> bool {
425 if let Some((ref upper_fs, ref upper_node)) = self.upper {
426 let whiteout_name = format!(".wh.{}",
427 path.split('/').last().unwrap_or(path));
428 let parent_path = if let Some(pos) = path.rfind('/') {
429 &path[..pos]
430 } else {
431 "/"
432 };
433 let whiteout_path = if parent_path == "/" {
434 format!("/{}", whiteout_name)
435 } else {
436 format!("{}/{}", parent_path, whiteout_name)
437 };
438
439 self.resolve_in_layer(upper_fs, upper_node, &whiteout_path).is_ok()
440 } else {
441 false
442 }
443 }
444
445 /// Get upper layer, error if not available
446 ///
447 /// Returns the upper layer mount point and entry, or an error if the
448 /// overlay filesystem is read-only (no upper layer configured).
449 /// This is used by write operations that require an upper layer.
450 ///
451 /// # Returns
452 ///
453 /// Returns (MountPoint, VfsEntry) tuple for upper layer, or PermissionDenied
454 /// error if no upper layer is available.
455 fn get_upper_layer(&self) -> Result<(Arc<MountPoint>, Arc<VfsEntry>), FileSystemError> {
456 self.upper.as_ref().map(|fs| fs.clone()).ok_or_else(||
457 FileSystemError::new(FileSystemErrorKind::PermissionDenied, "Overlay is read-only (no upper layer)")
458 )
459 }
460
461 /// Create a whiteout file to hide a file from lower layers
462 fn create_whiteout(&self, path: &str) -> Result<(), FileSystemError> {
463 let upper = self.get_upper_layer()?;
464 let whiteout_name = format!(".wh.{}",
465 path.split('/').last().unwrap_or(path));
466 let parent_path = if let Some(pos) = path.rfind('/') {
467 &path[..pos]
468 } else {
469 "/"
470 };
471 let whiteout_path = if parent_path == "/" {
472 format!("/{}", whiteout_name)
473 } else {
474 format!("{}/{}", parent_path, whiteout_name)
475 };
476 // Create parent directories if needed
477 self.ensure_parent_dirs(&whiteout_path)?;
478 let parent_node = self.resolve_in_layer(&upper.0, &upper.1, parent_path)?;
479 let fs = Self::fs_from_mount(&upper.0)?;
480 fs.create(&parent_node, &whiteout_name, FileType::RegularFile, 0o644)
481 .map(|_| ())
482 }
483
484 /// Perform copy-up operation: copy a file from lower layer to upper layer
485 fn copy_up(&self, path: &str) -> Result<(), FileSystemError> {
486 let upper = self.get_upper_layer()?;
487 let upper_fs = Self::fs_from_mount(&upper.0)?;
488 // Check if file already exists in upper layer
489 if self.resolve_in_layer(&upper.0, &upper.1, path).is_ok() {
490 return Ok(());
491 }
492 // Find the file in lower layers
493 for (lower_mount, lower_node) in &self.lower_layers {
494 if let Ok(lower_node) = self.resolve_in_layer(lower_mount, lower_node, path) {
495 let metadata = lower_node.metadata()?;
496 // Ensure parent directories exist in upper layer
497 self.ensure_parent_dirs(path)?;
498 let parent_path = if let Some(pos) = path.rfind('/') {
499 &path[..pos]
500 } else {
501 "/"
502 };
503 let filename = path.split('/').last().unwrap_or(path);
504 let parent_node = self.resolve_in_layer(&upper.0, &upper.1, parent_path)?;
505 match metadata.file_type {
506 FileType::Directory => {
507 upper_fs.create(&parent_node, &filename.to_string(), FileType::Directory, 0o755)?;
508 }
509 FileType::RegularFile => {
510 // Create file and copy content
511 let new_node = upper_fs.create(&parent_node, &filename.to_string(), FileType::RegularFile, 0o644)?;
512 // Copy file content
513 let lower_fs = Self::fs_from_mount(lower_mount)?;
514 if let Ok(source_file) = lower_fs.open(&lower_node, 0) { // Read-only
515 if let Ok(dest_file) = upper_fs.open(&new_node, 1) { // Write-only
516 let _ = dest_file.seek(SeekFrom::Start(0));
517 let mut buffer = [0u8; 4096];
518 loop {
519 match source_file.read(&mut buffer) {
520 Ok(bytes_read) if bytes_read > 0 => {
521 if dest_file.write(&buffer[..bytes_read]).is_err() {
522 break;
523 }
524 }
525 _ => break,
526 }
527 }
528 }
529 }
530 }
531 _ => {
532 // For other file types, create a placeholder
533 upper_fs.create(&parent_node, &filename.to_string(), metadata.file_type, 0o644)?;
534 }
535 }
536 return Ok(());
537 }
538 }
539 Err(FileSystemError::new(FileSystemErrorKind::NotFound, "File not found for copy-up"))
540 }
541
542 /// Ensure parent directories exist in upper layer
543 fn ensure_parent_dirs(&self, path: &str) -> Result<(), FileSystemError> {
544 let upper = self.get_upper_layer()?;
545 let _upper_fs = Self::fs_from_mount(&upper.0)?;
546 let parent_path = if let Some(pos) = path.rfind('/') {
547 &path[..pos]
548 } else {
549 return Ok(());
550 };
551 if parent_path.is_empty() || parent_path == "/" {
552 return Ok(());
553 }
554 // Try to resolve parent - if it fails, create it
555 if self.resolve_in_layer(&upper.0, &upper.1, parent_path).is_err() {
556 self.ensure_parent_dirs(parent_path)?;
557 let grandparent_path = if let Some(pos) = parent_path.rfind('/') {
558 &parent_path[..pos]
559 } else {
560 "/"
561 };
562 let dirname = parent_path.split('/').last().unwrap_or(parent_path);
563 let grandparent_node = self.resolve_in_layer(&upper.0, &upper.1, if grandparent_path.is_empty() { "/" } else { grandparent_path })?;
564 let upper_fs = Self::fs_from_mount(&upper.0)?;
565 upper_fs.create(&grandparent_node, &dirname.to_string(), FileType::Directory, 0o755)?;
566 }
567 Ok(())
568 }
569
570 /// Check if file exists only in lower layers (not in upper)
571 fn file_exists_in_lower_only(&self, path: &str) -> bool {
572 // Check if exists in upper
573 if let Some((ref upper_fs, ref upper_node)) = self.upper {
574 if self.resolve_in_layer(upper_fs, upper_node, path).is_ok() {
575 return false;
576 }
577 }
578
579 // Check if exists in any lower layer
580 for (lower_fs, lower_node) in &self.lower_layers {
581 if self.resolve_in_layer(lower_fs, lower_node, path).is_ok() {
582 return true;
583 }
584 }
585
586 false
587 }
588
589 /// Create an OverlayFS from an option string
590 /// example: option = Some("upper=tmpfs,lower=cpiofs")
591 pub fn create_from_option_string(
592 _option: Option<&str>,
593 upper: Option<(Arc<MountPoint>, Arc<VfsEntry>)>,
594 lower_layers: Vec<(Arc<MountPoint>, Arc<VfsEntry>)>,
595 ) -> Arc<dyn FileSystemOperations> {
596 // Parse options if provided
597 let name = "overlayfs".to_string();
598 OverlayFS::new(upper, lower_layers, name).expect("Failed to create OverlayFS") as Arc<dyn FileSystemOperations>
599 }
600}
601
602impl FileSystemOperations for OverlayFS {
603 fn lookup(&self, parent_node: &Arc<dyn VfsNode>, name: &String) -> Result<Arc<dyn VfsNode>, FileSystemError> {
604 let overlay_parent = parent_node.as_any()
605 .downcast_ref::<OverlayNode>()
606 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "Invalid node type for OverlayFS"))?;
607
608 let child_path = if overlay_parent.path == "/" {
609 format!("/{}", name)
610 } else {
611 format!("{}/{}", overlay_parent.path, name)
612 };
613
614 // Handle special directory entries
615 if name == "." {
616 return Ok(parent_node.clone());
617 }
618 if name == ".." {
619 let parent_path = if let Some(pos) = overlay_parent.path.rfind('/') {
620 if pos == 0 { "/" } else { &overlay_parent.path[..pos] }
621 } else {
622 "/"
623 };
624 let parent_name = parent_path.split('/').last().unwrap_or("/");
625 let node = OverlayNode::new(parent_name.to_string(), parent_path.to_string(), FileType::Directory, 0);
626 if let Some(ref fs) = *overlay_parent.overlay_fs.read() {
627 node.set_overlay_fs(Arc::clone(fs));
628 }
629 return Ok(node);
630 }
631
632 // Check for whiteout
633 if self.is_whiteout(&child_path) {
634 return Err(FileSystemError::new(FileSystemErrorKind::NotFound, "File is hidden by whiteout"));
635 }
636
637 // Try upper layer first
638 if let Some((ref upper_fs, ref upper_node)) = self.upper {
639 if let Ok(_) = self.resolve_in_layer(upper_fs, upper_node, &child_path) {
640 let metadata = self.get_metadata_for_path(&child_path)?;
641 let node = OverlayNode::new(name.clone(), child_path.clone(), metadata.file_type, metadata.file_id);
642 if let Some(ref fs) = *overlay_parent.overlay_fs.read() {
643 node.set_overlay_fs(Arc::clone(fs));
644 }
645 return Ok(node);
646 }
647 }
648
649 // Try lower layers
650 for (lower_fs, lower_node) in &self.lower_layers {
651 if let Ok(_) = self.resolve_in_layer(lower_fs, lower_node, &child_path) {
652 let metadata = self.get_metadata_for_path(&child_path)?;
653 let node = OverlayNode::new(name.clone(), child_path.clone(), metadata.file_type, metadata.file_id);
654 if let Some(ref fs) = *overlay_parent.overlay_fs.read() {
655 node.set_overlay_fs(Arc::clone(fs));
656 }
657 return Ok(node);
658 }
659 }
660
661 Err(FileSystemError::new(FileSystemErrorKind::NotFound, "File not found"))
662 }
663
664 fn open(&self, overlay_node: &Arc<dyn VfsNode>, flags: u32) -> Result<Arc<dyn FileObject>, FileSystemError> {
665 // Downcast to OverlayNode
666 let overlay_node_ref = overlay_node.as_any()
667 .downcast_ref::<OverlayNode>()
668 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "Invalid node type for OverlayFS"))?;
669
670
671 // Get metadata using path instead of node metadata to avoid filesystem reference issues
672 let metadata = self.get_metadata_for_path(&overlay_node_ref.path)?;
673
674 // If this is a directory, return OverlayDirectoryObject
675 if metadata.file_type == FileType::Directory {
676 return Ok(Arc::new(OverlayDirectoryObject::new(
677 Arc::new(self.clone()),
678 overlay_node_ref.path.clone()
679 )));
680 }
681
682 // Check if this is a write operation
683 let is_write_operation = (flags & 0x3) != 0; // O_WRONLY=1, O_RDWR=2
684 // If writing to a file that exists only in lower layer, copy it up first
685 if is_write_operation && self.file_exists_in_lower_only(&overlay_node_ref.path) {
686 self.copy_up(&overlay_node_ref.path)?;
687 }
688 // Try upper layer first
689 if let Some((ref upper_mount, ref upper_node)) = self.upper {
690 if let Ok(upper_node) = self.resolve_in_layer(upper_mount, upper_node, &overlay_node_ref.path) {
691 let fs = Self::fs_from_mount(upper_mount)?;
692 if let Ok(file) = fs.open(&upper_node, flags) {
693 return Ok(file);
694 }
695 }
696 }
697 // For write operations, we need an upper layer
698 if is_write_operation {
699 return Err(FileSystemError::new(FileSystemErrorKind::PermissionDenied, "Cannot write to read-only overlay"));
700 }
701 // Try lower layers for read operations
702 for (lower_mount, lower_node) in &self.lower_layers {
703 if let Ok(lower_node) = self.resolve_in_layer(lower_mount, lower_node, &overlay_node_ref.path) {
704 let fs = Self::fs_from_mount(lower_mount)?;
705 if let Ok(file) = fs.open(&lower_node, flags) {
706 return Ok(file);
707 }
708 }
709 }
710 Err(FileSystemError::new(FileSystemErrorKind::NotFound, "File not found"))
711 }
712
713 fn create(&self, parent_node: &Arc<dyn VfsNode>, name: &String, file_type: FileType, mode: u32) -> Result<Arc<dyn VfsNode>, FileSystemError> {
714 let upper = self.get_upper_layer()?;
715 let upper_fs = Self::fs_from_mount(&upper.0)?;
716 let overlay_parent = parent_node.as_any()
717 .downcast_ref::<OverlayNode>()
718 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "Invalid node type for OverlayFS"))?;
719 let child_path = if overlay_parent.path == "/" {
720 format!("/{}", name)
721 } else {
722 format!("{}/{}", overlay_parent.path, name)
723 };
724 // Ensure parent exists in upper layer (copy-up if needed)
725 if self.file_exists_in_lower_only(&overlay_parent.path) {
726 self.copy_up(&overlay_parent.path)?;
727 }
728 // Remove any existing whiteout
729 if self.is_whiteout(&child_path) {
730 // Remove whiteout file
731 let whiteout_name = format!(".wh.{}", name);
732 let parent_path = if let Some(pos) = overlay_parent.path.rfind('/') {
733 &overlay_parent.path[..pos]
734 } else {
735 "/"
736 };
737 if let Ok(whiteout_parent) = self.resolve_in_layer(&upper.0, &upper.1, parent_path) {
738 if upper_fs.remove(&whiteout_parent, &whiteout_name).is_err() {
739 return Err(FileSystemError::new(FileSystemErrorKind::NotFound, "Whiteout file not found"));
740 }
741 // Successfully removed whiteout file
742 }
743 }
744 let upper_parent = self.resolve_in_layer(&upper.0, &upper.1, &overlay_parent.path)?;
745 let fs = Self::fs_from_mount(&upper.0)?;
746 let new_node = fs.create(&upper_parent, name, file_type, mode)?;
747 // Return overlay node
748 let metadata = new_node.metadata()?;
749 let overlay_node = OverlayNode::new(name.clone(), child_path, metadata.file_type, metadata.file_id);
750 if let Some(ref fs) = *overlay_parent.overlay_fs.read() {
751 overlay_node.set_overlay_fs(Arc::clone(fs));
752 }
753 Ok(overlay_node)
754 }
755
756 fn remove(&self, parent_node: &Arc<dyn VfsNode>, name: &String) -> Result<(), FileSystemError> {
757 let overlay_parent = parent_node.as_any()
758 .downcast_ref::<OverlayNode>()
759 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "Invalid node type for OverlayFS"))?;
760
761 let child_path = if overlay_parent.path == "/" {
762 format!("/{}", name)
763 } else {
764 format!("{}/{}", overlay_parent.path, name)
765 };
766
767 // If file exists in upper layer, remove it
768 if let Some((ref upper_mount, ref upper_entry)) = self.upper {
769 if let Ok(upper_parent) = self.resolve_in_layer(upper_mount, upper_entry, &overlay_parent.path) {
770 let fs = Self::fs_from_mount(upper_mount)?;
771 if fs.remove(&upper_parent, name).is_ok() {
772 // If file also exists in lower layers, create whiteout
773 for (lower_mount, lower_entry) in &self.lower_layers {
774 if self.resolve_in_layer(lower_mount, lower_entry, &child_path).is_ok() {
775 self.create_whiteout(&child_path)?;
776 break;
777 }
778 }
779 return Ok(());
780 }
781 }
782 }
783
784 // If file exists only in lower layers, create whiteout
785 for (lower_mount, lower_node) in &self.lower_layers {
786 if self.resolve_in_layer(lower_mount, lower_node, &child_path).is_ok() {
787 self.create_whiteout(&child_path)?;
788 return Ok(());
789 }
790 }
791
792 Err(FileSystemError::new(FileSystemErrorKind::NotFound, "File not found"))
793 }
794
795 fn root_node(&self) -> Arc<dyn VfsNode> {
796 Arc::clone(&self.root_node) as Arc<dyn VfsNode>
797 }
798
799 fn name(&self) -> &str {
800 &self.name
801 }
802
803 fn is_read_only(&self) -> bool {
804 self.upper.is_none()
805 }
806
807 fn readdir(&self, node: &Arc<dyn VfsNode>) -> Result<Vec<DirectoryEntryInternal>, FileSystemError> {
808 let overlay_node = node.as_any()
809 .downcast_ref::<OverlayNode>()
810 .ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "Invalid node type for OverlayFS"))?;
811
812 let mut entries = Vec::new();
813 let mut seen_names = BTreeSet::new();
814
815 // Get parent directory file_id for ".."
816 let parent_file_id = if overlay_node.path == "/" {
817 // Root directory's parent is itself
818 overlay_node.file_id
819 } else {
820 // Get parent by looking up ".." from current directory
821 match self.lookup(node, &"..".to_string()) {
822 Ok(parent_node) => parent_node.id(),
823 Err(_) => overlay_node.file_id, // Fallback to current if parent can't be resolved
824 }
825 };
826
827 // Add "." and ".." entries
828 entries.push(DirectoryEntryInternal {
829 name: ".".to_string(),
830 file_type: FileType::Directory,
831 file_id: overlay_node.file_id,
832 });
833 entries.push(DirectoryEntryInternal {
834 name: "..".to_string(),
835 file_type: FileType::Directory,
836 file_id: parent_file_id,
837 });
838 seen_names.insert(".".to_string());
839 seen_names.insert("..".to_string());
840
841 // Read from upper layer first
842 if let Some((ref upper_mount, ref upper_node)) = self.upper {
843 if let Ok(upper_node) = self.resolve_in_layer(upper_mount, upper_node, &overlay_node.path) {
844 let fs = upper_node.filesystem().and_then(|w| w.upgrade()).ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "Node has no filesystem"))?;
845 if let Ok(upper_entries) = fs.readdir(&upper_node) {
846 for entry in upper_entries {
847 // Skip whiteout files themselves and . .. entries
848 if entry.name.starts_with(".wh.") || entry.name == "." || entry.name == ".." {
849 continue;
850 }
851 if !seen_names.contains(&entry.name) {
852 seen_names.insert(entry.name.clone());
853 entries.push(entry);
854 }
855 }
856 }
857 }
858 }
859
860 // Read from lower layers (skip entries already seen in upper layers)
861 for (lower_mount, lower_node) in &self.lower_layers {
862 if let Ok(lower_node) = self.resolve_in_layer(lower_mount, lower_node, &overlay_node.path) {
863 let fs = lower_node.filesystem().and_then(|w| w.upgrade()).ok_or_else(|| FileSystemError::new(FileSystemErrorKind::NotSupported, "Node has no filesystem"))?;
864 if let Ok(lower_entries) = fs.readdir(&lower_node) {
865 for entry in lower_entries {
866 // Skip . .. entries
867 if entry.name == "." || entry.name == ".." {
868 continue;
869 }
870 let entry_full_path = if overlay_node.path == "/" {
871 format!("/{}", entry.name)
872 } else {
873 format!("{}/{}", overlay_node.path, entry.name)
874 };
875 // Only add if not already seen and not hidden by whiteout
876 if !seen_names.contains(&entry.name) && !self.is_whiteout(&entry_full_path) {
877 seen_names.insert(entry.name.clone());
878 entries.push(entry);
879 }
880 }
881 }
882 }
883 }
884 entries.sort_by(|a, b| a.file_id.cmp(&b.file_id)); // Sort entries by file_id
885 Ok(entries)
886 }
887}
888
889/// File object for OverlayFS directory operations
890///
891/// OverlayDirectoryObject handles reading directory entries from overlayfs,
892/// merging entries from upper and lower layers while respecting whiteout files.
893pub struct OverlayDirectoryObject {
894 overlay_fs: Arc<OverlayFS>,
895 path: String, // Store path instead of node
896 position: RwLock<u64>,
897}
898
899impl OverlayDirectoryObject {
900 pub fn new(overlay_fs: Arc<OverlayFS>, path: String) -> Self {
901 Self {
902 overlay_fs,
903 path,
904 position: RwLock::new(0),
905 }
906 }
907
908 /// Collect all directory entries from all layers, handling whiteouts and merging
909 fn collect_directory_entries(&self) -> Result<Vec<crate::fs::DirectoryEntryInternal>, FileSystemError> {
910 let mut special_entries = Vec::new();
911 let mut all_entries = Vec::new();
912
913 let mut seen_names = BTreeSet::new();
914
915 // Get current directory node by resolving path components
916 let current_dir_node = {
917 let mut current = self.overlay_fs.root_node();
918 if self.path != "/" {
919 for component in self.path.trim_start_matches('/').split('/') {
920 if !component.is_empty() {
921 current = self.overlay_fs.lookup(¤t, &component.to_string())?;
922 }
923 }
924 }
925 current
926 };
927 let current_file_id = current_dir_node.id();
928
929 // Get parent directory file_id for ".."
930 let parent_file_id = if self.path == "/" {
931 // Root directory's parent is itself
932 current_file_id
933 } else {
934 // Get parent by looking up ".." from current directory
935 match self.overlay_fs.lookup(¤t_dir_node, &"..".to_string()) {
936 Ok(parent_node) => parent_node.id(),
937 Err(_) => current_file_id, // Fallback to current if parent can't be resolved
938 }
939 };
940
941 // Add "." and ".." entries first
942 special_entries.push(crate::fs::DirectoryEntryInternal {
943 name: ".".to_string(),
944 file_type: FileType::Directory,
945 file_id: current_file_id,
946 size: 0,
947 metadata: None,
948 });
949 special_entries.push(crate::fs::DirectoryEntryInternal {
950 name: "..".to_string(),
951 file_type: FileType::Directory,
952 file_id: parent_file_id,
953 size: 0,
954 metadata: None,
955 });
956 seen_names.insert(".".to_string());
957 seen_names.insert("..".to_string());
958
959 // Check upper layer first
960 if let Some((ref upper_mount, ref upper_node)) = self.overlay_fs.upper {
961 if let Ok(upper_dir_node) = self.overlay_fs.resolve_in_layer(upper_mount, upper_node, &self.path) {
962 // Try to get filesystem from mount and read directory
963 if let Ok(upper_fs) = Self::try_fs_from_mount(upper_mount) {
964 if let Ok(upper_entries) = upper_fs.readdir(&upper_dir_node) {
965 for entry in upper_entries {
966 if entry.name == "." || entry.name == ".." {
967 continue; // Skip, already added
968 }
969
970 // Check for whiteout
971 if entry.name.starts_with(".wh.") {
972 // Hide the corresponding file from lower layers
973 let hidden_name = &entry.name[4..]; // Remove ".wh." prefix
974 seen_names.insert(hidden_name.to_string());
975 continue; // Don't add the whiteout file itself
976 }
977
978 if !seen_names.contains(&entry.name) {
979 all_entries.push(crate::fs::DirectoryEntryInternal {
980 name: entry.name.clone(),
981 file_type: entry.file_type,
982 file_id: entry.file_id,
983 size: 0,
984 metadata: None,
985 });
986 seen_names.insert(entry.name);
987 }
988 }
989 }
990 }
991 }
992 }
993
994 // Check lower layers
995 for (lower_mount, lower_node) in &self.overlay_fs.lower_layers {
996 if let Ok(lower_dir_node) = self.overlay_fs.resolve_in_layer(lower_mount, lower_node, &self.path) {
997 if let Ok(lower_fs) = Self::try_fs_from_mount(lower_mount) {
998 if let Ok(lower_entries) = lower_fs.readdir(&lower_dir_node) {
999 for entry in lower_entries {
1000 if entry.name == "." || entry.name == ".." {
1001 continue; // Skip, already added
1002 }
1003
1004 // Only add if not already seen (upper layer takes precedence)
1005 if !seen_names.contains(&entry.name) {
1006 all_entries.push(crate::fs::DirectoryEntryInternal {
1007 name: entry.name.clone(),
1008 file_type: entry.file_type,
1009 file_id: entry.file_id,
1010 size: 0,
1011 metadata: None,
1012 });
1013 seen_names.insert(entry.name);
1014 }
1015 }
1016 }
1017 }
1018 }
1019 }
1020
1021 // Sort entries by file_id to maintain consistent order
1022 all_entries.sort_by(|a, b| a.file_id.cmp(&b.file_id));
1023 special_entries.extend(all_entries);
1024 Ok(special_entries)
1025 }
1026
1027 /// Safe version of fs_from_mount that returns Result instead of panicking
1028 fn try_fs_from_mount(mount: &Arc<MountPoint>) -> Result<Arc<dyn FileSystemOperations>, FileSystemError> {
1029 let filesystem = mount.root.node().filesystem()
1030 .ok_or_else(|| {
1031 FileSystemError::new(FileSystemErrorKind::BrokenFileSystem, "Mount point has no filesystem")
1032 })?;
1033
1034 let fs_ops = filesystem.upgrade()
1035 .ok_or_else(|| {
1036 FileSystemError::new(FileSystemErrorKind::BrokenFileSystem, "Filesystem operations are no longer available")
1037 })?;
1038
1039 Ok(fs_ops)
1040 }
1041}
1042
1043impl StreamOps for OverlayDirectoryObject {
1044 fn read(&self, buffer: &mut [u8]) -> Result<usize, StreamError> {
1045 // Collect all directory entries from all layers (simplified version)
1046 let all_entries = self.collect_directory_entries().map_err(StreamError::from)?;
1047
1048 let position = *self.position.read() as usize;
1049
1050 if position >= all_entries.len() {
1051 return Ok(0); // EOF
1052 }
1053
1054 // Get current entry
1055 let fs_entry = &all_entries[position];
1056
1057 // Convert to binary format
1058 let dir_entry = crate::fs::DirectoryEntry::from_internal(fs_entry);
1059
1060 // Calculate actual entry size
1061 let entry_size = dir_entry.entry_size();
1062
1063 // Check buffer size
1064 if buffer.len() < entry_size {
1065 return Err(StreamError::InvalidArgument); // Buffer too small
1066 }
1067
1068 // Treat struct as byte array
1069 let entry_bytes = unsafe {
1070 core::slice::from_raw_parts(
1071 &dir_entry as *const _ as *const u8,
1072 entry_size
1073 )
1074 };
1075
1076 // Copy to buffer
1077 buffer[..entry_size].copy_from_slice(entry_bytes);
1078
1079 // Move to next entry
1080 *self.position.write() += 1;
1081
1082 Ok(entry_size)
1083 }
1084
1085 fn write(&self, _buffer: &[u8]) -> Result<usize, StreamError> {
1086 // Directories cannot be written to directly
1087 Err(StreamError::from(FileSystemError::new(
1088 FileSystemErrorKind::IsADirectory,
1089 "Cannot write to directory"
1090 )))
1091 }
1092}
1093
1094impl FileObject for OverlayDirectoryObject {
1095 fn seek(&self, _whence: crate::fs::SeekFrom) -> Result<u64, StreamError> {
1096 // Seeking in directories not supported for now
1097 Err(StreamError::NotSupported)
1098 }
1099
1100 fn metadata(&self) -> Result<crate::fs::FileMetadata, StreamError> {
1101 // Get metadata for the directory path
1102 self.overlay_fs.get_metadata_for_path(&self.path).map_err(StreamError::from)
1103 }
1104
1105 fn truncate(&self, _size: u64) -> Result<(), StreamError> {
1106 Err(StreamError::from(FileSystemError::new(
1107 FileSystemErrorKind::IsADirectory,
1108 "Cannot truncate directory"
1109 )))
1110 }
1111}
1112
1113/// Driver for creating OverlayFS instances
1114///
1115/// This driver implements the FileSystemDriver trait to allow OverlayFS
1116/// to be created through the standard filesystem driver infrastructure.
1117/// Currently, OverlayFS instances are typically created programmatically
1118/// rather than through driver parameters due to the complexity of specifying
1119/// multiple layer mount points.
1120pub struct OverlayFSDriver;
1121
1122impl FileSystemDriver for OverlayFSDriver {
1123 fn create_from_memory(&self, _memory_area: &MemoryArea) -> Result<Arc<dyn FileSystemOperations>, FileSystemError> {
1124 Ok(OverlayFS::create_from_option_string(None, None, Vec::new()))
1125 }
1126
1127 fn create_from_params(&self, _params: &dyn crate::fs::params::FileSystemParams) -> Result<Arc<dyn FileSystemOperations>, FileSystemError> {
1128 Ok(OverlayFS::create_from_option_string(None, None, Vec::new()))
1129 }
1130
1131 fn name(&self) -> &'static str {
1132 "overlayfs"
1133 }
1134
1135 fn filesystem_type(&self) -> crate::fs::FileSystemType {
1136 crate::fs::FileSystemType::Virtual
1137 }
1138}
1139
1140/// Register the OverlayFS driver with the filesystem driver manager
1141///
1142/// This function is called during kernel initialization to make the OverlayFS
1143/// driver available for use. It's automatically invoked by the driver_initcall
1144/// mechanism.
1145fn register_driver() {
1146 let fs_driver_manager = get_fs_driver_manager();
1147 fs_driver_manager.register_driver(Box::new(OverlayFSDriver));
1148}
1149
1150driver_initcall!(register_driver);
1151
1152// ========================================================================
1153// Implementation Notes and Usage Examples
1154// ========================================================================
1155//
1156// ## Creating an Overlay
1157//
1158// ```rust,no_run
1159// // Mount base filesystem
1160// let base_fs = create_base_filesystem()?;
1161// vfs.mount(base_fs, "/base", 0)?;
1162//
1163// // Mount overlay filesystem
1164// let overlay_fs = create_overlay_filesystem()?;
1165// vfs.mount(overlay_fs, "/overlay", 0)?;
1166//
1167// // Create overlay combining them
1168// let (base_mount, base_entry) = vfs.resolve_path("/base")?;
1169// let (overlay_mount, overlay_entry) = vfs.resolve_path("/overlay")?;
1170//
1171// let overlay = OverlayFS::new(
1172// Some((overlay_mount, overlay_entry)), // Upper (writable)
1173// vec![(base_mount, base_entry)], // Lower (read-only)
1174// "system_overlay".to_string()
1175// )?;
1176//
1177// vfs.mount(overlay, "/merged", 0)?;
1178// ```
1179//
1180// ## Key Behaviors
1181//
1182// - **Read operations**: Check upper first, then lower layers in order
1183// - **Write operations**: Always go to upper layer (copy-up if needed)
1184// - **Delete operations**: Create whiteout files in upper layer
1185// - **Directory listing**: Merge all layers, respecting whiteouts
1186//
1187// ## Whiteout Files
1188//
1189// To hide `/merged/file.txt`, create `/overlay/.wh.file.txt` in upper layer.
1190// This follows the standard overlay filesystem whiteout convention.
1191//
1192
1193// ========================================================================
1194// Usage Examples - Normal Filesystem Approach
1195// ========================================================================
1196//
1197// ## Example 1: Basic Overlay (Same VFS)
1198//
1199// ```rust,no_run
1200// // Setup base filesystem
1201// let base_fs = crate::fs::vfs_v2::drivers::tmpfs::TmpFS::new(0);
1202// vfs.mount(base_fs, "/base", 0)?;
1203//
1204// // Setup upper filesystem for writes
1205// let upper_fs = crate::fs::vfs_v2::drivers::tmpfs::TmpFS::new(0);
1206// vfs.mount(upper_fs, "/upper", 0)?;
1207//
1208// // Create overlay combining them - NORMAL FILESYSTEM APPROACH
1209// let overlay = OverlayFS::new_from_paths(
1210// &vfs,
1211// Some("/upper"), // Upper layer (writable)
1212// vec!["/base"], // Lower layers (read-only)
1213// "my_overlay"
1214// )?;
1215//
1216// // Mount like any other filesystem!
1217// vfs.mount(overlay, "/merged", 0)?;
1218// ```
1219//
1220// ## Example 2: Multi-layer Overlay
1221//
1222// ```rust,no_run
1223// // Mount multiple base layers
1224// vfs.mount(system_fs, "/system", 0)?; // System files
1225// vfs.mount(config_fs, "/config", 0)?; // Configuration
1226// vfs.mount(overlay_fs, "/overlay", 0)?; // Overlay workspace
1227//
1228// // Create multi-layer overlay
1229// let overlay = OverlayFS::new_from_paths(
1230// &vfs,
1231// Some("/overlay"), // Writable layer
1232// vec!["/config", "/system"], // Read-only layers (priority order)
1233// "container_overlay"
1234// )?;
1235//
1236// // Mount normally
1237// vfs.mount(overlay, "/merged", 0)?;
1238// ```
1239//
1240// ## Benefits of Normal Filesystem Approach
1241//
1242// - **Consistent API**: Uses standard mount()/unmount() operations
1243// - **No special VFS methods**: No need for create_and_mount_overlay() etc.
1244// - **Flexible**: Can be combined with other filesystem operations
1245// - **Maintainable**: Less complexity in VfsManager
1246// - **Testable**: Easy to unit test overlay creation independently
1247//
1248
1249// ========================================================================
1250// Usage Examples - Including Cross-VFS Support
1251// ========================================================================
1252//
1253// ## Example 1: Same-VFS Overlay
1254//
1255// ```rust,no_run
1256// // Setup base filesystem
1257// let base_fs = crate::fs::vfs_v2::drivers::tmpfs::TmpFS::new(0);
1258// vfs.mount(base_fs, "/base", 0)?;
1259//
1260// // Setup upper filesystem for writes
1261// let upper_fs = crate::fs::vfs_v2::drivers::tmpfs::TmpFS::new(0);
1262// vfs.mount(upper_fs, "/upper", 0)?;
1263//
1264// // Create overlay combining them - NORMAL FILESYSTEM APPROACH
1265// let overlay = OverlayFS::new_from_paths(
1266// &vfs,
1267// Some("/upper"), // Upper layer (writable)
1268// vec!["/base"], // Lower layers (read-only)
1269// "my_overlay"
1270// )?;
1271//
1272// // Mount like any other filesystem!
1273// vfs.mount(overlay, "/merged", 0)?;
1274// ```
1275//
1276// ## Example 2: Cross-VFS Overlay (Container Scenario)
1277//
1278// ```rust,no_run
1279// // Get global VFS with base system
1280// let base_vfs = get_global_vfs_manager();
1281//
1282// // Create container VFS
1283// let container_vfs = VfsManager::new();
1284//
1285// // Mount container-specific filesystems
1286// let overlay_fs = TmpFS::new(0);
1287// container_vfs.mount(overlay_fs, "/overlay", 0)?;
1288//
1289// let config_fs = TmpFS::new(0);
1290// container_vfs.mount(config_fs, "/config", 0)?;
1291//
1292// // Create cross-VFS overlay: base system from global VFS, overlay from container VFS
1293// let overlay = OverlayFS::new_from_paths_and_vfs(
1294// Some((&container_vfs, "/overlay")), // Upper in container VFS (writable)
1295// vec![
1296// (&container_vfs, "/config"), // Container config (higher priority)
1297// (&base_vfs, "/system"), // Global system files (lower priority)
1298// ],
1299// "container_overlay"
1300// )?;
1301//
1302// // Mount in container VFS - completely seamless!
1303// container_vfs.mount(overlay, "/", 0)?;
1304// ```
1305//
1306// ## Example 3: Multi-layer Same-VFS Overlay
1307//
1308// ```rust,no_run
1309// // Mount multiple base layers
1310// vfs.mount(system_fs, "/system", 0)?; // System files
1311// vfs.mount(config_fs, "/config", 0)?; // Configuration
1312// vfs.mount(overlay_fs, "/overlay", 0)?; // Overlay workspace
1313//
1314// // Create multi-layer overlay
1315// let overlay = OverlayFS::new_from_paths(
1316// &vfs,
1317// Some("/overlay"), // Writable layer
1318// vec!["/config", "/system"], // Read-only layers (priority order)
1319// "container_overlay"
1320// )?;
1321//
1322// // Mount normally
1323// vfs.mount(overlay, "/merged", 0)?;
1324// ```
1325//
1326// ## Benefits of This Approach
1327//
1328// - **Cross-VFS Support**: Layers can come from different VFS managers
1329// - **Consistent API**: Uses standard mount()/unmount() operations
1330// - **No special VFS methods**: No need for create_and_mount_overlay() etc.
1331// - **Flexible**: Can be combined with other filesystem operations
1332// - **Container-friendly**: Perfect for namespace isolation
1333// - **Maintainable**: Less complexity in VfsManager
1334// - **Testable**: Easy to unit test overlay creation independently
1335//
1336
1337#[cfg(test)]
1338mod tests;