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