A module for accessing disk image files (with virtio-blk)

This commit is contained in:
Bruce Leidl 2019-09-11 15:58:04 -04:00
parent 9cca10a2c0
commit e79502c271
4 changed files with 316 additions and 0 deletions

67
rust/src/disk/memory.rs Normal file
View File

@ -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<Self> {
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<D: DiskImage>(&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(())
}
}

88
rust/src/disk/mod.rs Normal file
View File

@ -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<u8> {
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<T> = result::Result<T, Error>;
#[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),
}
}
}

116
rust/src/disk/raw.rs Normal file
View File

@ -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<u8>,
overlay: Option<MemoryOverlay>,
}
impl RawDiskImage {
#[allow(dead_code)]
pub fn open<P: AsRef<Path>>(path: P, open_type: OpenType) -> Result<Self> {
Self::open_with_offset(path, open_type, 0)
}
pub fn open_with_offset<P: AsRef<Path>>(path: P, open_type: OpenType, offset: usize) -> Result<Self> {
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<MemoryOverlay>) -> 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
}
}

45
rust/src/disk/realmfs.rs Normal file
View File

@ -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<P: AsRef<Path>>(path: P, read_only: bool) -> Result<Self> {
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()
}
}