Moved stuff

This commit is contained in:
Amogus 2022-12-10 23:03:08 +01:00
commit 117d019aa5
16 changed files with 2608 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/Cargo.lock

16
Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "ext2"
version = "0.1.1"
authors = ["Szymon Walter <walter.szymon.98@gmail.com>",
"able <abl3theabove@gmail.com>"]
[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"]

22
LICENSE.md Normal file
View file

@ -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

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# ext2-rs
An OS and architecture independent implementation of ext2 in pure Rust.

2
rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
max_width = 80
wrap_comments = true

124
src/error.rs Normal file
View file

@ -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<Infallible> for Error {
fn from(_: Infallible) -> Error {
unreachable!()
}
}
#[cfg(any(test, not(feature = "no_std")))]
impl From<io::Error> for Error {
fn from(inner: io::Error) -> Error {
Error::Io { inner }
}
}
/// Infalliable
pub enum Infallible {}

177
src/fs/mod.rs Normal file
View file

@ -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<T, S: SectorSize> {
pub inner: T,
pub offset: Address<S>,
}
impl<T, S: SectorSize> From<(T, Address<S>)> for Struct<T, S> {
#[inline]
fn from((inner, offset): (T, Address<S>)) -> Struct<T, S> {
Struct { inner, offset }
}
}
/// Safe wrapper for raw sys structs
pub struct Ext2<S: SectorSize, V: Volume<u8, S>> {
// TODO: should this have some different vis?
pub(crate) volume: V,
pub(crate) superblock: Struct<Superblock, S>,
pub(crate) block_groups: Struct<Vec<BlockGroupDescriptor>, S>,
}
impl<S: SectorSize, V: Volume<u8, S>> Ext2<S, V> {
///
pub fn new(volume: V) -> Result<Ext2<S, V>, 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::<RawInode>()
} 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<usize, Error> {
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::<Size512>::from(2048_u64)
- Address::<Size512>::from(1024_u64),
Address::<Size512>::new(2, 0)
);
assert_eq!(
unsafe {
file.slice_unchecked(
Address::<Size512>::from(1024_u64)
..Address::<Size512>::from(2048_u64),
)
.len()
},
1024
);
}
#[test]
fn file() {
let file = RefCell::new(File::open("ext2.img").unwrap());
let fs = Ext2::<Size512, _>::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());
}
}

905
src/fs/sync.rs Normal file
View file

