tape: add command to read cartridge memory (MAM)
Thsi add an additional dependency to sg3-utils (small).
This commit is contained in:
parent
8001c82e81
commit
1e20f819d5
|
@ -14,6 +14,7 @@ Depends: fonts-font-awesome,
|
||||||
smartmontools,
|
smartmontools,
|
||||||
mtx,
|
mtx,
|
||||||
mt-st,
|
mt-st,
|
||||||
|
sg3-utils,
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
${shlibs:Depends},
|
${shlibs:Depends},
|
||||||
Recommends: zfsutils-linux,
|
Recommends: zfsutils-linux,
|
||||||
|
|
|
@ -34,6 +34,7 @@ use crate::{
|
||||||
TapeDeviceInfo,
|
TapeDeviceInfo,
|
||||||
MediaIdFlat,
|
MediaIdFlat,
|
||||||
LabelUuidMap,
|
LabelUuidMap,
|
||||||
|
MamAttribute,
|
||||||
},
|
},
|
||||||
server::WorkerTask,
|
server::WorkerTask,
|
||||||
tape::{
|
tape::{
|
||||||
|
@ -46,6 +47,7 @@ use crate::{
|
||||||
mtx_load,
|
mtx_load,
|
||||||
mtx_unload,
|
mtx_unload,
|
||||||
linux_tape_device_list,
|
linux_tape_device_list,
|
||||||
|
read_mam_attributes,
|
||||||
open_drive,
|
open_drive,
|
||||||
media_changer,
|
media_changer,
|
||||||
update_changer_online_status,
|
update_changer_online_status,
|
||||||
|
@ -768,6 +770,32 @@ fn barcode_label_media_worker(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
drive: {
|
||||||
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
description: "A List of medium auxiliary memory attributes.",
|
||||||
|
type: Array,
|
||||||
|
items: {
|
||||||
|
type: MamAttribute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Read Medium auxiliary memory attributes (Cartridge Memory)
|
||||||
|
pub fn mam_attributes(drive: String) -> Result<Vec<MamAttribute>, Error> {
|
||||||
|
|
||||||
|
let (config, _digest) = config::drive::config()?;
|
||||||
|
|
||||||
|
let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
|
||||||
|
|
||||||
|
read_mam_attributes(&drive_config.path)
|
||||||
|
}
|
||||||
|
|
||||||
#[sortable]
|
#[sortable]
|
||||||
pub const SUBDIRS: SubdirMap = &sorted!([
|
pub const SUBDIRS: SubdirMap = &sorted!([
|
||||||
(
|
(
|
||||||
|
@ -801,6 +829,11 @@ pub const SUBDIRS: SubdirMap = &sorted!([
|
||||||
&Router::new()
|
&Router::new()
|
||||||
.put(&API_METHOD_LOAD_SLOT)
|
.put(&API_METHOD_LOAD_SLOT)
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"mam-attributes",
|
||||||
|
&Router::new()
|
||||||
|
.put(&API_METHOD_MAM_ATTRIBUTES)
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"read-label",
|
"read-label",
|
||||||
&Router::new()
|
&Router::new()
|
||||||
|
|
|
@ -99,3 +99,15 @@ pub struct DriveListEntry {
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub serial: Option<String>,
|
pub serial: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[api()]
|
||||||
|
#[derive(Serialize,Deserialize)]
|
||||||
|
/// Medium auxiliary memory attributes (MAM)
|
||||||
|
pub struct MamAttribute {
|
||||||
|
/// Attribute id
|
||||||
|
pub id: u16,
|
||||||
|
/// Attribute name
|
||||||
|
pub name: String,
|
||||||
|
/// Attribute value
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
|
@ -512,6 +512,48 @@ fn debug_scan(param: Value) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
drive: {
|
||||||
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"output-format": {
|
||||||
|
schema: OUTPUT_FORMAT,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Read Medium auxiliary memory attributes (Cartridge Memory)
|
||||||
|
fn mam_attributes(
|
||||||
|
mut param: Value,
|
||||||
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
|
let (config, _digest) = config::drive::config()?;
|
||||||
|
|
||||||
|
param["drive"] = lookup_drive_name(¶m, &config)?.into();
|
||||||
|
|
||||||
|
let output_format = get_output_format(¶m);
|
||||||
|
let info = &api2::tape::drive::API_METHOD_MAM_ATTRIBUTES;
|
||||||
|
|
||||||
|
let mut data = match info.handler {
|
||||||
|
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let options = default_table_format_options()
|
||||||
|
.column(ColumnConfig::new("id"))
|
||||||
|
.column(ColumnConfig::new("name"))
|
||||||
|
.column(ColumnConfig::new("value"))
|
||||||
|
;
|
||||||
|
|
||||||
|
format_and_print_result_full(&mut data, info.returns, &output_format, &options);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -593,6 +635,11 @@ fn main() {
|
||||||
CliCommand::new(&API_METHOD_READ_LABEL)
|
CliCommand::new(&API_METHOD_READ_LABEL)
|
||||||
.completion_cb("drive", complete_drive_name)
|
.completion_cb("drive", complete_drive_name)
|
||||||
)
|
)
|
||||||
|
.insert(
|
||||||
|
"mam",
|
||||||
|
CliCommand::new(&API_METHOD_MAM_ATTRIBUTES)
|
||||||
|
.completion_cb("drive", complete_drive_name)
|
||||||
|
)
|
||||||
.insert(
|
.insert(
|
||||||
"label",
|
"label",
|
||||||
CliCommand::new(&API_METHOD_LABEL_MEDIA)
|
CliCommand::new(&API_METHOD_LABEL_MEDIA)
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -2,6 +2,9 @@ mod virtual_tape;
|
||||||
mod linux_mtio;
|
mod linux_mtio;
|
||||||
mod linux_tape;
|
mod linux_tape;
|
||||||
|
|
||||||
|
mod mam;
|
||||||
|
pub use mam::*;
|
||||||
|
|
||||||
mod linux_list_drives;
|
mod linux_list_drives;
|
||||||
pub use linux_list_drives::*;
|
pub use linux_list_drives::*;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue