Better virtual file system, keyboard through MMIO&VirtIO
This commit is contained in:
48
crates/io/src/error.rs
Normal file
48
crates/io/src/error.rs
Normal 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",
|
||||
)
|
||||
}
|
||||
}
|
||||
299
crates/io/src/lib.rs
Normal file
299
crates/io/src/lib.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
#![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;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// Provides IO error as an associated type.
|
||||
///
|
||||
/// Must be implemented for all types that also implement at least one of the following traits: `Read`, `Write`,
|
||||
/// `Seek`.
|
||||
pub trait IoBase {
|
||||
/// Type of errors returned by input/output operations.
|
||||
type Error: IoError;
|
||||
}
|
||||
|
||||
/// The `Read` trait allows for reading bytes from a source.
|
||||
///
|
||||
/// It is based on the `std::io::Read` trait.
|
||||
pub trait Read: IoBase {
|
||||
/// Pull some bytes from this source into the specified buffer, returning how many bytes were read.
|
||||
///
|
||||
/// This function does not provide any guarantees about whether it blocks waiting for data, but if an object needs
|
||||
/// to block for a read and cannot, it will typically signal this via an Err return value.
|
||||
///
|
||||
/// If the return value of this method is `Ok(n)`, then it must be guaranteed that `0 <= n <= buf.len()`. A nonzero
|
||||
/// `n` value indicates that the buffer buf has been filled in with n bytes of data from this source. If `n` is
|
||||
/// `0`, then it can indicate one of two scenarios:
|
||||
///
|
||||
/// 1. This reader has reached its "end of file" and will likely no longer be able to produce bytes. Note that this
|
||||
/// does not mean that the reader will always no longer be able to produce bytes.
|
||||
/// 2. The buffer specified was 0 bytes in length.
|
||||
///
|
||||
/// It is not an error if the returned value `n` is smaller than the buffer size, even when the reader is not at
|
||||
/// the end of the stream yet. This may happen for example because fewer bytes are actually available right now
|
||||
/// (e. g. being close to end-of-file) or because `read()` was interrupted by a signal.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If this function encounters any form of I/O or other error, an error will be returned. If an error is returned
|
||||
/// 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, Self::Error>;
|
||||
|
||||
/// Read the exact number of bytes required to fill `buf`.
|
||||
///
|
||||
/// This function reads as many bytes as necessary to completely fill the specified buffer `buf`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If this function encounters an error for which `IoError::is_interrupted` returns true then the error is ignored
|
||||
/// and the operation will continue.
|
||||
///
|
||||
/// If this function encounters an end of file before completely filling the buffer, it returns an error
|
||||
/// instantiated by a call to `IoError::new_unexpected_eof_error`. The contents of `buf` are unspecified in this
|
||||
/// case.
|
||||
///
|
||||
/// 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<(), Self::Error> {
|
||||
while !buf.is_empty() {
|
||||
match self.read(buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
let tmp = buf;
|
||||
buf = &mut tmp[n..];
|
||||
}
|
||||
Err(ref e) if e.is_interrupted() => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
if buf.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Self::Error::new_unexpected_eof_error())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
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 {
|
||||
let mut chunk_buf = [0; CHUNK_SIZE];
|
||||
let read = self.read(&mut chunk_buf)?;
|
||||
buf.extend_from_slice(&chunk_buf[..read]);
|
||||
|
||||
if read == 0 {
|
||||
return Ok(buf.len() - start_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
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(Self::Error::new_invalid_utf8_error())
|
||||
} else {
|
||||
Ok(read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The `Write` trait allows for writing bytes into the sink.
|
||||
///
|
||||
/// It is based on the `std::io::Write` trait.
|
||||
pub trait Write: IoBase {
|
||||
/// Write a buffer into this writer, returning how many bytes were written.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 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, Self::Error>;
|
||||
|
||||
/// Attempts to write an entire buffer into this writer.
|
||||
///
|
||||
/// This method will continuously call `write` until there is no more data to be written or an error is returned.
|
||||
/// Errors for which `IoError::is_interrupted` method returns true are being skipped. This method will not return
|
||||
/// until the entire buffer has been successfully written or such an error occurs.
|
||||
/// If `write` returns 0 before the entire buffer has been written this method will return an error instantiated by
|
||||
/// a call to `IoError::new_write_zero_error`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 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<(), Self::Error> {
|
||||
while !buf.is_empty() {
|
||||
match self.write(buf) {
|
||||
Ok(0) => {
|
||||
return Err(Self::Error::new_write_zero_error());
|
||||
}
|
||||
Ok(n) => buf = &buf[n..],
|
||||
Err(ref e) if e.is_interrupted() => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
|
||||
///
|
||||
/// # 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<(), Self::Error>;
|
||||
}
|
||||
|
||||
/// Enumeration of possible methods to seek within an I/O object.
|
||||
///
|
||||
/// It is based on the `std::io::SeekFrom` enum.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SeekFrom {
|
||||
/// Sets the offset to the provided number of bytes.
|
||||
Start(u64),
|
||||
/// Sets the offset to the size of this object plus the specified number of bytes.
|
||||
End(i64),
|
||||
/// Sets the offset to the current position plus the specified number of bytes.
|
||||
Current(i64),
|
||||
}
|
||||
|
||||
/// The `Seek` trait provides a cursor which can be moved within a stream of bytes.
|
||||
///
|
||||
/// It is based on the `std::io::Seek` trait.
|
||||
pub trait Seek: IoBase {
|
||||
/// Seek to an offset, in bytes, in a stream.
|
||||
///
|
||||
/// A seek beyond the end of a stream or to a negative position is not allowed.
|
||||
///
|
||||
/// If the seek operation completed successfully, this method returns the new position from the start of the
|
||||
/// stream. That position can be used later with `SeekFrom::Start`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Seeking to a negative offset is considered an error.
|
||||
fn seek(&mut self, pos: SeekFrom) -> Result<u64, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
|
||||
impl From<SeekFrom> for std::io::SeekFrom {
|
||||
fn from(from: SeekFrom) -> Self {
|
||||
match from {
|
||||
SeekFrom::Start(n) => std::io::SeekFrom::Start(n),
|
||||
SeekFrom::End(n) => std::io::SeekFrom::End(n),
|
||||
SeekFrom::Current(n) => std::io::SeekFrom::Current(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
|
||||
impl From<std::io::SeekFrom> for SeekFrom {
|
||||
fn from(from: std::io::SeekFrom) -> Self {
|
||||
match from {
|
||||
std::io::SeekFrom::Start(n) => SeekFrom::Start(n),
|
||||
std::io::SeekFrom::End(n) => SeekFrom::End(n),
|
||||
std::io::SeekFrom::Current(n) => SeekFrom::Current(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "std", not(target_arch = "riscv64")))]
|
||||
impl IoBase for std::fs::File {
|
||||
type Error = std::io::Error;
|
||||
}
|
||||
|
||||
#[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, Self::Error> {
|
||||
self.read(buf).map_err(Error::Io)
|
||||
}
|
||||
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, Self::Error> {
|
||||
self.write(buf).map_err(Error::Io)
|
||||
}
|
||||
|
||||
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
|
||||
self.write_all(buf).map_err(Error::Io)
|
||||
}
|
||||
|
||||
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, Self::Error> {
|
||||
self.seek(pos.into()).map_err(Error::Io)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadLeExt {
|
||||
type 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, Self::Error> {
|
||||
let mut buf = [0_u8; 1];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
|
||||
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, Self::Error> {
|
||||
let mut buf = [0_u8; 4];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(u32::from_le_bytes(buf))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) trait WriteLeExt {
|
||||
type 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<(), Self::Error> {
|
||||
self.write_all(&[n])
|
||||
}
|
||||
|
||||
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<(), Self::Error> {
|
||||
self.write_all(&n.to_le_bytes())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user