@ -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<T> {
inner: Arc<Mutex<T>>,
}
impl<T> Synced<T> {
/// DOCME: what is this?
pub fn with_inner(inner: T) -> Synced<T> {
Synced {
inner: Arc::new(Mutex::new(inner)),
}
}
/// DOCME: what is this?
pub fn inner<'a>(&'a self) -> MutexGuard<'a, T> {
self.inner.lock()
}
}
impl<T> Clone for Synced<T> {
fn clone(&self) -> Self {
Synced {
inner: self.inner.clone(),
}
}
}
impl<S: SectorSize, V: Volume<u8, S>> Synced<Ext2<S, V>> {
/// DOCME: what is this?
pub fn new(volume: V) -> Result<Synced<Ext2<S, V>>, Error> {
Ext2::new(volume).map(Synced::with_inner)
}
/// Get the root inode.
pub fn root_inode(&self) -> Inode<S, V> {
self.inode_nth(2).unwrap()
}
/// Get the inode at the given index.
pub fn inode_nth(&self, index: usize) -> Option<Inode<S, V>> {
self.inodes_nth(index).next()
}
/// DOCME: what is this?
pub fn inodes(&self) -> Inodes<S, V> {
self.inodes_nth(1)
}
/// DOCME: what is this?
pub fn inodes_nth(&self, index: usize) -> Inodes<S, V> {
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<S: SectorSize, V: Volume<u8, S>> Fs for Synced<Ext2<S, V>> {
type Path = [u8];
type PathOwned = Vec<u8>;
type File = Inode<S, V>;
type Dir = Directory<S, V>;
type DirEntry = DirectoryEntry;
type Metadata = (); // TODO
type Permissions = (); // TODO
type Error = Error;
fn open(
&self,
abs_path: &Self::Path,
_options: &OpenOptions<Self::Permissions>,
) -> Result<Self::File, Self::Error> {
fn inner<'a, S, V, I>(
fs: &Synced<Ext2<S, V>>,
inode: Inode<S, V>,
mut path: I,
abs_path: &[u8],
) -> Result<Inode<S, V>, Error>
where
S: SectorSize,
V: Volume<u8, S>,
I: Iterator<Item = &'a [u8]>,
{
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<Self::Metadata, Self::Error> {
unimplemented!()
}
fn symlink_metadata(
&self,
_path: &Self::Path,
) -> Result<Self::Metadata, Self::Error> {
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<u64, Self::Error> {
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<Self::PathOwned, Self::Error> {
unimplemented!()
}
fn canonicalize(
&self,
_path: &Self::Path,
) -> Result<Self::PathOwned, Self::Error> {
unimplemented!()
}
fn create_dir(
&mut self,
_path: &Self::Path,
_options: &DirOptions<Self::Permissions>,
) -> 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<Self::Dir, Self::Error> {
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<S: SectorSize, V: Volume<u8, S>> Debug for Synced<Ext2<S, V>> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Synced<Ext2<{}>>", S::SIZE)
}
}
#[derive(Debug, Clone)]
/// A collection of inodes.
pub struct Inodes<S: SectorSize, V: Volume<u8, S>> {
fs: Synced<Ext2<S, V>>,
log_block_size: u32,
inode_size: usize,
inodes_per_group: usize,
inodes_count: usize,
index: usize,
}
impl<S: SectorSize, V: Volume<u8, S>> Iterator for Inodes<S, V> {
type Item = Inode<S, V>;
fn next(&mut self) -> Option<Self::Item> {
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<S: SectorSize, V: Volume<u8, S>> {
fs: Synced<Ext2<S, V>>,
inner: RawInode,
addr: Address<S>,
num: u32,
}
impl<S: SectorSize, V: Volume<u8, S>> Clone for Inode<S, V> {
fn clone(&self) -> Self {
Inode {
fs: self.fs.clone(),
inner: self.inner,
addr: self.addr,
num: self.num,
}
}
}
impl<S: SectorSize, V: Volume<u8, S>> Inode<S, V> {
///
pub fn new(
fs: Synced<Ext2<S, V>>,
inner: RawInode,
addr: Address<S>,
num: u32,
) -> Inode<S, V> {
Inode {
fs,
inner,
addr,
num,
}
}
/// Read to the end of a buffer.
pub fn read_to_end(&self, buf: &mut Vec<u8>) -> Result<usize, Error> {
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<S, V> {
InodeBlocks {
inode: self.clone(),
index: 0,
}
}
/// return a directory iterator
pub fn directory(&self) -> Option<Directory<S, V>> {
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<NonZeroU32> {
self.try_block(index).ok().and_then(|block| block)
}
/// Try to get a block
pub fn try_block(
&self,
mut index: usize,
) -> Result<Option<NonZeroU32>, 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<S: SectorSize, V: Volume<u8, S>>(
volume: &V,
block: u32,
index: usize,
log_block_size: u32,
) -> Result<Option<NonZeroU32>, 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::<u32>().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<S: SectorSize, V: Volume<u8, S>> File for Inode<S, V> {
type Error = Error;
fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
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<usize, Self::Error> {
unimplemented!()
}
fn flush(&mut self) -> Result<(), Self::Error> {
unimplemented!()
}
fn seek(&mut self, _pos: SeekFrom) -> Result<u64, Self::Error> {
unimplemented!()
}
}
///
#[derive(Debug, Clone)]
pub struct InodeBlocks<S: SectorSize, V: Volume<u8, S>> {
inode: Inode<S, V>,
index: usize,
}
impl<S: SectorSize, V: Volume<u8, S>> Iterator for InodeBlocks<S, V> {
type Item = Result<(Vec<u8>, Address<S>), Error>;
fn next(&mut self) -> Option<Self::Item> {
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<S: SectorSize, V: Volume<u8, S>> {
blocks: InodeBlocks<S, V>,
offset: usize,
buffer: Option<Vec<u8>>,
block_size: usize,
}
impl<S: SectorSize, V: Volume<u8, S>> Dir<DirectoryEntry, Error>
for Directory<S, V>
{
}
impl<S: SectorSize, V: Volume<u8, S>> Iterator for Directory<S, V> {
type Item = Result<DirectoryEntry, Error>;
fn next(&mut self) -> Option<Self::Item> {
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<u8>,
/// The inode of the entry
pub inode: usize,
///
pub ty: u8,
}
impl DirEntry for DirectoryEntry {
type Path = [u8];
type PathOwned = Vec<u8>;
type Metadata = (); // TODO
type FileType = u8; // TODO: enum FileType
type Error = Error;
fn path(&self) -> Self::PathOwned {
unimplemented!()
}
fn metadata(&self) -> Result<Self::Metadata, Self::Error> {
unimplemented!()
}
fn file_type(&self) -> Result<Self::FileType, Self::Error> {
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::<Ext2<Size512, _>>::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::<Ext2<Size512, _>>::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::<Ext2<Size512, _>>::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::<Ext2<Size512, _>>::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::<Ext2<Size512, _>>::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<u8, S>>(
fs: &'vol Synced<Ext2<S, V>>,
inode: Inode<S, V>,
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::<Ext2<Size512, _>>::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::<Ext2<Size512, _>>::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());
}
}

36
src/lib.rs Normal file
View file

@ -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::<Superblock>(), 1024);
assert_eq!(size_of::<BlockGroupDescriptor>(), 32);
assert_eq!(size_of::<Inode>(), 128);
}
}

242
src/sector.rs Normal file
View file

@ -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<S: SectorSize> {
sector: u32,
offset: u32,
_phantom: PhantomData<S>,
}
impl<S: SectorSize> Address<S> {
///
pub unsafe fn new_unchecked(sector: u32, offset: u32) -> Address<S> {
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<S> {
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<S> {
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<S: SectorSize> Step for Address<S> {
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
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> {
self.sector
.checked_add(n as u32)
.map(|sector| Address::new(sector, 0))
}
*/
fn forward_checked(_start: Self, count: usize) -> Option<Self> {
todo!("forward_checked: count: {}", count);
}
fn backward_checked(_start: Self, count: usize) -> Option<Self> {
todo!("backward_checked count: {}", count);
}
}
impl<S: SectorSize> Display for Address<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.sector, self.offset)
}
}
impl<S: SectorSize> LowerHex for Address<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:x}:{:x}", self.sector, self.offset)
}
}
impl<S: SectorSize> From<u64> for Address<S> {
fn from(idx: u64) -> Address<S> {
let sector = idx >> S::LOG_SIZE;
let offset = idx & S::OFFSET_MASK as u64;
Address::new(sector as u32, offset as i32)
}
}
impl<S: SectorSize> From<usize> for Address<S> {
fn from(idx: usize) -> Address<S> {
let sector = idx >> S::LOG_SIZE;
let offset = idx & S::OFFSET_MASK as usize;
Address::new(sector as u32, offset as i32)
}
}
impl<S: SectorSize> Add for Address<S> {
type Output = Address<S>;
fn add(self, rhs: Address<S>) -> Address<S> {
Address::new(
self.sector + rhs.sector,
(self.offset + rhs.offset) as i32,
)
}
}
impl<S: SectorSize> Sub for Address<S> {
type Output = Address<S>;
fn sub(self, rhs: Address<S>) -> Address<S> {
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::<Size512>::new(0, 1024).into_index(), 1024);
assert_eq!(Address::<Size512>::from(1024_u64).into_index(), 1024);
assert_eq!(
Address::<Size512>::with_block_size(1, 256, 10).into_index(),
1024 + 256
);
assert_eq!(
Address::<Size512>::with_block_size(2, 0, 10).into_index(),
2048
);
assert_eq!(
Address::<Size512>::with_block_size(0, 1792, 10).into_index(),
1792
);
}
#[test]
fn arithmetic() {
assert_eq!(
Address::<Size512>::new(0, 512),
Address::<Size512>::new(1, 0),
);
assert_eq!(
Address::<Size512>::new(2, -256),
Address::<Size512>::new(1, 256),
);
let a = Address::<Size2048>::new(0, 1024);
let b = Address::<Size2048>::new(0, 1024);
assert_eq!(a + b, Address::<Size2048>::new(1, 0));
assert_eq!((a + b).into_index(), 2048);
let a = Address::<Size512>::new(0, 2048);
let b = Address::<Size512>::new(0, 256);
assert_eq!(a - b, Address::<Size512>::new(3, 256));
assert_eq!((a - b).into_index(), 1792);
}
}

116
src/sys/block_group.rs Normal file
View file

@ -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<S: SectorSize, V: Volume<u8, S>>(
haystack: &V,
offset: Address<S>,
) -> Result<(BlockGroupDescriptor, Address<S>), Error> {
let end =
offset + Address::from(mem::size_of::<BlockGroupDescriptor>());
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::<BlockGroupDescriptor>();
Ok(descr)
}
/// find a descriptor table
pub unsafe fn find_descriptor_table<S: SectorSize, V: Volume<u8, S>>(
haystack: &V,
offset: Address<S>,
count: usize,
) -> Result<(Vec<BlockGroupDescriptor>, Address<S>), Error> {
let end = offset
+ Address::from(count * mem::size_of::<BlockGroupDescriptor>());
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::<BlockGroupDescriptor>());
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::<Size512>::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);
}
}

188
src/sys/inode.rs Normal file
View file

@ -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<S: SectorSize, V: Volume<u8, S>>(
haystack: &V,
offset: Address<S>,
size: usize,
) -> Result<(Inode, Address<S>), Error> {
if size != mem::size_of::<Inode>() {
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::<Inode>();
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;

5
src/sys/mod.rs Normal file
View file

@ -0,0 +1,5 @@
//!
pub mod block_group;
pub mod inode;
pub mod superblock;

284
src/sys/superblock.rs Normal file
View file

@ -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<S: SectorSize, V: Volume<u8, S>>(
haystack: &V,
) -> Result<(Superblock, Address<S>), Error> {
let offset = Address::from(1024_usize);
let end = offset + Address::from(mem::size_of::<Superblock>());
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::<Superblock>()
};
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<u32, (u32, u32)> {
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::<Size512, _>(&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::<Size512, _>(&file) };
assert!(
superblock.is_ok(),
"Err({:?})",
superblock.err().unwrap_or_else(|| unreachable!()),
);
}
}

371
src/volume/mod.rs Normal file
View file

@ -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<T: Clone, S: SectorSize> {
type Error: Into<Error>;
fn size(&self) -> Size<S>;
fn commit(
&mut self,
slice: Option<VolumeCommit<T, S>>,
) -> Result<(), Self::Error>;
unsafe fn slice_unchecked<'a>(
&'a self,
range: Range<Address<S>>,
) -> VolumeSlice<'a, T, S>;
fn slice<'a>(
&'a self,
range: Range<Address<S>>,
) -> Result<VolumeSlice<'a, T, S>, Self::Error>;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VolumeSlice<'a, T: 'a + Clone, S: SectorSize> {
inner: Cow<'a, [T]>,
index: Address<S>,
}
impl<T: Clone, S: SectorSize> 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<S>,
) -> 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<S>) -> 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<S> {
self.index
}
}
impl<'a, S: SectorSize> VolumeSlice<'a, u8, S> {
pub unsafe fn dynamic_cast<T: Copy>(&self) -> (T, Address<S>) {
assert!(self.inner.len() >= mem::size_of::<T>());
let index = self.index;
let cast = self.inner.as_ptr().cast::<T>().read_unaligned();
// mem::transmute_copy(self.inner.as_ptr().as_ref().unwrap());
(cast, index)
}
pub fn from_cast<T: Copy>(
cast: &'a T,
index: Address<S>,
) -> VolumeSlice<'a, u8, S> {
let len = mem::size_of::<T>();
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<VolumeCommit<T, S>> {
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<T, S: SectorSize> {
inner: Vec<T>,
index: Address<S>,
}
impl<T: Clone, S: SectorSize> VolumeCommit<T, S> {
pub fn with_vec(inner: Vec<T>) -> VolumeCommit<T, S> {
VolumeCommit {
inner,
index: Address::new(0, 0),
}
}
}
impl<T: Clone, S: SectorSize> VolumeCommit<T, S> {
pub fn new(inner: Vec<T>, index: Address<S>) -> VolumeCommit<T, S> {
VolumeCommit { inner, index }
}
pub fn into_inner(self) -> Vec<T> {
self.inner
}
pub fn address(&self) -> Address<S> {
self.index
}
}
impl<T: Clone, S: SectorSize> AsRef<[T]> for VolumeCommit<T, S> {
fn as_ref(&self) -> &[T] {
self.inner.as_ref()
}
}
impl<T: Clone, S: SectorSize> AsMut<[T]> for VolumeCommit<T, S> {
fn as_mut(&mut self) -> &mut [T] {
self.inner.as_mut()
}
}
impl<T: Clone, S: SectorSize> Deref for VolumeCommit<T, S> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<T: Clone, S: SectorSize> DerefMut for VolumeCommit<T, S> {
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<T, S>
for $volume
{
type Error = Error;
fn size(&self) -> Size<S> {
Size::Bounded(
Address::from(<Self as AsRef<[T]>>::as_ref(self).len())
)
}
fn commit(
&mut self,
slice: Option<VolumeCommit<T, S>>,
) -> 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 <Self as AsMut<[T]>>::as_mut(self)[index..end];
dst.clone_from_slice(slice.as_ref());
});
Ok(())
}
unsafe fn slice_unchecked<'a>(
&'a self,
range: Range<Address<S>>,
) -> VolumeSlice<'a, T, S> {
let index = range.start;
let range = range.start.into_index() as usize
..range.end.into_index() as usize;
VolumeSlice::new(
<Self as AsRef<[T]>>::as_ref(self).get_unchecked(range),
index,
)
}
fn slice<'a>(
&'a self,
range: Range<Address<S>>,
) -> Result<VolumeSlice<'a, T, S>, 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<T>);
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<S: SectorSize> Volume<u8, S> for RefCell<File> {
type Error = io::Error;
fn size(&self) -> Size<S> {
Size::Bounded(
self.borrow()
.metadata()
.map(|data| Address::from(data.len()))
.unwrap_or(Address::new(0, 0)),
)
}
fn commit(
&mut self,
slice: Option<VolumeCommit<u8, S>>,
) -> 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<Address<S>>,
) -> 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<Address<S>>,
) -> Result<VolumeSlice<'a, u8, S>, 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::<Size512>::from(256_u64)
..Address::<Size512>::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);
}
}
}
}

