[pbs-devel] [PATCH proxmox-backup 2/2] tape: write informational MAM attributes on tapes

Dominik Csapak d.csapak at proxmox.com
Tue May 14 16:12:48 CEST 2024


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 at proxmox.com>
---
 pbs-tape/Cargo.toml            |  1 +
 pbs-tape/src/sg_tape.rs        | 39 ++++++++++++++++++++++
 pbs-tape/src/sg_tape/mam.rs    | 61 +++++++++++++++++++++++++++++++++-
 src/api2/tape/drive.rs         |  2 ++
 src/tape/drive/lto/mod.rs      |  6 ++++
 src/tape/drive/mod.rs          |  5 +++
 src/tape/drive/virtual_tape.rs |  4 +++
 7 files changed, 117 insertions(+), 1 deletion(-)

diff --git a/pbs-tape/Cargo.toml b/pbs-tape/Cargo.toml
index 970315b7a..f4110706b 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 f30481b31..058c14ae9 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<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> {
diff --git a/pbs-tape/src/sg_tape/mam.rs b/pbs-tape/src/sg_tape/mam.rs
index 74e09c6d6..4e995d0b9 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<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)?;
diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index 7a791e09b..c6fc9f9c2 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 f3143c907..23e043ce6 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<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> {
diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs
index b21a62d20..b912b234d 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<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`].
diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs
index c183e2681..866e4d323 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<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 {
-- 
2.39.2





More information about the pbs-devel mailing list