Change io crate & add a small shell

This commit is contained in:
2026-03-25 20:45:11 +01:00
parent f966a1239e
commit ae0593c972
98 changed files with 11102 additions and 810 deletions

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
io = { path = "../io" }
io = { package = "no-std-io", path = "../io" }
bitflags = "2"
[features]

View File

@@ -1,5 +1,6 @@
use crate::error::Error;
use io::{Read, ReadLeExt};
use io::Read;
use crate::io_ext::ReadLeExt;
#[derive(Debug, Clone, Copy)]
pub struct Fat32BootSector {
@@ -94,7 +95,7 @@ impl Fat32BootSector {
}
impl Fat32BootSector {
pub fn deserialize<T: Read>(disk: &mut T) -> Result<Self, Error<T::Error>> {
pub fn deserialize<T: Read>(disk: &mut T) -> Result<Self, io::Error> {
let mut jump_boot = [0u8; _];
disk.read_exact(&mut jump_boot)?;
let mut oem_name = [0u8; _];

View File

@@ -1,36 +1,32 @@
use crate::{
Fat32FileSystem, ReadSeek, ReadWriteSeek,
entry::{DirEntry, DirectoryIterator},
error::Error,
file::{File, RawFile},
path::Path,
};
use io::{self, IoBase, Read, Seek, Write};
use io::{self, Read, Seek, Write};
pub struct Dir<'a, T> {
raw: RawFile<'a, T>,
fs: &'a Fat32FileSystem<T>,
}
impl<'a, T: IoBase> IoBase for Dir<'a, T> {
type Error = Error<T::Error>;
}
impl<'a, T: ReadSeek> Seek for Dir<'a, T> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
self.raw.seek(pos)
}
}
impl<'a, T: ReadSeek> Read for Dir<'a, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.raw.read(buf)
}
}
impl<'a, T: ReadWriteSeek> Write for Dir<'a, T> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.raw.write(buf)
}
fn flush(&mut self) -> Result<(), Self::Error> {
fn flush(&mut self) -> io::Result<()> {
self.raw.flush()
}
}
@@ -44,7 +40,7 @@ impl<'a, T> Dir<'a, T> {
}
}
impl<'a, T: ReadSeek> Dir<'a, T> {
pub fn open_entry<P: AsRef<Path>>(&self, path: P) -> Result<DirEntry<'a, T>, Error<T::Error>> {
pub fn open_entry<P: AsRef<Path>>(&self, path: P) -> Result<DirEntry<'a, T>, io::Error> {
if path.as_ref().is_absolute() {
return self.fs.open_entry(path);
}
@@ -56,16 +52,16 @@ impl<'a, T: ReadSeek> Dir<'a, T> {
if f.is_dir() {
return f.to_dir().open_entry(entry_name);
} else {
return Err(Error::NotFound);
return Err(io::Error::from(io::ErrorKind::NotFound));
}
} else {
return Ok(f);
}
}
}
Err(Error::NotFound)
Err(io::Error::from(io::ErrorKind::NotFound))
}
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<File<'a, T>, Error<T::Error>> {
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<File<'a, T>, io::Error> {
if path.as_ref().is_absolute() {
return self.fs.open_file(path);
}
@@ -74,19 +70,19 @@ impl<'a, T: ReadSeek> Dir<'a, T> {
match dirname {
Some(name) => {
if !entry.is_dir() {
return Err(Error::NotFound);
return Err(io::Error::from(io::ErrorKind::NotFound));
}
entry.to_dir().open_file(name)
}
None => {
if !entry.is_file() {
return Err(Error::NotFound);
return Err(io::Error::from(io::ErrorKind::NotFound));
}
Ok(entry.to_file())
}
}
}
pub fn open_dir<P: AsRef<Path>>(&self, path: P) -> Result<Self, Error<T::Error>> {
pub fn open_dir<P: AsRef<Path>>(&self, path: P) -> Result<Self, io::Error> {
let path = path.as_ref();
if path.is_absolute() {
return self.fs.open_dir(path);
@@ -94,7 +90,7 @@ impl<'a, T: ReadSeek> Dir<'a, T> {
let (start, dirname) = path.split_path();
let entry = self.open_entry(start)?;
if !entry.is_dir() {
return Err(Error::NotFound);
return Err(io::Error::from(io::ErrorKind::NotFound));
}
match dirname {
Some(name) => entry.to_dir().open_dir(name),

View File

@@ -1,15 +1,15 @@
use core::{array::IntoIter, iter::Copied, str::Utf8Error};
use core::{char::DecodeUtf16, marker::PhantomData, slice::Iter};
use crate::io_ext::ReadLeExt;
use crate::{
Fat32FileSystem, ReadSeek,
consts::FATAttr,
dir::Dir,
error::Error,
file::{File, RawFile},
};
use io::{IoBase, Read, ReadLeExt};
use io::Read;
#[cfg(feature = "alloc")]
use alloc::borrow::ToOwned;
@@ -33,7 +33,7 @@ pub struct FatDirEntry {
}
impl FatDirEntry {
pub fn deserialize<T: Read>(reader: &mut T) -> Result<Self, T::Error> {
pub fn deserialize<T: Read>(reader: &mut T) -> Result<Self, io::Error> {
let mut name = [0u8; _];
reader.read_exact(&mut name)?;
let attr = reader.read_u8()?;
@@ -131,7 +131,7 @@ pub struct LongFileNameBuilder<T> {
_phantom: PhantomData<T>,
}
impl<T: IoBase> LongFileNameBuilder<T> {
impl<T> LongFileNameBuilder<T> {
pub fn new() -> Self {
Self {
inner: [0; _],
@@ -160,12 +160,12 @@ impl<T: IoBase> LongFileNameBuilder<T> {
}
}
#[cfg(feature = "alloc")]
pub fn to_string(&self) -> Result<String, Error<T::Error>> {
pub fn to_string(&self) -> Result<String, io::Error> {
self.iter().try_collect::<String>()
}
}
impl<T: IoBase> IntoIterator for LongFileNameBuilder<T> {
impl<T> IntoIterator for LongFileNameBuilder<T> {
type Item = <LongFileNameIterator<T, IntoIter<u16, 256>> as Iterator>::Item;
type IntoIter = LongFileNameIterator<T, IntoIter<u16, 256>>;
@@ -178,7 +178,7 @@ impl<T: IoBase> IntoIterator for LongFileNameBuilder<T> {
}
}
impl<T: IoBase> LongFileNameBuilder<T> {
impl<T> LongFileNameBuilder<T> {
pub fn iter(&self) -> LongFileNameIterator<T, Copied<Iter<'_, u16>>> {
LongFileNameIterator {
iterator: char::decode_utf16(self.inner.iter().copied()),
@@ -192,8 +192,8 @@ pub struct LongFileNameIterator<T, I: Iterator<Item = u16>> {
_phantom: PhantomData<T>,
}
impl<T: IoBase, I: Iterator<Item = u16>> Iterator for LongFileNameIterator<T, I> {
type Item = Result<char, Error<T::Error>>;
impl<T, I: Iterator<Item = u16>> Iterator for LongFileNameIterator<T, I> {
type Item = Result<char, io::Error>;
fn next(&mut self) -> Option<Self::Item> {
match self.iterator.next()? {
@@ -204,12 +204,15 @@ impl<T: IoBase, I: Iterator<Item = u16>> Iterator for LongFileNameIterator<T, I>
Some(Ok(value))
}
}
Err(_) => Some(Err(Error::UnsupportedFileNameCharacter)),
Err(_) => Some(Err(io::Error::new(
io::ErrorKind::Unsupported,
"Unsupported file name character",
))),
}
}
}
impl<T: IoBase> Default for LongFileNameBuilder<T> {
impl<T> Default for LongFileNameBuilder<T> {
fn default() -> Self {
Self::new()
}
@@ -226,7 +229,7 @@ impl<'a, T> DirectoryIterator<'a, T> {
}
impl<'a, T: ReadSeek + 'a> Iterator for DirectoryIterator<'a, T> {
type Item = Result<DirEntry<'a, T>, Error<T::Error>>;
type Item = Result<DirEntry<'a, T>, io::Error>;
fn next(&mut self) -> Option<Self::Item> {
let mut lfn_builder = LongFileNameBuilder::new();
@@ -283,7 +286,7 @@ pub struct DirEntry<'a, T> {
fs: &'a Fat32FileSystem<T>,
}
impl<'a, T: IoBase> DirEntry<'a, T> {
impl<'a, T> DirEntry<'a, T> {
fn new(
entry: FatDirEntry,
short_name: [u8; 11],
@@ -298,13 +301,16 @@ impl<'a, T: IoBase> DirEntry<'a, T> {
}
}
#[cfg(feature = "alloc")]
pub fn name(&self) -> Result<String, Error<T::Error>> {
pub fn name(&self) -> Result<String, io::Error> {
if let Some(long_name) = self.long_name() {
long_name.to_string()
} else {
self.short_name()
.map(|n| n.to_owned())
.map_err(|_| Error::UnsupportedFileNameCharacter)
self.short_name().map(|n| n.to_owned()).map_err(|_| {
io::Error::new(
io::ErrorKind::Unsupported,
"Unsupported file name character",
)
})
}
}
pub fn long_name(&self) -> Option<&LongFileNameBuilder<T>> {

View File

@@ -1,79 +1 @@
use io::error::IoError;
/// Error enum with all errors that can be returned by functions from this crate
///
/// Generic parameter `T` is a type of external error returned by the user provided storage
#[derive(Debug)]
#[non_exhaustive]
pub enum Error<T> {
/// A user provided storage instance returned an error during an input/output operation.
Io(T),
/// A read operation cannot be completed because an end of a file has been reached prematurely.
UnexpectedEof,
/// A write operation cannot be completed because `Write::write` returned 0.
WriteZero,
/// A parameter was incorrect.
InvalidInput,
/// A requested file or directory has not been found.
NotFound,
/// A file or a directory with the same name already exists.
AlreadyExists,
/// An operation cannot be finished because a directory is not empty.
DirectoryIsNotEmpty,
/// File system internal structures are corrupted/invalid.
CorruptedFileSystem,
/// There is not enough free space on the storage to finish the requested operation.
NotEnoughSpace,
/// The provided file name is either too long or empty.
InvalidFileNameLength,
/// The provided file name contains an invalid character.
UnsupportedFileNameCharacter,
/// The file content contains invalid UTF-8 characters.
InvalidUTF8,
}
impl<T: IoError> From<T> for Error<T> {
fn from(error: T) -> Self {
Error::Io(error)
}
}
impl<T: core::fmt::Display> core::fmt::Display for Error<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::Io(io_error) => write!(f, "IO error: {}", io_error),
Error::UnexpectedEof => write!(f, "Unexpected end of file"),
Error::NotEnoughSpace => write!(f, "Not enough space"),
Error::WriteZero => write!(f, "Write zero"),
Error::InvalidInput => write!(f, "Invalid input"),
Error::InvalidFileNameLength => write!(f, "Invalid file name length"),
Error::UnsupportedFileNameCharacter => write!(f, "Unsupported file name character"),
Error::DirectoryIsNotEmpty => write!(f, "Directory is not empty"),
Error::NotFound => write!(f, "No such file or directory"),
Error::AlreadyExists => write!(f, "File or directory already exists"),
Error::CorruptedFileSystem => write!(f, "Corrupted file system"),
Error::InvalidUTF8 => write!(f, "File contains invalid UTF-8 characters"),
}
}
}
impl<T: core::fmt::Debug + IoError> IoError for Error<T> {
fn is_interrupted(&self) -> bool {
match self {
Error::<T>::Io(io_error) => io_error.is_interrupted(),
_ => false,
}
}
fn new_unexpected_eof_error() -> Self {
Error::<T>::UnexpectedEof
}
fn new_write_zero_error() -> Self {
Error::<T>::WriteZero
}
fn new_invalid_utf8_error() -> Self {
Error::<T>::InvalidUTF8
}
}

View File

@@ -1,10 +1,9 @@
use crate::{
Fat32FileSystem, ReadSeek, ReadWriteSeek,
consts::{FAT32_BAD_CLUSTER, FAT32_END_OF_CHAIN},
error::Error,
};
use io::{self, IoBase, Read, Seek, Write};
use io::{self, Read, Seek, Write};
#[derive(Debug, Clone)]
pub struct RawFile<'a, T> {
@@ -30,12 +29,8 @@ impl<'a, T> RawFile<'a, T> {
}
}
impl<'a, T: IoBase> IoBase for RawFile<'a, T> {
type Error = Error<T::Error>;
}
impl<'a, T: ReadSeek> Seek for RawFile<'a, T> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, io::Error> {
let new_pos = match pos {
io::SeekFrom::Start(s) => s,
io::SeekFrom::Current(c) => (self.pos as i64 + c) as u64,
@@ -67,7 +62,7 @@ impl<'a, T: ReadSeek> Seek for RawFile<'a, T> {
}
impl<'a, T: ReadSeek> Read for RawFile<'a, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
let max_len = self
.size
.map(|size| core::cmp::min(buf.len(), size as usize - self.pos as usize))
@@ -116,11 +111,11 @@ impl<'a, T: ReadSeek> Read for RawFile<'a, T> {
}
}
impl<'a, T: ReadWriteSeek> Write for RawFile<'a, T> {
fn write(&mut self, _buf: &[u8]) -> Result<usize, Self::Error> {
fn write(&mut self, _buf: &[u8]) -> Result<usize, io::Error> {
todo!()
}
fn flush(&mut self) -> Result<(), Self::Error> {
fn flush(&mut self) -> Result<(), io::Error> {
todo!()
}
}
@@ -132,25 +127,22 @@ pub struct File<'a, T> {
fs: &'a Fat32FileSystem<T>,
}
impl<'a, T: IoBase> IoBase for File<'a, T> {
type Error = Error<T::Error>;
}
impl<'a, T: ReadSeek> Seek for File<'a, T> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, io::Error> {
self.raw.seek(pos)
}
}
impl<'a, T: ReadSeek> Read for File<'a, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
self.raw.read(buf)
}
}
impl<'a, T: ReadWriteSeek> Write for File<'a, T> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
self.raw.write(buf)
}
fn flush(&mut self) -> Result<(), Self::Error> {
fn flush(&mut self) -> Result<(), io::Error> {
self.raw.flush()
}
}

27
crates/bffs/src/io_ext.rs Normal file
View File

@@ -0,0 +1,27 @@
use io::{Read, Result};
pub trait ReadLeExt {
fn read_u8(&mut self) -> Result<u8>;
fn read_u16_le(&mut self) -> Result<u16>;
fn read_u32_le(&mut self) -> Result<u32>;
}
impl<T: Read> ReadLeExt for T {
fn read_u8(&mut self) -> Result<u8> {
let mut buf = [0_u8; 1];
self.read_exact(&mut buf)?;
Ok(buf[0])
}
fn read_u16_le(&mut self) -> Result<u16> {
let mut buf = [0_u8; 2];
self.read_exact(&mut buf)?;
Ok(u16::from_le_bytes(buf))
}
fn read_u32_le(&mut self) -> Result<u32> {
let mut buf = [0_u8; 4];
self.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
}

View File

@@ -6,16 +6,10 @@ use core::cell::RefCell;
use core::fmt::Display;
use crate::{
boot_sector::Fat32BootSector,
consts::FAT32_CLUSTER_MASK,
dir::Dir,
entry::{DirEntry, FatEntry},
error::Error,
file::{File, RawFile},
path::Path,
boot_sector::Fat32BootSector, consts::FAT32_CLUSTER_MASK, dir::Dir, entry::{DirEntry, FatEntry}, file::{File, RawFile}, io_ext::ReadLeExt, path::Path
};
use io::{Read, ReadLeExt, Seek, Write};
use io::{Read, Seek, Write};
#[cfg(feature = "alloc")]
extern crate alloc;
@@ -24,8 +18,8 @@ pub mod boot_sector;
pub mod consts;
pub mod dir;
pub mod entry;
pub mod error;
pub mod file;
pub mod io_ext;
pub mod path;
pub trait ReadSeek: Read + Seek {}
@@ -47,7 +41,7 @@ pub struct Fat32FsInfo {
}
impl Fat32FsInfo {
pub fn deserialize<T: Read>(disk: &mut T) -> Result<Self, Error<T::Error>> {
pub fn deserialize<T: Read>(disk: &mut T) -> Result<Self, io::Error> {
let lead_signature = disk.read_u32_le()?;
let mut reserved1 = [0u8; _];
disk.read_exact(&mut reserved1)?;
@@ -82,7 +76,7 @@ impl<T> Display for Fat32FileSystem<T> {
}
impl<T: ReadSeek> Fat32FileSystem<T> {
pub fn new(mut device: T) -> Result<Self, Error<T::Error>> {
pub fn new(mut device: T) -> Result<Self, io::Error> {
device.seek(io::SeekFrom::Start(0))?;
let boot_sector = Fat32BootSector::deserialize(&mut device)?;
@@ -92,7 +86,7 @@ impl<T: ReadSeek> Fat32FileSystem<T> {
})
}
/// Get the next cluster from the current one
fn get_next_cluster(&self, current_cluster: u32) -> Result<u32, Error<T::Error>> {
fn get_next_cluster(&self, current_cluster: u32) -> Result<u32, io::Error> {
let fat_offset =
self.fat_start_offset() + (current_cluster as u64 * size_of::<FatEntry>() as u64);
self.device
@@ -137,15 +131,15 @@ impl<T> Fat32FileSystem<T> {
}
}
impl<T: ReadSeek> Fat32FileSystem<T> {
pub fn open_entry<P: AsRef<Path>>(&self, path: P) -> Result<DirEntry<'_, T>, Error<T::Error>> {
pub fn open_entry<P: AsRef<Path>>(&self, path: P) -> Result<DirEntry<'_, T>, io::Error> {
let path = path.as_ref().as_str().trim_start_matches("/");
self.root_directory().open_entry(path)
}
pub fn open_dir<P: AsRef<Path>>(&self, path: P) -> Result<Dir<'_, T>, Error<T::Error>> {
pub fn open_dir<P: AsRef<Path>>(&self, path: P) -> Result<Dir<'_, T>, io::Error> {
let path = path.as_ref().as_str().trim_start_matches("/");
self.root_directory().open_dir(path)
}
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<File<'_, T>, Error<T::Error>> {
pub fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<File<'_, T>, io::Error> {
let path = path.as_ref().as_str().trim_start_matches("/");
self.root_directory().open_file(path)
}

View File

@@ -9,7 +9,7 @@ pub fn main() {
// walk(fs.root_directory());
let mut f = fs.open_file("/usr/bin/test_pic").unwrap();
let mut f = fs.open_file("/usr/bin/agetty").unwrap();
let mut content = Vec::new();
f.read_to_end(&mut content).unwrap();
println!("file content len: {}", content.len());

View File

@@ -1,3 +1,4 @@
use core::fmt::Display;
#[cfg(feature = "alloc")]
use core::{borrow::Borrow, ops::Deref};
@@ -12,6 +13,12 @@ pub struct Path {
inner: str,
}
impl Display for Path {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", &self.inner)
}
}
impl From<&str> for &Path {
fn from(value: &str) -> Self {
unsafe { &*(value as *const str as *const Path) }