mirror of
https://github.com/lexmount/moli.git
synced 2026-11-13 16:10:01 +00:00
Store immutable resource extents in one anonymous temporary file using positioned I/O. Reuse exact or worst-fit holes through offset and size indexes, coalesce released extents, and preserve bounded-capacity and failure behavior. Include allocator regression tests, concise documentation, and runnable examples.
149 lines
4.8 KiB
Rust
149 lines
4.8 KiB
Rust
use std::{fs::File, io};
|
|
|
|
#[cfg(not(any(unix, windows)))]
|
|
use parking_lot::Mutex;
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
pub(crate) struct PoolFile {
|
|
#[cfg(any(unix, windows))]
|
|
file: File,
|
|
#[cfg(not(any(unix, windows)))]
|
|
file: Mutex<File>,
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
fail_next_write: AtomicBool,
|
|
}
|
|
|
|
impl PoolFile {
|
|
pub(crate) fn new(file: File) -> Self {
|
|
Self {
|
|
#[cfg(any(unix, windows))]
|
|
file,
|
|
#[cfg(not(any(unix, windows)))]
|
|
file: Mutex::new(file),
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
fail_next_write: AtomicBool::new(false),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn read_exact_at(&self, mut buffer: &mut [u8], mut offset: u64) -> io::Result<()> {
|
|
while !buffer.is_empty() {
|
|
match self.read_at(buffer, offset) {
|
|
Ok(0) => {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::UnexpectedEof,
|
|
"disk pool file ended inside an allocated extent",
|
|
));
|
|
}
|
|
Ok(read) => {
|
|
offset = offset
|
|
.checked_add(u64::try_from(read).map_err(|_| {
|
|
io::Error::new(io::ErrorKind::InvalidInput, "read size is too large")
|
|
})?)
|
|
.ok_or_else(|| {
|
|
io::Error::new(io::ErrorKind::InvalidInput, "read offset overflow")
|
|
})?;
|
|
buffer = &mut buffer[read..];
|
|
}
|
|
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
|
|
Err(error) => return Err(error),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn write_all_at(&self, mut data: &[u8], mut offset: u64) -> io::Result<()> {
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
if self.fail_next_write.swap(false, Ordering::SeqCst) {
|
|
return Err(io::Error::other("injected disk pool write failure"));
|
|
}
|
|
|
|
while !data.is_empty() {
|
|
match self.write_at(data, offset) {
|
|
Ok(0) => {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::WriteZero,
|
|
"failed to write an allocated disk extent",
|
|
));
|
|
}
|
|
Ok(written) => {
|
|
offset = offset
|
|
.checked_add(u64::try_from(written).map_err(|_| {
|
|
io::Error::new(io::ErrorKind::InvalidInput, "write size is too large")
|
|
})?)
|
|
.ok_or_else(|| {
|
|
io::Error::new(io::ErrorKind::InvalidInput, "write offset overflow")
|
|
})?;
|
|
data = &data[written..];
|
|
}
|
|
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
|
|
Err(error) => return Err(error),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
pub(crate) fn fail_next_write_for_test(&self) {
|
|
self.fail_next_write.store(true, Ordering::SeqCst);
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn read_at(&self, buffer: &mut [u8], offset: u64) -> io::Result<usize> {
|
|
use std::os::unix::fs::FileExt;
|
|
|
|
self.file.read_at(buffer, offset)
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn write_at(&self, data: &[u8], offset: u64) -> io::Result<usize> {
|
|
use std::os::unix::fs::FileExt;
|
|
|
|
self.file.write_at(data, offset)
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn read_at(&self, buffer: &mut [u8], offset: u64) -> io::Result<usize> {
|
|
use std::os::windows::fs::FileExt;
|
|
|
|
self.file.seek_read(buffer, offset)
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn write_at(&self, data: &[u8], offset: u64) -> io::Result<usize> {
|
|
use std::os::windows::fs::FileExt;
|
|
|
|
self.file.seek_write(data, offset)
|
|
}
|
|
|
|
#[cfg(not(any(unix, windows)))]
|
|
fn read_at(&self, buffer: &mut [u8], offset: u64) -> io::Result<usize> {
|
|
use std::io::{Read, Seek};
|
|
|
|
let mut file = self.file.lock();
|
|
file.seek(io::SeekFrom::Start(offset))?;
|
|
file.read(buffer)
|
|
}
|
|
|
|
#[cfg(not(any(unix, windows)))]
|
|
fn write_at(&self, data: &[u8], offset: u64) -> io::Result<usize> {
|
|
use std::io::{Seek, Write};
|
|
|
|
let mut file = self.file.lock();
|
|
file.seek(io::SeekFrom::Start(offset))?;
|
|
file.write(data)
|
|
}
|
|
|
|
#[cfg(feature = "test-support")]
|
|
pub(crate) fn set_len(&self, len: u64) -> io::Result<()> {
|
|
#[cfg(any(unix, windows))]
|
|
{
|
|
self.file.set_len(len)
|
|
}
|
|
#[cfg(not(any(unix, windows)))]
|
|
{
|
|
self.file.lock().set_len(len)
|
|
}
|
|
}
|
|
}
|