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
;
7 use std
::time
::SystemTime
;
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
::*;
28 use proxmox_io
::{ReadExt, WriteExt}
;
29 use proxmox_sys
::error
::SysResult
;
31 use pbs_api_types
::{Lp17VolumeStatistics, LtoDriveAndMediaStatus, MamAttribute}
;
35 alloc_page_aligned_buffer
, scsi_inquiry
, scsi_mode_sense
, scsi_request_sense
, InquiryInfo
,
36 ModeBlockDescriptor
, ModeParameterHeader
, ScsiError
, SenseInfo
, SgRaw
,
38 BlockRead
, BlockReadError
, BlockWrite
, BlockedReader
, BlockedWriter
,
42 #[derive(Endian, Debug, Copy, Clone)]
43 pub struct ReadPositionLongPage
{
46 partition_number
: u32,
47 pub logical_object_number
: u64,
48 pub logical_file_id
: u64,
53 #[derive(Endian, Debug, Copy, Clone)]
54 struct DataCompressionModePage
{
55 page_code
: u8, // 0x0f
56 page_length
: u8, // 0x0e
59 compression_algorithm
: u32,
60 decompression_algorithm
: u32,
64 impl DataCompressionModePage
{
65 pub fn set_compression(&mut self, enable
: bool
) {
73 pub fn compression_enabled(&self) -> bool
{
74 (self.flags2
& 0b1000_0000) != 0
80 struct MediumConfigurationModePage
{
81 page_code
: u8, // 0x1d
82 page_length
: u8, // 0x1e
87 impl MediumConfigurationModePage
{
88 pub fn is_worm(&self) -> bool
{
89 (self.flags2
& 1) == 1
94 pub struct LtoTapeStatus
{
95 pub block_length
: u32,
98 pub write_protect
: bool
,
99 pub compression
: bool
,
104 locate_offset
: Option
<i64>,
106 encryption_key_loaded
: bool
,
110 const SCSI_TAPE_DEFAULT_TIMEOUT
: usize = 60 * 10; // 10 minutes
112 /// Create a new instance
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
)?
;
118 if info
.peripheral_type
!= 1 {
120 "not a tape device (peripheral_type = {})",
128 encryption_key_loaded
: false,
133 /// Access to file descriptor - useful for testing
134 pub fn file_mut(&mut self) -> &mut File
{
138 pub fn info(&self) -> &InquiryInfo
{
142 /// Return the maximum supported density code
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
)
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()
154 .custom_flags(libc
::O_NONBLOCK
)
157 // then clear O_NONBLOCK
158 let flags
= fcntl(file
.as_raw_fd(), FcntlArg
::F_GETFL
).into_io_result()?
;
160 let mut flags
= OFlag
::from_bits_truncate(flags
);
161 flags
.remove(OFlag
::O_NONBLOCK
);
163 fcntl(file
.as_raw_fd(), FcntlArg
::F_SETFL(flags
)).into_io_result()?
;
168 pub fn inquiry(&mut self) -> Result
<InquiryInfo
, Error
> {
169 scsi_inquiry(&mut self.file
)
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
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();
184 cmd
.push(0); // LONG=0
186 cmd
.push(1); // LONG=1
188 cmd
.extend(&[0, 0, 0, 0]);
192 .map_err(|err
| format_err
!("erase failed - {}", err
))?
;
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
)
208 // LTO3 and older do not supprt medium configuration mode page
214 // We cannot FORMAT WORM media! Instead we check if its empty.
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.");
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();
231 cmd
.extend(&[0x04, 0, 0, 0, 0, 0]); // FORMAT
232 sg_raw
.do_command(&cmd
)?
;
234 self.erase_media(false)?
; // overwrite everything
237 // try rewind/erase instead
238 self.erase_media(fast
)?
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]);
256 cmd
.push(0); // control
258 sg_raw
.do_command(&cmd
)?
;
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
271 .map_err(|err
| format_err
!("rewind failed - {}", err
))?
;
276 pub fn locate_file(&mut self, position
: u64) -> Result
<(), Error
> {
278 return self.rewind();
281 const SPACE_ONE_FILEMARK
: &[u8] = &[0x11, 0x01, 0, 0, 1, 0];
283 // Special case for position 1, because LOCATE 0 does not work
286 let mut sg_raw
= SgRaw
::new(&mut self.file
, 16)?
;
287 sg_raw
.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT
);
289 .do_command(SPACE_ONE_FILEMARK
)
290 .map_err(|err
| format_err
!("locate file {} (space) failed - {}", position
, err
))?
;
294 let mut sg_raw
= SgRaw
::new(&mut self.file
, 16)?
;
295 sg_raw
.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT
);
297 // Note: LOCATE(16) works for LTO4 or newer
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
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)
307 position
.saturating_add(locate_offset
as u64)
312 // always sub(1), so that it works for IBM drives without locate_offset
313 let fixed_position
= fixed_position
.saturating_sub(1);
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]);
322 .map_err(|err
| format_err
!("locate file {} failed - {}", position
, err
))?
;
324 // LOCATE always position at the BOT side of the filemark, so
325 // we need to move to other side of filemark
327 .do_command(SPACE_ONE_FILEMARK
)
328 .map_err(|err
| format_err
!("locate file {} (space) failed - {}", position
, err
))?
;
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
))
337 "locate_file: offset between {} and {} invalid: {}",
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...");
350 self.locate_offset
= Some(0);
357 pub fn position(&mut self) -> Result
<ReadPositionLongPage
, Error
> {
358 let expected_size
= std
::mem
::size_of
::<ReadPositionLongPage
>();
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
366 cmd
.extend(&[0x34, 0x06, 0, 0, 0, 0, 0, 0, 0, 0]); // READ POSITION LONG FORM
370 .map_err(|err
| format_err
!("read position failed - {}", err
))?
;
372 let page
= proxmox_lang
::try_block
!({
373 if data
.len() != expected_size
{
375 "got unexpected data len ({} != {}",
381 let mut reader
= data
;
383 let page
: ReadPositionLongPage
= unsafe { reader.read_be_value()? }
;
387 .map_err(|err
: Error
| format_err
!("decode position page failed - {}", err
))?
;
389 if page
.partition_number
!= 0 {
390 bail
!("detecthed partitioned tape - not supported");
396 pub fn current_file_number(&mut self) -> Result
<u64, Error
> {
397 let position
= self.position()?
;
398 Ok(position
.logical_file_id
)
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)
409 // Note: SPACE blocks returns Err at filemark
410 match self.space(-1, true) {
412 self.space(1, true) // move back to end
414 format_err
!("check_filemark failed (space forward) - {}", err
)
418 Err(ScsiError
::Sense(SenseInfo
{
423 // Filemark detected - good
424 self.space(1, false) // move to EOT side of filemark
427 "check_filemark failed (move to EOT side of filemark) - {}",
434 bail
!("check_filemark failed - {:?}", err
);
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
447 .map_err(|err
| format_err
!("move to EOD failed - {}", err
))?
;
449 if write_missing_eof
&& !self.check_filemark()?
{
450 self.write_filemarks(1, false)?
;
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();
461 // Use short command if possible (supported by all drives)
462 if (count
<= 0x7fffff) && (count
> -0x7fffff) {
463 cmd
.push(0x11); // SPACE(6)
465 cmd
.push(0); // blocks
467 cmd
.push(1); // filemarks
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
474 cmd
.push(0x91); // SPACE(16)
476 cmd
.push(0); // blocks
478 cmd
.push(1); // filemarks
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
486 sg_raw
.do_command(&cmd
)?
;
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
))
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
))
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
509 .map_err(|err
| format_err
!("eject failed - {}", err
))?
;
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
522 .map_err(|err
| format_err
!("load media failed - {}", err
))?
;
527 pub fn write_filemarks(&mut self, count
: usize, immediate
: bool
) -> Result
<(), std
::io
::Error
> {
529 proxmox_lang
::io_bail
!("write_filemarks failed: got strange count '{}'", count
);
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
)
536 sg_raw
.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT
);
537 let mut cmd
= Vec
::new();
540 cmd
.push(1); // IMMED=1
542 cmd
.push(0); // IMMED=0
544 cmd
.extend(&[0, 0, count
as u8]); // COUNT
545 cmd
.push(0); // control byte
547 match sg_raw
.do_command(&cmd
) {
548 Ok(_
) => { /* OK */ }
549 Err(ScsiError
::Sense(SenseInfo
{
553 })) => { /* LEOM - ignore */ }
555 proxmox_lang
::io_bail
!("write filemark failed - {}", err
);
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)?
;
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
574 match sg_raw
.do_command(&cmd
) {
577 bail
!("test_unit_ready failed - {}", err
);
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);
587 match self.test_unit_ready() {
588 Ok(()) => return Ok(()),
590 std
::thread
::sleep(std
::time
::Duration
::new(1, 0));
591 if start
.elapsed()?
> max_wait
{
592 bail
!("wait_until_ready failed - got timeout");
599 /// Read Tape Alert Flags
600 pub fn tape_alert_flags(&mut self) -> Result
<TapeAlertFlags
, Error
> {
601 read_tape_alert_flags(&mut self.file
)
604 /// Read Cartridge Memory (MAM Attributes)
605 pub fn cartridge_memory(&mut self) -> Result
<Vec
<MamAttribute
>, Error
> {
606 read_mam_attributes(&mut self.file
)
609 /// Read Volume Statistics
610 pub fn volume_statistics(&mut self) -> Result
<Lp17VolumeStatistics
, Error
> {
611 read_volume_statistics(&mut self.file
)
614 pub fn set_encryption(&mut self, key
: Option
<[u8; 32]>) -> Result
<(), Error
> {
615 self.encryption_key_loaded
= key
.is_some();
617 set_encryption(&mut self.file
, key
)
620 // Note: use alloc_page_aligned_buffer to alloc data transfer buffer
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();
626 if transfer_len
> 0x800000 {
627 proxmox_lang
::io_bail
!("write failed - data too large");
630 let mut sg_raw
= SgRaw
::new(&mut self.file
, 0).unwrap(); // cannot fail with size 0
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
641 //println!("WRITE {:?}", cmd);
642 //println!("WRITE {:?}", data);
644 match sg_raw
.do_out_command(&cmd
, data
) {
646 Err(ScsiError
::Sense(SenseInfo
{
654 proxmox_lang
::io_bail
!("write failed - {}", err
);
659 fn read_block(&mut self, buffer
: &mut [u8]) -> Result
<usize, BlockReadError
> {
660 let transfer_len
= buffer
.len();
662 if transfer_len
> 0xFFFFFF {
663 return Err(BlockReadError
::Error(proxmox_lang
::io_format_err
!(
664 "read failed - buffer too large"
668 let mut sg_raw
= SgRaw
::new(&mut self.file
, 0).unwrap(); // cannot fail with size 0
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
680 let data
= match sg_raw
.do_in_command(&cmd
, buffer
) {
682 Err(ScsiError
::Sense(SenseInfo
{
687 return Err(BlockReadError
::EndOfFile
);
689 Err(ScsiError
::Sense(SenseInfo
{
694 return Err(BlockReadError
::EndOfStream
);
697 return Err(BlockReadError
::Error(proxmox_lang
::io_format_err
!(
704 if data
.len() != transfer_len
{
705 return Err(BlockReadError
::Error(proxmox_lang
::io_format_err
!(
706 "read failed - unexpected block len ({} != {})",
715 pub fn open_writer(&mut self) -> BlockedWriter
<SgTapeWriter
> {
716 let writer
= SgTapeWriter
::new(self);
717 BlockedWriter
::new(writer
)
720 pub fn open_reader(&mut self) -> Result
<BlockedReader
<SgTapeReader
>, BlockReadError
> {
721 let reader
= SgTapeReader
::new(self);
722 BlockedReader
::open(reader
)
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
731 self.set_drive_options(compression
, block_length
, buffer_mode
)?
;
736 /// Set important drive options
737 pub fn set_drive_options(
739 compression
: Option
<bool
>,
740 block_length
: Option
<u32>,
741 buffer_mode
: Option
<bool
>,
742 ) -> Result
<(), Error
> {
743 // Note: Read/Modify/Write
745 let (mut head
, mut block_descriptor
, mut page
) = self.read_compression_page()?
;
747 let mut sg_raw
= SgRaw
::new(&mut self.file
, 0)?
;
748 sg_raw
.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT
);
750 head
.mode_data_len
= 0; // need to b e zero
752 if let Some(compression
) = compression
{
753 page
.set_compression(compression
);
756 if let Some(block_length
) = block_length
{
757 block_descriptor
.set_block_length(block_length
)?
;
760 if let Some(buffer_mode
) = buffer_mode
{
761 head
.set_buffer_mode(buffer_mode
);
764 let mut data
= Vec
::new();
766 data
.write_be_value(head
)?
;
767 data
.write_be_value(block_descriptor
)?
;
768 data
.write_be_value(page
)?
;
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
776 let param_list_len
: u16 = data
.len() as u16;
777 cmd
.extend(¶m_list_len
.to_be_bytes());
778 cmd
.push(0); // control
780 let mut buffer
= alloc_page_aligned_buffer(4096)?
;
782 buffer
[..data
.len()].copy_from_slice(&data
[..]);
785 .do_out_command(&cmd
, &buffer
[..data
.len()])
786 .map_err(|err
| format_err
!("set drive options failed - {}", err
))?
;
791 fn read_medium_configuration_page(
797 MediumConfigurationModePage
,
801 let (head
, block_descriptor
, page
): (_
, _
, MediumConfigurationModePage
) =
802 scsi_mode_sense(&mut self.file
, false, 0x1d, 0)?
;
804 proxmox_lang
::try_block
!({
805 if (page
.page_code
& 0b0011_1111) != 0x1d {
806 bail
!("wrong page code {}", page
.page_code
);
808 if page
.page_length
!= 0x1e {
809 bail
!("wrong page length {}", page
.page_length
);
812 let block_descriptor
= match block_descriptor
{
813 Some(block_descriptor
) => block_descriptor
,
814 None
=> bail
!("missing block descriptor"),
817 Ok((head
, block_descriptor
, page
))
819 .map_err(|err
| format_err
!("read_medium_configuration failed - {}", err
))
822 fn read_compression_page(
828 DataCompressionModePage
,
832 let (head
, block_descriptor
, page
): (_
, _
, DataCompressionModePage
) =
833 scsi_mode_sense(&mut self.file
, false, 0x0f, 0)?
;
835 proxmox_lang
::try_block
!({
836 if (page
.page_code
& 0b0011_1111) != 0x0f {
837 bail
!("wrong page code {}", page
.page_code
);
839 if page
.page_length
!= 0x0e {
840 bail
!("wrong page length {}", page
.page_length
);
843 let block_descriptor
= match block_descriptor
{
844 Some(block_descriptor
) => block_descriptor
,
845 None
=> bail
!("missing block descriptor"),
848 Ok((head
, block_descriptor
, page
))
850 .map_err(|err
| format_err
!("read_compression_page failed: {}", err
))
853 /// Read drive options/status
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
);
862 let (head
, block_descriptor
, page
) = self.read_compression_page()?
;
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
,
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()?
;
877 let alert_flags
= self
879 .map(|flags
| format
!("{:?}", flags
))
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()?
,
898 medium_wearout
: None
,
902 if self.test_unit_ready().is_ok() {
903 if drive_status
.write_protect
{
904 status
.write_protect
= Some(drive_status
.write_protect
);
907 let position
= self.position()?
;
909 status
.file_number
= Some(position
.logical_file_id
);
910 status
.block_number
= Some(position
.logical_object_number
);
912 if let Ok(mam
) = self.cartridge_memory() {
913 let usage
= mam_extract_media_usage(&mam
)?
;
915 status
.manufactured
= Some(usage
.manufactured
);
916 status
.bytes_read
= Some(usage
.bytes_read
);
917 status
.bytes_written
= Some(usage
.bytes_written
);
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
,
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;
929 status
.medium_passes
= Some(passes
);
930 status
.medium_wearout
= Some(wearout
);
932 status
.volume_mounts
= Some(volume_stats
.volume_mounts
);
941 impl Drop
for SgTape
{
943 // For security reasons, clear the encryption key
944 if self.encryption_key_loaded
{
945 let _
= self.set_encryption(None
);
950 pub struct SgTapeReader
<'a
> {
951 sg_tape
: &'a
mut SgTape
,
955 impl<'a
> SgTapeReader
<'a
> {
956 pub fn new(sg_tape
: &'a
mut SgTape
) -> Self {
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!"
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
)
977 Err(err
) => Err(err
),
982 pub struct SgTapeWriter
<'a
> {
983 sg_tape
: &'a
mut SgTape
,
987 impl<'a
> SgTapeWriter
<'a
> {
988 pub fn new(sg_tape
: &'a
mut SgTape
) -> Self {
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
)
1001 fn write_filemark(&mut self) -> Result
<(), std
::io
::Error
> {
1002 self.sg_tape
.write_filemarks(1, true)