public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH 00/11] Userspace tape driver
@ 2021-04-07 10:22 Dietmar Maurer
  2021-04-07 10:22 ` [pbs-devel] [PATCH 01/11] tape: introduce trait BlockRead Dietmar Maurer
                   ` (10 more replies)
  0 siblings, 11 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:22 UTC (permalink / raw)
  To: pbs-devel

This is a userspace drive implementation using SG_IO.

Why we do not use the Linux tape driver anymore, because:

- missing features (MAM, Encryption, ...)

- strange permission handling - only root (or CAP_SYS_RAWIO) can
   do SG_IO (SYS_RAW_IO)

- unability to detect EOT (you just get EIO)


Dietmar Maurer (11):
  tape: introduce trait BlockRead
  tape: introduce trait BlockWrite
  tape: implement LTO userspace driver
  tape: implement format/erase
  tape: fix LEOM handling
  tape: make fsf/bsf driver specific
  tape: make sure there is a filemark at the end of the tape
  sgutils2: add scsi_mode_sense helper
  tape: correctly set/display drive option
  tape: pmt - re-implement fsr/bsr
  tape: pmt - re-implement lock/unlock command

 debian/proxmox-backup-server.udev       |  18 +
 src/api2/config/changer.rs              |   4 +-
 src/api2/config/drive.rs                |  44 +-
 src/api2/tape/changer.rs                |   4 +-
 src/api2/tape/drive.rs                  |  65 +--
 src/api2/tape/mod.rs                    |   4 +-
 src/api2/types/tape/drive.rs            |  34 +-
 src/bin/pmt.rs                          | 347 +++---------
 src/bin/pmtx.rs                         |   4 +-
 src/bin/proxmox-tape.rs                 |  17 +-
 src/bin/proxmox_tape/drive.rs           |   8 +-
 src/bin/sg-tape-cmd.rs                  | 189 +------
 src/config/drive.rs                     |  18 +-
 src/tape/changer/mod.rs                 |   4 +-
 src/tape/drive/linux_tape.rs            | 173 ++++--
 src/tape/drive/lto/mod.rs               | 474 ++++++++++++++++
 src/tape/drive/lto/sg_tape.rs           | 720 ++++++++++++++++++++++++
 src/tape/drive/mod.rs                   |  53 +-
 src/tape/drive/virtual_tape.rs          | 116 ++--
 src/tape/file_formats/blocked_reader.rs |  70 ++-
 src/tape/file_formats/blocked_writer.rs |  37 +-
 src/tape/helpers/emulate_tape_reader.rs |  64 +--
 src/tape/helpers/emulate_tape_writer.rs |  37 +-
 src/tape/linux_list_drives.rs           |  51 +-
 src/tape/pool_writer/mod.rs             |   2 +-
 src/tape/tape_read.rs                   |  35 +-
 src/tape/tape_write.rs                  |  61 +-
 src/tools/sgutils2.rs                   | 125 +++-
 www/Utils.js                            |   2 +-
 www/tape/DriveStatus.js                 |   8 +-
 www/tape/window/Erase.js                |   4 +-
 31 files changed, 1939 insertions(+), 853 deletions(-)
 create mode 100644 debian/proxmox-backup-server.udev
 create mode 100644 src/tape/drive/lto/mod.rs
 create mode 100644 src/tape/drive/lto/sg_tape.rs

-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 01/11] tape: introduce trait BlockRead
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
@ 2021-04-07 10:22 ` Dietmar Maurer
  2021-04-07 10:22 ` [pbs-devel] [PATCH 02/11] tape: introduce trait BlockWrite Dietmar Maurer
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:22 UTC (permalink / raw)
  To: pbs-devel

---
 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




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 02/11] tape: introduce trait BlockWrite
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
  2021-04-07 10:22 ` [pbs-devel] [PATCH 01/11] tape: introduce trait BlockRead Dietmar Maurer
@ 2021-04-07 10:22 ` Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 03/11] tape: implement LTO userspace driver Dietmar Maurer
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:22 UTC (permalink / raw)
  To: pbs-devel

---
 src/tape/drive/linux_tape.rs            | 126 +++++++++++++++---------
 src/tape/drive/virtual_tape.rs          |   3 +-
 src/tape/file_formats/blocked_writer.rs |  37 +++++--
 src/tape/helpers/emulate_tape_writer.rs |  37 +++----
 src/tape/tape_write.rs                  |  61 ++----------
 5 files changed, 141 insertions(+), 123 deletions(-)

diff --git a/src/tape/drive/linux_tape.rs b/src/tape/drive/linux_tape.rs
index 9e86d0a3..d556f0f5 100644
--- a/src/tape/drive/linux_tape.rs
+++ b/src/tape/drive/linux_tape.rs
@@ -1,7 +1,7 @@
 //! Driver for Linux SCSI tapes
 
 use std::fs::{OpenOptions, File};
-use std::io::Read;
+use std::io::{Read, Write};
 use std::os::unix::fs::OpenOptionsExt;
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
 use std::convert::TryFrom;
@@ -9,8 +9,10 @@ use std::convert::TryFrom;
 use anyhow::{bail, format_err, Error};
 use nix::fcntl::{fcntl, FcntlArg, OFlag};
 
