Initial commit

This commit is contained in:
pjht 2024-09-04 10:44:29 -05:00
commit e205e31797
Signed by: pjht
GPG Key ID: 7B5F6AFBEC7EE78E
13 changed files with 1537 additions and 0 deletions

5
.cargo/config.toml Normal file
View File

@ -0,0 +1,5 @@
[build]
target = "x86_64-unknown-mikros"
[install]
root = "../os_build/sysroot"

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

427
Cargo.lock generated Normal file
View File

@ -0,0 +1,427 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "binread"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f"
dependencies = [
"binread_derive",
"rustversion",
]
[[package]]
name = "binread_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed"
dependencies = [
"either",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cobs"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
[[package]]
name = "critical-section"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "ext2"
version = "0.1.0"
dependencies = [
"binread",
"file_rpc",
"fs_rpc",
"itertools",
"parking_lot",
"syslog_rpc",
"vfs_rpc",
]
[[package]]
name = "file_rpc"
version = "0.1.0"
dependencies = [
"parking_lot",
"postcard",
"serde",
]
[[package]]
name = "fs_rpc"
version = "0.1.0"
dependencies = [
"parking_lot",
"postcard",
"serde",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32",
"rustc_version",
"serde",
"spin",
"stable_deref_trait",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "postcard"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"heapless",
"postcard-derive",
"serde",
]
[[package]]
name = "postcard-derive"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0239fa9c1d225d4b7eb69925c25c5e082307a141e470573fbbe3a817ce6a7a37"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
dependencies = [
"bitflags",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syslog_rpc"
version = "0.1.0"
dependencies = [
"parking_lot",
"postcard",
"syslog_structs",
]
[[package]]
name = "syslog_structs"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "vfs_rpc"
version = "0.1.0"
dependencies = [
"parking_lot",
"postcard",
"serde",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

13
Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "ext2"
version = "0.1.0"
edition = "2021"
[dependencies]
binread = "2.2.0"
file_rpc = { version = "0.1.0", path = "../file_rpc" }
fs_rpc = { version = "0.1.0", path = "../fs_rpc" }
itertools = "0.13.0"
parking_lot = "0.12.3"
syslog_rpc = { version = "0.1.0", path = "../syslog/syslog_rpc" }
vfs_rpc = { version = "0.1.0", path = "../vfs/vfs_rpc" }

2
rust-toolchain.toml Normal file
View File

@ -0,0 +1,2 @@
[toolchain]
channel = "dev-x86_64-unknown-mikros"

80
src/ext2.rs Normal file
View File

@ -0,0 +1,80 @@
mod block_group_table;
mod block_reader;
mod dir;
mod file;
mod metadata;
mod structs;
use block_group_table::BlockGroupDescriptorTable;
use block_reader::BlockReader;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::io;
use std::path::{Path, PathBuf};
use structs::{DirEntryDisk, Inode, Superblock};
pub use dir::ReadDir;
pub use file::File;
pub use metadata::Metadata;
use std::fs::File as StdFile;
#[derive(Debug)]
pub struct Ext2 {
reader: BlockReader,
descriptor_table: BlockGroupDescriptorTable,
inode_cache: RwLock<HashMap<u32, Inode>>,
dentry_cache: RwLock<HashMap<PathBuf, DirEntryDisk>>,
}
impl Ext2 {
pub fn new(mut disk: StdFile) -> io::Result<Self> {
let superblock = Superblock::read_from_disk(&mut disk)?;
let reader = BlockReader::new(disk, &superblock);
Ok(Self {
descriptor_table: BlockGroupDescriptorTable::new(&superblock, &reader)?,
reader,
inode_cache: RwLock::new(HashMap::new()),
dentry_cache: RwLock::new(HashMap::new()),
})
}
fn path_inode<P: AsRef<Path>>(&self, path: P) -> io::Result<u32> {
let path = path.as_ref();
let dentry = { self.dentry_cache.read().get(path).cloned() };
if let Some(dentry) = dentry {
//println!("Dentry {}: In cache", path.display());
Ok(dentry.inode)
} else if let Some(parent) = path.parent() {
//println!("Dentry {}: Not in cache", path.display());
let file_name = path.file_name().ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
"A path ending in .. or . was passed to path_inode",
)
})?;
let dir_inode = self.path_inode(parent)?;
let dir = ReadDir::read_inode(self, dir_inode, parent.to_owned())?;
let entry = dir.get_entry(file_name)?;
Ok(entry.inode)
} else {
Ok(2)
}
}
#[allow(unused)]
pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<ReadDir> {
let path = path.as_ref();
let inode = self.path_inode(path)?;
ReadDir::read_inode(self, inode, path.to_owned())
}
#[allow(unused)]
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
File::from_inode(Inode::read(self, self.path_inode(path)?)?, self)
}
#[allow(unused)]
pub fn metadata<P: AsRef<Path>>(&self, path: P) -> io::Result<Metadata> {
Inode::read(self, self.path_inode(path)?).map(|x| Metadata::from_inode(&x))
}
}

