* [pbs-devel] [PATCH proxmox-backup v2] tape: implement 6 byte fallback for MODE SENSE/SELECT
@ 2023-04-20 12:13 Dietmar Maurer
0 siblings, 0 replies; 2+ messages in thread
From: Dietmar Maurer @ 2023-04-20 12:13 UTC (permalink / raw)
To: pbs-devel
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@proxmox.com>
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
This is a rewrite with same functionality, but IMHO simpler code.
pbs-tape/src/sg_tape.rs | 62 ++++---
pbs-tape/src/sgutils2.rs | 339 ++++++++++++++++++++++++++++++++-------
2 files changed, 325 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..b9f24ca1 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,65 @@ 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 +741,157 @@ 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 +900,77 @@ pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
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 sg_raw = SgRaw::new(file, 4096)?;
- 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)
+ 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(true, 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)),
}
- 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
+/// 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 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
^ permalink raw reply [flat|nested] 2+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v2] tape: implement 6 byte fallback for MODE SENSE/SELECT
@ 2023-04-21 11:01 Dominik Csapak
0 siblings, 0 replies; 2+ messages in thread
From: Dominik Csapak @ 2023-04-21 11:01 UTC (permalink / raw)
To: pbs-devel
From: Dietmar Maurer <dietmar@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@proxmox.com>
Signed-off-by: Dietmar Maurer <dietmar@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
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2023-04-21 11:01 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-04-20 12:13 [pbs-devel] [PATCH proxmox-backup v2] tape: implement 6 byte fallback for MODE SENSE/SELECT Dietmar Maurer
2023-04-21 11:01 Dominik Csapak
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal