use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::sync::Mutex;
use crate::error::{Error, Result};
pub trait BlockDevice: Send + Sync {
fn read_block(&self, block: u64, buf: &mut [u8]) -> Result<()>;
fn read_blocks(&self, block: u64, count: u32, buf: &mut [u8]) -> Result<()>;
fn block_size(&self) -> u32;
fn write_block(&self, block: u64, data: &[u8]) -> Result<()>;
fn write_blocks(&self, block: u64, count: u32, data: &[u8]) -> Result<()>;
fn flush(&self) -> Result<()>;
}
pub struct FileBlockDevice {
file: Mutex<File>,
block_bytes: u32,
partition_offset: u64,
total_blocks: u64,
writable: bool,
}
impl FileBlockDevice {
pub fn open(
path: &std::path::Path,
block_bytes: u32,
partition_offset: u64,
total_blocks: u64,
) -> Result<Self> {
Self::new_impl(
File::open(path)?,
block_bytes,
partition_offset,
total_blocks,
false,
)
}
pub fn open_rw(
path: &std::path::Path,
block_bytes: u32,
partition_offset: u64,
total_blocks: u64,
) -> Result<Self> {
let file = OpenOptions::new().read(true).write(true).open(path)?;
Self::new_impl(file, block_bytes, partition_offset, total_blocks, true)
}
fn new_impl(
file: File,
block_bytes: u32,
partition_offset: u64,
total_blocks: u64,
writable: bool,
) -> Result<Self> {
let file_len = file.metadata()?.len();
let total_blocks = if total_blocks == 0 {
(file_len - partition_offset) / block_bytes as u64
} else {
total_blocks
};
Ok(Self {
file: Mutex::new(file),
block_bytes,
partition_offset,
total_blocks,
writable,
})
}
pub fn create(path: &std::path::Path, block_bytes: u32, total_blocks: u64) -> Result<Self> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)?;
let size = total_blocks * block_bytes as u64;
file.set_len(size)?;
Ok(Self {
file: Mutex::new(file),
block_bytes,
partition_offset: 0,
total_blocks,
writable: true,
})
}
pub fn total_blocks(&self) -> u64 {
self.total_blocks
}
}
impl BlockDevice for FileBlockDevice {
fn read_block(&self, block: u64, buf: &mut [u8]) -> Result<()> {
self.read_blocks(block, 1, buf)
}
fn read_blocks(&self, block: u64, count: u32, buf: &mut [u8]) -> Result<()> {
let offset = self.partition_offset + block * self.block_bytes as u64;
let len = count as usize * self.block_bytes as usize;
if buf.len() < len {
return Err(Error::TooShort("read buffer"));
}
let mut file = self.file.lock().unwrap();
file.seek(SeekFrom::Start(offset))?;
file.read_exact(&mut buf[..len])?;
Ok(())
}
fn block_size(&self) -> u32 {
self.block_bytes
}
fn write_block(&self, block: u64, data: &[u8]) -> Result<()> {
self.write_blocks(block, 1, data)
}
fn write_blocks(&self, block: u64, count: u32, data: &[u8]) -> Result<()> {
if !self.writable {
return Err(Error::ReadOnly);
}
let offset = self.partition_offset + block * self.block_bytes as u64;
let len = count as usize * self.block_bytes as usize;
let mut file = self.file.lock().unwrap();
file.seek(SeekFrom::Start(offset))?;
file.write_all(&data[..len])?;
Ok(())
}
fn flush(&self) -> Result<()> {
let file = self.file.lock().unwrap();
file.sync_all()?;
Ok(())
}
}