]> git.proxmox.com Git - proxmox-backup.git/blob - src/tape/changer/sg_pt_changer.rs
tape: rename retry_command to execute_scsi_command, make retry a flag
[proxmox-backup.git] / src / tape / changer / sg_pt_changer.rs
1 //! SCSI changer implementation using libsgutil2
2
3 use std::os::unix::prelude::AsRawFd;
4 use std::io::Read;
5 use std::collections::HashMap;
6 use std::path::Path;
7 use std::fs::{OpenOptions, File};
8
9 use anyhow::{bail, format_err, Error};
10 use endian_trait::Endian;
11
12 use proxmox::tools::io::ReadExt;
13
14 use crate::{
15 tape::{
16 changer::{
17 DriveStatus,
18 ElementStatus,
19 StorageElementStatus,
20 TransportElementStatus,
21 MtxStatus,
22 },
23 },
24 tools::sgutils2::{
25 SgRaw,
26 SENSE_KEY_NO_SENSE,
27 SENSE_KEY_RECOVERED_ERROR,
28 SENSE_KEY_UNIT_ATTENTION,
29 SENSE_KEY_NOT_READY,
30 InquiryInfo,
31 scsi_ascii_to_string,
32 scsi_inquiry,
33 },
34 };
35
36 const SCSI_CHANGER_DEFAULT_TIMEOUT: usize = 60*5; // 5 minutes
37
38 /// Initialize element status (Inventory)
39 pub fn initialize_element_status<F: AsRawFd>(file: &mut F) -> Result<(), Error> {
40
41 let mut sg_raw = SgRaw::new(file, 64)?;
42
43 // like mtx(1), set a very long timeout (30 minutes)
44 sg_raw.set_timeout(30*60);
45
46 let mut cmd = Vec::new();
47 cmd.extend(&[0x07, 0, 0, 0, 0, 0]); // INITIALIZE ELEMENT STATUS (07h)
48
49 sg_raw.do_command(&cmd)
50 .map_err(|err| format_err!("initializte element status (07h) failed - {}", err))?;
51
52 Ok(())
53 }
54
55 #[repr(C, packed)]
56 #[derive(Endian)]
57 struct AddressAssignmentPage {
58 data_len: u8,
59 reserved1: u8,
60 reserved2: u8,
61 block_descriptor_len: u8,
62
63 page_code: 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,
73 reserved22: u8,
74 reserved23: u8,
75 }
76
77 /// Execute scsi commands, optionally repeat the command until
78 /// successful (sleep 1 second between invovations)
79 ///
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.
83 ///
84 /// Skipped errors are printed on stderr.
85 fn execute_scsi_command<F: AsRawFd>(
86 sg_raw: &mut SgRaw<F>,
87 cmd: &[u8],
88 error_prefix: &str,
89 retry: bool,
90 ) -> Result<Vec<u8>, Error> {
91
92 let start = std::time::SystemTime::now();
93
94 let mut last_msg: Option<String> = None;
95
96 let mut timeout = std::time::Duration::new(5, 0); // short timeout by default
97
98 loop {
99 match sg_raw.do_command(&cmd) {
100 Ok(data) => return Ok(data.to_vec()),
101 Err(err) => {
102 if !retry {
103 bail!("{} failed: {}", error_prefix, err);
104 }
105 if let Some(ref sense) = err.sense {
106
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
111 {
112 let msg = err.to_string();
113 if let Some(ref last) = last_msg {
114 if &msg != last {
115 eprintln!("{}", err);
116 last_msg = Some(msg);
117 }
118 } else {
119 eprintln!("{}", err);
120 last_msg = Some(msg);
121 }
122
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);
127 }
128
129 if start.elapsed()? > timeout {
130 bail!("{} failed: {}", error_prefix, err);
131 }
132
133 std::thread::sleep(std::time::Duration::new(1, 0));
134 continue; // try again
135 }
136 }
137 }
138 }
139 }
140 }
141
142
143 fn read_element_address_assignment<F: AsRawFd>(
144 file: &mut F,
145 ) -> Result<AddressAssignmentPage, Error> {
146
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);
150
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
155 cmd.push(0);
156 cmd.push(allocation_len); // allocation len
157 cmd.push(0); //control
158
159 let data = execute_scsi_command(&mut sg_raw, &cmd, "read element address assignment", true)?;
160
161 proxmox::try_block!({
162 let mut reader = &data[..];
163 let page: AddressAssignmentPage = unsafe { reader.read_be_value()? };
164
165 if page.data_len != 23 {
166 bail!("got unexpected page len ({} != 23)", page.data_len);
167 }
168
169 Ok(page)
170 }).map_err(|err: Error| format_err!("decode element address assignment page failed - {}", err))
171 }
172
173 fn scsi_move_medium_cdb(
174 medium_transport_address: u16,
175 source_element_address: u16,
176 destination_element_address: u16,
177 ) -> Vec<u8> {
178
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
189
190 cmd
191 }
192
193 /// Load media from storage slot into drive
194 pub fn load_slot(
195 file: &mut File,
196 from_slot: u64,
197 drivenum: u64,
198 ) -> Result<(), Error> {
199 let status = read_element_status(file)?;
200
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)?;
204
205 let cmd = scsi_move_medium_cdb(
206 transport_address,
207 source_element_address,
208 drive_element_address,
209 );
210
211 let mut sg_raw = SgRaw::new(file, 64)?;
212 sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
213
214 sg_raw.do_command(&cmd)
215 .map_err(|err| format_err!("load drive failed - {}", err))?;
216
217 Ok(())
218 }
219
220 /// Unload media from drive into a storage slot
221 pub fn unload(
222 file: &mut File,
223 to_slot: u64,
224 drivenum: u64,
225 ) -> Result<(), Error> {
226
227 let status = read_element_status(file)?;
228
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)?;
232
233 let cmd = scsi_move_medium_cdb(
234 transport_address,
235 drive_element_address,
236 target_element_address,
237 );
238
239 let mut sg_raw = SgRaw::new(file, 64)?;
240 sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
241
242 sg_raw.do_command(&cmd)
243 .map_err(|err| format_err!("unload drive failed - {}", err))?;
244
245 Ok(())
246 }
247
248 /// Tranfer medium from one storage slot to another
249 pub fn transfer_medium<F: AsRawFd>(
250 file: &mut F,
251 from_slot: u64,
252 to_slot: u64,
253 ) -> Result<(), Error> {
254
255 let status = read_element_status(file)?;
256
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)?;
260
261 let cmd = scsi_move_medium_cdb(
262 transport_address,
263 source_element_address,
264 target_element_address,
265 );
266
267 let mut sg_raw = SgRaw::new(file, 64)?;
268 sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
269
270 sg_raw.do_command(&cmd)
271 .map_err(|err| {
272 format_err!("transfer medium from slot {} to slot {} failed - {}",
273 from_slot, to_slot, err)
274 })?;
275
276 Ok(())
277 }
278
279 fn scsi_read_element_status_cdb(
280 start_element_address: u16,
281 allocation_len: u32,
282 ) -> Vec<u8> {
283
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());
288
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]);
293 cmd.push(0);
294 cmd.push(0);
295
296 cmd
297 }
298
299 /// Read element status.
300 pub fn read_element_status<F: AsRawFd>(file: &mut F) -> Result<MtxStatus, Error> {
301
302 let inquiry = scsi_inquiry(file)?;
303
304 if inquiry.peripheral_type != 8 {
305 bail!("wrong device type (not a scsi changer device)");
306 }
307
308 // first, request address assignment (used for sanity checks)
309 let setup = read_element_address_assignment(file)?;
310
311 let allocation_len: u32 = 0x10000;
312
313 let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
314 sg_raw.set_timeout(SCSI_CHANGER_DEFAULT_TIMEOUT);
315
316 let mut start_element_address = 0;
317
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();
322
323 let mut retry = true;
324
325 loop {
326 let cmd = scsi_read_element_status_cdb(start_element_address, allocation_len);
327
328 let data = execute_scsi_command(&mut sg_raw, &cmd, "read element status (B8h)", retry)?;
329
330 let page = decode_element_status_page(&inquiry, &data, start_element_address)?;
331
332 retry = false; // only retry the first command
333
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);
338
339 if data.len() < (allocation_len as usize) {
340 break;
341 }
342
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;
346 } else {
347 bail!("got strange element address");
348 }
349 } else {
350 break;
351 }
352 }
353
354 if (setup.transport_element_count as usize) != transports.len() {
355 bail!("got wrong number of transport elements");
356 }
357 if (setup.storage_element_count as usize) != storage_slots.len() {
358 bail!("got wrong number of storage elements");
359 }
360 if (setup.import_export_element_count as usize) != import_export_slots.len() {
361 bail!("got wrong number of import/export elements");
362 }
363 if (setup.transfer_element_count as usize) != drives.len() {
364 bail!("got wrong number of tranfer elements");
365 }
366
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);
372
373 let mut status = MtxStatus { transports, drives, slots };
374
375 // sanity checks
376 if status.drives.is_empty() {
377 bail!("no data transfer elements reported");
378 }
379 if status.slots.is_empty() {
380 bail!("no storage elements reported");
381 }
382
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);
387 }
388
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);
394 }
395 }
396
397 Ok(status)
398 }
399
400 #[repr(C, packed)]
401 #[derive(Endian)]
402 struct ElementStatusHeader {
403 first_element_address_reported: u16,
404 number_of_elements_available: u16,
405 reserved: u8,
406 byte_count_of_report_available: [u8;3],
407 }
408
409 #[repr(C, packed)]
410 #[derive(Endian)]
411 struct SubHeader {
412 element_type_code: u8,
413 flags: u8,
414 descriptor_length: u16,
415 reseved: u8,
416 byte_count_of_descriptor_data_available: [u8;3],
417 }
418
419 impl SubHeader {
420
421 fn parse_optional_volume_tag<R: Read>(
422 &self,
423 reader: &mut R,
424 full: bool,
425 ) -> Result<Option<String>, Error> {
426
427 if (self.flags & 128) != 0 { // has PVolTag
428 let tmp = reader.read_exact_allocated(36)?;
429 if full {
430 let volume_tag = scsi_ascii_to_string(&tmp);
431 return Ok(Some(volume_tag));
432 }
433 }
434 Ok(None)
435 }
436
437 // AFAIK, tape changer do not use AlternateVolumeTag
438 // but parse anyways, just to be sure
439 fn skip_alternate_volume_tag<R: Read>(
440 &self,
441 reader: &mut R,
442 ) -> Result<Option<String>, Error> {
443
444 if (self.flags & 64) != 0 { // has AVolTag
445 let _tmp = reader.read_exact_allocated(36)?;
446 }
447
448 Ok(None)
449 }
450 }
451
452 #[repr(C, packed)]
453 #[derive(Endian)]
454 struct TrasnsportDescriptor { // Robot/Griper
455 element_address: u16,
456 flags1: u8,
457 reserved_3: u8,
458 additional_sense_code: u8,
459 additional_sense_code_qualifier: u8,
460 reserved_6: [u8;3],
461 flags2: u8,
462 source_storage_element_address: u16,
463 // volume tag and Mixed media descriptor follows (depends on flags)
464 }
465
466 #[repr(C, packed)]
467 #[derive(Endian)]
468 struct TransferDescriptor { // Tape drive
469 element_address: u16,
470 flags1: u8,
471 reserved_3: u8,
472 additional_sense_code: u8,
473 additional_sense_code_qualifier: u8,
474 id_valid: u8,
475 scsi_bus_address: u8,
476 reserved_8: u8,
477 flags2: u8,
478 source_storage_element_address: u16,
479 // volume tag, drive identifier and Mixed media descriptor follows
480 // (depends on flags)
481 }
482
483 #[repr(C, packed)]
484 #[derive(Endian)]
485 struct DvcidHead { // Drive Identifier Header
486 code_set: u8,
487 identifier_type: u8,
488 reserved: u8,
489 identifier_len: u8,
490 // Identifier follows
491 }
492
493 #[repr(C, packed)]
494 #[derive(Endian)]
495 struct StorageDescriptor { // Mail Slot
496 element_address: u16,
497 flags1: u8,
498 reserved_3: u8,
499 additional_sense_code: u8,
500 additional_sense_code_qualifier: u8,
501 reserved_6: [u8;3],
502 flags2: u8,
503 source_storage_element_address: u16,
504 // volume tag and Mixed media descriptor follows (depends on flags)
505 }
506
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>,
513 }
514
515 fn create_element_status(full: bool, volume_tag: Option<String>) -> ElementStatus {
516 if full {
517 if let Some(volume_tag) = volume_tag {
518 ElementStatus::VolumeTag(volume_tag)
519 } else {
520 ElementStatus::Full
521 }
522 } else {
523 ElementStatus::Empty
524 }
525 }
526
527 fn decode_element_status_page(
528 _info: &InquiryInfo,
529 data: &[u8],
530 start_element_address: u16,
531 ) -> Result<DecodedStatusPage, Error> {
532
533 proxmox::try_block!({
534
535 let mut result = DecodedStatusPage {
536 last_element_address: None,
537 transports: Vec::new(),
538 drives: Vec::new(),
539 storage_slots: Vec::new(),
540 import_export_slots: Vec::new(),
541 };
542
543 let mut reader = &data[..];
544
545 let head: ElementStatusHeader = unsafe { reader.read_be_value()? };
546
547 if head.number_of_elements_available == 0 {
548 return Ok(result);
549 }
550
551 if head.first_element_address_reported < start_element_address {
552 bail!("got wrong first_element_address_reported"); // sanity check
553 }
554
555 loop {
556 if reader.is_empty() {
557 break;
558 }
559
560 let subhead: SubHeader = unsafe { reader.read_be_value()? };
561
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() {
565 len = reader.len();
566 }
567
568 let descr_data = reader.read_exact_allocated(len)?;
569 let mut reader = &descr_data[..];
570
571 loop {
572 if reader.is_empty() {
573 break;
574 }
575 if reader.len() < (subhead.descriptor_length as usize) {
576 break;
577 }
578
579 match subhead.element_type_code {
580 1 => {
581 let desc: TrasnsportDescriptor = unsafe { reader.read_be_value()? };
582
583 let full = (desc.flags1 & 1) != 0;
584 let volume_tag = subhead.parse_optional_volume_tag(&mut reader, full)?;
585
586 subhead.skip_alternate_volume_tag(&mut reader)?;
587
588 let mut reserved = [0u8; 4];
589 reader.read_exact(&mut reserved)?;
590
591 result.last_element_address = Some(desc.element_address);
592
593 let status = TransportElementStatus {
594 status: create_element_status(full, volume_tag),
595 element_address: desc.element_address,
596 };
597 result.transports.push(status);
598 }
599 2 | 3 => {
600 let desc: StorageDescriptor = unsafe { reader.read_be_value()? };
601
602 let full = (desc.flags1 & 1) != 0;
603 let volume_tag = subhead.parse_optional_volume_tag(&mut reader, full)?;
604
605 subhead.skip_alternate_volume_tag(&mut reader)?;
606
607 let mut reserved = [0u8; 4];
608 reader.read_exact(&mut reserved)?;
609
610 result.last_element_address = Some(desc.element_address);
611
612 if subhead.element_type_code == 3 {
613 let status = StorageElementStatus {
614 import_export: true,
615 status: create_element_status(full, volume_tag),
616 element_address: desc.element_address,
617 };
618 result.import_export_slots.push(status);
619 } else {
620 let status = StorageElementStatus {
621 import_export: false,
622 status: create_element_status(full, volume_tag),
623 element_address: desc.element_address,
624 };
625 result.storage_slots.push(status);
626 }
627 }
628 4 => {
629 let desc: TransferDescriptor = unsafe { reader.read_be_value()? };
630
631 let loaded_slot = if (desc.flags2 & 128) != 0 { // SValid
632 Some(desc.source_storage_element_address as u64)
633 } else {
634 None
635 };
636
637 let full = (desc.flags1 & 1) != 0;
638 let volume_tag = subhead.parse_optional_volume_tag(&mut reader, full)?;
639
640 subhead.skip_alternate_volume_tag(&mut reader)?;
641
642 let dvcid: DvcidHead = unsafe { reader.read_be_value()? };
643
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);
648 Some(serial)
649 }
650 (2, 1) => {
651 if dvcid.identifier_len != 34 {
652 bail!("got wrong DVCID length");
653 }
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);
658 Some(serial)
659 }
660 _ => None,
661 };
662
663 result.last_element_address = Some(desc.element_address);
664
665 let drive = DriveStatus {
666 loaded_slot,
667 status: create_element_status(full, volume_tag),
668 drive_serial_number,
669 element_address: desc.element_address,
670 };
671 result.drives.push(drive);
672 }
673 code => bail!("got unknown element type code {}", code),
674 }
675 }
676 }
677
678 Ok(result)
679 }).map_err(|err: Error| format_err!("decode element status failed - {}", err))
680 }
681
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()
685 .read(true)
686 .write(true)
687 .open(path)?;
688
689 Ok(file)
690 }