tape: improve EOT error handling

This commit is contained in:
Dietmar Maurer 2021-04-12 11:25:40 +02:00
parent 164ad7b706
commit 318b310638
10 changed files with 152 additions and 116 deletions

View File

@ -10,7 +10,6 @@ use proxmox::{
identity, identity,
list_subdirs_api_method, list_subdirs_api_method,
tools::Uuid, tools::Uuid,
sys::error::SysError,
api::{ api::{
api, api,
section_config::SectionConfigData, section_config::SectionConfigData,
@ -60,6 +59,7 @@ use crate::{
Inventory, Inventory,
MediaCatalog, MediaCatalog,
MediaId, MediaId,
BlockReadError,
lock_media_set, lock_media_set,
lock_media_pool, lock_media_pool,
lock_unassigned_media_pool, lock_unassigned_media_pool,
@ -528,15 +528,11 @@ pub fn label_media(
drive.rewind()?; drive.rewind()?;
match drive.read_next_file() { match drive.read_next_file() {
Ok(Some(_file)) => bail!("media is not empty (format it first)"), Ok(_reader) => bail!("media is not empty (format it first)"),
Ok(None) => { /* EOF mark at BOT, assume tape is empty */ }, Err(BlockReadError::EndOfFile) => { /* EOF mark at BOT, assume tape is empty */ },
Err(BlockReadError::EndOfStream) => { /* tape is empty */ },
Err(err) => { Err(err) => {
println!("TEST {:?}", err); bail!("media read error - {}", err);
if err.is_errno(nix::errno::Errno::ENOSPC) || err.is_errno(nix::errno::Errno::EIO) {
/* assume tape is empty */
} else {
bail!("media read error - {}", err);
}
} }
} }
@ -1091,18 +1087,15 @@ fn barcode_label_media_worker(
drive.rewind()?; drive.rewind()?;
match drive.read_next_file() { match drive.read_next_file() {
Ok(Some(_file)) => { Ok(_reader) => {
worker.log(format!("media '{}' is not empty (format it first)", label_text)); worker.log(format!("media '{}' is not empty (format it first)", label_text));
continue; continue;
} }
Ok(None) => { /* EOF mark at BOT, assume tape is empty */ }, Err(BlockReadError::EndOfFile) => { /* EOF mark at BOT, assume tape is empty */ },
Err(err) => { Err(BlockReadError::EndOfStream) => { /* tape is empty */ },
if err.is_errno(nix::errno::Errno::ENOSPC) || err.is_errno(nix::errno::Errno::EIO) { Err(_err) => {
/* assume tape is empty */ worker.warn(format!("media '{}' read error (maybe not empty - format it first)", label_text));
} else { continue;
worker.warn(format!("media '{}' read error (maybe not empty - format it first)", label_text));
continue;
}
} }
} }

View File

@ -70,6 +70,7 @@ use crate::{
tape::{ tape::{
TAPE_STATUS_DIR, TAPE_STATUS_DIR,
TapeRead, TapeRead,
BlockReadError,
MediaId, MediaId,
MediaSet, MediaSet,
MediaCatalog, MediaCatalog,
@ -418,12 +419,19 @@ pub fn restore_media(
loop { loop {
let current_file_number = drive.current_file_number()?; let current_file_number = drive.current_file_number()?;
let reader = match drive.read_next_file()? { let reader = match drive.read_next_file() {
None => { Err(BlockReadError::EndOfFile) => {
task_log!(worker, "skip unexpected filemark at pos {}", current_file_number);
continue;
}
Err(BlockReadError::EndOfStream) => {
task_log!(worker, "detected EOT after {} files", current_file_number); task_log!(worker, "detected EOT after {} files", current_file_number);
break; break;
} }
Some(reader) => reader, Err(BlockReadError::Error(err)) => {
return Err(err.into());
}
Ok(reader) => reader,
}; };
restore_archive(worker, reader, current_file_number, target, &mut catalog, verbose)?; restore_archive(worker, reader, current_file_number, target, &mut catalog, verbose)?;
@ -811,12 +819,19 @@ pub fn fast_catalog_restore(
let current_file_number = drive.current_file_number()?; let current_file_number = drive.current_file_number()?;
{ // limit reader scope { // limit reader scope
let mut reader = match drive.read_next_file()? { let mut reader = match drive.read_next_file() {
None => { Err(BlockReadError::EndOfFile) => {
task_log!(worker, "skip unexpected filemark at pos {}", current_file_number);
continue;
}
Err(BlockReadError::EndOfStream) => {
task_log!(worker, "detected EOT after {} files", current_file_number); task_log!(worker, "detected EOT after {} files", current_file_number);
break; break;
} }
Some(reader) => reader, Err(BlockReadError::Error(err)) => {
return Err(err.into());
}
Ok(reader) => reader,
}; };
let header: MediaContentHeader = unsafe { reader.read_le_value()? }; let header: MediaContentHeader = unsafe { reader.read_le_value()? };

View File

@ -43,6 +43,7 @@ use proxmox_backup::{
media_pool::complete_pool_name, media_pool::complete_pool_name,
}, },
tape::{ tape::{
BlockReadError,
drive::{ drive::{
open_drive, open_drive,
lock_tape_device, lock_tape_device,
@ -587,12 +588,19 @@ fn debug_scan(mut param: Value) -> Result<(), Error> {
loop { loop {
let file_number = drive.current_file_number()?; let file_number = drive.current_file_number()?;
match drive.read_next_file()? { match drive.read_next_file() {
None => { Err(BlockReadError::EndOfFile) => {
println!("EOD"); println!("filemark number {}", file_number);
continue; continue;
}, }
Some(mut reader) => { Err(BlockReadError::EndOfStream) => {
println!("got EOT");
return Ok(());
}
Err(BlockReadError::Error(err)) => {
return Err(err.into());
}
Ok(mut reader) => {
println!("got file number {}", file_number); println!("got file number {}", file_number);
let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() }; let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() };

View File

@ -43,6 +43,7 @@ use crate::{
tape::{ tape::{
TapeRead, TapeRead,
TapeWrite, TapeWrite,
BlockReadError,
drive::{ drive::{
TapeDriver, TapeDriver,
}, },
@ -320,16 +321,9 @@ impl TapeDriver for LtoTapeHandle {
self.sg_tape.format_media(fast) self.sg_tape.format_media(fast)
} }
fn read_next_file<'a>(&'a mut self) -> Result<Option<Box<dyn TapeRead + 'a>>, std::io::Error> { fn read_next_file<'a>(&'a mut self) -> Result<Box<dyn TapeRead + 'a>, BlockReadError> {
let reader = self.sg_tape.open_reader()?; let reader = self.sg_tape.open_reader()?;
let handle = match reader { let handle: Box<dyn TapeRead> = Box::new(reader);
Some(reader) => {
let reader: Box<dyn TapeRead> = Box::new(reader);
Some(reader)
}
None => None,
};
Ok(handle) Ok(handle)
} }

View File

@ -32,7 +32,7 @@ use crate::{
}, },
tape::{ tape::{
BlockRead, BlockRead,
BlockReadStatus, BlockReadError,
BlockWrite, BlockWrite,
file_formats::{ file_formats::{
BlockedWriter, BlockedWriter,
@ -526,11 +526,13 @@ impl SgTape {
} }
} }
fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> { fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
let transfer_len = buffer.len(); let transfer_len = buffer.len();
if transfer_len > 0xFFFFFF { if transfer_len > 0xFFFFFF {
proxmox::io_bail!("read failed - buffer too large"); return Err(BlockReadError::Error(
proxmox::io_format_err!("read failed - buffer too large")
));
} }
let mut sg_raw = SgRaw::new(&mut self.file, 0) let mut sg_raw = SgRaw::new(&mut self.file, 0)
@ -549,21 +551,25 @@ impl SgTape {
let data = match sg_raw.do_in_command(&cmd, buffer) { let data = match sg_raw.do_in_command(&cmd, buffer) {
Ok(data) => data, Ok(data) => data,
Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 })) => { Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 })) => {
return Ok(BlockReadStatus::EndOfFile); return Err(BlockReadError::EndOfFile);
} }
Err(ScsiError::Sense(SenseInfo { sense_key: 8, asc: 0, ascq: 5 })) => { Err(ScsiError::Sense(SenseInfo { sense_key: 8, asc: 0, ascq: 5 })) => {
return Ok(BlockReadStatus::EndOfStream); return Err(BlockReadError::EndOfStream);
} }
Err(err) => { Err(err) => {
proxmox::io_bail!("read failed - {}", err); return Err(BlockReadError::Error(
proxmox::io_format_err!("read failed - {}", err)
));
} }
}; };
if data.len() != transfer_len { if data.len() != transfer_len {
proxmox::io_bail!("read failed - unexpected block len ({} != {})", data.len(), buffer.len()) return Err(BlockReadError::Error(
proxmox::io_format_err!("read failed - unexpected block len ({} != {})", data.len(), buffer.len())
));
} }
Ok(BlockReadStatus::Ok(transfer_len)) Ok(transfer_len)
} }
pub fn open_writer(&mut self) -> BlockedWriter<SgTapeWriter> { pub fn open_writer(&mut self) -> BlockedWriter<SgTapeWriter> {
@ -571,12 +577,9 @@ impl SgTape {
BlockedWriter::new(writer) BlockedWriter::new(writer)
} }
pub fn open_reader(&mut self) -> Result<Option<BlockedReader<SgTapeReader>>, std::io::Error> { pub fn open_reader(&mut self) -> Result<BlockedReader<SgTapeReader>, BlockReadError> {
let reader = SgTapeReader::new(self); let reader = SgTapeReader::new(self);
match BlockedReader::open(reader)? { BlockedReader::open(reader)
Some(reader) => Ok(Some(reader)),
None => Ok(None),
}
} }
/// Set important drive options /// Set important drive options
@ -702,7 +705,7 @@ impl <'a> SgTapeReader<'a> {
impl <'a> BlockRead for SgTapeReader<'a> { impl <'a> BlockRead for SgTapeReader<'a> {
fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> { fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
self.sg_tape.read_block(buffer) self.sg_tape.read_block(buffer)
} }
} }

View File

@ -44,6 +44,7 @@ use crate::{
tape::{ tape::{
TapeWrite, TapeWrite,
TapeRead, TapeRead,
BlockReadError,
MediaId, MediaId,
drive::lto::TapeAlertFlags, drive::lto::TapeAlertFlags,
file_formats::{ file_formats::{
@ -86,7 +87,7 @@ pub trait TapeDriver {
fn format_media(&mut self, fast: bool) -> Result<(), Error>; fn format_media(&mut self, fast: bool) -> Result<(), Error>;
/// Read/Open the next file /// Read/Open the next file
fn read_next_file<'a>(&'a mut self) -> Result<Option<Box<dyn TapeRead + 'a>>, std::io::Error>; fn read_next_file<'a>(&'a mut self) -> Result<Box<dyn TapeRead + 'a>, BlockReadError>;
/// Write/Append a new file /// Write/Append a new file
fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error>; fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error>;
@ -132,9 +133,17 @@ pub trait TapeDriver {
self.rewind()?; self.rewind()?;
let label = { let label = {
let mut reader = match self.read_next_file()? { let mut reader = match self.read_next_file() {
None => return Ok((None, None)), // tape is empty Err(BlockReadError::EndOfStream) => {
Some(reader) => reader, return Ok((None, None)); // tape is empty
}
Err(BlockReadError::EndOfFile) => {
bail!("got unexpected filemark at BOT");
}
Err(BlockReadError::Error(err)) => {
return Err(err.into());
}
Ok(reader) => reader,
}; };
let header: MediaContentHeader = unsafe { reader.read_le_value()? }; let header: MediaContentHeader = unsafe { reader.read_le_value()? };
@ -155,9 +164,17 @@ pub trait TapeDriver {
let mut media_id = MediaId { label, media_set_label: None }; let mut media_id = MediaId { label, media_set_label: None };
// try to read MediaSet label // try to read MediaSet label
let mut reader = match self.read_next_file()? { let mut reader = match self.read_next_file() {
None => return Ok((Some(media_id), None)), Err(BlockReadError::EndOfStream) => {
Some(reader) => reader, return Ok((Some(media_id), None));
}
Err(BlockReadError::EndOfFile) => {
bail!("got unexpected filemark after label");
}
Err(BlockReadError::Error(err)) => {
return Err(err.into());
}
Ok(reader) => reader,
}; };
let header: MediaContentHeader = unsafe { reader.read_le_value()? }; let header: MediaContentHeader = unsafe { reader.read_le_value()? };

View File

@ -15,6 +15,7 @@ use crate::{
tape::{ tape::{
TapeWrite, TapeWrite,
TapeRead, TapeRead,
BlockReadError,
changer::{ changer::{
MediaChange, MediaChange,
MtxStatus, MtxStatus,
@ -261,18 +262,18 @@ impl TapeDriver for VirtualTapeHandle {
} }
fn read_next_file(&mut self) -> Result<Option<Box<dyn TapeRead>>, io::Error> { fn read_next_file(&mut self) -> Result<Box<dyn TapeRead>, BlockReadError> {
let mut status = self.load_status() let mut status = self.load_status()
.map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?; .map_err(|err| BlockReadError::Error(io::Error::new(io::ErrorKind::Other, err.to_string())))?;
match status.current_tape { match status.current_tape {
Some(VirtualTapeStatus { ref name, ref mut pos }) => { Some(VirtualTapeStatus { ref name, ref mut pos }) => {
let index = self.load_tape_index(name) let index = self.load_tape_index(name)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?; .map_err(|err| BlockReadError::Error(io::Error::new(io::ErrorKind::Other, err.to_string())))?;
if *pos >= index.files { if *pos >= index.files {
return Ok(None); // EOM return Err(BlockReadError::EndOfStream);
} }
let path = self.tape_file_path(name, *pos); let path = self.tape_file_path(name, *pos);
@ -282,16 +283,15 @@ impl TapeDriver for VirtualTapeHandle {
*pos += 1; *pos += 1;
self.store_status(&status) self.store_status(&status)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?; .map_err(|err| BlockReadError::Error(io::Error::new(io::ErrorKind::Other, err.to_string())))?;
let reader = EmulateTapeReader::new(file); let reader = EmulateTapeReader::new(file);
let reader = BlockedReader::open(reader)?;
match BlockedReader::open(reader)? { Ok(Box::new(reader))
Some(reader) => Ok(Some(Box::new(reader))), }
None => Ok(None), None => {
} return Err(BlockReadError::Error(proxmox::io_format_err!("drive is empty (no tape loaded).")));
} }
None => proxmox::io_bail!("drive is empty (no tape loaded)."),
} }
} }

View File

@ -3,7 +3,7 @@ use std::io::Read;
use crate::tape::{ use crate::tape::{
TapeRead, TapeRead,
BlockRead, BlockRead,
BlockReadStatus, BlockReadError,
file_formats::{ file_formats::{
PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0, PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0,
BlockHeader, BlockHeader,
@ -37,15 +37,13 @@ impl <R: BlockRead> BlockedReader<R> {
/// Create a new BlockedReader instance. /// Create a new BlockedReader instance.
/// ///
/// This tries to read the first block, and returns None if we are /// This tries to read the first block. Please inspect the error
/// at EOT. /// to detect EOF and EOT.
pub fn open(mut reader: R) -> Result<Option<Self>, std::io::Error> { pub fn open(mut reader: R) -> Result<Self, BlockReadError> {
let mut buffer = BlockHeader::new(); let mut buffer = BlockHeader::new();
if !Self::read_block_frame(&mut buffer, &mut reader)? { Self::read_block_frame(&mut buffer, &mut reader)?;
return Ok(None);
}
let (_size, found_end_marker) = Self::check_buffer(&buffer, 0)?; let (_size, found_end_marker) = Self::check_buffer(&buffer, 0)?;
@ -58,7 +56,7 @@ impl <R: BlockRead> BlockedReader<R> {
got_eod = true; got_eod = true;
} }
Ok(Some(Self { Ok(Self {
reader, reader,
buffer, buffer,
found_end_marker, found_end_marker,
@ -67,7 +65,7 @@ impl <R: BlockRead> BlockedReader<R> {
seq_nr: 1, seq_nr: 1,
read_error: false, read_error: false,
read_pos: 0, read_pos: 0,
})) })
} }
fn check_buffer(buffer: &BlockHeader, seq_nr: u32) -> Result<(usize, bool), std::io::Error> { fn check_buffer(buffer: &BlockHeader, seq_nr: u32) -> Result<(usize, bool), std::io::Error> {
@ -95,7 +93,7 @@ impl <R: BlockRead> BlockedReader<R> {
Ok((size, found_end_marker)) Ok((size, found_end_marker))
} }
fn read_block_frame(buffer: &mut BlockHeader, reader: &mut R) -> Result<bool, std::io::Error> { fn read_block_frame(buffer: &mut BlockHeader, reader: &mut R) -> Result<(), BlockReadError> {
let data = unsafe { let data = unsafe {
std::slice::from_raw_parts_mut( std::slice::from_raw_parts_mut(
@ -104,38 +102,28 @@ impl <R: BlockRead> BlockedReader<R> {
) )
}; };
match reader.read_block(data) { let bytes = reader.read_block(data)?;
Ok(BlockReadStatus::Ok(bytes)) => {
if bytes != BlockHeader::SIZE { if bytes != BlockHeader::SIZE {
proxmox::io_bail!("got wrong block size"); return Err(proxmox::io_format_err!("got wrong block size").into());
}
Ok(true)
}
Ok(BlockReadStatus::EndOfFile) => {
Ok(false)
}
Ok(BlockReadStatus::EndOfStream) => {
return Err(std::io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32));
}
Err(err) => {
Err(err)
}
} }
Ok(())
} }
fn consume_eof_marker(reader: &mut R) -> Result<(), std::io::Error> { fn consume_eof_marker(reader: &mut R) -> Result<(), std::io::Error> {
let mut tmp_buf = [0u8; 512]; // use a small buffer for testing EOF let mut tmp_buf = [0u8; 512]; // use a small buffer for testing EOF
match reader.read_block(&mut tmp_buf) { match reader.read_block(&mut tmp_buf) {
Ok(BlockReadStatus::Ok(_)) => { Ok(_) => {
proxmox::io_bail!("detected tape block after block-stream end marker"); proxmox::io_bail!("detected tape block after block-stream end marker");
} }
Ok(BlockReadStatus::EndOfFile) => { Err(BlockReadError::EndOfFile) => {
return Ok(()); return Ok(());
} }
Ok(BlockReadStatus::EndOfStream) => { Err(BlockReadError::EndOfStream) => {
proxmox::io_bail!("got unexpected end of tape"); proxmox::io_bail!("got unexpected end of tape");
} }
Err(err) => { Err(BlockReadError::Error(err)) => {
return Err(err); return Err(err);
} }
} }
@ -143,13 +131,22 @@ impl <R: BlockRead> BlockedReader<R> {
fn read_block(&mut self) -> Result<usize, std::io::Error> { fn read_block(&mut self) -> Result<usize, std::io::Error> {
if !Self::read_block_frame(&mut self.buffer, &mut self.reader)? { match Self::read_block_frame(&mut self.buffer, &mut self.reader) {
self.got_eod = true; Ok(()) => { /* ok */ }
self.read_pos = self.buffer.payload.len(); Err(BlockReadError::EndOfFile) => {
if !self.found_end_marker { self.got_eod = true;
proxmox::io_bail!("detected tape stream without end marker"); self.read_pos = self.buffer.payload.len();
if !self.found_end_marker {
proxmox::io_bail!("detected tape stream without end marker");
}
return Ok(0); // EOD
}
Err(BlockReadError::EndOfStream) => {
proxmox::io_bail!("got unexpected end of tape");
}
Err(BlockReadError::Error(err)) => {
return Err(err);
} }
return Ok(0); // EOD
} }
let (size, found_end_marker) = Self::check_buffer(&self.buffer, self.seq_nr)?; let (size, found_end_marker) = Self::check_buffer(&self.buffer, self.seq_nr)?;

View File

@ -4,7 +4,7 @@ use proxmox::tools::io::ReadExt;
use crate::tape::{ use crate::tape::{
BlockRead, BlockRead,
BlockReadStatus, BlockReadError,
file_formats::PROXMOX_TAPE_BLOCK_SIZE, file_formats::PROXMOX_TAPE_BLOCK_SIZE,
}; };
@ -24,22 +24,27 @@ impl <R: Read> EmulateTapeReader<R> {
} }
impl <R: Read> BlockRead for EmulateTapeReader<R> { impl <R: Read> BlockRead for EmulateTapeReader<R> {
fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> { fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
if self.got_eof { if self.got_eof {
proxmox::io_bail!("detected read after EOF!"); return Err(BlockReadError::Error(proxmox::io_format_err!("detected read after EOF!")));
} }
match self.reader.read_exact_or_eof(buffer)? { match self.reader.read_exact_or_eof(buffer)? {
false => { false => {
self.got_eof = true; self.got_eof = true;
Ok(BlockReadStatus::EndOfFile) Err(BlockReadError::EndOfFile)
} }
true => { true => {
// test buffer len after EOF test (to allow EOF test with small buffers in BufferedReader) // test buffer len after EOF test (to allow EOF test with small buffers in BufferedReader)
if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE { if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE {
proxmox::io_bail!("EmulateTapeReader: read_block with wrong block size ({} != {})", return Err(BlockReadError::Error(
buffer.len(), PROXMOX_TAPE_BLOCK_SIZE); proxmox::io_format_err!(
"EmulateTapeReader: read_block with wrong block size ({} != {})",
buffer.len(),
PROXMOX_TAPE_BLOCK_SIZE,
)
));
} }
Ok(BlockReadStatus::Ok(buffer.len())) Ok(buffer.len())
} }
} }
} }

View File

@ -15,14 +15,18 @@ pub trait TapeRead: Read {
fn has_end_marker(&self) -> Result<bool, std::io::Error>; fn has_end_marker(&self) -> Result<bool, std::io::Error>;
} }
pub enum BlockReadStatus { #[derive(thiserror::Error, Debug)]
Ok(usize), pub enum BlockReadError {
#[error("{0}")]
Error(#[from] std::io::Error),
#[error("end of file")]
EndOfFile, EndOfFile,
#[error("end of data stream")]
EndOfStream, EndOfStream,
} }
/// Read streams of blocks /// Read streams of blocks
pub trait BlockRead { pub trait BlockRead {
/// Read the next block (whole buffer) /// Read the next block (whole buffer)
fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error>; fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError>;
} }