[pbs-devel] [PATCH 01/11] tape: introduce trait BlockRead

Dietmar Maurer dietmar at proxmox.com
Wed Apr 7 12:22:58 CEST 2021


---
 src/api2/tape/drive.rs                  |  1 +
 src/tape/drive/linux_tape.rs            | 53 ++++++++++++++++++-
 src/tape/drive/virtual_tape.rs          |  3 +-
 src/tape/file_formats/blocked_reader.rs | 70 +++++++++++++++++++------
 src/tape/helpers/emulate_tape_reader.rs | 64 ++++++++++------------
 src/tape/tape_read.rs                   | 35 ++++---------
 6 files changed, 145 insertions(+), 81 deletions(-)

diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index b753eb5b..b17f4203 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -531,6 +531,7 @@ pub fn label_media(
                 Ok(Some(_file)) => bail!("media is not empty (erase first)"),
                 Ok(None) => { /* EOF mark at BOT, assume 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 {
diff --git a/src/tape/drive/linux_tape.rs b/src/tape/drive/linux_tape.rs
index f8949196..9e86d0a3 100644
--- a/src/tape/drive/linux_tape.rs
+++ b/src/tape/drive/linux_tape.rs
@@ -1,6 +1,7 @@
 //! Driver for Linux SCSI tapes
 
 use std::fs::{OpenOptions, File};
+use std::io::Read;
 use std::os::unix::fs::OpenOptionsExt;
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
 use std::convert::TryFrom;
@@ -24,6 +25,8 @@ use crate::{
         LinuxDriveAndMediaStatus,
     },
     tape::{
+        BlockRead,
+        BlockReadStatus,
         TapeRead,
         TapeWrite,
         drive::{
@@ -507,7 +510,8 @@ impl TapeDriver for LinuxTapeHandle {
     }
 
     fn read_next_file<'a>(&'a mut self) -> Result<Option<Box<dyn TapeRead + 'a>>, std::io::Error> {
-        match BlockedReader::open(&mut self.file)? {
+        let reader = LinuxTapeReader::new(&mut self.file);
+        match BlockedReader::open(reader)? {
             Some(reader) => Ok(Some(Box::new(reader))),
             None => Ok(None),
         }
@@ -766,3 +770,50 @@ impl TapeWrite for TapeWriterHandle<'_> {
         self.writer.logical_end_of_media()
     }
 }
+
+pub struct LinuxTapeReader<'a> {
+    /// Assumes that 'file' is a linux tape device.
+    file: &'a mut File,
+    got_eof: bool,
+}
+
+impl <'a> LinuxTapeReader<'a> {
+
+    pub fn new(file: &'a mut File) -> Self {
+        Self { file, got_eof: false }
+    }
+}
+
+impl <'a> BlockRead for LinuxTapeReader<'a> {
+
+    /// Read a single block from a linux tape device
+    ///
+    /// Return true on success, false on EOD
+    fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> {
+        loop {
+            match self.file.read(buffer) {
+                Ok(0) => {
+                    let eod = self.got_eof;
+                    self.got_eof = true;
+                    if eod {
+                        return Ok(BlockReadStatus::EndOfStream);
+                    } else {
+                        return Ok(BlockReadStatus::EndOfFile);
+                    }
+                }
+                Ok(count) => {
+                    if count == buffer.len() {
+                        return Ok(BlockReadStatus::Ok(count));
+                    }
+                    proxmox::io_bail!("short block read ({} < {}). Tape drive uses wrong block size.",
+                                      count, buffer.len());
+                }
+                // handle interrupted system call
+                Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
+                    continue;
+                }
+                Err(err) => return Err(err),
+            }
+        }
+    }
+}
diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs
index d6b3d0c9..2fc9fdea 100644
--- a/src/tape/drive/virtual_tape.rs
+++ b/src/tape/drive/virtual_tape.rs
@@ -222,8 +222,7 @@ impl TapeDriver for VirtualTapeHandle {
                 self.store_status(&status)
                     .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
 
-                let reader = Box::new(file);
-                let reader = Box::new(EmulateTapeReader::new(reader));
+                let reader = EmulateTapeReader::new(file);
 
                 match BlockedReader::open(reader)? {
                     Some(reader) => Ok(Some(Box::new(reader))),
diff --git a/src/tape/file_formats/blocked_reader.rs b/src/tape/file_formats/blocked_reader.rs
index 3ef7e7f4..3df84a1b 100644
--- a/src/tape/file_formats/blocked_reader.rs
+++ b/src/tape/file_formats/blocked_reader.rs
@@ -2,7 +2,8 @@ use std::io::Read;
 
 use crate::tape::{
     TapeRead,
-    tape_device_read_block,
+    BlockRead,
+    BlockReadStatus,
     file_formats::{
         PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0,
         BlockHeader,
@@ -32,7 +33,7 @@ pub struct BlockedReader<R> {
     read_pos: usize,
 }
 
-impl <R: Read> BlockedReader<R> {
+impl <R: BlockRead> BlockedReader<R> {
 
     /// Create a new BlockedReader instance.
     ///
@@ -103,15 +104,41 @@ impl <R: Read> BlockedReader<R> {
             )
         };
 
-        tape_device_read_block(reader, data)
+        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)
+            }
+        }
     }
 
     fn consume_eof_marker(reader: &mut R) -> Result<(), std::io::Error> {
         let mut tmp_buf = [0u8; 512]; // use a small buffer for testing EOF
-        if tape_device_read_block(reader, &mut tmp_buf)? {
-            proxmox::io_bail!("detected tape block after stream end marker");
+        match reader.read_block(&mut tmp_buf) {
+            Ok(BlockReadStatus::Ok(_)) => {
+                proxmox::io_bail!("detected tape block after block-stream end marker");
+            }
+            Ok(BlockReadStatus::EndOfFile) => {
+                return Ok(());
+            }
+            Ok(BlockReadStatus::EndOfStream) => {
+                proxmox::io_bail!("got unexpected end of tape");
+            }
+            Err(err) => {
+                return Err(err);
+            }
         }
-        Ok(())
     }
 
     fn read_block(&mut self) -> Result<usize, std::io::Error> {
@@ -141,7 +168,7 @@ impl <R: Read> BlockedReader<R> {
     }
 }
 
-impl <R: Read> TapeRead for BlockedReader<R> {
+impl <R: BlockRead> TapeRead for BlockedReader<R> {
 
     fn is_incomplete(&self) -> Result<bool, std::io::Error> {
         if !self.got_eod {
@@ -163,7 +190,7 @@ impl <R: Read> TapeRead for BlockedReader<R> {
     }
 }
 
-impl <R: Read> Read for BlockedReader<R> {
+impl <R: BlockRead> Read for BlockedReader<R> {
 
     fn read(&mut self, buffer: &mut [u8]) -> Result<usize, std::io::Error> {
 
@@ -207,6 +234,7 @@ mod test {
     use anyhow::Error;
     use crate::tape::{
         TapeWrite,
+        helpers::{EmulateTapeReader, EmulateTapeWriter},
         file_formats::{
             PROXMOX_TAPE_BLOCK_SIZE,
             BlockedReader,
@@ -218,11 +246,14 @@ mod test {
 
         let mut tape_data = Vec::new();
 
-        let mut writer = BlockedWriter::new(&mut tape_data);
+        {
+            let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024*10);
+            let mut writer = BlockedWriter::new(writer);
 
-        writer.write_all(data)?;
+            writer.write_all(data)?;
 
-        writer.finish(false)?;
+            writer.finish(false)?;
+        }
 
         assert_eq!(
             tape_data.len(),
@@ -231,6 +262,7 @@ mod test {
         );
 
         let reader = &mut &tape_data[..];
+        let reader = EmulateTapeReader::new(reader);
         let mut reader = BlockedReader::open(reader)?.unwrap();
 
         let mut read_data = Vec::with_capacity(PROXMOX_TAPE_BLOCK_SIZE);
@@ -263,6 +295,7 @@ mod test {
     fn no_data() -> Result<(), Error> {
         let tape_data = Vec::new();
         let reader = &mut &tape_data[..];
+        let reader = EmulateTapeReader::new(reader);
         let reader = BlockedReader::open(reader)?;
         assert!(reader.is_none());
 
@@ -273,13 +306,16 @@ mod test {
     fn no_end_marker() -> Result<(), Error> {
         let mut tape_data = Vec::new();
         {
-            let mut writer = BlockedWriter::new(&mut tape_data);
+            let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024);
+            let mut writer = BlockedWriter::new(writer);
             // write at least one block
             let data = proxmox::sys::linux::random_data(PROXMOX_TAPE_BLOCK_SIZE)?;
             writer.write_all(&data)?;
             // but do not call finish here
         }
+
         let reader = &mut &tape_data[..];
+        let reader = EmulateTapeReader::new(reader);
         let mut reader = BlockedReader::open(reader)?.unwrap();
 
         let mut data = Vec::with_capacity(PROXMOX_TAPE_BLOCK_SIZE);
@@ -292,13 +328,17 @@ mod test {
     fn small_read_buffer() -> Result<(), Error> {
         let mut tape_data = Vec::new();
 
-        let mut writer = BlockedWriter::new(&mut tape_data);
+        {
+            let writer = EmulateTapeWriter::new(&mut tape_data, 1024*1024);
+            let mut writer = BlockedWriter::new(writer);
 
-        writer.write_all(b"ABC")?;
+            writer.write_all(b"ABC")?;
 
-        writer.finish(false)?;
+            writer.finish(false)?;
+        }
 
         let reader = &mut &tape_data[..];
+        let reader = EmulateTapeReader::new(reader);
         let mut reader = BlockedReader::open(reader)?.unwrap();
 
         let mut buf = [0u8; 1];
diff --git a/src/tape/helpers/emulate_tape_reader.rs b/src/tape/helpers/emulate_tape_reader.rs
index 1b6d4c5e..fbb19028 100644
--- a/src/tape/helpers/emulate_tape_reader.rs
+++ b/src/tape/helpers/emulate_tape_reader.rs
@@ -1,56 +1,46 @@
-use std::io::{self, Read};
+use std::io::Read;
 
-use crate::tape::file_formats::PROXMOX_TAPE_BLOCK_SIZE;
+use proxmox::tools::io::ReadExt;
+
+use crate::tape::{
+    BlockRead,
+    BlockReadStatus,
+    file_formats::PROXMOX_TAPE_BLOCK_SIZE,
+};
 
 /// Emulate tape read behavior on a normal Reader
 ///
 /// Tapes reads are always return one whole block PROXMOX_TAPE_BLOCK_SIZE.
-pub struct EmulateTapeReader<R> {
+pub struct EmulateTapeReader<R: Read> {
     reader: R,
+    got_eof: bool,
 }
 
 impl <R: Read> EmulateTapeReader<R> {
 
     pub fn new(reader: R) -> Self {
-        Self { reader }
+        Self { reader, got_eof: false }
     }
 }
 
-impl <R: Read> Read for EmulateTapeReader<R> {
-
-    fn read(&mut self, mut buffer: &mut [u8]) -> Result<usize, io::Error> {
-
-        let initial_buffer_len = buffer.len(); // store, check later
-
-        let mut bytes = 0;
-
-        while !buffer.is_empty() {
-            match self.reader.read(buffer) {
-                Ok(0) => break,
-                Ok(n) => {
-                    bytes += n;
-                    let tmp = buffer;
-                    buffer = &mut tmp[n..];
+impl <R: Read> BlockRead for EmulateTapeReader<R> {
+    fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> {
+        if self.got_eof {
+            proxmox::io_bail!("detected read after EOF!");
+        }
+        match self.reader.read_exact_or_eof(buffer)? {
+            false => {
+                self.got_eof = true;
+                Ok(BlockReadStatus::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);
                 }
-                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
-                Err(e) => return Err(e),
+                Ok(BlockReadStatus::Ok(buffer.len()))
             }
         }
-
-        if bytes == 0 {
-            return Ok(0);
-        }
-
-        // test buffer len after EOF test (to allow EOF test with small buffers in BufferedReader)
-        if initial_buffer_len != PROXMOX_TAPE_BLOCK_SIZE {
-            proxmox::io_bail!("EmulateTapeReader: got read with wrong block size ({} != {})",
-                              buffer.len(), PROXMOX_TAPE_BLOCK_SIZE);
-        }
-
-        if !buffer.is_empty() {
-            Err(io::Error::new(io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
-        } else {
-            Ok(bytes)
-        }
     }
 }
diff --git a/src/tape/tape_read.rs b/src/tape/tape_read.rs
index c1ba52e6..7cbe2b8b 100644
--- a/src/tape/tape_read.rs
+++ b/src/tape/tape_read.rs
@@ -15,31 +15,14 @@ pub trait TapeRead: Read {
     fn has_end_marker(&self) -> Result<bool, std::io::Error>;
 }
 
-/// Read a single block from a tape device
-///
-/// Assumes that 'reader' is a linux tape device.
-///
-/// Return true on success, false on EOD
-pub fn tape_device_read_block<R: Read>(
-    reader: &mut R,
-    buffer: &mut [u8],
-) -> Result<bool, std::io::Error> {
+pub enum BlockReadStatus {
+    Ok(usize),
+    EndOfFile,
+    EndOfStream,
+}
 
-    loop {
-        match reader.read(buffer) {
-            Ok(0) => { return Ok(false); /* EOD */ }
-            Ok(count) => {
-                if count == buffer.len() {
-                    return Ok(true);
-                }
-                proxmox::io_bail!("short block read ({} < {}). Tape drive uses wrong block size.",
-                                  count, buffer.len());
-            }
-            // handle interrupted system call
-            Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
-                continue;
-            }
-            Err(err) => return Err(err),
-        }
-    }
+/// 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>;
 }
-- 
2.20.1





More information about the pbs-devel mailing list