kernel/fs/vfs_v2/drivers/
cpiofs.rs

1//! CpioFS v2 - CPIO filesystem implementation for initramfs
2//!
3//! This is a simplified read-only filesystem for handling CPIO archives
4//! used as initramfs. It implements the VFS v2 architecture.
5
6use alloc::{
7    boxed::Box, collections::BTreeMap, format, string::{String, ToString}, sync::Arc, vec::Vec
8};
9use spin::RwLock;
10use core::any::Any;
11use alloc::sync::Weak;
12
13use crate::{driver_initcall, fs::{core::DirectoryEntryInternal, get_fs_driver_manager, vfs_v2::core::{
14    FileSystemOperations, VfsNode
15}, FileSystemDriver, FileSystemType}, vm::vmem::MemoryArea};
16use crate::fs::{
17    FileSystemError, FileSystemErrorKind, FileMetadata, FileObject, FileType, FilePermission
18};
19use crate::object::capability::{StreamOps, StreamError};
20
21/// CPIO filesystem implementation
22pub struct CpioFS {
23    /// Root node of the filesystem
24    root_node: Arc<CpioNode>,
25    
26    /// Filesystem name
27    name: String,
28}
29
30/// A single node in the CPIO filesystem
31pub struct CpioNode {
32    /// File name
33    name: String,
34    
35    /// File type
36    file_type: FileType,
37    
38    /// File content (for regular files)
39    content: Vec<u8>,
40    
41    /// Child nodes (for directories)
42    children: RwLock<BTreeMap<String, Arc<CpioNode>>>,
43    
44    /// Reference to filesystem
45    filesystem: RwLock<Option<Arc<CpioFS>>>,
46    
47    /// File ID
48    file_id: usize,
49
50    /// Parent node (weak reference)
51    parent: RwLock<Option<Weak<CpioNode>>>,
52}
53
54impl CpioNode {
55    /// Create a new CPIO node
56    pub fn new(name: String, file_type: FileType, content: Vec<u8>, file_id: usize) -> Arc<Self> {
57        Arc::new(Self {
58            name,
59            file_type,
60            content,
61            children: RwLock::new(BTreeMap::new()),
62            filesystem: RwLock::new(None),
63            file_id,
64            parent: RwLock::new(None),
65        })
66    }
67    
68    /// Add a child to this directory node
69    pub fn add_child(self: &Arc<Self>, name: String, child: Arc<CpioNode>) -> Result<(), FileSystemError> {
70        if self.file_type != FileType::Directory {
71            return Err(FileSystemError::new(
72                FileSystemErrorKind::NotADirectory,
73                "Cannot add child to non-directory node"
74            ));
75        }
76        // Set parent pointer
77        *child.parent.write() = Some(Arc::downgrade(self));
78        let mut children = self.children.write();
79        children.insert(name, child);
80        Ok(())
81    }
82    
83    /// Get a child by name
84    pub fn get_child(&self, name: &str) -> Option<Arc<CpioNode>> {
85        let children = self.children.read();
86        children.get(name).cloned()
87    }
88
89    pub fn parent_file_id(&self) -> Option<u64> {
90        self.parent.read().as_ref()?.upgrade().map(|p| p.file_id as u64)
91    }
92
93    /// Helper to convert from Arc<dyn VfsNode> to Arc<CpioNode>
94    pub fn from_vfsnode_arc(node: &Arc<dyn VfsNode>) -> Option<Arc<CpioNode>> {
95        match Arc::downcast::<CpioNode>(node.clone()) {
96            Ok(cpio_node) => Some(cpio_node),
97            Err(_) => None,
98        }
99    }
100}
101
102impl VfsNode for CpioNode {
103    fn id(&self) -> u64 {
104        self.file_id as u64
105    }
106
107    fn filesystem(&self) -> Option<Weak<dyn FileSystemOperations>> {
108        let fs_guard = self.filesystem.read();
109        fs_guard.as_ref().map(|fs| Arc::downgrade(fs) as Weak<dyn FileSystemOperations>)
110    }
111    
112    fn metadata(&self) -> Result<FileMetadata, FileSystemError> {
113        Ok(FileMetadata {
114            file_type: self.file_type.clone(),
115            size: self.content.len(),
116            created_time: 0,
117            modified_time: 0,
118            accessed_time: 0,
119            permissions: FilePermission {
120                read: true,
121                write: false,
122                execute: false,
123            },
124            file_id: self.file_id as u64,
125            link_count: 1,
126        })
127    }
128    
129    fn as_any(&self) -> &dyn Any {
130        self
131    }
132    
133    fn read_link(&self) -> Result<String, FileSystemError> {
134        // Check if this is actually a symbolic link and return target
135        match &self.file_type {
136            FileType::SymbolicLink(target) => Ok(target.clone()),
137            _ => Err(FileSystemError::new(
138                FileSystemErrorKind::NotSupported,
139                "Not a symbolic link"
140            ))
141        }
142    }
143}
144
145impl CpioFS {
146    /// Create a new CpioFS from CPIO archive data
147    pub fn new(name: String, cpio_data: &[u8]) -> Result<Arc<Self>, FileSystemError> {
148        let root_node = CpioNode::new("/".to_string(), FileType::Directory, Vec::new(), 1);
149        let filesystem = Arc::new(Self {
150            root_node: Arc::clone(&root_node),
151            name,
152        });
153        {
154            let mut fs_guard = root_node.filesystem.write();
155            *fs_guard = Some(Arc::clone(&filesystem));
156        }
157        // Parse CPIO data and build directory tree
158        filesystem.parse_cpio_archive(cpio_data)?;
159        Ok(filesystem)
160    }
161
162    /// VFS v2 driver registration API: create from option string
163    /// Example: option = Some("initramfs_addr=0x80000000,size=65536")
164    pub fn create_from_option_string(option: Option<&str>, cpio_data: &[u8]) -> Arc<dyn FileSystemOperations> {
165        // Name is fixed, cpio_data is assumed to be provided externally
166        let name = "cpiofs".to_string();
167        // Extend option parsing as needed
168        CpioFS::new(name, cpio_data).expect("Failed to create CpioFS") as Arc<dyn FileSystemOperations>
169    }
170    
171    /// Parse CPIO archive and build directory tree
172    fn parse_cpio_archive(self: &Arc<Self>, data: &[u8]) -> Result<(), FileSystemError> {
173        // CPIO new ASCII format: magic "070701"
174        let mut offset = 0;
175        let mut file_id = 2;
176        while offset + 110 <= data.len() {
177            // Parse header
178            let magic = &data[offset..offset+6];
179            if magic != b"070701" {
180                break;
181            }
182            let _inode = match core::str::from_utf8(&data[offset+6..offset+14]) {
183                Ok(s) => u32::from_str_radix(s, 16).map_err(|_| FileSystemError::new(FileSystemErrorKind::InvalidData, "Invalid inode value"))?,
184                Err(_) => return Err(FileSystemError::new(FileSystemErrorKind::InvalidData, "Invalid UTF-8 in inode field")),
185            };
186            let mode = match core::str::from_utf8(&data[offset+14..offset+22]) {
187                Ok(s) => u32::from_str_radix(s, 16).map_err(|_| FileSystemError::new(FileSystemErrorKind::InvalidData, "Invalid mode value"))?,
188                Err(_) => return Err(FileSystemError::new(FileSystemErrorKind::InvalidData, "Invalid UTF-8 in mode field")),
189            };
190            let namesize = match core::str::from_utf8(&data[offset+94..offset+102]) {
191                Ok(s) => usize::from_str_radix(s, 16).map_err(|_| FileSystemError::new(FileSystemErrorKind::InvalidData, "Invalid namesize value"))?,
192                Err(_) => return Err(FileSystemError::new(FileSystemErrorKind::InvalidData, "Invalid UTF-8 in namesize field")),
193            };
194            let filesize = match core::str::from_utf8(&data[offset+54..offset+62]) {
195                Ok(s) => usize::from_str_radix(s, 16).map_err(|_| FileSystemError::new(FileSystemErrorKind::InvalidData, "Invalid filesize value"))?,
196                Err(_) => return Err(FileSystemError::new(FileSystemErrorKind::InvalidData, "Invalid UTF-8 in filesize field")),
197            };
198            let name_start = offset + 110;
199            let name_end = name_start + namesize;
200            if name_end > data.len() { break; }
201            let name = &data[name_start..name_end-1]; // remove trailing NUL
202            let name_str = core::str::from_utf8(name).unwrap_or("").to_string();
203            let file_start = (name_end + 3) & !3; // 4-byte align
204            let file_end = file_start + filesize;
205            if file_end > data.len() { break; }
206            if name_str == "TRAILER!!!" { break; }
207            
208            // Determine file type
209            let (file_type, content) = match mode & 0o170000 {
210                0o040000 => (FileType::Directory, Vec::new()),
211                0o100000 => (FileType::RegularFile, data[file_start..file_end].to_vec()),
212                0o120000 => {
213                    // For symbolic links, extract target path from content
214                    let target_bytes = data[file_start..file_end].to_vec();
215                    let target_path = String::from_utf8(target_bytes.clone()).unwrap_or_else(|_| String::new());
216                    (FileType::SymbolicLink(target_path), target_bytes)
217                },
218                _ => (FileType::RegularFile, data[file_start..file_end].to_vec()),
219            };
220            // Build node and insert into tree
221            let base_name = if let Some(pos) = name_str.rfind('/') {
222                &name_str[pos+1..]
223            } else {
224                &name_str[..]
225            };
226            
227            // Skip "." and ".." entries as they are handled automatically by the VFS
228            if base_name == "." || base_name == ".." {
229                offset = (file_end + 3) & !3;
230                continue;
231            }
232            
233            let node = CpioNode::new(base_name.to_string(), file_type, content, file_id);
234            {
235                let mut fs_guard = node.filesystem.write();
236                *fs_guard = Some(Arc::clone(self));
237            }
238            file_id += 1;
239            // Insert into parent
240            let parent_path = if let Some(pos) = name_str.rfind('/') { &name_str[..pos] } else { "" };
241            let parent = if parent_path.is_empty() {
242                Arc::clone(&self.root_node)
243            } else {
244                // Traverse from root to find parent
245                let mut cur = Arc::clone(&self.root_node);
246                for part in parent_path.split('/') {
247                    if part.is_empty() { continue; }
248                    if let Some(child) = cur.get_child(part) {
249                        cur = child;
250                    } else {
251                        // Create intermediate directory if missing
252                        let dir = CpioNode::new(part.to_string(), FileType::Directory, Vec::new(), file_id);
253                        {
254                            let mut fs_guard = dir.filesystem.write();
255                            *fs_guard = Some(Arc::clone(self));
256                        }
257                        file_id += 1;
258                        cur.add_child(part.to_string(), Arc::clone(&dir)).ok();
259                        cur = dir;
260                    }
261                }
262                cur
263            };
264            parent.add_child(base_name.to_string(), Arc::clone(&node)).ok();
265            offset = (file_end + 3) & !3;
266        }
267        Ok(())
268    }
269}
270
271impl FileSystemOperations for CpioFS {
272    fn lookup(
273        &self,
274        parent_node: &Arc<dyn VfsNode>,
275        name: &String,
276    ) -> Result<Arc<dyn VfsNode>, FileSystemError> {
277        // Downcast to CpioNode
278        let cpio_parent = parent_node.as_any()
279            .downcast_ref::<CpioNode>()
280            .ok_or_else(|| FileSystemError::new(
281                FileSystemErrorKind::NotSupported,
282                "Invalid node type for CpioFS"
283            ))?;
284        
285        // Look up child
286        cpio_parent.get_child(name)
287            .map(|n| n as Arc<dyn VfsNode>)
288            .ok_or_else(|| FileSystemError::new(
289                FileSystemErrorKind::NotFound,
290                format!("File not found: {} in {}", name, cpio_parent.name)
291            ))
292    }
293    
294    fn open(
295        &self,
296        node: &Arc<dyn VfsNode>,
297        _flags: u32,
298    ) -> Result<Arc<dyn FileObject>, FileSystemError> {
299        let cpio_node = node.as_any()
300            .downcast_ref::<CpioNode>()
301            .ok_or_else(|| FileSystemError::new(
302                FileSystemErrorKind::NotSupported,
303                "Invalid node type for CpioFS"
304            ))?;
305        
306        match cpio_node.file_type {
307            FileType::RegularFile => {
308                Ok(Arc::new(CpioFileObject::new(Arc::clone(node))))
309            },
310            FileType::Directory => {
311                Ok(Arc::new(CpioDirectoryObject::new(Arc::clone(node))))
312            },
313            _ => Err(FileSystemError::new(
314                FileSystemErrorKind::NotSupported,
315                "Unsupported file type"
316            ))
317        }
318    }
319    
320    fn create(
321        &self,
322        _parent_node: &Arc<dyn VfsNode>,
323        _name: &String,
324        _file_type: FileType,
325        _mode: u32,
326    ) -> Result<Arc<dyn VfsNode>, FileSystemError> {
327        Err(FileSystemError::new(
328            FileSystemErrorKind::ReadOnly,
329            "CPIO filesystem is read-only"
330        ))
331    }
332    
333    fn remove(
334        &self,
335        _parent_node: &Arc<dyn VfsNode>,
336        _name: &String,
337    ) -> Result<(), FileSystemError> {
338        Err(FileSystemError::new(
339            FileSystemErrorKind::ReadOnly,
340            "CPIO filesystem is read-only"
341        ))
342    }
343    
344    fn root_node(&self) -> Arc<dyn VfsNode> {
345        Arc::clone(&self.root_node) as Arc<dyn VfsNode>
346    }
347    
348    fn name(&self) -> &str {
349        &self.name
350    }
351    
352    fn is_read_only(&self) -> bool {
353        true
354    }
355    
356    fn readdir(
357        &self,
358        node: &Arc<dyn VfsNode>,
359    ) -> Result<Vec<DirectoryEntryInternal>, FileSystemError> {
360        let cpio_node = node.as_any()
361            .downcast_ref::<CpioNode>()
362            .ok_or_else(|| FileSystemError::new(
363                FileSystemErrorKind::NotSupported,
364                "Invalid node type for CpioFS"
365            ))?;
366        if cpio_node.file_type != FileType::Directory {
367            return Err(FileSystemError::new(
368                FileSystemErrorKind::NotADirectory,
369                "Not a directory"
370            ));
371        }
372        let mut entries = Vec::new();
373        
374        // Add "." and ".." entries
375        entries.push(DirectoryEntryInternal {
376            name: ".".to_string(),
377            file_type: FileType::Directory,
378            file_id: cpio_node.file_id as u64,
379        });
380        
381        // .. entry should have the parent directory's file_id
382        let parent_file_id = cpio_node.parent_file_id().unwrap_or(0);
383        entries.push(DirectoryEntryInternal {
384            name: "..".to_string(),
385            file_type: FileType::Directory,
386            file_id: parent_file_id,
387        });
388        
389        // Add children
390        let children = cpio_node.children.read();
391        for child in children.values() {
392            entries.push(DirectoryEntryInternal {
393                name: child.name.clone(),
394                file_type: child.file_type.clone(),
395                file_id: child.file_id as u64,
396            });
397        }
398        
399        Ok(entries)
400    }
401}
402
403/// File object for CPIO regular files
404pub struct CpioFileObject {
405    node: Arc<dyn VfsNode>,
406    position: RwLock<u64>,
407}
408
409impl CpioFileObject {
410    pub fn new(node: Arc<dyn VfsNode>) -> Self {
411        Self {
412            node,
413            position: RwLock::new(0),
414        }
415    }
416}
417
418impl StreamOps for CpioFileObject {
419    fn read(&self, buf: &mut [u8]) -> Result<usize, StreamError> {
420        let cpio_node = self.node.as_any()
421            .downcast_ref::<CpioNode>()
422            .ok_or(StreamError::IoError)?;
423        
424        let mut pos = self.position.write();
425        let start = *pos as usize;
426        let end = (start + buf.len()).min(cpio_node.content.len());
427        
428        if start >= cpio_node.content.len() {
429            return Ok(0); // EOF
430        }
431        
432        let bytes_to_read = end - start;
433        buf[..bytes_to_read].copy_from_slice(&cpio_node.content[start..end]);
434        *pos += bytes_to_read as u64;
435        
436        Ok(bytes_to_read)
437    }
438    
439    fn write(&self, _buf: &[u8]) -> Result<usize, StreamError> {
440        Err(StreamError::PermissionDenied)
441    }
442}
443
444impl FileObject for CpioFileObject {
445    fn seek(&self, whence: crate::fs::SeekFrom) -> Result<u64, StreamError> {
446        let cpio_node = self.node.as_any()
447            .downcast_ref::<CpioNode>()
448            .ok_or(StreamError::IoError)?;
449        
450        let mut pos = self.position.write();
451        let file_size = cpio_node.content.len() as u64;
452        
453        let new_pos = match whence {
454            crate::fs::SeekFrom::Start(offset) => offset,
455            crate::fs::SeekFrom::Current(offset) => {
456                if offset >= 0 {
457                    *pos + offset as u64
458                } else {
459                    pos.saturating_sub((-offset) as u64)
460                }
461            },
462            crate::fs::SeekFrom::End(offset) => {
463                if offset >= 0 {
464                    file_size + offset as u64
465                } else {
466                    file_size.saturating_sub((-offset) as u64)
467                }
468            }
469        };
470        
471        *pos = new_pos.min(file_size);
472        Ok(*pos)
473    }
474    
475    fn metadata(&self) -> Result<crate::fs::FileMetadata, StreamError> {
476        self.node.metadata().map_err(StreamError::from)
477    }
478    
479    fn truncate(&self, _size: u64) -> Result<(), StreamError> {
480        Err(StreamError::PermissionDenied)
481    }
482}
483
484/// Directory object for CPIO directories
485pub struct CpioDirectoryObject {
486    node: Arc<dyn VfsNode>,
487    position: RwLock<u64>,
488}
489
490impl CpioDirectoryObject {
491    pub fn new(node: Arc<dyn VfsNode>) -> Self {
492        Self {
493            node,
494            position: RwLock::new(0),
495        }
496    }
497}
498
499impl StreamOps for CpioDirectoryObject {
500    fn read(&self, buf: &mut [u8]) -> Result<usize, StreamError> {
501        let cpio_node = self.node.as_any()
502            .downcast_ref::<CpioNode>()
503            .ok_or(StreamError::NotSupported)?;
504        if cpio_node.file_type != FileType::Directory {
505            return Err(StreamError::NotSupported);
506        }
507        let mut all_entries = Vec::new();
508        // . entry
509        all_entries.push(crate::fs::DirectoryEntryInternal {
510            name: ".".to_string(),
511            file_type: FileType::Directory,
512            size: 0,
513            file_id: cpio_node.file_id as u64,
514            metadata: None,
515        });
516        // .. entry
517        let parent_file_id = cpio_node.parent_file_id().unwrap_or(0);
518        all_entries.push(crate::fs::DirectoryEntryInternal {
519            name: "..".to_string(),
520            file_type: FileType::Directory,
521            size: 0,
522            file_id: parent_file_id,
523            metadata: None,
524        });
525        // children entries
526        for child in cpio_node.children.read().values() {
527            all_entries.push(crate::fs::DirectoryEntryInternal {
528                name: child.name.clone(),
529                file_type: child.file_type.clone(),
530                size: child.content.len(),
531                file_id: child.file_id as u64,
532                metadata: None,
533            });
534        }
535        
536        let position = *self.position.read() as usize;
537        if position >= all_entries.len() {
538            return Ok(0); // EOF
539        }
540        let internal_entry = &all_entries[position];
541        let dir_entry = crate::fs::DirectoryEntry::from_internal(internal_entry);
542        let entry_size = dir_entry.entry_size();
543        if buf.len() < entry_size {
544            return Err(StreamError::InvalidArgument); 
545        }
546        let entry_bytes = unsafe {
547            core::slice::from_raw_parts(
548                &dir_entry as *const _ as *const u8,
549                entry_size
550            )
551        };
552        buf[..entry_size].copy_from_slice(entry_bytes);
553        *self.position.write() += 1;
554        Ok(entry_size)
555    }
556    fn write(&self, _buf: &[u8]) -> Result<usize, StreamError> {
557        Err(StreamError::FileSystemError(FileSystemError::new(
558            FileSystemErrorKind::ReadOnly,
559            "CPIO filesystem is read-only"
560        )))
561    }
562}
563
564impl FileObject for CpioDirectoryObject {
565    fn seek(&self, _whence: crate::fs::SeekFrom) -> Result<u64, StreamError> {
566        // Seeking in directories not supported
567        Err(StreamError::NotSupported)
568    }
569    
570    fn metadata(&self) -> Result<crate::fs::FileMetadata, StreamError> {
571        self.node.metadata().map_err(StreamError::from)
572    }
573    
574    fn truncate(&self, _size: u64) -> Result<(), StreamError> {
575        Err(StreamError::FileSystemError(FileSystemError::new(
576            FileSystemErrorKind::ReadOnly,
577            "CPIO filesystem is read-only"
578        )))
579    }
580}
581
582
583/// Driver for CPIO-format filesystems (initramfs)
584/// 
585/// This driver creates filesystems from memory areas only.
586pub struct CpiofsDriver;
587
588impl FileSystemDriver for CpiofsDriver {
589    fn name(&self) -> &'static str {
590        "cpiofs"
591    }
592    
593    /// This filesystem only supports creation from memory
594    fn filesystem_type(&self) -> FileSystemType {
595        FileSystemType::Memory
596    }
597    
598    /// Create a file system from memory area
599    /// 
600    /// # Arguments
601    /// 
602    /// * `memory_area` - A reference to the memory area containing the CPIO filesystem data
603    /// 
604    /// # Returns
605    /// 
606    /// A result containing a boxed CPIO filesystem or an error
607    /// 
608    fn create_from_memory(&self, memory_area: &MemoryArea) -> Result<Arc<dyn FileSystemOperations>, FileSystemError> {
609        let data = unsafe { memory_area.as_slice() };
610        // Create the Cpiofs from the memory data
611        match CpioFS::new("cpiofs".to_string(), data) {
612            Ok(cpio_fs) => Ok(cpio_fs),
613            Err(err) => Err(FileSystemError {
614                kind: FileSystemErrorKind::InvalidData,
615                message: format!("Failed to create CPIO filesystem from memory: {}", err.message),
616            })
617        }
618    }
619
620    fn create_from_params(&self, params: &dyn crate::fs::params::FileSystemParams) -> Result<Arc<dyn FileSystemOperations>, FileSystemError> {
621        use crate::fs::params::*;
622        
623        // Try to downcast to CpioFSParams
624        if let Some(_cpio_params) = params.as_any().downcast_ref::<CpioFSParams>() {
625            // CPIO filesystem requires memory area for creation, so we cannot create from parameters alone
626            return Err(FileSystemError {
627                kind: FileSystemErrorKind::NotSupported,
628                message: "CPIO filesystem requires memory area for creation. Use create_from_memory instead.".to_string(),
629            });
630        }
631        
632        // Try to downcast to BasicFSParams for compatibility
633        if let Some(_basic_params) = params.as_any().downcast_ref::<BasicFSParams>() {
634            return Err(FileSystemError {
635                kind: FileSystemErrorKind::NotSupported,
636                message: "CPIO filesystem requires memory area for creation. Use create_from_memory instead.".to_string(),
637            });
638        }
639        
640        // If all downcasts fail, return error
641        Err(FileSystemError {
642            kind: FileSystemErrorKind::NotSupported,
643            message: "CPIO filesystem requires CpioFSParams and memory area for creation".to_string(),
644        })
645    }
646}
647
648fn register_driver() {
649    let fs_driver_manager = get_fs_driver_manager();
650    fs_driver_manager.register_driver(Box::new(CpiofsDriver));
651}
652
653driver_initcall!(register_driver);
654
655#[cfg(test)]
656#[path = "cpiofs_tests.rs"]
657mod tests;