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