kernel/fs/vfs_v2/drivers/ext2/
node.rs

1//! ext2 VFS Node Implementation
2//!
3//! This module implements the VFS node interface for ext2 filesystem nodes,
4//! providing file and directory objects that integrate with the VFS v2 architecture.
5
6use alloc::{boxed::Box, collections::BTreeMap, format, string::String, sync::Weak, vec, vec::Vec};
7use core::{any::Any, fmt::Debug};
8use spin::{Mutex, RwLock};
9
10use crate::object::capability::selectable::{
11    ReadyInterest, ReadySet, SelectWaitOutcome, Selectable,
12};
13use crate::{
14    DeviceManager,
15    environment::PAGE_SIZE,
16    fs::{
17        DeviceFileInfo, FileMetadata, FileObject, FilePermission, FileSystemError,
18        FileSystemErrorKind, FileType, SeekFrom, SocketFileInfo, vfs_v2::cache::PageCacheCapable,
19    },
20    mem::{
21        page::allocate_boxed_pages,
22        page_cache::{PageCacheManager, PageIndex},
23    },
24    object::capability::{ControlOps, MemoryMappingOps, StreamError, StreamOps},
25};
26
27use super::{
28    Ext2FileSystem,
29    structures::{EXT2_S_IFDIR, EXT2_S_IFMT, EXT2_S_IFREG},
30};
31use crate::fs::vfs_v2::core::{FileSystemOperations, VfsNode};
32
33/// ext2 VFS Node
34///
35/// Represents a file or directory in the ext2 filesystem. This node
36/// implements the VfsNode trait and provides access to ext2-specific
37/// file operations.
38#[derive(Debug)]
39pub struct Ext2Node {
40    /// Inode number in the ext2 filesystem
41    inode_number: u32,
42    /// File type (directory, regular file, etc.)
43    file_type: FileType,
44    /// Unique file ID for VFS
45    file_id: u64,
46    /// Weak reference to the filesystem
47    filesystem: RwLock<Option<Weak<dyn FileSystemOperations>>>,
48}
49
50impl Ext2Node {
51    /// Create a new ext2 node
52    pub fn new(inode_number: u32, file_type: FileType, file_id: u64) -> Self {
53        Self {
54            inode_number,
55            file_type,
56            file_id,
57            filesystem: RwLock::new(None),
58        }
59    }
60
61    /// Get the inode number
62    pub fn inode_number(&self) -> u32 {
63        self.inode_number
64    }
65
66    /// Set the filesystem reference
67    pub fn set_filesystem(&self, fs: Weak<dyn FileSystemOperations>) {
68        *self.filesystem.write() = Some(fs);
69    }
70
71    /// Get the filesystem reference
72    pub fn filesystem(&self) -> Option<Weak<dyn FileSystemOperations>> {
73        self.filesystem.read().clone()
74    }
75}
76
77impl VfsNode for Ext2Node {
78    fn id(&self) -> u64 {
79        self.file_id
80    }
81
82    fn filesystem(&self) -> Option<Weak<dyn FileSystemOperations>> {
83        self.filesystem.read().clone()
84    }
85
86    fn file_type(&self) -> Result<FileType, FileSystemError> {
87        Ok(self.file_type.clone())
88    }
89
90    fn metadata(&self) -> Result<FileMetadata, FileSystemError> {
91        crate::profile_scope!("ext2::node::metadata");
92
93        // Read the actual inode to get real metadata
94        let filesystem = self
95            .filesystem()
96            .and_then(|weak_fs| weak_fs.upgrade())
97            .ok_or_else(|| {
98                FileSystemError::new(
99                    FileSystemErrorKind::NotSupported,
100                    "Filesystem not available",
101                )
102            })?;
103
104        let ext2_fs = filesystem
105            .as_any()
106            .downcast_ref::<Ext2FileSystem>()
107            .ok_or_else(|| {
108                FileSystemError::new(FileSystemErrorKind::NotSupported, "Invalid filesystem type")
109            })?;
110
111        let inode = ext2_fs.read_inode(self.inode_number)?;
112
113        // Convert inode mode to permissions
114        let mode = inode.get_mode();
115        let permissions = FilePermission {
116            read: (mode & 0o444) != 0,
117            write: (mode & 0o222) != 0,
118            execute: (mode & 0o111) != 0,
119        };
120
121        Ok(FileMetadata {
122            file_type: self.file_type.clone(),
123            size: inode.get_size() as usize,
124            permissions,
125            created_time: inode.get_ctime() as u64,
126            modified_time: inode.get_mtime() as u64,
127            accessed_time: 0,
128            file_id: self.file_id,
129            link_count: 1,
130        })
131    }
132
133    fn as_any(&self) -> &dyn Any {
134        self
135    }
136
137    fn read_link(&self) -> Result<String, FileSystemError> {
138        // Check if this is actually a symbolic link
139        if !matches!(self.file_type, FileType::SymbolicLink(_)) {
140            return Err(FileSystemError::new(
141                FileSystemErrorKind::NotSupported,
142                "Not a symbolic link",
143            ));
144        }
145
146        // Get filesystem reference
147        let filesystem = self
148            .filesystem()
149            .and_then(|weak_fs| weak_fs.upgrade())
150            .ok_or_else(|| {
151                FileSystemError::new(
152                    FileSystemErrorKind::NotSupported,
153                    "Filesystem not available",
154                )
155            })?;
156
157        let ext2_fs = filesystem
158            .as_any()
159            .downcast_ref::<Ext2FileSystem>()
160            .ok_or_else(|| {
161                FileSystemError::new(FileSystemErrorKind::NotSupported, "Invalid filesystem type")
162            })?;
163
164        // Read the inode and use the new read_symlink_target method
165        let inode = ext2_fs.read_inode(self.inode_number)?;
166        inode.read_symlink_target(ext2_fs)
167    }
168}
169
170/// ext2 File Object
171///
172/// Handles file operations for regular files in the ext2 filesystem.
173#[derive(Debug)]
174pub struct Ext2FileObject {
175    /// Inode number of the file
176    inode_number: u32,
177    /// File ID
178    file_id: u64,
179    /// Current position in the file
180    position: Mutex<u64>,
181    /// Optional logical size override after in-memory writes (not yet flushed)
182    size_override: Mutex<Option<usize>>,
183    /// Dirty flag indicating in-memory changes not yet persisted to disk
184    dirty: Mutex<bool>,
185    /// Weak reference to the filesystem
186    filesystem: RwLock<Option<Weak<dyn FileSystemOperations>>>,
187    /// Page-aligned backing for mmap operations (lazy initialized)
188    mmap_backing: RwLock<Option<Box<[crate::mem::page::Page]>>>,
189    /// Byte length of the mmap backing (file size snapshot)
190    mmap_backing_len: Mutex<usize>,
191    /// Active mmap ranges keyed by starting virtual address
192    mmap_ranges: RwLock<BTreeMap<usize, MmapRange>>,
193}
194
195impl Ext2FileObject {
196    /// Create a new ext2 file object
197    pub fn new(inode_number: u32, file_id: u64) -> Self {
198        Self {
199            inode_number,
200            file_id,
201            position: Mutex::new(0),
202            size_override: Mutex::new(None),
203            dirty: Mutex::new(false),
204            filesystem: RwLock::new(None),
205            mmap_backing: RwLock::new(None),
206            mmap_backing_len: Mutex::new(0),
207            mmap_ranges: RwLock::new(BTreeMap::new()),
208        }
209    }
210
211    /// Set the filesystem reference
212    pub fn set_filesystem(&self, fs: Weak<dyn FileSystemOperations>) {
213        *self.filesystem.write() = Some(fs);
214    }
215
216    /// Get the file ID
217    pub fn file_id(&self) -> u64 {
218        self.file_id
219    }
220
221    /// Flush current page-cache-backed content to disk.
222    fn sync_to_disk(&self) -> Result<(), StreamError> {
223        let no_size_override = self.size_override.lock().is_none();
224        let is_dirty = *self.dirty.lock();
225        if no_size_override && !is_dirty {
226            return Ok(());
227        }
228
229        let fs = self
230            .filesystem
231            .read()
232            .as_ref()
233            .and_then(|weak| weak.upgrade())
234            .ok_or(StreamError::Closed)?;
235        let ext2_fs = fs
236            .as_any()
237            .downcast_ref::<Ext2FileSystem>()
238            .ok_or(StreamError::NotSupported)?;
239
240        let on_disk = ext2_fs
241            .read_inode(self.inode_number)
242            .map_err(|_| StreamError::IoError)?
243            .size as usize;
244        let eff_size = match *self.size_override.lock() {
245            Some(ov) => core::cmp::max(on_disk, ov),
246            None => on_disk,
247        };
248
249        let mut buffer = Vec::with_capacity(eff_size);
250        buffer.resize(eff_size, 0);
251        let cache_id = self.cache_id();
252        let page_count = (eff_size + PAGE_SIZE - 1) / PAGE_SIZE;
253        for page_index in 0..(page_count as u64) {
254            let start = page_index as usize * PAGE_SIZE;
255            let len = core::cmp::min(PAGE_SIZE, eff_size.saturating_sub(start));
256            if len == 0 {
257                break;
258            }
259
260            let pinned = if let Some(p) = PageCacheManager::global().try_pin(cache_id, page_index) {
261                p
262            } else {
263                PageCacheManager::global()
264                    .pin_or_load(cache_id, page_index, |paddr| {
265                        ext2_fs
266                            .read_page_content(self.inode_number, page_index, paddr)
267                            .map_err(|_| "io error")
268                    })
269                    .map_err(|_| StreamError::IoError)?
270            };
271
272            unsafe {
273                core::ptr::copy_nonoverlapping(
274                    pinned.paddr() as *const u8,
275                    buffer.as_mut_ptr().add(start),
276                    len,
277                );
278            }
279        }
280
281        ext2_fs
282            .write_file_content(self.inode_number, &buffer)
283            .map_err(|_| StreamError::IoError)?;
284
285        *self.size_override.lock() = None;
286        *self.dirty.lock() = false;
287        Ok(())
288    }
289
290    fn effective_size(&self, inode_size: usize) -> usize {
291        let mut file_size = inode_size;
292        if let Some(override_size) = *self.size_override.lock() {
293            if override_size > file_size {
294                file_size = override_size;
295            }
296        }
297        file_size
298    }
299
300    fn ensure_mmap_backing(
301        &self,
302        file_size: usize,
303        required_size: usize,
304    ) -> Result<(), StreamError> {
305        if file_size == 0 || required_size == 0 {
306            return Err(StreamError::InvalidArgument);
307        }
308
309        let num_pages = (required_size + PAGE_SIZE - 1) / PAGE_SIZE;
310        let mut backing_guard = self.mmap_backing.write();
311        let needs_alloc = backing_guard
312            .as_ref()
313            .map(|buf| buf.len() < num_pages)
314            .unwrap_or(true);
315        if needs_alloc {
316            *backing_guard = Some(allocate_boxed_pages(num_pages));
317        }
318
319        let backing = backing_guard.as_mut().expect("mmap backing missing");
320        *self.mmap_backing_len.lock() = file_size;
321
322        let fs = self
323            .filesystem
324            .read()
325            .as_ref()
326            .and_then(|weak| weak.upgrade())
327            .ok_or(StreamError::Closed)?;
328        let ext2_fs = fs
329            .as_any()
330            .downcast_ref::<Ext2FileSystem>()
331            .ok_or(StreamError::NotSupported)?;
332
333        let cache_id = self.cache_id();
334        let backing_ptr = backing.as_mut_ptr() as *mut u8;
335        for page_index in 0..num_pages {
336            let pinned = PageCacheManager::global()
337                .pin_or_load(cache_id, page_index as u64, |paddr| {
338                    ext2_fs
339                        .read_page_content(self.inode_number, page_index as u64, paddr)
340                        .map_err(|_| "ext2: read_page_content failed")
341                })
342                .map_err(|_| StreamError::IoError)?;
343            unsafe {
344                core::ptr::copy_nonoverlapping(
345                    pinned.paddr() as *const u8,
346                    backing_ptr.add(page_index * PAGE_SIZE),
347                    PAGE_SIZE,
348                );
349            }
350        }
351
352        Ok(())
353    }
354}
355
356#[derive(Clone, Copy, Debug)]
357struct MmapRange {
358    vaddr_start: usize,
359    vaddr_end: usize,
360    offset: usize,
361}
362
363impl StreamOps for Ext2FileObject {
364    fn read(&self, buffer: &mut [u8]) -> Result<usize, StreamError> {
365        crate::profile_scope!("ext2::node::read");
366
367        let fs = self
368            .filesystem
369            .read()
370            .as_ref()
371            .and_then(|weak| weak.upgrade())
372            .ok_or(StreamError::Closed)?;
373        let ext2_fs = fs
374            .as_any()
375            .downcast_ref::<Ext2FileSystem>()
376            .ok_or(StreamError::NotSupported)?;
377
378        let inode = ext2_fs
379            .read_inode(self.inode_number)
380            .map_err(|_| StreamError::IoError)?;
381        let mut file_size = inode.size as usize;
382        if let Some(override_size) = *self.size_override.lock() {
383            if override_size > file_size {
384                file_size = override_size;
385            }
386        }
387
388        let mut position_guard = self.position.lock();
389        let current_pos = *position_guard as usize;
390        if current_pos >= file_size {
391            return Ok(0);
392        }
393
394        let bytes_available = file_size - current_pos;
395        let bytes_to_read = core::cmp::min(buffer.len(), bytes_available);
396        if bytes_to_read == 0 {
397            return Ok(0);
398        }
399
400        let cache_id = self.cache_id();
401        let mut bytes_read = 0usize;
402        let mut buf_offset = 0usize;
403        let mut pos = current_pos;
404
405        while bytes_read < bytes_to_read {
406            let page_index = (pos / PAGE_SIZE) as PageIndex;
407            let page_offset = pos % PAGE_SIZE;
408            let remaining_in_page = PAGE_SIZE - page_offset;
409            let bytes_in_page = core::cmp::min(bytes_to_read - bytes_read, remaining_in_page);
410
411            let pinned = PageCacheManager::global()
412                .pin_or_load(cache_id, page_index, |paddr| {
413                    ext2_fs
414                        .read_page_content(self.inode_number, page_index, paddr)
415                        .map_err(|_| "Failed to load page")
416                })
417                .map_err(|_| StreamError::IoError)?;
418
419            unsafe {
420                let page_ptr = pinned.paddr() as *const u8;
421                let src = page_ptr.add(page_offset);
422                let dst = buffer.as_mut_ptr().add(buf_offset);
423                core::ptr::copy_nonoverlapping(src, dst, bytes_in_page);
424            }
425
426            bytes_read += bytes_in_page;
427            buf_offset += bytes_in_page;
428            pos += bytes_in_page;
429        }
430
431        *position_guard += bytes_read as u64;
432        Ok(bytes_read)
433    }
434
435    fn write(&self, buffer: &[u8]) -> Result<usize, StreamError> {
436        crate::profile_scope!("ext2::node::write");
437
438        let fs = self
439            .filesystem
440            .read()
441            .as_ref()
442            .and_then(|weak| weak.upgrade())
443            .ok_or(StreamError::Closed)?;
444        let ext2_fs = fs
445            .as_any()
446            .downcast_ref::<Ext2FileSystem>()
447            .ok_or(StreamError::NotSupported)?;
448
449        let mut position_guard = self.position.lock();
450        let mut pos = *position_guard as usize;
451        let bytes_to_write = buffer.len();
452        if bytes_to_write == 0 {
453            return Ok(0);
454        }
455
456        let cache_id = self.cache_id();
457        let mut written = 0usize;
458        let mut buf_offset = 0usize;
459        while written < bytes_to_write {
460            let page_index = (pos / PAGE_SIZE) as PageIndex;
461            let page_off = pos % PAGE_SIZE;
462            let remain_in_page = PAGE_SIZE - page_off;
463            let chunk = core::cmp::min(bytes_to_write - written, remain_in_page);
464
465            let pinned = PageCacheManager::global()
466                .pin_or_load(cache_id, page_index, |paddr| {
467                    ext2_fs
468                        .read_page_content(self.inode_number, page_index, paddr)
469                        .map_err(|_| "Failed to load page")
470                })
471                .map_err(|_| StreamError::IoError)?;
472
473            unsafe {
474                let dst = (pinned.paddr() as *mut u8).add(page_off);
475                let src = buffer.as_ptr().add(buf_offset);
476                core::ptr::copy_nonoverlapping(src, dst, chunk);
477            }
478
479            pinned.mark_dirty();
480            *self.dirty.lock() = true;
481
482            written += chunk;
483            buf_offset += chunk;
484            pos += chunk;
485        }
486
487        *position_guard = (*position_guard as usize + written) as u64;
488
489        let mut override_size = self.size_override.lock();
490        let new_end = pos;
491        match *override_size {
492            Some(cur) => {
493                if new_end > cur {
494                    *override_size = Some(new_end);
495                }
496            }
497            None => {
498                let base = ext2_fs
499                    .read_inode(self.inode_number)
500                    .map_err(|_| StreamError::IoError)?
501                    .size as usize;
502                if new_end > base {
503                    *override_size = Some(new_end);
504                }
505            }
506        }
507
508        Ok(written)
509    }
510}
511
512impl ControlOps for Ext2FileObject {}
513
514impl MemoryMappingOps for Ext2FileObject {
515    fn get_mapping_info(
516        &self,
517        offset: usize,
518        length: usize,
519    ) -> Result<(usize, usize, bool), &'static str> {
520        if offset % PAGE_SIZE != 0 {
521            return Err("Offset not page aligned");
522        }
523
524        let fs = self
525            .filesystem
526            .read()
527            .as_ref()
528            .and_then(|weak| weak.upgrade())
529            .ok_or("Filesystem closed")?;
530        let ext2_fs = fs
531            .as_any()
532            .downcast_ref::<Ext2FileSystem>()
533            .ok_or("Invalid filesystem type")?;
534        let inode = ext2_fs
535            .read_inode(self.inode_number)
536            .map_err(|_| "Read inode failed")?;
537        let file_size = self.effective_size(inode.size as usize);
538
539        if file_size == 0 || offset >= file_size {
540            return Err("Offset beyond file size");
541        }
542
543        let required_size = offset.saturating_add(length).max(file_size);
544        self.ensure_mmap_backing(file_size, required_size)
545            .map_err(|_| "Failed to prepare mmap backing")?;
546
547        let backing_guard = self.mmap_backing.read();
548        let backing = backing_guard.as_ref().ok_or("mmap backing missing")?;
549        let base = backing.as_ptr() as usize;
550        let paddr = base + offset;
551        if paddr % PAGE_SIZE != 0 {
552            return Err("Backing address not aligned");
553        }
554
555        Ok((paddr, 0x3, true))
556    }
557
558    fn get_mapping_info_with(
559        &self,
560        offset: usize,
561        length: usize,
562        is_shared: bool,
563    ) -> Result<(usize, usize, bool), &'static str> {
564        if is_shared {
565            if offset % PAGE_SIZE != 0 {
566                return Err("Offset not page aligned");
567            }
568
569            let fs = self
570                .filesystem
571                .read()
572                .as_ref()
573                .and_then(|weak| weak.upgrade())
574                .ok_or("Filesystem closed")?;
575            let ext2_fs = fs
576                .as_any()
577                .downcast_ref::<Ext2FileSystem>()
578                .ok_or("Invalid filesystem type")?;
579            let inode = ext2_fs
580                .read_inode(self.inode_number)
581                .map_err(|_| "Read inode failed")?;
582            let file_size = self.effective_size(inode.size as usize);
583
584            if file_size == 0 || offset >= file_size {
585                return Err("Offset beyond file size");
586            }
587
588            let _ = length;
589            return Ok((0, 0x3, true));
590        }
591
592        self.get_mapping_info(offset, length)
593    }
594
595    fn on_mapped(&self, _vaddr: usize, _paddr: usize, _length: usize, _offset: usize) {
596        if _length == 0 {
597            return;
598        }
599        let vaddr_end = _vaddr.saturating_add(_length - 1);
600        let range = MmapRange {
601            vaddr_start: _vaddr,
602            vaddr_end,
603            offset: _offset,
604        };
605        self.mmap_ranges.write().insert(_vaddr, range);
606    }
607
608    fn on_unmapped(&self, vaddr: usize, _length: usize) {
609        self.mmap_ranges.write().remove(&vaddr);
610        let backing_guard = self.mmap_backing.read();
611        let backing = match backing_guard.as_ref() {
612            Some(buf) => buf,
613            None => {
614                let _ = self.sync_to_disk();
615                return;
616            }
617        };
618        let backing_len = *self.mmap_backing_len.lock();
619        if backing_len == 0 {
620            return;
621        }
622
623        let fs = match self
624            .filesystem
625            .read()
626            .as_ref()
627            .and_then(|weak| weak.upgrade())
628        {
629            Some(fs) => fs,
630            None => return,
631        };
632        let ext2_fs = match fs.as_any().downcast_ref::<Ext2FileSystem>() {
633            Some(fs) => fs,
634            None => return,
635        };
636
637        let backing_ptr = backing.as_ptr() as *const u8;
638        let data = unsafe { core::slice::from_raw_parts(backing_ptr, backing_len) };
639        let _ = ext2_fs.write_file_content(self.inode_number, data);
640        PageCacheManager::global().invalidate(self.cache_id());
641    }
642
643    fn supports_mmap(&self) -> bool {
644        true
645    }
646
647    fn resolve_fault(
648        &self,
649        access: &crate::object::capability::memory_mapping::AccessKind,
650        map: &crate::vm::vmem::VirtualMemoryMap,
651    ) -> core::result::Result<
652        crate::object::capability::memory_mapping::ResolveFaultResult,
653        crate::object::capability::memory_mapping::ResolveFaultError,
654    > {
655        let range = self
656            .mmap_ranges
657            .read()
658            .get(&map.vmarea.start)
659            .copied()
660            .ok_or(crate::object::capability::memory_mapping::ResolveFaultError::Invalid)?;
661        if access.vaddr < range.vaddr_start || access.vaddr > range.vaddr_end {
662            return Err(crate::object::capability::memory_mapping::ResolveFaultError::Invalid);
663        }
664
665        let fs = self
666            .filesystem
667            .read()
668            .as_ref()
669            .and_then(|weak| weak.upgrade())
670            .ok_or(crate::object::capability::memory_mapping::ResolveFaultError::Invalid)?;
671        let ext2_fs = fs
672            .as_any()
673            .downcast_ref::<Ext2FileSystem>()
674            .ok_or(crate::object::capability::memory_mapping::ResolveFaultError::Invalid)?;
675        let inode = ext2_fs
676            .read_inode(self.inode_number)
677            .map_err(|_| crate::object::capability::memory_mapping::ResolveFaultError::Invalid)?;
678        let file_size = self.effective_size(inode.size as usize);
679
680        let file_offset = range
681            .offset
682            .saturating_add(access.vaddr.saturating_sub(range.vaddr_start));
683        if file_size == 0 || file_offset >= file_size {
684            return Err(crate::object::capability::memory_mapping::ResolveFaultError::Invalid);
685        }
686
687        let page_index = (file_offset / PAGE_SIZE) as u64;
688        let pinned = PageCacheManager::global()
689            .pin_or_load(self.cache_id(), page_index, |paddr| {
690                ext2_fs
691                    .read_page_content(self.inode_number, page_index, paddr)
692                    .map_err(|_| "ext2: read_page_content failed")
693            })
694            .map_err(|_| crate::object::capability::memory_mapping::ResolveFaultError::Invalid)?;
695
696        if matches!(
697            access.op,
698            crate::object::capability::memory_mapping::AccessOp::Store
699        ) {
700            pinned.mark_dirty();
701            *self.dirty.lock() = true;
702        }
703
704        Ok(
705            crate::object::capability::memory_mapping::ResolveFaultResult {
706                paddr_page_base: pinned.paddr(),
707                is_tail: false,
708            },
709        )
710    }
711}
712
713impl FileObject for Ext2FileObject {
714    fn metadata(&self) -> Result<FileMetadata, StreamError> {
715        // Get filesystem reference
716        let fs = self
717            .filesystem
718            .read()
719            .as_ref()
720            .and_then(|weak| weak.upgrade())
721            .ok_or(StreamError::Closed)?;
722
723        // Downcast to Ext2FileSystem
724        let ext2_fs = fs
725            .as_any()
726            .downcast_ref::<Ext2FileSystem>()
727            .ok_or(StreamError::NotSupported)?;
728
729        // Read inode metadata
730        let inode = ext2_fs
731            .read_inode(self.inode_number)
732            .map_err(|_| StreamError::IoError)?;
733
734        // Convert inode permissions to FilePermission
735        let permissions = FilePermission {
736            read: (inode.mode & 0o444) != 0,
737            write: (inode.mode & 0o222) != 0,
738            execute: (inode.mode & 0o111) != 0,
739        };
740
741        // Determine file type from inode mode
742        let file_type = if (inode.mode & EXT2_S_IFMT) == EXT2_S_IFREG {
743            FileType::RegularFile
744        } else if (inode.mode & EXT2_S_IFMT) == EXT2_S_IFDIR {
745            FileType::Directory
746        } else {
747            FileType::RegularFile // Default fallback
748        };
749
750        Ok(FileMetadata {
751            file_type,
752            size: inode.size as usize,
753            permissions,
754            created_time: inode.ctime as u64,
755            modified_time: inode.mtime as u64,
756            accessed_time: inode.atime as u64,
757            file_id: self.file_id,
758            link_count: inode.links_count as u32,
759        })
760    }
761
762    fn read_at(&self, offset: u64, buffer: &mut [u8]) -> Result<usize, StreamError> {
763        let file_size = {
764            let fs = self
765                .filesystem
766                .read()
767                .as_ref()
768                .and_then(|weak| weak.upgrade())
769                .ok_or(StreamError::Closed)?;
770            let ext2_fs = fs
771                .as_any()
772                .downcast_ref::<Ext2FileSystem>()
773                .ok_or(StreamError::NotSupported)?;
774            ext2_fs
775                .read_inode(self.inode_number)
776                .map_err(|_| StreamError::IoError)?
777                .size as usize
778        };
779
780        let off = usize::try_from(offset).map_err(|_| StreamError::InvalidArgument)?;
781        if off >= file_size {
782            return Ok(0);
783        }
784
785        let mut total_read = 0usize;
786        let cache_id = self.cache_id();
787        while total_read < buffer.len() && off + total_read < file_size {
788            let absolute = off + total_read;
789            let page_index = (absolute / PAGE_SIZE) as PageIndex;
790            let offset_in_page = absolute % PAGE_SIZE;
791
792            let pinned = PageCacheManager::global()
793                .pin_or_load(cache_id, page_index, |paddr| {
794                    let fs = self
795                        .filesystem
796                        .read()
797                        .as_ref()
798                        .and_then(|weak| weak.upgrade())
799                        .ok_or("filesystem gone")?;
800                    let ext2_fs = fs
801                        .as_any()
802                        .downcast_ref::<Ext2FileSystem>()
803                        .ok_or("bad fs type")?;
804                    ext2_fs
805                        .read_page_content(self.inode_number, page_index, paddr)
806                        .map_err(|_| "Failed to load page")
807                })
808                .map_err(|_| StreamError::IoError)?;
809
810            unsafe {
811                let src = (pinned.paddr() as *const u8).add(offset_in_page);
812                let remaining_in_page = PAGE_SIZE - offset_in_page;
813                let remaining_file = file_size - (off + total_read);
814                let remaining_buf = buffer.len() - total_read;
815                let chunk = core::cmp::min(
816                    remaining_in_page,
817                    core::cmp::min(remaining_file, remaining_buf),
818                );
819                core::ptr::copy_nonoverlapping(src, buffer.as_mut_ptr().add(total_read), chunk);
820                total_read += chunk;
821            }
822        }
823
824        Ok(total_read)
825    }
826
827    fn write_at(&self, offset: u64, buffer: &[u8]) -> Result<usize, StreamError> {
828        if buffer.is_empty() {
829            return Ok(0);
830        }
831        let off = usize::try_from(offset).map_err(|_| StreamError::InvalidArgument)?;
832        let mut written = 0usize;
833        let cache_id = self.cache_id();
834
835        while written < buffer.len() {
836            let absolute = off + written;
837            let page_index = (absolute / PAGE_SIZE) as PageIndex;
838            let page_off = absolute % PAGE_SIZE;
839            let remain_in_page = PAGE_SIZE - page_off;
840            let chunk = core::cmp::min(buffer.len() - written, remain_in_page);
841
842            let pinned = PageCacheManager::global()
843                .pin_or_load(cache_id, page_index, |paddr| {
844                    let fs = self
845                        .filesystem
846                        .read()
847                        .as_ref()
848                        .and_then(|weak| weak.upgrade())
849                        .ok_or("filesystem gone")?;
850                    let ext2_fs = fs
851                        .as_any()
852                        .downcast_ref::<Ext2FileSystem>()
853                        .ok_or("bad fs type")?;
854                    ext2_fs
855                        .read_page_content(self.inode_number, page_index, paddr)
856                        .map_err(|_| "Failed to load page")
857                })
858                .map_err(|_| StreamError::IoError)?;
859
860            unsafe {
861                let dst = (pinned.paddr() as *mut u8).add(page_off);
862                let src = buffer.as_ptr().add(written);
863                core::ptr::copy_nonoverlapping(src, dst, chunk);
864            }
865
866            pinned.mark_dirty();
867            written += chunk;
868        }
869
870        let new_end = off + written;
871        let mut size_override = self.size_override.lock();
872        match *size_override {
873            Some(cur) => {
874                if new_end > cur {
875                    *size_override = Some(new_end);
876                }
877            }
878            None => {
879                *size_override = Some(new_end);
880            }
881        }
882
883        *self.dirty.lock() = true;
884
885        Ok(written)
886    }
887
888    fn truncate(&self, size: u64) -> Result<(), StreamError> {
889        let new_size = usize::try_from(size).map_err(|_| StreamError::InvalidArgument)?;
890        let fs = self
891            .filesystem
892            .read()
893            .as_ref()
894            .and_then(|weak| weak.upgrade())
895            .ok_or(StreamError::Closed)?;
896        let ext2_fs = fs
897            .as_any()
898            .downcast_ref::<Ext2FileSystem>()
899            .ok_or(StreamError::NotSupported)?;
900        let inode_size = ext2_fs
901            .read_inode(self.inode_number)
902            .map_err(|_| StreamError::IoError)?
903            .size as usize;
904        let cur_size = self.effective_size(inode_size);
905        if new_size == cur_size {
906            return Ok(());
907        }
908
909        let mut buffer = Vec::with_capacity(new_size);
910        buffer.resize(new_size, 0);
911        let copy_len = core::cmp::min(cur_size, new_size);
912        if copy_len > 0 {
913            let cache_id = self.cache_id();
914            let page_count = (copy_len + PAGE_SIZE - 1) / PAGE_SIZE;
915            for page_index in 0..(page_count as u64) {
916                let start = page_index as usize * PAGE_SIZE;
917                let len = core::cmp::min(PAGE_SIZE, copy_len.saturating_sub(start));
918                if len == 0 {
919                    break;
920                }
921                let pinned = PageCacheManager::global()
922                    .pin_or_load(cache_id, page_index, |paddr| {
923                        ext2_fs
924                            .read_page_content(self.inode_number, page_index, paddr)
925                            .map_err(|_| "io error")
926                    })
927                    .map_err(|_| StreamError::IoError)?;
928                unsafe {
929                    core::ptr::copy_nonoverlapping(
930                        pinned.paddr() as *const u8,
931                        buffer.as_mut_ptr().add(start),
932                        len,
933                    );
934                }
935            }
936        }
937
938        ext2_fs
939            .write_file_content(self.inode_number, &buffer)
940            .map_err(|_| StreamError::IoError)?;
941        PageCacheManager::global().invalidate(self.cache_id());
942        *self.size_override.lock() = None;
943        *self.dirty.lock() = false;
944
945        let mut position = self.position.lock();
946        if *position > size {
947            *position = size;
948        }
949
950        Ok(())
951    }
952
953    fn seek(&self, whence: SeekFrom) -> Result<u64, StreamError> {
954        let mut pos = self.position.lock();
955
956        match whence {
957            SeekFrom::Start(offset) => {
958                *pos = offset;
959                Ok(*pos)
960            }
961            SeekFrom::Current(offset) => {
962                if offset >= 0 {
963                    *pos += offset as u64;
964                } else {
965                    let abs_offset = (-offset) as u64;
966                    if abs_offset > *pos {
967                        *pos = 0;
968                    } else {
969                        *pos -= abs_offset;
970                    }
971                }
972                Ok(*pos)
973            }
974            SeekFrom::End(offset) => {
975                let file_size = self.metadata()?.size as u64;
976
977                let new_pos = if offset >= 0 {
978                    file_size.saturating_add(offset as u64)
979                } else {
980                    let abs_offset = (-offset) as u64;
981                    if abs_offset > file_size {
982                        0
983                    } else {
984                        file_size - abs_offset
985                    }
986                };
987
988                *pos = new_pos;
989                Ok(*pos)
990            }
991        }
992    }
993
994    fn as_any(&self) -> &dyn Any {
995        self
996    }
997
998    fn sync(&self) -> Result<(), StreamError> {
999        self.sync_to_disk()
1000    }
1001}
1002
1003impl PageCacheCapable for Ext2FileObject {
1004    fn cache_id(&self) -> crate::fs::vfs_v2::cache::CacheId {
1005        let fs = self
1006            .filesystem
1007            .read()
1008            .as_ref()
1009            .and_then(|weak| weak.upgrade())
1010            .expect("Ext2FileObject: filesystem gone");
1011        let ext2_fs = fs
1012            .as_any()
1013            .downcast_ref::<Ext2FileSystem>()
1014            .expect("Ext2FileObject: invalid filesystem type");
1015
1016        let fs_id = ext2_fs.fs_id().get();
1017        let cache_id = (fs_id << 32) | self.file_id;
1018        crate::fs::vfs_v2::cache::CacheId::new(cache_id)
1019    }
1020}
1021
1022impl crate::object::capability::selectable::Selectable for Ext2FileObject {
1023    fn current_ready(
1024        &self,
1025        interest: crate::object::capability::selectable::ReadyInterest,
1026    ) -> crate::object::capability::selectable::ReadySet {
1027        let mut set = crate::object::capability::selectable::ReadySet::none();
1028        if interest.read {
1029            set.read = true;
1030        }
1031        if interest.write {
1032            set.write = true;
1033        }
1034        if interest.except {
1035            set.except = false;
1036        }
1037        set
1038    }
1039
1040    fn wait_until_ready(
1041        &self,
1042        _interest: crate::object::capability::selectable::ReadyInterest,
1043        _trapframe: &mut crate::arch::Trapframe,
1044        _timeout_ticks: Option<u64>,
1045    ) -> crate::object::capability::selectable::SelectWaitOutcome {
1046        crate::object::capability::selectable::SelectWaitOutcome::Ready
1047    }
1048
1049    fn is_nonblocking(&self) -> bool {
1050        true
1051    }
1052}
1053
1054impl Drop for Ext2FileObject {
1055    fn drop(&mut self) {
1056        let _ = self.sync_to_disk();
1057        #[cfg(test)]
1058        crate::early_println!(
1059            "[ext2] Drop: File object dropped for inode {}",
1060            self.inode_number
1061        );
1062    }
1063}
1064
1065/// ext2 Directory Object
1066///
1067/// Handles directory operations for directories in the ext2 filesystem.
1068#[derive(Debug)]
1069pub struct Ext2DirectoryObject {
1070    /// Inode number of the directory
1071    inode_number: u32,
1072    /// File ID
1073    file_id: u64,
1074    /// Current position in directory listing
1075    position: Mutex<u64>,
1076    /// Weak reference to the filesystem
1077    filesystem: RwLock<Option<Weak<dyn FileSystemOperations>>>,
1078    /// Cached directory entries to avoid re-reading on every access
1079    cached_entries: Mutex<Option<Vec<crate::fs::DirectoryEntryInternal>>>,
1080    /// Cache generation (based on directory modification time) to detect stale cache
1081    cache_generation: Mutex<u32>,
1082}
1083
1084impl Ext2DirectoryObject {
1085    /// Create a new ext2 directory object
1086    pub fn new(inode_number: u32, file_id: u64) -> Self {
1087        Self {
1088            inode_number,
1089            file_id,
1090            position: Mutex::new(0),
1091            filesystem: RwLock::new(None),
1092            cached_entries: Mutex::new(None),
1093            cache_generation: Mutex::new(0),
1094        }
1095    }
1096
1097    /// Set the filesystem reference
1098    pub fn set_filesystem(&self, fs: Weak<dyn FileSystemOperations>) {
1099        *self.filesystem.write() = Some(fs);
1100    }
1101
1102    /// Get cached directory entries or read them if not cached
1103    fn get_cached_entries(&self) -> Result<Vec<crate::fs::DirectoryEntryInternal>, StreamError> {
1104        let filesystem = self
1105            .filesystem
1106            .read()
1107            .as_ref()
1108            .and_then(|weak_fs| weak_fs.upgrade())
1109            .ok_or(StreamError::IoError)?;
1110
1111        let ext2_fs = filesystem
1112            .as_any()
1113            .downcast_ref::<Ext2FileSystem>()
1114            .ok_or(StreamError::IoError)?;
1115
1116        // Get current directory inode to check modification time
1117        let current_inode = match ext2_fs.read_inode(self.inode_number) {
1118            Ok(inode) => inode,
1119            Err(_) => return Err(StreamError::IoError),
1120        };
1121
1122        let current_generation = current_inode.mtime;
1123
1124        // Check if we have cached entries and if they're still valid
1125        {
1126            let cached = self.cached_entries.lock();
1127            let cache_gen = *self.cache_generation.lock();
1128            if let Some(ref entries) = *cached {
1129                if cache_gen == current_generation {
1130                    return Ok(entries.clone());
1131                }
1132            }
1133        }
1134
1135        // Read directory entries
1136        let entries = match ext2_fs.read_directory_entries(&current_inode) {
1137            Ok(entries) => entries,
1138            Err(_) => return Err(StreamError::IoError),
1139        };
1140
1141        // Convert to internal directory entries with detailed file type detection
1142        let mut all_entries = Vec::new();
1143
1144        for entry in entries {
1145            if entry.entry.inode == 0 {
1146                continue; // Skip deleted entries
1147            }
1148
1149            // Detailed file type detection based on ext2 file_type field
1150            let inode_num = entry.entry.inode; // Copy to avoid alignment issues
1151            let file_type = match entry.entry.file_type {
1152                1 => FileType::RegularFile, // EXT2_FT_REG_FILE
1153                2 => FileType::Directory,   // EXT2_FT_DIR
1154                3 => {
1155                    // EXT2_FT_CHRDEV - Character device
1156                    // For device files, we need device information
1157                    // Extract device ID from inode's block array
1158                    let device_id = match ext2_fs.read_inode(inode_num) {
1159                        Ok(inode) => {
1160                            // ext2 stores device ID in block[0] for special files
1161                            inode.block[0] as usize
1162                        }
1163                        Err(_) => 0,
1164                    };
1165                    FileType::CharDevice(DeviceFileInfo {
1166                        device_id,
1167                        device_type: crate::device::DeviceType::Char,
1168                    })
1169                }
1170                4 => {
1171                    // EXT2_FT_BLKDEV - Block device
1172                    // Extract device ID from inode's block array
1173                    let device_id = match ext2_fs.read_inode(inode_num) {
1174                        Ok(inode) => {
1175                            // ext2 stores device ID in block[0] for special files
1176                            inode.block[0] as usize
1177                        }
1178                        Err(_) => 0,
1179                    };
1180                    FileType::BlockDevice(DeviceFileInfo {
1181                        device_id,
1182                        device_type: crate::device::DeviceType::Block,
1183                    })
1184                }
1185                5 => FileType::Pipe, // EXT2_FT_FIFO
1186                6 => FileType::Socket(SocketFileInfo {
1187                    socket_id: crate::fs::UNBOUND_SOCKET_ID,
1188                }), // EXT2_FT_SOCK - Socket ID will be bound at runtime
1189                7 => {
1190                    // EXT2_FT_SYMLINK - Symbolic link
1191                    // Read the actual symlink target from the inode using the new method
1192                    let target = match ext2_fs.read_inode(inode_num) {
1193                        Ok(inode) => inode
1194                            .read_symlink_target(ext2_fs)
1195                            .unwrap_or_else(|_| format!("<symlink:{}>", inode_num)),
1196                        Err(_) => String::new(),
1197                    };
1198                    FileType::SymbolicLink(target)
1199                }
1200                _ => FileType::Unknown, // Unknown file type
1201            };
1202
1203            all_entries.push(crate::fs::DirectoryEntryInternal {
1204                name: entry.name,
1205                file_type,
1206                size: 0,                   // Size not immediately available
1207                file_id: inode_num as u64, // Use copied inode number
1208                metadata: None,
1209            });
1210        }
1211
1212        // Sort entries by file_id for consistent ordering
1213        all_entries.sort_by_key(|entry| entry.file_id);
1214
1215        // Cache the entries with current generation
1216        {
1217            let mut cached = self.cached_entries.lock();
1218            let mut cache_gen = self.cache_generation.lock();
1219            *cached = Some(all_entries.clone());
1220            *cache_gen = current_generation;
1221        }
1222
1223        Ok(all_entries)
1224    }
1225}
1226
1227impl StreamOps for Ext2DirectoryObject {
1228    fn read(&self, buffer: &mut [u8]) -> Result<usize, StreamError> {
1229        // Use cached entries to avoid re-reading directory on every call
1230        let all_entries = self.get_cached_entries()?;
1231
1232        // position is the entry index
1233        let position = *self.position.lock() as usize;
1234
1235        if position >= all_entries.len() {
1236            return Ok(0); // EOF
1237        }
1238
1239        // Get current entry
1240        let internal_entry = &all_entries[position];
1241
1242        // Convert to binary format
1243        let dir_entry = crate::fs::DirectoryEntry::from_internal(internal_entry);
1244
1245        // Calculate actual entry size
1246        let entry_size = dir_entry.entry_size();
1247
1248        // Check buffer size
1249        if buffer.len() < entry_size {
1250            return Err(StreamError::InvalidArgument); // Buffer too small
1251        }
1252
1253        // Treat struct as byte array
1254        let entry_bytes =
1255            unsafe { core::slice::from_raw_parts(&dir_entry as *const _ as *const u8, entry_size) };
1256
1257        // Copy to buffer
1258        buffer[..entry_size].copy_from_slice(entry_bytes);
1259
1260        // Move to next entry
1261        *self.position.lock() += 1;
1262
1263        Ok(entry_size)
1264    }
1265
1266    fn write(&self, _buffer: &[u8]) -> Result<usize, StreamError> {
1267        Err(StreamError::IoError)
1268    }
1269}
1270
1271impl ControlOps for Ext2DirectoryObject {}
1272
1273impl MemoryMappingOps for Ext2DirectoryObject {
1274    fn get_mapping_info(
1275        &self,
1276        _offset: usize,
1277        _length: usize,
1278    ) -> Result<(usize, usize, bool), &'static str> {
1279        Err("Memory mapping not supported for directories")
1280    }
1281}
1282
1283impl FileObject for Ext2DirectoryObject {
1284    fn metadata(&self) -> Result<FileMetadata, StreamError> {
1285        // Get filesystem reference
1286        let fs = self
1287            .filesystem
1288            .read()
1289            .as_ref()
1290            .and_then(|weak| weak.upgrade())
1291            .ok_or(StreamError::Closed)?;
1292
1293        // Downcast to Ext2FileSystem
1294        let ext2_fs = fs
1295            .as_any()
1296            .downcast_ref::<Ext2FileSystem>()
1297            .ok_or(StreamError::NotSupported)?;
1298
1299        // Read inode metadata
1300        let inode = ext2_fs
1301            .read_inode(self.inode_number)
1302            .map_err(|_| StreamError::IoError)?;
1303
1304        // Convert inode permissions to FilePermission
1305        let permissions = FilePermission {
1306            read: (inode.mode & 0o444) != 0,
1307            write: (inode.mode & 0o222) != 0,
1308            execute: (inode.mode & 0o111) != 0,
1309        };
1310
1311        Ok(FileMetadata {
1312            file_type: FileType::Directory,
1313            size: inode.size as usize,
1314            permissions,
1315            created_time: inode.ctime as u64,
1316            modified_time: inode.mtime as u64,
1317            accessed_time: inode.atime as u64,
1318            file_id: self.file_id,
1319            link_count: inode.links_count as u32,
1320        })
1321    }
1322
1323    fn seek(&self, whence: SeekFrom) -> Result<u64, StreamError> {
1324        let mut pos = self.position.lock();
1325
1326        match whence {
1327            SeekFrom::Start(offset) => {
1328                *pos = offset;
1329                Ok(*pos)
1330            }
1331            _ => Err(StreamError::IoError),
1332        }
1333    }
1334
1335    fn as_any(&self) -> &dyn Any {
1336        self
1337    }
1338}
1339
1340impl crate::object::capability::selectable::Selectable for Ext2DirectoryObject {
1341    fn current_ready(
1342        &self,
1343        interest: crate::object::capability::selectable::ReadyInterest,
1344    ) -> crate::object::capability::selectable::ReadySet {
1345        let mut set = crate::object::capability::selectable::ReadySet::none();
1346        if interest.read {
1347            set.read = true;
1348        }
1349        if interest.write {
1350            set.write = true;
1351        }
1352        if interest.except {
1353            set.except = false;
1354        }
1355        set
1356    }
1357
1358    fn wait_until_ready(
1359        &self,
1360        _interest: crate::object::capability::selectable::ReadyInterest,
1361        _trapframe: &mut crate::arch::Trapframe,
1362        _timeout_ticks: Option<u64>,
1363    ) -> crate::object::capability::selectable::SelectWaitOutcome {
1364        crate::object::capability::selectable::SelectWaitOutcome::Ready
1365    }
1366
1367    fn is_nonblocking(&self) -> bool {
1368        true
1369    }
1370}
1371
1372/// ext2 Character Device File Object
1373///
1374/// Handles character device operations through ext2 device files.
1375#[derive(Debug)]
1376pub struct Ext2CharDeviceFileObject {
1377    /// Device file info
1378    device_info: DeviceFileInfo,
1379    /// File ID
1380    file_id: u64,
1381    /// Current position in the device (for seekable devices)
1382    position: Mutex<u64>,
1383    /// Weak reference to the filesystem
1384    filesystem: RwLock<Option<Weak<dyn FileSystemOperations>>>,
1385}
1386
1387impl Ext2CharDeviceFileObject {
1388    /// Create a new ext2 character device file object
1389    pub fn new(device_info: DeviceFileInfo, file_id: u64) -> Self {
1390        Self {
1391            device_info,
1392            file_id,
1393            position: Mutex::new(0),
1394            filesystem: RwLock::new(None),
1395        }
1396    }
1397
1398    /// Set the filesystem reference
1399    pub fn set_filesystem(&self, fs: Weak<dyn FileSystemOperations>) {
1400        *self.filesystem.write() = Some(fs);
1401    }
1402}
1403
1404impl StreamOps for Ext2CharDeviceFileObject {
1405    fn read(&self, buffer: &mut [u8]) -> Result<usize, StreamError> {
1406        #[cfg(test)]
1407        crate::early_println!(
1408            "[ext2] CharDevice read: device_id={}",
1409            self.device_info.device_id
1410        );
1411
1412        // Get the device from device manager
1413        let device = DeviceManager::get_manager()
1414            .get_device(self.device_info.device_id)
1415            .ok_or_else(|| {
1416                #[cfg(test)]
1417                crate::early_println!(
1418                    "[ext2] CharDevice: Device with ID {} not found in DeviceManager",
1419                    self.device_info.device_id
1420                );
1421                StreamError::NotSupported
1422            })?;
1423
1424        #[cfg(test)]
1425        crate::early_println!(
1426            "[ext2] CharDevice: Found device with ID {}",
1427            self.device_info.device_id
1428        );
1429
1430        // Try to cast to CharDevice
1431        if let Some(char_device) = device.as_char_device() {
1432            #[cfg(test)]
1433            crate::early_println!("[ext2] CharDevice: Successfully cast to CharDevice");
1434            // Use the CharDevice read method
1435            Ok(char_device.read(buffer))
1436        } else {
1437            #[cfg(test)]
1438            crate::early_println!("[ext2] CharDevice: Device is not a CharDevice");
1439            Err(StreamError::NotSupported)
1440        }
1441    }
1442
1443    fn write(&self, buffer: &[u8]) -> Result<usize, StreamError> {
1444        #[cfg(test)]
1445        crate::early_println!(
1446            "[ext2] CharDevice write: device_id={}, buffer_len={}",
1447            self.device_info.device_id,
1448            buffer.len()
1449        );
1450
1451        // Get the device from device manager
1452        let device = DeviceManager::get_manager()
1453            .get_device(self.device_info.device_id)
1454            .ok_or_else(|| {
1455                #[cfg(test)]
1456                crate::early_println!(
1457                    "[ext2] CharDevice: Device with ID {} not found in DeviceManager",
1458                    self.device_info.device_id
1459                );
1460                StreamError::NotSupported
1461            })?;
1462
1463        #[cfg(test)]
1464        crate::early_println!(
1465            "[ext2] CharDevice: Found device with ID {}",
1466            self.device_info.device_id
1467        );
1468
1469        // Try to cast to CharDevice
1470        if let Some(char_device) = device.as_char_device() {
1471            #[cfg(test)]
1472            crate::early_println!("[ext2] CharDevice: Successfully cast to CharDevice");
1473            // Use the CharDevice write method
1474            char_device.write(buffer).map_err(|_err| {
1475                #[cfg(test)]
1476                crate::early_println!("[ext2] CharDevice write error");
1477                StreamError::IoError
1478            })
1479        } else {
1480            #[cfg(test)]
1481            crate::early_println!("[ext2] CharDevice: Device is not a CharDevice");
1482            Err(StreamError::NotSupported)
1483        }
1484    }
1485}
1486
1487impl ControlOps for Ext2CharDeviceFileObject {
1488    fn control(&self, command: u32, arg: usize) -> Result<i32, &'static str> {
1489        // Character devices can support control operations
1490        // For now, return not supported
1491        let _ = (command, arg);
1492        Err("Control operation not supported")
1493    }
1494}
1495
1496impl MemoryMappingOps for Ext2CharDeviceFileObject {
1497    fn get_mapping_info(
1498        &self,
1499        _offset: usize,
1500        _length: usize,
1501    ) -> Result<(usize, usize, bool), &'static str> {
1502        // Most character devices don't support memory mapping
1503        Err("Memory mapping not supported")
1504    }
1505}
1506
1507impl FileObject for Ext2CharDeviceFileObject {
1508    fn metadata(&self) -> Result<FileMetadata, StreamError> {
1509        Ok(FileMetadata {
1510            file_type: FileType::CharDevice(self.device_info),
1511            size: 0, // Character devices don't have a meaningful size
1512            permissions: FilePermission {
1513                read: true,
1514                write: true,
1515                execute: false,
1516            },
1517            created_time: 0,
1518            modified_time: 0,
1519            accessed_time: 0,
1520            file_id: self.file_id,
1521            link_count: 1,
1522        })
1523    }
1524
1525    fn seek(&self, whence: SeekFrom) -> Result<u64, StreamError> {
1526        // Get the device to check if it supports seeking
1527        let device = DeviceManager::get_manager()
1528            .get_device(self.device_info.device_id)
1529            .ok_or(StreamError::NotSupported)?;
1530
1531        if let Some(char_device) = device.as_char_device() {
1532            if char_device.can_seek() {
1533                let mut pos = self.position.lock();
1534                match whence {
1535                    SeekFrom::Start(offset) => {
1536                        *pos = offset;
1537                        Ok(*pos)
1538                    }
1539                    SeekFrom::Current(offset) => {
1540                        if offset >= 0 {
1541                            *pos = (*pos).saturating_add(offset as u64);
1542                        } else {
1543                            *pos = (*pos).saturating_sub((-offset) as u64);
1544                        }
1545                        Ok(*pos)
1546                    }
1547                    SeekFrom::End(_) => {
1548                        // Most character devices don't have a meaningful end
1549                        Err(StreamError::NotSupported)
1550                    }
1551                }
1552            } else {
1553                Err(StreamError::NotSupported)
1554            }
1555        } else {
1556            Err(StreamError::NotSupported)
1557        }
1558    }
1559
1560    fn as_any(&self) -> &dyn Any {
1561        self
1562    }
1563}
1564
1565impl Selectable for Ext2CharDeviceFileObject {
1566    fn current_ready(&self, interest: ReadyInterest) -> ReadySet {
1567        // Delegate to underlying Device's Selectable implementation when available
1568        if let Some(device) = DeviceManager::get_manager().get_device(self.device_info.device_id) {
1569            return device.current_ready(interest);
1570        }
1571        // Fallback: conservative defaults (always ready, non-blocking)
1572        ReadySet {
1573            read: interest.read,
1574            write: interest.write,
1575            except: interest.except && false,
1576        }
1577    }
1578
1579    fn wait_until_ready(
1580        &self,
1581        interest: ReadyInterest,
1582        trapframe: &mut crate::arch::Trapframe,
1583        timeout_ticks: Option<u64>,
1584    ) -> SelectWaitOutcome {
1585        if let Some(device) = DeviceManager::get_manager().get_device(self.device_info.device_id) {
1586            return device.wait_until_ready(interest, trapframe, timeout_ticks);
1587        }
1588        // No device found: do not block
1589        SelectWaitOutcome::Ready
1590    }
1591}