proxmox-backup/src/tape/drive/mam.rs

176 lines
6.3 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
use std::convert::TryInto;
use anyhow::{bail, format_err, Error};
use endian_trait::Endian;
use proxmox::tools::io::ReadExt;
use crate::api2::types::MamAttribute;
// 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: &'static [ (u16, u16, MamFormat, &'static str) ] = &[
(0x00_00, 8, MamFormat::DEC, "Remaining Capacity In Partition"),
(0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"),
(0x00_02, 8, MamFormat::BINARY, "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 Identifer"),
(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 Identifer"),
(0x08_21, 36, MamFormat::ASCII, "Media Pool Globally Unique Identifer"),
(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(path: &str) -> Result<Vec<u8>, Error> {
let mut command = std::process::Command::new("sg_raw");
command.args(&[
"-r", "32k",
"-o", "-",
path,
"8c", "00", "00", "00", "00", "00", "00", "00",
"00", "00", // first attribute
"00", "00", "8f", "ff", // alloc len
"00", "00",
]);
let output = command.output()
.map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?;
Ok(output.stdout)
}
pub fn read_mam_attributes(path: &str) -> Result<Vec<MamAttribute>, Error> {
let data = read_tape_mam(path)?;
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);
}
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 {
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)
}