]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-tape/src/sg_tape/mam.rs
update to first proxmox crate split
[proxmox-backup.git] / pbs-tape / src / sg_tape / mam.rs
1 use std::collections::HashMap;
2 use std::convert::TryInto;
3 use std::os::unix::io::AsRawFd;
4
5 use anyhow::{bail, format_err, Error};
6 use endian_trait::Endian;
7
8 use proxmox_io::ReadExt;
9
10 use pbs_api_types::MamAttribute;
11
12 use crate::sgutils2::SgRaw;
13
14 use super::TapeAlertFlags;
15
16 // Read Medium auxiliary memory attributes (MAM)
17 // see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1
18
19 #[derive(Endian)]
20 #[repr(C,packed)]
21 struct MamAttributeHeader {
22 id: u16,
23 flags: u8,
24 len: u16,
25 }
26
27 enum MamFormat {
28 BINARY,
29 ASCII,
30 DEC,
31 }
32
33 static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
34 (0x00_00, 8, MamFormat::DEC, "Remaining Capacity In Partition"),
35 (0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"),
36 (0x00_02, 8, MamFormat::DEC, "Tapealert Flags"),
37 (0x00_03, 8, MamFormat::DEC, "Load Count"),
38 (0x00_04, 8, MamFormat::DEC, "MAM Space Remaining"),
39 (0x00_05, 8, MamFormat::ASCII, "Assigning Organization"),
40 (0x00_06, 1, MamFormat::BINARY, "Formatted Density Code"),
41 (0x00_07, 2, MamFormat::DEC, "Initialization Count"),
42 (0x00_09, 4, MamFormat::BINARY, "Volume Change Reference"),
43
44 (0x02_0A, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Last Load"),
45 (0x02_0B, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-1"),
46 (0x02_0C, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-2"),
47 (0x02_0D, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-3"),
48
49 (0x02_20, 8, MamFormat::DEC, "Total MBytes Written in Medium Life"),
50 (0x02_21, 8, MamFormat::DEC, "Total MBytes Read In Medium Life"),
51 (0x02_22, 8, MamFormat::DEC, "Total MBytes Written in Current Load"),
52 (0x02_23, 8, MamFormat::DEC, "Total MBytes Read in Current/Last Load"),
53 (0x02_24, 8, MamFormat::BINARY, "Logical Position of First Encrypted Block"),
54 (0x02_25, 8, MamFormat::BINARY, "Logical Position of First Unencrypted Block After the First Encrypted Block"),
55
56 (0x04_00, 8, MamFormat::ASCII, "Medium Manufacturer"),
57 (0x04_01, 32, MamFormat::ASCII, "Medium Serial Number"),
58 (0x04_02, 4, MamFormat::DEC, "Medium Length"),
59 (0x04_03, 4, MamFormat::DEC, "Medium Width"),
60 (0x04_04, 8, MamFormat::ASCII, "Assigning Organization"),
61 (0x04_05, 1, MamFormat::BINARY, "Medium Density Code"),
62 (0x04_06, 8, MamFormat::ASCII, "Medium Manufacture Date"),
63 (0x04_07, 8, MamFormat::DEC, "MAM Capacity"),
64 (0x04_08, 1, MamFormat::BINARY, "Medium Type"),
65 (0x04_09, 2, MamFormat::BINARY, "Medium Type Information"),
66 (0x04_0B, 10, MamFormat::BINARY, "Supported Density Codes"),
67
68 (0x08_00, 8, MamFormat::ASCII, "Application Vendor"),
69 (0x08_01, 32, MamFormat::ASCII, "Application Name"),
70 (0x08_02, 8, MamFormat::ASCII, "Application Version"),
71 (0x08_03, 160, MamFormat::ASCII, "User Medium Text Label"),
72 (0x08_04, 12, MamFormat::ASCII, "Date And Time Last Written"),
73 (0x08_05, 1, MamFormat::BINARY, "Text Localization Identifier"),
74 (0x08_06, 32, MamFormat::ASCII, "Barcode"),
75 (0x08_07, 80, MamFormat::ASCII, "Owning Host Textual Name"),
76 (0x08_08, 160, MamFormat::ASCII, "Media Pool"),
77 (0x08_0B, 16, MamFormat::ASCII, "Application Format Version"),
78 (0x08_0C, 50, MamFormat::ASCII, "Volume Coherency Information"),
79 (0x08_20, 36, MamFormat::ASCII, "Medium Globally Unique Identifier"),
80 (0x08_21, 36, MamFormat::ASCII, "Media Pool Globally Unique Identifier"),
81
82 (0x10_00, 28, MamFormat::BINARY, "Unique Cartridge Identify (UCI)"),
83 (0x10_01, 24, MamFormat::BINARY, "Alternate Unique Cartridge Identify (Alt-UCI)"),
84
85 ];
86
87 lazy_static::lazy_static!{
88
89 static ref MAM_ATTRIBUTE_NAMES: HashMap<u16, &'static (u16, u16, MamFormat, &'static str)> = {
90 let mut map = HashMap::new();
91
92 for entry in MAM_ATTRIBUTES {
93 map.insert(entry.0, entry);
94 }
95
96 map
97 };
98 }
99
100 fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
101
102 let alloc_len: u32 = 32*1024;
103 let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
104
105 let mut cmd = Vec::new();
106 cmd.extend(&[0x8c, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
107 cmd.extend(&[0u8, 0u8]); // first attribute
108 cmd.extend(&alloc_len.to_be_bytes()); // alloc len
109 cmd.extend(&[0u8, 0u8]);
110
111 sg_raw.do_command(&cmd)
112 .map_err(|err| format_err!("read cartidge memory failed - {}", err))
113 .map(|v| v.to_vec())
114 }
115
116 /// Read Medium auxiliary memory attributes (cartridge memory) using raw SCSI command.
117 pub fn read_mam_attributes<F: AsRawFd>(file: &mut F) -> Result<Vec<MamAttribute>, Error> {
118
119 let data = read_tape_mam(file)?;
120
121 decode_mam_attributes(&data)
122 }
123
124 fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
125
126 let mut reader = &data[..];
127
128 let data_len: u32 = unsafe { reader.read_be_value()? };
129
130 let expected_len = data_len as usize;
131
132
133 if reader.len() < expected_len {
134 bail!("read_mam_attributes: got unexpected data len ({} != {})", reader.len(), expected_len);
135 } else if reader.len() > expected_len {
136 // Note: Quantum hh7 returns the allocation_length instead of real data_len
137 reader = &data[4..expected_len+4];
138 }
139
140 let mut list = Vec::new();
141
142 loop {
143 if reader.is_empty() {
144 break;
145 }
146 let head: MamAttributeHeader = unsafe { reader.read_be_value()? };
147 //println!("GOT ID {:04X} {:08b} {}", head.id, head.flags, head.len);
148
149 let head_id = head.id;
150
151 let data = if head.len > 0 {
152 reader.read_exact_allocated(head.len as usize)?
153 } else {
154 Vec::new()
155 };
156
157 if let Some(info) = MAM_ATTRIBUTE_NAMES.get(&head_id) {
158 if info.1 == head.len {
159 let value = match info.2 {
160 MamFormat::ASCII => String::from_utf8_lossy(&data).to_string(),
161 MamFormat::DEC => {
162 if info.1 == 2 {
163 format!("{}", u16::from_be_bytes(data[0..2].try_into()?))
164 } else if info.1 == 4 {
165 format!("{}", u32::from_be_bytes(data[0..4].try_into()?))
166 } else if info.1 == 8 {
167 if head_id == 2 { // Tape Alert Flags
168 let value = u64::from_be_bytes(data[0..8].try_into()?);
169 let flags = TapeAlertFlags::from_bits_truncate(value);
170 format!("{:?}", flags)
171 } else {
172 format!("{}", u64::from_be_bytes(data[0..8].try_into()?))
173 }
174 } else {
175 unreachable!();
176 }
177 },
178 MamFormat::BINARY => proxmox::tools::digest_to_hex(&data),
179 };
180 list.push(MamAttribute {
181 id: head_id,
182 name: info.3.to_string(),
183 value,
184 });
185 } else {
186 eprintln!("read_mam_attributes: got starnge data len for id {:04X}", head_id);
187 }
188 } else {
189 // skip unknown IDs
190 }
191 }
192 Ok(list)
193 }
194
195 /// Media Usage Information from Cartridge Memory
196 pub struct MediaUsageInfo {
197 pub manufactured: i64,
198 pub bytes_read: u64,
199 pub bytes_written: u64,
200 }
201
202 /// Extract Media Usage Information from Cartridge Memory
203 pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, Error> {
204
205 let manufactured: i64 = match mam.iter().find(|v| v.id == 0x04_06).map(|v| v.value.clone()) {
206 Some(date_str) => {
207 if date_str.len() != 8 {
208 bail!("unable to parse 'Medium Manufacture Date' - wrong length");
209 }
210 let year: i32 = date_str[..4].parse()?;
211 let mon: i32 = date_str[4..6].parse()?;
212 let mday: i32 = date_str[6..8].parse()?;
213
214 use proxmox_time::TmEditor;
215 let mut t = TmEditor::new(true);
216 t.set_year(year)?;
217 t.set_mon(mon)?;
218 t.set_mday(mday)?;
219
220 t.into_epoch()?
221 }
222 None => bail!("unable to read MAM 'Medium Manufacture Date'"),
223 };
224
225 let bytes_written: u64 = match mam.iter().find(|v| v.id == 0x02_20).map(|v| v.value.clone()) {
226 Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
227 None => bail!("unable to read MAM 'Total MBytes Written In Medium Life'"),
228 };
229
230 let bytes_read: u64 = match mam.iter().find(|v| v.id == 0x02_21).map(|v| v.value.clone()) {
231 Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
232 None => bail!("unable to read MAM 'Total MBytes Read In Medium Life'"),
233 };
234
235 Ok(MediaUsageInfo { manufactured, bytes_written, bytes_read })
236 }