[pbs-devel] [PATCH proxmox-backup v2] tape: implement 6 byte fallback for MODE SENSE/SELECT
Dominik Csapak
d.csapak at proxmox.com
Fri Apr 21 13:01:44 CEST 2023
From: Dietmar Maurer <dietmar at proxmox.com>
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 <d.csapak at proxmox.com>
Signed-off-by: Dietmar Maurer <dietmar at proxmox.com>
---
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<F: AsRawFd>(file: &mut F) -> Result<InquiryInfo, Error> {
.map_err(|err: Error| format_err!("decode inquiry page failed - {}", err))
}
-/// Run SCSI Mode Sense
+fn decode_mode_sense10_result<P: Endian>(
+ data: &[u8],
+ disable_block_descriptor: bool,
+) -> Result<(ModeParameterHeader10, Option<ModeBlockDescriptor>, 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::<ModeParameterHeader10>();
+ 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<ModeBlockDescriptor> = 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<P: Endian>(
+ data: &[u8],
+ disable_block_descriptor: bool,
+) -> Result<(ModeParameterHeader6, Option<ModeBlockDescriptor>, 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::<ModeParameterHeader10>();
+ 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<ModeBlockDescriptor> = 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<u8> {
+ 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<u8> {
+ 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<u8> {
+ 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<F: AsRawFd, P: Endian>(
@@ -679,69 +907,74 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
page_code: u8,
sub_page_code: u8,
) -> Result<(ModeParameterHeader, Option<ModeBlockDescriptor>, 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<F: AsRawFd, P: Endian>(
+ file: &mut F,
+ disable_block_descriptor: bool,
+ page_code: u8,
+ sub_page_code: u8,
+) -> Result<(ModeParameterHeader10, Option<ModeBlockDescriptor>, 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::<ModeParameterHeader>();
- 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<ModeBlockDescriptor> = 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<F: AsRawFd, P: Endian>(
+ file: &mut F,
+ disable_block_descriptor: bool,
+ page_code: u8,
+ sub_page_code: u8,
+) -> Result<(ModeParameterHeader6, Option<ModeBlockDescriptor>, 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
More information about the pbs-devel
mailing list