use vapfs::{Index, Timestamp}; use crate::crc32; /// # Magic /// Constant value that identifies the Superblock as a valid VapUFS filesystem pub const MAGIC: u64 = 0x766170554653; /// # Superblock /// The primary struct of a VapUFS filesystem, contains metadata about the filesystem. /// Located at byte offset 1024 of the block device. /// All values are big-endian unless otherwise specified. /// Directly after the Superblock are the free data blocks bitmap and the free inodes bitmap. /// Free data blocks bitmap is data_block_count / 8 bytes long (rounded up). /// Free inodes bitmap is inode_count / 8 bytes long (rounded up). #[repr(C)] pub struct Superblock { /// magic number that identifies the Superblock as a valid VapUFS filesystem pub magic: u64, /// size of each block in bytes pub block_size: u32, /// location of first data block in blocks pub first_data_block: Index, /// location of first inode block in blocks pub first_inode_block: Index, /// location of first journal block in blocks pub first_journal_block: Index, /// total count of inodes, including unused inodes pub inode_count: Index, /// total count of data blocks, including unused blocks pub data_block_count: Index, /// total count of blocks dedicated to journal pub journal_block_count: Index, /// total count of inodes in use pub allocated_inode_count: Index, /// total count of data blocks in use pub allocated_data_block_count: Index, /// timestamp of creation pub creation_time: Timestamp, /// timestamp of last modification pub last_modification_time: Timestamp, /// crc32 checksum of this Superblock pub checksum: u32, /// index of the current journal entry being processed /// incremented after a journal entry is committed, set to 0 if past max pub journal_position: u32, /// reserved values for expansion pub reserved: [u64; 7], // 156 bytes used so far // rest of the block is used by the free data blocks bitmap and the free inodes bitmap } impl Superblock { /// in-place conversion from the storage representation (big endian) to the native representation pub fn convert_big_endian_to_native(&mut self) { #[cfg(target_endian = "little")] { self.magic = u64::from_be(self.magic); self.block_size = u32::from_be(self.block_size); self.first_data_block = u64::from_be(self.first_data_block); self.first_inode_block = u64::from_be(self.first_inode_block); self.first_journal_block = u64::from_be(self.first_journal_block); self.inode_count = u64::from_be(self.inode_count); self.data_block_count = u64::from_be(self.data_block_count); self.journal_block_count = u64::from_be(self.journal_block_count); self.allocated_inode_count = u64::from_be(self.allocated_inode_count); self.allocated_data_block_count = u64::from_be(self.allocated_data_block_count); self.creation_time = u64::from_be(self.creation_time); self.last_modification_time = u64::from_be(self.last_modification_time); for i in 0..8 { self.reserved[i] = u64::from_be(self.reserved[i]); } } } /// in-place conversion from the native representation to the storage representation (big endian) pub fn convert_native_to_big_endian(&mut self) { #[cfg(target_endian = "little")] { self.magic = u64::to_be(self.magic); self.block_size = u32::to_be(self.block_size); self.first_data_block = u64::to_be(self.first_data_block); self.first_inode_block = u64::to_be(self.first_inode_block); self.first_journal_block = u64::to_be(self.first_journal_block); self.inode_count = u64::to_be(self.inode_count); self.data_block_count = u64::to_be(self.data_block_count); self.journal_block_count = u64::to_be(self.journal_block_count); self.allocated_inode_count = u64::to_be(self.allocated_inode_count); self.allocated_data_block_count = u64::to_be(self.allocated_data_block_count); self.creation_time = u64::to_be(self.creation_time); self.last_modification_time = u64::to_be(self.last_modification_time); for i in 0..8 { self.reserved[i] = u64::to_be(self.reserved[i]); } } } /// returns true if the crc32 checksum is currently valid /// returns false otherwise pub fn is_checksum_valid(&self) -> bool { let mut buf = [0; core::mem::size_of::() - (8 * 8)]; // don't hash last 7 u64s, the reserved u32, or the checksum unsafe { core::ptr::copy(self as *const Self as *const u8, buf.as_mut_ptr(), buf.len()); } let checksum = crc32::crc32(&buf); checksum == self.checksum } /// updates the crc32 checksum pub fn recalculate_checksum(&mut self) { let mut buf = [0; core::mem::size_of::() - (8 * 8)]; // don't hash last 7 u64s, the reserved u32, or the checksum unsafe { core::ptr::copy(self as *const Self as *const u8, buf.as_mut_ptr(), buf.len()); } let checksum = crc32::crc32(&buf); self.checksum = checksum; } } /// # UNIXPermissions /// UNIX permissions #[repr(u16)] pub enum UNIXMode { /// Read, Other Read = 1 << 0, /// Write, Other Write = 1 << 1, /// Execute, Other Execute = 1 << 2, /// Read, Group ReadGroup = 1 << 3, /// Write, Group WriteGroup = 1 << 4, /// Execute, Group ExecuteGroup = 1 << 5, /// Read, Owner ReadOwner = 1 << 6, /// Write, Owner WriteOwner = 1 << 7, /// Execute, Owner ExecuteOwner = 1 << 8, /// Sticky bit Sticky = 1 << 9, /// Set GID SetGID = 1 << 10, /// Set UID SetUID = 1 << 11, } /// # Filetype /// Represents a file's type in the last 4 bits of the mode field of an inode. #[repr(u16)] pub enum Filetype { /// Named FIFO pipe FIFO = 0b0001 << 12, /// Character device CharDevice = 0b0010 << 12, /// Directory Directory = 0b0011 << 12, /// Block device BlockDevice = 0b0100 << 12, /// Regular file RegularFile = 0b0101 << 12, /// Symbolic link SymbolicLink = 0b0110 << 12, /// Socket Socket = 0b0111 << 12, } /// # Inode /// Usually represents a file or directory, used to store metadata and locations of data blocks. #[repr(C)] pub struct Inode { /// UNIX permissions / mode and filetype pub mode: u16, /// Number of links to this inode pub link_count: u16, /// User ID of owner pub uid: u32, /// Group ID of owner pub gid: u32, /// Size in bytes pub size: Index, /// Size in blocks pub block_count: Index, /// Timestamp of creation pub creation_time: Timestamp, /// Timestamp of last access pub last_access_time: Timestamp, /// Timestamp of last modification pub last_modification_time: Timestamp, /// Timestamp of last Inode modification pub last_inode_modification_time: Timestamp, /// Timestamp of deletion pub deletion_time: Timestamp, /// Flags field, see `InodeFlags` pub flags: u32, /// Direct-Block-Addresses, last three are indirect if `InodeFlags::INDIRECT` is set /// Indirect blocks are Indexes to other blocks, their contents are a u64 count "N" followed by N u64 indexes /// both are pointers to data blocks, starting from data block 0 pub direct_block_addresses: [Index; 12], /// CRC32 checksum of this inode pub checksum: u32, // 172 bytes used so far } impl Inode { /// in-place conversion from the storage representation (big endian) to the native representation pub fn convert_big_endian_to_native(&mut self) { #[cfg(target_endian = "little")] { self.mode = u16::from_be(self.mode); self.link_count = u16::from_be(self.link_count); self.uid = u32::from_be(self.uid); self.gid = u32::from_be(self.gid); self.size = u64::from_be(self.size); self.block_count = u64::from_be(self.block_count); self.creation_time = u64::from_be(self.creation_time); self.last_access_time = u64::from_be(self.last_access_time); self.last_modification_time = u64::from_be(self.last_modification_time); self.last_inode_modification_time = u64::from_be(self.last_inode_modification_time); self.deletion_time = u64::from_be(self.deletion_time); self.flags = u32::from_be(self.flags); for i in 0..12 { self.direct_block_addresses[i] = u64::from_be(self.direct_block_addresses[i]); } self.checksum = u32::from_be(self.checksum); } } /// in-place conversion from the native representation to the storage representation (big endian) pub fn convert_native_to_big_endian(&mut self) { #[cfg(target_endian = "little")] { self.mode = u16::to_be(self.mode); self.link_count = u16::to_be(self.link_count); self.uid = u32::to_be(self.uid); self.gid = u32::to_be(self.gid); self.size = u64::to_be(self.size); self.block_count = u64::to_be(self.block_count); self.creation_time = u64::to_be(self.creation_time); self.last_access_time = u64::to_be(self.last_access_time); self.last_modification_time = u64::to_be(self.last_modification_time); self.last_inode_modification_time = u64::to_be(self.last_inode_modification_time); self.deletion_time = u64::to_be(self.deletion_time); self.flags = u32::to_be(self.flags); for i in 0..12 { self.direct_block_addresses[i] = u64::to_be(self.direct_block_addresses[i]); } self.checksum = u32::to_be(self.checksum); } } /// returns true if the crc32 checksum is currently valid /// returns false otherwise pub fn is_checksum_valid(&self) -> bool { let mut buf = [0; core::mem::size_of::() - 4]; // don't hash the checksum unsafe { core::ptr::copy(self as *const Self as *const u8, buf.as_mut_ptr(), buf.len()); } let checksum = crc32::crc32(&buf); checksum == self.checksum } /// updates the crc32 checksum pub fn recalculate_checksum(&mut self) { let mut buf = [0; core::mem::size_of::() - 4]; // don't hash the checksum unsafe { core::ptr::copy(self as *const Self as *const u8, buf.as_mut_ptr(), buf.len()); } let checksum = crc32::crc32(&buf); self.checksum = checksum; } } /// # InodeFlags /// Flags field of an inode #[repr(u32)] pub enum InodeFlags { /// File is corrupted CORRUPT = 1 << 0, /// File uses indirect blocks INDIRECT = 1 << 1, } /// # JournalEntry /// A Journal Entry #[repr(C)] #[derive(Copy, Clone)] pub struct JournalEntry { /// JournalOperation pub operation: u32, /// crc32 hash of the content with flags set to zero, used to verify journal was fully written without verifying the stage pub zeroed_content_crc32: u32, pub content: JournalEntryContents, } impl JournalEntry { /// in-place conversion from the storage representation (big endian) to the native representation pub fn convert_big_endian_to_native(&mut self) { #[cfg(target_endian = "little")] { self.operation = u32::from_be(self.operation); self.zeroed_content_crc32 = u32::from_be(self.zeroed_content_crc32); match self.operation { 0 => { // SingleBlockWrite unsafe { self.content.block_write.convert_big_endian_to_native() }; } 1 => { // MultiblockWrite unsafe { self.content.multiblock_write.convert_big_endian_to_native() }; } _ => {} } } } /// in-place conversion from the native representation to the storage representation (big endian) pub fn convert_native_to_big_endian(&mut self) { #[cfg(target_endian = "little")] { self.operation = u32::to_be(self.operation); self.zeroed_content_crc32 = u32::to_be(self.zeroed_content_crc32); match self.operation { 0 => { // SingleBlockWrite unsafe { self.content.block_write.convert_native_to_big_endian() }; } 1 => { // MultiblockWrite unsafe { self.content.multiblock_write.convert_native_to_big_endian() }; } _ => {} } } } } /// # JournalOperation /// Type of operation performed by a Journal Entry #[repr(u32)] pub enum JournalOperation { /// A single block write, described by a `JournalBlockWrite` SingleBlockWrite = 0, /// A multi-block write, described by a `JournalMultiblockWrite` MultiblockWrite = 1, } /// # JournalBlockWrite /// writes are performed as follows: /// 1. create and write the journal entry /// 2. allocate a data block for the data to be written /// 3. set the entry's allocated flag /// 4. write the data to the allocated data block /// 5. set the entry's stored flag /// == the following steps will be performed upon a journal flush, i.e. when the driver has decided that it's time to finally "commit" the journal == /// 6. depending on the destination of the write, either: /// - update inode metadata to point to the new data block /// - copy the data from the old data block to the new data block /// 7. set the entry's written flag /// 8. deallocate the old data block /// 9. set the entry's deallocated flag /// == done! == /// ideally, with this schema the filesystem shall never become corrupt /// all fields in the entry should be modified individually on the block device instead of rewriting the entire entry #[repr(C)] #[derive(Copy, Clone)] pub struct JournalBlockWrite { /// JBRFlags stating how far the write has progressed pub flags: u32, /// are we writing to an inode instead of a data block? pub target_is_inode: bool, /// target inode number pub target_inode: Index, /// target block number (if target is a data block, this will be the index in the inode's direct block array; /// if greater than 12, the indirect block this points to (i / 12) will be used, and the index in that block will be i % 12) /// (if target is an inode, this field will be ignored) pub target_block: Index, /// block number of source data block pub source_block: Index, /// crc32 hash of the source data block pub source_block_crc32: u32, } impl JournalBlockWrite { /// in-place conversion from the storage representation (big endian) to the native representation pub fn convert_big_endian_to_native(&mut self) { #[cfg(target_endian = "little")] { self.flags = u32::from_be(self.flags); self.target_block = u64::from_be(self.target_block); self.source_block = u64::from_be(self.source_block); self.source_block_crc32 = u32::from_be(self.source_block_crc32); } } /// in-place conversion from the native representation to the storage representation (big endian) pub fn convert_native_to_big_endian(&mut self) { #[cfg(target_endian = "little")] { self.flags = u32::to_be(self.flags); self.target_block = u64::to_be(self.target_block); self.source_block = u64::to_be(self.source_block); self.source_block_crc32 = u32::to_be(self.source_block_crc32); } } } /// # JBRFlags /// Flags field of a JournalBlockWrite #[repr(u32)] #[derive(Copy, Clone)] pub enum JBRFlags { /// source data block has been chosen but not yet allocated Chosen = 1, /// source data block has been allocated Allocated = 2, /// data has been written to the source data block Stored = 3, /// source data block has either replaced an old data block or has been written to an inode Written = 4, /// source data block (in the case of a write to an inode) or old data block (in the case of a write to a data block) has been deallocated /// (i.e. this journal entry has been fully committed) CompleteAndDeallocated = 0, } /// # JournalMultiblockWrite /// a special entry for writing to multiple blocks at once, /// used for circumstances where it is very important that all blocks are either /// written successfully, or not written at all (i.e. directory blocks) /// writes are performed as follows: /// 1. create and write the journal entry /// 2. allocate a data block to store a MultiblockWriteList /// 3. set the entry's allocated_list flag /// 4. allocate data blocks to store the data to be written /// 5. set the entry's allocated_data flag /// 6. write the data to the allocated data blocks and the list to the allocated list block /// 7. set the entry's stored flag /// == the following steps will be performed upon a journal flush == /// 8. update inode metadata to point to the new data blocks /// 9. set the entry's written flag /// 10. deallocate the old data blocks & list block /// 11. set the entry's deallocated flag /// == done! == #[repr(C)] #[derive(Copy, Clone)] pub struct JournalMultiblockWrite { /// JMWFlags stating how far the write has progressed pub flags: u32, /// inode number of target inode pub target_inode: Index, /// block number of first target block pub target_block: Index, /// number of target blocks pub target_block_count: Index, /// block number of list block structure pub list_block: Index, /// crc32 hash of the list block pub list_block_crc32: u32, } impl JournalMultiblockWrite { /// in-place conversion from the storage representation (big endian) to the native representation pub fn convert_big_endian_to_native(&mut self) { #[cfg(target_endian = "little")] { self.flags = u32::from_be(self.flags); self.target_block = u64::from_be(self.target_block); self.target_block_count = u64::from_be(self.target_block_count); self.list_block = u64::from_be(self.list_block); self.list_block_crc32 = u32::from_be(self.list_block_crc32); } } /// in-place conversion from the native representation to the storage representation (big endian) pub fn convert_native_to_big_endian(&mut self) { #[cfg(target_endian = "little")] { self.flags = u32::to_be(self.flags); self.target_block = u64::to_be(self.target_block); self.target_block_count = u64::to_be(self.target_block_count); self.list_block = u64::to_be(self.list_block); self.list_block_crc32 = u32::to_be(self.list_block_crc32); } } } /// # JMWFlags /// Flags field of a JournalMultiblockWrite #[repr(u32)] #[derive(Copy, Clone)] pub enum JMWFlags { /// list block has been chosen but not yet allocated ChosenList = 1, /// list block has been allocated AllocatedList = 2, /// data blocks have been chosen and stored in the list block but not yet allocated ChosenData = 3, /// data blocks have been allocated AllocatedData = 4, /// data has been written to the data blocks and the list block Stored = 5, /// data blocks have replaced old data blocks Written = 6, /// data blocks and list block have been deallocated /// (i.e. this journal entry has been fully committed) CompleteAndDeallocated = 0, } /// # ListBlock /// a list of data blocks for a journaled multiblock write, similar in structure to an inode #[repr(C)] #[derive(Copy, Clone)] pub struct ListBlock { pub using_indirect_blocks: bool, /// Direct-Block-Addresses, last three are indirect if `using_indirect_blocks` is set /// Indirect blocks are Indexes to other blocks, their contents are a u64 count "N" followed by N u64 indexes /// both are pointers to data blocks, starting from data block 0 pub direct_block_addresses: [Index; 12], } impl ListBlock { /// in-place conversion from the storage representation (big endian) to the native representation pub fn convert_big_endian_to_native(&mut self) { #[cfg(target_endian = "little")] { for i in 0..12 { self.direct_block_addresses[i] = u64::from_be(self.direct_block_addresses[i]); } } } /// in-place conversion from the native representation to the storage representation (big endian) pub fn convert_native_to_big_endian(&mut self) { #[cfg(target_endian = "little")] { for i in 0..12 { self.direct_block_addresses[i] = u64::to_be(self.direct_block_addresses[i]); } } } } /// # JournalEntryContents /// union of all possible journal entries #[repr(C)] #[derive(Copy, Clone)] pub union JournalEntryContents { pub block_write: JournalBlockWrite, pub multiblock_write: JournalMultiblockWrite, }