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 (i.e. the next block 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)] #[derive(Copy, Clone)] pub struct Superblock { /// magic number that identifies the Superblock as a valid VapUFS filesystem pub magic: u64, /// size of each block in bytes, must be *at least* the size of the superblock without the bitmaps 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 // next 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 /// for each entry... /// is_data: bool (true if data block, false if another indirect block) /// depth: u56 (in total, how many indirect blocks are pointed to if this isn't data, and how many indirect blocks do they point to if they have indirect blocks) /// ptr: u64 (if is_data, then this is a data block, otherwise it is an indirect block) /// both direct and indirect are pointers to data blocks, starting from data block 0 /// ==== /// these pointers can be addressed via a u64 in the following way: /// if i < 12, then it refers to a direct block in this array (undefined behaviour if indirect is set and i > 9) /// if i == 12, then it refers to the first index found by going to the first indirect block, and recursing through each found indirect block until the first data block is found. /// if i == 13, then it refers to the second index found by going to the first indirect block, and recursing through each found indirect block until the second data block is found. /// == /// the algorithm for finding the nth data block is as follows: /// if n < 12, then it is the nth direct block /// if n >= 12... /// let mut n = n - 12; /// let mut block_i = n / (block_size - 8); /// // block_i is how many indirect blocks we need to traverse to get to the indirect block that contains the nth data block /// let mut current_block = self.direct_block_addresses[12]; /// while not found... /// // read block, if not data then check depth, /// // if depth implies that this indirect block could contain the nth data block, then recurse /// // otherwise, subtract the depth from n, and continue /// ==== /// ideally, this should not take up too much storage space, while allowing for a large number of blocks to be addressed with minimal performance impact 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, or maybe even directly to the disk? /// see JBRTargetType pub target_type: u32, /// 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, /// actual data block number, unused if target is an inode pub real_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, } /// # JBRTargetType /// Type of target of a JournalBlockWrite #[repr(u32)] #[derive(Copy, Clone)] pub enum JBRTargetType { /// target is a data block DataBlock = 0, /// target is an inode Inode = 1, /// target is the disk itself Disk = 2, } /// # 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). /// all data stored in an inode must be written at once using this operation, /// so it may not be suitable for large files. /// 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, /// block number of old list block structure pub old_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, }