Skip to content

Commit

Permalink
feat(io): Add a bunch of default implementations & make documentation…
Browse files Browse the repository at this point in the history
… more clear
  • Loading branch information
Oakchris1955 committed Aug 3, 2024
1 parent 49a67d1 commit 72cd1bd
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 33 deletions.
11 changes: 11 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub trait IOErrorKind: PartialEq + Sized {
fn new_unexpected_eof() -> Self;
/// Create a new `Interrupted` [`IOErrorKind`]
fn new_interrupted() -> Self;
/// Create a new `InvalidData` [`IOErrorKind`]
fn new_invalid_data() -> Self;

#[inline]
/// Check whether this [`IOErrorKind`] is of kind `UnexpectedEOF`
Expand All @@ -60,6 +62,11 @@ pub trait IOErrorKind: PartialEq + Sized {
fn is_interrupted(&self) -> bool {
self == &Self::new_interrupted()
}
/// Check whether this [`IOErrorKind`] is of kind `InvalidData`
#[inline]
fn is_invalid_data(&self) -> bool {
self == &Self::new_invalid_data()
}
}

#[cfg(feature = "std")]
Expand All @@ -72,6 +79,10 @@ impl IOErrorKind for std::io::ErrorKind {
fn new_interrupted() -> Self {
std::io::ErrorKind::Interrupted
}
#[inline]
fn new_invalid_data() -> Self {
std::io::ErrorKind::InvalidData
}
}

/// An error type that denotes that there is something wrong
Expand Down
37 changes: 23 additions & 14 deletions src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,20 @@ where

Ok(bytes_read)
}

// the default `read_to_end` implementation isn't efficient enough, so we just do this
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Self::Error> {
let bytes_to_read = self.file_size as usize - self.offset as usize;
let init_buf_len = buf.len();

// resize buffer to fit the file contents exactly
buf.resize(init_buf_len + bytes_to_read, 0);

// this is guaranteed not to raise an EOF (although other error kinds might be raised...)
self.read_exact(&mut buf[init_buf_len..])?;

Ok(bytes_to_read)
}
}

