1 //! SCSI changer implementation using libsgutil2
3 use std
::os
::unix
::prelude
::AsRawFd
;
5 use std
::collections
::HashMap
;
7 use std
::fs
::{OpenOptions, File}
;
9 use anyhow
::{bail, format_err, Error}
;
10 use endian_trait
::Endian
;
12 use proxmox
::tools
::io
::ReadExt
;
14 use pbs_api_types
::ScsiTapeChanger
;
22 TransportElementStatus
,
35 const SCSI_CHANGER_DEFAULT_TIMEOUT
: usize = 60*5; // 5 minutes
36 const SCSI_VOLUME_TAG_LEN
: usize = 36;
38 /// Initialize element status (Inventory)
39 pub fn initialize_element_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<(), Error
> {
41 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
43 // like mtx(1), set a very long timeout (30 minutes)
44 sg_raw
.set_timeout(30*60);
46 let mut cmd
= Vec
::new();
47 cmd
.extend(&[0x07, 0, 0, 0, 0, 0]); // INITIALIZE ELEMENT STATUS (07h)
49 sg_raw
.do_command(&cmd
)
50 .map_err(|err
| format_err
!("initializte element status (07h) failed - {}", err
))?
;
57 struct AddressAssignmentPage
{
61 block_descriptor_len
: 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,
77 /// Execute scsi commands, optionally repeat the command until
78 /// successful or timeout (sleep 1 second between invovations)
80 /// Timeout is 5 seconds. If the device reports "Not Ready - becoming
81 /// ready", we wait up to 5 minutes.
83 /// Skipped errors are printed on stderr.
84 fn execute_scsi_command
<F
: AsRawFd
>(
85 sg_raw
: &mut SgRaw
<F
>,
89 ) -> Result
<Vec
<u8>, Error
> {
91 let start
= std
::time
::SystemTime
::now();
93 let mut last_msg
: Option
<String
> = None
;
95 let mut timeout
= std
::time
::Duration
::new(5, 0); // short timeout by default
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
),
102 let msg
= err
.to_string();
103 if let Some(ref last
) = last_msg
{
105 eprintln
!("{}", err
);
106 last_msg
= Some(msg
);
109 eprintln
!("{}", err
);
110 last_msg
= Some(msg
);
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);
121 if start
.elapsed()?
> timeout
{
122 bail
!("{} failed: {}", error_prefix
, err
);
125 std
::thread
::sleep(std
::time
::Duration
::new(1, 0));
126 continue; // try again
133 fn read_element_address_assignment
<F
: AsRawFd
>(
135 ) -> Result
<AddressAssignmentPage
, Error
> {
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
);
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
146 cmd
.push(allocation_len
); // allocation len
147 cmd
.push(0); //control
149 let data
= execute_scsi_command(&mut sg_raw
, &cmd
, "read element address assignment", true)?
;
151 proxmox
::try_block
!({
152 let mut reader
= &data
[..];
153 let page
: AddressAssignmentPage
= unsafe { reader.read_be_value()? }
;
155 if page
.data_len
!= 23 {
156 bail
!("got unexpected page len ({} != 23)", page
.data_len
);
160 }).map_err(|err
: Error
| format_err
!("decode element address assignment page failed - {}", err
))
163 fn scsi_move_medium_cdb(
164 medium_transport_address
: u16,
165 source_element_address
: u16,
166 destination_element_address
: u16,
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
183 /// Load media from storage slot into drive
188 ) -> Result
<(), Error
> {
189 let status
= read_element_status(file
)?
;
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
)?
;
195 let cmd
= scsi_move_medium_cdb(
197 source_element_address
,
198 drive_element_address
,
201 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
202 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
204 sg_raw
.do_command(&cmd
)
205 .map_err(|err
| format_err
!("load drive failed - {}", err
))?
;
210 /// Unload media from drive into a storage slot
215 ) -> Result
<(), Error
> {
217 let status
= read_element_status(file
)?
;
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
)?
;
223 let cmd
= scsi_move_medium_cdb(
225 drive_element_address
,
226 target_element_address
,
229 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
230 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
232 sg_raw
.do_command(&cmd
)
233 .map_err(|err
| format_err
!("unload drive failed - {}", err
))?
;
238 /// Transfer medium from one storage slot to another
239 pub fn transfer_medium
<F
: AsRawFd
>(
243 ) -> Result
<(), Error
> {
245 let status
= read_element_status(file
)?
;
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
)?
;
251 let cmd
= scsi_move_medium_cdb(
253 source_element_address
,
254 target_element_address
,
257 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
258 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
260 sg_raw
.do_command(&cmd
)
262 format_err
!("transfer medium from slot {} to slot {} failed - {}",
263 from_slot
, to_slot
, err
)
269 #[derive(Clone, Copy)]
275 DataTransferWithDVCID
,
279 fn byte1(&self) -> u8 {
280 let volume_tag_bit
= 1u8 << 4;
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,
291 fn byte6(&self) -> u8 {
293 ElementType
::DataTransferWithDVCID
=> 0b001, // Mixed=0,CurData=0,DVCID=1
294 _
=> 0b000, // Mixed=0,CurData=0,DVCID=0
299 fn scsi_read_element_status_cdb(
300 start_element_address
: u16,
301 number_of_elements
: u16,
302 element_type
: ElementType
,
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());
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]);
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
,
326 ) -> Result
<DecodedStatusPage
, Error
> {
328 let mut start_element_address
= 0;
329 let number_of_elements
: u16 = 1000; // some changers limit the query
331 let mut result
= DecodedStatusPage
{
332 last_element_address
: None
,
333 transports
: Vec
::new(),
335 storage_slots
: Vec
::new(),
336 import_export_slots
: Vec
::new(),
340 let cmd
= scsi_read_element_status_cdb(start_element_address
, number_of_elements
, element_type
, allocation_len
);
342 let data
= execute_scsi_command(sg_raw
, &cmd
, "read element status (B8h)", retry
)?
;
344 let page
= decode_element_status_page(&data
, start_element_address
)?
;
346 retry
= false; // only retry the first command
348 let returned_number_of_elements
= page
.transports
.len()
350 + page
.storage_slots
.len()
351 + page
.import_export_slots
.len();
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
;
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");
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
374 /// Read element status.
375 pub fn read_element_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<MtxStatus
, Error
> {
377 let inquiry
= scsi_inquiry(file
)?
;
379 if inquiry
.peripheral_type
!= 8 {
380 bail
!("wrong device type (not a scsi changer device)");
383 // first, request address assignment (used for sanity checks)
384 let setup
= read_element_address_assignment(file
)?
;
386 let allocation_len
: u32 = 0x10000;
388 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
389 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
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();
396 let page
= get_element(&mut sg_raw
, ElementType
::Storage
, allocation_len
, true)?
;
397 storage_slots
.extend(page
.storage_slots
);
399 let page
= get_element(&mut sg_raw
, ElementType
::ImportExport
, allocation_len
, false)?
;
400 import_export_slots
.extend(page
.import_export_slots
);
402 let page
= get_element(&mut sg_raw
, ElementType
::DataTransfer
, allocation_len
, false)?
;
403 drives
.extend(page
.drives
);
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();
420 let page
= get_element(&mut sg_raw
, ElementType
::MediumTransport
, allocation_len
, false)?
;
421 transports
.extend(page
.transports
);
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;
428 if transport_count
!= transports
.len() {
430 "got wrong number of transport elements: expoected {}, got{}",
435 if storage_count
!= storage_slots
.len() {
437 "got wrong number of storage elements: expected {}, got {}",
442 if import_export_count
!= import_export_slots
.len() {
444 "got wrong number of import/export elements: expected {}, got {}",
446 import_export_slots
.len(),
449 if transfer_count
!= drives
.len() {
451 "got wrong number of transfer elements: expected {}, got {}",
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
);
463 let mut status
= MtxStatus { transports, drives, slots }
;
466 if status
.drives
.is_empty() {
467 bail
!("no data transfer elements reported");
469 if status
.slots
.is_empty() {
470 bail
!("no storage elements reported");
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);
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
);
490 /// Read status and map import-export slots from config
491 pub fn status(config
: &ScsiTapeChanger
) -> Result
<MtxStatus
, Error
> {
492 let path
= &config
.path
;
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
))?
;
499 status
.mark_import_export_slots(&config
)?
;
507 struct ElementStatusHeader
{
508 first_element_address_reported
: u16,
509 number_of_elements_available
: u16,
511 byte_count_of_report_available
: [u8;3],
517 element_type_code
: u8,
519 descriptor_length
: u16,
521 byte_count_of_descriptor_data_available
: [u8;3],
526 fn parse_optional_volume_tag
<R
: Read
>(
530 ) -> Result
<Option
<String
>, Error
> {
532 if (self.flags
& 128) != 0 { // has PVolTag
533 let tmp
= reader
.read_exact_allocated(SCSI_VOLUME_TAG_LEN
)?
;
535 let volume_tag
= scsi_ascii_to_string(&tmp
);
536 return Ok(Some(volume_tag
));
542 // AFAIK, tape changer do not use AlternateVolumeTag
543 // but parse anyways, just to be sure
544 fn skip_alternate_volume_tag
<R
: Read
>(
547 ) -> Result
<Option
<String
>, Error
> {
549 if (self.flags
& 64) != 0 { // has AVolTag
550 let _tmp
= reader
.read_exact_allocated(SCSI_VOLUME_TAG_LEN
)?
;
559 struct TransportDescriptor
{ // Robot/Griper
560 element_address
: u16,
563 additional_sense_code
: u8,
564 additional_sense_code_qualifier
: u8,
567 source_storage_element_address
: u16,
568 // volume tag and Mixed media descriptor follows (depends on flags)
573 struct TransferDescriptor
{ // Tape drive
574 element_address
: u16,
577 additional_sense_code
: u8,
578 additional_sense_code_qualifier
: u8,
580 scsi_bus_address
: u8,
583 source_storage_element_address
: u16,
584 // volume tag, drive identifier and Mixed media descriptor follows
585 // (depends on flags)
590 struct DvcidHead
{ // Drive Identifier Header
595 // Identifier follows
600 struct StorageDescriptor
{ // Mail Slot
601 element_address
: u16,
604 additional_sense_code
: u8,
605 additional_sense_code_qualifier
: u8,
608 source_storage_element_address
: u16,
609 // volume tag and Mixed media descriptor follows (depends on flags)
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
>,
620 fn create_element_status(full
: bool
, volume_tag
: Option
<String
>) -> ElementStatus
{
622 if let Some(volume_tag
) = volume_tag
{
623 ElementStatus
::VolumeTag(volume_tag
)
633 vendor
: Option
<String
>,
634 model
: Option
<String
>,
635 serial
: Option
<String
>,
638 fn decode_dvcid_info
<R
: Read
>(reader
: &mut R
) -> Result
<DvcidInfo
, Error
> {
639 let dvcid
: DvcidHead
= unsafe { reader.read_be_value()? }
;
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
)
648 if dvcid
.identifier_len
!= 34 {
649 bail
!("got wrong DVCID length");
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
))
659 _
=> (None
, None
, None
),
669 fn decode_element_status_page(
671 start_element_address
: u16,
672 ) -> Result
<DecodedStatusPage
, Error
> {
674 proxmox
::try_block
!({
676 let mut result
= DecodedStatusPage
{
677 last_element_address
: None
,
678 transports
: Vec
::new(),
680 storage_slots
: Vec
::new(),
681 import_export_slots
: Vec
::new(),
684 let mut reader
= &data
[..];
686 let head
: ElementStatusHeader
= unsafe { reader.read_be_value()? }
;
688 if head
.number_of_elements_available
== 0 {
692 if head
.first_element_address_reported
< start_element_address
{
693 bail
!("got wrong first_element_address_reported"); // sanity check
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);
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());
706 if reader
.is_empty() {
710 let subhead
: SubHeader
= unsafe { reader.read_be_value()? }
;
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() {
718 let descr_data
= reader
.read_exact_allocated(len
)?
;
720 let descr_len
= subhead
.descriptor_length
as usize;
723 bail
!("got elements, but descriptor length 0");
726 for descriptor
in descr_data
.chunks_exact(descr_len
) {
727 let mut reader
= &descriptor
[..];
729 match subhead
.element_type_code
{
731 let desc
: TransportDescriptor
= unsafe { reader.read_be_value()? }
;
733 let full
= (desc
.flags1
& 1) != 0;
734 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
736 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
738 result
.last_element_address
= Some(desc
.element_address
);
740 let status
= TransportElementStatus
{
741 status
: create_element_status(full
, volume_tag
),
742 element_address
: desc
.element_address
,
744 result
.transports
.push(status
);
747 let desc
: StorageDescriptor
= unsafe { reader.read_be_value()? }
;
749 let full
= (desc
.flags1
& 1) != 0;
750 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
752 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
754 result
.last_element_address
= Some(desc
.element_address
);
756 if subhead
.element_type_code
== 3 {
757 let status
= StorageElementStatus
{
759 status
: create_element_status(full
, volume_tag
),
760 element_address
: desc
.element_address
,
762 result
.import_export_slots
.push(status
);
764 let status
= StorageElementStatus
{
765 import_export
: false,
766 status
: create_element_status(full
, volume_tag
),
767 element_address
: desc
.element_address
,
769 result
.storage_slots
.push(status
);
773 let desc
: TransferDescriptor
= unsafe { reader.read_be_value()? }
;
775 let loaded_slot
= if (desc
.flags2
& 128) != 0 { // SValid
776 Some(desc
.source_storage_element_address
as u64)
781 let full
= (desc
.flags1
& 1) != 0;
782 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
784 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
786 let dvcid
= decode_dvcid_info(&mut reader
).unwrap_or(DvcidInfo
{
792 result
.last_element_address
= Some(desc
.element_address
);
794 let drive
= DriveStatus
{
796 status
: create_element_status(full
, volume_tag
),
797 drive_serial_number
: dvcid
.serial
,
798 vendor
: dvcid
.vendor
,
800 element_address
: desc
.element_address
,
802 result
.drives
.push(drive
);
804 code
=> bail
!("got unknown element type code {}", code
),
810 }).map_err(|err
: Error
| format_err
!("decode element status failed - {}", err
))
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()
830 pvoltag
: Option
<String
>,
833 fn build_element_status_page(
834 descriptors
: Vec
<StorageDesc
>,
838 let descs
: Vec
<Vec
<u8>> = descriptors
.iter().map(|desc
| {
839 build_storage_descriptor(&desc
, trailing
)
842 let (desc_len
, address
) = if let Some(el
) = descs
.get(0) {
843 (el
.len() as u16, descriptors
[0].address
)
848 let descriptor_byte_count
= desc_len
* descs
.len() as u16;
849 let byte_count
= 8 + descriptor_byte_count
;
851 let mut res
= Vec
::new();
853 res
.extend_from_slice(&address
.to_be_bytes());
854 res
.extend_from_slice(&(descs
.len() as u16).to_be_bytes());
856 let byte_count
= byte_count
as u32;
857 res
.extend_from_slice(&byte_count
.to_be_bytes()[1..]);
859 res
.push(element_type
);
861 res
.extend_from_slice(&desc_len
.to_be_bytes());
863 let descriptor_byte_count
= descriptor_byte_count
as u32;
864 res
.extend_from_slice(&descriptor_byte_count
.to_be_bytes()[1..]);
867 res
.extend_from_slice(&desc
);
870 res
.extend_from_slice(trailing
);
875 fn build_storage_descriptor(
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
885 res
.push(0x00); // full
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);
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();
896 res
.resize(res
.len() + rem
, 0);
900 res
.extend_from_slice(trailing
);
906 fn status_page_valid() -> Result
<(), Error
> {
910 pvoltag
: Some("0123456789".to_string()),
914 pvoltag
: Some("1234567890".to_string()),
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);
924 fn status_page_too_short() -> Result
<(), Error
> {
928 pvoltag
: Some("0123456789".to_string()),
932 pvoltag
: Some("1234567890".to_string()),
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());
943 fn status_page_too_large() -> Result
<(), Error
> {
947 pvoltag
: Some("0123456789".to_string()),
951 pvoltag
: Some("1234567890".to_string()),
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);