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
,
34 api2
::types
::ScsiTapeChanger
,
37 const SCSI_CHANGER_DEFAULT_TIMEOUT
: usize = 60*5; // 5 minutes
39 /// Initialize element status (Inventory)
40 pub fn initialize_element_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<(), Error
> {
42 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
44 // like mtx(1), set a very long timeout (30 minutes)
45 sg_raw
.set_timeout(30*60);
47 let mut cmd
= Vec
::new();
48 cmd
.extend(&[0x07, 0, 0, 0, 0, 0]); // INITIALIZE ELEMENT STATUS (07h)
50 sg_raw
.do_command(&cmd
)
51 .map_err(|err
| format_err
!("initializte element status (07h) failed - {}", err
))?
;
58 struct AddressAssignmentPage
{
62 block_descriptor_len
: u8,
65 additional_page_len
: u8,
66 first_transport_element_address
: u16,
67 transport_element_count
: u16,
68 first_storage_element_address
: u16,
69 storage_element_count
: u16,
70 first_import_export_element_address
: u16,
71 import_export_element_count
: u16,
72 first_tranfer_element_address
: u16,
73 transfer_element_count
: u16,
78 /// Execute scsi commands, optionally repeat the command until
79 /// successful (sleep 1 second between invovations)
81 /// Any Sense key other than NO_SENSE, RECOVERED_ERROR, NOT_READY and
82 /// UNIT_ATTENTION aborts the loop and returns an error. If the device
83 /// reports "Not Ready - becoming ready", we wait up to 5 minutes.
85 /// Skipped errors are printed on stderr.
86 fn execute_scsi_command
<F
: AsRawFd
>(
87 sg_raw
: &mut SgRaw
<F
>,
91 ) -> Result
<Vec
<u8>, Error
> {
93 let start
= std
::time
::SystemTime
::now();
95 let mut last_msg
: Option
<String
> = None
;
97 let mut timeout
= std
::time
::Duration
::new(5, 0); // short timeout by default
100 match sg_raw
.do_command(&cmd
) {
101 Ok(data
) => return Ok(data
.to_vec()),
104 bail
!("{} failed: {}", error_prefix
, err
);
106 if let Some(ref sense
) = err
.sense
{
108 if sense
.sense_key
== SENSE_KEY_NO_SENSE
||
109 sense
.sense_key
== SENSE_KEY_RECOVERED_ERROR
||
110 sense
.sense_key
== SENSE_KEY_UNIT_ATTENTION
||
111 sense
.sense_key
== SENSE_KEY_NOT_READY
113 let msg
= err
.to_string();
114 if let Some(ref last
) = last_msg
{
116 eprintln
!("{}", err
);
117 last_msg
= Some(msg
);
120 eprintln
!("{}", err
);
121 last_msg
= Some(msg
);
124 // Not Ready - becoming ready
125 if sense
.sense_key
== SENSE_KEY_NOT_READY
&& sense
.asc
== 0x04 && sense
.ascq
== 1 {
126 // wait up to 5 minutes, long enough to finish inventorize
127 timeout
= std
::time
::Duration
::new(5*60, 0);
130 if start
.elapsed()?
> timeout
{
131 bail
!("{} failed: {}", error_prefix
, err
);
134 std
::thread
::sleep(std
::time
::Duration
::new(1, 0));
135 continue; // try again
144 fn read_element_address_assignment
<F
: AsRawFd
>(
146 ) -> Result
<AddressAssignmentPage
, Error
> {
148 let allocation_len
: u8 = u8::MAX
;
149 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
150 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
152 let mut cmd
= Vec
::new();
153 cmd
.push(0x1A); // MODE SENSE6 (1Ah)
154 cmd
.push(0x08); // DBD=1 (The Disable Block Descriptors)
155 cmd
.push(0x1D); // Element Address Assignment Page
157 cmd
.push(allocation_len
); // allocation len
158 cmd
.push(0); //control
160 let data
= execute_scsi_command(&mut sg_raw
, &cmd
, "read element address assignment", true)?
;
162 proxmox
::try_block
!({
163 let mut reader
= &data
[..];
164 let page
: AddressAssignmentPage
= unsafe { reader.read_be_value()? }
;
166 if page
.data_len
!= 23 {
167 bail
!("got unexpected page len ({} != 23)", page
.data_len
);
171 }).map_err(|err
: Error
| format_err
!("decode element address assignment page failed - {}", err
))
174 fn scsi_move_medium_cdb(
175 medium_transport_address
: u16,
176 source_element_address
: u16,
177 destination_element_address
: u16,
180 let mut cmd
= Vec
::new();
181 cmd
.push(0xA5); // MOVE MEDIUM (A5h)
182 cmd
.push(0); // reserved
183 cmd
.extend(&medium_transport_address
.to_be_bytes());
184 cmd
.extend(&source_element_address
.to_be_bytes());
185 cmd
.extend(&destination_element_address
.to_be_bytes());
186 cmd
.push(0); // reserved
187 cmd
.push(0); // reserved
188 cmd
.push(0); // Invert=0
189 cmd
.push(0); // control
194 /// Load media from storage slot into drive
199 ) -> Result
<(), Error
> {
200 let status
= read_element_status(file
)?
;
202 let transport_address
= status
.transport_address();
203 let source_element_address
= status
.slot_address(from_slot
)?
;
204 let drive_element_address
= status
.drive_address(drivenum
)?
;
206 let cmd
= scsi_move_medium_cdb(
208 source_element_address
,
209 drive_element_address
,
212 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
213 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
215 sg_raw
.do_command(&cmd
)
216 .map_err(|err
| format_err
!("load drive failed - {}", err
))?
;
221 /// Unload media from drive into a storage slot
226 ) -> Result
<(), Error
> {
228 let status
= read_element_status(file
)?
;
230 let transport_address
= status
.transport_address();
231 let target_element_address
= status
.slot_address(to_slot
)?
;
232 let drive_element_address
= status
.drive_address(drivenum
)?
;
234 let cmd
= scsi_move_medium_cdb(
236 drive_element_address
,
237 target_element_address
,
240 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
241 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
243 sg_raw
.do_command(&cmd
)
244 .map_err(|err
| format_err
!("unload drive failed - {}", err
))?
;
249 /// Tranfer medium from one storage slot to another
250 pub fn transfer_medium
<F
: AsRawFd
>(
254 ) -> Result
<(), Error
> {
256 let status
= read_element_status(file
)?
;
258 let transport_address
= status
.transport_address();
259 let source_element_address
= status
.slot_address(from_slot
)?
;
260 let target_element_address
= status
.slot_address(to_slot
)?
;
262 let cmd
= scsi_move_medium_cdb(
264 source_element_address
,
265 target_element_address
,
268 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
269 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
271 sg_raw
.do_command(&cmd
)
273 format_err
!("transfer medium from slot {} to slot {} failed - {}",
274 from_slot
, to_slot
, err
)
280 fn scsi_read_element_status_cdb(
281 start_element_address
: u16,
285 let mut cmd
= Vec
::new();
286 cmd
.push(0xB8); // READ ELEMENT STATUS (B8h)
287 cmd
.push(1u8<<4); // report all types and volume tags
288 cmd
.extend(&start_element_address
.to_be_bytes());
290 let number_of_elements
: u16 = 0xffff;
291 cmd
.extend(&number_of_elements
.to_be_bytes());
292 cmd
.push(0b001); // Mixed=0,CurData=0,DVCID=1
293 cmd
.extend(&allocation_len
.to_be_bytes()[1..4]);
300 /// Read element status.
301 pub fn read_element_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<MtxStatus
, Error
> {
303 let inquiry
= scsi_inquiry(file
)?
;
305 if inquiry
.peripheral_type
!= 8 {
306 bail
!("wrong device type (not a scsi changer device)");
309 // first, request address assignment (used for sanity checks)
310 let setup
= read_element_address_assignment(file
)?
;
312 let allocation_len
: u32 = 0x10000;
314 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
315 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
317 let mut start_element_address
= 0;
319 let mut drives
= Vec
::new();
320 let mut storage_slots
= Vec
::new();
321 let mut import_export_slots
= Vec
::new();
322 let mut transports
= Vec
::new();
324 let mut retry
= true;
327 let cmd
= scsi_read_element_status_cdb(start_element_address
, allocation_len
);
329 let data
= execute_scsi_command(&mut sg_raw
, &cmd
, "read element status (B8h)", retry
)?
;
331 let page
= decode_element_status_page(&inquiry
, &data
, start_element_address
)?
;
333 retry
= false; // only retry the first command
335 transports
.extend(page
.transports
);
336 drives
.extend(page
.drives
);
337 storage_slots
.extend(page
.storage_slots
);
338 import_export_slots
.extend(page
.import_export_slots
);
340 if data
.len() < (allocation_len
as usize) {
344 if let Some(last_element_address
) = page
.last_element_address
{
345 if last_element_address
>= start_element_address
{
346 start_element_address
= last_element_address
+ 1;
348 bail
!("got strange element address");
355 if (setup
.transport_element_count
as usize) != transports
.len() {
356 bail
!("got wrong number of transport elements");
358 if (setup
.storage_element_count
as usize) != storage_slots
.len() {
359 bail
!("got wrong number of storage elements");
361 if (setup
.import_export_element_count
as usize) != import_export_slots
.len() {
362 bail
!("got wrong number of import/export elements");
364 if (setup
.transfer_element_count
as usize) != drives
.len() {
365 bail
!("got wrong number of tranfer elements");
368 // create same virtual slot order as mtx(1)
369 // - storage slots first
370 // - import export slots at the end
371 let mut slots
= storage_slots
;
372 slots
.extend(import_export_slots
);
374 let mut status
= MtxStatus { transports, drives, slots }
;
377 if status
.drives
.is_empty() {
378 bail
!("no data transfer elements reported");
380 if status
.slots
.is_empty() {
381 bail
!("no storage elements reported");
384 // compute virtual storage slot to element_address map
385 let mut slot_map
= HashMap
::new();
386 for (i
, slot
) in status
.slots
.iter().enumerate() {
387 slot_map
.insert(slot
.element_address
, (i
+ 1) as u64);
390 // translate element addresses in loaded_lot
391 for drive
in status
.drives
.iter_mut() {
392 if let Some(source_address
) = drive
.loaded_slot
{
393 let source_address
= source_address
as u16;
394 drive
.loaded_slot
= slot_map
.get(&source_address
).map(|v
| *v
);
401 /// Read status and map import-export slots from config
402 pub fn status(config
: &ScsiTapeChanger
) -> Result
<MtxStatus
, Error
> {
403 let path
= &config
.path
;
405 let mut file
= open(path
)
406 .map_err(|err
| format_err
!("error opening '{}': {}", path
, err
))?
;
407 let mut status
= read_element_status(&mut file
)
408 .map_err(|err
| format_err
!("error reading element status: {}", err
))?
;
410 status
.mark_import_export_slots(&config
)?
;
418 struct ElementStatusHeader
{
419 first_element_address_reported
: u16,
420 number_of_elements_available
: u16,
422 byte_count_of_report_available
: [u8;3],
428 element_type_code
: u8,
430 descriptor_length
: u16,
432 byte_count_of_descriptor_data_available
: [u8;3],
437 fn parse_optional_volume_tag
<R
: Read
>(
441 ) -> Result
<Option
<String
>, Error
> {
443 if (self.flags
& 128) != 0 { // has PVolTag
444 let tmp
= reader
.read_exact_allocated(36)?
;
446 let volume_tag
= scsi_ascii_to_string(&tmp
);
447 return Ok(Some(volume_tag
));
453 // AFAIK, tape changer do not use AlternateVolumeTag
454 // but parse anyways, just to be sure
455 fn skip_alternate_volume_tag
<R
: Read
>(
458 ) -> Result
<Option
<String
>, Error
> {
460 if (self.flags
& 64) != 0 { // has AVolTag
461 let _tmp
= reader
.read_exact_allocated(36)?
;
470 struct TrasnsportDescriptor
{ // Robot/Griper
471 element_address
: u16,
474 additional_sense_code
: u8,
475 additional_sense_code_qualifier
: u8,
478 source_storage_element_address
: u16,
479 // volume tag and Mixed media descriptor follows (depends on flags)
484 struct TransferDescriptor
{ // Tape drive
485 element_address
: u16,
488 additional_sense_code
: u8,
489 additional_sense_code_qualifier
: u8,
491 scsi_bus_address
: u8,
494 source_storage_element_address
: u16,
495 // volume tag, drive identifier and Mixed media descriptor follows
496 // (depends on flags)
501 struct DvcidHead
{ // Drive Identifier Header
506 // Identifier follows
511 struct StorageDescriptor
{ // Mail Slot
512 element_address
: u16,
515 additional_sense_code
: u8,
516 additional_sense_code_qualifier
: u8,
519 source_storage_element_address
: u16,
520 // volume tag and Mixed media descriptor follows (depends on flags)
523 struct DecodedStatusPage
{
524 last_element_address
: Option
<u16>,
525 transports
: Vec
<TransportElementStatus
>,
526 drives
: Vec
<DriveStatus
>,
527 storage_slots
: Vec
<StorageElementStatus
>,
528 import_export_slots
: Vec
<StorageElementStatus
>,
531 fn create_element_status(full
: bool
, volume_tag
: Option
<String
>) -> ElementStatus
{
533 if let Some(volume_tag
) = volume_tag
{
534 ElementStatus
::VolumeTag(volume_tag
)
543 fn decode_element_status_page(
546 start_element_address
: u16,
547 ) -> Result
<DecodedStatusPage
, Error
> {
549 proxmox
::try_block
!({
551 let mut result
= DecodedStatusPage
{
552 last_element_address
: None
,
553 transports
: Vec
::new(),
555 storage_slots
: Vec
::new(),
556 import_export_slots
: Vec
::new(),
559 let mut reader
= &data
[..];
561 let head
: ElementStatusHeader
= unsafe { reader.read_be_value()? }
;
563 if head
.number_of_elements_available
== 0 {
567 if head
.first_element_address_reported
< start_element_address
{
568 bail
!("got wrong first_element_address_reported"); // sanity check
572 if reader
.is_empty() {
576 let subhead
: SubHeader
= unsafe { reader.read_be_value()? }
;
578 let len
= subhead
.byte_count_of_descriptor_data_available
;
579 let mut len
= ((len
[0] as usize) << 16) + ((len
[1] as usize) << 8) + (len
[2] as usize);
580 if len
> reader
.len() {
584 let descr_data
= reader
.read_exact_allocated(len
)?
;
585 let mut reader
= &descr_data
[..];
588 if reader
.is_empty() {
591 if reader
.len() < (subhead
.descriptor_length
as usize) {
595 match subhead
.element_type_code
{
597 let desc
: TrasnsportDescriptor
= unsafe { reader.read_be_value()? }
;
599 let full
= (desc
.flags1
& 1) != 0;
600 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
602 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
604 let mut reserved
= [0u8; 4];
605 reader
.read_exact(&mut reserved
)?
;
607 result
.last_element_address
= Some(desc
.element_address
);
609 let status
= TransportElementStatus
{
610 status
: create_element_status(full
, volume_tag
),
611 element_address
: desc
.element_address
,
613 result
.transports
.push(status
);
616 let desc
: StorageDescriptor
= unsafe { reader.read_be_value()? }
;
618 let full
= (desc
.flags1
& 1) != 0;
619 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
621 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
623 let mut reserved
= [0u8; 4];
624 reader
.read_exact(&mut reserved
)?
;
626 result
.last_element_address
= Some(desc
.element_address
);
628 if subhead
.element_type_code
== 3 {
629 let status
= StorageElementStatus
{
631 status
: create_element_status(full
, volume_tag
),
632 element_address
: desc
.element_address
,
634 result
.import_export_slots
.push(status
);
636 let status
= StorageElementStatus
{
637 import_export
: false,
638 status
: create_element_status(full
, volume_tag
),
639 element_address
: desc
.element_address
,
641 result
.storage_slots
.push(status
);
645 let desc
: TransferDescriptor
= unsafe { reader.read_be_value()? }
;
647 let loaded_slot
= if (desc
.flags2
& 128) != 0 { // SValid
648 Some(desc
.source_storage_element_address
as u64)
653 let full
= (desc
.flags1
& 1) != 0;
654 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
656 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
658 let dvcid
: DvcidHead
= unsafe { reader.read_be_value()? }
;
660 let (drive_serial_number
, vendor
, model
) = match (dvcid
.code_set
, dvcid
.identifier_type
) {
661 (2, 0) => { // Serial number only (Quantum Superloader3 uses this)
662 let serial
= reader
.read_exact_allocated(dvcid
.identifier_len
as usize)?
;
663 let serial
= scsi_ascii_to_string(&serial
);
664 (Some(serial
), None
, None
)
667 if dvcid
.identifier_len
!= 34 {
668 bail
!("got wrong DVCID length");
670 let vendor
= reader
.read_exact_allocated(8)?
;
671 let vendor
= scsi_ascii_to_string(&vendor
);
672 let model
= reader
.read_exact_allocated(16)?
;
673 let model
= scsi_ascii_to_string(&model
);
674 let serial
= reader
.read_exact_allocated(10)?
;
675 let serial
= scsi_ascii_to_string(&serial
);
676 (Some(serial
), Some(vendor
), Some(model
))
678 _
=> (None
, None
, None
),
681 result
.last_element_address
= Some(desc
.element_address
);
683 let drive
= DriveStatus
{
685 status
: create_element_status(full
, volume_tag
),
689 element_address
: desc
.element_address
,
691 result
.drives
.push(drive
);
693 code
=> bail
!("got unknown element type code {}", code
),
699 }).map_err(|err
: Error
| format_err
!("decode element status failed - {}", err
))
702 /// Open the device for read/write, returns the file handle
703 pub fn open
<P
: AsRef
<Path
>>(path
: P
) -> Result
<File
, Error
> {
704 let file
= OpenOptions
::new()