use super::{File, FileHandle, FileInternal};
use crate::{Error, Result, Status, StatusExt};
#[repr(transparent)]
#[derive(Debug)]
pub struct RegularFile(FileHandle);
impl RegularFile {
pub const END_OF_FILE: u64 = u64::MAX;
#[must_use]
pub const unsafe fn new(handle: FileHandle) -> Self {
Self(handle)
}
pub fn read(&mut self, buffer: &mut [u8]) -> Result<usize> {
let chunk_size = 1024 * 1024;
read_chunked(buffer, chunk_size, |buf, buf_size| unsafe {
(self.imp().read)(self.imp(), buf_size, buf.cast())
})
}
pub(super) fn read_unchunked(&mut self, buffer: &mut [u8]) -> Result<usize, Option<usize>> {
let mut buffer_size = buffer.len();
let status =
unsafe { (self.imp().read)(self.imp(), &mut buffer_size, buffer.as_mut_ptr().cast()) };
status.to_result_with(
|| buffer_size,
|s| {
if s == Status::BUFFER_TOO_SMALL {
Some(buffer_size)
} else {
None
}
},
)
}
pub fn write(&mut self, buffer: &[u8]) -> Result<(), usize> {
let mut buffer_size = buffer.len();
unsafe { (self.imp().write)(self.imp(), &mut buffer_size, buffer.as_ptr().cast()) }
.to_result_with_err(|_| buffer_size)
}
pub fn get_position(&mut self) -> Result<u64> {
let mut pos = 0u64;
unsafe { (self.imp().get_position)(self.imp(), &mut pos) }.to_result_with_val(|| pos)
}
pub fn set_position(&mut self, position: u64) -> Result {
unsafe { (self.imp().set_position)(self.imp(), position) }.to_result()
}
}
impl File for RegularFile {
#[inline]
fn handle(&mut self) -> &mut FileHandle {
&mut self.0
}
fn is_regular_file(&self) -> Result<bool> {
Ok(true)
}
fn is_directory(&self) -> Result<bool> {
Ok(false)
}
}
fn read_chunked<F>(buffer: &mut [u8], chunk_size: usize, mut read: F) -> Result<usize>
where
F: FnMut(*mut u8, &mut usize) -> Status,
{
let mut remaining_size = buffer.len();
let mut total_read_size = 0;
let mut output_ptr = buffer.as_mut_ptr();
while remaining_size > 0 {
let requested_read_size = remaining_size.min(chunk_size);
let mut read_size = requested_read_size;
let status = read(output_ptr, &mut read_size);
if status.is_success() {
total_read_size += read_size;
remaining_size -= read_size;
output_ptr = unsafe { output_ptr.add(read_size) };
if read_size < requested_read_size {
break;
}
} else {
return Err(Error::new(status, ()));
}
}
Ok(total_read_size)
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::rc::Rc;
use alloc::vec;
use alloc::vec::Vec;
use core::cell::RefCell;
#[derive(Default)]
struct TestFile {
data: Rc<RefCell<Vec<u8>>>,
offset: Rc<RefCell<usize>>,
}
impl TestFile {
fn read(&self, buffer: *mut u8, buffer_size: &mut usize) -> Status {
let mut offset = self.offset.borrow_mut();
let data = self.data.borrow();
let remaining_data_size = data.len() - *offset;
let size_to_read = remaining_data_size.min(*buffer_size);
unsafe { buffer.copy_from(data.as_ptr().add(*offset), size_to_read) };
*offset += size_to_read;
*buffer_size = size_to_read;
Status::SUCCESS
}
fn reset(&self) {
*self.data.borrow_mut() = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
*self.offset.borrow_mut() = 0;
}
}
#[test]
fn test_file_read_chunked() {
let file = TestFile::default();
let read = |buf, buf_size: &mut usize| file.read(buf, buf_size);
file.reset();
let mut buffer = [0; 10];
assert_eq!(read_chunked(&mut buffer, 10, read), Ok(10));
assert_eq!(buffer.as_slice(), *file.data.borrow());
file.reset();
let mut buffer = [0; 10];
assert_eq!(read_chunked(&mut buffer, 2, read), Ok(10));
assert_eq!(buffer.as_slice(), *file.data.borrow());
file.reset();
let mut buffer = [0; 10];
assert_eq!(read_chunked(&mut buffer, 20, read), Ok(10));
assert_eq!(buffer.as_slice(), *file.data.borrow());
file.reset();
let mut buffer = [0; 4];
assert_eq!(read_chunked(&mut buffer, 10, read), Ok(4));
assert_eq!(buffer.as_slice(), [1, 2, 3, 4]);
file.reset();
let mut buffer = [0; 20];
assert_eq!(read_chunked(&mut buffer, 10, read), Ok(10));
assert_eq!(
buffer,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
);
file.reset();
let mut buffer = [];
assert_eq!(read_chunked(&mut buffer, 10, read), Ok(0));
assert_eq!(buffer, []);
file.reset();
file.data.borrow_mut().clear();
let mut buffer = [0; 10];
assert_eq!(read_chunked(&mut buffer, 10, read), Ok(0));
assert_eq!(buffer, [0; 10]);
}
}