diff --git a/src/tape/file_formats/mod.rs b/src/tape/file_formats/mod.rs index da71e1cb..c8d211bc 100644 --- a/src/tape/file_formats/mod.rs +++ b/src/tape/file_formats/mod.rs @@ -13,6 +13,12 @@ pub use chunk_archive::*; mod snapshot_archive; pub use snapshot_archive::*; +mod multi_volume_writer; +pub use multi_volume_writer::*; + +mod multi_volume_reader; +pub use multi_volume_reader::*; + use std::collections::HashMap; use anyhow::{bail, Error}; diff --git a/src/tape/file_formats/multi_volume_reader.rs b/src/tape/file_formats/multi_volume_reader.rs new file mode 100644 index 00000000..801a7eab --- /dev/null +++ b/src/tape/file_formats/multi_volume_reader.rs @@ -0,0 +1,102 @@ +use std::io::{Read}; + +use anyhow::{bail, Error}; + +use proxmox::tools::io::ReadExt; + +use crate::tape::{ + TapeRead, + file_formats::MediaContentHeader, +}; + +/// Read multi volume data streams written by `MultiVolumeWriter` +/// +/// Note: We do not use this feature currently. +pub struct MultiVolumeReader<'a> { + reader: Option>, + next_reader_fn: Box Result, Error>>, + complete: bool, + header: MediaContentHeader, +} + +impl <'a> MultiVolumeReader<'a> { + + /// Creates a new instance + pub fn new( + reader: Box, + header: MediaContentHeader, + next_reader_fn: Box Result, Error>>, + ) -> Result { + + if header.part_number != 0 { + bail!("MultiVolumeReader::new - got wrong header part_number ({} != 0)", + header.part_number); + } + + Ok(Self { + reader: Some(reader), + next_reader_fn, + complete: false, + header, + }) + } +} + +impl <'a> Read for MultiVolumeReader<'a> { + + fn read(&mut self, buf: &mut [u8]) -> Result { + if self.complete { + return Ok(0); + } + + if self.reader.is_none() { + let mut reader = (self.next_reader_fn)() + .map_err(|err| proxmox::io_format_err!("multi-volume next failed: {}", err))?; + + proxmox::try_block!({ + let part_header: MediaContentHeader = unsafe { reader.read_le_value()? }; + self.reader = Some(reader); + + if part_header.uuid != self.header.uuid { + proxmox::io_bail!("got wrong part uuid"); + } + if part_header.content_magic!= self.header.content_magic { + proxmox::io_bail!("got wrong part content magic"); + } + + let expect_part_number = self.header.part_number + 1; + + if part_header.part_number != expect_part_number { + proxmox::io_bail!("got wrong part number ({} != {})", + part_header.part_number, expect_part_number); + } + + self.header.part_number = expect_part_number; + + Ok(()) + }).map_err(|err| { + proxmox::io_format_err!("multi-volume read content header failed: {}", err) + })?; + } + + match self.reader { + None => unreachable!(), + Some(ref mut reader) => { + match reader.read(buf) { + Ok(0) => { + if reader.is_incomplete()? { + self.reader = None; + self.read(buf) + } else { + self.reader = None; + self.complete = true; + Ok(0) + } + } + Ok(n) => Ok(n), + Err(err) => Err(err) + } + } + } + } +} diff --git a/src/tape/file_formats/multi_volume_writer.rs b/src/tape/file_formats/multi_volume_writer.rs new file mode 100644 index 00000000..d58285e9 --- /dev/null +++ b/src/tape/file_formats/multi_volume_writer.rs @@ -0,0 +1,136 @@ +use anyhow::Error; + +use proxmox::tools::Uuid; + +use crate::tape::{ + TapeWrite, + file_formats::MediaContentHeader, +}; + +/// Writes data streams using multiple volumes +/// +/// Note: We do not use this feature currently. +pub struct MultiVolumeWriter<'a> { + writer: Option>, + next_writer_fn: Box Result, Error>>, + got_leom: bool, + finished: bool, + wrote_header: bool, + header: MediaContentHeader, + header_data: Vec, + bytes_written: usize, // does not include bytes from current writer +} + +impl <'a> MultiVolumeWriter<'a> { + + /// Creates a new instance + pub fn new( + writer: Box, + content_magic: [u8; 8], + header_data: Vec, + next_writer_fn: Box Result, Error>>, + ) -> Self { + + let header = MediaContentHeader::new(content_magic, header_data.len() as u32); + + Self { + writer: Some(writer), + next_writer_fn, + got_leom: false, + finished: false, + header, + header_data, + wrote_header: false, + bytes_written: 0, + } + } + + /// Returns the cuntent Uuid with the current part number + pub fn uuid_and_part_number(&self) -> (Uuid, usize) { + (self.header.uuid.into(), self.header.part_number as usize) + } +} + +impl <'a> TapeWrite for MultiVolumeWriter<'a> { + + fn write_all(&mut self, buf: &[u8]) -> Result { + + if self.finished { + proxmox::io_bail!("multi-volume writer already finished: internal error"); + } + + if self.got_leom { + if !self.wrote_header { + proxmox::io_bail!("multi-volume writer: got LEOM before writing anything - internal error"); + } + let mut writer = match self.writer.take() { + Some(writer) => writer, + None => proxmox::io_bail!("multi-volume writer: no writer -internal error"), + }; + self.bytes_written = writer.bytes_written(); + writer.finish(true)?; + } + + if self.writer.is_none() { + if self.header.part_number >= 255 { + proxmox::io_bail!("multi-volume writer: too many parts"); + } + self.writer = Some( + (self.next_writer_fn)() + .map_err(|err| proxmox::io_format_err!("multi-volume get next volume failed: {}", err))? + ); + self.got_leom = false; + self.wrote_header = false; + self.header.part_number += 1; + } + + let leom = match self.writer { + None => unreachable!(), + Some(ref mut writer) => { + if !self.wrote_header { + writer.write_header(&self.header, &self.header_data)?; + self.wrote_header = true; + } + writer.write_all(buf)? + } + }; + + if leom { self.got_leom = true; } + + Ok(false) + } + + fn bytes_written(&self) -> usize { + let mut bytes_written = self.bytes_written; + if let Some(ref writer) = self.writer { + bytes_written += writer.bytes_written(); + } + bytes_written + } + + fn finish(&mut self, incomplete: bool) -> Result { + if incomplete { + proxmox::io_bail!( + "incomplete flag makes no sense for multi-volume stream: internal error"); + } + + match self.writer.take() { + None if self.finished => proxmox::io_bail!( + "multi-volume writer already finished: internal error"), + None => Ok(false), + Some(ref mut writer) => { + self.finished = true; + if !self.wrote_header { + writer.write_header(&self.header, &self.header_data)?; + self.wrote_header = true; + } + writer.finish(false) + } + } + } + + fn logical_end_of_media(&self) -> bool { + self.got_leom + } + +}