2020-12-21 11:11:52 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::convert::TryInto;
|
2020-12-23 08:44:53 +00:00
|
|
|
use std::os::unix::io::AsRawFd;
|
2020-12-21 11:11:52 +00:00
|
|
|
|
|
|
|
use anyhow::{bail, format_err, Error};
|
|
|
|
use endian_trait::Endian;
|
|
|
|
|
|
|
|
use proxmox::tools::io::ReadExt;
|
|
|
|
|
2020-12-23 08:44:53 +00:00
|
|
|
use crate::{
|
|
|
|
api2::types::MamAttribute,
|
2021-01-02 09:55:30 +00:00
|
|
|
tape::{
|
2021-01-03 09:09:43 +00:00
|
|
|
TapeAlertFlags,
|
2021-01-02 09:55:30 +00:00
|
|
|
sgutils2::SgRaw,
|
|
|
|
},
|
2020-12-23 08:44:53 +00:00
|
|
|
};
|
2020-12-21 11:11:52 +00:00
|
|
|
|
|
|
|
// 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,
|
|
|
|
}
|
|
|
|
|
2021-01-18 12:25:04 +00:00
|
|
|
static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
|
2020-12-21 11:11:52 +00:00
|
|
|
(0x00_00, 8, MamFormat::DEC, "Remaining Capacity In Partition"),
|
|
|
|
(0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"),
|
2021-01-02 09:55:30 +00:00
|
|
|
(0x00_02, 8, MamFormat::DEC, "Tapealert Flags"),
|
2020-12-21 11:11:52 +00:00
|
|
|
(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
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-12-23 08:44:53 +00:00
|
|
|
fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
|
2020-12-21 11:11:52 +00:00
|
|
|
|
2020-12-23 08:44:53 +00:00
|
|
|
let mut sg_raw = SgRaw::new(file, 32*1024)?;
|
2020-12-21 11:11:52 +00:00
|
|
|
|
2020-12-23 08:44:53 +00:00
|
|
|
let mut cmd = Vec::new();
|
|
|
|
cmd.extend(&[0x8c, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
|
|
|
|
cmd.extend(&[0u8, 0u8]); // first attribute
|
|
|
|
cmd.extend(&[0u8, 0u8, 0x8f, 0xff]); // alloc len
|
|
|
|
cmd.extend(&[0u8, 0u8]);
|
2020-12-21 11:11:52 +00:00
|
|
|
|
2020-12-23 08:44:53 +00:00
|
|
|
sg_raw.do_command(&cmd)
|
|
|
|
.map_err(|err| format_err!("read cartidge memory failed - {}", err))
|
|
|
|
.map(|v| v.to_vec())
|
2020-12-21 11:11:52 +00:00
|
|
|
}
|
|
|
|
|
2020-12-23 08:44:53 +00:00
|
|
|
pub fn read_mam_attributes<F: AsRawFd>(file: &mut F) -> Result<Vec<MamAttribute>, Error> {
|
2020-12-21 11:11:52 +00:00
|
|
|
|
2020-12-23 08:44:53 +00:00
|
|
|
let data = read_tape_mam(file)?;
|
|
|
|
|
|
|
|
decode_mam_attributes(&data)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
|
2020-12-21 11:11:52 +00:00
|
|
|
|
|
|
|
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 {
|
2021-01-02 09:55:30 +00:00
|
|
|
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()?))
|
|
|
|
}
|
2020-12-21 11:11:52 +00:00
|
|
|
} 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)
|
|
|
|
}
|
2020-12-23 10:24:34 +00:00
|
|
|
|
|
|
|
/// 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 })
|
|
|
|
}
|