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