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) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id CF4FD9819B for ; Fri, 21 Apr 2023 13:01:48 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B7EDE1A7C2 for ; Fri, 21 Apr 2023 13:01:48 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Fri, 21 Apr 2023 13:01:47 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 0017D41802 for ; Fri, 21 Apr 2023 13:01:46 +0200 (CEST) From: Dominik Csapak To: pbs-devel@lists.proxmox.com Date: Fri, 21 Apr 2023 13:01:44 +0200 Message-Id: <20230421110144.2641478-1-d.csapak@proxmox.com> X-Mailer: git-send-email 2.30.2 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.135 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment POISEN_SPAM_PILL 0.1 Meta: its spam POISEN_SPAM_PILL_2 0.1 random spam to be learned in bayes POISEN_SPAM_PILL_4 0.1 random spam to be learned in bayes SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pbs-devel] [PATCH proxmox-backup v2] tape: implement 6 byte fallback for MODE SENSE/SELECT 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: Fri, 21 Apr 2023 11:01:48 -0000 From: Dietmar Maurer there are tape drives (esp. virtual ones) that don't implement the 10-byte variants of MODE SENSE/SELECT. Since the pages we set/request are never bigger than 255 bytes anyway, we can implement a fallback with the 6 byte variant here. Implementing this as a fallback to make sure that existing working drives keep the existing implementation. Tested with Starwind VTL. Signed-off-by: Dominik Csapak Signed-off-by: Dietmar Maurer --- changes from v1: * fixes the second call to scsi_cmd_mode_sense with the correct long parameter (was true, but needs to be false to be actual the mode sense(6)) * clippy fixes + rustfmt patch looks good from my side tested again with the starwind and quadstore vtl, i'll test again next week on real hardware pbs-tape/src/sg_tape.rs | 62 ++++--- pbs-tape/src/sgutils2.rs | 343 ++++++++++++++++++++++++++++++++------- 2 files changed, 329 insertions(+), 76 deletions(-) diff --git a/pbs-tape/src/sg_tape.rs b/pbs-tape/src/sg_tape.rs index 6a5569ac..e4198491 100644 --- a/pbs-tape/src/sg_tape.rs +++ b/pbs-tape/src/sg_tape.rs @@ -30,8 +30,9 @@ use pbs_api_types::{Lp17VolumeStatistics, LtoDriveAndMediaStatus, MamAttribute}; use crate::{ sgutils2::{ - alloc_page_aligned_buffer, scsi_inquiry, scsi_mode_sense, scsi_request_sense, InquiryInfo, - ModeBlockDescriptor, ModeParameterHeader, ScsiError, SenseInfo, SgRaw, + alloc_page_aligned_buffer, scsi_inquiry, scsi_mode_sense, scsi_request_sense, + scsi_cmd_mode_select6, scsi_cmd_mode_select10, + InquiryInfo, ModeBlockDescriptor, ModeParameterHeader, ScsiError, SenseInfo, SgRaw, }, BlockRead, BlockReadError, BlockWrite, BlockedReader, BlockedWriter, }; @@ -748,7 +749,7 @@ impl SgTape { 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 + head.reset_mode_data_len(); // mode_data_len need to be zero if let Some(compression) = compression { page.set_compression(compression); @@ -762,29 +763,48 @@ impl SgTape { head.set_buffer_mode(buffer_mode); } - let mut data = Vec::new(); - unsafe { - data.write_be_value(head)?; - data.write_be_value(block_descriptor)?; - data.write_be_value(page)?; - } + match head { + ModeParameterHeader::Long(head) => { + 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; + let cmd = scsi_cmd_mode_select10(param_list_len); - let param_list_len: u16 = data.len() as u16; - cmd.extend(param_list_len.to_be_bytes()); - cmd.push(0); // control + let mut buffer = alloc_page_aligned_buffer(4096)?; - let mut buffer = alloc_page_aligned_buffer(4096)?; + buffer[..data.len()].copy_from_slice(&data[..]); - buffer[..data.len()].copy_from_slice(&data[..]); + sg_raw + .do_out_command(&cmd, &buffer[..data.len()]) + .map_err(|err| format_err!("set drive options (mode select(10)) failed - {}", err))?; + } + ModeParameterHeader::Short(head) => { + let mut data = Vec::new(); + unsafe { + data.write_be_value(head)?; + data.write_be_value(block_descriptor)?; + data.write_be_value(page)?; + } - sg_raw - .do_out_command(&cmd, &buffer[..data.len()]) - .map_err(|err| format_err!("set drive options failed - {}", err))?; + if data.len() > u8::MAX as usize { + bail!("set drive options (mode select(6)) failed - parameters too long") + } + let cmd = scsi_cmd_mode_select6(data.len() as u8); + + 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 (mode select(6)) failed - {err}"))?; + } + } Ok(()) } diff --git a/pbs-tape/src/sgutils2.rs b/pbs-tape/src/sgutils2.rs index dd1aa1b4..30f287b0 100644 --- a/pbs-tape/src/sgutils2.rs +++ b/pbs-tape/src/sgutils2.rs @@ -224,7 +224,7 @@ pub struct InquiryInfo { #[repr(C, packed)] #[derive(Endian, Debug, Copy, Clone)] -pub struct ModeParameterHeader { +pub struct ModeParameterHeader10 { pub mode_data_len: u16, // Note: medium_type and density_code are not the same. On HP // drives, medium_type provides very limited information and is @@ -235,7 +235,19 @@ pub struct ModeParameterHeader { pub block_descriptior_len: u16, } -impl ModeParameterHeader { +#[repr(C, packed)] +#[derive(Endian, Debug, Copy, Clone)] +pub struct ModeParameterHeader6 { + pub mode_data_len: u8, + // Note: medium_type and density_code are not the same. On HP + // drives, medium_type provides very limited information and is + // not compatible with IBM. + pub medium_type: u8, + pub flags2: u8, + pub block_descriptior_len: u8, +} + +impl ModeParameterHeader10 { #[allow(clippy::unusual_byte_groupings)] pub fn buffer_mode(&self) -> u8 { (self.flags3 & 0b0_111_0000) >> 4 @@ -256,6 +268,63 @@ impl ModeParameterHeader { } } +impl ModeParameterHeader6 { + #[allow(clippy::unusual_byte_groupings)] + pub fn buffer_mode(&self) -> u8 { + (self.flags2 & 0b0_111_0000) >> 4 + } + + #[allow(clippy::unusual_byte_groupings)] + pub fn set_buffer_mode(&mut self, buffer_mode: bool) { + let mut mode = self.flags2 & 0b1_000_1111; + if buffer_mode { + mode |= 0b0_001_0000; + } + self.flags2 = mode; + } + + #[allow(clippy::unusual_byte_groupings)] + pub fn write_protect(&self) -> bool { + (self.flags2 & 0b1_000_0000) != 0 + } +} + +#[derive(Debug, Copy, Clone)] +pub enum ModeParameterHeader { + Long(ModeParameterHeader10), + Short(ModeParameterHeader6), +} + +impl ModeParameterHeader { + pub fn buffer_mode(&self) -> u8 { + match self { + ModeParameterHeader::Long(mode) => mode.buffer_mode(), + ModeParameterHeader::Short(mode) => mode.buffer_mode(), + } + } + + pub fn set_buffer_mode(&mut self, buffer_mode: bool) { + match self { + ModeParameterHeader::Long(mode) => mode.set_buffer_mode(buffer_mode), + ModeParameterHeader::Short(mode) => mode.set_buffer_mode(buffer_mode), + } + } + + pub fn write_protect(&self) -> bool { + match self { + ModeParameterHeader::Long(mode) => mode.write_protect(), + ModeParameterHeader::Short(mode) => mode.write_protect(), + } + } + + pub fn reset_mode_data_len(&mut self) { + match self { + ModeParameterHeader::Long(mode) => mode.mode_data_len = 0, + ModeParameterHeader::Short(mode) => mode.mode_data_len = 0, + } + } +} + #[repr(C, packed)] #[derive(Endian, Debug, Copy, Clone)] /// SCSI ModeBlockDescriptor for Tape devices @@ -670,7 +739,166 @@ pub fn scsi_inquiry(file: &mut F) -> Result { .map_err(|err: Error| format_err!("decode inquiry page failed - {}", err)) } -/// Run SCSI Mode Sense +fn decode_mode_sense10_result( + data: &[u8], + disable_block_descriptor: bool, +) -> Result<(ModeParameterHeader10, Option, P), Error> { + let mut reader = data; + + let head: ModeParameterHeader10 = unsafe { reader.read_be_value()? }; + let expected_len = head.mode_data_len as usize + 2; + + use std::cmp::Ordering; + match data.len().cmp(&expected_len) { + Ordering::Less => bail!( + "wrong mode_data_len: got {}, expected {}", + data.len(), + expected_len + ), + Ordering::Greater => { + // Note: Some hh7 drives returns the allocation_length + // instead of real data_len + let header_size = std::mem::size_of::(); + reader = &data[header_size..expected_len]; + } + _ => (), + } + + if disable_block_descriptor && head.block_descriptior_len != 0 { + let len = head.block_descriptior_len; + bail!("wrong block_descriptior_len: {}, expected 0", len); + } + + let mut block_descriptor: Option = None; + + if !disable_block_descriptor { + if head.block_descriptior_len != 8 { + let len = head.block_descriptior_len; + bail!("wrong block_descriptior_len: {}, expected 8", len); + } + + block_descriptor = Some(unsafe { reader.read_be_value()? }); + } + + let page: P = unsafe { reader.read_be_value()? }; + + Ok((head, block_descriptor, page)) +} + +fn decode_mode_sense6_result( + data: &[u8], + disable_block_descriptor: bool, +) -> Result<(ModeParameterHeader6, Option, P), Error> { + let mut reader = data; + + let head: ModeParameterHeader6 = unsafe { reader.read_be_value()? }; + let expected_len = head.mode_data_len as usize + 1; + + use std::cmp::Ordering; + match data.len().cmp(&expected_len) { + Ordering::Less => bail!( + "wrong mode_data_len: got {}, expected {}", + data.len(), + expected_len + ), + Ordering::Greater => { + // Note: Some hh7 drives returns the allocation_length + // instead of real data_len + let header_size = std::mem::size_of::(); + reader = &data[header_size..expected_len]; + } + _ => (), + } + + if disable_block_descriptor && head.block_descriptior_len != 0 { + let len = head.block_descriptior_len; + bail!("wrong block_descriptior_len: {}, expected 0", len); + } + + let mut block_descriptor: Option = None; + + if !disable_block_descriptor { + if head.block_descriptior_len != 8 { + let len = head.block_descriptior_len; + bail!("wrong block_descriptior_len: {}, expected 8", len); + } + + block_descriptor = Some(unsafe { reader.read_be_value()? }); + } + + let page: P = unsafe { reader.read_be_value()? }; + + Ok((head, block_descriptor, page)) +} + +pub(crate) fn scsi_cmd_mode_select10(param_list_len: u16) -> Vec { + 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 + cmd.extend(param_list_len.to_be_bytes()); + cmd.push(0); // control + cmd +} + +pub(crate) fn scsi_cmd_mode_select6(param_list_len: u8) -> Vec { + let mut cmd = Vec::new(); + cmd.push(0x15); // MODE SELECT(6) + cmd.push(0b0001_0000); // PF=1 + cmd.extend([0, 0]); //reserved + cmd.push(param_list_len); + cmd.push(0); // control + cmd +} + +fn scsi_cmd_mode_sense( + long: bool, + page_code: u8, + sub_page_code: u8, + disable_block_descriptor: bool, +) -> Vec { + let mut cmd = Vec::new(); + if long { + let allocation_len: u16 = 4096; + + 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 + } else { + cmd.push(0x1A); // MODE SENSE(6) + + 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.push(0xff); // allocation len + cmd.push(0); // control + } + cmd +} + +/// True if the given sense info is INVALID COMMAND OPERATION CODE +/// means that the device does not know/support the command +/// https://www.t10.org/lists/asc-num.htm#ASC_20 +pub fn sense_err_is_invalid_command(err: &SenseInfo) -> bool { + err.sense_key == SENSE_KEY_ILLEGAL_REQUEST && err.asc == 0x20 && err.ascq == 0x00 +} + +/// Run SCSI Mode Sense - try Mode Sense(10) first, fallback to Mode Sense(6) /// /// Warning: P needs to be repr(C, packed)] pub fn scsi_mode_sense( @@ -679,69 +907,74 @@ pub fn scsi_mode_sense( page_code: u8, sub_page_code: u8, ) -> Result<(ModeParameterHeader, Option, P), Error> { + let mut sg_raw = SgRaw::new(file, 4096)?; + + let cmd10 = scsi_cmd_mode_sense(true, page_code, sub_page_code, disable_block_descriptor); + + match sg_raw.do_command(&cmd10) { + Ok(data10) => decode_mode_sense10_result(data10, disable_block_descriptor) + .map(|(head, block_descriptor, page)| { + (ModeParameterHeader::Long(head), block_descriptor, page) + }) + .map_err(|err| format_err!("decode mode sense(10) failed - {}", err)), + Err(ScsiError::Sense(err)) if sense_err_is_invalid_command(&err) => { + let cmd6 = + scsi_cmd_mode_sense(false, page_code, sub_page_code, disable_block_descriptor); + match sg_raw.do_command(&cmd6) { + Ok(data6) => decode_mode_sense6_result(data6, disable_block_descriptor) + .map(|(head, block_descriptor, page)| { + (ModeParameterHeader::Short(head), block_descriptor, page) + }) + .map_err(|err| format_err!("decode mode sense(6) failed - {}", err)), + Err(err) => Err(format_err!("mode sense(6) failed - {}", err)), + } + } + Err(err) => Err(format_err!("mode sense(10) failed - {}", err)), + } +} + +/// Run SCSI Mode Sense(10) +/// +/// Warning: P needs to be repr(C, packed)] +pub fn scsi_mode_sense10( + file: &mut F, + disable_block_descriptor: bool, + page_code: u8, + sub_page_code: u8, +) -> Result<(ModeParameterHeader10, Option, P), Error> { let allocation_len: u16 = 4096; let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; - let mut cmd = vec![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 cmd = scsi_cmd_mode_sense(true, page_code, sub_page_code, disable_block_descriptor); let data = sg_raw .do_command(&cmd) - .map_err(|err| format_err!("mode sense failed - {}", err))?; - - proxmox_lang::try_block!({ - let mut reader = data; - - let head: ModeParameterHeader = unsafe { reader.read_be_value()? }; - let expected_len = head.mode_data_len as usize + 2; - - use std::cmp::Ordering; - match data.len().cmp(&expected_len) { - Ordering::Less => bail!( - "wrong mode_data_len: got {}, expected {}", - data.len(), - expected_len - ), - Ordering::Greater => { - // Note: Some hh7 drives returns the allocation_length - // instead of real data_len - let header_size = std::mem::size_of::(); - reader = &data[header_size..expected_len]; - } - _ => (), - } + .map_err(|err| format_err!("mode sense(10) failed - {}", err))?; - if disable_block_descriptor && head.block_descriptior_len != 0 { - let len = head.block_descriptior_len; - bail!("wrong block_descriptior_len: {}, expected 0", len); - } - - let mut block_descriptor: Option = None; + decode_mode_sense10_result(data, disable_block_descriptor) + .map_err(|err| format_err!("decode mode sense failed - {}", err)) +} - if !disable_block_descriptor { - if head.block_descriptior_len != 8 { - let len = head.block_descriptior_len; - bail!("wrong block_descriptior_len: {}, expected 8", len); - } +/// Run SCSI Mode Sense(6) +/// +/// Warning: P needs to be repr(C, packed)] +pub fn scsi_mode_sense6( + file: &mut F, + disable_block_descriptor: bool, + page_code: u8, + sub_page_code: u8, +) -> Result<(ModeParameterHeader6, Option, P), Error> { + let allocation_len: u8 = 0xff; + let mut sg_raw = SgRaw::new(file, allocation_len as usize)?; - block_descriptor = Some(unsafe { reader.read_be_value()? }); - } + let cmd = scsi_cmd_mode_sense(false, page_code, sub_page_code, disable_block_descriptor); - let page: P = unsafe { reader.read_be_value()? }; + let data = sg_raw + .do_command(&cmd) + .map_err(|err| format_err!("mode sense(6) failed - {}", err))?; - Ok((head, block_descriptor, page)) - }) - .map_err(|err: Error| format_err!("decode mode sense failed - {}", err)) + decode_mode_sense6_result(data, disable_block_descriptor) + .map_err(|err| format_err!("decode mode sense(6) failed - {}", err)) } /// Resuqest Sense -- 2.30.2