-use proxmox::sys::error::SysResult;
-use proxmox::tools::Uuid;
+use proxmox::{
+    tools::Uuid,
+    sys::error::{SysError, SysResult},
+};
 
 use crate::{
     config,
@@ -25,6 +27,7 @@ use crate::{
         LinuxDriveAndMediaStatus,
     },
     tape::{
+        BlockWrite,
         BlockRead,
         BlockReadStatus,
         TapeRead,
@@ -257,10 +260,9 @@ impl LinuxTapeHandle {
         Ok(())
     }
 
-    /// Write a single EOF mark
-    pub fn write_eof_mark(&self) -> Result<(), Error> {
-        tape_write_eof_mark(&self.file)?;
-        Ok(())
+    /// Write a single EOF mark without flushing buffers
+    pub fn write_eof_mark(&mut self) -> Result<(), std::io::Error> {
+        tape_write_eof_mark(&mut self.file)
     }
 
     /// Set the drive's block length to the value specified.
@@ -519,10 +521,8 @@ impl TapeDriver for LinuxTapeHandle {
 
     fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error> {
 
-        let handle = TapeWriterHandle {
-            writer: BlockedWriter::new(&mut self.file),
-        };
-
+        let writer = LinuxTapeWriter::new(&mut self.file);
+        let handle = BlockedWriter::new(writer);
         Ok(Box::new(handle))
     }
 
@@ -545,27 +545,27 @@ impl TapeDriver for LinuxTapeHandle {
 
         self.set_encryption(None)?;
 
-        let mut handle = TapeWriterHandle {
-            writer: BlockedWriter::new(&mut self.file),
-        };
+        { // limit handle scope
+            let mut handle = self.write_file()?;
 
-        let mut value = serde_json::to_value(media_set_label)?;
-        if media_set_label.encryption_key_fingerprint.is_some() {
-            match key_config {
-                Some(key_config) => {
-                    value["key-config"] = serde_json::to_value(key_config)?;
-                }
-                None => {
-                    bail!("missing encryption key config");
+            let mut value = serde_json::to_value(media_set_label)?;
+            if media_set_label.encryption_key_fingerprint.is_some() {
+                match key_config {
+                    Some(key_config) => {
+                        value["key-config"] = serde_json::to_value(key_config)?;
+                    }
+                    None => {
+                        bail!("missing encryption key config");
+                    }
                 }
             }
-        }
 
-        let raw = serde_json::to_string_pretty(&value)?;
+            let raw = serde_json::to_string_pretty(&value)?;
 
-        let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32);
-        handle.write_header(&header, raw.as_bytes())?;
-        handle.finish(false)?;
+            let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32);
+            handle.write_header(&header, raw.as_bytes())?;
+            handle.finish(false)?;
+        }
 
         self.sync()?; // sync data to tape
 
@@ -655,7 +655,7 @@ impl TapeDriver for LinuxTapeHandle {
 }
 
 /// Write a single EOF mark without flushing buffers
-fn tape_write_eof_mark(file: &File) -> Result<(), std::io::Error> {
+fn tape_write_eof_mark(file: &mut File) -> Result<(), std::io::Error> {
 
     let cmd = mtop { mt_op: MTCmd::MTWEOFI, mt_count: 1 };
 
@@ -745,29 +745,67 @@ pub fn read_tapedev_options(file: &File) -> Result<SetDrvBufferOptions, Error> {
 }
 
 
-/// like BlockedWriter, but writes EOF mark on finish
-pub struct TapeWriterHandle<'a> {
-    writer: BlockedWriter<&'a mut File>,
+struct LinuxTapeWriter<'a> {
+    /// Assumes that 'file' is a linux tape device.
+    file: &'a mut File,
 }
 
-impl TapeWrite for TapeWriterHandle<'_> {
-
-    fn write_all(&mut self, data: &[u8]) -> Result<bool, std::io::Error> {
-        self.writer.write_all(data)
+impl <'a> LinuxTapeWriter<'a> {
+    pub fn new(file: &'a mut File) -> Self {
+        Self { file }
     }
+}
 
-    fn bytes_written(&self) -> usize {
-        self.writer.bytes_written()
-    }
+impl <'a> BlockWrite for LinuxTapeWriter<'a> {
 
-    fn finish(&mut self, incomplete: bool) -> Result<bool, std::io::Error> {
-        let leof = self.writer.finish(incomplete)?;
-        tape_write_eof_mark(self.writer.writer_ref_mut())?;
-        Ok(leof)
+    /// Write a single block to a linux tape device
+    ///
+    /// EOM Behaviour on Linux: When the end of medium early warning is
+    /// encountered, the current write is finished and the number of bytes
+    /// is returned. The next write returns -1 and errno is set to
+    /// ENOSPC. To enable writing a trailer, the next write is allowed to
+    /// proceed and, if successful, the number of bytes is returned. After
+    /// this, -1 and the number of bytes are alternately returned until
+    /// the physical end of medium (or some other error) is encountered.
+    ///
+    /// See: https://github.com/torvalds/linux/blob/master/Documentation/scsi/st.rst
+    ///
+    /// On success, this returns if we en countered a EOM condition.
+    fn write_block(&mut self, data: &[u8]) -> Result<bool, std::io::Error> {
+
+        let mut leof = false;
+
+        loop {
+            match self.file.write(data) {
+                Ok(count) if count == data.len() => return Ok(leof),
+                Ok(count) if count > 0 => {
+                    proxmox::io_bail!(
+                        "short block write ({} < {}). Tape drive uses wrong block size.",
+                        count, data.len());
+                }
+                Ok(_) => { // count is 0 here, assume EOT
+                    return Err(std::io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32));
+                }
+                // handle interrupted system call
+                Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
+                    continue;
+                }
+                // detect and handle LEOM (early warning)
+                Err(err) if err.is_errno(nix::errno::Errno::ENOSPC) => {
+                    if leof {
+                        return Err(err);
+                    } else {
+                        leof = true;
+                        continue; // next write will succeed
+                    }
+                }
+                Err(err) => return Err(err),
+            }
+        }
     }
 
-    fn logical_end_of_media(&self) -> bool {
-        self.writer.logical_end_of_media()
+    fn write_filemark(&mut self) -> Result<(), std::io::Error> {
+        tape_write_eof_mark(&mut self.file)
     }
 }
 
diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs
index 2fc9fdea..54e0887f 100644
--- a/src/tape/drive/virtual_tape.rs
+++ b/src/tape/drive/virtual_tape.rs
@@ -276,8 +276,7 @@ impl TapeDriver for VirtualTapeHandle {
                     free_space = self.max_size - used_space;
                 }
 
-                let writer = Box::new(file);
-                let writer = Box::new(EmulateTapeWriter::new(writer, free_space));
+                let writer = EmulateTapeWriter::new(file, free_space);
                 let writer = Box::new(BlockedWriter::new(writer));
 
                 Ok(writer)
diff --git a/src/tape/file_formats/blocked_writer.rs b/src/tape/file_formats/blocked_writer.rs
index 961b2ed2..33fa2955 100644
--- a/src/tape/file_formats/blocked_writer.rs
+++ b/src/tape/file_formats/blocked_writer.rs
@@ -1,10 +1,8 @@
-use std::io::Write;
-
 use proxmox::tools::vec;
 
 use crate::tape::{
     TapeWrite,
-    tape_device_write_block,
+    BlockWrite,
     file_formats::{
         BlockHeader,
         BlockHeaderFlags,
@@ -16,16 +14,27 @@ use crate::tape::{
 /// This type implement 'TapeWrite'. Data written is assembled to
 /// equally sized blocks (see 'BlockHeader'), which are then written
 /// to the underlying writer.
-pub struct BlockedWriter<W> {
+pub struct BlockedWriter<W: BlockWrite> {
     writer: W,
     buffer: Box<BlockHeader>,
     buffer_pos: usize,
     seq_nr: u32,
     logical_end_of_media: bool,
     bytes_written: usize,
+    wrote_eof: bool,
+}
+
+impl <W: BlockWrite> Drop for BlockedWriter<W> {
+
+    // Try to make sure to end the file with a filemark
+    fn drop(&mut self) {
+        if !self.wrote_eof {
+            let _ = self.writer.write_filemark();
+        }
+    }
 }
 
-impl <W: Write> BlockedWriter<W> {
+impl <W: BlockWrite> BlockedWriter<W> {
 
     /// Allow access to underlying writer
     pub fn writer_ref_mut(&mut self) -> &mut W {
@@ -41,6 +50,7 @@ impl <W: Write> BlockedWriter<W> {
             seq_nr: 0,
             logical_end_of_media: false,
             bytes_written: 0,
+            wrote_eof: false,
         }
     }
 
@@ -52,7 +62,16 @@ impl <W: Write> BlockedWriter<W> {
                 BlockHeader::SIZE,
             )
         };
-        tape_device_write_block(writer, data)
+        writer.write_block(data)
+    }
+
+    fn write_eof(&mut self) -> Result<(), std::io::Error> {
+        if self.wrote_eof {
+            proxmox::io_bail!("BlockedWriter: detected multiple EOF writes");
+        }
+        self.wrote_eof = true;
+
+        self.writer.write_filemark()
     }
 
     fn write(&mut self, data: &[u8]) -> Result<usize, std::io::Error> {
@@ -85,7 +104,7 @@ impl <W: Write> BlockedWriter<W> {
 
 }
 
-impl <W: Write> TapeWrite for BlockedWriter<W> {
+impl <W: BlockWrite> TapeWrite for BlockedWriter<W> {
 
     fn write_all(&mut self, mut data: &[u8]) -> Result<bool, std::io::Error> {
         while !data.is_empty() {
@@ -113,7 +132,9 @@ impl <W: Write> TapeWrite for BlockedWriter<W> {
         self.buffer.set_seq_nr(self.seq_nr);
         self.seq_nr += 1;
         self.bytes_written += BlockHeader::SIZE;
-        Self::write_block(&self.buffer, &mut self.writer)
+        let leom = Self::write_block(&self.buffer, &mut self.writer)?;
+        self.write_eof()?;
+        Ok(leom)
     }
 
     /// Returns if the writer already detected the logical end of media
diff --git a/src/tape/helpers/emulate_tape_writer.rs b/src/tape/helpers/emulate_tape_writer.rs
index b385d6b3..eb4f29d7 100644
--- a/src/tape/helpers/emulate_tape_writer.rs
+++ b/src/tape/helpers/emulate_tape_writer.rs
@@ -1,6 +1,9 @@
 use std::io::{self, Write};
 
-use crate::tape::file_formats::PROXMOX_TAPE_BLOCK_SIZE;
+use crate::tape::{
+    BlockWrite,
+    file_formats::PROXMOX_TAPE_BLOCK_SIZE,
+};
 
 /// Emulate tape write behavior on a normal Writer
 ///
@@ -11,7 +14,7 @@ pub struct EmulateTapeWriter<W> {
     block_nr: usize,
     max_blocks: usize,
     writer: W,
-    leom_sent: bool,
+    wrote_eof: bool,
 }
 
 impl <W: Write> EmulateTapeWriter<W> {
@@ -27,16 +30,16 @@ impl <W: Write> EmulateTapeWriter<W> {
 
         Self {
             block_nr: 0,
-            leom_sent: false,
+            wrote_eof: false,
             writer,
             max_blocks,
         }
     }
 }
 
-impl <W: Write> Write for EmulateTapeWriter<W> {
+impl <W: Write> BlockWrite for EmulateTapeWriter<W> {
 
-    fn write(&mut self, buffer: &[u8]) -> Result<usize, io::Error> {
+    fn write_block(&mut self, buffer: &[u8]) -> Result<bool, io::Error> {
 
         if buffer.len() != PROXMOX_TAPE_BLOCK_SIZE {
             proxmox::io_bail!("EmulateTapeWriter: got write with wrong block size ({} != {}",
@@ -47,22 +50,22 @@ impl <W: Write> Write for EmulateTapeWriter<W> {
             return Err(io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32));
         }
 
-        if self.block_nr >= self.max_blocks {
-            if !self.leom_sent {
-                self.leom_sent = true;
-                return Err(io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32));
-            } else {
-                self.leom_sent = false;
-            }
-        }
-
         self.writer.write_all(buffer)?;
         self.block_nr += 1;
 
-        Ok(buffer.len())
+        if self.block_nr > self.max_blocks {
+            Ok(true)
+        } else {
+            Ok(false)
+        }
     }
 
-    fn flush(&mut self) -> Result<(), io::Error> {
-        proxmox::io_bail!("EmulateTapeWriter does not support flush");
+    fn write_filemark(&mut self) -> Result<(), std::io::Error> {
+        if self.wrote_eof {
+            proxmox::io_bail!("EmulateTapeWriter: detected multiple EOF writes");
+        }
+        // do nothing, just record the call
+        self.wrote_eof = true;
+        Ok(())
     }
 }
diff --git a/src/tape/tape_write.rs b/src/tape/tape_write.rs
index 8a3d4fd6..593d1a29 100644
--- a/src/tape/tape_write.rs
+++ b/src/tape/tape_write.rs
@@ -1,9 +1,5 @@
-use std::io::Write;
-
 use endian_trait::Endian;
 
-use proxmox::sys::error::SysError;
-
 use crate::tape::file_formats::MediaContentHeader;
 
 /// Write trait for tape devices
@@ -53,53 +49,14 @@ pub trait TapeWrite {
     }
 }
 
-/// Write a single block to a tape device
-///
-/// Assumes that 'writer' is a linux tape device.
-///
-/// EOM Behaviour on Linux: When the end of medium early warning is
-/// encountered, the current write is finished and the number of bytes
-/// is returned. The next write returns -1 and errno is set to
-/// ENOSPC. To enable writing a trailer, the next write is allowed to
-/// proceed and, if successful, the number of bytes is returned. After
-/// this, -1 and the number of bytes are alternately returned until
-/// the physical end of medium (or some other error) is encountered.
-///
-/// See: https://github.com/torvalds/linux/blob/master/Documentation/scsi/st.rst
-///
-/// On success, this returns if we en countered a EOM condition.
-pub fn tape_device_write_block<W: Write>(
-    writer: &mut W,
-    data: &[u8],
-) -> Result<bool, std::io::Error> {
-
-    let mut leof = false;
+/// Write streams of blocks
+pub trait BlockWrite {
+    /// Write a data block
+    ///
+    /// Returns true if the drive reached the Logical End Of Media
+    /// (early warning)
+    fn write_block(&mut self, buffer: &[u8]) -> Result<bool, std::io::Error>;
 
-    loop {
-        match writer.write(data) {
-            Ok(count) if count == data.len() => return Ok(leof),
-            Ok(count) if count > 0 => {
-                proxmox::io_bail!(
-                    "short block write ({} < {}). Tape drive uses wrong block size.",
-                    count, data.len());
-            }
-            Ok(_) => { // count is 0 here, assume EOT
-                return Err(std::io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32));
-            }
-            // handle interrupted system call
-            Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {
-                continue;
-            }
-            // detect and handle LEOM (early warning)
-            Err(err) if err.is_errno(nix::errno::Errno::ENOSPC) => {
-                if leof {
-                    return Err(err);
-                } else {
-                    leof = true;
-                    continue; // next write will succeed
-                }
-            }
-            Err(err) => return Err(err),
-        }
-    }
+    /// Write a filemark
+    fn write_filemark(&mut self) -> Result<(), std::io::Error>;
 }
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 03/11] tape: implement LTO userspace driver
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
  2021-04-07 10:22 ` [pbs-devel] [PATCH 01/11] tape: introduce trait BlockRead Dietmar Maurer
  2021-04-07 10:22 ` [pbs-devel] [PATCH 02/11] tape: introduce trait BlockWrite Dietmar Maurer
@ 2021-04-07 10:23 ` Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 04/11] tape: implement format/erase Dietmar Maurer
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:23 UTC (permalink / raw)
  To: pbs-devel

---
 debian/proxmox-backup-server.udev |  18 ++
 src/api2/config/changer.rs        |   4 +-
 src/api2/config/drive.rs          |  44 +--
 src/api2/tape/changer.rs          |   4 +-
 src/api2/tape/drive.rs            |  36 +--
 src/api2/tape/mod.rs              |   4 +-
 src/api2/types/tape/drive.rs      |  24 +-
 src/bin/pmt.rs                    | 314 ++++-----------------
 src/bin/pmtx.rs                   |   4 +-
 src/bin/proxmox_tape/drive.rs     |   8 +-
 src/bin/sg-tape-cmd.rs            | 189 ++-----------
 src/config/drive.rs               |  18 +-
 src/tape/changer/mod.rs           |   4 +-
 src/tape/drive/lto/mod.rs         | 420 ++++++++++++++++++++++++++++
 src/tape/drive/lto/sg_tape.rs     | 445 ++++++++++++++++++++++++++++++
 src/tape/drive/mod.rs             |  22 +-
 src/tape/linux_list_drives.rs     |  51 ++--
 17 files changed, 1078 insertions(+), 531 deletions(-)
 create mode 100644 debian/proxmox-backup-server.udev
 create mode 100644 src/tape/drive/lto/mod.rs
 create mode 100644 src/tape/drive/lto/sg_tape.rs

diff --git a/debian/proxmox-backup-server.udev b/debian/proxmox-backup-server.udev
new file mode 100644
index 00000000..afdfb2bc
--- /dev/null
+++ b/debian/proxmox-backup-server.udev
@@ -0,0 +1,18 @@
+# do not edit this file, it will be overwritten on update
+
+# persistent storage links: /dev/tape/{by-id,by-path}
+
+ACTION=="remove", GOTO="persistent_storage_tape_end"
+ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
+
+# also see: /lib/udev/rules.d/60-persistent-storage-tape.rules
+
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="1", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
+  SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}-sg"
+
+# iSCSI devices from the same host have all the same ID_SERIAL,
+# but additionally a property named ID_SCSI_SERIAL.
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="1", ENV{ID_SCSI_SERIAL}=="?*", \
+  SYMLINK+="tape/by-id/scsi-$env{ID_SCSI_SERIAL}-sg"
+
+LABEL="persistent_storage_tape_end"
diff --git a/src/api2/config/changer.rs b/src/api2/config/changer.rs
index 380eb089..62a0fba4 100644
--- a/src/api2/config/changer.rs
+++ b/src/api2/config/changer.rs
@@ -27,7 +27,7 @@ use crate::{
         SLOT_ARRAY_SCHEMA,
         EXPORT_SLOT_LIST_SCHEMA,
         ScsiTapeChanger,
-        LinuxTapeDrive,
+        LtoTapeDrive,
     },
     tape::{
         linux_tape_changer_list,
@@ -303,7 +303,7 @@ pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
         None => bail!("Delete changer '{}' failed - no such entry", name),
     }
 
-    let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+    let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
     for drive in drive_list {
         if let Some(changer) = drive.changer {
             if changer == name {
diff --git a/src/api2/config/drive.rs b/src/api2/config/drive.rs
index 98337024..e2626e14 100644
--- a/src/api2/config/drive.rs
+++ b/src/api2/config/drive.rs
@@ -19,12 +19,12 @@ use crate::{
         DRIVE_NAME_SCHEMA,
         CHANGER_NAME_SCHEMA,
         CHANGER_DRIVENUM_SCHEMA,
-        LINUX_DRIVE_PATH_SCHEMA,
-        LinuxTapeDrive,
+        LTO_DRIVE_PATH_SCHEMA,
+        LtoTapeDrive,
         ScsiTapeChanger,
     },
     tape::{
-        linux_tape_device_list,
+        lto_tape_device_list,
         check_drive_path,
     },
 };
@@ -37,7 +37,7 @@ use crate::{
                 schema: DRIVE_NAME_SCHEMA,
             },
             path: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
             },
             changer: {
                 schema: CHANGER_NAME_SCHEMA,
@@ -60,13 +60,13 @@ pub fn create_drive(param: Value) -> Result<(), Error> {
 
     let (mut config, _digest) = config::drive::config()?;
 
-    let item: LinuxTapeDrive = serde_json::from_value(param)?;
+    let item: LtoTapeDrive = serde_json::from_value(param)?;
 
-    let linux_drives = linux_tape_device_list();
+    let lto_drives = lto_tape_device_list();
 
-    check_drive_path(&linux_drives, &item.path)?;
+    check_drive_path(&lto_drives, &item.path)?;
 
-    let existing: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+    let existing: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
 
     for drive in existing {
         if drive.name == item.name {
@@ -77,7 +77,7 @@ pub fn create_drive(param: Value) -> Result<(), Error> {
         }
     }
 
-    config.set_data(&item.name, "linux", &item)?;
+    config.set_data(&item.name, "lto", &item)?;
 
     config::drive::save_config(&config)?;
 
@@ -93,7 +93,7 @@ pub fn create_drive(param: Value) -> Result<(), Error> {
         },
     },
     returns: {
-        type: LinuxTapeDrive,
+        type: LtoTapeDrive,
     },
     access: {
         permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false),
@@ -104,11 +104,11 @@ pub fn get_config(
     name: String,
     _param: Value,
     mut rpcenv: &mut dyn RpcEnvironment,
-) -> Result<LinuxTapeDrive, Error> {
+) -> Result<LtoTapeDrive, Error> {
 
     let (config, digest) = config::drive::config()?;
 
-    let data: LinuxTapeDrive = config.lookup("linux", &name)?;
+    let data: LtoTapeDrive = config.lookup("lto", &name)?;
 
     rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
 
@@ -123,7 +123,7 @@ pub fn get_config(
         description: "The list of configured drives (with config digest).",
         type: Array,
         items: {
-            type: LinuxTapeDrive,
+            type: LtoTapeDrive,
         },
     },
     access: {
@@ -135,13 +135,13 @@ pub fn get_config(
 pub fn list_drives(
     _param: Value,
     mut rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Vec<LinuxTapeDrive>, Error> {
+) -> Result<Vec<LtoTapeDrive>, Error> {
     let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
     let user_info = CachedUserInfo::new()?;
 
     let (config, digest) = config::drive::config()?;
 
-    let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+    let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
 
     let drive_list = drive_list
         .into_iter()
@@ -176,7 +176,7 @@ pub enum DeletableProperty {
                 schema: DRIVE_NAME_SCHEMA,
             },
             path: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             changer: {
@@ -225,7 +225,7 @@ pub fn update_drive(
         crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
     }
 
-    let mut data: LinuxTapeDrive = config.lookup("linux", &name)?;
+    let mut data: LtoTapeDrive = config.lookup("lto", &name)?;
 
     if let Some(delete) = delete {
         for delete_prop in delete {
@@ -240,8 +240,8 @@ pub fn update_drive(
     }
 
     if let Some(path) = path {
-        let linux_drives = linux_tape_device_list();
-        check_drive_path(&linux_drives, &path)?;
+        let lto_drives = lto_tape_device_list();
+        check_drive_path(&lto_drives, &path)?;
         data.path = path;
     }
 
@@ -261,7 +261,7 @@ pub fn update_drive(
         }
     }
 
-    config.set_data(&name, "linux", &data)?;
+    config.set_data(&name, "lto", &data)?;
 
     config::drive::save_config(&config)?;
 
@@ -290,8 +290,8 @@ pub fn delete_drive(name: String, _param: Value) -> Result<(), Error> {
 
     match config.sections.get(&name) {
         Some((section_type, _)) => {
-            if section_type != "linux" {
-                bail!("Entry '{}' exists, but is not a linux tape drive", name);
+            if section_type != "lto" {
+                bail!("Entry '{}' exists, but is not a lto tape drive", name);
             }
             config.sections.remove(&name);
         },
diff --git a/src/api2/tape/changer.rs b/src/api2/tape/changer.rs
index e2d1edc0..59dfe044 100644
--- a/src/api2/tape/changer.rs
+++ b/src/api2/tape/changer.rs
@@ -20,7 +20,7 @@ use crate::{
         Authid,
         CHANGER_NAME_SCHEMA,
         ChangerListEntry,
-        LinuxTapeDrive,
+        LtoTapeDrive,
         MtxEntryKind,
         MtxStatusEntry,
         ScsiTapeChanger,
@@ -88,7 +88,7 @@ pub async fn get_status(
 
     inventory.update_online_status(&map)?;
 
-    let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+    let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
     let mut drive_map: HashMap<u64, String> = HashMap::new();
 
     for drive in drive_list {
diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index b17f4203..80d17a27 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -42,11 +42,11 @@ use crate::{
             MEDIA_POOL_NAME_SCHEMA,
             Authid,
             DriveListEntry,
-            LinuxTapeDrive,
+            LtoTapeDrive,
             MediaIdFlat,
             LabelUuidMap,
             MamAttribute,
-            LinuxDriveAndMediaStatus,
+            LtoDriveAndMediaStatus,
         },
         tape::restore::{
             fast_catalog_restore,
@@ -62,7 +62,7 @@ use crate::{
         lock_media_set,
         lock_media_pool,
         lock_unassigned_media_pool,
-        linux_tape_device_list,
+        lto_tape_device_list,
         lookup_device_identification,
         file_formats::{
             MediaLabel,
@@ -70,9 +70,9 @@ use crate::{
         },
         drive::{
             TapeDriver,
-            LinuxTapeHandle,
+            LtoTapeHandle,
             Lp17VolumeStatistics,
-            open_linux_tape_device,
+            open_lto_tape_device,
             media_changer,
             required_media_changer,
             open_drive,
@@ -794,9 +794,9 @@ pub fn clean_drive(
 
             changer.clean_drive()?;
 
-             if let Ok(drive_config) = config.lookup::<LinuxTapeDrive>("linux", &drive) {
+             if let Ok(drive_config) = config.lookup::<LtoTapeDrive>("lto", &drive) {
                  // Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
-                 let mut handle = LinuxTapeHandle::new(open_linux_tape_device(&drive_config.path)?);
+                 let mut handle = LtoTapeHandle::new(open_lto_tape_device(&drive_config.path)?)?;
 
                  // test for critical tape alert flags
                  if let Ok(alert_flags) = handle.tape_alert_flags() {
@@ -1144,7 +1144,7 @@ pub async fn cartridge_memory(drive: String) -> Result<Vec<MamAttribute>, Error>
         drive.clone(),
         "reading cartridge memory".to_string(),
         move |config| {
-            let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
+            let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?;
             let mut handle = drive_config.open()?;
 
             handle.cartridge_memory()
@@ -1174,7 +1174,7 @@ pub async fn volume_statistics(drive: String) -> Result<Lp17VolumeStatistics, Er
         drive.clone(),
         "reading volume statistics".to_string(),
         move |config| {
-            let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
+            let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?;
             let mut handle = drive_config.open()?;
 
             handle.volume_statistics()
@@ -1192,24 +1192,24 @@ pub async fn volume_statistics(drive: String) -> Result<Lp17VolumeStatistics, Er
         },
     },
     returns: {
-        type: LinuxDriveAndMediaStatus,
+        type: LtoDriveAndMediaStatus,
     },
     access: {
         permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT, false),
     },
 )]
 /// Get drive/media status
-pub async fn status(drive: String) -> Result<LinuxDriveAndMediaStatus, Error> {
+pub async fn status(drive: String) -> Result<LtoDriveAndMediaStatus, Error> {
     run_drive_blocking_task(
         drive.clone(),
         "reading drive status".to_string(),
         move |config| {
-            let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
+            let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?;
 
-            // Note: use open_linux_tape_device, because this also works if no medium loaded
-            let file = open_linux_tape_device(&drive_config.path)?;
+            // Note: use open_lto_tape_device, because this also works if no medium loaded
+            let file = open_lto_tape_device(&drive_config.path)?;
 
-            let mut handle = LinuxTapeHandle::new(file);
+            let mut handle = LtoTapeHandle::new(file)?;
 
             handle.get_drive_and_media_status()
         }
@@ -1382,9 +1382,9 @@ pub fn list_drives(
 
     let (config, _) = config::drive::config()?;
 
-    let linux_drives = linux_tape_device_list();
+    let lto_drives = lto_tape_device_list();
 
-    let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+    let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
 
     let mut list = Vec::new();
 
@@ -1398,7 +1398,7 @@ pub fn list_drives(
             continue;
         }
 
-        let info = lookup_device_identification(&linux_drives, &drive.path);
+        let info = lookup_device_identification(&lto_drives, &drive.path);
         let state = get_tape_device_state(&config, &drive.name)?;
         let entry = DriveListEntry { config: drive, info, state };
         list.push(entry);
diff --git a/src/api2/tape/mod.rs b/src/api2/tape/mod.rs
index b560f077..219a721b 100644
--- a/src/api2/tape/mod.rs
+++ b/src/api2/tape/mod.rs
@@ -15,7 +15,7 @@ use proxmox::{
 use crate::{
     api2::types::TapeDeviceInfo,
     tape::{
-        linux_tape_device_list,
+        lto_tape_device_list,
         linux_tape_changer_list,
     },
 };
@@ -41,7 +41,7 @@ pub mod restore;
 /// Scan tape drives
 pub fn scan_drives(_param: Value) -> Result<Vec<TapeDeviceInfo>, Error> {
 
-    let list = linux_tape_device_list();
+    let list = lto_tape_device_list();
 
     Ok(list)
 }
diff --git a/src/api2/types/tape/drive.rs b/src/api2/types/tape/drive.rs
index 2fd480ac..058e544f 100644
--- a/src/api2/types/tape/drive.rs
+++ b/src/api2/types/tape/drive.rs
@@ -21,8 +21,8 @@ pub const DRIVE_NAME_SCHEMA: Schema = StringSchema::new("Drive Identifier.")
     .max_length(32)
     .schema();
 
-pub const LINUX_DRIVE_PATH_SCHEMA: Schema = StringSchema::new(
-    "The path to a LINUX non-rewinding SCSI tape device (i.e. '/dev/nst0')")
+pub const LTO_DRIVE_PATH_SCHEMA: Schema = StringSchema::new(
+    "The path to a LTO SCSI-generic tape device (i.e. '/dev/sg0')")
     .schema();
 
 pub const CHANGER_DRIVENUM_SCHEMA: Schema = IntegerSchema::new(
@@ -57,7 +57,7 @@ pub struct VirtualTapeDrive {
             schema: DRIVE_NAME_SCHEMA,
         },
         path: {
-            schema: LINUX_DRIVE_PATH_SCHEMA,
+            schema: LTO_DRIVE_PATH_SCHEMA,
         },
         changer: {
             schema: CHANGER_NAME_SCHEMA,
@@ -71,8 +71,8 @@ pub struct VirtualTapeDrive {
 )]
 #[derive(Serialize,Deserialize)]
 #[serde(rename_all = "kebab-case")]
-/// Linux SCSI tape driver
-pub struct LinuxTapeDrive {
+/// Lto SCSI tape driver
+pub struct LtoTapeDrive {
     pub name: String,
     pub path: String,
     #[serde(skip_serializing_if="Option::is_none")]
@@ -84,7 +84,7 @@ pub struct LinuxTapeDrive {
 #[api(
     properties: {
         config: {
-            type: LinuxTapeDrive,
+            type: LtoTapeDrive,
         },
         info: {
             type: OptionalDeviceIdentification,
@@ -96,7 +96,7 @@ pub struct LinuxTapeDrive {
 /// Drive list entry
 pub struct DriveListEntry {
     #[serde(flatten)]
-    pub config: LinuxTapeDrive,
+    pub config: LtoTapeDrive,
     #[serde(flatten)]
     pub info: OptionalDeviceIdentification,
     /// the state of the drive if locked
@@ -169,11 +169,11 @@ impl TryFrom<u8> for TapeDensity {
 )]
 #[derive(Serialize,Deserialize)]
 #[serde(rename_all = "kebab-case")]
-/// Drive/Media status for Linux SCSI drives.
+/// Drive/Media status for Lto SCSI drives.
 ///
 /// Media related data is optional - only set if there is a medium
 /// loaded.
-pub struct LinuxDriveAndMediaStatus {
+pub struct LtoDriveAndMediaStatus {
     /// Block size (0 is variable size)
     pub blocksize: u32,
     /// Tape density
@@ -181,17 +181,17 @@ pub struct LinuxDriveAndMediaStatus {
     pub density: Option<TapeDensity>,
     /// Status flags
     pub status: String,
-    /// Linux Driver Options
+    /// Lto Driver Options
     pub options: String,
     /// Tape Alert Flags
     #[serde(skip_serializing_if="Option::is_none")]
     pub alert_flags: Option<String>,
     /// Current file number
     #[serde(skip_serializing_if="Option::is_none")]
-    pub file_number: Option<u32>,
+    pub file_number: Option<u64>,
     /// Current block number
     #[serde(skip_serializing_if="Option::is_none")]
-    pub block_number: Option<u32>,
+    pub block_number: Option<u64>,
     /// Medium Manufacture Date (epoch)
     #[serde(skip_serializing_if="Option::is_none")]
     pub manufactured: Option<i64>,
diff --git a/src/bin/pmt.rs b/src/bin/pmt.rs
index a097df2c..df3ad9ec 100644
--- a/src/bin/pmt.rs
+++ b/src/bin/pmt.rs
@@ -1,18 +1,18 @@
 /// Control magnetic tape drive operation
 ///
-/// This is a Rust implementation, meant to replace the 'mt' command
-/// line tool.
+/// This is a Rust implementation, using the Proxmox userspace tape
+/// driver. This is meant as replacement fot the 'mt' command line
+/// tool.
 ///
 /// Features:
 ///
 /// - written in Rust
+/// - use Proxmox userspace driver (using SG_IO)
 /// - optional json output format
 /// - support tape alert flags
 /// - support volume statistics
 /// - read cartridge memory
 
-use std::collections::HashMap;
-
 use anyhow::{bail, Error};
 use serde_json::Value;
 
@@ -43,7 +43,7 @@ pub const RECORD_COUNT_SCHEMA: Schema =
     .schema();
 
 pub const DRIVE_OPTION_SCHEMA: Schema = StringSchema::new(
-    "Linux Tape Driver Option, either numeric value or option name.")
+    "Lto Tape Driver Option, either numeric value or option name.")
     .schema();
 
 pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
@@ -57,103 +57,60 @@ use proxmox_backup::{
         drive::complete_drive_name,
     },
     api2::types::{
-        LINUX_DRIVE_PATH_SCHEMA,
+        LTO_DRIVE_PATH_SCHEMA,
         DRIVE_NAME_SCHEMA,
-        LinuxTapeDrive,
+        LtoTapeDrive,
     },
     tape::{
         complete_drive_path,
-        linux_tape_device_list,
+        lto_tape_device_list,
         drive::{
-            linux_mtio::{MTCmd, SetDrvBufferOptions},
             TapeDriver,
-            LinuxTapeHandle,
-            open_linux_tape_device,
+            LtoTapeHandle,
+            open_lto_tape_device,
        },
     },
 };
 
-lazy_static::lazy_static!{
-
-    static ref DRIVE_OPTIONS: HashMap<String, SetDrvBufferOptions> = {
-        let mut map = HashMap::new();
-
-        for i in 0..31 {
-            let bit: i32 = 1 << i;
-            let flag = SetDrvBufferOptions::from_bits_truncate(bit);
-            if flag.bits() == 0 { continue; }
-            let name = format!("{:?}", flag)
-                .to_lowercase()
-                .replace("_", "-");
-
-            map.insert(name, flag);
-        }
-        map
-    };
-
-}
-
-fn parse_drive_options(options: Vec<String>) -> Result<SetDrvBufferOptions, Error> {
-
-    let mut value = SetDrvBufferOptions::empty();
-
-    for option in options.iter() {
-        if let Ok::<i32,_>(v) = option.parse() {
-            value |= SetDrvBufferOptions::from_bits_truncate(v);
-        } else if let Some(v) = DRIVE_OPTIONS.get(option) {
-            value |= *v;
-        } else {
-            let option = option.to_lowercase().replace("_", "-");
-            if let Some(v) = DRIVE_OPTIONS.get(&option) {
-                value |= *v;
-            } else {
-                bail!("unknown drive option {}", option);
-            }
-        }
-    }
-
-    Ok(value)
-}
-
-fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
+fn get_tape_handle(param: &Value) -> Result<LtoTapeHandle, Error> {
 
     if let Some(name) = param["drive"].as_str() {
         let (config, _digest) = config::drive::config()?;
-        let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+        let drive: LtoTapeDrive = config.lookup("lto", &name)?;
         eprintln!("using device {}", drive.path);
-        return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?))
+        return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?);
     }
 
     if let Some(device) = param["device"].as_str() {
         eprintln!("using device {}", device);
-        return Ok(LinuxTapeHandle::new(open_linux_tape_device(&device)?))
+        return LtoTapeHandle::new(open_lto_tape_device(&device)?);
     }
 
     if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
         let (config, _digest) = config::drive::config()?;
-        let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+        let drive: LtoTapeDrive = config.lookup("lto", &name)?;
         eprintln!("using device {}", drive.path);
-        return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?))
+        return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?);
     }
 
     if let Ok(device) = std::env::var("TAPE") {
         eprintln!("using device {}", device);
-        return Ok(LinuxTapeHandle::new(open_linux_tape_device(&device)?))
+        return LtoTapeHandle::new(open_lto_tape_device(&device)?);
     }
 
     let (config, _digest) = config::drive::config()?;
 
     let mut drive_names = Vec::new();
     for (name, (section_type, _)) in config.sections.iter() {
-        if section_type != "linux" { continue; }
+        if section_type != "lto" { continue; }
         drive_names.push(name);
     }
 
     if drive_names.len() == 1 {
         let name = drive_names[0];
-        let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+        let drive: LtoTapeDrive = config.lookup("lto", &name)?;
         eprintln!("using device {}", drive.path);
-        return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?))
+        return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?);
     }
 
     bail!("no drive/device specified");
@@ -167,7 +124,7 @@ fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             count: {
@@ -200,7 +157,7 @@ fn asf(count: usize, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             count: {
@@ -230,7 +187,7 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             count: {
@@ -243,11 +200,12 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> {
 ///
 /// This leaves the tape positioned at the first block of the file
 /// that is count - 1 files before the current file.
-fn bsfm(count: i32, param: Value) -> Result<(), Error> {
+fn bsfm(count: usize, param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    handle.mtop(MTCmd::MTBSFM, count, "bsfm")?;
+    handle.backward_space_count_files(count)?;
+    handle.forward_space_count_files(1)?;
 
     Ok(())
 }
@@ -261,7 +219,7 @@ fn bsfm(count: i32, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             count: {
@@ -275,7 +233,9 @@ fn bsr(count: i32, param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    handle.mtop(MTCmd::MTBSR, count, "backward space records")?;
+    unimplemented!();
+
+    // fixme: handle.mtop(MTCmd::MTBSR, count, "backward space records")?;
 
     Ok(())
 }
@@ -289,7 +249,7 @@ fn bsr(count: i32, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             "output-format": {
@@ -340,7 +300,7 @@ fn cartridge_memory(param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             "output-format": {
@@ -389,7 +349,7 @@ fn tape_alert_flags(param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
        },
@@ -413,7 +373,7 @@ fn eject(param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
        },
@@ -437,7 +397,7 @@ fn eod(param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             fast: {
@@ -466,7 +426,7 @@ fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             count: {
@@ -495,7 +455,7 @@ fn fsf(count: usize, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             count: {
@@ -508,11 +468,12 @@ fn fsf(count: usize, param: Value) -> Result<(), Error> {
 ///
 /// This leaves the tape positioned at the last block of the file that
 /// is count - 1 files past the current file.
-fn fsfm(count: i32, param: Value) -> Result<(), Error> {
+fn fsfm(count: usize, param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    handle.mtop(MTCmd::MTFSFM, count, "fsfm")?;
+    handle.forward_space_count_files(count)?;
+    handle.backward_space_count_files(1)?;
 
     Ok(())
 }
@@ -526,7 +487,7 @@ fn fsfm(count: i32, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             count: {
@@ -540,7 +501,8 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    handle.mtop(MTCmd::MTFSR, count, "forward space records")?;
+    unimplemented!();
+    // fixme: handle.mtop(MTCmd::MTFSR, count, "forward space records")?;
 
     Ok(())
 }
@@ -554,7 +516,7 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
        },
@@ -564,7 +526,7 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> {
 fn load(param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
-    handle.mtload()?;
+    handle.load()?;
 
     Ok(())
 }
@@ -578,7 +540,7 @@ fn load(param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
        },
@@ -589,7 +551,8 @@ fn lock(param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    handle.mtop(MTCmd::MTLOCK, 1, "lock tape drive door")?;
+    unimplemented!();
+    // fixme: handle.mtop(MTCmd::MTLOCK, 1, "lock tape drive door")?;
 
     Ok(())
 }
@@ -603,7 +566,7 @@ fn lock(param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
        },
@@ -634,7 +597,7 @@ fn scan(param: Value) -> Result<(), Error> {
 
     let output_format = get_output_format(&param);
 
-    let list = linux_tape_device_list();
+    let list = lto_tape_device_list();
 
     if output_format == "json-pretty" {
         println!("{}", serde_json::to_string_pretty(&list)?);
@@ -657,36 +620,6 @@ fn scan(param: Value) -> Result<(), Error> {
     Ok(())
 }
 
-
-#[api(
-    input: {
-        properties: {
-            drive: {
-                schema: DRIVE_NAME_SCHEMA,
-                optional: true,
-            },
-            device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-                optional: true,
-            },
-            size: {
-                description: "Block size in bytes.",
-                minimum: 0,
-            },
-        },
-    },
-)]
-/// Set the block size of the drive
-fn setblk(size: i32, param: Value) -> Result<(), Error> {
-
-    let mut handle = get_tape_handle(&param)?;
-
-    handle.mtop(MTCmd::MTSETBLK, size, "set block size")?;
-
-    Ok(())
-}
-
-
 #[api(
     input: {
         properties: {
@@ -695,7 +628,7 @@ fn setblk(size: i32, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             "output-format": {
@@ -737,122 +670,6 @@ fn status(param: Value) -> Result<(), Error> {
 }
 
 
-#[api(
-    input: {
-        properties: {
-            drive: {
-                schema: DRIVE_NAME_SCHEMA,
-                optional: true,
-            },
-            device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-                optional: true,
-            },
-            options: {
-                schema: DRIVE_OPTION_LIST_SCHEMA,
-                optional: true,
-            },
-            defaults: {
-                description: "Set default options (buffer-writes async-writes read-ahead can-bsr).",
-                type: bool,
-                optional: true,
-            },
-        },
-    },
-)]
-/// Set device driver options (root only)
-fn st_options(
-    options: Option<Vec<String>>,
-    defaults: Option<bool>,
-    param: Value) -> Result<(), Error> {
-
-    let handle = get_tape_handle(&param)?;
-
-    let options = match defaults {
-        Some(true) => {
-            if options.is_some() {
-                bail!("option --defaults conflicts with specified options");
-            }
-            let mut list = Vec::new();
-            list.push(String::from("buffer-writes"));
-            list.push(String::from("async-writes"));
-            list.push(String::from("read-ahead"));
-            list.push(String::from("can-bsr"));
-            list
-        }
-        Some(false) | None => {
-            options.unwrap_or_else(|| Vec::new())
-        }
-    };
-
-    let value = parse_drive_options(options)?;
-
-    handle.set_drive_buffer_options(value)?;
-
-    Ok(())
-}
-
-
-#[api(
-    input: {
-        properties: {
-            drive: {
-                schema: DRIVE_NAME_SCHEMA,
-                optional: true,
-            },
-            device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-                optional: true,
-            },
-            options: {
-                schema: DRIVE_OPTION_LIST_SCHEMA,
-            },
-        },
-    },
-)]
-/// Set selected device driver options bits (root only)
-fn st_set_options(options: Vec<String>, param: Value) -> Result<(), Error> {
-
-    let handle = get_tape_handle(&param)?;
-
-    let value = parse_drive_options(options)?;
-
-    handle.drive_buffer_set_options(value)?;
-
-    Ok(())
-}
-
-
-#[api(
-    input: {
-        properties: {
-            drive: {
-                schema: DRIVE_NAME_SCHEMA,
-                optional: true,
-            },
-            device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-                optional: true,
-            },
-            options: {
-                schema: DRIVE_OPTION_LIST_SCHEMA,
-            },
-        },
-    },
-)]
-/// Clear selected device driver options bits (root only)
-fn st_clear_options(options: Vec<String>, param: Value) -> Result<(), Error> {
-
-    let handle = get_tape_handle(&param)?;
-
-    let value = parse_drive_options(options)?;
-
-    handle.drive_buffer_clear_options(value)?;
-
-    Ok(())
-}
-
-
 #[api(
    input: {
         properties: {
@@ -861,7 +678,7 @@ fn st_clear_options(options: Vec<String>, param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
        },
@@ -872,7 +689,8 @@ fn unlock(param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    handle.mtop(MTCmd::MTUNLOCK, 1, "unlock tape drive door")?;
+    unimplemented!();
+    //handle.mtop(MTCmd::MTUNLOCK, 1, "unlock tape drive door")?;
 
     Ok(())
 }
@@ -886,7 +704,7 @@ fn unlock(param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             "output-format": {
@@ -935,7 +753,7 @@ fn volume_statistics(param: Value) -> Result<(), Error> {
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             count: {
@@ -946,10 +764,13 @@ fn volume_statistics(param: Value) -> Result<(), Error> {
     },
 )]
 /// Write count (default 1) EOF marks at current position.
-fn weof(count: Option<i32>, param: Value) -> Result<(), Error> {
+fn weof(count: Option<usize>, param: Value) -> Result<(), Error> {
+
+    let count = count.unwrap_or(1);
 
     let mut handle = get_tape_handle(&param)?;
-    handle.mtop(MTCmd::MTWEOF, count.unwrap_or(1), "write EOF mark")?;
+
+    handle.write_filemarks(count)?;
 
     Ok(())
 }
@@ -967,7 +788,6 @@ fn main() -> Result<(), Error> {
         CliCommand::new(method)
             .completion_cb("drive", complete_drive_name)
             .completion_cb("device", complete_drive_path)
-            .completion_cb("options", complete_option_name)
     };
 
     let cmd_def = CliCommandMap::new()
@@ -987,11 +807,7 @@ fn main() -> Result<(), Error> {
         .insert("lock", std_cmd(&API_METHOD_LOCK))
         .insert("rewind", std_cmd(&API_METHOD_REWIND))
         .insert("scan", CliCommand::new(&API_METHOD_SCAN))
-        .insert("setblk", CliCommand::new(&API_METHOD_SETBLK).arg_param(&["size"]))
         .insert("status", std_cmd(&API_METHOD_STATUS))
-        .insert("stoptions", std_cmd(&API_METHOD_ST_OPTIONS).arg_param(&["options"]))
-        .insert("stsetoptions", std_cmd(&API_METHOD_ST_SET_OPTIONS).arg_param(&["options"]))
-        .insert("stclearoptions", std_cmd(&API_METHOD_ST_CLEAR_OPTIONS).arg_param(&["options"]))
         .insert("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS))
         .insert("unlock", std_cmd(&API_METHOD_UNLOCK))
         .insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
@@ -1005,11 +821,3 @@ fn main() -> Result<(), Error> {
 
     Ok(())
 }
-
-// Completion  helpers
-pub fn complete_option_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
-    DRIVE_OPTIONS
-        .keys()
-        .map(String::from)
-        .collect()
-}
diff --git a/src/bin/pmtx.rs b/src/bin/pmtx.rs
index 85114811..88074002 100644
--- a/src/bin/pmtx.rs
+++ b/src/bin/pmtx.rs
@@ -33,7 +33,7 @@ use proxmox_backup::{
         SCSI_CHANGER_PATH_SCHEMA,
         CHANGER_NAME_SCHEMA,
         ScsiTapeChanger,
-        LinuxTapeDrive,
+        LtoTapeDrive,
     },
     tape::{
         linux_tape_changer_list,
@@ -67,7 +67,7 @@ fn get_changer_handle(param: &Value) -> Result<File, Error> {
 
     if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
         let (config, _digest) = config::drive::config()?;
-        let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+        let drive: LtoTapeDrive = config.lookup("lto", &name)?;
         if let Some(changer) = drive.changer {
             let changer_config: ScsiTapeChanger = config.lookup("changer", &changer)?;
             eprintln!("using device {}", changer_config.path);
diff --git a/src/bin/proxmox_tape/drive.rs b/src/bin/proxmox_tape/drive.rs
index 84bdb524..f8831aec 100644
--- a/src/bin/proxmox_tape/drive.rs
+++ b/src/bin/proxmox_tape/drive.rs
@@ -21,7 +21,7 @@ use proxmox_backup::{
     config::drive::{
         complete_drive_name,
         complete_changer_name,
-        complete_linux_drive_name,
+        complete_lto_drive_name,
     },
 };
 
@@ -33,13 +33,13 @@ pub fn drive_commands() -> CommandLineInterface {
         .insert("config",
                 CliCommand::new(&API_METHOD_GET_CONFIG)
                 .arg_param(&["name"])
-                .completion_cb("name", complete_linux_drive_name)
+                .completion_cb("name", complete_lto_drive_name)
         )
         .insert(
             "remove",
             CliCommand::new(&api2::config::drive::API_METHOD_DELETE_DRIVE)
                 .arg_param(&["name"])
-                .completion_cb("name", complete_linux_drive_name)
+                .completion_cb("name", complete_lto_drive_name)
         )
         .insert(
             "create",
@@ -53,7 +53,7 @@ pub fn drive_commands() -> CommandLineInterface {
             "update",
             CliCommand::new(&api2::config::drive::API_METHOD_UPDATE_DRIVE)
                 .arg_param(&["name"])
-                .completion_cb("name", complete_linux_drive_name)
+                .completion_cb("name", complete_lto_drive_name)
                 .completion_cb("path", complete_drive_path)
                 .completion_cb("changer", complete_changer_name)
         )
diff --git a/src/bin/sg-tape-cmd.rs b/src/bin/sg-tape-cmd.rs
index 86998972..a2f0283d 100644
--- a/src/bin/sg-tape-cmd.rs
+++ b/src/bin/sg-tape-cmd.rs
@@ -1,7 +1,5 @@
-/// Tape command implemented using scsi-generic raw commands
-///
-/// SCSI-generic command needs root privileges, so this binary need
-/// to be setuid root.
+/// Helper to run tape commands as root. Currently only required
+/// to read and set the encryption key.
 ///
 /// This command can use STDIN as tape device handle.
 
@@ -24,41 +22,41 @@ use proxmox_backup::{
     config,
     backup::Fingerprint,
     api2::types::{
-        LINUX_DRIVE_PATH_SCHEMA,
+        LTO_DRIVE_PATH_SCHEMA,
         DRIVE_NAME_SCHEMA,
         TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
         MEDIA_SET_UUID_SCHEMA,
-        LinuxTapeDrive,
+        LtoTapeDrive,
     },
     tape::{
         drive::{
             TapeDriver,
-            LinuxTapeHandle,
-            open_linux_tape_device,
-            check_tape_is_linux_tape_device,
+            LtoTapeHandle,
+            open_lto_tape_device,
+            check_tape_is_lto_tape_device,
         },
     },
 };
 
-fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
+fn get_tape_handle(param: &Value) -> Result<LtoTapeHandle, Error> {
 
     let handle = if let Some(name) = param["drive"].as_str() {
         let (config, _digest) = config::drive::config()?;
-        let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+        let drive: LtoTapeDrive = config.lookup("lto", &name)?;
         eprintln!("using device {}", drive.path);
         drive.open()?
     } else if let Some(device) = param["device"].as_str() {
         eprintln!("using device {}", device);
-        LinuxTapeHandle::new(open_linux_tape_device(&device)?)
+        LtoTapeHandle::new(open_lto_tape_device(&device)?)?
     } else if let Some(true) = param["stdin"].as_bool() {
         eprintln!("using stdin");
         let fd = std::io::stdin().as_raw_fd();
         let file = unsafe { File::from_raw_fd(fd) };
-        check_tape_is_linux_tape_device(&file)?;
-        LinuxTapeHandle::new(file)
+        check_tape_is_lto_tape_device(&file)?;
+        LtoTapeHandle::new(file)?
     } else if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
         let (config, _digest) = config::drive::config()?;
-        let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+        let drive: LtoTapeDrive = config.lookup("lto", &name)?;
         eprintln!("using device {}", drive.path);
         drive.open()?
     } else {
@@ -66,13 +64,13 @@ fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
 
         let mut drive_names = Vec::new();
         for (name, (section_type, _)) in config.sections.iter() {
-            if section_type != "linux" { continue; }
+            if section_type != "lto" { continue; }
             drive_names.push(name);
         }
 
         if drive_names.len() == 1 {
             let name = drive_names[0];
-            let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+            let drive: LtoTapeDrive = config.lookup("lto", &name)?;
             eprintln!("using device {}", drive.path);
             drive.open()?
         } else {
@@ -83,111 +81,6 @@ fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
     Ok(handle)
 }
 
-#[api(
-   input: {
-        properties: {
-            drive: {
-                schema: DRIVE_NAME_SCHEMA,
-                optional: true,
-            },
-            device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-                optional: true,
-            },
-            stdin: {
-                description: "Use standard input as device handle.",
-                type: bool,
-                optional: true,
-            },
-        },
-    },
-)]
-/// Tape/Media Status
-fn status(
-    param: Value,
-) -> Result<(), Error> {
-
-    let result = proxmox::try_block!({
-        let mut handle = get_tape_handle(&param)?;
-        handle.get_drive_and_media_status()
-   }).map_err(|err: Error| err.to_string());
-
-    println!("{}", serde_json::to_string_pretty(&result)?);
-
-    Ok(())
-}
-
-#[api(
-   input: {
-        properties: {
-            drive: {
-                schema: DRIVE_NAME_SCHEMA,
-                optional: true,
-            },
-            device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-                optional: true,
-            },
-            stdin: {
-                description: "Use standard input as device handle.",
-                type: bool,
-                optional: true,
-            },
-        },
-    },
-)]
-/// Read Cartridge Memory (Medium auxiliary memory attributes)
-fn cartridge_memory(
-    param: Value,
-) -> Result<(), Error> {
-
-    let result = proxmox::try_block!({
-        let mut handle = get_tape_handle(&param)?;
-
-        handle.cartridge_memory()
-    }).map_err(|err| err.to_string());
-
-    println!("{}", serde_json::to_string_pretty(&result)?);
-
-    Ok(())
-}
-
-#[api(
-   input: {
-        properties: {
-            drive: {
-                schema: DRIVE_NAME_SCHEMA,
-                optional: true,
-            },
-            device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-                optional: true,
-            },
-            stdin: {
-                description: "Use standard input as device handle.",
-                type: bool,
-                optional: true,
-            },
-        },
-    },
-)]
-/// Read Tape Alert Flags
-fn tape_alert_flags(
-    param: Value,
-) -> Result<(), Error> {
-
-    let result = proxmox::try_block!({
-        let mut handle = get_tape_handle(&param)?;
-
-        let flags = handle.tape_alert_flags()?;
-        Ok(flags.bits())
-    }).map_err(|err: Error| err.to_string());
-
-    println!("{}", serde_json::to_string_pretty(&result)?);
-
-    Ok(())
-}
-
 #[api(
     input: {
         properties: {
@@ -204,7 +97,7 @@ fn tape_alert_flags(
                 optional: true,
             },
             device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
+                schema: LTO_DRIVE_PATH_SCHEMA,
                 optional: true,
             },
             stdin: {
@@ -245,40 +138,6 @@ fn set_encryption(
     Ok(())
 }
 
-#[api(
-   input: {
-        properties: {
-            drive: {
-                schema: DRIVE_NAME_SCHEMA,
-                optional: true,
-            },
-            device: {
-                schema: LINUX_DRIVE_PATH_SCHEMA,
-                optional: true,
-            },
-            stdin: {
-                description: "Use standard input as device handle.",
-                type: bool,
-                optional: true,
-            },
-        },
-    },
-)]
-/// Read volume statistics
-fn volume_statistics(
-    param: Value,
-) -> Result<(), Error> {
-
-    let result = proxmox::try_block!({
-        let mut handle = get_tape_handle(&param)?;
-        handle.volume_statistics()
-    }).map_err(|err: Error| err.to_string());
-
-    println!("{}", serde_json::to_string_pretty(&result)?);
-
-    Ok(())
-}
-
 fn main() -> Result<(), Error> {
 
     // check if we are user root or backup
@@ -300,22 +159,6 @@ fn main() -> Result<(), Error> {
     }
 
     let cmd_def = CliCommandMap::new()
-        .insert(
-            "status",
-            CliCommand::new(&API_METHOD_STATUS)
-        )
-        .insert(
-            "cartridge-memory",
-            CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY)
-        )
-        .insert(
-            "tape-alert-flags",
-            CliCommand::new(&API_METHOD_TAPE_ALERT_FLAGS)
-        )
-        .insert(
-            "volume-statistics",
-            CliCommand::new(&API_METHOD_VOLUME_STATISTICS)
-        )
         .insert(
             "encryption",
             CliCommand::new(&API_METHOD_SET_ENCRYPTION)
diff --git a/src/config/drive.rs b/src/config/drive.rs
index 63839d0d..57f6911f 100644
--- a/src/config/drive.rs
+++ b/src/config/drive.rs
@@ -1,12 +1,12 @@
 //! Tape drive/changer configuration
 //!
 //! This configuration module is based on [`SectionConfig`], and
-//! provides a type safe interface to store [`LinuxTapeDrive`],
+//! provides a type safe interface to store [`LtoTapeDrive`],
 //! [`VirtualTapeDrive`] and [`ScsiTapeChanger`] configurations.
 //!
 //! Drive type [`VirtualTapeDrive`] is only useful for debugging.
 //!
-//! [LinuxTapeDrive]: crate::api2::types::LinuxTapeDrive
+//! [LtoTapeDrive]: crate::api2::types::LtoTapeDrive
 //! [VirtualTapeDrive]: crate::api2::types::VirtualTapeDrive
 //! [ScsiTapeChanger]: crate::api2::types::ScsiTapeChanger
 //! [SectionConfig]: proxmox::api::section_config::SectionConfig
@@ -36,7 +36,7 @@ use crate::{
     api2::types::{
         DRIVE_NAME_SCHEMA,
         VirtualTapeDrive,
-        LinuxTapeDrive,
+        LtoTapeDrive,
         ScsiTapeChanger,
     },
 };
@@ -57,11 +57,11 @@ fn init() -> SectionConfig {
     let plugin = SectionConfigPlugin::new("virtual".to_string(), Some("name".to_string()), obj_schema);
     config.register_plugin(plugin);
 
-    let obj_schema = match LinuxTapeDrive::API_SCHEMA {
+    let obj_schema = match LtoTapeDrive::API_SCHEMA {
         Schema::Object(ref obj_schema) => obj_schema,
         _ => unreachable!(),
     };
-    let plugin = SectionConfigPlugin::new("linux".to_string(), Some("name".to_string()), obj_schema);
+    let plugin = SectionConfigPlugin::new("lto".to_string(), Some("name".to_string()), obj_schema);
     config.register_plugin(plugin);
 
     let obj_schema = match ScsiTapeChanger::API_SCHEMA {
@@ -116,7 +116,7 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
 pub fn check_drive_exists(config: &SectionConfigData, drive: &str) -> Result<(), Error> {
     match config.sections.get(drive) {
         Some((section_type, _)) => {
-            if !(section_type == "linux" || section_type == "virtual") {
+            if !(section_type == "lto" || section_type == "virtual") {
                 bail!("Entry '{}' exists, but is not a tape drive", drive);
             }
         }
@@ -138,12 +138,12 @@ pub fn complete_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<
     }
 }
 
-/// List Linux tape drives
-pub fn complete_linux_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
+/// List Lto tape drives
+pub fn complete_lto_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
     match config() {
         Ok((data, _digest)) => data.sections.iter()
             .filter(|(_id, (section_type, _))| {
-                section_type == "linux"
+                section_type == "lto"
             })
             .map(|(id, _)| id.to_string())
             .collect(),
diff --git a/src/tape/changer/mod.rs b/src/tape/changer/mod.rs
index a25df49b..1fc0d435 100644
--- a/src/tape/changer/mod.rs
+++ b/src/tape/changer/mod.rs
@@ -26,7 +26,7 @@ use proxmox::{
 use crate::api2::types::{
     SLOT_ARRAY_SCHEMA,
     ScsiTapeChanger,
-    LinuxTapeDrive,
+    LtoTapeDrive,
 };
 
 /// Changer element status.
@@ -523,7 +523,7 @@ pub struct MtxMediaChanger {
 
 impl MtxMediaChanger {
 
-    pub fn with_drive_config(drive_config: &LinuxTapeDrive) -> Result<Self, Error> {
+    pub fn with_drive_config(drive_config: &LtoTapeDrive) -> Result<Self, Error> {
         let (config, _digest) = crate::config::drive::config()?;
         let changer_config: ScsiTapeChanger = match drive_config.changer {
             Some(ref changer) => config.lookup("changer", changer)?,
diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
new file mode 100644
index 00000000..becbad50
--- /dev/null
+++ b/src/tape/drive/lto/mod.rs
@@ -0,0 +1,420 @@
+//! Driver for LTO SCSI tapes
+//!
+//! This is a userspace drive implementation using SG_IO.
+//!
+//! Why we do not use the Linux tape driver:
+//!
+//! - missing features (MAM, Encryption, ...)
+//!
+//! - strange permission handling - only root (or CAP_SYS_RAWIO) can
+//!   do SG_IO (SYS_RAW_IO)
+//!
+//! - unability to detect EOT (you just get EIO)
+
+mod sg_tape;
+pub use sg_tape::*;
+
+use std::fs::{OpenOptions, File};
+use std::os::unix::fs::OpenOptionsExt;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::convert::TryFrom;
+
+use anyhow::{bail, format_err, Error};
+use nix::fcntl::{fcntl, FcntlArg, OFlag};
+
+use proxmox::{
+    tools::Uuid,
+    sys::error::SysResult,
+};
+
+use crate::{
+    config,
+    tools::run_command,
+    backup::{
+        Fingerprint,
+        KeyConfig,
+    },
+    api2::types::{
+        MamAttribute,
+        LtoDriveAndMediaStatus,
+        LtoTapeDrive,
+    },
+    tape::{
+        TapeRead,
+        TapeWrite,
+        drive::{
+            TapeDriver,
+            TapeAlertFlags,
+            Lp17VolumeStatistics,
+            mam_extract_media_usage,
+        },
+        file_formats::{
+            PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
+            MediaSetLabel,
+            MediaContentHeader,
+        },
+    },
+};
+
+impl LtoTapeDrive {
+
+    /// Open a tape device
+    ///
+    /// This does additional checks:
+    ///
+    /// - check if it is a non-rewinding tape device
+    /// - check if drive is ready (tape loaded)
+    /// - check block size
+    /// - for autoloader only, try to reload ejected tapes
+    pub fn open(&self) -> Result<LtoTapeHandle, Error> {
+
+        proxmox::try_block!({
+            let file = open_lto_tape_device(&self.path)?;
+
+            let mut handle = LtoTapeHandle::new(file)?;
+
+            if !handle.sg_tape.test_unit_ready().is_ok() {
+                // for autoloader only, try to reload ejected tapes
+                if self.changer.is_some() {
+                    let _ = handle.sg_tape.load(); // just try, ignore error
+                }
+            }
+
+            handle.sg_tape.wait_until_ready()?;
+
+            // Only root can set driver options, so we cannot
+            // handle.set_default_options()?;
+
+            Ok(handle)
+        }).map_err(|err: Error| format_err!("open drive '{}' ({}) failed - {}", self.name, self.path, err))
+    }
+}
+
+/// Lto Tape device handle
+pub struct LtoTapeHandle {
+    sg_tape: SgTape,
+}
+
+impl LtoTapeHandle {
+
+    /// Creates a new instance
+    pub fn new(file: File) -> Result<Self, Error> {
+        let sg_tape = SgTape::new(file)?;
+        Ok(Self { sg_tape })
+    }
+
+    /// Set all options we need/want
+    pub fn set_default_options(&self) -> Result<(), Error> {
+        // fixme
+        Ok(())
+    }
+
+    /// Write a single EOF mark without flushing buffers
+    pub fn write_filemarks(&mut self, count: usize) -> Result<(), std::io::Error> {
+        self.sg_tape.write_filemarks(count, false)
+    }
+
+    /// Get Tape and Media status
+    pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error>  {
+
+        let (file_number, block_number) = match self.sg_tape.position() {
+            Ok(position) => (
+                Some(position.logical_file_id),
+                Some(position.logical_object_number),
+            ),
+            Err(_) => (None, None),
+        };
+
+        let options = String::from("FIXME");
+
+        let alert_flags = self.tape_alert_flags()
+            .map(|flags| format!("{:?}", flags))
+            .ok();
+
+        let mut status = LtoDriveAndMediaStatus {
+            blocksize: 0, // fixme: remove
+            density: None, // fixme
+            status: String::from("FIXME"),
+            options,
+            alert_flags,
+            file_number,
+            block_number,
+            manufactured: None,
+            bytes_read: None,
+            bytes_written: None,
+            medium_passes: None,
+            medium_wearout: None,
+            volume_mounts: None,
+        };
+
+        if self.sg_tape.test_unit_ready()? {
+
+            if let Ok(mam) = self.cartridge_memory() {
+
+                let usage = mam_extract_media_usage(&mam)?;
+
+                status.manufactured = Some(usage.manufactured);
+                status.bytes_read = Some(usage.bytes_read);
+                status.bytes_written = Some(usage.bytes_written);
+
+                if let Ok(volume_stats) = self.volume_statistics() {
+
+                    let passes = std::cmp::max(
+                        volume_stats.beginning_of_medium_passes,
+                        volume_stats.middle_of_tape_passes,
+                    );
+
+                    // assume max. 16000 medium passes
+                    // see: https://en.wikipedia.org/wiki/Linear_Tape-Open
+                    let wearout: f64 = (passes as f64)/(16000.0 as f64);
+
+                    status.medium_passes = Some(passes);
+                    status.medium_wearout = Some(wearout);
+
+                    status.volume_mounts = Some(volume_stats.volume_mounts);
+                }
+            }
+        }
+
+        Ok(status)
+    }
+
+    pub fn load(&mut self) ->  Result<(), Error> {
+        self.sg_tape.load()
+    }
+
+    /// Read Cartridge Memory (MAM Attributes)
+    pub fn cartridge_memory(&mut self) -> Result<Vec<MamAttribute>, Error> {
+        self.sg_tape.cartridge_memory()
+     }
+
+    /// Read Volume Statistics
+    pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> {
+        self.sg_tape.volume_statistics()
+    }
+}
+
+
+impl TapeDriver for LtoTapeHandle {
+
+    fn sync(&mut self) -> Result<(), Error> {
+        self.sg_tape.sync()?;
+        Ok(())
+    }
+
+    /// Go to the end of the recorded media (for appending files).
+    fn move_to_eom(&mut self) -> Result<(), Error> {
+        self.sg_tape.move_to_eom()
+    }
+
+    fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
+        self.sg_tape.space_filemarks(isize::try_from(count)?)
+    }
+
+    fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
+        self.sg_tape.space_filemarks(-isize::try_from(count)?)
+    }
+
+    fn rewind(&mut self) -> Result<(), Error> {
+        self.sg_tape.rewind()
+    }
+
+    fn current_file_number(&mut self) -> Result<u64, Error> {
+        self.sg_tape.current_file_number()
+    }
+
+    fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
+        self.rewind()?; // important - erase from BOT
+        self.sg_tape.erase_media(fast)
+    }
+
+    fn read_next_file<'a>(&'a mut self) -> Result<Option<Box<dyn TapeRead + 'a>>, std::io::Error> {
+        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,
+        };
+
+        Ok(handle)
+    }
+
+    fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error> {
+        let handle = self.sg_tape.open_writer();
+        Ok(Box::new(handle))
+    }
+
+    fn write_media_set_label(
+        &mut self,
+        media_set_label: &MediaSetLabel,
+        key_config: Option<&KeyConfig>,
+    ) -> Result<(), Error> {
+
+        let file_number = self.current_file_number()?;
+        if file_number != 1 {
+            self.rewind()?;
+            self.forward_space_count_files(1)?; // skip label
+        }
+
+        let file_number = self.current_file_number()?;
+        if file_number != 1 {
+            bail!("write_media_set_label failed - got wrong file number ({} != 1)", file_number);
+        }
+
+        self.set_encryption(None)?;
+
+        { // limit handle scope
+            let mut handle = self.write_file()?;
+
+            let mut value = serde_json::to_value(media_set_label)?;
+            if media_set_label.encryption_key_fingerprint.is_some() {
+                match key_config {
+                    Some(key_config) => {
+                        value["key-config"] = serde_json::to_value(key_config)?;
+                    }
+                    None => {
+                        bail!("missing encryption key config");
+                    }
+                }
+            }
+
+            let raw = serde_json::to_string_pretty(&value)?;
+
+            let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32);
+            handle.write_header(&header, raw.as_bytes())?;
+            handle.finish(false)?;
+        }
+
+        self.sync()?; // sync data to tape
+
+        Ok(())
+    }
+
+    /// Rewind and put the drive off line (Eject media).
+    fn eject_media(&mut self) -> Result<(), Error> {
+        self.sg_tape.eject()
+    }
+
+    /// Read Tape Alert Flags
+    fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
+        self.sg_tape.tape_alert_flags()
+    }
+
+    /// Set or clear encryption key
+    ///
+    /// Note: Only 'root' can read secret encryption keys, so we need
+    /// to spawn setuid binary 'sg-tape-cmd'.
+    fn set_encryption(
+        &mut self,
+        key_fingerprint: Option<(Fingerprint, Uuid)>,
+    ) -> Result<(), Error> {
+
+        if nix::unistd::Uid::effective().is_root() {
+
+            if let Some((ref key_fingerprint, ref uuid)) = key_fingerprint {
+
+                let (key_map, _digest) = config::tape_encryption_keys::load_keys()?;
+                match key_map.get(key_fingerprint) {
+                    Some(item) => {
+
+                        // derive specialized key for each media-set
+
+                        let mut tape_key = [0u8; 32];
+
+                        let uuid_bytes: [u8; 16] = uuid.as_bytes().clone();
+
+                        openssl::pkcs5::pbkdf2_hmac(
+                            &item.key,
+                            &uuid_bytes,
+                            10,
+                            openssl::hash::MessageDigest::sha256(),
+                            &mut tape_key)?;
+
+                        return self.sg_tape.set_encryption(Some(tape_key));
+                    }
+                    None => bail!("unknown tape encryption key '{}'", key_fingerprint),
+                }
+            } else {
+                return self.sg_tape.set_encryption(None);
+            }
+        }
+
+        let output = if let Some((fingerprint, uuid)) = key_fingerprint {
+            let fingerprint = crate::tools::format::as_fingerprint(fingerprint.bytes());
+            run_sg_tape_cmd("encryption", &[
+                "--fingerprint", &fingerprint,
+                "--uuid", &uuid.to_string(),
+            ], self.sg_tape.file_mut().as_raw_fd())?
+        } else {
+            run_sg_tape_cmd("encryption", &[], self.sg_tape.file_mut().as_raw_fd())?
+        };
+        let result: Result<(), String> = serde_json::from_str(&output)?;
+        result.map_err(|err| format_err!("{}", err))
+    }
+}
+
+/// Check for correct Major/Minor numbers
+pub fn check_tape_is_lto_tape_device(file: &File) -> Result<(), Error> {
+
+    let stat = nix::sys::stat::fstat(file.as_raw_fd())?;
+
+    let devnum = stat.st_rdev;
+
+    let major = unsafe { libc::major(devnum) };
+    let _minor = unsafe { libc::minor(devnum) };
+
+    if major == 9 {
+        bail!("not a scsi-generic tape device (cannot use linux tape devices)");
+    }
+
+    if major != 21 {
+        bail!("not a scsi-generic tape device");
+    }
+
+    Ok(())
+}
+
+/// Opens a Lto tape device
+///
+/// The open call use O_NONBLOCK, but that flag is cleard after open
+/// succeeded. This also checks if the device is a non-rewinding tape
+/// device.
+pub fn open_lto_tape_device(
+    path: &str,
+) -> Result<File, Error> {
+
+    let file = OpenOptions::new()
+        .read(true)
+        .write(true)
+        .custom_flags(libc::O_NONBLOCK)
+        .open(path)?;
+
+    // clear O_NONBLOCK from now on.
+
+    let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL)
+        .into_io_result()?;
+
+    let mut flags = OFlag::from_bits_truncate(flags);
+    flags.remove(OFlag::O_NONBLOCK);
+
+    fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags))
+        .into_io_result()?;
+
+    check_tape_is_lto_tape_device(&file)
+        .map_err(|err| format_err!("device type check {:?} failed - {}", path, err))?;
+
+    Ok(file)
+}
+
+fn run_sg_tape_cmd(subcmd: &str, args: &[&str], fd: RawFd) -> Result<String, Error> {
+    let mut command = std::process::Command::new(
+        "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd");
+    command.args(&[subcmd]);
+    command.args(&["--stdin"]);
+    command.args(args);
+    let device_fd = nix::unistd::dup(fd)?;
+    command.stdin(unsafe { std::process::Stdio::from_raw_fd(device_fd)});
+    run_command(command, None)
+}
diff --git a/src/tape/drive/lto/sg_tape.rs b/src/tape/drive/lto/sg_tape.rs
new file mode 100644
index 00000000..802756fa
--- /dev/null
+++ b/src/tape/drive/lto/sg_tape.rs
@@ -0,0 +1,445 @@
+use std::time::SystemTime;
+use std::fs::{File, OpenOptions};
+use std::os::unix::fs::OpenOptionsExt;
+use std::os::unix::io::AsRawFd;
+use std::path::Path;
+
+use anyhow::{bail, format_err, Error};
+use endian_trait::Endian;
+use nix::fcntl::{fcntl, FcntlArg, OFlag};
+
+use proxmox::{
+    sys::error::SysResult,
+    tools::io::ReadExt,
+};
+
+use crate::{
+    api2::types::{
+        MamAttribute,
+    },
+    tape::{
+        BlockRead,
+        BlockReadStatus,
+        BlockWrite,
+        file_formats::{
+            BlockedWriter,
+            BlockedReader,
+        },
+        drive::{
+            TapeAlertFlags,
+            Lp17VolumeStatistics,
+            read_mam_attributes,
+            read_tape_alert_flags,
+            read_volume_statistics,
+            set_encryption,
+        },
+    },
+    tools::sgutils2::{
+        SgRaw,
+        SenseInfo,
+        ScsiError,
+        InquiryInfo,
+        scsi_inquiry,
+    },
+};
+
+#[repr(C, packed)]
+#[derive(Endian, Debug, Copy, Clone)]
+pub struct ReadPositionLongPage {
+    flags: u8,
+    reserved: [u8;3],
+    partition_number: u32,
+    pub logical_object_number: u64,
+    pub logical_file_id: u64,
+    obsolete: [u8;8],
+}
+
+pub struct SgTape {
+    file: File,
+}
+
+impl SgTape {
+
+    const SCSI_TAPE_DEFAULT_TIMEOUT: usize = 60*2; // 2 minutes
+
+    /// Create a new instance
+    ///
+    /// Uses scsi_inquiry to check the device type.
+    pub fn new(mut file: File) -> Result<Self, Error> {
+
+        let info = scsi_inquiry(&mut file)?;
+
+        if info.peripheral_type != 1 {
+            bail!("not a tape device (peripheral_type = {})", info.peripheral_type);
+        }
+        Ok(Self { file })
+    }
+
+    pub fn open<P: AsRef<Path>>(path: P) -> Result<SgTape, Error> {
+        // do not wait for media, use O_NONBLOCK
+        let file = OpenOptions::new()
+            .read(true)
+            .write(true)
+            .custom_flags(libc::O_NONBLOCK)
+            .open(path)?;
+
+        // then clear O_NONBLOCK
+        let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL)
+            .into_io_result()?;
+
+        let mut flags = OFlag::from_bits_truncate(flags);
+        flags.remove(OFlag::O_NONBLOCK);
+
+        fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags))
+            .into_io_result()?;
+
+        Self::new(file)
+    }
+
+    pub fn inquiry(&mut self) -> Result<InquiryInfo, Error> {
+        scsi_inquiry(&mut self.file)
+    }
+
+    pub fn erase_media(&mut self, _fast: bool) -> Result<(), Error> {
+        // fixme:
+        unimplemented!();
+    }
+
+    pub fn rewind(&mut self) -> Result<(), Error> {
+
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.extend(&[0x01, 0, 0, 0, 0, 0]); // REWIND
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("rewind failed - {}", err))?;
+
+        Ok(())
+    }
+
+    pub fn position(&mut self) -> Result<ReadPositionLongPage, Error> {
+
+        let expected_size = std::mem::size_of::<ReadPositionLongPage>();
+
+        let mut sg_raw = SgRaw::new(&mut self.file, 32)?;
+        sg_raw.set_timeout(30); // use short timeout
+        let mut cmd = Vec::new();
+        cmd.extend(&[0x34, 0x06, 0, 0, 0, 0, 0, 0, 0, 0]); // READ POSITION LONG FORM
+
+        let data = sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("read position failed - {}", err))?;
+
+        let page = proxmox::try_block!({
+            if data.len() != expected_size {
+                bail!("got unexpected data len ({} != {}", data.len(), expected_size);
+            }
+
+            let mut reader = &data[..];
+
+            let page: ReadPositionLongPage = unsafe { reader.read_be_value()? };
+
+            Ok(page)
+        }).map_err(|err: Error| format_err!("decode position page failed - {}", err))?;
+
+        if page.partition_number != 0 {
+            bail!("detecthed partitioned tape - not supported");
+        }
+
+        println!("DATA: {:?}", page);
+
+        Ok(page)
+    }
+
+    pub fn current_file_number(&mut self) -> Result<u64, Error> {
+        let position = self.position()?;
+        Ok(position.logical_file_id)
+    }
+
+    pub fn locate(&mut self) ->  Result<(), Error> {
+        // fixme: impl LOCATE
+        unimplemented!();
+    }
+
+    pub fn move_to_eom(&mut self) ->  Result<(), Error> {
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.extend(&[0x11, 0x03, 0, 0, 0, 0]); // SPACE(6) move to EOD
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("move to EOD failed - {}", err))?;
+
+        Ok(())
+    }
+
+    pub fn space_filemarks(&mut self, count: isize) ->  Result<(), Error> {
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+
+        // Use short command if possible (supported by all drives)
+        if (count <= 0x7fffff) && (count > -0x7fffff) {
+            cmd.extend(&[0x11, 0x01]); // SPACE(6) with filemarks
+            cmd.push(((count >> 16) & 0xff) as u8);
+            cmd.push(((count >> 8) & 0xff) as u8);
+            cmd.push((count & 0xff) as u8);
+            cmd.push(0); //control byte
+        } else {
+
+            cmd.extend(&[0x91, 0x01, 0, 0]); // SPACE(16) with filemarks
+            let count: i64 = count as i64;
+            cmd.extend(&count.to_be_bytes());
+            cmd.extend(&[0, 0, 0, 0]);
+        }
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("space filemarks failed - {}", err))?;
+
+        Ok(())
+    }
+
+    pub fn eject(&mut self) ->  Result<(), Error> {
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.extend(&[0x1B, 0, 0, 0, 0, 0]); // LODA/UNLOAD HOLD=0, LOAD=0
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("eject failed - {}", err))?;
+
+        Ok(())
+    }
+
+    pub fn load(&mut self) ->  Result<(), Error> {
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.extend(&[0x1B, 0, 0, 0, 0b0000_0001, 0]); // LODA/UNLOAD HOLD=0, LOAD=1
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("load media failed - {}", err))?;
+
+        Ok(())
+    }
+
+    pub fn write_filemarks(
+        &mut self,
+        count: usize,
+        immediate: bool,
+    ) ->  Result<(), std::io::Error> {
+
+        if count > 255 {
+            proxmox::io_bail!("write_filemarks failed: got strange count '{}'", count);
+        }
+
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)
+            .map_err(|err| proxmox::io_format_err!("write_filemarks failed (alloc) - {}", err))?;
+
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.push(0x10);
+        if immediate {
+            cmd.push(1); // IMMED=1
+        } else {
+            cmd.push(0); // IMMED=0
+        }
+        cmd.extend(&[0, 0, count as u8]); // COUNT
+        cmd.push(0); // control byte
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| proxmox::io_format_err!("write filemark failed - {}", err))?;
+
+        Ok(())
+    }
+
+    // Flush tape buffers (WEOF with count 0 => flush)
+    pub fn sync(&mut self) -> Result<(), std::io::Error> {
+        self.write_filemarks(0, false)?;
+        Ok(())
+    }
+
+    pub fn test_unit_ready(&mut self) -> Result<bool, Error> {
+
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(30); // use short timeout
+        let mut cmd = Vec::new();
+        cmd.extend(&[0x00, 0, 0, 0, 0, 0]); // TEST UNIT READY
+
+        // fixme: check sense
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("unit not ready - {}", err))?;
+
+        Ok(true)
+
+    }
+
+    pub fn wait_until_ready(&mut self) -> Result<(), Error> {
+
+        let start = SystemTime::now();
+        let max_wait = std::time::Duration::new(Self::SCSI_TAPE_DEFAULT_TIMEOUT as u64, 0);
+
+        loop {
+            match self.test_unit_ready() {
+                Ok(true) => return Ok(()),
+                _ => {
+                    std::thread::sleep(std::time::Duration::new(1, 0));
+                    if start.elapsed()? > max_wait {
+                        bail!("wait_until_ready failed - got timeout");
+                    }
+                }
+            }
+        }
+    }
+
+    /// Read Tape Alert Flags
+    pub fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
+        read_tape_alert_flags(&mut self.file)
+    }
+
+    /// Read Cartridge Memory (MAM Attributes)
+    pub fn cartridge_memory(&mut self) -> Result<Vec<MamAttribute>, Error> {
+        read_mam_attributes(&mut self.file)
+    }
+
+    /// Read Volume Statistics
+    pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> {
+        return read_volume_statistics(&mut self.file);
+    }
+
+    pub fn set_encryption(
+        &mut self,
+        key: Option<[u8; 32]>,
+    ) -> Result<(), Error> {
+        set_encryption(&mut self.file, key)
+    }
+
+    // Note: use alloc_page_aligned_buffer to alloc data transfer buffer
+    //
+    // Returns true if the drive reached the Logical End Of Media (early warning)
+    fn write_block(&mut self, data: &[u8]) -> Result<bool, std::io::Error> {
+
+        let transfer_len = data.len();
+
+        if transfer_len > 0xFFFFFF {
+           proxmox::io_bail!("write failed - data too large");
+        }
+
+        let mut sg_raw = SgRaw::new(&mut self.file, 0)
+            .unwrap(); // cannot fail with size 0
+
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.push(0x0A);  // WRITE
+        cmd.push(0x00); // VARIABLE SIZED BLOCKS
+        cmd.push(((transfer_len >> 16) & 0xff) as u8);
+        cmd.push(((transfer_len >> 8) & 0xff) as u8);
+        cmd.push((transfer_len & 0xff) as u8);
+        cmd.push(0); // control byte
+
+        //println!("WRITE {:?}", cmd);
+        //println!("WRITE {:?}", data);
+
+        sg_raw.do_out_command(&cmd, data)
+            .map_err(|err| proxmox::io_format_err!("write failed - {}", err))?;
+
+        // fixme: LEOM?
+
+        Ok(false)
+    }
+
+    fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> {
+        let transfer_len = buffer.len();
+
+        if transfer_len > 0xFFFFFF {
+            proxmox::io_bail!("read failed - buffer too large");
+        }
+
+        let mut sg_raw = SgRaw::new(&mut self.file, 0)
+            .unwrap(); // cannot fail with size 0
+
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.push(0x08); // READ
+        cmd.push(0x02); // VARIABLE SIZED BLOCKS, SILI=1
+        //cmd.push(0x00); // VARIABLE SIZED BLOCKS, SILI=0
+        cmd.push(((transfer_len >> 16) & 0xff) as u8);
+        cmd.push(((transfer_len >> 8) & 0xff) as u8);
+        cmd.push((transfer_len & 0xff) as u8);
+        cmd.push(0); // control byte
+
+        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);
+            }
+            Err(ScsiError::Sense(SenseInfo { sense_key: 8, asc: 0, ascq: 5 })) => {
+                return Ok(BlockReadStatus::EndOfStream);
+            }
+            Err(err) => {
+                println!("READ ERR {:?}", err);
+                proxmox::io_bail!("read failed - {}", err);
+            }
+        };
+
+        if data.len() != transfer_len {
+            proxmox::io_bail!("read failed - unexpected block len ({} != {})", data.len(), buffer.len())
+        }
+
+        Ok(BlockReadStatus::Ok(transfer_len))
+    }
+
+    pub fn open_writer(&mut self) -> BlockedWriter<SgTapeWriter> {
+        let writer = SgTapeWriter::new(self);
+        BlockedWriter::new(writer)
+    }
+
+    pub fn open_reader(&mut self) -> Result<Option<BlockedReader<SgTapeReader>>, std::io::Error> {
+        let reader = SgTapeReader::new(self);
+        match BlockedReader::open(reader)? {
+            Some(reader) => Ok(Some(reader)),
+            None => Ok(None),
+        }
+    }
+}
+
+pub struct SgTapeReader<'a> {
+    sg_tape: &'a mut SgTape,
+}
+
+impl <'a> SgTapeReader<'a> {
+
+    pub fn new(sg_tape: &'a mut SgTape) -> Self {
+        Self { sg_tape }
+    }
+}
+
+impl <'a> BlockRead for SgTapeReader<'a> {
+
+    fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> {
+        self.sg_tape.read_block(buffer)
+    }
+}
+
+pub struct SgTapeWriter<'a> {
+    sg_tape: &'a mut SgTape,
+    _leom_sent: bool,
+}
+
+impl <'a> SgTapeWriter<'a> {
+
+    pub fn new(sg_tape: &'a mut SgTape) -> Self {
+        Self { sg_tape, _leom_sent: false }
+    }
+}
+
+impl <'a> BlockWrite for SgTapeWriter<'a> {
+
+    fn write_block(&mut self, buffer: &[u8]) -> Result<bool, std::io::Error> {
+        self.sg_tape.write_block(buffer)
+    }
+
+    fn write_filemark(&mut self) -> Result<(), std::io::Error> {
+        self.sg_tape.write_filemarks(1, true)
+    }
+}
diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs
index 5509728c..71f61642 100644
--- a/src/tape/drive/mod.rs
+++ b/src/tape/drive/mod.rs
@@ -13,8 +13,8 @@ pub use volume_statistics::*;
 mod encryption;
 pub use encryption::*;
 
-mod linux_tape;
-pub use linux_tape::*;
+mod lto;
+pub use lto::*;
 
 mod mam;
 pub use mam::*;
@@ -49,7 +49,7 @@ use crate::{
     },
     api2::types::{
         VirtualTapeDrive,
-        LinuxTapeDrive,
+        LtoTapeDrive,
     },
     server::{
         send_load_media_email,
@@ -263,8 +263,8 @@ pub fn media_changer(
                     let tape = VirtualTapeDrive::deserialize(config)?;
                     Ok(Some((Box::new(tape), drive.to_string())))
                 }
-                "linux" => {
-                    let drive_config = LinuxTapeDrive::deserialize(config)?;
+                "lto" => {
+                    let drive_config = LtoTapeDrive::deserialize(config)?;
                     match drive_config.changer {
                         Some(ref changer_name) => {
                             let changer = MtxMediaChanger::with_drive_config(&drive_config)?;
@@ -317,8 +317,8 @@ pub fn open_drive(
                     let handle = tape.open()?;
                     Ok(Box::new(handle))
                 }
-                "linux" => {
-                    let tape = LinuxTapeDrive::deserialize(config)?;
+                "lto" => {
+                    let tape = LtoTapeDrive::deserialize(config)?;
                     let handle = tape.open()?;
                     Ok(Box::new(handle))
                 }
@@ -379,8 +379,8 @@ pub fn request_and_load_media(
 
                     Ok((handle, media_id))
                 }
-                "linux" => {
-                    let drive_config = LinuxTapeDrive::deserialize(config)?;
+                "lto" => {
+                    let drive_config = LtoTapeDrive::deserialize(config)?;
 
                     let label_text = label.label_text.clone();
 
@@ -546,8 +546,8 @@ fn tape_device_path(
                 "virtual" => {
                     VirtualTapeDrive::deserialize(config)?.path
                 }
-                "linux" => {
-                    LinuxTapeDrive::deserialize(config)?.path
+                "lto" => {
+                    LtoTapeDrive::deserialize(config)?.path
                 }
                 _ => bail!("unknown drive type '{}' - internal error"),
             };
diff --git a/src/tape/linux_list_drives.rs b/src/tape/linux_list_drives.rs
index dacbda2c..78ee6e42 100644
--- a/src/tape/linux_list_drives.rs
+++ b/src/tape/linux_list_drives.rs
@@ -12,14 +12,14 @@ use crate::{
     tools::fs::scan_subdir,
 };
 
+lazy_static::lazy_static!{
+    static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
+        regex::Regex::new(r"^sg\d+$").unwrap();
+}
+
 /// List linux tape changer devices
 pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
 
-    lazy_static::lazy_static!{
-        static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
-            regex::Regex::new(r"^sg\d+$").unwrap();
-    }
-
     let mut list = Vec::new();
 
     let dir_iter = match scan_subdir(
@@ -111,20 +111,15 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
     list
 }
 
-/// List linux tape devices (non-rewinding)
-pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
-
-    lazy_static::lazy_static!{
-        static ref NST_TAPE_NAME_REGEX: regex::Regex =
-            regex::Regex::new(r"^nst\d+$").unwrap();
-    }
+/// List LTO drives
+pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
 
     let mut list = Vec::new();
 
     let dir_iter = match scan_subdir(
         libc::AT_FDCWD,
-        "/sys/class/scsi_tape",
-        &NST_TAPE_NAME_REGEX)
+        "/sys/class/scsi_generic",
+        &SCSI_GENERIC_NAME_REGEX)
     {
         Err(_) => return list,
         Ok(iter) => iter,
@@ -138,7 +133,7 @@ pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
 
         let name = item.file_name().to_str().unwrap().to_string();
 
-        let mut sys_path = PathBuf::from("/sys/class/scsi_tape");
+        let mut sys_path = PathBuf::from("/sys/class/scsi_generic");
         sys_path.push(&name);
 
         let device = match udev::Device::from_syspath(&sys_path) {
@@ -151,6 +146,24 @@ pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
             Some(devnum) => devnum,
         };
 
+        let parent = match device.parent() {
+            None => continue,
+            Some(parent) => parent,
+        };
+
+        match parent.attribute_value("type") {
+            Some(type_osstr) => {
+                if type_osstr != "1" {
+                    continue;
+                }
+            }
+            _ => { continue; }
+        }
+
+        // let mut test_path = sys_path.clone();
+        // test_path.push("device/scsi_tape");
+        // if !test_path.exists() { continue; }
+
         let _dev_path = match device.devnode().map(Path::to_owned) {
             None => continue,
             Some(dev_path) => dev_path,
@@ -174,7 +187,7 @@ pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
             .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
             .unwrap_or_else(|| String::from("unknown"));
 
-        let dev_path = format!("/dev/tape/by-id/scsi-{}-nst", serial);
+        let dev_path = format!("/dev/tape/by-id/scsi-{}-sg", serial);
 
         if PathBuf::from(&dev_path).exists() {
             list.push(TapeDeviceInfo {
@@ -230,13 +243,13 @@ pub fn lookup_device_identification<'a>(
     }
 }
 
-/// Make sure path is a linux tape device
+/// Make sure path is a lto tape device
 pub fn check_drive_path(
     drives: &[TapeDeviceInfo],
     path: &str,
 ) -> Result<(), Error> {
     if lookup_device(drives, path).is_none() {
-        bail!("path '{}' is not a linux (non-rewinding) tape device", path);
+        bail!("path '{}' is not a lto SCSI-generic tape device", path);
     }
     Ok(())
 }
@@ -250,5 +263,5 @@ pub fn complete_changer_path(_arg: &str, _param: &HashMap<String, String>) -> Ve
 
 /// List tape device paths
 pub fn complete_drive_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
-    linux_tape_device_list().iter().map(|v| v.path.clone()).collect()
+    lto_tape_device_list().iter().map(|v| v.path.clone()).collect()
 }
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 04/11] tape: implement format/erase
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
                   ` (2 preceding siblings ...)
  2021-04-07 10:23 ` [pbs-devel] [PATCH 03/11] tape: implement LTO userspace driver Dietmar Maurer
@ 2021-04-07 10:23 ` Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 05/11] tape: fix LEOM handling Dietmar Maurer
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:23 UTC (permalink / raw)
  To: pbs-devel

---
 src/api2/tape/drive.rs         | 28 ++++++++++-----------
 src/bin/pmt.rs                 | 32 +++++++++++++++++++++++-
 src/bin/proxmox-tape.rs        | 10 ++++----
 src/tape/drive/lto/mod.rs      |  9 ++++---
 src/tape/drive/lto/sg_tape.rs  | 45 +++++++++++++++++++++++++++++++---
 src/tape/drive/mod.rs          |  6 ++---
 src/tape/drive/virtual_tape.rs |  2 +-
 www/Utils.js                   |  2 +-
 www/tape/DriveStatus.js        |  8 +++---
 www/tape/window/Erase.js       |  4 +--
 10 files changed, 108 insertions(+), 38 deletions(-)

diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index 80d17a27..e354f4c0 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -321,8 +321,8 @@ pub fn unload(
         permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE, false),
     },
 )]
-/// Erase media. Check for label-text if given (cancels if wrong media).
-pub fn erase_media(
+/// Format media. Check for label-text if given (cancels if wrong media).
+pub fn format_media(
     drive: String,
     fast: Option<bool>,
     label_text: Option<String>,
@@ -331,7 +331,7 @@ pub fn erase_media(
     let upid_str = run_drive_worker(
         rpcenv,
         drive.clone(),
-        "erase-media",
+        "format-media",
         Some(drive.clone()),
         move |worker, config| {
             if let Some(ref label) = label_text {
@@ -350,15 +350,15 @@ pub fn erase_media(
                     }
                     /* assume drive contains no or unrelated data */
                     task_log!(worker, "unable to read media label: {}", err);
-                    task_log!(worker, "erase anyways");
-                    handle.erase_media(fast.unwrap_or(true))?;
+                    task_log!(worker, "format anyways");
+                    handle.format_media(fast.unwrap_or(true))?;
                 }
                 Ok((None, _)) => {
                     if let Some(label) = label_text {
                         bail!("expected label '{}', found empty tape", label);
                     }
-                    task_log!(worker, "found empty media - erase anyways");
-                    handle.erase_media(fast.unwrap_or(true))?;
+                    task_log!(worker, "found empty media - format anyways");
+                    handle.format_media(fast.unwrap_or(true))?;
                 }
                 Ok((Some(media_id), _key_config)) => {
                     if let Some(label_text) = label_text {
@@ -391,7 +391,7 @@ pub fn erase_media(
                         inventory.remove_media(&media_id.label.uuid)?;
                     };
 
-                    handle.erase_media(fast.unwrap_or(true))?;
+                    handle.format_media(fast.unwrap_or(true))?;
                 }
             }
 
@@ -503,7 +503,7 @@ pub fn eject_media(
 /// Write a new media label to the media in 'drive'. The media is
 /// assigned to the specified 'pool', or else to the free media pool.
 ///
-/// Note: The media need to be empty (you may want to erase it first).
+/// Note: The media need to be empty (you may want to format it first).
 pub fn label_media(
     drive: String,
     pool: Option<String>,
@@ -528,7 +528,7 @@ pub fn label_media(
             drive.rewind()?;
 
             match drive.read_next_file() {
-                Ok(Some(_file)) => bail!("media is not empty (erase first)"),
+                Ok(Some(_file)) => bail!("media is not empty (format it first)"),
                 Ok(None) => { /* EOF mark at BOT, assume tape is empty */ },
                 Err(err) => {
                     println!("TEST {:?}", err);
@@ -1092,7 +1092,7 @@ fn barcode_label_media_worker(
 
         match drive.read_next_file() {
             Ok(Some(_file)) => {
-                worker.log(format!("media '{}' is not empty (erase first)", label_text));
+                worker.log(format!("media '{}' is not empty (format it first)", label_text));
                 continue;
             }
             Ok(None) => { /* EOF mark at BOT, assume tape is empty */ },
@@ -1100,7 +1100,7 @@ fn barcode_label_media_worker(
                 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 - erase first)", label_text));
+                    worker.warn(format!("media '{}' read error (maybe not empty - format it first)", label_text));
                     continue;
                 }
             }
@@ -1430,9 +1430,9 @@ pub const SUBDIRS: SubdirMap = &sorted!([
             .post(&API_METHOD_EJECT_MEDIA)
     ),
     (
-        "erase-media",
+        "format-media",
         &Router::new()
-            .post(&API_METHOD_ERASE_MEDIA)
+            .post(&API_METHOD_FORMAT_MEDIA)
     ),
     (
         "export-media",
diff --git a/src/bin/pmt.rs b/src/bin/pmt.rs
index df3ad9ec..da0d4fd9 100644
--- a/src/bin/pmt.rs
+++ b/src/bin/pmt.rs
@@ -409,7 +409,7 @@ fn eod(param: Value) -> Result<(), Error> {
         },
     },
 )]
-/// Erase media
+/// Erase media (from current position)
 fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
@@ -418,6 +418,35 @@ fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
     Ok(())
 }
 
+#[api(
+   input: {
+        properties: {
+            drive: {
+                schema: DRIVE_NAME_SCHEMA,
+                optional: true,
+            },
+            device: {
+                schema: LTO_DRIVE_PATH_SCHEMA,
+                optional: true,
+            },
+            fast: {
+                description: "Use fast erase.",
+                type: bool,
+                optional: true,
+                default: true,
+            },
+        },
+    },
+)]
+/// Format media,  single partition
+fn format(fast: Option<bool>, param: Value) -> Result<(), Error> {
+
+    let mut handle = get_tape_handle(&param)?;
+    handle.format_media(fast.unwrap_or(true))?;
+
+    Ok(())
+}
+
 #[api(
    input: {
         properties: {
@@ -800,6 +829,7 @@ fn main() -> Result<(), Error> {
         .insert("eject", std_cmd(&API_METHOD_EJECT))
         .insert("eod", std_cmd(&API_METHOD_EOD))
         .insert("erase", std_cmd(&API_METHOD_ERASE))
+        .insert("format", std_cmd(&API_METHOD_FORMAT))
         .insert("fsf", std_cmd(&API_METHOD_FSF).arg_param(&["count"]))
         .insert("fsfm", std_cmd(&API_METHOD_FSFM).arg_param(&["count"]))
         .insert("fsr", std_cmd(&API_METHOD_FSR).arg_param(&["count"]))
diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs
index cddac1b4..2a784632 100644
--- a/src/bin/proxmox-tape.rs
+++ b/src/bin/proxmox-tape.rs
@@ -115,8 +115,8 @@ pub fn extract_drive_name(
        },
     },
 )]
-/// Erase media
-async fn erase_media(mut param: Value) -> Result<(), Error> {
+/// Format media
+async fn format_media(mut param: Value) -> Result<(), Error> {
 
     let output_format = get_output_format(&param);
 
@@ -126,7 +126,7 @@ async fn erase_media(mut param: Value) -> Result<(), Error> {
 
     let mut client = connect_to_localhost()?;
 
-    let path = format!("api2/json/tape/drive/{}/erase-media", drive);
+    let path = format!("api2/json/tape/drive/{}/format-media", drive);
     let result = client.post(&path, Some(param)).await?;
 
     view_task_result(&mut client, result, &output_format).await?;
@@ -992,8 +992,8 @@ fn main() {
                 .completion_cb("drive", complete_drive_name)
         )
         .insert(
-            "erase",
-            CliCommand::new(&API_METHOD_ERASE_MEDIA)
+            "format",
+            CliCommand::new(&API_METHOD_FORMAT_MEDIA)
                 .completion_cb("drive", complete_drive_name)
         )
         .insert(
diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
index becbad50..a4e0499e 100644
--- a/src/tape/drive/lto/mod.rs
+++ b/src/tape/drive/lto/mod.rs
@@ -179,6 +179,10 @@ impl LtoTapeHandle {
         Ok(status)
     }
 
+    pub fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
+        self.sg_tape.erase_media(fast)
+    }
+
     pub fn load(&mut self) ->  Result<(), Error> {
         self.sg_tape.load()
     }
@@ -223,9 +227,8 @@ impl TapeDriver for LtoTapeHandle {
         self.sg_tape.current_file_number()
     }
 
-    fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
-        self.rewind()?; // important - erase from BOT
-        self.sg_tape.erase_media(fast)
+    fn format_media(&mut self, fast: bool) -> Result<(), Error> {
+        self.sg_tape.format_media(fast)
     }
 
     fn read_next_file<'a>(&'a mut self) -> Result<Option<Box<dyn TapeRead + 'a>>, std::io::Error> {
diff --git a/src/tape/drive/lto/sg_tape.rs b/src/tape/drive/lto/sg_tape.rs
index 802756fa..531acbee 100644
--- a/src/tape/drive/lto/sg_tape.rs
+++ b/src/tape/drive/lto/sg_tape.rs
@@ -100,9 +100,48 @@ impl SgTape {
         scsi_inquiry(&mut self.file)
     }
 
-    pub fn erase_media(&mut self, _fast: bool) -> Result<(), Error> {
-        // fixme:
-        unimplemented!();
+    /// Erase medium.
+    ///
+    /// EOD is written at the current position, which marks it as end
+    /// of data. After the command is successfully completed, the
+    /// drive is positioned immediately before End Of Data (not End Of
+    /// Tape).
+    pub fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.push(0x19);
+        if fast {
+            cmd.push(0); // LONG=0
+        } else {
+            cmd.push(1); // LONG=1
+        }
+        cmd.extend(&[0, 0, 0, 0]);
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("erase failed - {}", err))?;
+
+        Ok(())
+    }
+
+    /// Format media, single partition
+    pub fn format_media(&mut self, fast: bool) -> Result<(), Error> {
+
+        self.rewind()?;
+
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.extend(&[0x04, 0, 0, 0, 0, 0]);
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("erase failed - {}", err))?;
+
+        if !fast {
+            self.erase_media(false)?; // overwrite everything
+        }
+
+        Ok(())
     }
 
     pub fn rewind(&mut self) -> Result<(), Error> {
diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs
index 71f61642..061c1cfc 100644
--- a/src/tape/drive/mod.rs
+++ b/src/tape/drive/mod.rs
@@ -111,7 +111,7 @@ pub trait TapeDriver {
     fn current_file_number(&mut self) -> Result<u64, Error>;
 
     /// Completely erase the media
-    fn erase_media(&mut self, fast: bool) -> Result<(), Error>;
+    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>;
@@ -122,11 +122,9 @@ pub trait TapeDriver {
     /// Write label to tape (erase tape content)
     fn label_tape(&mut self, label: &MediaLabel) -> Result<(), Error> {
 
-        self.rewind()?;
-
         self.set_encryption(None)?;
 
-        self.erase_media(true)?;
+        self.format_media(true)?; // this rewinds the tape
 
         let raw = serde_json::to_string_pretty(&serde_json::to_value(&label)?)?;
 
diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs
index 54e0887f..e4d09c2f 100644
--- a/src/tape/drive/virtual_tape.rs
+++ b/src/tape/drive/virtual_tape.rs
@@ -360,7 +360,7 @@ impl TapeDriver for VirtualTapeHandle {
         }
     }
 
-    fn erase_media(&mut self, _fast: bool) -> Result<(), Error> {
+    fn format_media(&mut self, _fast: bool) -> Result<(), Error> {
         let mut status = self.load_status()?;
         match status.current_tape {
             Some(VirtualTapeStatus { ref name, ref mut pos }) => {
diff --git a/www/Utils.js b/www/Utils.js
index dc7e539f..b9012374 100644
--- a/www/Utils.js
+++ b/www/Utils.js
@@ -374,7 +374,7 @@ Ext.define('PBS.Utils', {
 	    dircreate: [gettext('Directory Storage'), gettext('Create')],
 	    dirremove: [gettext('Directory'), gettext('Remove')],
 	    'eject-media': [gettext('Drive'), gettext('Eject Media')],
-	    'erase-media': [gettext('Drive'), gettext('Erase Media')],
+	    "format-media": [gettext('Drive'), gettext('Format media')],
 	    garbage_collection: ['Datastore', gettext('Garbage Collect')],
 	    'inventory-update': [gettext('Drive'), gettext('Inventory Update')],
 	    'label-media': [gettext('Drive'), gettext('Label Media')],
diff --git a/www/tape/DriveStatus.js b/www/tape/DriveStatus.js
index 65197285..2bf05f88 100644
--- a/www/tape/DriveStatus.js
+++ b/www/tape/DriveStatus.js
@@ -84,11 +84,11 @@ Ext.define('PBS.TapeManagement.DriveStatus', {
 	    }).show();
 	},
 
-	erase: function() {
+	format: function() {
 	    let me = this;
 	    let view = me.getView();
 	    let driveid = view.drive;
-	    PBS.Utils.driveCommand(driveid, 'erase-media', {
+	    PBS.Utils.driveCommand(driveid, 'format-media', {
 		waitMsgTarget: view,
 		method: 'POST',
 		success: function(response) {
@@ -212,9 +212,9 @@ Ext.define('PBS.TapeManagement.DriveStatus', {
 	    },
 	},
 	{
-	    text: gettext('Erase'),
+	    text: gettext('Format'),
 	    xtype: 'proxmoxButton',
-	    handler: 'erase',
+	    handler: 'format',
 	    iconCls: 'fa fa-trash-o',
 	    dangerous: true,
 	    confirmMsg: gettext('Are you sure you want to erase the inserted tape?'),
diff --git a/www/tape/window/Erase.js b/www/tape/window/Erase.js
index 61bd2130..1177dfeb 100644
--- a/www/tape/window/Erase.js
+++ b/www/tape/window/Erase.js
@@ -11,13 +11,13 @@ Ext.define('PBS.TapeManagement.EraseWindow', {
 	return {};
     },
 
-    title: gettext('Erase'),
+    title: gettext('Format/Erase'),
     url: `/api2/extjs/tape/drive`,
     showProgress: true,
     submitUrl: function(url, values) {
 	let drive = values.drive;
 	delete values.drive;
-	return `${url}/${drive}/erase-media`;
+	return `${url}/${drive}/format-media`;
     },
 
     method: 'POST',
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 05/11] tape: fix LEOM handling
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
                   ` (3 preceding siblings ...)
  2021-04-07 10:23 ` [pbs-devel] [PATCH 04/11] tape: implement format/erase Dietmar Maurer
@ 2021-04-07 10:23 ` Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 06/11] tape: make fsf/bsf driver specific Dietmar Maurer
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:23 UTC (permalink / raw)
  To: pbs-devel

---
 src/tape/drive/lto/sg_tape.rs | 57 +++++++++++++++++++++++++++--------
 src/tools/sgutils2.rs         | 26 ++++++++--------
 2 files changed, 57 insertions(+), 26 deletions(-)

diff --git a/src/tape/drive/lto/sg_tape.rs b/src/tape/drive/lto/sg_tape.rs
index 531acbee..fd1a067a 100644
--- a/src/tape/drive/lto/sg_tape.rs
+++ b/src/tape/drive/lto/sg_tape.rs
@@ -75,6 +75,11 @@ impl SgTape {
         Ok(Self { file })
     }
 
+    // fixme: remove - only for testing
+    pub fn file_mut(&mut self) -> &mut File {
+        &mut self.file
+    }
+
     pub fn open<P: AsRef<Path>>(path: P) -> Result<SgTape, Error> {
         // do not wait for media, use O_NONBLOCK
         let file = OpenOptions::new()
@@ -195,9 +200,26 @@ impl SgTape {
         Ok(position.logical_file_id)
     }
 
-    pub fn locate(&mut self) ->  Result<(), Error> {
-        // fixme: impl LOCATE
-        unimplemented!();
+    // fixme: dont use - needs LTO5
+    pub fn locate_file(&mut self, position: u64) ->  Result<(), Error> {
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.extend(&[0x92, 0b000_01_000, 0, 0]); // LOCATE(16) filemarks
+        cmd.extend(&position.to_be_bytes());
+        cmd.extend(&[0, 0, 0, 0]);
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("locate file {} failed - {}", position, err))?;
+
+        // move to other side of filemark
+        cmd.truncate(0);
+        cmd.extend(&[0x11, 0x01, 0, 0, 1, 0]); // SPACE(6) one filemarks
+
+        sg_raw.do_command(&cmd)
+            .map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
+
+        Ok(())
     }
 
     pub fn move_to_eom(&mut self) ->  Result<(), Error> {
@@ -286,8 +308,15 @@ impl SgTape {
         cmd.extend(&[0, 0, count as u8]); // COUNT
         cmd.push(0); // control byte
 
-        sg_raw.do_command(&cmd)
-            .map_err(|err| proxmox::io_format_err!("write filemark failed - {}", err))?;
+        match sg_raw.do_command(&cmd) {
+            Ok(_) => { /* OK */ }
+            Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => {
+                /* LEOM - ignore */
+            }
+            Err(err) => {
+                proxmox::io_bail!("write filemark  failed - {}", err);
+            }
+        }
 
         Ok(())
     }
@@ -360,7 +389,7 @@ impl SgTape {
 
         let transfer_len = data.len();
 
-        if transfer_len > 0xFFFFFF {
+        if transfer_len > 0x800000 {
            proxmox::io_bail!("write failed - data too large");
         }
 
@@ -379,12 +408,15 @@ impl SgTape {
         //println!("WRITE {:?}", cmd);
         //println!("WRITE {:?}", data);
 
-        sg_raw.do_out_command(&cmd, data)
-            .map_err(|err| proxmox::io_format_err!("write failed - {}", err))?;
-
-        // fixme: LEOM?
-
-        Ok(false)
+        match sg_raw.do_out_command(&cmd, data) {
+            Ok(()) => { return Ok(false) }
+            Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => {
+                return Ok(true); // LEOM
+            }
+            Err(err) => {
+                proxmox::io_bail!("write failed - {}", err);
+            }
+        }
     }
 
     fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> {
@@ -416,7 +448,6 @@ impl SgTape {
                 return Ok(BlockReadStatus::EndOfStream);
             }
             Err(err) => {
-                println!("READ ERR {:?}", err);
                 proxmox::io_bail!("read failed - {}", err);
             }
         };
diff --git a/src/tools/sgutils2.rs b/src/tools/sgutils2.rs
index 987d5738..4edfd9d9 100644
--- a/src/tools/sgutils2.rs
+++ b/src/tools/sgutils2.rs
@@ -203,17 +203,17 @@ struct InquiryPage {
 
 #[repr(C, packed)]
 #[derive(Endian, Debug)]
-struct RequestSenseFixed {
-    response_code: u8,
+pub struct RequestSenseFixed {
+    pub response_code: u8,
     obsolete: u8,
-    flags2: u8,
-    information: [u8;4],
-    additional_sense_len: u8,
-    command_specific_information: [u8;4],
-    additional_sense_code: u8,
-    additional_sense_code_qualifier: u8,
-    field_replacable_unit_code: u8,
-    sense_key_specific: [u8; 3],
+    pub flags2: u8,
+    pub information: [u8;4],
+    pub additional_sense_len: u8,
+    pub command_specific_information: [u8;4],
+    pub additional_sense_code: u8,
+    pub additional_sense_code_qualifier: u8,
+    pub field_replacable_unit_code: u8,
+    pub sense_key_specific: [u8; 3],
 }
 
 #[repr(C, packed)]
@@ -575,15 +575,15 @@ impl <'a, F: AsRawFd> SgRaw<'a, F> {
     /// Run dataout command
     ///
     /// Note: use alloc_page_aligned_buffer to alloc data transfer buffer
-    pub fn do_out_command(&mut self, cmd: &[u8], data: &[u8]) -> Result<(), Error> {
+    pub fn do_out_command(&mut self, cmd: &[u8], data: &[u8]) -> Result<(), ScsiError> {
 
         if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) } {
-            bail!("no valid SCSI command");
+            return Err(format_err!("no valid SCSI command").into());
         }
 
         let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
         if ((data.as_ptr() as usize) & (page_size -1)) != 0 {
-            bail!("wrong transfer buffer alignment");
+            return Err(format_err!("wrong transfer buffer alignment").into());
         }
 
         let mut ptvp = self.create_scsi_pt_obj()?;
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 06/11] tape: make fsf/bsf driver specific
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
                   ` (4 preceding siblings ...)
  2021-04-07 10:23 ` [pbs-devel] [PATCH 05/11] tape: fix LEOM handling Dietmar Maurer
@ 2021-04-07 10:23 ` Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 07/11] tape: make sure there is a filemark at the end of the tape Dietmar Maurer
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:23 UTC (permalink / raw)
  To: pbs-devel

Because the virtual tape driver behaves different than LTO drives.
---
 src/tape/drive/lto/mod.rs      |  31 ++++++++--
 src/tape/drive/mod.rs          |  19 +-----
 src/tape/drive/virtual_tape.rs | 106 +++++++++++++++++++--------------
 3 files changed, 89 insertions(+), 67 deletions(-)

diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
index a4e0499e..25df897f 100644
--- a/src/tape/drive/lto/mod.rs
+++ b/src/tape/drive/lto/mod.rs
@@ -179,6 +179,14 @@ impl LtoTapeHandle {
         Ok(status)
     }
 
+    pub fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
+        self.sg_tape.space_filemarks(isize::try_from(count)?)
+    }
+
+    pub fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
+        self.sg_tape.space_filemarks(-isize::try_from(count)?)
+    }
+
     pub fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
         self.sg_tape.erase_media(fast)
     }
@@ -211,12 +219,25 @@ impl TapeDriver for LtoTapeHandle {
         self.sg_tape.move_to_eom()
     }
 
-    fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
-        self.sg_tape.space_filemarks(isize::try_from(count)?)
-    }
+    fn move_to_last_file(&mut self) -> Result<(), Error> {
 
-    fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
-        self.sg_tape.space_filemarks(-isize::try_from(count)?)
+        self.move_to_eom()?;
+
+        let pos = self.current_file_number()?;
+
+        if pos == 0 {
+            bail!("move_to_last_file failed - media contains no data");
+        }
+
+        if pos == 1 {
+            self.rewind()?;
+            return Ok(());
+        }
+
+        self.backward_space_count_files(2)?;
+        self.forward_space_count_files(1)?;
+
+        Ok(())
     }
 
     fn rewind(&mut self) -> Result<(), Error> {
diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs
index 061c1cfc..28ff0a3a 100644
--- a/src/tape/drive/mod.rs
+++ b/src/tape/drive/mod.rs
@@ -88,24 +88,7 @@ pub trait TapeDriver {
     fn move_to_eom(&mut self) -> Result<(), Error>;
 
     /// Move to last file
-    fn move_to_last_file(&mut self) -> Result<(), Error> {
-
-        self.move_to_eom()?;
-
-        if self.current_file_number()? == 0 {
-            bail!("move_to_last_file failed - media contains no data");
-        }
-
-        self.backward_space_count_files(2)?;
-
-        Ok(())
-    }
-
-    /// Forward space count files. The tape is positioned on the first block of the next file.
-    fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error>;
-
-    /// Backward space count files.  The tape is positioned on the last block of the previous file.
-    fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error>;
+    fn move_to_last_file(&mut self) -> Result<(), Error>;
 
     /// Current file number
     fn current_file_number(&mut self) -> Result<u64, Error>;
diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs
index e4d09c2f..bb4b4e3c 100644
--- a/src/tape/drive/virtual_tape.rs
+++ b/src/tape/drive/virtual_tape.rs
@@ -181,6 +181,53 @@ impl VirtualTapeHandle {
         Ok(list)
     }
 
+    #[allow(dead_code)]
+    fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
+        let mut status = self.load_status()?;
+        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()))?;
+
+                let new_pos = *pos + count;
+                if new_pos <= index.files {
+                    *pos = new_pos;
+                } else {
+                    bail!("forward_space_count_files failed: move beyond EOT");
+                }
+
+                self.store_status(&status)
+                    .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
+
+                Ok(())
+            }
+            None => bail!("drive is empty (no tape loaded)."),
+        }
+    }
+
+    // Note: behavior differs from LTO, because we always position at
+    // EOT side.
+    fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
+        let mut status = self.load_status()?;
+        match status.current_tape {
+            Some(VirtualTapeStatus { ref mut pos, .. }) => {
+
+                if count <= *pos {
+                    *pos = *pos - count;
+                } else {
+                    bail!("backward_space_count_files failed: move before BOT");
+                }
+
+                self.store_status(&status)
+                    .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
+
+                Ok(())
+            }
+            None => bail!("drive is empty (no tape loaded)."),
+        }
+    }
+
 }
 
 impl TapeDriver for VirtualTapeHandle {
@@ -199,6 +246,21 @@ impl TapeDriver for VirtualTapeHandle {
         }
     }
 
+    /// Move to last file
+    fn move_to_last_file(&mut self) -> Result<(), Error> {
+
+        self.move_to_eom()?;
+
+        if self.current_file_number()? == 0 {
+            bail!("move_to_last_file failed - media contains no data");
+        }
+
+        self.backward_space_count_files(1)?;
+
+        Ok(())
+    }
+
+
     fn read_next_file(&mut self) -> Result<Option<Box<dyn TapeRead>>, io::Error> {
         let mut status = self.load_status()
             .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
@@ -304,50 +366,6 @@ impl TapeDriver for VirtualTapeHandle {
         }
     }
 
-    fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
-        let mut status = self.load_status()?;
-        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()))?;
-
-                let new_pos = *pos + count;
-                if new_pos <= index.files {
-                    *pos = new_pos;
-                } else {
-                    bail!("forward_space_count_files failed: move beyond EOT");
-                }
-
-                self.store_status(&status)
-                    .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
-
-                Ok(())
-            }
-            None => bail!("drive is empty (no tape loaded)."),
-        }
-    }
-
-    fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
-        let mut status = self.load_status()?;
-        match status.current_tape {
-            Some(VirtualTapeStatus { ref mut pos, .. }) => {
-
-                if count <= *pos {
-                    *pos = *pos - count;
-                } else {
-                    bail!("backward_space_count_files failed: move before BOT");
-                }
-
-                self.store_status(&status)
-                    .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
-
-                Ok(())
-            }
-            None => bail!("drive is empty (no tape loaded)."),
-        }
-    }
-
     fn rewind(&mut self) -> Result<(), Error> {
         let mut status = self.load_status()?;
         match status.current_tape {
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 07/11] tape: make sure there is a filemark at the end of the tape
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
                   ` (5 preceding siblings ...)
  2021-04-07 10:23 ` [pbs-devel] [PATCH 06/11] tape: make fsf/bsf driver specific Dietmar Maurer
@ 2021-04-07 10:23 ` Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 08/11] sgutils2: add scsi_mode_sense helper Dietmar Maurer
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:23 UTC (permalink / raw)
  To: pbs-devel

---
 src/bin/pmt.rs                 |  2 +-
 src/bin/proxmox-tape.rs        |  2 +-
 src/tape/drive/lto/mod.rs      |  8 ++--
 src/tape/drive/lto/sg_tape.rs  | 71 +++++++++++++++++++++++++++++-----
 src/tape/drive/mod.rs          |  6 ++-
 src/tape/drive/virtual_tape.rs |  4 +-
 src/tape/pool_writer/mod.rs    |  2 +-
 7 files changed, 75 insertions(+), 20 deletions(-)

diff --git a/src/bin/pmt.rs b/src/bin/pmt.rs
index da0d4fd9..854364c7 100644
--- a/src/bin/pmt.rs
+++ b/src/bin/pmt.rs
@@ -383,7 +383,7 @@ fn eject(param: Value) -> Result<(), Error> {
 fn eod(param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
-    handle.move_to_eom()?;
+    handle.move_to_eom(false)?;
 
     Ok(())
 }
diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs
index 2a784632..f1de6236 100644
--- a/src/bin/proxmox-tape.rs
+++ b/src/bin/proxmox-tape.rs
@@ -551,7 +551,7 @@ fn move_to_eom(mut param: Value) -> Result<(), Error> {
 
     let mut drive = open_drive(&config, &drive)?;
 
-    drive.move_to_eom()?;
+    drive.move_to_eom(false)?;
 
     Ok(())
 }
diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
index 25df897f..7fcef8a0 100644
--- a/src/tape/drive/lto/mod.rs
+++ b/src/tape/drive/lto/mod.rs
@@ -215,13 +215,15 @@ impl TapeDriver for LtoTapeHandle {
     }
 
     /// Go to the end of the recorded media (for appending files).
-    fn move_to_eom(&mut self) -> Result<(), Error> {
-        self.sg_tape.move_to_eom()
+    fn move_to_eom(&mut self, write_missing_eof: bool) -> Result<(), Error> {
+        self.sg_tape.move_to_eom(write_missing_eof)
     }
 
     fn move_to_last_file(&mut self) -> Result<(), Error> {
 
-        self.move_to_eom()?;
+        self.move_to_eom(false)?;
+
+        self.sg_tape.check_filemark()?;
 
         let pos = self.current_file_number()?;
 
diff --git a/src/tape/drive/lto/sg_tape.rs b/src/tape/drive/lto/sg_tape.rs
index fd1a067a..9af0eae3 100644
--- a/src/tape/drive/lto/sg_tape.rs
+++ b/src/tape/drive/lto/sg_tape.rs
@@ -190,8 +190,6 @@ impl SgTape {
             bail!("detecthed partitioned tape - not supported");
         }
 
-        println!("DATA: {:?}", page);
-
         Ok(page)
     }
 
@@ -222,7 +220,35 @@ impl SgTape {
         Ok(())
     }
 
-    pub fn move_to_eom(&mut self) ->  Result<(), Error> {
+    /// Check if we are positioned after a filemark (or BOT)
+    pub fn check_filemark(&mut self) -> Result<bool, Error> {
+
+        let pos = self.position()?;
+        if pos.logical_object_number == 0 {
+            // at BOT, Ok (no filemark required)
+            return Ok(true);
+        }
+
+        // Note: SPACE blocks returns Err at filemark
+        match self.space(-1, true) {
+            Ok(_) => {
+                self.space(1, true) // move back to end
+                    .map_err(|err| format_err!("check_filemark failed (space forward) - {}", err))?;
+                Ok(false)
+            }
+            Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 })) => {
+                // Filemark detected - good
+                self.space(1, true) // move back to end
+                    .map_err(|err| format_err!("check_filemark failed (space forward) - {}", err))?;
+                Ok(true)
+            }
+            Err(err) => {
+                bail!("check_filemark failed - {:?}", err);
+            }
+        }
+    }
+
+    pub fn move_to_eom(&mut self, write_missing_eof: bool) ->  Result<(), Error> {
         let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
         let mut cmd = Vec::new();
@@ -231,35 +257,60 @@ impl SgTape {
         sg_raw.do_command(&cmd)
             .map_err(|err| format_err!("move to EOD failed - {}", err))?;
 
+        if write_missing_eof {
+            if !self.check_filemark()? {
+                self.write_filemarks(1, false)?;
+            }
+        }
+
         Ok(())
     }
 
-    pub fn space_filemarks(&mut self, count: isize) ->  Result<(), Error> {
+    fn space(&mut self, count: isize, blocks: bool) ->  Result<(), ScsiError> {
         let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
         let mut cmd = Vec::new();
 
         // Use short command if possible (supported by all drives)
         if (count <= 0x7fffff) && (count > -0x7fffff) {
-            cmd.extend(&[0x11, 0x01]); // SPACE(6) with filemarks
+            cmd.push(0x11); // SPACE(6)
+            if blocks {
+                cmd.push(0); // blocks
+            } else {
+                cmd.push(1); // filemarks
+            }
             cmd.push(((count >> 16) & 0xff) as u8);
             cmd.push(((count >> 8) & 0xff) as u8);
             cmd.push((count & 0xff) as u8);
             cmd.push(0); //control byte
         } else {
-
-            cmd.extend(&[0x91, 0x01, 0, 0]); // SPACE(16) with filemarks
+            cmd.push(0x91); // SPACE(16)
+            if blocks {
+                cmd.push(0); // blocks
+            } else {
+                cmd.push(1); // filemarks
+            }
+            cmd.extend(&[0, 0]); // reserved
             let count: i64 = count as i64;
             cmd.extend(&count.to_be_bytes());
-            cmd.extend(&[0, 0, 0, 0]);
+            cmd.extend(&[0, 0, 0, 0]); // reserved
         }
 
-        sg_raw.do_command(&cmd)
-            .map_err(|err| format_err!("space filemarks failed - {}", err))?;
+        sg_raw.do_command(&cmd)?;
 
         Ok(())
     }
 
+    pub fn space_filemarks(&mut self, count: isize) ->  Result<(), Error> {
+        self.space(count, false)
+            .map_err(|err| format_err!("space filemarks failed - {}", err))
+    }
+
+    pub fn space_blocks(&mut self, count: isize) ->  Result<(), Error> {
+        self.space(count, true)
+            .map_err(|err| format_err!("space blocks failed - {}", err))
+    }
+
     pub fn eject(&mut self) ->  Result<(), Error> {
         let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
         sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs
index 28ff0a3a..cd02e16d 100644
--- a/src/tape/drive/mod.rs
+++ b/src/tape/drive/mod.rs
@@ -84,8 +84,10 @@ pub trait TapeDriver {
 
     /// Move to end of recorded data
     ///
-    /// We assume this flushes the tape write buffer.
-    fn move_to_eom(&mut self) -> Result<(), Error>;
+    /// We assume this flushes the tape write buffer. if
+    /// write_missing_eof is true, we verify that there is a filemark
+    /// at the end. If not, we write one.
+    fn move_to_eom(&mut self, write_missing_eof: bool) -> Result<(), Error>;
 
     /// Move to last file
     fn move_to_last_file(&mut self) -> Result<(), Error>;
diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs
index bb4b4e3c..a852056a 100644
--- a/src/tape/drive/virtual_tape.rs
+++ b/src/tape/drive/virtual_tape.rs
@@ -249,7 +249,7 @@ impl TapeDriver for VirtualTapeHandle {
     /// Move to last file
     fn move_to_last_file(&mut self) -> Result<(), Error> {
 
-        self.move_to_eom()?;
+        self.move_to_eom(false)?;
 
         if self.current_file_number()? == 0 {
             bail!("move_to_last_file failed - media contains no data");
@@ -347,7 +347,7 @@ impl TapeDriver for VirtualTapeHandle {
         }
     }
 
-    fn move_to_eom(&mut self) -> Result<(), Error> {
+    fn move_to_eom(&mut self, _write_missing_eof: bool) -> Result<(), Error> {
         let mut status = self.load_status()?;
         match status.current_tape {
             Some(VirtualTapeStatus { ref name, ref mut pos }) => {
diff --git a/src/tape/pool_writer/mod.rs b/src/tape/pool_writer/mod.rs
index 05aa52a4..99fdb48c 100644
--- a/src/tape/pool_writer/mod.rs
+++ b/src/tape/pool_writer/mod.rs
@@ -297,7 +297,7 @@ impl PoolWriter {
 
         if !status.at_eom {
             worker.log(String::from("moving to end of media"));
-            status.drive.move_to_eom()?;
+            status.drive.move_to_eom(true)?;
             status.at_eom = true;
         }
 
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 08/11] sgutils2: add scsi_mode_sense helper
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
                   ` (6 preceding siblings ...)
  2021-04-07 10:23 ` [pbs-devel] [PATCH 07/11] tape: make sure there is a filemark at the end of the tape Dietmar Maurer
@ 2021-04-07 10:23 ` Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 09/11] tape: correctly set/display drive option Dietmar Maurer
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:23 UTC (permalink / raw)
  To: pbs-devel

---
 src/tools/sgutils2.rs | 99 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)

diff --git a/src/tools/sgutils2.rs b/src/tools/sgutils2.rs
index 4edfd9d9..af6ab6de 100644
--- a/src/tools/sgutils2.rs
+++ b/src/tools/sgutils2.rs
@@ -242,6 +242,46 @@ pub struct InquiryInfo {
     pub revision: String,
 }
 
+#[repr(C, packed)]
+#[derive(Endian, Debug, Copy, Clone)]
+pub struct ModeParameterHeader {
+    pub mode_data_len: u16,
+    pub medium_type: u8,
+    pub flags3: u8,
+    reserved4: [u8;2],
+    pub block_descriptior_len: u16,
+}
+
+#[repr(C, packed)]
+#[derive(Endian, Debug, Copy, Clone)]
+/// SCSI ModeBlockDescriptor for Tape devices
+pub struct ModeBlockDescriptor {
+    pub density_code: u8,
+    pub number_of_blocks: [u8;3],
+    reserverd: u8,
+    pub block_length: [u8; 3],
+}
+
+impl ModeBlockDescriptor {
+
+    pub fn block_length(&self) -> u32 {
+        ((self.block_length[0] as u32) << 16) +
+            ((self.block_length[1] as u32) << 8) +
+            (self.block_length[2] as u32)
+
+    }
+
+    pub fn set_block_length(&mut self, length: u32) -> Result<(), Error> {
+        if length > 0x80_00_00 {
+            bail!("block length '{}' is too large", length);
+        }
+        self.block_length[0] = ((length & 0x00ff0000) >> 16) as u8;
+        self.block_length[1] = ((length & 0x0000ff00) >> 8) as u8;
+        self.block_length[2] = (length & 0x000000ff) as u8;
+        Ok(())
+    }
+}
+
 pub const SCSI_PT_DO_START_OK:c_int = 0;
 pub const SCSI_PT_DO_BAD_PARAMS:c_int = 1;
 pub const SCSI_PT_DO_TIMEOUT:c_int = 2;
@@ -654,3 +694,62 @@ pub fn scsi_inquiry<F: AsRawFd>(
         Ok(info)
     }).map_err(|err: Error| format_err!("decode inquiry page failed - {}", err))
 }
+
+/// Run SCSI Mode Sense
+///
+/// Warning: P needs to be repr(C, packed)]
+pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
+    file: &mut F,
+    disable_block_descriptor: bool,
+    page_code: u8,
+    sub_page_code: u8,
+) -> Result<(ModeParameterHeader, Option<ModeBlockDescriptor>, P), Error> {
+
+    let allocation_len: u16 = 4096;
+    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
+
+    let mut cmd = Vec::new();
+    cmd.push(0x5A); // MODE SENSE(10)
+    if disable_block_descriptor {
+        cmd.push(8); // DBD=1 (Disable Block Descriptors)
+    } else {
+        cmd.push(0); // DBD=0 (Include Block Descriptors)
+    }
+    cmd.push(page_code & 63); // report current values for page_code
+    cmd.push(sub_page_code);
+
+    cmd.extend(&[0, 0, 0]); // reserved
+    cmd.extend(&allocation_len.to_be_bytes()); // allocation len
+    cmd.push(0); //control
+
+    let data = sg_raw.do_command(&cmd)
+        .map_err(|err| format_err!("mode sense failed - {}", err))?;
+
+    proxmox::try_block!({
+        let mut reader = &data[..];
+
+        let head: ModeParameterHeader = unsafe { reader.read_be_value()? };
+
+        if (head.mode_data_len as usize + 2) != data.len() {
+            bail!("wrong mode_data_len");
+        }
+
+        if disable_block_descriptor && head.block_descriptior_len != 0 {
+            bail!("wrong block_descriptior_len");
+        }
+
+        let mut block_descriptor: Option<ModeBlockDescriptor> = None;
+
+        if !disable_block_descriptor {
+            if head.block_descriptior_len != 8 {
+                bail!("wrong block_descriptior_len");
+            }
+
+            block_descriptor = Some(unsafe { reader.read_be_value()? });
+        }
+
+        let page: P = unsafe { reader.read_be_value()? };
+
+        Ok((head, block_descriptor, page))
+    }).map_err(|err: Error| format_err!("decode mode sense failed - {}", err))
+}
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 09/11] tape: correctly set/display drive option
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
                   ` (7 preceding siblings ...)
  2021-04-07 10:23 ` [pbs-devel] [PATCH 08/11] sgutils2: add scsi_mode_sense helper Dietmar Maurer
@ 2021-04-07 10:23 ` Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 10/11] tape: pmt - re-implement fsr/bsr Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 11/11] tape: pmt - re-implement lock/unlock command Dietmar Maurer
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:23 UTC (permalink / raw)
  To: pbs-devel

---
 src/api2/types/tape/drive.rs  |  12 +--
 src/bin/proxmox-tape.rs       |   5 +-
 src/tape/drive/lto/mod.rs     |  48 ++++++-----
 src/tape/drive/lto/sg_tape.rs | 153 ++++++++++++++++++++++++++++++++--
 4 files changed, 182 insertions(+), 36 deletions(-)

diff --git a/src/api2/types/tape/drive.rs b/src/api2/types/tape/drive.rs
index 058e544f..2a0857fd 100644
--- a/src/api2/types/tape/drive.rs
+++ b/src/api2/types/tape/drive.rs
@@ -176,13 +176,15 @@ impl TryFrom<u8> for TapeDensity {
 pub struct LtoDriveAndMediaStatus {
     /// Block size (0 is variable size)
     pub blocksize: u32,
+    /// Compression enabled
+    pub compression: bool,
+    /// Drive buffer mode
+    pub buffer_mode: u8,
     /// Tape density
+    pub density: TapeDensity,
+    /// Media is write protected
     #[serde(skip_serializing_if="Option::is_none")]
-    pub density: Option<TapeDensity>,
-    /// Status flags
-    pub status: String,
-    /// Lto Driver Options
-    pub options: String,
+    pub write_protect: Option<bool>,
     /// Tape Alert Flags
     #[serde(skip_serializing_if="Option::is_none")]
     pub alert_flags: Option<String>,
diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs
index f1de6236..1d23b71c 100644
--- a/src/bin/proxmox-tape.rs
+++ b/src/bin/proxmox-tape.rs
@@ -741,8 +741,9 @@ async fn status(mut param: Value) -> Result<(), Error> {
     let options = default_table_format_options()
         .column(ColumnConfig::new("blocksize"))
         .column(ColumnConfig::new("density"))
-        .column(ColumnConfig::new("status"))
-        .column(ColumnConfig::new("options"))
+        .column(ColumnConfig::new("compression"))
+        .column(ColumnConfig::new("buffer-mode"))
+        .column(ColumnConfig::new("write-protect"))
         .column(ColumnConfig::new("alert-flags"))
         .column(ColumnConfig::new("file-number"))
         .column(ColumnConfig::new("block-number"))
diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
index 7fcef8a0..c9f3bf93 100644
--- a/src/tape/drive/lto/mod.rs
+++ b/src/tape/drive/lto/mod.rs
@@ -38,6 +38,7 @@ use crate::{
         MamAttribute,
         LtoDriveAndMediaStatus,
         LtoTapeDrive,
+        TapeDensity,
     },
     tape::{
         TapeRead,
@@ -82,8 +83,7 @@ impl LtoTapeDrive {
 
             handle.sg_tape.wait_until_ready()?;
 
-            // Only root can set driver options, so we cannot
-            // handle.set_default_options()?;
+            handle.set_default_options()?;
 
             Ok(handle)
         }).map_err(|err: Error| format_err!("open drive '{}' ({}) failed - {}", self.name, self.path, err))
@@ -104,8 +104,14 @@ impl LtoTapeHandle {
     }
 
     /// Set all options we need/want
-    pub fn set_default_options(&self) -> Result<(), Error> {
-        // fixme
+    pub fn set_default_options(&mut self) -> Result<(), Error> {
+
+        let compression = Some(true);
+        let block_length = Some(0); // variable length mode
+        let buffer_mode = Some(true); // Always use drive buffer
+
+        self.sg_tape.set_drive_options(compression, block_length, buffer_mode)?;
+
         Ok(())
     }
 
@@ -117,28 +123,21 @@ impl LtoTapeHandle {
     /// Get Tape and Media status
     pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error>  {
 
-        let (file_number, block_number) = match self.sg_tape.position() {
-            Ok(position) => (
-                Some(position.logical_file_id),
-                Some(position.logical_object_number),
-            ),
-            Err(_) => (None, None),
-        };
-
-        let options = String::from("FIXME");
+        let drive_status = self.sg_tape.read_drive_status()?;
 
         let alert_flags = self.tape_alert_flags()
             .map(|flags| format!("{:?}", flags))
             .ok();
 
         let mut status = LtoDriveAndMediaStatus {
-            blocksize: 0, // fixme: remove
-            density: None, // fixme
-            status: String::from("FIXME"),
-            options,
+            blocksize: drive_status.block_length,
+            compression: drive_status.compression,
+            buffer_mode: drive_status.buffer_mode,
+            density: TapeDensity::try_from(drive_status.density_code)?,
             alert_flags,
-            file_number,
-            block_number,
+            write_protect: None,
+            file_number: None,
+            block_number: None,
             manufactured: None,
             bytes_read: None,
             bytes_written: None,
@@ -147,7 +146,16 @@ impl LtoTapeHandle {
             volume_mounts: None,
         };
 
-        if self.sg_tape.test_unit_ready()? {
+        if self.sg_tape.test_unit_ready().is_ok() {
+
+            if drive_status.write_protect {
+                status.write_protect = Some(drive_status.write_protect);
+            }
+
+            let position = self.sg_tape.position()?;
+
+            status.file_number = Some(position.logical_file_id);
+            status.block_number = Some(position.logical_object_number);
 
             if let Ok(mam) = self.cartridge_memory() {
 
diff --git a/src/tape/drive/lto/sg_tape.rs b/src/tape/drive/lto/sg_tape.rs
index 9af0eae3..f3d71edd 100644
--- a/src/tape/drive/lto/sg_tape.rs
+++ b/src/tape/drive/lto/sg_tape.rs
@@ -10,7 +10,7 @@ use nix::fcntl::{fcntl, FcntlArg, OFlag};
 
 use proxmox::{
     sys::error::SysResult,
-    tools::io::ReadExt,
+    tools::io::{ReadExt, WriteExt},
 };
 
 use crate::{
@@ -39,7 +39,11 @@ use crate::{
         SenseInfo,
         ScsiError,
         InquiryInfo,
+        ModeParameterHeader,
+        ModeBlockDescriptor,
+        alloc_page_aligned_buffer,
         scsi_inquiry,
+        scsi_mode_sense,
     },
 };
 
@@ -54,6 +58,42 @@ pub struct ReadPositionLongPage {
     obsolete: [u8;8],
 }
 
+#[repr(C, packed)]
+#[derive(Endian, Debug, Copy, Clone)]
+struct DataCompressionModePage {
+    page_code: u8,   // 0x0f
+    page_length: u8,  // 0x0e
+    flags2: u8,
+    flags3: u8,
+    compression_algorithm: u32,
+    decompression_algorithm: u32,
+    reserved: [u8;4],
+}
+
+impl DataCompressionModePage {
+
+    pub fn set_compression(&mut self, enable: bool) {
+        if enable {
+            self.flags2 |= 128;
+        } else {
+            self.flags2 = self.flags2 & 127;
+        }
+    }
+
+    pub fn compression_enabled(&self) -> bool {
+        (self.flags2 & 0b1000_0000) != 0
+    }
+}
+
+#[derive(Debug)]
+pub struct LtoTapeStatus {
+    pub block_length: u32,
+    pub density_code: u8,
+    pub buffer_mode: u8,
+    pub write_protect: bool,
+    pub compression: bool,
+}
+
 pub struct SgTape {
     file: File,
 }
@@ -378,19 +418,19 @@ impl SgTape {
         Ok(())
     }
 
-    pub fn test_unit_ready(&mut self) -> Result<bool, Error> {
+    pub fn test_unit_ready(&mut self) -> Result<(), Error> {
 
         let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
         sg_raw.set_timeout(30); // use short timeout
         let mut cmd = Vec::new();
         cmd.extend(&[0x00, 0, 0, 0, 0, 0]); // TEST UNIT READY
 
-        // fixme: check sense
-        sg_raw.do_command(&cmd)
-            .map_err(|err| format_err!("unit not ready - {}", err))?;
-
-        Ok(true)
-
+        match sg_raw.do_command(&cmd) {
+            Ok(_) => Ok(()),
+            Err(err) => {
+                bail!("test_unit_ready failed - {}", err);
+            }
+        }
     }
 
     pub fn wait_until_ready(&mut self) -> Result<(), Error> {
@@ -400,7 +440,7 @@ impl SgTape {
 
         loop {
             match self.test_unit_ready() {
-                Ok(true) => return Ok(()),
+                Ok(()) => return Ok(()),
                 _ => {
                     std::thread::sleep(std::time::Duration::new(1, 0));
                     if start.elapsed()? > max_wait {
@@ -522,6 +562,101 @@ impl SgTape {
             None => Ok(None),
         }
     }
+
+    /// Set important drive options
+    pub fn set_drive_options(
+        &mut self,
+        compression: Option<bool>,
+        block_length: Option<u32>,
+        buffer_mode: Option<bool>,
+    ) -> Result<(), Error> {
+
+        // Note: Read/Modify/Write
+
+        let (mut head, mut block_descriptor, mut page) = self.read_compression_page()?;
+
+        let mut sg_raw = SgRaw::new(&mut self.file, 0)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+
+        head.mode_data_len = 0; // need to b e zero
+
+        if let Some(compression) = compression {
+            page.set_compression(compression);
+        }
+
+        if let Some(block_length) = block_length {
+            block_descriptor.set_block_length(block_length)?;
+        }
+
+        if let Some(buffer_mode) = buffer_mode {
+            let mut mode = head.flags3 & 0b1_000_1111;
+            if buffer_mode {
+                mode |= 0b0_001_0000;
+            }
+            head.flags3 = mode;
+        }
+
+        let mut data = Vec::new();
+        unsafe {
+            data.write_be_value(head)?;
+            data.write_be_value(block_descriptor)?;
+            data.write_be_value(page)?;
+        }
+
+        let mut cmd = Vec::new();
+        cmd.push(0x55); // MODE SELECT(10)
+        cmd.push(0b0001_0000); // PF=1
+        cmd.extend(&[0,0,0,0,0]); //reserved
+
+        let param_list_len: u16 = data.len() as u16;
+        cmd.extend(&param_list_len.to_be_bytes());
+        cmd.push(0); // control
+
+        let mut buffer = alloc_page_aligned_buffer(4096)?;
+
+        buffer[..data.len()].copy_from_slice(&data[..]);
+
+        sg_raw.do_out_command(&cmd, &buffer[..data.len()])
+            .map_err(|err| format_err!("set drive options failed - {}", err))?;
+
+        Ok(())
+    }
+
+    fn read_compression_page(
+        &mut self,
+    ) -> Result<(ModeParameterHeader, ModeBlockDescriptor, DataCompressionModePage), Error> {
+
+        let (head, block_descriptor, page): (_,_, DataCompressionModePage)
+            = scsi_mode_sense(&mut self.file, false, 0x0f, 0)?;
+
+        if !(page.page_code == 0x0f && page.page_length == 0x0e) {
+            bail!("read_compression_page: got strange page code/length");
+        }
+
+        let block_descriptor = match block_descriptor {
+            Some(block_descriptor) => block_descriptor,
+            None => bail!("read_compression_page failed: missing block descriptor"),
+        };
+
+        Ok((head, block_descriptor, page))
+    }
+
+    /// Read drive options/status
+    ///
+    /// We read the drive compression page, including the
+    /// block_descriptor. This is all information we need for now.
+    pub fn read_drive_status(&mut self) -> Result<LtoTapeStatus, Error> {
+
+        let (head, block_descriptor, page) = self.read_compression_page()?;
+
+        Ok(LtoTapeStatus {
+            block_length: block_descriptor.block_length(),
+            write_protect: (head.flags3 & 0b1000_0000) != 0,
+            buffer_mode: (head.flags3 & 0b0111_0000) >> 4,
+            compression: page.compression_enabled(),
+            density_code: block_descriptor.density_code,
+        })
+    }
 }
 
 pub struct SgTapeReader<'a> {
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 10/11] tape: pmt - re-implement fsr/bsr
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
                   ` (8 preceding siblings ...)
  2021-04-07 10:23 ` [pbs-devel] [PATCH 09/11] tape: correctly set/display drive option Dietmar Maurer
@ 2021-04-07 10:23 ` Dietmar Maurer
  2021-04-07 10:23 ` [pbs-devel] [PATCH 11/11] tape: pmt - re-implement lock/unlock command Dietmar Maurer
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:23 UTC (permalink / raw)
  To: pbs-devel

---
 src/bin/pmt.rs            | 11 ++++-------
 src/tape/drive/lto/mod.rs |  8 ++++++++
 2 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/src/bin/pmt.rs b/src/bin/pmt.rs
index 854364c7..7183dfe5 100644
--- a/src/bin/pmt.rs
+++ b/src/bin/pmt.rs
@@ -229,13 +229,11 @@ fn bsfm(count: usize, param: Value) -> Result<(), Error> {
     },
 )]
 /// Backward space records.
-fn bsr(count: i32, param: Value) -> Result<(), Error> {
+fn bsr(count: usize, param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    unimplemented!();
-
-    // fixme: handle.mtop(MTCmd::MTBSR, count, "backward space records")?;
+    handle.backward_space_count_records(count)?;
 
     Ok(())
 }
@@ -526,12 +524,11 @@ fn fsfm(count: usize, param: Value) -> Result<(), Error> {
     },
 )]
 /// Forward space records.
-fn fsr(count: i32, param: Value) -> Result<(), Error> {
+fn fsr(count: usize, param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    unimplemented!();
-    // fixme: handle.mtop(MTCmd::MTFSR, count, "forward space records")?;
+    handle.forward_space_count_records(count)?;
 
     Ok(())
 }
diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
index c9f3bf93..f33049ab 100644
--- a/src/tape/drive/lto/mod.rs
+++ b/src/tape/drive/lto/mod.rs
@@ -195,6 +195,14 @@ impl LtoTapeHandle {
         self.sg_tape.space_filemarks(-isize::try_from(count)?)
     }
 
+    pub fn forward_space_count_records(&mut self, count: usize) -> Result<(), Error> {
+        self.sg_tape.space_blocks(isize::try_from(count)?)
+    }
+
+    pub fn backward_space_count_records(&mut self, count: usize) -> Result<(), Error> {
+        self.sg_tape.space_blocks(-isize::try_from(count)?)
+    }
+
     pub fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
         self.sg_tape.erase_media(fast)
     }
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* [pbs-devel] [PATCH 11/11] tape: pmt - re-implement lock/unlock command
  2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
                   ` (9 preceding siblings ...)
  2021-04-07 10:23 ` [pbs-devel] [PATCH 10/11] tape: pmt - re-implement fsr/bsr Dietmar Maurer
@ 2021-04-07 10:23 ` Dietmar Maurer
  10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 10:23 UTC (permalink / raw)
  To: pbs-devel

---
 src/bin/pmt.rs                |  6 ++----
 src/tape/drive/lto/mod.rs     | 12 ++++++++++++
 src/tape/drive/lto/sg_tape.rs | 19 +++++++++++++++++++
 3 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/src/bin/pmt.rs b/src/bin/pmt.rs
index 7183dfe5..15fa591d 100644
--- a/src/bin/pmt.rs
+++ b/src/bin/pmt.rs
@@ -577,8 +577,7 @@ fn lock(param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    unimplemented!();
-    // fixme: handle.mtop(MTCmd::MTLOCK, 1, "lock tape drive door")?;
+    handle.lock()?;
 
     Ok(())
 }
@@ -715,8 +714,7 @@ fn unlock(param: Value) -> Result<(), Error> {
 
     let mut handle = get_tape_handle(&param)?;
 
-    unimplemented!();
-    //handle.mtop(MTCmd::MTUNLOCK, 1, "unlock tape drive door")?;
+    handle.unlock()?;
 
     Ok(())
 }
diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
index f33049ab..de997cde 100644
--- a/src/tape/drive/lto/mod.rs
+++ b/src/tape/drive/lto/mod.rs
@@ -220,6 +220,18 @@ impl LtoTapeHandle {
     pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> {
         self.sg_tape.volume_statistics()
     }
+
+    /// Lock the drive door
+    pub fn lock(&mut self) -> Result<(), Error>  {
+        self.sg_tape.set_medium_removal(false)
+            .map_err(|err| format_err!("lock door failed - {}", err))
+    }
+
+    /// Unlock the drive door
+    pub fn unlock(&mut self) -> Result<(), Error>  {
+        self.sg_tape.set_medium_removal(true)
+            .map_err(|err| format_err!("unlock door failed - {}", err))
+    }
 }
 
 
diff --git a/src/tape/drive/lto/sg_tape.rs b/src/tape/drive/lto/sg_tape.rs
index f3d71edd..638ba330 100644
--- a/src/tape/drive/lto/sg_tape.rs
+++ b/src/tape/drive/lto/sg_tape.rs
@@ -189,6 +189,25 @@ impl SgTape {
         Ok(())
     }
 
+    /// Lock/Unlock drive door
+    pub fn set_medium_removal(&mut self, allow: bool) -> Result<(), ScsiError> {
+
+        let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
+        sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
+        let mut cmd = Vec::new();
+        cmd.extend(&[0x1E, 0, 0, 0]);
+        if allow {
+            cmd.push(0);
+        } else {
+            cmd.push(1);
+        }
+        cmd.push(0); // control
+
+        sg_raw.do_command(&cmd)?;
+
+        Ok(())
+    }
+
     pub fn rewind(&mut self) -> Result<(), Error> {
 
         let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
-- 
2.20.1




^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [pbs-devel] [PATCH 00/11] Userspace tape driver
@ 2021-04-07 11:26 Dietmar Maurer
  0 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-04-07 11:26 UTC (permalink / raw)
  To: pbs-devel

I forgot to mention that this series include new udev rules. You need
to activate them using:

# udevadm control --reload-rules && udevadm trigger

(or reboot)

You also need to adopt your current drive configuration to use the new
devices ...


> On 04/07/2021 12:22 PM Dietmar Maurer <dietmar@proxmox.com> wrote:
> 
>  
> This is a userspace drive implementation using SG_IO.




^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2021-04-07 11:27 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
2021-04-07 10:22 ` [pbs-devel] [PATCH 01/11] tape: introduce trait BlockRead Dietmar Maurer
2021-04-07 10:22 ` [pbs-devel] [PATCH 02/11] tape: introduce trait BlockWrite Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 03/11] tape: implement LTO userspace driver Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 04/11] tape: implement format/erase Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 05/11] tape: fix LEOM handling Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 06/11] tape: make fsf/bsf driver specific Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 07/11] tape: make sure there is a filemark at the end of the tape Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 08/11] sgutils2: add scsi_mode_sense helper Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 09/11] tape: correctly set/display drive option Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 10/11] tape: pmt - re-implement fsr/bsr Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 11/11] tape: pmt - re-implement lock/unlock command Dietmar Maurer
2021-04-07 11:26 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal