Moved stuff
This commit is contained in:
commit
117d019aa5
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
16
Cargo.toml
Normal file
16
Cargo.toml
Normal 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
22
LICENSE.md
Normal 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
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# ext2-rs
|
||||||
|
|
||||||
|
An OS and architecture independent implementation of ext2 in pure Rust.
|
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
max_width = 80
|
||||||
|
wrap_comments = true
|
124
src/error.rs
Normal file
124
src/error.rs
Normal 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
177
src/fs/mod.rs
Normal 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
905
src/fs/sync.rs
Normal 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
36
src/lib.rs
Normal 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
242
src/sector.rs
Normal 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
116
src/sys/block_group.rs
Normal 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
188
src/sys/inode.rs
Normal 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
5
src/sys/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//!
|
||||||
|
|
||||||
|
pub mod block_group;
|
||||||
|
pub mod inode;
|
||||||
|
pub mod superblock;
|
284
src/sys/superblock.rs
Normal file
284
src/sys/superblock.rs
Normal 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
371
src/volume/mod.rs
Normal 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
115
src/volume/size.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue