use std::{ cmp, convert::TryInto, fs::{File, OpenOptions}, io::{Read, Result, Seek, SeekFrom, Write}, path::Path, }; #[derive(Debug)] pub struct DiskWrapper { disk: File, size: u64, block: Box<[u8]>, seek: u64, } enum Buffer<'a> { Read(&'a mut [u8]), Write(&'a [u8]), } impl DiskWrapper { pub fn open>(path: P) -> Result { let disk = OpenOptions::new().read(true).write(true).open(path)?; let metadata = disk.metadata()?; let size = metadata.len(); // TODO: get real block size: disk_metadata.blksize() works on disks but not image files let block_size = 512; let block = vec![0u8; block_size].into_boxed_slice(); Ok(Self { disk, size, block, seek: 0, }) } pub fn block_size(&self) -> usize { self.block.len() } pub fn size(&self) -> u64 { self.size } fn io<'a>(&mut self, buf: &mut Buffer<'a>) -> Result { let buf_len = match buf { Buffer::Read(read) => read.len(), Buffer::Write(write) => write.len(), }; let block_len: u64 = self.block.len().try_into().unwrap(); // Do aligned I/O quickly if self.seek % block_len == 0 && buf_len as u64 % block_len == 0 { self.disk.seek(SeekFrom::Start(self.seek))?; match buf { Buffer::Read(read) => self.disk.read_exact(read)?, Buffer::Write(write) => self.disk.write_all(write)?, } self.seek = self.seek.checked_add(buf_len.try_into().unwrap()).unwrap(); return Ok(buf_len); } let mut i = 0; while i < buf_len { let block = self.seek / block_len; let offset: usize = (self.seek % block_len).try_into().unwrap(); let remaining = buf_len.checked_sub(i).unwrap(); let len = cmp::min( remaining, self.block .len() .checked_sub(offset.try_into().unwrap()) .unwrap(), ); self.disk .seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?; self.disk.read_exact(&mut self.block)?; match buf { Buffer::Read(read) => { read[i..i.checked_add(len).unwrap()] .copy_from_slice(&self.block[offset..offset.checked_add(len).unwrap()]); } Buffer::Write(write) => { self.block[offset..offset.checked_add(len).unwrap()] .copy_from_slice(&write[i..i.checked_add(len).unwrap()]); self.disk .seek(SeekFrom::Start(block.checked_mul(block_len).unwrap()))?; self.disk.write_all(&mut self.block)?; } } i = i.checked_add(len).unwrap(); self.seek = self.seek.checked_add(len.try_into().unwrap()).unwrap(); } Ok(i) } } impl Read for DiskWrapper { fn read(&mut self, buf: &mut [u8]) -> Result { self.io(&mut Buffer::Read(buf)) } } impl Seek for DiskWrapper { fn seek(&mut self, pos: SeekFrom) -> Result { let current: i64 = self.seek.try_into().unwrap(); let end: i64 = self.size.try_into().unwrap(); self.seek = match pos { SeekFrom::Start(offset) => cmp::min(self.size, offset), SeekFrom::End(offset) => cmp::max(0, cmp::min(end, end.wrapping_add(offset))) as u64, SeekFrom::Current(offset) => { cmp::max(0, cmp::min(end, current.wrapping_add(offset))) as u64 } }; Ok(self.seek) } } impl Write for DiskWrapper { fn write(&mut self, buf: &[u8]) -> Result { self.io(&mut Buffer::Write(buf)) } fn flush(&mut self) -> Result<()> { self.disk.flush() } }