A module for accessing disk image files (with virtio-blk)
This commit is contained in:
parent
9cca10a2c0
commit
e79502c271
67
rust/src/disk/memory.rs
Normal file
67
rust/src/disk/memory.rs
Normal 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
88
rust/src/disk/mod.rs
Normal 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
116
rust/src/disk/raw.rs
Normal 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
45
rust/src/disk/realmfs.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user