diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..64cc81d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "vapfs_ufs" +version = "0.1.0" +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 diff --git a/src/bitmap.rs b/src/bitmap.rs new file mode 100644 index 0000000..d3390e9 --- /dev/null +++ b/src/bitmap.rs @@ -0,0 +1,31 @@ +pub const SET: bool = true; +pub const UNSET: bool = false; + +/// returns the bit index of the first bit that is, if value is true, set, or if value is false, unset +pub fn find_first_bit_equal_to(bitmap: &[u8], value: bool) -> Option { + for (byte_index, byte) in bitmap.iter().enumerate() { + for bit_index in 0..8 { + let bit = (byte >> bit_index) & 1; + if bit == (value as u8) { + return Some(byte_index * 8 + bit_index); + } + } + } + None +} + +/// sets the bit at the given index to the given value +pub fn set_bit(bitmap: &mut [u8], index: usize, value: bool) { + let byte_index = index / 8; + let bit_index = index % 8; + let bit = (value as u8) << bit_index; + bitmap[byte_index] |= bit; +} + +/// returns the bit at the given index +pub fn get_bit(bitmap: &[u8], index: usize) -> bool { + let byte_index = index / 8; + let bit_index = index % 8; + let bit = (bitmap[byte_index] >> bit_index) & 1; + bit == 1 +} \ No newline at end of file diff --git a/src/btree.rs b/src/btree.rs new file mode 100644 index 0000000..1c5d278 --- /dev/null +++ b/src/btree.rs @@ -0,0 +1,182 @@ +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use vapfs::{BlockDevice, Index}; + +/// # BTd +/// min-degree of all BTrees +pub const BTd: u32 = 3; + +/// # BTKeyType +/// key used to index a BTree +pub type BTKeyType = u32; + +/// # BTKey +/// whole value of a key in a BTree +#[repr(C)] +#[derive(Copy, Clone)] +pub struct BTKey { + pub key: BTKeyType, + /// length of this entry in bytes + pub length: u32, + /// amount of files represented by this key, used in the case of hash collisions + pub count: u32, + /// if count is 1, this is the inode number of the file represented by this key + /// if count is >1, offset from first BTNode in bytes to an HCList struct + pub value: Index, + /// length of filename + /// zero if count is >1 + pub filename_length: u32, + // rest of the struct is the filename +} + +/// # HCList +/// list of files represented by a single key in a BTree, used in the case of hash collisions +#[repr(C)] +#[derive(Copy, Clone)] +pub struct HCList { + /// offset from first HCList in bytes to the next HCList + pub next: Index, + /// inode number of the file represented by this HCList + pub inode: Index, + /// length of filename + pub filename_length: u32, + // rest of the struct is the filename +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct BTNode { + /// offset from first BTNode in bytes, each entry is a DirectoryKey + pub key_array: Index, + pub key_count: u32, + /// offset from first BTNode in bytes, each entry is another BTNode + pub child_array: Index, + pub child_count: u32, +} + +impl BTKey { + /// 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.key = u32::from_be(self.key); + self.length = u32::from_be(self.length); + self.count = u32::from_be(self.count); + self.value = u64::from_be(self.value); + self.filename_length = u32::from_be(self.filename_length); + } + } + + /// 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.key = u32::to_be(self.key); + self.length = u32::to_be(self.length); + self.count = u32::to_be(self.count); + self.value = u64::to_be(self.value); + self.filename_length = u32::to_be(self.filename_length); + } + } + + /// offset is from 0x0 in the block device to the beginning of the struct + pub fn read(offset: Index, bd: &mut dyn BlockDevice) -> Option<(Self, String)> { + let mut buf: [u8; core::mem::size_of::()] = [0; core::mem::size_of::()]; + bd.seek(offset); + let read_count = bd.read_blocks(&mut buf); + if read_count < core::mem::size_of::() { + return None; + } + let mut key = unsafe { core::ptr::read(buf.as_ptr() as *const Self) }; + key.convert_big_endian_to_native(); + let filename = key.read_filename(offset, bd); + Some((key, filename)) + } + + /// offset is from 0x0 in the block device to the beginning of the struct + pub fn read_filename(&self, offset: Index, bd: &mut dyn BlockDevice) -> String { + let mut filename_buf: Vec = Vec::new(); + filename_buf.resize(self.filename_length as usize, 0); + bd.seek(offset); + bd.read_blocks(&mut filename_buf); + String::from_utf8_lossy(unsafe { core::slice::from_raw_parts(filename_buf.as_ptr(), filename_buf.len()) }).to_string() + } +} + +impl BTNode { + pub const fn new(key_array: Index, key_count: u32, child_array: Index, child_count: u32) -> Self { + Self { + key_array, + key_count, + child_array, + child_count, + } + } + + /// 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.key_array = u64::from_be(self.key_array); + self.key_count = u32::from_be(self.key_count); + self.child_array = u64::from_be(self.child_array); + self.child_count = u32::from_be(self.child_count); + } + } + + /// 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.key_array = u64::to_be(self.key_array); + self.key_count = u32::to_be(self.key_count); + self.child_array = u64::to_be(self.child_array); + self.child_count = u32::to_be(self.child_count); + } + } + + /// offset is from root in bytes + pub fn read(root: Index, offset: Index, bd: &mut dyn BlockDevice) -> Option { + let mut buf: [u8; core::mem::size_of::()] = [0; core::mem::size_of::()]; + bd.seek(root + offset); + let read_count = bd.read_blocks(&mut buf); + if read_count < core::mem::size_of::() { + return None; + } + let mut node = unsafe { core::ptr::read(buf.as_ptr() as *const Self) }; + node.convert_big_endian_to_native(); + Some(node) + } + + pub fn search(self, root: Index, key_to_find: BTKeyType, bd: &mut dyn BlockDevice) -> Option<(BTKey, String)> { + let mut node = self; + loop { + let mut i = 0; + let mut last_key: Option = None; + while i < node.key_count { + let key_offset = if i == 0 { + node.key_array + } else { + last_key.as_ref().unwrap().length as Index + last_key.as_ref().unwrap().value + }; + let key = BTKey::read(key_offset, bd)?; + if key.0.key == key_to_find { + return Some(key); + } else if key.0.key > key_to_find { + break; + } + i += 1; + last_key = Some(key.0); + } + if node.child_count == 0 { + return None; + } + let child_offset = if i == 0 { + node.child_array + } else { + last_key.unwrap().value + }; + node = BTNode::read(root, child_offset, bd)?; + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6cdecf1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +#![no_std] + +extern crate alloc; + +use vapfs::BlockDevice; +use crate::structs::Superblock; + +pub mod btree; +pub mod structs; +pub mod bitmap; + +pub fn get_superblock(bd: &mut dyn BlockDevice) -> Option { + todo!() +} \ No newline at end of file diff --git a/src/structs.rs b/src/structs.rs new file mode 100644 index 0000000..bc49a15 --- /dev/null +++ b/src/structs.rs @@ -0,0 +1,139 @@ +use vapfs::{Index, Timestamp}; + +/// # Magic +/// Constant value that identifies the Superblock as a valid VapUFS filesystem +pub const MAGIC: u64 = 0x766170554653; + +/// # Superblock +/// The primary struct of a VapUFS filesystem, contains metadata about the filesystem. +/// Located at byte offset 1024 of the block device. +/// All values are big-endian unless otherwise specified. +/// Directly after the Superblock are the free data blocks bitmap and the free inodes bitmap. +/// Free data blocks bitmap is data_block_count / 8 bytes long (rounded up). +/// Free inodes bitmap is inode_count / 8 bytes long (rounded up). +#[repr(C)] +pub struct Superblock { + /// magic number that identifies the Superblock as a valid VapUFS filesystem + pub magic: u64, + /// size of each block in bytes + pub block_size: u32, + /// location of first data block in blocks + pub first_data_block: Index, + /// location of first inode block in blocks + pub first_inode_block: Index, + /// location of first journal block in blocks + pub first_journal_block: Index, + /// total count of inodes, including unused inodes + pub inode_count: Index, + /// total count of data blocks, including unused blocks + pub data_block_count: Index, + /// total count of blocks dedicated to journal + pub journal_block_count: Index, + /// total count of inodes in use + pub allocated_inode_count: Index, + /// total count of data blocks in use + pub allocated_data_block_count: Index, + /// timestamp of creation + pub creation_time: Timestamp, + /// timestamp of last modification + pub last_modification_time: Timestamp, + /// reserved values for expansion + pub reserved: [u64; 8], + // 156 bytes used so far + // rest of the block is used by the free data blocks bitmap and the free inodes bitmap +} + +/// # UNIXPermissions +/// UNIX permissions +#[repr(u16)] +pub enum UNIXMode { + /// Read, Other + Read = 1 << 0, + /// Write, Other + Write = 1 << 1, + /// Execute, Other + Execute = 1 << 2, + /// Read, Group + ReadGroup = 1 << 3, + /// Write, Group + WriteGroup = 1 << 4, + /// Execute, Group + ExecuteGroup = 1 << 5, + /// Read, Owner + ReadOwner = 1 << 6, + /// Write, Owner + WriteOwner = 1 << 7, + /// Execute, Owner + ExecuteOwner = 1 << 8, + /// Sticky bit + Sticky = 1 << 9, + /// Set GID + SetGID = 1 << 10, + /// Set UID + SetUID = 1 << 11, +} + +/// # Filetype +/// Represents a file's type in the last 4 bits of the mode field of an inode. +#[repr(u16)] +pub enum Filetype { + /// Named FIFO pipe + FIFO = 0b0001 << 12, + /// Character device + CharDevice = 0b0010 << 12, + /// Directory + Directory = 0b0011 << 12, + /// Block device + BlockDevice = 0b0100 << 12, + /// Regular file + RegularFile = 0b0101 << 12, + /// Symbolic link + SymbolicLink = 0b0110 << 12, + /// Socket + Socket = 0b0111 << 12, +} + +/// # Inode +/// Usually represents a file or directory, used to store metadata and locations of data blocks. +#[repr(C)] +pub struct Inode { + /// UNIX permissions / mode and filetype + pub mode: u16, + /// Number of links to this inode + pub link_count: u16, + /// User ID of owner + pub uid: u32, + /// Group ID of owner + pub gid: u32, + /// Size in bytes + pub size: Index, + /// Size in blocks + pub block_count: Index, + /// Timestamp of creation + pub creation_time: Timestamp, + /// Timestamp of last access + pub last_access_time: Timestamp, + /// Timestamp of last modification + pub last_modification_time: Timestamp, + /// Timestamp of last Inode modification + pub last_inode_modification_time: Timestamp, + /// Timestamp of deletion + pub deletion_time: Timestamp, + /// Flags field, see `InodeFlags` + pub flags: u32, + /// Direct-Block-Addresses, last three are indirect if `InodeFlags::INDIRECT` is set + /// Indirect blocks are Indexes to other blocks, their contents are a u64 count "N" followed by N u64 indexes + pub direct_block_addresses: [Index; 12], + /// CRC32 checksum of this inode + pub checksum: u32, +} + +/// # InodeFlags +/// Flags field of an inode +#[repr(u32)] +pub enum InodeFlags { + /// File is corrupted + CORRUPT = 1 << 0, + /// File uses indirect blocks + INDIRECT = 1 << 1, +} \ No newline at end of file