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
,
36 const SCSI_CHANGER_DEFAULT_TIMEOUT
: usize = 60*5; // 5 minutes
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 (sleep 1 second between invovations)
80 /// Any Sense key other than NO_SENSE, RECOVERED_ERROR, NOT_READY and
81 /// UNIT_ATTENTION aborts the loop and returns an error. If the device
82 /// reports "Not Ready - becoming ready", we wait up to 5 minutes.
84 /// Skipped errors are printed on stderr.
85 fn execute_scsi_command
<F
: AsRawFd
>(
86 sg_raw
: &mut SgRaw
<F
>,
90 ) -> Result
<Vec
<u8>, Error
> {
92 let start
= std
::time
::SystemTime
::now();
94 let mut last_msg
: Option
<String
> = None
;
96 let mut timeout
= std
::time
::Duration
::new(5, 0); // short timeout by default
99 match sg_raw
.do_command(&cmd
) {
100 Ok(data
) => return Ok(data
.to_vec()),
103 bail
!("{} failed: {}", error_prefix
, err
);
105 if let Some(ref sense
) = err
.sense
{
107 if sense
.sense_key
== SENSE_KEY_NO_SENSE
||
108 sense
.sense_key
== SENSE_KEY_RECOVERED_ERROR
||
109 sense
.sense_key
== SENSE_KEY_UNIT_ATTENTION
||
110 sense
.sense_key
== SENSE_KEY_NOT_READY
112 let msg
= err
.to_string();
113 if let Some(ref last
) = last_msg
{
115 eprintln
!("{}", err
);
116 last_msg
= Some(msg
);
119 eprintln
!("{}", err
);
120 last_msg
= Some(msg
);
123 // Not Ready - becoming ready
124 if sense
.sense_key
== SENSE_KEY_NOT_READY
&& sense
.asc
== 0x04 && sense
.ascq
== 1 {
125 // wait up to 5 minutes, long enough to finish inventorize
126 timeout
= std
::time
::Duration
::new(5*60, 0);
129 if start
.elapsed()?
> timeout
{
130 bail
!("{} failed: {}", error_prefix
, err
);
133 std
::thread
::sleep(std
::time
::Duration
::new(1, 0));
134 continue; // try again
143 fn read_element_address_assignment
<F
: AsRawFd
>(
145 ) -> Result
<AddressAssignmentPage
, Error
> {
147 let allocation_len
: u8 = u8::MAX
;
148 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
149 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
151 let mut cmd
= Vec
::new();
152 cmd
.push(0x1A); // MODE SENSE6 (1Ah)
153 cmd
.push(0x08); // DBD=1 (The Disable Block Descriptors)
154 cmd
.push(0x1D); // Element Address Assignment Page
156 cmd
.push(allocation_len
); // allocation len
157 cmd
.push(0); //control
159 let data
= execute_scsi_command(&mut sg_raw
, &cmd
, "read element address assignment", true)?
;
161 proxmox
::try_block
!({
162 let mut reader
= &data
[..];
163 let page
: AddressAssignmentPage
= unsafe { reader.read_be_value()? }
;
165 if page
.data_len
!= 23 {
166 bail
!("got unexpected page len ({} != 23)", page
.data_len
);
170 }).map_err(|err
: Error
| format_err
!("decode element address assignment page failed - {}", err
))
173 fn scsi_move_medium_cdb(
174 medium_transport_address
: u16,
175 source_element_address
: u16,
176 destination_element_address
: u16,
179 let mut cmd
= Vec
::new();
180 cmd
.push(0xA5); // MOVE MEDIUM (A5h)
181 cmd
.push(0); // reserved
182 cmd
.extend(&medium_transport_address
.to_be_bytes());
183 cmd
.extend(&source_element_address
.to_be_bytes());
184 cmd
.extend(&destination_element_address
.to_be_bytes());
185 cmd
.push(0); // reserved
186 cmd
.push(0); // reserved
187 cmd
.push(0); // Invert=0
188 cmd
.push(0); // control
193 /// Load media from storage slot into drive
198 ) -> Result
<(), Error
> {
199 let status
= read_element_status(file
)?
;
201 let transport_address
= status
.transport_address();
202 let source_element_address
= status
.slot_address(from_slot
)?
;
203 let drive_element_address
= status
.drive_address(drivenum
)?
;
205 let cmd
= scsi_move_medium_cdb(
207 source_element_address
,
208 drive_element_address
,
211 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
212 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
214 sg_raw
.do_command(&cmd
)
215 .map_err(|err
| format_err
!("load drive failed - {}", err
))?
;
220 /// Unload media from drive into a storage slot
225 ) -> Result
<(), Error
> {
227 let status
= read_element_status(file
)?
;
229 let transport_address
= status
.transport_address();
230 let target_element_address
= status
.slot_address(to_slot
)?
;
231 let drive_element_address
= status
.drive_address(drivenum
)?
;
233 let cmd
= scsi_move_medium_cdb(
235 drive_element_address
,
236 target_element_address
,
239 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
240 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
242 sg_raw
.do_command(&cmd
)
243 .map_err(|err
| format_err
!("unload drive failed - {}", err
))?
;
248 /// Tranfer medium from one storage slot to another
249 pub fn transfer_medium
<F
: AsRawFd
>(
253 ) -> Result
<(), Error
> {
255 let status
= read_element_status(file
)?
;
257 let transport_address
= status
.transport_address();
258 let source_element_address
= status
.slot_address(from_slot
)?
;
259 let target_element_address
= status
.slot_address(to_slot
)?
;
261 let cmd
= scsi_move_medium_cdb(
263 source_element_address
,
264 target_element_address
,
267 let mut sg_raw
= SgRaw
::new(file
, 64)?
;
268 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
270 sg_raw
.do_command(&cmd
)
272 format_err
!("transfer medium from slot {} to slot {} failed - {}",
273 from_slot
, to_slot
, err
)
279 fn scsi_read_element_status_cdb(
280 start_element_address
: u16,
284 let mut cmd
= Vec
::new();
285 cmd
.push(0xB8); // READ ELEMENT STATUS (B8h)
286 cmd
.push(1u8<<4); // report all types and volume tags
287 cmd
.extend(&start_element_address
.to_be_bytes());
289 let number_of_elements
: u16 = 0xffff;
290 cmd
.extend(&number_of_elements
.to_be_bytes());
291 cmd
.push(0b001); // Mixed=0,CurData=0,DVCID=1
292 cmd
.extend(&allocation_len
.to_be_bytes()[1..4]);
299 /// Read element status.
300 pub fn read_element_status
<F
: AsRawFd
>(file
: &mut F
) -> Result
<MtxStatus
, Error
> {
302 let inquiry
= scsi_inquiry(file
)?
;
304 if inquiry
.peripheral_type
!= 8 {
305 bail
!("wrong device type (not a scsi changer device)");
308 // first, request address assignment (used for sanity checks)
309 let setup
= read_element_address_assignment(file
)?
;
311 let allocation_len
: u32 = 0x10000;
313 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
314 sg_raw
.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT
);
316 let mut start_element_address
= 0;
318 let mut drives
= Vec
::new();
319 let mut storage_slots
= Vec
::new();
320 let mut import_export_slots
= Vec
::new();
321 let mut transports
= Vec
::new();
323 let mut retry
= true;
326 let cmd
= scsi_read_element_status_cdb(start_element_address
, allocation_len
);
328 let data
= execute_scsi_command(&mut sg_raw
, &cmd
, "read element status (B8h)", retry
)?
;
330 let page
= decode_element_status_page(&inquiry
, &data
, start_element_address
)?
;
332 retry
= false; // only retry the first command
334 transports
.extend(page
.transports
);
335 drives
.extend(page
.drives
);
336 storage_slots
.extend(page
.storage_slots
);
337 import_export_slots
.extend(page
.import_export_slots
);
339 if data
.len() < (allocation_len
as usize) {
343 if let Some(last_element_address
) = page
.last_element_address
{
344 if last_element_address
>= start_element_address
{
345 start_element_address
= last_element_address
+ 1;
347 bail
!("got strange element address");
354 if (setup
.transport_element_count
as usize) != transports
.len() {
355 bail
!("got wrong number of transport elements");
357 if (setup
.storage_element_count
as usize) != storage_slots
.len() {
358 bail
!("got wrong number of storage elements");
360 if (setup
.import_export_element_count
as usize) != import_export_slots
.len() {
361 bail
!("got wrong number of import/export elements");
363 if (setup
.transfer_element_count
as usize) != drives
.len() {
364 bail
!("got wrong number of tranfer elements");
367 // create same virtual slot order as mtx(1)
368 // - storage slots first
369 // - import export slots at the end
370 let mut slots
= storage_slots
;
371 slots
.extend(import_export_slots
);
373 let mut status
= MtxStatus { transports, drives, slots }
;
376 if status
.drives
.is_empty() {
377 bail
!("no data transfer elements reported");
379 if status
.slots
.is_empty() {
380 bail
!("no storage elements reported");
383 // compute virtual storage slot to element_address map
384 let mut slot_map
= HashMap
::new();
385 for (i
, slot
) in status
.slots
.iter().enumerate() {
386 slot_map
.insert(slot
.element_address
, (i
+ 1) as u64);
389 // translate element addresses in loaded_lot
390 for drive
in status
.drives
.iter_mut() {
391 if let Some(source_address
) = drive
.loaded_slot
{
392 let source_address
= source_address
as u16;
393 drive
.loaded_slot
= slot_map
.get(&source_address
).map(|v
| *v
);
402 struct ElementStatusHeader
{
403 first_element_address_reported
: u16,
404 number_of_elements_available
: u16,
406 byte_count_of_report_available
: [u8;3],
412 element_type_code
: u8,
414 descriptor_length
: u16,
416 byte_count_of_descriptor_data_available
: [u8;3],
421 fn parse_optional_volume_tag
<R
: Read
>(
425 ) -> Result
<Option
<String
>, Error
> {
427 if (self.flags
& 128) != 0 { // has PVolTag
428 let tmp
= reader
.read_exact_allocated(36)?
;
430 let volume_tag
= scsi_ascii_to_string(&tmp
);
431 return Ok(Some(volume_tag
));
437 // AFAIK, tape changer do not use AlternateVolumeTag
438 // but parse anyways, just to be sure
439 fn skip_alternate_volume_tag
<R
: Read
>(
442 ) -> Result
<Option
<String
>, Error
> {
444 if (self.flags
& 64) != 0 { // has AVolTag
445 let _tmp
= reader
.read_exact_allocated(36)?
;
454 struct TrasnsportDescriptor
{ // Robot/Griper
455 element_address
: u16,
458 additional_sense_code
: u8,
459 additional_sense_code_qualifier
: u8,
462 source_storage_element_address
: u16,
463 // volume tag and Mixed media descriptor follows (depends on flags)
468 struct TransferDescriptor
{ // Tape drive
469 element_address
: u16,
472 additional_sense_code
: u8,
473 additional_sense_code_qualifier
: u8,
475 scsi_bus_address
: u8,
478 source_storage_element_address
: u16,
479 // volume tag, drive identifier and Mixed media descriptor follows
480 // (depends on flags)
485 struct DvcidHead
{ // Drive Identifier Header
490 // Identifier follows
495 struct StorageDescriptor
{ // Mail Slot
496 element_address
: u16,
499 additional_sense_code
: u8,
500 additional_sense_code_qualifier
: u8,
503 source_storage_element_address
: u16,
504 // volume tag and Mixed media descriptor follows (depends on flags)
507 struct DecodedStatusPage
{
508 last_element_address
: Option
<u16>,
509 transports
: Vec
<TransportElementStatus
>,
510 drives
: Vec
<DriveStatus
>,
511 storage_slots
: Vec
<StorageElementStatus
>,
512 import_export_slots
: Vec
<StorageElementStatus
>,
515 fn create_element_status(full
: bool
, volume_tag
: Option
<String
>) -> ElementStatus
{
517 if let Some(volume_tag
) = volume_tag
{
518 ElementStatus
::VolumeTag(volume_tag
)
527 fn decode_element_status_page(
530 start_element_address
: u16,
531 ) -> Result
<DecodedStatusPage
, Error
> {
533 proxmox
::try_block
!({
535 let mut result
= DecodedStatusPage
{
536 last_element_address
: None
,
537 transports
: Vec
::new(),
539 storage_slots
: Vec
::new(),
540 import_export_slots
: Vec
::new(),
543 let mut reader
= &data
[..];
545 let head
: ElementStatusHeader
= unsafe { reader.read_be_value()? }
;
547 if head
.number_of_elements_available
== 0 {
551 if head
.first_element_address_reported
< start_element_address
{
552 bail
!("got wrong first_element_address_reported"); // sanity check
556 if reader
.is_empty() {
560 let subhead
: SubHeader
= unsafe { reader.read_be_value()? }
;
562 let len
= subhead
.byte_count_of_descriptor_data_available
;
563 let mut len
= ((len
[0] as usize) << 16) + ((len
[1] as usize) << 8) + (len
[2] as usize);
564 if len
> reader
.len() {
568 let descr_data
= reader
.read_exact_allocated(len
)?
;
569 let mut reader
= &descr_data
[..];
572 if reader
.is_empty() {
575 if reader
.len() < (subhead
.descriptor_length
as usize) {
579 match subhead
.element_type_code
{
581 let desc
: TrasnsportDescriptor
= unsafe { reader.read_be_value()? }
;
583 let full
= (desc
.flags1
& 1) != 0;
584 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
586 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
588 let mut reserved
= [0u8; 4];
589 reader
.read_exact(&mut reserved
)?
;
591 result
.last_element_address
= Some(desc
.element_address
);
593 let status
= TransportElementStatus
{
594 status
: create_element_status(full
, volume_tag
),
595 element_address
: desc
.element_address
,
597 result
.transports
.push(status
);
600 let desc
: StorageDescriptor
= unsafe { reader.read_be_value()? }
;
602 let full
= (desc
.flags1
& 1) != 0;
603 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
605 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
607 let mut reserved
= [0u8; 4];
608 reader
.read_exact(&mut reserved
)?
;
610 result
.last_element_address
= Some(desc
.element_address
);
612 if subhead
.element_type_code
== 3 {
613 let status
= StorageElementStatus
{
615 status
: create_element_status(full
, volume_tag
),
616 element_address
: desc
.element_address
,
618 result
.import_export_slots
.push(status
);
620 let status
= StorageElementStatus
{
621 import_export
: false,
622 status
: create_element_status(full
, volume_tag
),
623 element_address
: desc
.element_address
,
625 result
.storage_slots
.push(status
);
629 let desc
: TransferDescriptor
= unsafe { reader.read_be_value()? }
;
631 let loaded_slot
= if (desc
.flags2
& 128) != 0 { // SValid
632 Some(desc
.source_storage_element_address
as u64)
637 let full
= (desc
.flags1
& 1) != 0;
638 let volume_tag
= subhead
.parse_optional_volume_tag(&mut reader
, full
)?
;
640 subhead
.skip_alternate_volume_tag(&mut reader
)?
;
642 let dvcid
: DvcidHead
= unsafe { reader.read_be_value()? }
;
644 let drive_serial_number
= match (dvcid
.code_set
, dvcid
.identifier_type
) {
645 (2, 0) => { // Serial number only (Quantum Superloader3 uses this)
646 let serial
= reader
.read_exact_allocated(dvcid
.identifier_len
as usize)?
;
647 let serial
= scsi_ascii_to_string(&serial
);
651 if dvcid
.identifier_len
!= 34 {
652 bail
!("got wrong DVCID length");
654 let _vendor
= reader
.read_exact_allocated(8)?
;
655 let _product
= reader
.read_exact_allocated(16)?
;
656 let serial
= reader
.read_exact_allocated(10)?
;
657 let serial
= scsi_ascii_to_string(&serial
);
663 result
.last_element_address
= Some(desc
.element_address
);
665 let drive
= DriveStatus
{
667 status
: create_element_status(full
, volume_tag
),
669 element_address
: desc
.element_address
,
671 result
.drives
.push(drive
);
673 code
=> bail
!("got unknown element type code {}", code
),
679 }).map_err(|err
: Error
| format_err
!("decode element status failed - {}", err
))
682 /// Open the device for read/write, returns the file handle
683 pub fn open
<P
: AsRef
<Path
>>(path
: P
) -> Result
<File
, Error
> {
684 let file
= OpenOptions
::new()