use std::io::Write; use proxmox::tools::vec; use crate::tape::{ TapeWrite, tape_device_write_block, file_formats::{ BlockHeader, BlockHeaderFlags, }, }; /// Assemble and write blocks of data /// /// This type implement 'TapeWrite'. Data written is assembled to /// equally sized blocks (see 'BlockHeader'), which are then written /// to the underlying writer. pub struct BlockedWriter { writer: W, buffer: Box, buffer_pos: usize, seq_nr: u32, logical_end_of_media: bool, bytes_written: usize, } impl BlockedWriter { /// Allow access to underlying writer pub fn writer_ref_mut(&mut self) -> &mut W { &mut self.writer } /// Creates a new instance. pub fn new(writer: W) -> Self { Self { writer, buffer: BlockHeader::new(), buffer_pos: 0, seq_nr: 0, logical_end_of_media: false, bytes_written: 0, } } fn write_block(buffer: &BlockHeader, writer: &mut W) -> Result { let data = unsafe { std::slice::from_raw_parts( (buffer as *const BlockHeader) as *const u8, BlockHeader::SIZE, ) }; tape_device_write_block(writer, data) } fn write(&mut self, data: &[u8]) -> Result { if data.is_empty() { return Ok(0); } let rest = self.buffer.payload.len() - self.buffer_pos; let bytes = if data.len() < rest { data.len() } else { rest }; self.buffer.payload[self.buffer_pos..(self.buffer_pos+bytes)] .copy_from_slice(&data[..bytes]); let rest = rest - bytes; if rest == 0 { self.buffer.flags = BlockHeaderFlags::empty(); self.buffer.set_size(self.buffer.payload.len()); self.buffer.set_seq_nr(self.seq_nr); self.seq_nr += 1; let leom = Self::write_block(&self.buffer, &mut self.writer)?; if leom { self.logical_end_of_media = true; } self.buffer_pos = 0; self.bytes_written += BlockHeader::SIZE; } else { self.buffer_pos += bytes; } Ok(bytes) } } impl TapeWrite for BlockedWriter { fn write_all(&mut self, mut data: &[u8]) -> Result { while !data.is_empty() { match self.write(data) { Ok(n) => data = &data[n..], Err(e) => return Err(e), } } Ok(self.logical_end_of_media) } fn bytes_written(&self) -> usize { self.bytes_written } /// flush last block, set END_OF_STREAM flag /// /// Note: This may write an empty block just including the /// END_OF_STREAM flag. fn finish(&mut self, incomplete: bool) -> Result { vec::clear(&mut self.buffer.payload[self.buffer_pos..]); self.buffer.flags = BlockHeaderFlags::END_OF_STREAM; if incomplete { self.buffer.flags |= BlockHeaderFlags::INCOMPLETE; } self.buffer.set_size(self.buffer_pos); self.buffer.set_seq_nr(self.seq_nr); self.seq_nr += 1; self.bytes_written += BlockHeader::SIZE; Self::write_block(&self.buffer, &mut self.writer) } /// Returns if the writer already detected the logical end of media fn logical_end_of_media(&self) -> bool { self.logical_end_of_media } }