Better virtual file system, keyboard through MMIO&VirtIO

This commit is contained in:
2026-03-05 14:41:28 +01:00
parent 041e544330
commit 9b6aec28f5
37 changed files with 1191 additions and 355 deletions

View File

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

View File

@@ -1,4 +1,5 @@
use crate::{error::Error, io::{Read, ReadLeExt}};
use crate::error::Error;
use io::{Read, ReadLeExt};
#[derive(Debug, Clone, Copy)]
pub struct Fat32BootSector {

View File

@@ -1,11 +1,11 @@
use crate::{
Fat32FileSystem, ReadSeek, ReadWriteSeek,
entry::{DirEntry, DirectoryIterator},
error::Error,
file::{File, RawFile},
io::{self, IoBase, Read, Seek, Write},
path::Path,
Fat32FileSystem, ReadSeek, ReadWriteSeek,
};
use io::{self, IoBase, Read, Seek, Write};
pub struct Dir<'a, T> {
raw: RawFile<'a, T>,
@@ -13,24 +13,24 @@ pub struct Dir<'a, T> {
}
impl<'a, T: IoBase> IoBase for Dir<'a, T> {
type Error = T::Error;
type Error = Error<T::Error>;
}
impl<'a, T: ReadSeek> Seek for Dir<'a, T> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Error<Self::Error>> {
fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, Self::Error> {
self.raw.seek(pos)
}
}
impl<'a, T: ReadSeek> Read for Dir<'a, T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.raw.read(buf)
}
}
impl<'a, T: ReadWriteSeek> Write for Dir<'a, T> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error<Self::Error>> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.raw.write(buf)
}
fn flush(&mut self) -> Result<(), Error<Self::Error>> {
fn flush(&mut self) -> Result<(), Self::Error> {
self.raw.flush()
}
}
@@ -44,25 +44,28 @@ 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>, Error<T::Error>> {
if path.as_ref().is_absolute() {
return self.fs.open_entry(path);
}
let (start, entryname) = path.as_ref().split_path();
for file in self.iter() {
let f = file?;
if f.name_is(path.as_ref().as_str()) {
return Ok(f);
if f.name_is(start) {
if let Some(entry_name) = entryname {
if f.is_dir() {
return f.to_dir().open_entry(entry_name);
} else {
return Err(Error::NotFound);
}
} else {
return Ok(f);
}
}
}
Err(Error::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>, Error<T::Error>> {
if path.as_ref().is_absolute() {
return self.fs.open_file(path);
}

View File

@@ -2,14 +2,15 @@ use core::{array::IntoIter, iter::Copied, str::Utf8Error};
use core::{char::DecodeUtf16, marker::PhantomData, slice::Iter};
use crate::{
Fat32FileSystem, ReadSeek,
consts::FATAttr,
dir::Dir,
error::Error,
file::{File, RawFile},
io::{IoBase, Read, ReadLeExt},
Fat32FileSystem, ReadSeek,
};
use io::{IoBase, Read, ReadLeExt};
#[cfg(feature = "alloc")]
use alloc::borrow::ToOwned;
#[cfg(feature = "alloc")]
@@ -32,7 +33,7 @@ pub struct FatDirEntry {
}
impl FatDirEntry {
pub fn deserialize<T: Read>(reader: &mut T) -> Result<Self, Error<T::Error>> {
pub fn deserialize<T: Read>(reader: &mut T) -> Result<Self, T::Error> {
let mut name = [0u8; _];
reader.read_exact(&mut name)?;
let attr = reader.read_u8()?;
@@ -224,7 +225,7 @@ impl<'a, T> DirectoryIterator<'a, T> {
}
}
impl<'a, T: ReadSeek> Iterator for DirectoryIterator<'a, T> {
impl<'a, T: ReadSeek + 'a> Iterator for DirectoryIterator<'a, T> {
type Item = Result<DirEntry<'a, T>, Error<T::Error>>;
fn next(&mut self) -> Option<Self::Item> {

View File

@@ -1,3 +1,5 @@
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
@@ -55,15 +57,6 @@ impl<T: core::fmt::Display> core::fmt::Display for Error<T> {
}
}
/// Trait that should be implemented by errors returned from the user supplied storage.
///
/// Implementations for `std::io::Error` and `()` are provided by this crate.
pub trait IoError: core::fmt::Debug {
fn is_interrupted(&self) -> bool;
fn new_unexpected_eof_error() -> Self;
fn new_write_zero_error() -> Self;
}
impl<T: core::fmt::Debug + IoError> IoError for Error<T> {
fn is_interrupted(&self) -> bool {
match self {
@@ -79,39 +72,8 @@ impl<T: core::fmt::Debug + IoError> IoError for Error<T> {
fn new_write_zero_error() -> Self {
Error::<T>::WriteZero
}
}
impl IoError for () {
fn is_interrupted(&self) -> bool {
false
}
fn new_unexpected_eof_error() -> Self {
// empty
}
fn new_write_zero_error() -> Self {
// empty
}
}
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
impl IoError for std::io::Error {
fn is_interrupted(&self) -> bool {
self.kind() == std::io::ErrorKind::Interrupted
}
fn new_unexpected_eof_error() -> Self {
Self::new(
std::io::ErrorKind::UnexpectedEof,
"failed to fill whole buffer",
)
}
fn new_write_zero_error() -> Self {
Self::new(
std::io::ErrorKind::WriteZero,
"failed to write whole buffer",
)
fn new_invalid_utf8_error() -> Self {
Error::<T>::InvalidUTF8
}
}

View File

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

View File

@@ -11,10 +11,11 @@ use crate::{
entry::{DirEntry, FatEntry},
error::Error,
file::{File, RawFile},
io::{Read, ReadLeExt, Seek, Write},
path::Path,
};
use io::{Read, ReadLeExt, Seek, Write};
#[cfg(feature = "alloc")]
extern crate alloc;
@@ -24,7 +25,6 @@ pub mod dir;
pub mod entry;
pub mod error;
pub mod file;
pub mod io;
pub mod path;
pub trait ReadSeek: Read + Seek {}
@@ -136,10 +136,7 @@ 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>, Error<T::Error>> {
let path = path.as_ref().as_str().trim_start_matches("/");
self.root_directory().open_entry(path)
}
@@ -147,10 +144,7 @@ impl<T: ReadSeek> Fat32FileSystem<T> {
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>, Error<T::Error>> {
let path = path.as_ref().as_str().trim_start_matches("/");
self.root_directory().open_file(path)
}

View File

@@ -1,9 +1,13 @@
use core::ops::Deref;
#[cfg(feature = "alloc")]
use core::{borrow::Borrow, ops::Deref};
#[cfg(feature = "alloc")]
use alloc::borrow::ToOwned;
#[cfg(feature = "alloc")]
use alloc::string::String;
#[repr(transparent)]
#[derive(Debug)]
pub struct Path {
inner: str,
}
@@ -19,6 +23,13 @@ impl AsRef<Path> for str {
}
}
#[cfg(feature = "alloc")]
impl AsRef<Path> for String {
fn as_ref(&self) -> &Path {
self.as_str().as_ref()
}
}
impl AsRef<Path> for Path {
fn as_ref(&self) -> &Path {
self
@@ -32,9 +43,17 @@ impl AsRef<str> for Path {
}
impl Path {
pub fn new(path: &str) -> &Self {
path.as_ref()
}
pub fn split_path(&self) -> (&str, Option<&Path>) {
if let Some((start, end)) = self.inner.split_once("/") {
(start, Some(end.into()))
if end.is_empty() {
(start, None)
} else {
(start, Some(end.into()))
}
} else {
(&self.inner, None)
}
@@ -48,9 +67,46 @@ impl Path {
pub fn is_relative(&self) -> bool {
!self.is_absolute()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn is_root(&self) -> bool {
self.as_str() == "/"
}
pub fn starts_with<P: AsRef<Self>>(&self, other: P) -> bool {
let this = self.split_path();
let other = other.as_ref().split_path();
if this.0 == other.0 {
if let Some(this) = this.1
&& let Some(other) = other.1
{
this.starts_with(other)
} else {
other.1.is_none()
}
} else {
false
}
}
pub fn without<P: AsRef<Self>>(&self, other: P) -> &Path {
let this = self.split_path();
let other = other.as_ref().split_path();
if this.0 == other.0 {
match (this.1, other.1) {
(None, None) => Path::new(""),
(None, Some(_)) => todo!(),
(Some(p), None) => p,
(Some(this), Some(other)) => this.without(other),
}
} else {
todo!()
}
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Hash, PartialEq, Eq)]
pub struct PathBuf {
inner: String,
}
@@ -63,3 +119,26 @@ impl Deref for PathBuf {
self.inner.as_str().into()
}
}
#[cfg(feature = "alloc")]
impl AsRef<Path> for PathBuf {
fn as_ref(&self) -> &Path {
self.inner.as_ref()
}
}
#[cfg(feature = "alloc")]
impl Borrow<str> for PathBuf {
fn borrow(&self) -> &str {
&self.inner
}
}
#[cfg(feature = "alloc")]
impl From<&str> for PathBuf {
fn from(val: &str) -> Self {
PathBuf {
inner: val.to_owned(),
}
}
}

10
crates/io/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "io"
version = "0.1.0"
edition = "2024"
[dependencies]
[features]
alloc = []
std = ["alloc"]

48
crates/io/src/error.rs Normal file
View File

@@ -0,0 +1,48 @@
/// Trait that should be implemented by errors returned from the user supplied storage.
///
/// Implementations for `std::io::Error` and `()` are provided by this crate.
pub trait IoError: core::fmt::Debug {
fn is_interrupted(&self) -> bool;
fn new_unexpected_eof_error() -> Self;
fn new_write_zero_error() -> Self;
fn new_invalid_utf8_error() -> Self;
}
impl IoError for () {
fn is_interrupted(&self) -> bool {
false
}
fn new_unexpected_eof_error() -> Self {
// empty
}
fn new_write_zero_error() -> Self {
// empty
}
fn new_invalid_utf8_error() -> Self {
// empty
}
}
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
impl IoError for std::io::Error {
fn is_interrupted(&self) -> bool {
self.kind() == std::io::ErrorKind::Interrupted
}
fn new_unexpected_eof_error() -> Self {
Self::new(
std::io::ErrorKind::UnexpectedEof,
"failed to fill whole buffer",
)
}
fn new_write_zero_error() -> Self {
Self::new(
std::io::ErrorKind::WriteZero,
"failed to write whole buffer",
)
}
}

View File

@@ -1,4 +1,10 @@
use crate::error::{Error, IoError};
#![cfg_attr(any(not(feature = "std"), target_arch = "riscv64"), no_std)]
pub mod error;
use error::IoError;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::string::String;
@@ -41,7 +47,7 @@ pub trait Read: IoBase {
/// then it must be guaranteed that no bytes were read.
/// An error for which `IoError::is_interrupted` returns true is non-fatal and the read operation should be retried
/// if there is nothing else to do.
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>>;
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
/// Read the exact number of bytes required to fill `buf`.
///
@@ -58,7 +64,7 @@ pub trait Read: IoBase {
///
/// If this function returns an error, it is unspecified how many bytes it has read, but it will never read more
/// than would be necessary to completely fill the buffer.
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Error<Self::Error>> {
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Self::Error> {
while !buf.is_empty() {
match self.read(buf) {
Ok(0) => break,
@@ -73,12 +79,12 @@ pub trait Read: IoBase {
if buf.is_empty() {
Ok(())
} else {
Err(Error::<Self::Error>::new_unexpected_eof_error())
Err(Self::Error::new_unexpected_eof_error())
}
}
#[cfg(feature = "alloc")]
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Error<Self::Error>> {
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Self::Error> {
const CHUNK_SIZE: usize = 32;
let start_len = buf.len();
loop {
@@ -93,11 +99,11 @@ pub trait Read: IoBase {
}
#[cfg(feature = "alloc")]
fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Error<Self::Error>> {
fn read_to_string(&mut self, buf: &mut String) -> Result<usize, Self::Error> {
let read = self.read_to_end(unsafe { buf.as_mut_vec() })?;
if str::from_utf8(buf.as_bytes()).is_err() {
Err(Error::InvalidUTF8)
Err(Self::Error::new_invalid_utf8_error())
} else {
Ok(read)
}
@@ -115,7 +121,7 @@ pub trait Write: IoBase {
/// Each call to write may generate an I/O error indicating that the operation could not be completed. If an error
/// is returned then no bytes in the buffer were written to this writer.
/// It is not considered an error if the entire buffer could not be written to this writer.
fn write(&mut self, buf: &[u8]) -> Result<usize, Error<Self::Error>>;
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error>;
/// Attempts to write an entire buffer into this writer.
///
@@ -129,11 +135,11 @@ pub trait Write: IoBase {
///
/// This function will return the first error for which `IoError::is_interrupted` method returns false that `write`
/// returns.
fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Error<Self::Error>> {
fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Self::Error> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => {
return Err(Error::<Self::Error>::new_write_zero_error());
return Err(Self::Error::new_write_zero_error());
}
Ok(n) => buf = &buf[n..],
Err(ref e) if e.is_interrupted() => {}
@@ -148,7 +154,7 @@ pub trait Write: IoBase {
/// # Errors
///
/// It is considered an error if not all bytes could be written due to I/O errors or EOF being reached.
fn flush(&mut self) -> Result<(), Error<Self::Error>>;
fn flush(&mut self) -> Result<(), Self::Error>;
}
/// Enumeration of possible methods to seek within an I/O object.
@@ -177,7 +183,7 @@ pub trait Seek: IoBase {
///
/// # Errors
/// Seeking to a negative offset is considered an error.
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error<Self::Error>>;
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error>;
}
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
@@ -209,59 +215,59 @@ impl IoBase for std::fs::File {
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
impl<T: std::io::Read + IoBase<Error = std::io::Error>> Read for T {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error<Self::Error>> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.read(buf).map_err(Error::Io)
}
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error<Self::Error>> {
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
self.read_exact(buf).map_err(Error::Io)
}
}
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
impl<T: std::io::Write + IoBase<Error = std::io::Error>> Write for T {
fn write(&mut self, buf: &[u8]) -> Result<usize, Error<Self::Error>> {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.write(buf).map_err(Error::Io)
}
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error<Self::Error>> {
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
self.write_all(buf).map_err(Error::Io)
}
fn flush(&mut self) -> Result<(), Error<Self::Error>> {
fn flush(&mut self) -> Result<(), Self::Error> {
self.flush().map_err(Error::Io)
}
}
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
impl<T: std::io::Seek + IoBase<Error = std::io::Error>> Seek for T {
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error<Self::Error>> {
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error> {
self.seek(pos.into()).map_err(Error::Io)
}
}
pub(crate) trait ReadLeExt {
pub trait ReadLeExt {
type Error;
fn read_u8(&mut self) -> Result<u8, Error<Self::Error>>;
fn read_u16_le(&mut self) -> Result<u16, Error<Self::Error>>;
fn read_u32_le(&mut self) -> Result<u32, Error<Self::Error>>;
fn read_u8(&mut self) -> Result<u8, Self::Error>;
fn read_u16_le(&mut self) -> Result<u16, Self::Error>;
fn read_u32_le(&mut self) -> Result<u32, Self::Error>;
}
impl<T: Read> ReadLeExt for T {
type Error = <Self as IoBase>::Error;
fn read_u8(&mut self) -> Result<u8, Error<Self::Error>> {
fn read_u8(&mut self) -> Result<u8, Self::Error> {
let mut buf = [0_u8; 1];
self.read_exact(&mut buf)?;
Ok(buf[0])
}
fn read_u16_le(&mut self) -> Result<u16, Error<Self::Error>> {
fn read_u16_le(&mut self) -> Result<u16, Self::Error> {
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, Error<Self::Error>> {
fn read_u32_le(&mut self) -> Result<u32, Self::Error> {
let mut buf = [0_u8; 4];
self.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
@@ -271,23 +277,23 @@ impl<T: Read> ReadLeExt for T {
#[allow(unused)]
pub(crate) trait WriteLeExt {
type Error;
fn write_u8(&mut self, n: u8) -> Result<(), Error<Self::Error>>;
fn write_u16_le(&mut self, n: u16) -> Result<(), Error<Self::Error>>;
fn write_u32_le(&mut self, n: u32) -> Result<(), Error<Self::Error>>;
fn write_u8(&mut self, n: u8) -> Result<(), Self::Error>;
fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error>;
fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error>;
}
impl<T: Write> WriteLeExt for T {
type Error = <Self as IoBase>::Error;
fn write_u8(&mut self, n: u8) -> Result<(), Error<Self::Error>> {
fn write_u8(&mut self, n: u8) -> Result<(), Self::Error> {
self.write_all(&[n])
}
fn write_u16_le(&mut self, n: u16) -> Result<(), Error<Self::Error>> {
fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error> {
self.write_all(&n.to_le_bytes())
}
fn write_u32_le(&mut self, n: u32) -> Result<(), Error<Self::Error>> {
fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error> {
self.write_all(&n.to_le_bytes())
}
}

View File

@@ -6,3 +6,4 @@ edition = "2024"
[dependencies]
os-std-macros = { path = "../os-std-macros" }
shared = { path = "../shared", features = ["user"] }
io = { path = "../io", features = ["alloc"] }

1
crates/os-std/src/io.rs Normal file
View File

@@ -0,0 +1 @@
pub use io::SeekFrom;

View File

@@ -2,6 +2,7 @@
extern crate alloc;
pub mod io;
pub mod prelude;
pub use shared::fs;

View File

@@ -5,6 +5,7 @@ edition = "2024"
[dependencies]
bffs = { path = "../bffs" }
io = { path = "../io" }
[features]
kernel = []

View File

@@ -1,9 +1,16 @@
#[derive(Debug)]
pub struct File {
fd: u64,
}
impl File {
/// # Safety
/// The file descriptor must be valid
pub unsafe fn new(fd: u64) -> Self {
Self { fd }
}
pub fn as_fd(&self) -> u64 {
self.fd
}
}

View File

@@ -1,10 +1,15 @@
use core::{alloc::Layout, time::Duration};
use bffs::path::Path;
use io::SeekFrom;
use crate::fs::File;
#[repr(u64)]
pub enum SysCall {
Write = 1,
Open = 2,
Seek = 8,
Alloc = 40,
Dealloc = 41,
Exit = 60,
@@ -17,7 +22,9 @@ pub enum SysCall {
impl From<u64> for SysCall {
fn from(value: u64) -> Self {
match value {
1 => SysCall::Write,
2 => SysCall::Open,
8 => SysCall::Seek,
40 => SysCall::Alloc,
41 => SysCall::Dealloc,
60 => SysCall::Exit,
@@ -128,12 +135,29 @@ pub fn dealloc(ptr: *mut u8, layout: core::alloc::Layout) {
syscall!(SysCall::Dealloc, ptr as u64, size as u64, align as u64);
}
}
pub fn open<P: AsRef<Path>>(path: P) -> u64 {
pub fn open<P: AsRef<Path>>(path: P) -> File {
unsafe {
let path_str = path.as_ref().as_str();
let ptr = path_str.as_ptr();
let size = path_str.len();
let (fd, ..) = syscall!(SysCall::Open, ptr as u64, size as u64);
fd
File::new(fd)
}
}
pub fn write(file: &mut File, buf: &[u8]) {
unsafe {
let ptr = buf.as_ptr();
let size = buf.len();
syscall!(SysCall::Write, file.as_fd(), ptr as u64, size as u64);
}
}
pub fn seek(file: &mut File, seek: SeekFrom) {
unsafe {
let (discriminant, value) = match seek {
SeekFrom::Start(v) => (0, v),
SeekFrom::End(v) => (1, v as u64),
SeekFrom::Current(v) => (2, v as u64),
};
syscall!(SysCall::Seek, file.as_fd(), discriminant, value);
}
}