From 999336ff9ed07fea851980fa8c8e023bbefe2423 Mon Sep 17 00:00:00 2001 From: husky Date: Thu, 27 Jul 2023 09:28:06 -0700 Subject: [PATCH] some more basic stuff for the filesystem --- Cargo.toml | 3 +- src/crc32.rs | 62 +++++++++++ src/lib.rs | 198 ++++++++++++++++++++++++++++++++- src/structs.rs | 289 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 546 insertions(+), 6 deletions(-) create mode 100644 src/crc32.rs diff --git a/Cargo.toml b/Cargo.toml index 64cc81d..4a4c621 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -vapfs = { path = "../vapfs" } -embedded-crc32c = "0.1.0" \ No newline at end of file +vapfs = { path = "../vapfs" } \ No newline at end of file diff --git a/src/crc32.rs b/src/crc32.rs new file mode 100644 index 0000000..ba9f221 --- /dev/null +++ b/src/crc32.rs @@ -0,0 +1,62 @@ +/* +based upon code written by Gary S. Brown. +code and tables are in the public domain. + */ + +/// precomputed crc32 table taken from crc32.c +pub const CRC_TAB: [u32; 256] = [0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +]; + +/// function that calculates crc32 using the above table +pub const fn crc32(buf: &[u8]) -> u32 { + let mut crc: u32 = !0; + let mut i = 0; + while i < buf.len() { + crc = CRC_TAB[(crc ^ buf[i] as u32) as usize] ^ (crc >> 8); + i += 1; + } + + !crc +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6cdecf1..a2949bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,13 +2,205 @@ extern crate alloc; -use vapfs::BlockDevice; -use crate::structs::Superblock; +use alloc::vec::Vec; +use vapfs::{BlockDevice, Index}; +use crate::structs::{Inode, Superblock}; pub mod btree; pub mod structs; pub mod bitmap; +pub mod crc32; +/// Reads the superblock (located at byte offset 1024 of the block device) and returns it. +/// Returns None if the block device is too small to contain a superblock. pub fn get_superblock(bd: &mut dyn BlockDevice) -> Option { - todo!() + let mut buf: [u8; core::mem::size_of::()] = [0; core::mem::size_of::()]; + bd.seek(1024); + let read_count = bd.read_blocks(&mut buf); + if read_count < core::mem::size_of::() { + return None; + } + let mut superblock = unsafe { core::ptr::read(buf.as_ptr() as *const Superblock) }; + superblock.convert_big_endian_to_native(); + Some(superblock) +} + +/// Performs a direct write of a superblock to the block device. +/// # Safety +/// unsafe because it does not journal the write, and does not update any other metadata. +pub unsafe fn write_superblock(mut sb: Superblock, bd: &mut dyn BlockDevice) -> bool { + sb.convert_native_to_big_endian(); + let mut buf: [u8; core::mem::size_of::()] = [0; core::mem::size_of::()]; + core::ptr::write(buf.as_mut_ptr() as *mut Superblock, sb); + bd.seek(1024); + let write_count = bd.write_blocks(&buf); + write_count == core::mem::size_of::() +} + +/// Reads the inode at the given index and returns it. +pub fn read_inode(index: Index, sb: &Superblock, bd: &mut dyn BlockDevice) -> Option { + let mut buf: [u8; core::mem::size_of::()] = [0; core::mem::size_of::()]; + bd.seek((sb.first_inode_block * sb.block_size as u64) + (index * core::mem::size_of::() as u64)); + let read_count = bd.read_blocks(&mut buf); + if read_count < core::mem::size_of::() { + return None; + } + let mut inode = unsafe { core::ptr::read(buf.as_ptr() as *const Inode) }; + inode.convert_big_endian_to_native(); + Some(inode) +} + +/// Performs a direct write of an inode to the block device. +/// # Safety +/// unsafe because it does not journal the write, and does not update any other metadata. +pub unsafe fn write_inode(index: Index, sb: &Superblock, bd: &mut dyn BlockDevice, mut inode: Inode) -> bool { + inode.convert_native_to_big_endian(); + let mut buf: [u8; core::mem::size_of::()] = [0; core::mem::size_of::()]; + core::ptr::write(buf.as_mut_ptr() as *mut Inode, inode); + bd.seek((sb.first_inode_block * sb.block_size as u64) + (index * core::mem::size_of::() as u64)); + let write_count = bd.write_blocks(&buf); + write_count == core::mem::size_of::() +} + +/// Reads a single datablock into memory, may return less than the block size if the end of the block device is reached. +pub fn read_datablock(index: Index, sb: &Superblock, bd: &mut dyn BlockDevice) -> Vec { + let mut buf: Vec = Vec::new(); + buf.resize(sb.block_size as usize, 0); + bd.seek((sb.first_data_block * sb.block_size as u64) + (index * sb.block_size as u64)); + bd.read_blocks(&mut buf); + buf +} + +/// Performs a direct write of a datablock to the block device. +/// # Safety +/// unsafe because it does not journal the write, and does not update any other metadata. +pub unsafe fn write_datablock(index: Index, sb: &Superblock, bd: &mut dyn BlockDevice, buf: &[u8]) -> bool { + bd.seek((sb.first_data_block * sb.block_size as u64) + (index * sb.block_size as u64)); + let write_count = bd.write_blocks(buf); + write_count == sb.block_size as usize +} + +/// Checks if a datablock is allocated. +/// Will return None if the index is out of bounds or if the block device cannot fill the buffer. +pub fn is_datablock_allocated(index: Index, sb: &Superblock, bd: &mut dyn BlockDevice) -> Option { + // datablock bitmap is at 1024 + 156 byte offset, length is data_block_count / 8 rounded up + let bitmap_offset = 1024 + 156; + let bitmap_length = (sb.data_block_count + 7) / 8; + if index >= bitmap_length { + return None; + } + let mut bitmap_buf: Vec = Vec::new(); + bitmap_buf.resize(bitmap_length as usize, 0); + bd.seek(bitmap_offset); + let read_count = bd.read_blocks(&mut bitmap_buf); + if read_count < bitmap_length as usize { + return None; + } + Some(bitmap::get_bit(&bitmap_buf, index as usize) == bitmap::SET) +} + +/// Checks if an inode is allocated. +/// Will return None if the index is out of bounds or if the block device cannot fill the buffer. +pub fn is_inode_allocated(index: Index, sb: &Superblock, bd: &mut dyn BlockDevice) -> Option { + // inode bitmap is at 1024 + 156 + datablock_bitmap_length byte offset, + // length is inode_count / 8 rounded up + let bitmap_offset = 1024 + 156 + ((sb.data_block_count + 7) / 8); + let bitmap_length = (sb.inode_count + 7) / 8; + if index >= bitmap_length { + return None; + } + let mut bitmap_buf: Vec = Vec::new(); + bitmap_buf.resize(bitmap_length as usize, 0); + bd.seek(bitmap_offset); + let read_count = bd.read_blocks(&mut bitmap_buf); + if read_count < bitmap_length as usize { + return None; + } + Some(bitmap::get_bit(&bitmap_buf, index as usize) == bitmap::SET) +} + +/// Finds the first unallocated datablock and returns its index. +/// Will return None if no unallocated datablock is found, or if the block device cannot fill the buffer. +pub fn find_first_unallocated_datablock(sb: &Superblock, bd: &mut dyn BlockDevice) -> Option { + // datablock bitmap is at 1024 + 156 byte offset, length is data_block_count / 8 rounded up + let bitmap_offset = 1024 + 156; + let bitmap_length = (sb.data_block_count + 7) / 8; + let mut bitmap_buf: Vec = Vec::new(); + bitmap_buf.resize(bitmap_length as usize, 0); + bd.seek(bitmap_offset); + let read_count = bd.read_blocks(&mut bitmap_buf); + if read_count < bitmap_length as usize { + return None; + } + bitmap::find_first_bit_equal_to(&bitmap_buf, bitmap::UNSET).map(|index| index as Index) +} + +/// Finds the first unallocated inode and returns its index. +/// Will return None if no unallocated inode is found, or if the block device cannot fill the buffer. +pub fn find_first_unallocated_inode(sb: &Superblock, bd: &mut dyn BlockDevice) -> Option { + // inode bitmap is at 1024 + 156 + datablock_bitmap_length byte offset, + // length is inode_count / 8 rounded up + let bitmap_offset = 1024 + 156 + ((sb.data_block_count + 7) / 8); + let bitmap_length = (sb.inode_count + 7) / 8; + let mut bitmap_buf: Vec = Vec::new(); + bitmap_buf.resize(bitmap_length as usize, 0); + bd.seek(bitmap_offset); + let read_count = bd.read_blocks(&mut bitmap_buf); + if read_count < bitmap_length as usize { + return None; + } + bitmap::find_first_bit_equal_to(&bitmap_buf, bitmap::UNSET).map(|index| index as Index) +} + +/// Sets the allocation status of a datablock. +/// # Safety +/// unsafe because it does not journal the write, and does not update any other metadata. +pub unsafe fn set_datablock_allocation_status(index: Index, sb: &Superblock, bd: &mut dyn BlockDevice, allocated: bool) -> bool { + // todo! we should maybe optimise this to only write the byte that contains the bit, instead of the whole bitmap + // todo! see how much time this saves? + + // datablock bitmap is at 1024 + 156 byte offset, length is data_block_count / 8 rounded up + let bitmap_offset = 1024 + 156; + let bitmap_length = (sb.data_block_count + 7) / 8; + if index >= bitmap_length { + return false; + } + let mut bitmap_buf: Vec = Vec::new(); + bitmap_buf.resize(bitmap_length as usize, 0); + bd.seek(bitmap_offset); + let read_count = bd.read_blocks(&mut bitmap_buf); + if read_count < bitmap_length as usize { + return false; + } + bitmap::set_bit(&mut bitmap_buf, index as usize, allocated); + bd.seek(bitmap_offset); + let write_count = bd.write_blocks(&bitmap_buf); + write_count == bitmap_length as usize +} + +/// Sets the allocation status of an inode. +/// # Safety +/// unsafe because it does not journal the write, and does not update any other metadata. +pub unsafe fn set_inode_allocation_status(index: Index, sb: &Superblock, bd: &mut dyn BlockDevice, allocated: bool) -> bool { + // todo! we should maybe optimise this to only write the byte that contains the bit, instead of the whole bitmap + // todo! see how much time this saves? + + // inode bitmap is at 1024 + 156 + datablock_bitmap_length byte offset, + // length is inode_count / 8 rounded up + let bitmap_offset = 1024 + 156 + ((sb.data_block_count + 7) / 8); + let bitmap_length = (sb.inode_count + 7) / 8; + if index >= bitmap_length { + return false; + } + let mut bitmap_buf: Vec = Vec::new(); + bitmap_buf.resize(bitmap_length as usize, 0); + bd.seek(bitmap_offset); + let read_count = bd.read_blocks(&mut bitmap_buf); + if read_count < bitmap_length as usize { + return false; + } + bitmap::set_bit(&mut bitmap_buf, index as usize, allocated); + bd.seek(bitmap_offset); + let write_count = bd.write_blocks(&bitmap_buf); + write_count == bitmap_length as usize } \ No newline at end of file diff --git a/src/structs.rs b/src/structs.rs index bc49a15..f39200a 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,4 +1,5 @@ use vapfs::{Index, Timestamp}; +use crate::crc32; /// # Magic /// Constant value that identifies the Superblock as a valid VapUFS filesystem @@ -37,12 +38,85 @@ pub struct Superblock { pub creation_time: Timestamp, /// timestamp of last modification pub last_modification_time: Timestamp, + /// crc32 checksum of this Superblock + pub checksum: u32, + /// reserved u32 + pub reserved_1: u32, /// reserved values for expansion - pub reserved: [u64; 8], + 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)] @@ -123,9 +197,82 @@ pub struct Inode { 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 @@ -136,4 +283,144 @@ pub enum InodeFlags { CORRUPT = 1 << 0, /// File uses indirect blocks INDIRECT = 1 << 1, +} + +/// # JournalEntry +/// A Journal Entry +#[repr(C)] +pub struct JournalHeader { + pub flags: u32, + pub operation: JournalOperation, + /// 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: JournalEntry, +} + +/// # 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 { + /// flags stating how far the write has progressed + pub flags: JBRFlags, + /// are we writing to an inode instead of a data block? + pub target_is_inode: bool, + /// block number of target + 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, +} + +/// # JBRFlags +/// Flags field of a JournalBlockWrite +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum JBRFlags { + /// source data block has been allocated + Allocated = 1, + /// data has been written to the source data block + Stored = 2, + /// source data block has either replaced an old data block or has been written to an inode + Written = 3, + /// 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 = 4, +} + +/// # 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 { + /// flags stating how far the write has progressed + pub flags: JMWFlags, + /// 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, +} + +/// # JMWFlags +/// Flags field of a JournalMultiblockWrite +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum JMWFlags { + /// list block has been allocated + AllocatedList = 1, + /// data blocks have been allocated + AllocatedData = 2, + /// data has been written to the data blocks and the list block + Stored = 3, + /// data blocks have replaced old data blocks + Written = 4, + /// data blocks and list block have been deallocated + /// (i.e. this journal entry has been fully committed) + CompleteAndDeallocated = 5, +} + +/// # 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], +} + +/// # JournalEntry +/// union of all possible journal entries +#[repr(C)] +pub union JournalEntry { + pub block_write: JournalBlockWrite, + pub multiblock_write: JournalMultiblockWrite, } \ No newline at end of file