diff --git a/rust/src/disk/memory.rs b/rust/src/disk/memory.rs new file mode 100644 index 0000000..9b1f73b --- /dev/null +++ b/rust/src/disk/memory.rs @@ -0,0 +1,67 @@ +use crate::system::{MemoryFd, BitVec}; +use crate::disk::{Result, Error, SECTOR_SIZE, DiskImage}; +use std::io::SeekFrom; + +pub struct MemoryOverlay { + memory: MemoryFd, + written_sectors: BitVec, +} + +impl MemoryOverlay { + pub fn new() -> Result { + let memory = MemoryFd::new_memfd(0, false) + .map_err(Error::MemoryOverlayCreate)?; + let written_sectors = BitVec::new(); + Ok(MemoryOverlay { memory, written_sectors }) + } + + pub fn write_sectors(&mut self, start: u64, buffer: &[u8]) -> Result<()> { + let sector_count = buffer.len() / SECTOR_SIZE; + let len = sector_count * SECTOR_SIZE; + let seek_offset = SeekFrom::Start(start * SECTOR_SIZE as u64); + + self.memory.fd_mut() + .seek(seek_offset) + .map_err(Error::DiskSeek)?; + + self.memory.fd_mut() + .write_all(&buffer[..len]) + .map_err(Error::DiskWrite)?; + + for n in 0..sector_count { + let idx = start as usize + n; + self.written_sectors.set_bit(idx); + } + Ok(()) + } + + pub fn read_sectors(&mut self, disk: &mut D, start: u64, buffer: &mut [u8]) -> Result<()> { + let sector_count = buffer.len() / SECTOR_SIZE; + if (0..sector_count).all(|i| !self.written_sectors.get_bit(i)) { + return disk.read_sectors(start, buffer); + } + + for n in 0..sector_count { + let sector = start + n as u64; + let offset = n * SECTOR_SIZE; + let sector_buffer = &mut buffer[offset..offset+SECTOR_SIZE]; + if self.written_sectors.get_bit(sector as usize) { + self.read_single_sector(sector, sector_buffer)?; + } else { + disk.read_sectors(sector, sector_buffer)?; + } + } + Ok(()) + } + + fn read_single_sector(&mut self, sector: u64, buffer: &mut [u8]) -> Result<()> { + assert_eq!(buffer.len(), SECTOR_SIZE); + let offset = SeekFrom::Start(sector * SECTOR_SIZE as u64); + self.memory.fd_mut().seek(offset) + .map_err(Error::DiskSeek)?; + self.memory.fd_mut().read_exact(buffer) + .map_err(Error::DiskRead)?; + Ok(()) + } + +} \ No newline at end of file diff --git a/rust/src/disk/mod.rs b/rust/src/disk/mod.rs new file mode 100644 index 0000000..1f3b4e5 --- /dev/null +++ b/rust/src/disk/mod.rs @@ -0,0 +1,88 @@ +use std::{io, error, fmt, result, cmp}; +use std::fs::File; +use std::os::linux::fs::MetadataExt; +use std::io::{SeekFrom, Seek}; + +use crate::system; + +mod realmfs; +mod raw; +mod memory; + +pub use raw::RawDiskImage; +pub use realmfs::RealmFSImage; +use std::path::PathBuf; + +const SECTOR_SIZE: usize = 512; + +#[derive(Debug,PartialEq)] +pub enum OpenType { + ReadOnly, + ReadWrite, + MemoryOverlay, +} + +pub trait DiskImage: Sync+Send { + fn read_only(&self) -> bool; + fn sector_count(&self) -> u64; + fn disk_file(&self) -> &File; + + fn seek_to_sector(&self, sector: u64) -> Result<()> { + if sector > self.sector_count() { + return Err(Error::BadSectorOffset(sector)); + } + let offset = SeekFrom::Start(sector * SECTOR_SIZE as u64); + self.disk_file().seek(offset) + .map_err(Error::DiskSeek)?; + Ok(()) + } + fn write_sectors(&mut self, start_sector: u64, buffer: &[u8]) -> Result<()>; + fn read_sectors(&mut self, start_sector: u64, buffer: &mut [u8]) -> Result<()>; + fn flush(&mut self) -> Result<()> { Ok(()) } + + fn disk_image_id(&self) -> &[u8]; +} + +fn generate_disk_image_id(disk_file: &File) -> Vec { + const VIRTIO_BLK_ID_BYTES: usize = 20; + let meta = match disk_file.metadata() { + Ok(meta) => meta, + Err(_) => return vec![0u8; VIRTIO_BLK_ID_BYTES] + }; + let dev_id = format!("{}{}{}", meta.st_dev(), meta.st_rdev(), meta.st_ino()); + let bytes = dev_id.as_bytes(); + let len = cmp::min(bytes.len(), VIRTIO_BLK_ID_BYTES); + Vec::from(&bytes[..len]) +} + +pub type Result = result::Result; + +#[derive(Debug)] +pub enum Error { + ReadOnly, + DiskOpen(PathBuf,io::Error), + DiskOpenTooShort(PathBuf), + DiskRead(io::Error), + DiskWrite(io::Error), + DiskSeek(io::Error), + BadSectorOffset(u64), + MemoryOverlayCreate(system::Error), +} + +impl error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Error::*; + match self { + ReadOnly => write!(f, "attempted write to read-only device"), + DiskOpen(path, err) => write!(f, "failed to open disk image {}: {}", path.display(), err), + DiskOpenTooShort(path) => write!(f, "failed to open disk image {} because file is too short", path.display()), + DiskRead(err) => write!(f, "error reading from disk image: {}", err), + DiskWrite(err) => write!(f, "error writing to disk image: {}", err), + DiskSeek(err) => write!(f, "error seeking to offset on disk image: {}", err), + BadSectorOffset(sector) => write!(f, "attempt to access invalid sector offset {}", sector), + MemoryOverlayCreate(err) => write!(f, "failed to create memory overlay: {}", err), + } + } +} \ No newline at end of file diff --git a/rust/src/disk/raw.rs b/rust/src/disk/raw.rs new file mode 100644 index 0000000..3ba6597 --- /dev/null +++ b/rust/src/disk/raw.rs @@ -0,0 +1,116 @@ +use crate::disk::{Result, Error, DiskImage, SECTOR_SIZE, generate_disk_image_id, OpenType}; +use std::fs::{File, OpenOptions}; +use std::io::{Write, Read, SeekFrom, Seek}; +use crate::disk::Error::DiskRead; +use crate::disk::memory::MemoryOverlay; +use std::path::Path; + +pub struct RawDiskImage { + file: File, + offset: usize, + nsectors: u64, + read_only: bool, + disk_image_id: Vec, + overlay: Option, +} + +impl RawDiskImage { + #[allow(dead_code)] + pub fn open>(path: P, open_type: OpenType) -> Result { + Self::open_with_offset(path, open_type, 0) + } + + pub fn open_with_offset>(path: P, open_type: OpenType, offset: usize) -> Result { + let path = path.as_ref(); + let meta = path.metadata() + .map_err(|e| Error::DiskOpen(path.into(), e))?; + + if meta.len() < offset as u64 { + return Err(Error::DiskOpenTooShort(path.into())) + } + + let nsectors = (meta.len() - offset as u64) / SECTOR_SIZE as u64; + + let file = OpenOptions::new() + .read(true) + .write(open_type == OpenType::ReadWrite) + .open(path) + .map_err(|e| Error::DiskOpen(path.into(), e))?; + + + let disk = match open_type { + OpenType::MemoryOverlay => { + let overlay = MemoryOverlay::new()?; + Self::new(file, nsectors, offset, false, Some(overlay)) + } + OpenType::ReadOnly => { + Self::new(file, nsectors, offset, true, None) + } + OpenType::ReadWrite => { + Self::new(file, nsectors, offset, false, None) + } + }; + Ok(disk) + } + + pub fn new(file: File, nsectors: u64, offset: usize, read_only: bool, overlay: Option) -> Self { + let disk_image_id = generate_disk_image_id(&file); + RawDiskImage { file, nsectors, read_only, offset, disk_image_id, overlay } + } +} + +impl DiskImage for RawDiskImage { + fn read_only(&self) -> bool { + self.read_only + } + + fn sector_count(&self) -> u64 { + self.nsectors + } + + fn disk_file(&self) -> &File { + &self.file + } + + fn seek_to_sector(&self, sector: u64) -> Result<()> { + if sector > self.sector_count() { + return Err(Error::BadSectorOffset(sector)); + } + let offset = SeekFrom::Start(sector * SECTOR_SIZE as u64 + self.offset as u64); + self.disk_file().seek(offset) + .map_err(Error::DiskSeek)?; + Ok(()) + } + + fn write_sectors(&mut self, start_sector: u64, buffer: &[u8]) -> Result<()> { + if let Some(ref mut overlay) = self.overlay { + return overlay.write_sectors(start_sector, buffer); + } + if self.read_only { + return Err(Error::ReadOnly) + } + self.seek_to_sector(start_sector)?; + let len = (buffer.len() / SECTOR_SIZE) * SECTOR_SIZE; + self.file.write_all(&buffer[..len]) + .map_err(Error::DiskWrite)?; + Ok(()) + } + + fn read_sectors(&mut self, start_sector: u64, buffer: &mut [u8]) -> Result<()> { + if let Some(mut overlay) = self.overlay.take() { + let ret = overlay.read_sectors(self, start_sector, buffer); + self.overlay.replace(overlay); + return ret; + } + + self.seek_to_sector(start_sector)?; + let len = (buffer.len() / SECTOR_SIZE) * SECTOR_SIZE; + self.file.read_exact(&mut buffer[..len]) + .map_err(DiskRead)?; + Ok(()) + } + + fn disk_image_id(&self) -> &[u8] { + &self.disk_image_id + } +} \ No newline at end of file diff --git a/rust/src/disk/realmfs.rs b/rust/src/disk/realmfs.rs new file mode 100644 index 0000000..615afaa --- /dev/null +++ b/rust/src/disk/realmfs.rs @@ -0,0 +1,45 @@ +use crate::disk::{Result, DiskImage, SECTOR_SIZE, RawDiskImage, OpenType}; +use std::fs::File; +use std::path::Path; + +// skip 4096 byte realmfs header +const HEADER_SECTOR_COUNT: usize = 8; + +pub struct RealmFSImage { + raw: RawDiskImage, +} + +// Just pass everything through to raw image for now +impl RealmFSImage { + pub fn open>(path: P, read_only: bool) -> Result { + let open_type = if read_only { OpenType::ReadOnly } else { OpenType::MemoryOverlay }; + let raw = RawDiskImage::open_with_offset(path, open_type, HEADER_SECTOR_COUNT * SECTOR_SIZE)?; + Ok(RealmFSImage { raw }) + } +} + +impl DiskImage for RealmFSImage { + fn read_only(&self) -> bool { + self.raw.read_only() + } + + fn sector_count(&self) -> u64 { + self.raw.sector_count() + } + + fn disk_file(&self) -> &File { + self.raw.disk_file() + } + + fn write_sectors(&mut self, start_sector: u64, buffer: &[u8]) -> Result<()> { + self.raw.write_sectors(start_sector, buffer) + } + + fn read_sectors(&mut self, start_sector: u64, buffer: &mut [u8]) -> Result<()> { + self.raw.read_sectors(start_sector, buffer) + } + + fn disk_image_id(&self) -> &[u8] { + self.raw.disk_image_id() + } +}