use super::seal::{Layout, SegSrc};
use super::InodeHandle;
use crate::sink::RegionSink;
use crate::spec::BLOCK_SIZE;
use crate::{Error, Result};
use std::io::Read;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Summary {
pub image_len: u64,
pub data_bytes: u64,
}
enum FillState {
NotFillable,
Pending,
Done,
}
pub struct ImageWriter<'a, S: RegionSink> {
layout: &'a Layout,
sink: S,
fill_state: Vec<FillState>,
pending: usize,
data_bytes: u64,
poisoned: bool,
scratch: Vec<u8>,
}
impl<'a, S: RegionSink> ImageWriter<'a, S> {
pub(super) fn new(layout: &'a Layout, mut sink: S) -> Result<ImageWriter<'a, S>> {
emit_metadata(layout, &mut sink)?;
let mut pending = 0;
let fill_state = layout
.file_runs
.iter()
.enumerate()
.map(|(slot, (runs, _size))| {
if layout.slot_ino[slot] == 0 || runs.is_empty() {
FillState::NotFillable
} else {
pending += 1;
FillState::Pending
}
})
.collect();
Ok(ImageWriter {
layout,
sink,
fill_state,
pending,
data_bytes: 0,
poisoned: false,
scratch: vec![0u8; 1024 * BLOCK_SIZE],
})
}
fn check_poisoned(&self) -> Result<()> {
if self.poisoned {
return Err(Error::Invalid(
"writer is poisoned by an earlier failed fill; rebuild from the layout".into(),
));
}
Ok(())
}
pub fn fill(&mut self, f: InodeHandle, reader: &mut impl Read) -> Result<()> {
self.check_poisoned()?;
let slot = f.0 as usize;
match self.fill_state.get(slot) {
Some(FillState::Pending) => {}
Some(FillState::Done) => return Err(Error::Invalid("file already filled".into())),
_ => {
return Err(Error::Invalid(
"handle is not a fillable file in this layout".into(),
))
}
}
match self.fill_inner(slot, reader) {
Ok(()) => {
self.fill_state[slot] = FillState::Done;
self.pending -= 1;
Ok(())
}
Err(e) => {
self.poisoned = true;
Err(e)
}
}
}
fn fill_inner(&mut self, slot: usize, reader: &mut impl Read) -> Result<()> {
let (runs, size) = &self.layout.file_runs[slot];
let mut remaining = *size;
let buf = &mut self.scratch;
for run in runs {
let mut offset = run.start * BLOCK_SIZE as u64;
let mut run_bytes = (run.len * BLOCK_SIZE as u64).min(remaining);
while run_bytes > 0 {
let want = run_bytes.min(buf.len() as u64) as usize;
reader
.read_exact(&mut buf[..want])
.map_err(|e| Error::Io(std::io::Error::other(format!("short fill: {e}"))))?;
self.sink.data(offset, &buf[..want])?;
offset += want as u64;
run_bytes -= want as u64;
remaining -= want as u64;
}
let pad = (BLOCK_SIZE as u64 - offset % BLOCK_SIZE as u64) % BLOCK_SIZE as u64;
if pad > 0 {
self.sink.zeros(offset, pad)?;
}
}
self.data_bytes += *size;
Ok(())
}
pub fn finish(self) -> Result<Summary> {
self.check_poisoned()?;
if self.pending > 0 {
return Err(Error::Invalid(format!(
"{} declared file(s) never filled",
self.pending
)));
}
Ok(Summary {
image_len: self.layout.image_len(),
data_bytes: self.data_bytes,
})
}
}
fn emit_metadata<S: RegionSink>(layout: &Layout, sink: &mut S) -> Result<()> {
let bs = BLOCK_SIZE as u64;
let total_blocks = layout.image_len() / bs;
let mut cursor = 0u64; let mut data_iter = layout.data_runs.iter().peekable();
let emit_gap =
|sink: &mut S,
from: u64,
to: u64,
data_iter: &mut std::iter::Peekable<std::slice::Iter<crate::layout::alloc::Run>>|
-> Result<()> {
let mut at = from;
while at < to {
while let Some(r) = data_iter.peek() {
if r.start + r.len <= at {
data_iter.next();
} else {
break;
}
}
match data_iter.peek() {
Some(r) if r.start < to => {
if r.start > at {
sink.zeros(at * bs, (r.start - at) * bs)?;
}
at = (r.start + r.len).min(to);
if r.start + r.len <= to {
data_iter.next();
}
}
_ => {
sink.zeros(at * bs, (to - at) * bs)?;
at = to;
}
}
}
Ok(())
};
for seg in &layout.segments {
if seg.block > cursor {
emit_gap(sink, cursor, seg.block, &mut data_iter)?;
}
match &seg.src {
SegSrc::Bytes(bytes) => {
debug_assert_eq!(bytes.len() as u64, seg.len * bs);
sink.data(seg.block * bs, bytes)?;
}
SegSrc::Itable { group } => {
emit_itable(layout, *group, seg.block, seg.len, sink)?;
}
}
cursor = seg.block + seg.len;
}
if cursor < total_blocks {
emit_gap(sink, cursor, total_blocks, &mut data_iter)?;
}
Ok(())
}
fn emit_itable<S: RegionSink>(
layout: &Layout,
group: u32,
start_block: u64,
len: u64,
sink: &mut S,
) -> Result<()> {
let ipg = layout.geo.inodes_per_group;
let first_ino = group * ipg + 1;
let bs = BLOCK_SIZE as u64;
let last_used_in_group = layout
.max_ino
.clamp(first_ino.saturating_sub(1), first_ino + ipg - 1)
.saturating_sub(first_ino - 1);
let used_blocks = (last_used_in_group as u64).div_ceil(16);
let mut buf = vec![0u8; BLOCK_SIZE];
for blk in 0..used_blocks {
let base = (first_ino - 1) as usize + blk as usize * 16;
let avail = layout.inodes.len().saturating_sub(base).min(16);
for (i, raw) in layout.inodes[base..base + avail].iter().enumerate() {
buf[i * 256..(i + 1) * 256].copy_from_slice(raw);
}
buf[avail * 256..].fill(0);
sink.data((start_block + blk) * bs, &buf)?;
}
if used_blocks < len {
sink.zeros((start_block + used_blocks) * bs, (len - used_blocks) * bs)?;
}
Ok(())
}