115
src/volume/size.rs Normal file
View file

@ -0,0 +1,115 @@
//!
use {
core::{
cmp::Ordering,
fmt::{self, Display},
},
sector::{Address, SectorSize},
};
#[derive(Clone, Copy, Debug, Hash)]
/// A size
pub enum Size<S: SectorSize> {
/// An unbounded size
Unbounded,
/// A bounded size
Bounded(Address<S>),
}
impl<S: SectorSize> Size<S> {
/// Try to get the length of the sector
pub fn try_len(&self) -> Option<Address<S>> {
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<S> {
match *self {
Size::Unbounded => panic!(
"attempt to convert `Size::Unbounded` to a concrete length"
),
Size::Bounded(n) => n,
}
}
}
impl<S: SectorSize> Size<S> {
/// Check if the size is unbounded
pub fn is_bounded(&self) -> bool {
match *self {
Size::Unbounded => false,
Size::Bounded(_) => true,
}
}
}
impl<S: SectorSize> Display for Size<S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Size::Unbounded => write!(f, "Unbounded"),
Size::Bounded(n) => write!(f, "Bounded({})", n),
}
}
}
impl<S: SectorSize> PartialEq for Size<S> {
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<S: SectorSize> PartialEq<Address<S>> for Size<S> {
fn eq(&self, rhs: &Address<S>) -> bool {
match *self {
Size::Unbounded => false,
Size::Bounded(ref n) => n.eq(rhs),
}
}
fn ne(&self, rhs: &Address<S>) -> bool {
match *self {
Size::Unbounded => false,
Size::Bounded(ref n) => n.eq(rhs),
}
}
}
impl<S: SectorSize> PartialOrd for Size<S> {
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
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<S: SectorSize> PartialOrd<Address<S>> for Size<S> {
fn partial_cmp(&self, rhs: &Address<S>) -> Option<Ordering> {
match *self {
Size::Unbounded => Some(Ordering::Greater),
Size::Bounded(ref n) => n.partial_cmp(rhs),
}
}
}