1 //! SCSI changer implementation using libsgutil2
2 use std
::os
::unix
::prelude
::AsRawFd
;
4 use std
::collections
::HashMap
;
6 use std
::fs
::{OpenOptions, File}
;
8 use anyhow
::{bail, format_err, Error}
;
9 use endian_trait
::Endian
;
11 use proxmox
::tools
::io
::ReadExt
;
13 use pbs_api_types
::ScsiTapeChanger
;
16 ElementStatus
,MtxStatus
,TransportElementStatus
,DriveStatus
,StorageElementStatus
,
26 const SCSI_CHANGER_DEFAULT_TIMEOUT
: usize = 60*5; // 5 minutes
27 const SCSI_VOLUME_TAG_LEN
: usize = 36;
29 /// Initialize element status (Inventory)
30 pub fn initialize_element_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<(), Error
> {
32 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
34 // like mtx(1), set a very long timeout (30 minutes)
35 sg_raw
.set_timeout(30*60);
37 let mut cmd
= Vec
::new();
38 cmd
.extend(&[0x07, 0, 0, 0, 0, 0]); // INITIALIZE ELEMENT STATUS (07h)
40 sg_raw
.do_command(&cmd
)
41 .map_err(|err
| format_err
!("initializte element status (07h) failed - {}", err
))?
;
48 struct AddressAssignmentPage
{
52 block_descriptor_len
: 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,
68 /// Execute scsi commands, optionally repeat the command until
69 /// successful or timeout (sleep 1 second between invovations)
71 /// Timeout is 5 seconds. If the device reports "Not Ready - becoming
72 /// ready", we wait up to 5 minutes.
74 /// Skipped errors are printed on stderr.
75 fn execute_scsi_command
<F
: AsRawFd
>(
76 sg_raw
: &mut SgRaw
<F
>,
80 ) -> Result
<Vec
<u8>, Error
> {
82 let start
= std
::time
::SystemTime
::now();
84 let mut last_msg
: Option
<String
> = None
;
86 let mut timeout
= std
::time
::Duration
::new(5, 0); // short timeout by default
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
),
93 let msg
= err
.to_string();
94 if let Some(ref last
) = last_msg
{
100 eprintln
!("{}", err
);
101 last_msg
= Some(msg
);
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);
112 if start
.elapsed()?
> timeout
{
113 bail
!("{} failed: {}", error_prefix
, err
);
116 std
::thread
::sleep(std
::time
::Duration
::new(1, 0));
117 continue; // try again
124 fn read_element_address_assignment
<F
: AsRawFd
>(
126 ) -> Result
<AddressAssignmentPage
, Error
> {
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
);
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
137 cmd
.push(allocation_len
); // allocation len
138 cmd
.push(0); //control
140 let data
= execute_scsi_command(&mut sg_raw
, &cmd
, "read element address assignment", true)?
;
142 proxmox
::try_block
!({
143 let mut reader
= &data
[..];
144 let page
: AddressAssignmentPage
= unsafe { reader.read_be_value()? }
;
146 if page
.data_len
!= 23 {
147 bail
!("got unexpected page len ({} != 23)", page
.data_len
);
151 }).map_err(|err
: Error
| format_err
!("decode element address assignment page failed - {}", err
))
154 fn scsi_move_medium_cdb(
155 medium_transport_address
: u16,
156 source_element_address
: u16,
157 destination_element_address
: u16,
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
174 /// Load media from storage slot into drive
179 ) -> Result
<(), Error
> {
180 let status
= read_element_status(file
)?
;
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
)?
;
186 let cmd
= scsi_move_medium_cdb(
188 source_element_address
,
189 drive_element_address
,
192 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
193 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
195 sg_raw
.do_command(&cmd
)
196 .map_err(|err
| format_err
!("load drive failed - {}", err
))?
;
201 /// Unload media from drive into a storage slot
206 ) -> Result
<(), Error
> {
208 let status
= read_element_status(file
)?
;
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
)?
;
214 let cmd
= scsi_move_medium_cdb(
216 drive_element_address
,
217 target_element_address
,
220 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
221 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
223 sg_raw
.do_command(&cmd
)
224 .map_err(|err
| format_err
!("unload drive failed - {}", err
))?
;
229 /// Transfer medium from one storage slot to another
230 pub fn transfer_medium
<F
: AsRawFd
>(
234 ) -> Result
<(), Error
> {
236 let status
= read_element_status(file
)?
;
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
)?
;
242 let cmd
= scsi_move_medium_cdb(
244 source_element_address
,
245 target_element_address
,
248 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
249 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
251 sg_raw
.do_command(&cmd
)
253 format_err
!("transfer medium from slot {} to slot {} failed - {}",
254 from_slot
, to_slot
, err
)
260 #[derive(Clone, Copy)]
266 DataTransferWithDVCID
,
270 fn byte1(&self) -> u8 {
271 let volume_tag_bit
= 1u8 << 4;
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,
282 fn byte6(&self) -> u8 {
284 ElementType
::DataTransferWithDVCID
=> 0b001, // Mixed=0,CurData=0,DVCID=1
285 _
=> 0b000, // Mixed=0,CurData=0,DVCID=0
290 fn scsi_read_element_status_cdb(
291 start_element_address
: u16,
292 number_of_elements
: u16,
293 element_type
: ElementType
,
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());
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]);
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
,
317 ) -> Result
<DecodedStatusPage
, Error
> {
319 let mut start_element_address
= 0;
320 let number_of_elements
: u16 = 1000; // some changers limit the query
322 let mut result
= DecodedStatusPage
{
323 last_element_address
: None
,
324 transports
: Vec
::new(),
326 storage_slots
: Vec
::new(),
327 import_export_slots
: Vec
::new(),
331 let cmd
= scsi_read_element_status_cdb(start_element_address
, number_of_elements
, element_type
, allocation_len
);
333 let data
= execute_scsi_command(sg_raw
, &cmd
, "read element status (B8h)", retry
)?
;
335 let page
= decode_element_status_page(&data
, start_element_address
)?
;
337 retry
= false; // only retry the first command
339 let returned_number_of_elements
= page
.transports
.len()
341 + page
.storage_slots
.len()
342 + page
.import_export_slots
.len();
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
;
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");
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
365 /// Read element status.
366 pub fn read_element_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<MtxStatus
, Error
> {
368 let inquiry
= scsi_inquiry(file
)?
;
370 if inquiry
.peripheral_type
!= 8 {
371 bail
!("wrong device type (not a scsi changer device)");
374 // first, request address assignment (used for sanity checks)
375 let setup
= read_element_address_assignment(file
)?
;
377 let allocation_len
: u32 = 0x10000;
379 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
380 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
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();
387 let page
= get_element(&mut sg_raw
, ElementType
::Storage
, allocation_len
, true)?
;
388 storage_slots
.extend(page
.storage_slots
);
390 let page
= get_element(&mut sg_raw
, ElementType
::ImportExport
, allocation_len
, false)?
;
391 import_export_slots
.extend(page
.import_export_slots
);
393 let page
= get_element(&mut sg_raw
, ElementType
::DataTransfer
, allocation_len
, false)?
;
394 drives
.extend(page
.drives
);
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();
411 let page
= get_element(&mut sg_raw
, ElementType
::MediumTransport
, allocation_len
, false)?
;
412 transports
.extend(page
.transports
);
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;
419 if transport_count
!= transports
.len() {
421 "got wrong number of transport elements: expoected {}, got{}",
426 if storage_count
!= storage_slots
.len() {
428 "got wrong number of storage elements: expected {}, got {}",
433 if import_export_count
!= import_export_slots
.len() {
435 "got wrong number of import/export elements: expected {}, got {}",
437 import_export_slots
.len(),
440 if transfer_count
!= drives
.len() {
442 "got wrong number of transfer elements: expected {}, got {}",
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
);
454 let mut status
= MtxStatus { transports, drives, slots }
;
457 if status
.drives
.is_empty() {
458 bail
!("no data transfer elements reported");
460 if status
.slots
.is_empty() {
461 bail
!("no storage elements reported");
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);
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
);
481 /// Read status and map import-export slots from config
482 pub fn status(config
: &ScsiTapeChanger
) -> Result
<MtxStatus
, Error
> {
483 let path
= &config
.path
;
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
))?
;
490 status
.mark_import_export_slots(&config
)?
;
498 struct ElementStatusHeader
{
499 first_element_address_reported
: u16,
500 number_of_elements_available
: u16,
502 byte_count_of_report_available
: [u8;3],
508 element_type_code
: u8,
510 descriptor_length
: u16,
512 byte_count_of_descriptor_data_available
: [u8;3],
517 fn parse_optional_volume_tag
<R
: Read
>(
521 ) -> Result
<Option
<String
>, Error
> {
523 if (self.flags
& 128) != 0 { // has PVolTag
524 let tmp
= reader
.read_exact_allocated(SCSI_VOLUME_TAG_LEN
)?
;
526 let volume_tag
= scsi_ascii_to_string(&tmp
);
527 return Ok(Some(volume_tag
));
533 // AFAIK, tape changer do not use AlternateVolumeTag
534 // but parse anyways, just to be sure
535 fn skip_alternate_volume_tag
<R
: Read
>(
538 ) -> Result
<Option
<String
>, Error
> {
540 if (self.flags
& 64) != 0 { // has AVolTag
541 let _tmp
= reader
.read_exact_allocated(SCSI_VOLUME_TAG_LEN
)?
;
550 struct TransportDescriptor
{ // Robot/Griper
551 element_address
: u16,
554 additional_sense_code
: u8,
555 additional_sense_code_qualifier
: u8,
558 source_storage_element_address
: u16,
559 // volume tag and Mixed media descriptor follows (depends on flags)
564 struct TransferDescriptor
{ // Tape drive
565 element_address
: u16,
568 additional_sense_code
: u8,
569 additional_sense_code_qualifier
: u8,
571 scsi_bus_address
: u8,
574 source_storage_element_address
: u16,
575 // volume tag, drive identifier and Mixed media descriptor follows
576 // (depends on flags)
581 struct DvcidHead
{ // Drive Identifier Header
586 // Identifier follows
591 struct StorageDescriptor
{ // Mail Slot
592 element_address
: u16,
595 additional_sense_code
: u8,
596 additional_sense_code_qualifier
: u8,
599 source_storage_element_address
: u16,
600 // volume tag and Mixed media descriptor follows (depends on flags)
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
>,
611 fn create_element_status(full
: bool
, volume_tag
: Option
<String
>) -> ElementStatus
{
613 if let Some(volume_tag
) = volume_tag
{
614 ElementStatus
::VolumeTag(volume_tag
)
624 vendor
: Option
<String
>,
625 model
: Option
<String
>,
626 serial
: Option
<String
>,
629 fn decode_dvcid_info
<R
: Read
>(reader
: &mut R
) -> Result
<DvcidInfo
, Error
> {
630 let dvcid
: DvcidHead
= unsafe { reader.read_be_value()? }
;
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
)
639 if dvcid
.identifier_len
!= 34 {
640 bail
!("got wrong DVCID length");
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
))
650 _
=> (None
, None
, None
),
660 fn decode_element_status_page(
662 start_element_address
: u16,
663 ) -> Result
<DecodedStatusPage
, Error
> {
665 proxmox
::try_block
!({
667 let mut result
= DecodedStatusPage
{
668 last_element_address
: None
,
669 transports
: Vec
::new(),
671 storage_slots
: Vec
::new(),
672 import_export_slots
: Vec
::new(),
675 let mut reader
= &data
[..];
677 let head
: ElementStatusHeader
= unsafe { reader.read_be_value()? }
;
679 if head
.number_of_elements_available
== 0 {
683 if head
.first_element_address_reported
< start_element_address
{
684 bail
!("got wrong first_element_address_reported"); // sanity check
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);
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());
697 if reader
.is_empty() {
701 let subhead
: SubHeader
= unsafe { reader.read_be_value()? }
;
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() {
709 let descr_data
= reader
.read_exact_allocated(len
)?
;
711 let descr_len
= subhead
.descriptor_length
as usize;
714 bail
!("got elements, but descriptor length 0");
717 for descriptor
in descr_data
.chunks_exact(descr_len
) {
718 let mut reader
= &descriptor
[..];
720 match subhead
.element_type_code
{
722 let desc
: TransportDescriptor
= unsafe { reader.read_be_value()? }
;
724 let full
= (desc
.flags1
& 1) != 0;
725 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
727 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
729 result
.last_element_address
= Some(desc
.element_address
);
731 let status
= TransportElementStatus
{
732 status
: create_element_status(full
, volume_tag
),
733 element_address
: desc
.element_address
,
735 result
.transports
.push(status
);
738 let desc
: StorageDescriptor
= unsafe { reader.read_be_value()? }
;
740 let full
= (desc
.flags1
& 1) != 0;
741 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
743 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
745 result
.last_element_address
= Some(desc
.element_address
);
747 if subhead
.element_type_code
== 3 {
748 let status
= StorageElementStatus
{
750 status
: create_element_status(full
, volume_tag
),
751 element_address
: desc
.element_address
,
753 result
.import_export_slots
.push(status
);
755 let status
= StorageElementStatus
{
756 import_export
: false,
757 status
: create_element_status(full
, volume_tag
),
758 element_address
: desc
.element_address
,
760 result
.storage_slots
.push(status
);
764 let desc
: TransferDescriptor
= unsafe { reader.read_be_value()? }
;
766 let loaded_slot
= if (desc
.flags2
& 128) != 0 { // SValid
767 Some(desc
.source_storage_element_address
as u64)
772 let full
= (desc
.flags1
& 1) != 0;
773 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
775 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
777 let dvcid
= decode_dvcid_info(&mut reader
).unwrap_or(DvcidInfo
{
783 result
.last_element_address
= Some(desc
.element_address
);
785 let drive
= DriveStatus
{
787 status
: create_element_status(full
, volume_tag
),
788 drive_serial_number
: dvcid
.serial
,
789 vendor
: dvcid
.vendor
,
791 element_address
: desc
.element_address
,
793 result
.drives
.push(drive
);
795 code
=> bail
!("got unknown element type code {}", code
),
801 }).map_err(|err
: Error
| format_err
!("decode element status failed - {}", err
))
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()
821 pvoltag
: Option
<String
>,
824 fn build_element_status_page(
825 descriptors
: Vec
<StorageDesc
>,
829 let descs
: Vec
<Vec
<u8>> = descriptors
.iter().map(|desc
| {
830 build_storage_descriptor(&desc
, trailing
)
833 let (desc_len
, address
) = if let Some(el
) = descs
.get(0) {
834 (el
.len() as u16, descriptors
[0].address
)
839 let descriptor_byte_count
= desc_len
* descs
.len() as u16;
840 let byte_count
= 8 + descriptor_byte_count
;
842 let mut res
= Vec
::new();
844 res
.extend_from_slice(&address
.to_be_bytes());
845 res
.extend_from_slice(&(descs
.len() as u16).to_be_bytes());
847 let byte_count
= byte_count
as u32;
848 res
.extend_from_slice(&byte_count
.to_be_bytes()[1..]);
850 res
.push(element_type
);
852 res
.extend_from_slice(&desc_len
.to_be_bytes());
854 let descriptor_byte_count
= descriptor_byte_count
as u32;
855 res
.extend_from_slice(&descriptor_byte_count
.to_be_bytes()[1..]);
858 res
.extend_from_slice(&desc
);
861 res
.extend_from_slice(trailing
);
866 fn build_storage_descriptor(
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
876 res
.push(0x00); // full
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);
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();
887 res
.resize(res
.len() + rem
, 0);
891 res
.extend_from_slice(trailing
);
897 fn status_page_valid() -> Result
<(), Error
> {
901 pvoltag
: Some("0123456789".to_string()),
905 pvoltag
: Some("1234567890".to_string()),
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);
915 fn status_page_too_short() -> Result
<(), Error
> {
919 pvoltag
: Some("0123456789".to_string()),
923 pvoltag
: Some("1234567890".to_string()),
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());
934 fn status_page_too_large() -> Result
<(), Error
> {
938 pvoltag
: Some("0123456789".to_string()),
942 pvoltag
: Some("1234567890".to_string()),
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);