diff --git a/pbs-tape/Cargo.toml b/pbs-tape/Cargo.toml index 970315b7..f4110706 100644 --- a/pbs-tape/Cargo.toml +++ b/pbs-tape/Cargo.toml @@ -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 diff --git a/pbs-tape/src/sg_tape.rs b/pbs-tape/src/sg_tape.rs index f30481b3..058c14ae 100644 --- a/pbs-tape/src/sg_tape.rs +++ b/pbs-tape/src/sg_tape.rs @@ -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, pool: Option) { + 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> { diff --git a/pbs-tape/src/sg_tape/mam.rs b/pbs-tape/src/sg_tape/mam.rs index 74e09c6d..4e995d0b 100644 --- a/pbs-tape/src/sg_tape/mam.rs +++ b/pbs-tape/src/sg_tape/mam.rs @@ -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(file: &mut F) -> Result, Error> { .map(|v| v.to_vec()) } +/// Write attribute to MAM +pub fn write_mam_attribute( + 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(file: &mut F) -> Result, Error> { let data = read_tape_mam(file)?; diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index 7a791e09..c6fc9f9c 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -606,6 +606,8 @@ fn write_media_label( drive.rewind()?; + drive.write_additional_attributes(Some(media_id.label.label_text), pool); + Ok(()) } diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs index f3143c90..23e043ce 100644 --- a/src/tape/drive/lto/mod.rs +++ b/src/tape/drive/lto/mod.rs @@ -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 { self.volume_statistics() } + + fn write_additional_attributes(&mut self, label: Option, pool: Option) { + self.sg_tape.write_mam_attributes(label, pool) + } } fn run_sg_tape_cmd(subcmd: &str, args: &[&str], fd: RawFd) -> Result { diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs index b21a62d2..b912b234 100644 --- a/src/tape/drive/mod.rs +++ b/src/tape/drive/mod.rs @@ -245,6 +245,11 @@ pub trait TapeDriver { /// Returns volume statistics from a loaded tape fn get_volume_statistics(&mut self) -> Result; + + /// 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, pool: Option); } /// A boxed implementor of [`MediaChange`]. diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs index c183e268..866e4d32 100644 --- a/src/tape/drive/virtual_tape.rs +++ b/src/tape/drive/virtual_tape.rs @@ -465,6 +465,10 @@ impl TapeDriver for VirtualTapeHandle { fn get_volume_statistics(&mut self) -> Result { Ok(Default::default()) } + + fn write_additional_attributes(&mut self, _label: Option, _pool: Option) { + // not implemented + } } impl MediaChange for VirtualTapeHandle {