morpheus_core/iso/iso9660_bridge.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
//! ISO9660 Integration
//!
//! Bridge between chunked ISO storage and the iso9660 crate.
//! Provides a `BlockIo` implementation that iso9660 can use directly.
//!
//! # Usage
//!
//! ```ignore
//! use morpheus_core::iso::{IsoStorageManager, IsoBlockIoAdapter};
//! use iso9660::{mount, find_file, find_boot_image};
//!
//! // Get ISO read context from storage manager
//! let ctx = storage_manager.get_read_context(iso_index)?;
//!
//! // Create adapter with disk read function
//! let mut adapter = IsoBlockIoAdapter::new(ctx, &mut disk_block_io);
//!
//! // Use with iso9660
//! let volume = mount(&mut adapter, 0)?;
//! let boot = find_boot_image(&mut adapter, &volume)?;
//! let kernel_file = find_file(&mut adapter, &volume, "/boot/vmlinuz")?;
//! ```
use super::reader::IsoReadContext;
use gpt_disk_io::BlockIo;
use gpt_disk_types::Lba;
/// ISO9660 sector size (what iso9660 crate expects)
const ISO_SECTOR_SIZE: usize = 2048;
/// Physical disk block size
const DISK_BLOCK_SIZE: usize = 512;
/// Ratio of ISO sectors to disk blocks
const BLOCKS_PER_ISO_SECTOR: usize = ISO_SECTOR_SIZE / DISK_BLOCK_SIZE;
/// Block I/O adapter that implements gpt_disk_io::BlockIo
///
/// This allows iso9660 to read from chunked ISO storage transparently.
/// The adapter translates virtual sector addresses to physical disk locations.
///
/// iso9660 uses 2048-byte sectors, but physical disk uses 512-byte blocks.
/// This adapter handles the translation.
pub struct IsoBlockIoAdapter<'a, B: BlockIo> {
/// ISO read context (chunk partition info)
ctx: IsoReadContext,
/// Underlying block device
block_io: &'a mut B,
}
impl<'a, B: BlockIo> IsoBlockIoAdapter<'a, B> {
/// Create a new adapter
///
/// # Arguments
/// * `ctx` - ISO read context from IsoStorageManager
/// * `block_io` - Underlying disk block device
pub fn new(ctx: IsoReadContext, block_io: &'a mut B) -> Self {
Self { ctx, block_io }
}
/// Get total size in bytes
pub fn total_size(&self) -> u64 {
self.ctx.total_size
}
/// Get total number of ISO sectors (2048-byte sectors)
pub fn total_iso_sectors(&self) -> u64 {
self.ctx.total_size / ISO_SECTOR_SIZE as u64
}
/// Find which chunk contains a given byte offset
fn find_chunk_for_offset(&self, byte_offset: u64) -> Option<(usize, u64)> {
let mut cumulative = 0u64;
for i in 0..self.ctx.num_chunks {
let chunk_size = self.ctx.chunk_sizes[i];
if byte_offset < cumulative + chunk_size {
let offset_in_chunk = byte_offset - cumulative;
return Some((i, offset_in_chunk));
}
cumulative += chunk_size;
}
None
}
/// Translate ISO byte offset to physical disk LBA (512-byte sectors)
fn translate_byte_offset_to_disk_lba(&self, byte_offset: u64) -> Option<u64> {
let (chunk_idx, offset_in_chunk) = self.find_chunk_for_offset(byte_offset)?;
// ISO data is written directly to the partition start (raw mode)
let (part_start, _) = self.ctx.chunk_lbas[chunk_idx];
let disk_sector_in_chunk = offset_in_chunk / DISK_BLOCK_SIZE as u64;
let physical_lba = part_start + disk_sector_in_chunk;
Some(physical_lba)
}
}
impl<'a, B: BlockIo> BlockIo for IsoBlockIoAdapter<'a, B> {
type Error = B::Error;
fn block_size(&self) -> gpt_disk_types::BlockSize {
// Report 2048-byte block size (ISO9660 sector size)
gpt_disk_types::BlockSize::new(ISO_SECTOR_SIZE as u32).unwrap()
}
fn num_blocks(&mut self) -> Result<u64, Self::Error> {
Ok(self.total_iso_sectors())
}
fn read_blocks(&mut self, start_lba: Lba, buffer: &mut [u8]) -> Result<(), Self::Error> {
// iso9660 requests reads in 2048-byte sectors
let num_iso_sectors = buffer.len() / ISO_SECTOR_SIZE;
if num_iso_sectors == 0 {
return Ok(());
}
// Try to batch contiguous reads for better performance
// This is critical for large files like initrd (70MB+)
let mut current_pos = 0usize;
while current_pos < num_iso_sectors {
let iso_sector = start_lba.0 + current_pos as u64;
let byte_offset = iso_sector * ISO_SECTOR_SIZE as u64;
// Check bounds
if byte_offset >= self.ctx.total_size {
// Read beyond EOF - fill remaining with zeros
let buf_start = current_pos * ISO_SECTOR_SIZE;
buffer[buf_start..].fill(0);
break;
}
// Get the physical LBA for the start of this ISO sector
let first_physical_lba = match self.translate_byte_offset_to_disk_lba(byte_offset) {
Some(lba) => lba,
None => {
// Chunk not found - fill this sector with zeros and continue
let buf_offset = current_pos * ISO_SECTOR_SIZE;
buffer[buf_offset..buf_offset + ISO_SECTOR_SIZE].fill(0);
current_pos += 1;
continue;
}
};
// Determine how many contiguous ISO sectors we can read at once
// Sectors are contiguous if they map to consecutive physical LBAs
let mut batch_count = 1usize;
while current_pos + batch_count < num_iso_sectors {
let next_iso_sector = start_lba.0 + (current_pos + batch_count) as u64;
let next_byte_offset = next_iso_sector * ISO_SECTOR_SIZE as u64;
// Check bounds
if next_byte_offset >= self.ctx.total_size {
break;
}
// Check if this sector is contiguous with the batch
let expected_lba =
first_physical_lba + (batch_count * BLOCKS_PER_ISO_SECTOR) as u64;
let actual_lba = match self.translate_byte_offset_to_disk_lba(next_byte_offset) {
Some(lba) => lba,
None => break, // End batch at chunk boundary
};
if actual_lba != expected_lba {
// Not contiguous (chunk boundary), end this batch
break;
}
batch_count += 1;
}
// Read the entire batch in one disk operation
let buf_start = current_pos * ISO_SECTOR_SIZE;
let buf_end = buf_start + batch_count * ISO_SECTOR_SIZE;
let total_disk_blocks = batch_count * BLOCKS_PER_ISO_SECTOR;
self.block_io
.read_blocks(Lba(first_physical_lba), &mut buffer[buf_start..buf_end])?;
current_pos += batch_count;
}
Ok(())
}
fn write_blocks(&mut self, _start_lba: Lba, _buffer: &[u8]) -> Result<(), Self::Error> {
// ISO storage is read-only for booting
// Return success but don't actually write (iso9660 shouldn't write anyway)
Ok(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.block_io.flush()
}
}
/// High-level helper to boot from a chunked ISO
///
/// Combines ISO storage access with iso9660 parsing in a single interface.
pub struct ChunkedIso<'a, B: BlockIo> {
adapter: IsoBlockIoAdapter<'a, B>,
}
impl<'a, B: BlockIo> ChunkedIso<'a, B> {
/// Create a new chunked ISO accessor
pub fn new(ctx: IsoReadContext, block_io: &'a mut B) -> Self {
Self {
adapter: IsoBlockIoAdapter::new(ctx, block_io),
}
}
/// Get mutable reference to the adapter for iso9660 operations
pub fn block_io(&mut self) -> &mut IsoBlockIoAdapter<'a, B> {
&mut self.adapter
}
/// Get ISO total size
pub fn total_size(&self) -> u64 {
self.adapter.total_size()
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::fmt;
// Mock error type that implements Display
#[derive(Debug)]
struct MockError;
impl fmt::Display for MockError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MockError")
}
}
// Mock BlockIo for testing
struct MockBlockIo {
data: [u8; 4096],
}
impl BlockIo for MockBlockIo {
type Error = MockError;
fn block_size(&self) -> gpt_disk_types::BlockSize {
gpt_disk_types::BlockSize::new(512).unwrap()
}
fn num_blocks(&mut self) -> Result<u64, Self::Error> {
Ok(8)
}
fn read_blocks(&mut self, lba: Lba, buffer: &mut [u8]) -> Result<(), Self::Error> {
let offset = (lba.0 as usize) * 512;
if offset + buffer.len() <= self.data.len() {
buffer.copy_from_slice(&self.data[offset..offset + buffer.len()]);
}
Ok(())
}
fn write_blocks(&mut self, _lba: Lba, _buffer: &[u8]) -> Result<(), Self::Error> {
Ok(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
#[test]
fn test_byte_offset_translation() {
use crate::iso::MAX_CHUNKS;
let mut ctx = IsoReadContext {
chunk_lbas: [(0, 0); MAX_CHUNKS],
chunk_sizes: [0; MAX_CHUNKS],
num_chunks: 1,
total_size: 1_000_000,
};
// Chunk starts at disk LBA 1000
ctx.chunk_lbas[0] = (1000, 10000);
ctx.chunk_sizes[0] = 1_000_000;
let mut mock = MockBlockIo { data: [0; 4096] };
let adapter = IsoBlockIoAdapter::new(ctx, &mut mock);
// Byte offset 0 should translate to partition start (1000)
let phys = adapter.translate_byte_offset_to_disk_lba(0).unwrap();
assert_eq!(phys, 1000);
// Byte offset 512 should be 1 disk sector further
let phys = adapter.translate_byte_offset_to_disk_lba(512).unwrap();
assert_eq!(phys, 1001);
// Byte offset 2048 (1 ISO sector) should be 4 disk sectors from start
let phys = adapter.translate_byte_offset_to_disk_lba(2048).unwrap();
assert_eq!(phys, 1004);
}
}