[pbs-devel] [PATCH 2/2] tape: improve EOT error handling

Dietmar Maurer dietmar at proxmox.com
Mon Apr 12 11:32:47 CEST 2021


---
 src/api2/tape/drive.rs                  | 29 ++++-------
 src/api2/tape/restore.rs                | 27 +++++++---
 src/bin/proxmox-tape.rs                 | 18 +++++--
 src/tape/drive/lto/mod.rs               | 12 ++---
 src/tape/drive/lto/sg_tape.rs           | 31 ++++++-----
 src/tape/drive/mod.rs                   | 31 ++++++++---
 src/tape/drive/virtual_tape.rs          | 22 ++++----
 src/tape/file_formats/blocked_reader.rs | 69 ++++++++++++-------------
 src/tape/helpers/emulate_tape_reader.rs | 19 ++++---
 src/tape/tape_read.rs                   | 10 ++--
 10 files changed, 152 insertions(+), 116 deletions(-)

diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index ca45bb69..c1fd72e1 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -10,7 +10,6 @@ use proxmox::{
     identity,
     list_subdirs_api_method,
     tools::Uuid,
-    sys::error::SysError,
     api::{
         api,
         section_config::SectionConfigData,
@@ -60,6 +59,7 @@ use crate::{
         Inventory,
         MediaCatalog,
         MediaId,
+        BlockReadError,
         lock_media_set,
         lock_media_pool,
         lock_unassigned_media_pool,
@@ -528,15 +528,11 @@ pub fn label_media(
             drive.rewind()?;
 
             match drive.read_next_file() {
-                Ok(Some(_file)) => bail!("media is not empty (format it first)"),
-                Ok(None) => { /* EOF mark at BOT, assume tape is empty */ },
+                Ok(_reader) => bail!("media is not empty (format it first)"),
+                Err(BlockReadError::EndOfFile) => { /* EOF mark at BOT, assume tape is empty */ },
+                Err(BlockReadError::EndOfStream) => { /* tape is empty */ },
                 Err(err) => {
-                    println!("TEST {:?}", 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);
-                    }
+                    bail!("media read error - {}", err);
                 }
             }
 
@@ -1091,18 +1087,15 @@ fn barcode_label_media_worker(
         drive.rewind()?;
 
         match drive.read_next_file() {
-            Ok(Some(_file)) => {
+            Ok(_reader) => {
                 worker.log(format!("media '{}' is not empty (format it first)", label_text));
                 continue;
             }
-            Ok(None) => { /* EOF mark at BOT, assume tape is empty */ },
-            Err(err) => {
-                if err.is_errno(nix::errno::Errno::ENOSPC) || err.is_errno(nix::errno::Errno::EIO) {
-                    /* assume tape is empty */
-                } else {
-                    worker.warn(format!("media '{}' read error (maybe not empty - format it first)", label_text));
-                    continue;
-                }
+            Err(BlockReadError::EndOfFile) => { /* EOF mark at BOT, assume tape is empty */ },
+            Err(BlockReadError::EndOfStream) => { /* tape is empty */ },
+            Err(_err) => {
+                worker.warn(format!("media '{}' read error (maybe not empty - format it first)", label_text));
+                continue;
             }
         }
 
diff --git a/src/api2/tape/restore.rs b/src/api2/tape/restore.rs
index 78aac73c..afce3449 100644
--- a/src/api2/tape/restore.rs
+++ b/src/api2/tape/restore.rs
@@ -70,6 +70,7 @@ use crate::{
     tape::{
         TAPE_STATUS_DIR,
         TapeRead,
+        BlockReadError,
         MediaId,
         MediaSet,
         MediaCatalog,
@@ -418,12 +419,19 @@ pub fn restore_media(
 
     loop {
         let current_file_number = drive.current_file_number()?;
-        let reader = match drive.read_next_file()? {
-            None => {
+        let reader = match drive.read_next_file() {
+            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);
                 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)?;
@@ -811,12 +819,19 @@ pub fn fast_catalog_restore(
         let current_file_number = drive.current_file_number()?;
 
         { // limit reader scope
-            let mut reader = match drive.read_next_file()? {
-                None => {
+            let mut reader = match drive.read_next_file() {
+                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);
                     break;
                 }
-                Some(reader) => reader,
+                Err(BlockReadError::Error(err)) => {
+                    return Err(err.into());
+                }
+                Ok(reader) => reader,
             };
 
             let header: MediaContentHeader = unsafe { reader.read_le_value()? };
diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs
index 1d23b71c..3b3bbafa 100644
--- a/src/bin/proxmox-tape.rs
+++ b/src/bin/proxmox-tape.rs
@@ -43,6 +43,7 @@ use proxmox_backup::{
         media_pool::complete_pool_name,
     },
     tape::{
+        BlockReadError,
         drive::{
             open_drive,
             lock_tape_device,
@@ -587,12 +588,19 @@ fn debug_scan(mut param: Value) -> Result<(), Error> {
     loop {
         let file_number = drive.current_file_number()?;
 
-        match drive.read_next_file()? {
-            None => {
-                println!("EOD");
+        match drive.read_next_file() {
+            Err(BlockReadError::EndOfFile) => {
+                println!("filemark");
                 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);
 
                 let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() };
diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
index 9dc85ac9..642c3cc7 100644
--- a/src/tape/drive/lto/mod.rs
+++ b/src/tape/drive/lto/mod.rs
@@ -43,6 +43,7 @@ use crate::{
     tape::{
         TapeRead,
         TapeWrite,
+        BlockReadError,
         drive::{
             TapeDriver,
         },
@@ -320,16 +321,9 @@ impl TapeDriver for LtoTapeHandle {
         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 handle = match reader {
-            Some(reader) => {
-                let reader: Box<dyn TapeRead> = Box::new(reader);
-                Some(reader)
-            }
-            None => None,
-        };
-
+        let handle: Box<dyn TapeRead> = Box::new(reader);
         Ok(handle)
     }
 
diff --git a/src/tape/drive/lto/sg_tape.rs b/src/tape/drive/lto/sg_tape.rs
index 9f59b321..3f73e025 100644
--- a/src/tape/drive/lto/sg_tape.rs
+++ b/src/tape/drive/lto/sg_tape.rs
@@ -32,7 +32,7 @@ use crate::{
     },
     tape::{
         BlockRead,
-        BlockReadStatus,
+        BlockReadError,
         BlockWrite,
         file_formats::{
             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();
 
         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)
@@ -549,21 +551,25 @@ impl SgTape {
         let data = match sg_raw.do_in_command(&cmd, buffer) {
             Ok(data) => data,
             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 })) => {
-                return Ok(BlockReadStatus::EndOfStream);
+                return Err(BlockReadError::EndOfStream);
             }
             Err(err) => {
-                proxmox::io_bail!("read failed - {}", err);
+                return Err(BlockReadError::Error(
+                    proxmox::io_format_err!("read failed - {}", err)
+                ));
             }
         };
 
         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> {
@@ -571,12 +577,9 @@ impl SgTape {
         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);
-        match BlockedReader::open(reader)? {
-            Some(reader) => Ok(Some(reader)),
-            None => Ok(None),
-        }
+        BlockedReader::open(reader)
     }
 
     /// Set important drive options
@@ -702,7 +705,7 @@ impl <'a> 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)
     }
 }
diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs
index 0d9e3db3..fd8f503d 100644
--- a/src/tape/drive/mod.rs
+++ b/src/tape/drive/mod.rs
@@ -44,6 +44,7 @@ use crate::{
     tape::{
         TapeWrite,
         TapeRead,
+        BlockReadError,
         MediaId,
         drive::lto::TapeAlertFlags,
         file_formats::{
@@ -86,7 +87,7 @@ pub trait TapeDriver {
     fn format_media(&mut self, fast: bool) -> Result<(), Error>;
 
     /// 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
     fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error>;
@@ -132,9 +133,17 @@ pub trait TapeDriver {
         self.rewind()?;
 
         let label = {
-            let mut reader = match self.read_next_file()? {
-                None => return Ok((None, None)), // tape is empty
-                Some(reader) => reader,
+            let mut reader = match self.read_next_file() {
+                Err(BlockReadError::EndOfStream) => {
+                    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()? };
@@ -155,9 +164,17 @@ pub trait TapeDriver {
         let mut media_id = MediaId { label, media_set_label: None };
 
         // try to read MediaSet label
-        let mut reader = match self.read_next_file()? {
-            None => return Ok((Some(media_id), None)),
-            Some(reader) => reader,
+        let mut reader = match self.read_next_file() {
+            Err(BlockReadError::EndOfStream) => {
+                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()? };
diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs
index a852056a..f7ebc0bb 100644
--- a/src/tape/drive/virtual_tape.rs
+++ b/src/tape/drive/virtual_tape.rs
@@ -15,6 +15,7 @@ use crate::{
     tape::{
         TapeWrite,
         TapeRead,
+        BlockReadError,
         changer::{
             MediaChange,
             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()
-            .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 {
             Some(VirtualTapeStatus { ref name, ref mut pos }) => {
 
                 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 {
-                    return Ok(None); // EOM
+                    return Err(BlockReadError::EndOfStream);
                 }
 
                 let path = self.tape_file_path(name, *pos);
@@ -282,16 +283,15 @@ impl TapeDriver for VirtualTapeHandle {
 
                 *pos += 1;
                 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);
-
-                match BlockedReader::open(reader)? {
-                    Some(reader) => Ok(Some(Box::new(reader))),
-                    None => Ok(None),
-                }
+                let reader = BlockedReader::open(reader)?;
+                Ok(Box::new(reader))
+            }
+            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)."),
         }
     }
 
diff --git a/src/tape/file_formats/blocked_reader.rs b/src/tape/file_formats/blocked_reader.rs
index 3df84a1b..936eb24b 100644
--- a/src/tape/file_formats/blocked_reader.rs
+++ b/src/tape/file_formats/blocked_reader.rs
@@ -3,7 +3,7 @@ use std::io::Read;
 use crate::tape::{
     TapeRead,
     BlockRead,
-    BlockReadStatus,
+    BlockReadError,
     file_formats::{
         PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0,
         BlockHeader,
@@ -37,15 +37,13 @@ impl <R: BlockRead> BlockedReader<R> {
 
     /// Create a new BlockedReader instance.
     ///
-    /// This tries to read the first block, and returns None if we are
-    /// at EOT.
-    pub fn open(mut reader: R) -> Result<Option<Self>, std::io::Error> {
+    /// This tries to read the first block. Please inspect the error
+    /// to detect EOF and EOT.
+    pub fn open(mut reader: R) -> Result<Self, BlockReadError> {
 
         let mut buffer = BlockHeader::new();
 
-        if !Self::read_block_frame(&mut buffer, &mut reader)? {
-            return Ok(None);
-        }
+        Self::read_block_frame(&mut buffer, &mut reader)?;
 
         let (_size, found_end_marker) = Self::check_buffer(&buffer, 0)?;
 
@@ -58,7 +56,7 @@ impl <R: BlockRead> BlockedReader<R> {
             got_eod = true;
         }
 
-        Ok(Some(Self {
+        Ok(Self {
             reader,
             buffer,
             found_end_marker,
@@ -67,7 +65,7 @@ impl <R: BlockRead> BlockedReader<R> {
             seq_nr: 1,
             read_error: false,
             read_pos: 0,
-        }))
+        })
     }
 
     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))
     }
 
-    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 {
             std::slice::from_raw_parts_mut(
@@ -104,38 +102,28 @@ impl <R: BlockRead> BlockedReader<R> {
             )
         };
 
-        match reader.read_block(data) {
-            Ok(BlockReadStatus::Ok(bytes)) => {
-                if bytes != BlockHeader::SIZE {
-                    proxmox::io_bail!("got wrong block size");
-                }
-                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)
-            }
+        let bytes = reader.read_block(data)?;
+
+        if bytes != BlockHeader::SIZE {
+            return Err(proxmox::io_format_err!("got wrong block size").into());
         }
+
+        Ok(())
     }
 
     fn consume_eof_marker(reader: &mut R) -> Result<(), std::io::Error> {
         let mut tmp_buf = [0u8; 512]; // use a small buffer for testing EOF
         match reader.read_block(&mut tmp_buf) {
-            Ok(BlockReadStatus::Ok(_)) => {
+            Ok(_) => {
                 proxmox::io_bail!("detected tape block after block-stream end marker");
             }
-            Ok(BlockReadStatus::EndOfFile) => {
+            Err(BlockReadError::EndOfFile) => {
                 return Ok(());
             }
-            Ok(BlockReadStatus::EndOfStream) => {
+            Err(BlockReadError::EndOfStream) => {
                 proxmox::io_bail!("got unexpected end of tape");
             }
-            Err(err) => {
+            Err(BlockReadError::Error(err)) => {
                 return Err(err);
             }
         }
@@ -143,13 +131,22 @@ impl <R: BlockRead> BlockedReader<R> {
 
     fn read_block(&mut self) -> Result<usize, std::io::Error> {
 
-        if !Self::read_block_frame(&mut self.buffer, &mut self.reader)? {
-            self.got_eod = true;
-            self.read_pos = self.buffer.payload.len();
-            if !self.found_end_marker {
-                proxmox::io_bail!("detected tape stream without end marker");
+        match Self::read_block_frame(&mut self.buffer, &mut self.reader) {
+            Ok(()) => { /* ok */ }
+            Err(BlockReadError::EndOfFile) => {
+                self.got_eod = true;
+                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)?;
diff --git a/src/tape/helpers/emulate_tape_reader.rs b/src/tape/helpers/emulate_tape_reader.rs
index fbb19028..92c975f9 100644
--- a/src/tape/helpers/emulate_tape_reader.rs
+++ b/src/tape/helpers/emulate_tape_reader.rs
@@ -4,7 +4,7 @@ use proxmox::tools::io::ReadExt;
 
 use crate::tape::{
     BlockRead,
-    BlockReadStatus,
+    BlockReadError,
     file_formats::PROXMOX_TAPE_BLOCK_SIZE,
 };
 
@@ -24,22 +24,27 @@ impl <R: Read> 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 {
-            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)? {
             false => {
                 self.got_eof = true;
-                Ok(BlockReadStatus::EndOfFile)
+                Err(BlockReadError::EndOfFile)
             }
             true => {
                 // test buffer len after EOF test (to allow EOF test with small buffers in BufferedReader)
                 if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE {
-                    proxmox::io_bail!("EmulateTapeReader: read_block with wrong block size ({} != {})",
-                                      buffer.len(), PROXMOX_TAPE_BLOCK_SIZE);
+                    return Err(BlockReadError::Error(
+                        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())
             }
         }
     }
diff --git a/src/tape/tape_read.rs b/src/tape/tape_read.rs
index 7cbe2b8b..c617ec61 100644
--- a/src/tape/tape_read.rs
+++ b/src/tape/tape_read.rs
@@ -15,14 +15,18 @@ pub trait TapeRead: Read {
     fn has_end_marker(&self) -> Result<bool, std::io::Error>;
 }
 
-pub enum BlockReadStatus {
-    Ok(usize),
+#[derive(thiserror::Error, Debug)]
+pub enum BlockReadError {
+    #[error("{0}")]
+    Error(#[from] std::io::Error),
+    #[error("end of file")]
     EndOfFile,
+    #[error("end of data stream")]
     EndOfStream,
 }
 
 /// Read streams of blocks
 pub trait BlockRead {
     /// 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>;
 }
-- 
2.20.1





More information about the pbs-devel mailing list