impl<'a, S> Seek for File<'a, S>
Expand Down Expand Up @@ -1347,10 +1361,9 @@ mod tests {
let mut fs = FileSystem::from_storage(&mut storage).unwrap();

let mut file = fs.get_file(PathBuf::from("/root.txt")).unwrap();
let mut file_bytes = [0_u8; 1024];
let bytes_read = file.read(&mut file_bytes).unwrap();

let file_string = String::from_utf8_lossy(&file_bytes[..bytes_read]).to_string();
let mut file_string = String::new();
file.read_to_string(&mut file_string).unwrap();
const EXPECTED_STR: &str = "I am in the filesystem's root!!!\n\n";
assert_eq!(file_string, EXPECTED_STR);
}
Expand All @@ -1360,14 +1373,13 @@ mod tests {
where
S: Read + Write + Seek,
{
let mut file_bytes = [0_u8; 65536];
let bytes_read = file.read(&mut file_bytes).unwrap();
let mut file_string = String::new();
let bytes_read = file.read_to_string(&mut file_string).unwrap();

let expected_filesize = BEE_MOVIE_SCRIPT.len();
assert_eq!(bytes_read, expected_filesize);

let utf8_string = str::from_utf8(&file_bytes[..bytes_read]).unwrap();
assert_eq!(utf8_string, BEE_MOVIE_SCRIPT);
assert_eq!(file_string, BEE_MOVIE_SCRIPT);
}

#[test]
Expand Down Expand Up @@ -1423,10 +1435,9 @@ mod tests {
let mut fs = FileSystem::from_storage(&mut storage).unwrap();

let mut file = fs.get_file(PathBuf::from("/rootdir/example.txt")).unwrap();
let mut file_bytes = [0_u8; 1024];
let bytes_read = file.read(&mut file_bytes).unwrap();

let file_string = String::from_utf8_lossy(&file_bytes[..bytes_read]).to_string();
let mut file_string = String::new();
file.read_to_string(&mut file_string).unwrap();
const EXPECTED_STR: &str = "I am not in the root directory :(\n\n";
assert_eq!(file_string, EXPECTED_STR);
}
Expand All @@ -1453,10 +1464,8 @@ mod tests {
let mut fs = FileSystem::from_storage(&mut storage).unwrap();

let mut file = fs.get_file(PathBuf::from("/foo/bar.txt")).unwrap();
let mut file_bytes = [0_u8; 1024];
let bytes_read = file.read(&mut file_bytes).unwrap();

let file_string = String::from_utf8_lossy(&file_bytes[..bytes_read]).to_string();
let mut file_string = String::new();
file.read_to_string(&mut file_string).unwrap();
const EXPECTED_STR: &str = "Hello, World!\n";
assert_eq!(file_string, EXPECTED_STR);

Expand Down
205 changes: 186 additions & 19 deletions src/io.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
//! This module contains all the IO-related objects of the crate
#[cfg(not(feature = "std"))]
use core::*;
#[cfg(feature = "std")]
use std::*;

use ::alloc::{string::String, vec::Vec};

use crate::error::{IOError, IOErrorKind};

/// With `use prelude::*`, all IO-related traits are automatically imported
Expand All @@ -19,14 +26,86 @@ pub trait IOBase {

/// A simplified version of [`std::io::Read`] for use within a `no_std` context
pub trait Read: IOBase {
/// Pull some bytes from this source into the specified buffer, returning how many bytes were read.
/// Pull some bytes from this source into the specified buffer,
/// returning how many bytes were read.
///
/// If the return value of this method is [`Ok(n)`], then implementations must
/// guarantee 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,
/// although for the later, an [`IOErrorKind`] of kind `Interrupted` should preferably be raised
///
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;

/// Read all bytes until EOF in this source, placing them into `buf`.
///
/// All bytes read from this source will be appended to the specified buffer
/// `buf`. This function will continuously call [`read()`] to append more data to
/// `buf` until [`read()`] returns either [`Ok(0)`] or an [`IOErrorKind`] of
/// non-`Interrupted` kind.
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Self::Error> {
let mut bytes_read = 0;

const PROBE_SIZE: usize = 32;
let mut probe = [0_u8; PROBE_SIZE];

loop {
match self.read(&mut probe) {
Ok(0) => break,
Ok(n) => {
buf.extend_from_slice(&probe[..n]);
bytes_read += n;
}
Err(ref e) if e.kind().is_interrupted() => continue,
Err(e) => return Err(e),
}
}

Ok(bytes_read)
}

/// Read all bytes until EOF in this source, appending them to `string`.
///
/// If successful, this function returns the number of bytes which were read and appended to buf.
///
/// # Errors
///
/// If the data in this stream is *not* valid UTF-8 then an error is returned and `string` is unchanged.
///
/// See [`read_to_end`](Read::read_to_end) for other error semantics.
fn read_to_string(&mut self, string: &mut String) -> Result<usize, Self::Error> {
let mut buf = Vec::new();
let bytes_read = self.read_to_end(&mut buf)?;

string.push_str(str::from_utf8(&buf).map_err(|_| {
IOError::new(
<Self::Error as IOError>::Kind::new_invalid_data(),
"found invalid utf-8",
)
})?);

Ok(bytes_read)
}

/// Read the exact number of bytes required to fill `buf`.
///
/// Blocks until enough bytes could be read
/// This function reads as many bytes as necessary to completely fill the
/// specified buffer `buf`.
///
/// Returns an error if EOF is met.
/// *Implementations* of this method can make no assumptions about the contents of `buf` when
/// this function is called. It is recommended that implementations only write data to `buf`
/// instead of reading its contents. The documentation on [`read`](Read::read) has a more detailed
/// explanation of this subject.
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Self::Error> {
while !buf.is_empty() {
match self.read(buf) {
Expand All @@ -49,15 +128,73 @@ pub trait Read: IOBase {

/// A simplified version of [`std::io::Write`] for use within a `no_std` context
pub trait Write: IOBase {
/// Write a buffer into this writer, returning how many bytes were written.
///
/// This function will attempt to write the entire contents of `buf`, but
/// the entire write might not succeed, or the write may also generate an
/// error. Typically, a call to `write` represents one attempt to write to
/// any wrapped object.
///
/// If this method consumed `n > 0` bytes of `buf` it must return [`Ok(n)`].
/// If the return value is `Ok(n)` then `n` must satisfy `n <= buf.len()`.
/// A return value of `Ok(0)` typically means that the underlying object is
/// no longer able to accept bytes and will likely not be able to in the
/// future as well, or that the buffer provided is empty.
///
/// # 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.
///
/// An error of the `Interrupted` [`IOErrorKind`] is non-fatal and the
/// write operation should be retried if there is nothing else to do.
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error>;

/// Attempts to write an entire buffer into this writer.
///
/// Blocks until the entire buffer has been written
fn write_all(&mut self, data: &[u8]) -> Result<(), Self::Error>;
/// This method will continuously call [`write`] until there is no more data
/// to be written or an error of non-[`ErrorKind::Interrupted`] kind is
/// returned. This method will not return until the entire buffer has been
/// successfully written or such an error occurs. The first error that is
/// not of [`ErrorKind::Interrupted`] kind generated from this method will be
/// returned.
///
/// If the buffer contains no data, this will never call [`write`].
///
/// # Errors
///
/// This function will return the first error of
/// non-[`ErrorKind::Interrupted`] kind that [`write`] returns.
///
/// [`write`]: Write::write
fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Self::Error> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => {
return Err(IOError::new(
IOErrorKind::new_unexpected_eof(),
"writer returned EOF before all data could be written",
));
}
Ok(n) => buf = &buf[n..],
Err(ref e) if e.kind().is_interrupted() => {}
Err(e) => return Err(e),
}
}
Ok(())
}

/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
/// 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.
///
/// 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>;
}

Expand Down Expand Up @@ -152,13 +289,23 @@ where
T: std::io::Read + IOBase<Error = std::io::Error>,
{
#[inline]
fn read(&mut self, data: &mut [u8]) -> Result<usize, Self::Error> {
self.read(data)
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
self.read(buf)
}

#[inline]
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Self::Error> {
self.read_to_end(buf)
}

#[inline]
fn read_exact(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.read_exact(data)
fn read_to_string(&mut self, string: &mut String) -> Result<usize, Self::Error> {
self.read_to_string(string)
}

#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
self.read_exact(buf)
}
}

Expand All @@ -168,8 +315,13 @@ where
T: std::io::Write + IOBase<Error = std::io::Error>,
{
#[inline]
fn write_all(&mut self, data: &[u8]) -> Result<(), Self::Error> {
self.write_all(data)
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.write(buf)
}

#[inline]
fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
self.write_all(buf)
}

#[inline]
Expand Down Expand Up @@ -211,21 +363,36 @@ where
#[cfg(not(feature = "std"))]
impl<R: Read + IOBase> Read for &mut R {
#[inline]
fn read(&mut self, data: &mut [u8]) -> Result<usize, R::Error> {
(**self).read(data)
fn read(&mut self, buf: &mut [u8]) -> Result<usize, R::Error> {
(**self).read(buf)
}

#[inline]
fn read_exact(&mut self, data: &mut [u8]) -> Result<(), R::Error> {
(**self).read_exact(data)
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, R::Error> {
(**self).read_to_end(buf)
}

#[inline]
fn read_to_string(&mut self, buf: &mut String) -> Result<usize, R::Error> {
(**self).read_to_string(buf)
}

#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), R::Error> {
(**self).read_exact(buf)
}
}

#[cfg(not(feature = "std"))]
impl<W: Write + IOBase> Write for &mut W {
#[inline]
fn write_all(&mut self, data: &[u8]) -> Result<(), W::Error> {
(**self).write_all(data)
fn write(&mut self, buf: &[u8]) -> Result<usize, W::Error> {
(**self).write(buf)
}

#[inline]
fn write_all(&mut self, buf: &[u8]) -> Result<(), W::Error> {
(**self).write_all(buf)
}

#[inline]
Expand Down

0 comments on commit 72cd1bd

Please sign in to comment.