tape: write informational MAM attributes on tapes

namely:

Vendor: Proxmox
Name: Backup Server
Version: current running package version
User Label Text: the label text
Media Pool: the current media pool

write it on labeling and when writing a new media-set to a tape.

While we currently don't use this info for anything, this can help users
to identify tapes, even with different backup software.

If we need it in the future, we can e.g. make decisions based on these
fields (e.g. the version).

On format, delete them again.

Note that some VTLs don't correctly delete the attributes from the
virtual tapes.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
Dominik Csapak 2024-05-14 16:12:48 +02:00 committed by Dietmar Maurer
parent 9d2fc6565f
commit 6d4b380c3d
7 changed files with 117 additions and 1 deletions

View File

@ -34,4 +34,5 @@ proxmox-schema = { workspace = true, features = [ "api-macro" ] }
proxmox-router = { workspace = true, features = ["cli", "server"] }
pbs-api-types.workspace = true
pbs-buildcfg.workspace = true
pbs-config.workspace = true

View File

@ -295,6 +295,8 @@ impl SgTape {
self.erase_media(fast)?
}
self.clear_mam_attributes();
Ok(())
}
}
@ -1048,6 +1050,43 @@ impl SgTape {
Ok(status)
}
/// Tries to write useful attributes to the MAM like Vendor/Application/Version
pub fn write_mam_attributes(&mut self, label: Option<String>, pool: Option<String>) {
let version = format!(
"{}-{}",
pbs_buildcfg::PROXMOX_PKG_VERSION,
pbs_buildcfg::PROXMOX_PKG_RELEASE
);
let mut attribute_list: Vec<(u16, &[u8])> = vec![
(0x08_00, b"Proxmox"),
(0x08_01, b"Backup Server"),
(0x08_02, version.as_bytes()),
];
if let Some(ref label) = label {
attribute_list.push((0x08_03, label.as_bytes()));
}
if let Some(ref pool) = pool {
attribute_list.push((0x08_08, pool.as_bytes()));
}
for (id, data) in attribute_list {
if let Err(err) = write_mam_attribute(&mut self.file, id, data) {
log::warn!("could not set MAM Attribute {id:x}: {err}");
}
}
}
// clear all custom set mam attributes
fn clear_mam_attributes(&mut self) {
for attr in [0x08_00, 0x08_01, 0x08_02, 0x08_03, 0x08_08] {
// ignore error
if let Err(err) = write_mam_attribute(&mut self.file, attr, b"") {
log::warn!("could not clear MAM attribute {attr:x}: {err}");
}
}
}
}
pub struct SgTapeReader<'a> {

View File

@ -8,7 +8,7 @@ use proxmox_io::ReadExt;
use pbs_api_types::MamAttribute;
use crate::sgutils2::SgRaw;
use crate::sgutils2::{alloc_page_aligned_buffer, SgRaw};
use super::TapeAlertFlags;
@ -143,6 +143,65 @@ fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
.map(|v| v.to_vec())
}
/// Write attribute to MAM
pub fn write_mam_attribute<F: AsRawFd>(
file: &mut F,
attribute_id: u16,
data: &[u8],
) -> Result<(), Error> {
let mut sg_raw = SgRaw::new(file, 0)?;
let mut parameters = Vec::new();
let attribute = MAM_ATTRIBUTE_NAMES
.get(&attribute_id)
.ok_or_else(|| format_err!("MAM attribute '{attribute_id:x}' unknown"))?;
let mut attr_data = Vec::new();
attr_data.extend(attribute_id.to_be_bytes());
attr_data.push(match attribute.format {
MamFormat::BINARY | MamFormat::DEC => 0x00,
MamFormat::ASCII => 0x01,
MamFormat::TEXT => 0x02,
});
let len = if data.is_empty() { 0 } else { attribute.len };
attr_data.extend(len.to_be_bytes());
attr_data.extend(data);
if !data.is_empty() && data.len() < attribute.len as usize {
attr_data.resize(attr_data.len() - data.len() + attribute.len as usize, 0);
} else if data.len() > u16::MAX as usize {
bail!("data to long");
}
parameters.extend(attr_data);
let mut data_out = alloc_page_aligned_buffer(parameters.len() + 4)?;
data_out[..4].copy_from_slice(&(parameters.len() as u32).to_be_bytes());
data_out[4..].copy_from_slice(&parameters);
let mut cmd = vec![
0x8d, // WRITE ATTRIBUTE CDB (8Dh)
0x01, // WTC=1
0x00, // reserved
0x00, // reserved
0x00, // reserved
0x00, // Volume Number
0x00, // reserved
0x00, // Partition Number
0x00, // reserved
0x00, // reserved
];
cmd.extend((data_out.len() as u32).to_be_bytes());
cmd.extend([
0x00, // reserved
0x00, // reserved
]);
sg_raw.do_out_command(&cmd, &data_out)?;
Ok(())
}
/// 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)?;

View File

@ -606,6 +606,8 @@ fn write_media_label(
drive.rewind()?;
drive.write_additional_attributes(Some(media_id.label.label_text), pool);
Ok(())
}

View File

@ -225,6 +225,8 @@ impl TapeDriver for LtoTapeHandle {
self.set_encryption(encrypt_fingerprint)?;
self.write_additional_attributes(None, Some(media_set_label.pool.clone()));
Ok(())
}
@ -272,6 +274,10 @@ impl TapeDriver for LtoTapeHandle {
fn get_volume_statistics(&mut self) -> Result<pbs_api_types::Lp17VolumeStatistics, Error> {
self.volume_statistics()
}
fn write_additional_attributes(&mut self, label: Option<String>, pool: Option<String>) {
self.sg_tape.write_mam_attributes(label, pool)
}
}
fn run_sg_tape_cmd(subcmd: &str, args: &[&str], fd: RawFd) -> Result<String, Error> {

View File

@ -245,6 +245,11 @@ pub trait TapeDriver {
/// Returns volume statistics from a loaded tape
fn get_volume_statistics(&mut self) -> Result<pbs_api_types::Lp17VolumeStatistics, Error>;
/// Writes additional attributes on the drive, like the vendor/application/etc. (e.g. on MAM)
///
/// Since it's not fatal when it does not work, it only logs warnings in that case
fn write_additional_attributes(&mut self, label: Option<String>, pool: Option<String>);
}
/// A boxed implementor of [`MediaChange`].

View File

@ -465,6 +465,10 @@ impl TapeDriver for VirtualTapeHandle {
fn get_volume_statistics(&mut self) -> Result<pbs_api_types::Lp17VolumeStatistics, Error> {
Ok(Default::default())
}
fn write_additional_attributes(&mut self, _label: Option<String>, _pool: Option<String>) {
// not implemented
}
}
impl MediaChange for VirtualTapeHandle {