View File

@ -0,0 +1,83 @@
use super::{
block_reader::BlockReader,
structs::{BlockGroupDescriptor, Inode, Superblock},
};
use binread::prelude::*;
use std::io;
use std::vec::Vec;
const INODE_SIZE: usize = 256;
#[derive(Debug, BinRead)]
#[br(import(len: usize, block_group_inode_count: u32, block_size: usize))]
pub struct BlockGroupDescriptorTable {
#[br(count = len)]
table: Vec<BlockGroupDescriptor>,
#[br(calc = block_group_inode_count)]
block_group_inode_count: u32,
#[br(calc = block_size)]
block_size: usize,
}
impl BlockGroupDescriptorTable {
pub fn new(superblock: &Superblock, reader: &BlockReader) -> io::Result<Self> {
let num_block_groups = superblock
.total_blocks
.div_ceil(superblock.block_group_block_count) as usize;
let table_blocks = num_block_groups.div_ceil(reader.block_size / 32);
Ok(reader
.read_blocks(superblock.superblock_block + 1, table_blocks)?
.read_le_args((
num_block_groups,
superblock.block_group_inode_count,
1024 << superblock.block_size_raw,
))
.expect("Parsing the block group descriptor table should never fail"))
}
fn inode_group(&self, inode: u32) -> &BlockGroupDescriptor {
&self.table[((inode - 1) / self.block_group_inode_count) as usize]
}
fn inode_group_offset(&self, inode: u32) -> usize {
((inode - 1) % self.block_group_inode_count) as usize
}
fn inode_block(&self, inode: u32) -> u32 {
(((self.inode_group_offset(inode) * INODE_SIZE) / self.block_size) as u32)
+ self.inode_group(inode).inode_table_start
}
fn inode_block_byte_start(&self, inode: u32) -> usize {
(self.inode_group_offset(inode) * INODE_SIZE) % self.block_size
}
pub fn inode_loc(&self, inode: u32) -> InodeLoc {
InodeLoc {
block: self.inode_block(inode),
byte_offset: self.inode_block_byte_start(inode),
num: inode,
}
}
}
#[derive(Debug)]
pub struct InodeLoc {
block: u32,
byte_offset: usize,
num: u32,
}
impl InodeLoc {
pub fn read(&self, reader: &BlockReader) -> io::Result<Inode> {
//println!("InodeLoc::read");
reader
.read_block_offset(self.block, self.byte_offset as u32)?
.read_le_args((self.num,))
.map_err(|err| match err {
binread::Error::Io(_) => io::Error::new(io::ErrorKind::Other, "Binread IO error"),
_ => io::Error::new(io::ErrorKind::InvalidData, "Inode data was invalid"),
})
//println!("InodeLoc::read done");
}
}

52
src/ext2/block_reader.rs Normal file
View File

@ -0,0 +1,52 @@
use std::vec::Vec;
use super::structs::Superblock;
use std::fs::File as StdFile;
use std::io::{self, Cursor, Read, Seek, SeekFrom};
#[derive(Debug)]
pub struct BlockReader {
pub disk: StdFile,
pub block_size: usize,
}
impl BlockReader {
pub fn new(disk: StdFile, superblock: &Superblock) -> Self {
Self {
disk,
block_size: 1024 << superblock.block_size_raw,
}
}
pub fn read_block(&self, block: u32) -> io::Result<Cursor<Vec<u8>>> {
//println!("Read block {}", block);
let mut disk = &self.disk;
let start = self.block_to_byte_offset(block);
let size = self.block_size;
let mut vec = vec![0; size];
disk.seek(SeekFrom::Start(start))?;
disk.read_exact(&mut vec)?;
Ok(Cursor::new(vec))
}
pub fn read_block_offset(&self, block: u32, offset: u32) -> io::Result<Cursor<Vec<u8>>> {
let block = self.read_block(block)?.into_inner();
let trunc_block = block[(offset as usize)..].to_vec();
Ok(Cursor::new(trunc_block))
}
pub fn read_blocks(&self, block: u32, count: usize) -> io::Result<Cursor<Vec<u8>>> {
let mut vec = vec![0; self.block_size * count];
for block_no in block..(block + count as u32) {
let data = self.read_block(block)?.into_inner();
vec[((block_no - block) as usize * self.block_size)
..(((block_no - block) as usize + 1) * self.block_size)]
.copy_from_slice(&data);
}
Ok(Cursor::new(vec))
}
fn block_to_byte_offset(&self, block: u32) -> u64 {
u64::from(block) * (self.block_size as u64)
}
}

170
src/ext2/dir.rs Normal file
View File

@ -0,0 +1,170 @@
use super::{
file::{File, FileReader},
metadata::{FileType, Metadata},
structs::{DirEntryDisk, Inode},
Ext2,
};
use std::string::String;
use std::{ffi::OsStr, io, os::mikros::ffi::OsStrExt};
use std::{io::BufReader, path::PathBuf};
/// Entries returned by the [`ReadDir`] iterator.
///
/// An instance of `DirEntry` represents an entry inside of a directory on the
/// filesystem. Each entry can be inspected via methods to learn about the full
/// path or possibly other metadata through per-platform extension traits.
#[allow(unused)]
#[allow(clippy::module_name_repetitions)]
pub struct DirEntry<'a> {
pub(super) inode: u32,
pub(super) name: String,
pub(super) metadata: Metadata,
pub(super) directory_path: PathBuf,
fs: &'a Ext2,
}
impl<'a> core::fmt::Debug for DirEntry<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DirEntry")
.field("inode", &self.inode)
.field("name", &self.name)
.field("metadata", &self.metadata)
.field("directory_path", &self.directory_path)
.finish()
}
}
#[allow(unused)]
impl<'a> DirEntry<'a> {
/// Returns the bare file name of this directory entry without any other
/// leading path component.
pub fn file_name(&self) -> String {
self.name.clone()
}
/// Returns the file type for the file that this entry points at.
///
/// This function will not traverse symlinks if this entry points at a
/// symlink.
pub fn file_type(&self) -> FileType {
self.metadata.file_type()
}
/// Returns the metadata for the file that this entry points at.
///
/// This function will not traverse symlinks if this entry points at a
/// symlink.
pub fn metadata(&self) -> Metadata {
self.metadata
}
/// Returns the full path to the file that this entry represents.
///
/// The full path is created by joining the original path to `read_dir`
/// with the filename of this entry.
///
/// # Panics
///
/// This function will panic if this entry came either directly from a `ReadDir` made with
/// `Ext2::read_dir_no_path`, or indirectly from such a `ReadDir` by use of the
/// `DirEntry::read_dir` function. This is an explicit decision, so such a panic indicates a
/// definite bug in your code.
pub fn path(&self) -> PathBuf {
self.directory_path.join(self.name.clone())
}
pub fn open(&self) -> io::Result<File> {
File::from_inode(Inode::read(self.fs, self.inode)?, self.fs)
}
pub fn read_dir(&self) -> io::Result<ReadDir<'a>> {
ReadDir::read_inode(self.fs, self.inode, self.path())
}
}
/// Iterator over the entries in a directory.
///
/// This iterator is returned from the [`super::Ext2::read_dir`] function and
/// will yield instances of <code>[`io::Result`]<[`DirEntry`]></code>. Through a [`DirEntry`]
/// information like the entry's path and possibly other metadata can be
/// learned.
///
/// The order in which this iterator returns entries is platform and filesystem
/// dependent.
///
/// # Errors
///
/// This [`io::Result`] will be an `Err` if there's some sort of intermittent
/// IO error during iteration.
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub struct ReadDir<'a> {
fs: &'a Ext2,
file: BufReader<FileReader<'a>>,
directory_path: PathBuf,
}
impl<'a> ReadDir<'a> {
pub fn read_inode(fs: &'a Ext2, inode: u32, path: PathBuf) -> io::Result<ReadDir<'a>> {
Ok(Self {
fs,
file: BufReader::with_capacity(
1024,
FileReader::new(File::from_inode_dir(Inode::read(fs, inode)?, fs)?, fs),
),
directory_path: path,
})
}
pub(super) fn get_entry(mut self, name: &OsStr) -> io::Result<DirEntry<'a>> {
self.find(|entry| {
if let Ok(entry) = entry {
entry.name.as_bytes() == name.as_bytes()
} else {
true
}
})
.unwrap_or_else(|| Err(io::Error::new(io::ErrorKind::NotFound, "File not found")))
}
fn next_impl(&mut self) -> io::Result<Option<DirEntry<'a>>> {
let entry: DirEntryDisk = match DirEntryDisk::read(&mut self.file) {
Ok(entry) => entry,
Err(error) => {
return if error.kind() == io::ErrorKind::UnexpectedEof {
Ok(None)
} else {
Err(error)
}
}
};
let name = String::from_utf8(entry.name.clone()).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"Non UTF-8 directory entry names are unspported",
)
})?;
if name == "." || name == ".." || entry.inode == 0 {
return self.next_impl();
}
let entry_inode = Inode::read(self.fs, entry.inode)?;
self.fs
.dentry_cache
.write()
.insert(self.directory_path.join(name.clone()), entry.clone());
Ok(Some(DirEntry {
inode: entry.inode,
name,
metadata: Metadata::from_inode(&entry_inode),
directory_path: self.directory_path.clone(),
fs: self.fs,
}))
}
}
impl<'a> Iterator for ReadDir<'a> {
type Item = io::Result<DirEntry<'a>>;
fn next(&mut self) -> Option<Self::Item> {
self.next_impl().transpose()
}
}

128
src/ext2/file.rs Normal file
View File

@ -0,0 +1,128 @@
use super::Ext2;
use super::{structs::Inode, Metadata};
use std::cmp::Ordering;
use std::io::{self, Read};
/// A reference to an open file on the filesystem.
///
/// An instance of a `File` can be read and/or written depending on what options
/// it was opened with. Files also implement [`Seek`] to alter the logical cursor
/// that the file contains internally.
///
/// Files are automatically closed when they go out of scope. Errors detected
/// on closing are ignored by the implementation of `Drop`.
#[derive(Debug)]
pub struct File {
inode: Inode,
block_size: usize,
}
impl File {
pub(super) fn from_inode(inode: Inode, fs: &Ext2) -> io::Result<Self> {
if !Metadata::from_inode(&inode).file_type().is_file() {
return Err(io::Error::new(io::ErrorKind::Other, "Not a file"));
}
Ok(Self {
block_size: fs.reader.block_size,
inode,
})
}
/// This version of `from_inode` skips checks that the provided inode is a directory, not a file. It
/// should, only be used by `ReadDir::read_inode` to read the raw directory data.
pub(super) fn from_inode_dir(inode: Inode, fs: &Ext2) -> io::Result<Self> {
if !Metadata::from_inode(&inode).file_type().is_dir() {
return Err(io::Error::new(io::ErrorKind::Other, "Not a directory"));
}
Ok(Self {
block_size: fs.reader.block_size,
inode,
})
}
/// Queries metadata about the underlying file.
#[allow(unused)]
pub fn metadata(&self) -> Metadata {
Metadata::from_inode(&self.inode)
}
pub fn read_at(&self, buf: &mut [u8], pos: u64, fs: &Ext2) -> io::Result<usize> {
//println!("File::read_at(File {{pos: {}, inode: {}, ...}}, &mut [?; {}], {})", self.pos, self.inode.number, buf.len(), pos);
let bytes_to_end = u64::from(self.inode.size_lower32).saturating_sub(pos);
if bytes_to_end == 0 {
//println!("File::read_at done, reporting 0 bytes read");
return Ok(0);
}
let start_block = (pos / self.block_size as u64) as u32;
let offset = (pos % self.block_size as u64) as usize;
let length = core::cmp::min(bytes_to_end, buf.len() as u64);
let _ = fs
.reader
.read_block_offset(
self.inode
.log_to_phys_block(&fs.reader, start_block)
.ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "EOF reached"))?,
offset as u32,
)?
.read(buf)?;
let mut remaining_bytes = length.saturating_sub((self.block_size - offset) as u64);
for block in (start_block + 1)..=u32::MAX {
if remaining_bytes == 0 {
break;
};
let _ =
fs.reader
.read_block(self.inode.log_to_phys_block(&fs.reader, block).ok_or_else(
|| io::Error::new(io::ErrorKind::UnexpectedEof, "EOF reached"),
)?)?
.read(&mut buf[((length - remaining_bytes) as usize)..])?;
remaining_bytes = remaining_bytes.saturating_sub(self.block_size as u64);
}
//println!("File::read_at done, reporting {} bytes read", length - remaining_bytes);
Ok((length - remaining_bytes) as usize)
}
}
#[derive(Debug)]
pub struct FileReader<'a> {
file: File,
fs: &'a Ext2,
pos: u64,
}
impl<'a> FileReader<'a> {
pub fn new(file: File, fs: &'a Ext2) -> Self {
Self { file, fs, pos: 0 }
}
}
impl io::Seek for FileReader<'_> {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
match pos {
io::SeekFrom::Start(x) => self.pos = x,
io::SeekFrom::End(x) => match x.cmp(&0) {
Ordering::Equal => self.pos = u64::from(self.file.inode.size_lower32),
Ordering::Greater => unimplemented!(),
Ordering::Less => {
self.pos = u64::from(self.file.inode.size_lower32) - x.unsigned_abs()
}
},
io::SeekFrom::Current(x) => match x.cmp(&0) {
Ordering::Equal => (),
Ordering::Greater => self.pos += x.unsigned_abs(),
Ordering::Less => self.pos -= x.unsigned_abs(),
},
};
Ok(self.pos)
}
}
impl io::Read for FileReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let res = self.file.read_at(buf, self.pos, &self.fs);
if let Ok(bytes_read) = res {
self.pos += bytes_read as u64;
}
res
}
}

