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
;
20 TransportElementStatus
,
27 SENSE_KEY_RECOVERED_ERROR
,
28 SENSE_KEY_UNIT_ATTENTION
,
35 api2
::types
::ScsiTapeChanger
,
38 const SCSI_CHANGER_DEFAULT_TIMEOUT
: usize = 60*5; // 5 minutes
40 /// Initialize element status (Inventory)
41 pub fn initialize_element_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<(), Error
> {
43 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
45 // like mtx(1), set a very long timeout (30 minutes)
46 sg_raw
.set_timeout(30*60);
48 let mut cmd
= Vec
::new();
49 cmd
.extend(&[0x07, 0, 0, 0, 0, 0]); // INITIALIZE ELEMENT STATUS (07h)
51 sg_raw
.do_command(&cmd
)
52 .map_err(|err
| format_err
!("initializte element status (07h) failed - {}", err
))?
;
59 struct AddressAssignmentPage
{
63 block_descriptor_len
: u8,
66 additional_page_len
: u8,
67 first_transport_element_address
: u16,
68 transport_element_count
: u16,
69 first_storage_element_address
: u16,
70 storage_element_count
: u16,
71 first_import_export_element_address
: u16,
72 import_export_element_count
: u16,
73 first_tranfer_element_address
: u16,
74 transfer_element_count
: u16,
79 /// Execute scsi commands, optionally repeat the command until
80 /// successful (sleep 1 second between invovations)
82 /// Any Sense key other than NO_SENSE, RECOVERED_ERROR, NOT_READY and
83 /// UNIT_ATTENTION aborts the loop and returns an error. If the device
84 /// reports "Not Ready - becoming ready", we wait up to 5 minutes.
86 /// Skipped errors are printed on stderr.
87 fn execute_scsi_command
<F
: AsRawFd
>(
88 sg_raw
: &mut SgRaw
<F
>,
92 ) -> Result
<Vec
<u8>, Error
> {
94 let start
= std
::time
::SystemTime
::now();
96 let mut last_msg
: Option
<String
> = None
;
98 let mut timeout
= std
::time
::Duration
::new(5, 0); // short timeout by default
101 match sg_raw
.do_command(&cmd
) {
102 Ok(data
) => return Ok(data
.to_vec()),
105 bail
!("{} failed: {}", error_prefix
, err
);
107 if let ScsiError
::Sense(ref sense
) = err
{
109 if sense
.sense_key
== SENSE_KEY_NO_SENSE
||
110 sense
.sense_key
== SENSE_KEY_RECOVERED_ERROR
||
111 sense
.sense_key
== SENSE_KEY_UNIT_ATTENTION
||
112 sense
.sense_key
== SENSE_KEY_NOT_READY
114 let msg
= err
.to_string();
115 if let Some(ref last
) = last_msg
{
117 eprintln
!("{}", err
);
118 last_msg
= Some(msg
);
121 eprintln
!("{}", err
);
122 last_msg
= Some(msg
);
125 // Not Ready - becoming ready
126 if sense
.sense_key
== SENSE_KEY_NOT_READY
&& sense
.asc
== 0x04 && sense
.ascq
== 1 {
127 // wait up to 5 minutes, long enough to finish inventorize
128 timeout
= std
::time
::Duration
::new(5*60, 0);
131 if start
.elapsed()?
> timeout
{
132 bail
!("{} failed: {}", error_prefix
, err
);
135 std
::thread
::sleep(std
::time
::Duration
::new(1, 0));
136 continue; // try again
145 fn read_element_address_assignment
<F
: AsRawFd
>(
147 ) -> Result
<AddressAssignmentPage
, Error
> {
149 let allocation_len
: u8 = u8::MAX
;
150 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
151 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
153 let mut cmd
= Vec
::new();
154 cmd
.push(0x1A); // MODE SENSE6 (1Ah)
155 cmd
.push(0x08); // DBD=1 (The Disable Block Descriptors)
156 cmd
.push(0x1D); // Element Address Assignment Page
158 cmd
.push(allocation_len
); // allocation len
159 cmd
.push(0); //control
161 let data
= execute_scsi_command(&mut sg_raw
, &cmd
, "read element address assignment", true)?
;
163 proxmox
::try_block
!({
164 let mut reader
= &data
[..];
165 let page
: AddressAssignmentPage
= unsafe { reader.read_be_value()? }
;
167 if page
.data_len
!= 23 {
168 bail
!("got unexpected page len ({} != 23)", page
.data_len
);
172 }).map_err(|err
: Error
| format_err
!("decode element address assignment page failed - {}", err
))
175 fn scsi_move_medium_cdb(
176 medium_transport_address
: u16,
177 source_element_address
: u16,
178 destination_element_address
: u16,
181 let mut cmd
= Vec
::new();
182 cmd
.push(0xA5); // MOVE MEDIUM (A5h)
183 cmd
.push(0); // reserved
184 cmd
.extend(&medium_transport_address
.to_be_bytes());
185 cmd
.extend(&source_element_address
.to_be_bytes());
186 cmd
.extend(&destination_element_address
.to_be_bytes());
187 cmd
.push(0); // reserved
188 cmd
.push(0); // reserved
189 cmd
.push(0); // Invert=0
190 cmd
.push(0); // control
195 /// Load media from storage slot into drive
200 ) -> Result
<(), Error
> {
201 let status
= read_element_status(file
)?
;
203 let transport_address
= status
.transport_address();
204 let source_element_address
= status
.slot_address(from_slot
)?
;
205 let drive_element_address
= status
.drive_address(drivenum
)?
;
207 let cmd
= scsi_move_medium_cdb(
209 source_element_address
,
210 drive_element_address
,
213 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
214 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
216 sg_raw
.do_command(&cmd
)
217 .map_err(|err
| format_err
!("load drive failed - {}", err
))?
;
222 /// Unload media from drive into a storage slot
227 ) -> Result
<(), Error
> {
229 let status
= read_element_status(file
)?
;
231 let transport_address
= status
.transport_address();
232 let target_element_address
= status
.slot_address(to_slot
)?
;
233 let drive_element_address
= status
.drive_address(drivenum
)?
;
235 let cmd
= scsi_move_medium_cdb(
237 drive_element_address
,
238 target_element_address
,
241 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
242 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
244 sg_raw
.do_command(&cmd
)
245 .map_err(|err
| format_err
!("unload drive failed - {}", err
))?
;
250 /// Transfer medium from one storage slot to another
251 pub fn transfer_medium
<F
: AsRawFd
>(
255 ) -> Result
<(), Error
> {
257 let status
= read_element_status(file
)?
;
259 let transport_address
= status
.transport_address();
260 let source_element_address
= status
.slot_address(from_slot
)?
;
261 let target_element_address
= status
.slot_address(to_slot
)?
;
263 let cmd
= scsi_move_medium_cdb(
265 source_element_address
,
266 target_element_address
,
269 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
270 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
272 sg_raw
.do_command(&cmd
)
274 format_err
!("transfer medium from slot {} to slot {} failed - {}",
275 from_slot
, to_slot
, err
)
282 #[derive(Clone, Copy)]
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(1u8<<4 | (element_type
as u8)); // volume tags and given type
300 cmd
.extend(&start_element_address
.to_be_bytes());
302 cmd
.extend(&number_of_elements
.to_be_bytes());
303 let byte6
= match element_type
{
304 ElementType
::DataTransfer
=> 0b001, // Mixed=0,CurData=0,DVCID=1
305 _
=> 0b000, // Mixed=0,CurData=0,DVCID=0
308 cmd
.extend(&allocation_len
.to_be_bytes()[1..4]);
315 // query a single element type from the changer
316 fn get_element
<F
: AsRawFd
>(
317 inquiry
: &InquiryInfo
,
318 sg_raw
: &mut SgRaw
<F
>,
319 element_type
: ElementType
,
322 ) -> Result
<DecodedStatusPage
, Error
> {
324 let mut start_element_address
= 0;
325 let number_of_elements
: u16 = 1000; // some changers limit the query
327 let mut result
= DecodedStatusPage
{
328 last_element_address
: None
,
329 transports
: Vec
::new(),
331 storage_slots
: Vec
::new(),
332 import_export_slots
: Vec
::new(),
336 let cmd
= scsi_read_element_status_cdb(start_element_address
, number_of_elements
, element_type
, allocation_len
);
338 let data
= execute_scsi_command(sg_raw
, &cmd
, "read element status (B8h)", retry
)?
;
340 let page
= decode_element_status_page(&inquiry
, &data
, start_element_address
)?
;
342 retry
= false; // only retry the first command
344 let returned_number_of_elements
= page
.transports
.len()
346 + page
.storage_slots
.len()
347 + page
.import_export_slots
.len();
349 result
.transports
.extend(page
.transports
);
350 result
.drives
.extend(page
.drives
);
351 result
.storage_slots
.extend(page
.storage_slots
);
352 result
.import_export_slots
.extend(page
.import_export_slots
);
353 result
.last_element_address
= page
.last_element_address
;
355 if let Some(last_element_address
) = page
.last_element_address
{
356 if last_element_address
< start_element_address
{
357 bail
!("got strange element address");
359 if returned_number_of_elements
>= (number_of_elements
as usize) {
360 start_element_address
= last_element_address
+ 1;
361 continue; // we possibly have to read additional elements
370 /// Read element status.
371 pub fn read_element_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<MtxStatus
, Error
> {
373 let inquiry
= scsi_inquiry(file
)?
;
375 if inquiry
.peripheral_type
!= 8 {
376 bail
!("wrong device type (not a scsi changer device)");
379 // first, request address assignment (used for sanity checks)
380 let setup
= read_element_address_assignment(file
)?
;
382 let allocation_len
: u32 = 0x10000;
384 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
385 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
387 let mut drives
= Vec
::new();
388 let mut storage_slots
= Vec
::new();
389 let mut import_export_slots
= Vec
::new();
390 let mut transports
= Vec
::new();
392 let page
= get_element(&inquiry
, &mut sg_raw
, ElementType
::Storage
, allocation_len
, true)?
;
393 storage_slots
.extend(page
.storage_slots
);
395 let page
= get_element(&inquiry
, &mut sg_raw
, ElementType
::ImportExport
, allocation_len
, false)?
;
396 import_export_slots
.extend(page
.import_export_slots
);
398 let page
= get_element(&inquiry
, &mut sg_raw
, ElementType
::DataTransfer
, allocation_len
, false)?
;
399 drives
.extend(page
.drives
);
401 let page
= get_element(&inquiry
, &mut sg_raw
, ElementType
::MediumTransport
, allocation_len
, false)?
;
402 transports
.extend(page
.transports
);
404 if (setup
.transport_element_count
as usize) != transports
.len() {
405 bail
!("got wrong number of transport elements");
407 if (setup
.storage_element_count
as usize) != storage_slots
.len() {
408 bail
!("got wrong number of storage elements");
410 if (setup
.import_export_element_count
as usize) != import_export_slots
.len() {
411 bail
!("got wrong number of import/export elements");
413 if (setup
.transfer_element_count
as usize) != drives
.len() {
414 bail
!("got wrong number of transfer elements");
417 // create same virtual slot order as mtx(1)
418 // - storage slots first
419 // - import export slots at the end
420 let mut slots
= storage_slots
;
421 slots
.extend(import_export_slots
);
423 let mut status
= MtxStatus { transports, drives, slots }
;
426 if status
.drives
.is_empty() {
427 bail
!("no data transfer elements reported");
429 if status
.slots
.is_empty() {
430 bail
!("no storage elements reported");
433 // compute virtual storage slot to element_address map
434 let mut slot_map
= HashMap
::new();
435 for (i
, slot
) in status
.slots
.iter().enumerate() {
436 slot_map
.insert(slot
.element_address
, (i
+ 1) as u64);
439 // translate element addresses in loaded_lot
440 for drive
in status
.drives
.iter_mut() {
441 if let Some(source_address
) = drive
.loaded_slot
{
442 let source_address
= source_address
as u16;
443 drive
.loaded_slot
= slot_map
.get(&source_address
).map(|v
| *v
);
450 /// Read status and map import-export slots from config
451 pub fn status(config
: &ScsiTapeChanger
) -> Result
<MtxStatus
, Error
> {
452 let path
= &config
.path
;
454 let mut file
= open(path
)
455 .map_err(|err
| format_err
!("error opening '{}': {}", path
, err
))?
;
456 let mut status
= read_element_status(&mut file
)
457 .map_err(|err
| format_err
!("error reading element status: {}", err
))?
;
459 status
.mark_import_export_slots(&config
)?
;
467 struct ElementStatusHeader
{
468 first_element_address_reported
: u16,
469 number_of_elements_available
: u16,
471 byte_count_of_report_available
: [u8;3],
477 element_type_code
: u8,
479 descriptor_length
: u16,
481 byte_count_of_descriptor_data_available
: [u8;3],
486 fn parse_optional_volume_tag
<R
: Read
>(
490 ) -> Result
<Option
<String
>, Error
> {
492 if (self.flags
& 128) != 0 { // has PVolTag
493 let tmp
= reader
.read_exact_allocated(36)?
;
495 let volume_tag
= scsi_ascii_to_string(&tmp
);
496 return Ok(Some(volume_tag
));
502 // AFAIK, tape changer do not use AlternateVolumeTag
503 // but parse anyways, just to be sure
504 fn skip_alternate_volume_tag
<R
: Read
>(
507 ) -> Result
<Option
<String
>, Error
> {
509 if (self.flags
& 64) != 0 { // has AVolTag
510 let _tmp
= reader
.read_exact_allocated(36)?
;
519 struct TrasnsportDescriptor
{ // Robot/Griper
520 element_address
: u16,
523 additional_sense_code
: u8,
524 additional_sense_code_qualifier
: u8,
527 source_storage_element_address
: u16,
528 // volume tag and Mixed media descriptor follows (depends on flags)
533 struct TransferDescriptor
{ // Tape drive
534 element_address
: u16,
537 additional_sense_code
: u8,
538 additional_sense_code_qualifier
: u8,
540 scsi_bus_address
: u8,
543 source_storage_element_address
: u16,
544 // volume tag, drive identifier and Mixed media descriptor follows
545 // (depends on flags)
550 struct DvcidHead
{ // Drive Identifier Header
555 // Identifier follows
560 struct StorageDescriptor
{ // Mail Slot
561 element_address
: u16,
564 additional_sense_code
: u8,
565 additional_sense_code_qualifier
: u8,
568 source_storage_element_address
: u16,
569 // volume tag and Mixed media descriptor follows (depends on flags)
572 struct DecodedStatusPage
{
573 last_element_address
: Option
<u16>,
574 transports
: Vec
<TransportElementStatus
>,
575 drives
: Vec
<DriveStatus
>,
576 storage_slots
: Vec
<StorageElementStatus
>,
577 import_export_slots
: Vec
<StorageElementStatus
>,
580 fn create_element_status(full
: bool
, volume_tag
: Option
<String
>) -> ElementStatus
{
582 if let Some(volume_tag
) = volume_tag
{
583 ElementStatus
::VolumeTag(volume_tag
)
592 fn decode_element_status_page(
595 start_element_address
: u16,
596 ) -> Result
<DecodedStatusPage
, Error
> {
598 proxmox
::try_block
!({
600 let mut result
= DecodedStatusPage
{
601 last_element_address
: None
,
602 transports
: Vec
::new(),
604 storage_slots
: Vec
::new(),
605 import_export_slots
: Vec
::new(),
608 let mut reader
= &data
[..];
610 let head
: ElementStatusHeader
= unsafe { reader.read_be_value()? }
;
612 if head
.number_of_elements_available
== 0 {
616 if head
.first_element_address_reported
< start_element_address
{
617 bail
!("got wrong first_element_address_reported"); // sanity check
621 if reader
.is_empty() {
625 let subhead
: SubHeader
= unsafe { reader.read_be_value()? }
;
627 let len
= subhead
.byte_count_of_descriptor_data_available
;
628 let mut len
= ((len
[0] as usize) << 16) + ((len
[1] as usize) << 8) + (len
[2] as usize);
629 if len
> reader
.len() {
633 let descr_data
= reader
.read_exact_allocated(len
)?
;
634 let mut reader
= &descr_data
[..];
637 if reader
.is_empty() {
640 if reader
.len() < (subhead
.descriptor_length
as usize) {
644 let len_before
= reader
.len();
646 match subhead
.element_type_code
{
648 let desc
: TrasnsportDescriptor
= unsafe { reader.read_be_value()? }
;
650 let full
= (desc
.flags1
& 1) != 0;
651 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
653 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
655 let mut reserved
= [0u8; 4];
656 reader
.read_exact(&mut reserved
)?
;
658 result
.last_element_address
= Some(desc
.element_address
);
660 let status
= TransportElementStatus
{
661 status
: create_element_status(full
, volume_tag
),
662 element_address
: desc
.element_address
,
664 result
.transports
.push(status
);
667 let desc
: StorageDescriptor
= unsafe { reader.read_be_value()? }
;
669 let full
= (desc
.flags1
& 1) != 0;
670 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
672 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
674 let mut reserved
= [0u8; 4];
675 reader
.read_exact(&mut reserved
)?
;
677 result
.last_element_address
= Some(desc
.element_address
);
679 if subhead
.element_type_code
== 3 {
680 let status
= StorageElementStatus
{
682 status
: create_element_status(full
, volume_tag
),
683 element_address
: desc
.element_address
,
685 result
.import_export_slots
.push(status
);
687 let status
= StorageElementStatus
{
688 import_export
: false,
689 status
: create_element_status(full
, volume_tag
),
690 element_address
: desc
.element_address
,
692 result
.storage_slots
.push(status
);
696 let desc
: TransferDescriptor
= unsafe { reader.read_be_value()? }
;
698 let loaded_slot
= if (desc
.flags2
& 128) != 0 { // SValid
699 Some(desc
.source_storage_element_address
as u64)
704 let full
= (desc
.flags1
& 1) != 0;
705 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
707 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
709 let dvcid
: DvcidHead
= unsafe { reader.read_be_value()? }
;
711 let (drive_serial_number
, vendor
, model
) = match (dvcid
.code_set
, dvcid
.identifier_type
) {
712 (2, 0) => { // Serial number only (Quantum Superloader3 uses this)
713 let serial
= reader
.read_exact_allocated(dvcid
.identifier_len
as usize)?
;
714 let serial
= scsi_ascii_to_string(&serial
);
715 (Some(serial
), None
, None
)
718 if dvcid
.identifier_len
!= 34 {
719 bail
!("got wrong DVCID length");
721 let vendor
= reader
.read_exact_allocated(8)?
;
722 let vendor
= scsi_ascii_to_string(&vendor
);
723 let model
= reader
.read_exact_allocated(16)?
;
724 let model
= scsi_ascii_to_string(&model
);
725 let serial
= reader
.read_exact_allocated(10)?
;
726 let serial
= scsi_ascii_to_string(&serial
);
727 (Some(serial
), Some(vendor
), Some(model
))
729 _
=> (None
, None
, None
),
732 result
.last_element_address
= Some(desc
.element_address
);
734 let drive
= DriveStatus
{
736 status
: create_element_status(full
, volume_tag
),
740 element_address
: desc
.element_address
,
742 result
.drives
.push(drive
);
744 code
=> bail
!("got unknown element type code {}", code
),
747 // we have to consume the whole descriptor size, else
748 // our position in the reader is not correct
749 let len_after
= reader
.len();
750 let have_read
= len_before
- len_after
;
751 let desc_len
= subhead
.descriptor_length
as usize;
752 if desc_len
> have_read
{
753 let mut left_to_read
= desc_len
- have_read
;
754 if left_to_read
> len_after
{
755 left_to_read
= len_after
; // reader has not enough data?
757 let _
= reader
.read_exact_allocated(left_to_read
)?
;
763 }).map_err(|err
: Error
| format_err
!("decode element status failed - {}", err
))
766 /// Open the device for read/write, returns the file handle
767 pub fn open
<P
: AsRef
<Path
>>(path
: P
) -> Result
<File
, Error
> {
768 let file
= OpenOptions
::new()