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

1//! ext2 data structures
2//!
3//! This module defines the on-disk data structures used by the ext2 filesystem.
4//! All structures are packed and follow the ext2 filesystem specification.
5
6use crate::fs::{FileSystemError, FileSystemErrorKind};
7use alloc::{boxed::Box, format, string::String, vec};
8use core::mem;
9
10/// ext2 magic number
11pub const EXT2_SUPER_MAGIC: u16 = 0xEF53;
12
13/// ext2 root inode number
14pub const EXT2_ROOT_INO: u32 = 2;
15
16/// ext2 file type constants for inode mode field
17pub const EXT2_S_IFMT: u16 = 0xF000; // File type mask
18pub const EXT2_S_IFREG: u16 = 0x8000; // Regular file
19pub const EXT2_S_IFDIR: u16 = 0x4000; // Directory
20pub const EXT2_S_IFLNK: u16 = 0xA000; // Symbolic link
21pub const EXT2_S_IFCHR: u16 = 0x2000; // Character device
22pub const EXT2_S_IFBLK: u16 = 0x6000; // Block device
23pub const EXT2_S_IFIFO: u16 = 0x1000; // FIFO (pipe)
24pub const EXT2_S_IFSOCK: u16 = 0xC000; // Socket
25
26/// ext2 Superblock structure
27///
28/// This structure represents the superblock of an ext2 filesystem.
29/// It contains essential information about the filesystem layout and parameters.
30#[derive(Debug, Clone, Copy)]
31#[repr(C, packed)]
32pub struct Ext2Superblock {
33    /// Total number of inodes
34    pub inodes_count: u32,
35    /// Total number of blocks
36    pub blocks_count: u32,
37    /// Number of blocks reserved for superuser
38    pub r_blocks_count: u32,
39    /// Number of free blocks
40    pub free_blocks_count: u32,
41    /// Number of free inodes
42    pub free_inodes_count: u32,
43    /// First data block (0 for 1K blocks, 1 for larger blocks)
44    pub first_data_block: u32,
45    /// Block size (log2(block_size) - 10)
46    pub log_block_size: u32,
47    /// Fragment size (log2(fragment_size) - 10)
48    pub log_frag_size: u32,
49    /// Number of blocks per group
50    pub blocks_per_group: u32,
51    /// Number of fragments per group
52    pub frags_per_group: u32,
53    /// Number of inodes per group
54    pub inodes_per_group: u32,
55    /// Mount time
56    pub mtime: u32,
57    /// Write time
58    pub wtime: u32,
59    /// Mount count
60    pub mnt_count: u16,
61    /// Maximum mount count
62    pub max_mnt_count: u16,
63    /// Magic signature
64    pub magic: u16,
65    /// File system state
66    pub state: u16,
67    /// Behavior when detecting errors
68    pub errors: u16,
69    /// Minor revision level
70    pub minor_rev_level: u16,
71    /// Time of last check
72    pub lastcheck: u32,
73    /// Maximum time between checks
74    pub checkinterval: u32,
75    /// Creator OS
76    pub creator_os: u32,
77    /// Revision level
78    pub rev_level: u32,
79    /// Default uid for reserved blocks
80    pub def_resuid: u16,
81    /// Default gid for reserved blocks
82    pub def_resgid: u16,
83    /// First non-reserved inode
84    pub first_ino: u32,
85    /// Size of inode structure
86    pub inode_size: u16,
87    /// Block group this superblock is part of
88    pub block_group_nr: u16,
89    /// Compatible feature set
90    pub feature_compat: u32,
91    /// Incompatible feature set
92    pub feature_incompat: u32,
93    /// Read-only feature set
94    pub feature_ro_compat: u32,
95    /// 128-bit UUID for volume
96    pub uuid: [u8; 16],
97    /// Volume name
98    pub volume_name: [u8; 16],
99    /// Directory where last mounted
100    pub last_mounted: [u8; 64],
101    /// Algorithm usage bitmap
102    pub algorithm_usage_bitmap: u32,
103    /// Number of blocks to try to preallocate for files
104    pub prealloc_blocks: u8,
105    /// Number of blocks to preallocate for directories
106    pub prealloc_dir_blocks: u8,
107    /// Padding to 1024 bytes
108    pub padding: [u8; 1024 - 204],
109}
110
111impl Ext2Superblock {
112    /// Parse superblock from raw bytes using unsafe type conversion for efficiency
113    pub fn from_bytes(data: &[u8]) -> Result<Self, FileSystemError> {
114        // Standard ext2 superblock is 1024 bytes, but our struct might be slightly larger due to alignment
115        if data.len() < 1024 {
116            return Err(FileSystemError::new(
117                FileSystemErrorKind::InvalidData,
118                format!(
119                    "Insufficient data for ext2 superblock: got {} bytes, need at least 1024 bytes",
120                    data.len()
121                ),
122            ));
123        }
124
125        // Use unsafe cast for efficiency since ext2 structures are packed and have fixed layout
126        let superblock = unsafe {
127            // Ensure proper alignment by copying to stack
128            let mut aligned_data = [0u8; 1024];
129            aligned_data[..1024].copy_from_slice(&data[..1024]);
130            *(aligned_data.as_ptr() as *const Self)
131        };
132
133        // Validate magic number to ensure we have a valid ext2 superblock
134        if u16::from_le(superblock.magic) != EXT2_SUPER_MAGIC {
135            return Err(FileSystemError::new(
136                FileSystemErrorKind::InvalidData,
137                "Invalid ext2 magic number",
138            ));
139        }
140
141        Ok(superblock)
142    }
143
144    /// Parse superblock from raw bytes and return as Box to avoid stack overflow
145    pub fn from_bytes_boxed(data: &[u8]) -> Result<Box<Self>, FileSystemError> {
146        // Standard ext2 superblock is 1024 bytes, but our struct might be slightly larger due to alignment
147        if data.len() < 1024 {
148            return Err(FileSystemError::new(
149                FileSystemErrorKind::InvalidData,
150                format!(
151                    "Insufficient data for ext2 superblock: got {} bytes, need at least 1024 bytes",
152                    data.len()
153                ),
154            ));
155        }
156
157        // Safe approach: create zeroed superblock on heap, then copy data
158        let mut superblock = Box::new(unsafe { core::mem::zeroed::<Self>() });
159
160        // Copy data safely
161        unsafe {
162            core::ptr::copy_nonoverlapping(
163                data.as_ptr(),
164                superblock.as_mut() as *mut Self as *mut u8,
165                core::mem::size_of::<Self>().min(1024),
166            );
167        }
168
169        // Validate magic number to ensure we have a valid ext2 superblock
170        if u16::from_le(superblock.magic) != EXT2_SUPER_MAGIC {
171            return Err(FileSystemError::new(
172                FileSystemErrorKind::InvalidData,
173                "Invalid ext2 magic number",
174            ));
175        }
176
177        Ok(superblock)
178    }
179
180    /// Get block size in bytes
181    pub fn get_block_size(&self) -> u32 {
182        1024 << u32::from_le(self.log_block_size)
183    }
184
185    /// Get total blocks count
186    pub fn get_blocks_count(&self) -> u32 {
187        u32::from_le(self.blocks_count)
188    }
189
190    /// Get total inodes count  
191    pub fn get_inodes_count(&self) -> u32 {
192        u32::from_le(self.inodes_count)
193    }
194
195    /// Get blocks per group
196    pub fn get_blocks_per_group(&self) -> u32 {
197        u32::from_le(self.blocks_per_group)
198    }
199
200    /// Get inodes per group
201    pub fn get_inodes_per_group(&self) -> u32 {
202        u32::from_le(self.inodes_per_group)
203    }
204
205    /// Get inode size
206    pub fn get_inode_size(&self) -> u16 {
207        u16::from_le(self.inode_size)
208    }
209
210    /// Get first data block
211    pub fn get_first_data_block(&self) -> u32 {
212        u32::from_le(self.first_data_block)
213    }
214}
215
216/// ext2 Block Group Descriptor
217///
218/// Each block group has a descriptor that contains information about
219/// the location of important data structures within that group.
220#[derive(Debug, Clone, Copy)]
221#[repr(C, packed)]
222pub struct Ext2BlockGroupDescriptor {
223    /// Block address of block bitmap
224    pub block_bitmap: u32,
225    /// Block address of inode bitmap
226    pub inode_bitmap: u32,
227    /// Block address of inode table
228    pub inode_table: u32,
229    /// Number of free blocks in group
230    pub free_blocks_count: u16,
231    /// Number of free inodes in group
232    pub free_inodes_count: u16,
233    /// Number of directories in group
234    pub used_dirs_count: u16,
235    /// Padding to 32 bytes
236    pub pad: u16,
237    /// Reserved for future use
238    pub reserved: [u32; 3],
239}
240
241impl Ext2BlockGroupDescriptor {
242    /// Parse block group descriptor from raw bytes using unsafe type conversion
243    pub fn from_bytes(data: &[u8]) -> Result<Self, FileSystemError> {
244        if data.len() < mem::size_of::<Self>() {
245            return Err(FileSystemError::new(
246                FileSystemErrorKind::InvalidData,
247                "Insufficient data for ext2 block group descriptor",
248            ));
249        }
250
251        // Use unsafe cast for efficiency since the structure is packed and has fixed layout
252        let descriptor = unsafe { *(data.as_ptr() as *const Self) };
253
254        Ok(descriptor)
255    }
256
257    /// Get block bitmap address
258    pub fn get_block_bitmap(&self) -> u32 {
259        u32::from_le(self.block_bitmap)
260    }
261
262    /// Get inode bitmap address
263    pub fn get_inode_bitmap(&self) -> u32 {
264        u32::from_le(self.inode_bitmap)
265    }
266
267    /// Get inode table address
268    pub fn get_inode_table(&self) -> u32 {
269        u32::from_le(self.inode_table)
270    }
271
272    /// Get free blocks count
273    pub fn get_free_blocks_count(&self) -> u16 {
274        u16::from_le(self.free_blocks_count)
275    }
276
277    /// Set free blocks count
278    pub fn set_free_blocks_count(&mut self, count: u16) {
279        self.free_blocks_count = count.to_le();
280    }
281
282    /// Get free inodes count
283    pub fn get_free_inodes_count(&self) -> u16 {
284        u16::from_le(self.free_inodes_count)
285    }
286
287    /// Set free inodes count
288    pub fn set_free_inodes_count(&mut self, count: u16) {
289        self.free_inodes_count = count.to_le();
290    }
291
292    /// Get used directories count
293    pub fn get_used_dirs_count(&self) -> u16 {
294        u16::from_le(self.used_dirs_count)
295    }
296
297    /// Set used directories count
298    pub fn set_used_dirs_count(&mut self, count: u16) {
299        self.used_dirs_count = count.to_le();
300    }
301
302    /// Write the descriptor back to bytes
303    pub fn write_to_bytes(&self, data: &mut [u8]) {
304        if data.len() >= mem::size_of::<Self>() {
305            unsafe {
306                let ptr = data.as_mut_ptr() as *mut Self;
307                *ptr = *self;
308            }
309        }
310    }
311}
312
313/// ext2 Inode structure
314///
315/// Each file and directory is represented by an inode that contains
316/// metadata about the file and pointers to its data blocks.
317#[derive(Debug, Clone, Copy)]
318#[repr(C, packed)]
319pub struct Ext2Inode {
320    /// File mode (permissions and file type)
321    pub mode: u16,
322    /// Owner UID
323    pub uid: u16,
324    /// Size in bytes
325    pub size: u32,
326    /// Access time
327    pub atime: u32,
328    /// Creation time
329    pub ctime: u32,
330    /// Modification time
331    pub mtime: u32,
332    /// Deletion time
333    pub dtime: u32,
334    /// Group ID
335    pub gid: u16,
336    /// Link count
337    pub links_count: u16,
338    /// Blocks count (512-byte blocks)
339    pub blocks: u32,
340    /// File flags
341    pub flags: u32,
342    /// OS dependent 1
343    pub osd1: u32,
344    /// Pointers to blocks (0-11 direct, 12 indirect, 13 double indirect, 14 triple indirect)
345    pub block: [u32; 15],
346    /// File version (for NFS)
347    pub generation: u32,
348    /// File ACL
349    pub file_acl: u32,
350    /// Directory ACL / high 32 bits of file size
351    pub dir_acl: u32,
352    /// Fragment address
353    pub faddr: u32,
354    /// OS dependent 2
355    pub osd2: [u8; 12],
356}
357
358impl Ext2Inode {
359    pub fn empty() -> Self {
360        Self {
361            mode: 0,
362            uid: 0,
363            size: 0,
364            atime: 0,
365            ctime: 0,
366            mtime: 0,
367            dtime: 0,
368            gid: 0,
369            links_count: 0,
370            blocks: 0,
371            flags: 0,
372            osd1: 0,
373            block: [0; 15],
374            generation: 0,
375            file_acl: 0,
376            dir_acl: 0,
377            faddr: 0,
378            osd2: [0; 12],
379        }
380    }
381    /// Parse inode from raw bytes using unsafe type conversion for efficiency
382    pub fn from_bytes(data: &[u8]) -> Result<Self, FileSystemError> {
383        if data.len() < mem::size_of::<Self>() {
384            return Err(FileSystemError::new(
385                FileSystemErrorKind::InvalidData,
386                "Insufficient data for ext2 inode",
387            ));
388        }
389
390        // Use unsafe cast for efficiency since the inode structure is packed and has fixed layout
391        let inode = unsafe { *(data.as_ptr() as *const Self) };
392
393        Ok(inode)
394    }
395
396    /// Get file mode (permissions and type)
397    pub fn get_mode(&self) -> u16 {
398        u16::from_le(self.mode)
399    }
400
401    /// Get file size in bytes
402    pub fn get_size(&self) -> u32 {
403        u32::from_le(self.size)
404    }
405
406    /// Get modification time
407    pub fn get_mtime(&self) -> u32 {
408        u32::from_le(self.mtime)
409    }
410
411    /// Get access time
412    pub fn get_atime(&self) -> u32 {
413        u32::from_le(self.atime)
414    }
415
416    /// Get creation time
417    pub fn get_ctime(&self) -> u32 {
418        u32::from_le(self.ctime)
419    }
420
421    /// Get link count
422    pub fn get_links_count(&self) -> u16 {
423        u16::from_le(self.links_count)
424    }
425
426    /// Get blocks count (512-byte blocks)
427    pub fn get_blocks(&self) -> u32 {
428        u32::from_le(self.blocks)
429    }
430
431    /// Get block pointer at index
432    pub fn get_block(&self, index: usize) -> Option<u32> {
433        if index < 15 {
434            Some(u32::from_le(self.block[index]))
435        } else {
436            None
437        }
438    }
439
440    /// Check if this is a directory
441    pub fn is_dir(&self) -> bool {
442        (self.get_mode() & EXT2_S_IFMT) == EXT2_S_IFDIR
443    }
444
445    /// Check if this is a regular file
446    pub fn is_file(&self) -> bool {
447        (self.get_mode() & EXT2_S_IFMT) == EXT2_S_IFREG
448    }
449
450    /// Check if this is a character device
451    pub fn is_char_device(&self) -> bool {
452        (self.get_mode() & EXT2_S_IFMT) == EXT2_S_IFCHR
453    }
454
455    /// Check if this is a block device
456    pub fn is_block_device(&self) -> bool {
457        (self.get_mode() & EXT2_S_IFMT) == EXT2_S_IFBLK
458    }
459
460    /// Check if this is a symbolic link
461    pub fn is_symlink(&self) -> bool {
462        (self.get_mode() & EXT2_S_IFMT) == EXT2_S_IFLNK
463    }
464
465    /// Check if this is a FIFO (pipe)
466    pub fn is_fifo(&self) -> bool {
467        (self.get_mode() & EXT2_S_IFMT) == EXT2_S_IFIFO
468    }
469
470    /// Check if this is a socket
471    pub fn is_socket(&self) -> bool {
472        (self.get_mode() & EXT2_S_IFMT) == EXT2_S_IFSOCK
473    }
474
475    /// Get device information for device files
476    /// Returns (major, minor) device numbers
477    pub fn get_device_info(&self) -> Option<(u32, u32)> {
478        if self.is_char_device() || self.is_block_device() {
479            // In ext2, device info is stored in the first direct block pointer
480            let device_id = u32::from_le(self.block[0]);
481            let major = (device_id >> 8) & 0xFF;
482            let minor = device_id & 0xFF;
483            Some((major, minor))
484        } else {
485            None
486        }
487    }
488
489    /// Read symbolic link target from inode
490    ///
491    /// This method handles both fast symlinks (target stored in block array)
492    /// and slow symlinks (target stored in data blocks).
493    ///
494    /// # Arguments
495    ///
496    /// * `filesystem` - Reference to the ext2 filesystem for block reading
497    ///
498    /// # Returns
499    ///
500    /// The target path of the symbolic link, or an error if this is not a symlink
501    /// or if the target cannot be read.
502    pub fn read_symlink_target(
503        &self,
504        filesystem: &super::Ext2FileSystem,
505    ) -> Result<String, FileSystemError> {
506        // Check if this is actually a symbolic link
507        if !self.is_symlink() {
508            return Err(FileSystemError::new(
509                FileSystemErrorKind::NotSupported,
510                "Not a symbolic link",
511            ));
512        }
513
514        let size = self.get_size() as usize;
515
516        if size <= 60 {
517            // Fast symlink: target path is stored in inode.block array
518            let inode_bytes = unsafe {
519                core::slice::from_raw_parts(
520                    self as *const Self as *const u8,
521                    core::mem::size_of::<Self>(),
522                )
523            };
524            // Block array starts at offset 40 in the inode structure
525            let block_start_offset = 40;
526            let block_bytes = &inode_bytes[block_start_offset..block_start_offset + 60];
527            let target_bytes = &block_bytes[..size];
528
529            String::from_utf8(target_bytes.to_vec()).map_err(|_| {
530                FileSystemError::new(
531                    FileSystemErrorKind::InvalidData,
532                    "Invalid UTF-8 in symlink target",
533                )
534            })
535        } else {
536            // Slow symlink: target path is stored in data blocks
537            let first_block = u32::from_le(self.block[0]);
538            if first_block == 0 {
539                return Err(FileSystemError::new(
540                    FileSystemErrorKind::InvalidData,
541                    "Symlink has no data block",
542                ));
543            }
544
545            // Read the block containing the target path
546            let block_sector = filesystem.block_to_sector(first_block as u64);
547            let request = Box::new(crate::device::block::request::BlockIORequest {
548                request_type: crate::device::block::request::BlockIORequestType::Read,
549                sector: block_sector as usize,
550                sector_count: (filesystem.block_size / 512) as usize,
551                head: 0,
552                cylinder: 0,
553                buffer: vec![0u8; filesystem.block_size as usize],
554            });
555
556            filesystem.block_device.enqueue_request(request);
557            let results = filesystem.block_device.process_requests();
558
559            let block_data = if let Some(result) = results.first() {
560                match &result.result {
561                    Ok(_) => result.request.buffer.clone(),
562                    Err(_) => {
563                        return Err(FileSystemError::new(
564                            FileSystemErrorKind::IoError,
565                            "Failed to read symlink data block",
566                        ));
567                    }
568                }
569            } else {
570                return Err(FileSystemError::new(
571                    FileSystemErrorKind::IoError,
572                    "No result from symlink data block read",
573                ));
574            };
575
576            let target_bytes = &block_data[..size];
577            String::from_utf8(target_bytes.to_vec()).map_err(|_| {
578                FileSystemError::new(
579                    FileSystemErrorKind::InvalidData,
580                    "Invalid UTF-8 in symlink target",
581                )
582            })
583        }
584    }
585}
586
587/// ext2 Directory Entry
588///
589/// Directory entries are stored as variable-length records within directory data blocks.
590#[derive(Debug, Clone, Copy)]
591#[repr(C, packed)]
592pub struct Ext2DirectoryEntryRaw {
593    /// Inode number
594    pub inode: u32,
595    /// Record length
596    pub rec_len: u16,
597    /// Name length
598    pub name_len: u8,
599    /// File type (ext2 revision 1.0 and later)
600    pub file_type: u8,
601    // Name follows this header
602}
603
604impl Ext2DirectoryEntryRaw {
605    /// Parse directory entry from raw bytes using unsafe type conversion
606    pub fn from_bytes(data: &[u8]) -> Result<Self, FileSystemError> {
607        if data.len() < mem::size_of::<Self>() {
608            return Err(FileSystemError::new(
609                FileSystemErrorKind::InvalidData,
610                "Insufficient data for ext2 directory entry header",
611            ));
612        }
613
614        // Use unsafe cast for efficiency since the directory entry header is packed and fixed-size
615        let entry = unsafe { *(data.as_ptr() as *const Self) };
616
617        Ok(entry)
618    }
619
620    /// Get inode number
621    pub fn get_inode(&self) -> u32 {
622        u32::from_le(self.inode)
623    }
624
625    /// Get record length
626    pub fn get_rec_len(&self) -> u16 {
627        u16::from_le(self.rec_len)
628    }
629
630    /// Get name length
631    pub fn get_name_len(&self) -> u8 {
632        self.name_len
633    }
634
635    /// Get file type
636    pub fn get_file_type(&self) -> u8 {
637        self.file_type
638    }
639}
640
641/// Complete directory entry with name
642#[derive(Debug, Clone)]
643pub struct Ext2DirectoryEntry {
644    pub entry: Ext2DirectoryEntryRaw,
645    pub name: String,
646}
647
648impl Ext2DirectoryEntry {
649    /// Parse a complete directory entry with name from raw bytes
650    pub fn from_bytes(data: &[u8]) -> Result<Self, FileSystemError> {
651        if data.len() < 8 {
652            return Err(FileSystemError::new(
653                FileSystemErrorKind::InvalidData,
654                "Insufficient data for ext2 directory entry",
655            ));
656        }
657
658        let entry = Ext2DirectoryEntryRaw::from_bytes(data)?;
659
660        if data.len() < 8 + entry.name_len as usize {
661            return Err(FileSystemError::new(
662                FileSystemErrorKind::InvalidData,
663                "Insufficient data for directory entry name",
664            ));
665        }
666
667        let name_bytes = &data[8..8 + entry.name_len as usize];
668        let name = String::from_utf8(name_bytes.to_vec()).map_err(|_| {
669            FileSystemError::new(
670                FileSystemErrorKind::InvalidData,
671                "Invalid UTF-8 in directory entry name",
672            )
673        })?;
674
675        Ok(Self { entry, name })
676    }
677
678    pub fn name_str(&self) -> Result<String, FileSystemError> {
679        Ok(self.name.clone())
680    }
681}
682
683// Ensure structures have correct sizes
684// Note: Ext2Superblock is flexible in size but we define minimum 1024 bytes
685// const _: () = assert!(mem::size_of::<Ext2Superblock>() >= 1024);