all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Dietmar Maurer <dietmar@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox-backup v2] tape: implement 6 byte fallback for MODE SENSE/SELECT
Date: Thu, 20 Apr 2023 14:13:30 +0200	[thread overview]
Message-ID: <20230420121330.89218-1-dietmar@proxmox.com> (raw)

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





             reply	other threads:[~2023-04-20 12:13 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-04-20 12:13 Dietmar Maurer [this message]
2023-04-21 11:01 Dominik Csapak

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230420121330.89218-1-dietmar@proxmox.com \
    --to=dietmar@proxmox.com \
    --cc=pbs-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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