[pbs-devel] [PATCH proxmox-backup 3/4] tape: implement 6 byte fallback for MODE SENSE/SELECT
Dominik Csapak
d.csapak at proxmox.com
Thu Mar 30 13:28:44 CEST 2023
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>
---
pbs-tape/src/sg_tape.rs | 44 ++++++++-
pbs-tape/src/sgutils2.rs | 197 ++++++++++++++++++++++++++++++++-------
2 files changed, 202 insertions(+), 39 deletions(-)
diff --git a/pbs-tape/src/sg_tape.rs b/pbs-tape/src/sg_tape.rs
index 6a5569ac..97d90201 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,
+ sense_err_is_invalid_command, InquiryInfo, ModeBlockDescriptor, ModeParameterHeader,
+ ModeParameterHeader10, ModeParameterHeader6, ScsiError, SenseInfo, SgRaw,
},
BlockRead, BlockReadError, BlockWrite, BlockedReader, BlockedWriter,
};
@@ -764,6 +765,7 @@ impl SgTape {
let mut data = Vec::new();
unsafe {
+ let head: ModeParameterHeader10 = head.clone().into();
data.write_be_value(head)?;
data.write_be_value(block_descriptor)?;
data.write_be_value(page)?;
@@ -782,9 +784,41 @@ impl SgTape {
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))?;
+ match sg_raw.do_out_command(&cmd, &buffer[..data.len()]) {
+ Ok(()) => {}
+ Err(ScsiError::Sense(err)) if sense_err_is_invalid_command(&err) => {
+ let mut data = Vec::new();
+ unsafe {
+ let head: ModeParameterHeader6 = head.try_into()?;
+ data.write_be_value(head)?;
+ data.write_be_value(block_descriptor)?;
+ data.write_be_value(page)?;
+ }
+
+ let mut cmd = Vec::new();
+ cmd.push(0x15); // MODE SELECT(6)
+ cmd.push(0b0001_0000); // PF=1
+ cmd.extend([0, 0]); //reserved
+
+ if data.len() > u8::MAX as usize {
+ bail!("set drive options (mode select(6)) failed - parameters too long")
+ }
+
+ cmd.push(data.len() as u8);
+ 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 (mode select(6)) failed - {err}")
+ })?;
+ }
+ Err(err) => bail!("set drive options (mode select(10)) failed - {err}"),
+ }
Ok(())
}
diff --git a/pbs-tape/src/sgutils2.rs b/pbs-tape/src/sgutils2.rs
index dd1aa1b4..91444cec 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
@@ -232,27 +232,106 @@ pub struct ModeParameterHeader {
pub medium_type: u8,
pub flags3: u8,
reserved4: [u8; 2],
- pub block_descriptior_len: u16,
+ pub block_descriptor_len: u16,
+}
+
+// header for the short variant of MODE SENSE/SELECT
+#[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_descriptor_len: u8,
+}
+
+#[derive(Clone)]
+pub struct ModeParameterHeader {
+ 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
+ // not compatible with IBM.
+ pub medium_type: u8,
+ pub flags: u8,
+ pub block_descriptor_len: u16,
+}
+
+impl TryFrom<ModeParameterHeader> for ModeParameterHeader6 {
+ type Error = Error;
+
+ fn try_from(value: ModeParameterHeader) -> Result<Self, Self::Error> {
+ if value.mode_data_len > u8::MAX as u16 {
+ bail!("mode_data_len too big for 6 byte mode parameter header")
+ }
+
+ if value.block_descriptor_len > u8::MAX as u16 {
+ bail!("block_descriptor_len too big for 6 byte mode parameter header")
+ }
+
+ Ok(Self {
+ mode_data_len: value.mode_data_len as u8,
+ medium_type: value.medium_type,
+ flags2: value.flags,
+ block_descriptor_len: value.block_descriptor_len as u8,
+ })
+ }
+}
+
+impl From<ModeParameterHeader> for ModeParameterHeader10 {
+ fn from(value: ModeParameterHeader) -> Self {
+ Self {
+ mode_data_len: value.mode_data_len,
+ medium_type: value.medium_type,
+ flags3: value.flags,
+ block_descriptor_len: value.block_descriptor_len,
+ reserved4: [0, 0],
+ }
+ }
+}
+
+impl From<ModeParameterHeader10> for ModeParameterHeader {
+ fn from(val: ModeParameterHeader10) -> Self {
+ Self {
+ mode_data_len: val.mode_data_len,
+ medium_type: val.medium_type,
+ flags: val.flags3,
+ block_descriptor_len: val.block_descriptor_len,
+ }
+ }
+}
+
+impl From<ModeParameterHeader6> for ModeParameterHeader {
+ fn from(val: ModeParameterHeader6) -> Self {
+ Self {
+ mode_data_len: val.mode_data_len as u16,
+ medium_type: val.medium_type,
+ flags: val.flags2,
+ block_descriptor_len: val.block_descriptor_len as u16,
+ }
+ }
}
impl ModeParameterHeader {
#[allow(clippy::unusual_byte_groupings)]
pub fn buffer_mode(&self) -> u8 {
- (self.flags3 & 0b0_111_0000) >> 4
+ (self.flags & 0b0_111_0000) >> 4
}
#[allow(clippy::unusual_byte_groupings)]
pub fn set_buffer_mode(&mut self, buffer_mode: bool) {
- let mut mode = self.flags3 & 0b1_000_1111;
+ let mut mode = self.flags & 0b1_000_1111;
if buffer_mode {
mode |= 0b0_001_0000;
}
- self.flags3 = mode;
+ self.flags = mode;
}
#[allow(clippy::unusual_byte_groupings)]
pub fn write_protect(&self) -> bool {
- (self.flags3 & 0b1_000_0000) != 0
+ (self.flags & 0b1_000_0000) != 0
}
}
@@ -670,6 +749,13 @@ pub fn scsi_inquiry<F: AsRawFd>(file: &mut F) -> Result<InquiryInfo, Error> {
.map_err(|err: Error| format_err!("decode inquiry page failed - {}", err))
}
+/// 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
///
/// Warning: P needs to be repr(C, packed)]
@@ -695,43 +781,86 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
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_lang::try_block!({
- let mut reader = data;
+ let (head, mut reader): (ModeParameterHeader, &[u8]) = match sg_raw.do_command(&cmd) {
+ Ok(data) => {
+ 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];
+ }
+ _ => (),
+ }
- 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];
+ (head.into(), reader)
+ }
+ Err(ScsiError::Sense(err)) if sense_err_is_invalid_command(&err) => {
+ // fall back to small mode sense
+ let mut cmd = vec![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
+ let data = sg_raw
+ .do_command(&cmd)
+ .map_err(|err| format_err!("mode sense(6) failed - {err}"))?;
+
+ 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::<ModeParameterHeader6>();
+ reader = &data[header_size..expected_len];
+ }
+ _ => (),
}
- _ => (),
+
+ (head.into(), reader)
}
+ Err(err) => bail!("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);
+ proxmox_lang::try_block!({
+ if disable_block_descriptor && head.block_descriptor_len != 0 {
+ let len = head.block_descriptor_len;
+ bail!("wrong block_descriptor_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);
+ if head.block_descriptor_len != 8 {
+ let len = head.block_descriptor_len;
+ bail!("wrong block_descriptor_len: {}, expected 8", len);
}
block_descriptor = Some(unsafe { reader.read_be_value()? });
--
2.30.2
More information about the pbs-devel
mailing list