From 66e7e905edf0a3b09ebcae81542173e01eee472b Mon Sep 17 00:00:00 2001 From: Rat Cornu Date: Sat, 11 Nov 2023 21:40:24 +0100 Subject: [PATCH] feat(dev): `read_at` and `write_at` given functions chore(dev): change `u32` in `Address` implementation to `usize` --- .github/workflows/build.yml | 2 +- src/dev/mod.rs | 199 ++++++++++++++++++++++++++++++++---- src/dev/sector.rs | 142 +++++++++++-------------- src/lib.rs | 1 + 4 files changed, 242 insertions(+), 102 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c03b727..374a747 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,6 @@ name: Build -on: [push, pull_request, workflow_dispatch] +on: [push, workflow_dispatch] concurrency: group: ${{ github.base_ref }}-build diff --git a/src/dev/mod.rs b/src/dev/mod.rs index f6ade6c..0ec76bc 100644 --- a/src/dev/mod.rs +++ b/src/dev/mod.rs @@ -2,9 +2,10 @@ use alloc::borrow::{Cow, ToOwned}; use alloc::boxed::Box; -use alloc::vec; use alloc::vec::Vec; +use alloc::{slice, vec}; use core::cell::RefCell; +use core::iter::Step; use core::mem; use core::ops::{Deref, DerefMut, Range}; use core::ptr::{addr_of, slice_from_raw_parts}; @@ -184,7 +185,7 @@ impl AsMut<[T]> for Commit { } /// General interface for devices containing a file system. -pub trait Device { +pub trait Device { /// Error type associated with the device. type Error: Into>; @@ -193,7 +194,7 @@ pub trait Device { /// # Errors /// /// Returns an [`Error`] if the size of the device cannot be determined. - fn size(&self) -> Result, Self::Error>; + fn size(&self) -> Size; /// Returns a [`Slice`] with elements of this device. /// @@ -208,22 +209,98 @@ pub trait Device { /// /// Returns an [`Error`](Device::Error) if the write could not be completed. fn commit(&mut self, commit: Commit) -> Result<(), Self::Error>; + + /// Read an element of type `O` on the device starting at the address `starting_addr`. + /// + /// # Errors + /// + /// Returns an [`Error`] if the read tries to go out of the device's bounds or if [`Device::slice`] failed. + /// + /// # Safety + /// + /// Must verifies the safety conditions of [`core::ptr::read`]. + #[inline] + unsafe fn read_at(&self, starting_addr: Address) -> Result> { + let length = mem::size_of::(); + let range = starting_addr + ..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() + .try_len() + .and_then(|len| len.index().into()) + .unwrap_or(S::SIZE) + .try_into() + .unwrap_unchecked(), + ), + )))?; + let slice = self.slice(range).map_err(Into::into)?; + let ptr = slice.inner.as_ptr(); + Ok(ptr.cast::().read()) + } + + /// Writes an element of type `O` on the device starting at the address `starting_addr`. + /// + /// # Errors + /// + /// Returns an [`Error`] if the read tries to go out of the device's bounds or if [`Device::slice`] or [`Device::commit`] + /// failed. + /// + /// # Safety + /// + /// Must ensure that `mem::size_of::() % mem::size_of::() == 0`. + #[inline] + unsafe fn write_at(&mut self, starting_addr: Address, object: O) -> Result<(), Error> { + let length = mem::size_of::(); + assert_eq!( + length % mem::size_of::(), + 0, + "Cannot write an element whose memory size is not divisible by the memory size of the elements of the device" + ); + assert!(length > 0, "Cannot write a 0-byte object on a device"); + let object_slice = slice::from_raw_parts(addr_of!(object).cast::(), length / mem::size_of::()); + + let range = starting_addr + ..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() + .try_len() + .and_then(|len| len.index().into()) + .unwrap_or(S::SIZE) + .try_into() + .unwrap_unchecked(), + ), + )))?; + let mut device_slice = self.slice(range).map_err(Into::into)?; + let buffer = device_slice + .get_mut(..) + .unwrap_or_else(|| unreachable!("It is always possible to take all the elements of a slice")); + buffer.copy_from_slice(object_slice); + + let commit = device_slice.commit(); + self.commit(commit).map_err(Into::into) + } } /// Generic implementation of the [`Device`] trait. macro_rules! impl_device { ($volume:ty) => { - impl Device for $volume { + impl Device for $volume { type Error = Error; #[inline] - fn size(&self) -> Result, Self::Error> { - Ok(Size::Bound(Address::try_from(self.len()).map_err(Error::Device)?)) + fn size(&self) -> Size { + Size::Bound(Address::from(self.len())) } #[inline] fn slice(&self, addr_range: Range>) -> Result, Self::Error> { - if self.size()? >= addr_range.end { + if Device::::size(self) >= addr_range.end { 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 { @@ -234,9 +311,11 @@ macro_rules! impl_device { } else { Err(Error::Device(DevError::OutOfBounds( "address", - addr_range.end.index().into(), - (0, match >::size(self)? { - Size::Bound(addr) => addr.index().into(), + // SAFETY: it is assumed that `usize` can always be converted to `i128` + unsafe { addr_range.end.index().try_into().unwrap_unchecked() }, + (0, match >::size(self) { + // SAFETY: it is assumed that `usize` can always be converted to `i128` + Size::Bound(addr) => unsafe { addr.index().try_into().unwrap_unchecked() }, Size::Unbounded => 0, }), ))) @@ -278,10 +357,10 @@ impl Device for RefCell { type Error = Error; #[inline] - fn size(&self) -> Result, Self::Error> { - let metadata = self.borrow().metadata().map_err(Error::Other)?; - let size = TryInto::>::try_into(metadata.len()).map_err(Error::Device)?; - Ok(size.into()) + 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() } #[inline] @@ -290,13 +369,14 @@ impl Device for RefCell { let len = TryInto::::try_into((addr_range.end - addr_range.start).index()).map_err(|_err| { Error::Device(DevError::OutOfBounds( "addr range", - i128::from((addr_range.end - addr_range.start).index()), - (0, usize::MAX as i128), + // SAFETY: `usize::MAX <= i128::MAX` + unsafe { i128::try_from((addr_range.end - addr_range.start).index()).unwrap_unchecked() }, + (0, i128::MAX), )) })?; let mut slice = vec![0; len]; let mut file = self.borrow_mut(); - file.seek(std::io::SeekFrom::Start(starting_addr.index())) + file.seek(std::io::SeekFrom::Start(starting_addr.index().try_into().expect("Could not convert `usize` to `u64`"))) .and_then(|_| file.read_exact(&mut slice)) .map_err(Error::Other)?; @@ -306,7 +386,7 @@ impl Device for RefCell { #[inline] fn commit(&mut self, commit: Commit) -> Result<(), Self::Error> { let mut file = self.borrow_mut(); - file.seek(std::io::SeekFrom::Start(commit.addr().index())) + 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())) .map_err(Error::Other) } @@ -315,6 +395,9 @@ impl Device for RefCell { #[cfg(test)] mod test { use core::cell::RefCell; + use core::mem::{self, size_of}; + use core::ptr::addr_of; + use core::slice; use std::fs::{self, OpenOptions}; use super::sector::Size4096; @@ -347,9 +430,7 @@ mod test { let mut file_1 = RefCell::new(OpenOptions::new().read(true).write(true).open("./tests/device_file_1_copy.txt").unwrap()); - let mut slice = file_1 - .slice(Address::::new(0, 0).unwrap()..Address::::new(0, 13).unwrap()) - .unwrap(); + let mut slice = file_1.slice(Address::::new(0, 0)..Address::::new(0, 13)).unwrap(); let word = slice.get_mut(6..=10).unwrap(); word[0] = b'e'; @@ -370,4 +451,80 @@ mod test { fs::remove_file("./tests/device_file_1_copy.txt").unwrap(); } + + #[test] + fn device_read() { + const OFFSET: usize = 123; + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct Test { + nb_1: u16, + nb_2: u8, + nb_3: usize, + nb_4: u128, + } + + let test = Test { + nb_1: 0xabcd, + nb_2: 0x99, + nb_3: 0x1234, + nb_4: 0x1234_5678_90ab_cdef, + }; + let test_bytes = unsafe { slice::from_raw_parts(addr_of!(test).cast::(), mem::size_of::()) }; + + let mut device = vec![0_u8; 1024]; + let mut slice = Device::::slice( + &device, + Address::::try_from(OFFSET).unwrap()..Address::::try_from(OFFSET + size_of::()).unwrap(), + ) + .unwrap(); + let buffer = slice.get_mut(..).unwrap(); + buffer.clone_from_slice(test_bytes); + + let commit = slice.commit(); + Device::::commit(&mut device, commit).unwrap(); + + let read_test = unsafe { + Device::::read_at::(&device, Address::::try_from(OFFSET).unwrap()) + .unwrap() + }; + assert_eq!(test, read_test); + } + + #[test] + fn device_write() { + const OFFSET: u64 = 123; + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct Test { + nb_1: u16, + nb_2: u8, + nb_3: usize, + nb_4: u128, + } + + let test = Test { + nb_1: 0xabcd, + nb_2: 0x99, + nb_3: 0x1234, + nb_4: 0x1234_5678_90ab_cdef, + }; + let test_bytes = unsafe { slice::from_raw_parts(addr_of!(test).cast::(), mem::size_of::()) }; + + let mut device = vec![0_u8; 1024]; + unsafe { + Device::::write_at(&mut device, Address::::try_from(OFFSET).unwrap(), test) + .unwrap(); + }; + + let slice = Device::::slice( + &device, + Address::try_from(OFFSET).unwrap()..Address::try_from(OFFSET + mem::size_of::() as u64).unwrap(), + ) + .unwrap(); + + assert_eq!(test_bytes, slice.get(..).unwrap()); + } } diff --git a/src/dev/sector.rs b/src/dev/sector.rs index 1906a1e..b9bb0e9 100644 --- a/src/dev/sector.rs +++ b/src/dev/sector.rs @@ -6,18 +6,16 @@ use core::iter::Step; use core::marker::PhantomData; use core::ops::{Add, Sub}; -use super::error::DevError; - /// General interface for sector sizes that are device-dependant. pub trait Sector: Clone + Copy + PartialEq + Eq { /// Logarithm in base 2 of the sector size. - const LOG_SIZE: u32; + const LOG_SIZE: usize; /// Size of a sector. - const SIZE: u32 = 1 << Self::LOG_SIZE; + const SIZE: usize = 1 << Self::LOG_SIZE; /// Offset mask of the sector size. - const OFFSET_MASK: u32 = Self::SIZE - 1; + const OFFSET_MASK: usize = Self::SIZE - 1; } /// Size sector of 512 bytes. @@ -25,7 +23,7 @@ pub trait Sector: Clone + Copy + PartialEq + Eq { pub struct Size512; impl Sector for Size512 { - const LOG_SIZE: u32 = 9; + const LOG_SIZE: usize = 9; } /// Size sector of 1024 bytes. @@ -33,7 +31,7 @@ impl Sector for Size512 { pub struct Size1024; impl Sector for Size1024 { - const LOG_SIZE: u32 = 10; + const LOG_SIZE: usize = 10; } /// Size sector of 2048 bytes. @@ -41,7 +39,7 @@ impl Sector for Size1024 { pub struct Size2048; impl Sector for Size2048 { - const LOG_SIZE: u32 = 11; + const LOG_SIZE: usize = 11; } /// Size sector of 4096 bytes. @@ -49,17 +47,17 @@ impl Sector for Size2048 { pub struct Size4096; impl Sector for Size4096 { - const LOG_SIZE: u32 = 12; + const LOG_SIZE: usize = 12; } /// Address of a physical sector #[derive(Clone, Copy, PartialEq, Eq)] pub struct Address { /// Sector in which the address is located. - sector: u32, + sector: usize, /// Offset of this address in the sector. - offset: u32, + offset: usize, /// Phantom data to store the sector size. _phantom: PhantomData, @@ -115,85 +113,72 @@ impl LowerHex for Address { impl Address { /// Returns a new [`Address`] with an offset such that `0 <= offset < S::SIZE` and a positive sector. /// - /// # Errors - /// - /// Returns an [`DevError`] if the given `sector` and the given `offset` does not correspond to a valid address. - /// /// # Panics /// - /// Panics if `S::SIZE` is greater than `i32::MAX`, which is equivalent to `S::LOG_SIZE >= 16`. + /// This will panic if the given address is below the [`Address`] with sector and offset equal to 0. #[inline] - pub fn new(sector: u32, offset: i32) -> Result { - let real_signed_sector = TryInto::::try_into(sector) - .map_err(|_err| DevError::OutOfBounds("sector", sector.into(), (0_i128, 0x1000_i128)))? - + (offset >> S::LOG_SIZE); - let real_sector = TryInto::::try_into(real_signed_sector) - .map_err(|_err| DevError::OutOfBounds("sector", real_signed_sector.into(), (0_i128, u32::MAX.into())))?; + #[must_use] + pub fn new(sector: usize, offset: isize) -> Self { + let real_sector = usize::try_from( + TryInto::::try_into(sector).expect("Could not convert `sector` to `isize`") + (offset >> S::LOG_SIZE), + ) + .expect("The given address is below the address with sector and offset equal to 0"); let real_offset = offset - // SAFETY: it is checked at compile time that `S::SIZE < u32::MAX`, so as `S::SIZE` is a power of 2, then `S::SIZE <= + // SAFETY: it is checked at compile time that `S::SIZE < usize::MAX`, so as `S::SIZE` is a power of 2, then `S::SIZE <= // i32::MAX` - .rem_euclid(unsafe { TryInto::::try_into(S::SIZE).unwrap_unchecked() }) + .rem_euclid(unsafe { S::SIZE.try_into().unwrap_unchecked() }) .unsigned_abs() & S::OFFSET_MASK; - if real_offset >= S::SIZE { - Err(DevError::OutOfBounds("offset", real_offset.into(), (0_i128, S::SIZE.into()))) - } else { - Ok(Self { - sector: real_sector, - offset: real_offset, - _phantom: PhantomData, - }) + + Self { + sector: real_sector, + offset: real_offset, + _phantom: PhantomData, } } /// Returns the sector containing this address. #[inline] #[must_use] - pub const fn sector(&self) -> u32 { + pub const fn sector(&self) -> usize { self.sector } /// Returns the offset of this address in its sector. #[inline] #[must_use] - pub const fn offset(&self) -> u32 { + pub const fn offset(&self) -> usize { self.offset } /// Returns the index of this address, which corresponds to its offset from the start of the device. #[inline] #[must_use] - pub const fn index(&self) -> u64 { - ((self.sector() as u64) << S::LOG_SIZE) + self.offset() as u64 + pub const fn index(&self) -> usize { + (self.sector() << S::LOG_SIZE) + self.offset() } } -impl TryFrom for Address { - type Error = DevError; - +impl From for Address { #[inline] - fn try_from(value: u64) -> Result { + fn from(value: usize) -> Self { Self::new( - TryInto::::try_into(value >> S::LOG_SIZE) - .map_err(|_err| DevError::OutOfBounds("sector", value.into(), (0_i128, u32::MAX.into())))?, - TryInto::::try_into(value & u64::from(S::OFFSET_MASK)) - .unwrap_or_else(|_| unreachable!("`S::OFFSET_MASK <= u32::MAX` is checked at compile time")), + // SAFETY: `S::SIZE` cannot be equal to 0 + unsafe { value.checked_div(S::SIZE).unwrap_unchecked() }, + // SAFETY: `S::SIZE` cannot be equal to 0 + unsafe { value.checked_rem(S::SIZE).unwrap_unchecked() } + .try_into() + .unwrap_or_else(|_| unreachable!()), ) } } -impl TryFrom for Address { - type Error = DevError; +impl TryFrom for Address { + type Error = >::Error; #[inline] - fn try_from(value: usize) -> Result { - Self::new( - TryInto::::try_into(value >> S::LOG_SIZE).map_err(|_err| { - DevError::OutOfBounds("sector", value.try_into().unwrap_or_else(|_| unreachable!()), (0_i128, u32::MAX.into())) - })?, - TryInto::::try_into(value & usize::try_from(S::OFFSET_MASK).unwrap_or_else(|_| unreachable!())) - .unwrap_or_else(|_| unreachable!("`S::OFFSET_MASK <= u32::MAX` is checked at compile time")), - ) + fn try_from(value: u64) -> Result { + Ok(Self::from(TryInto::::try_into(value)?)) } } @@ -202,11 +187,8 @@ impl Add for Address { #[inline] fn add(self, rhs: Self) -> Self::Output { - Self::new( - self.sector() + rhs.sector(), - TryInto::::try_into(self.offset() + rhs.offset()).expect("offset addition overflows"), - ) - .expect("addresses addition overflows") + // SAFETY: `offset` returns a value smaller that `S::SIZE` + Self::new(self.sector() + rhs.sector(), unsafe { (self.offset() + rhs.offset()).try_into().unwrap_unchecked() }) } } @@ -217,18 +199,18 @@ impl Sub for Address { fn sub(self, rhs: Self) -> Self::Output { Self::new( self.sector() - rhs.sector(), - TryInto::::try_into(self.offset()).expect("Could not cast offset to an i32") - - TryInto::::try_into(rhs.offset()).expect("Could not cast offset to an i32"), + // SAFETY: `offset` returns a value smaller that `S::SIZE` + unsafe { TryInto::::try_into(self.offset()).unwrap_unchecked() } + // SAFETY: `offset` returns a value smaller that `S::SIZE` + - unsafe { TryInto::::try_into(rhs.offset()).unwrap_unchecked() }, ) - .expect("offset substraction overflows") } } impl Step for Address { #[inline] fn steps_between(start: &Self, end: &Self) -> Option { - // SAFETY: it is not possible to manipulate addresses with a higher bit number than the device's - (start.sector() <= end.sector()).then_some(unsafe { (end.index() - start.index()).try_into().unwrap_unchecked() }) + (start.sector() <= end.sector()).then_some(end.index() - start.index()) } #[inline] @@ -257,31 +239,31 @@ mod test { #[test] fn addresses_manipulation() { - assert_eq!(Address::::new(1, 0).unwrap(), Address::::new(0, 4096).unwrap()); - assert_eq!(Address::::new(1, -1000).unwrap(), Address::::new(0, 3096).unwrap()); - assert_eq!(Address::::new(4, 0).unwrap(), Address::::new(2, 8192).unwrap()); - assert_eq!(Address::::new(4, -5000).unwrap(), Address::::new(2, 3192).unwrap()); - - let address_1 = Address::::new(1, 1024).unwrap(); - let address_2 = Address::::new(3, 1024).unwrap(); - let address_3 = Address::::new(4, 2048).unwrap(); + assert_eq!(Address::::new(1, 0), Address::::new(0, 4096)); + assert_eq!(Address::::new(1, -1000), Address::::new(0, 3096)); + assert_eq!(Address::::new(4, 0), Address::::new(2, 8192)); + assert_eq!(Address::::new(4, -5000), Address::::new(2, 3192)); + + let address_1 = Address::::new(1, 1024); + let address_2 = Address::::new(3, 1024); + let address_3 = Address::::new(4, 2048); assert_eq!(address_1 + address_2, address_3); assert_eq!(address_3 - address_1, address_2); - let address_1 = Address::::new(1, 2048).unwrap(); - let address_2 = Address::::new(3, 3048).unwrap(); - let address_3 = Address::::new(5, 1000).unwrap(); + let address_1 = Address::::new(1, 2048); + let address_2 = Address::::new(3, 3048); + let address_3 = Address::::new(5, 1000); assert_eq!(address_1 + address_2, address_3); assert_eq!(address_3 - address_2, address_1); } #[test] fn indices() { - assert_eq!(Address::::new(2, 0).unwrap().index(), 1024); - assert_eq!(Address::::new(4, 1000).unwrap().index(), 3048); - assert_eq!(Address::::new(4, 1000).unwrap().index(), 5096); + assert_eq!(Address::::new(2, 0).index(), 1024); + assert_eq!(Address::::new(4, 1000).index(), 3048); + assert_eq!(Address::::new(4, 1000).index(), 5096); - assert_eq!(Address::::try_from(5_096_u64).unwrap(), Address::new(4, 1000).unwrap()); - assert_eq!(Address::::try_from(3_048_usize).unwrap(), Address::new(5, 488).unwrap()); + assert_eq!(Address::::try_from(5_096_u64).unwrap(), Address::new(4, 1000)); + assert_eq!(Address::::from(3_048_usize), Address::new(5, 488)); } } diff --git a/src/lib.rs b/src/lib.rs index 2288c92..70312bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ clippy::indexing_slicing, clippy::non_ascii_literal, clippy::too_many_lines, + clippy::undocumented_unsafe_blocks, clippy::unwrap_used, clippy::wildcard_imports )