cleanup: move tape SCSI code to src/tape/drive/lto/sg_tape/
This commit is contained in:
		| @ -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, | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
							
								
								
									
										307
									
								
								src/tape/drive/lto/sg_tape/encryption.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										307
									
								
								src/tape/drive/lto/sg_tape/encryption.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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)) | ||||
| } | ||||
							
								
								
									
										238
									
								
								src/tape/drive/lto/sg_tape/mam.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								src/tape/drive/lto/sg_tape/mam.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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 }) | ||||
| } | ||||
							
								
								
									
										185
									
								
								src/tape/drive/lto/sg_tape/tape_alert_flags.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/tape/drive/lto/sg_tape/tape_alert_flags.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										231
									
								
								src/tape/drive/lto/sg_tape/volume_statistics.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								src/tape/drive/lto/sg_tape/volume_statistics.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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)) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user