tape: add multi volume reader/writer implementations

We currently do not use it. Added anaways, to show the possibility.
This commit is contained in:
Dietmar Maurer 2021-02-04 08:36:35 +01:00
parent f47e035721
commit 983e929e25
3 changed files with 244 additions and 0 deletions

View File

@ -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};

View File

@ -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<Box<dyn TapeRead + 'a>>,
next_reader_fn: Box<dyn 'a + FnMut() -> Result<Box<dyn TapeRead +'a>, Error>>,
complete: bool,
header: MediaContentHeader,
}
impl <'a> MultiVolumeReader<'a> {
/// Creates a new instance
pub fn new(
reader: Box<dyn TapeRead +'a>,
header: MediaContentHeader,
next_reader_fn: Box<dyn 'a + FnMut() -> Result<Box<dyn TapeRead +'a>, Error>>,
) -> Result<Self, Error> {
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<usize, std::io::Error> {
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)
}
}
}
}
}

View File

@ -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<Box<dyn TapeWrite + 'a>>,
next_writer_fn: Box<dyn 'a + FnMut() -> Result<Box<dyn TapeWrite +'a>, Error>>,
got_leom: bool,
finished: bool,
wrote_header: bool,
header: MediaContentHeader,
header_data: Vec<u8>,
bytes_written: usize, // does not include bytes from current writer
}
impl <'a> MultiVolumeWriter<'a> {
/// Creates a new instance
pub fn new(
writer: Box<dyn TapeWrite +'a>,
content_magic: [u8; 8],
header_data: Vec<u8>,
next_writer_fn: Box<dyn 'a + FnMut() -> Result<Box<dyn TapeWrite + 'a>, 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<bool, std::io::Error> {
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<bool, std::io::Error> {
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
}
}