use crate::error::{Error, Result};
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
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) {}
fn invalidate_cache(&self) -> Result<()> {
Ok(())
}
}
pub struct FileDevice {
file: Mutex<File>,
size: u64,
writable: bool,
}
impl FileDevice {
pub fn open(path: &str) -> Result<Self> {
Self::open_path(Path::new(path))
}
pub fn open_path(path: &Path) -> 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> {
Self::open_path_rw(Path::new(path))
}
pub fn open_path_rw(path: &Path) -> 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()
}
}
pub struct AlignedDevice<D: BlockDevice> {
inner: D,
sector: u64,
write_lock: Mutex<()>,
}
impl<D: BlockDevice> AlignedDevice<D> {
pub fn new(inner: D, sector: u32) -> Self {
assert!(
sector.is_power_of_two(),
"sector size {sector} is not a power of two"
);
AlignedDevice {
inner,
sector: sector as u64,
write_lock: Mutex::new(()),
}
}
fn is_aligned(&self, offset: u64, len: usize) -> bool {
let mask = self.sector - 1;
offset & mask == 0 && (len as u64) & mask == 0
}
fn span(&self, offset: u64, len: usize) -> Result<(u64, u64)> {
let mask = self.sector - 1;
let end = offset
.checked_add(len as u64)
.and_then(|e| e.checked_add(mask))
.ok_or(Error::Corrupt("device request overflows u64"))?
& !mask;
if end > self.inner.size_bytes() {
return Err(Error::Corrupt("device request past the end of the device"));
}
Ok((offset & !mask, end))
}
}
impl<D: BlockDevice> BlockDevice for AlignedDevice<D> {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
if self.is_aligned(offset, buf.len()) {
return self.inner.read_at(offset, buf);
}
let (start, end) = self.span(offset, buf.len())?;
let mut bounce = vec![0u8; (end - start) as usize];
self.inner.read_at(start, &mut bounce)?;
let head = (offset - start) as usize;
buf.copy_from_slice(&bounce[head..head + buf.len()]);
Ok(())
}
fn size_bytes(&self) -> u64 {
self.inner.size_bytes()
}
fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
let _guard = self.write_lock.lock().unwrap_or_else(|p| p.into_inner());
if self.is_aligned(offset, buf.len()) {
return self.inner.write_at(offset, buf);
}
let (start, end) = self.span(offset, buf.len())?;
let sector = self.sector as usize;
let mut bounce = vec![0u8; (end - start) as usize];
let head = (offset - start) as usize;
let tail = head + buf.len();
if head != 0 {
self.inner.read_at(start, &mut bounce[..sector])?;
}
let last = bounce.len() - sector;
if !tail.is_multiple_of(sector) && (last != 0 || head == 0) {
self.inner
.read_at(start + last as u64, &mut bounce[last..])?;
}
bounce[head..tail].copy_from_slice(buf);
self.inner.write_at(start, &bounce)
}
fn flush(&self) -> Result<()> {
self.inner.flush()
}
fn is_writable(&self) -> bool {
self.inner.is_writable()
}
fn populate_cache(&self, block: u64, bytes: Vec<u8>) {
self.inner.populate_cache(block, bytes)
}
fn unpin_all(&self) {
self.inner.unpin_all()
}
fn invalidate_cache(&self) -> Result<()> {
self.inner.invalidate_cache()
}
}
#[cfg(test)]
mod aligned_device_tests {
use super::*;
const SECTOR: u64 = 512;
struct Strict(Mutex<Vec<u8>>);
impl Strict {
fn check(offset: u64, len: usize) -> Result<()> {
if !offset.is_multiple_of(SECTOR) || !(len as u64).is_multiple_of(SECTOR) {
return Err(Error::Corrupt("unaligned request"));
}
Ok(())
}
}
impl BlockDevice for Strict {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
Self::check(offset, buf.len())?;
let b = self.0.lock().unwrap();
buf.copy_from_slice(&b[offset as usize..offset as usize + buf.len()]);
Ok(())
}
fn size_bytes(&self) -> u64 {
self.0.lock().unwrap().len() as u64
}
fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
Self::check(offset, buf.len())?;
let mut b = self.0.lock().unwrap();
b[offset as usize..offset as usize + buf.len()].copy_from_slice(buf);
Ok(())
}
fn is_writable(&self) -> bool {
true
}
}
fn pattern(len: usize) -> Vec<u8> {
(0..len).map(|i| (i * 7 + 3) as u8).collect()
}
#[test]
fn unaligned_reads_and_writes_match_a_byte_granular_model() {
let size = 4 * SECTOR as usize;
let cases: &[(u64, usize)] = &[
(0, 1),
(1, 10),
(500, 12),
(511, 1),
(510, 4),
(0, 512),
(512, 1024),
(100, 1500),
(1, 2046),
(1024, 1),
(2047, 1),
(0, 2048),
(3, 0),
];
for &(off, len) in cases {
let base = pattern(size);
let dev = AlignedDevice::new(Strict(Mutex::new(base.clone())), SECTOR as u32);
let data: Vec<u8> = (0..len).map(|i| !(i as u8)).collect();
dev.write_at(off, &data)
.unwrap_or_else(|e| panic!("write {len}@{off}: {e:?}"));
let mut model = base;
model[off as usize..off as usize + len].copy_from_slice(&data);
assert!(
*dev.inner.0.lock().unwrap() == model,
"write {len}@{off} disturbed bytes outside it"
);
let mut back = vec![0u8; len];
dev.read_at(off, &mut back)
.unwrap_or_else(|e| panic!("read {len}@{off}: {e:?}"));
assert_eq!(back, data, "read {len}@{off}");
}
}
#[test]
fn a_request_past_the_end_is_refused_not_wrapped() {
let dev = AlignedDevice::new(Strict(Mutex::new(vec![0; 1024])), SECTOR as u32);
let mut buf = [0u8; 4];
assert!(dev.read_at(1022, &mut buf).is_err());
assert!(dev.write_at(1022, &buf).is_err());
assert!(dev.read_at(u64::MAX - 1, &mut buf).is_err());
}
}