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:
parent
9d2fc6565f
commit
6d4b380c3d
@ -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
|
||||
|
@ -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> {
|
||||
|
@ -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(¶meters);
|
||||
|
||||
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)?;
|
||||
|
@ -606,6 +606,8 @@ fn write_media_label(
|
||||
|
||||
drive.rewind()?;
|
||||
|
||||
drive.write_additional_attributes(Some(media_id.label.label_text), pool);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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`].
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user