diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index 275e1de6..6ec8fc84 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -35,7 +35,7 @@ use crate::{ MediaIdFlat, LabelUuidMap, MamAttribute, - LinuxDriveStatusFlat, + LinuxDriveAndMediaStatus, }, server::WorkerTask, tape::{ @@ -51,6 +51,7 @@ use crate::{ open_drive, media_changer, update_changer_online_status, + mam_extract_media_usage, file_formats::{ MediaLabel, MediaSetLabel, @@ -807,22 +808,37 @@ pub fn cartridge_memory(drive: String) -> Result, Error> { }, }, returns: { - type: LinuxDriveStatusFlat, + type: LinuxDriveAndMediaStatus, }, )] -/// Get drive status -pub fn status(drive: String) -> Result { +/// Get drive/media status +pub fn status(drive: String) -> Result { let (config, _digest) = config::drive::config()?; let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; - let handle = drive_config.open() + let mut handle = drive_config.open() .map_err(|err| format_err!("open drive '{}' ({}) failed - {}", drive, drive_config.path, err))?; let drive_status = handle.get_drive_status()?; - Ok(drive_status.into()) + let mam = handle.cartridge_memory()?; + + let usage = mam_extract_media_usage(&mam)?; + + let status = LinuxDriveAndMediaStatus { + blocksize: drive_status.blocksize, + density: drive_status.density, + status: format!("{:?}", drive_status.status), + file_number: drive_status.file_number, + block_number: drive_status.block_number, + manufactured: usage.manufactured, + bytes_read: usage.bytes_read, + bytes_written: usage.bytes_written, + }; + + Ok(status) } #[sortable] diff --git a/src/api2/types/tape/drive.rs b/src/api2/types/tape/drive.rs index db33967c..ffce55bb 100644 --- a/src/api2/types/tape/drive.rs +++ b/src/api2/types/tape/drive.rs @@ -166,8 +166,8 @@ impl TryFrom for TapeDensity { )] #[derive(Serialize,Deserialize)] #[serde(rename_all = "kebab-case")] -/// Drive status for Linux SCSI drives. -pub struct LinuxDriveStatusFlat { +/// Drive/Media status for Linux SCSI drives. +pub struct LinuxDriveAndMediaStatus { /// Block size (0 is variable size) pub blocksize: u32, /// Tape density @@ -178,4 +178,10 @@ pub struct LinuxDriveStatusFlat { pub file_number: i32, /// Current block number pub block_number: i32, + /// Medium Manufacture Date (epoch) + pub manufactured: i64, + /// Total Bytes Read in Medium Life + pub bytes_read: u64, + /// Total Bytes Written in Medium Life + pub bytes_written: u64, } diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs index 32dd516b..31768eda 100644 --- a/src/bin/proxmox-tape.rs +++ b/src/bin/proxmox-tape.rs @@ -19,6 +19,7 @@ use proxmox_backup::{ tools::format::{ HumanByte, render_epoch, + render_bytes_human_readable, }, server::{ UPID, @@ -568,7 +569,7 @@ fn cartridge_memory( }, }, )] -/// Get drive status +/// Get drive/media status fn status( mut param: Value, rpcenv: &mut dyn RpcEnvironment, @@ -592,6 +593,9 @@ fn status( .column(ColumnConfig::new("status")) .column(ColumnConfig::new("file-number")) .column(ColumnConfig::new("block-number")) + .column(ColumnConfig::new("manufactured").renderer(render_epoch)) + .column(ColumnConfig::new("bytes-written").renderer(render_bytes_human_readable)) + .column(ColumnConfig::new("bytes-read").renderer(render_bytes_human_readable)) ; format_and_print_result_full(&mut data, &info.returns, &output_format, &options); diff --git a/src/tape/drive/linux_tape.rs b/src/tape/drive/linux_tape.rs index 7cec8840..76011240 100644 --- a/src/tape/drive/linux_tape.rs +++ b/src/tape/drive/linux_tape.rs @@ -11,7 +11,6 @@ use proxmox::sys::error::SysResult; use crate::{ api2::types::{ TapeDensity, - LinuxDriveStatusFlat, MamAttribute, }, tape::{ @@ -52,18 +51,6 @@ impl LinuxDriveStatus { } } -impl From for LinuxDriveStatusFlat { - fn from(status: LinuxDriveStatus) -> Self { - LinuxDriveStatusFlat { - blocksize: status.blocksize, - density: status.density, - status: format!("{:?}", status.status), - file_number: status.file_number, - block_number: status.block_number, - } - } -} - impl LinuxTapeDrive { /// This needs to lock the drive diff --git a/src/tape/drive/mam.rs b/src/tape/drive/mam.rs index 73142b74..0986452a 100644 --- a/src/tape/drive/mam.rs +++ b/src/tape/drive/mam.rs @@ -178,3 +178,46 @@ fn decode_mam_attributes(data: &[u8]) -> Result, Error> { } Ok(list) } + +/// Media Usage Information from Cartridge Memory +pub struct MediaUsageInfo { + pub manufactured: i64, + pub bytes_read: u64, + pub bytes_written: u64, +} + +/// Extract Media Usage Information from Cartridge Memory +pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result { + + let manufactured: i64 = match mam.iter().find(|v| v.id == 0x04_06).map(|v| v.value.clone()) { + Some(date_str) => { + if date_str.len() != 8 { + bail!("unable to parse 'Medium Manufacture Date' - wrong length"); + } + let year: i32 = date_str[..4].parse()?; + let mon: i32 = date_str[4..6].parse()?; + let mday: i32 = date_str[6..8].parse()?; + + use proxmox::tools::time::TmEditor; + let mut t = TmEditor::new(true); + t.set_year(year)?; + t.set_mon(mon)?; + t.set_mday(mday)?; + + t.into_epoch()? + } + None => bail!("unable to read MAM 'Medium Manufacture Date'"), + }; + + let bytes_written: u64 = match mam.iter().find(|v| v.id == 0x02_20).map(|v| v.value.clone()) { + Some(read_str) => read_str.parse::()? * 1024*1024, + None => bail!("unable to read MAM 'Total MBytes Written In Medium Life'"), + }; + + let bytes_read: u64 = match mam.iter().find(|v| v.id == 0x02_21).map(|v| v.value.clone()) { + Some(read_str) => read_str.parse::()? * 1024*1024, + None => bail!("unable to read MAM 'Total MBytes Read In Medium Life'"), + }; + + Ok(MediaUsageInfo { manufactured, bytes_written, bytes_read }) +}