1 use std
::collections
::HashMap
;
2 use std
::convert
::TryInto
;
3 use std
::os
::unix
::io
::AsRawFd
;
5 use anyhow
::{bail, format_err, Error}
;
6 use endian_trait
::Endian
;
8 use proxmox_io
::ReadExt
;
10 use pbs_api_types
::MamAttribute
;
12 use crate::sgutils2
::SgRaw
;
14 use super::TapeAlertFlags
;
16 // Read Medium auxiliary memory attributes (MAM)
17 // see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1
21 struct MamAttributeHeader
{
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"),
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"),
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"),
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"),
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"),
82 (0x10_00, 28, MamFormat
::BINARY
, "Unique Cartridge Identify (UCI)"),
83 (0x10_01, 24, MamFormat
::BINARY
, "Alternate Unique Cartridge Identify (Alt-UCI)"),
87 lazy_static
::lazy_static
!{
89 static ref MAM_ATTRIBUTE_NAMES
: HashMap
<u16, &'
static (u16, u16, MamFormat
, &'
static str)> = {
90 let mut map
= HashMap
::new();
92 for entry
in MAM_ATTRIBUTES
{
93 map
.insert(entry
.0, entry
);
100 fn read_tape_mam
<F
: AsRawFd
>(file
: &mut F
) -> Result
<Vec
<u8>, Error
> {
102 let alloc_len
: u32 = 32*1024;
103 let mut sg_raw
= SgRaw
::new(file
, alloc_len
as usize)?
;
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]);
111 sg_raw
.do_command(&cmd
)
112 .map_err(|err
| format_err
!("read cartidge memory failed - {}", err
))
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
> {
119 let data
= read_tape_mam(file
)?
;
121 decode_mam_attributes(&data
)
124 fn decode_mam_attributes(data
: &[u8]) -> Result
<Vec
<MamAttribute
>, Error
> {
126 let mut reader
= &data
[..];
128 let data_len
: u32 = unsafe { reader.read_be_value()? }
;
130 let expected_len
= data_len
as usize;
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];
140 let mut list
= Vec
::new();
143 if reader
.is_empty() {
146 let head
: MamAttributeHeader
= unsafe { reader.read_be_value()? }
;
147 //println!("GOT ID {:04X} {:08b} {}", head.id, head.flags, head.len);
149 let head_id
= head
.id
;
151 let data
= if head
.len
> 0 {
152 reader
.read_exact_allocated(head
.len
as usize)?
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(),
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
)
172 format
!("{}", u64::from_be_bytes(data
[0..8].try_into()?
))
178 MamFormat
::BINARY
=> proxmox
::tools
::digest_to_hex(&data
),
180 list
.push(MamAttribute
{
182 name
: info
.3.to_string(),
186 eprintln
!("read_mam_attributes: got starnge data len for id {:04X}", head_id
);
195 /// Media Usage Information from Cartridge Memory
196 pub struct MediaUsageInfo
{
197 pub manufactured
: i64,
199 pub bytes_written
: u64,
202 /// Extract Media Usage Information from Cartridge Memory
203 pub fn mam_extract_media_usage(mam
: &[MamAttribute
]) -> Result
<MediaUsageInfo
, Error
> {
205 let manufactured
: i64 = match mam
.iter().find(|v
| v
.id
== 0x04_06).map(|v
| v
.value
.clone()) {
207 if date_str
.len() != 8 {
208 bail
!("unable to parse 'Medium Manufacture Date' - wrong length");
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()?
;
214 use proxmox_time
::TmEditor
;
215 let mut t
= TmEditor
::new(true);
222 None
=> bail
!("unable to read MAM 'Medium Manufacture Date'"),
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'"),
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'"),
235 Ok(MediaUsageInfo { manufactured, bytes_written, bytes_read }
)