1#![allow(dead_code)]
11
12use alloc::{collections::BTreeMap, string::String, sync::Arc, vec, vec::Vec};
13use core::sync::atomic::{AtomicU64, Ordering};
14
15#[cfg(not(target_arch = "aarch64"))]
16use spin::RwLock;
17
18#[cfg(target_arch = "aarch64")]
19use super::bare_lock::RwLock;
20use super::{DirEntry, Filesystem, Metadata, NodeType, Permissions, VfsNode};
21use crate::error::{FsError, KernelError};
22
23const EXT4_SUPER_MAGIC: u16 = 0xEF53;
25
26const SUPERBLOCK_OFFSET: usize = 1024;
28
29const EXT4_EXTENTS_FL: u32 = 0x0008_0000;
31
32const S_IFMT: u16 = 0xF000;
34const S_IFREG: u16 = 0x8000;
35const S_IFDIR: u16 = 0x4000;
36const S_IFLNK: u16 = 0xA000;
37
38const EXT4_FT_UNKNOWN: u8 = 0;
40const EXT4_FT_REG_FILE: u8 = 1;
41const EXT4_FT_DIR: u8 = 2;
42const EXT4_FT_SYMLINK: u8 = 7;
43
44const EXT4_ROOT_INO: u32 = 2;
46
47const EXT4_EXTENT_HEADER_SIZE: usize = 12;
49const EXT4_EXTENT_SIZE: usize = 12;
51const EXT4_EXTENT_IDX_SIZE: usize = 12;
53
54const EXT4_EXT_MAGIC: u16 = 0xF30A;
56
57static EXT4_NEXT_INODE: AtomicU64 = AtomicU64::new(1);
58
59#[derive(Clone)]
61struct Ext4Superblock {
62 inodes_count: u32,
63 blocks_count_lo: u32,
64 free_blocks_count_lo: u32,
65 free_inodes_count: u32,
66 first_data_block: u32,
67 log_block_size: u32,
68 blocks_per_group: u32,
69 inodes_per_group: u32,
70 magic: u16,
71 inode_size: u16,
72 feature_incompat: u32,
73 feature_ro_compat: u32,
74 desc_size: u16,
75 blocks_count_hi: u32,
76}
77
78impl Ext4Superblock {
79 fn parse(data: &[u8]) -> Result<Self, KernelError> {
80 if data.len() < 256 {
81 return Err(KernelError::FsError(FsError::CorruptedData));
82 }
83
84 let magic = u16::from_le_bytes([data[0x38], data[0x39]]);
85 if magic != EXT4_SUPER_MAGIC {
86 return Err(KernelError::FsError(FsError::CorruptedData));
87 }
88
89 let desc_size_raw = u16::from_le_bytes([data[0xFE], data[0xFF]]);
90 let desc_size = if desc_size_raw == 0 {
92 32
93 } else {
94 desc_size_raw
95 };
96
97 Ok(Self {
98 inodes_count: u32::from_le_bytes([data[0], data[1], data[2], data[3]]),
99 blocks_count_lo: u32::from_le_bytes([data[4], data[5], data[6], data[7]]),
100 free_blocks_count_lo: u32::from_le_bytes([
101 data[0x0C], data[0x0D], data[0x0E], data[0x0F],
102 ]),
103 free_inodes_count: u32::from_le_bytes([data[0x10], data[0x11], data[0x12], data[0x13]]),
104 first_data_block: u32::from_le_bytes([data[0x14], data[0x15], data[0x16], data[0x17]]),
105 log_block_size: u32::from_le_bytes([data[0x18], data[0x19], data[0x1A], data[0x1B]]),
106 blocks_per_group: u32::from_le_bytes([data[0x20], data[0x21], data[0x22], data[0x23]]),
107 inodes_per_group: u32::from_le_bytes([data[0x28], data[0x29], data[0x2A], data[0x2B]]),
108 magic,
109 inode_size: u16::from_le_bytes([data[0x58], data[0x59]]),
110 feature_incompat: u32::from_le_bytes([data[0x60], data[0x61], data[0x62], data[0x63]]),
111 feature_ro_compat: u32::from_le_bytes([data[0x64], data[0x65], data[0x66], data[0x67]]),
112 desc_size,
113 blocks_count_hi: u32::from_le_bytes([
114 data[0x150],
115 data[0x151],
116 data[0x152],
117 data[0x153],
118 ]),
119 })
120 }
121
122 fn block_size(&self) -> usize {
123 1024 << self.log_block_size
124 }
125
126 fn num_block_groups(&self) -> u32 {
127 self.blocks_count_lo.div_ceil(self.blocks_per_group)
128 }
129}
130
131#[derive(Clone)]
133struct BlockGroupDesc {
134 inode_table_lo: u32,
135 inode_table_hi: u32,
136}
137
138impl BlockGroupDesc {
139 fn parse(data: &[u8], desc_size: u16) -> Self {
140 let inode_table_lo = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
141 let inode_table_hi = if desc_size >= 64 && data.len() >= 44 {
142 u32::from_le_bytes([data[40], data[41], data[42], data[43]])
143 } else {
144 0
145 };
146 Self {
147 inode_table_lo,
148 inode_table_hi,
149 }
150 }
151
152 fn inode_table_block(&self) -> u64 {
153 self.inode_table_lo as u64 | ((self.inode_table_hi as u64) << 32)
154 }
155}
156
157struct Ext4Inode {
159 mode: u16,
160 size_lo: u32,
161 size_hi: u32,
162 atime: u32,
163 mtime: u32,
164 flags: u32,
165 block_data: [u8; 60],
167 links_count: u16,
168}
169
170impl Ext4Inode {
171 fn parse(data: &[u8]) -> Result<Self, KernelError> {
172 if data.len() < 128 {
173 return Err(KernelError::FsError(FsError::CorruptedData));
174 }
175
176 let mut block_data = [0u8; 60];
177 block_data.copy_from_slice(&data[0x28..0x64]);
178
179 Ok(Self {
180 mode: u16::from_le_bytes([data[0], data[1]]),
181 size_lo: u32::from_le_bytes([data[4], data[5], data[6], data[7]]),
182 size_hi: u32::from_le_bytes([data[0x6C], data[0x6D], data[0x6E], data[0x6F]]),
183 atime: u32::from_le_bytes([data[8], data[9], data[10], data[11]]),
184 mtime: u32::from_le_bytes([data[0x10], data[0x11], data[0x12], data[0x13]]),
185 flags: u32::from_le_bytes([data[0x20], data[0x21], data[0x22], data[0x23]]),
186 block_data,
187 links_count: u16::from_le_bytes([data[0x1A], data[0x1B]]),
188 })
189 }
190
191 fn size(&self) -> u64 {
192 self.size_lo as u64 | ((self.size_hi as u64) << 32)
193 }
194
195 fn is_dir(&self) -> bool {
196 (self.mode & S_IFMT) == S_IFDIR
197 }
198
199 fn is_file(&self) -> bool {
200 (self.mode & S_IFMT) == S_IFREG
201 }
202
203 fn is_symlink(&self) -> bool {
204 (self.mode & S_IFMT) == S_IFLNK
205 }
206
207 fn uses_extents(&self) -> bool {
208 (self.flags & EXT4_EXTENTS_FL) != 0
209 }
210
211 fn node_type(&self) -> NodeType {
212 if self.is_dir() {
213 NodeType::Directory
214 } else if self.is_symlink() {
215 NodeType::Symlink
216 } else {
217 NodeType::File
218 }
219 }
220
221 fn permissions(&self) -> Permissions {
222 let mode = self.mode;
223 Permissions {
224 owner_read: (mode & 0o400) != 0,
225 owner_write: (mode & 0o200) != 0,
226 owner_exec: (mode & 0o100) != 0,
227 group_read: (mode & 0o040) != 0,
228 group_write: (mode & 0o020) != 0,
229 group_exec: (mode & 0o010) != 0,
230 other_read: (mode & 0o004) != 0,
231 other_write: (mode & 0o002) != 0,
232 other_exec: (mode & 0o001) != 0,
233 }
234 }
235}
236
237struct ExtentHeader {
239 magic: u16,
240 entries: u16,
241 _max: u16,
242 depth: u16,
243}
244
245impl ExtentHeader {
246 fn parse(data: &[u8]) -> Result<Self, KernelError> {
247 if data.len() < EXT4_EXTENT_HEADER_SIZE {
248 return Err(KernelError::FsError(FsError::CorruptedData));
249 }
250 let magic = u16::from_le_bytes([data[0], data[1]]);
251 if magic != EXT4_EXT_MAGIC {
252 return Err(KernelError::FsError(FsError::CorruptedData));
253 }
254 Ok(Self {
255 magic,
256 entries: u16::from_le_bytes([data[2], data[3]]),
257 _max: u16::from_le_bytes([data[4], data[5]]),
258 depth: u16::from_le_bytes([data[6], data[7]]),
259 })
260 }
261}
262
263struct Extent {
265 block: u32,
267 len: u16,
269 start_hi: u16,
271 start_lo: u32,
273}
274
275impl Extent {
276 fn parse(data: &[u8]) -> Self {
277 Self {
278 block: u32::from_le_bytes([data[0], data[1], data[2], data[3]]),
279 len: u16::from_le_bytes([data[4], data[5]]),
280 start_hi: u16::from_le_bytes([data[6], data[7]]),
281 start_lo: u32::from_le_bytes([data[8], data[9], data[10], data[11]]),
282 }
283 }
284
285 fn physical_block(&self) -> u64 {
286 self.start_lo as u64 | ((self.start_hi as u64) << 32)
287 }
288
289 fn actual_len(&self) -> u32 {
291 (self.len & 0x7FFF) as u32
292 }
293}
294
295struct ExtentIdx {
297 block: u32,
299 leaf_lo: u32,
301 leaf_hi: u16,
302}
303
304impl ExtentIdx {
305 fn parse(data: &[u8]) -> Self {
306 Self {
307 block: u32::from_le_bytes([data[0], data[1], data[2], data[3]]),
308 leaf_lo: u32::from_le_bytes([data[4], data[5], data[6], data[7]]),
309 leaf_hi: u16::from_le_bytes([data[8], data[9]]),
310 }
311 }
312
313 fn child_block(&self) -> u64 {
314 self.leaf_lo as u64 | ((self.leaf_hi as u64) << 32)
315 }
316}
317
318struct Ext4State {
320 sb: Ext4Superblock,
321 group_descs: Vec<BlockGroupDesc>,
322 data: Vec<u8>,
324}
325
326impl Ext4State {
327 fn read_block(&self, block: u64) -> Option<&[u8]> {
329 let bs = self.sb.block_size();
330 let offset = block as usize * bs;
331 let end = offset + bs;
332 if end <= self.data.len() {
333 Some(&self.data[offset..end])
334 } else {
335 None
336 }
337 }
338
339 fn read_inode(&self, ino: u32) -> Result<Ext4Inode, KernelError> {
341 if ino == 0 || ino > self.sb.inodes_count {
342 return Err(KernelError::FsError(FsError::NotFound));
343 }
344
345 let group = ((ino - 1) / self.sb.inodes_per_group) as usize;
346 let index = ((ino - 1) % self.sb.inodes_per_group) as usize;
347
348 if group >= self.group_descs.len() {
349 return Err(KernelError::FsError(FsError::CorruptedData));
350 }
351
352 let table_block = self.group_descs[group].inode_table_block();
353 let bs = self.sb.block_size();
354 let inode_size = self.sb.inode_size as usize;
355 let offset = table_block as usize * bs + index * inode_size;
356
357 if offset + inode_size > self.data.len() {
358 return Err(KernelError::FsError(FsError::CorruptedData));
359 }
360
361 Ext4Inode::parse(&self.data[offset..offset + inode_size])
362 }
363
364 fn read_inode_data(&self, inode: &Ext4Inode, max_size: u64) -> Vec<u8> {
366 let size = core::cmp::min(inode.size(), max_size);
367 if size == 0 {
368 return Vec::new();
369 }
370
371 if inode.is_symlink() && size < 60 {
373 return inode.block_data[..size as usize].to_vec();
374 }
375
376 if inode.uses_extents() {
377 self.read_extent_data(&inode.block_data, size)
378 } else {
379 self.read_block_map_data(&inode.block_data, size)
380 }
381 }
382
383 fn read_extent_data(&self, block_data: &[u8; 60], size: u64) -> Vec<u8> {
385 let mut result = vec![0u8; size as usize];
386 let bs = self.sb.block_size();
387
388 self.walk_extent_tree(block_data, &mut result, bs);
390
391 result
392 }
393
394 fn walk_extent_tree(&self, tree_data: &[u8], result: &mut [u8], bs: usize) {
396 let header = match ExtentHeader::parse(tree_data) {
397 Ok(h) => h,
398 Err(_) => return,
399 };
400
401 if header.magic != EXT4_EXT_MAGIC {
402 return;
403 }
404
405 let entries = header.entries as usize;
406
407 if header.depth == 0 {
408 for i in 0..entries {
410 let off = EXT4_EXTENT_HEADER_SIZE + i * EXT4_EXTENT_SIZE;
411 if off + EXT4_EXTENT_SIZE > tree_data.len() {
412 break;
413 }
414 let extent = Extent::parse(&tree_data[off..]);
415 let file_offset = extent.block as usize * bs;
416 let phys_block = extent.physical_block();
417 let len = extent.actual_len() as usize;
418
419 for b in 0..len {
420 let src_block = phys_block + b as u64;
421 let dst_offset = file_offset + b * bs;
422 if dst_offset >= result.len() {
423 break;
424 }
425
426 if let Some(block_data) = self.read_block(src_block) {
427 let copy_len = core::cmp::min(bs, result.len() - dst_offset);
428 result[dst_offset..dst_offset + copy_len]
429 .copy_from_slice(&block_data[..copy_len]);
430 }
431 }
432 }
433 } else {
434 for i in 0..entries {
436 let off = EXT4_EXTENT_HEADER_SIZE + i * EXT4_EXTENT_SIZE;
437 if off + EXT4_EXTENT_SIZE > tree_data.len() {
438 break;
439 }
440 let idx = ExtentIdx::parse(&tree_data[off..]);
441 if let Some(child_block) = self.read_block(idx.child_block()) {
442 let child_len = core::cmp::min(child_block.len(), bs);
444 self.walk_extent_tree(&child_block[..child_len], result, bs);
445 }
446 }
447 }
448 }
449
450 fn read_block_map_data(&self, block_data: &[u8; 60], size: u64) -> Vec<u8> {
452 let mut result = vec![0u8; size as usize];
453 let bs = self.sb.block_size();
454 let mut file_offset = 0usize;
455
456 for i in 0..12 {
458 if file_offset >= result.len() {
459 return result;
460 }
461 let block_num = u32::from_le_bytes([
462 block_data[i * 4],
463 block_data[i * 4 + 1],
464 block_data[i * 4 + 2],
465 block_data[i * 4 + 3],
466 ]);
467 if block_num != 0 {
468 if let Some(bdata) = self.read_block(block_num as u64) {
469 let copy_len = core::cmp::min(bs, result.len() - file_offset);
470 result[file_offset..file_offset + copy_len].copy_from_slice(&bdata[..copy_len]);
471 }
472 }
473 file_offset += bs;
474 }
475
476 let indirect = u32::from_le_bytes([
478 block_data[48],
479 block_data[49],
480 block_data[50],
481 block_data[51],
482 ]);
483 if indirect != 0 && file_offset < result.len() {
484 file_offset =
485 self.read_indirect_blocks(indirect as u64, &mut result, file_offset, bs, 0);
486 }
487
488 let dindirect = u32::from_le_bytes([
490 block_data[52],
491 block_data[53],
492 block_data[54],
493 block_data[55],
494 ]);
495 if dindirect != 0 && file_offset < result.len() {
496 file_offset =
497 self.read_indirect_blocks(dindirect as u64, &mut result, file_offset, bs, 1);
498 }
499
500 let tindirect = u32::from_le_bytes([
502 block_data[56],
503 block_data[57],
504 block_data[58],
505 block_data[59],
506 ]);
507 if tindirect != 0 && file_offset < result.len() {
508 let _ = self.read_indirect_blocks(tindirect as u64, &mut result, file_offset, bs, 2);
509 }
510
511 result
512 }
513
514 fn read_indirect_blocks(
516 &self,
517 block: u64,
518 result: &mut [u8],
519 mut offset: usize,
520 bs: usize,
521 level: u32,
522 ) -> usize {
523 let bdata = match self.read_block(block) {
524 Some(d) => d,
525 None => return offset,
526 };
527
528 let ptrs_per_block = bs / 4;
529 for i in 0..ptrs_per_block {
530 if offset >= result.len() {
531 break;
532 }
533 let ptr = u32::from_le_bytes([
534 bdata[i * 4],
535 bdata[i * 4 + 1],
536 bdata[i * 4 + 2],
537 bdata[i * 4 + 3],
538 ]);
539 if ptr == 0 {
540 if level == 0 {
541 offset += bs;
542 }
543 continue;
544 }
545
546 if level == 0 {
547 if let Some(data_block) = self.read_block(ptr as u64) {
549 let copy_len = core::cmp::min(bs, result.len() - offset);
550 result[offset..offset + copy_len].copy_from_slice(&data_block[..copy_len]);
551 }
552 offset += bs;
553 } else {
554 offset = self.read_indirect_blocks(ptr as u64, result, offset, bs, level - 1);
556 }
557 }
558 offset
559 }
560
561 fn parse_directory(&self, data: &[u8]) -> Vec<(String, u32, u8)> {
563 let mut entries = Vec::new();
564 let mut pos = 0;
565
566 while pos + 8 <= data.len() {
567 let inode =
568 u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
569 let rec_len = u16::from_le_bytes([data[pos + 4], data[pos + 5]]) as usize;
570 let name_len = data[pos + 6] as usize;
571 let file_type = data[pos + 7];
572
573 if rec_len == 0 {
574 break;
575 }
576
577 if inode != 0 && name_len > 0 && pos + 8 + name_len <= data.len() {
578 let name_bytes = &data[pos + 8..pos + 8 + name_len];
579 if let Ok(name) = core::str::from_utf8(name_bytes) {
580 if name != "." && name != ".." {
581 entries.push((String::from(name), inode, file_type));
582 }
583 }
584 }
585
586 pos += rec_len;
587 }
588
589 entries
590 }
591}
592
593pub struct Ext4Node {
595 vfs_inode: u64,
596 ext4_ino: u32,
597 node_type: NodeType,
598 metadata: RwLock<Metadata>,
599 children: RwLock<BTreeMap<String, Arc<Ext4Node>>>,
600 fs_state: Arc<RwLock<Ext4State>>,
601 link_target: RwLock<Option<String>>,
603}
604
605impl Ext4Node {
606 fn new(
607 ext4_ino: u32,
608 inode: &Ext4Inode,
609 _parent_inode: u64,
610 fs_state: Arc<RwLock<Ext4State>>,
611 ) -> Self {
612 let vfs_inode = EXT4_NEXT_INODE.fetch_add(1, Ordering::Relaxed);
613 Self {
614 vfs_inode,
615 ext4_ino,
616 node_type: inode.node_type(),
617 metadata: RwLock::new(Metadata {
618 inode: vfs_inode,
619 size: inode.size() as usize,
620 node_type: inode.node_type(),
621 permissions: inode.permissions(),
622 created: 0,
623 modified: inode.mtime as u64,
624 accessed: 0,
625 uid: 0,
626 gid: 0,
627 }),
628 children: RwLock::new(BTreeMap::new()),
629 fs_state,
630 link_target: RwLock::new(None),
631 }
632 }
633
634 fn ensure_children_loaded(&self) {
636 if self.node_type != NodeType::Directory {
637 return;
638 }
639
640 {
641 let children = self.children.read();
642 if !children.is_empty() {
643 return;
644 }
645 }
646
647 let entries = {
648 let state = self.fs_state.read();
649 let inode = match state.read_inode(self.ext4_ino) {
650 Ok(i) => i,
651 Err(_) => return,
652 };
653 let dir_data = state.read_inode_data(&inode, inode.size());
654 let parsed = state.parse_directory(&dir_data);
655
656 let mut result = Vec::new();
658 for (name, ino, ft) in parsed {
659 if let Ok(child_inode) = state.read_inode(ino) {
660 result.push((name, ino, child_inode, ft));
661 }
662 }
663 result
664 };
665
666 let mut children = self.children.write();
667 for (name, ino, child_inode, _ft) in entries {
668 let child = Arc::new(Ext4Node::new(
669 ino,
670 &child_inode,
671 self.vfs_inode,
672 self.fs_state.clone(),
673 ));
674 children.insert(name, child);
675 }
676 }
677
678 fn ensure_link_loaded(&self) {
680 if self.node_type != NodeType::Symlink {
681 return;
682 }
683
684 {
685 let link = self.link_target.read();
686 if link.is_some() {
687 return;
688 }
689 }
690
691 let target = {
692 let state = self.fs_state.read();
693 match state.read_inode(self.ext4_ino) {
694 Ok(inode) => {
695 let data = state.read_inode_data(&inode, inode.size());
696 String::from_utf8_lossy(&data).into_owned()
697 }
698 Err(_) => return,
699 }
700 };
701
702 let mut link = self.link_target.write();
703 *link = Some(target);
704 }
705}
706
707impl VfsNode for Ext4Node {
708 fn node_type(&self) -> NodeType {
709 self.node_type
710 }
711
712 fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<usize, KernelError> {
713 if self.node_type != NodeType::File {
714 return Err(KernelError::FsError(FsError::NotAFile));
715 }
716
717 let meta = self.metadata.read();
718 if offset >= meta.size {
719 return Ok(0);
720 }
721
722 let file_data = {
723 let state = self.fs_state.read();
724 let inode = state.read_inode(self.ext4_ino)?;
725 state.read_inode_data(&inode, inode.size())
726 };
727
728 let bytes_to_read = core::cmp::min(buffer.len(), file_data.len().saturating_sub(offset));
729 buffer[..bytes_to_read].copy_from_slice(&file_data[offset..offset + bytes_to_read]);
730
731 Ok(bytes_to_read)
732 }
733
734 fn write(&self, _offset: usize, _data: &[u8]) -> Result<usize, KernelError> {
735 Err(KernelError::FsError(FsError::ReadOnly))
736 }
737
738 fn metadata(&self) -> Result<Metadata, KernelError> {
739 Ok(self.metadata.read().clone())
740 }
741
742 fn readdir(&self) -> Result<Vec<DirEntry>, KernelError> {
743 if self.node_type != NodeType::Directory {
744 return Err(KernelError::FsError(FsError::NotADirectory));
745 }
746
747 self.ensure_children_loaded();
748 let children = self.children.read();
749
750 let entries = children
751 .iter()
752 .map(|(name, node)| DirEntry {
753 name: name.clone(),
754 inode: node.vfs_inode,
755 node_type: node.node_type,
756 })
757 .collect();
758
759 Ok(entries)
760 }
761
762 fn lookup(&self, name: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
763 if self.node_type != NodeType::Directory {
764 return Err(KernelError::FsError(FsError::NotADirectory));
765 }
766
767 self.ensure_children_loaded();
768 let children = self.children.read();
769 children
770 .get(name)
771 .map(|n| n.clone() as Arc<dyn VfsNode>)
772 .ok_or(KernelError::FsError(FsError::NotFound))
773 }
774
775 fn create(
776 &self,
777 _name: &str,
778 _permissions: Permissions,
779 ) -> Result<Arc<dyn VfsNode>, KernelError> {
780 Err(KernelError::FsError(FsError::ReadOnly))
781 }
782
783 fn mkdir(
784 &self,
785 _name: &str,
786 _permissions: Permissions,
787 ) -> Result<Arc<dyn VfsNode>, KernelError> {
788 Err(KernelError::FsError(FsError::ReadOnly))
789 }
790
791 fn unlink(&self, _name: &str) -> Result<(), KernelError> {
792 Err(KernelError::FsError(FsError::ReadOnly))
793 }
794
795 fn truncate(&self, _size: usize) -> Result<(), KernelError> {
796 Err(KernelError::FsError(FsError::ReadOnly))
797 }
798
799 fn readlink(&self) -> Result<String, KernelError> {
800 if self.node_type != NodeType::Symlink {
801 return Err(KernelError::FsError(FsError::NotASymlink));
802 }
803 self.ensure_link_loaded();
804 let link = self.link_target.read();
805 link.clone().ok_or(KernelError::FsError(FsError::IoError))
806 }
807
808 fn chmod(&self, _permissions: Permissions) -> Result<(), KernelError> {
809 Err(KernelError::FsError(FsError::ReadOnly))
810 }
811}
812
813pub struct Ext4Fs {
815 root: Arc<Ext4Node>,
816 state: Arc<RwLock<Ext4State>>,
817}
818
819impl Ext4Fs {
820 pub fn from_image(image_data: &[u8]) -> Result<Self, KernelError> {
822 if image_data.len() < SUPERBLOCK_OFFSET + 256 {
823 return Err(KernelError::FsError(FsError::CorruptedData));
824 }
825
826 let sb = Ext4Superblock::parse(&image_data[SUPERBLOCK_OFFSET..])?;
827 let bs = sb.block_size();
828
829 let gd_block = if bs == 1024 { 2 } else { 1 };
832 let gd_offset = gd_block * bs;
833 let num_groups = sb.num_block_groups() as usize;
834 let desc_size = sb.desc_size as usize;
835
836 let mut group_descs = Vec::with_capacity(num_groups);
837 for i in 0..num_groups {
838 let off = gd_offset + i * desc_size;
839 if off + desc_size > image_data.len() {
840 break;
841 }
842 group_descs.push(BlockGroupDesc::parse(
843 &image_data[off..off + desc_size],
844 sb.desc_size,
845 ));
846 }
847
848 let state = Arc::new(RwLock::new(Ext4State {
849 sb: sb.clone(),
850 group_descs,
851 data: image_data.to_vec(),
852 }));
853
854 let root_inode = {
856 let st = state.read();
857 st.read_inode(EXT4_ROOT_INO)?
858 };
859
860 let root = Arc::new(Ext4Node::new(EXT4_ROOT_INO, &root_inode, 0, state.clone()));
861
862 Ok(Self { root, state })
863 }
864}
865
866impl Filesystem for Ext4Fs {
867 fn root(&self) -> Arc<dyn VfsNode> {
868 self.root.clone() as Arc<dyn VfsNode>
869 }
870
871 fn name(&self) -> &str {
872 "ext4"
873 }
874
875 fn is_readonly(&self) -> bool {
876 true
877 }
878
879 fn sync(&self) -> Result<(), KernelError> {
880 Ok(()) }
882}
883
884#[cfg(test)]
885mod tests {
886 use super::*;
887
888 fn make_test_ext4_image() -> Vec<u8> {
894 let block_size = 1024usize;
895 let total_blocks = 64u32;
896 let total_size = total_blocks as usize * block_size;
897 let mut img = vec![0u8; total_size];
898
899 let sb_off = SUPERBLOCK_OFFSET;
901 let inodes_count = 16u32;
902 let blocks_per_group = total_blocks;
903 let inodes_per_group = inodes_count;
904 let inode_size = 128u16;
905
906 img[sb_off..sb_off + 4].copy_from_slice(&inodes_count.to_le_bytes());
908 img[sb_off + 0x04..sb_off + 0x08].copy_from_slice(&total_blocks.to_le_bytes());
910 img[sb_off + 0x14..sb_off + 0x18].copy_from_slice(&1u32.to_le_bytes());
912 img[sb_off + 0x18..sb_off + 0x1C].copy_from_slice(&0u32.to_le_bytes());
914 img[sb_off + 0x20..sb_off + 0x24].copy_from_slice(&blocks_per_group.to_le_bytes());
916 img[sb_off + 0x28..sb_off + 0x2C].copy_from_slice(&inodes_per_group.to_le_bytes());
918 img[sb_off + 0x38..sb_off + 0x3A].copy_from_slice(&EXT4_SUPER_MAGIC.to_le_bytes());
920 img[sb_off + 0x58..sb_off + 0x5A].copy_from_slice(&inode_size.to_le_bytes());
922 img[sb_off + 0xFE..sb_off + 0x100].copy_from_slice(&0u16.to_le_bytes());
924
925 let gd_off = 2 * block_size;
927 let inode_table_block = 4u32;
929 img[gd_off + 8..gd_off + 12].copy_from_slice(&inode_table_block.to_le_bytes());
930
931 let it_off = inode_table_block as usize * block_size;
933
934 let root_off = it_off + 1 * inode_size as usize;
936 img[root_off..root_off + 2].copy_from_slice(&0x41EDu16.to_le_bytes());
938 img[root_off + 4..root_off + 8].copy_from_slice(&(block_size as u32).to_le_bytes());
940 img[root_off + 0x20..root_off + 0x24].copy_from_slice(&0u32.to_le_bytes());
942 let root_data_block = 10u32;
944 img[root_off + 0x28..root_off + 0x2C].copy_from_slice(&root_data_block.to_le_bytes());
945
946 let file_content = b"Hello, ext4!\n";
948 let file_off = it_off + 2 * inode_size as usize;
949 img[file_off..file_off + 2].copy_from_slice(&0x81A4u16.to_le_bytes());
951 img[file_off + 4..file_off + 8].copy_from_slice(&(file_content.len() as u32).to_le_bytes());
953 img[file_off + 0x20..file_off + 0x24].copy_from_slice(&0u32.to_le_bytes());
955 let file_data_block = 11u32;
957 img[file_off + 0x28..file_off + 0x2C].copy_from_slice(&file_data_block.to_le_bytes());
958
959 let link_target = b"hello.txt";
961 let link_off = it_off + 3 * inode_size as usize;
962 img[link_off..link_off + 2].copy_from_slice(&0xA1FFu16.to_le_bytes());
964 img[link_off + 4..link_off + 8].copy_from_slice(&(link_target.len() as u32).to_le_bytes());
966 img[link_off + 0x28..link_off + 0x28 + link_target.len()].copy_from_slice(link_target);
968
969 let dir_off = root_data_block as usize * block_size;
971
972 let mut pos = dir_off;
974 img[pos..pos + 4].copy_from_slice(&2u32.to_le_bytes()); img[pos + 4..pos + 6].copy_from_slice(&12u16.to_le_bytes()); img[pos + 6] = 1; img[pos + 7] = EXT4_FT_DIR; img[pos + 8] = b'.';
979 pos += 12;
980
981 img[pos..pos + 4].copy_from_slice(&2u32.to_le_bytes());
983 img[pos + 4..pos + 6].copy_from_slice(&12u16.to_le_bytes());
984 img[pos + 6] = 2;
985 img[pos + 7] = EXT4_FT_DIR;
986 img[pos + 8] = b'.';
987 img[pos + 9] = b'.';
988 pos += 12;
989
990 let name = b"hello.txt";
992 img[pos..pos + 4].copy_from_slice(&3u32.to_le_bytes());
993 img[pos + 4..pos + 6].copy_from_slice(&20u16.to_le_bytes()); img[pos + 6] = name.len() as u8;
995 img[pos + 7] = EXT4_FT_REG_FILE;
996 img[pos + 8..pos + 8 + name.len()].copy_from_slice(name);
997 pos += 20;
998
999 let link_name = b"link";
1001 let rec_len = (block_size - (pos - dir_off)) as u16;
1003 img[pos..pos + 4].copy_from_slice(&4u32.to_le_bytes());
1004 img[pos + 4..pos + 6].copy_from_slice(&rec_len.to_le_bytes());
1005 img[pos + 6] = link_name.len() as u8;
1006 img[pos + 7] = EXT4_FT_SYMLINK;
1007 img[pos + 8..pos + 8 + link_name.len()].copy_from_slice(link_name);
1008
1009 let fdata_off = file_data_block as usize * block_size;
1011 img[fdata_off..fdata_off + file_content.len()].copy_from_slice(file_content);
1012
1013 img
1014 }
1015
1016 #[test]
1017 fn test_superblock_parse() {
1018 let img = make_test_ext4_image();
1019 let sb = Ext4Superblock::parse(&img[SUPERBLOCK_OFFSET..]).unwrap();
1020 assert_eq!(sb.magic, EXT4_SUPER_MAGIC);
1021 assert_eq!(sb.block_size(), 1024);
1022 assert_eq!(sb.inodes_count, 16);
1023 assert_eq!(sb.inode_size, 128);
1024 }
1025
1026 #[test]
1027 fn test_mount_and_list_root() {
1028 let img = make_test_ext4_image();
1029 let fs = Ext4Fs::from_image(&img).unwrap();
1030 assert_eq!(fs.name(), "ext4");
1031 assert!(fs.is_readonly());
1032
1033 let root = fs.root();
1034 assert_eq!(root.node_type(), NodeType::Directory);
1035
1036 let entries = root.readdir().unwrap();
1037 let names: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect();
1038 assert!(names.contains(&"hello.txt"));
1039 assert!(names.contains(&"link"));
1040 }
1041
1042 #[test]
1043 fn test_read_file() {
1044 let img = make_test_ext4_image();
1045 let fs = Ext4Fs::from_image(&img).unwrap();
1046 let root = fs.root();
1047
1048 let file = root.lookup("hello.txt").unwrap();
1049 assert_eq!(file.node_type(), NodeType::File);
1050
1051 let mut buf = [0u8; 64];
1052 let n = file.read(0, &mut buf).unwrap();
1053 assert_eq!(&buf[..n], b"Hello, ext4!\n");
1054 }
1055
1056 #[test]
1057 fn test_read_symlink() {
1058 let img = make_test_ext4_image();
1059 let fs = Ext4Fs::from_image(&img).unwrap();
1060 let root = fs.root();
1061
1062 let link = root.lookup("link").unwrap();
1063 assert_eq!(link.node_type(), NodeType::Symlink);
1064
1065 let target = link.readlink().unwrap();
1066 assert_eq!(target, "hello.txt");
1067 }
1068
1069 #[test]
1070 fn test_read_only_enforcement() {
1071 let img = make_test_ext4_image();
1072 let fs = Ext4Fs::from_image(&img).unwrap();
1073 let root = fs.root();
1074
1075 assert!(root.write(0, b"test").is_err());
1076 assert!(root.create("new", Permissions::default()).is_err());
1077 assert!(root.mkdir("dir", Permissions::default()).is_err());
1078 assert!(root.unlink("hello.txt").is_err());
1079 assert!(root.truncate(0).is_err());
1080 }
1081
1082 #[test]
1083 fn test_metadata() {
1084 let img = make_test_ext4_image();
1085 let fs = Ext4Fs::from_image(&img).unwrap();
1086 let root = fs.root();
1087
1088 let file = root.lookup("hello.txt").unwrap();
1089 let meta = file.metadata().unwrap();
1090 assert_eq!(meta.size, 13); assert_eq!(meta.node_type, NodeType::File);
1092 assert!(meta.permissions.owner_read);
1093 assert!(meta.permissions.owner_write);
1094 }
1095
1096 #[test]
1097 fn test_invalid_magic() {
1098 let mut img = vec![0u8; 2048];
1099 assert!(Ext4Fs::from_image(&img).is_err());
1101
1102 img[SUPERBLOCK_OFFSET + 0x38] = 0xFF;
1104 img[SUPERBLOCK_OFFSET + 0x39] = 0xFF;
1105 assert!(Ext4Fs::from_image(&img).is_err());
1106 }
1107
1108 #[test]
1109 fn test_file_not_found() {
1110 let img = make_test_ext4_image();
1111 let fs = Ext4Fs::from_image(&img).unwrap();
1112 let root = fs.root();
1113
1114 assert!(root.lookup("nonexistent").is_err());
1115 }
1116
1117 #[test]
1118 fn test_read_at_offset() {
1119 let img = make_test_ext4_image();
1120 let fs = Ext4Fs::from_image(&img).unwrap();
1121 let root = fs.root();
1122
1123 let file = root.lookup("hello.txt").unwrap();
1124 let mut buf = [0u8; 64];
1125 let n = file.read(7, &mut buf).unwrap();
1126 assert_eq!(&buf[..n], b"ext4!\n");
1127 }
1128
1129 #[test]
1130 fn test_read_past_end() {
1131 let img = make_test_ext4_image();
1132 let fs = Ext4Fs::from_image(&img).unwrap();
1133 let root = fs.root();
1134
1135 let file = root.lookup("hello.txt").unwrap();
1136 let mut buf = [0u8; 64];
1137 let n = file.read(100, &mut buf).unwrap();
1138 assert_eq!(n, 0);
1139 }
1140}