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 useful information.
10 use std
::os
::unix
::io
::AsRawFd
;
11 use std
::ptr
::NonNull
;
13 use anyhow
::{bail, format_err, Error}
;
14 use endian_trait
::Endian
;
15 use libc
::{c_char, c_int}
;
16 use serde
::{Deserialize, Serialize}
;
18 use proxmox_io
::ReadExt
;
20 #[derive(thiserror::Error, Debug)]
21 pub struct SenseInfo
{
27 impl std
::fmt
::Display
for SenseInfo
{
28 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
29 let sense_text
= SENSE_KEY_DESCRIPTIONS
30 .get(self.sense_key
as usize)
31 .map(|s
| String
::from(*s
))
32 .unwrap_or_else(|| format
!("Invalid sense {:02X}", self.sense_key
));
34 if self.asc
== 0 && self.ascq
== 0 {
35 write
!(f
, "{}", sense_text
)
37 let additional_sense_text
= get_asc_ascq_string(self.asc
, self.ascq
);
38 write
!(f
, "{}, {}", sense_text
, additional_sense_text
)
43 #[derive(thiserror::Error, Debug)]
48 Sense(#[from] SenseInfo),
51 impl From
<std
::io
::Error
> for ScsiError
{
52 fn from(error
: std
::io
::Error
) -> Self {
53 Self::Error(error
.into())
57 // Opaque wrapper for sg_pt_base
65 raw
: NonNull
<SgPtBase
>,
70 unsafe { destruct_scsi_pt_obj(self.as_mut_ptr()) }
;
75 fn new() -> Result
<Self, Error
> {
77 raw
: NonNull
::new(unsafe { construct_scsi_pt_obj() }
)
78 .ok_or_else(|| format_err
!("construct_scsi_pt_ob failed"))?
,
82 fn as_ptr(&self) -> *const SgPtBase
{
86 fn as_mut_ptr(&mut self) -> *mut SgPtBase
{
91 /// Peripheral device type text (see `inquiry` command)
93 /// see <https://en.wikipedia.org/wiki/SCSI_Peripheral_Device_Type>
94 pub const PERIPHERAL_DEVICE_TYPE_TEXT
: [&str; 32] = [
103 "Medium Changer", // 08h
108 "Enclosure Services",
109 "Simplified direct-access",
110 "Optical card reader/writer",
112 "Object-based Storage",
113 "Automation/Drive Interface",
130 pub const SENSE_KEY_NO_SENSE
: u8 = 0x00;
131 pub const SENSE_KEY_RECOVERED_ERROR
: u8 = 0x01;
132 pub const SENSE_KEY_NOT_READY
: u8 = 0x02;
133 pub const SENSE_KEY_MEDIUM_ERROR
: u8 = 0x03;
134 pub const SENSE_KEY_HARDWARE_ERROR
: u8 = 0x04;
135 pub const SENSE_KEY_ILLEGAL_REQUEST
: u8 = 0x05;
136 pub const SENSE_KEY_UNIT_ATTENTION
: u8 = 0x06;
137 pub const SENSE_KEY_DATA_PROTECT
: u8 = 0x07;
138 pub const SENSE_KEY_BLANK_CHECK
: u8 = 0x08;
139 pub const SENSE_KEY_COPY_ABORTED
: u8 = 0x0a;
140 pub const SENSE_KEY_ABORTED_COMMAND
: u8 = 0x0b;
141 pub const SENSE_KEY_VOLUME_OVERFLOW
: u8 = 0x0d;
142 pub const SENSE_KEY_MISCOMPARE
: u8 = 0x0e;
145 const SAM_STAT_CHECK_CONDITION
: i32 = 0x02;
147 /// Sense Key Descriptions
148 pub const SENSE_KEY_DESCRIPTIONS
: [&str; 16] = [
169 // Standard Inquiry page - 36 bytes
175 additional_length
: u8,
185 #[derive(Endian, Debug)]
186 pub struct RequestSenseFixed
{
187 pub response_code
: u8,
190 pub information
: [u8; 4],
191 pub additional_sense_len
: u8,
192 pub command_specific_information
: [u8; 4],
193 pub additional_sense_code
: u8,
194 pub additional_sense_code_qualifier
: u8,
195 pub field_replacable_unit_code
: u8,
196 pub sense_key_specific
: [u8; 3],
200 #[derive(Endian, Debug)]
201 struct RequestSenseDescriptor
{
204 additional_sense_code
: u8,
205 additional_sense_code_qualifier
: u8,
207 additional_sense_len
: u8,
211 #[derive(Serialize, Deserialize, Debug)]
212 pub struct InquiryInfo
{
213 /// Peripheral device type (0-31)
214 pub peripheral_type
: u8,
215 /// Peripheral device type as string
216 pub peripheral_type_text
: String
,
222 pub revision
: String
,
226 #[derive(Endian, Debug, Copy, Clone)]
227 pub struct ModeParameterHeader
{
228 pub mode_data_len
: u16,
229 // Note: medium_type and density_code are not the same. On HP
230 // drives, medium_type provides very limited information and is
231 // not compatible with IBM.
235 pub block_descriptior_len
: u16,
238 impl ModeParameterHeader
{
239 #[allow(clippy::unusual_byte_groupings)]
240 pub fn buffer_mode(&self) -> u8 {
241 (self.flags3
& 0b0_111_0000) >> 4
244 #[allow(clippy::unusual_byte_groupings)]
245 pub fn set_buffer_mode(&mut self, buffer_mode
: bool
) {
246 let mut mode
= self.flags3
& 0b1_000_1111;
248 mode
|= 0b0_001_0000;
253 #[allow(clippy::unusual_byte_groupings)]
254 pub fn write_protect(&self) -> bool
{
255 (self.flags3
& 0b1_000_0000) != 0
260 #[derive(Endian, Debug, Copy, Clone)]
261 /// SCSI ModeBlockDescriptor for Tape devices
262 pub struct ModeBlockDescriptor
{
263 pub density_code
: u8,
264 pub number_of_blocks
: [u8; 3],
266 pub block_length
: [u8; 3],
269 impl ModeBlockDescriptor
{
270 pub fn block_length(&self) -> u32 {
271 ((self.block_length
[0] as u32) << 16)
272 + ((self.block_length
[1] as u32) << 8)
273 + (self.block_length
[2] as u32)
276 pub fn set_block_length(&mut self, length
: u32) -> Result
<(), Error
> {
277 if length
> 0x80_00_00 {
278 bail
!("block length '{}' is too large", length
);
280 self.block_length
[0] = ((length
& 0x00ff0000) >> 16) as u8;
281 self.block_length
[1] = ((length
& 0x0000ff00) >> 8) as u8;
282 self.block_length
[2] = (length
& 0x000000ff) as u8;
287 pub const SCSI_PT_DO_START_OK
: c_int
= 0;
288 pub const SCSI_PT_DO_BAD_PARAMS
: c_int
= 1;
289 pub const SCSI_PT_DO_TIMEOUT
: c_int
= 2;
291 pub const SCSI_PT_RESULT_GOOD
: c_int
= 0;
292 pub const SCSI_PT_RESULT_STATUS
: c_int
= 1;
293 pub const SCSI_PT_RESULT_SENSE
: c_int
= 2;
294 pub const SCSI_PT_RESULT_TRANSPORT_ERR
: c_int
= 3;
295 pub const SCSI_PT_RESULT_OS_ERR
: c_int
= 4;
297 #[link(name = "sgutils2")]
301 fn scsi_pt_open_device(device_name
: *const c_char
, read_only
: bool
, verbose
: c_int
) -> c_int
;
303 fn sg_is_scsi_cdb(cdbp
: *const u8, clen
: c_int
) -> bool
;
305 fn construct_scsi_pt_obj() -> *mut SgPtBase
;
306 fn destruct_scsi_pt_obj(objp
: *mut SgPtBase
);
308 fn set_scsi_pt_data_in(objp
: *mut SgPtBase
, dxferp
: *mut u8, dxfer_ilen
: c_int
);
310 fn set_scsi_pt_data_out(objp
: *mut SgPtBase
, dxferp
: *const u8, dxfer_olen
: c_int
);
312 fn set_scsi_pt_cdb(objp
: *mut SgPtBase
, cdb
: *const u8, cdb_len
: c_int
);
314 fn set_scsi_pt_sense(objp
: *mut SgPtBase
, sense
: *mut u8, max_sense_len
: c_int
);
316 fn do_scsi_pt(objp
: *mut SgPtBase
, fd
: c_int
, timeout_secs
: c_int
, verbose
: c_int
) -> c_int
;
318 fn get_scsi_pt_resid(objp
: *const SgPtBase
) -> c_int
;
320 fn get_scsi_pt_sense_len(objp
: *const SgPtBase
) -> c_int
;
322 fn get_scsi_pt_status_response(objp
: *const SgPtBase
) -> c_int
;
324 fn get_scsi_pt_result_category(objp
: *const SgPtBase
) -> c_int
;
326 fn get_scsi_pt_os_err(objp
: *const SgPtBase
) -> c_int
;
328 fn sg_get_asc_ascq_str(
336 /// Safe interface to run RAW SCSI commands
337 pub struct SgRaw
<'a
, F
> {
340 sense_buffer
: [u8; 32],
344 /// Get the string associated with ASC/ASCQ values
345 pub fn get_asc_ascq_string(asc
: u8, ascq
: u8) -> String
{
346 let mut buffer
= [0u8; 1024];
351 buffer
.len() as c_int
,
352 buffer
.as_mut_ptr() as *mut c_char
,
356 proxmox_lang
::try_block
!({
359 bail
!("unexpected NULL ptr");
361 Ok(unsafe { CStr::from_ptr(res) }
.to_str()?
.to_owned())
363 .unwrap_or_else(|_err
: Error
| format
!("ASC={:02x}x, ASCQ={:02x}x", asc
, ascq
))
366 /// Allocate a page aligned buffer
368 /// SG RAWIO commands needs page aligned transfer buffers.
369 pub fn alloc_page_aligned_buffer(buffer_size
: usize) -> Result
<Box
<[u8]>, Error
> {
370 let page_size
= unsafe { libc::sysconf(libc::_SC_PAGESIZE) }
as usize;
371 let layout
= std
::alloc
::Layout
::from_size_align(buffer_size
, page_size
)?
;
372 let dinp
= unsafe { std::alloc::alloc_zeroed(layout) }
;
374 bail
!("alloc SCSI output buffer failed");
377 let buffer_slice
= unsafe { std::slice::from_raw_parts_mut(dinp, buffer_size) }
;
378 Ok(unsafe { Box::from_raw(buffer_slice) }
)
381 impl<'a
, F
: AsRawFd
> SgRaw
<'a
, F
> {
382 /// Create a new instance to run commands
384 /// The file must be a handle to a SCSI device.
385 pub fn new(file
: &'a
mut F
, buffer_size
: usize) -> Result
<Self, Error
> {
386 let buffer
= if buffer_size
> 0 {
387 alloc_page_aligned_buffer(buffer_size
)?
392 let sense_buffer
= [0u8; 32];
402 /// Set the command timeout in seconds (0 means default (60 seconds))
403 pub fn set_timeout(&mut self, seconds
: usize) {
404 if seconds
> (i32::MAX
as usize) {
405 self.timeout
= i32::MAX
; // don't care about larger values
407 self.timeout
= seconds
as i32;
411 // create new object with initialized data_in and sense buffer
412 fn create_scsi_pt_obj(&mut self) -> Result
<SgPt
, Error
> {
413 let mut ptvp
= SgPt
::new()?
;
415 if !self.buffer
.is_empty() {
419 self.buffer
.as_mut_ptr(),
420 self.buffer
.len() as c_int
,
428 self.sense_buffer
.as_mut_ptr(),
429 self.sense_buffer
.len() as c_int
,
436 fn do_scsi_pt_checked(&mut self, ptvp
: &mut SgPt
) -> Result
<(), ScsiError
> {
437 let res
= unsafe { do_scsi_pt(ptvp.as_mut_ptr(), self.file.as_raw_fd(), self.timeout, 0) }
;
439 SCSI_PT_DO_START_OK
=> { /* Ok */ }
440 SCSI_PT_DO_BAD_PARAMS
=> {
441 return Err(format_err
!("do_scsi_pt failed - bad pass through setup").into())
443 SCSI_PT_DO_TIMEOUT
=> return Err(format_err
!("do_scsi_pt failed - timeout").into()),
444 code
if code
< 0 => {
445 let errno
= unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) }
;
446 let err
= nix
::errno
::Errno
::from_i32(errno
);
447 return Err(format_err
!("do_scsi_pt failed with err {}", err
).into());
450 return Err(format_err
!("do_scsi_pt failed: unknown error {}", unknown
).into())
455 let err
= nix
::Error
::last();
456 return Err(format_err
!("do_scsi_pt failed - {}", err
).into());
459 return Err(format_err
!("do_scsi_pt failed {}", res
).into());
462 let sense_len
= unsafe { get_scsi_pt_sense_len(ptvp.as_ptr()) }
;
464 let mut res_cat
= unsafe { get_scsi_pt_result_category(ptvp.as_ptr()) }
;
465 let status
= unsafe { get_scsi_pt_status_response(ptvp.as_ptr()) }
;
467 if res_cat
== SCSI_PT_RESULT_TRANSPORT_ERR
&& status
== SAM_STAT_CHECK_CONDITION
{
468 res_cat
= SCSI_PT_RESULT_SENSE
;
472 SCSI_PT_RESULT_GOOD
=> Ok(()),
473 SCSI_PT_RESULT_STATUS
=> {
476 format_err
!("unknown scsi error - status response {}", status
).into(),
481 SCSI_PT_RESULT_SENSE
=> {
483 return Err(format_err
!("scsi command failed, but got no sense data").into());
486 let code
= self.sense_buffer
[0] & 0x7f;
488 let mut reader
= &self.sense_buffer
[..(sense_len
as usize)];
490 let sense
= match code
{
492 let sense
: RequestSenseFixed
= unsafe { reader.read_be_value()? }
;
494 sense_key
: sense
.flags2
& 0xf,
495 asc
: sense
.additional_sense_code
,
496 ascq
: sense
.additional_sense_code_qualifier
,
500 let sense
: RequestSenseDescriptor
= unsafe { reader.read_be_value()? }
;
502 sense_key
: sense
.sense_key
& 0xf,
503 asc
: sense
.additional_sense_code
,
504 ascq
: sense
.additional_sense_code_qualifier
,
509 format_err
!("scsi command failed: received deferred Sense").into()
513 return Err(format_err
!(
514 "scsi command failed: invalid Sense response code {:x}",
521 Err(ScsiError
::Sense(sense
))
523 SCSI_PT_RESULT_TRANSPORT_ERR
=> {
524 Err(format_err
!("scsi command failed: transport error").into())
526 SCSI_PT_RESULT_OS_ERR
=> {
527 let errno
= unsafe { get_scsi_pt_os_err(ptvp.as_ptr()) }
;
528 let err
= nix
::errno
::Errno
::from_i32(errno
);
529 Err(format_err
!("scsi command failed with err {}", err
).into())
532 Err(format_err
!("scsi command failed: unknown result category {}", unknown
).into())
537 /// Run the specified RAW SCSI command
538 pub fn do_command(&mut self, cmd
: &[u8]) -> Result
<&[u8], ScsiError
> {
539 if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) }
{
540 return Err(format_err
!("no valid SCSI command").into());
543 if self.buffer
.len() < 16 {
544 return Err(format_err
!("input buffer too small").into());
547 let mut ptvp
= self.create_scsi_pt_obj()?
;
549 unsafe { set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int) }
;
551 self.do_scsi_pt_checked(&mut ptvp
)?
;
553 let resid
= unsafe { get_scsi_pt_resid(ptvp.as_ptr()) }
as usize;
554 if resid
> self.buffer
.len() {
556 format_err
!("do_scsi_pt failed - got strange resid (value too big)").into(),
559 let data_len
= self.buffer
.len() - resid
;
561 Ok(&self.buffer
[..data_len
])
564 /// Run the specified RAW SCSI command, use data as input buffer
565 pub fn do_in_command
<'b
>(
569 ) -> Result
<&'b
[u8], ScsiError
> {
570 if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) }
{
571 return Err(format_err
!("no valid SCSI command").into());
575 return Err(format_err
!("got zero-sized input buffer").into());
578 let mut ptvp
= self.create_scsi_pt_obj()?
;
581 set_scsi_pt_data_in(ptvp
.as_mut_ptr(), data
.as_mut_ptr(), data
.len() as c_int
);
583 set_scsi_pt_cdb(ptvp
.as_mut_ptr(), cmd
.as_ptr(), cmd
.len() as c_int
);
586 self.do_scsi_pt_checked(&mut ptvp
)?
;
588 let resid
= unsafe { get_scsi_pt_resid(ptvp.as_ptr()) }
as usize;
590 if resid
> data
.len() {
592 format_err
!("do_scsi_pt failed - got strange resid (value too big)").into(),
595 let data_len
= data
.len() - resid
;
597 Ok(&data
[..data_len
])
600 /// Run dataout command
602 /// Note: use alloc_page_aligned_buffer to alloc data transfer buffer
603 pub fn do_out_command(&mut self, cmd
: &[u8], data
: &[u8]) -> Result
<(), ScsiError
> {
604 if !unsafe { sg_is_scsi_cdb(cmd.as_ptr(), cmd.len() as c_int) }
{
605 return Err(format_err
!("no valid SCSI command").into());
608 let page_size
= unsafe { libc::sysconf(libc::_SC_PAGESIZE) }
as usize;
609 if ((data
.as_ptr() as usize) & (page_size
- 1)) != 0 {
610 return Err(format_err
!("wrong transfer buffer alignment").into());
613 let mut ptvp
= self.create_scsi_pt_obj()?
;
616 set_scsi_pt_data_out(ptvp
.as_mut_ptr(), data
.as_ptr(), data
.len() as c_int
);
618 set_scsi_pt_cdb(ptvp
.as_mut_ptr(), cmd
.as_ptr(), cmd
.len() as c_int
);
621 self.do_scsi_pt_checked(&mut ptvp
)?
;
629 /// Converts SCSI ASCII text into String, trim zero and spaces
630 pub fn scsi_ascii_to_string(data
: &[u8]) -> String
{
631 String
::from_utf8_lossy(data
)
632 .trim_matches(char::from(0))
637 /// Read SCSI Inquiry page
639 /// Returns Product/Vendor/Revision and device type.
640 pub fn scsi_inquiry
<F
: AsRawFd
>(file
: &mut F
) -> Result
<InquiryInfo
, Error
> {
641 let allocation_len
: u8 = std
::mem
::size_of
::<InquiryPage
>() as u8;
643 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
644 sg_raw
.set_timeout(30); // use short timeout
646 let mut cmd
= Vec
::new();
647 cmd
.extend([0x12, 0, 0, 0, allocation_len
, 0]); // INQUIRY
651 .map_err(|err
| format_err
!("SCSI inquiry failed - {}", err
))?
;
653 proxmox_lang
::try_block
!({
654 let mut reader
= data
;
656 let page
: InquiryPage
= unsafe { reader.read_be_value()? }
;
658 let peripheral_type
= page
.peripheral_type
& 31;
660 let info
= InquiryInfo
{
662 peripheral_type_text
: PERIPHERAL_DEVICE_TYPE_TEXT
[peripheral_type
as usize].to_string(),
663 vendor
: scsi_ascii_to_string(&page
.vendor
),
664 product
: scsi_ascii_to_string(&page
.product
),
665 revision
: scsi_ascii_to_string(&page
.revision
),
670 .map_err(|err
: Error
| format_err
!("decode inquiry page failed - {}", err
))
673 /// Run SCSI Mode Sense
675 /// Warning: P needs to be repr(C, packed)]
676 pub fn scsi_mode_sense
<F
: AsRawFd
, P
: Endian
>(
678 disable_block_descriptor
: bool
,
681 ) -> Result
<(ModeParameterHeader
, Option
<ModeBlockDescriptor
>, P
), Error
> {
682 let allocation_len
: u16 = 4096;
683 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
685 let mut cmd
= vec
![0x5A]; // MODE SENSE(10)
686 if disable_block_descriptor
{
687 cmd
.push(8); // DBD=1 (Disable Block Descriptors)
689 cmd
.push(0); // DBD=0 (Include Block Descriptors)
691 cmd
.push(page_code
& 63); // report current values for page_code
692 cmd
.push(sub_page_code
);
694 cmd
.extend([0, 0, 0]); // reserved
695 cmd
.extend(allocation_len
.to_be_bytes()); // allocation len
696 cmd
.push(0); //control
700 .map_err(|err
| format_err
!("mode sense failed - {}", err
))?
;
702 proxmox_lang
::try_block
!({
703 let mut reader
= data
;
705 let head
: ModeParameterHeader
= unsafe { reader.read_be_value()? }
;
706 let expected_len
= head
.mode_data_len
as usize + 2;
708 use std
::cmp
::Ordering
;
709 match data
.len().cmp(&expected_len
) {
710 Ordering
::Less
=> bail
!(
711 "wrong mode_data_len: got {}, expected {}",
715 Ordering
::Greater
=> {
716 // Note: Some hh7 drives returns the allocation_length
717 // instead of real data_len
718 let header_size
= std
::mem
::size_of
::<ModeParameterHeader
>();
719 reader
= &data
[header_size
..expected_len
];
724 if disable_block_descriptor
&& head
.block_descriptior_len
!= 0 {
725 let len
= head
.block_descriptior_len
;
726 bail
!("wrong block_descriptior_len: {}, expected 0", len
);
729 let mut block_descriptor
: Option
<ModeBlockDescriptor
> = None
;
731 if !disable_block_descriptor
{
732 if head
.block_descriptior_len
!= 8 {
733 let len
= head
.block_descriptior_len
;
734 bail
!("wrong block_descriptior_len: {}, expected 8", len
);
737 block_descriptor
= Some(unsafe { reader.read_be_value()? }
);
740 let page
: P
= unsafe { reader.read_be_value()? }
;
742 Ok((head
, block_descriptor
, page
))
744 .map_err(|err
: Error
| format_err
!("decode mode sense failed - {}", err
))
748 pub fn scsi_request_sense
<F
: AsRawFd
>(file
: &mut F
) -> Result
<RequestSenseFixed
, ScsiError
> {
749 // request 252 bytes, as mentioned in the Seagate SCSI reference
750 let allocation_len
: u8 = 252;
752 let mut sg_raw
= SgRaw
::new(file
, allocation_len
as usize)?
;
753 sg_raw
.set_timeout(30); // use short timeout
754 let mut cmd
= Vec
::new();
755 cmd
.extend([0x03, 0, 0, 0, allocation_len
, 0]); // REQUEST SENSE FIXED FORMAT
759 .map_err(|err
| format_err
!("request sense failed - {}", err
))?
;
761 let sense
= proxmox_lang
::try_block
!({
762 let data_len
= data
.len();
764 if data_len
< std
::mem
::size_of
::<RequestSenseFixed
>() {
765 bail
!("got short data len ({})", data_len
);
767 let code
= data
[0] & 0x7f;
769 bail
!("received unexpected sense code '0x{:02x}'", code
);
772 let mut reader
= data
;
774 let sense
: RequestSenseFixed
= unsafe { reader.read_be_value()? }
;
778 .map_err(|err
: Error
| format_err
!("decode request sense failed - {}", err
))?
;