use crate::error::{Error, Result};
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::sync::Mutex;
pub trait BlockDevice: Send + Sync {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()>;
fn size_bytes(&self) -> u64;
fn write_at(&self, _offset: u64, _buf: &[u8]) -> Result<()> {
Err(Error::Corrupt(
"block device is read-only (no write_at impl)",
))
}
fn flush(&self) -> Result<()> {
Ok(())
}
fn is_writable(&self) -> bool {
false
}
fn populate_cache(&self, _block: u64, _bytes: Vec<u8>) {}
fn unpin_all(&self) {}
}
pub struct FileDevice {
file: Mutex<File>,
size: u64,
writable: bool,
}
impl FileDevice {
pub fn open(path: &str) -> Result<Self> {
let file = File::open(path)?;
let size = file.metadata()?.len();
Ok(Self {
file: Mutex::new(file),
size,
writable: false,
})
}
pub fn open_rw(path: &str) -> Result<Self> {
let file = OpenOptions::new().read(true).write(true).open(path)?;
let size = file.metadata()?.len();
Ok(Self {
file: Mutex::new(file),
size,
writable: true,
})
}
pub fn open_best_effort(path: &str) -> Result<Self> {
match Self::open_rw(path) {
Ok(d) => Ok(d),
Err(_) => Self::open(path),
}
}
}
impl BlockDevice for FileDevice {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
let mut f = self.file.lock().unwrap();
f.seek(SeekFrom::Start(offset))?;
f.read_exact(buf)?;
Ok(())
}
fn size_bytes(&self) -> u64 {
self.size
}
fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
if !self.writable {
return Err(Error::Corrupt("FileDevice opened read-only"));
}
let mut f = self.file.lock().unwrap();
f.seek(SeekFrom::Start(offset))?;
f.write_all(buf)?;
Ok(())
}
fn flush(&self) -> Result<()> {
if !self.writable {
return Ok(());
}
let mut f = self.file.lock().unwrap();
f.flush()?;
f.sync_data()?;
Ok(())
}
fn is_writable(&self) -> bool {
self.writable
}
}
pub type ReadCb = Box<dyn Fn(u64, &mut [u8]) -> std::io::Result<()> + Send + Sync>;
pub type WriteCb = Box<dyn Fn(u64, &[u8]) -> std::io::Result<()> + Send + Sync>;
pub type FlushCb = Box<dyn Fn() -> std::io::Result<()> + Send + Sync>;
pub struct CallbackDevice {
pub size: u64,
pub read: ReadCb,
pub write: Option<WriteCb>,
pub flush: Option<FlushCb>,
}
impl BlockDevice for CallbackDevice {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
(self.read)(offset, buf)?;
Ok(())
}
fn size_bytes(&self) -> u64 {
self.size
}
fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
match &self.write {
Some(f) => {
f(offset, buf)?;
Ok(())
}
None => Err(Error::Corrupt("CallbackDevice has no write callback")),
}
}
fn flush(&self) -> Result<()> {
match &self.flush {
Some(f) => {
f()?;
Ok(())
}
None => Ok(()),
}
}
fn is_writable(&self) -> bool {
self.write.is_some()
}
}