diff --git a/src/dev/error.rs b/src/dev/error.rs index 3e05a55..b1f8d54 100644 --- a/src/dev/error.rs +++ b/src/dev/error.rs @@ -8,8 +8,19 @@ use core::fmt::Display; #[allow(clippy::module_name_repetitions)] #[derive(Debug, PartialEq, Eq)] pub enum DevError { - /// `OutOfBounds(structure, value, (lower_bound, upper_bound))`: the given `struct` has a `value` not between the given bounds + /// `OutOfBounds(structure, value, (lower_bound, upper_bound))`: the given `struct` has a `value` not between the given + /// bounds. OutOfBounds(&'static str, i128, (i128, i128)), + + /// An error returned when an operation could not be completed because an “end of file” was reached prematurely. + /// + /// This typically means that an operation could only succeed if it read a particular number of bytes but only a smaller number + /// of bytes could be read. + UnexpectedEof, + + /// An error returned when an operation could not be completed because a call to [`write`](crate::io::Write::write) returned + /// `Ok(0)`. + WriteZero, } impl Display for DevError { @@ -22,6 +33,14 @@ impl Display for DevError { "Out of Bounds: the {structure} has a value {value} not between the lower bound {lower_bound} and the upper bound {upper_bound}" ) }, + Self::UnexpectedEof => write!( + formatter, + "Unexpected End of File: an operation could not be completed because an \"end of file\" was reached prematurely" + ), + Self::WriteZero => write!( + formatter, + "Write Zero: An error returned when an operation could not be completed because a call to write returned Ok(0)" + ), } } } diff --git a/src/dev/mod.rs b/src/dev/mod.rs index 07c7a3a..b732b36 100644 --- a/src/dev/mod.rs +++ b/src/dev/mod.rs @@ -13,12 +13,13 @@ use core::ptr::{addr_of, slice_from_raw_parts}; #[cfg(feature = "std")] use std::fs::File; #[cfg(feature = "std")] -use std::io::{ErrorKind, Read, Seek, Write}; +use std::io::ErrorKind; use self::sector::Address; use self::size::Size; use crate::dev::error::DevError; use crate::error::Error; +use crate::io::{Base, Read, Seek, SeekFrom, Write}; pub mod celled; pub mod error; @@ -224,7 +225,7 @@ pub trait Device { ..Address::forward_checked(starting_addr, length).ok_or(Error::Device(DevError::OutOfBounds( "address", i128::try_from(starting_addr.index() + length).unwrap_unchecked(), - (0, self.size().len().index().try_into().unwrap_unchecked()), + (0, self.size().0.into()), )))?; let slice = self.slice(range)?; let ptr = slice.inner.as_ptr(); @@ -259,7 +260,7 @@ pub trait Device { ..Address::forward_checked(starting_addr, length).ok_or(Error::Device(DevError::OutOfBounds( "address", i128::try_from(starting_addr.index() + length).unwrap_unchecked(), - (0, self.size().len().index().try_into().unwrap_unchecked()), + (0, self.size().0.into()), )))?; let mut device_slice = self.slice(range)?; let buffer = device_slice @@ -278,12 +279,12 @@ macro_rules! impl_device { impl Device for $volume { #[inline] fn size(&self) -> Size { - Size(Address::from(self.len())) + Size(self.len() as u64) } #[inline] fn slice(&self, addr_range: Range
) -> Result, Error> { - if Device::::size(self) >= addr_range.end { + if Device::::size(self) >= usize::from(addr_range.end) as u64 { let addr_start = addr_range.start; // SAFETY: it is not possible to manipulate addresses with a higher bit number than the device's let range = unsafe { usize::try_from(addr_range.start.index()).unwrap_unchecked() }..unsafe { @@ -296,8 +297,7 @@ macro_rules! impl_device { "address", // SAFETY: it is assumed that `usize` can always be converted to `i128` unsafe { addr_range.end.index().try_into().unwrap_unchecked() }, - // SAFETY: it is assumed that `usize` can always be converted to `i128` - (0, unsafe { >::size(self).len().index().try_into().unwrap_unchecked() }), + (0, >::size(self).0.into()), ))) } } @@ -331,17 +331,62 @@ impl_device!(&mut [T]); impl_device!(Vec); impl_device!(Box<[T]>); +impl + Read + Write + Seek> Device for RefCell { + #[inline] + fn size(&self) -> Size { + let mut device = self.borrow_mut(); + let offset = device.seek(SeekFrom::End(0)).expect("Could not seek the device at its end"); + let size = device + .seek(SeekFrom::Start(offset)) + .expect("Could not seek the device at its original offset"); + Size(size) + } + + #[inline] + fn slice(&self, addr_range: Range
) -> Result, Error> { + let starting_addr = addr_range.start; + let len = TryInto::::try_into((addr_range.end - addr_range.start).index()).map_err(|_err| { + Error::Device(DevError::OutOfBounds( + "addr range", + // SAFETY: `usize::MAX <= i128::MAX` + unsafe { i128::try_from((addr_range.end - addr_range.start).index()).unwrap_unchecked() }, + (0, i128::MAX), + )) + })?; + + let mut slice = alloc::vec![0; len]; + let mut device = self.borrow_mut(); + device + .seek(SeekFrom::Start(starting_addr.index().try_into().expect("Could not convert `usize` to `u64`"))) + .and_then(|_| device.read_exact(&mut slice))?; + + Ok(Slice::new_owned(slice, starting_addr)) + } + + #[inline] + fn commit(&mut self, commit: Commit) -> Result<(), Error> { + let mut device = self.borrow_mut(); + + let offset = device.seek(SeekFrom::Start(commit.addr().index().try_into().expect("Could not convert `usize` to `u64`")))?; + device.write_all(commit.as_ref())?; + device.seek(SeekFrom::Start(offset))?; + + Ok(()) + } +} + #[cfg(feature = "std")] impl Device for RefCell { #[inline] fn size(&self) -> Size { let metadata = self.borrow().metadata().expect("Could not read the file"); - let size = TryInto::
::try_into(metadata.len()).expect("Could not convert `usize` to `u64`"); - size.into() + Size(metadata.len()) } #[inline] fn slice(&self, addr_range: Range
) -> Result, Error> { + use std::io::{Read, Seek}; + let starting_addr = addr_range.start; let len = TryInto::::try_into((addr_range.end - addr_range.start).index()).map_err(|_err| { Error::Device(DevError::OutOfBounds( @@ -364,11 +409,10 @@ impl Device for RefCell { "address", // SAFETY: `usize::MAX <= i128::MAX` unsafe { i128::try_from(usize::from(starting_addr + len)).unwrap_unchecked() }, - // SAFETY: the size of a file is smaller than `i128::MAX` - (0, unsafe { usize::from(Device::::size(self).0).try_into().unwrap_unchecked() }), + (0, Device::::size(self).0.into()), ))) } else { - panic!("{err}"); + Err(Error::IO(err)) } }, } @@ -376,10 +420,17 @@ impl Device for RefCell { #[inline] fn commit(&mut self, commit: Commit) -> Result<(), Error> { + use std::io::{Seek, Write}; + let mut file = self.borrow_mut(); - file.seek(std::io::SeekFrom::Start(commit.addr().index().try_into().expect("Could not convert `usize` to `u64`"))) - .and_then(|_| file.write_all(commit.as_ref())) - .expect("Could not seek/write on the given file"); + match file.seek(std::io::SeekFrom::Start(commit.addr().index().try_into().expect("Could not convert `usize` to `u64`"))) { + Ok(offset) => { + file.write_all(commit.as_ref()).expect("Could not write on the given file"); + file.seek(std::io::SeekFrom::Start(offset)).expect("Could not seek on the given file"); + }, + Err(err) => panic!("{err}: Could not seek on the given file"), + } + Ok(()) } } diff --git a/src/dev/size.rs b/src/dev/size.rs index 301c4c2..ef3fe32 100644 --- a/src/dev/size.rs +++ b/src/dev/size.rs @@ -2,40 +2,27 @@ use core::cmp::Ordering; -use super::sector::Address; - -/// Caracterize the size of a device. -/// -/// For a device, it is the lowest non-accessible address. +/// Caracterize the size of a device in bytes. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct Size(pub Address); +pub struct Size(pub u64); -impl PartialEq
for Size { +impl PartialEq for Size { #[inline] - fn eq(&self, other: &Address) -> bool { + fn eq(&self, other: &u64) -> bool { self.0.eq(other) } } -impl PartialOrd
for Size { +impl PartialOrd for Size { #[inline] - fn partial_cmp(&self, other: &Address) -> Option { + fn partial_cmp(&self, other: &u64) -> Option { self.0.partial_cmp(other) } } -impl From
for Size { +impl From for Size { #[inline] - fn from(value: Address) -> Self { + fn from(value: u64) -> Self { Self(value) } } - -impl Size { - /// Returns the length of the device if it exists - #[inline] - #[must_use] - pub const fn len(&self) -> Address { - self.0 - } -} diff --git a/src/io.rs b/src/io.rs index 184bb6d..11d3d3f 100644 --- a/src/io.rs +++ b/src/io.rs @@ -2,6 +2,7 @@ use derive_more::{Deref, DerefMut}; +use crate::dev::error::DevError; use crate::error::Error; /// Base I/O trait that must be implemented for all types implementing [`Read`], [`Write`] or [`Seek`]. @@ -26,6 +27,31 @@ pub trait Read: Base { /// /// Returns an [`DevError`](crate::dev::error::DevError) if the device on which the directory is located could not be read. fn read(&mut self, buf: &mut [u8]) -> Result>; + + /// Read the exact number of bytes required to fill buf. + /// + /// See [`read_exact`](https://docs.rs/no_std_io/latest/no_std_io/io/trait.Read.html#method.read_exact) for more information. + /// + /// # Errors + /// + /// Returns an [`UnexpectedEof`](DevError::UnexpectedEof) if the buffer could not be entirely filled. + /// + /// Otherwise, returns the same errors as [`read`](Read::read). + #[allow(clippy::indexing_slicing)] + #[inline] + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Error> { + while !buf.is_empty() { + match self.read(buf) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + }, + Err(err) => return Err(err), + } + } + if buf.is_empty() { Ok(()) } else { Err(Error::Device(DevError::UnexpectedEof)) } + } } /// Allows for writing bytes to a destination. @@ -53,6 +79,30 @@ pub trait Write: Base { /// /// Returns an [`DevError`](crate::dev::error::DevError) if the device on which the directory is located could not be read. fn flush(&mut self) -> Result<(), Error>; + + /// Attempts to write an entire buffer into this writer. + /// + /// See [`write_all`](https://docs.rs/no_std_io/latest/no_std_io/io/trait.Write.html#method.write_all) for more information. + /// + /// # Errors + /// + /// Returns a [`WriteZero`](DevError::WriteZero) error if the buffer could not be written entirely. + /// + /// Otherwise, returns the same errors as [`write`](Write::write). + #[allow(clippy::indexing_slicing)] + #[inline] + fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Error> { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => { + return Err(Error::Device(DevError::WriteZero)); + }, + Ok(n) => buf = &buf[n..], + Err(err) => return Err(err), + } + } + Ok(()) + } } /// Enumeration of possible methods to seek within an I/O object.