253
src/ext2/metadata.rs Normal file
View File

@ -0,0 +1,253 @@
#![allow(unused)]
use super::structs::Inode;
#[derive(Copy, Clone, Debug)]
pub struct Permissions(u32);
impl Permissions {
/// Returns `true` if these permissions describe a readonly (unwritable) file.
pub fn readonly(self) -> bool {
// check if any class (owner, group, others) has write permission
self.0 & 0o222 == 0
}
/// Modifies the readonly flag for this set of permissions. If the
/// `readonly` argument is `true`, using the resulting `Permission` will
/// update file permissions to forbid writing. Conversely, if it's `false`,
/// using the resulting `Permission` will update file permissions to allow
/// writing.
///
/// This operation does **not** modify the filesystem.
pub fn set_readonly(mut self, readonly: bool) {
if readonly {
// remove write permission for all classes; equivalent to `chmod a-w <file>`
self.0 &= !0o222;
} else {
// add write permission for all classes; equivalent to `chmod a+w <file>`
self.0 |= 0o222;
}
}
/// Returns the underlying raw `st_mode` bits that contain the standard
/// Unix permissions for this file.
pub fn mode(self) -> u32 {
self.0
}
/// Sets the underlying raw bits for this set of permissions.
pub fn set_mode(mut self, mode: u32) {
self.0 = mode;
}
/// Creates a new instance of `Permissions` from the given set of Unix
/// permission bits.
pub fn from_mode(mode: u32) -> Self {
Self(mode)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum FileType {
Fifo,
CharDev,
Directory,
BlockDev,
File,
SymLink,
Socket,
}
impl FileType {
/// Tests whether this file type represents a directory. The
/// result is mutually exclusive to the results of
/// [`is_file`] and [`is_symlink`]; only zero or one of these
/// tests may pass.
pub fn is_dir(self) -> bool {
self == Self::Directory
}
/// Tests whether this file type represents a regular file.
/// The result is mutually exclusive to the results of
/// [`is_dir`] and [`is_symlink`]; only zero or one of these
/// tests may pass.
pub fn is_file(self) -> bool {
self == Self::File
}
/// Tests whether this file type represents a symbolic link.
/// The result is mutually exclusive to the results of
/// [`is_dir`] and [`is_file`]; only zero or one of these
/// tests may pass.
pub fn is_symlink(self) -> bool {
self == Self::SymLink
}
/// Returns `true` if this file type is a fifo.
pub fn is_fifo(self) -> bool {
self == Self::Fifo
}
/// Returns `true` if this file type is a block device.
pub fn is_block_device(self) -> bool {
self == Self::BlockDev
}
/// Returns `true` if this file type is a char device.
pub fn is_char_device(self) -> bool {
self == Self::CharDev
}
/// Returns `true` if this file type is a socket.
pub fn is_socket(self) -> bool {
self == Self::Socket
}
}
#[derive(Copy, Clone, Debug)]
pub struct Metadata {
accessed: u32,
created: u32,
file_type: FileType,
length: u64,
modified: u32,
permissions: Permissions,
ino: u64,
nlink: u64,
uid: u32,
gid: u32,
// blksize: u64,
blocks: u64,
}
impl Metadata {
pub(super) fn from_inode(inode: &Inode) -> Self {
let file_type = match (inode.type_perms & 0xF000) >> 12 {
1 => FileType::Fifo,
2 => FileType::CharDev,
4 => FileType::Directory,
6 => FileType::BlockDev,
8 => FileType::File,
0xA => FileType::SymLink,
0xC => FileType::Socket,
x => panic!("Invalid inode file type {}", x),
};
let permissions = Permissions(u32::from(inode.type_perms & 0xFFF));
Self {
accessed: inode.last_access_time,
created: inode.creation_time,
file_type,
length: u64::from(inode.size_lower32),
modified: inode.last_modification_time,
permissions,
ino: u64::from(inode.number),
nlink: u64::from(inode.hard_links_count),
uid: u32::from(inode.uid),
gid: u32::from(inode.gid),
// blksize: inode.reader.block_size as u64,
blocks: u64::from(inode.sectors_used),
}
}
/// Returns the last access time of this metadata.
///
/// The returned value corresponds to the `atime` field of `stat`
pub fn accessed(&self) -> u32 {
self.accessed
}
/// Returns the creation time listed in this metadata.
///
/// The returned value corresponds to the `btime` field of `statx` on
/// Linux kernel starting from to 4.11, and the `birthtime` field of `stat` on
/// other Unix platforms.
pub fn created(&self) -> u32 {
self.created
}
/// Returns the last modification time listed in this metadata.
///
/// The returned value corresponds to the `mtime` field of `stat`
pub fn modified(&self) -> u32 {
self.modified
}
/// Returns the file type for this metadata.
pub fn file_type(&self) -> FileType {
self.file_type
}
/// Returns `true` if this metadata is for a directory. The
/// result is mutually exclusive to the result of
/// [`Metadata::is_file`], and will be false for symlink metadata
/// obtained from [`symlink_metadata`].
pub fn is_dir(&self) -> bool {
self.file_type.is_dir()
}
/// Returns `true` if this metadata is for a regular file. The
/// result is mutually exclusive to the result of
/// [`Metadata::is_dir`], and will be false for symlink metadata
/// obtained from [`symlink_metadata`].
///
/// When the goal is simply to read from (or write to) the source, the most0.227s
/// reliable way to test the source can be read (or written to) is to open
/// it. Only using `is_file` can break workflows like `diff <( prog_a )` on
/// a Unix-like system for example. See [`File::open`] or
/// [`OpenOptions::open`] for more information.
pub fn is_file(&self) -> bool {
self.file_type.is_file()
}
/// Returns `true` if this metadata is for a symbolic link.
pub fn is_symlink(&self) -> bool {
self.file_type.is_symlink()
}
/// Returns the size of the file, in bytes, this metadata is for.
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> u64 {
self.length
}
/// Returns the permissions of the file this metadata is for.
pub fn permissions(&self) -> Permissions {
self.permissions
}
/// Returns the inode number.
pub fn ino(&self) -> u64 {
self.ino
}
/// Returns the rights applied to this file.
pub fn mode(&self) -> u32 {
self.permissions.0
}
/// Returns the number of hard links pointing to this file.
pub fn nlink(&self) -> u64 {
self.nlink
}
/// Returns the user ID of the owner of this file.
pub fn uid(&self) -> u32 {
self.uid
}
/// Returns the group ID of the owner of this file.
pub fn gid(&self) -> u32 {
self.gid
}
// /// Returns the block size for filesystem I/O.
// pub fn blksize(&self) -> u64 {
// self.blksize
// }
/// Returns the number of blocks allocated to the file, in 512-byte units.
///
/// Please note that this may be smaller than `st_size / 512` when the file has holes.
pub fn blocks(&self) -> u64 {
self.blocks
}
}

232
src/ext2/structs.rs Normal file
View File

@ -0,0 +1,232 @@
use super::{block_reader::BlockReader, Ext2};
use binread::prelude::*;
use itertools::Itertools;
use std::io::{self, Cursor, Read, Seek, SeekFrom};
use std::vec::Vec;
use std::fs::File as StdFile;
#[repr(u16)]
#[derive(Debug, BinRead)]
#[br(repr=u16)]
pub enum FSState {
Clean = 1,
HasErrors = 2,
}
#[repr(u16)]
#[derive(Debug, BinRead)]
#[br(repr=u16)]
pub enum ErrorHandlingMethod {
Ignore = 1,
RemountRO = 2,
Panic = 3,
}
#[derive(Debug, BinRead)]
#[allow(unused)]
pub struct Superblock {
pub total_inodes: u32,
pub total_blocks: u32,
pub superuser_blocks: u32,
pub unallocated_blocks: u32,
pub unallocated_inodes: u32,
pub superblock_block: u32,
pub block_size_raw: u32,
pub fragment_size_raw: u32,
pub block_group_block_count: u32,
pub block_group_fragment_count: u32,
pub block_group_inode_count: u32,
pub last_mount_time: u32,
pub last_written_time: u32,
pub times_mounted_since_last_check: u16,
pub max_mounts_before_check: u16,
pub ext2_sig: u16,
//pub fs_state: FSState,
//pub error_handling_method: ErrorHandlingMethod,
fs_state: u16,
error_handling_method: u16,
pub version_minor: u16,
pub last_check_time: u32,
pub max_interval_between_check: u32,
pub creation_os_id: u32,
pub version_major: u32,
pub reserved_uid: u16,
pub reserved_gid: u16,
}
impl Superblock {
pub fn read_from_disk(disk: &mut StdFile) -> io::Result<Self> {
disk.seek(SeekFrom::Start(1024))?;
let mut bytes = vec![0; 1024];
disk.read_exact(&mut bytes)?;
let sblock = Cursor::new(bytes).read_le().unwrap();
//dbg!(&sblock);
Ok(sblock)
}
}
#[derive(Debug, Clone, BinRead)]
#[allow(unused)]
pub struct BlockGroupDescriptor {
pub block_usage_block: u32,
pub inode_usage_block: u32,
pub inode_table_start: u32,
pub unallocated_blocks: u16,
pub unallocated_inodes: u16,
pub num_directories: u16,
pub reserved: [u8; 14],
}
#[derive(Debug, Clone)]
pub struct DirEntryDisk {
pub inode: u32,
pub entry_size: u16,
pub name_len: u8,
pub high_len_or_type: u8,
pub name: Vec<u8>,
}
impl DirEntryDisk {
pub fn read(reader: &mut (impl Read + Seek)) -> io::Result<Self> {
let mut inode = [0u8; 4];
reader.read_exact(&mut inode)?;
let inode = u32::from_le_bytes(inode);
let mut entry_size = [0u8; 2];
reader.read_exact(&mut entry_size)?;
let entry_size = u16::from_le_bytes(entry_size);
let mut name_len = [0u8; 1];
reader.read_exact(&mut name_len)?;
let name_len = name_len[0];
let mut high_len_or_type = [0u8; 1];
reader.read_exact(&mut high_len_or_type)?;
let high_len_or_type = high_len_or_type[0];
let mut name = vec![0; name_len as usize];
reader.read_exact(&mut name)?;
reader.seek_relative(i64::from(entry_size - u16::from(name_len) - 8))?;
//dbg!(inode, entry_size, &name);
Ok(Self {
inode,
entry_size,
name_len,
high_len_or_type,
name,
})
}
}
#[derive(Debug, Clone, BinRead)]
#[br(import(number: u32))]
#[allow(unused)]
pub struct Inode {
pub type_perms: u16,
pub uid: u16,
pub size_lower32: u32,
pub last_access_time: u32,
pub creation_time: u32,
pub last_modification_time: u32,
pub deletion_time: u32,
pub gid: u16,
pub hard_links_count: u16,
pub sectors_used: u32,
pub flags: u32,
pub os_specific_1: u32,
pub direct_block_pointers: [u32; 12],
pub singly_indirect_block_pointer: u32,
pub doubly_indirect_block_pointer: u32,
pub triply_indirect_block_pointer: u32,
pub generation_number: u32,
pub file_acl: u32,
pub file_size_upper32_dir_acl: u32,
pub fragment_block_address: u32,
pub os_specific_2: [u8; 12],
#[br(calc = number)]
pub number: u32,
}
impl Inode {
pub(super) fn read(fs: &Ext2, inode_num: u32) -> io::Result<Self> {
if let Some(ino) = fs.inode_cache.read().get(&inode_num) {
//println!("Inode {}: In cache", inode_num);
return Ok(ino.clone());
}
//println!("Inode {}: Not in cache", inode_num);
let ino = fs.descriptor_table.inode_loc(inode_num).read(&fs.reader)?;
fs.inode_cache.write().insert(inode_num, ino.clone());
Ok(ino)
}
pub fn pointer_block_blocks(&self, block: u32, reader: &BlockReader) -> io::Result<Vec<u32>> {
Ok(reader
.read_block(block)?
.into_inner()
.iter()
.copied()
.tuples::<(_, _, _, _)>()
.map(|raw| u32::from_le_bytes([raw.0, raw.1, raw.2, raw.3]))
.collect_vec())
}
pub fn log_to_phys_block(&self, reader: &BlockReader, block: u32) -> Option<u32> {
let num_sing_indir = reader.block_size / 4;
let num_doub_indir = num_sing_indir * num_sing_indir;
let num_trip_indir = num_sing_indir * num_doub_indir;
if block < 12 {
if self.direct_block_pointers[block as usize] == 0 {
None
} else {
Some(self.direct_block_pointers[block as usize])
}
} else if block < (12 + num_sing_indir) as u32 {
let blocks = self
.pointer_block_blocks(self.singly_indirect_block_pointer, reader)
.ok()?;
let block = blocks[(block - 12) as usize];
if block == 0 {
None
} else {
Some(block)
}
} else if block < (12 + num_sing_indir + num_doub_indir) as u32 {
let base_block = 12 - num_sing_indir;
let doub_indir_offset = (block as usize - base_block) / num_sing_indir;
let sing_indir_offset = (block as usize - base_block) % num_sing_indir;
let sing_indir_blocks = self
.pointer_block_blocks(self.doubly_indirect_block_pointer, reader)
.ok()?;
let blocks = self
.pointer_block_blocks(sing_indir_blocks[doub_indir_offset], reader)
.ok()?;
let block = blocks[sing_indir_offset];
if block == 0 {
None
} else {
Some(block)
}
} else if block < (12 + num_sing_indir + num_doub_indir + num_trip_indir) as u32 {
let base_block = 12 - num_sing_indir;
let trip_indir_offset = (block as usize - base_block) / num_doub_indir;
let doub_indir_offset =
((block as usize - base_block) % num_doub_indir) / num_sing_indir;
let sing_indir_offset =
((block as usize - base_block) % num_doub_indir) % num_sing_indir;
let doub_indir_blocks = self
.pointer_block_blocks(self.triply_indirect_block_pointer, reader)
.ok()?;
let sing_indir_blocks = self
.pointer_block_blocks(doub_indir_blocks[trip_indir_offset], reader)
.ok()?;
let blocks = self
.pointer_block_blocks(sing_indir_blocks[doub_indir_offset], reader)
.ok()?;
let block = blocks[sing_indir_offset];
if block == 0 {
None
} else {
Some(block)
}
} else {
None
}
}
}

91
src/main.rs Normal file
View File

@ -0,0 +1,91 @@
mod ext2;
use std::{
borrow::Cow,
fs::File,
os::mikros::{ipc, syscalls},
sync::Arc,
};
use ext2::Ext2;
use parking_lot::RwLock;
#[derive(Clone)]
struct Serv {
mounts: Arc<RwLock<Vec<Ext2>>>,
files: Arc<RwLock<Vec<(ext2::File, usize)>>>,
}
impl fs_rpc::Server for Serv {
fn mount(&self, dev: &std::path::Path) -> Result<u64, ()> {
let disk = File::open(dev).map_err(|_| ())?;
let fs = Ext2::new(disk).map_err(|_| ())?;
self.mounts.write().push(fs);
let res = Ok((self.mounts.read().len() - 1) as u64);
res
}
fn open(&self, path: &std::path::Path, mount_id: u64) -> Result<(Option<u64>, u64), ()> {
let mounts = self.mounts.read();
let mount = &mounts[mount_id as usize];
let file = mount.open(path).map_err(|_| ())?;
self.files.write().push((file, mount_id as usize));
let res = Ok((None, (self.files.read().len() - 1) as u64));
res
}
}
impl file_rpc::Server for Serv {
fn read(&self, fd: u64, pos: u64, len: usize) -> Result<Cow<'_, [u8]>, ()> {
let files = self.files.read();
let (file, mount_id) = &files[fd as usize];
let mounts = self.mounts.read();
let mount = &mounts[*mount_id];
let mut buf = vec![0; len];
let read_len = file.read_at(&mut buf, pos, mount).map_err(|_| ())?;
buf.truncate(read_len);
Ok(buf.into())
}
fn write(&self, _fd: u64, _pos: u64, _data: &[u8]) -> Result<(), ()> {
Err(())
}
fn close(&self, _fd: u64) {}
fn size(&self, fd: u64) -> Option<u64> {
let files = self.files.read();
let (file, _mount_id) = &files[fd as usize];
Some(file.metadata().len())
}
}
fn main() {
let serv = Serv {
mounts: Arc::new(RwLock::new(Vec::new())),
files: Arc::new(RwLock::new(Vec::new())),
};
fs_rpc::register_server(Box::new(serv.clone()));
file_rpc::register_server(Box::new(serv));
let vfs_pid;
loop {
if let Some(pid) = syscalls::try_get_registered(0) {
vfs_pid = pid;
break;
}
}
vfs_rpc::Client::new(vfs_pid).register_fs("ext2").unwrap();
let syslog_pid;
loop {
if let Some(pid) = syscalls::try_get_registered(2) {
syslog_pid = pid;
break;
}
}
syslog_rpc::Client::new(syslog_pid)
.send_text_binary_message("ext2".to_string(), "Ext2 FS initialized".to_string(), 0, [])
.unwrap();
loop {
ipc::process_messages()
}
}