]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-tape/src/sg_pt_changer.rs
more clippy fixes and annotations
[proxmox-backup.git] / pbs-tape / src / sg_pt_changer.rs
CommitLineData
697c41c5 1//! SCSI changer implementation using libsgutil2
697c41c5 2use std::collections::HashMap;
b23adfd4
TL
3use std::fs::{File, OpenOptions};
4use std::io::Read;
5use std::os::unix::prelude::AsRawFd;
697c41c5 6use std::path::Path;
697c41c5
DM
7
8use anyhow::{bail, format_err, Error};
9use endian_trait::Endian;
10
6ef1b649 11use proxmox_io::ReadExt;
697c41c5 12
6227654a
DM
13use pbs_api_types::ScsiTapeChanger;
14
697c41c5 15use crate::{
b23adfd4
TL
16 sgutils2::{scsi_ascii_to_string, scsi_inquiry, ScsiError, SgRaw, SENSE_KEY_NOT_READY},
17 DriveStatus, ElementStatus, MtxStatus, StorageElementStatus, TransportElementStatus,
697c41c5
DM
18};
19
b23adfd4 20const SCSI_CHANGER_DEFAULT_TIMEOUT: usize = 60 * 5; // 5 minutes
62a0e190 21const SCSI_VOLUME_TAG_LEN: usize = 36;
697c41c5
DM
22
23/// Initialize element status (Inventory)
24pub fn initialize_element_status<F: AsRawFd>(file: &mut F) -> Result<(), Error> {
697c41c5
DM
25 let mut sg_raw = SgRaw::new(file, 64)?;
26
27 // like mtx(1), set a very long timeout (30 minutes)
b23adfd4 28 sg_raw.set_timeout(30 * 60);
697c41c5 29
e1db0670 30 let cmd = &[0x07, 0, 0, 0, 0, 0]; // INITIALIZE ELEMENT STATUS (07h)
697c41c5 31
b23adfd4 32 sg_raw
e1db0670 33 .do_command(cmd)
697c41c5
DM
34 .map_err(|err| format_err!("initializte element status (07h) failed - {}", err))?;
35
36 Ok(())
37}
38
39#[repr(C, packed)]
40#[derive(Endian)]
41struct AddressAssignmentPage {
42 data_len: u8,
43 reserved1: u8,
44 reserved2: u8,
45 block_descriptor_len: u8,
46
47 page_code: u8,
48 additional_page_len: u8,
49 first_transport_element_address: u16,
50 transport_element_count: u16,
51 first_storage_element_address: u16,
52 storage_element_count: u16,
53 first_import_export_element_address: u16,
54 import_export_element_count: u16,
55 first_tranfer_element_address: u16,
56 transfer_element_count: u16,
57 reserved22: u8,
58 reserved23: u8,
59}
60
16b4d784 61/// Execute scsi commands, optionally repeat the command until
9ee4c238 62/// successful or timeout (sleep 1 second between invovations)
ec8d9c6b 63///
9ee4c238
DM
64/// Timeout is 5 seconds. If the device reports "Not Ready - becoming
65/// ready", we wait up to 5 minutes.
ec8d9c6b
DM
66///
67/// Skipped errors are printed on stderr.
16b4d784 68fn execute_scsi_command<F: AsRawFd>(
ec8d9c6b
DM
69 sg_raw: &mut SgRaw<F>,
70 cmd: &[u8],
71 error_prefix: &str,
16b4d784 72 retry: bool,
ec8d9c6b 73) -> Result<Vec<u8>, Error> {
ec8d9c6b
DM
74 let start = std::time::SystemTime::now();
75
76 let mut last_msg: Option<String> = None;
77
78 let mut timeout = std::time::Duration::new(5, 0); // short timeout by default
79
80 loop {
9a37bd6c 81 match sg_raw.do_command(cmd) {
ec8d9c6b 82 Ok(data) => return Ok(data.to_vec()),
9ee4c238 83 Err(err) if !retry => bail!("{} failed: {}", error_prefix, err),
ec8d9c6b 84 Err(err) => {
9ee4c238
DM
85 let msg = err.to_string();
86 if let Some(ref last) = last_msg {
87 if &msg != last {
e82c03a1 88 log::error!("{}", err);
9ee4c238
DM
89 last_msg = Some(msg);
90 }
91 } else {
e82c03a1 92 log::error!("{}", err);
9ee4c238 93 last_msg = Some(msg);
16b4d784 94 }
ec8d9c6b 95
9ee4c238
DM
96 if let ScsiError::Sense(ref sense) = err {
97 // Not Ready - becoming ready
b23adfd4
TL
98 if sense.sense_key == SENSE_KEY_NOT_READY
99 && sense.asc == 0x04
100 && sense.ascq == 1
101 {
9ee4c238 102 // wait up to 5 minutes, long enough to finish inventorize
b23adfd4 103 timeout = std::time::Duration::new(5 * 60, 0);
ec8d9c6b
DM
104 }
105 }
9ee4c238
DM
106
107 if start.elapsed()? > timeout {
108 bail!("{} failed: {}", error_prefix, err);
109 }
110
111 std::thread::sleep(std::time::Duration::new(1, 0));
112 continue; // try again
ec8d9c6b
DM
113 }
114 }
b23adfd4 115 }
ec8d9c6b
DM
116}
117
697c41c5
DM
118fn read_element_address_assignment<F: AsRawFd>(
119 file: &mut F,
120) -> Result<AddressAssignmentPage, Error> {
697c41c5
DM
121 let allocation_len: u8 = u8::MAX;
122 let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
123 sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
124
e1db0670
FG
125 let cmd = &[
126 0x1A, // MODE SENSE6 (1Ah)
127 0x08, // DBD=1 (The Disable Block Descriptors)
128 0x1D, // Element Address Assignment Page
129 0,
130 allocation_len, // allocation len
131 0, //control
132 ];
697c41c5 133
e1db0670 134 let data = execute_scsi_command(&mut sg_raw, cmd, "read element address assignment", true)?;
697c41c5 135
6ef1b649 136 proxmox_lang::try_block!({
697c41c5
DM
137 let mut reader = &data[..];
138 let page: AddressAssignmentPage = unsafe { reader.read_be_value()? };
139
140 if page.data_len != 23 {
141 bail!("got unexpected page len ({} != 23)", page.data_len);
142 }
143
144 Ok(page)
b23adfd4
TL
145 })
146 .map_err(|err: Error| format_err!("decode element address assignment page failed - {}", err))
697c41c5
DM
147}
148
e1db0670 149#[allow(clippy::vec_init_then_push)]
697c41c5
DM
150fn scsi_move_medium_cdb(
151 medium_transport_address: u16,
152 source_element_address: u16,
153 destination_element_address: u16,
154) -> Vec<u8> {
697c41c5
DM
155 let mut cmd = Vec::new();
156 cmd.push(0xA5); // MOVE MEDIUM (A5h)
157 cmd.push(0); // reserved
158 cmd.extend(&medium_transport_address.to_be_bytes());
159 cmd.extend(&source_element_address.to_be_bytes());
160 cmd.extend(&destination_element_address.to_be_bytes());
161 cmd.push(0); // reserved
162 cmd.push(0); // reserved
163 cmd.push(0); // Invert=0
164 cmd.push(0); // control
165
166 cmd
167}
168
169/// Load media from storage slot into drive
b23adfd4 170pub fn load_slot(file: &mut File, from_slot: u64, drivenum: u64) -> Result<(), Error> {
697c41c5
DM
171 let status = read_element_status(file)?;
172
173 let transport_address = status.transport_address();
174 let source_element_address = status.slot_address(from_slot)?;
175 let drive_element_address = status.drive_address(drivenum)?;
176
177 let cmd = scsi_move_medium_cdb(
178 transport_address,
179 source_element_address,
180 drive_element_address,
181 );
182
183 let mut sg_raw = SgRaw::new(file, 64)?;
184 sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
185
b23adfd4
TL
186 sg_raw
187 .do_command(&cmd)
697c41c5
DM
188 .map_err(|err| format_err!("load drive failed - {}", err))?;
189
190 Ok(())
191}
192
193/// Unload media from drive into a storage slot
b23adfd4 194pub fn unload(file: &mut File, to_slot: u64, drivenum: u64) -> Result<(), Error> {
697c41c5
DM
195 let status = read_element_status(file)?;
196
197 let transport_address = status.transport_address();
198 let target_element_address = status.slot_address(to_slot)?;
199 let drive_element_address = status.drive_address(drivenum)?;
200
201 let cmd = scsi_move_medium_cdb(
202 transport_address,
203 drive_element_address,
204 target_element_address,
205 );
206
207 let mut sg_raw = SgRaw::new(file, 64)?;
208 sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
209
b23adfd4
TL
210 sg_raw
211 .do_command(&cmd)
697c41c5
DM
212 .map_err(|err| format_err!("unload drive failed - {}", err))?;
213
214 Ok(())
215}
216
d1d74c43 217/// Transfer medium from one storage slot to another
697c41c5
DM
218pub fn transfer_medium<F: AsRawFd>(
219 file: &mut F,
220 from_slot: u64,
221 to_slot: u64,
222) -> Result<(), Error> {
697c41c5
DM
223 let status = read_element_status(file)?;
224
225 let transport_address = status.transport_address();
226 let source_element_address = status.slot_address(from_slot)?;
227 let target_element_address = status.slot_address(to_slot)?;
228
229 let cmd = scsi_move_medium_cdb(
230 transport_address,
231 source_element_address,
232 target_element_address,
233 );
234
235 let mut sg_raw = SgRaw::new(file, 64)?;
236 sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
237
b23adfd4
TL
238 sg_raw.do_command(&cmd).map_err(|err| {
239 format_err!(
240 "transfer medium from slot {} to slot {} failed - {}",
241 from_slot,
242 to_slot,
243 err
244 )
245 })?;
697c41c5
DM
246
247 Ok(())
248}
249
0ed40b19
DC
250#[derive(Clone, Copy)]
251enum ElementType {
6c053ffc
DC
252 MediumTransport,
253 Storage,
254 ImportExport,
255 DataTransfer,
256 DataTransferWithDVCID,
257}
258
259impl ElementType {
260 fn byte1(&self) -> u8 {
261 let volume_tag_bit = 1u8 << 4;
262 match *self {
263 ElementType::MediumTransport => volume_tag_bit | 1,
264 ElementType::Storage => volume_tag_bit | 2,
265 ElementType::ImportExport => volume_tag_bit | 3,
266 ElementType::DataTransfer => volume_tag_bit | 4,
267 // some changers cannot get voltag + dvcid at the same time
268 ElementType::DataTransferWithDVCID => 4,
269 }
270 }
271
272 fn byte6(&self) -> u8 {
273 match *self {
274 ElementType::DataTransferWithDVCID => 0b001, // Mixed=0,CurData=0,DVCID=1
b23adfd4 275 _ => 0b000, // Mixed=0,CurData=0,DVCID=0
6c053ffc
DC
276 }
277 }
0ed40b19
DC
278}
279
e1db0670 280#[allow(clippy::vec_init_then_push)]
697c41c5
DM
281fn scsi_read_element_status_cdb(
282 start_element_address: u16,
0ed40b19
DC
283 number_of_elements: u16,
284 element_type: ElementType,
697c41c5
DM
285 allocation_len: u32,
286) -> Vec<u8> {
697c41c5
DM
287 let mut cmd = Vec::new();
288 cmd.push(0xB8); // READ ELEMENT STATUS (B8h)
6c053ffc 289 cmd.push(element_type.byte1());
697c41c5
DM
290 cmd.extend(&start_element_address.to_be_bytes());
291
697c41c5 292 cmd.extend(&number_of_elements.to_be_bytes());
6c053ffc 293 cmd.push(element_type.byte6());
697c41c5
DM
294 cmd.extend(&allocation_len.to_be_bytes()[1..4]);
295 cmd.push(0);
296 cmd.push(0);
297
298 cmd
299}
300
0ed40b19
DC
301// query a single element type from the changer
302fn get_element<F: AsRawFd>(
0ed40b19
DC
303 sg_raw: &mut SgRaw<F>,
304 element_type: ElementType,
305 allocation_len: u32,
306 mut retry: bool,
307) -> Result<DecodedStatusPage, Error> {
0ed40b19
DC
308 let mut start_element_address = 0;
309 let number_of_elements: u16 = 1000; // some changers limit the query
310
311 let mut result = DecodedStatusPage {
312 last_element_address: None,
313 transports: Vec::new(),
314 drives: Vec::new(),
315 storage_slots: Vec::new(),
316 import_export_slots: Vec::new(),
317 };
318
319 loop {
b23adfd4
TL
320 let cmd = scsi_read_element_status_cdb(
321 start_element_address,
322 number_of_elements,
323 element_type,
324 allocation_len,
325 );
0ed40b19
DC
326
327 let data = execute_scsi_command(sg_raw, &cmd, "read element status (B8h)", retry)?;
328
3ab2432a 329 let page = decode_element_status_page(&data, start_element_address)?;
0ed40b19
DC
330
331 retry = false; // only retry the first command
332
333 let returned_number_of_elements = page.transports.len()
334 + page.drives.len()
335 + page.storage_slots.len()
336 + page.import_export_slots.len();
337
338 result.transports.extend(page.transports);
339 result.drives.extend(page.drives);
340 result.storage_slots.extend(page.storage_slots);
341 result.import_export_slots.extend(page.import_export_slots);
342 result.last_element_address = page.last_element_address;
343
344 if let Some(last_element_address) = page.last_element_address {
345 if last_element_address < start_element_address {
346 bail!("got strange element address");
347 }
348 if returned_number_of_elements >= (number_of_elements as usize) {
349 start_element_address = last_element_address + 1;
350 continue; // we possibly have to read additional elements
351 }
352 }
353 break;
354 }
355
356 Ok(result)
357}
358
697c41c5
DM
359/// Read element status.
360pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error> {
697c41c5
DM
361 let inquiry = scsi_inquiry(file)?;
362
363 if inquiry.peripheral_type != 8 {
364 bail!("wrong device type (not a scsi changer device)");
365 }
366
367 // first, request address assignment (used for sanity checks)
368 let setup = read_element_address_assignment(file)?;
369
370 let allocation_len: u32 = 0x10000;
371
372 let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
373 sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
374
697c41c5
DM
375 let mut drives = Vec::new();
376 let mut storage_slots = Vec::new();
377 let mut import_export_slots = Vec::new();
378 let mut transports = Vec::new();
379
3ab2432a 380 let page = get_element(&mut sg_raw, ElementType::Storage, allocation_len, true)?;
0ed40b19 381 storage_slots.extend(page.storage_slots);
16b4d784 382
b23adfd4
TL
383 let page = get_element(
384 &mut sg_raw,
385 ElementType::ImportExport,
386 allocation_len,
387 false,
388 )?;
0ed40b19 389 import_export_slots.extend(page.import_export_slots);
697c41c5 390
b23adfd4
TL
391 let page = get_element(
392 &mut sg_raw,
393 ElementType::DataTransfer,
394 allocation_len,
395 false,
396 )?;
0ed40b19 397 drives.extend(page.drives);
697c41c5 398
6c053ffc
DC
399 // get the serial + vendor + model,
400 // some changer require this to be an extra scsi command
b23adfd4
TL
401 let page = get_element(
402 &mut sg_raw,
403 ElementType::DataTransferWithDVCID,
404 allocation_len,
405 false,
406 )?;
6c053ffc
DC
407 // should be in same order and same count, but be on the safe side.
408 // there should not be too many drives normally
409 for drive in drives.iter_mut() {
410 for drive2 in &page.drives {
411 if drive2.element_address == drive.element_address {
412 drive.vendor = drive2.vendor.clone();
413 drive.model = drive2.model.clone();
414 drive.drive_serial_number = drive2.drive_serial_number.clone();
415 }
416 }
417 }
418
b23adfd4
TL
419 let page = get_element(
420 &mut sg_raw,
421 ElementType::MediumTransport,
422 allocation_len,
423 false,
424 )?;
0ed40b19 425 transports.extend(page.transports);
697c41c5 426
131d0f10
DC
427 let transport_count = setup.transport_element_count as usize;
428 let storage_count = setup.storage_element_count as usize;
429 let import_export_count = setup.import_export_element_count as usize;
430 let transfer_count = setup.transfer_element_count as usize;
431
432 if transport_count != transports.len() {
433 bail!(
434 "got wrong number of transport elements: expoected {}, got{}",
435 transport_count,
436 transports.len()
437 );
697c41c5 438 }
131d0f10
DC
439 if storage_count != storage_slots.len() {
440 bail!(
441 "got wrong number of storage elements: expected {}, got {}",
442 storage_count,
443 storage_slots.len(),
444 );
697c41c5 445 }
131d0f10
DC
446 if import_export_count != import_export_slots.len() {
447 bail!(
448 "got wrong number of import/export elements: expected {}, got {}",
449 import_export_count,
450 import_export_slots.len(),
451 );
697c41c5 452 }
131d0f10
DC
453 if transfer_count != drives.len() {
454 bail!(
455 "got wrong number of transfer elements: expected {}, got {}",
456 transfer_count,
457 drives.len(),
458 );
697c41c5
DM
459 }
460
461 // create same virtual slot order as mtx(1)
462 // - storage slots first
463 // - import export slots at the end
464 let mut slots = storage_slots;
465 slots.extend(import_export_slots);
466
b23adfd4
TL
467 let mut status = MtxStatus {
468 transports,
469 drives,
470 slots,
471 };
697c41c5
DM
472
473 // sanity checks
474 if status.drives.is_empty() {
475 bail!("no data transfer elements reported");
476 }
477 if status.slots.is_empty() {
b23adfd4 478 bail!("no storage elements reported");
697c41c5
DM
479 }
480
481 // compute virtual storage slot to element_address map
482 let mut slot_map = HashMap::new();
483 for (i, slot) in status.slots.iter().enumerate() {
484 slot_map.insert(slot.element_address, (i + 1) as u64);
485 }
486
487 // translate element addresses in loaded_lot
488 for drive in status.drives.iter_mut() {
489 if let Some(source_address) = drive.loaded_slot {
490 let source_address = source_address as u16;
dcf5a0f6 491 drive.loaded_slot = slot_map.get(&source_address).copied();
697c41c5
DM
492 }
493 }
494
495 Ok(status)
496}
497
4be47366
DC
498/// Read status and map import-export slots from config
499pub fn status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
500 let path = &config.path;
501
b23adfd4 502 let mut file = open(path).map_err(|err| format_err!("error opening '{}': {}", path, err))?;
4be47366
DC
503 let mut status = read_element_status(&mut file)
504 .map_err(|err| format_err!("error reading element status: {}", err))?;
505
9a37bd6c 506 status.mark_import_export_slots(config)?;
4be47366
DC
507
508 Ok(status)
509}
510
697c41c5
DM
511#[repr(C, packed)]
512#[derive(Endian)]
513struct ElementStatusHeader {
514 first_element_address_reported: u16,
515 number_of_elements_available: u16,
516 reserved: u8,
b23adfd4 517 byte_count_of_report_available: [u8; 3],
697c41c5
DM
518}
519
520#[repr(C, packed)]
521#[derive(Endian)]
522struct SubHeader {
523 element_type_code: u8,
524 flags: u8,
525 descriptor_length: u16,
d1d74c43 526 reserved: u8,
b23adfd4 527 byte_count_of_descriptor_data_available: [u8; 3],
697c41c5
DM
528}
529
530impl SubHeader {
697c41c5
DM
531 fn parse_optional_volume_tag<R: Read>(
532 &self,
533 reader: &mut R,
534 full: bool,
535 ) -> Result<Option<String>, Error> {
b23adfd4
TL
536 if (self.flags & 128) != 0 {
537 // has PVolTag
62a0e190 538 let tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?;
697c41c5
DM
539 if full {
540 let volume_tag = scsi_ascii_to_string(&tmp);
541 return Ok(Some(volume_tag));
542 }
543 }
544 Ok(None)
545 }
546
547 // AFAIK, tape changer do not use AlternateVolumeTag
548 // but parse anyways, just to be sure
b23adfd4
TL
549 fn skip_alternate_volume_tag<R: Read>(&self, reader: &mut R) -> Result<Option<String>, Error> {
550 if (self.flags & 64) != 0 {
551 // has AVolTag
62a0e190 552 let _tmp = reader.read_exact_allocated(SCSI_VOLUME_TAG_LEN)?;
697c41c5
DM
553 }
554
555 Ok(None)
556 }
557}
558
559#[repr(C, packed)]
560#[derive(Endian)]
b23adfd4
TL
561struct TransportDescriptor {
562 // Robot/Griper
697c41c5
DM
563 element_address: u16,
564 flags1: u8,
565 reserved_3: u8,
566 additional_sense_code: u8,
567 additional_sense_code_qualifier: u8,
b23adfd4 568 reserved_6: [u8; 3],
697c41c5
DM
569 flags2: u8,
570 source_storage_element_address: u16,
571 // volume tag and Mixed media descriptor follows (depends on flags)
572}
573
574#[repr(C, packed)]
575#[derive(Endian)]
b23adfd4
TL
576struct TransferDescriptor {
577 // Tape drive
697c41c5
DM
578 element_address: u16,
579 flags1: u8,
580 reserved_3: u8,
581 additional_sense_code: u8,
582 additional_sense_code_qualifier: u8,
583 id_valid: u8,
584 scsi_bus_address: u8,
585 reserved_8: u8,
586 flags2: u8,
587 source_storage_element_address: u16,
588 // volume tag, drive identifier and Mixed media descriptor follows
589 // (depends on flags)
590}
591
592#[repr(C, packed)]
593#[derive(Endian)]
b23adfd4
TL
594struct DvcidHead {
595 // Drive Identifier Header
697c41c5
DM
596 code_set: u8,
597 identifier_type: u8,
598 reserved: u8,
599 identifier_len: u8,
600 // Identifier follows
601}
602
603#[repr(C, packed)]
604#[derive(Endian)]
b23adfd4
TL
605struct StorageDescriptor {
606 // Mail Slot
697c41c5
DM
607 element_address: u16,
608 flags1: u8,
609 reserved_3: u8,
610 additional_sense_code: u8,
611 additional_sense_code_qualifier: u8,
b23adfd4 612 reserved_6: [u8; 3],
697c41c5
DM
613 flags2: u8,
614 source_storage_element_address: u16,
615 // volume tag and Mixed media descriptor follows (depends on flags)
616}
617
618struct DecodedStatusPage {
619 last_element_address: Option<u16>,
620 transports: Vec<TransportElementStatus>,
621 drives: Vec<DriveStatus>,
622 storage_slots: Vec<StorageElementStatus>,
623 import_export_slots: Vec<StorageElementStatus>,
624}
625
626fn create_element_status(full: bool, volume_tag: Option<String>) -> ElementStatus {
627 if full {
628 if let Some(volume_tag) = volume_tag {
629 ElementStatus::VolumeTag(volume_tag)
630 } else {
631 ElementStatus::Full
632 }
633 } else {
634 ElementStatus::Empty
635 }
636}
637
02631056
DC
638struct DvcidInfo {
639 vendor: Option<String>,
640 model: Option<String>,
641 serial: Option<String>,
642}
643
644fn decode_dvcid_info<R: Read>(reader: &mut R) -> Result<DvcidInfo, Error> {
645 let dvcid: DvcidHead = unsafe { reader.read_be_value()? };
646
647 let (serial, vendor, model) = match (dvcid.code_set, dvcid.identifier_type) {
b23adfd4
TL
648 (2, 0) => {
649 // Serial number only (Quantum Superloader3 uses this)
02631056
DC
650 let serial = reader.read_exact_allocated(dvcid.identifier_len as usize)?;
651 let serial = scsi_ascii_to_string(&serial);
652 (Some(serial), None, None)
653 }
654 (2, 1) => {
655 if dvcid.identifier_len != 34 {
656 bail!("got wrong DVCID length");
657 }
658 let vendor = reader.read_exact_allocated(8)?;
659 let vendor = scsi_ascii_to_string(&vendor);
660 let model = reader.read_exact_allocated(16)?;
661 let model = scsi_ascii_to_string(&model);
662 let serial = reader.read_exact_allocated(10)?;
663 let serial = scsi_ascii_to_string(&serial);
664 (Some(serial), Some(vendor), Some(model))
665 }
666 _ => (None, None, None),
667 };
668
669 Ok(DvcidInfo {
670 vendor,
671 model,
672 serial,
673 })
674}
675
697c41c5 676fn decode_element_status_page(
697c41c5
DM
677 data: &[u8],
678 start_element_address: u16,
679) -> Result<DecodedStatusPage, Error> {
6ef1b649 680 proxmox_lang::try_block!({
697c41c5
DM
681 let mut result = DecodedStatusPage {
682 last_element_address: None,
683 transports: Vec::new(),
684 drives: Vec::new(),
685 storage_slots: Vec::new(),
686 import_export_slots: Vec::new(),
687 };
688
dcf5a0f6 689 let mut reader = data;
697c41c5
DM
690
691 let head: ElementStatusHeader = unsafe { reader.read_be_value()? };
692
693 if head.number_of_elements_available == 0 {
694 return Ok(result);
695 }
696
697 if head.first_element_address_reported < start_element_address {
698 bail!("got wrong first_element_address_reported"); // sanity check
699 }
700
13e13d83
DC
701 let len = head.byte_count_of_report_available;
702 let len = ((len[0] as usize) << 16) + ((len[1] as usize) << 8) + (len[2] as usize);
703
704 if len < reader.len() {
705 reader = &reader[..len];
706 } else if len > reader.len() {
b23adfd4
TL
707 bail!(
708 "wrong amount of data: expected {}, got {}",
709 len,
710 reader.len()
711 );
13e13d83
DC
712 }
713
697c41c5
DM
714 loop {
715 if reader.is_empty() {
716 break;
717 }
718
719 let subhead: SubHeader = unsafe { reader.read_be_value()? };
720
721 let len = subhead.byte_count_of_descriptor_data_available;
722 let mut len = ((len[0] as usize) << 16) + ((len[1] as usize) << 8) + (len[2] as usize);
723 if len > reader.len() {
724 len = reader.len();
725 }
726
727 let descr_data = reader.read_exact_allocated(len)?;
697c41c5 728
42b01017
DC
729 let descr_len = subhead.descriptor_length as usize;
730
731 if descr_len == 0 {
732 bail!("got elements, but descriptor length 0");
733 }
734
735 for descriptor in descr_data.chunks_exact(descr_len) {
dcf5a0f6 736 let mut reader = descriptor;
71e83e1b 737
697c41c5
DM
738 match subhead.element_type_code {
739 1 => {
c1feb447 740 let desc: TransportDescriptor = unsafe { reader.read_be_value()? };
697c41c5
DM
741
742 let full = (desc.flags1 & 1) != 0;
743 let volume_tag = subhead.parse_optional_volume_tag(&mut reader, full)?;
744
745 subhead.skip_alternate_volume_tag(&mut reader)?;
746
697c41c5
DM
747 result.last_element_address = Some(desc.element_address);
748
749 let status = TransportElementStatus {
750 status: create_element_status(full, volume_tag),
751 element_address: desc.element_address,
752 };
753 result.transports.push(status);
754 }
755 2 | 3 => {
756 let desc: StorageDescriptor = unsafe { reader.read_be_value()? };
757
758 let full = (desc.flags1 & 1) != 0;
759 let volume_tag = subhead.parse_optional_volume_tag(&mut reader, full)?;
760
761 subhead.skip_alternate_volume_tag(&mut reader)?;
762
697c41c5
DM
763 result.last_element_address = Some(desc.element_address);
764
765 if subhead.element_type_code == 3 {
766 let status = StorageElementStatus {
767 import_export: true,
768 status: create_element_status(full, volume_tag),
769 element_address: desc.element_address,
770 };
771 result.import_export_slots.push(status);
772 } else {
773 let status = StorageElementStatus {
774 import_export: false,
775 status: create_element_status(full, volume_tag),
776 element_address: desc.element_address,
777 };
778 result.storage_slots.push(status);
779 }
780 }
781 4 => {
782 let desc: TransferDescriptor = unsafe { reader.read_be_value()? };
783
b23adfd4
TL
784 let loaded_slot = if (desc.flags2 & 128) != 0 {
785 // SValid
697c41c5
DM
786 Some(desc.source_storage_element_address as u64)
787 } else {
788 None
789 };
790
791 let full = (desc.flags1 & 1) != 0;
792 let volume_tag = subhead.parse_optional_volume_tag(&mut reader, full)?;
793
794 subhead.skip_alternate_volume_tag(&mut reader)?;
795
02631056
DC
796 let dvcid = decode_dvcid_info(&mut reader).unwrap_or(DvcidInfo {
797 vendor: None,
798 model: None,
799 serial: None,
800 });
697c41c5
DM
801
802 result.last_element_address = Some(desc.element_address);
803
804 let drive = DriveStatus {
805 loaded_slot,
806 status: create_element_status(full, volume_tag),
02631056
DC
807 drive_serial_number: dvcid.serial,
808 vendor: dvcid.vendor,
809 model: dvcid.model,
697c41c5
DM
810 element_address: desc.element_address,
811 };
812 result.drives.push(drive);
813 }
814 code => bail!("got unknown element type code {}", code),
815 }
816 }
817 }
818
819 Ok(result)
b23adfd4
TL
820 })
821 .map_err(|err: Error| format_err!("decode element status failed - {}", err))
697c41c5
DM
822}
823
824/// Open the device for read/write, returns the file handle
825pub fn open<P: AsRef<Path>>(path: P) -> Result<File, Error> {
b23adfd4 826 let file = OpenOptions::new().read(true).write(true).open(path)?;
697c41c5
DM
827
828 Ok(file)
829}
4f57f4ad
DC
830
831#[cfg(test)]
832mod test {
4f57f4ad 833 use super::*;
b23adfd4 834 use anyhow::Error;
4f57f4ad
DC
835
836 struct StorageDesc {
837 address: u16,
838 pvoltag: Option<String>,
839 }
840
841 fn build_element_status_page(
842 descriptors: Vec<StorageDesc>,
843 trailing: &[u8],
844 element_type: u8,
845 ) -> Vec<u8> {
b23adfd4
TL
846 let descs: Vec<Vec<u8>> = descriptors
847 .iter()
848 .map(|desc| build_storage_descriptor(desc, trailing))
849 .collect();
4f57f4ad
DC
850
851 let (desc_len, address) = if let Some(el) = descs.get(0) {
852 (el.len() as u16, descriptors[0].address)
853 } else {
854 (0u16, 0u16)
855 };
856
857 let descriptor_byte_count = desc_len * descs.len() as u16;
858 let byte_count = 8 + descriptor_byte_count;
859
860 let mut res = Vec::new();
861
862 res.extend_from_slice(&address.to_be_bytes());
863 res.extend_from_slice(&(descs.len() as u16).to_be_bytes());
864 res.push(0);
865 let byte_count = byte_count as u32;
866 res.extend_from_slice(&byte_count.to_be_bytes()[1..]);
867
868 res.push(element_type);
869 res.push(0x80);
870 res.extend_from_slice(&desc_len.to_be_bytes());
871 res.push(0);
872 let descriptor_byte_count = descriptor_byte_count as u32;
873 res.extend_from_slice(&descriptor_byte_count.to_be_bytes()[1..]);
874
875 for desc in descs {
876 res.extend_from_slice(&desc);
877 }
878
879 res.extend_from_slice(trailing);
880
881 res
882 }
883
b23adfd4 884 fn build_storage_descriptor(desc: &StorageDesc, trailing: &[u8]) -> Vec<u8> {
4f57f4ad
DC
885 let mut res = Vec::new();
886 res.push(((desc.address >> 8) & 0xFF) as u8);
887 res.push((desc.address & 0xFF) as u8);
888 if desc.pvoltag.is_some() {
889 res.push(0x01); // full
890 } else {
891 res.push(0x00); // full
892 }
893
b23adfd4 894 res.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0x80]);
4f57f4ad
DC
895 res.push(((desc.address >> 8) & 0xFF) as u8);
896 res.push((desc.address & 0xFF) as u8);
897
898 if let Some(voltag) = &desc.pvoltag {
899 res.extend_from_slice(voltag.as_bytes());
900 let rem = SCSI_VOLUME_TAG_LEN - voltag.as_bytes().len();
901 if rem > 0 {
902 res.resize(res.len() + rem, 0);
903 }
904 }
905
906 res.extend_from_slice(trailing);
907
908 res
909 }
910
911 #[test]
912 fn status_page_valid() -> Result<(), Error> {
913 let descs = vec![
914 StorageDesc {
915 address: 0,
916 pvoltag: Some("0123456789".to_string()),
917 },
918 StorageDesc {
919 address: 1,
920 pvoltag: Some("1234567890".to_string()),
921 },
922 ];
923 let test_data = build_element_status_page(descs, &[], 0x2);
924 let page = decode_element_status_page(&test_data, 0)?;
925 assert_eq!(page.storage_slots.len(), 2);
926 Ok(())
927 }
928
929 #[test]
930 fn status_page_too_short() -> Result<(), Error> {
931 let descs = vec![
932 StorageDesc {
933 address: 0,
934 pvoltag: Some("0123456789".to_string()),
935 },
936 StorageDesc {
937 address: 1,
938 pvoltag: Some("1234567890".to_string()),
939 },
940 ];
941 let test_data = build_element_status_page(descs, &[], 0x2);
942 let len = test_data.len();
943 let res = decode_element_status_page(&test_data[..(len - 10)], 0);
944 assert!(res.is_err());
945 Ok(())
946 }
947
948 #[test]
949 fn status_page_too_large() -> Result<(), Error> {
950 let descs = vec![
951 StorageDesc {
952 address: 0,
953 pvoltag: Some("0123456789".to_string()),
954 },
955 StorageDesc {
956 address: 1,
957 pvoltag: Some("1234567890".to_string()),
958 },
959 ];
b23adfd4 960 let test_data = build_element_status_page(descs, &[0, 0, 0, 0, 0], 0x2);
4f57f4ad
DC
961 let page = decode_element_status_page(&test_data, 0)?;
962 assert_eq!(page.storage_slots.len(), 2);
963 Ok(())
964 }
965}