1use 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#[derive(Debug)]
39pub struct Ext2Node {
40 inode_number: u32,
42 file_type: FileType,
44 file_id: u64,
46 filesystem: RwLock<Option<Weak<dyn FileSystemOperations>>>,
48}
49
50impl Ext2Node {
51 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 pub fn inode_number(&self) -> u32 {
63 self.inode_number
64 }
65
66 pub fn set_filesystem(&self, fs: Weak<dyn FileSystemOperations>) {
68 *self.filesystem.write() = Some(fs);
69 }
70
71 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 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 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 if !matches!(self.file_type, FileType::SymbolicLink(_)) {
140 return Err(FileSystemError::new(
141 FileSystemErrorKind::NotSupported,
142 "Not a symbolic link",
143 ));
144 }
145
146 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 let inode = ext2_fs.read_inode(self.inode_number)?;
166 inode.read_symlink_target(ext2_fs)
167 }
168}
169
170#[derive(Debug)]
174pub struct Ext2FileObject {
175 inode_number: u32,
177 file_id: u64,
179 position: Mutex<u64>,
181 size_override: Mutex<Option<usize>>,
183 dirty: Mutex<bool>,
185 filesystem: RwLock<Option<Weak<dyn FileSystemOperations>>>,
187 mmap_backing: RwLock<Option<Box<[crate::mem::page::Page]>>>,
189 mmap_backing_len: Mutex<usize>,
191 mmap_ranges: RwLock<BTreeMap<usize, MmapRange>>,
193}
194
195impl Ext2FileObject {
196 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 pub fn set_filesystem(&self, fs: Weak<dyn FileSystemOperations>) {
213 *self.filesystem.write() = Some(fs);
214 }
215
216 pub fn file_id(&self) -> u64 {
218 self.file_id
219 }
220
221 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 let fs = self
717 .filesystem
718 .read()
719 .as_ref()
720 .and_then(|weak| weak.upgrade())
721 .ok_or(StreamError::Closed)?;
722
723 let ext2_fs = fs
725 .as_any()
726 .downcast_ref::<Ext2FileSystem>()
727 .ok_or(StreamError::NotSupported)?;
728
729 let inode = ext2_fs
731 .read_inode(self.inode_number)
732 .map_err(|_| StreamError::IoError)?;
733
734 let permissions = FilePermission {
736 read: (inode.mode & 0o444) != 0,
737 write: (inode.mode & 0o222) != 0,
738 execute: (inode.mode & 0o111) != 0,
739 };
740
741 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 };
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#[derive(Debug)]
1069pub struct Ext2DirectoryObject {
1070 inode_number: u32,
1072 file_id: u64,
1074 position: Mutex<u64>,
1076 filesystem: RwLock<Option<Weak<dyn FileSystemOperations>>>,
1078 cached_entries: Mutex<Option<Vec<crate::fs::DirectoryEntryInternal>>>,
1080 cache_generation: Mutex<u32>,
1082}
1083
1084impl Ext2DirectoryObject {
1085 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 pub fn set_filesystem(&self, fs: Weak<dyn FileSystemOperations>) {
1099 *self.filesystem.write() = Some(fs);
1100 }
1101
1102 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 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 {
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 let entries = match ext2_fs.read_directory_entries(¤t_inode) {
1137 Ok(entries) => entries,
1138 Err(_) => return Err(StreamError::IoError),
1139 };
1140
1141 let mut all_entries = Vec::new();
1143
1144 for entry in entries {
1145 if entry.entry.inode == 0 {
1146 continue; }
1148
1149 let inode_num = entry.entry.inode; let file_type = match entry.entry.file_type {
1152 1 => FileType::RegularFile, 2 => FileType::Directory, 3 => {
1155 let device_id = match ext2_fs.read_inode(inode_num) {
1159 Ok(inode) => {
1160 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 let device_id = match ext2_fs.read_inode(inode_num) {
1174 Ok(inode) => {
1175 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, 6 => FileType::Socket(SocketFileInfo {
1187 socket_id: crate::fs::UNBOUND_SOCKET_ID,
1188 }), 7 => {
1190 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, };
1202
1203 all_entries.push(crate::fs::DirectoryEntryInternal {
1204 name: entry.name,
1205 file_type,
1206 size: 0, file_id: inode_num as u64, metadata: None,
1209 });
1210 }
1211
1212 all_entries.sort_by_key(|entry| entry.file_id);
1214
1215 {
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 let all_entries = self.get_cached_entries()?;
1231
1232 let position = *self.position.lock() as usize;
1234
1235 if position >= all_entries.len() {
1236 return Ok(0); }
1238
1239 let internal_entry = &all_entries[position];
1241
1242 let dir_entry = crate::fs::DirectoryEntry::from_internal(internal_entry);
1244
1245 let entry_size = dir_entry.entry_size();
1247
1248 if buffer.len() < entry_size {
1250 return Err(StreamError::InvalidArgument); }
1252
1253 let entry_bytes =
1255 unsafe { core::slice::from_raw_parts(&dir_entry as *const _ as *const u8, entry_size) };
1256
1257 buffer[..entry_size].copy_from_slice(entry_bytes);
1259
1260 *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 let fs = self
1287 .filesystem
1288 .read()
1289 .as_ref()
1290 .and_then(|weak| weak.upgrade())
1291 .ok_or(StreamError::Closed)?;
1292
1293 let ext2_fs = fs
1295 .as_any()
1296 .downcast_ref::<Ext2FileSystem>()
1297 .ok_or(StreamError::NotSupported)?;
1298
1299 let inode = ext2_fs
1301 .read_inode(self.inode_number)
1302 .map_err(|_| StreamError::IoError)?;
1303
1304 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#[derive(Debug)]
1376pub struct Ext2CharDeviceFileObject {
1377 device_info: DeviceFileInfo,
1379 file_id: u64,
1381 position: Mutex<u64>,
1383 filesystem: RwLock<Option<Weak<dyn FileSystemOperations>>>,
1385}
1386
1387impl Ext2CharDeviceFileObject {
1388 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 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 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 if let Some(char_device) = device.as_char_device() {
1432 #[cfg(test)]
1433 crate::early_println!("[ext2] CharDevice: Successfully cast to CharDevice");
1434 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 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 if let Some(char_device) = device.as_char_device() {
1471 #[cfg(test)]
1472 crate::early_println!("[ext2] CharDevice: Successfully cast to CharDevice");
1473 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 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 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, 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 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 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 if let Some(device) = DeviceManager::get_manager().get_device(self.device_info.device_id) {
1569 return device.current_ready(interest);
1570 }
1571 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 SelectWaitOutcome::Ready
1590 }
1591}