WIP: Remove UB by using closures

This commit is contained in:
Nicolas Stalder
2021-01-31 13:39:04 +01:00
committed by Nicolas Stalder
parent 274a8dfef7
commit 45d8faacd4
8 changed files with 1487 additions and 55 deletions
+9 -4
View File
@@ -1,7 +1,7 @@
[package]
name = "littlefs2"
description = "Idiomatic Rust API for littlefs"
version = "0.1.0-alpha.0"
version = "0.1.0-alpha.1"
authors = ["Nicolas Stalder <n@stalder.io>", "Brandon Edens <brandonedens@gmail.com>"]
edition = "2018"
license = "Apache-2.0 OR MIT"
@@ -10,13 +10,14 @@ categories = ["embedded", "filesystem", "no-std"]
repository = "https://github.com/nickray/littlefs2"
[dependencies]
# aligned = "0.3.2"
aligned = { version = "0.3.2", optional = true }
bitflags = "1.0.4"
# cortex-m-semihosting = "0.3.5"
# cstr_core = "0.1.2"
cty = "0.2.1"
generic-array = "0.13.2"
# heapless = "0.5.1"
heapless-bytes = { path = "../heapless-bytes", optional = true }
# Listed as regular dependency behind feature flag,
# since dev-dependencies cannot be optional, and we
@@ -24,12 +25,12 @@ generic-array = "0.13.2"
# and test the `no_std` situation mainly.
# Run UI tests with `cargo test --features ui-tests`.
trybuild = { version = "1.0", optional = true }
ufmt = "0.1.0"
[dependencies.littlefs2-sys]
version = "0.1.2"
version = "0.1.3"
# git = "https://github.com/nickray/littlefs2-sys"
# branch = "main"
# path = "../littlefs2-sys"
[dev-dependencies]
desse = "0.2.1"
@@ -42,6 +43,10 @@ serde = { version = "1.0", default-features = false, features = ["derive"] }
# serde-json-core = { version = "0.1.0" }
[features]
# default = ["closures"]
# use experimental closure-based API
closures = ["aligned", "heapless-bytes"]
dir-entry-path = []
# enable assertions in backend C code
ll-assertions = ["littlefs2-sys/assertions"]
# enable trace in backend C code
+2
View File
@@ -1,4 +1,5 @@
//! The `Storage`, `Read`, `Write` and `Seek` driver.
#![allow(non_camel_case_types)]
use generic_array::ArrayLength;
use littlefs2_sys as ll;
@@ -56,6 +57,7 @@ pub trait Storage {
/// Hence, we further restrict `LOOKAHEAD_SIZE` to be a multiple of 32.
/// Our LOOKAHEADWORDS_SIZE is this multiple.
type LOOKAHEADWORDS_SIZE: ArrayLength<u32>;
// type LOOKAHEAD_SIZE: ArrayLength<u8>;
/// Maximum length of a filename plus one. Stored in superblock.
/// Should default to 255+1, but associated type defaults don't exist currently.
+135 -44
View File
@@ -16,6 +16,7 @@ use crate::{
self,
Error,
Result,
SeekFrom,
},
path::{
Filename,
@@ -27,7 +28,7 @@ use crate::{
// use aligned::{A4, Aligned};
use bitflags::bitflags;
use littlefs2_sys as ll;
pub use littlefs2_sys as ll;
use generic_array::{
GenericArray,
@@ -86,6 +87,15 @@ where
Storage: 'alloc,
{
pub fn within<R>(
&mut self,
f: impl FnOnce(&mut Filesystem<'_, Storage>, &mut Storage) -> io::Result<R>,
)
-> Result<R>
{
f(&mut Filesystem { alloc: self.alloc }, self.storage)
}
pub fn mount(
alloc: &'alloc mut FilesystemAllocation<Storage>,
storage: &'storage mut Storage,
@@ -173,6 +183,95 @@ where
Error::result_from(return_code).map(|_| info.into())
}
/// Returns a pseudo-iterator over the entries within a directory.
pub fn read_dir(
&mut self,
path: impl Into<Path<Storage>>,
) ->
Result<ReadDir<Storage>>
{
let mut read_dir = ReadDir {
state: unsafe { mem::MaybeUninit::zeroed().assume_init() },
_storage: PhantomData,
};
let return_code = unsafe {
ll::lfs_dir_open(
&mut self.alloc.state,
&mut read_dir.state,
&path.into() as *const _ as *const cty::c_char,
)
};
Error::result_from(return_code).map(|_| read_dir)
}
/// Read attribute.
pub fn attribute(
&mut self,
path: impl Into<Path<Storage>>,
id: u8,
) ->
Result<Option<Attribute<Storage>>>
{
let mut attribute = Attribute::new(id);
let attr_max = <Storage as driver::Storage>::ATTRBYTES_MAX::to_u32();
let return_code = unsafe { ll::lfs_getattr(
&mut self.alloc.state,
&path.into() as *const _ as *const cty::c_char,
id,
&mut attribute.data as *mut _ as *mut cty::c_void,
attr_max,
) };
if return_code >= 0 {
attribute.size = cmp::min(attr_max, return_code as u32) as usize;
return Ok(Some(attribute));
}
if return_code == ll::lfs_error_LFS_ERR_NOATTR {
return Ok(None)
}
Error::result_from(return_code)?;
// TODO: get rid of this
unreachable!();
}
/// Remove attribute.
pub fn remove_attribute(
&mut self,
path: impl Into<Path<Storage>>,
id: u8,
) -> Result<()> {
let return_code = unsafe { ll::lfs_removeattr(
&mut self.alloc.state,
&path.into() as *const _ as *const cty::c_char,
id,
) };
Error::result_from(return_code)
}
/// Set attribute.
pub fn set_attribute(
&mut self,
path: impl Into<Path<Storage>>,
attribute: &Attribute<Storage>
) ->
Result<()>
{
let return_code = unsafe { ll::lfs_setattr(
&mut self.alloc.state,
&path.into() as *const _ as *const cty::c_char,
attribute.id,
&attribute.data as *const _ as *const cty::c_void,
attribute.size as u32,
) };
Error::result_from(return_code)
}
}
@@ -186,9 +285,9 @@ where
let read_size: u32 = Storage::READ_SIZE as _;
let write_size: u32 = Storage::WRITE_SIZE as _;
let block_size: u32 = Storage::BLOCK_SIZE as _;
let cache_size: u32 = <Storage as driver::Storage>::CACHE_SIZE::to_u32();
let cache_size: u32 = <Storage as driver::Storage>::CACHE_SIZE::U32;
let lookahead_size: u32 =
32 * <Storage as driver::Storage>::LOOKAHEADWORDS_SIZE::to_u32();
32 * <Storage as driver::Storage>::LOOKAHEADWORDS_SIZE::U32;
let block_cycles: i32 = Storage::BLOCK_CYCLES as _;
let block_count: u32 = Storage::BLOCK_COUNT as _;
@@ -1012,6 +1111,7 @@ impl OpenOptions {
S: driver::Storage,
{
// fs.alloc.config.context = storage as *mut _ as *mut cty::c_void;
// fs_with.alloc.config.context = fs_with.storage as *mut _ as *mut cty::c_void;
alloc.config.buffer = alloc.cache.as_mut_slice() as *mut _ as *mut cty::c_void;
let return_code = unsafe { ll::lfs_file_opencfg(
@@ -1028,36 +1128,6 @@ impl OpenOptions {
}
}
/** Enumeration of possible methods to seek within an I/O object.
Use the [`Seek`](../io/trait.Seek.html) trait.
*/
#[derive(Clone,Copy,Debug,Eq,PartialEq)]
pub enum SeekFrom {
Start(u32),
End(i32),
Current(i32),
}
impl SeekFrom {
pub(crate) fn off(self) -> i32 {
match self {
SeekFrom::Start(u) => u as i32,
SeekFrom::End(i) => i,
SeekFrom::Current(i) => i,
}
}
pub(crate) fn whence(self) -> i32 {
match self {
SeekFrom::Start(_) => 0,
SeekFrom::End(_) => 2,
SeekFrom::Current(_) => 1,
}
}
}
// /// The state of a `Dir`. Pre-allocate with `File::allocate()`.
// pub struct DirAllocation
// {
@@ -1117,6 +1187,13 @@ where
fs_with: &'fs mut FilesystemWith<'fsalloc, 'storage, S>,
}
impl<S: driver::Storage> Drop for FileWith<'_, '_, '_, '_, S> {
fn drop(&mut self) {
self.nonconsuming_close_for_drop().expect("could not close FileWith");
}
}
impl<'falloc, 'fs, 'fsalloc, 'storage, S> FileWith<'falloc, 'fs, 'fsalloc, 'storage, S>
where
S: driver::Storage,
@@ -1165,9 +1242,20 @@ where
Error::result_from(return_code)
}
pub fn nonconsuming_close_for_drop(&mut self) ->
Result<()>
{
// fs.alloc.config.context = storage as *mut _ as *mut cty::c_void;
let return_code = unsafe { ll::lfs_file_close(
&mut self.fs_with.alloc.state,
&mut self.alloc.state,
) };
Error::result_from(return_code)
}
/// Synchronize file contents to storage.
pub fn sync(&mut self) -> Result<()> {
// assert!(self.fs_with.alloc.config.context == self.fs_with.storage as *mut _ as *mut cty::c_void);
assert!(self.fs_with.alloc.config.context == self.fs_with.storage as *mut _ as *mut cty::c_void);
// fs.alloc.config.context = storage as *mut _ as *mut cty::c_void;
let return_code = unsafe { ll::lfs_file_sync(
&mut self.fs_with.alloc.state,
@@ -1235,17 +1323,20 @@ where
let cache_size: u32 = <S as driver::Storage>::CACHE_SIZE::to_u32();
debug_assert!(cache_size > 0);
let config = ll::lfs_file_config {
buffer: core::ptr::null_mut(),
attrs: core::ptr::null_mut(),
attr_count: 0,
};
// let config = ll::lfs_file_config {
// buffer: core::ptr::null_mut(),
// attrs: core::ptr::null_mut(),
// attr_count: 0,
// };
FileAllocation {
cache: Default::default(),
state: unsafe { mem::MaybeUninit::zeroed().assume_init() },
config,
}
// FileAllocation {
// cache: Default::default(),
// state: unsafe { mem::MaybeUninit::zeroed().assume_init() },
// config,
// }
// does not help with our variation on https://github.com/ARMmbed/littlefs/issues/145
unsafe { mem::MaybeUninit::zeroed().assume_init() }
}
pub fn open<'fsalloc: 'falloc>(
+1159
View File
File diff suppressed because it is too large Load Diff
+44 -2
View File
@@ -3,11 +3,11 @@
pub mod prelude;
use littlefs2_sys as ll;
use ufmt::derive::uDebug;
use crate::{
fs::{
Filesystem,
SeekFrom,
},
driver::Storage,
};
@@ -108,6 +108,48 @@ pub trait WriteWith {
/// Write out all pending writes to storage.
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
while !buf.is_empty() {
match self.write(buf) {
Ok(0) => {
// failed to write whole buffer
return Err(Error::Io)
}
Ok(n) => buf = &buf[n..],
Err(e) => return Err(e),
}
}
Ok(())
}
}
/** Enumeration of possible methods to seek within an I/O object.
Use the [`Seek`](../io/trait.Seek.html) trait.
*/
#[derive(Clone,Copy,Debug,Eq,PartialEq)]
pub enum SeekFrom {
Start(u32),
End(i32),
Current(i32),
}
impl SeekFrom {
pub(crate) fn off(self) -> i32 {
match self {
SeekFrom::Start(u) => u as i32,
SeekFrom::End(i) => i,
SeekFrom::Current(i) => i,
}
}
pub(crate) fn whence(self) -> i32 {
match self {
SeekFrom::Start(_) => 0,
SeekFrom::End(_) => 2,
SeekFrom::Current(_) => 1,
}
}
}
/** The `Seek` trait provides a cursor which can be moved within a file.
@@ -135,7 +177,7 @@ pub trait SeekWith {
pub type Result<T> = core::result::Result<T, Error>;
/// Definition of errors that might be returned by filesystem functionality.
#[derive(Clone,Copy,Debug,PartialEq)]
#[derive(Clone,Copy,Debug,PartialEq,uDebug)]
pub enum Error {
/// Input / output error occurred.
Io,
+2
View File
@@ -114,6 +114,8 @@ pub mod macros;
pub mod driver;
pub mod fs;
#[cfg(feature = "closures")]
pub mod fsc;
pub mod io;
pub mod path;
+99 -2
View File
@@ -20,7 +20,7 @@ macro_rules! ram_storage { (
result=$Result:ident,
) => {
struct $Backend {
pub struct $Backend {
buf: [u8; $block_size * $block_count],
}
@@ -32,7 +32,7 @@ macro_rules! ram_storage { (
}
}
struct $Name<'backend> {
pub struct $Name<'backend> {
backend: &'backend mut $Backend,
}
@@ -137,3 +137,100 @@ macro_rules! ram_storage { (
);
};
}
#[macro_export]
macro_rules! const_ram_storage { (
name=$Name:ident,
trait=$StorageTrait:path,
erase_value=$erase_value:expr,
read_size=$read_size:expr,
write_size=$write_size:expr,
cache_size_ty=$cache_size:path,
block_size=$block_size:expr,
block_count=$block_count:expr,
lookaheadwords_size_ty=$lookaheadwords_size:path,
filename_max_plus_one_ty=$filename_max_plus_one:path,
path_max_plus_one_ty=$path_max_plus_one:path,
result=$Result:ident,
) => {
pub struct $Name {
buf: [u8; $block_size * $block_count],
}
impl $Name {
const ERASE_VALUE: u8 = $erase_value;
pub const fn new() -> Self {
// Self::default()
Self { buf: [$erase_value; $block_size * $block_count] }
}
}
impl Default for $Name {
fn default() -> Self {
Self {
buf: [$erase_value; $block_size * $block_count],
}
}
}
impl $StorageTrait for $Name {
const READ_SIZE: usize = $read_size;
const WRITE_SIZE: usize = $write_size;
type CACHE_SIZE = $cache_size;
const BLOCK_SIZE: usize = $block_size;
const BLOCK_COUNT: usize = $block_count;
type LOOKAHEADWORDS_SIZE = $lookaheadwords_size;
type FILENAME_MAX_PLUS_ONE = $filename_max_plus_one;
type PATH_MAX_PLUS_ONE = $path_max_plus_one;
type ATTRBYTES_MAX = consts::U1022;
fn read(&self, offset: usize, buf: &mut [u8]) -> $Result<usize> {
let read_size: usize = Self::READ_SIZE;
debug_assert!(offset % read_size == 0);
debug_assert!(buf.len() % read_size == 0);
for (from, to) in self.buf[offset..].iter().zip(buf.iter_mut()) {
*to = *from;
}
Ok(buf.len())
}
fn write(&mut self, offset: usize, data: &[u8]) -> $Result<usize> {
let write_size: usize = Self::WRITE_SIZE;
debug_assert!(offset % write_size == 0);
debug_assert!(data.len() % write_size == 0);
for (from, to) in data.iter().zip(self.buf[offset..].iter_mut()) {
*to = *from;
}
Ok(data.len())
}
fn erase(&mut self, offset: usize, len: usize) -> $Result<usize> {
let block_size: usize = Self::BLOCK_SIZE;
debug_assert!(offset % block_size == 0);
debug_assert!(len % block_size == 0);
for byte in self.buf[offset..offset + len].iter_mut() {
*byte = Self::ERASE_VALUE;
}
Ok(len)
}
}
};
($Name:ident, $bytes:expr) => {
const_ram_storage!(
name=$Name,
trait=LfsStorage,
erase_value=0xff,
read_size=16,
write_size=512,
cache_size_ty=consts::U512,
block_size=512,
block_count=$bytes/512,
lookaheadwords_size_ty=consts::U1,
filename_max_plus_one_ty=consts::U256,
path_max_plus_one_ty=consts::U256,
result=LfsResult,
);
};
}
+37 -3
View File
@@ -66,19 +66,23 @@ where
// pub fn new<F: AsRef<[u8]> + ?Sized>(f: &F) -> Self {
pub fn new(f: &[u8]) -> Self {
let mut padded_filename: GenericArray<u8, S::FILENAME_MAX_PLUS_ONE> = Default::default();
let name_max = <S as driver::Storage>::FILENAME_MAX_PLUS_ONE::to_usize();
let name_max = <S as driver::Storage>::FILENAME_MAX_PLUS_ONE::USIZE;
// let len = cmp::min(name_max - 1, f.as_ref().len());
// padded_filename[..len].copy_from_slice(&f.as_ref()[..len]);
let len = cmp::min(name_max - 1, f.len());
padded_filename[..len].copy_from_slice(&f[..len]);
Filename(padded_filename)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
/// A slice of a specification of the location of a [`File`](../fs/struct.File.html).
///
/// This module is rather incomplete, compared to `std::path`.
pub struct Path<S> (GenericArray<u8, S::PATH_MAX_PLUS_ONE>)
pub struct Path<S> (pub(crate) GenericArray<u8, S::PATH_MAX_PLUS_ONE>)
where
S: driver::Storage,
<S as driver::Storage>::PATH_MAX_PLUS_ONE: ArrayLength<u8>,
@@ -97,6 +101,16 @@ where
}
}
impl<S> PartialEq for Path<S>
where
S: driver::Storage,
<S as driver::Storage>::PATH_MAX_PLUS_ONE: ArrayLength<u8>,
{
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
// to make `Metadata` Debug
impl<S> fmt::Debug for Path<S>
where
@@ -116,7 +130,7 @@ where
/// Silently truncates to maximum configured path length
pub fn new<P: AsRef<[u8]> + ?Sized>(p: &P) -> Self {
let mut padded_path: GenericArray<u8, S::PATH_MAX_PLUS_ONE> = Default::default();
let name_max = <S as driver::Storage>::PATH_MAX_PLUS_ONE::to_usize();
let name_max = <S as driver::Storage>::PATH_MAX_PLUS_ONE::USIZE;
let len = cmp::min(name_max - 1, p.as_ref().len());
padded_path[..len].copy_from_slice(&p.as_ref()[..len]);
Path(padded_path)
@@ -133,6 +147,26 @@ where
pub fn has_root(&self) -> bool {
self.0.len() > 0 && self.0[0] == b'/'
}
// what to do about possible "array-too-small" errors?
// what does littlefs actually do?
// one option would be:
//
// enum Path {
// NotTruncated(RawPath),
// Truncated(RawPath),
// }
//
// impl Deref<RawPath> for Path { ... }
//
// that is, never fail, but tag if truncation was necessary
// this way, no need to do error handling for the rare cases,
// but can still detect them
// pub fn join<P: AsRef<Path>>(&self, path: P) -> Path {
// }
// pub fn try_join<P: AsRef<Path>>(&self, path: P) -> Result<Path> {
// }
}
impl<S> From<&str> for Path<S>