LabelUuidMap,
MamAttribute,
LtoDriveAndMediaStatus,
+ Lp17VolumeStatistics,
},
tape::restore::{
fast_catalog_restore,
drive::{
TapeDriver,
LtoTapeHandle,
- Lp17VolumeStatistics,
open_lto_tape_device,
media_changer,
required_media_changer,
#[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,
+}
+++ /dev/null
-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))
-}
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,
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},
use crate::{
api2::types::{
MamAttribute,
+ Lp17VolumeStatistics,
},
tape::{
BlockRead,
BlockedWriter,
BlockedReader,
},
- drive::{
- TapeAlertFlags,
- Lp17VolumeStatistics,
- read_mam_attributes,
- read_tape_alert_flags,
- read_volume_statistics,
- set_encryption,
- },
},
tools::sgutils2::{
SgRaw,
--- /dev/null
+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))
+}
--- /dev/null
+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 })
+}
--- /dev/null
+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
+}
--- /dev/null
+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))
+}
+++ /dev/null
-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 })
-}
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;
TapeWrite,
TapeRead,
MediaId,
+ drive::lto::TapeAlertFlags,
file_formats::{
PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
+++ /dev/null
-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
-}
+++ /dev/null
-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))
-}