From a059198b2bc8b5fd0962f577f44947b526dd6031 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 23 Nov 2023 09:46:45 +0100 Subject: [PATCH 1/2] chunked: Add PartialReadFile syscall --- CHANGELOG.md | 1 + src/streaming/mod.rs | 80 ++++++++++++++++++++++++++++++++++++++++++ src/streaming/store.rs | 22 +++++++++++- 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f3e82e..466a42c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased][] - Add `ManageExtension`: Factory reset the entire device or the state of a given client ([#11][]) +- `ChunkedExtension`: Add `PartialReadFile` syscall. [#11]: https://github.com/trussed-dev/trussed-staging/pull/11 diff --git a/src/streaming/mod.rs b/src/streaming/mod.rs index d368b7d..8340b9f 100644 --- a/src/streaming/mod.rs +++ b/src/streaming/mod.rs @@ -4,6 +4,7 @@ mod store; use store::OpenSeekFrom; +#[cfg(feature = "encrypted-chunked")] pub mod utils; #[cfg(feature = "encrypted-chunked")] @@ -85,6 +86,7 @@ pub enum ChunkedRequest { ReadChunk(request::ReadChunk), WriteChunk(request::WriteChunk), AbortChunkedWrite(request::AbortChunkedWrite), + PartialReadFile(request::PartialReadFile), } #[derive(Debug, Deserialize, Serialize)] @@ -99,6 +101,7 @@ pub enum ChunkedReply { StartEncryptedChunkedRead(reply::StartEncryptedChunkedRead), WriteChunk(reply::WriteChunk), AbortChunkedWrite(reply::AbortChunkedWrite), + PartialReadFile(reply::PartialReadFile), } mod request { @@ -265,6 +268,30 @@ mod request { Self::AbortChunkedWrite(request) } } + + #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] + pub struct PartialReadFile { + pub location: Location, + pub path: PathBuf, + pub offset: usize, + pub length: usize, + } + + impl TryFrom for PartialReadFile { + type Error = Error; + fn try_from(request: ChunkedRequest) -> Result { + match request { + ChunkedRequest::PartialReadFile(request) => Ok(request), + _ => Err(Error::InternalError), + } + } + } + + impl From for ChunkedRequest { + fn from(request: PartialReadFile) -> Self { + Self::PartialReadFile(request) + } + } } mod reply { @@ -419,6 +446,28 @@ mod reply { Self::AbortChunkedWrite(reply) } } + + #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] + pub struct PartialReadFile { + pub data: Message, + pub file_length: usize, + } + + impl TryFrom for PartialReadFile { + type Error = Error; + fn try_from(reply: ChunkedReply) -> Result { + match reply { + ChunkedReply::PartialReadFile(reply) => Ok(reply), + _ => Err(Error::InternalError), + } + } + } + + impl From for ChunkedReply { + fn from(reply: PartialReadFile) -> Self { + Self::PartialReadFile(reply) + } + } } impl ExtensionImpl for super::StagingBackend { @@ -502,6 +551,17 @@ impl ExtensionImpl for super::StagingBackend { store::start_chunked_write(store, client_id, &request.path, request.location, &[])?; Ok(reply::StartChunkedWrite {}.into()) } + ChunkedRequest::PartialReadFile(request) => { + let (data, file_length) = store::partial_read_file( + store, + client_id, + &request.path, + request.location, + request.offset, + request.length, + )?; + Ok(reply::PartialReadFile { data, file_length }.into()) + } #[cfg(feature = "encrypted-chunked")] ChunkedRequest::StartEncryptedChunkedWrite(request) => { clear_chunked_state(store, client_id, backend_ctx)?; @@ -834,6 +894,26 @@ pub trait ChunkedClient: ExtensionClient + FilesystemClient { fn read_file_chunk(&mut self) -> ChunkedResult<'_, reply::ReadChunk, Self> { self.extension(request::ReadChunk {}) } + + /// Partially read a file from a given offset, returning a chunk of the given length and the + /// total file size. + /// + /// If the length is greater than [`trussed::config::MAX_MESSAGE_LENGTH`][] or if the offset is + /// greater than the file size, an error is returned. + fn partial_read_file( + &mut self, + location: Location, + path: PathBuf, + offset: usize, + length: usize, + ) -> ChunkedResult<'_, reply::PartialReadFile, Self> { + self.extension(request::PartialReadFile { + location, + path, + offset, + length, + }) + } } impl + FilesystemClient> ChunkedClient for C {} diff --git a/src/streaming/store.rs b/src/streaming/store.rs index 38d101b..c2f86aa 100644 --- a/src/streaming/store.rs +++ b/src/streaming/store.rs @@ -5,8 +5,9 @@ use littlefs2::driver::Storage as LfsStorage; use littlefs2::fs::{File, Filesystem}; use littlefs2::io::{SeekFrom, Write}; +use trussed::config::MAX_MESSAGE_LENGTH; use trussed::store::{create_directories, Store}; -use trussed::types::{Bytes, Location, Path, PathBuf}; +use trussed::types::{Bytes, Location, Message, Path, PathBuf}; use trussed::Error; use serde::{Deserialize, Serialize}; @@ -290,3 +291,22 @@ pub fn flush_chunks( &client_path, ) } + +pub fn partial_read_file( + store: impl Store, + client_id: &Path, + path: &PathBuf, + location: Location, + offset: usize, + length: usize, +) -> Result<(Message, usize), Error> { + if length > MAX_MESSAGE_LENGTH { + return Err(Error::FilesystemReadFailure); + } + let path = actual_path(client_id, path)?; + let offset = u32::try_from(offset).map_err(|_| Error::FilesystemReadFailure)?; + let pos = OpenSeekFrom::Start(offset); + let (mut data, file_length) = read_chunk(store, location, &path, pos)?; + data.truncate(length); + Ok((data, file_length)) +} From 818adfce283b6f814fce93428c1ff80619654384 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 23 Nov 2023 10:03:10 +0100 Subject: [PATCH 2/2] chunked: Add AppendFile syscall --- CHANGELOG.md | 2 +- src/streaming/mod.rs | 70 ++++++++++++++++++++++++++++++++++++++++++ src/streaming/store.rs | 57 ++++++++++++++++++++++++++-------- 3 files changed, 116 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466a42c..e44b0cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased][] - Add `ManageExtension`: Factory reset the entire device or the state of a given client ([#11][]) -- `ChunkedExtension`: Add `PartialReadFile` syscall. +- `ChunkedExtension`: Add `AppendFile` and `PartialReadFile` syscalls. [#11]: https://github.com/trussed-dev/trussed-staging/pull/11 diff --git a/src/streaming/mod.rs b/src/streaming/mod.rs index 8340b9f..f8277e7 100644 --- a/src/streaming/mod.rs +++ b/src/streaming/mod.rs @@ -87,6 +87,7 @@ pub enum ChunkedRequest { WriteChunk(request::WriteChunk), AbortChunkedWrite(request::AbortChunkedWrite), PartialReadFile(request::PartialReadFile), + AppendFile(request::AppendFile), } #[derive(Debug, Deserialize, Serialize)] @@ -102,6 +103,7 @@ pub enum ChunkedReply { WriteChunk(reply::WriteChunk), AbortChunkedWrite(reply::AbortChunkedWrite), PartialReadFile(reply::PartialReadFile), + AppendFile(reply::AppendFile), } mod request { @@ -292,6 +294,29 @@ mod request { Self::PartialReadFile(request) } } + + #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] + pub struct AppendFile { + pub location: Location, + pub path: PathBuf, + pub data: Message, + } + + impl TryFrom for AppendFile { + type Error = Error; + fn try_from(request: ChunkedRequest) -> Result { + match request { + ChunkedRequest::AppendFile(request) => Ok(request), + _ => Err(Error::InternalError), + } + } + } + + impl From for ChunkedRequest { + fn from(request: AppendFile) -> Self { + Self::AppendFile(request) + } + } } mod reply { @@ -468,6 +493,27 @@ mod reply { Self::PartialReadFile(reply) } } + + #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] + pub struct AppendFile { + pub file_length: usize, + } + + impl TryFrom for AppendFile { + type Error = Error; + fn try_from(reply: ChunkedReply) -> Result { + match reply { + ChunkedReply::AppendFile(reply) => Ok(reply), + _ => Err(Error::InternalError), + } + } + } + + impl From for ChunkedReply { + fn from(reply: AppendFile) -> Self { + Self::AppendFile(reply) + } + } } impl ExtensionImpl for super::StagingBackend { @@ -562,6 +608,16 @@ impl ExtensionImpl for super::StagingBackend { )?; Ok(reply::PartialReadFile { data, file_length }.into()) } + ChunkedRequest::AppendFile(request) => { + let file_length = store::append_file( + store, + client_id, + &request.path, + request.location, + &request.data, + )?; + Ok(reply::AppendFile { file_length }.into()) + } #[cfg(feature = "encrypted-chunked")] ChunkedRequest::StartEncryptedChunkedWrite(request) => { clear_chunked_state(store, client_id, backend_ctx)?; @@ -914,6 +970,20 @@ pub trait ChunkedClient: ExtensionClient + FilesystemClient { length, }) } + + /// Append data to an existing file and return the size of the file after the write. + fn append_file( + &mut self, + location: Location, + path: PathBuf, + data: Message, + ) -> ChunkedResult<'_, reply::AppendFile, Self> { + self.extension(request::AppendFile { + location, + path, + data, + }) + } } impl + FilesystemClient> ChunkedClient for C {} diff --git a/src/streaming/store.rs b/src/streaming/store.rs index c2f86aa..3d57c2b 100644 --- a/src/streaming/store.rs +++ b/src/streaming/store.rs @@ -2,10 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 or MIT use littlefs2::driver::Storage as LfsStorage; -use littlefs2::fs::{File, Filesystem}; +use littlefs2::fs::{File, Filesystem, OpenOptions}; use littlefs2::io::{SeekFrom, Write}; -use trussed::config::MAX_MESSAGE_LENGTH; use trussed::store::{create_directories, Store}; use trussed::types::{Bytes, Location, Message, Path, PathBuf}; use trussed::Error; @@ -34,9 +33,13 @@ pub fn fs_read_chunk( fs: &Filesystem, path: &Path, pos: OpenSeekFrom, + length: usize, ) -> Result<(Bytes, usize), Error> { let mut contents = Bytes::default(); - contents.resize_default(contents.capacity()).unwrap(); + if length > contents.capacity() { + return Err(Error::FilesystemReadFailure); + } + contents.resize_default(length).unwrap(); let file_len = File::open_and_then(fs, path, |file| { file.seek(pos.into())?; let read_n = file.read(&mut contents)?; @@ -46,6 +49,7 @@ pub fn fs_read_chunk( .map_err(|_| Error::FilesystemReadFailure)?; Ok((contents, file_len)) } + /// Reads contents from path in location of store. #[inline(never)] pub fn read_chunk( @@ -56,9 +60,9 @@ pub fn read_chunk( ) -> Result<(Bytes, usize), Error> { debug_now!("reading chunk {},{:?}", &path, pos); match location { - Location::Internal => fs_read_chunk(store.ifs(), path, pos), - Location::External => fs_read_chunk(store.efs(), path, pos), - Location::Volatile => fs_read_chunk(store.vfs(), path, pos), + Location::Internal => fs_read_chunk(store.ifs(), path, pos, N), + Location::External => fs_read_chunk(store.efs(), path, pos, N), + Location::Volatile => fs_read_chunk(store.vfs(), path, pos, N), } } @@ -300,13 +304,42 @@ pub fn partial_read_file( offset: usize, length: usize, ) -> Result<(Message, usize), Error> { - if length > MAX_MESSAGE_LENGTH { - return Err(Error::FilesystemReadFailure); - } let path = actual_path(client_id, path)?; let offset = u32::try_from(offset).map_err(|_| Error::FilesystemReadFailure)?; let pos = OpenSeekFrom::Start(offset); - let (mut data, file_length) = read_chunk(store, location, &path, pos)?; - data.truncate(length); - Ok((data, file_length)) + match location { + Location::Internal => fs_read_chunk(store.ifs(), &path, pos, length), + Location::External => fs_read_chunk(store.efs(), &path, pos, length), + Location::Volatile => fs_read_chunk(store.vfs(), &path, pos, length), + } +} + +fn fs_append_file( + fs: &Filesystem, + path: &Path, + data: &[u8], +) -> Result { + OpenOptions::new() + .write(true) + .append(true) + .open_and_then(fs, path, |file| { + file.write_all(data)?; + file.len() + }) + .map_err(|_| Error::FilesystemWriteFailure) +} + +pub fn append_file( + store: impl Store, + client_id: &Path, + path: &PathBuf, + location: Location, + data: &[u8], +) -> Result { + let path = actual_path(client_id, path)?; + match location { + Location::Internal => fs_append_file(store.ifs(), &path, data), + Location::External => fs_append_file(store.efs(), &path, data), + Location::Volatile => fs_append_file(store.vfs(), &path, data), + } }