]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-tape/src/sgutils2.rs
clippy 1.65 fixes
[proxmox-backup.git] / pbs-tape / src / sgutils2.rs
1 //! Bindings for libsgutils2
2 //!
3 //! Incomplete, but we currently do not need more.
4 //!
5 //! See: `/usr/include/scsi/sg_pt.h`
6 //!
7 //! The SCSI Commands Reference Manual also contains some useful information.
8
9 use std::ffi::CStr;
10 use std::os::unix::io::AsRawFd;
11 use std::ptr::NonNull;
12
13 use anyhow::{bail, format_err, Error};
14 use endian_trait::Endian;
15 use libc::{c_char, c_int};
16 use serde::{Deserialize, Serialize};
17
18 use proxmox_io::ReadExt;
19
20 #[derive(thiserror::Error, Debug)]
21 pub struct SenseInfo {
22 pub sense_key: u8,
23 pub asc: u8,
24 pub ascq: u8,
25 }
26
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));
33
34 if self.asc == 0 && self.ascq == 0 {
35 write!(f, "{}", sense_text)
36 } else {
37 let additional_sense_text = get_asc_ascq_string(self.asc, self.ascq);
38 write!(f, "{}, {}", sense_text, additional_sense_text)
39 }
40 }
41 }
42
43 #[derive(thiserror::Error, Debug)]
44 pub enum ScsiError {
45 #[error("{0}")]
46 Error(#[from] Error),
47 #[error("{0}")]
48 Sense(#[from] SenseInfo),
49 }
50
51 impl From<std::io::Error> for ScsiError {
52 fn from(error: std::io::Error) -> Self {
53 Self::Error(error.into())
54 }
55 }
56
57 // Opaque wrapper for sg_pt_base
58 #[repr(C)]
59 struct SgPtBase {
60 _private: [u8; 0],
61 }
62
63 #[repr(transparent)]
64 struct SgPt {
65 raw: NonNull<SgPtBase>,
66 }
67
68 impl Drop for SgPt {
69 fn drop(&mut self) {
70 unsafe { destruct_scsi_pt_obj(self.as_mut_ptr()) };
71 }
72 }
73
74 impl SgPt {
75 fn new() -> Result<Self, Error> {
76 Ok(Self {
77 raw: NonNull::new(unsafe { construct_scsi_pt_obj() })
78 .ok_or_else(|| format_err!("construct_scsi_pt_ob failed"))?,
79 })
80 }
81
82 fn as_ptr(&self) -> *const SgPtBase {
83 self.raw.as_ptr()
84 }
85
86 fn as_mut_ptr(&mut self) -> *mut SgPtBase {
87 self.raw.as_ptr()
88 }
89 }
90
91 /// Peripheral device type text (see `inquiry` command)
92 ///
93 /// see <https://en.wikipedia.org/wiki/SCSI_Peripheral_Device_Type>
94 pub const PERIPHERAL_DEVICE_TYPE_TEXT: [&str; 32] = [
95 "Disk Drive",
96 "Tape Drive",
97 "Printer",
98 "Processor",
99 "Write-once",
100 "CD-ROM", // 05h
101 "Scanner",
102 "Optical",
103 "Medium Changer", // 08h
104 "Communications",
105 "ASC IT8",
106 "ASC IT8",
107 "RAID Array",
108 "Enclosure Services",
109 "Simplified direct-access",
110 "Optical card reader/writer",
111 "Bridging Expander",
112 "Object-based Storage",
113 "Automation/Drive Interface",
114 "Security manager",
115 "Reserved",
116 "Reserved",
117 "Reserved",
118 "Reserved",
119 "Reserved",
120 "Reserved",
121 "Reserved",
122 "Reserved",
123 "Reserved",
124 "Reserved",
125 "Reserved",
126 "Unknown",
127 ];
128
129 // SENSE KEYS
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;
143
144 // SAM STAT
145 const SAM_STAT_CHECK_CONDITION: i32 = 0x02;
146
147 /// Sense Key Descriptions
148 pub const SENSE_KEY_DESCRIPTIONS: [&str; 16] = [
149 "No Sense",
150 "Recovered Error",
151 "Not Ready",
152 "Medium Error",
153 "Hardware Error",
154 "Illegal Request",
155 "Unit Attention",
156 "Data Protect",
157 "Blank Check",
158 "Vendor specific",
159 "Copy Aborted",
160 "Aborted Command",
161 "Equal",
162 "Volume Overflow",
163 "Miscompare",
164 "Completed",
165 ];
166
167 #[repr(C, packed)]
168 #[derive(Endian)]
169 // Standard Inquiry page - 36 bytes
170 struct InquiryPage {
171 peripheral_type: u8,
172 rmb: u8,
173 version: u8,
174 flags3: u8,
175 additional_length: u8,
176 flags5: u8,
177 flags6: u8,
178 flags7: u8,
179 vendor: [u8; 8],
180 product: [u8; 16],
181 revision: [u8; 4],
182 }
183
184 #[repr(C, packed)]
185 #[derive(Endian, Debug)]
186 pub struct RequestSenseFixed {
187 pub response_code: u8,
188 obsolete: u8,
189 pub flags2: 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],
197 }
198
199 #[repr(C, packed)]
200 #[derive(Endian, Debug)]
201 struct RequestSenseDescriptor {
202 response_code: u8,
203 sense_key: u8,
204 additional_sense_code: u8,
205 additional_sense_code_qualifier: u8,
206 reserved: [u8; 4],
207 additional_sense_len: u8,
208 }
209
210 /// Inquiry result
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,
217 /// Vendor
218 pub vendor: String,
219 /// Product
220 pub product: String,
221 /// Revision
222 pub revision: String,
223 }
224
225 #[repr(C, packed)]
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.
232 pub medium_type: u8,
233 pub flags3: u8,
234 reserved4: [u8; 2],
235 pub block_descriptior_len: u16,
236 }
237
238 impl ModeParameterHeader {
239 #[allow(clippy::unusual_byte_groupings)]
240 pub fn buffer_mode(&self) -> u8 {
241 (self.flags3 & 0b0_111_0000) >> 4
242 }
243
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;
247 if buffer_mode {
248 mode |= 0b0_001_0000;
249 }
250 self.flags3 = mode;
251 }
252
253 #[allow(clippy::unusual_byte_groupings)]
254 pub fn write_protect(&self) -> bool {
255 (self.flags3 & 0b1_000_0000) != 0
256 }
257 }
258
259 #[repr(C, packed)]
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],
265 reserved: u8,
266 pub block_length: [u8; 3],
267 }
268
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)
274 }
275
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);
279 }
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;
283 Ok(())
284 }
285 }
286
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;
290
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;
296
297 #[link(name = "sgutils2")]
298 extern "C" {
299
300 #[allow(dead_code)]
301 fn scsi_pt_open_device(device_name: *const c_char, read_only: bool, verbose: c_int) -> c_int;
302
303 fn sg_is_scsi_cdb(cdbp: *const u8, clen: c_int) -> bool;
304
305 fn construct_scsi_pt_obj() -> *mut SgPtBase;
306 fn destruct_scsi_pt_obj(objp: *mut SgPtBase);
307
308 fn set_scsi_pt_data_in(objp: *mut SgPtBase, dxferp: *mut u8, dxfer_ilen: c_int);
309
310 fn set_scsi_pt_data_out(objp: *mut SgPtBase, dxferp: *const u8, dxfer_olen: c_int);
311
312 fn set_scsi_pt_cdb(objp: *mut SgPtBase, cdb: *const u8, cdb_len: c_int);
313
314 fn set_scsi_pt_sense(objp: *mut SgPtBase, sense: *mut u8, max_sense_len: c_int);
315
316 fn do_scsi_pt(objp: *mut SgPtBase, fd: c_int, timeout_secs: c_int, verbose: c_int) -> c_int;
317
318 fn get_scsi_pt_resid(objp: *const SgPtBase) -> c_int;
319
320 fn get_scsi_pt_sense_len(objp: *const SgPtBase) -> c_int;
321
322 fn get_scsi_pt_status_response(objp: *const SgPtBase) -> c_int;
323
324 fn get_scsi_pt_result_category(objp: *const SgPtBase) -> c_int;
325
326 fn get_scsi_pt_os_err(objp: *const SgPtBase) -> c_int;
327
328 fn sg_get_asc_ascq_str(
329 asc: c_int,
330 ascq: c_int,
331 buff_len: c_int,
332 buffer: *mut c_char,
333 ) -> *const c_char;
334 }
335
336 /// Safe interface to run RAW SCSI commands
337 pub struct SgRaw<'a, F> {
338 file: &'a mut F,
339 buffer: Box<[u8]>,
340 sense_buffer: [u8; 32],
341 timeout: i32,
342 }
343
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];
347 let res = unsafe {
348 sg_get_asc_ascq_str(
349 asc as c_int,
350 ascq as c_int,
351 buffer.len() as c_int,
352 buffer.as_mut_ptr() as *mut c_char,
353 )
354 };
355
356 proxmox_lang::try_block!({
357 if res.is_null() {
358 // just to be safe
359 bail!("unexpected NULL ptr");
360 }
361 Ok(unsafe { CStr::from_ptr(res) }.to_str()?.to_owned())
362 })
363 .unwrap_or_else(|_err: Error| format!("ASC={:02x}x, ASCQ={:02x}x", asc, ascq))
364 }
365
366 /// Allocate a page aligned buffer
367 ///
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) };
373 if dinp.is_null() {
374 bail!("alloc SCSI output buffer failed");
375 }
376
377 let buffer_slice = unsafe { std::slice::from_raw_parts_mut(dinp, buffer_size) };
378 Ok(unsafe { Box::from_raw(buffer_slice) })
379 }
380
381 impl<'a, F: AsRawFd> SgRaw<'a, F> {
382 /// Create a new instance to run commands
383 ///
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)?
388 } else {
389 Box::new([])
390 };
391
392 let sense_buffer = [0u8; 32];
393
394 Ok(Self {
395 file,
396 buffer,
397 sense_buffer,
398 timeout: 0,
399 })
400 }
401
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
406 } else {
407 self.timeout = seconds as i32;
408 }
409 }
410
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()?;
414
415 if !self.buffer.is_empty() {
416 unsafe {
417 set_scsi_pt_data_in(
418 ptvp.as_mut_ptr(),
419 self.buffer.as_mut_ptr(),
420 self.buffer.len() as c_int,
421 )
422 };
423 }
424
425 unsafe {
426 set_scsi_pt_sense(
427 ptvp.as_mut_ptr(),
428 self.sense_buffer.as_mut_ptr(),
429 self.sense_buffer.len() as c_int,
430 )
431 };
432
433 Ok(ptvp)
434 }
435
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) };
438 match res {
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())
442 }
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());
448 }
449 unknown => {
450 return Err(format_err!("do_scsi_pt failed: unknown error {}", unknown).into())
451 }
452 }
453
454 if res < 0 {
455 let err = nix::Error::last();
456 return Err(format_err!("do_scsi_pt failed - {}", err).into());
457 }
458 if res != 0 {
459 return Err(format_err!("do_scsi_pt failed {}", res).into());
460 }
461
462 let sense_len = unsafe { get_scsi_pt_sense_len(ptvp.as_ptr()) };
463
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()) };
466
467 if res_cat == SCSI_PT_RESULT_TRANSPORT_ERR && status == SAM_STAT_CHECK_CONDITION {
468 res_cat = SCSI_PT_RESULT_SENSE;
469 }
470
471 match res_cat {
472 SCSI_PT_RESULT_GOOD => Ok(()),
473 SCSI_PT_RESULT_STATUS => {
474 if status != 0 {
475 return Err(
476 format_err!("unknown scsi error - status response {}", status).into(),
477 );
478 }
479 Ok(())
480 }
481 SCSI_PT_RESULT_SENSE => {
482 if sense_len == 0 {
483 return Err(format_err!("scsi command failed, but got no sense data").into());
484 }
485
486 let code = self.sense_buffer[0] & 0x7f;
487
488 let mut reader = &self.sense_buffer[..(sense_len as usize)];
489
490 let sense = match code {
491 0x70 => {
492 let sense: RequestSenseFixed = unsafe { reader.read_be_value()? };
493 SenseInfo {
494 sense_key: sense.flags2 & 0xf,
495 asc: sense.additional_sense_code,
496 ascq: sense.additional_sense_code_qualifier,
497 }
498 }
499 0x72 => {
500 let sense: RequestSenseDescriptor = unsafe { reader.read_be_value()? };
501 SenseInfo {
502 sense_key: sense.sense_key & 0xf,
503 asc: sense.additional_sense_code,
504 ascq: sense.additional_sense_code_qualifier,
505 }
506 }
507 0x71 | 0x73 => {
508 return Err(
509 format_err!("scsi command failed: received deferred Sense").into()
510 );
511 }
512 unknown => {
513 return Err(format_err!(
514 "scsi command failed: invalid Sense response code {:x}",
515 unknown
516 )
517 .into());
518 }
519 };
520
521 Err(ScsiError::Sense(sense))
522 }
523 SCSI_PT_RESULT_TRANSPORT_ERR => {
524 Err(format_err!("scsi command failed: transport error").into())
525 }
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())
530 }
531 unknown => {
532 Err(format_err!("scsi command failed: unknown result category {}", unknown).into())
533 }
534 }
535 }
536
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());
541 }
542
543 if self.buffer.len() < 16 {
544 return Err(format_err!("input buffer too small").into());
545 }
546
547 let mut ptvp = self.create_scsi_pt_obj()?;
548
549 unsafe { set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int) };
550
551 self.do_scsi_pt_checked(&mut ptvp)?;
552
553 let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize;
554 if resid > self.buffer.len() {
555 return Err(
556 format_err!("do_scsi_pt failed - got strange resid (value too big)").into(),
557 );
558 }
559 let data_len = self.buffer.len() - resid;
560
561 Ok(&self.buffer[..data_len])
562 }
563
564 /// Run the specified RAW SCSI command, use data as input buffer
565 pub fn do_in_command<'b>(
566 &mut self,
567 cmd: &[u8],
568 data: &'b mut [u8],
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());
572 }
573
574 if data.is_empty() {
575 return Err(format_err!("got zero-sized input buffer").into());
576 }
577
578 let mut ptvp = self.create_scsi_pt_obj()?;
579
580 unsafe {
581 set_scsi_pt_data_in(ptvp.as_mut_ptr(), data.as_mut_ptr(), data.len() as c_int);
582
583 set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int);
584 };
585
586 self.do_scsi_pt_checked(&mut ptvp)?;
587
588 let resid = unsafe { get_scsi_pt_resid(ptvp.as_ptr()) } as usize;
589
590 if resid > data.len() {
591 return Err(
592 format_err!("do_scsi_pt failed - got strange resid (value too big)").into(),
593 );
594 }
595 let data_len = data.len() - resid;
596
597 Ok(&data[..data_len])
598 }
599
600 /// Run dataout command
601 ///
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());
606 }
607
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());
611 }
612
613 let mut ptvp = self.create_scsi_pt_obj()?;
614
615 unsafe {
616 set_scsi_pt_data_out(ptvp.as_mut_ptr(), data.as_ptr(), data.len() as c_int);
617
618 set_scsi_pt_cdb(ptvp.as_mut_ptr(), cmd.as_ptr(), cmd.len() as c_int);
619 };
620
621 self.do_scsi_pt_checked(&mut ptvp)?;
622
623 Ok(())
624 }
625 }
626
627 // Useful helpers
628
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))
633 .trim()
634 .to_string()
635 }
636
637 /// Read SCSI Inquiry page
638 ///
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;
642
643 let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
644 sg_raw.set_timeout(30); // use short timeout
645
646 let mut cmd = Vec::new();
647 cmd.extend([0x12, 0, 0, 0, allocation_len, 0]); // INQUIRY
648
649 let data = sg_raw
650 .do_command(&cmd)
651 .map_err(|err| format_err!("SCSI inquiry failed - {}", err))?;
652
653 proxmox_lang::try_block!({
654 let mut reader = data;
655
656 let page: InquiryPage = unsafe { reader.read_be_value()? };
657
658 let peripheral_type = page.peripheral_type & 31;
659
660 let info = InquiryInfo {
661 peripheral_type,
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),
666 };
667
668 Ok(info)
669 })
670 .map_err(|err: Error| format_err!("decode inquiry page failed - {}", err))
671 }
672
673 /// Run SCSI Mode Sense
674 ///
675 /// Warning: P needs to be repr(C, packed)]
676 pub fn scsi_mode_sense<F: AsRawFd, P: Endian>(
677 file: &mut F,
678 disable_block_descriptor: bool,
679 page_code: u8,
680 sub_page_code: u8,
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)?;
684
685 let mut cmd = vec![0x5A]; // MODE SENSE(10)
686 if disable_block_descriptor {
687 cmd.push(8); // DBD=1 (Disable Block Descriptors)
688 } else {
689 cmd.push(0); // DBD=0 (Include Block Descriptors)
690 }
691 cmd.push(page_code & 63); // report current values for page_code
692 cmd.push(sub_page_code);
693
694 cmd.extend([0, 0, 0]); // reserved
695 cmd.extend(allocation_len.to_be_bytes()); // allocation len
696 cmd.push(0); //control
697
698 let data = sg_raw
699 .do_command(&cmd)
700 .map_err(|err| format_err!("mode sense failed - {}", err))?;
701
702 proxmox_lang::try_block!({
703 let mut reader = data;
704
705 let head: ModeParameterHeader = unsafe { reader.read_be_value()? };
706 let expected_len = head.mode_data_len as usize + 2;
707
708 use std::cmp::Ordering;
709 match data.len().cmp(&expected_len) {
710 Ordering::Less => bail!(
711 "wrong mode_data_len: got {}, expected {}",
712 data.len(),
713 expected_len
714 ),
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];
720 }
721 _ => (),
722 }
723
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);
727 }
728
729 let mut block_descriptor: Option<ModeBlockDescriptor> = None;
730
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);
735 }
736
737 block_descriptor = Some(unsafe { reader.read_be_value()? });
738 }
739
740 let page: P = unsafe { reader.read_be_value()? };
741
742 Ok((head, block_descriptor, page))
743 })
744 .map_err(|err: Error| format_err!("decode mode sense failed - {}", err))
745 }
746
747 /// Resuqest Sense
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;
751
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
756
757 let data = sg_raw
758 .do_command(&cmd)
759 .map_err(|err| format_err!("request sense failed - {}", err))?;
760
761 let sense = proxmox_lang::try_block!({
762 let data_len = data.len();
763
764 if data_len < std::mem::size_of::<RequestSenseFixed>() {
765 bail!("got short data len ({})", data_len);
766 }
767 let code = data[0] & 0x7f;
768 if code != 0x70 {
769 bail!("received unexpected sense code '0x{:02x}'", code);
770 }
771
772 let mut reader = data;
773
774 let sense: RequestSenseFixed = unsafe { reader.read_be_value()? };
775
776 Ok(sense)
777 })
778 .map_err(|err: Error| format_err!("decode request sense failed - {}", err))?;
779
780 Ok(sense)
781 }