kernel/fs/drivers/cpio/
mod.rs

1//! CPIO (Copy In/Out) filesystem implementation for the kernel.
2//!
3//! This module provides a read-only implementation of the CPIO archive format,
4//! typically used for the initial ramdisk (initramfs). The CPIO format is a
5//! simple archive format that stores files sequentially with headers containing
6//! metadata.
7//!
8//! # Features
9//! 
10//! - Parsing of CPIO archives in the "new ASCII" format (magic "070701")
11//! - Read-only access to files stored in the archive
12//! - Directory traversal and metadata retrieval
13//! 
14//! # Limitations
15//!
16//! - Write operations are not supported as the filesystem is read-only
17//! - Block operations are not implemented since CPIO is not a block-based filesystem
18//!
19//! # Components
20//!
21//! - `CpiofsEntry`: Represents an individual file or directory within the archive
22//! - `Cpiofs`: The main filesystem implementation handling mounting and file operations
23//! - `CpiofsFileHandle`: Handles file read operations and seeking
24//! - `CpiofsDriver`: Driver that creates CPIO filesystems from memory areas
25//!
26//! # Usage
27//!
28//! The CPIO filesystem is typically created from a memory region containing the
29//! archive data, such as an initramfs loaded by the bootloader:
30//!
31//! ```rust
32//! let cpio_driver = CpiofsDriver;
33//! let fs = cpio_driver.create_from_memory(&initramfs_memory_area)?;
34//! vfs_manager.register_filesystem(fs)?;
35//! ```
36use alloc::{boxed::Box, format, string::{String, ToString}, sync::Arc, vec::Vec};
37use spin::{Mutex, RwLock};
38
39use crate::{driver_initcall, fs::{
40    get_fs_driver_manager, Directory, DirectoryEntry, FileHandle, FileMetadata, FileOperations, FileSystem, FileSystemDriver, FileSystemError, FileSystemErrorKind, FileSystemType, FileType, Result, VirtualFileSystem
41}, vm::vmem::MemoryArea};
42
43/// Structure representing an Initramfs entry
44#[derive(Debug, Clone)]
45pub struct CpiofsEntry {
46    pub name: String,
47    pub file_type: FileType,
48    pub size: usize,
49    pub modified_time: u64,
50    pub data: Option<Vec<u8>>, // File data (None for directories)
51}
52
53/// Structure representing the entire Initramfs
54pub struct Cpiofs {
55    name: &'static str,
56    entries: Mutex<Vec<CpiofsEntry>>, // List of entries
57    mounted: bool,
58    mount_point: String,
59}
60
61impl Cpiofs {
62    /// Create a new Initramfs
63    /// 
64    /// # Arguments
65    /// 
66    /// * `name` - The name of the filesystem
67    /// * `cpio_data` - The CPIO data to parse
68    /// 
69    /// # Returns
70    /// 
71    /// A result containing the created Cpiofs instance or an error
72    /// 
73    pub fn new(name: &'static str, cpio_data: &[u8]) -> Result<Self> {
74        let entries = Self::parse_cpio(cpio_data)?;
75        Ok(Self {
76            name,
77            entries: Mutex::new(entries),
78            mounted: false,
79            mount_point: String::new(),
80        })
81    }
82
83    /// Parse CPIO data to generate entries
84    /// 
85    /// # Arguments
86    /// 
87    /// * `cpio_data` - The CPIO data to parse
88    /// 
89    /// # Returns
90    /// 
91    /// A result containing a vector of CpiofsEntry or an error
92    /// 
93    fn parse_cpio(cpio_data: &[u8]) -> Result<Vec<CpiofsEntry>> {
94        let mut entries = Vec::new();
95        let mut offset = 0;
96
97        while offset < cpio_data.len() {
98            if offset + 110 > cpio_data.len() {
99                break; // Exit if the header is incomplete
100            }
101
102            // Parse the header
103            let header = &cpio_data[offset..offset + 110];
104            let name_size = usize::from_str_radix(
105                core::str::from_utf8(&header[94..102]).unwrap_or("0"),
106                16,
107            )
108            .unwrap_or(0);
109
110            // Check magic number
111            if &header[0..6] != b"070701" {
112                return Err(FileSystemError {
113                    kind: FileSystemErrorKind::InvalidData,
114                    message: format!("Invalid CPIO magic number {:#x} at offset {}", 
115                        u32::from_str_radix(core::str::from_utf8(&header[0..6]).unwrap_or("0"), 16).unwrap_or(0),
116                        offset,
117                    ),
118                });
119            }
120
121            // Get the file name
122            let name_offset = offset + 110;
123            let name_end = name_offset + name_size - 1; // Exclude `\0`
124            if name_end > cpio_data.len() {
125                break; // Exit if the file name is incomplete
126            }
127
128            let name = core::str::from_utf8(&cpio_data[name_offset..name_end])
129                .unwrap_or("")
130                .to_string();
131
132            // Exit if the end marker is found
133            if name == "TRAILER!!!" {
134                break;
135            }
136
137            // Get the mode
138            let mode = usize::from_str_radix(
139                core::str::from_utf8(&header[14..22]).unwrap_or("0"),
140                16,
141            ).unwrap_or(0);
142
143            let file_type = if mode & 0o170000 == 0o040000 {
144                FileType::Directory
145            } else if mode & 0o170000 == 0o100000 {
146                FileType::RegularFile
147            } else {
148                FileType::Unknown
149            };
150
151            let modified_time = u64::from_str_radix(
152                core::str::from_utf8(&header[46..54]).unwrap_or("0"),
153                16,
154            ).unwrap_or(0);
155
156            // Get the size
157            let file_size = usize::from_str_radix(
158                core::str::from_utf8(&header[54..62]).unwrap_or("0"),
159                16,
160            )
161            .unwrap_or(0);
162
163            // Get the file data
164            let data_offset = (name_offset + name_size + 3) & !3; // Align to 4-byte boundary
165            let data_end = data_offset + file_size;
166            let data = if file_size > 0 && data_end <= cpio_data.len() {
167                Some(cpio_data[data_offset..data_end].to_vec())
168            } else {
169                None
170            };
171
172            entries.push(CpiofsEntry {
173                name,
174                file_type,
175                size: file_size,
176                modified_time,
177                data,
178            });
179
180            // Move to the next entry
181            offset = (data_end + 3) & !3; // Align to 4-byte boundary
182        }
183
184        Ok(entries)
185    }
186
187    fn normalize_path(&self, path: &str) -> String {
188        // Convert absolute paths to relative paths considering the mount point
189        if path.starts_with('/') {
190            path.trim_start_matches('/').to_string()
191        } else {
192            path.to_string()
193        }
194    }
195    
196}
197
198impl FileSystem for Cpiofs {
199    fn mount(&mut self, mount_point: &str) -> Result<()> {
200        if self.mounted {
201            return Err(FileSystemError {
202                kind: FileSystemErrorKind::AlreadyExists,
203                message: "File system already mounted".to_string(),
204            });
205        }
206        self.mounted = true;
207        self.mount_point = mount_point.to_string();
208        Ok(())
209    }
210
211    fn unmount(&mut self) -> Result<()> {
212        if !self.mounted {
213            return Err(FileSystemError {
214                kind: FileSystemErrorKind::NotFound,
215                message: "File system not mounted".to_string(),
216            });
217        }
218        self.mounted = false;
219        self.mount_point = String::new();
220        Ok(())
221    }
222
223    fn name(&self) -> &str {
224        self.name
225    }
226}
227
228impl FileOperations for Cpiofs {
229    fn open(&self, path: &str, _flags: u32) -> Result<Arc<dyn crate::fs::FileHandle>> {
230        let path = self.normalize_path(path);
231        let entries = self.entries.lock();
232        if let Some(entry) = entries.iter().find(|e| e.name == path) {
233            Ok(Arc::new(CpiofsFileHandle {
234                content: RwLock::new(entry.data.clone().unwrap_or_default()),
235                position: RwLock::new(0),
236            }))
237        } else {
238            Err(FileSystemError {
239                kind: FileSystemErrorKind::NotFound,
240                message: format!("File not found: {}", path),
241            })
242        }
243    }
244
245    fn read_dir(&self, _path: &str) -> Result<Vec<DirectoryEntry>> {
246        let path = self.normalize_path(_path);
247        let entries = self.entries.lock();
248    
249        // Filter entries in the specified directory
250        let filtered_entries: Vec<DirectoryEntry> = entries
251            .iter()
252            .filter_map(|e| {
253                // Determine entries within the directory
254                let parent_path = e.name.rfind('/').map_or("", |idx| &e.name[..idx]);
255                if parent_path == path {
256                    // Extract only the file name
257                    let file_name = e.name.rfind('/').map_or(&e.name[..], |idx| &e.name[idx + 1..]);
258                    Some(DirectoryEntry {
259                        name: file_name.to_string(),
260                        file_type: e.file_type,
261                        size: e.size,
262                        metadata: None,
263                    })
264                } else {
265                    None
266                }
267            })
268            .collect();
269    
270        if filtered_entries.is_empty() && path != "" && path != "/" {
271            // Return an error if the specified directory does not exist
272            return Err(FileSystemError {
273                kind: FileSystemErrorKind::NotFound,
274                message: format!("Directory not found: {}", _path),
275            });
276        }
277    
278        Ok(filtered_entries)
279    }
280
281    fn create_file(&self, _path: &str, _file_type: FileType) -> Result<()> {
282        Err(FileSystemError {
283            kind: FileSystemErrorKind::ReadOnly,
284            message: "Initramfs is read-only".to_string(),
285        })
286    }
287
288    fn create_dir(&self, _path: &str) -> Result<()> {
289        Err(FileSystemError {
290            kind: FileSystemErrorKind::ReadOnly,
291            message: "Initramfs is read-only".to_string(),
292        })
293    }
294
295    fn remove(&self, _path: &str) -> Result<()> {
296        Err(FileSystemError {
297            kind: FileSystemErrorKind::ReadOnly,
298            message: "Initramfs is read-only".to_string(),
299        })
300    }
301
302    fn metadata(&self, path: &str) -> Result<FileMetadata> {
303        let path = self.normalize_path(path);
304        let entries = self.entries.lock();
305        if let Some(entry) = entries.iter().find(|e| e.name == path) {
306            Ok(FileMetadata {
307                file_type: entry.file_type,
308                size: entry.size,
309                permissions: crate::fs::FilePermission {
310                    read: true,
311                    write: false,
312                    execute: false,
313                },
314                created_time: 0,
315                modified_time: entry.modified_time,
316                accessed_time: 0,
317            })
318        } else {
319            Err(FileSystemError {
320                kind: FileSystemErrorKind::NotFound,
321                message: format!("File not found: {}", path),
322            })
323        }
324    }
325    
326    fn root_dir(&self) -> Result<crate::fs::Directory> {
327        Ok(Directory::open(self.mount_point.clone() + "/"))
328    }
329}
330
331struct CpiofsFileHandle {
332    content: RwLock<Vec<u8>>,
333    position: RwLock<usize>,
334}
335
336impl FileHandle for CpiofsFileHandle {
337    fn read(&self, buffer: &mut [u8]) -> Result<usize> {
338        let content = self.content.read();
339        let mut position = self.position.write();
340        let available = content.len() - *position;
341        let to_read = buffer.len().min(available);
342        buffer[..to_read].copy_from_slice(&content[*position..*position + to_read]);
343        *position += to_read;
344        Ok(to_read)
345    }
346
347    fn write(&self, _buffer: &[u8]) -> Result<usize> {
348        Err(FileSystemError {
349            kind: FileSystemErrorKind::ReadOnly,
350            message: "Initramfs is read-only".to_string(),
351        })
352    }
353
354    fn seek(&self, whence: crate::fs::SeekFrom) -> Result<u64> {
355        let mut position = self.position.write();
356        let content = self.content.read();
357        let new_pos = match whence {
358            crate::fs::SeekFrom::Start(offset) => offset as usize,
359            crate::fs::SeekFrom::Current(offset) => {
360                if offset < 0 && *position < offset.abs() as usize {
361                    0
362                } else if offset < 0 {
363                    *position - offset.abs() as usize
364                } else {
365                    *position + offset as usize
366                }
367            },
368            crate::fs::SeekFrom::End(offset) => {
369                let end = content.len();
370                if offset < 0 && end < offset.abs() as usize {
371                    0
372                } else if offset < 0 {
373                    end - offset.abs() as usize
374                } else {
375                    end + offset as usize
376                }
377            },
378        };
379        
380        *position = new_pos;
381        Ok(*position as u64)
382    }
383
384    fn release(&self) -> Result<()> {
385        Ok(())
386    }
387
388    fn metadata(&self) -> Result<FileMetadata> {
389        let content = self.content.read();
390        Ok(FileMetadata {
391            file_type: FileType::RegularFile,
392            size: content.len(),
393            permissions: crate::fs::FilePermission {
394                read: true,
395                write: false,
396                execute: false,
397            },
398            created_time: 0,
399            modified_time: 0,
400            accessed_time: 0,
401        })
402    }
403}
404
405/// Driver for CPIO-format filesystems (initramfs)
406/// 
407/// This driver creates filesystems from memory areas only.
408pub struct CpiofsDriver;
409
410impl FileSystemDriver for CpiofsDriver {
411    fn name(&self) -> &'static str {
412        "cpiofs"
413    }
414    
415    /// This filesystem only supports creation from memory
416    fn filesystem_type(&self) -> FileSystemType {
417        FileSystemType::Memory
418    }
419    
420    /// Create a file system from memory area
421    /// 
422    /// # Arguments
423    /// 
424    /// * `memory_area` - A reference to the memory area containing the CPIO filesystem data
425    /// 
426    /// # Returns
427    /// 
428    /// A result containing a boxed CPIO filesystem or an error
429    /// 
430    fn create_from_memory(&self, memory_area: &MemoryArea) -> Result<Box<dyn VirtualFileSystem>> {
431        let data = unsafe { memory_area.as_slice() };
432        // Create the Cpiofs from the memory data
433        match Cpiofs::new("cpiofs", data) {
434            Ok(cpio_fs) => Ok(Box::new(cpio_fs)),
435            Err(err) => Err(FileSystemError {
436                kind: FileSystemErrorKind::InvalidData,
437                message: format!("Failed to create CPIO filesystem from memory: {}", err.message),
438            })
439        }
440    }
441    
442    fn create_with_params(&self, params: &dyn crate::fs::params::FileSystemParams) -> Result<Box<dyn VirtualFileSystem>> {
443        use crate::fs::params::*;
444        
445        // Try to downcast to CpioFSParams
446        if let Some(_cpio_params) = params.as_any().downcast_ref::<CpioFSParams>() {
447            // CPIO filesystem requires memory area for creation, so we cannot create from parameters alone
448            return Err(FileSystemError {
449                kind: FileSystemErrorKind::NotSupported,
450                message: "CPIO filesystem requires memory area for creation. Use create_from_memory instead.".to_string(),
451            });
452        }
453        
454        // Try to downcast to BasicFSParams for compatibility
455        if let Some(_basic_params) = params.as_any().downcast_ref::<BasicFSParams>() {
456            return Err(FileSystemError {
457                kind: FileSystemErrorKind::NotSupported,
458                message: "CPIO filesystem requires memory area for creation. Use create_from_memory instead.".to_string(),
459            });
460        }
461        
462        // If all downcasts fail, return error
463        Err(FileSystemError {
464            kind: FileSystemErrorKind::NotSupported,
465            message: "CPIO filesystem requires CpioFSParams and memory area for creation".to_string(),
466        })
467    }
468}
469
470fn register_driver() {
471    let fs_driver_manager = get_fs_driver_manager();
472    fs_driver_manager.register_driver(Box::new(CpiofsDriver));
473}
474
475driver_initcall!(register_driver);
476
477#[cfg(test)]
478mod tests;