From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id BF361711D5 for ; Wed, 7 Apr 2021 12:24:14 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id BBD78F3E4 for ; Wed, 7 Apr 2021 12:24:14 +0200 (CEST) Received: from elsa.proxmox.com (unknown [94.136.29.99]) by firstgate.proxmox.com (Proxmox) with ESMTP id 50F77F352 for ; Wed, 7 Apr 2021 12:24:07 +0200 (CEST) Received: by elsa.proxmox.com (Postfix, from userid 0) id 1C080AEAF2A; Wed, 7 Apr 2021 12:24:07 +0200 (CEST) From: Dietmar Maurer To: pbs-devel@lists.proxmox.com Date: Wed, 7 Apr 2021 12:23:06 +0200 Message-Id: <20210407102308.9750-10-dietmar@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210407102308.9750-1-dietmar@proxmox.com> References: <20210407102308.9750-1-dietmar@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 1 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RDNS_NONE 1.274 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [drive.rs, mod.rs, proxmox-tape.rs] Subject: [pbs-devel] [PATCH 09/11] tape: correctly set/display drive option X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 07 Apr 2021 10:24:14 -0000 --- 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 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, - /// Status flags - pub status: String, - /// Lto Driver Options - pub options: String, + pub write_protect: Option, /// Tape Alert Flags #[serde(skip_serializing_if="Option::is_none")] pub alert_flags: Option, 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 { - 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 { + 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, + block_length: Option, + buffer_mode: Option, + ) -> 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 { + + 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