]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-tape/src/sg_tape.rs
AuthId: derive Ord and PartialOrd
[proxmox-backup.git] / pbs-tape / src / sg_tape.rs
1 use std::convert::TryFrom;
2 use std::convert::TryInto;
3 use std::fs::{File, OpenOptions};
4 use std::os::unix::fs::OpenOptionsExt;
5 use std::os::unix::io::AsRawFd;
6 use std::path::Path;
7 use std::time::SystemTime;
8
9 use anyhow::{bail, format_err, Error};
10 use endian_trait::Endian;
11 use nix::fcntl::{fcntl, FcntlArg, OFlag};
12
13 mod encryption;
14 pub use encryption::*;
15
16 mod volume_statistics;
17 pub use volume_statistics::*;
18
19 mod tape_alert_flags;
20 pub use tape_alert_flags::*;
21
22 mod mam;
23 pub use mam::*;
24
25 mod report_density;
26 pub use report_density::*;
27
28 use proxmox_io::{ReadExt, WriteExt};
29 use proxmox_sys::error::SysResult;
30
31 use pbs_api_types::{Lp17VolumeStatistics, LtoDriveAndMediaStatus, MamAttribute};
32
33 use crate::{
34 sgutils2::{
35 alloc_page_aligned_buffer, scsi_inquiry, scsi_mode_sense, scsi_request_sense, InquiryInfo,
36 ModeBlockDescriptor, ModeParameterHeader, ScsiError, SenseInfo, SgRaw,
37 },
38 BlockRead, BlockReadError, BlockWrite, BlockedReader, BlockedWriter,
39 };
40
41 #[repr(C, packed)]
42 #[derive(Endian, Debug, Copy, Clone)]
43 pub struct ReadPositionLongPage {
44 flags: u8,
45 reserved: [u8; 3],
46 partition_number: u32,
47 pub logical_object_number: u64,
48 pub logical_file_id: u64,
49 obsolete: [u8; 8],
50 }
51
52 #[repr(C, packed)]
53 #[derive(Endian, Debug, Copy, Clone)]
54 struct DataCompressionModePage {
55 page_code: u8, // 0x0f
56 page_length: u8, // 0x0e
57 flags2: u8,
58 flags3: u8,
59 compression_algorithm: u32,
60 decompression_algorithm: u32,
61 reserved: [u8; 4],
62 }
63
64 impl DataCompressionModePage {
65 pub fn set_compression(&mut self, enable: bool) {
66 if enable {
67 self.flags2 |= 128;
68 } else {
69 self.flags2 &= 127;
70 }
71 }
72
73 pub fn compression_enabled(&self) -> bool {
74 (self.flags2 & 0b1000_0000) != 0
75 }
76 }
77
78 #[repr(C, packed)]
79 #[derive(Endian)]
80 struct MediumConfigurationModePage {
81 page_code: u8, // 0x1d
82 page_length: u8, // 0x1e
83 flags2: u8,
84 reserved: [u8; 29],
85 }
86
87 impl MediumConfigurationModePage {
88 pub fn is_worm(&self) -> bool {
89 (self.flags2 & 1) == 1
90 }
91 }
92
93 #[derive(Debug)]
94 pub struct LtoTapeStatus {
95 pub block_length: u32,
96 pub density_code: u8,
97 pub buffer_mode: u8,
98 pub write_protect: bool,
99 pub compression: bool,
100 }
101
102 pub struct SgTape {
103 file: File,
104 locate_offset: Option<i64>,
105 info: InquiryInfo,
106 encryption_key_loaded: bool,
107 }
108
109 impl SgTape {
110 const SCSI_TAPE_DEFAULT_TIMEOUT: usize = 60 * 10; // 10 minutes
111
112 /// Create a new instance
113 ///
114 /// Uses scsi_inquiry to check the device type.
115 pub fn new(mut file: File) -> Result<Self, Error> {
116 let info = scsi_inquiry(&mut file)?;
117
118 if info.peripheral_type != 1 {
119 bail!(
120 "not a tape device (peripheral_type = {})",
121 info.peripheral_type
122 );
123 }
124
125 Ok(Self {
126 file,
127 info,
128 encryption_key_loaded: false,
129 locate_offset: None,
130 })
131 }
132
133 /// Access to file descriptor - useful for testing
134 pub fn file_mut(&mut self) -> &mut File {
135 &mut self.file
136 }
137
138 pub fn info(&self) -> &InquiryInfo {
139 &self.info
140 }
141
142 /// Return the maximum supported density code
143 ///
144 /// This can be used to detect the drive generation.
145 pub fn max_density_code(&mut self) -> Result<u8, Error> {
146 report_density(&mut self.file)
147 }
148
149 pub fn open<P: AsRef<Path>>(path: P) -> Result<SgTape, Error> {
150 // do not wait for media, use O_NONBLOCK
151 let file = OpenOptions::new()
152 .read(true)
153 .write(true)
154 .custom_flags(libc::O_NONBLOCK)
155 .open(path)?;
156
157 // then clear O_NONBLOCK
158 let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL).into_io_result()?;
159
160 let mut flags = OFlag::from_bits_truncate(flags);
161 flags.remove(OFlag::O_NONBLOCK);
162
163 fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)).into_io_result()?;
164
165 Self::new(file)
166 }
167
168 pub fn inquiry(&mut self) -> Result<InquiryInfo, Error> {
169 scsi_inquiry(&mut self.file)
170 }
171
172 /// Erase medium.
173 ///
174 /// EOD is written at the current position, which marks it as end
175 /// of data. After the command is successfully completed, the
176 /// drive is positioned immediately before End Of Data (not End Of
177 /// Tape).
178 pub fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
179 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
180 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
181 let mut cmd = Vec::new();
182 cmd.push(0x19);
183 if fast {
184 cmd.push(0); // LONG=0
185 } else {
186 cmd.push(1); // LONG=1
187 }
188 cmd.extend(&[0, 0, 0, 0]);
189
190 sg_raw
191 .do_command(&cmd)
192 .map_err(|err| format_err!("erase failed - {}", err))?;
193
194 Ok(())
195 }
196
197 /// Format media, single partition
198 pub fn format_media(&mut self, fast: bool) -> Result<(), Error> {
199 // try to get info about loaded media first
200 let (has_format, is_worm) = match self.read_medium_configuration_page() {
201 Ok((_head, block_descriptor, page)) => {
202 // FORMAT requires LTO5 or newer
203 let has_format = block_descriptor.density_code >= 0x58;
204 let is_worm = page.is_worm();
205 (has_format, is_worm)
206 }
207 Err(_) => {
208 // LTO3 and older do not supprt medium configuration mode page
209 (false, false)
210 }
211 };
212
213 if is_worm {
214 // We cannot FORMAT WORM media! Instead we check if its empty.
215
216 self.move_to_eom(false)?;
217 let pos = self.position()?;
218 if pos.logical_object_number != 0 {
219 bail!("format failed - detected WORM media with data.");
220 }
221
222 Ok(())
223 } else {
224 self.rewind()?;
225
226 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
227 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
228 let mut cmd = Vec::new();
229
230 if has_format {
231 cmd.extend(&[0x04, 0, 0, 0, 0, 0]); // FORMAT
232 sg_raw.do_command(&cmd)?;
233 if !fast {
234 self.erase_media(false)?; // overwrite everything
235 }
236 } else {
237 // try rewind/erase instead
238 self.erase_media(fast)?
239 }
240
241 Ok(())
242 }
243 }
244
245 /// Lock/Unlock drive door
246 pub fn set_medium_removal(&mut self, allow: bool) -> Result<(), ScsiError> {
247 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
248 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
249 let mut cmd = Vec::new();
250 cmd.extend(&[0x1E, 0, 0, 0]);
251 if allow {
252 cmd.push(0);
253 } else {
254 cmd.push(1);
255 }
256 cmd.push(0); // control
257
258 sg_raw.do_command(&cmd)?;
259
260 Ok(())
261 }
262
263 pub fn rewind(&mut self) -> Result<(), Error> {
264 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
265 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
266 let mut cmd = Vec::new();
267 cmd.extend(&[0x01, 0, 0, 0, 0, 0]); // REWIND
268
269 sg_raw
270 .do_command(&cmd)
271 .map_err(|err| format_err!("rewind failed - {}", err))?;
272
273 Ok(())
274 }
275
276 pub fn locate_file(&mut self, position: u64) -> Result<(), Error> {
277 if position == 0 {
278 return self.rewind();
279 }
280
281 const SPACE_ONE_FILEMARK: &[u8] = &[0x11, 0x01, 0, 0, 1, 0];
282
283 // Special case for position 1, because LOCATE 0 does not work
284 if position == 1 {
285 self.rewind()?;
286 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
287 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
288 sg_raw
289 .do_command(SPACE_ONE_FILEMARK)
290 .map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
291 return Ok(());
292 }
293
294 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
295 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
296
297 // Note: LOCATE(16) works for LTO4 or newer
298 //
299 // It seems the LOCATE command behaves slightly different across vendors
300 // e.g. for IBM drives, LOCATE 1 moves to File #2, but
301 // for HP drives, LOCATE 1 move to File #1
302
303 let fixed_position = if let Some(locate_offset) = self.locate_offset {
304 if locate_offset < 0 {
305 position.saturating_sub((-locate_offset) as u64)
306 } else {
307 position.saturating_add(locate_offset as u64)
308 }
309 } else {
310 position
311 };
312 // always sub(1), so that it works for IBM drives without locate_offset
313 let fixed_position = fixed_position.saturating_sub(1);
314
315 let mut cmd = Vec::new();
316 cmd.extend(&[0x92, 0b000_01_000, 0, 0]); // LOCATE(16) filemarks
317 cmd.extend(&fixed_position.to_be_bytes());
318 cmd.extend(&[0, 0, 0, 0]);
319
320 sg_raw
321 .do_command(&cmd)
322 .map_err(|err| format_err!("locate file {} failed - {}", position, err))?;
323
324 // LOCATE always position at the BOT side of the filemark, so
325 // we need to move to other side of filemark
326 sg_raw
327 .do_command(SPACE_ONE_FILEMARK)
328 .map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
329
330 if self.locate_offset.is_none() {
331 // check if we landed at correct position
332 let current_file = self.current_file_number()?;
333 if current_file != position {
334 let offset: i64 = i64::try_from((position as i128) - (current_file as i128))
335 .map_err(|err| {
336 format_err!(
337 "locate_file: offset between {} and {} invalid: {}",
338 position,
339 current_file,
340 err
341 )
342 })?;
343 self.locate_offset = Some(offset);
344 self.locate_file(position)?;
345 let current_file = self.current_file_number()?;
346 if current_file != position {
347 bail!("locate_file: compensating offset did not work, aborting...");
348 }
349 } else {
350 self.locate_offset = Some(0);
351 }
352 }
353
354 Ok(())
355 }
356
357 pub fn position(&mut self) -> Result<ReadPositionLongPage, Error> {
358 let expected_size = std::mem::size_of::<ReadPositionLongPage>();
359
360 let mut sg_raw = SgRaw::new(&mut self.file, 32)?;
361 sg_raw.set_timeout(30); // use short timeout
362 let mut cmd = Vec::new();
363 // READ POSITION LONG FORM works on LTO4 or newer (with recent
364 // firmware), although it is missing in the IBM LTO4 SSCI
365 // reference manual.
366 cmd.extend(&[0x34, 0x06, 0, 0, 0, 0, 0, 0, 0, 0]); // READ POSITION LONG FORM
367
368 let data = sg_raw
369 .do_command(&cmd)
370 .map_err(|err| format_err!("read position failed - {}", err))?;
371
372 let page = proxmox_lang::try_block!({
373 if data.len() != expected_size {
374 bail!(
375 "got unexpected data len ({} != {}",
376 data.len(),
377 expected_size
378 );
379 }
380
381 let mut reader = data;
382
383 let page: ReadPositionLongPage = unsafe { reader.read_be_value()? };
384
385 Ok(page)
386 })
387 .map_err(|err: Error| format_err!("decode position page failed - {}", err))?;
388
389 if page.partition_number != 0 {
390 bail!("detecthed partitioned tape - not supported");
391 }
392
393 Ok(page)
394 }
395
396 pub fn current_file_number(&mut self) -> Result<u64, Error> {
397 let position = self.position()?;
398 Ok(position.logical_file_id)
399 }
400
401 /// Check if we are positioned after a filemark (or BOT)
402 pub fn check_filemark(&mut self) -> Result<bool, Error> {
403 let pos = self.position()?;
404 if pos.logical_object_number == 0 {
405 // at BOT, Ok (no filemark required)
406 return Ok(true);
407 }
408
409 // Note: SPACE blocks returns Err at filemark
410 match self.space(-1, true) {
411 Ok(_) => {
412 self.space(1, true) // move back to end
413 .map_err(|err| {
414 format_err!("check_filemark failed (space forward) - {}", err)
415 })?;
416 Ok(false)
417 }
418 Err(ScsiError::Sense(SenseInfo {
419 sense_key: 0,
420 asc: 0,
421 ascq: 1,
422 })) => {
423 // Filemark detected - good
424 self.space(1, false) // move to EOT side of filemark
425 .map_err(|err| {
426 format_err!(
427 "check_filemark failed (move to EOT side of filemark) - {}",
428 err
429 )
430 })?;
431 Ok(true)
432 }
433 Err(err) => {
434 bail!("check_filemark failed - {:?}", err);
435 }
436 }
437 }
438
439 pub fn move_to_eom(&mut self, write_missing_eof: bool) -> Result<(), Error> {
440 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
441 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
442 let mut cmd = Vec::new();
443 cmd.extend(&[0x11, 0x03, 0, 0, 0, 0]); // SPACE(6) move to EOD
444
445 sg_raw
446 .do_command(&cmd)
447 .map_err(|err| format_err!("move to EOD failed - {}", err))?;
448
449 if write_missing_eof && !self.check_filemark()? {
450 self.write_filemarks(1, false)?;
451 }
452
453 Ok(())
454 }
455
456 fn space(&mut self, count: isize, blocks: bool) -> Result<(), ScsiError> {
457 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
458 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
459 let mut cmd = Vec::new();
460
461 // Use short command if possible (supported by all drives)
462 if (count <= 0x7fffff) && (count > -0x7fffff) {
463 cmd.push(0x11); // SPACE(6)
464 if blocks {
465 cmd.push(0); // blocks
466 } else {
467 cmd.push(1); // filemarks
468 }
469 cmd.push(((count >> 16) & 0xff) as u8);
470 cmd.push(((count >> 8) & 0xff) as u8);
471 cmd.push((count & 0xff) as u8);
472 cmd.push(0); //control byte
473 } else {
474 cmd.push(0x91); // SPACE(16)
475 if blocks {
476 cmd.push(0); // blocks
477 } else {
478 cmd.push(1); // filemarks
479 }
480 cmd.extend(&[0, 0]); // reserved
481 let count: i64 = count as i64;
482 cmd.extend(&count.to_be_bytes());
483 cmd.extend(&[0, 0, 0, 0]); // reserved
484 }
485
486 sg_raw.do_command(&cmd)?;
487
488 Ok(())
489 }
490
491 pub fn space_filemarks(&mut self, count: isize) -> Result<(), Error> {
492 self.space(count, false)
493 .map_err(|err| format_err!("space filemarks failed - {}", err))
494 }
495
496 pub fn space_blocks(&mut self, count: isize) -> Result<(), Error> {
497 self.space(count, true)
498 .map_err(|err| format_err!("space blocks failed - {}", err))
499 }
500
501 pub fn eject(&mut self) -> Result<(), Error> {
502 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
503 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
504 let mut cmd = Vec::new();
505 cmd.extend(&[0x1B, 0, 0, 0, 0, 0]); // LODA/UNLOAD HOLD=0, LOAD=0
506
507 sg_raw
508 .do_command(&cmd)
509 .map_err(|err| format_err!("eject failed - {}", err))?;
510
511 Ok(())
512 }
513
514 pub fn load(&mut self) -> Result<(), Error> {
515 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
516 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
517 let mut cmd = Vec::new();
518 cmd.extend(&[0x1B, 0, 0, 0, 0b0000_0001, 0]); // LODA/UNLOAD HOLD=0, LOAD=1
519
520 sg_raw
521 .do_command(&cmd)
522 .map_err(|err| format_err!("load media failed - {}", err))?;
523
524 Ok(())
525 }
526
527 pub fn write_filemarks(&mut self, count: usize, immediate: bool) -> Result<(), std::io::Error> {
528 if count > 255 {
529 proxmox_lang::io_bail!("write_filemarks failed: got strange count '{}'", count);
530 }
531
532 let mut sg_raw = SgRaw::new(&mut self.file, 16).map_err(|err| {
533 proxmox_lang::io_format_err!("write_filemarks failed (alloc) - {}", err)
534 })?;
535
536 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
537 let mut cmd = Vec::new();
538 cmd.push(0x10);
539 if immediate {
540 cmd.push(1); // IMMED=1
541 } else {
542 cmd.push(0); // IMMED=0
543 }
544 cmd.extend(&[0, 0, count as u8]); // COUNT
545 cmd.push(0); // control byte
546
547 match sg_raw.do_command(&cmd) {
548 Ok(_) => { /* OK */ }
549 Err(ScsiError::Sense(SenseInfo {
550 sense_key: 0,
551 asc: 0,
552 ascq: 2,
553 })) => { /* LEOM - ignore */ }
554 Err(err) => {
555 proxmox_lang::io_bail!("write filemark failed - {}", err);
556 }
557 }
558
559 Ok(())
560 }
561
562 // Flush tape buffers (WEOF with count 0 => flush)
563 pub fn sync(&mut self) -> Result<(), std::io::Error> {
564 self.write_filemarks(0, false)?;
565 Ok(())
566 }
567
568 pub fn test_unit_ready(&mut self) -> Result<(), Error> {
569 let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
570 sg_raw.set_timeout(30); // use short timeout
571 let mut cmd = Vec::new();
572 cmd.extend(&[0x00, 0, 0, 0, 0, 0]); // TEST UNIT READY
573
574 match sg_raw.do_command(&cmd) {
575 Ok(_) => Ok(()),
576 Err(err) => {
577 bail!("test_unit_ready failed - {}", err);
578 }
579 }
580 }
581
582 pub fn wait_until_ready(&mut self) -> Result<(), Error> {
583 let start = SystemTime::now();
584 let max_wait = std::time::Duration::new(Self::SCSI_TAPE_DEFAULT_TIMEOUT as u64, 0);
585
586 loop {
587 match self.test_unit_ready() {
588 Ok(()) => return Ok(()),
589 _ => {
590 std::thread::sleep(std::time::Duration::new(1, 0));
591 if start.elapsed()? > max_wait {
592 bail!("wait_until_ready failed - got timeout");
593 }
594 }
595 }
596 }
597 }
598
599 /// Read Tape Alert Flags
600 pub fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
601 read_tape_alert_flags(&mut self.file)
602 }
603
604 /// Read Cartridge Memory (MAM Attributes)
605 pub fn cartridge_memory(&mut self) -> Result<Vec<MamAttribute>, Error> {
606 read_mam_attributes(&mut self.file)
607 }
608
609 /// Read Volume Statistics
610 pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> {
611 read_volume_statistics(&mut self.file)
612 }
613
614 pub fn set_encryption(&mut self, key: Option<[u8; 32]>) -> Result<(), Error> {
615 self.encryption_key_loaded = key.is_some();
616
617 set_encryption(&mut self.file, key)
618 }
619
620 // Note: use alloc_page_aligned_buffer to alloc data transfer buffer
621 //
622 // Returns true if the drive reached the Logical End Of Media (early warning)
623 fn write_block(&mut self, data: &[u8]) -> Result<bool, std::io::Error> {
624 let transfer_len = data.len();
625
626 if transfer_len > 0x800000 {
627 proxmox_lang::io_bail!("write failed - data too large");
628 }
629
630 let mut sg_raw = SgRaw::new(&mut self.file, 0).unwrap(); // cannot fail with size 0
631
632 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
633 let mut cmd = Vec::new();
634 cmd.push(0x0A); // WRITE
635 cmd.push(0x00); // VARIABLE SIZED BLOCKS
636 cmd.push(((transfer_len >> 16) & 0xff) as u8);
637 cmd.push(((transfer_len >> 8) & 0xff) as u8);
638 cmd.push((transfer_len & 0xff) as u8);
639 cmd.push(0); // control byte
640
641 //println!("WRITE {:?}", cmd);
642 //println!("WRITE {:?}", data);
643
644 match sg_raw.do_out_command(&cmd, data) {
645 Ok(()) => Ok(false),
646 Err(ScsiError::Sense(SenseInfo {
647 sense_key: 0,
648 asc: 0,
649 ascq: 2,
650 })) => {
651 Ok(true) // LEOM
652 }
653 Err(err) => {
654 proxmox_lang::io_bail!("write failed - {}", err);
655 }
656 }
657 }
658
659 fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
660 let transfer_len = buffer.len();
661
662 if transfer_len > 0xFFFFFF {
663 return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
664 "read failed - buffer too large"
665 )));
666 }
667
668 let mut sg_raw = SgRaw::new(&mut self.file, 0).unwrap(); // cannot fail with size 0
669
670 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
671 let mut cmd = Vec::new();
672 cmd.push(0x08); // READ
673 cmd.push(0x02); // VARIABLE SIZED BLOCKS, SILI=1
674 //cmd.push(0x00); // VARIABLE SIZED BLOCKS, SILI=0
675 cmd.push(((transfer_len >> 16) & 0xff) as u8);
676 cmd.push(((transfer_len >> 8) & 0xff) as u8);
677 cmd.push((transfer_len & 0xff) as u8);
678 cmd.push(0); // control byte
679
680 let data = match sg_raw.do_in_command(&cmd, buffer) {
681 Ok(data) => data,
682 Err(ScsiError::Sense(SenseInfo {
683 sense_key: 0,
684 asc: 0,
685 ascq: 1,
686 })) => {
687 return Err(BlockReadError::EndOfFile);
688 }
689 Err(ScsiError::Sense(SenseInfo {
690 sense_key: 8,
691 asc: 0,
692 ascq: 5,
693 })) => {
694 return Err(BlockReadError::EndOfStream);
695 }
696 Err(err) => {
697 return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
698 "read failed - {}",
699 err
700 )));
701 }
702 };
703
704 if data.len() != transfer_len {
705 return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
706 "read failed - unexpected block len ({} != {})",
707 data.len(),
708 buffer.len()
709 )));
710 }
711
712 Ok(transfer_len)
713 }
714
715 pub fn open_writer(&mut self) -> BlockedWriter<SgTapeWriter> {
716 let writer = SgTapeWriter::new(self);
717 BlockedWriter::new(writer)
718 }
719
720 pub fn open_reader(&mut self) -> Result<BlockedReader<SgTapeReader>, BlockReadError> {
721 let reader = SgTapeReader::new(self);
722 BlockedReader::open(reader)
723 }
724
725 /// Set all options we need/want
726 pub fn set_default_options(&mut self) -> Result<(), Error> {
727 let compression = Some(true);
728 let block_length = Some(0); // variable length mode
729 let buffer_mode = Some(true); // Always use drive buffer
730
731 self.set_drive_options(compression, block_length, buffer_mode)?;
732
733 Ok(())
734 }
735
736 /// Set important drive options
737 pub fn set_drive_options(
738 &mut self,
739 compression: Option<bool>,
740 block_length: Option<u32>,
741 buffer_mode: Option<bool>,
742 ) -> Result<(), Error> {
743 // Note: Read/Modify/Write
744
745 let (mut head, mut block_descriptor, mut page) = self.read_compression_page()?;
746
747 let mut sg_raw = SgRaw::new(&mut self.file, 0)?;
748 sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
749
750 head.mode_data_len = 0; // need to b e zero
751
752 if let Some(compression) = compression {
753 page.set_compression(compression);
754 }
755
756 if let Some(block_length) = block_length {
757 block_descriptor.set_block_length(block_length)?;
758 }
759
760 if let Some(buffer_mode) = buffer_mode {
761 head.set_buffer_mode(buffer_mode);
762 }
763
764 let mut data = Vec::new();
765 unsafe {
766 data.write_be_value(head)?;
767 data.write_be_value(block_descriptor)?;
768 data.write_be_value(page)?;
769 }
770
771 let mut cmd = Vec::new();
772 cmd.push(0x55); // MODE SELECT(10)
773 cmd.push(0b0001_0000); // PF=1
774 cmd.extend(&[0, 0, 0, 0, 0]); //reserved
775
776 let param_list_len: u16 = data.len() as u16;
777 cmd.extend(&param_list_len.to_be_bytes());
778 cmd.push(0); // control
779
780 let mut buffer = alloc_page_aligned_buffer(4096)?;
781
782 buffer[..data.len()].copy_from_slice(&data[..]);
783
784 sg_raw
785 .do_out_command(&cmd, &buffer[..data.len()])
786 .map_err(|err| format_err!("set drive options failed - {}", err))?;
787
788 Ok(())
789 }
790
791 fn read_medium_configuration_page(
792 &mut self,
793 ) -> Result<
794 (
795 ModeParameterHeader,
796 ModeBlockDescriptor,
797 MediumConfigurationModePage,
798 ),
799 Error,
800 > {
801 let (head, block_descriptor, page): (_, _, MediumConfigurationModePage) =
802 scsi_mode_sense(&mut self.file, false, 0x1d, 0)?;
803
804 proxmox_lang::try_block!({
805 if (page.page_code & 0b0011_1111) != 0x1d {
806 bail!("wrong page code {}", page.page_code);
807 }
808 if page.page_length != 0x1e {
809 bail!("wrong page length {}", page.page_length);
810 }
811
812 let block_descriptor = match block_descriptor {
813 Some(block_descriptor) => block_descriptor,
814 None => bail!("missing block descriptor"),
815 };
816
817 Ok((head, block_descriptor, page))
818 })
819 .map_err(|err| format_err!("read_medium_configuration failed - {}", err))
820 }
821
822 fn read_compression_page(
823 &mut self,
824 ) -> Result<
825 (
826 ModeParameterHeader,
827 ModeBlockDescriptor,
828 DataCompressionModePage,
829 ),
830 Error,
831 > {
832 let (head, block_descriptor, page): (_, _, DataCompressionModePage) =
833 scsi_mode_sense(&mut self.file, false, 0x0f, 0)?;
834
835 proxmox_lang::try_block!({
836 if (page.page_code & 0b0011_1111) != 0x0f {
837 bail!("wrong page code {}", page.page_code);
838 }
839 if page.page_length != 0x0e {
840 bail!("wrong page length {}", page.page_length);
841 }
842
843 let block_descriptor = match block_descriptor {
844 Some(block_descriptor) => block_descriptor,
845 None => bail!("missing block descriptor"),
846 };
847
848 Ok((head, block_descriptor, page))
849 })
850 .map_err(|err| format_err!("read_compression_page failed: {}", err))
851 }
852
853 /// Read drive options/status
854 ///
855 /// We read the drive compression page, including the
856 /// block_descriptor. This is all information we need for now.
857 pub fn read_drive_status(&mut self) -> Result<LtoTapeStatus, Error> {
858 // We do a Request Sense, but ignore the result.
859 // This clears deferred error or media changed events.
860 let _ = scsi_request_sense(&mut self.file);
861
862 let (head, block_descriptor, page) = self.read_compression_page()?;
863
864 Ok(LtoTapeStatus {
865 block_length: block_descriptor.block_length(),
866 write_protect: head.write_protect(),
867 buffer_mode: head.buffer_mode(),
868 compression: page.compression_enabled(),
869 density_code: block_descriptor.density_code,
870 })
871 }
872
873 /// Get Tape and Media status
874 pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error> {
875 let drive_status = self.read_drive_status()?;
876
877 let alert_flags = self
878 .tape_alert_flags()
879 .map(|flags| format!("{:?}", flags))
880 .ok();
881
882 let mut status = LtoDriveAndMediaStatus {
883 vendor: self.info().vendor.clone(),
884 product: self.info().product.clone(),
885 revision: self.info().revision.clone(),
886 blocksize: drive_status.block_length,
887 compression: drive_status.compression,
888 buffer_mode: drive_status.buffer_mode,
889 density: drive_status.density_code.try_into()?,
890 alert_flags,
891 write_protect: None,
892 file_number: None,
893 block_number: None,
894 manufactured: None,
895 bytes_read: None,
896 bytes_written: None,
897 medium_passes: None,
898 medium_wearout: None,
899 volume_mounts: None,
900 };
901
902 if self.test_unit_ready().is_ok() {
903 if drive_status.write_protect {
904 status.write_protect = Some(drive_status.write_protect);
905 }
906
907 let position = self.position()?;
908
909 status.file_number = Some(position.logical_file_id);
910 status.block_number = Some(position.logical_object_number);
911
912 if let Ok(mam) = self.cartridge_memory() {
913 let usage = mam_extract_media_usage(&mam)?;
914
915 status.manufactured = Some(usage.manufactured);
916 status.bytes_read = Some(usage.bytes_read);
917 status.bytes_written = Some(usage.bytes_written);
918
919 if let Ok(volume_stats) = self.volume_statistics() {
920 let passes = std::cmp::max(
921 volume_stats.beginning_of_medium_passes,
922 volume_stats.middle_of_tape_passes,
923 );
924
925 // assume max. 16000 medium passes
926 // see: https://en.wikipedia.org/wiki/Linear_Tape-Open
927 let wearout: f64 = (passes as f64) / 16000.0_f64;
928
929 status.medium_passes = Some(passes);
930 status.medium_wearout = Some(wearout);
931
932 status.volume_mounts = Some(volume_stats.volume_mounts);
933 }
934 }
935 }
936
937 Ok(status)
938 }
939 }
940
941 impl Drop for SgTape {
942 fn drop(&mut self) {
943 // For security reasons, clear the encryption key
944 if self.encryption_key_loaded {
945 let _ = self.set_encryption(None);
946 }
947 }
948 }
949
950 pub struct SgTapeReader<'a> {
951 sg_tape: &'a mut SgTape,
952 end_of_file: bool,
953 }
954
955 impl<'a> SgTapeReader<'a> {
956 pub fn new(sg_tape: &'a mut SgTape) -> Self {
957 Self {
958 sg_tape,
959 end_of_file: false,
960 }
961 }
962 }
963
964 impl<'a> BlockRead for SgTapeReader<'a> {
965 fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
966 if self.end_of_file {
967 return Err(BlockReadError::Error(proxmox_lang::io_format_err!(
968 "detected read after EOF!"
969 )));
970 }
971 match self.sg_tape.read_block(buffer) {
972 Ok(usize) => Ok(usize),
973 Err(BlockReadError::EndOfFile) => {
974 self.end_of_file = true;
975 Err(BlockReadError::EndOfFile)
976 }
977 Err(err) => Err(err),
978 }
979 }
980 }
981
982 pub struct SgTapeWriter<'a> {
983 sg_tape: &'a mut SgTape,
984 _leom_sent: bool,
985 }
986
987 impl<'a> SgTapeWriter<'a> {
988 pub fn new(sg_tape: &'a mut SgTape) -> Self {
989 Self {
990 sg_tape,
991 _leom_sent: false,
992 }
993 }
994 }
995
996 impl<'a> BlockWrite for SgTapeWriter<'a> {
997 fn write_block(&mut self, buffer: &[u8]) -> Result<bool, std::io::Error> {
998 self.sg_tape.write_block(buffer)
999 }
1000
1001 fn write_filemark(&mut self) -> Result<(), std::io::Error> {
1002 self.sg_tape.write_filemarks(1, true)
1003 }
1004 }