5
0
mirror of git://git.proxmox.com/git/proxmox-backup.git synced 2025-03-07 00:58:32 +03:00

tape: implement 6 byte fallback for MODE SENSE/SELECT

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 commit is contained in:
Dietmar Maurer 2023-04-21 13:01:44 +02:00
parent e2f3f2d7da
commit 5d9e5fb475
2 changed files with 333 additions and 80 deletions

View File

@ -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,30 +763,49 @@ 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 param_list_len: u16 = data.len() as u16;
let cmd = scsi_cmd_mode_select10(param_list_len);
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(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)?;
}
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}"))?;
}
}
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(param_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(())
}

View File

@ -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))?;
.map_err(|err| format_err!("mode sense(10) failed - {}", err))?;
proxmox_lang::try_block!({
let mut reader = data;
decode_mode_sense10_result(data, disable_block_descriptor)
.map_err(|err| format_err!("decode mode sense failed - {}", err))
}
let head: ModeParameterHeader = unsafe { reader.read_be_value()? };
let expected_len = head.mode_data_len as usize + 2;
/// 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)?;
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];
}
_ => (),
}
let cmd = scsi_cmd_mode_sense(false, page_code, sub_page_code, disable_block_descriptor);
if disable_block_descriptor && head.block_descriptior_len != 0 {
let len = head.block_descriptior_len;
bail!("wrong block_descriptior_len: {}, expected 0", len);
}
let data = sg_raw
.do_command(&cmd)
.map_err(|err| format_err!("mode sense(6) failed - {}", err))?;
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))
})
.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