cleanup: move tape SCSI code to src/tape/drive/lto/sg_tape/

This commit is contained in:
Dietmar Maurer
2021-04-09 11:33:33 +02:00
parent c287b28725
commit 109ccd300f
9 changed files with 81 additions and 91 deletions

View File

@ -38,15 +38,13 @@ use crate::{
MamAttribute,
LtoDriveAndMediaStatus,
LtoTapeDrive,
Lp17VolumeStatistics,
},
tape::{
TapeRead,
TapeWrite,
drive::{
TapeDriver,
TapeAlertFlags,
Lp17VolumeStatistics,
mam_extract_media_usage,
},
file_formats::{
PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,

View File

@ -8,6 +8,18 @@ use anyhow::{bail, format_err, Error};
use endian_trait::Endian;
use nix::fcntl::{fcntl, FcntlArg, OFlag};
mod encryption;
pub use encryption::*;
mod volume_statistics;
pub use volume_statistics::*;
mod tape_alert_flags;
pub use tape_alert_flags::*;
mod mam;
pub use mam::*;
use proxmox::{
sys::error::SysResult,
tools::io::{ReadExt, WriteExt},
@ -16,6 +28,7 @@ use proxmox::{
use crate::{
api2::types::{
MamAttribute,
Lp17VolumeStatistics,
},
tape::{
BlockRead,
@ -25,14 +38,6 @@ use crate::{
BlockedWriter,
BlockedReader,
},
drive::{
TapeAlertFlags,
Lp17VolumeStatistics,
read_mam_attributes,
read_tape_alert_flags,
read_volume_statistics,
set_encryption,
},
},
tools::sgutils2::{
SgRaw,

View 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))
}

View 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 })
}

View 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
}

View 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))
}