* [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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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(<o_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(<o_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(<o_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(¶m)?; - 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(¶m)?; - 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(¶m)?; - 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(¶m)?; - 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(¶m)?; - 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(¶m)?; - 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(¶m); - 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(¶m)?; - - 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(¶m)?; - - 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(¶m)?; - - 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(¶m)?; - - 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(¶m)?; - 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(¶m)?; - 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(¶m)?; - 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(¶m)?; - - 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(¶m)?; - - 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(¶m)?; - 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] 12+ 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; 12+ 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(¶m)?; @@ -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(¶m)?; + 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(¶m); @@ -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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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(¶m)?; - 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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(¶m_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] 12+ 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; 12+ 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(¶m)?; - 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(¶m)?; - 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] 12+ 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; 12+ 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(¶m)?; - 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(¶m)?; - 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] 12+ messages in thread
end of thread, other threads:[~2021-04-07 10:24 UTC | newest] Thread overview: 12+ 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
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.Service provided by Proxmox Server Solutions GmbH | Privacy | Legal