#[cfg(feature = "testing")]
use alloc::vec::Vec;
use crate::{StorageError, storage::Spill};
#[cfg(feature = "libc")]
pub struct LibcSpill {
fd: aranya_libc::OwnedFd,
}
#[cfg(feature = "libc")]
impl LibcSpill {
pub fn new<P: AsRef<aranya_libc::Path>>(dir: P) -> Result<Self, StorageError> {
use aranya_libc::{
self as libc, O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL, O_RDONLY, O_RDWR, S_IRUSR,
S_IWUSR,
};
static COUNTER: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
let id = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
let dir_fd = libc::open(dir.as_ref(), O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0)
.map_err(|_| StorageError::IoError)?;
let name = alloc::format!(".aranya_spill_{}\0", id);
let file_path = aranya_libc::Path::new(name.as_bytes());
let fd = libc::openat(
libc::AsFd::as_fd(&dir_fd),
file_path,
O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC,
S_IRUSR | S_IWUSR,
)
.map_err(|_| StorageError::IoError)?;
let _ = libc::unlinkat(libc::AsFd::as_fd(&dir_fd), file_path, 0);
Ok(Self { fd })
}
}
#[cfg(feature = "libc")]
impl Spill for LibcSpill {
fn write_at(&mut self, offset: usize, buf: &[u8]) -> Result<(), StorageError> {
use aranya_libc::{self as libc, Errno};
use buggy::BugExt as _;
let mut off = i64::try_from(offset).assume("`offset` fits in i64")?;
let mut remaining = buf;
while !remaining.is_empty() {
match libc::pwrite(&self.fd, remaining, off) {
Ok(0) => return Err(StorageError::IoError),
Ok(n) => {
remaining = remaining.get(n..).assume("`n` is in bounds")?;
off = off
.checked_add(i64::try_from(n).assume("write within bounds")?)
.assume("write within bounds")?;
}
Err(Errno::EINTR) => {}
Err(_) => return Err(StorageError::IoError),
}
}
Ok(())
}
fn read_at(&mut self, offset: usize, buf: &mut [u8]) -> Result<(), StorageError> {
use aranya_libc::{self as libc, Errno};
use buggy::BugExt as _;
let mut off = i64::try_from(offset).assume("`offset` fits in i64")?;
let mut remaining = buf;
while !remaining.is_empty() {
match libc::pread(&self.fd, remaining, off) {
Ok(0) => return Err(StorageError::IoError),
Ok(n) => {
remaining = remaining.get_mut(n..).assume("`n` is in bounds")?;
off = off
.checked_add(i64::try_from(n).assume("read within bounds")?)
.assume("read within bounds")?;
}
Err(Errno::EINTR) => {}
Err(_) => return Err(StorageError::IoError),
}
}
Ok(())
}
}
#[cfg(feature = "testing")]
pub struct MemSpill {
buf: Vec<u8>,
}
#[cfg(feature = "testing")]
impl MemSpill {
pub fn new() -> Result<Self, StorageError> {
Ok(Self { buf: Vec::new() })
}
}
#[cfg(feature = "testing")]
impl Spill for MemSpill {
fn write_at(&mut self, offset: usize, data: &[u8]) -> Result<(), StorageError> {
let end = offset
.checked_add(data.len())
.ok_or(StorageError::IoError)?;
if end > self.buf.len() {
self.buf.resize(end, 0);
}
self.buf[offset..end].copy_from_slice(data);
Ok(())
}
fn read_at(&mut self, offset: usize, data: &mut [u8]) -> Result<(), StorageError> {
let end = offset
.checked_add(data.len())
.ok_or(StorageError::IoError)?;
let src = self.buf.get(offset..end).ok_or(StorageError::IoError)?;
data.copy_from_slice(src);
Ok(())
}
}