* [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 a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox