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
;
6 use std
::convert
::TryFrom
;
7 use std
::convert
::TryInto
;
9 use anyhow
::{bail, format_err, Error}
;
10 use endian_trait
::Endian
;
11 use nix
::fcntl
::{fcntl, FcntlArg, OFlag}
;
14 pub use encryption
::*;
16 mod volume_statistics
;
17 pub use volume_statistics
::*;
20 pub use tape_alert_flags
::*;
26 pub use report_density
::*;
29 sys
::error
::SysResult
,
30 tools
::io
::{ReadExt, WriteExt}
,
33 use pbs_api_types
::{MamAttribute, Lp17VolumeStatistics, LtoDriveAndMediaStatus}
;
48 alloc_page_aligned_buffer
,
56 #[derive(Endian, Debug, Copy, Clone)]
57 pub struct ReadPositionLongPage
{
60 partition_number
: u32,
61 pub logical_object_number
: u64,
62 pub logical_file_id
: u64,
67 #[derive(Endian, Debug, Copy, Clone)]
68 struct DataCompressionModePage
{
69 page_code
: u8, // 0x0f
70 page_length
: u8, // 0x0e
73 compression_algorithm
: u32,
74 decompression_algorithm
: u32,
78 impl DataCompressionModePage
{
80 pub fn set_compression(&mut self, enable
: bool
) {
84 self.flags2
= self.flags2
& 127;
88 pub fn compression_enabled(&self) -> bool
{
89 (self.flags2
& 0b1000_0000) != 0
95 struct MediumConfigurationModePage
{
96 page_code
: u8, // 0x1d
97 page_length
: u8, // 0x1e
102 impl MediumConfigurationModePage
{
104 pub fn is_worm(&self) -> bool
{
105 (self.flags2
& 1) == 1
111 pub struct LtoTapeStatus
{
112 pub block_length
: u32,
113 pub density_code
: u8,
115 pub write_protect
: bool
,
116 pub compression
: bool
,
121 locate_offset
: Option
<i64>,
123 encryption_key_loaded
: bool
,
128 const SCSI_TAPE_DEFAULT_TIMEOUT
: usize = 60*10; // 10 minutes
130 /// Create a new instance
132 /// Uses scsi_inquiry to check the device type.
133 pub fn new(mut file
: File
) -> Result
<Self, Error
> {
135 let info
= scsi_inquiry(&mut file
)?
;
137 if info
.peripheral_type
!= 1 {
138 bail
!("not a tape device (peripheral_type = {})", info
.peripheral_type
);
144 encryption_key_loaded
: false,
149 /// Access to file descriptor - useful for testing
150 pub fn file_mut(&mut self) -> &mut File
{
154 pub fn info(&self) -> &InquiryInfo
{
158 /// Return the maximum supported density code
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
)
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()
170 .custom_flags(libc
::O_NONBLOCK
)
173 // then clear O_NONBLOCK
174 let flags
= fcntl(file
.as_raw_fd(), FcntlArg
::F_GETFL
)
177 let mut flags
= OFlag
::from_bits_truncate(flags
);
178 flags
.remove(OFlag
::O_NONBLOCK
);
180 fcntl(file
.as_raw_fd(), FcntlArg
::F_SETFL(flags
))
186 pub fn inquiry(&mut self) -> Result
<InquiryInfo
, Error
> {
187 scsi_inquiry(&mut self.file
)
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
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();
202 cmd
.push(0); // LONG=0
204 cmd
.push(1); // LONG=1
206 cmd
.extend(&[0, 0, 0, 0]);
208 sg_raw
.do_command(&cmd
)
209 .map_err(|err
| format_err
!("erase failed - {}", err
))?
;
214 /// Format media, single partition
215 pub fn format_media(&mut self, fast
: bool
) -> Result
<(), Error
> {
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
)
226 // LTO3 and older do not supprt medium configuration mode page
232 // We cannot FORMAT WORM media! Instead we check if its empty.
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.");
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();
250 cmd
.extend(&[0x04, 0, 0, 0, 0, 0]); // FORMAT
251 sg_raw
.do_command(&cmd
)?
;
253 self.erase_media(false)?
; // overwrite everything
256 // try rewind/erase instead
257 self.erase_media(fast
)?
264 /// Lock/Unlock drive door
265 pub fn set_medium_removal(&mut self, allow
: bool
) -> Result
<(), ScsiError
> {
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]);
276 cmd
.push(0); // control
278 sg_raw
.do_command(&cmd
)?
;
283 pub fn rewind(&mut self) -> Result
<(), Error
> {
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
290 sg_raw
.do_command(&cmd
)
291 .map_err(|err
| format_err
!("rewind failed - {}", err
))?
;
296 pub fn locate_file(&mut self, position
: u64) -> Result
<(), Error
> {
298 return self.rewind();
301 const SPACE_ONE_FILEMARK
: &[u8] = &[0x11, 0x01, 0, 0, 1, 0];
303 // Special case for position 1, because LOCATE 0 does not work
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
))?
;
313 let mut sg_raw
= SgRaw
::new(&mut self.file
, 16)?
;
314 sg_raw
.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT
);
316 // Note: LOCATE(16) works for LTO4 or newer
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
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)
326 position
.saturating_add(locate_offset
as u64)
331 // always sub(1), so that it works for IBM drives without locate_offset
332 let fixed_position
= fixed_position
.saturating_sub(1);
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]);
339 sg_raw
.do_command(&cmd
)
340 .map_err(|err
| format_err
!("locate file {} failed - {}", position
, err
))?
;
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
))?
;
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
{
352 i64::try_from((position
as i128
) - (current_file
as i128
)).map_err(|err
| {
354 "locate_file: offset between {} and {} invalid: {}",
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...");
367 self.locate_offset
= Some(0);
374 pub fn position(&mut self) -> Result
<ReadPositionLongPage
, Error
> {
376 let expected_size
= std
::mem
::size_of
::<ReadPositionLongPage
>();
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
384 cmd
.extend(&[0x34, 0x06, 0, 0, 0, 0, 0, 0, 0, 0]); // READ POSITION LONG FORM
386 let data
= sg_raw
.do_command(&cmd
)
387 .map_err(|err
| format_err
!("read position failed - {}", err
))?
;
389 let page
= proxmox
::try_block
!({
390 if data
.len() != expected_size
{
391 bail
!("got unexpected data len ({} != {}", data
.len(), expected_size
);
394 let mut reader
= &data
[..];
396 let page
: ReadPositionLongPage
= unsafe { reader.read_be_value()? }
;
399 }).map_err(|err
: Error
| format_err
!("decode position page failed - {}", err
))?
;
401 if page
.partition_number
!= 0 {
402 bail
!("detecthed partitioned tape - not supported");
408 pub fn current_file_number(&mut self) -> Result
<u64, Error
> {
409 let position
= self.position()?
;
410 Ok(position
.logical_file_id
)
413 /// Check if we are positioned after a filemark (or BOT)
414 pub fn check_filemark(&mut self) -> Result
<bool
, Error
> {
416 let pos
= self.position()?
;
417 if pos
.logical_object_number
== 0 {
418 // at BOT, Ok (no filemark required)
422 // Note: SPACE blocks returns Err at filemark
423 match self.space(-1, true) {
425 self.space(1, true) // move back to end
426 .map_err(|err
| format_err
!("check_filemark failed (space forward) - {}", err
))?
;
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
))?
;
436 bail
!("check_filemark failed - {:?}", err
);
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
447 sg_raw
.do_command(&cmd
)
448 .map_err(|err
| format_err
!("move to EOD failed - {}", err
))?
;
450 if write_missing_eof
{
451 if !self.check_filemark()?
{
452 self.write_filemarks(1, false)?
;
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();
464 // Use short command if possible (supported by all drives)
465 if (count
<= 0x7fffff) && (count
> -0x7fffff) {
466 cmd
.push(0x11); // SPACE(6)
468 cmd
.push(0); // blocks
470 cmd
.push(1); // filemarks
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
477 cmd
.push(0x91); // SPACE(16)
479 cmd
.push(0); // blocks
481 cmd
.push(1); // filemarks
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
489 sg_raw
.do_command(&cmd
)?
;
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
))
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
))
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
510 sg_raw
.do_command(&cmd
)
511 .map_err(|err
| format_err
!("eject failed - {}", err
))?
;
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
522 sg_raw
.do_command(&cmd
)
523 .map_err(|err
| format_err
!("load media failed - {}", err
))?
;
528 pub fn write_filemarks(
532 ) -> Result
<(), std
::io
::Error
> {
535 proxmox
::io_bail
!("write_filemarks failed: got strange count '{}'", count
);
538 let mut sg_raw
= SgRaw
::new(&mut self.file
, 16)
539 .map_err(|err
| proxmox
::io_format_err
!("write_filemarks failed (alloc) - {}", err
))?
;
541 sg_raw
.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT
);
542 let mut cmd
= Vec
::new();
545 cmd
.push(1); // IMMED=1
547 cmd
.push(0); // IMMED=0
549 cmd
.extend(&[0, 0, count
as u8]); // COUNT
550 cmd
.push(0); // control byte
552 match sg_raw
.do_command(&cmd
) {
553 Ok(_
) => { /* OK */ }
554 Err(ScsiError
::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 }
)) => {
558 proxmox
::io_bail
!("write filemark failed - {}", err
);
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)?
;
571 pub fn test_unit_ready(&mut self) -> Result
<(), Error
> {
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
578 match sg_raw
.do_command(&cmd
) {
581 bail
!("test_unit_ready failed - {}", err
);
586 pub fn wait_until_ready(&mut self) -> Result
<(), Error
> {
588 let start
= SystemTime
::now();
589 let max_wait
= std
::time
::Duration
::new(Self::SCSI_TAPE_DEFAULT_TIMEOUT
as u64, 0);
592 match self.test_unit_ready() {
593 Ok(()) => return Ok(()),
595 std
::thread
::sleep(std
::time
::Duration
::new(1, 0));
596 if start
.elapsed()?
> max_wait
{
597 bail
!("wait_until_ready failed - got timeout");
604 /// Read Tape Alert Flags
605 pub fn tape_alert_flags(&mut self) -> Result
<TapeAlertFlags
, Error
> {
606 read_tape_alert_flags(&mut self.file
)
609 /// Read Cartridge Memory (MAM Attributes)
610 pub fn cartridge_memory(&mut self) -> Result
<Vec
<MamAttribute
>, Error
> {
611 read_mam_attributes(&mut self.file
)
614 /// Read Volume Statistics
615 pub fn volume_statistics(&mut self) -> Result
<Lp17VolumeStatistics
, Error
> {
616 return read_volume_statistics(&mut self.file
);
619 pub fn set_encryption(
621 key
: Option
<[u8; 32]>,
622 ) -> Result
<(), Error
> {
624 self.encryption_key_loaded
= key
.is_some();
626 set_encryption(&mut self.file
, key
)
629 // Note: use alloc_page_aligned_buffer to alloc data transfer buffer
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
> {
634 let transfer_len
= data
.len();
636 if transfer_len
> 0x800000 {
637 proxmox
::io_bail
!("write failed - data too large");
640 let mut sg_raw
= SgRaw
::new(&mut self.file
, 0)
641 .unwrap(); // cannot fail with size 0
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
652 //println!("WRITE {:?}", cmd);
653 //println!("WRITE {:?}", data);
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
661 proxmox
::io_bail
!("write failed - {}", err
);
666 fn read_block(&mut self, buffer
: &mut [u8]) -> Result
<usize, BlockReadError
> {
667 let transfer_len
= buffer
.len();
669 if transfer_len
> 0xFFFFFF {
670 return Err(BlockReadError
::Error(
671 proxmox
::io_format_err
!("read failed - buffer too large")
675 let mut sg_raw
= SgRaw
::new(&mut self.file
, 0)
676 .unwrap(); // cannot fail with size 0
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
688 let data
= match sg_raw
.do_in_command(&cmd
, buffer
) {
690 Err(ScsiError
::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 }
)) => {
691 return Err(BlockReadError
::EndOfFile
);
693 Err(ScsiError
::Sense(SenseInfo { sense_key: 8, asc: 0, ascq: 5 }
)) => {
694 return Err(BlockReadError
::EndOfStream
);
697 return Err(BlockReadError
::Error(
698 proxmox
::io_format_err
!("read failed - {}", err
)
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())
712 pub fn open_writer(&mut self) -> BlockedWriter
<SgTapeWriter
> {
713 let writer
= SgTapeWriter
::new(self);
714 BlockedWriter
::new(writer
)
717 pub fn open_reader(&mut self) -> Result
<BlockedReader
<SgTapeReader
>, BlockReadError
> {
718 let reader
= SgTapeReader
::new(self);
719 BlockedReader
::open(reader
)
722 /// Set all options we need/want
723 pub fn set_default_options(&mut self) -> Result
<(), Error
> {
725 let compression
= Some(true);
726 let block_length
= Some(0); // variable length mode
727 let buffer_mode
= Some(true); // Always use drive buffer
729 self.set_drive_options(compression
, block_length
, buffer_mode
)?
;
734 /// Set important drive options
735 pub fn set_drive_options(
737 compression
: Option
<bool
>,
738 block_length
: Option
<u32>,
739 buffer_mode
: Option
<bool
>,
740 ) -> Result
<(), Error
> {
742 // Note: Read/Modify/Write
744 let (mut head
, mut block_descriptor
, mut page
) = self.read_compression_page()?
;
746 let mut sg_raw
= SgRaw
::new(&mut self.file
, 0)?
;
747 sg_raw
.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT
);
749 head
.mode_data_len
= 0; // need to b e zero
751 if let Some(compression
) = compression
{
752 page
.set_compression(compression
);
755 if let Some(block_length
) = block_length
{
756 block_descriptor
.set_block_length(block_length
)?
;
759 if let Some(buffer_mode
) = buffer_mode
{
760 head
.set_buffer_mode(buffer_mode
);
763 let mut data
= Vec
::new();
765 data
.write_be_value(head
)?
;
766 data
.write_be_value(block_descriptor
)?
;
767 data
.write_be_value(page
)?
;
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
775 let param_list_len
: u16 = data
.len() as u16;
776 cmd
.extend(¶m_list_len
.to_be_bytes());
777 cmd
.push(0); // control
779 let mut buffer
= alloc_page_aligned_buffer(4096)?
;
781 buffer
[..data
.len()].copy_from_slice(&data
[..]);
783 sg_raw
.do_out_command(&cmd
, &buffer
[..data
.len()])
784 .map_err(|err
| format_err
!("set drive options failed - {}", err
))?
;
789 fn read_medium_configuration_page(
791 ) -> Result
<(ModeParameterHeader
, ModeBlockDescriptor
, MediumConfigurationModePage
), Error
> {
793 let (head
, block_descriptor
, page
): (_
,_
, MediumConfigurationModePage
)
794 = scsi_mode_sense(&mut self.file
, false, 0x1d, 0)?
;
796 proxmox
::try_block
!({
797 if (page
.page_code
& 0b0011_1111) != 0x1d {
798 bail
!("wrong page code {}", page
.page_code
);
800 if page
.page_length
!= 0x1e {
801 bail
!("wrong page length {}", page
.page_length
);
804 let block_descriptor
= match block_descriptor
{
805 Some(block_descriptor
) => block_descriptor
,
806 None
=> bail
!("missing block descriptor"),
809 Ok((head
, block_descriptor
, page
))
810 }).map_err(|err
| format_err
!("read_medium_configuration failed - {}", err
))
813 fn read_compression_page(
815 ) -> Result
<(ModeParameterHeader
, ModeBlockDescriptor
, DataCompressionModePage
), Error
> {
817 let (head
, block_descriptor
, page
): (_
,_
, DataCompressionModePage
)
818 = scsi_mode_sense(&mut self.file
, false, 0x0f, 0)?
;
820 proxmox
::try_block
!({
821 if (page
.page_code
& 0b0011_1111) != 0x0f {
822 bail
!("wrong page code {}", page
.page_code
);
824 if page
.page_length
!= 0x0e {
825 bail
!("wrong page length {}", page
.page_length
);
828 let block_descriptor
= match block_descriptor
{
829 Some(block_descriptor
) => block_descriptor
,
830 None
=> bail
!("missing block descriptor"),
833 Ok((head
, block_descriptor
, page
))
834 }).map_err(|err
| format_err
!("read_compression_page failed: {}", err
))
837 /// Read drive options/status
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
> {
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
);
847 let (head
, block_descriptor
, page
) = self.read_compression_page()?
;
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
,
858 /// Get Tape and Media status
859 pub fn get_drive_and_media_status(&mut self) -> Result
<LtoDriveAndMediaStatus
, Error
> {
861 let drive_status
= self.read_drive_status()?
;
863 let alert_flags
= self.tape_alert_flags()
864 .map(|flags
| format
!("{:?}", flags
))
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()?
,
883 medium_wearout
: None
,
887 if self.test_unit_ready().is_ok() {
889 if drive_status
.write_protect
{
890 status
.write_protect
= Some(drive_status
.write_protect
);
893 let position
= self.position()?
;
895 status
.file_number
= Some(position
.logical_file_id
);
896 status
.block_number
= Some(position
.logical_object_number
);
898 if let Ok(mam
) = self.cartridge_memory() {
900 let usage
= mam_extract_media_usage(&mam
)?
;
902 status
.manufactured
= Some(usage
.manufactured
);
903 status
.bytes_read
= Some(usage
.bytes_read
);
904 status
.bytes_written
= Some(usage
.bytes_written
);
906 if let Ok(volume_stats
) = self.volume_statistics() {
908 let passes
= std
::cmp
::max(
909 volume_stats
.beginning_of_medium_passes
,
910 volume_stats
.middle_of_tape_passes
,
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);
917 status
.medium_passes
= Some(passes
);
918 status
.medium_wearout
= Some(wearout
);
920 status
.volume_mounts
= Some(volume_stats
.volume_mounts
);
930 impl Drop
for SgTape
{
932 // For security reasons, clear the encryption key
933 if self.encryption_key_loaded
{
934 let _
= self.set_encryption(None
);
940 pub struct SgTapeReader
<'a
> {
941 sg_tape
: &'a
mut SgTape
,
945 impl <'a
> SgTapeReader
<'a
> {
947 pub fn new(sg_tape
: &'a
mut SgTape
) -> Self {
948 Self { sg_tape, end_of_file: false, }
952 impl <'a
> BlockRead
for SgTapeReader
<'a
> {
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!")));
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
)
964 Err(err
) => Err(err
),
969 pub struct SgTapeWriter
<'a
> {
970 sg_tape
: &'a
mut SgTape
,
974 impl <'a
> SgTapeWriter
<'a
> {
976 pub fn new(sg_tape
: &'a
mut SgTape
) -> Self {
977 Self { sg_tape, _leom_sent: false }
981 impl <'a
> BlockWrite
for SgTapeWriter
<'a
> {
983 fn write_block(&mut self, buffer
: &[u8]) -> Result
<bool
, std
::io
::Error
> {
984 self.sg_tape
.write_block(buffer
)
987 fn write_filemark(&mut self) -> Result
<(), std
::io
::Error
> {
988 self.sg_tape
.write_filemarks(1, true)