split tape code into new pbs_tape workspace
This commit is contained in:
@ -11,41 +11,29 @@
|
||||
//!
|
||||
//! - unability to detect EOT (you just get EIO)
|
||||
|
||||
mod sg_tape;
|
||||
pub use sg_tape::*;
|
||||
|
||||
use std::fs::{OpenOptions, File};
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::fs::File;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::convert::TryInto;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use nix::fcntl::{fcntl, FcntlArg, OFlag};
|
||||
|
||||
use proxmox::{
|
||||
tools::Uuid,
|
||||
sys::error::SysResult,
|
||||
};
|
||||
use proxmox::tools::Uuid;
|
||||
|
||||
use pbs_api_types::{
|
||||
Fingerprint, MamAttribute, LtoDriveAndMediaStatus, LtoTapeDrive, Lp17VolumeStatistics,
|
||||
};
|
||||
use pbs_config::key_config::KeyConfig;
|
||||
use pbs_tools::run_command;
|
||||
use pbs_tape::{
|
||||
TapeWrite, TapeRead, BlockReadError, MediaContentHeader,
|
||||
sg_tape::{SgTape, TapeAlertFlags},
|
||||
linux_list_drives::open_lto_tape_device,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
tape::{
|
||||
TapeRead,
|
||||
TapeWrite,
|
||||
BlockReadError,
|
||||
drive::{
|
||||
TapeDriver,
|
||||
},
|
||||
file_formats::{
|
||||
PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
|
||||
MediaSetLabel,
|
||||
MediaContentHeader,
|
||||
},
|
||||
drive::TapeDriver,
|
||||
file_formats::{PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, MediaSetLabel},
|
||||
},
|
||||
};
|
||||
|
||||
@ -94,13 +82,7 @@ impl LtoTapeHandle {
|
||||
|
||||
/// Set all options we need/want
|
||||
pub fn set_default_options(&mut self) -> Result<(), Error> {
|
||||
|
||||
let compression = Some(true);
|
||||
let block_length = Some(0); // variable length mode
|
||||
let buffer_mode = Some(true); // Always use drive buffer
|
||||
|
||||
self.set_drive_options(compression, block_length, buffer_mode)?;
|
||||
|
||||
self.sg_tape.set_default_options()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -121,72 +103,7 @@ impl LtoTapeHandle {
|
||||
|
||||
/// Get Tape and Media status
|
||||
pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error> {
|
||||
|
||||
let drive_status = self.sg_tape.read_drive_status()?;
|
||||
|
||||
let alert_flags = self.tape_alert_flags()
|
||||
.map(|flags| format!("{:?}", flags))
|
||||
.ok();
|
||||
|
||||
let mut status = LtoDriveAndMediaStatus {
|
||||
vendor: self.sg_tape.info().vendor.clone(),
|
||||
product: self.sg_tape.info().product.clone(),
|
||||
revision: self.sg_tape.info().revision.clone(),
|
||||
blocksize: drive_status.block_length,
|
||||
compression: drive_status.compression,
|
||||
buffer_mode: drive_status.buffer_mode,
|
||||
density: drive_status.density_code.try_into()?,
|
||||
alert_flags,
|
||||
write_protect: None,
|
||||
file_number: None,
|
||||
block_number: None,
|
||||
manufactured: None,
|
||||
bytes_read: None,
|
||||
bytes_written: None,
|
||||
medium_passes: None,
|
||||
medium_wearout: None,
|
||||
volume_mounts: None,
|
||||
};
|
||||
|
||||
if self.sg_tape.test_unit_ready().is_ok() {
|
||||
|
||||
if drive_status.write_protect {
|
||||
status.write_protect = Some(drive_status.write_protect);
|
||||
}
|
||||
|
||||
let position = self.sg_tape.position()?;
|
||||
|
||||
status.file_number = Some(position.logical_file_id);
|
||||
status.block_number = Some(position.logical_object_number);
|
||||
|
||||
if let Ok(mam) = self.cartridge_memory() {
|
||||
|
||||
let usage = mam_extract_media_usage(&mam)?;
|
||||
|
||||
status.manufactured = Some(usage.manufactured);
|
||||
status.bytes_read = Some(usage.bytes_read);
|
||||
status.bytes_written = Some(usage.bytes_written);
|
||||
|
||||
if let Ok(volume_stats) = self.volume_statistics() {
|
||||
|
||||
let passes = std::cmp::max(
|
||||
volume_stats.beginning_of_medium_passes,
|
||||
volume_stats.middle_of_tape_passes,
|
||||
);
|
||||
|
||||
// assume max. 16000 medium passes
|
||||
// see: https://en.wikipedia.org/wiki/Linear_Tape-Open
|
||||
let wearout: f64 = (passes as f64)/(16000.0 as f64);
|
||||
|
||||
status.medium_passes = Some(passes);
|
||||
status.medium_wearout = Some(wearout);
|
||||
|
||||
status.volume_mounts = Some(volume_stats.volume_mounts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(status)
|
||||
self.sg_tape.get_drive_and_media_status()
|
||||
}
|
||||
|
||||
pub fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
|
||||
@ -413,59 +330,6 @@ impl TapeDriver for LtoTapeHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check for correct Major/Minor numbers
|
||||
pub fn check_tape_is_lto_tape_device(file: &File) -> Result<(), Error> {
|
||||
|
||||
let stat = nix::sys::stat::fstat(file.as_raw_fd())?;
|
||||
|
||||
let devnum = stat.st_rdev;
|
||||
|
||||
let major = unsafe { libc::major(devnum) };
|
||||
let _minor = unsafe { libc::minor(devnum) };
|
||||
|
||||
if major == 9 {
|
||||
bail!("not a scsi-generic tape device (cannot use linux tape devices)");
|
||||
}
|
||||
|
||||
if major != 21 {
|
||||
bail!("not a scsi-generic tape device");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Opens a Lto tape device
|
||||
///
|
||||
/// The open call use O_NONBLOCK, but that flag is cleard after open
|
||||
/// succeeded. This also checks if the device is a non-rewinding tape
|
||||
/// device.
|
||||
pub fn open_lto_tape_device(
|
||||
path: &str,
|
||||
) -> Result<File, Error> {
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.custom_flags(libc::O_NONBLOCK)
|
||||
.open(path)?;
|
||||
|
||||
// clear O_NONBLOCK from now on.
|
||||
|
||||
let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL)
|
||||
.into_io_result()?;
|
||||
|
||||
let mut flags = OFlag::from_bits_truncate(flags);
|
||||
flags.remove(OFlag::O_NONBLOCK);
|
||||
|
||||
fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags))
|
||||
.into_io_result()?;
|
||||
|
||||
check_tape_is_lto_tape_device(&file)
|
||||
.map_err(|err| format_err!("device type check {:?} failed - {}", path, err))?;
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
fn run_sg_tape_cmd(subcmd: &str, args: &[&str], fd: RawFd) -> Result<String, Error> {
|
||||
let mut command = std::process::Command::new(
|
||||
"/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd");
|
||||
|
@ -1,910 +0,0 @@
|
||||
use std::time::SystemTime;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::path::Path;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use endian_trait::Endian;
|
||||
use nix::fcntl::{fcntl, FcntlArg, OFlag};
|
||||
|
||||
mod encryption;
|
||||
pub use encryption::*;
|
||||
|
||||
mod volume_statistics;
|
||||
pub use volume_statistics::*;
|
||||
|
||||
mod tape_alert_flags;
|
||||
pub use tape_alert_flags::*;
|
||||
|
||||
mod mam;
|
||||
pub use mam::*;
|
||||
|
||||
mod report_density;
|
||||
pub use report_density::*;
|
||||
|
||||
use proxmox::{
|
||||
sys::error::SysResult,
|
||||
tools::io::{ReadExt, WriteExt},
|
||||
};
|
||||
|
||||
use pbs_api_types::{MamAttribute, Lp17VolumeStatistics};
|
||||
|
||||
use crate::{
|
||||
tape::{
|
||||
BlockRead,
|
||||
BlockReadError,
|
||||
BlockWrite,
|
||||
file_formats::{
|
||||
BlockedWriter,
|
||||
BlockedReader,
|
||||
},
|
||||
},
|
||||
tools::sgutils2::{
|
||||
SgRaw,
|
||||
SenseInfo,
|
||||
ScsiError,
|
||||
InquiryInfo,
|
||||
ModeParameterHeader,
|
||||
ModeBlockDescriptor,
|
||||
alloc_page_aligned_buffer,
|
||||
scsi_inquiry,
|
||||
scsi_mode_sense,
|
||||
scsi_request_sense,
|
||||
},
|
||||
};
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Endian, Debug, Copy, Clone)]
|
||||
pub struct ReadPositionLongPage {
|
||||
flags: u8,
|
||||
reserved: [u8;3],
|
||||
partition_number: u32,
|
||||
pub logical_object_number: u64,
|
||||
pub logical_file_id: u64,
|
||||
obsolete: [u8;8],
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Endian, Debug, Copy, Clone)]
|
||||
struct DataCompressionModePage {
|
||||
page_code: u8, // 0x0f
|
||||
page_length: u8, // 0x0e
|
||||
flags2: u8,
|
||||
flags3: u8,
|
||||
compression_algorithm: u32,
|
||||
decompression_algorithm: u32,
|
||||
reserved: [u8;4],
|
||||
}
|
||||
|
||||
impl DataCompressionModePage {
|
||||
|
||||
pub fn set_compression(&mut self, enable: bool) {
|
||||
if enable {
|
||||
self.flags2 |= 128;
|
||||
} else {
|
||||
self.flags2 = self.flags2 & 127;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compression_enabled(&self) -> bool {
|
||||
(self.flags2 & 0b1000_0000) != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Endian)]
|
||||
struct MediumConfigurationModePage {
|
||||
page_code: u8, // 0x1d
|
||||
page_length: u8, // 0x1e
|
||||
flags2: u8,
|
||||
reserved: [u8;29],
|
||||
}
|
||||
|
||||
impl MediumConfigurationModePage {
|
||||
|
||||
pub fn is_worm(&self) -> bool {
|
||||
(self.flags2 & 1) == 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LtoTapeStatus {
|
||||
pub block_length: u32,
|
||||
pub density_code: u8,
|
||||
pub buffer_mode: u8,
|
||||
pub write_protect: bool,
|
||||
pub compression: bool,
|
||||
}
|
||||
|
||||
pub struct SgTape {
|
||||
file: File,
|
||||
locate_offset: Option<i64>,
|
||||
info: InquiryInfo,
|
||||
encryption_key_loaded: bool,
|
||||
}
|
||||
|
||||
impl SgTape {
|
||||
|
||||
const SCSI_TAPE_DEFAULT_TIMEOUT: usize = 60*10; // 10 minutes
|
||||
|
||||
/// Create a new instance
|
||||
///
|
||||
/// Uses scsi_inquiry to check the device type.
|
||||
pub fn new(mut file: File) -> Result<Self, Error> {
|
||||
|
||||
let info = scsi_inquiry(&mut file)?;
|
||||
|
||||
if info.peripheral_type != 1 {
|
||||
bail!("not a tape device (peripheral_type = {})", info.peripheral_type);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
file,
|
||||
info,
|
||||
encryption_key_loaded: false,
|
||||
locate_offset: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Access to file descriptor - useful for testing
|
||||
pub fn file_mut(&mut self) -> &mut File {
|
||||
&mut self.file
|
||||
}
|
||||
|
||||
pub fn info(&self) -> &InquiryInfo {
|
||||
&self.info
|
||||
}
|
||||
|
||||
/// Return the maximum supported density code
|
||||
///
|
||||
/// This can be used to detect the drive generation.
|
||||
pub fn max_density_code(&mut self) -> Result<u8, Error> {
|
||||
report_density(&mut self.file)
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<SgTape, Error> {
|
||||
// do not wait for media, use O_NONBLOCK
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.custom_flags(libc::O_NONBLOCK)
|
||||
.open(path)?;
|
||||
|
||||
// then clear O_NONBLOCK
|
||||
let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL)
|
||||
.into_io_result()?;
|
||||
|
||||
let mut flags = OFlag::from_bits_truncate(flags);
|
||||
flags.remove(OFlag::O_NONBLOCK);
|
||||
|
||||
fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags))
|
||||
.into_io_result()?;
|
||||
|
||||
Self::new(file)
|
||||
}
|
||||
|
||||
pub fn inquiry(&mut self) -> Result<InquiryInfo, Error> {
|
||||
scsi_inquiry(&mut self.file)
|
||||
}
|
||||
|
||||
/// Erase medium.
|
||||
///
|
||||
/// EOD is written at the current position, which marks it as end
|
||||
/// of data. After the command is successfully completed, the
|
||||
/// drive is positioned immediately before End Of Data (not End Of
|
||||
/// Tape).
|
||||
pub fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0x19);
|
||||
if fast {
|
||||
cmd.push(0); // LONG=0
|
||||
} else {
|
||||
cmd.push(1); // LONG=1
|
||||
}
|
||||
cmd.extend(&[0, 0, 0, 0]);
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("erase failed - {}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Format media, single partition
|
||||
pub fn format_media(&mut self, fast: bool) -> Result<(), Error> {
|
||||
|
||||
// try to get info about loaded media first
|
||||
let (has_format, is_worm) = match self.read_medium_configuration_page() {
|
||||
Ok((_head, block_descriptor, page)) => {
|
||||
// FORMAT requires LTO5 or newer
|
||||
let has_format = block_descriptor.density_code >= 0x58;
|
||||
let is_worm = page.is_worm();
|
||||
(has_format, is_worm)
|
||||
}
|
||||
Err(_) => {
|
||||
// LTO3 and older do not supprt medium configuration mode page
|
||||
(false, false)
|
||||
}
|
||||
};
|
||||
|
||||
if is_worm {
|
||||
// We cannot FORMAT WORM media! Instead we check if its empty.
|
||||
|
||||
self.move_to_eom(false)?;
|
||||
let pos = self.position()?;
|
||||
if pos.logical_object_number != 0 {
|
||||
bail!("format failed - detected WORM media with data.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
} else {
|
||||
self.rewind()?;
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
|
||||
if has_format {
|
||||
cmd.extend(&[0x04, 0, 0, 0, 0, 0]); // FORMAT
|
||||
sg_raw.do_command(&cmd)?;
|
||||
if !fast {
|
||||
self.erase_media(false)?; // overwrite everything
|
||||
}
|
||||
} else {
|
||||
// try rewind/erase instead
|
||||
self.erase_media(fast)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Lock/Unlock drive door
|
||||
pub fn set_medium_removal(&mut self, allow: bool) -> Result<(), ScsiError> {
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
cmd.extend(&[0x1E, 0, 0, 0]);
|
||||
if allow {
|
||||
cmd.push(0);
|
||||
} else {
|
||||
cmd.push(1);
|
||||
}
|
||||
cmd.push(0); // control
|
||||
|
||||
sg_raw.do_command(&cmd)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rewind(&mut self) -> Result<(), Error> {
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
cmd.extend(&[0x01, 0, 0, 0, 0, 0]); // REWIND
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("rewind failed - {}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn locate_file(&mut self, position: u64) -> Result<(), Error> {
|
||||
if position == 0 {
|
||||
return self.rewind();
|
||||
}
|
||||
|
||||
const SPACE_ONE_FILEMARK: &[u8] = &[0x11, 0x01, 0, 0, 1, 0];
|
||||
|
||||
// Special case for position 1, because LOCATE 0 does not work
|
||||
if position == 1 {
|
||||
self.rewind()?;
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
sg_raw.do_command(SPACE_ONE_FILEMARK)
|
||||
.map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
|
||||
// Note: LOCATE(16) works for LTO4 or newer
|
||||
//
|
||||
// It seems the LOCATE command behaves slightly different across vendors
|
||||
// e.g. for IBM drives, LOCATE 1 moves to File #2, but
|
||||
// for HP drives, LOCATE 1 move to File #1
|
||||
|
||||
let fixed_position = if let Some(locate_offset) = self.locate_offset {
|
||||
if locate_offset < 0 {
|
||||
position.saturating_sub((-locate_offset) as u64)
|
||||
} else {
|
||||
position.saturating_add(locate_offset as u64)
|
||||
}
|
||||
} else {
|
||||
position
|
||||
};
|
||||
// always sub(1), so that it works for IBM drives without locate_offset
|
||||
let fixed_position = fixed_position.saturating_sub(1);
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.extend(&[0x92, 0b000_01_000, 0, 0]); // LOCATE(16) filemarks
|
||||
cmd.extend(&fixed_position.to_be_bytes());
|
||||
cmd.extend(&[0, 0, 0, 0]);
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("locate file {} failed - {}", position, err))?;
|
||||
|
||||
// LOCATE always position at the BOT side of the filemark, so
|
||||
// we need to move to other side of filemark
|
||||
sg_raw.do_command(SPACE_ONE_FILEMARK)
|
||||
.map_err(|err| format_err!("locate file {} (space) failed - {}", position, err))?;
|
||||
|
||||
if self.locate_offset.is_none() {
|
||||
// check if we landed at correct position
|
||||
let current_file = self.current_file_number()?;
|
||||
if current_file != position {
|
||||
let offset: i64 =
|
||||
i64::try_from((position as i128) - (current_file as i128)).map_err(|err| {
|
||||
format_err!(
|
||||
"locate_file: offset between {} and {} invalid: {}",
|
||||
position,
|
||||
current_file,
|
||||
err
|
||||
)
|
||||
})?;
|
||||
self.locate_offset = Some(offset);
|
||||
self.locate_file(position)?;
|
||||
let current_file = self.current_file_number()?;
|
||||
if current_file != position {
|
||||
bail!("locate_file: compensating offset did not work, aborting...");
|
||||
}
|
||||
} else {
|
||||
self.locate_offset = Some(0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn position(&mut self) -> Result<ReadPositionLongPage, Error> {
|
||||
|
||||
let expected_size = std::mem::size_of::<ReadPositionLongPage>();
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 32)?;
|
||||
sg_raw.set_timeout(30); // use short timeout
|
||||
let mut cmd = Vec::new();
|
||||
// READ POSITION LONG FORM works on LTO4 or newer (with recent
|
||||
// firmware), although it is missing in the IBM LTO4 SSCI
|
||||
// reference manual.
|
||||
cmd.extend(&[0x34, 0x06, 0, 0, 0, 0, 0, 0, 0, 0]); // READ POSITION LONG FORM
|
||||
|
||||
let data = sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("read position failed - {}", err))?;
|
||||
|
||||
let page = proxmox::try_block!({
|
||||
if data.len() != expected_size {
|
||||
bail!("got unexpected data len ({} != {}", data.len(), expected_size);
|
||||
}
|
||||
|
||||
let mut reader = &data[..];
|
||||
|
||||
let page: ReadPositionLongPage = unsafe { reader.read_be_value()? };
|
||||
|
||||
Ok(page)
|
||||
}).map_err(|err: Error| format_err!("decode position page failed - {}", err))?;
|
||||
|
||||
if page.partition_number != 0 {
|
||||
bail!("detecthed partitioned tape - not supported");
|
||||
}
|
||||
|
||||
Ok(page)
|
||||
}
|
||||
|
||||
pub fn current_file_number(&mut self) -> Result<u64, Error> {
|
||||
let position = self.position()?;
|
||||
Ok(position.logical_file_id)
|
||||
}
|
||||
|
||||
/// Check if we are positioned after a filemark (or BOT)
|
||||
pub fn check_filemark(&mut self) -> Result<bool, Error> {
|
||||
|
||||
let pos = self.position()?;
|
||||
if pos.logical_object_number == 0 {
|
||||
// at BOT, Ok (no filemark required)
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Note: SPACE blocks returns Err at filemark
|
||||
match self.space(-1, true) {
|
||||
Ok(_) => {
|
||||
self.space(1, true) // move back to end
|
||||
.map_err(|err| format_err!("check_filemark failed (space forward) - {}", err))?;
|
||||
Ok(false)
|
||||
}
|
||||
Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 })) => {
|
||||
// Filemark detected - good
|
||||
self.space(1, false) // move to EOT side of filemark
|
||||
.map_err(|err| format_err!("check_filemark failed (move to EOT side of filemark) - {}", err))?;
|
||||
Ok(true)
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("check_filemark failed - {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_to_eom(&mut self, write_missing_eof: bool) -> Result<(), Error> {
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
cmd.extend(&[0x11, 0x03, 0, 0, 0, 0]); // SPACE(6) move to EOD
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("move to EOD failed - {}", err))?;
|
||||
|
||||
if write_missing_eof {
|
||||
if !self.check_filemark()? {
|
||||
self.write_filemarks(1, false)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn space(&mut self, count: isize, blocks: bool) -> Result<(), ScsiError> {
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
|
||||
// Use short command if possible (supported by all drives)
|
||||
if (count <= 0x7fffff) && (count > -0x7fffff) {
|
||||
cmd.push(0x11); // SPACE(6)
|
||||
if blocks {
|
||||
cmd.push(0); // blocks
|
||||
} else {
|
||||
cmd.push(1); // filemarks
|
||||
}
|
||||
cmd.push(((count >> 16) & 0xff) as u8);
|
||||
cmd.push(((count >> 8) & 0xff) as u8);
|
||||
cmd.push((count & 0xff) as u8);
|
||||
cmd.push(0); //control byte
|
||||
} else {
|
||||
cmd.push(0x91); // SPACE(16)
|
||||
if blocks {
|
||||
cmd.push(0); // blocks
|
||||
} else {
|
||||
cmd.push(1); // filemarks
|
||||
}
|
||||
cmd.extend(&[0, 0]); // reserved
|
||||
let count: i64 = count as i64;
|
||||
cmd.extend(&count.to_be_bytes());
|
||||
cmd.extend(&[0, 0, 0, 0]); // reserved
|
||||
}
|
||||
|
||||
sg_raw.do_command(&cmd)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn space_filemarks(&mut self, count: isize) -> Result<(), Error> {
|
||||
self.space(count, false)
|
||||
.map_err(|err| format_err!("space filemarks failed - {}", err))
|
||||
}
|
||||
|
||||
pub fn space_blocks(&mut self, count: isize) -> Result<(), Error> {
|
||||
self.space(count, true)
|
||||
.map_err(|err| format_err!("space blocks failed - {}", err))
|
||||
}
|
||||
|
||||
pub fn eject(&mut self) -> Result<(), Error> {
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
cmd.extend(&[0x1B, 0, 0, 0, 0, 0]); // LODA/UNLOAD HOLD=0, LOAD=0
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("eject failed - {}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(&mut self) -> Result<(), Error> {
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
cmd.extend(&[0x1B, 0, 0, 0, 0b0000_0001, 0]); // LODA/UNLOAD HOLD=0, LOAD=1
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("load media failed - {}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_filemarks(
|
||||
&mut self,
|
||||
count: usize,
|
||||
immediate: bool,
|
||||
) -> Result<(), std::io::Error> {
|
||||
|
||||
if count > 255 {
|
||||
proxmox::io_bail!("write_filemarks failed: got strange count '{}'", count);
|
||||
}
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)
|
||||
.map_err(|err| proxmox::io_format_err!("write_filemarks failed (alloc) - {}", err))?;
|
||||
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0x10);
|
||||
if immediate {
|
||||
cmd.push(1); // IMMED=1
|
||||
} else {
|
||||
cmd.push(0); // IMMED=0
|
||||
}
|
||||
cmd.extend(&[0, 0, count as u8]); // COUNT
|
||||
cmd.push(0); // control byte
|
||||
|
||||
match sg_raw.do_command(&cmd) {
|
||||
Ok(_) => { /* OK */ }
|
||||
Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => {
|
||||
/* LEOM - ignore */
|
||||
}
|
||||
Err(err) => {
|
||||
proxmox::io_bail!("write filemark failed - {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Flush tape buffers (WEOF with count 0 => flush)
|
||||
pub fn sync(&mut self) -> Result<(), std::io::Error> {
|
||||
self.write_filemarks(0, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_unit_ready(&mut self) -> Result<(), Error> {
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 16)?;
|
||||
sg_raw.set_timeout(30); // use short timeout
|
||||
let mut cmd = Vec::new();
|
||||
cmd.extend(&[0x00, 0, 0, 0, 0, 0]); // TEST UNIT READY
|
||||
|
||||
match sg_raw.do_command(&cmd) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
bail!("test_unit_ready failed - {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wait_until_ready(&mut self) -> Result<(), Error> {
|
||||
|
||||
let start = SystemTime::now();
|
||||
let max_wait = std::time::Duration::new(Self::SCSI_TAPE_DEFAULT_TIMEOUT as u64, 0);
|
||||
|
||||
loop {
|
||||
match self.test_unit_ready() {
|
||||
Ok(()) => return Ok(()),
|
||||
_ => {
|
||||
std::thread::sleep(std::time::Duration::new(1, 0));
|
||||
if start.elapsed()? > max_wait {
|
||||
bail!("wait_until_ready failed - got timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read Tape Alert Flags
|
||||
pub fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
|
||||
read_tape_alert_flags(&mut self.file)
|
||||
}
|
||||
|
||||
/// Read Cartridge Memory (MAM Attributes)
|
||||
pub fn cartridge_memory(&mut self) -> Result<Vec<MamAttribute>, Error> {
|
||||
read_mam_attributes(&mut self.file)
|
||||
}
|
||||
|
||||
/// Read Volume Statistics
|
||||
pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> {
|
||||
return read_volume_statistics(&mut self.file);
|
||||
}
|
||||
|
||||
pub fn set_encryption(
|
||||
&mut self,
|
||||
key: Option<[u8; 32]>,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
self.encryption_key_loaded = key.is_some();
|
||||
|
||||
set_encryption(&mut self.file, key)
|
||||
}
|
||||
|
||||
// Note: use alloc_page_aligned_buffer to alloc data transfer buffer
|
||||
//
|
||||
// Returns true if the drive reached the Logical End Of Media (early warning)
|
||||
fn write_block(&mut self, data: &[u8]) -> Result<bool, std::io::Error> {
|
||||
|
||||
let transfer_len = data.len();
|
||||
|
||||
if transfer_len > 0x800000 {
|
||||
proxmox::io_bail!("write failed - data too large");
|
||||
}
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 0)
|
||||
.unwrap(); // cannot fail with size 0
|
||||
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0x0A); // WRITE
|
||||
cmd.push(0x00); // VARIABLE SIZED BLOCKS
|
||||
cmd.push(((transfer_len >> 16) & 0xff) as u8);
|
||||
cmd.push(((transfer_len >> 8) & 0xff) as u8);
|
||||
cmd.push((transfer_len & 0xff) as u8);
|
||||
cmd.push(0); // control byte
|
||||
|
||||
//println!("WRITE {:?}", cmd);
|
||||
//println!("WRITE {:?}", data);
|
||||
|
||||
match sg_raw.do_out_command(&cmd, data) {
|
||||
Ok(()) => { return Ok(false) }
|
||||
Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 2 })) => {
|
||||
return Ok(true); // LEOM
|
||||
}
|
||||
Err(err) => {
|
||||
proxmox::io_bail!("write failed - {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
|
||||
let transfer_len = buffer.len();
|
||||
|
||||
if transfer_len > 0xFFFFFF {
|
||||
return Err(BlockReadError::Error(
|
||||
proxmox::io_format_err!("read failed - buffer too large")
|
||||
));
|
||||
}
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 0)
|
||||
.unwrap(); // cannot fail with size 0
|
||||
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0x08); // READ
|
||||
cmd.push(0x02); // VARIABLE SIZED BLOCKS, SILI=1
|
||||
//cmd.push(0x00); // VARIABLE SIZED BLOCKS, SILI=0
|
||||
cmd.push(((transfer_len >> 16) & 0xff) as u8);
|
||||
cmd.push(((transfer_len >> 8) & 0xff) as u8);
|
||||
cmd.push((transfer_len & 0xff) as u8);
|
||||
cmd.push(0); // control byte
|
||||
|
||||
let data = match sg_raw.do_in_command(&cmd, buffer) {
|
||||
Ok(data) => data,
|
||||
Err(ScsiError::Sense(SenseInfo { sense_key: 0, asc: 0, ascq: 1 })) => {
|
||||
return Err(BlockReadError::EndOfFile);
|
||||
}
|
||||
Err(ScsiError::Sense(SenseInfo { sense_key: 8, asc: 0, ascq: 5 })) => {
|
||||
return Err(BlockReadError::EndOfStream);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(BlockReadError::Error(
|
||||
proxmox::io_format_err!("read failed - {}", err)
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if data.len() != transfer_len {
|
||||
return Err(BlockReadError::Error(
|
||||
proxmox::io_format_err!("read failed - unexpected block len ({} != {})", data.len(), buffer.len())
|
||||
));
|
||||
}
|
||||
|
||||
Ok(transfer_len)
|
||||
}
|
||||
|
||||
pub fn open_writer(&mut self) -> BlockedWriter<SgTapeWriter> {
|
||||
let writer = SgTapeWriter::new(self);
|
||||
BlockedWriter::new(writer)
|
||||
}
|
||||
|
||||
pub fn open_reader(&mut self) -> Result<BlockedReader<SgTapeReader>, BlockReadError> {
|
||||
let reader = SgTapeReader::new(self);
|
||||
BlockedReader::open(reader)
|
||||
}
|
||||
|
||||
/// Set important drive options
|
||||
pub fn set_drive_options(
|
||||
&mut self,
|
||||
compression: Option<bool>,
|
||||
block_length: Option<u32>,
|
||||
buffer_mode: Option<bool>,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
// Note: Read/Modify/Write
|
||||
|
||||
let (mut head, mut block_descriptor, mut page) = self.read_compression_page()?;
|
||||
|
||||
let mut sg_raw = SgRaw::new(&mut self.file, 0)?;
|
||||
sg_raw.set_timeout(Self::SCSI_TAPE_DEFAULT_TIMEOUT);
|
||||
|
||||
head.mode_data_len = 0; // need to b e zero
|
||||
|
||||
if let Some(compression) = compression {
|
||||
page.set_compression(compression);
|
||||
}
|
||||
|
||||
if let Some(block_length) = block_length {
|
||||
block_descriptor.set_block_length(block_length)?;
|
||||
}
|
||||
|
||||
if let Some(buffer_mode) = buffer_mode {
|
||||
head.set_buffer_mode(buffer_mode);
|
||||
}
|
||||
|
||||
let mut data = Vec::new();
|
||||
unsafe {
|
||||
data.write_be_value(head)?;
|
||||
data.write_be_value(block_descriptor)?;
|
||||
data.write_be_value(page)?;
|
||||
}
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0x55); // MODE SELECT(10)
|
||||
cmd.push(0b0001_0000); // PF=1
|
||||
cmd.extend(&[0,0,0,0,0]); //reserved
|
||||
|
||||
let param_list_len: u16 = data.len() as u16;
|
||||
cmd.extend(¶m_list_len.to_be_bytes());
|
||||
cmd.push(0); // control
|
||||
|
||||
let mut buffer = alloc_page_aligned_buffer(4096)?;
|
||||
|
||||
buffer[..data.len()].copy_from_slice(&data[..]);
|
||||
|
||||
sg_raw.do_out_command(&cmd, &buffer[..data.len()])
|
||||
.map_err(|err| format_err!("set drive options failed - {}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_medium_configuration_page(
|
||||
&mut self,
|
||||
) -> Result<(ModeParameterHeader, ModeBlockDescriptor, MediumConfigurationModePage), Error> {
|
||||
|
||||
let (head, block_descriptor, page): (_,_, MediumConfigurationModePage)
|
||||
= scsi_mode_sense(&mut self.file, false, 0x1d, 0)?;
|
||||
|
||||
proxmox::try_block!({
|
||||
if (page.page_code & 0b0011_1111) != 0x1d {
|
||||
bail!("wrong page code {}", page.page_code);
|
||||
}
|
||||
if page.page_length != 0x1e {
|
||||
bail!("wrong page length {}", page.page_length);
|
||||
}
|
||||
|
||||
let block_descriptor = match block_descriptor {
|
||||
Some(block_descriptor) => block_descriptor,
|
||||
None => bail!("missing block descriptor"),
|
||||
};
|
||||
|
||||
Ok((head, block_descriptor, page))
|
||||
}).map_err(|err| format_err!("read_medium_configuration failed - {}", err))
|
||||
}
|
||||
|
||||
fn read_compression_page(
|
||||
&mut self,
|
||||
) -> Result<(ModeParameterHeader, ModeBlockDescriptor, DataCompressionModePage), Error> {
|
||||
|
||||
let (head, block_descriptor, page): (_,_, DataCompressionModePage)
|
||||
= scsi_mode_sense(&mut self.file, false, 0x0f, 0)?;
|
||||
|
||||
proxmox::try_block!({
|
||||
if (page.page_code & 0b0011_1111) != 0x0f {
|
||||
bail!("wrong page code {}", page.page_code);
|
||||
}
|
||||
if page.page_length != 0x0e {
|
||||
bail!("wrong page length {}", page.page_length);
|
||||
}
|
||||
|
||||
let block_descriptor = match block_descriptor {
|
||||
Some(block_descriptor) => block_descriptor,
|
||||
None => bail!("missing block descriptor"),
|
||||
};
|
||||
|
||||
Ok((head, block_descriptor, page))
|
||||
}).map_err(|err| format_err!("read_compression_page failed: {}", err))
|
||||
}
|
||||
|
||||
/// Read drive options/status
|
||||
///
|
||||
/// We read the drive compression page, including the
|
||||
/// block_descriptor. This is all information we need for now.
|
||||
pub fn read_drive_status(&mut self) -> Result<LtoTapeStatus, Error> {
|
||||
|
||||
// We do a Request Sense, but ignore the result.
|
||||
// This clears deferred error or media changed events.
|
||||
let _ = scsi_request_sense(&mut self.file);
|
||||
|
||||
let (head, block_descriptor, page) = self.read_compression_page()?;
|
||||
|
||||
Ok(LtoTapeStatus {
|
||||
block_length: block_descriptor.block_length(),
|
||||
write_protect: head.write_protect(),
|
||||
buffer_mode: head.buffer_mode(),
|
||||
compression: page.compression_enabled(),
|
||||
density_code: block_descriptor.density_code,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SgTape {
|
||||
fn drop(&mut self) {
|
||||
// For security reasons, clear the encryption key
|
||||
if self.encryption_key_loaded {
|
||||
let _ = self.set_encryption(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct SgTapeReader<'a> {
|
||||
sg_tape: &'a mut SgTape,
|
||||
end_of_file: bool,
|
||||
}
|
||||
|
||||
impl <'a> SgTapeReader<'a> {
|
||||
|
||||
pub fn new(sg_tape: &'a mut SgTape) -> Self {
|
||||
Self { sg_tape, end_of_file: false, }
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a> BlockRead for SgTapeReader<'a> {
|
||||
|
||||
fn read_block(&mut self, buffer: &mut [u8]) -> Result<usize, BlockReadError> {
|
||||
if self.end_of_file {
|
||||
return Err(BlockReadError::Error(proxmox::io_format_err!("detected read after EOF!")));
|
||||
}
|
||||
match self.sg_tape.read_block(buffer) {
|
||||
Ok(usize) => Ok(usize),
|
||||
Err(BlockReadError::EndOfFile) => {
|
||||
self.end_of_file = true;
|
||||
Err(BlockReadError::EndOfFile)
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SgTapeWriter<'a> {
|
||||
sg_tape: &'a mut SgTape,
|
||||
_leom_sent: bool,
|
||||
}
|
||||
|
||||
impl <'a> SgTapeWriter<'a> {
|
||||
|
||||
pub fn new(sg_tape: &'a mut SgTape) -> Self {
|
||||
Self { sg_tape, _leom_sent: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a> BlockWrite for SgTapeWriter<'a> {
|
||||
|
||||
fn write_block(&mut self, buffer: &[u8]) -> Result<bool, std::io::Error> {
|
||||
self.sg_tape.write_block(buffer)
|
||||
}
|
||||
|
||||
fn write_filemark(&mut self) -> Result<(), std::io::Error> {
|
||||
self.sg_tape.write_filemarks(1, true)
|
||||
}
|
||||
}
|
@ -1,296 +0,0 @@
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use endian_trait::Endian;
|
||||
|
||||
use proxmox::tools::io::{ReadExt, WriteExt};
|
||||
|
||||
use crate::tools::sgutils2::{
|
||||
SgRaw,
|
||||
alloc_page_aligned_buffer,
|
||||
};
|
||||
|
||||
/// Test if drive supports hardware encryption
|
||||
///
|
||||
/// We search for AES_CGM algorithm with 256bits key.
|
||||
pub fn has_encryption<F: AsRawFd>(
|
||||
file: &mut F,
|
||||
) -> bool {
|
||||
|
||||
let data = match sg_spin_data_encryption_caps(file) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return false,
|
||||
};
|
||||
decode_spin_data_encryption_caps(&data).is_ok()
|
||||
}
|
||||
|
||||
/// Set or clear encryption key
|
||||
///
|
||||
/// We always use mixed mode,
|
||||
pub fn set_encryption<F: AsRawFd>(
|
||||
file: &mut F,
|
||||
key: Option<[u8; 32]>,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let data = match sg_spin_data_encryption_caps(file) {
|
||||
Ok(data) => data,
|
||||
Err(_) if key.is_none() => {
|
||||
// Assume device does not support HW encryption
|
||||
// We can simply ignore the clear key request
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
let algorithm_index = decode_spin_data_encryption_caps(&data)?;
|
||||
|
||||
sg_spout_set_encryption(file, algorithm_index, key)?;
|
||||
|
||||
let data = sg_spin_data_encryption_status(file)?;
|
||||
let status = decode_spin_data_encryption_status(&data)?;
|
||||
|
||||
match status.mode {
|
||||
DataEncryptionMode::Off => {
|
||||
if key.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
DataEncryptionMode::Mixed => {
|
||||
if key.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
bail!("got unexpected encryption mode {:?}", status.mode);
|
||||
}
|
||||
|
||||
#[derive(Endian)]
|
||||
#[repr(C, packed)]
|
||||
struct SspSetDataEncryptionPage {
|
||||
page_code: u16,
|
||||
page_len: u16,
|
||||
scope_byte: u8,
|
||||
control_byte_5: u8,
|
||||
encryption_mode: u8,
|
||||
decryption_mode: u8,
|
||||
algorythm_index: u8,
|
||||
key_format: u8,
|
||||
reserved: [u8; 8],
|
||||
key_len: u16,
|
||||
/* key follows */
|
||||
}
|
||||
|
||||
fn sg_spout_set_encryption<F: AsRawFd>(
|
||||
file: &mut F,
|
||||
algorythm_index: u8,
|
||||
key: Option<[u8; 32]>,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let mut sg_raw = SgRaw::new(file, 0)?;
|
||||
|
||||
let mut outbuf_len = std::mem::size_of::<SspSetDataEncryptionPage>();
|
||||
if let Some(ref key) = key {
|
||||
outbuf_len += key.len();
|
||||
}
|
||||
|
||||
let mut outbuf = alloc_page_aligned_buffer(outbuf_len)?;
|
||||
let chok: u8 = 0;
|
||||
|
||||
let page = SspSetDataEncryptionPage {
|
||||
page_code: 0x10,
|
||||
page_len: (outbuf_len - 4) as u16,
|
||||
scope_byte: (0b10 << 5), // all IT nexus
|
||||
control_byte_5: (chok << 2),
|
||||
encryption_mode: if key.is_some() { 2 } else { 0 },
|
||||
decryption_mode: if key.is_some() { 3 } else { 0 }, // mixed mode
|
||||
algorythm_index,
|
||||
key_format: 0,
|
||||
reserved: [0u8; 8],
|
||||
key_len: if let Some(ref key) = key { key.len() as u16 } else { 0 },
|
||||
};
|
||||
|
||||
let mut writer = &mut outbuf[..];
|
||||
unsafe { writer.write_be_value(page)? };
|
||||
|
||||
if let Some(ref key) = key {
|
||||
writer.write_all(key)?;
|
||||
}
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0xB5); // SECURITY PROTOCOL IN (SPOUT)
|
||||
cmd.push(0x20); // Tape Data Encryption Page
|
||||
cmd.push(0);cmd.push(0x10); // Set Data Encryption page
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.extend(&(outbuf_len as u32).to_be_bytes()); // data out len
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
|
||||
sg_raw.do_out_command(&cmd, &outbuf)
|
||||
.map_err(|err| format_err!("set data encryption SPOUT(20h[0010h]) failed - {}", err))
|
||||
}
|
||||
|
||||
// Warning: this blocks and fails if there is no media loaded
|
||||
fn sg_spin_data_encryption_status<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
|
||||
|
||||
let allocation_len: u32 = 8192+4;
|
||||
|
||||
let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
|
||||
cmd.push(0x20); // Tape Data Encryption Page
|
||||
cmd.push(0);cmd.push(0x20); // Data Encryption Status page
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.extend(&allocation_len.to_be_bytes());
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("read data encryption status SPIN(20h[0020h]) failed - {}", err))
|
||||
.map(|v| v.to_vec())
|
||||
}
|
||||
|
||||
// Warning: this blocks and fails if there is no media loaded
|
||||
fn sg_spin_data_encryption_caps<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
|
||||
|
||||
let allocation_len: u32 = 8192+4;
|
||||
|
||||
let mut sg_raw = SgRaw::new(file, allocation_len as usize)?;
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0xA2); // SECURITY PROTOCOL IN (SPIN)
|
||||
cmd.push(0x20); // Tape Data Encryption Page
|
||||
cmd.push(0);cmd.push(0x10); // Data Encryption Capabilities page
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.extend(&allocation_len.to_be_bytes());
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("read data encryption caps SPIN(20h[0010h]) failed - {}", err))
|
||||
.map(|v| v.to_vec())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum DataEncryptionMode {
|
||||
On,
|
||||
Mixed,
|
||||
RawRead,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DataEncryptionStatus {
|
||||
mode: DataEncryptionMode,
|
||||
}
|
||||
|
||||
#[derive(Endian)]
|
||||
#[repr(C, packed)]
|
||||
struct SspDataEncryptionCapabilityPage {
|
||||
page_code: u16,
|
||||
page_len: u16,
|
||||
reserved: [u8; 16],
|
||||
}
|
||||
|
||||
#[derive(Endian)]
|
||||
#[repr(C, packed)]
|
||||
struct SspDataEncryptionAlgorithmDescriptor {
|
||||
algorythm_index: u8,
|
||||
reserved1: u8,
|
||||
descriptor_len: u16,
|
||||
control_byte_4: u8,
|
||||
control_byte_5: u8,
|
||||
max_ucad_bytes: u16,
|
||||
max_acad_bytes: u16,
|
||||
key_size: u16,
|
||||
control_byte_12: u8,
|
||||
reserved2: u8,
|
||||
msdk_count: u16,
|
||||
reserved3: [u8; 4],
|
||||
algorithm_code: u32,
|
||||
}
|
||||
|
||||
// Returns the algorythm_index for AES-CGM
|
||||
fn decode_spin_data_encryption_caps(data: &[u8]) -> Result<u8, Error> {
|
||||
|
||||
proxmox::try_block!({
|
||||
let mut reader = &data[..];
|
||||
let _page: SspDataEncryptionCapabilityPage = unsafe { reader.read_be_value()? };
|
||||
|
||||
let mut aes_cgm_index = None;
|
||||
|
||||
loop {
|
||||
if reader.is_empty() { break; };
|
||||
let desc: SspDataEncryptionAlgorithmDescriptor =
|
||||
unsafe { reader.read_be_value()? };
|
||||
if desc.descriptor_len != 0x14 {
|
||||
bail!("got wrong key descriptor len");
|
||||
}
|
||||
if (desc.control_byte_4 & 0b00000011) != 2 {
|
||||
continue; // can't encrypt in hardware
|
||||
}
|
||||
if ((desc.control_byte_4 & 0b00001100) >> 2) != 2 {
|
||||
continue; // can't decrypt in hardware
|
||||
}
|
||||
if desc.algorithm_code == 0x00010014 && desc.key_size == 32 {
|
||||
aes_cgm_index = Some(desc.algorythm_index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match aes_cgm_index {
|
||||
Some(index) => Ok(index),
|
||||
None => bail!("drive does not support AES-CGM encryption"),
|
||||
}
|
||||
}).map_err(|err: Error| format_err!("decode data encryption caps page failed - {}", err))
|
||||
|
||||
}
|
||||
|
||||
#[derive(Endian)]
|
||||
#[repr(C, packed)]
|
||||
struct SspDataEncryptionStatusPage {
|
||||
page_code: u16,
|
||||
page_len: u16,
|
||||
scope_byte: u8,
|
||||
encryption_mode: u8,
|
||||
decryption_mode: u8,
|
||||
algorythm_index: u8,
|
||||
key_instance_counter: u32,
|
||||
control_byte: u8,
|
||||
key_format: u8,
|
||||
key_len: u16,
|
||||
reserved: [u8; 8],
|
||||
}
|
||||
|
||||
fn decode_spin_data_encryption_status(data: &[u8]) -> Result<DataEncryptionStatus, Error> {
|
||||
|
||||
proxmox::try_block!({
|
||||
let mut reader = &data[..];
|
||||
let page: SspDataEncryptionStatusPage = unsafe { reader.read_be_value()? };
|
||||
|
||||
if page.page_code != 0x20 {
|
||||
bail!("invalid response");
|
||||
}
|
||||
|
||||
let mode = match (page.encryption_mode, page.decryption_mode) {
|
||||
(0, 0) => DataEncryptionMode::Off,
|
||||
(2, 1) => DataEncryptionMode::RawRead,
|
||||
(2, 2) => DataEncryptionMode::On,
|
||||
(2, 3) => DataEncryptionMode::Mixed,
|
||||
_ => bail!("unknown encryption mode"),
|
||||
};
|
||||
|
||||
let status = DataEncryptionStatus {
|
||||
mode,
|
||||
};
|
||||
|
||||
Ok(status)
|
||||
|
||||
}).map_err(|err| format_err!("decode data encryption status page failed - {}", err))
|
||||
}
|
@ -1,237 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use endian_trait::Endian;
|
||||
|
||||
use proxmox::tools::io::ReadExt;
|
||||
|
||||
use pbs_api_types::MamAttribute;
|
||||
|
||||
use crate::{
|
||||
tools::sgutils2::SgRaw,
|
||||
tape::drive::lto::TapeAlertFlags,
|
||||
};
|
||||
|
||||
// Read Medium auxiliary memory attributes (MAM)
|
||||
// see IBM SCSI reference: https://www-01.ibm.com/support/docview.wss?uid=ssg1S7003556&aid=1
|
||||
|
||||
#[derive(Endian)]
|
||||
#[repr(C,packed)]
|
||||
struct MamAttributeHeader {
|
||||
id: u16,
|
||||
flags: u8,
|
||||
len: u16,
|
||||
}
|
||||
|
||||
enum MamFormat {
|
||||
BINARY,
|
||||
ASCII,
|
||||
DEC,
|
||||
}
|
||||
|
||||
static MAM_ATTRIBUTES: &[ (u16, u16, MamFormat, &str) ] = &[
|
||||
(0x00_00, 8, MamFormat::DEC, "Remaining Capacity In Partition"),
|
||||
(0x00_01, 8, MamFormat::DEC, "Maximum Capacity In Partition"),
|
||||
(0x00_02, 8, MamFormat::DEC, "Tapealert Flags"),
|
||||
(0x00_03, 8, MamFormat::DEC, "Load Count"),
|
||||
(0x00_04, 8, MamFormat::DEC, "MAM Space Remaining"),
|
||||
(0x00_05, 8, MamFormat::ASCII, "Assigning Organization"),
|
||||
(0x00_06, 1, MamFormat::BINARY, "Formatted Density Code"),
|
||||
(0x00_07, 2, MamFormat::DEC, "Initialization Count"),
|
||||
(0x00_09, 4, MamFormat::BINARY, "Volume Change Reference"),
|
||||
|
||||
(0x02_0A, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Last Load"),
|
||||
(0x02_0B, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-1"),
|
||||
(0x02_0C, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-2"),
|
||||
(0x02_0D, 40, MamFormat::ASCII, "Device Vendor/Serial Number at Load-3"),
|
||||
|
||||
(0x02_20, 8, MamFormat::DEC, "Total MBytes Written in Medium Life"),
|
||||
(0x02_21, 8, MamFormat::DEC, "Total MBytes Read In Medium Life"),
|
||||
(0x02_22, 8, MamFormat::DEC, "Total MBytes Written in Current Load"),
|
||||
(0x02_23, 8, MamFormat::DEC, "Total MBytes Read in Current/Last Load"),
|
||||
(0x02_24, 8, MamFormat::BINARY, "Logical Position of First Encrypted Block"),
|
||||
(0x02_25, 8, MamFormat::BINARY, "Logical Position of First Unencrypted Block After the First Encrypted Block"),
|
||||
|
||||
(0x04_00, 8, MamFormat::ASCII, "Medium Manufacturer"),
|
||||
(0x04_01, 32, MamFormat::ASCII, "Medium Serial Number"),
|
||||
(0x04_02, 4, MamFormat::DEC, "Medium Length"),
|
||||
(0x04_03, 4, MamFormat::DEC, "Medium Width"),
|
||||
(0x04_04, 8, MamFormat::ASCII, "Assigning Organization"),
|
||||
(0x04_05, 1, MamFormat::BINARY, "Medium Density Code"),
|
||||
(0x04_06, 8, MamFormat::ASCII, "Medium Manufacture Date"),
|
||||
(0x04_07, 8, MamFormat::DEC, "MAM Capacity"),
|
||||
(0x04_08, 1, MamFormat::BINARY, "Medium Type"),
|
||||
(0x04_09, 2, MamFormat::BINARY, "Medium Type Information"),
|
||||
(0x04_0B, 10, MamFormat::BINARY, "Supported Density Codes"),
|
||||
|
||||
(0x08_00, 8, MamFormat::ASCII, "Application Vendor"),
|
||||
(0x08_01, 32, MamFormat::ASCII, "Application Name"),
|
||||
(0x08_02, 8, MamFormat::ASCII, "Application Version"),
|
||||
(0x08_03, 160, MamFormat::ASCII, "User Medium Text Label"),
|
||||
(0x08_04, 12, MamFormat::ASCII, "Date And Time Last Written"),
|
||||
(0x08_05, 1, MamFormat::BINARY, "Text Localization Identifier"),
|
||||
(0x08_06, 32, MamFormat::ASCII, "Barcode"),
|
||||
(0x08_07, 80, MamFormat::ASCII, "Owning Host Textual Name"),
|
||||
(0x08_08, 160, MamFormat::ASCII, "Media Pool"),
|
||||
(0x08_0B, 16, MamFormat::ASCII, "Application Format Version"),
|
||||
(0x08_0C, 50, MamFormat::ASCII, "Volume Coherency Information"),
|
||||
(0x08_20, 36, MamFormat::ASCII, "Medium Globally Unique Identifier"),
|
||||
(0x08_21, 36, MamFormat::ASCII, "Media Pool Globally Unique Identifier"),
|
||||
|
||||
(0x10_00, 28, MamFormat::BINARY, "Unique Cartridge Identify (UCI)"),
|
||||
(0x10_01, 24, MamFormat::BINARY, "Alternate Unique Cartridge Identify (Alt-UCI)"),
|
||||
|
||||
];
|
||||
|
||||
lazy_static::lazy_static!{
|
||||
|
||||
static ref MAM_ATTRIBUTE_NAMES: HashMap<u16, &'static (u16, u16, MamFormat, &'static str)> = {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for entry in MAM_ATTRIBUTES {
|
||||
map.insert(entry.0, entry);
|
||||
}
|
||||
|
||||
map
|
||||
};
|
||||
}
|
||||
|
||||
fn read_tape_mam<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
|
||||
|
||||
let alloc_len: u32 = 32*1024;
|
||||
let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.extend(&[0x8c, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]);
|
||||
cmd.extend(&[0u8, 0u8]); // first attribute
|
||||
cmd.extend(&alloc_len.to_be_bytes()); // alloc len
|
||||
cmd.extend(&[0u8, 0u8]);
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("read cartidge memory failed - {}", err))
|
||||
.map(|v| v.to_vec())
|
||||
}
|
||||
|
||||
/// Read Medium auxiliary memory attributes (cartridge memory) using raw SCSI command.
|
||||
pub fn read_mam_attributes<F: AsRawFd>(file: &mut F) -> Result<Vec<MamAttribute>, Error> {
|
||||
|
||||
let data = read_tape_mam(file)?;
|
||||
|
||||
decode_mam_attributes(&data)
|
||||
}
|
||||
|
||||
fn decode_mam_attributes(data: &[u8]) -> Result<Vec<MamAttribute>, Error> {
|
||||
|
||||
let mut reader = &data[..];
|
||||
|
||||
let data_len: u32 = unsafe { reader.read_be_value()? };
|
||||
|
||||
let expected_len = data_len as usize;
|
||||
|
||||
|
||||
if reader.len() < expected_len {
|
||||
bail!("read_mam_attributes: got unexpected data len ({} != {})", reader.len(), expected_len);
|
||||
} else if reader.len() > expected_len {
|
||||
// Note: Quantum hh7 returns the allocation_length instead of real data_len
|
||||
reader = &data[4..expected_len+4];
|
||||
}
|
||||
|
||||
let mut list = Vec::new();
|
||||
|
||||
loop {
|
||||
if reader.is_empty() {
|
||||
break;
|
||||
}
|
||||
let head: MamAttributeHeader = unsafe { reader.read_be_value()? };
|
||||
//println!("GOT ID {:04X} {:08b} {}", head.id, head.flags, head.len);
|
||||
|
||||
let head_id = head.id;
|
||||
|
||||
let data = if head.len > 0 {
|
||||
reader.read_exact_allocated(head.len as usize)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
if let Some(info) = MAM_ATTRIBUTE_NAMES.get(&head_id) {
|
||||
if info.1 == head.len {
|
||||
let value = match info.2 {
|
||||
MamFormat::ASCII => String::from_utf8_lossy(&data).to_string(),
|
||||
MamFormat::DEC => {
|
||||
if info.1 == 2 {
|
||||
format!("{}", u16::from_be_bytes(data[0..2].try_into()?))
|
||||
} else if info.1 == 4 {
|
||||
format!("{}", u32::from_be_bytes(data[0..4].try_into()?))
|
||||
} else if info.1 == 8 {
|
||||
if head_id == 2 { // Tape Alert Flags
|
||||
let value = u64::from_be_bytes(data[0..8].try_into()?);
|
||||
let flags = TapeAlertFlags::from_bits_truncate(value);
|
||||
format!("{:?}", flags)
|
||||
} else {
|
||||
format!("{}", u64::from_be_bytes(data[0..8].try_into()?))
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
},
|
||||
MamFormat::BINARY => proxmox::tools::digest_to_hex(&data),
|
||||
};
|
||||
list.push(MamAttribute {
|
||||
id: head_id,
|
||||
name: info.3.to_string(),
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
eprintln!("read_mam_attributes: got starnge data len for id {:04X}", head_id);
|
||||
}
|
||||
} else {
|
||||
// skip unknown IDs
|
||||
}
|
||||
}
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
/// Media Usage Information from Cartridge Memory
|
||||
pub struct MediaUsageInfo {
|
||||
pub manufactured: i64,
|
||||
pub bytes_read: u64,
|
||||
pub bytes_written: u64,
|
||||
}
|
||||
|
||||
/// Extract Media Usage Information from Cartridge Memory
|
||||
pub fn mam_extract_media_usage(mam: &[MamAttribute]) -> Result<MediaUsageInfo, Error> {
|
||||
|
||||
let manufactured: i64 = match mam.iter().find(|v| v.id == 0x04_06).map(|v| v.value.clone()) {
|
||||
Some(date_str) => {
|
||||
if date_str.len() != 8 {
|
||||
bail!("unable to parse 'Medium Manufacture Date' - wrong length");
|
||||
}
|
||||
let year: i32 = date_str[..4].parse()?;
|
||||
let mon: i32 = date_str[4..6].parse()?;
|
||||
let mday: i32 = date_str[6..8].parse()?;
|
||||
|
||||
use proxmox::tools::time::TmEditor;
|
||||
let mut t = TmEditor::new(true);
|
||||
t.set_year(year)?;
|
||||
t.set_mon(mon)?;
|
||||
t.set_mday(mday)?;
|
||||
|
||||
t.into_epoch()?
|
||||
}
|
||||
None => bail!("unable to read MAM 'Medium Manufacture Date'"),
|
||||
};
|
||||
|
||||
let bytes_written: u64 = match mam.iter().find(|v| v.id == 0x02_20).map(|v| v.value.clone()) {
|
||||
Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
|
||||
None => bail!("unable to read MAM 'Total MBytes Written In Medium Life'"),
|
||||
};
|
||||
|
||||
let bytes_read: u64 = match mam.iter().find(|v| v.id == 0x02_21).map(|v| v.value.clone()) {
|
||||
Some(read_str) => read_str.parse::<u64>()? * 1024*1024,
|
||||
None => bail!("unable to read MAM 'Total MBytes Read In Medium Life'"),
|
||||
};
|
||||
|
||||
Ok(MediaUsageInfo { manufactured, bytes_written, bytes_read })
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use std::io::Read;
|
||||
use endian_trait::Endian;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use proxmox::tools::io::ReadExt;
|
||||
|
||||
use crate::tools::sgutils2::SgRaw;
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Endian)]
|
||||
struct DesnityDescriptorBlock {
|
||||
primary_density_code: u8,
|
||||
secondary_density_code: u8,
|
||||
flags2: u8,
|
||||
reserved: [u8; 2],
|
||||
bits_per_mm: [u8; 3],
|
||||
media_width: u16,
|
||||
tracks: u16,
|
||||
capacity: u32,
|
||||
organizazion: [u8; 8],
|
||||
density_name: [u8; 8],
|
||||
description: [u8; 20],
|
||||
}
|
||||
|
||||
// Returns the maximum supported drive density code
|
||||
pub fn report_density<F: AsRawFd>(file: &mut F) -> Result<u8, Error> {
|
||||
let alloc_len: u16 = 8192;
|
||||
let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.extend(&[0x44, 0, 0, 0, 0, 0, 0]); // REPORT DENSITY SUPPORT (MEDIA=0)
|
||||
cmd.extend(&alloc_len.to_be_bytes()); // alloc len
|
||||
cmd.push(0u8); // control byte
|
||||
|
||||
let data = sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("report density failed - {}", err))?;
|
||||
|
||||
let mut max_density = 0u8;
|
||||
|
||||
proxmox::try_block!({
|
||||
let mut reader = &data[..];
|
||||
|
||||
let page_len: u16 = unsafe { reader.read_be_value()? };
|
||||
let page_len = page_len as usize;
|
||||
|
||||
if (page_len + 2) > data.len() {
|
||||
bail!("invalid page length {} {}", page_len + 2, data.len());
|
||||
} else {
|
||||
// Note: Quantum hh7 returns the allocation_length instead of real data_len
|
||||
reader = &data[2..page_len+2];
|
||||
}
|
||||
let mut reserved = [0u8; 2];
|
||||
reader.read_exact(&mut reserved)?;
|
||||
|
||||
loop {
|
||||
if reader.is_empty() { break; }
|
||||
let block: DesnityDescriptorBlock = unsafe { reader.read_be_value()? };
|
||||
if block.primary_density_code > max_density {
|
||||
max_density = block.primary_density_code;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
}).map_err(|err| format_err!("decode report density failed - {}", err))?;
|
||||
|
||||
Ok(max_density)
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
use std::io::Read;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
|
||||
use proxmox::tools::io::ReadExt;
|
||||
|
||||
use crate::tools::sgutils2::SgRaw;
|
||||
|
||||
bitflags::bitflags!{
|
||||
|
||||
/// Tape Alert Flags
|
||||
///
|
||||
/// See LTO SCSI Reference LOG_SENSE - LP 2Eh: TapeAlerts
|
||||
pub struct TapeAlertFlags: u64 {
|
||||
#[allow(clippy::eq_op)]
|
||||
const READ_WARNING = 1 << (0x0001 -1);
|
||||
const WRITE_WARNING = 1 << (0x0002 -1);
|
||||
const HARD_ERROR = 1 << (0x0003 -1);
|
||||
const MEDIA = 1 << (0x0004 -1);
|
||||
const READ_FAILURE = 1 << (0x0005 -1);
|
||||
const WRITE_FAILURE = 1 << (0x0006 -1);
|
||||
const MEDIA_LIFE = 1 << (0x0007 -1);
|
||||
const NOT_DATA_GRADE = 1 << (0x0008 -1);
|
||||
const WRITE_PROTECT = 1 << (0x0009 -1);
|
||||
const NO_REMOVAL = 1 << (0x000A -1);
|
||||
const CLEANING_MEDIA = 1 << (0x000B -1);
|
||||
const UNSUPPORTED_FORMAT = 1 << (0x000C -1);
|
||||
const RECOVERABLE_MECHANICAL_CARTRIDGE_FAILURE = 1 << (0x000D -1); // LTO5
|
||||
const UNRECOVERABLE_SNAPPED_TAPE = 1 << (0x000E -1);
|
||||
const MEMORY_CHIP_IN_CARTRIDGE_FAILURE = 1 << (0x000F -1);
|
||||
const FORCED_EJECT = 1 << (0x0010 -1);
|
||||
const READ_ONLY_FORMAT = 1 << (0x0011 -1);
|
||||
const TAPE_DIRECTORY_CORRUPTED = 1 << (0x0012 -1);
|
||||
const NEARING_MEDIA_LIFE = 1 << (0x0013 -1);
|
||||
const CLEAN_NOW = 1 << (0x0014 -1);
|
||||
const CLEAN_PERIODIC = 1 << (0x0015 -1);
|
||||
const EXPIRED_CLEANING_MEDIA = 1 << (0x0016 -1);
|
||||
const INVALID_CLEANING_TAPE = 1 << (0x0017 -1);
|
||||
const RETENSION_REQUEST = 1 << (0x0018 -1); // LTO5
|
||||
const HOST_CHANNEL_FAILURE = 1 << (0x0019 -1);
|
||||
const COOLING_FAN_FAILURE = 1 << (0x001A -1);
|
||||
const POWER_SUPPLY_FAILURE = 1 << (0x001B -1);
|
||||
const POWER_CONSUMPTION = 1 << (0x001C -1); // LTO5
|
||||
const DRIVE_MANTAINANCE = 1 << (0x001D -1); // LTO5
|
||||
const HARDWARE_A = 1 << (0x001E -1);
|
||||
const HARDWARE_B = 1 << (0x001F -1);
|
||||
const INTERFACE = 1 << (0x0020 -1);
|
||||
const EJECT_MEDIA = 1 << (0x0021 -1);
|
||||
const DOWNLOAD_FAULT = 1 << (0x0022 -1);
|
||||
const DRIVE_HUMIDITY = 1 << (0x0023 -1); // LTO5
|
||||
const DRIVE_TEMPERATURE = 1 << (0x0024 -1);
|
||||
const DRIVE_VOLTAGE = 1 << (0x0025 -1);
|
||||
const PREDICTIVE_FAILURE = 1 << (0x0026 -1);
|
||||
const DIAGNOSTICS_REQUIRED = 1 << (0x0027 -1);
|
||||
const LOADER_STRAY_TAPE = 1 << (0x0029 -1);
|
||||
const LOADER_HARDWARE = 1 << (0x002A -1);
|
||||
const LOADER_MAGAZINE = 1 << (0x002D -1);
|
||||
const DIMINISHED_NATIVE_CAPACITY = 1 << (0x0031 -1);
|
||||
const LOST_STATISTICS = 1 << (0x0032 -1);
|
||||
const TAPE_DIRECTORY_INVALID_AT_UNLOAD = 1 << (0x0033 -1);
|
||||
const TAPE_SYSTEM_AREA_WRITE_FAILURE = 1 << (0x0034 -1);
|
||||
const TAPE_SYSTEM_AREA_READ_FAILURE = 1 << (0x0035 -1);
|
||||
const NO_START_OF_DATA = 1 << (0x0036 -1);
|
||||
const LOADING_FAILURE = 1 << (0x0037 -1);
|
||||
const UNRECOVERABLE_UNLOAD_FAILURE = 1 << (0x0038 -1);
|
||||
const AUTOMATION_INTERFACE_FAILURE = 1 << (0x0039 -1);
|
||||
const FIRMWARE_FAILURE = 1 << (0x003A -1);
|
||||
const WORM_INTEGRITY_CHECK_FAILED = 1 << (0x003B -1);
|
||||
const WORM_OVERWRITE_ATTEMPTED = 1 << (0x003C -1);
|
||||
const ENCRYPTION_POLICY_VIOLATION = 1 << (0x003D -1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Read Tape Alert Flags using raw SCSI command.
|
||||
pub fn read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<TapeAlertFlags, Error> {
|
||||
|
||||
let data = sg_read_tape_alert_flags(file)?;
|
||||
|
||||
decode_tape_alert_flags(&data)
|
||||
}
|
||||
|
||||
|
||||
fn sg_read_tape_alert_flags<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
|
||||
|
||||
let mut sg_raw = SgRaw::new(file, 512)?;
|
||||
|
||||
// Note: We cannjot use LP 2Eh TapeAlerts, because that clears flags on read.
|
||||
// Instead, we use LP 12h TapeAlert Response. which does not clear the flags.
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0x4D); // LOG SENSE
|
||||
cmd.push(0);
|
||||
cmd.push((1<<6) | 0x12); // Tape Alert Response log page
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.extend(&[2u8, 0u8]); // alloc len
|
||||
cmd.push(0u8); // control byte
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("read tape alert flags failed - {}", err))
|
||||
.map(|v| v.to_vec())
|
||||
}
|
||||
|
||||
fn decode_tape_alert_flags(data: &[u8]) -> Result<TapeAlertFlags, Error> {
|
||||
|
||||
proxmox::try_block!({
|
||||
if !((data[0] & 0x7f) == 0x12 && data[1] == 0) {
|
||||
bail!("invalid response");
|
||||
}
|
||||
|
||||
let mut reader = &data[2..];
|
||||
|
||||
let page_len: u16 = unsafe { reader.read_be_value()? };
|
||||
if page_len != 0x0c {
|
||||
bail!("invalid page length");
|
||||
}
|
||||
|
||||
let parameter_code: u16 = unsafe { reader.read_be_value()? };
|
||||
if parameter_code != 0 {
|
||||
bail!("invalid parameter code");
|
||||
}
|
||||
|
||||
let mut control_buf = [0u8; 2];
|
||||
reader.read_exact(&mut control_buf)?;
|
||||
|
||||
if control_buf[1] != 8 {
|
||||
bail!("invalid parameter length");
|
||||
}
|
||||
|
||||
let mut value: u64 = unsafe { reader.read_be_value()? };
|
||||
|
||||
// bits are in wrong order, reverse them
|
||||
value = value.reverse_bits();
|
||||
|
||||
Ok(TapeAlertFlags::from_bits_truncate(value))
|
||||
}).map_err(|err| format_err!("decode tape alert flags failed - {}", err))
|
||||
}
|
||||
|
||||
const CRITICAL_FLAG_MASK: u64 =
|
||||
TapeAlertFlags::MEDIA.bits() |
|
||||
TapeAlertFlags::WRITE_FAILURE.bits() |
|
||||
TapeAlertFlags::READ_FAILURE.bits() |
|
||||
TapeAlertFlags::WRITE_PROTECT.bits() |
|
||||
TapeAlertFlags::UNRECOVERABLE_SNAPPED_TAPE.bits() |
|
||||
TapeAlertFlags::FORCED_EJECT.bits() |
|
||||
TapeAlertFlags::EXPIRED_CLEANING_MEDIA.bits() |
|
||||
TapeAlertFlags::INVALID_CLEANING_TAPE.bits() |
|
||||
TapeAlertFlags::HARDWARE_A.bits() |
|
||||
TapeAlertFlags::HARDWARE_B.bits() |
|
||||
TapeAlertFlags::EJECT_MEDIA.bits() |
|
||||
TapeAlertFlags::PREDICTIVE_FAILURE.bits() |
|
||||
TapeAlertFlags::LOADER_STRAY_TAPE.bits() |
|
||||
TapeAlertFlags::LOADER_MAGAZINE.bits() |
|
||||
TapeAlertFlags::TAPE_SYSTEM_AREA_WRITE_FAILURE.bits() |
|
||||
TapeAlertFlags::TAPE_SYSTEM_AREA_READ_FAILURE.bits() |
|
||||
TapeAlertFlags::NO_START_OF_DATA.bits() |
|
||||
TapeAlertFlags::LOADING_FAILURE.bits() |
|
||||
TapeAlertFlags::UNRECOVERABLE_UNLOAD_FAILURE.bits() |
|
||||
TapeAlertFlags::AUTOMATION_INTERFACE_FAILURE.bits();
|
||||
|
||||
/// Check if tape-alert-flags contains critial errors.
|
||||
pub fn tape_alert_flags_critical(flags: TapeAlertFlags) -> bool {
|
||||
(flags.bits() & CRITICAL_FLAG_MASK) != 0
|
||||
}
|
||||
|
||||
const MEDIA_LIFE_MASK: u64 =
|
||||
TapeAlertFlags::MEDIA_LIFE.bits() |
|
||||
TapeAlertFlags::NEARING_MEDIA_LIFE.bits();
|
||||
|
||||
/// Check if tape-alert-flags indicates media-life end
|
||||
pub fn tape_alert_flags_media_life(flags: TapeAlertFlags) -> bool {
|
||||
(flags.bits() & MEDIA_LIFE_MASK) != 0
|
||||
}
|
||||
|
||||
const MEDIA_CLEAN_MASK: u64 =
|
||||
TapeAlertFlags::CLEAN_NOW.bits() |
|
||||
TapeAlertFlags::CLEAN_PERIODIC.bits();
|
||||
|
||||
/// Check if tape-alert-flags indicates media cleaning request
|
||||
pub fn tape_alert_flags_cleaning_request(flags: TapeAlertFlags) -> bool {
|
||||
(flags.bits() & MEDIA_CLEAN_MASK) != 0
|
||||
}
|
@ -1,230 +0,0 @@
|
||||
use std::io::Read;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use endian_trait::Endian;
|
||||
|
||||
use proxmox::tools::io::ReadExt;
|
||||
|
||||
use pbs_api_types::Lp17VolumeStatistics;
|
||||
|
||||
use crate::tools::sgutils2::SgRaw;
|
||||
|
||||
/// SCSI command to query volume statistics
|
||||
///
|
||||
/// CDB: LOG SENSE / LP17h Volume Statistics
|
||||
///
|
||||
/// The Volume Statistics log page is included in Ultrium 5 and later
|
||||
/// drives.
|
||||
pub fn read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Lp17VolumeStatistics, Error> {
|
||||
|
||||
let data = sg_read_volume_statistics(file)?;
|
||||
|
||||
decode_volume_statistics(&data)
|
||||
}
|
||||
|
||||
fn sg_read_volume_statistics<F: AsRawFd>(file: &mut F) -> Result<Vec<u8>, Error> {
|
||||
|
||||
let alloc_len: u16 = 8192;
|
||||
let mut sg_raw = SgRaw::new(file, alloc_len as usize)?;
|
||||
|
||||
let mut cmd = Vec::new();
|
||||
cmd.push(0x4D); // LOG SENSE
|
||||
cmd.push(0);
|
||||
cmd.push((1<<6) | 0x17); // Volume Statistics log page
|
||||
cmd.push(0); // Subpage 0
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.push(0);
|
||||
cmd.extend(&alloc_len.to_be_bytes()); // alloc len
|
||||
cmd.push(0u8); // control byte
|
||||
|
||||
sg_raw.do_command(&cmd)
|
||||
.map_err(|err| format_err!("read tape volume statistics failed - {}", err))
|
||||
.map(|v| v.to_vec())
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
#[derive(Endian)]
|
||||
struct LpParameterHeader {
|
||||
parameter_code: u16,
|
||||
control: u8,
|
||||
parameter_len: u8,
|
||||
}
|
||||
|
||||
fn decode_volume_statistics(data: &[u8]) -> Result<Lp17VolumeStatistics, Error> {
|
||||
|
||||
|
||||
let read_be_counter = |reader: &mut &[u8], len: u8| {
|
||||
let len = len as usize;
|
||||
if len == 0 || len > 8 {
|
||||
bail!("invalid conter size '{}'", len);
|
||||
}
|
||||
let mut buffer = [0u8; 8];
|
||||
reader.read_exact(&mut buffer[..len])?;
|
||||
|
||||
let value = buffer
|
||||
.iter()
|
||||
.take(len)
|
||||
.fold(0, |value, curr| (value << 8) | *curr as u64);
|
||||
|
||||
Ok(value)
|
||||
};
|
||||
|
||||
proxmox::try_block!({
|
||||
if !((data[0] & 0x7f) == 0x17 && data[1] == 0) {
|
||||
bail!("invalid response");
|
||||
}
|
||||
|
||||
let mut reader = &data[2..];
|
||||
|
||||
let page_len: u16 = unsafe { reader.read_be_value()? };
|
||||
|
||||
let page_len = page_len as usize;
|
||||
|
||||
if (page_len + 4) > data.len() {
|
||||
bail!("invalid page length");
|
||||
} else {
|
||||
// Note: Quantum hh7 returns the allocation_length instead of real data_len
|
||||
reader = &data[4..page_len+4];
|
||||
}
|
||||
|
||||
let mut stat = Lp17VolumeStatistics::default();
|
||||
let mut page_valid = false;
|
||||
|
||||
loop {
|
||||
if reader.is_empty() {
|
||||
break;
|
||||
}
|
||||
let head: LpParameterHeader = unsafe { reader.read_be_value()? };
|
||||
|
||||
match head.parameter_code {
|
||||
0x0000 => {
|
||||
let value: u64 = read_be_counter(&mut reader, head.parameter_len)?;
|
||||
if value == 0 {
|
||||
bail!("page-valid flag not set");
|
||||
}
|
||||
page_valid = true;
|
||||
}
|
||||
0x0001 => {
|
||||
stat.volume_mounts =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0002 => {
|
||||
stat.volume_datasets_written =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0003 => {
|
||||
stat.volume_recovered_write_data_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0004 => {
|
||||
stat.volume_unrecovered_write_data_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0005 => {
|
||||
stat.volume_write_servo_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0006 => {
|
||||
stat.volume_unrecovered_write_servo_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0007 => {
|
||||
stat.volume_datasets_read =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0008 => {
|
||||
stat.volume_recovered_read_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0009 => {
|
||||
stat.volume_unrecovered_read_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x000C => {
|
||||
stat.last_mount_unrecovered_write_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x000D => {
|
||||
stat.last_mount_unrecovered_read_errors =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x000E => {
|
||||
stat.last_mount_bytes_written =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x000F => {
|
||||
stat.last_mount_bytes_read =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0010 => {
|
||||
stat.lifetime_bytes_written =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0011 => {
|
||||
stat.lifetime_bytes_read =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0012 => {
|
||||
stat.last_load_write_compression_ratio =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0013 => {
|
||||
stat.last_load_read_compression_ratio =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0014 => {
|
||||
stat.medium_mount_time =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0015 => {
|
||||
stat.medium_ready_time =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0016 => {
|
||||
stat.total_native_capacity =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0017 => {
|
||||
stat.total_used_native_capacity =
|
||||
read_be_counter(&mut reader, head.parameter_len)? * 1_000_000;
|
||||
}
|
||||
0x0040 => {
|
||||
let data = reader.read_exact_allocated(head.parameter_len as usize)?;
|
||||
stat.serial = String::from_utf8_lossy(&data).to_string();
|
||||
}
|
||||
0x0080 => {
|
||||
let value = read_be_counter(&mut reader, head.parameter_len)?;
|
||||
if value == 1 {
|
||||
stat.write_protect = true;
|
||||
}
|
||||
}
|
||||
0x0081 => {
|
||||
let value = read_be_counter(&mut reader, head.parameter_len)?;
|
||||
if value == 1 {
|
||||
stat.worm = true;
|
||||
}
|
||||
}
|
||||
0x0101 => {
|
||||
stat.beginning_of_medium_passes =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
0x0102 => {
|
||||
stat.middle_of_tape_passes =
|
||||
read_be_counter(&mut reader, head.parameter_len)?;
|
||||
}
|
||||
_ => {
|
||||
reader.read_exact_allocated(head.parameter_len as usize)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !page_valid {
|
||||
bail!("missing page-valid parameter");
|
||||
}
|
||||
|
||||
Ok(stat)
|
||||
|
||||
}).map_err(|err| format_err!("decode volume statistics failed - {}", err))
|
||||
}
|
@ -33,26 +33,26 @@ use pbs_config::key_config::KeyConfig;
|
||||
use pbs_datastore::task::TaskState;
|
||||
use pbs_datastore::task_log;
|
||||
|
||||
use pbs_tape::{
|
||||
TapeWrite, TapeRead, BlockReadError, MediaContentHeader,
|
||||
sg_tape::TapeAlertFlags,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
server::{
|
||||
send_load_media_email,
|
||||
WorkerTask,
|
||||
},
|
||||
tape::{
|
||||
TapeWrite,
|
||||
TapeRead,
|
||||
BlockReadError,
|
||||
MediaId,
|
||||
drive::{
|
||||
virtual_tape::open_virtual_tape_drive,
|
||||
lto::TapeAlertFlags,
|
||||
},
|
||||
file_formats::{
|
||||
PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
|
||||
PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
|
||||
MediaLabel,
|
||||
MediaSetLabel,
|
||||
MediaContentHeader,
|
||||
},
|
||||
changer::{
|
||||
MediaChange,
|
||||
|
@ -11,33 +11,31 @@ use proxmox::tools::{
|
||||
};
|
||||
|
||||
use pbs_config::key_config::KeyConfig;
|
||||
use pbs_tape::{
|
||||
TapeWrite,
|
||||
TapeRead,
|
||||
BlockedReader,
|
||||
BlockedWriter,
|
||||
BlockReadError,
|
||||
MtxStatus,
|
||||
DriveStatus,
|
||||
ElementStatus,
|
||||
StorageElementStatus,
|
||||
MediaContentHeader,
|
||||
EmulateTapeReader,
|
||||
EmulateTapeWriter,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
tape::{
|
||||
TapeWrite,
|
||||
TapeRead,
|
||||
BlockReadError,
|
||||
changer::{
|
||||
MediaChange,
|
||||
MtxStatus,
|
||||
DriveStatus,
|
||||
ElementStatus,
|
||||
StorageElementStatus,
|
||||
},
|
||||
drive::{
|
||||
VirtualTapeDrive,
|
||||
TapeDriver,
|
||||
drive::{
|
||||
VirtualTapeDrive,
|
||||
TapeDriver,
|
||||
MediaChange,
|
||||
},
|
||||
file_formats::{
|
||||
MediaSetLabel,
|
||||
MediaContentHeader,
|
||||
PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
|
||||
BlockedReader,
|
||||
BlockedWriter,
|
||||
},
|
||||
helpers::{
|
||||
EmulateTapeReader,
|
||||
EmulateTapeWriter,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
Reference in New Issue
Block a user