]> git.proxmox.com Git - proxmox-backup.git/commitdiff
cleanup: move tape SCSI code to src/tape/drive/lto/sg_tape/
authorDietmar Maurer <dietmar@proxmox.com>
Fri, 9 Apr 2021 09:33:33 +0000 (11:33 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Fri, 9 Apr 2021 09:34:45 +0000 (11:34 +0200)
13 files changed:
src/api2/tape/drive.rs
src/api2/types/tape/drive.rs
src/tape/drive/encryption.rs [deleted file]
src/tape/drive/lto/mod.rs
src/tape/drive/lto/sg_tape.rs
src/tape/drive/lto/sg_tape/encryption.rs [new file with mode: 0644]
src/tape/drive/lto/sg_tape/mam.rs [new file with mode: 0644]
src/tape/drive/lto/sg_tape/tape_alert_flags.rs [new file with mode: 0644]
src/tape/drive/lto/sg_tape/volume_statistics.rs [new file with mode: 0644]
src/tape/drive/mam.rs [deleted file]
src/tape/drive/mod.rs
src/tape/drive/tape_alert_flags.rs [deleted file]
src/tape/drive/volume_statistics.rs [deleted file]

index e354f4c0599734af3c7855c02ff0aa4207ab681f..ca45bb69a2e4eb03318afe55ba2b1c00870d8023 100644 (file)
@@ -47,6 +47,7 @@ use crate::{
             LabelUuidMap,
             MamAttribute,
             LtoDriveAndMediaStatus,
+            Lp17VolumeStatistics,
         },
         tape::restore::{
             fast_catalog_restore,
@@ -71,7 +72,6 @@ use crate::{
         drive::{
             TapeDriver,
             LtoTapeHandle,
-            Lp17VolumeStatistics,
             open_lto_tape_device,
             media_changer,
             required_media_changer,
index d060acc5c261facbffcb75cfe8d3c6e7cefe6739..ecc65088dcb6bd0b2a1eb9978adcba5c9857faf3 100644 (file)
@@ -223,3 +223,62 @@ pub struct LtoDriveAndMediaStatus {
     #[serde(skip_serializing_if="Option::is_none")]
     pub medium_wearout: Option<f64>,
 }
+
+#[api()]
+/// Volume statistics from SCSI log page 17h
+#[derive(Default, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub struct Lp17VolumeStatistics {
+    /// Volume mounts (thread count)
+    pub volume_mounts: u64,
+    /// Total data sets written
+    pub volume_datasets_written: u64,
+    /// Write retries
+    pub volume_recovered_write_data_errors: u64,
+    /// Total unrecovered write errors
+    pub volume_unrecovered_write_data_errors: u64,
+    /// Total suspended writes
+    pub volume_write_servo_errors: u64,
+    /// Total fatal suspended writes
+    pub volume_unrecovered_write_servo_errors: u64,
+    /// Total datasets read
+    pub volume_datasets_read: u64,
+    /// Total read retries
+    pub volume_recovered_read_errors: u64,
+    /// Total unrecovered read errors
+    pub volume_unrecovered_read_errors: u64,
+    /// Last mount unrecovered write errors
+    pub last_mount_unrecovered_write_errors: u64,
+    /// Last mount unrecovered read errors
+    pub last_mount_unrecovered_read_errors: u64,
+    /// Last mount bytes written
+    pub last_mount_bytes_written: u64,
+    /// Last mount bytes read
+    pub last_mount_bytes_read: u64,
+    /// Lifetime bytes written
+    pub lifetime_bytes_written: u64,
+    /// Lifetime bytes read
+    pub lifetime_bytes_read: u64,
+    /// Last load write compression ratio
+    pub last_load_write_compression_ratio: u64,
+    /// Last load read compression ratio
+    pub last_load_read_compression_ratio: u64,
+    /// Medium mount time
+    pub medium_mount_time: u64,
+    /// Medium ready time
+    pub medium_ready_time: u64,
+    /// Total native capacity
+    pub total_native_capacity: u64,
+    /// Total used native capacity
+    pub total_used_native_capacity: u64,
+    /// Write protect
+    pub write_protect: bool,
+    /// Volume is WORM
+    pub worm: bool,
+    /// Beginning of medium passes
+    pub beginning_of_medium_passes: u64,
+    /// Middle of medium passes
+    pub middle_of_tape_passes: u64,
+    /// Volume serial number
+    pub serial: String,
+}
diff --git a/src/tape/drive/encryption.rs b/src/tape/drive/encryption.rs
deleted file mode 100644 (file)
index 7b687f2..0000000
+++ /dev/null
@@ -1,307 +0,0 @@
-use std::os::unix::prelude::AsRawFd;
-use std::io::Write;
-
-use anyhow::{bail, format_err, Error};
-use endian_trait::Endian;
-
-use proxmox::tools::io::{ReadExt, WriteExt};
-
-use crate::tools::sgutils2::{
-    SgRaw,
-    alloc_page_aligned_buffer,
-};
-
-/// Test if drive supports hardware encryption
-///
-/// We search for AES_CGM algorithm with 256bits key.
-pub fn has_encryption<F: AsRawFd>(
-    file: &mut F,
-) -> bool {
-
-    let data = match sg_spin_data_encryption_caps(file) {
-        Ok(data) => data,
-        Err(_) => return false,
-    };
-    decode_spin_data_encryption_caps(&data).is_ok()
-}
-
-/// Set or clear encryption key
-///
-/// We always use mixed mode,
-pub fn set_encryption<F: AsRawFd>(
-    file: &mut F,
-    key: Option<[u8; 32]>,
-) -> Result<(), Error> {
-
-    let data = match sg_spin_data_encryption_caps(file) {
-        Ok(data) => data,
-        Err(_) if key.is_none() => {
-            // Assume device does not support HW encryption
-            // We can simply ignore the clear key request
-            return Ok(());
-        }
-        Err(err) => return Err(err),
-    };
-
-    let algorithm_index = decode_spin_data_encryption_caps(&data)?;
-
-    sg_spout_set_encryption(file, algorithm_index, key)?;
-
-    let data = sg_spin_data_encryption_status(file)?;
-    let status = decode_spin_data_encryption_status(&data)?;
-
-    match status.mode {
-        DataEncryptionMode::Off => {
-            if key.is_none() {
-                return Ok(());
-            }
-        }
-        DataEncryptionMode::Mixed => {
-            if key.is_some() {
-                return Ok(());
-            }
-        }
-        _ => {}
-    }
-
-    bail!("got unexpected encryption mode {:?}", status.mode);
-}
-
-#[derive(Endian)]
-#[repr(C, packed)]
-struct SspSetDataEncryptionPage {
-    page_code: u16,
-    page_len: u16,
-    scope_byte: u8,
-    control_byte_5: u8,
-    encryption_mode: u8,
-    decryption_mode: u8,
-    algorythm_index: u8,
-    key_format: u8,
-    reserved: [u8; 8],
-    key_len: u16,
-    /* key follows */
-}
-
-fn sg_spout_set_encryption<F: AsRawFd>(
-    file: &mut F,
-    algorythm_index: u8,
-    key: Option<[u8; 32]>,
-) -> Result<(), Error> {
-
-    let mut sg_raw = SgRaw::new(file, 0)?;
-
-    let mut outbuf_len = std::mem::size_of::<SspSetDataEncryptionPage>();
-    if let Some(ref key) = key {
-        outbuf_len += key.len();
-    }
-
-    let mut outbuf = alloc_page_aligned_buffer(outbuf_len)?;
-    let chok: u8 = 0;
-
-    let page = SspSetDataEncryptionPage {
-        page_code: 0x10,
-        page_len: (outbuf_len - 4) as u16,
-        scope_byte: (0b10 << 5), // all IT nexus
-        control_byte_5: (chok << 2),
-        encryption_mode: if key.is_some() { 2 } else { 0 },
-        decryption_mode: if key.is_some() { 3 } else { 0 }, // mixed mode
-        algorythm_index,
-        key_format: 0,
-        reserved: [0u8; 8],
-        key_len: if let Some(ref key) = key { key.len() as u16 } else { 0 },
-    };
-
-    let mut writer = &mut outbuf[..];
-    unsafe { writer.write_be_value(page)? };
-
-    if let Some(ref key) = key {
-        writer.write_all(key)?;
-    }
-
-    let mut cmd = Vec::new();
-    cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT)
-    cmd.push(0x20); // Tape Data Encryption Page
-    cmd.push(0);cmd.push(0x10); // Set Data Encryption page
-    cmd.push(0);
-    cmd.push(0);
-    cmd.extend(&(outbuf_len as u32).to_be_bytes()); // data out len
-    cmd.push(0);
-    cmd.push(0);
-
-    sg_raw.do_out_command(&cmd, &outbuf)
-        .map_err(|err| format_err!("set data encryption SPOUT(20h[0010h]) failed - {}", err))
-}
-
-// Warning: this blocks and fails if there is no media loaded
-fn sg_spin_data_encryption_status<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
-
-    let allocation_len: u32 = 8192+4;
-
-    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
-
-    let mut cmd = Vec::new();
-    cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
-    cmd.push(0x20); // Tape Data Encryption Page
-    cmd.push(0);cmd.push(0x20); // Data Encryption Status page
-    cmd.push(0);
-    cmd.push(0);
-    cmd.extend(&allocation_len.to_be_bytes());
-    cmd.push(0);
-    cmd.push(0);
-
-    sg_raw.do_command(&cmd)
-        .map_err(|err| format_err!("read data encryption status SPIN(20h[0020h]) failed - {}", err))
-        .map(|v| v.to_vec())
-}
-
-// Warning: this blocks and fails if there is no media loaded
-fn sg_spin_data_encryption_caps<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
-
-    let allocation_len: u32 = 8192+4;
-
-    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
-
-    let mut cmd = Vec::new();
-    cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
-    cmd.push(0x20); // Tape Data Encryption Page
-    cmd.push(0);cmd.push(0x10); // Data Encryption Capabilities page
-    cmd.push(0);
-    cmd.push(0);
-    cmd.extend(&allocation_len.to_be_bytes());
-    cmd.push(0);
-    cmd.push(0);
-
-    sg_raw.do_command(&cmd)
-        .map_err(|err| format_err!("read data encryption caps SPIN(20h[0010h]) failed - {}", err))
-        .map(|v| v.to_vec())
-}
-
-#[derive(Debug)]
-enum DataEncryptionMode {
-    On,
-    Mixed,
-    RawRead,
-    Off,
-}
-
-#[derive(Debug)]
-struct DataEncryptionStatus {
-    mode: DataEncryptionMode,
-}
-
-#[derive(Endian)]
-#[repr(C, packed)]
-struct SspDataEncryptionCapabilityPage {
-    page_code: u16,
-    page_len: u16,
-    extdecc_cfgp_byte: u8,
-    reserved: [u8; 15],
-}
-
-#[derive(Endian)]
-#[repr(C, packed)]
-struct SspDataEncryptionAlgorithmDescriptor {
-    algorythm_index: u8,
-    reserved1: u8,
-    descriptor_len: u16,
-    control_byte_4: u8,
-    control_byte_5: u8,
-    max_ucad_bytes: u16,
-    max_acad_bytes: u16,
-    key_size: u16,
-    control_byte_12: u8,
-    reserved2: u8,
-    msdk_count: u16,
-    reserved3: [u8; 4],
-    algorithm_code: u32,
-}
-
-// Returns the algorythm_index for AES-CGM
-fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
-
-    proxmox::try_block!({
-        let mut reader = &data[..];
-        let page: SspDataEncryptionCapabilityPage = unsafe { reader.read_be_value()? };
-
-        let extdecc = (page.extdecc_cfgp_byte & 0b00001100) >> 2;
-        if extdecc != 2 {
-            bail!("not external data encryption control capable");
-        }
-
-        let cfg_p = page.extdecc_cfgp_byte & 0b00000011;
-        if cfg_p != 1 {
-            bail!("not allow to change logical block encryption parameters");
-        }
-
-        let mut aes_cgm_index = None;
-
-        loop {
-            if reader.is_empty() { break; };
-            let desc: SspDataEncryptionAlgorithmDescriptor =
-                unsafe { reader.read_be_value()? };
-            if desc.descriptor_len != 0x14 {
-                bail!("got wrong key descriptor len");
-            }
-            if (desc.control_byte_4 & 0b00000011) != 2 {
-                continue; // can't encrypt in hardware
-            }
-            if ((desc.control_byte_4 & 0b00001100) >> 2) != 2 {
-                continue; // can't decrypt in hardware
-            }
-            if desc.algorithm_code == 0x00010014 && desc.key_size == 32 {
-                aes_cgm_index = Some(desc.algorythm_index);
-                break;
-            }
-        }
-
-        match aes_cgm_index {
-            Some(index) => Ok(index),
-            None => bail!("drive dies not support AES-CGM encryption"),
-        }
-    }).map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err))
-
-}
-
-#[derive(Endian)]
-#[repr(C, packed)]
-struct SspDataEncryptionStatusPage {
-    page_code: u16,
-    page_len: u16,
-    scope_byte: u8,
-    encryption_mode: u8,
-    decryption_mode: u8,
-    algorythm_index: u8,
-    key_instance_counter: u32,
-    control_byte: u8,
-    key_format: u8,
-    key_len: u16,
-    reserved: [u8; 8],
-}
-
-fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatus, Error> {
-
-    proxmox::try_block!({
-        let mut reader = &data[..];
-        let page: SspDataEncryptionStatusPage = unsafe { reader.read_be_value()? };
-
-        if page.page_code != 0x20 {
-            bail!("invalid response");
-        }
-
-        let mode = match (page.encryption_mode, page.decryption_mode) {
-            (0, 0) => DataEncryptionMode::Off,
-            (2, 1) => DataEncryptionMode::RawRead,
-            (2, 2) => DataEncryptionMode::On,
-            (2, 3) => DataEncryptionMode::Mixed,
-            _ => bail!("unknown encryption mode"),
-        };
-
-        let status = DataEncryptionStatus {
-            mode,
-        };
-
-        Ok(status)
-
-    }).map_err(|err| format_err!("decode data encryption status page failed - {}", err))
-}
index e38b6e854ba9cf8b6771940c9833f390112d1723..9dc85ac91ff3b7eb56bbaddd3337b12da5f1af4d 100644 (file)
@@ -38,15 +38,13 @@ use crate::{
         MamAttribute,
         LtoDriveAndMediaStatus,
         LtoTapeDrive,
+        Lp17VolumeStatistics,
     },
     tape::{
         TapeRead,
         TapeWrite,
         drive::{
             TapeDriver,
-            TapeAlertFlags,
-            Lp17VolumeStatistics,
-            mam_extract_media_usage,
         },
         file_formats::{
             PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
index 5ef779229be11fa76318b32f9a026b3d396b881e..9f59b321fa74b91ffd003f10d80f56994592d767 100644 (file)
@@ -8,6 +8,18 @@ use anyhow::{bail, format_err, Error};
 use endian_trait::Endian;
 use nix::fcntl::{fcntl, FcntlArg, OFlag};
 
+mod encryption;
+pub use encryption::*;
+
+mod volume_statistics;
+pub use volume_statistics::*;
+
+mod tape_alert_flags;
+pub use tape_alert_flags::*;
+
+mod mam;
+pub use mam::*;
+
 use proxmox::{
     sys::error::SysResult,
     tools::io::{ReadExt, WriteExt},
@@ -16,6 +28,7 @@ use proxmox::{
 use crate::{
     api2::types::{
         MamAttribute,
+        Lp17VolumeStatistics,
     },
     tape::{
         BlockRead,
@@ -25,14 +38,6 @@ use crate::{
             BlockedWriter,
             BlockedReader,
         },
-        drive::{
-            TapeAlertFlags,
-            Lp17VolumeStatistics,
-            read_mam_attributes,
-            read_tape_alert_flags,
-            read_volume_statistics,
-            set_encryption,
-        },
     },
     tools::sgutils2::{
         SgRaw,
diff --git a/src/tape/drive/lto/sg_tape/encryption.rs b/src/tape/drive/lto/sg_tape/encryption.rs
new file mode 100644 (file)
index 0000000..7b687f2
--- /dev/null
@@ -0,0 +1,307 @@
+use std::os::unix::prelude::AsRawFd;
+use std::io::Write;
+
+use anyhow::{bail, format_err, Error};
+use endian_trait::Endian;
+
+use proxmox::tools::io::{ReadExt, WriteExt};
+
+use crate::tools::sgutils2::{
+    SgRaw,
+    alloc_page_aligned_buffer,
+};
+
+/// Test if drive supports hardware encryption
+///
+/// We search for AES_CGM algorithm with 256bits key.
+pub fn has_encryption<F: AsRawFd>(
+    file: &mut F,
+) -> bool {
+
+    let data = match sg_spin_data_encryption_caps(file) {
+        Ok(data) => data,
+        Err(_) => return false,
+    };
+    decode_spin_data_encryption_caps(&data).is_ok()
+}
+
+/// Set or clear encryption key
+///
+/// We always use mixed mode,
+pub fn set_encryption<F: AsRawFd>(
+    file: &mut F,
+    key: Option<[u8; 32]>,
+) -> Result<(), Error> {
+
+    let data = match sg_spin_data_encryption_caps(file) {
+        Ok(data) => data,
+        Err(_) if key.is_none() => {
+            // Assume device does not support HW encryption
+            // We can simply ignore the clear key request
+            return Ok(());
+        }
+        Err(err) => return Err(err),
+    };
+
+    let algorithm_index = decode_spin_data_encryption_caps(&data)?;
+
+    sg_spout_set_encryption(file, algorithm_index, key)?;
+
+    let data = sg_spin_data_encryption_status(file)?;
+    let status = decode_spin_data_encryption_status(&data)?;
+
+    match status.mode {
+        DataEncryptionMode::Off => {
+            if key.is_none() {
+                return Ok(());
+            }
+        }
+        DataEncryptionMode::Mixed => {
+            if key.is_some() {
+                return Ok(());
+            }
+        }
+        _ => {}
+    }
+
+    bail!("got unexpected encryption mode {:?}", status.mode);
+}
+
+#[derive(Endian)]
+#[repr(C, packed)]
+struct SspSetDataEncryptionPage {
+    page_code: u16,
+    page_len: u16,
+    scope_byte: u8,
+    control_byte_5: u8,
+    encryption_mode: u8,
+    decryption_mode: u8,
+    algorythm_index: u8,
+    key_format: u8,
+    reserved: [u8; 8],
+    key_len: u16,
+    /* key follows */
+}
+
+fn sg_spout_set_encryption<F: AsRawFd>(
+    file: &mut F,
+    algorythm_index: u8,
+    key: Option<[u8; 32]>,
+) -> Result<(), Error> {
+
+    let mut sg_raw = SgRaw::new(file, 0)?;
+
+    let mut outbuf_len = std::mem::size_of::<SspSetDataEncryptionPage>();
+    if let Some(ref key) = key {
+        outbuf_len += key.len();
+    }
+
+    let mut outbuf = alloc_page_aligned_buffer(outbuf_len)?;
+    let chok: u8 = 0;
+
+    let page = SspSetDataEncryptionPage {
+        page_code: 0x10,
+        page_len: (outbuf_len - 4) as u16,
+        scope_byte: (0b10 << 5), // all IT nexus
+        control_byte_5: (chok << 2),
+        encryption_mode: if key.is_some() { 2 } else { 0 },
+        decryption_mode: if key.is_some() { 3 } else { 0 }, // mixed mode
+        algorythm_index,
+        key_format: 0,
+        reserved: [0u8; 8],
+        key_len: if let Some(ref key) = key { key.len() as u16 } else { 0 },
+    };
+
+    let mut writer = &mut outbuf[..];
+    unsafe { writer.write_be_value(page)? };
+
+    if let Some(ref key) = key {
+        writer.write_all(key)?;
+    }
+
+    let mut cmd = Vec::new();
+    cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT)
+    cmd.push(0x20); // Tape Data Encryption Page
+    cmd.push(0);cmd.push(0x10); // Set Data Encryption page
+    cmd.push(0);
+    cmd.push(0);
+    cmd.extend(&(outbuf_len as u32).to_be_bytes()); // data out len
+    cmd.push(0);
+    cmd.push(0);
+
+    sg_raw.do_out_command(&cmd, &outbuf)
+        .map_err(|err| format_err!("set data encryption SPOUT(20h[0010h]) failed - {}", err))
+}
+
+// Warning: this blocks and fails if there is no media loaded
+fn sg_spin_data_encryption_status<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
+
+    let allocation_len: u32 = 8192+4;
+
+    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
+
+    let mut cmd = Vec::new();
+    cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
+    cmd.push(0x20); // Tape Data Encryption Page
+    cmd.push(0);cmd.push(0x20); // Data Encryption Status page
+    cmd.push(0);
+    cmd.push(0);
+    cmd.extend(&allocation_len.to_be_bytes());
+    cmd.push(0);
+    cmd.push(0);
+
+    sg_raw.do_command(&cmd)
+        .map_err(|err| format_err!("read data encryption status SPIN(20h[0020h]) failed - {}", err))
+        .map(|v| v.to_vec())
+}
+
+// Warning: this blocks and fails if there is no media loaded
+fn sg_spin_data_encryption_caps<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
+
+    let allocation_len: u32 = 8192+4;
+
+    let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
+
+    let mut cmd = Vec::new();
+    cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
+    cmd.push(0x20); // Tape Data Encryption Page
+    cmd.push(0);cmd.push(0x10); // Data Encryption Capabilities page
+    cmd.push(0);
+    cmd.push(0);
+    cmd.extend(&allocation_len.to_be_bytes());
+    cmd.push(0);
+    cmd.push(0);
+
+    sg_raw.do_command(&cmd)
+        .map_err(|err| format_err!("read data encryption caps SPIN(20h[0010h]) failed - {}", err))
+        .map(|v| v.to_vec())
+}
+
+#[derive(Debug)]
+enum DataEncryptionMode {
+    On,
+    Mixed,
+    RawRead,
+    Off,
+}
+
+#[derive(Debug)]
+struct DataEncryptionStatus {
+    mode: DataEncryptionMode,
+}
+
+#[derive(Endian)]
+#[repr(C, packed)]
+struct SspDataEncryptionCapabilityPage {
+    page_code: u16,
+    page_len: u16,
+    extdecc_cfgp_byte: u8,
+    reserved: [u8; 15],
+}
+
+#[derive(Endian)]
+#[repr(C, packed)]
+struct SspDataEncryptionAlgorithmDescriptor {
+    algorythm_index: u8,
+    reserved1: u8,
+    descriptor_len: u16,
+    control_byte_4: u8,
+    control_byte_5: u8,
+    max_ucad_bytes: u16,
+    max_acad_bytes: u16,
+    key_size: u16,
+    control_byte_12: u8,
+    reserved2: u8,
+    msdk_count: u16,
+    reserved3: [u8; 4],
+    algorithm_code: u32,
+}
+
+// Returns the algorythm_index for AES-CGM
+fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
+
+    proxmox::try_block!({
+        let mut reader = &data[..];
+        let page: SspDataEncryptionCapabilityPage = unsafe { reader.read_be_value()? };
+
+        let extdecc = (page.extdecc_cfgp_byte & 0b00001100) >> 2;
+        if extdecc != 2 {
+            bail!("not external data encryption control capable");
+        }
+
+        let cfg_p = page.extdecc_cfgp_byte & 0b00000011;
+        if cfg_p != 1 {
+            bail!("not allow to change logical block encryption parameters");
+        }
+
+        let mut aes_cgm_index = None;
+
+        loop {
+            if reader.is_empty() { break; };
+            let desc: SspDataEncryptionAlgorithmDescriptor =
+                unsafe { reader.read_be_value()? };
+            if desc.descriptor_len != 0x14 {
+                bail!("got wrong key descriptor len");
+            }
+            if (desc.control_byte_4 & 0b00000011) != 2 {
+                continue; // can't encrypt in hardware
+            }
+            if ((desc.control_byte_4 & 0b00001100) >> 2) != 2 {
+                continue; // can't decrypt in hardware
+            }
+            if desc.algorithm_code == 0x00010014 && desc.key_size == 32 {
+                aes_cgm_index = Some(desc.algorythm_index);
+                break;
+            }
+        }
+
+        match aes_cgm_index {
+            Some(index) => Ok(index),
+            None => bail!("drive dies not support AES-CGM encryption"),
+        }
+    }).map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err))
+
+}
+
+#[derive(Endian)]
+#[repr(C, packed)]
+struct SspDataEncryptionStatusPage {
+    page_code: u16,
+    page_len: u16,
+    scope_byte: u8,
+    encryption_mode: u8,
+    decryption_mode: u8,
+    algorythm_index: u8,
+    key_instance_counter: u32,
+    control_byte: u8,
+    key_format: u8,
+    key_len: u16,
+    reserved: [u8; 8],
+}
+
+fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatus, Error> {
+
+    proxmox::try_block!({
+        let mut reader = &data[..];
+        let page: SspDataEncryptionStatusPage = unsafe { reader.read_be_value()? };
+
+        if page.page_code != 0x20 {
+            bail!("invalid response");
+        }
+
+        let mode = match (page.encryption_mode, page.decryption_mode) {
+            (0, 0) => DataEncryptionMode::Off,
+            (2, 1) => DataEncryptionMode::RawRead,
+            (2, 2) => DataEncryptionMode::On,
+            (2, 3) => DataEncryptionMode::Mixed,
+            _ => bail!("unknown encryption mode"),
+        };
+
+        let status = DataEncryptionStatus {
+            mode,
+        };
+
+        Ok(status)
+
+    }).map_err(|err| format_err!("decode data encryption status page failed - {}", err))
+}
diff --git a/src/tape/drive/lto/sg_tape/mam.rs b/src/tape/drive/lto/sg_tape/mam.rs
new file mode 100644 (file)
index 0000000..06b8185
--- /dev/null
@@ -0,0 +1,238 @@
+use std::collections::HashMap;
+use std::convert::TryInto;
+use std::os::unix::io::AsRawFd;
+
+use anyhow::{bail, format_err, Error};
+use endian_trait::Endian;
+
+use proxmox::tools::io::ReadExt;
+
+use crate::{
+    api2::types::MamAttribute,
+    tools::sgutils2::SgRaw,
+    tape::{
+        drive::lto::TapeAlertFlags,
+    },
+};
+
+// Read Medium auxiliary memory attributes (MAM)
+// see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1
+
+#[derive(Endian)]
+#[repr(C,packed)]
+struct MamAttributeHeader {
+    id: u16,
+    flags: u8,
+    len: u16,
+}
+
+enum MamFormat {
+    BINARY,
+    ASCII,
+    DEC,
+}
+
+static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
+    (0x00_00, 8, MamFormat::DEC, "Remaining Capacity In Partition"),
+    (0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"),
+    (0x00_02, 8, MamFormat::DEC, "Tapealert Flags"),
+    (0x00_03, 8, MamFormat::DEC, "Load Count"),
+    (0x00_04, 8, MamFormat::DEC, "MAM Space Remaining"),
+    (0x00_05, 8, MamFormat::ASCII, "Assigning Organization"),
+    (0x00_06, 1, MamFormat::BINARY, "Formatted Density Code"),
+    (0x00_07, 2, MamFormat::DEC, "Initialization Count"),
+    (0x00_09, 4, MamFormat::BINARY, "Volume Change Reference"),
+
+    (0x02_0A, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Last Load"),
+    (0x02_0B, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-1"),
+    (0x02_0C, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-2"),
+    (0x02_0D, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-3"),
+
+    (0x02_20, 8, MamFormat::DEC, "Total MBytes Written in Medium Life"),
+    (0x02_21, 8, MamFormat::DEC, "Total MBytes Read In Medium Life"),
+    (0x02_22, 8, MamFormat::DEC, "Total MBytes Written in Current Load"),
+    (0x02_23, 8, MamFormat::DEC, "Total MBytes Read in Current/Last Load"),
+    (0x02_24, 8, MamFormat::BINARY, "Logical Position of First Encrypted Block"),
+    (0x02_25, 8, MamFormat::BINARY, "Logical Position of First Unencrypted Block After the First Encrypted Block"),
+
+    (0x04_00, 8, MamFormat::ASCII, "Medium Manufacturer"),
+    (0x04_01, 32, MamFormat::ASCII, "Medium Serial Number"),
+    (0x04_02, 4, MamFormat::DEC, "Medium Length"),
+    (0x04_03, 4, MamFormat::DEC, "Medium Width"),
+    (0x04_04, 8, MamFormat::ASCII, "Assigning Organization"),
+    (0x04_05, 1, MamFormat::BINARY, "Medium Density Code"),
+    (0x04_06, 8, MamFormat::ASCII, "Medium Manufacture Date"),
+    (0x04_07, 8, MamFormat::DEC, "MAM Capacity"),
+    (0x04_08, 1, MamFormat::BINARY, "Medium Type"),
+    (0x04_09, 2, MamFormat::BINARY, "Medium Type Information"),
+    (0x04_0B, 10, MamFormat::BINARY, "Supported Density Codes"),
+
+    (0x08_00, 8, MamFormat::ASCII, "Application Vendor"),
+    (0x08_01, 32, MamFormat::ASCII, "Application Name"),
+    (0x08_02, 8, MamFormat::ASCII, "Application Version"),
+    (0x08_03, 160, MamFormat::ASCII, "User Medium Text Label"),
+    (0x08_04, 12, MamFormat::ASCII, "Date And Time Last Written"),
+    (0x08_05, 1, MamFormat::BINARY, "Text Localization Identifier"),
+    (0x08_06, 32, MamFormat::ASCII, "Barcode"),
+    (0x08_07, 80, MamFormat::ASCII, "Owning Host Textual Name"),
+    (0x08_08, 160, MamFormat::ASCII, "Media Pool"),
+    (0x08_0B, 16, MamFormat::ASCII, "Application Format Version"),
+    (0x08_0C, 50, MamFormat::ASCII, "Volume Coherency Information"),
+    (0x08_20, 36, MamFormat::ASCII, "Medium Globally Unique Identifier"),
+    (0x08_21, 36, MamFormat::ASCII, "Media Pool Globally Unique Identifier"),
+
+    (0x10_00, 28,  MamFormat::BINARY, "Unique Cartridge Identify (UCI)"),
+    (0x10_01, 24,  MamFormat::BINARY, "Alternate Unique Cartridge Identify (Alt-UCI)"),
+
+];
+
+lazy_static::lazy_static!{
+
+    static ref MAM_ATTRIBUTE_NAMES: HashMap<u16, &'static (u16, u16, MamFormat, &'static str)> = {
+        let mut map = HashMap::new();
+
+        for entry in MAM_ATTRIBUTES {
+            map.insert(entry.0, entry);
+        }
+
+        map
+    };
+}
+
+fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
+
+    let alloc_len: u32 = 32*1024;
+    let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
+
+    let mut cmd = Vec::new();
+    cmd.extend(&[0x8c, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
+    cmd.extend(&[0u8, 0u8]); // first attribute
+    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
+    cmd.extend(&[0u8, 0u8]);
+
+    sg_raw.do_command(&cmd)
+        .map_err(|err| format_err!("read cartidge memory failed - {}", err))
+        .map(|v| v.to_vec())
+}
+
+/// Read Medium auxiliary memory attributes (cartridge memory) using raw SCSI command.
+pub fn read_mam_attributes<F: AsRawFd>(file: &mut F) -> Result<Vec<MamAttribute>, Error> {
+
+    let data = read_tape_mam(file)?;
+
+    decode_mam_attributes(&data)
+}
+
+fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
+
+    let mut reader = &data[..];
+
+    let data_len: u32 = unsafe { reader.read_be_value()? };
+
+    let expected_len = data_len as usize;
+
+
+    if reader.len() < expected_len {
+        bail!("read_mam_attributes: got unexpected data len ({} != {})", reader.len(), expected_len);
+    } else if reader.len() > expected_len {
+        // Note: Quantum hh7 returns the allocation_length instead of real data_len
+        reader = &data[4..expected_len+4];
+    }
+
+    let mut list = Vec::new();
+
+    loop {
+        if reader.is_empty() {
+            break;
+        }
+        let head: MamAttributeHeader =  unsafe { reader.read_be_value()? };
+        //println!("GOT ID {:04X} {:08b} {}", head.id, head.flags, head.len);
+
+        let head_id = head.id;
+
+        let data = if head.len > 0 {
+            reader.read_exact_allocated(head.len as usize)?
+        } else {
+            Vec::new()
+        };
+
+        if let Some(info) = MAM_ATTRIBUTE_NAMES.get(&head_id) {
+            if info.1 == head.len {
+                let value = match info.2 {
+                    MamFormat::ASCII => String::from_utf8_lossy(&data).to_string(),
+                    MamFormat::DEC => {
+                        if info.1 == 2 {
+                            format!("{}", u16::from_be_bytes(data[0..2].try_into()?))
+                        } else if info.1 == 4 {
+                            format!("{}", u32::from_be_bytes(data[0..4].try_into()?))
+                        } else if info.1 == 8 {
+                            if head_id == 2 { // Tape Alert Flags
+                                let value = u64::from_be_bytes(data[0..8].try_into()?);
+                                let flags = TapeAlertFlags::from_bits_truncate(value);
+                                format!("{:?}", flags)
+                            } else {
+                                format!("{}", u64::from_be_bytes(data[0..8].try_into()?))
+                            }
+                        } else {
+                            unreachable!();
+                        }
+                    },
+                    MamFormat::BINARY => proxmox::tools::digest_to_hex(&data),
+                };
+                list.push(MamAttribute {
+                    id: head_id,
+                    name: info.3.to_string(),
+                    value,
+                });
+            } else {
+                eprintln!("read_mam_attributes: got starnge data len for id {:04X}", head_id);
+            }
+        } else {
+            // skip unknown IDs
+        }
+    }
+    Ok(list)
+}
+
+/// Media Usage Information from Cartridge Memory
+pub struct MediaUsageInfo {
+    pub manufactured: i64,
+    pub bytes_read: u64,
+    pub bytes_written: u64,
+}
+
+/// Extract Media Usage Information from Cartridge Memory
+pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, Error> {
+
+   let manufactured: i64 = match mam.iter().find(|v| v.id == 0x04_06).map(|v| v.value.clone()) {
+        Some(date_str) => {
+            if date_str.len() != 8 {
+                bail!("unable to parse 'Medium Manufacture Date' - wrong length");
+            }
+            let year: i32 = date_str[..4].parse()?;
+            let mon: i32 = date_str[4..6].parse()?;
+            let mday: i32 = date_str[6..8].parse()?;
+
+            use proxmox::tools::time::TmEditor;
+            let mut t = TmEditor::new(true);
+            t.set_year(year)?;
+            t.set_mon(mon)?;
+            t.set_mday(mday)?;
+
+            t.into_epoch()?
+        }
+        None => bail!("unable to read MAM 'Medium Manufacture Date'"),
+    };
+
+    let bytes_written: u64 = match mam.iter().find(|v| v.id == 0x02_20).map(|v| v.value.clone()) {
+        Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
+        None => bail!("unable to read MAM 'Total MBytes Written In Medium Life'"),
+    };
+
+    let bytes_read: u64 = match mam.iter().find(|v| v.id == 0x02_21).map(|v| v.value.clone()) {
+        Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
+        None => bail!("unable to read MAM 'Total MBytes Read In Medium Life'"),
+    };
+
+    Ok(MediaUsageInfo { manufactured, bytes_written, bytes_read })
+}
diff --git a/src/tape/drive/lto/sg_tape/tape_alert_flags.rs b/src/tape/drive/lto/sg_tape/tape_alert_flags.rs
new file mode 100644 (file)
index 0000000..6338155
--- /dev/null
@@ -0,0 +1,185 @@
+use std::io::Read;
+use std::os::unix::io::AsRawFd;
+
+use anyhow::{bail, format_err, Error};
+
+use proxmox::tools::io::ReadExt;
+
+use crate::tools::sgutils2::SgRaw;
+
+bitflags::bitflags!{
+
+    /// Tape Alert Flags
+    ///
+    /// See LTO SCSI Reference LOG_SENSE - LP 2Eh: TapeAlerts
+    pub struct TapeAlertFlags: u64 {
+        #[allow(clippy::eq_op)]
+        const READ_WARNING = 1 << (0x0001 -1);
+        const WRITE_WARNING = 1 << (0x0002 -1);
+        const HARD_ERROR = 1 << (0x0003 -1);
+        const MEDIA = 1 << (0x0004 -1);
+        const READ_FAILURE = 1 << (0x0005 -1);
+        const WRITE_FAILURE = 1 << (0x0006 -1);
+        const MEDIA_LIFE = 1 << (0x0007 -1);
+        const NOT_DATA_GRADE = 1 << (0x0008 -1);
+        const WRITE_PROTECT = 1 << (0x0009 -1);
+        const NO_REMOVAL = 1 << (0x000A -1);
+        const CLEANING_MEDIA = 1 << (0x000B -1);
+        const UNSUPPORTED_FORMAT = 1 << (0x000C -1);
+        const RECOVERABLE_MECHANICAL_CARTRIDGE_FAILURE = 1 << (0x000D -1); // LTO5
+        const UNRECOVERABLE_SNAPPED_TAPE = 1 << (0x000E -1);
+        const MEMORY_CHIP_IN_CARTRIDGE_FAILURE = 1 << (0x000F -1);
+        const FORCED_EJECT = 1 << (0x0010 -1);
+        const READ_ONLY_FORMAT = 1 << (0x0011 -1);
+        const TAPE_DIRECTORY_CORRUPTED = 1 << (0x0012 -1);
+        const NEARING_MEDIA_LIFE = 1 << (0x0013 -1);
+        const CLEAN_NOW = 1 << (0x0014 -1);
+        const CLEAN_PERIODIC = 1 << (0x0015 -1);
+        const EXPIRED_CLEANING_MEDIA = 1 << (0x0016 -1);
+        const INVALID_CLEANING_TAPE = 1 << (0x0017 -1);
+        const RETENSION_REQUEST = 1 << (0x0018 -1); // LTO5
+        const HOST_CHANNEL_FAILURE = 1 << (0x0019 -1);
+        const COOLING_FAN_FAILURE = 1 << (0x001A -1);
+        const POWER_SUPPLY_FAILURE = 1 << (0x001B -1);
+        const POWER_CONSUMPTION = 1 << (0x001C -1); // LTO5
+        const DRIVE_MANTAINANCE = 1 << (0x001D -1); // LTO5
+        const HARDWARE_A = 1 << (0x001E -1);
+        const HARDWARE_B = 1 << (0x001F -1);
+        const INTERFACE = 1 << (0x0020 -1);
+        const EJECT_MEDIA = 1 << (0x0021 -1);
+        const DOWNLOAD_FAULT = 1 << (0x0022 -1);
+        const DRIVE_HUMIDITY = 1 << (0x0023 -1); // LTO5
+        const DRIVE_TEMPERATURE = 1 << (0x0024 -1);
+        const DRIVE_VOLTAGE = 1 << (0x0025 -1);
+        const PREDICTIVE_FAILURE = 1 << (0x0026 -1);
+        const DIAGNOSTICS_REQUIRED = 1 << (0x0027 -1);
+        const LOADER_STRAY_TAPE = 1 << (0x0029 -1);
+        const LOADER_HARDWARE = 1 << (0x002A -1);
+        const LOADER_MAGAZINE = 1 << (0x002D -1);
+        const DIMINISHED_NATIVE_CAPACITY = 1 << (0x0031 -1);
+        const LOST_STATISTICS = 1 << (0x0032 -1);
+        const TAPE_DIRECTORY_INVALID_AT_UNLOAD = 1 << (0x0033 -1);
+        const TAPE_SYSTEM_AREA_WRITE_FAILURE = 1 << (0x0034 -1);
+        const TAPE_SYSTEM_AREA_READ_FAILURE = 1 << (0x0035 -1);
+        const NO_START_OF_DATA = 1 << (0x0036 -1);
+        const LOADING_FAILURE = 1 << (0x0037 -1);
+        const UNRECOVERABLE_UNLOAD_FAILURE = 1 << (0x0038 -1);
+        const AUTOMATION_INTERFACE_FAILURE = 1 << (0x0039 -1);
+        const FIRMWARE_FAILURE = 1 << (0x003A -1);
+        const WORM_INTEGRITY_CHECK_FAILED = 1 << (0x003B -1);
+        const WORM_OVERWRITE_ATTEMPTED = 1 << (0x003C -1);
+        const ENCRYPTION_POLICY_VIOLATION = 1 << (0x003D -1);
+    }
+}
+
+/// Read Tape Alert Flags using raw SCSI command.
+pub fn read_tape_alert_flags<F: AsRawFd>(file: &mut F) ->  Result<TapeAlertFlags, Error> {
+
+    let data = sg_read_tape_alert_flags(file)?;
+
+    decode_tape_alert_flags(&data)
+}
+
+
+fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
+
+    let mut sg_raw = SgRaw::new(file, 512)?;
+
+    // Note: We cannjot use LP 2Eh TapeAlerts, because that clears flags on read.
+    // Instead, we use LP 12h TapeAlert Response. which does not clear the flags.
+
+    let mut cmd = Vec::new();
+    cmd.push(0x4D); // LOG SENSE
+    cmd.push(0);
+    cmd.push((1<<6) | 0x12); // Tape Alert Response log page
+    cmd.push(0);
+    cmd.push(0);
+    cmd.push(0);
+    cmd.push(0);
+    cmd.extend(&[2u8, 0u8]); // alloc len
+    cmd.push(0u8); // control byte
+
+    sg_raw.do_command(&cmd)
+        .map_err(|err| format_err!("read tape alert flags failed - {}", err))
+        .map(|v| v.to_vec())
+}
+
+fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> {
+
+    proxmox::try_block!({
+        if !((data[0] & 0x7f) == 0x12 && data[1] == 0) {
+            bail!("invalid response");
+        }
+
+        let mut reader = &data[2..];
+
+        let page_len: u16 = unsafe { reader.read_be_value()? };
+        if page_len != 0x0c {
+            bail!("invalid page length");
+        }
+
+        let parameter_code: u16 = unsafe { reader.read_be_value()? };
+        if parameter_code != 0 {
+            bail!("invalid parameter code");
+        }
+
+        let mut control_buf = [0u8; 2];
+        reader.read_exact(&mut control_buf)?;
+
+        if control_buf[1] != 8 {
+            bail!("invalid parameter length");
+        }
+
+        let mut value: u64 =  unsafe { reader.read_be_value()? };
+
+        // bits are in wrong order, reverse them
+        value = value.reverse_bits();
+
+        Ok(TapeAlertFlags::from_bits_truncate(value))
+    }).map_err(|err| format_err!("decode tape alert flags failed - {}", err))
+}
+
+const CRITICAL_FLAG_MASK: u64 =
+TapeAlertFlags::MEDIA.bits() |
+TapeAlertFlags::WRITE_FAILURE.bits() |
+TapeAlertFlags::READ_FAILURE.bits() |
+TapeAlertFlags::WRITE_PROTECT.bits() |
+TapeAlertFlags::UNRECOVERABLE_SNAPPED_TAPE.bits() |
+TapeAlertFlags::FORCED_EJECT.bits() |
+TapeAlertFlags::EXPIRED_CLEANING_MEDIA.bits() |
+TapeAlertFlags::INVALID_CLEANING_TAPE.bits() |
+TapeAlertFlags::HARDWARE_A.bits() |
+TapeAlertFlags::HARDWARE_B.bits() |
+TapeAlertFlags::EJECT_MEDIA.bits() |
+TapeAlertFlags::PREDICTIVE_FAILURE.bits() |
+TapeAlertFlags::LOADER_STRAY_TAPE.bits() |
+TapeAlertFlags::LOADER_MAGAZINE.bits() |
+TapeAlertFlags::TAPE_SYSTEM_AREA_WRITE_FAILURE.bits() |
+TapeAlertFlags::TAPE_SYSTEM_AREA_READ_FAILURE.bits() |
+TapeAlertFlags::NO_START_OF_DATA.bits() |
+TapeAlertFlags::LOADING_FAILURE.bits() |
+TapeAlertFlags::UNRECOVERABLE_UNLOAD_FAILURE.bits() |
+TapeAlertFlags::AUTOMATION_INTERFACE_FAILURE.bits();
+
+/// Check if tape-alert-flags contains critial errors.
+pub fn tape_alert_flags_critical(flags: TapeAlertFlags) -> bool {
+    (flags.bits() & CRITICAL_FLAG_MASK) != 0
+}
+
+const MEDIA_LIFE_MASK: u64 =
+TapeAlertFlags::MEDIA_LIFE.bits() |
+TapeAlertFlags::NEARING_MEDIA_LIFE.bits();
+
+/// Check if tape-alert-flags indicates media-life end
+pub fn tape_alert_flags_media_life(flags: TapeAlertFlags) -> bool {
+    (flags.bits() & MEDIA_LIFE_MASK) != 0
+}
+
+const MEDIA_CLEAN_MASK: u64 =
+TapeAlertFlags::CLEAN_NOW.bits() |
+TapeAlertFlags::CLEAN_PERIODIC.bits();
+
+/// Check if tape-alert-flags indicates media cleaning request
+pub fn tape_alert_flags_cleaning_request(flags: TapeAlertFlags) -> bool {
+    (flags.bits() & MEDIA_CLEAN_MASK) != 0
+}
diff --git a/src/tape/drive/lto/sg_tape/volume_statistics.rs b/src/tape/drive/lto/sg_tape/volume_statistics.rs
new file mode 100644 (file)
index 0000000..1996e46
--- /dev/null
@@ -0,0 +1,231 @@
+use std::io::Read;
+use std::os::unix::io::AsRawFd;
+
+use anyhow::{bail, format_err, Error};
+use endian_trait::Endian;
+
+use proxmox::tools::io::ReadExt;
+
+use crate::{
+    api2::types::Lp17VolumeStatistics,
+    tools::sgutils2::SgRaw,
+};
+
+/// SCSI command to query volume statistics
+///
+/// CDB: LOG SENSE / LP17h Volume Statistics
+///
+/// The Volume Statistics log page is included in Ultrium 5 and later
+/// drives.
+pub fn read_volume_statistics<F: AsRawFd>(file: &mut F) ->  Result<Lp17VolumeStatistics, Error> {
+
+    let data = sg_read_volume_statistics(file)?;
+
+    decode_volume_statistics(&data)
+}
+
+fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
+
+    let alloc_len: u16 = 8192;
+    let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
+
+    let mut cmd = Vec::new();
+    cmd.push(0x4D); // LOG SENSE
+    cmd.push(0);
+    cmd.push((1<<6) | 0x17); // Volume Statistics log page
+    cmd.push(0); // Subpage 0
+    cmd.push(0);
+    cmd.push(0);
+    cmd.push(0);
+    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
+    cmd.push(0u8); // control byte
+
+    sg_raw.do_command(&cmd)
+        .map_err(|err| format_err!("read tape volume statistics failed - {}", err))
+        .map(|v| v.to_vec())
+}
+
+#[repr(C, packed)]
+#[derive(Endian)]
+struct LpParameterHeader {
+    parameter_code: u16,
+    control: u8,
+    parameter_len: u8,
+}
+
+fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> {
+
+
+    let read_be_counter = |reader: &mut &[u8], len: u8| {
+        let len = len as usize;
+        if len == 0 || len > 8 {
+            bail!("invalid conter size '{}'", len);
+        }
+        let mut buffer = [0u8; 8];
+        reader.read_exact(&mut buffer[..len])?;
+
+        let value = buffer
+            .iter()
+            .take(len)
+            .fold(0, |value, curr| (value << 8) | *curr as u64);
+
+        Ok(value)
+    };
+
+    proxmox::try_block!({
+        if !((data[0] & 0x7f) == 0x17 && data[1] == 0) {
+            bail!("invalid response");
+        }
+
+        let mut reader = &data[2..];
+
+        let page_len: u16 = unsafe { reader.read_be_value()? };
+
+        let page_len = page_len as usize;
+
+        if (page_len + 4) > data.len() {
+            bail!("invalid page length");
+        } else {
+            // Note: Quantum hh7 returns the allocation_length instead of real data_len
+            reader = &data[4..page_len+4];
+        }
+
+        let mut stat = Lp17VolumeStatistics::default();
+        let mut page_valid = false;
+
+        loop {
+            if reader.is_empty() {
+                break;
+            }
+            let head: LpParameterHeader = unsafe { reader.read_be_value()? };
+
+            match head.parameter_code {
+                0x0000 => {
+                    let value: u64 = read_be_counter(&mut reader, head.parameter_len)?;
+                     if value == 0 {
+                         bail!("page-valid flag not set");
+                    }
+                    page_valid = true;
+                }
+                0x0001 => {
+                    stat.volume_mounts =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0002 => {
+                    stat.volume_datasets_written =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0003 => {
+                    stat.volume_recovered_write_data_errors =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0004 => {
+                    stat.volume_unrecovered_write_data_errors =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0005 => {
+                    stat.volume_write_servo_errors =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0006 => {
+                    stat.volume_unrecovered_write_servo_errors =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0007 => {
+                    stat.volume_datasets_read =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0008 => {
+                    stat.volume_recovered_read_errors =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0009 => {
+                    stat.volume_unrecovered_read_errors =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x000C => {
+                    stat.last_mount_unrecovered_write_errors =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x000D => {
+                    stat.last_mount_unrecovered_read_errors =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x000E => {
+                    stat.last_mount_bytes_written =
+                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
+                }
+                0x000F => {
+                    stat.last_mount_bytes_read =
+                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
+                }
+                0x0010 => {
+                    stat.lifetime_bytes_written =
+                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
+                }
+                0x0011 => {
+                    stat.lifetime_bytes_read =
+                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
+                }
+                0x0012 => {
+                    stat.last_load_write_compression_ratio =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0013 => {
+                    stat.last_load_read_compression_ratio =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0014 => {
+                    stat.medium_mount_time =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0015 => {
+                    stat.medium_ready_time =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0016 => {
+                    stat.total_native_capacity =
+                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
+                }
+                0x0017 => {
+                    stat.total_used_native_capacity =
+                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
+                }
+                0x0040 => {
+                    let data = reader.read_exact_allocated(head.parameter_len as usize)?;
+                    stat.serial = String::from_utf8_lossy(&data).to_string();
+                }
+                0x0080 => {
+                    let value = read_be_counter(&mut reader, head.parameter_len)?;
+                    if value == 1 {
+                        stat.write_protect = true;
+                    }
+                }
+                0x0081 => {
+                    let value = read_be_counter(&mut reader, head.parameter_len)?;
+                    if value == 1 {
+                        stat.worm = true;
+                    }
+                }
+                0x0101 => {
+                   stat.beginning_of_medium_passes =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                0x0102 => {
+                   stat.middle_of_tape_passes =
+                        read_be_counter(&mut reader, head.parameter_len)?;
+                }
+                _ => {
+                    reader.read_exact_allocated(head.parameter_len as usize)?;
+                }
+            }
+        }
+
+        if !page_valid {
+            bail!("missing page-valid parameter");
+        }
+
+        Ok(stat)
+
+    }).map_err(|err| format_err!("decode volume statistics failed - {}", err))
+}
diff --git a/src/tape/drive/mam.rs b/src/tape/drive/mam.rs
deleted file mode 100644 (file)
index cbb377d..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-use std::collections::HashMap;
-use std::convert::TryInto;
-use std::os::unix::io::AsRawFd;
-
-use anyhow::{bail, format_err, Error};
-use endian_trait::Endian;
-
-use proxmox::tools::io::ReadExt;
-
-use crate::{
-    api2::types::MamAttribute,
-    tools::sgutils2::SgRaw,
-    tape::{
-        drive::TapeAlertFlags,
-    },
-};
-
-// Read Medium auxiliary memory attributes (MAM)
-// see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1
-
-#[derive(Endian)]
-#[repr(C,packed)]
-struct MamAttributeHeader {
-    id: u16,
-    flags: u8,
-    len: u16,
-}
-
-enum MamFormat {
-    BINARY,
-    ASCII,
-    DEC,
-}
-
-static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
-    (0x00_00, 8, MamFormat::DEC, "Remaining Capacity In Partition"),
-    (0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"),
-    (0x00_02, 8, MamFormat::DEC, "Tapealert Flags"),
-    (0x00_03, 8, MamFormat::DEC, "Load Count"),
-    (0x00_04, 8, MamFormat::DEC, "MAM Space Remaining"),
-    (0x00_05, 8, MamFormat::ASCII, "Assigning Organization"),
-    (0x00_06, 1, MamFormat::BINARY, "Formatted Density Code"),
-    (0x00_07, 2, MamFormat::DEC, "Initialization Count"),
-    (0x00_09, 4, MamFormat::BINARY, "Volume Change Reference"),
-
-    (0x02_0A, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Last Load"),
-    (0x02_0B, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-1"),
-    (0x02_0C, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-2"),
-    (0x02_0D, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-3"),
-
-    (0x02_20, 8, MamFormat::DEC, "Total MBytes Written in Medium Life"),
-    (0x02_21, 8, MamFormat::DEC, "Total MBytes Read In Medium Life"),
-    (0x02_22, 8, MamFormat::DEC, "Total MBytes Written in Current Load"),
-    (0x02_23, 8, MamFormat::DEC, "Total MBytes Read in Current/Last Load"),
-    (0x02_24, 8, MamFormat::BINARY, "Logical Position of First Encrypted Block"),
-    (0x02_25, 8, MamFormat::BINARY, "Logical Position of First Unencrypted Block After the First Encrypted Block"),
-
-    (0x04_00, 8, MamFormat::ASCII, "Medium Manufacturer"),
-    (0x04_01, 32, MamFormat::ASCII, "Medium Serial Number"),
-    (0x04_02, 4, MamFormat::DEC, "Medium Length"),
-    (0x04_03, 4, MamFormat::DEC, "Medium Width"),
-    (0x04_04, 8, MamFormat::ASCII, "Assigning Organization"),
-    (0x04_05, 1, MamFormat::BINARY, "Medium Density Code"),
-    (0x04_06, 8, MamFormat::ASCII, "Medium Manufacture Date"),
-    (0x04_07, 8, MamFormat::DEC, "MAM Capacity"),
-    (0x04_08, 1, MamFormat::BINARY, "Medium Type"),
-    (0x04_09, 2, MamFormat::BINARY, "Medium Type Information"),
-    (0x04_0B, 10, MamFormat::BINARY, "Supported Density Codes"),
-
-    (0x08_00, 8, MamFormat::ASCII, "Application Vendor"),
-    (0x08_01, 32, MamFormat::ASCII, "Application Name"),
-    (0x08_02, 8, MamFormat::ASCII, "Application Version"),
-    (0x08_03, 160, MamFormat::ASCII, "User Medium Text Label"),
-    (0x08_04, 12, MamFormat::ASCII, "Date And Time Last Written"),
-    (0x08_05, 1, MamFormat::BINARY, "Text Localization Identifier"),
-    (0x08_06, 32, MamFormat::ASCII, "Barcode"),
-    (0x08_07, 80, MamFormat::ASCII, "Owning Host Textual Name"),
-    (0x08_08, 160, MamFormat::ASCII, "Media Pool"),
-    (0x08_0B, 16, MamFormat::ASCII, "Application Format Version"),
-    (0x08_0C, 50, MamFormat::ASCII, "Volume Coherency Information"),
-    (0x08_20, 36, MamFormat::ASCII, "Medium Globally Unique Identifier"),
-    (0x08_21, 36, MamFormat::ASCII, "Media Pool Globally Unique Identifier"),
-
-    (0x10_00, 28,  MamFormat::BINARY, "Unique Cartridge Identify (UCI)"),
-    (0x10_01, 24,  MamFormat::BINARY, "Alternate Unique Cartridge Identify (Alt-UCI)"),
-
-];
-
-lazy_static::lazy_static!{
-
-    static ref MAM_ATTRIBUTE_NAMES: HashMap<u16, &'static (u16, u16, MamFormat, &'static str)> = {
-        let mut map = HashMap::new();
-
-        for entry in MAM_ATTRIBUTES {
-            map.insert(entry.0, entry);
-        }
-
-        map
-    };
-}
-
-fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
-
-    let alloc_len: u32 = 32*1024;
-    let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
-
-    let mut cmd = Vec::new();
-    cmd.extend(&[0x8c, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
-    cmd.extend(&[0u8, 0u8]); // first attribute
-    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
-    cmd.extend(&[0u8, 0u8]);
-
-    sg_raw.do_command(&cmd)
-        .map_err(|err| format_err!("read cartidge memory failed - {}", err))
-        .map(|v| v.to_vec())
-}
-
-/// Read Medium auxiliary memory attributes (cartridge memory) using raw SCSI command.
-pub fn read_mam_attributes<F: AsRawFd>(file: &mut F) -> Result<Vec<MamAttribute>, Error> {
-
-    let data = read_tape_mam(file)?;
-
-    decode_mam_attributes(&data)
-}
-
-fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
-
-    let mut reader = &data[..];
-
-    let data_len: u32 = unsafe { reader.read_be_value()? };
-
-    let expected_len = data_len as usize;
-
-
-    if reader.len() < expected_len {
-        bail!("read_mam_attributes: got unexpected data len ({} != {})", reader.len(), expected_len);
-    } else if reader.len() > expected_len {
-        // Note: Quantum hh7 returns the allocation_length instead of real data_len
-        reader = &data[4..expected_len+4];
-    }
-
-    let mut list = Vec::new();
-
-    loop {
-        if reader.is_empty() {
-            break;
-        }
-        let head: MamAttributeHeader =  unsafe { reader.read_be_value()? };
-        //println!("GOT ID {:04X} {:08b} {}", head.id, head.flags, head.len);
-
-        let head_id = head.id;
-
-        let data = if head.len > 0 {
-            reader.read_exact_allocated(head.len as usize)?
-        } else {
-            Vec::new()
-        };
-
-        if let Some(info) = MAM_ATTRIBUTE_NAMES.get(&head_id) {
-            if info.1 == head.len {
-                let value = match info.2 {
-                    MamFormat::ASCII => String::from_utf8_lossy(&data).to_string(),
-                    MamFormat::DEC => {
-                        if info.1 == 2 {
-                            format!("{}", u16::from_be_bytes(data[0..2].try_into()?))
-                        } else if info.1 == 4 {
-                            format!("{}", u32::from_be_bytes(data[0..4].try_into()?))
-                        } else if info.1 == 8 {
-                            if head_id == 2 { // Tape Alert Flags
-                                let value = u64::from_be_bytes(data[0..8].try_into()?);
-                                let flags = TapeAlertFlags::from_bits_truncate(value);
-                                format!("{:?}", flags)
-                            } else {
-                                format!("{}", u64::from_be_bytes(data[0..8].try_into()?))
-                            }
-                        } else {
-                            unreachable!();
-                        }
-                    },
-                    MamFormat::BINARY => proxmox::tools::digest_to_hex(&data),
-                };
-                list.push(MamAttribute {
-                    id: head_id,
-                    name: info.3.to_string(),
-                    value,
-                });
-            } else {
-                eprintln!("read_mam_attributes: got starnge data len for id {:04X}", head_id);
-            }
-        } else {
-            // skip unknown IDs
-        }
-    }
-    Ok(list)
-}
-
-/// Media Usage Information from Cartridge Memory
-pub struct MediaUsageInfo {
-    pub manufactured: i64,
-    pub bytes_read: u64,
-    pub bytes_written: u64,
-}
-
-/// Extract Media Usage Information from Cartridge Memory
-pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, Error> {
-
-   let manufactured: i64 = match mam.iter().find(|v| v.id == 0x04_06).map(|v| v.value.clone()) {
-        Some(date_str) => {
-            if date_str.len() != 8 {
-                bail!("unable to parse 'Medium Manufacture Date' - wrong length");
-            }
-            let year: i32 = date_str[..4].parse()?;
-            let mon: i32 = date_str[4..6].parse()?;
-            let mday: i32 = date_str[6..8].parse()?;
-
-            use proxmox::tools::time::TmEditor;
-            let mut t = TmEditor::new(true);
-            t.set_year(year)?;
-            t.set_mon(mon)?;
-            t.set_mday(mday)?;
-
-            t.into_epoch()?
-        }
-        None => bail!("unable to read MAM 'Medium Manufacture Date'"),
-    };
-
-    let bytes_written: u64 = match mam.iter().find(|v| v.id == 0x02_20).map(|v| v.value.clone()) {
-        Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
-        None => bail!("unable to read MAM 'Total MBytes Written In Medium Life'"),
-    };
-
-    let bytes_read: u64 = match mam.iter().find(|v| v.id == 0x02_21).map(|v| v.value.clone()) {
-        Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
-        None => bail!("unable to read MAM 'Total MBytes Read In Medium Life'"),
-    };
-
-    Ok(MediaUsageInfo { manufactured, bytes_written, bytes_read })
-}
index 920ae89f0a61cb6bc3ad726ebf60c438a5efa88c..0d9e3db3c266b32960a335ee2efc3c8b25583c32 100644 (file)
@@ -2,21 +2,9 @@
 
 mod virtual_tape;
 
-mod tape_alert_flags;
-pub use tape_alert_flags::*;
-
-mod volume_statistics;
-pub use volume_statistics::*;
-
-mod encryption;
-pub use encryption::*;
-
 mod lto;
 pub use lto::*;
 
-mod mam;
-pub use mam::*;
-
 use std::os::unix::io::AsRawFd;
 use std::path::PathBuf;
 
@@ -57,6 +45,7 @@ use crate::{
         TapeWrite,
         TapeRead,
         MediaId,
+        drive::lto::TapeAlertFlags,
         file_formats::{
             PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
             PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
diff --git a/src/tape/drive/tape_alert_flags.rs b/src/tape/drive/tape_alert_flags.rs
deleted file mode 100644 (file)
index 6338155..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-use std::io::Read;
-use std::os::unix::io::AsRawFd;
-
-use anyhow::{bail, format_err, Error};
-
-use proxmox::tools::io::ReadExt;
-
-use crate::tools::sgutils2::SgRaw;
-
-bitflags::bitflags!{
-
-    /// Tape Alert Flags
-    ///
-    /// See LTO SCSI Reference LOG_SENSE - LP 2Eh: TapeAlerts
-    pub struct TapeAlertFlags: u64 {
-        #[allow(clippy::eq_op)]
-        const READ_WARNING = 1 << (0x0001 -1);
-        const WRITE_WARNING = 1 << (0x0002 -1);
-        const HARD_ERROR = 1 << (0x0003 -1);
-        const MEDIA = 1 << (0x0004 -1);
-        const READ_FAILURE = 1 << (0x0005 -1);
-        const WRITE_FAILURE = 1 << (0x0006 -1);
-        const MEDIA_LIFE = 1 << (0x0007 -1);
-        const NOT_DATA_GRADE = 1 << (0x0008 -1);
-        const WRITE_PROTECT = 1 << (0x0009 -1);
-        const NO_REMOVAL = 1 << (0x000A -1);
-        const CLEANING_MEDIA = 1 << (0x000B -1);
-        const UNSUPPORTED_FORMAT = 1 << (0x000C -1);
-        const RECOVERABLE_MECHANICAL_CARTRIDGE_FAILURE = 1 << (0x000D -1); // LTO5
-        const UNRECOVERABLE_SNAPPED_TAPE = 1 << (0x000E -1);
-        const MEMORY_CHIP_IN_CARTRIDGE_FAILURE = 1 << (0x000F -1);
-        const FORCED_EJECT = 1 << (0x0010 -1);
-        const READ_ONLY_FORMAT = 1 << (0x0011 -1);
-        const TAPE_DIRECTORY_CORRUPTED = 1 << (0x0012 -1);
-        const NEARING_MEDIA_LIFE = 1 << (0x0013 -1);
-        const CLEAN_NOW = 1 << (0x0014 -1);
-        const CLEAN_PERIODIC = 1 << (0x0015 -1);
-        const EXPIRED_CLEANING_MEDIA = 1 << (0x0016 -1);
-        const INVALID_CLEANING_TAPE = 1 << (0x0017 -1);
-        const RETENSION_REQUEST = 1 << (0x0018 -1); // LTO5
-        const HOST_CHANNEL_FAILURE = 1 << (0x0019 -1);
-        const COOLING_FAN_FAILURE = 1 << (0x001A -1);
-        const POWER_SUPPLY_FAILURE = 1 << (0x001B -1);
-        const POWER_CONSUMPTION = 1 << (0x001C -1); // LTO5
-        const DRIVE_MANTAINANCE = 1 << (0x001D -1); // LTO5
-        const HARDWARE_A = 1 << (0x001E -1);
-        const HARDWARE_B = 1 << (0x001F -1);
-        const INTERFACE = 1 << (0x0020 -1);
-        const EJECT_MEDIA = 1 << (0x0021 -1);
-        const DOWNLOAD_FAULT = 1 << (0x0022 -1);
-        const DRIVE_HUMIDITY = 1 << (0x0023 -1); // LTO5
-        const DRIVE_TEMPERATURE = 1 << (0x0024 -1);
-        const DRIVE_VOLTAGE = 1 << (0x0025 -1);
-        const PREDICTIVE_FAILURE = 1 << (0x0026 -1);
-        const DIAGNOSTICS_REQUIRED = 1 << (0x0027 -1);
-        const LOADER_STRAY_TAPE = 1 << (0x0029 -1);
-        const LOADER_HARDWARE = 1 << (0x002A -1);
-        const LOADER_MAGAZINE = 1 << (0x002D -1);
-        const DIMINISHED_NATIVE_CAPACITY = 1 << (0x0031 -1);
-        const LOST_STATISTICS = 1 << (0x0032 -1);
-        const TAPE_DIRECTORY_INVALID_AT_UNLOAD = 1 << (0x0033 -1);
-        const TAPE_SYSTEM_AREA_WRITE_FAILURE = 1 << (0x0034 -1);
-        const TAPE_SYSTEM_AREA_READ_FAILURE = 1 << (0x0035 -1);
-        const NO_START_OF_DATA = 1 << (0x0036 -1);
-        const LOADING_FAILURE = 1 << (0x0037 -1);
-        const UNRECOVERABLE_UNLOAD_FAILURE = 1 << (0x0038 -1);
-        const AUTOMATION_INTERFACE_FAILURE = 1 << (0x0039 -1);
-        const FIRMWARE_FAILURE = 1 << (0x003A -1);
-        const WORM_INTEGRITY_CHECK_FAILED = 1 << (0x003B -1);
-        const WORM_OVERWRITE_ATTEMPTED = 1 << (0x003C -1);
-        const ENCRYPTION_POLICY_VIOLATION = 1 << (0x003D -1);
-    }
-}
-
-/// Read Tape Alert Flags using raw SCSI command.
-pub fn read_tape_alert_flags<F: AsRawFd>(file: &mut F) ->  Result<TapeAlertFlags, Error> {
-
-    let data = sg_read_tape_alert_flags(file)?;
-
-    decode_tape_alert_flags(&data)
-}
-
-
-fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
-
-    let mut sg_raw = SgRaw::new(file, 512)?;
-
-    // Note: We cannjot use LP 2Eh TapeAlerts, because that clears flags on read.
-    // Instead, we use LP 12h TapeAlert Response. which does not clear the flags.
-
-    let mut cmd = Vec::new();
-    cmd.push(0x4D); // LOG SENSE
-    cmd.push(0);
-    cmd.push((1<<6) | 0x12); // Tape Alert Response log page
-    cmd.push(0);
-    cmd.push(0);
-    cmd.push(0);
-    cmd.push(0);
-    cmd.extend(&[2u8, 0u8]); // alloc len
-    cmd.push(0u8); // control byte
-
-    sg_raw.do_command(&cmd)
-        .map_err(|err| format_err!("read tape alert flags failed - {}", err))
-        .map(|v| v.to_vec())
-}
-
-fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> {
-
-    proxmox::try_block!({
-        if !((data[0] & 0x7f) == 0x12 && data[1] == 0) {
-            bail!("invalid response");
-        }
-
-        let mut reader = &data[2..];
-
-        let page_len: u16 = unsafe { reader.read_be_value()? };
-        if page_len != 0x0c {
-            bail!("invalid page length");
-        }
-
-        let parameter_code: u16 = unsafe { reader.read_be_value()? };
-        if parameter_code != 0 {
-            bail!("invalid parameter code");
-        }
-
-        let mut control_buf = [0u8; 2];
-        reader.read_exact(&mut control_buf)?;
-
-        if control_buf[1] != 8 {
-            bail!("invalid parameter length");
-        }
-
-        let mut value: u64 =  unsafe { reader.read_be_value()? };
-
-        // bits are in wrong order, reverse them
-        value = value.reverse_bits();
-
-        Ok(TapeAlertFlags::from_bits_truncate(value))
-    }).map_err(|err| format_err!("decode tape alert flags failed - {}", err))
-}
-
-const CRITICAL_FLAG_MASK: u64 =
-TapeAlertFlags::MEDIA.bits() |
-TapeAlertFlags::WRITE_FAILURE.bits() |
-TapeAlertFlags::READ_FAILURE.bits() |
-TapeAlertFlags::WRITE_PROTECT.bits() |
-TapeAlertFlags::UNRECOVERABLE_SNAPPED_TAPE.bits() |
-TapeAlertFlags::FORCED_EJECT.bits() |
-TapeAlertFlags::EXPIRED_CLEANING_MEDIA.bits() |
-TapeAlertFlags::INVALID_CLEANING_TAPE.bits() |
-TapeAlertFlags::HARDWARE_A.bits() |
-TapeAlertFlags::HARDWARE_B.bits() |
-TapeAlertFlags::EJECT_MEDIA.bits() |
-TapeAlertFlags::PREDICTIVE_FAILURE.bits() |
-TapeAlertFlags::LOADER_STRAY_TAPE.bits() |
-TapeAlertFlags::LOADER_MAGAZINE.bits() |
-TapeAlertFlags::TAPE_SYSTEM_AREA_WRITE_FAILURE.bits() |
-TapeAlertFlags::TAPE_SYSTEM_AREA_READ_FAILURE.bits() |
-TapeAlertFlags::NO_START_OF_DATA.bits() |
-TapeAlertFlags::LOADING_FAILURE.bits() |
-TapeAlertFlags::UNRECOVERABLE_UNLOAD_FAILURE.bits() |
-TapeAlertFlags::AUTOMATION_INTERFACE_FAILURE.bits();
-
-/// Check if tape-alert-flags contains critial errors.
-pub fn tape_alert_flags_critical(flags: TapeAlertFlags) -> bool {
-    (flags.bits() & CRITICAL_FLAG_MASK) != 0
-}
-
-const MEDIA_LIFE_MASK: u64 =
-TapeAlertFlags::MEDIA_LIFE.bits() |
-TapeAlertFlags::NEARING_MEDIA_LIFE.bits();
-
-/// Check if tape-alert-flags indicates media-life end
-pub fn tape_alert_flags_media_life(flags: TapeAlertFlags) -> bool {
-    (flags.bits() & MEDIA_LIFE_MASK) != 0
-}
-
-const MEDIA_CLEAN_MASK: u64 =
-TapeAlertFlags::CLEAN_NOW.bits() |
-TapeAlertFlags::CLEAN_PERIODIC.bits();
-
-/// Check if tape-alert-flags indicates media cleaning request
-pub fn tape_alert_flags_cleaning_request(flags: TapeAlertFlags) -> bool {
-    (flags.bits() & MEDIA_CLEAN_MASK) != 0
-}
diff --git a/src/tape/drive/volume_statistics.rs b/src/tape/drive/volume_statistics.rs
deleted file mode 100644 (file)
index a6a1159..0000000
+++ /dev/null
@@ -1,292 +0,0 @@
-use std::io::Read;
-use std::os::unix::io::AsRawFd;
-
-use anyhow::{bail, format_err, Error};
-use serde::{Serialize, Deserialize};
-use endian_trait::Endian;
-
-use proxmox::{
-    api::api,
-    tools::io::ReadExt,
-};
-
-use crate::tools::sgutils2::SgRaw;
-
-/// SCSI command to query volume statistics
-///
-/// CDB: LOG SENSE / LP17h Volume Statistics
-///
-/// The Volume Statistics log page is included in Ultrium 5 and later
-/// drives.
-pub fn read_volume_statistics<F: AsRawFd>(file: &mut F) ->  Result<Lp17VolumeStatistics, Error> {
-
-    let data = sg_read_volume_statistics(file)?;
-
-    decode_volume_statistics(&data)
-}
-
-fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
-
-    let alloc_len: u16 = 8192;
-    let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
-
-    let mut cmd = Vec::new();
-    cmd.push(0x4D); // LOG SENSE
-    cmd.push(0);
-    cmd.push((1<<6) | 0x17); // Volume Statistics log page
-    cmd.push(0); // Subpage 0
-    cmd.push(0);
-    cmd.push(0);
-    cmd.push(0);
-    cmd.extend(&alloc_len.to_be_bytes()); // alloc len
-    cmd.push(0u8); // control byte
-
-    sg_raw.do_command(&cmd)
-        .map_err(|err| format_err!("read tape volume statistics failed - {}", err))
-        .map(|v| v.to_vec())
-}
-
-#[repr(C, packed)]
-#[derive(Endian)]
-struct LpParameterHeader {
-    parameter_code: u16,
-    control: u8,
-    parameter_len: u8,
-}
-
-
-#[api()]
-/// Volume statistics from SCSI log page 17h
-#[derive(Default, Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
-pub struct Lp17VolumeStatistics {
-    /// Volume mounts (thread count)
-    pub volume_mounts: u64,
-    /// Total data sets written
-    pub volume_datasets_written: u64,
-    /// Write retries
-    pub volume_recovered_write_data_errors: u64,
-    /// Total unrecovered write errors
-    pub volume_unrecovered_write_data_errors: u64,
-    /// Total suspended writes
-    pub volume_write_servo_errors: u64,
-    /// Total fatal suspended writes
-    pub volume_unrecovered_write_servo_errors: u64,
-    /// Total datasets read
-    pub volume_datasets_read: u64,
-    /// Total read retries
-    pub volume_recovered_read_errors: u64,
-    /// Total unrecovered read errors
-    pub volume_unrecovered_read_errors: u64,
-    /// Last mount unrecovered write errors
-    pub last_mount_unrecovered_write_errors: u64,
-    /// Last mount unrecovered read errors
-    pub last_mount_unrecovered_read_errors: u64,
-    /// Last mount bytes written
-    pub last_mount_bytes_written: u64,
-    /// Last mount bytes read
-    pub last_mount_bytes_read: u64,
-    /// Lifetime bytes written
-    pub lifetime_bytes_written: u64,
-    /// Lifetime bytes read
-    pub lifetime_bytes_read: u64,
-    /// Last load write compression ratio
-    pub last_load_write_compression_ratio: u64,
-    /// Last load read compression ratio
-    pub last_load_read_compression_ratio: u64,
-    /// Medium mount time
-    pub medium_mount_time: u64,
-    /// Medium ready time
-    pub medium_ready_time: u64,
-    /// Total native capacity
-    pub total_native_capacity: u64,
-    /// Total used native capacity
-    pub total_used_native_capacity: u64,
-    /// Write protect
-    pub write_protect: bool,
-    /// Volume is WORM
-    pub worm: bool,
-    /// Beginning of medium passes
-    pub beginning_of_medium_passes: u64,
-    /// Middle of medium passes
-    pub middle_of_tape_passes: u64,
-    /// Volume serial number
-    pub serial: String,
-}
-
-fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> {
-
-
-    let read_be_counter = |reader: &mut &[u8], len: u8| {
-        let len = len as usize;
-        if len == 0 || len > 8 {
-            bail!("invalid conter size '{}'", len);
-        }
-        let mut buffer = [0u8; 8];
-        reader.read_exact(&mut buffer[..len])?;
-
-        let value = buffer
-            .iter()
-            .take(len)
-            .fold(0, |value, curr| (value << 8) | *curr as u64);
-
-        Ok(value)
-    };
-
-    proxmox::try_block!({
-        if !((data[0] & 0x7f) == 0x17 && data[1] == 0) {
-            bail!("invalid response");
-        }
-
-        let mut reader = &data[2..];
-
-        let page_len: u16 = unsafe { reader.read_be_value()? };
-
-        let page_len = page_len as usize;
-
-        if (page_len + 4) > data.len() {
-            bail!("invalid page length");
-        } else {
-            // Note: Quantum hh7 returns the allocation_length instead of real data_len
-            reader = &data[4..page_len+4];
-        }
-
-        let mut stat = Lp17VolumeStatistics::default();
-        let mut page_valid = false;
-
-        loop {
-            if reader.is_empty() {
-                break;
-            }
-            let head: LpParameterHeader = unsafe { reader.read_be_value()? };
-
-            match head.parameter_code {
-                0x0000 => {
-                    let value: u64 = read_be_counter(&mut reader, head.parameter_len)?;
-                     if value == 0 {
-                         bail!("page-valid flag not set");
-                    }
-                    page_valid = true;
-                }
-                0x0001 => {
-                    stat.volume_mounts =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0002 => {
-                    stat.volume_datasets_written =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0003 => {
-                    stat.volume_recovered_write_data_errors =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0004 => {
-                    stat.volume_unrecovered_write_data_errors =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0005 => {
-                    stat.volume_write_servo_errors =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0006 => {
-                    stat.volume_unrecovered_write_servo_errors =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0007 => {
-                    stat.volume_datasets_read =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0008 => {
-                    stat.volume_recovered_read_errors =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0009 => {
-                    stat.volume_unrecovered_read_errors =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x000C => {
-                    stat.last_mount_unrecovered_write_errors =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x000D => {
-                    stat.last_mount_unrecovered_read_errors =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x000E => {
-                    stat.last_mount_bytes_written =
-                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
-                }
-                0x000F => {
-                    stat.last_mount_bytes_read =
-                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
-                }
-                0x0010 => {
-                    stat.lifetime_bytes_written =
-                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
-                }
-                0x0011 => {
-                    stat.lifetime_bytes_read =
-                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
-                }
-                0x0012 => {
-                    stat.last_load_write_compression_ratio =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0013 => {
-                    stat.last_load_read_compression_ratio =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0014 => {
-                    stat.medium_mount_time =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0015 => {
-                    stat.medium_ready_time =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0016 => {
-                    stat.total_native_capacity =
-                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
-                }
-                0x0017 => {
-                    stat.total_used_native_capacity =
-                        read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
-                }
-                0x0040 => {
-                    let data = reader.read_exact_allocated(head.parameter_len as usize)?;
-                    stat.serial = String::from_utf8_lossy(&data).to_string();
-                }
-                0x0080 => {
-                    let value = read_be_counter(&mut reader, head.parameter_len)?;
-                    if value == 1 {
-                        stat.write_protect = true;
-                    }
-                }
-                0x0081 => {
-                    let value = read_be_counter(&mut reader, head.parameter_len)?;
-                    if value == 1 {
-                        stat.worm = true;
-                    }
-                }
-                0x0101 => {
-                   stat.beginning_of_medium_passes =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                0x0102 => {
-                   stat.middle_of_tape_passes =
-                        read_be_counter(&mut reader, head.parameter_len)?;
-                }
-                _ => {
-                    reader.read_exact_allocated(head.parameter_len as usize)?;
-                }
-            }
-        }
-
-        if !page_valid {
-            bail!("missing page-valid parameter");
-        }
-
-        Ok(stat)
-
-    }).map_err(|err| format_err!("decode volume statistics failed - {}", err))
-}