use super::AllocationProperty;
use crate::bytes::{
AccessError, AccessPolicy, AllocationController,
default_controller::{MAX_ALIGN, NativeAllocationController},
};
use spin::Once;
use std::{
boxed::Box,
fs::File,
io::{Read, Seek, SeekFrom},
path::PathBuf,
string::ToString,
sync::Arc,
vec,
};
pub(crate) struct FileAllocationController {
file: Arc<PathBuf>,
size: u64,
offset: u64,
controller: Once<Box<dyn AllocationController>>,
}
impl FileAllocationController {
pub fn new<P: Into<PathBuf>>(file: P, size: u64, offset: u64) -> Self {
Self::from_path_buf(Arc::new(file.into()), size, offset)
}
fn from_path_buf(file: Arc<PathBuf>, size: u64, offset: u64) -> Self {
Self {
file,
size,
controller: Once::new(),
offset,
}
}
fn ensure_init(&self, policy: AccessPolicy) -> Result<&dyn AllocationController, AccessError> {
if let Some(controller) = self.controller.get() {
return Ok(&**controller);
}
if !policy.copy_allowed() {
return Err(AccessError::WouldCopy);
}
let controller = self.controller.try_call_once(
|| -> Result<Box<dyn AllocationController>, AccessError> {
let mut file =
File::open(self.file.as_ref()).map_err(|e| AccessError::Read(e.to_string()))?;
let mut buf = vec![0u8; self.size as usize];
file.seek(SeekFrom::Start(self.offset))
.map_err(|e| AccessError::Read(e.to_string()))?;
file.read_exact(&mut buf)
.map_err(|e| AccessError::Read(e.to_string()))?;
Ok(Box::new(NativeAllocationController::from_elems(buf)))
},
)?;
Ok(&**controller)
}
}
impl AllocationController for FileAllocationController {
fn alloc_align(&self) -> usize {
MAX_ALIGN
}
fn split(
&mut self,
offset: usize,
) -> Result<(Box<dyn AllocationController>, Box<dyn AllocationController>), super::SplitError>
{
if self.size < offset as u64 {
return Err(super::SplitError::InvalidOffset);
}
let left =
FileAllocationController::from_path_buf(self.file.clone(), offset as u64, self.offset);
let right = FileAllocationController::from_path_buf(
self.file.clone(),
self.size - offset as u64,
self.offset + offset as u64,
);
Ok((Box::new(left), Box::new(right)))
}
fn view(&self, start: usize, end: usize) -> Option<Box<dyn AllocationController>> {
if self.controller.is_completed() {
return None;
}
if start > end || end as u64 > self.size {
return None;
}
Some(Box::new(FileAllocationController::from_path_buf(
self.file.clone(),
(end - start) as u64,
self.offset + start as u64,
)))
}
fn property(&self) -> AllocationProperty {
AllocationProperty::File
}
fn capacity(&self) -> usize {
self.size as usize
}
unsafe fn memory_mut(
&mut self,
policy: AccessPolicy,
) -> Result<&mut [core::mem::MaybeUninit<u8>], AccessError> {
self.ensure_init(policy)?;
let controller = self
.controller
.get_mut()
.expect("controller must be set after init");
unsafe { controller.memory_mut(policy) }
}
fn memory(&self, policy: AccessPolicy) -> Result<&[core::mem::MaybeUninit<u8>], AccessError> {
self.ensure_init(policy)?.memory(policy)
}
fn duplicate(&self) -> Option<Box<dyn AllocationController>> {
if self.controller.is_completed() {
return None;
}
let controller = Self::new(self.file.as_ref(), self.size, self.offset);
Some(Box::new(controller))
}
unsafe fn copy_into(&self, buf: &mut [u8]) {
if self.controller.is_completed() {
let len = buf.len();
let memory = self
.memory(AccessPolicy::default())
.expect("file: host access failed");
let memory_slice = &memory[0..len];
let data = unsafe {
core::slice::from_raw_parts(memory_slice.as_ptr().cast(), memory_slice.len())
};
buf.copy_from_slice(data);
return;
}
let mut file = File::open(self.file.as_ref()).unwrap();
file.seek(SeekFrom::Start(self.offset)).unwrap();
file.read_exact(buf).unwrap();
}
}
#[cfg(test)]
#[cfg(not(miri))]
mod tests {
use tempfile::TempDir;
use super::super::{AccessError, Bytes, Reader, SplitPolicy};
use std::{io::Write, path::PathBuf, vec::Vec};
#[test_log::test]
fn test_no_copy_read_and_length_do_not_materialize() {
let elems = (0..120).collect();
let (path, bytes_expected, dir) = with_data(elems);
let bytes = Bytes::from_file(&path, bytes_expected.len() as u64, 0);
assert_eq!(
bytes.read(Reader::new().no_copy()),
Err(AccessError::WouldCopy)
);
assert_eq!(bytes.len(), bytes_expected.len());
assert_eq!(bytes.capacity(), bytes_expected.len());
assert_eq!(
bytes.read(Reader::new().no_copy()),
Err(AccessError::WouldCopy)
);
assert_eq!(bytes.read(Reader::new()).unwrap(), &bytes_expected[..]);
assert_eq!(
bytes.read(Reader::new().no_copy()).unwrap(),
&bytes_expected[..]
);
core::mem::drop(dir);
}
#[test_log::test]
fn test_from_file() {
let elems = (0..250).collect();
let (path, bytes, dir) = with_data(elems);
let bytes_file = Bytes::from_file(&path, bytes.len() as u64, 0);
assert_eq!(&bytes, &bytes_file);
core::mem::drop(dir);
}
#[test_log::test]
fn test_split_file() {
let elems = (0..250).collect();
let (path, bytes, dir) = with_data(elems);
let bytes_file = Bytes::from_file(&path, bytes.len() as u64, 0);
let offset = 40;
let (left, right) = bytes_file.split(offset, SplitPolicy::Shared).unwrap();
let left_expected: &[u8] = &bytes[0..offset];
let right_expected: &[u8] = &bytes[offset..];
let left_actual: &[u8] = &left;
let right_actual: &[u8] = &right;
assert_eq!(left_expected, left_actual);
assert_eq!(right_expected, right_actual);
core::mem::drop(dir);
}
#[test_log::test]
fn test_split_file_at_zero() {
let elems: Vec<u8> = (0..100).collect();
let (path, bytes, dir) = with_data(elems);
let bytes_file = Bytes::from_file(&path, bytes.len() as u64, 0);
let (left, right) = bytes_file.split(0, SplitPolicy::Shared).unwrap();
assert_eq!(left.len(), 0);
assert_eq!(&right[..], &bytes[..]);
core::mem::drop(dir);
}
#[test_log::test]
fn test_view_file_is_zero_copy() {
let elems: Vec<u8> = (0..100).collect();
let (path, bytes, dir) = with_data(elems);
let bytes_file = Bytes::from_file(&path, bytes.len() as u64, 0);
let view = bytes_file.view(10, 20).unwrap();
assert_eq!(&view[..], &bytes[10..20]);
assert_eq!(&bytes_file[..], &bytes[..]);
core::mem::drop(dir);
}
#[test_log::test]
fn test_split_file_at_len() {
let elems: Vec<u8> = (0..100).collect();
let (path, bytes, dir) = with_data(elems);
let bytes_file = Bytes::from_file(&path, bytes.len() as u64, 0);
let len = bytes_file.len();
let (left, right) = bytes_file.split(len, SplitPolicy::Shared).unwrap();
assert_eq!(&left[..], &bytes[..]);
assert_eq!(right.len(), 0);
core::mem::drop(dir);
}
#[test_log::test]
fn test_memory_mut_on_duplicated_file() {
let elems = (0..250).collect();
let (path, bytes, dir) = with_data(elems);
let bytes_file = Bytes::from_file(&path, bytes.len() as u64, 0);
let mut bytes_mut = bytes_file.clone();
bytes_mut[0] = 5;
let expected: &[u8] = &bytes[1..];
let actual: &[u8] = &bytes_mut[1..];
assert_eq!(&bytes, &bytes_file);
assert_eq!(bytes_mut[0], 5);
assert_eq!(expected, actual);
core::mem::drop(dir);
}
fn with_data(elems: Vec<u8>) -> (PathBuf, Bytes, TempDir) {
let bytes = Bytes::from_bytes_vec(elems);
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test");
let mut file = std::fs::File::create(&path).unwrap();
file.write_all(&bytes).unwrap();
(path, bytes, dir)
}
}