From fad9028945c376447b01ada5ba32c760bf4b39bf Mon Sep 17 00:00:00 2001 From: tsatke Date: Mon, 24 Jun 2024 12:58:02 +0200 Subject: [PATCH] add support for creating and writing to a file (only direct pointer support, which means file sizes are limited to fs.block_size * 12) --- Cargo.lock | 199 ++++++++++++++++++++++++++++++++++------- ext2/Cargo.toml | 3 + ext2/src/address.rs | 8 ++ ext2/src/create.rs | 24 +++++ ext2/src/dir.rs | 152 ++++++++++++++++++++++++++----- ext2/src/error.rs | 1 + ext2/src/inode.rs | 36 +++++++- ext2/src/lib.rs | 52 +++++++---- ext2/src/read.rs | 4 +- ext2/src/superblock.rs | 4 + ext2/src/write.rs | 7 +- ext2/tests/common.rs | 34 +++++++ ext2/tests/write.rs | 31 +++++++ 13 files changed, 482 insertions(+), 73 deletions(-) create mode 100644 ext2/src/create.rs create mode 100644 ext2/tests/common.rs create mode 100644 ext2/tests/write.rs diff --git a/Cargo.lock b/Cargo.lock index 2f0d47f..c06f587 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -48,7 +48,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -59,15 +59,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] -name = "cc" -version = "1.0.79" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" @@ -119,23 +119,23 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "cc", + "cfg-if", "libc", + "wasi", ] [[package]] @@ -158,7 +158,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -170,14 +170,14 @@ dependencies = [ "hermit-abi", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "libc" -version = "0.2.144" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" @@ -199,8 +199,9 @@ dependencies = [ name = "mkfs-ext2" version = "0.1.0" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.5.0", "mkfs-filesystem", + "rand", ] [[package]] @@ -213,6 +214,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.66" @@ -231,6 +238,37 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.9.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31e63ea85be51c423e52ba8f2e68a3efd53eed30203ee029dd09947333693e" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78674ef918c19451dbd250f8201f8619b494f64c9aa6f3adb28fd8a0f1f6da46" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc89dffba8377c5ec847d12bb41492bda235dba31a25e8b695cd0fe6589eb8c9" +dependencies = [ + "getrandom", + "zerocopy", +] + [[package]] name = "rustix" version = "0.37.19" @@ -242,7 +280,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -253,9 +291,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.18" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -274,13 +312,28 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", ] [[package]] @@ -289,13 +342,29 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -304,38 +373,106 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zerocopy" +version = "0.8.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db678a6ee512bd06adf35c35be471cae2f9c82a5aed2b5d15e03628c98bddd57" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201585ea96d37ee69f2ac769925ca57160cef31acb137c16f38b02b76f4c1e62" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/ext2/Cargo.toml b/ext2/Cargo.toml index 6be32bb..e7e8d20 100644 --- a/ext2/Cargo.toml +++ b/ext2/Cargo.toml @@ -12,3 +12,6 @@ name = "ext2" [dependencies] bitflags = "2.3.1" mkfs-filesystem = { version = "0.1.0", path = "../filesystem" } + +[dev-dependencies] +rand = "0.9.0-alpha.1" \ No newline at end of file diff --git a/ext2/src/address.rs b/ext2/src/address.rs index bb1f35f..fe88450 100644 --- a/ext2/src/address.rs +++ b/ext2/src/address.rs @@ -2,6 +2,7 @@ use core::num::NonZeroU32; use core::ops::Deref; #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[repr(transparent)] pub struct InodeAddress(NonZeroU32); impl Deref for InodeAddress { @@ -18,6 +19,12 @@ impl From for InodeAddress { } } +impl From for u32 { + fn from(value: InodeAddress) -> u32 { + value.0.get() + } +} + impl InodeAddress { pub const fn new(n: u32) -> Option { let nzu32 = NonZeroU32::new(n); @@ -29,6 +36,7 @@ impl InodeAddress { } #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[repr(transparent)] pub struct BlockAddress(NonZeroU32); impl Deref for BlockAddress { diff --git a/ext2/src/create.rs b/ext2/src/create.rs new file mode 100644 index 0000000..f5076eb --- /dev/null +++ b/ext2/src/create.rs @@ -0,0 +1,24 @@ +use filesystem::BlockDevice; + +use crate::{Directory, Error, Ext2Fs, Inode, InodeAddress, RegularFile, Type}; + +impl Ext2Fs +where + T: BlockDevice, +{ + pub fn create_inode(&mut self, parent: &mut Directory, name: &str, typ: Type) -> Result<(InodeAddress, Inode), Error> { + let inode_address = self.allocate_inode()?.ok_or(Error::NoSpace)?; + let inode = Inode::new(typ); + + self.write_inode(inode_address, &inode)?; + + self.add_entry_to_dir(parent, name, inode_address, inode.typ().into())?; + + Ok((inode_address, inode)) + } + + pub fn create_regular_file(&mut self, parent: &mut Directory, name: &str) -> Result { + self.create_inode(parent, name, Type::RegularFile) + .map(|v| v.try_into().unwrap()) // if we don't get an inode with type RegularFile, something is really broken + } +} \ No newline at end of file diff --git a/ext2/src/dir.rs b/ext2/src/dir.rs index 8e5837d..fd2811b 100644 --- a/ext2/src/dir.rs +++ b/ext2/src/dir.rs @@ -1,15 +1,18 @@ -use crate::error::Error; -use crate::superblock::RequiredFeatures; -use crate::{ - bytefield, bytefield_field_read, bytefield_field_write, check_is_implemented, Directory, - Ext2Fs, Inode, InodeAddress, Type, -}; use alloc::vec; use alloc::vec::Vec; -use bitflags::bitflags; use core::fmt::{Debug, Formatter}; + +use bitflags::bitflags; + use filesystem::BlockDevice; +use crate::{ + bytefield, bytefield_field_read, bytefield_field_write, check_is_implemented, Directory, + Ext2Fs, Inode, InodeAddress, Type, +}; +use crate::error::Error; +use crate::superblock::RequiredFeatures; + impl Ext2Fs where T: BlockDevice, @@ -27,7 +30,7 @@ where .required_features() .contains(RequiredFeatures::DIRECTORY_ENTRIES_HAVE_TYPE); - for addr in (0_usize..12).filter_map(|i| dir.direct_ptr(i)) { + for addr in dir.direct_ptrs().filter_map(|v| v) { let mut data = vec![0_u8; block_size]; self.read_block(addr, &mut data) .map_err(|_| Error::DeviceRead)?; @@ -35,17 +38,13 @@ where let mut offset = 0; while offset < block_size - 8 { let dir_entry = DirEntry::from(dir_entries_have_type, &data[offset..]); - offset += dir_entry.total_size as usize; // we don't need to align the offset, as there must be no space between entries - if dir_entry.inode().is_none() { - // entry invalid, move on - continue; - } + offset += dir_entry.total_size as usize; entries.push(dir_entry); } } - // TODO: handle indirect ptrs + // TODO: handle indirect ptrsa Ok(entries) } @@ -66,14 +65,83 @@ where } pub fn resolve_dir_entry(&self, entry: DirEntry) -> Result<(InodeAddress, Inode), Error> { - let address = - InodeAddress::new(entry.inode).ok_or(Error::InvalidInodeAddress(entry.inode))?; - self.read_inode(address) + self.read_inode(entry.inode) + } + + pub fn add_entry_to_dir( + &mut self, + dir: &mut Directory, + name: &str, + inode_address: InodeAddress, + typ: DirType, + ) -> Result<(), Error> { + let block_size = self.superblock.block_size() as usize; + let dir_entries_have_type = self + .superblock + .required_features() + .contains(RequiredFeatures::DIRECTORY_ENTRIES_HAVE_TYPE); + + let inode = &mut dir.inode_mut(); + + // compute the size of the directory entry that we need + let required_size = DirEntry::size(name.len() as u16); + + // find a free slot and insert the entry + for block in inode.direct_ptrs().filter_map(|v| v) { + let mut block_data = vec![0_u8; block_size]; + self.read_block(block, &mut block_data)?; + + // In the block, we need to find the first entry, where + // entry.total_size - DirEntry::size(..., entry.name_length) >= required_size, + // adapt that entry and store our entry there. + let mut offset = 0; + while offset < block_size - 8 { + debug_assert_eq!(offset % 4, 0, "offset is not aligned"); + + let mut entry = DirEntry::from(dir_entries_have_type, &block_data[offset..]); + let entry_size = DirEntry::size(entry.name_length); + if entry.total_size >= required_size + entry_size { + // we found a slot that is big enough + + let old_total_size = entry.total_size; + entry.total_size = entry_size; // resize the old entry + + // merge the old entry back into the block data + let entry_serialized = entry.serialize(dir_entries_have_type); + block_data[offset..offset + entry_serialized.len()].copy_from_slice(&entry_serialized); + + let new_entry_total_size = old_total_size - entry_size; + let new_entry_offset = offset + entry_size as usize; + debug_assert_eq!(new_entry_offset % 4, 0, "new entry offset is not aligned"); + + let new_entry = DirEntry { + inode: inode_address, + total_size: new_entry_total_size, + name_length: name.len() as u16, + type_indicator: if dir_entries_have_type { Some(typ) } else { None }, + name_bytes: name.as_bytes().to_vec(), + }; + + // merge the new entry into the block data + let new_entry_serialized = new_entry.serialize(dir_entries_have_type); + block_data[new_entry_offset..new_entry_offset + new_entry_serialized.len()].copy_from_slice(&new_entry_serialized); + + // write the block back to the device + self.write_block(block, &block_data)?; + + return Ok(()); + } + + offset += entry.total_size as usize; + } + } + + todo!("add_entry_to_dir with indirect pointers") } } pub struct DirEntry { - inode: u32, + inode: InodeAddress, total_size: u16, name_length: u16, type_indicator: Option, @@ -81,6 +149,15 @@ pub struct DirEntry { } impl DirEntry { + const fn size(name_length: u16) -> u16 { + let unaligned_size = 4 + // inode + 2 + // total_size + 2 + // name_length and type_indicator + name_length; + // align up to 4 byte + (unaligned_size + 3) & !3 + } + fn from(dir_entries_have_type: bool, value: &[u8]) -> Self { debug_assert!( value.len() >= 8, @@ -103,7 +180,7 @@ impl DirEntry { }; let name_bytes = value[8..8 + name_length as usize].to_vec(); Self { - inode: arr.inode, + inode: InodeAddress::new(arr.inode).unwrap(), total_size: arr.total_size, name_length, type_indicator, @@ -111,6 +188,24 @@ impl DirEntry { } } + pub fn serialize(self, dir_entries_have_type: bool) -> Vec { + let mut result = Vec::with_capacity(Self::size(self.name_length) as usize); + + let mut entry_no_name = DirEntryNoName::try_from([0; 8]).unwrap(); + entry_no_name.inode = self.inode.into(); + entry_no_name.total_size = self.total_size; + entry_no_name.name_length_lsb = self.name_length as u8; + if dir_entries_have_type { + entry_no_name.type_indicator_or_name_length_msb = self.type_indicator.unwrap().bits(); + } else { + entry_no_name.type_indicator_or_name_length_msb = (self.name_length >> 8) as u8; // TODO: check for data loss + } + + result.extend_from_slice(&<[u8; 8]>::from(entry_no_name)); + result.extend_from_slice(&self.name_bytes); + result + } + pub fn name(&self) -> Option<&str> { match core::str::from_utf8(&self.name_bytes) { Ok(s) => Some(s), @@ -122,8 +217,8 @@ impl DirEntry { self.type_indicator } - pub fn inode(&self) -> Option { - InodeAddress::new(self.inode) + pub fn inode(&self) -> InodeAddress { + self.inode } } @@ -168,3 +263,18 @@ bitflags! { const SymLink = 7; } } + +impl From for DirType { + fn from(value: Type) -> Self { + match value { + Type::RegularFile => Self::RegularFile, + Type::Directory => Self::Directory, + Type::CharacterDevice => Self::CharacterDevice, + Type::BlockDevice => Self::BlockDevice, + Type::FIFO => Self::FIFO, + Type::UnixSocket => Self::UnixSocket, + Type::SymLink => Self::SymLink, + _ => panic!("invalid type") + } + } +} \ No newline at end of file diff --git a/ext2/src/error.rs b/ext2/src/error.rs index 6179224..0771101 100644 --- a/ext2/src/error.rs +++ b/ext2/src/error.rs @@ -13,6 +13,7 @@ pub enum Error { DeviceWrite, InvalidInodeAddress(u32), NoSpace, + NotSupported, } impl Display for Error { diff --git a/ext2/src/inode.rs b/ext2/src/inode.rs index 36afb4e..0e8aa7d 100644 --- a/ext2/src/inode.rs +++ b/ext2/src/inode.rs @@ -128,6 +128,34 @@ bytefield! { } impl Inode { + /// Creates a new inode with the given type. + /// All other fields are uninitialized (zeroed). + pub fn new(typ: Type) -> Self { + Self { + type_and_perm: typ.bits(), + user_id: 0, + byte_size_lower: 0, + last_access_time: 0, + creation_time: 0, + last_modification_time: 0, + deletion_time: 0, + group_id: 0, + num_hard_links: 0, + num_disk_sectors: 0, + flags: 0, + os_val_1: [0; 4], + direct_block_ptr: [0; 12], + singly_indirect_block_ptr: 0, + doubly_indirect_block_ptr: 0, + triply_indirect_block_ptr: 0, + generation: 0, + extended_attribute_block: 0, + byte_size_upper_or_dir_acl: 0, + fragment_block_address: 0, + os_val_2: [0; 12], + } + } + pub fn typ(&self) -> Type { Type::from_bits_truncate(self.type_and_perm) } @@ -148,8 +176,12 @@ impl Inode { self.byte_size_upper_or_dir_acl = size; } - pub fn direct_ptr(&self, index: usize) -> Option { - BlockAddress::new(self.direct_block_ptr[index]) + pub fn num_disk_sectors(&self) -> u32 { + self.num_disk_sectors + } + + pub fn num_disk_sectors_mut(&mut self) -> &mut u32 { + &mut self.num_disk_sectors } pub fn direct_ptrs(&self) -> impl Iterator> + '_ { diff --git a/ext2/src/lib.rs b/ext2/src/lib.rs index 22eef65..a9557ee 100644 --- a/ext2/src/lib.rs +++ b/ext2/src/lib.rs @@ -18,10 +18,11 @@ use crate::block_group::{BlockGroupDescriptor, BlockGroupDescriptorTable}; mod address; mod block_group; mod bytefield; +mod create; mod dir; mod error; -mod read; mod inode; +mod read; mod superblock; mod write; @@ -81,6 +82,7 @@ where } pub fn read_inode(&self, addr: InodeAddress) -> Result<(InodeAddress, Inode), Error> { + // FIXME: reading the inode will create multiple copies that alias the data in the block device, there needs to be some kind of cache that centralizes the inodes (and their data) let inodes_per_group = self.superblock.inodes_per_group(); let block_group_index = (addr.get() - 1) / inodes_per_group; let block_group = &self.bgdt[block_group_index as usize]; @@ -138,16 +140,30 @@ where } pub fn allocate_block(&mut self) -> Result, Error> { - let block_size = self.superblock.block_size(); let blocks_per_group = self.superblock.blocks_per_group(); + self.allocate_resource(blocks_per_group, Self::try_reserve_block_in_group) + .map(|block| block.map(BlockAddress::new).flatten()) + } + + pub fn allocate_inode(&mut self) -> Result, Error> { + let inodes_per_group = self.superblock.inodes_per_group(); + self.allocate_resource(inodes_per_group, Self::try_reserve_inode_in_group) + .map(|inode| inode.map(InodeAddress::new).flatten()) + } + + fn allocate_resource(&mut self, resource_per_group: u32, try_reserve_in_group: F) -> Result, Error> + where + F: Fn(&mut Self, usize) -> Result, Error>, + { + let block_size = self.superblock.block_size(); let num_groups = self.bgdt.len(); for group_index in 0..num_groups { - let first_free_block_index = self.try_reserve_block_in_group(group_index)?; - if first_free_block_index.is_none() { + let first_free_resource_index = try_reserve_in_group(self, group_index)?; + if first_free_resource_index.is_none() { continue; } - let first_free_block_index = first_free_block_index.unwrap(); + let first_free_resource_index = first_free_resource_index.unwrap(); let descriptor = &mut self.bgdt[group_index]; *descriptor.num_unallocated_blocks_mut() -= 1; @@ -167,30 +183,36 @@ where .write_at(BLOCK_GROUP_DESCRIPTOR_TABLE_OFFSET, &bgdt_data) .map_err(|_| Error::UnableToWriteBlockGroupDescriptorTable)?; - *self.superblock.num_unallocated_blocks_mut() -= 1; let superblock_data = Into::::into(&self.superblock); self.block_device .write_at(SUPERBLOCK_OFFSET, superblock_data.as_slice()) .map_err(|_| Error::UnableToWriteSuperblock)?; - let global_block_num = group_index as u32 * blocks_per_group + first_free_block_index as u32; - return Ok(Some(BlockAddress::new(global_block_num).unwrap())); + let global_resource_num = group_index as u32 * resource_per_group + first_free_resource_index as u32; + return Ok(Some(global_resource_num)); } Ok(None) } - /// Tries to allocate a block in the given block group and returns the index in the group - /// if successful. This reads the block usage bitmap from the block group, maybe modifies - /// it and writes it back. fn try_reserve_block_in_group(&mut self, group_index: usize) -> Result, Error> { - let mut block_bitmap = vec![0_u8; self.superblock.block_size() as usize]; let bitmap_block = self.bgdt[group_index].block_usage_bitmap_block(); let bitmap_block_address = BlockAddress::new(bitmap_block).expect("bgdt does not have valid block address for bitmap block"); - self.read_block(bitmap_block_address, &mut block_bitmap)?; + self.try_reserve_in_group_with_bitmap(bitmap_block_address) + } + + fn try_reserve_inode_in_group(&mut self, group_index: usize) -> Result, Error> { + let bitmap_block = self.bgdt[group_index].inode_usage_bitmap_block(); + let bitmap_block_address = BlockAddress::new(bitmap_block).expect("bgdt does not have valid block address for bitmap block"); + self.try_reserve_in_group_with_bitmap(bitmap_block_address) + } + + fn try_reserve_in_group_with_bitmap(&mut self, bitmap_block: BlockAddress) -> Result, Error> { + let mut inode_bitmap = vec![0_u8; self.superblock.block_size() as usize]; + self.read_block(bitmap_block, &mut inode_bitmap)?; - for (i, byte) in block_bitmap.iter_mut().enumerate() { + for (i, byte) in inode_bitmap.iter_mut().enumerate() { for bit_index in 0..8 { if *byte & (1_u8 << bit_index) == 0 { *byte |= 1 << bit_index; @@ -198,7 +220,7 @@ where } } } - self.write_block(bitmap_block_address, &block_bitmap)?; + self.write_block(bitmap_block, &inode_bitmap)?; Ok(None) } } diff --git a/ext2/src/read.rs b/ext2/src/read.rs index 8bc874c..322c769 100644 --- a/ext2/src/read.rs +++ b/ext2/src/read.rs @@ -52,7 +52,7 @@ where for (i, block) in (start_block..=end_block).enumerate() { let block_data = &mut buf[i * block_size..(i + 1) * block_size]; let block_pointer = if block < direct_limit as usize { - inode.direct_ptr(block) + inode.direct_ptrs().nth(block).flatten() } else if block < indirect_limit as usize { self.resolve_indirect_ptr(inode.single_indirect_ptr(), block as u32 - direct_limit)? } else if block < double_indirect_limit as usize { @@ -88,7 +88,7 @@ where Ok( if block_index < direct_limit { - inode.direct_ptr(block_index as usize) + inode.direct_ptrs().nth(block_index as usize).flatten() } else if block_index < indirect_limit { self.resolve_indirect_ptr(inode.single_indirect_ptr(), block_index - direct_limit)? } else if block_index < double_indirect_limit { diff --git a/ext2/src/superblock.rs b/ext2/src/superblock.rs index b79bd7e..72cfed7 100644 --- a/ext2/src/superblock.rs +++ b/ext2/src/superblock.rs @@ -109,6 +109,10 @@ impl Superblock { self.num_unallocated_inodes } + pub fn num_unallocated_inodes_mut(&mut self) -> &mut u32 { + &mut self.num_unallocated_inodes + } + pub fn superblock_block_number(&self) -> u32 { self.superblock_block_number } diff --git a/ext2/src/write.rs b/ext2/src/write.rs index 885e192..5127953 100644 --- a/ext2/src/write.rs +++ b/ext2/src/write.rs @@ -8,7 +8,6 @@ impl Ext2Fs where T: BlockDevice, { - #[allow(unused_variables)] pub fn write_to_file( &mut self, file: &mut RegularFile, @@ -34,8 +33,9 @@ where data }; + let mut num_new_allocated_blocks = 0; let mut chunks = data.chunks_exact(block_size as usize); - for ((i, block), chunk) in (start_block..=end_block).enumerate().zip(&mut chunks) { + for ((_, block), chunk) in (start_block..=end_block).enumerate().zip(&mut chunks) { let block_address = if let Some(block_address) = self.resolve_block_index(file, block)? { block_address @@ -46,6 +46,7 @@ where return Err(Error::NoSpace); } let free_block_address = free_block_address.unwrap(); + num_new_allocated_blocks += 1; let inode = file.inode_mut(); let free_slot = inode.direct_ptrs() @@ -75,6 +76,8 @@ where inode.set_file_size_lower(new_size_lower); inode.set_file_size_upper(new_size_upper); + *inode.num_disk_sectors_mut() += num_new_allocated_blocks; + self.write_inode(file.inode_address(), file)?; } diff --git a/ext2/tests/common.rs b/ext2/tests/common.rs new file mode 100644 index 0000000..649e8f1 --- /dev/null +++ b/ext2/tests/common.rs @@ -0,0 +1,34 @@ +use std::env::temp_dir; +use std::fs; +use std::path::{Path, PathBuf}; + +use rand::distributions::Alphanumeric; +use rand::Rng; + +#[macro_export] +macro_rules! generate_tests { + ($test_fn:ident : $($size:literal - $name:ident),*,) => { + const _: &dyn Fn(usize) = &$test_fn; + $( + #[test] + fn $name() { + $test_fn($size); + } + )* + }; +} + +pub fn copy_test_image_for_use(test_image: impl AsRef) -> PathBuf { + let temp_dir = temp_dir(); + let name = rand::thread_rng().sample_iter(&Alphanumeric).take(10).map(char::from).collect::(); + let temp_image = temp_dir.join(format!("{}.img", name)); + + fs::copy(test_image, &temp_image).unwrap(); + temp_image +} + +pub fn load_copy_of_image(test_image: impl AsRef) -> Vec { + let copy = copy_test_image_for_use(test_image); + println!("Using image: {:?}", copy); + fs::read(©).unwrap() +} \ No newline at end of file diff --git a/ext2/tests/write.rs b/ext2/tests/write.rs new file mode 100644 index 0000000..352490a --- /dev/null +++ b/ext2/tests/write.rs @@ -0,0 +1,31 @@ +use ext2::Ext2Fs; +use filesystem::MemoryBlockDevice; + +mod common; + +generate_tests!( + test_create_and_write_file: + 512 - test_create_and_write_file_standard, + 1 - test_create_and_write_file_tiny, + 32 - test_create_and_write_file_small, + 32768 - test_create_and_write_file_large, + 1048576 - test_create_and_write_file_huge, +); + +fn test_create_and_write_file(sector_size: usize) { + let image_data = common::load_copy_of_image("tests/filesystems/empty.img"); + let device = MemoryBlockDevice::try_new(sector_size, image_data).unwrap(); + let mut fs = Ext2Fs::try_new(device).unwrap(); + + let file_name = "my_file.txt"; + let mut root = fs.read_root_inode().unwrap(); + let mut file = fs.create_regular_file(&mut root, file_name).unwrap(); + assert!(fs.list_dir(&root).unwrap().iter().find(|e| e.name() == Some(file_name)).is_some()); + assert_eq!(file.len(), 0); + + let data = b"Hello, world!"; + // write `data` until all direct pointers are used + for i in 0..((1024 * 12) / data.len()) { + assert_eq!(fs.write_to_file(&mut file, i * data.len(), data).unwrap(), data.len()); + } +} \ No newline at end of file