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 block 1 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* 2048 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, /// 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.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.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, /// listblock for storing data block addresses pub listblock: ListBlock, /// CRC32 checksum of this inode pub checksum: u32, } 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); self.listblock.convert_big_endian_to_native(); 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); self.listblock.convert_native_to_big_endian(); 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 Single Indirect Block INDIRECT1 = 1 << 1, /// File uses Double Indirect Block INDIRECT2 = 1 << 2, /// File uses Triple Indirect Block INDIRECT3 = 1 << 3, /// File uses Quadruple Indirect Block INDIRECT4 = 1 << 4, /// File uses Quintuple Indirect Block INDIRECT5 = 1 << 5, /// File uses Sextuple Indirect Block INDIRECT6 = 1 << 6, } /// # 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, /// A file creation, described by a `JournalFileCreate` FileCreate = 2, /// A file deletion, described by a `JournalFileDelete` FileDelete = 3, /// A file truncation, described by a `JournalFileTruncate` FileTruncate = 4, } /// # 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 32, see the following: /// let N = maximum number of pointers in an indirect block /// (beginning...count) /// 32..N: single indirect block /// 32+N..N^2: double indirect block /// 32+N^2..N^3: triple indirect block /// 32+N^3..N^4: quadruple indirect block /// 32+N^4..N^5: quintuple indirect block /// 32+N^5..N^6: sextuple indirect block 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 { /// Count of blocks used pub count: Index, /// Direct-Block-Addresses pub direct_block_addresses: [Index; 32], /// Single Indirect-Block-Address /// Indirect blocks are Indexes to other blocks, their contents are N u64 data pointers pub single_indirect_block_address: [Index; 32], /// Double Indirect-Block-Address /// Double indirect blocks are Indexes to other blocks, their contents are N u64 indirect block pointers (they point to single indirect blocks) pub double_indirect_block_address: [Index; 32], /// Triple Indirect-Block-Address /// Triple indirect blocks are Indexes to other blocks, their contents are N u64 double indirect block pointers (they point to double indirect blocks) pub triple_indirect_block_address: [Index; 32], /// Quadruple Indirect-Block-Address /// Quadruple indirect blocks are Indexes to other blocks, their contents are N u64 triple indirect block pointers (they point to triple indirect blocks) pub quadruple_indirect_block_address: [Index; 32], /// Quintuple Indirect-Block-Address /// Quintuple indirect blocks are Indexes to other blocks, their contents are N u64 quadruple indirect block pointers (they point to quadruple indirect blocks) pub quintuple_indirect_block_address: [Index; 32], /// Sextuple Indirect-Block-Address /// Sextuple indirect blocks are Indexes to other blocks, their contents are N u64 quintuple indirect block pointers (they point to quintuple indirect blocks) pub sextuple_indirect_block_address: [Index; 32], } 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..32 { self.direct_block_addresses[i] = u64::from_be(self.direct_block_addresses[i]); self.single_indirect_block_address[i] = u64::from_be(self.single_indirect_block_address[i]); self.double_indirect_block_address[i] = u64::from_be(self.double_indirect_block_address[i]); self.triple_indirect_block_address[i] = u64::from_be(self.triple_indirect_block_address[i]); self.quadruple_indirect_block_address[i] = u64::from_be(self.quadruple_indirect_block_address[i]); self.quintuple_indirect_block_address[i] = u64::from_be(self.quintuple_indirect_block_address[i]); self.sextuple_indirect_block_address[i] = u64::from_be(self.sextuple_indirect_block_address[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..32 { self.direct_block_addresses[i] = u64::to_be(self.direct_block_addresses[i]); self.single_indirect_block_address[i] = u64::to_be(self.single_indirect_block_address[i]); self.double_indirect_block_address[i] = u64::to_be(self.double_indirect_block_address[i]); self.triple_indirect_block_address[i] = u64::to_be(self.triple_indirect_block_address[i]); self.quadruple_indirect_block_address[i] = u64::to_be(self.quadruple_indirect_block_address[i]); self.quintuple_indirect_block_address[i] = u64::to_be(self.quintuple_indirect_block_address[i]); self.sextuple_indirect_block_address[i] = u64::to_be(self.sextuple_indirect_block_address[i]); } } } } /// # JournalEntryContents /// union of all possible journal entries #[repr(C)] #[derive(Copy, Clone)] pub union JournalEntryContents { pub block_write: JournalBlockWrite, pub multiblock_write: JournalMultiblockWrite, }