Skip to content
This repository has been archived by the owner on Mar 15, 2024. It is now read-only.

Commit

Permalink
feat(dev): read_at and write_at given functions
Browse files Browse the repository at this point in the history
chore(dev): change `u32` in `Address` implementation to `usize`
  • Loading branch information
Rat Cornu committed Nov 11, 2023
1 parent 5d7f88c commit 66e7e90
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 102 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Build

on: [push, pull_request, workflow_dispatch]
on: [push, workflow_dispatch]

concurrency:
group: ${{ github.base_ref }}-build
Expand Down
199 changes: 178 additions & 21 deletions src/dev/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -184,7 +185,7 @@ impl<T: Clone, S: Sector> AsMut<[T]> for Commit<T, S> {
}

/// General interface for devices containing a file system.
pub trait Device<T: Clone, S: Sector, E: core::error::Error> {
pub trait Device<T: Copy, S: Sector, E: core::error::Error> {
/// Error type associated with the device.
type Error: Into<Error<E>>;

Expand All @@ -193,7 +194,7 @@ pub trait Device<T: Clone, S: Sector, E: core::error::Error> {
/// # Errors
///
/// Returns an [`Error`] if the size of the device cannot be determined.
fn size(&self) -> Result<Size<S>, Self::Error>;
fn size(&self) -> Size<S>;

/// Returns a [`Slice`] with elements of this device.
///
Expand All @@ -208,22 +209,98 @@ pub trait Device<T: Clone, S: Sector, E: core::error::Error> {
///
/// Returns an [`Error`](Device::Error) if the write could not be completed.
fn commit(&mut self, commit: Commit<T, S>) -> 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<O: Copy>(&self, starting_addr: Address<S>) -> Result<O, Error<E>> {
let length = mem::size_of::<O>();
let range = starting_addr
..Address::<S>::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::<O>().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::<O>() % mem::size_of::<T>() == 0`.
#[inline]
unsafe fn write_at<O: Copy>(&mut self, starting_addr: Address<S>, object: O) -> Result<(), Error<E>> {
let length = mem::size_of::<O>();
assert_eq!(
length % mem::size_of::<T>(),
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::<T>(), length / mem::size_of::<T>());

let range = starting_addr
..Address::<S>::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<T: Clone, S: Sector, E: core::error::Error> Device<T, S, E> for $volume {
impl<T: Copy, S: Sector, E: core::error::Error> Device<T, S, E> for $volume {
type Error = Error<E>;

#[inline]
fn size(&self) -> Result<Size<S>, Self::Error> {
Ok(Size::Bound(Address::try_from(self.len()).map_err(Error::Device)?))
fn size(&self) -> Size<S> {
Size::Bound(Address::from(self.len()))
}

#[inline]
fn slice(&self, addr_range: Range<Address<S>>) -> Result<Slice<'_, T, S>, Self::Error> {
if self.size()? >= addr_range.end {
if Device::<T, S, E>::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 {
Expand All @@ -234,9 +311,11 @@ macro_rules! impl_device {
} else {
Err(Error::Device(DevError::OutOfBounds(
"address",
addr_range.end.index().into(),
(0, match <Self as Device<T, S, E>>::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 <Self as Device<T, S, E>>::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,
}),
)))
Expand Down Expand Up @@ -278,10 +357,10 @@ impl<S: Sector> Device<u8, S, std::io::Error> for RefCell<File> {
type Error = Error<std::io::Error>;

#[inline]
fn size(&self) -> Result<Size<S>, Self::Error> {
let metadata = self.borrow().metadata().map_err(Error::Other)?;
let size = TryInto::<Address<S>>::try_into(metadata.len()).map_err(Error::Device)?;
Ok(size.into())
fn size(&self) -> Size<S> {
let metadata = self.borrow().metadata().expect("Could not read the file");
let size = TryInto::<Address<S>>::try_into(metadata.len()).expect("Could not convert `usize` to `u64`");
size.into()
}

#[inline]
Expand All @@ -290,13 +369,14 @@ impl<S: Sector> Device<u8, S, std::io::Error> for RefCell<File> {
let len = TryInto::<usize>::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)?;

Expand All @@ -306,7 +386,7 @@ impl<S: Sector> Device<u8, S, std::io::Error> for RefCell<File> {
#[inline]
fn commit(&mut self, commit: Commit<u8, S>) -> 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)
}
Expand All @@ -315,6 +395,9 @@ impl<S: Sector> Device<u8, S, std::io::Error> for RefCell<File> {
#[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;
Expand Down Expand Up @@ -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::<Size4096>::new(0, 0).unwrap()..Address::<Size4096>::new(0, 13).unwrap())
.unwrap();
let mut slice = file_1.slice(Address::<Size4096>::new(0, 0)..Address::<Size4096>::new(0, 13)).unwrap();

let word = slice.get_mut(6..=10).unwrap();
word[0] = b'e';
Expand All @@ -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::<u8>(), mem::size_of::<Test>()) };

let mut device = vec![0_u8; 1024];
let mut slice = Device::<u8, Size4096, std::io::Error>::slice(
&device,
Address::<Size4096>::try_from(OFFSET).unwrap()..Address::<Size4096>::try_from(OFFSET + size_of::<Test>()).unwrap(),
)
.unwrap();
let buffer = slice.get_mut(..).unwrap();
buffer.clone_from_slice(test_bytes);

let commit = slice.commit();
Device::<u8, Size4096, std::io::Error>::commit(&mut device, commit).unwrap();

let read_test = unsafe {
Device::<u8, Size4096, std::io::Error>::read_at::<Test>(&device, Address::<Size4096>::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::<u8>(), mem::size_of::<Test>()) };

let mut device = vec![0_u8; 1024];
unsafe {
Device::<u8, Size4096, std::io::Error>::write_at(&mut device, Address::<Size4096>::try_from(OFFSET).unwrap(), test)
.unwrap();
};

let slice = Device::<u8, Size4096, std::io::Error>::slice(
&device,
Address::try_from(OFFSET).unwrap()..Address::try_from(OFFSET + mem::size_of::<Test>() as u64).unwrap(),
)
.unwrap();

assert_eq!(test_bytes, slice.get(..).unwrap());
}
}
Loading

0 comments on commit 66e7e90

Please sign in to comment.