1 //! Bindings for libsgutils2
3 //! Incomplete, but we currently do not need more.
5 //! See: `/usr/include/scsi/sg_pt.h`
7 //! The SCSI Commands Reference Manual also contains some usefull information.
9 use std
::os
::unix
::io
::AsRawFd
;
10 use std
::ptr
::NonNull
;
12 use anyhow
::{bail, format_err, Error}
;
13 use endian_trait
::Endian
;
14 use serde
::{Deserialize, Serialize}
;
15 use libc
::{c_char, c_int}
;
17 use proxmox
::tools
::io
::ReadExt
;
20 pub struct SenseInfo
{
26 impl ToString
for SenseInfo
{
28 fn to_string(&self) -> String
{
30 // Added codes from IBM TS4300 Tape Library SCSI reference manual
31 // Added codes from Quantum Intelligent Libraries SCSI Reference Guide
32 match (self.sense_key
, self.asc
, self.ascq
) {
33 (0x00, asc
, ascq
) => format
!("no sense, ASC = 0x{:02x}, ASCQ = 0x{:02x}", asc
, ascq
),
34 (0x01, asc
, ascq
) => format
!("recevered error, ASC = 0x{:02x}, ASCQ = 0x{:02x}", asc
, ascq
),
36 (0x02, 0x04, 0x00) => String
::from("Not ready, cause not reportable"),
37 (0x02, 0x04, 0x01) => String
::from("Not ready, operation in progress"),
38 (0x02, 0x04, 0x03) => String
::from("Not ready, manual intervention required"),
39 (0x02, 0x04, 0x12) => String
::from("Not ready, offline"),
40 (0x02, 0x04, 0x83) => String
::from("The library is not ready due to aisle power being disabled"),
41 (0x02, 0x04, 0x8D) => String
::from(" The library is not ready because it is offline"),
42 (0x02, 0x3B, 0x12) => String
::from("Not ready, magazine removed"),
43 (0x02, asc
, ascq
) => format
!("not ready, ASC = 0x{:02x}, ASCQ = 0x{:02x}", asc
, ascq
),
45 (0x03, 0x30, 0x00) => String
::from("Media error"),
46 (0x03, 0x30, 0x07) => String
::from("Cleaning failure"),
47 (0x03, asc
, ascq
) => format
!("media error, ASC = 0x{:02x}, ASCQ = 0x{:02x}", asc
, ascq
),
49 (0x04, 0x15, 0x01) => String
::from("A mechanical positioning error occurred"),
50 (0x04, 0x3B, 0x0D) => String
::from("Medium destination element full"),
51 (0x04, 0x3B, 0x0E) => String
::from("Medium source element empty"),
52 (0x04, 0x3F, 0x0F) => String
::from("Echo buffer overwritten"),
53 (0x04, 0x40, 0x80) => String
::from("Component failure"),
54 (0x04, 0x44, 0x00) => String
::from("Firmware detected an internal logic failure"),
55 (0x04, 0x53, 0x00) => String
::from("A drive did not load or unload a tape"),
56 (0x04, 0x53, 0x01) => String
::from("A drive did not unload a cartridge"),
57 (0x04, 0x53, 0x82) => String
::from("Cannot lock the I/E station"),
58 (0x04, 0x53, 0x83) => String
::from("Cannot unlock the I/E station"),
59 (0x04, 0x80, 0xD7) => String
::from("Internal software error"),
60 (0x04, 0x80, 0xD8) => String
::from("Database access error"),
61 (0x04, 0x81, 0xB0) => String
::from("Internal system communication failed"),
62 (0x04, 0x81, 0xB2) => String
::from("Robotic controller communication failed"),
63 (0x04, 0x81, 0xB3) => String
::from("Mechanical positioning error"),
64 (0x04, 0x81, 0xB4) => String
::from("Cartridge did not transport completely."),
65 (0x04, 0x82, 0xFC) => String
::from("Drive configuration failed"),
66 (0x04, 0x83, 0x00) => String
::from("Label too short or too long"),
67 (0x04, asc
, ascq
) => format
!("hardware error, ASC = 0x{:02x}, ASCQ = 0x{:02x}", asc
, ascq
),
69 (0x05, 0x04, 0x83) => String
::from("Door open"),
70 (0x05, 0x1A, 0x00) => String
::from("Parameter length error"),
71 (0x05, 0x20, 0x00) => String
::from("Invalid command operation code"),
72 (0x05, 0x21, 0x01) => String
::from("Invalid element address"),
73 (0x05, 0x24, 0x00) => String
::from("Invalid field CDB"),
74 (0x05, 0x25, 0x00) => String
::from("Invalid LUN"),
75 (0x05, 0x26, 0x00) => String
::from("Invalid field in parameter list"),
76 (0x05, 0x26, 0x01) => String
::from("Parameter list error: parameter not supported"),
77 (0x05, 0x26, 0x02) => String
::from("Parameter value invalid"),
78 (0x05, 0x26, 0x04) => String
::from("Invalid release of persistent reservation"),
79 (0x05, 0x2C, 0x00) => String
::from("Saving parameters is not supported"),
80 (0x05, 0x30, 0x00) => String
::from("Incompatible medium installed"),
81 (0x05, 0x30, 0x12) => String
::from("Incompatible Media loaded to drive"),
82 (0x05, 0x39, 0x00) => String
::from("Saving parameters is not supported"),
83 (0x05, 0x3B, 0x0D) => String
::from("Medium destination element full"),
84 (0x05, 0x3B, 0x0E) => String
::from("Medium source element empty"),
85 (0x05, 0x3B, 0x11) => String
::from("Magazine not accessible"),
86 (0x05, 0x3B, 0x12) => String
::from("Magazine not installed"),
87 (0x05, 0x3B, 0x18) => String
::from("Element disabled"),
88 (0x05, 0x3B, 0x1A) => String
::from("Data transfer element removed"),
89 (0x05, 0x3B, 0xA0) => String
::from("Media type does not match destination media type"),
90 (0x05, 0x3F, 0x01) => String
::from("New firmware loaded"),
91 (0x05, 0x3F, 0x03) => String
::from("Inquiry data changed"),
92 (0x05, 0x44, 0x81) => String
::from("Source element not ready"),
93 (0x05, 0x44, 0x82) => String
::from("Destination element not ready"),
94 (0x05, 0x53, 0x02) => String
::from("Library media removal prevented state set."),
95 (0x05, 0x53, 0x03) => String
::from("Drive media removal prevented state set"),
96 (0x05, 0x53, 0x81) => String
::from("Insert/Eject station door is open"),
97 (0x05, 0x82, 0x93) => String
::from("Failure session sequence error"),
98 (0x05, 0x82, 0x94) => String
::from("Failover command sequence error"),
99 (0x05, 0x82, 0x95) => String
::from("Duplicate failover session key"),
100 (0x05, 0x82, 0x96) => String
::from("Invalid failover key"),
101 (0x05, 0x82, 0x97) => String
::from("Failover session that is released"),
102 (0x05, 0x83, 0x02) => String
::from("Barcode label questionable"),
103 (0x05, 0x83, 0x03) => String
::from("Cell status and barcode label questionable"),
104 (0x05, 0x83, 0x04) => String
::from("Data transfer element not installed"),
105 (0x05, 0x83, 0x05) => String
::from("Data transfer element is varied off and not accessible for library operations"),
106 (0x05, 0x83, 0x06) => String
::from("Element is contained within an offline tower or I/E station and is not accessible for library operations"),
107 (0x05, asc
, ascq
) => format
!("illegal request, ASC = 0x{:02x}, ASCQ = 0x{:02x}", asc
, ascq
),
109 (0x06, 0x28, 0x00) => String
::from("Not ready to change, medium changed"),
110 (0x06, 0x28, 0x01) => String
::from("Import/export element that is accessed"),
111 (0x06, 0x29, 0x00) => String
::from("Power-on or reset occurred"),
112 (0x06, 0x29, 0x01) => String
::from("Power on occurred"),
113 (0x06, 0x29, 0x02) => String
::from("SCSI Bus reset occurred"),
114 (0x06, 0x29, 0x03) => String
::from("Device reset occurred"),
115 (0x06, 0x29, 0x04) => String
::from("Internal reset occurred"),
116 (0x06, 0x2A, 0x01) => String
::from("Mode parameters have been changed"),
117 (0x06, 0x2A, 0x03) => String
::from("Reservations preempted"),
118 (0x06, 0x2A, 0x04) => String
::from("Reservations released"),
119 (0x06, 0x2A, 0x05) => String
::from("Registrations preempted"),
120 (0x06, asc
, ascq
) => format
!("unit attention, ASC = 0x{:02x}, ASCQ = 0x{:02x}", asc
, ascq
),
122 (0x0B, 0x3F, 0x0F) => String
::from("ECHO buffer overwritten"),
123 (0x0B, 0x44, 0x00) => String
::from("Firmware detected an internal logic failure"),
124 (0x0B, 0x45, 0x00) => String
::from("Select or reselect failure"),
125 (0x0B, 0x47, 0x00) => String
::from("SCSI parity error"),
126 (0x0B, 0x48, 0x00) => String
::from("Initiator detected error message received"),
127 (0x0B, 0x49, 0x00) => String
::from("Invalid message error"),
128 (0x0B, 0x4A, 0x00) => String
::from("Command phase error"),
129 (0x0B, 0x4B, 0x00) => String
::from("Data phase error"),
130 (0x0B, 0x4E, 0x00) => String
::from("Overlapped command attempt"),
131 (0x0B, asc
, ascq
) => format
!("aborted command, ASC = 0x{:02x}, ASCQ = 0x{:02x}", asc
, ascq
),
132 // Else, simply report values
133 _
=> format
!("sense_key = 0x{:02x}, ASC = 0x{:02x}, ASCQ = 0x{:02x}", self.sense_key
, self.asc
, self.ascq
),
139 pub struct ScsiError
{
141 pub sense
: Option
<SenseInfo
>,
144 impl std
::fmt
::Display
for ScsiError
{
145 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
146 write
!(f
, "{}", self.error
)
150 impl std
::error
::Error
for ScsiError
{
151 fn source(&self) -> Option
<&(dyn std
::error
::Error
+ '
static)> {
156 impl From
<anyhow
::Error
> for ScsiError
{
157 fn from(error
: anyhow
::Error
) -> Self {
158 Self { error, sense: None }
162 impl From
<std
::io
::Error
> for ScsiError
{
163 fn from(error
: std
::io
::Error
) -> Self {
164 Self { error: error.into(), sense: None }
168 // Opaque wrapper for sg_pt_base
170 struct SgPtBase { _private: [u8; 0] }
174 raw
: NonNull
<SgPtBase
>,
179 unsafe { destruct_scsi_pt_obj(self.as_mut_ptr()) }
;
184 fn new() -> Result
<Self, Error
> {
186 raw
: NonNull
::new(unsafe { construct_scsi_pt_obj() }
)
187 .ok_or_else(|| format_err
!("construct_scsi_pt_ob failed"))?
,
191 fn as_ptr(&self) -> *const SgPtBase
{
195 fn as_mut_ptr(&mut self) -> *mut SgPtBase
{
200 /// Peripheral device type text (see `inquiry` command)
202 /// see [https://en.wikipedia.org/wiki/SCSI_Peripheral_Device_Type]
203 pub const PERIPHERAL_DEVICE_TYPE_TEXT
: [&'
static str; 32] = [
212 "Medium Changer", // 08h
217 "Enclosure Services",
218 "Simplified direct-access",
219 "Optical card reader/writer",
221 "Object-based Storage",
222 "Automation/Drive Interface",
239 pub const SENSE_KEY_NO_SENSE
: u8 = 0x00;
240 pub const SENSE_KEY_RECOVERED_ERROR
: u8 = 0x01;
241 pub const SENSE_KEY_NOT_READY
: u8 = 0x02;
242 pub const SENSE_KEY_MEDIUM_ERROR
: u8 = 0x03;
243 pub const SENSE_KEY_HARDWARE_ERROR
: u8 = 0x04;
244 pub const SENSE_KEY_ILLEGAL_REQUEST
: u8 = 0x05;
245 pub const SENSE_KEY_UNIT_ATTENTION
: u8 = 0x06;
246 pub const SENSE_KEY_DATA_PROTECT
: u8 = 0x07;
247 pub const SENSE_KEY_BLANK_CHECK
: u8 = 0x08;
248 pub const SENSE_KEY_COPY_ABORTED
: u8 = 0x0a;
249 pub const SENSE_KEY_ABORTED_COMMAND
: u8 = 0x0b;
250 pub const SENSE_KEY_VOLUME_OVERFLOW
: u8 = 0x0d;
251 pub const SENSE_KEY_MISCOMPARE
: u8 = 0x0e;
261 additional_length
: u8,
268 // additional data follows, but we do not need that
272 #[derive(Endian, Debug)]
273 struct RequestSenseFixed
{
278 additional_sense_len
: u8,
279 command_specific_information
: [u8;4],
280 additional_sense_code
: u8,
281 additional_sense_code_qualifier
: u8,
282 field_replacable_unit_code
: u8,
283 sense_key_specific
: [u8; 3],
287 #[derive(Endian, Debug)]
288 struct RequestSenseDescriptor
{
291 additional_sense_code
: u8,
292 additional_sense_code_qualifier
: u8,
294 additional_sense_len
: u8,
298 #[derive(Serialize, Deserialize, Debug)]
299 pub struct InquiryInfo
{
300 /// Peripheral device type (0-31)
301 pub peripheral_type
: u8,
302 /// Peripheral device type as string
303 pub peripheral_type_text
: String
,
309 pub revision
: String
,
312 pub const SCSI_PT_DO_START_OK
:c_int
= 0;
313 pub const SCSI_PT_DO_BAD_PARAMS
:c_int
= 1;
314 pub const SCSI_PT_DO_TIMEOUT
:c_int
= 2;
316 pub const SCSI_PT_RESULT_GOOD
:c_int
= 0;
317 pub const SCSI_PT_RESULT_STATUS
:c_int
= 1;
318 pub const SCSI_PT_RESULT_SENSE
:c_int
= 2;
319 pub const SCSI_PT_RESULT_TRANSPORT_ERR
:c_int
= 3;
320 pub const SCSI_PT_RESULT_OS_ERR
:c_int
= 4;
322 #[link(name = "sgutils2")]
326 fn scsi_pt_open_device(
327 device_name
: * const c_char
,
337 fn construct_scsi_pt_obj() -> *mut SgPtBase
;
338 fn destruct_scsi_pt_obj(objp
: *mut SgPtBase
);
340 fn set_scsi_pt_data_in(
346 fn set_scsi_pt_data_out(
358 fn set_scsi_pt_sense(
361 max_sense_len
: c_int
,
371 fn get_scsi_pt_resid(objp
: *const SgPtBase
) -> c_int
;
373 fn get_scsi_pt_sense_len(objp
: *const SgPtBase
) -> c_int
;
375 fn get_scsi_pt_status_response(objp
: *const SgPtBase
) -> c_int
;
377 fn get_scsi_pt_result_category(objp
: *const SgPtBase
) -> c_int
;
379 fn get_scsi_pt_os_err(objp
: *const SgPtBase
) -> c_int
;
382 /// Safe interface to run RAW SCSI commands
383 pub struct SgRaw
<'a
, F
> {
386 sense_buffer
: [u8; 32],
390 /// Allocate a page aligned buffer
392 /// SG RAWIO commands needs page aligned transfer buffers.
393 pub fn alloc_page_aligned_buffer(buffer_size
: usize) -> Result
<Box
<[u8]> , Error
> {
394 let page_size
= unsafe { libc::sysconf(libc::_SC_PAGESIZE) }
as usize;
395 let layout
= std
::alloc
::Layout
::from_size_align(buffer_size
, page_size
)?
;
396 let dinp
= unsafe { std::alloc::alloc_zeroed(layout) }
;
398 bail
!("alloc SCSI output buffer failed");
401 let buffer_slice
= unsafe { std::slice::from_raw_parts_mut(dinp, buffer_size)}
;
402 Ok(unsafe { Box::from_raw(buffer_slice) }
)
405 impl <'a
, F
: AsRawFd
> SgRaw
<'a
, F
> {
407 /// Create a new instance to run commands
409 /// The file must be a handle to a SCSI device.
410 pub fn new(file
: &'a
mut F
, buffer_size
: usize) -> Result
<Self, Error
> {
415 buffer
= alloc_page_aligned_buffer(buffer_size
)?
;
417 buffer
= Box
::new([]);
420 let sense_buffer
= [0u8; 32];
422 Ok(Self { file, buffer, sense_buffer, timeout: 0 }
)
425 /// Set the command timeout in seconds (0 means default (60 seconds))
426 pub fn set_timeout(&mut self, seconds
: usize) {
427 if seconds
> (i32::MAX
as usize) {
428 self.timeout
= i32::MAX
; // don't care about larger values
430 self.timeout
= seconds
as i32;
434 // create new object with initialized data_in and sense buffer
435 fn create_scsi_pt_obj(&mut self) -> Result
<SgPt
, Error
> {
437 let mut ptvp
= SgPt
::new()?
;
439 if self.buffer
.len() > 0 {
443 self.buffer
.as_mut_ptr(),
444 self.buffer
.len() as c_int
,
452 self.sense_buffer
.as_mut_ptr(),
453 self.sense_buffer
.len() as c_int
,
460 fn do_scsi_pt_checked(&mut self, ptvp
: &mut SgPt
) -> Result
<(), ScsiError
> {
462 let res
= unsafe { do_scsi_pt(ptvp.as_mut_ptr(), self.file.as_raw_fd(), self.timeout, 0) }
;
464 SCSI_PT_DO_START_OK
=> { /* Ok */ }
,
465 SCSI_PT_DO_BAD_PARAMS
=> return Err(format_err
!("do_scsi_pt failed - bad pass through setup").into()),
466 SCSI_PT_DO_TIMEOUT
=> return Err(format_err
!("do_scsi_pt failed - timeout").into()),
467 code
if code
< 0 => {
468 let errno
= unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) }
;
469 let err
= nix
::Error
::from_errno(nix
::errno
::Errno
::from_i32(errno
));
470 return Err(format_err
!("do_scsi_pt failed with err {}", err
).into());
472 unknown
=> return Err(format_err
!("do_scsi_pt failed: unknown error {}", unknown
).into()),
476 let err
= nix
::Error
::last();
477 return Err(format_err
!("do_scsi_pt failed - {}", err
).into());
480 return Err(format_err
!("do_scsi_pt failed {}", res
).into());
483 let sense_len
= unsafe { get_scsi_pt_sense_len(ptvp.as_ptr()) }
;
485 let res_cat
= unsafe { get_scsi_pt_result_category(ptvp.as_ptr()) }
;
487 SCSI_PT_RESULT_GOOD
=> { /* Ok */ }
488 SCSI_PT_RESULT_STATUS
=> { /* test below */ }
489 SCSI_PT_RESULT_SENSE
=> {
491 return Err(format_err
!("scsi command failed: no Sense").into());
494 let code
= self.sense_buffer
[0] & 0x7f;
496 let mut reader
= &self.sense_buffer
[..(sense_len
as usize)];
498 let sense
= match code
{
500 let sense
: RequestSenseFixed
= unsafe { reader.read_be_value()? }
;
502 sense_key
: sense
.flags2
& 0xf,
503 asc
: sense
.additional_sense_code
,
504 ascq
: sense
.additional_sense_code_qualifier
,
508 let sense
: RequestSenseDescriptor
= unsafe { reader.read_be_value()? }
;
510 sense_key
: sense
.sense_key
& 0xf,
511 asc
: sense
.additional_sense_code
,
512 ascq
: sense
.additional_sense_code_qualifier
,
516 return Err(format_err
!("scsi command failed: received deferred Sense").into());
519 return Err(format_err
!("scsi command failed: invalid Sense response code {:x}", unknown
).into());
523 return Err(ScsiError
{
524 error
: format_err
!("scsi command failed: {}", sense
.to_string()),
528 SCSI_PT_RESULT_TRANSPORT_ERR
=> return Err(format_err
!("scsi command failed: transport error").into()),
529 SCSI_PT_RESULT_OS_ERR
=> {
530 let errno
= unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) }
;
531 let err
= nix
::Error
::from_errno(nix
::errno
::Errno
::from_i32(errno
));
532 return Err(format_err
!("scsi command failed with err {}", err
).into());
534 unknown
=> return Err(format_err
!("scsi command failed: unknown result category {}", unknown
).into()),
537 let status
= unsafe { get_scsi_pt_status_response(ptvp.as_ptr()) }
;
539 return Err(format_err
!("unknown scsi error - status response {}", status
).into());
545 /// Run the specified RAW SCSI command
546 pub fn do_command(&mut self, cmd
: &[u8]) -> Result
<&[u8], ScsiError
> {
548 if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) }
{
549 return Err(format_err
!("no valid SCSI command").into());
552 if self.buffer
.len() < 16 {
553 return Err(format_err
!("output buffer too small").into());
556 let mut ptvp
= self.create_scsi_pt_obj()?
;
566 self.do_scsi_pt_checked(&mut ptvp
)?
;
568 let resid
= unsafe { get_scsi_pt_resid(ptvp.as_ptr()) }
as usize;
569 if resid
> self.buffer
.len() {
570 return Err(format_err
!("do_scsi_pt failed - got strange resid (value too big)").into());
572 let data_len
= self.buffer
.len() - resid
;
574 Ok(&self.buffer
[..data_len
])
577 /// Run dataout command
579 /// Note: use alloc_page_aligned_buffer to alloc data transfer buffer
580 pub fn do_out_command(&mut self, cmd
: &[u8], data
: &[u8]) -> Result
<(), Error
> {
582 if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) }
{
583 bail
!("no valid SCSI command");
586 let page_size
= unsafe { libc::sysconf(libc::_SC_PAGESIZE) }
as usize;
587 if ((data
.as_ptr() as usize) & (page_size
-1)) != 0 {
588 bail
!("wrong transfer buffer alignment");
591 let mut ptvp
= self.create_scsi_pt_obj()?
;
594 set_scsi_pt_data_out(
607 self.do_scsi_pt_checked(&mut ptvp
)?
;
615 /// Converts SCSI ASCII text into String, trim zero and spaces
616 pub fn scsi_ascii_to_string(data
: &[u8]) -> String
{
617 String
::from_utf8_lossy(data
)
618 .trim_matches(char::from(0))
623 /// Read SCSI Inquiry page
625 /// Returns Product/Vendor/Revision and device type.
626 pub fn scsi_inquiry
<F
: AsRawFd
>(
628 ) -> Result
<InquiryInfo
, Error
> {
630 let allocation_len
: u8 = u8::MAX
;
631 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
632 sg_raw
.set_timeout(30); // use short timeout
634 let mut cmd
= Vec
::new();
635 cmd
.extend(&[0x12, 0, 0, 0, allocation_len
, 0]); // INQUIRY
637 let data
= sg_raw
.do_command(&cmd
)
638 .map_err(|err
| format_err
!("SCSI inquiry failed - {}", err
))?
;
640 proxmox
::try_block
!({
641 let mut reader
= &data
[..];
643 let page
: InquiryPage
= unsafe { reader.read_be_value()? }
;
645 let peripheral_type
= page
.peripheral_type
& 31;
647 let info
= InquiryInfo
{
649 peripheral_type_text
: PERIPHERAL_DEVICE_TYPE_TEXT
[peripheral_type
as usize].to_string(),
650 vendor
: scsi_ascii_to_string(&page
.vendor
),
651 product
: scsi_ascii_to_string(&page
.product
),
652 revision
: scsi_ascii_to_string(&page
.revision
),
656 }).map_err(|err
: Error
| format_err
!("decode inquiry page failed - {}", err
))