From 117d019aa51c543aa46c5b06c26dd5e32264086d Mon Sep 17 00:00:00 2001 From: Amogus Date: Sat, 10 Dec 2022 23:03:08 +0100 Subject: [PATCH] Moved stuff --- .gitignore | 2 + Cargo.toml | 16 + LICENSE.md | 22 + README.md | 3 + rustfmt.toml | 2 + src/error.rs | 124 ++++++ src/fs/mod.rs | 177 ++++++++ src/fs/sync.rs | 905 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 36 ++ src/sector.rs | 242 +++++++++++ src/sys/block_group.rs | 116 ++++++ src/sys/inode.rs | 188 +++++++++ src/sys/mod.rs | 5 + src/sys/superblock.rs | 284 +++++++++++++ src/volume/mod.rs | 371 +++++++++++++++++ src/volume/size.rs | 115 ++++++ 16 files changed, 2608 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 rustfmt.toml create mode 100644 src/error.rs create mode 100644 src/fs/mod.rs create mode 100644 src/fs/sync.rs create mode 100644 src/lib.rs create mode 100644 src/sector.rs create mode 100644 src/sys/block_group.rs create mode 100644 src/sys/inode.rs create mode 100644 src/sys/mod.rs create mode 100644 src/sys/superblock.rs create mode 100644 src/volume/mod.rs create mode 100644 src/volume/size.rs 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..f72ff11 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ext2" +version = "0.1.1" +authors = ["Szymon Walter ", +"able "] + + +[dependencies] +bitflags = "1.0" +rlibc = { version = "1.0", optional = true } +spin = "0.9.2" +genfs = "^0.1.4" + +[features] +default = ["no_std"] +no_std = ["rlibc"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9894777 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +# ext2-rs +## an ext2 implementation + +Copyright © 2018, Szymon Walter + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +#### walter.szymon.98@gmail.com diff --git a/README.md b/README.md new file mode 100644 index 0000000..8dd53d6 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ext2-rs + +An OS and architecture independent implementation of ext2 in pure Rust. diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..3450fc4 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 80 +wrap_comments = true diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..f506ae9 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,124 @@ +//! Errors +use { + alloc::string::String, + core::fmt::{self, Display}, +}; + +#[cfg(any(test, not(feature = "no_std")))] +use std::io; + +/// The set of all possible errors +#[derive(Debug)] +pub enum Error { + /// Generic error + Other(String), + /// Bad magic number + BadMagic { + /// The magic number + magic: u16, + }, + /// Out of bounds error + OutOfBounds { + /// index + index: usize, + }, + /// Address out of bounds + AddressOutOfBounds { + /// + sector: u32, + /// + offset: u32, + + /// + size: usize, + }, + /// Bad block group count + BadBlockGroupCount { + /// + by_blocks: u32, + /// + by_inodes: u32, + }, + /// Inode Not Found + InodeNotFound { + /// inode number + inode: u32, + }, + /// Inode is not a directory + NotADirectory { + /// inode number + inode: u32, + /// inode name + name: String, + }, + /// Not Absolute Path + NotAbsolute { + /// path name + name: String, + }, + + /// Not Found + NotFound { + /// inode name + name: String, + }, + // #[cfg(any(test, not(feature = "no_std")))] + // Io { inner: io::Error }, +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Error::Other(ref msg) => write!(f, "{}", msg), + Error::BadMagic { + magic, + } => write!(f, "invalid magic value: {}", magic), + Error::OutOfBounds { + index, + } => write!(f, "index ouf of bounds: {}", index), + Error::AddressOutOfBounds { + sector, + offset, + size, + } => write!(f, "address ouf of bounds: {}:{} with a block size of: {}", + sector, offset, size), + Error::BadBlockGroupCount { + by_blocks, + by_inodes, + } => write!(f, "conflicting block group count data; by blocks: {}, by inodes: {}", by_blocks, by_inodes), + Error::InodeNotFound { + inode, + } => write!(f, "couldn't find inode no. {}", &inode), + Error::NotADirectory { + inode, + ref name, + } => write!(f, "inode no. {} at: {} is not a directory", inode, &name), + Error::NotAbsolute { + ref name, + } => write!(f, "{} is not an absolute path", &name), + Error::NotFound { + ref name, + } => write!(f, "couldn't find {}", &name), + #[cfg(any(test, not(feature = "no_std")))] + Error::Io { + ref inner, + } => write!(f, "io error: {}", inner), + } + } +} + +impl From for Error { + fn from(_: Infallible) -> Error { + unreachable!() + } +} + +#[cfg(any(test, not(feature = "no_std")))] +impl From for Error { + fn from(inner: io::Error) -> Error { + Error::Io { inner } + } +} + +/// Infalliable +pub enum Infallible {} diff --git a/src/fs/mod.rs b/src/fs/mod.rs new file mode 100644 index 0000000..1844e31 --- /dev/null +++ b/src/fs/mod.rs @@ -0,0 +1,177 @@ +//! + +use { + alloc::vec::Vec, + core::mem, + error::Error, + sector::{Address, SectorSize}, + sys::{ + block_group::BlockGroupDescriptor, inode::Inode as RawInode, + superblock::Superblock, + }, + volume::Volume, +}; + +pub mod sync; + +#[allow(dead_code)] +pub(crate) struct Struct { + pub inner: T, + pub offset: Address, +} + +impl From<(T, Address)> for Struct { + #[inline] + fn from((inner, offset): (T, Address)) -> Struct { + Struct { inner, offset } + } +} + +/// Safe wrapper for raw sys structs +pub struct Ext2> { + // TODO: should this have some different vis? + pub(crate) volume: V, + pub(crate) superblock: Struct, + pub(crate) block_groups: Struct, S>, +} + +impl> Ext2 { + /// + pub fn new(volume: V) -> Result, Error> { + let superblock = unsafe { Struct::from(Superblock::find(&volume)?) }; + let block_groups_offset = Address::with_block_size( + superblock.inner.first_data_block + 1, + 0, + superblock.inner.log_block_size + 10, + ); + let block_groups_count = superblock + .inner + .block_group_count() + .map(|count| count as usize) + .map_err(|(a, b)| Error::BadBlockGroupCount { + by_blocks: a, + by_inodes: b, + })?; + let block_groups = unsafe { + BlockGroupDescriptor::find_descriptor_table( + &volume, + block_groups_offset, + block_groups_count, + )? + }; + let block_groups = Struct::from(block_groups); + Ok(Ext2 { + volume, + superblock, + block_groups, + }) + } + /// Return the version of the filesystem + pub fn version(&self) -> (u32, u16) { + ( + self.superblock.inner.rev_major, + self.superblock.inner.rev_minor, + ) + } + /// Return inode size + pub fn inode_size(&self) -> usize { + if self.version().0 == 0 { + mem::size_of::() + } else { + // note: inodes bigger than 128 are not supported + self.superblock.inner.inode_size as usize + } + } + /// + pub fn inodes_count(&self) -> usize { + self.superblock.inner.inodes_per_group as _ + } + /// + pub fn total_inodes_count(&self) -> usize { + self.superblock.inner.inodes_count as _ + } + /// + pub fn block_group_count(&self) -> Result { + self.superblock + .inner + .block_group_count() + .map(|count| count as usize) + .map_err(|(a, b)| Error::BadBlockGroupCount { + by_blocks: a, + by_inodes: b, + }) + } + /// + pub fn total_block_count(&self) -> usize { + self.superblock.inner.blocks_count as _ + } + /// + pub fn free_block_count(&self) -> usize { + self.superblock.inner.free_blocks_count as _ + } + /// + pub fn block_size(&self) -> usize { + self.superblock.inner.block_size() + } + /// + pub fn log_block_size(&self) -> u32 { + self.superblock.inner.log_block_size + 10 + } + /// + pub fn sector_size(&self) -> usize { + S::SIZE + } + /// + pub fn log_sector_size(&self) -> u32 { + S::LOG_SIZE + } +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + use std::fs::File; + + use sector::{Address, Size512}; + use volume::Volume; + + use super::Ext2; + + #[test] + fn file_len() { + let file = RefCell::new(File::open("ext2.img").unwrap()); + assert_eq!( + Address::::from(2048_u64) + - Address::::from(1024_u64), + Address::::new(2, 0) + ); + assert_eq!( + unsafe { + file.slice_unchecked( + Address::::from(1024_u64) + ..Address::::from(2048_u64), + ) + .len() + }, + 1024 + ); + } + + #[test] + fn file() { + let file = RefCell::new(File::open("ext2.img").unwrap()); + let fs = Ext2::::new(file); + + assert!( + fs.is_ok(), + "Err({:?})", + fs.err().unwrap_or_else(|| unreachable!()), + ); + + let fs = fs.unwrap(); + + let vers = fs.version(); + println!("version: {}.{}", vers.0, vers.1); + assert_eq!(128, fs.inode_size()); + } +} diff --git a/src/fs/sync.rs b/src/fs/sync.rs new file mode 100644 index 0000000..bda8360 --- /dev/null +++ b/src/fs/sync.rs @@ -0,0 +1,905 @@ +//! + +use crate::sys::inode::TypePerm; + +use { + super::Ext2, + alloc::{ + sync::Arc, + {string::String, vec::Vec}, + }, + core::{ + fmt::{self, Debug}, + iter::Iterator, + num::NonZeroU32, + }, + error::Error, + genfs::*, + sector::{Address, SectorSize}, + spin::{Mutex, MutexGuard}, + sys::inode::Inode as RawInode, + volume::Volume, +}; +/// DOCME: what is this? +pub struct Synced { + inner: Arc>, +} + +impl Synced { + /// DOCME: what is this? + pub fn with_inner(inner: T) -> Synced { + Synced { + inner: Arc::new(Mutex::new(inner)), + } + } + + /// DOCME: what is this? + pub fn inner<'a>(&'a self) -> MutexGuard<'a, T> { + self.inner.lock() + } +} + +impl Clone for Synced { + fn clone(&self) -> Self { + Synced { + inner: self.inner.clone(), + } + } +} + +impl> Synced> { + /// DOCME: what is this? + pub fn new(volume: V) -> Result>, Error> { + Ext2::new(volume).map(Synced::with_inner) + } + /// Get the root inode. + pub fn root_inode(&self) -> Inode { + self.inode_nth(2).unwrap() + } + /// Get the inode at the given index. + pub fn inode_nth(&self, index: usize) -> Option> { + self.inodes_nth(index).next() + } + /// DOCME: what is this? + pub fn inodes(&self) -> Inodes { + self.inodes_nth(1) + } + /// DOCME: what is this? + pub fn inodes_nth(&self, index: usize) -> Inodes { + assert!(index > 0, "inodes are 1-indexed"); + let inner = self.inner(); + Inodes { + fs: self.clone(), + log_block_size: inner.log_block_size(), + inode_size: inner.inode_size(), + inodes_per_group: inner.inodes_count(), + inodes_count: inner.total_inodes_count(), + index, + } + } + /// DOCME: what is this? + pub fn sector_size(&self) -> usize { + S::SIZE + } + /// DOCME: what is this? + pub fn log_sector_size(&self) -> u32 { + S::LOG_SIZE + } +} + +impl> Fs for Synced> { + type Path = [u8]; + type PathOwned = Vec; + type File = Inode; + type Dir = Directory; + type DirEntry = DirectoryEntry; + type Metadata = (); // TODO + type Permissions = (); // TODO + type Error = Error; + + fn open( + &self, + abs_path: &Self::Path, + _options: &OpenOptions, + ) -> Result { + fn inner<'a, S, V, I>( + fs: &Synced>, + inode: Inode, + mut path: I, + abs_path: &[u8], + ) -> Result, Error> + where + S: SectorSize, + V: Volume, + I: Iterator, + { + let name = match path.next() { + Some(name) => name, + None => return Ok(inode), + }; + + let mut dir = + inode.directory().ok_or_else(|| Error::NotADirectory { + inode: inode.num, + name: String::from_utf8_lossy(abs_path).into_owned(), + })?; + + let entry = dir + .find(|entry| { + entry.is_err() || entry.as_ref().unwrap().name == name + }) + .ok_or_else(|| Error::NotFound { + name: String::from_utf8_lossy(abs_path).into_owned(), + })??; + + let inode = fs + .inode_nth(entry.inode) + .ok_or(Error::InodeNotFound { inode: inode.num })?; + + inner(fs, inode, path, abs_path) + } + + if abs_path.is_empty() || abs_path[0] != b'/' { + return Err(Error::NotAbsolute { + name: String::from_utf8_lossy(abs_path).into_owned(), + }); + } + + if abs_path == b"/" { + return Ok(self.root_inode()); + } + + let mut path = abs_path.split(|byte| *byte == b'/'); + path.next(); + let root = self.root_inode(); + + inner(self, root, path, abs_path) + } + + fn remove_file(&mut self, _path: &Self::Path) -> Result<(), Self::Error> { + unimplemented!() + } + + fn metadata( + &self, + _path: &Self::Path, + ) -> Result { + unimplemented!() + } + + fn symlink_metadata( + &self, + _path: &Self::Path, + ) -> Result { + unimplemented!() + } + + fn rename( + &mut self, + _from: &Self::Path, + _to: &Self::Path, + ) -> Result<(), Self::Error> { + unimplemented!() + } + + fn copy( + &mut self, + _from: &Self::Path, + _to: &Self::Path, + ) -> Result { + unimplemented!() + } + + fn hard_link( + &mut self, + _src: &Self::Path, + _dst: &Self::Path, + ) -> Result<(), Self::Error> { + unimplemented!() + } + + fn symlink( + &mut self, + _src: &Self::Path, + _dst: &Self::Path, + ) -> Result<(), Self::Error> { + unimplemented!() + } + + fn read_link( + &self, + _path: &Self::Path, + ) -> Result { + unimplemented!() + } + + fn canonicalize( + &self, + _path: &Self::Path, + ) -> Result { + unimplemented!() + } + + fn create_dir( + &mut self, + _path: &Self::Path, + _options: &DirOptions, + ) -> Result<(), Self::Error> { + unimplemented!() + } + + fn remove_dir(&mut self, _path: &Self::Path) -> Result<(), Self::Error> { + unimplemented!() + } + + fn remove_dir_all( + &mut self, + _path: &Self::Path, + ) -> Result<(), Self::Error> { + unimplemented!() + } + + fn read_dir(&self, path: &Self::Path) -> Result { + let inode = self.open(path, OpenOptions::new().read(true))?; + inode.directory().ok_or(Error::NotADirectory { + inode: inode.num, + name: String::from_utf8_lossy(path).into_owned(), + }) + } + + fn set_permissions( + &mut self, + _path: &Self::Path, + _perm: Self::Permissions, + ) -> Result<(), Self::Error> { + unimplemented!() + } +} + +impl> Debug for Synced> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Synced>", S::SIZE) + } +} + +#[derive(Debug, Clone)] +/// A collection of inodes. +pub struct Inodes> { + fs: Synced>, + log_block_size: u32, + inode_size: usize, + inodes_per_group: usize, + inodes_count: usize, + index: usize, +} + +impl> Iterator for Inodes { + type Item = Inode; + + fn next(&mut self) -> Option { + if self.index < self.inodes_count { + let block_group = (self.index - 1) / self.inodes_per_group; + let index = (self.index - 1) % self.inodes_per_group; + self.index += 1; + + let fs = self.fs.inner(); + + let inodes_block = + fs.block_groups.inner[block_group].inode_table_block; + + let offset = Address::with_block_size( + inodes_block, + (index * self.inode_size) as i32, + self.log_block_size, + ); + let raw = unsafe { + RawInode::find_inode(&fs.volume, offset, self.inode_size).ok() + }; + raw.map(|(raw, offset)| { + Inode::new( + self.fs.clone(), + raw, + offset, + (self.index - 1) as u32, + ) + }) + } else { + None + } + } +} + +#[derive(Debug)] +/// A single inode in an ext2 filesystem. +pub struct Inode> { + fs: Synced>, + inner: RawInode, + addr: Address, + num: u32, +} + +impl> Clone for Inode { + fn clone(&self) -> Self { + Inode { + fs: self.fs.clone(), + inner: self.inner, + addr: self.addr, + num: self.num, + } + } +} + +impl> Inode { + /// + pub fn new( + fs: Synced>, + inner: RawInode, + addr: Address, + num: u32, + ) -> Inode { + Inode { + fs, + inner, + addr, + num, + } + } + /// Read to the end of a buffer. + pub fn read_to_end(&self, buf: &mut Vec) -> Result { + let total_size = self.size(); + let capacity = buf.capacity(); + if capacity < total_size { + buf.reserve_exact(total_size - capacity); + } + unsafe { + buf.set_len(total_size); + } + let size = self.read(&mut buf[..]); + size.map(|size| { + unsafe { + buf.set_len(size); + } + size + }) + .map_err(|err| { + unsafe { + buf.set_len(0); + } + err + }) + } + /// Return blocks on a sector + pub fn blocks(&self) -> InodeBlocks { + InodeBlocks { + inode: self.clone(), + index: 0, + } + } + /// return a directory iterator + pub fn directory(&self) -> Option> { + if self.is_dir() { + Some(Directory { + blocks: self.blocks(), + offset: 0, + buffer: None, + block_size: { + let fs = self.fs.inner(); + fs.block_size() + }, + }) + } else { + None + } + } + /// Determine if an inode is a directory + pub fn is_dir(&self) -> bool { + { self.inner.type_perm }.contains(TypePerm::DIRECTORY) + // self.inner.type_perm.contains(TypePerm::DIRECTORY) + } + /// + pub fn block(&self, index: usize) -> Option { + self.try_block(index).ok().and_then(|block| block) + } + /// Try to get a block + pub fn try_block( + &self, + mut index: usize, + ) -> Result, Error> { + // number of blocks in direct table: 12 + // number of blocks in indirect table: block_size/4 + // why? + // - a block is n bytes long + // - a block address occupies 32 bits, or 4 bytes + // - thus, n/4 + // number of blocks in doubly table: (block_size/4)^2 + // why? + // - every entry in the doubly table points to another block + // - that's n/4 blocks, where n is the block size + // - every block contains n/4 block pointers + // - that's n/4 blocks with n/4 pointers each = (n/4)^2 + // number of blocks in triply table: (block_size/4)^3 + + fn block_index>( + volume: &V, + block: u32, + index: usize, + log_block_size: u32, + ) -> Result, Error> { + let offset = (index * 4) as i32; + let end = offset + 4; + let addr = Address::with_block_size(block, offset, log_block_size); + let end = Address::with_block_size(block, end, log_block_size); + let block = volume.slice(addr..end); + match block { + Ok(block) => unsafe { + Ok(NonZeroU32::new(block.dynamic_cast::().0)) + }, + Err(err) => Err(err.into()), + } + } + + let fs = self.fs.inner(); + + let bs4 = fs.block_size() / 4; + let log_block_size = fs.log_block_size(); + + if index < 12 { + return Ok(NonZeroU32::new(self.inner.direct_pointer[index])); + } + + index -= 12; + + if index < bs4 { + let block = self.inner.indirect_pointer; + return block_index(&fs.volume, block, index, log_block_size); + } + + index -= bs4; + + if index < bs4 * bs4 { + let indirect_index = index >> (log_block_size + 2); + let block = match block_index( + &fs.volume, + self.inner.doubly_indirect, + indirect_index, + log_block_size, + ) { + Ok(Some(block)) => block.get(), + Ok(None) => return Ok(None), + Err(err) => return Err(err), + }; + return block_index( + &fs.volume, + block, + index & (bs4 - 1), + log_block_size, + ); + } + + index -= bs4 * bs4; + + if index < bs4 * bs4 * bs4 { + let doubly_index = index >> (2 * log_block_size + 4); + let indirect = match block_index( + &fs.volume, + self.inner.triply_indirect, + doubly_index, + log_block_size, + ) { + Ok(Some(block)) => block.get(), + Ok(None) => return Ok(None), + Err(err) => return Err(err), + }; + let indirect_index = (index >> (log_block_size + 2)) & (bs4 - 1); + let block = match block_index( + &fs.volume, + indirect as u32, + indirect_index, + log_block_size, + ) { + Ok(Some(block)) => block.get(), + Ok(None) => return Ok(None), + Err(err) => return Err(err), + }; + return block_index( + &fs.volume, + block, + index & (bs4 - 1), + log_block_size, + ); + } + + Ok(None) + } + /// + pub fn in_use(&self) -> bool { + self.inner.hard_links > 0 + } + /// return the uid + pub fn uid(&self) -> u16 { + self.inner.uid + } + /// + pub fn sectors(&self) -> usize { + self.inner.sectors_count as usize + } + /// + pub fn size32(&self) -> u32 { + self.inner.size_low + } + /// + pub fn size64(&self) -> u64 { + self.inner.size_low as u64 | (self.inner.size_high as u64) << 32 + } + /// + #[cfg(target_pointer_width = "64")] + #[inline] + pub fn size(&self) -> usize { + self.size64() as usize + } + + #[cfg(target_pointer_width = "32")] + #[inline] + pub fn size(&self) -> usize { + self.size32() as usize + } + + /// ableOS: expose type_perm + pub fn type_perm(&self) -> TypePerm { + self.inner.type_perm + } +} + +impl> File for Inode { + type Error = Error; + + fn read(&self, buf: &mut [u8]) -> Result { + let total_size = self.size(); + let block_size = { + let fs = self.fs.inner(); + fs.block_size() + }; + let mut offset = 0; + + for block in self.blocks() { + match block { + Ok((data, _)) => { + let data_size = block_size + .min(total_size - offset) + .min(buf.len() - offset); + let end = offset + data_size; + buf[offset..end].copy_from_slice(&data[..data_size]); + offset += data_size; + } + Err(err) => return Err(err), + } + } + + Ok(offset) + } + + fn write(&mut self, _buf: &[u8]) -> Result { + unimplemented!() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + unimplemented!() + } + + fn seek(&mut self, _pos: SeekFrom) -> Result { + unimplemented!() + } +} + +/// +#[derive(Debug, Clone)] +pub struct InodeBlocks> { + inode: Inode, + index: usize, +} + +impl> Iterator for InodeBlocks { + type Item = Result<(Vec, Address), Error>; + + fn next(&mut self) -> Option { + let block = self.inode.try_block(self.index); + let block = match block { + Ok(Some(ok)) => ok, + Ok(None) => return None, + Err(err) => return Some(Err(err)), + }; + + self.index += 1; + let fs = self.inode.fs.inner(); + + let block = block.get(); + let log_block_size = fs.log_block_size(); + let offset = Address::with_block_size(block, 0, log_block_size); + let end = Address::with_block_size(block + 1, 0, log_block_size); + + let slice = fs + .volume + .slice(offset..end) + .map(|slice| (slice.to_vec(), offset)) + .map_err(|err| err.into()); + Some(slice) + } +} + +#[derive(Debug, Clone)] +/// A directory structure +pub struct Directory> { + blocks: InodeBlocks, + offset: usize, + buffer: Option>, + block_size: usize, +} + +impl> Dir + for Directory +{ +} + +impl> Iterator for Directory { + type Item = Result; + + fn next(&mut self) -> Option { + if self.buffer.is_none() || self.offset >= self.block_size { + self.buffer = match self.blocks.next() { + None => return None, + Some(Ok((block, _))) => Some(block), + Some(Err(err)) => return Some(Err(err)), + }; + + self.offset = 0; + } + + let buffer = &self.buffer.as_ref().unwrap()[self.offset..]; + + let inode = buffer[0] as u32 + | (buffer[1] as u32) << 8 + | (buffer[2] as u32) << 16 + | (buffer[3] as u32) << 24; + if inode == 0 { + return None; + } + + let size = buffer[4] as u16 | (buffer[5] as u16) << 8; + let len = buffer[6]; + let ty = buffer[7]; + + let name = buffer[8..8 + len as usize].to_vec(); + + self.offset += size as usize; + + Some(Ok(DirectoryEntry { + name, + inode: inode as usize, + ty, + })) + } +} + +#[derive(Clone)] +/// A directory entry +pub struct DirectoryEntry { + /// The name of the entry + pub name: Vec, + /// The inode of the entry + pub inode: usize, + /// + pub ty: u8, +} + +impl DirEntry for DirectoryEntry { + type Path = [u8]; + type PathOwned = Vec; + type Metadata = (); // TODO + type FileType = u8; // TODO: enum FileType + type Error = Error; + + fn path(&self) -> Self::PathOwned { + unimplemented!() + } + + fn metadata(&self) -> Result { + unimplemented!() + } + + fn file_type(&self) -> Result { + Ok(self.ty) + } + + fn file_name(&self) -> &Self::Path { + &self.name + } +} + +impl DirectoryEntry { + /// Turns a filename into a string for display + pub fn file_name_string(&self) -> String { + let mut filename = String::new(); + for ch in &self.name { + filename.push(*ch as char); + } + filename + } +} + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + use std::fs::File; + + use genfs::{File as GenFile, Fs, OpenOptions}; + + use sector::{SectorSize, Size512}; + use volume::Volume; + + use super::{Ext2, Inode, Synced}; + + #[test] + fn file() { + let file = RefCell::new(File::open("ext2.img").unwrap()); + let fs = Synced::>::new(file); + + assert!( + fs.is_ok(), + "Err({:?})", + fs.err().unwrap_or_else(|| unreachable!()), + ); + + let fs = fs.unwrap(); + let inner = fs.inner(); + + let vers = inner.version(); + println!("version: {}.{}", vers.0, vers.1); + assert_eq!(128, inner.inode_size()); + } + + #[test] + fn inodes() { + let file = RefCell::new(File::open("ext2.img").unwrap()); + let fs = Synced::>::new(file); + + assert!( + fs.is_ok(), + "Err({:?})", + fs.err().unwrap_or_else(|| unreachable!()), + ); + + let fs = fs.unwrap(); + + let inodes = fs.inodes().filter(|inode| inode.in_use()); + for inode in inodes { + println!("{:?}", inode); + } + } + + #[test] + fn inode_blocks() { + use std::str; + let file = RefCell::new(File::open("ext2.img").unwrap()); + let fs = Synced::>::new(file).unwrap(); + + let inodes = fs.inodes().filter(|inode| { + inode.in_use() && inode.uid() == 1000 && inode.size() < 1024 + }); + for inode in inodes { + println!("{:?}", inode); + let size = inode.size(); + for block in inode.blocks() { + let (data, _) = block.unwrap(); + assert_eq!(data.len(), { + let fs = fs.inner(); + fs.block_size() + }); + println!("{:?}", &data[..size]); + let _ = str::from_utf8(&data[..size]) + .map(|string| println!("{}", string)); + } + } + } + + #[test] + fn read_inode() { + let file = RefCell::new(File::open("ext2.img").unwrap()); + let fs = Synced::>::new(file).unwrap(); + + let inodes = fs.inodes().filter(|inode| { + inode.in_use() && inode.uid() == 1000 && inode.size() < 1024 + }); + for inode in inodes { + let mut buf = Vec::with_capacity(inode.size()); + unsafe { + buf.set_len(inode.size()); + } + let size = inode.read(&mut buf[..]); + assert!(size.is_ok()); + let size = size.unwrap(); + assert_eq!(size, inode.size()); + unsafe { + buf.set_len(size); + } + } + } + + #[test] + fn read_big() { + let file = RefCell::new(File::open("ext2.img").unwrap()); + let fs = Synced::>::new(file).unwrap(); + + let inodes = fs.inodes().filter(|inode| { + inode.in_use() && inode.uid() == 1000 && inode.size() == 537600 + }); + for inode in inodes { + let mut buf = Vec::with_capacity(inode.size()); + unsafe { + buf.set_len(inode.size()); + } + let size = inode.read(&mut buf[..]); + assert!(size.is_ok()); + let size = size.unwrap(); + assert_eq!(size, inode.size()); + unsafe { + buf.set_len(size); + } + + for (i, &x) in buf.iter().enumerate() { + if i & 1 == 0 { + assert_eq!(x, b'u', "{}", i); + } else { + assert_eq!(x, b'\n', "{}", i); + } + } + } + } + + #[test] + fn walkdir() { + use std::str; + + fn walk<'vol, S: SectorSize, V: Volume>( + fs: &'vol Synced>, + inode: Inode, + name: String, + ) { + inode.directory().map(|dir| { + for entry in dir { + assert!(entry.is_ok()); + let entry = entry.unwrap(); + let entry_name = str::from_utf8(&entry.name).unwrap_or("?"); + println!("{}/{} => {}", name, entry_name, entry.inode,); + if entry_name != "." && entry_name != ".." { + walk( + fs, + fs.inode_nth(entry.inode).unwrap(), + format!("{}/{}", name, entry_name), + ); + } + } + }); + } + + let file = RefCell::new(File::open("ext2.img").unwrap()); + let fs = Synced::>::new(file).unwrap(); + + let root = fs.root_inode(); + walk(&fs, root, String::new()); + } + + #[test] + fn find() { + use std::str; + let file = RefCell::new(File::open("ext2.img").unwrap()); + let fs = Synced::>::new(file).unwrap(); + + let found = fs.open(b"/home/funky/README.md", &OpenOptions::new()); + + assert!(found.is_ok()); + let inode = found.unwrap(); + let mut vec = Vec::new(); + assert!(inode.read_to_end(&mut vec).is_ok()); + println!("{}", str::from_utf8(&vec).unwrap()); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..506163a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,36 @@ +//! Ext2 crate for ableOS + +#![deny(missing_docs)] +#![feature(min_specialization, step_trait, associated_type_defaults)] +#![cfg_attr(all(not(test), feature = "no_std"), no_std)] + +extern crate alloc; + +#[macro_use] +extern crate bitflags; +extern crate genfs; +extern crate spin; + +#[cfg(any(test, not(feature = "no_std")))] +extern crate core; + +pub mod error; +pub mod fs; +pub mod sector; +pub mod sys; +pub mod volume; + +#[cfg(test)] +mod tests { + use sys::block_group::*; + use sys::inode::*; + use sys::superblock::*; + + #[test] + fn sizes() { + use std::mem::size_of; + assert_eq!(size_of::(), 1024); + assert_eq!(size_of::(), 32); + assert_eq!(size_of::(), 128); + } +} diff --git a/src/sector.rs b/src/sector.rs new file mode 100644 index 0000000..f657e3a --- /dev/null +++ b/src/sector.rs @@ -0,0 +1,242 @@ +//! Sector data. + +use core::{ + fmt::{self, Debug, Display, LowerHex}, + iter::Step, + marker::PhantomData, + ops::{Add, Sub}, +}; +/// Size of a sector in bytes +pub trait SectorSize: Clone + Copy + PartialEq + PartialOrd + 'static { + /// DOCME: What is this? + const LOG_SIZE: u32; + /// DOCME: What is this? + const SIZE: usize = 1 << Self::LOG_SIZE; + /// DOCME: What is this? + const OFFSET_MASK: u32 = (Self::SIZE - 1) as u32; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +/// DOCME: What is this? +pub struct Size512; +impl SectorSize for Size512 { + const LOG_SIZE: u32 = 9; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +/// DOCME: What is this? +pub struct Size1024; +impl SectorSize for Size1024 { + const LOG_SIZE: u32 = 10; +} +/// DOCME: What is this? +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Size2048; +impl SectorSize for Size2048 { + const LOG_SIZE: u32 = 11; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +/// DOCME: What is this? +pub struct Size4096; +impl SectorSize for Size4096 { + const LOG_SIZE: u32 = 12; +} + +/// Address in a physical sector +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Address { + sector: u32, + offset: u32, + _phantom: PhantomData, +} + +impl Address { + /// + pub unsafe fn new_unchecked(sector: u32, offset: u32) -> Address { + assert!((offset as usize) < S::SIZE, "offset out of sector bounds"); + let _phantom = PhantomData; + Address { + sector, + offset, + _phantom, + } + } + /// + pub fn new(sector: u32, offset: i32) -> Address { + let sector = (sector as i32 + (offset >> S::LOG_SIZE)) as u32; + let offset = offset.unsigned_abs() & S::OFFSET_MASK; + unsafe { Address::new_unchecked(sector, offset) } + } + /// + pub fn with_block_size( + block: u32, + offset: i32, + log_block_size: u32, + ) -> Address { + let block = (block as i32 + (offset >> log_block_size)) as u32; + let offset = offset.unsigned_abs() & ((1 << log_block_size) - 1); + + let log_diff = log_block_size as i32 - S::LOG_SIZE as i32; + let top_offset = offset >> S::LOG_SIZE; + let offset = offset & ((1 << S::LOG_SIZE) - 1); + let sector = block << log_diff | top_offset; + unsafe { Address::new_unchecked(sector, offset) } + } + /// + pub fn into_index(&self) -> u64 { + ((self.sector as u64) << S::LOG_SIZE) + self.offset as u64 + } + /// Get the size of the sector + pub const fn sector_size(&self) -> usize { + S::SIZE + } + /// DOCME: What is this? + pub const fn log_sector_size(&self) -> u32 { + S::LOG_SIZE + } + /// Return the sector number + pub fn sector(&self) -> u32 { + self.sector + } + /// Return the offset in the sector + pub fn offset(&self) -> u32 { + self.offset + } +} + +impl Step for Address { + fn steps_between(start: &Self, end: &Self) -> Option { + if end.sector >= start.sector { + Some(end.sector as usize - start.sector as usize) + } else { + None + } + } + + /* + fn replace_one(&mut self) -> Self { + mem::replace(self, Address::new(1, 0)) + } + + fn replace_zero(&mut self) -> Self { + mem::replace(self, Address::new(0, 0)) + } + + fn add_one(&self) -> Self { + Address::new(self.sector + 1, 0) + } + + fn sub_one(&self) -> Self { + Address::new(self.sector - 1, 0) + } + + fn add_usize(&self, n: usize) -> Option { + self.sector + .checked_add(n as u32) + .map(|sector| Address::new(sector, 0)) + } + */ + + fn forward_checked(_start: Self, count: usize) -> Option { + todo!("forward_checked: count: {}", count); + } + + fn backward_checked(_start: Self, count: usize) -> Option { + todo!("backward_checked count: {}", count); + } +} + +impl Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}", self.sector, self.offset) + } +} + +impl LowerHex for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:x}:{:x}", self.sector, self.offset) + } +} + +impl From for Address { + fn from(idx: u64) -> Address { + let sector = idx >> S::LOG_SIZE; + let offset = idx & S::OFFSET_MASK as u64; + Address::new(sector as u32, offset as i32) + } +} + +impl From for Address { + fn from(idx: usize) -> Address { + let sector = idx >> S::LOG_SIZE; + let offset = idx & S::OFFSET_MASK as usize; + Address::new(sector as u32, offset as i32) + } +} + +impl Add for Address { + type Output = Address; + fn add(self, rhs: Address) -> Address { + Address::new( + self.sector + rhs.sector, + (self.offset + rhs.offset) as i32, + ) + } +} + +impl Sub for Address { + type Output = Address; + fn sub(self, rhs: Address) -> Address { + Address::new( + self.sector - rhs.sector, + self.offset as i32 - rhs.offset as i32, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn conv() { + assert_eq!(Address::::new(0, 1024).into_index(), 1024); + assert_eq!(Address::::from(1024_u64).into_index(), 1024); + assert_eq!( + Address::::with_block_size(1, 256, 10).into_index(), + 1024 + 256 + ); + assert_eq!( + Address::::with_block_size(2, 0, 10).into_index(), + 2048 + ); + assert_eq!( + Address::::with_block_size(0, 1792, 10).into_index(), + 1792 + ); + } + + #[test] + fn arithmetic() { + assert_eq!( + Address::::new(0, 512), + Address::::new(1, 0), + ); + + assert_eq!( + Address::::new(2, -256), + Address::::new(1, 256), + ); + + let a = Address::::new(0, 1024); + let b = Address::::new(0, 1024); + assert_eq!(a + b, Address::::new(1, 0)); + assert_eq!((a + b).into_index(), 2048); + + let a = Address::::new(0, 2048); + let b = Address::::new(0, 256); + assert_eq!(a - b, Address::::new(3, 256)); + assert_eq!((a - b).into_index(), 1792); + } +} diff --git a/src/sys/block_group.rs b/src/sys/block_group.rs new file mode 100644 index 0000000..90bf165 --- /dev/null +++ b/src/sys/block_group.rs @@ -0,0 +1,116 @@ +//! + +use { + alloc::vec::Vec, + core::{fmt::Debug, mem}, + error::Error, + sector::{Address, SectorSize}, + volume::Volume, +}; + +/// The Block Group Descriptor Table contains a descriptor for each block group +/// within the file system. The number of block groups within the file system, +/// and correspondingly, the number of entries in the Block Group Descriptor +/// Table, is described above. Each descriptor contains information regarding +/// where important data structures for that group are located. +/// +/// The (`BlockGroupDescriptor`) table is located in the block immediately +/// following the Superblock. So if the block size (determined from a field in +/// the superblock) is 1024 bytes per block, the Block Group Descriptor Table +/// will begin at block 2. For any other block size, it will begin at block 1. +/// Remember that blocks are numbered starting at 0, and that block numbers +/// don't usually correspond to physical block addresses. +#[repr(C, packed)] +#[derive(Clone, Debug, Copy)] +pub struct BlockGroupDescriptor { + /// Block address of block usage bitmap + pub block_usage_addr: u32, + /// Block address of inode usage bitmap + pub inode_usage_addr: u32, + /// Starting block address of inode table + pub inode_table_block: u32, + /// Number of unallocated blocks in group + pub free_blocks_count: u16, + /// Number of unallocated inodes in group + pub free_inodes_count: u16, + /// Number of directories in group + pub dirs_count: u16, + #[doc(hidden)] + _reserved: [u8; 14], +} + +impl BlockGroupDescriptor { + /// Find a descriptor in a descriptor table + pub unsafe fn find_descriptor>( + haystack: &V, + offset: Address, + ) -> Result<(BlockGroupDescriptor, Address), Error> { + let end = + offset + Address::from(mem::size_of::()); + if haystack.size() < end { + return Err(Error::AddressOutOfBounds { + sector: end.sector(), + offset: end.offset(), + size: end.sector_size(), + }); + } + + let descr = haystack + .slice_unchecked(offset..end) + .dynamic_cast::(); + + Ok(descr) + } + /// find a descriptor table + pub unsafe fn find_descriptor_table>( + haystack: &V, + offset: Address, + count: usize, + ) -> Result<(Vec, Address), Error> { + let end = offset + + Address::from(count * mem::size_of::()); + if haystack.size() < end { + return Err(Error::AddressOutOfBounds { + sector: end.sector(), + offset: end.offset(), + size: end.sector_size(), + }); + } + + let mut vec = Vec::with_capacity(count); + for i in 0..count { + let offset = offset + + Address::from(i * mem::size_of::()); + vec.push({ + BlockGroupDescriptor::find_descriptor(haystack, offset)?.0 + }); + } + + Ok((vec, offset)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sector::{Address, Size512}; + + #[test] + fn find() { + let volume = vec![0_u8; 4096]; + let table = unsafe { + BlockGroupDescriptor::find_descriptor_table( + &volume, + Address::::new(4, 0), + 8, + ) + }; + assert!( + table.is_ok(), + "Err({:?})", + table.err().unwrap_or_else(|| unreachable!()), + ); + let table = table.unwrap_or_else(|_| unreachable!()); + assert_eq!(table.0.len(), 8); + } +} diff --git a/src/sys/inode.rs b/src/sys/inode.rs new file mode 100644 index 0000000..0a129b6 --- /dev/null +++ b/src/sys/inode.rs @@ -0,0 +1,188 @@ +//! +use { + core::{fmt::Debug, mem}, + error::Error, + sector::{Address, SectorSize}, + volume::Volume, +}; + +/// An inode is a structure on the disk that represents a file, directory, +/// symbolic link, etc. Inodes do not contain the data of the file / directory / +/// etc. that they represent. Instead, they link to the blocks that actually +/// contain the data. This lets the inodes themselves have a well-defined size +/// which lets them be placed in easily indexed arrays. Each block group has an +/// array of inodes it is responsible for, and conversely every inode within a +/// file system belongs to one of such tables (and one of such block groups). +#[repr(C, packed)] +#[derive(Clone, Debug, Copy)] +pub struct Inode { + /// Type and Permissions (see below) + pub type_perm: TypePerm, + /// User ID + pub uid: u16, + /// Lower 32 bits of size in bytes + pub size_low: u32, + /// Last Access Time (in POSIX time) + pub atime: u32, + /// Creation Time (in POSIX time) + pub ctime: u32, + /// Last Modification time (in POSIX time) + pub mtime: u32, + /// Deletion time (in POSIX time) + pub dtime: u32, + /// Group ID + pub gid: u16, + /// Count of hard links (directory entries) to this inode. When this + /// reaches 0, the data blocks are marked as unallocated. + pub hard_links: u16, + /// Count of disk sectors (not Ext2 blocks) in use by this inode, not + /// counting the actual inode structure nor directory entries linking + /// to the inode. + pub sectors_count: u32, + /// Flags + pub flags: Flags, + /// Operating System Specific value #1 + pub _os_specific_1: [u8; 4], + /// Direct block pointers + pub direct_pointer: [u32; 12], + /// Singly Indirect Block Pointer (Points to a block that is a list of + /// block pointers to data) + pub indirect_pointer: u32, + /// Doubly Indirect Block Pointer (Points to a block that is a list of + /// block pointers to Singly Indirect Blocks) + pub doubly_indirect: u32, + /// Triply Indirect Block Pointer (Points to a block that is a list of + /// block pointers to Doubly Indirect Blocks) + pub triply_indirect: u32, + /// Generation number (Primarily used for NFS) + pub gen_number: u32, + /// In Ext2 version 0, this field is reserved. In version >= 1, + /// Extended attribute block (File ACL). + pub ext_attribute_block: u32, + /// In Ext2 version 0, this field is reserved. In version >= 1, Upper + /// 32 bits of file size (if feature bit set) if it's a file, + /// Directory ACL if it's a directory + pub size_high: u32, + /// Block address of fragment + pub frag_block_addr: u32, + /// Operating System Specific Value #2 + pub _os_specific_2: [u8; 12], +} + +impl Inode { + /// Discover the inode location on the disk. + pub unsafe fn find_inode>( + haystack: &V, + offset: Address, + size: usize, + ) -> Result<(Inode, Address), Error> { + if size != mem::size_of::() { + unimplemented!("inodes with a size != 128"); + } + + let end = offset + Address::from(size); + if haystack.size() < end { + return Err(Error::AddressOutOfBounds { + sector: end.sector(), + offset: end.offset(), + size: end.sector_size(), + }); + } + + let inode = haystack + .slice_unchecked(offset..end) + .dynamic_cast::(); + + Ok(inode) + } +} + +bitflags! { + /// + // #[derive(Copy)] + pub struct TypePerm: u16 { + /// FIFO + const FIFO = 0x1000; + /// Character device + const CHAR_DEVICE = 0x2000; + /// Directory + const DIRECTORY = 0x4000; + /// Block device + const BLOCK_DEVICE = 0x6000; + /// Regular file + const FILE = 0x8000; + /// Symbolic link + const SYMLINK = 0xA000; + /// Unix socket + const SOCKET = 0xC000; + /// Other—execute permission + const O_EXEC = 0x001; + /// Other—write permission + const O_WRITE = 0x002; + /// Other—read permission + const O_READ = 0x004; + /// Group—execute permission + const G_EXEC = 0x008; + /// Group—write permission + const G_WRITE = 0x010; + /// Group—read permission + const G_READ = 0x020; + /// User—execute permission + const U_EXEC = 0x040; + /// User—write permission + const U_WRITE = 0x080; + /// User—read permission + const U_READ = 0x100; + /// Sticky Bit + const STICKY = 0x200; + /// Set group ID + const SET_GID = 0x400; + /// Set user ID + const SET_UID = 0x800; + } +} + +bitflags! { + /// Flags + pub struct Flags: u32 { + /// Secure deletion (not used) + const SECURE_DEL = 0x00000001; + /// Keep a copy of data when deleted (not used) + const KEEP_COPY = 0x00000002; + /// File compression (not used) + const COMPRESSION = 0x00000004; + /// Synchronous updates—new data is written immediately to disk + const SYNC_UPDATE = 0x00000008; + /// Immutable file (content cannot be changed) + const IMMUTABLE = 0x00000010; + /// Append only + const APPEND_ONLY = 0x00000020; + /// File is not included in 'dump' command + const NODUMP = 0x00000040; + /// Last accessed time should not updated + const DONT_ATIME = 0x00000080; + /// Hash indexed directory + const HASH_DIR = 0x00010000; + /// AFS directory + const AFS_DIR = 0x00020000; + /// Journal file data + const JOURNAL_DATA = 0x00040000; + } +} + +/// Unknown entry type +pub const UNKNOWN: u8 = 0; +/// FIFO entry type +pub const FIFO: u8 = 1; +/// Character device entry type +pub const CHAR_DEVICE: u8 = 2; +/// Directory entry type +pub const DIRECTORY: u8 = 3; +/// Block device entry type +pub const BLOCK_DEVICE: u8 = 4; +/// Regular file entry type +pub const FILE: u8 = 5; +/// Symbolic link entry type +pub const SYMLINK: u8 = 6; +/// Unix socket entry type +pub const SOCKET: u8 = 7; diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..32ec8fb --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,5 @@ +//! + +pub mod block_group; +pub mod inode; +pub mod superblock; diff --git a/src/sys/superblock.rs b/src/sys/superblock.rs new file mode 100644 index 0000000..d97952e --- /dev/null +++ b/src/sys/superblock.rs @@ -0,0 +1,284 @@ +//! Superblock information + +use { + core::{fmt::Debug, mem}, + error::Error, + sector::{Address, SectorSize}, + volume::Volume, +}; + +/// Ext2 signature (0xef53), used to help confirm the presence of Ext2 on a +/// volume +pub const EXT2_MAGIC: u16 = 0xef53; + +/// Filesystem is free of errors +pub const FS_CLEAN: u16 = 1; +/// Filesystem has errors +pub const FS_ERR: u16 = 2; + +/// Ignore errors +pub const ERR_IGNORE: u16 = 1; +/// Remount as read-only on error +pub const ERR_RONLY: u16 = 2; +/// Panic on error +pub const ERR_PANIC: u16 = 3; + +/// Creator OS is Linux +pub const OS_LINUX: u32 = 0; +/// Creator OS is Hurd +pub const OS_HURD: u32 = 1; +/// Creator OS is Masix +pub const OS_MASIX: u32 = 2; +/// Creator OS is FreeBSD +pub const OS_FREEBSD: u32 = 3; +/// Creator OS is a BSD4.4-Lite derivative +pub const OS_LITE: u32 = 4; + +/// The Superblock contains all information about the layout of the file system +/// and possibly contains other important information like what optional +/// features were used to create the file system. +/// +/// The Superblock is always located at byte 1024 from the beginning of the +/// volume and is exactly 1024 bytes in length. For example, if the disk uses +/// 512 byte sectors, the Superblock will begin at LBA 2 and will occupy all of +/// sector 2 and 3. +#[repr(C, packed)] +#[derive(Clone, Debug, Copy)] +pub struct Superblock { + // taken from https://wiki.osdev.org/Ext2 + /// Total number of inodes in file system + pub inodes_count: u32, + /// Total number of blocks in file system + pub blocks_count: u32, + /// Number of blocks reserved for superuser (see offset 80) + pub r_blocks_count: u32, + /// Total number of unallocated blocks + pub free_blocks_count: u32, + /// Total number of unallocated inodes + pub free_inodes_count: u32, + /// Block number of the block containing the superblock + pub first_data_block: u32, + /// log2 (block size) - 10. (In other words, the number to shift 1,024 + /// to the left by to obtain the block size) + pub log_block_size: u32, + /// log2 (fragment size) - 10. (In other words, the number to shift + /// 1,024 to the left by to obtain the fragment size) + pub log_frag_size: i32, + /// Number of blocks in each block group + pub blocks_per_group: u32, + /// Number of fragments in each block group + pub frags_per_group: u32, + /// Number of inodes in each block group + pub inodes_per_group: u32, + /// Last mount time (in POSIX time) + pub mtime: u32, + /// Last written time (in POSIX time) + pub wtime: u32, + /// Number of times the volume has been mounted since its last + /// consistency check (fsck) + pub mnt_count: u16, + /// Number of mounts allowed before a consistency check (fsck) must be + /// done + pub max_mnt_count: i16, + /// Ext2 signature (0xef53), used to help confirm the presence of Ext2 + /// on a volume + pub magic: u16, + /// File system state (see `FS_CLEAN` and `FS_ERR`) + pub state: u16, + /// What to do when an error is detected (see `ERR_IGNORE`, `ERR_RONLY` and + /// `ERR_PANIC`) + pub errors: u16, + /// Minor portion of version (combine with Major portion below to + /// construct full version field) + pub rev_minor: u16, + /// POSIX time of last consistency check (fsck) + pub lastcheck: u32, + /// Interval (in POSIX time) between forced consistency checks (fsck) + pub checkinterval: u32, + /// Operating system ID from which the filesystem on this volume was + /// created + pub creator_os: u32, + /// Major portion of version (combine with Minor portion above to + /// construct full version field) + pub rev_major: u32, + /// User ID that can use reserved blocks + pub block_uid: u16, + /// Group ID that can use reserved blocks + pub block_gid: u16, + + /// First non-reserved inode in file system. + pub first_inode: u32, + /// SectorSize of each inode structure in bytes. + pub inode_size: u16, + /// Block group that this superblock is part of (if backup copy) + pub block_group: u16, + /// Optional features present (features that are not required to read + /// or write, but usually result in a performance increase) + pub features_opt: FeaturesOptional, + /// Required features present (features that are required to be + /// supported to read or write) + pub features_req: FeaturesRequired, + /// Features that if not supported, the volume must be mounted + /// read-only) + pub features_ronly: FeaturesROnly, + /// File system ID (what is output by blkid) + pub fs_id: [u8; 16], + /// Volume name (C-style string: characters terminated by a 0 byte) + pub volume_name: [u8; 16], + /// Path volume was last mounted to (C-style string: characters + /// terminated by a 0 byte) + pub last_mnt_path: [u8; 64], + /// Compression algorithms used (see Required features above) + pub compression: u32, + /// Number of blocks to preallocate for files + pub prealloc_blocks_files: u8, + /// Number of blocks to preallocate for directories + pub prealloc_blocks_dirs: u8, + #[doc(hidden)] + _unused: [u8; 2], + /// Journal ID (same style as the File system ID above) + pub journal_id: [u8; 16], + /// Journal inode + pub journal_inode: u32, + /// Journal device + pub journal_dev: u32, + /// Head of orphan inode list + pub journal_orphan_head: u32, + #[doc(hidden)] + _reserved: [u8; 788], +} +impl Superblock { + /// Discover the location of the superblock in the given block device. + pub unsafe fn find>( + haystack: &V, + ) -> Result<(Superblock, Address), Error> { + let offset = Address::from(1024_usize); + let end = offset + Address::from(mem::size_of::()); + if haystack.size() < end { + return Err(Error::AddressOutOfBounds { + sector: end.sector(), + offset: end.offset(), + size: end.sector_size(), + }); + } + + let superblock = { + haystack + .slice_unchecked(offset..end) + .dynamic_cast::() + }; + + if superblock.0.magic != EXT2_MAGIC { + Err(Error::BadMagic { + magic: superblock.0.magic, + }) + } else { + Ok(superblock) + } + } + + #[inline] + /// Return the block size + pub fn block_size(&self) -> usize { + 1024 << self.log_block_size + } + + #[inline] + /// Return the fragment size + pub fn frag_size(&self) -> usize { + 1024 << self.log_frag_size + } + /// Return the number of blocks per group + pub fn block_group_count(&self) -> Result { + let blocks_mod = self.blocks_count % self.blocks_per_group; + let inodes_mod = self.inodes_count % self.inodes_per_group; + let blocks_inc = if blocks_mod == 0 { 0 } else { 1 }; + let inodes_inc = if inodes_mod == 0 { 0 } else { 1 }; + let by_blocks = self.blocks_count / self.blocks_per_group + blocks_inc; + let by_inodes = self.inodes_count / self.inodes_per_group + inodes_inc; + if by_blocks == by_inodes { + Ok(by_blocks) + } else { + Err((by_blocks, by_inodes)) + } + } +} + +bitflags! { + /// Optional features + pub struct FeaturesOptional: u32 { + /// Preallocate some number of (contiguous?) blocks (see + /// `Superblock::prealloc_blocks_dirs`) to a directory when creating a new one + const PREALLOCATE = 0x0001; + /// AFS server inodes exist + const AFS = 0x0002; + /// File system has a journal (Ext3) + const JOURNAL = 0x0004; + /// Inodes have extended attributes + const EXTENDED_INODE = 0x0008; + /// File system can resize itself for larger partitions + const SELF_RESIZE = 0x0010; + /// Directories use hash index + const HASH_INDEX = 0x0020; + } +} + +bitflags! { + /// Required features. If these are not supported; can't mount + pub struct FeaturesRequired: u32 { + /// Compression is used + const REQ_COMPRESSION = 0x0001; + /// Directory entries contain a type field + const REQ_DIRECTORY_TYPE = 0x0002; + /// File system needs to replay its journal + const REQ_REPLAY_JOURNAL = 0x0004; + /// File system uses a journal device + const REQ_JOURNAL_DEVICE = 0x0008; + } +} + +bitflags! { + /// ROnly features. If these are not supported; remount as read-only + pub struct FeaturesROnly: u32 { + /// Sparse superblocks and group descriptor tables + const RONLY_SPARSE = 0x0001; + /// File system uses a 64-bit file size + const RONLY_FILE_SIZE_64 = 0x0002; + /// Directory contents are stored in the form of a Binary Tree + const RONLY_BTREE_DIRECTORY = 0x0004; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sector::Size512; + + #[test] + fn find() { + let mut volume = vec![0_u8; 4096]; + // magic + volume[1024 + 56] = EXT2_MAGIC as u8; + volume[1024 + 57] = (EXT2_MAGIC >> 8) as u8; + let superblock = unsafe { Superblock::find::(&volume) }; + assert!( + superblock.is_ok(), + "Err({:?})", + superblock.err().unwrap_or_else(|| unreachable!()), + ); + } + + #[test] + fn superblock() { + use std::cell::RefCell; + use std::fs::File; + + let file = RefCell::new(File::open("ext2.img").unwrap()); + let superblock = unsafe { Superblock::find::(&file) }; + assert!( + superblock.is_ok(), + "Err({:?})", + superblock.err().unwrap_or_else(|| unreachable!()), + ); + } +} diff --git a/src/volume/mod.rs b/src/volume/mod.rs new file mode 100644 index 0000000..d3d23b6 --- /dev/null +++ b/src/volume/mod.rs @@ -0,0 +1,371 @@ +#![allow(missing_docs)] +use { + alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + vec::Vec, + }, + core::{ + mem, + ops::{Deref, DerefMut, Range}, + slice, + }, + error::Error, + sector::{Address, SectorSize}, +}; + +pub mod size; +use self::size::Size; + +pub trait Volume { + type Error: Into; + + fn size(&self) -> Size; + fn commit( + &mut self, + slice: Option>, + ) -> Result<(), Self::Error>; + unsafe fn slice_unchecked<'a>( + &'a self, + range: Range>, + ) -> VolumeSlice<'a, T, S>; + + fn slice<'a>( + &'a self, + range: Range>, + ) -> Result, Self::Error>; +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct VolumeSlice<'a, T: 'a + Clone, S: SectorSize> { + inner: Cow<'a, [T]>, + index: Address, +} + +impl VolumeSlice<'static, T, S> { + pub fn with_static(inner: &'static [T]) -> VolumeSlice<'static, T, S> { + VolumeSlice { + inner: Cow::Borrowed(inner), + index: Address::new(0, 0), + } + } + + pub fn new_owned( + inner: <[T] as ToOwned>::Owned, + index: Address, + ) -> VolumeSlice<'static, T, S> { + VolumeSlice { + inner: Cow::Owned(inner), + index, + } + } +} + +impl<'a, T: Clone, S: SectorSize> VolumeSlice<'a, T, S> { + pub fn new(inner: &'a [T], index: Address) -> VolumeSlice<'a, T, S> { + VolumeSlice { + inner: Cow::Borrowed(inner), + index, + } + } + + pub fn is_mutated(&self) -> bool { + match self.inner { + Cow::Borrowed(_) => false, + Cow::Owned(_) => true, + } + } + + pub fn address(&self) -> Address { + self.index + } +} + +impl<'a, S: SectorSize> VolumeSlice<'a, u8, S> { + pub unsafe fn dynamic_cast(&self) -> (T, Address) { + assert!(self.inner.len() >= mem::size_of::()); + let index = self.index; + let cast = self.inner.as_ptr().cast::().read_unaligned(); + + // mem::transmute_copy(self.inner.as_ptr().as_ref().unwrap()); + (cast, index) + } + + pub fn from_cast( + cast: &'a T, + index: Address, + ) -> VolumeSlice<'a, u8, S> { + let len = mem::size_of::(); + let ptr = cast as *const T as *const u8; + let slice = unsafe { slice::from_raw_parts(ptr, len) }; + VolumeSlice::new(slice, index) + } +} + +impl<'a, T: Clone, S: SectorSize> VolumeSlice<'a, T, S> { + pub fn commit(self) -> Option> { + if self.is_mutated() { + Some(VolumeCommit::new(self.inner.into_owned(), self.index)) + } else { + None + } + } +} + +impl<'a, T: Clone, S: SectorSize> AsRef<[T]> for VolumeSlice<'a, T, S> { + fn as_ref(&self) -> &[T] { + self.inner.as_ref() + } +} + +impl<'a, T: Clone, S: SectorSize> AsMut<[T]> for VolumeSlice<'a, T, S> { + fn as_mut(&mut self) -> &mut [T] { + self.inner.to_mut().as_mut() + } +} + +impl<'a, T: Clone, S: SectorSize> Deref for VolumeSlice<'a, T, S> { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl<'a, T: Clone, S: SectorSize> DerefMut for VolumeSlice<'a, T, S> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + +pub struct VolumeCommit { + inner: Vec, + index: Address, +} + +impl VolumeCommit { + pub fn with_vec(inner: Vec) -> VolumeCommit { + VolumeCommit { + inner, + index: Address::new(0, 0), + } + } +} + +impl VolumeCommit { + pub fn new(inner: Vec, index: Address) -> VolumeCommit { + VolumeCommit { inner, index } + } + + pub fn into_inner(self) -> Vec { + self.inner + } + + pub fn address(&self) -> Address { + self.index + } +} + +impl AsRef<[T]> for VolumeCommit { + fn as_ref(&self) -> &[T] { + self.inner.as_ref() + } +} + +impl AsMut<[T]> for VolumeCommit { + fn as_mut(&mut self) -> &mut [T] { + self.inner.as_mut() + } +} + +impl Deref for VolumeCommit { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl DerefMut for VolumeCommit { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} + +macro_rules! impl_slice { + (@inner $volume:ty $( , $lt:lifetime )* ) => { + impl<$( $lt, )* T: Clone, S: SectorSize> Volume + for $volume + { + type Error = Error; + + fn size(&self) -> Size { + Size::Bounded( + Address::from(>::as_ref(self).len()) + ) + } + + fn commit( + &mut self, + slice: Option>, + ) -> Result<(), Self::Error> { + slice.map(|slice| { + let index = slice.address().into_index() as usize; + let end = index + slice.as_ref().len(); + // XXX: it would be much better to drop the contents of dst + // and move the contents of slice instead of cloning + let dst = + &mut >::as_mut(self)[index..end]; + dst.clone_from_slice(slice.as_ref()); + }); + Ok(()) + } + + unsafe fn slice_unchecked<'a>( + &'a self, + range: Range>, + ) -> VolumeSlice<'a, T, S> { + let index = range.start; + let range = range.start.into_index() as usize + ..range.end.into_index() as usize; + VolumeSlice::new( + >::as_ref(self).get_unchecked(range), + index, + ) + } + + fn slice<'a>( + &'a self, + range: Range>, + ) -> Result, Self::Error> { + if self.size() >= range.end { + unsafe { Ok(self.slice_unchecked(range)) } + } else { + Err(Error::AddressOutOfBounds { + sector: range.end.sector(), + offset: range.end.offset(), + size: range.end.sector_size() + }) + } + } + } + }; + ($volume:ty) => { + impl_slice!(@inner $volume); + }; + ($volume:ty $( , $lt:lifetime )* ) => { + impl_slice!(@inner $volume $( , $lt )* ); + }; +} + +impl_slice!(&'b mut [T], 'b); +impl_slice!(Vec); +impl_slice!(Box<[T]>); + +#[cfg(any(test, not(feature = "no_std")))] +mod file { + use std::cell::RefCell; + use std::fs::File; + use std::io::{self, Read, Seek, SeekFrom, Write}; + use std::ops::Range; + + use sector::{Address, SectorSize}; + + use super::size::Size; + use super::{Volume, VolumeCommit, VolumeSlice}; + + impl Volume for RefCell { + type Error = io::Error; + + fn size(&self) -> Size { + Size::Bounded( + self.borrow() + .metadata() + .map(|data| Address::from(data.len())) + .unwrap_or(Address::new(0, 0)), + ) + } + + fn commit( + &mut self, + slice: Option>, + ) -> Result<(), Self::Error> { + slice + .map(|slice| { + let index = slice.address(); + let mut refmut = self.borrow_mut(); + refmut + .seek(SeekFrom::Start(index.into_index())) + .and_then(|_| refmut.write(slice.as_ref())) + .map(|_| ()) + }) + .unwrap_or(Ok(())) + } + + unsafe fn slice_unchecked<'a>( + &'a self, + range: Range>, + ) -> VolumeSlice<'a, u8, S> { + let index = range.start; + let len = range.end - range.start; + let mut vec = Vec::with_capacity(len.into_index() as usize); + vec.set_len(len.into_index() as usize); + let mut refmut = self.borrow_mut(); + refmut + .seek(SeekFrom::Start(index.into_index())) + .and_then(|_| refmut.read_exact(&mut vec[..])) + .unwrap_or_else(|err| { + panic!("could't read from File Volume: {:?}", err) + }); + VolumeSlice::new_owned(vec, index) + } + + fn slice<'a>( + &'a self, + range: Range>, + ) -> Result, Self::Error> { + let index = range.start; + let mut vec = Vec::with_capacity( + (range.end - range.start).into_index() as usize, + ); + unsafe { + vec.set_len((range.end - range.start).into_index() as usize); + } + let mut refmut = self.borrow_mut(); + refmut + .seek(SeekFrom::Start(index.into_index())) + .and_then(|_| refmut.read_exact(&mut vec[..])) + .map(move |_| VolumeSlice::new_owned(vec, index)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sector::{Address, Size512}; + + #[test] + fn volume() { + let mut volume = vec![0; 1024]; + let commit = { + let mut slice = volume + .slice( + Address::::from(256_u64) + ..Address::::from(512_u64), + ) + .unwrap(); + slice.iter_mut().for_each(|x| *x = 1); + slice.commit() + }; + assert!(volume.commit(commit).is_ok()); + + for (i, &x) in volume.iter().enumerate() { + if i < 256 || i >= 512 { + assert_eq!(x, 0); + } else { + assert_eq!(x, 1); + } + } + } +} diff --git a/src/volume/size.rs b/src/volume/size.rs new file mode 100644 index 0000000..b8b5b26 --- /dev/null +++ b/src/volume/size.rs @@ -0,0 +1,115 @@ +//! + +use { + core::{ + cmp::Ordering, + fmt::{self, Display}, + }, + sector::{Address, SectorSize}, +}; + +#[derive(Clone, Copy, Debug, Hash)] +/// A size +pub enum Size { + /// An unbounded size + Unbounded, + /// A bounded size + Bounded(Address), +} + +impl Size { + /// Try to get the length of the sector + pub fn try_len(&self) -> Option> { + match *self { + Size::Unbounded => None, + Size::Bounded(n) => Some(n), + } + } + /// Get the length of the sector unsafely + /// + /// # Safety + /// + /// This function is unsafe because it does not check that the size is + /// bounded. + pub unsafe fn len(&self) -> Address { + match *self { + Size::Unbounded => panic!( + "attempt to convert `Size::Unbounded` to a concrete length" + ), + Size::Bounded(n) => n, + } + } +} + +impl Size { + /// Check if the size is unbounded + pub fn is_bounded(&self) -> bool { + match *self { + Size::Unbounded => false, + Size::Bounded(_) => true, + } + } +} + +impl Display for Size { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Size::Unbounded => write!(f, "Unbounded"), + Size::Bounded(n) => write!(f, "Bounded({})", n), + } + } +} + +impl PartialEq for Size { + fn eq(&self, rhs: &Self) -> bool { + match (self, rhs) { + (&Size::Unbounded, _) => false, + (_, &Size::Unbounded) => false, + (&Size::Bounded(ref a), &Size::Bounded(ref b)) => a.eq(b), + } + } + + fn ne(&self, rhs: &Self) -> bool { + match (self, rhs) { + (&Size::Unbounded, _) => false, + (_, &Size::Unbounded) => false, + (&Size::Bounded(ref a), &Size::Bounded(ref b)) => a.ne(b), + } + } +} + +impl PartialEq> for Size { + fn eq(&self, rhs: &Address) -> bool { + match *self { + Size::Unbounded => false, + Size::Bounded(ref n) => n.eq(rhs), + } + } + + fn ne(&self, rhs: &Address) -> bool { + match *self { + Size::Unbounded => false, + Size::Bounded(ref n) => n.eq(rhs), + } + } +} + +impl PartialOrd for Size { + fn partial_cmp(&self, rhs: &Self) -> Option { + match (self, rhs) { + (&Size::Unbounded, &Size::Unbounded) => None, + (&Size::Unbounded, _) => Some(Ordering::Greater), + (_, &Size::Unbounded) => Some(Ordering::Less), + (&Size::Bounded(ref a), &Size::Bounded(ref b)) => a.partial_cmp(b), + } + } +} + +impl PartialOrd> for Size { + fn partial_cmp(&self, rhs: &Address) -> Option { + match *self { + Size::Unbounded => Some(Ordering::Greater), + Size::Bounded(ref n) => n.partial_cmp(rhs), + } + } +}