[pbs-devel] [PATCH 03/11] tape: implement LTO userspace driver
Dietmar Maurer
dietmar at proxmox.com
Wed Apr 7 12:23:00 CEST 2021
---
debian/proxmox-backup-server.udev | 18 ++
src/api2/config/changer.rs | 4 +-
src/api2/config/drive.rs | 44 +--
src/api2/tape/changer.rs | 4 +-
src/api2/tape/drive.rs | 36 +--
src/api2/tape/mod.rs | 4 +-
src/api2/types/tape/drive.rs | 24 +-
src/bin/pmt.rs | 314 ++++-----------------
src/bin/pmtx.rs | 4 +-
src/bin/proxmox_tape/drive.rs | 8 +-
src/bin/sg-tape-cmd.rs | 189 ++-----------
src/config/drive.rs | 18 +-
src/tape/changer/mod.rs | 4 +-
src/tape/drive/lto/mod.rs | 420 ++++++++++++++++++++++++++++
src/tape/drive/lto/sg_tape.rs | 445 ++++++++++++++++++++++++++++++
src/tape/drive/mod.rs | 22 +-
src/tape/linux_list_drives.rs | 51 ++--
17 files changed, 1078 insertions(+), 531 deletions(-)
create mode 100644 debian/proxmox-backup-server.udev
create mode 100644 src/tape/drive/lto/mod.rs
create mode 100644 src/tape/drive/lto/sg_tape.rs
diff --git a/debian/proxmox-backup-server.udev b/debian/proxmox-backup-server.udev
new file mode 100644
index 00000000..afdfb2bc
--- /dev/null
+++ b/debian/proxmox-backup-server.udev
@@ -0,0 +1,18 @@
+# do not edit this file, it will be overwritten on update
+
+# persistent storage links: /dev/tape/{by-id,by-path}
+
+ACTION=="remove", GOTO="persistent_storage_tape_end"
+ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
+
+# also see: /lib/udev/rules.d/60-persistent-storage-tape.rules
+
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="1", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
+ SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}-sg"
+
+# iSCSI devices from the same host have all the same ID_SERIAL,
+# but additionally a property named ID_SCSI_SERIAL.
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="1", ENV{ID_SCSI_SERIAL}=="?*", \
+ SYMLINK+="tape/by-id/scsi-$env{ID_SCSI_SERIAL}-sg"
+
+LABEL="persistent_storage_tape_end"
diff --git a/src/api2/config/changer.rs b/src/api2/config/changer.rs
index 380eb089..62a0fba4 100644
--- a/src/api2/config/changer.rs
+++ b/src/api2/config/changer.rs
@@ -27,7 +27,7 @@ use crate::{
SLOT_ARRAY_SCHEMA,
EXPORT_SLOT_LIST_SCHEMA,
ScsiTapeChanger,
- LinuxTapeDrive,
+ LtoTapeDrive,
},
tape::{
linux_tape_changer_list,
@@ -303,7 +303,7 @@ pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
None => bail!("Delete changer '{}' failed - no such entry", name),
}
- let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+ let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
for drive in drive_list {
if let Some(changer) = drive.changer {
if changer == name {
diff --git a/src/api2/config/drive.rs b/src/api2/config/drive.rs
index 98337024..e2626e14 100644
--- a/src/api2/config/drive.rs
+++ b/src/api2/config/drive.rs
@@ -19,12 +19,12 @@ use crate::{
DRIVE_NAME_SCHEMA,
CHANGER_NAME_SCHEMA,
CHANGER_DRIVENUM_SCHEMA,
- LINUX_DRIVE_PATH_SCHEMA,
- LinuxTapeDrive,
+ LTO_DRIVE_PATH_SCHEMA,
+ LtoTapeDrive,
ScsiTapeChanger,
},
tape::{
- linux_tape_device_list,
+ lto_tape_device_list,
check_drive_path,
},
};
@@ -37,7 +37,7 @@ use crate::{
schema: DRIVE_NAME_SCHEMA,
},
path: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
},
changer: {
schema: CHANGER_NAME_SCHEMA,
@@ -60,13 +60,13 @@ pub fn create_drive(param: Value) -> Result<(), Error> {
let (mut config, _digest) = config::drive::config()?;
- let item: LinuxTapeDrive = serde_json::from_value(param)?;
+ let item: LtoTapeDrive = serde_json::from_value(param)?;
- let linux_drives = linux_tape_device_list();
+ let lto_drives = lto_tape_device_list();
- check_drive_path(&linux_drives, &item.path)?;
+ check_drive_path(<o_drives, &item.path)?;
- let existing: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+ let existing: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
for drive in existing {
if drive.name == item.name {
@@ -77,7 +77,7 @@ pub fn create_drive(param: Value) -> Result<(), Error> {
}
}
- config.set_data(&item.name, "linux", &item)?;
+ config.set_data(&item.name, "lto", &item)?;
config::drive::save_config(&config)?;
@@ -93,7 +93,7 @@ pub fn create_drive(param: Value) -> Result<(), Error> {
},
},
returns: {
- type: LinuxTapeDrive,
+ type: LtoTapeDrive,
},
access: {
permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false),
@@ -104,11 +104,11 @@ pub fn get_config(
name: String,
_param: Value,
mut rpcenv: &mut dyn RpcEnvironment,
-) -> Result<LinuxTapeDrive, Error> {
+) -> Result<LtoTapeDrive, Error> {
let (config, digest) = config::drive::config()?;
- let data: LinuxTapeDrive = config.lookup("linux", &name)?;
+ let data: LtoTapeDrive = config.lookup("lto", &name)?;
rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
@@ -123,7 +123,7 @@ pub fn get_config(
description: "The list of configured drives (with config digest).",
type: Array,
items: {
- type: LinuxTapeDrive,
+ type: LtoTapeDrive,
},
},
access: {
@@ -135,13 +135,13 @@ pub fn get_config(
pub fn list_drives(
_param: Value,
mut rpcenv: &mut dyn RpcEnvironment,
-) -> Result<Vec<LinuxTapeDrive>, Error> {
+) -> Result<Vec<LtoTapeDrive>, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
let (config, digest) = config::drive::config()?;
- let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+ let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
let drive_list = drive_list
.into_iter()
@@ -176,7 +176,7 @@ pub enum DeletableProperty {
schema: DRIVE_NAME_SCHEMA,
},
path: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
changer: {
@@ -225,7 +225,7 @@ pub fn update_drive(
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
}
- let mut data: LinuxTapeDrive = config.lookup("linux", &name)?;
+ let mut data: LtoTapeDrive = config.lookup("lto", &name)?;
if let Some(delete) = delete {
for delete_prop in delete {
@@ -240,8 +240,8 @@ pub fn update_drive(
}
if let Some(path) = path {
- let linux_drives = linux_tape_device_list();
- check_drive_path(&linux_drives, &path)?;
+ let lto_drives = lto_tape_device_list();
+ check_drive_path(<o_drives, &path)?;
data.path = path;
}
@@ -261,7 +261,7 @@ pub fn update_drive(
}
}
- config.set_data(&name, "linux", &data)?;
+ config.set_data(&name, "lto", &data)?;
config::drive::save_config(&config)?;
@@ -290,8 +290,8 @@ pub fn delete_drive(name: String, _param: Value) -> Result<(), Error> {
match config.sections.get(&name) {
Some((section_type, _)) => {
- if section_type != "linux" {
- bail!("Entry '{}' exists, but is not a linux tape drive", name);
+ if section_type != "lto" {
+ bail!("Entry '{}' exists, but is not a lto tape drive", name);
}
config.sections.remove(&name);
},
diff --git a/src/api2/tape/changer.rs b/src/api2/tape/changer.rs
index e2d1edc0..59dfe044 100644
--- a/src/api2/tape/changer.rs
+++ b/src/api2/tape/changer.rs
@@ -20,7 +20,7 @@ use crate::{
Authid,
CHANGER_NAME_SCHEMA,
ChangerListEntry,
- LinuxTapeDrive,
+ LtoTapeDrive,
MtxEntryKind,
MtxStatusEntry,
ScsiTapeChanger,
@@ -88,7 +88,7 @@ pub async fn get_status(
inventory.update_online_status(&map)?;
- let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+ let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
let mut drive_map: HashMap<u64, String> = HashMap::new();
for drive in drive_list {
diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs
index b17f4203..80d17a27 100644
--- a/src/api2/tape/drive.rs
+++ b/src/api2/tape/drive.rs
@@ -42,11 +42,11 @@ use crate::{
MEDIA_POOL_NAME_SCHEMA,
Authid,
DriveListEntry,
- LinuxTapeDrive,
+ LtoTapeDrive,
MediaIdFlat,
LabelUuidMap,
MamAttribute,
- LinuxDriveAndMediaStatus,
+ LtoDriveAndMediaStatus,
},
tape::restore::{
fast_catalog_restore,
@@ -62,7 +62,7 @@ use crate::{
lock_media_set,
lock_media_pool,
lock_unassigned_media_pool,
- linux_tape_device_list,
+ lto_tape_device_list,
lookup_device_identification,
file_formats::{
MediaLabel,
@@ -70,9 +70,9 @@ use crate::{
},
drive::{
TapeDriver,
- LinuxTapeHandle,
+ LtoTapeHandle,
Lp17VolumeStatistics,
- open_linux_tape_device,
+ open_lto_tape_device,
media_changer,
required_media_changer,
open_drive,
@@ -794,9 +794,9 @@ pub fn clean_drive(
changer.clean_drive()?;
- if let Ok(drive_config) = config.lookup::<LinuxTapeDrive>("linux", &drive) {
+ if let Ok(drive_config) = config.lookup::<LtoTapeDrive>("lto", &drive) {
// Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
- let mut handle = LinuxTapeHandle::new(open_linux_tape_device(&drive_config.path)?);
+ let mut handle = LtoTapeHandle::new(open_lto_tape_device(&drive_config.path)?)?;
// test for critical tape alert flags
if let Ok(alert_flags) = handle.tape_alert_flags() {
@@ -1144,7 +1144,7 @@ pub async fn cartridge_memory(drive: String) -> Result<Vec<MamAttribute>, Error>
drive.clone(),
"reading cartridge memory".to_string(),
move |config| {
- let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
+ let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?;
let mut handle = drive_config.open()?;
handle.cartridge_memory()
@@ -1174,7 +1174,7 @@ pub async fn volume_statistics(drive: String) -> Result<Lp17VolumeStatistics, Er
drive.clone(),
"reading volume statistics".to_string(),
move |config| {
- let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
+ let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?;
let mut handle = drive_config.open()?;
handle.volume_statistics()
@@ -1192,24 +1192,24 @@ pub async fn volume_statistics(drive: String) -> Result<Lp17VolumeStatistics, Er
},
},
returns: {
- type: LinuxDriveAndMediaStatus,
+ type: LtoDriveAndMediaStatus,
},
access: {
permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT, false),
},
)]
/// Get drive/media status
-pub async fn status(drive: String) -> Result<LinuxDriveAndMediaStatus, Error> {
+pub async fn status(drive: String) -> Result<LtoDriveAndMediaStatus, Error> {
run_drive_blocking_task(
drive.clone(),
"reading drive status".to_string(),
move |config| {
- let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
+ let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?;
- // Note: use open_linux_tape_device, because this also works if no medium loaded
- let file = open_linux_tape_device(&drive_config.path)?;
+ // Note: use open_lto_tape_device, because this also works if no medium loaded
+ let file = open_lto_tape_device(&drive_config.path)?;
- let mut handle = LinuxTapeHandle::new(file);
+ let mut handle = LtoTapeHandle::new(file)?;
handle.get_drive_and_media_status()
}
@@ -1382,9 +1382,9 @@ pub fn list_drives(
let (config, _) = config::drive::config()?;
- let linux_drives = linux_tape_device_list();
+ let lto_drives = lto_tape_device_list();
- let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?;
+ let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
let mut list = Vec::new();
@@ -1398,7 +1398,7 @@ pub fn list_drives(
continue;
}
- let info = lookup_device_identification(&linux_drives, &drive.path);
+ let info = lookup_device_identification(<o_drives, &drive.path);
let state = get_tape_device_state(&config, &drive.name)?;
let entry = DriveListEntry { config: drive, info, state };
list.push(entry);
diff --git a/src/api2/tape/mod.rs b/src/api2/tape/mod.rs
index b560f077..219a721b 100644
--- a/src/api2/tape/mod.rs
+++ b/src/api2/tape/mod.rs
@@ -15,7 +15,7 @@ use proxmox::{
use crate::{
api2::types::TapeDeviceInfo,
tape::{
- linux_tape_device_list,
+ lto_tape_device_list,
linux_tape_changer_list,
},
};
@@ -41,7 +41,7 @@ pub mod restore;
/// Scan tape drives
pub fn scan_drives(_param: Value) -> Result<Vec<TapeDeviceInfo>, Error> {
- let list = linux_tape_device_list();
+ let list = lto_tape_device_list();
Ok(list)
}
diff --git a/src/api2/types/tape/drive.rs b/src/api2/types/tape/drive.rs
index 2fd480ac..058e544f 100644
--- a/src/api2/types/tape/drive.rs
+++ b/src/api2/types/tape/drive.rs
@@ -21,8 +21,8 @@ pub const DRIVE_NAME_SCHEMA: Schema = StringSchema::new("Drive Identifier.")
.max_length(32)
.schema();
-pub const LINUX_DRIVE_PATH_SCHEMA: Schema = StringSchema::new(
- "The path to a LINUX non-rewinding SCSI tape device (i.e. '/dev/nst0')")
+pub const LTO_DRIVE_PATH_SCHEMA: Schema = StringSchema::new(
+ "The path to a LTO SCSI-generic tape device (i.e. '/dev/sg0')")
.schema();
pub const CHANGER_DRIVENUM_SCHEMA: Schema = IntegerSchema::new(
@@ -57,7 +57,7 @@ pub struct VirtualTapeDrive {
schema: DRIVE_NAME_SCHEMA,
},
path: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
},
changer: {
schema: CHANGER_NAME_SCHEMA,
@@ -71,8 +71,8 @@ pub struct VirtualTapeDrive {
)]
#[derive(Serialize,Deserialize)]
#[serde(rename_all = "kebab-case")]
-/// Linux SCSI tape driver
-pub struct LinuxTapeDrive {
+/// Lto SCSI tape driver
+pub struct LtoTapeDrive {
pub name: String,
pub path: String,
#[serde(skip_serializing_if="Option::is_none")]
@@ -84,7 +84,7 @@ pub struct LinuxTapeDrive {
#[api(
properties: {
config: {
- type: LinuxTapeDrive,
+ type: LtoTapeDrive,
},
info: {
type: OptionalDeviceIdentification,
@@ -96,7 +96,7 @@ pub struct LinuxTapeDrive {
/// Drive list entry
pub struct DriveListEntry {
#[serde(flatten)]
- pub config: LinuxTapeDrive,
+ pub config: LtoTapeDrive,
#[serde(flatten)]
pub info: OptionalDeviceIdentification,
/// the state of the drive if locked
@@ -169,11 +169,11 @@ impl TryFrom<u8> for TapeDensity {
)]
#[derive(Serialize,Deserialize)]
#[serde(rename_all = "kebab-case")]
-/// Drive/Media status for Linux SCSI drives.
+/// Drive/Media status for Lto SCSI drives.
///
/// Media related data is optional - only set if there is a medium
/// loaded.
-pub struct LinuxDriveAndMediaStatus {
+pub struct LtoDriveAndMediaStatus {
/// Block size (0 is variable size)
pub blocksize: u32,
/// Tape density
@@ -181,17 +181,17 @@ pub struct LinuxDriveAndMediaStatus {
pub density: Option<TapeDensity>,
/// Status flags
pub status: String,
- /// Linux Driver Options
+ /// Lto Driver Options
pub options: String,
/// Tape Alert Flags
#[serde(skip_serializing_if="Option::is_none")]
pub alert_flags: Option<String>,
/// Current file number
#[serde(skip_serializing_if="Option::is_none")]
- pub file_number: Option<u32>,
+ pub file_number: Option<u64>,
/// Current block number
#[serde(skip_serializing_if="Option::is_none")]
- pub block_number: Option<u32>,
+ pub block_number: Option<u64>,
/// Medium Manufacture Date (epoch)
#[serde(skip_serializing_if="Option::is_none")]
pub manufactured: Option<i64>,
diff --git a/src/bin/pmt.rs b/src/bin/pmt.rs
index a097df2c..df3ad9ec 100644
--- a/src/bin/pmt.rs
+++ b/src/bin/pmt.rs
@@ -1,18 +1,18 @@
/// Control magnetic tape drive operation
///
-/// This is a Rust implementation, meant to replace the 'mt' command
-/// line tool.
+/// This is a Rust implementation, using the Proxmox userspace tape
+/// driver. This is meant as replacement fot the 'mt' command line
+/// tool.
///
/// Features:
///
/// - written in Rust
+/// - use Proxmox userspace driver (using SG_IO)
/// - optional json output format
/// - support tape alert flags
/// - support volume statistics
/// - read cartridge memory
-use std::collections::HashMap;
-
use anyhow::{bail, Error};
use serde_json::Value;
@@ -43,7 +43,7 @@ pub const RECORD_COUNT_SCHEMA: Schema =
.schema();
pub const DRIVE_OPTION_SCHEMA: Schema = StringSchema::new(
- "Linux Tape Driver Option, either numeric value or option name.")
+ "Lto Tape Driver Option, either numeric value or option name.")
.schema();
pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
@@ -57,103 +57,60 @@ use proxmox_backup::{
drive::complete_drive_name,
},
api2::types::{
- LINUX_DRIVE_PATH_SCHEMA,
+ LTO_DRIVE_PATH_SCHEMA,
DRIVE_NAME_SCHEMA,
- LinuxTapeDrive,
+ LtoTapeDrive,
},
tape::{
complete_drive_path,
- linux_tape_device_list,
+ lto_tape_device_list,
drive::{
- linux_mtio::{MTCmd, SetDrvBufferOptions},
TapeDriver,
- LinuxTapeHandle,
- open_linux_tape_device,
+ LtoTapeHandle,
+ open_lto_tape_device,
},
},
};
-lazy_static::lazy_static!{
-
- static ref DRIVE_OPTIONS: HashMap<String, SetDrvBufferOptions> = {
- let mut map = HashMap::new();
-
- for i in 0..31 {
- let bit: i32 = 1 << i;
- let flag = SetDrvBufferOptions::from_bits_truncate(bit);
- if flag.bits() == 0 { continue; }
- let name = format!("{:?}", flag)
- .to_lowercase()
- .replace("_", "-");
-
- map.insert(name, flag);
- }
- map
- };
-
-}
-
-fn parse_drive_options(options: Vec<String>) -> Result<SetDrvBufferOptions, Error> {
-
- let mut value = SetDrvBufferOptions::empty();
-
- for option in options.iter() {
- if let Ok::<i32,_>(v) = option.parse() {
- value |= SetDrvBufferOptions::from_bits_truncate(v);
- } else if let Some(v) = DRIVE_OPTIONS.get(option) {
- value |= *v;
- } else {
- let option = option.to_lowercase().replace("_", "-");
- if let Some(v) = DRIVE_OPTIONS.get(&option) {
- value |= *v;
- } else {
- bail!("unknown drive option {}", option);
- }
- }
- }
-
- Ok(value)
-}
-
-fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
+fn get_tape_handle(param: &Value) -> Result<LtoTapeHandle, Error> {
if let Some(name) = param["drive"].as_str() {
let (config, _digest) = config::drive::config()?;
- let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+ let drive: LtoTapeDrive = config.lookup("lto", &name)?;
eprintln!("using device {}", drive.path);
- return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?))
+ return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?);
}
if let Some(device) = param["device"].as_str() {
eprintln!("using device {}", device);
- return Ok(LinuxTapeHandle::new(open_linux_tape_device(&device)?))
+ return LtoTapeHandle::new(open_lto_tape_device(&device)?);
}
if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
let (config, _digest) = config::drive::config()?;
- let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+ let drive: LtoTapeDrive = config.lookup("lto", &name)?;
eprintln!("using device {}", drive.path);
- return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?))
+ return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?);
}
if let Ok(device) = std::env::var("TAPE") {
eprintln!("using device {}", device);
- return Ok(LinuxTapeHandle::new(open_linux_tape_device(&device)?))
+ return LtoTapeHandle::new(open_lto_tape_device(&device)?);
}
let (config, _digest) = config::drive::config()?;
let mut drive_names = Vec::new();
for (name, (section_type, _)) in config.sections.iter() {
- if section_type != "linux" { continue; }
+ if section_type != "lto" { continue; }
drive_names.push(name);
}
if drive_names.len() == 1 {
let name = drive_names[0];
- let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+ let drive: LtoTapeDrive = config.lookup("lto", &name)?;
eprintln!("using device {}", drive.path);
- return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?))
+ return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?);
}
bail!("no drive/device specified");
@@ -167,7 +124,7 @@ fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
count: {
@@ -200,7 +157,7 @@ fn asf(count: usize, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
count: {
@@ -230,7 +187,7 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
count: {
@@ -243,11 +200,12 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> {
///
/// This leaves the tape positioned at the first block of the file
/// that is count - 1 files before the current file.
-fn bsfm(count: i32, param: Value) -> Result<(), Error> {
+fn bsfm(count: usize, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(¶m)?;
- handle.mtop(MTCmd::MTBSFM, count, "bsfm")?;
+ handle.backward_space_count_files(count)?;
+ handle.forward_space_count_files(1)?;
Ok(())
}
@@ -261,7 +219,7 @@ fn bsfm(count: i32, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
count: {
@@ -275,7 +233,9 @@ fn bsr(count: i32, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(¶m)?;
- handle.mtop(MTCmd::MTBSR, count, "backward space records")?;
+ unimplemented!();
+
+ // fixme: handle.mtop(MTCmd::MTBSR, count, "backward space records")?;
Ok(())
}
@@ -289,7 +249,7 @@ fn bsr(count: i32, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
"output-format": {
@@ -340,7 +300,7 @@ fn cartridge_memory(param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
"output-format": {
@@ -389,7 +349,7 @@ fn tape_alert_flags(param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
},
@@ -413,7 +373,7 @@ fn eject(param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
},
@@ -437,7 +397,7 @@ fn eod(param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
fast: {
@@ -466,7 +426,7 @@ fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
count: {
@@ -495,7 +455,7 @@ fn fsf(count: usize, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
count: {
@@ -508,11 +468,12 @@ fn fsf(count: usize, param: Value) -> Result<(), Error> {
///
/// This leaves the tape positioned at the last block of the file that
/// is count - 1 files past the current file.
-fn fsfm(count: i32, param: Value) -> Result<(), Error> {
+fn fsfm(count: usize, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(¶m)?;
- handle.mtop(MTCmd::MTFSFM, count, "fsfm")?;
+ handle.forward_space_count_files(count)?;
+ handle.backward_space_count_files(1)?;
Ok(())
}
@@ -526,7 +487,7 @@ fn fsfm(count: i32, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
count: {
@@ -540,7 +501,8 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(¶m)?;
- handle.mtop(MTCmd::MTFSR, count, "forward space records")?;
+ unimplemented!();
+ // fixme: handle.mtop(MTCmd::MTFSR, count, "forward space records")?;
Ok(())
}
@@ -554,7 +516,7 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
},
@@ -564,7 +526,7 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> {
fn load(param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(¶m)?;
- handle.mtload()?;
+ handle.load()?;
Ok(())
}
@@ -578,7 +540,7 @@ fn load(param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
},
@@ -589,7 +551,8 @@ fn lock(param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(¶m)?;
- handle.mtop(MTCmd::MTLOCK, 1, "lock tape drive door")?;
+ unimplemented!();
+ // fixme: handle.mtop(MTCmd::MTLOCK, 1, "lock tape drive door")?;
Ok(())
}
@@ -603,7 +566,7 @@ fn lock(param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
},
@@ -634,7 +597,7 @@ fn scan(param: Value) -> Result<(), Error> {
let output_format = get_output_format(¶m);
- let list = linux_tape_device_list();
+ let list = lto_tape_device_list();
if output_format == "json-pretty" {
println!("{}", serde_json::to_string_pretty(&list)?);
@@ -657,36 +620,6 @@ fn scan(param: Value) -> Result<(), Error> {
Ok(())
}
-
-#[api(
- input: {
- properties: {
- drive: {
- schema: DRIVE_NAME_SCHEMA,
- optional: true,
- },
- device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
- optional: true,
- },
- size: {
- description: "Block size in bytes.",
- minimum: 0,
- },
- },
- },
-)]
-/// Set the block size of the drive
-fn setblk(size: i32, param: Value) -> Result<(), Error> {
-
- let mut handle = get_tape_handle(¶m)?;
-
- handle.mtop(MTCmd::MTSETBLK, size, "set block size")?;
-
- Ok(())
-}
-
-
#[api(
input: {
properties: {
@@ -695,7 +628,7 @@ fn setblk(size: i32, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
"output-format": {
@@ -737,122 +670,6 @@ fn status(param: Value) -> Result<(), Error> {
}
-#[api(
- input: {
- properties: {
- drive: {
- schema: DRIVE_NAME_SCHEMA,
- optional: true,
- },
- device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
- optional: true,
- },
- options: {
- schema: DRIVE_OPTION_LIST_SCHEMA,
- optional: true,
- },
- defaults: {
- description: "Set default options (buffer-writes async-writes read-ahead can-bsr).",
- type: bool,
- optional: true,
- },
- },
- },
-)]
-/// Set device driver options (root only)
-fn st_options(
- options: Option<Vec<String>>,
- defaults: Option<bool>,
- param: Value) -> Result<(), Error> {
-
- let handle = get_tape_handle(¶m)?;
-
- let options = match defaults {
- Some(true) => {
- if options.is_some() {
- bail!("option --defaults conflicts with specified options");
- }
- let mut list = Vec::new();
- list.push(String::from("buffer-writes"));
- list.push(String::from("async-writes"));
- list.push(String::from("read-ahead"));
- list.push(String::from("can-bsr"));
- list
- }
- Some(false) | None => {
- options.unwrap_or_else(|| Vec::new())
- }
- };
-
- let value = parse_drive_options(options)?;
-
- handle.set_drive_buffer_options(value)?;
-
- Ok(())
-}
-
-
-#[api(
- input: {
- properties: {
- drive: {
- schema: DRIVE_NAME_SCHEMA,
- optional: true,
- },
- device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
- optional: true,
- },
- options: {
- schema: DRIVE_OPTION_LIST_SCHEMA,
- },
- },
- },
-)]
-/// Set selected device driver options bits (root only)
-fn st_set_options(options: Vec<String>, param: Value) -> Result<(), Error> {
-
- let handle = get_tape_handle(¶m)?;
-
- let value = parse_drive_options(options)?;
-
- handle.drive_buffer_set_options(value)?;
-
- Ok(())
-}
-
-
-#[api(
- input: {
- properties: {
- drive: {
- schema: DRIVE_NAME_SCHEMA,
- optional: true,
- },
- device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
- optional: true,
- },
- options: {
- schema: DRIVE_OPTION_LIST_SCHEMA,
- },
- },
- },
-)]
-/// Clear selected device driver options bits (root only)
-fn st_clear_options(options: Vec<String>, param: Value) -> Result<(), Error> {
-
- let handle = get_tape_handle(¶m)?;
-
- let value = parse_drive_options(options)?;
-
- handle.drive_buffer_clear_options(value)?;
-
- Ok(())
-}
-
-
#[api(
input: {
properties: {
@@ -861,7 +678,7 @@ fn st_clear_options(options: Vec<String>, param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
},
@@ -872,7 +689,8 @@ fn unlock(param: Value) -> Result<(), Error> {
let mut handle = get_tape_handle(¶m)?;
- handle.mtop(MTCmd::MTUNLOCK, 1, "unlock tape drive door")?;
+ unimplemented!();
+ //handle.mtop(MTCmd::MTUNLOCK, 1, "unlock tape drive door")?;
Ok(())
}
@@ -886,7 +704,7 @@ fn unlock(param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
"output-format": {
@@ -935,7 +753,7 @@ fn volume_statistics(param: Value) -> Result<(), Error> {
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
count: {
@@ -946,10 +764,13 @@ fn volume_statistics(param: Value) -> Result<(), Error> {
},
)]
/// Write count (default 1) EOF marks at current position.
-fn weof(count: Option<i32>, param: Value) -> Result<(), Error> {
+fn weof(count: Option<usize>, param: Value) -> Result<(), Error> {
+
+ let count = count.unwrap_or(1);
let mut handle = get_tape_handle(¶m)?;
- handle.mtop(MTCmd::MTWEOF, count.unwrap_or(1), "write EOF mark")?;
+
+ handle.write_filemarks(count)?;
Ok(())
}
@@ -967,7 +788,6 @@ fn main() -> Result<(), Error> {
CliCommand::new(method)
.completion_cb("drive", complete_drive_name)
.completion_cb("device", complete_drive_path)
- .completion_cb("options", complete_option_name)
};
let cmd_def = CliCommandMap::new()
@@ -987,11 +807,7 @@ fn main() -> Result<(), Error> {
.insert("lock", std_cmd(&API_METHOD_LOCK))
.insert("rewind", std_cmd(&API_METHOD_REWIND))
.insert("scan", CliCommand::new(&API_METHOD_SCAN))
- .insert("setblk", CliCommand::new(&API_METHOD_SETBLK).arg_param(&["size"]))
.insert("status", std_cmd(&API_METHOD_STATUS))
- .insert("stoptions", std_cmd(&API_METHOD_ST_OPTIONS).arg_param(&["options"]))
- .insert("stsetoptions", std_cmd(&API_METHOD_ST_SET_OPTIONS).arg_param(&["options"]))
- .insert("stclearoptions", std_cmd(&API_METHOD_ST_CLEAR_OPTIONS).arg_param(&["options"]))
.insert("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS))
.insert("unlock", std_cmd(&API_METHOD_UNLOCK))
.insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
@@ -1005,11 +821,3 @@ fn main() -> Result<(), Error> {
Ok(())
}
-
-// Completion helpers
-pub fn complete_option_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
- DRIVE_OPTIONS
- .keys()
- .map(String::from)
- .collect()
-}
diff --git a/src/bin/pmtx.rs b/src/bin/pmtx.rs
index 85114811..88074002 100644
--- a/src/bin/pmtx.rs
+++ b/src/bin/pmtx.rs
@@ -33,7 +33,7 @@ use proxmox_backup::{
SCSI_CHANGER_PATH_SCHEMA,
CHANGER_NAME_SCHEMA,
ScsiTapeChanger,
- LinuxTapeDrive,
+ LtoTapeDrive,
},
tape::{
linux_tape_changer_list,
@@ -67,7 +67,7 @@ fn get_changer_handle(param: &Value) -> Result<File, Error> {
if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
let (config, _digest) = config::drive::config()?;
- let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+ let drive: LtoTapeDrive = config.lookup("lto", &name)?;
if let Some(changer) = drive.changer {
let changer_config: ScsiTapeChanger = config.lookup("changer", &changer)?;
eprintln!("using device {}", changer_config.path);
diff --git a/src/bin/proxmox_tape/drive.rs b/src/bin/proxmox_tape/drive.rs
index 84bdb524..f8831aec 100644
--- a/src/bin/proxmox_tape/drive.rs
+++ b/src/bin/proxmox_tape/drive.rs
@@ -21,7 +21,7 @@ use proxmox_backup::{
config::drive::{
complete_drive_name,
complete_changer_name,
- complete_linux_drive_name,
+ complete_lto_drive_name,
},
};
@@ -33,13 +33,13 @@ pub fn drive_commands() -> CommandLineInterface {
.insert("config",
CliCommand::new(&API_METHOD_GET_CONFIG)
.arg_param(&["name"])
- .completion_cb("name", complete_linux_drive_name)
+ .completion_cb("name", complete_lto_drive_name)
)
.insert(
"remove",
CliCommand::new(&api2::config::drive::API_METHOD_DELETE_DRIVE)
.arg_param(&["name"])
- .completion_cb("name", complete_linux_drive_name)
+ .completion_cb("name", complete_lto_drive_name)
)
.insert(
"create",
@@ -53,7 +53,7 @@ pub fn drive_commands() -> CommandLineInterface {
"update",
CliCommand::new(&api2::config::drive::API_METHOD_UPDATE_DRIVE)
.arg_param(&["name"])
- .completion_cb("name", complete_linux_drive_name)
+ .completion_cb("name", complete_lto_drive_name)
.completion_cb("path", complete_drive_path)
.completion_cb("changer", complete_changer_name)
)
diff --git a/src/bin/sg-tape-cmd.rs b/src/bin/sg-tape-cmd.rs
index 86998972..a2f0283d 100644
--- a/src/bin/sg-tape-cmd.rs
+++ b/src/bin/sg-tape-cmd.rs
@@ -1,7 +1,5 @@
-/// Tape command implemented using scsi-generic raw commands
-///
-/// SCSI-generic command needs root privileges, so this binary need
-/// to be setuid root.
+/// Helper to run tape commands as root. Currently only required
+/// to read and set the encryption key.
///
/// This command can use STDIN as tape device handle.
@@ -24,41 +22,41 @@ use proxmox_backup::{
config,
backup::Fingerprint,
api2::types::{
- LINUX_DRIVE_PATH_SCHEMA,
+ LTO_DRIVE_PATH_SCHEMA,
DRIVE_NAME_SCHEMA,
TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
MEDIA_SET_UUID_SCHEMA,
- LinuxTapeDrive,
+ LtoTapeDrive,
},
tape::{
drive::{
TapeDriver,
- LinuxTapeHandle,
- open_linux_tape_device,
- check_tape_is_linux_tape_device,
+ LtoTapeHandle,
+ open_lto_tape_device,
+ check_tape_is_lto_tape_device,
},
},
};
-fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
+fn get_tape_handle(param: &Value) -> Result<LtoTapeHandle, Error> {
let handle = if let Some(name) = param["drive"].as_str() {
let (config, _digest) = config::drive::config()?;
- let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+ let drive: LtoTapeDrive = config.lookup("lto", &name)?;
eprintln!("using device {}", drive.path);
drive.open()?
} else if let Some(device) = param["device"].as_str() {
eprintln!("using device {}", device);
- LinuxTapeHandle::new(open_linux_tape_device(&device)?)
+ LtoTapeHandle::new(open_lto_tape_device(&device)?)?
} else if let Some(true) = param["stdin"].as_bool() {
eprintln!("using stdin");
let fd = std::io::stdin().as_raw_fd();
let file = unsafe { File::from_raw_fd(fd) };
- check_tape_is_linux_tape_device(&file)?;
- LinuxTapeHandle::new(file)
+ check_tape_is_lto_tape_device(&file)?;
+ LtoTapeHandle::new(file)?
} else if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
let (config, _digest) = config::drive::config()?;
- let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+ let drive: LtoTapeDrive = config.lookup("lto", &name)?;
eprintln!("using device {}", drive.path);
drive.open()?
} else {
@@ -66,13 +64,13 @@ fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
let mut drive_names = Vec::new();
for (name, (section_type, _)) in config.sections.iter() {
- if section_type != "linux" { continue; }
+ if section_type != "lto" { continue; }
drive_names.push(name);
}
if drive_names.len() == 1 {
let name = drive_names[0];
- let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
+ let drive: LtoTapeDrive = config.lookup("lto", &name)?;
eprintln!("using device {}", drive.path);
drive.open()?
} else {
@@ -83,111 +81,6 @@ fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
Ok(handle)
}
-#[api(
- input: {
- properties: {
- drive: {
- schema: DRIVE_NAME_SCHEMA,
- optional: true,
- },
- device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
- optional: true,
- },
- stdin: {
- description: "Use standard input as device handle.",
- type: bool,
- optional: true,
- },
- },
- },
-)]
-/// Tape/Media Status
-fn status(
- param: Value,
-) -> Result<(), Error> {
-
- let result = proxmox::try_block!({
- let mut handle = get_tape_handle(¶m)?;
- handle.get_drive_and_media_status()
- }).map_err(|err: Error| err.to_string());
-
- println!("{}", serde_json::to_string_pretty(&result)?);
-
- Ok(())
-}
-
-#[api(
- input: {
- properties: {
- drive: {
- schema: DRIVE_NAME_SCHEMA,
- optional: true,
- },
- device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
- optional: true,
- },
- stdin: {
- description: "Use standard input as device handle.",
- type: bool,
- optional: true,
- },
- },
- },
-)]
-/// Read Cartridge Memory (Medium auxiliary memory attributes)
-fn cartridge_memory(
- param: Value,
-) -> Result<(), Error> {
-
- let result = proxmox::try_block!({
- let mut handle = get_tape_handle(¶m)?;
-
- handle.cartridge_memory()
- }).map_err(|err| err.to_string());
-
- println!("{}", serde_json::to_string_pretty(&result)?);
-
- Ok(())
-}
-
-#[api(
- input: {
- properties: {
- drive: {
- schema: DRIVE_NAME_SCHEMA,
- optional: true,
- },
- device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
- optional: true,
- },
- stdin: {
- description: "Use standard input as device handle.",
- type: bool,
- optional: true,
- },
- },
- },
-)]
-/// Read Tape Alert Flags
-fn tape_alert_flags(
- param: Value,
-) -> Result<(), Error> {
-
- let result = proxmox::try_block!({
- let mut handle = get_tape_handle(¶m)?;
-
- let flags = handle.tape_alert_flags()?;
- Ok(flags.bits())
- }).map_err(|err: Error| err.to_string());
-
- println!("{}", serde_json::to_string_pretty(&result)?);
-
- Ok(())
-}
-
#[api(
input: {
properties: {
@@ -204,7 +97,7 @@ fn tape_alert_flags(
optional: true,
},
device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
+ schema: LTO_DRIVE_PATH_SCHEMA,
optional: true,
},
stdin: {
@@ -245,40 +138,6 @@ fn set_encryption(
Ok(())
}
-#[api(
- input: {
- properties: {
- drive: {
- schema: DRIVE_NAME_SCHEMA,
- optional: true,
- },
- device: {
- schema: LINUX_DRIVE_PATH_SCHEMA,
- optional: true,
- },
- stdin: {
- description: "Use standard input as device handle.",
- type: bool,
- optional: true,
- },
- },
- },
-)]
-/// Read volume statistics
-fn volume_statistics(
- param: Value,
-) -> Result<(), Error> {
-
- let result = proxmox::try_block!({
- let mut handle = get_tape_handle(¶m)?;
- handle.volume_statistics()
- }).map_err(|err: Error| err.to_string());
-
- println!("{}", serde_json::to_string_pretty(&result)?);
-
- Ok(())
-}
-
fn main() -> Result<(), Error> {
// check if we are user root or backup
@@ -300,22 +159,6 @@ fn main() -> Result<(), Error> {
}
let cmd_def = CliCommandMap::new()
- .insert(
- "status",
- CliCommand::new(&API_METHOD_STATUS)
- )
- .insert(
- "cartridge-memory",
- CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY)
- )
- .insert(
- "tape-alert-flags",
- CliCommand::new(&API_METHOD_TAPE_ALERT_FLAGS)
- )
- .insert(
- "volume-statistics",
- CliCommand::new(&API_METHOD_VOLUME_STATISTICS)
- )
.insert(
"encryption",
CliCommand::new(&API_METHOD_SET_ENCRYPTION)
diff --git a/src/config/drive.rs b/src/config/drive.rs
index 63839d0d..57f6911f 100644
--- a/src/config/drive.rs
+++ b/src/config/drive.rs
@@ -1,12 +1,12 @@
//! Tape drive/changer configuration
//!
//! This configuration module is based on [`SectionConfig`], and
-//! provides a type safe interface to store [`LinuxTapeDrive`],
+//! provides a type safe interface to store [`LtoTapeDrive`],
//! [`VirtualTapeDrive`] and [`ScsiTapeChanger`] configurations.
//!
//! Drive type [`VirtualTapeDrive`] is only useful for debugging.
//!
-//! [LinuxTapeDrive]: crate::api2::types::LinuxTapeDrive
+//! [LtoTapeDrive]: crate::api2::types::LtoTapeDrive
//! [VirtualTapeDrive]: crate::api2::types::VirtualTapeDrive
//! [ScsiTapeChanger]: crate::api2::types::ScsiTapeChanger
//! [SectionConfig]: proxmox::api::section_config::SectionConfig
@@ -36,7 +36,7 @@ use crate::{
api2::types::{
DRIVE_NAME_SCHEMA,
VirtualTapeDrive,
- LinuxTapeDrive,
+ LtoTapeDrive,
ScsiTapeChanger,
},
};
@@ -57,11 +57,11 @@ fn init() -> SectionConfig {
let plugin = SectionConfigPlugin::new("virtual".to_string(), Some("name".to_string()), obj_schema);
config.register_plugin(plugin);
- let obj_schema = match LinuxTapeDrive::API_SCHEMA {
+ let obj_schema = match LtoTapeDrive::API_SCHEMA {
Schema::Object(ref obj_schema) => obj_schema,
_ => unreachable!(),
};
- let plugin = SectionConfigPlugin::new("linux".to_string(), Some("name".to_string()), obj_schema);
+ let plugin = SectionConfigPlugin::new("lto".to_string(), Some("name".to_string()), obj_schema);
config.register_plugin(plugin);
let obj_schema = match ScsiTapeChanger::API_SCHEMA {
@@ -116,7 +116,7 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
pub fn check_drive_exists(config: &SectionConfigData, drive: &str) -> Result<(), Error> {
match config.sections.get(drive) {
Some((section_type, _)) => {
- if !(section_type == "linux" || section_type == "virtual") {
+ if !(section_type == "lto" || section_type == "virtual") {
bail!("Entry '{}' exists, but is not a tape drive", drive);
}
}
@@ -138,12 +138,12 @@ pub fn complete_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<
}
}
-/// List Linux tape drives
-pub fn complete_linux_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
+/// List Lto tape drives
+pub fn complete_lto_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
match config() {
Ok((data, _digest)) => data.sections.iter()
.filter(|(_id, (section_type, _))| {
- section_type == "linux"
+ section_type == "lto"
})
.map(|(id, _)| id.to_string())
.collect(),
diff --git a/src/tape/changer/mod.rs b/src/tape/changer/mod.rs
index a25df49b..1fc0d435 100644
--- a/src/tape/changer/mod.rs
+++ b/src/tape/changer/mod.rs
@@ -26,7 +26,7 @@ use proxmox::{
use crate::api2::types::{
SLOT_ARRAY_SCHEMA,
ScsiTapeChanger,
- LinuxTapeDrive,
+ LtoTapeDrive,
};
/// Changer element status.
@@ -523,7 +523,7 @@ pub struct MtxMediaChanger {
impl MtxMediaChanger {
- pub fn with_drive_config(drive_config: &LinuxTapeDrive) -> Result<Self, Error> {
+ pub fn with_drive_config(drive_config: &LtoTapeDrive) -> Result<Self, Error> {
let (config, _digest) = crate::config::drive::config()?;
let changer_config: ScsiTapeChanger = match drive_config.changer {
Some(ref changer) => config.lookup("changer", changer)?,
diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs
new file mode 100644
index 00000000..becbad50
--- /dev/null
+++ b/src/tape/drive/lto/mod.rs
@@ -0,0 +1,420 @@
+//! Driver for LTO SCSI tapes
+//!
+//! This is a userspace drive implementation using SG_IO.
+//!
+//! Why we do not use the Linux tape driver:
+//!
+//! - missing features (MAM, Encryption, ...)
+//!
+//! - strange permission handling - only root (or CAP_SYS_RAWIO) can
+//! do SG_IO (SYS_RAW_IO)
+//!
+//! - 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::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::convert::TryFrom;
+
+use anyhow::{bail, format_err, Error};
+use nix::fcntl::{fcntl, FcntlArg, OFlag};
+
+use proxmox::{
+ tools::Uuid,
+ sys::error::SysResult,
+};
+
+use crate::{
+ config,
+ tools::run_command,
+ backup::{
+ Fingerprint,
+ KeyConfig,
+ },
+ api2::types::{
+ MamAttribute,
+ LtoDriveAndMediaStatus,
+ LtoTapeDrive,
+ },
+ tape::{
+ TapeRead,
+ TapeWrite,
+ drive::{
+ TapeDriver,
+ TapeAlertFlags,
+ Lp17VolumeStatistics,
+ mam_extract_media_usage,
+ },
+ file_formats::{
+ PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
+ MediaSetLabel,
+ MediaContentHeader,
+ },
+ },
+};
+
+impl LtoTapeDrive {
+
+ /// Open a tape device
+ ///
+ /// This does additional checks:
+ ///
+ /// - check if it is a non-rewinding tape device
+ /// - check if drive is ready (tape loaded)
+ /// - check block size
+ /// - for autoloader only, try to reload ejected tapes
+ pub fn open(&self) -> Result<LtoTapeHandle, Error> {
+
+ proxmox::try_block!({
+ let file = open_lto_tape_device(&self.path)?;
+
+ let mut handle = LtoTapeHandle::new(file)?;
+
+ if !handle.sg_tape.test_unit_ready().is_ok() {
+ // for autoloader only, try to reload ejected tapes
+ if self.changer.is_some() {
+ let _ = handle.sg_tape.load(); // just try, ignore error
+ }
+ }
+
+ handle.sg_tape.wait_until_ready()?;
+
+ // Only root can set driver options, so we cannot
+ // handle.set_default_options()?;
+
+ Ok(handle)
+ }).map_err(|err: Error| format_err!("open drive '{}' ({}) failed - {}", self.name, self.path, err))
+ }
+}
+
+/// Lto Tape device handle
+pub struct LtoTapeHandle {
+ sg_tape: SgTape,
+}
+
+impl LtoTapeHandle {
+
+ /// Creates a new instance
+ pub fn new(file: File) -> Result<Self, Error> {
+ let sg_tape = SgTape::new(file)?;
+ Ok(Self { sg_tape })
+ }
+
+ /// Set all options we need/want
+ pub fn set_default_options(&self) -> Result<(), Error> {
+ // fixme
+ Ok(())
+ }
+
+ /// Write a single EOF mark without flushing buffers
+ pub fn write_filemarks(&mut self, count: usize) -> Result<(), std::io::Error> {
+ self.sg_tape.write_filemarks(count, false)
+ }
+
+ /// Get Tape and Media status
+ pub fn get_drive_and_media_status(&mut self) -> Result<LtoDriveAndMediaStatus, Error> {
+
+ let (file_number, block_number) = match self.sg_tape.position() {
+ Ok(position) => (
+ Some(position.logical_file_id),
+ Some(position.logical_object_number),
+ ),
+ Err(_) => (None, None),
+ };
+
+ let options = String::from("FIXME");
+
+ let alert_flags = self.tape_alert_flags()
+ .map(|flags| format!("{:?}", flags))
+ .ok();
+
+ let mut status = LtoDriveAndMediaStatus {
+ blocksize: 0, // fixme: remove
+ density: None, // fixme
+ status: String::from("FIXME"),
+ options,
+ alert_flags,
+ file_number,
+ block_number,
+ manufactured: None,
+ bytes_read: None,
+ bytes_written: None,
+ medium_passes: None,
+ medium_wearout: None,
+ volume_mounts: None,
+ };
+
+ if self.sg_tape.test_unit_ready()? {
+
+ 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)
+ }
+
+ pub fn load(&mut self) -> Result<(), Error> {
+ self.sg_tape.load()
+ }
+
+ /// Read Cartridge Memory (MAM Attributes)
+ pub fn cartridge_memory(&mut self) -> Result<Vec<MamAttribute>, Error> {
+ self.sg_tape.cartridge_memory()
+ }
+
+ /// Read Volume Statistics
+ pub fn volume_statistics(&mut self) -> Result<Lp17VolumeStatistics, Error> {
+ self.sg_tape.volume_statistics()
+ }
+}
+
+
+impl TapeDriver for LtoTapeHandle {
+
+ fn sync(&mut self) -> Result<(), Error> {
+ self.sg_tape.sync()?;
+ Ok(())
+ }
+
+ /// Go to the end of the recorded media (for appending files).
+ fn move_to_eom(&mut self) -> Result<(), Error> {
+ self.sg_tape.move_to_eom()
+ }
+
+ fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
+ self.sg_tape.space_filemarks(isize::try_from(count)?)
+ }
+
+ fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error> {
+ self.sg_tape.space_filemarks(-isize::try_from(count)?)
+ }
+
+ fn rewind(&mut self) -> Result<(), Error> {
+ self.sg_tape.rewind()
+ }
+
+ fn current_file_number(&mut self) -> Result<u64, Error> {
+ self.sg_tape.current_file_number()
+ }
+
+ fn erase_media(&mut self, fast: bool) -> Result<(), Error> {
+ self.rewind()?; // important - erase from BOT
+ self.sg_tape.erase_media(fast)
+ }
+
+ fn read_next_file<'a>(&'a mut self) -> Result<Option<Box<dyn TapeRead + 'a>>, std::io::Error> {
+ let reader = self.sg_tape.open_reader()?;
+ let handle = match reader {
+ Some(reader) => {
+ let reader: Box<dyn TapeRead> = Box::new(reader);
+ Some(reader)
+ }
+ None => None,
+ };
+
+ Ok(handle)
+ }
+
+ fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error> {
+ let handle = self.sg_tape.open_writer();
+ Ok(Box::new(handle))
+ }
+
+ fn write_media_set_label(
+ &mut self,
+ media_set_label: &MediaSetLabel,
+ key_config: Option<&KeyConfig>,
+ ) -> Result<(), Error> {
+
+ let file_number = self.current_file_number()?;
+ if file_number != 1 {
+ self.rewind()?;
+ self.forward_space_count_files(1)?; // skip label
+ }
+
+ let file_number = self.current_file_number()?;
+ if file_number != 1 {
+ bail!("write_media_set_label failed - got wrong file number ({} != 1)", file_number);
+ }
+
+ self.set_encryption(None)?;
+
+ { // limit handle scope
+ let mut handle = self.write_file()?;
+
+ let mut value = serde_json::to_value(media_set_label)?;
+ if media_set_label.encryption_key_fingerprint.is_some() {
+ match key_config {
+ Some(key_config) => {
+ value["key-config"] = serde_json::to_value(key_config)?;
+ }
+ None => {
+ bail!("missing encryption key config");
+ }
+ }
+ }
+
+ let raw = serde_json::to_string_pretty(&value)?;
+
+ let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32);
+ handle.write_header(&header, raw.as_bytes())?;
+ handle.finish(false)?;
+ }
+
+ self.sync()?; // sync data to tape
+
+ Ok(())
+ }
+
+ /// Rewind and put the drive off line (Eject media).
+ fn eject_media(&mut self) -> Result<(), Error> {
+ self.sg_tape.eject()
+ }
+
+ /// Read Tape Alert Flags
+ fn tape_alert_flags(&mut self) -> Result<TapeAlertFlags, Error> {
+ self.sg_tape.tape_alert_flags()
+ }
+
+ /// Set or clear encryption key
+ ///
+ /// Note: Only 'root' can read secret encryption keys, so we need
+ /// to spawn setuid binary 'sg-tape-cmd'.
+ fn set_encryption(
+ &mut self,
+ key_fingerprint: Option<(Fingerprint, Uuid)>,
+ ) -> Result<(), Error> {
+
+ if nix::unistd::Uid::effective().is_root() {
+
+ if let Some((ref key_fingerprint, ref uuid)) = key_fingerprint {
+
+ let (key_map, _digest) = config::tape_encryption_keys::load_keys()?;
+ match key_map.get(key_fingerprint) {
+ Some(item) => {
+
+ // derive specialized key for each media-set
+
+ let mut tape_key = [0u8; 32];
+
+ let uuid_bytes: [u8; 16] = uuid.as_bytes().clone();
+
+ openssl::pkcs5::pbkdf2_hmac(
+ &item.key,
+ &uuid_bytes,
+ 10,
+ openssl::hash::MessageDigest::sha256(),
+ &mut tape_key)?;
+
+ return self.sg_tape.set_encryption(Some(tape_key));
+ }
+ None => bail!("unknown tape encryption key '{}'", key_fingerprint),
+ }
+ } else {
+ return self.sg_tape.set_encryption(None);
+ }
+ }
+
+ let output = if let Some((fingerprint, uuid)) = key_fingerprint {
+ let fingerprint = crate::tools::format::as_fingerprint(fingerprint.bytes());
+ run_sg_tape_cmd("encryption", &[
+ "--fingerprint", &fingerprint,
+ "--uuid", &uuid.to_string(),
+ ], self.sg_tape.file_mut().as_raw_fd())?
+ } else {
+ run_sg_tape_cmd("encryption", &[], self.sg_tape.file_mut().as_raw_fd())?
+ };
+ let result: Result<(), String> = serde_json::from_str(&output)?;
+ result.map_err(|err| format_err!("{}", err))
+ }
+}
+
+/// 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");
+ command.args(&[subcmd]);
+ command.args(&["--stdin"]);
+ command.args(args);
+ let device_fd = nix::unistd::dup(fd)?;
+ command.stdin(unsafe { std::process::Stdio::from_raw_fd(device_fd)});
+ run_command(command, None)
+}
diff --git a/src/tape/drive/lto/sg_tape.rs b/src/tape/drive/lto/sg_tape.rs
new file mode 100644
index 00000000..802756fa
--- /dev/null
+++ b/src/tape/drive/lto/sg_tape.rs
@@ -0,0 +1,445 @@
+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 anyhow::{bail, format_err, Error};
+use endian_trait::Endian;
+use nix::fcntl::{fcntl, FcntlArg, OFlag};
+
+use proxmox::{
+ sys::error::SysResult,
+ tools::io::ReadExt,
+};
+
+use crate::{
+ api2::types::{
+ MamAttribute,
+ },
+ tape::{
+ BlockRead,
+ BlockReadStatus,
+ BlockWrite,
+ file_formats::{
+ BlockedWriter,
+ BlockedReader,
+ },
+ drive::{
+ TapeAlertFlags,
+ Lp17VolumeStatistics,
+ read_mam_attributes,
+ read_tape_alert_flags,
+ read_volume_statistics,
+ set_encryption,
+ },
+ },
+ tools::sgutils2::{
+ SgRaw,
+ SenseInfo,
+ ScsiError,
+ InquiryInfo,
+ scsi_inquiry,
+ },
+};
+
+#[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],
+}
+
+pub struct SgTape {
+ file: File,
+}
+
+impl SgTape {
+
+ const SCSI_TAPE_DEFAULT_TIMEOUT: usize = 60*2; // 2 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 })
+ }
+
+ 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)
+ }
+
+ pub fn erase_media(&mut self, _fast: bool) -> Result<(), Error> {
+ // fixme:
+ unimplemented!();
+ }
+
+ 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 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();
+ 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");
+ }
+
+ println!("DATA: {:?}", page);
+
+ Ok(page)
+ }
+
+ pub fn current_file_number(&mut self) -> Result<u64, Error> {
+ let position = self.position()?;
+ Ok(position.logical_file_id)
+ }
+
+ pub fn locate(&mut self) -> Result<(), Error> {
+ // fixme: impl LOCATE
+ unimplemented!();
+ }
+
+ pub fn move_to_eom(&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(&[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))?;
+
+ Ok(())
+ }
+
+ pub fn space_filemarks(&mut self, count: isize) -> 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();
+
+ // Use short command if possible (supported by all drives)
+ if (count <= 0x7fffff) && (count > -0x7fffff) {
+ cmd.extend(&[0x11, 0x01]); // SPACE(6) with 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.extend(&[0x91, 0x01, 0, 0]); // SPACE(16) with filemarks
+ let count: i64 = count as i64;
+ cmd.extend(&count.to_be_bytes());
+ cmd.extend(&[0, 0, 0, 0]);
+ }
+
+ sg_raw.do_command(&cmd)
+ .map_err(|err| format_err!("space filemarks failed - {}", err))?;
+
+ Ok(())
+ }
+
+ 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
+
+ sg_raw.do_command(&cmd)
+ .map_err(|err| proxmox::io_format_err!("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<bool, 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
+
+ // fixme: check sense
+ sg_raw.do_command(&cmd)
+ .map_err(|err| format_err!("unit not ready - {}", err))?;
+
+ Ok(true)
+
+ }
+
+ 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(true) => 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> {
+ 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 > 0xFFFFFF {
+ 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);
+
+ sg_raw.do_out_command(&cmd, data)
+ .map_err(|err| proxmox::io_format_err!("write failed - {}", err))?;
+
+ // fixme: LEOM?
+
+ Ok(false)
+ }
+
+ fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> {
+ let transfer_len = buffer.len();
+
+ if transfer_len > 0xFFFFFF {
+ proxmox::io_bail!("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 Ok(BlockReadStatus::EndOfFile);
+ }
+ Err(ScsiError::Sense(SenseInfo { sense_key: 8, asc: 0, ascq: 5 })) => {
+ return Ok(BlockReadStatus::EndOfStream);
+ }
+ Err(err) => {
+ println!("READ ERR {:?}", err);
+ proxmox::io_bail!("read failed - {}", err);
+ }
+ };
+
+ if data.len() != transfer_len {
+ proxmox::io_bail!("read failed - unexpected block len ({} != {})", data.len(), buffer.len())
+ }
+
+ Ok(BlockReadStatus::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<Option<BlockedReader<SgTapeReader>>, std::io::Error> {
+ let reader = SgTapeReader::new(self);
+ match BlockedReader::open(reader)? {
+ Some(reader) => Ok(Some(reader)),
+ None => Ok(None),
+ }
+ }
+}
+
+pub struct SgTapeReader<'a> {
+ sg_tape: &'a mut SgTape,
+}
+
+impl <'a> SgTapeReader<'a> {
+
+ pub fn new(sg_tape: &'a mut SgTape) -> Self {
+ Self { sg_tape }
+ }
+}
+
+impl <'a> BlockRead for SgTapeReader<'a> {
+
+ fn read_block(&mut self, buffer: &mut [u8]) -> Result<BlockReadStatus, std::io::Error> {
+ self.sg_tape.read_block(buffer)
+ }
+}
+
+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)
+ }
+}
diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs
index 5509728c..71f61642 100644
--- a/src/tape/drive/mod.rs
+++ b/src/tape/drive/mod.rs
@@ -13,8 +13,8 @@ pub use volume_statistics::*;
mod encryption;
pub use encryption::*;
-mod linux_tape;
-pub use linux_tape::*;
+mod lto;
+pub use lto::*;
mod mam;
pub use mam::*;
@@ -49,7 +49,7 @@ use crate::{
},
api2::types::{
VirtualTapeDrive,
- LinuxTapeDrive,
+ LtoTapeDrive,
},
server::{
send_load_media_email,
@@ -263,8 +263,8 @@ pub fn media_changer(
let tape = VirtualTapeDrive::deserialize(config)?;
Ok(Some((Box::new(tape), drive.to_string())))
}
- "linux" => {
- let drive_config = LinuxTapeDrive::deserialize(config)?;
+ "lto" => {
+ let drive_config = LtoTapeDrive::deserialize(config)?;
match drive_config.changer {
Some(ref changer_name) => {
let changer = MtxMediaChanger::with_drive_config(&drive_config)?;
@@ -317,8 +317,8 @@ pub fn open_drive(
let handle = tape.open()?;
Ok(Box::new(handle))
}
- "linux" => {
- let tape = LinuxTapeDrive::deserialize(config)?;
+ "lto" => {
+ let tape = LtoTapeDrive::deserialize(config)?;
let handle = tape.open()?;
Ok(Box::new(handle))
}
@@ -379,8 +379,8 @@ pub fn request_and_load_media(
Ok((handle, media_id))
}
- "linux" => {
- let drive_config = LinuxTapeDrive::deserialize(config)?;
+ "lto" => {
+ let drive_config = LtoTapeDrive::deserialize(config)?;
let label_text = label.label_text.clone();
@@ -546,8 +546,8 @@ fn tape_device_path(
"virtual" => {
VirtualTapeDrive::deserialize(config)?.path
}
- "linux" => {
- LinuxTapeDrive::deserialize(config)?.path
+ "lto" => {
+ LtoTapeDrive::deserialize(config)?.path
}
_ => bail!("unknown drive type '{}' - internal error"),
};
diff --git a/src/tape/linux_list_drives.rs b/src/tape/linux_list_drives.rs
index dacbda2c..78ee6e42 100644
--- a/src/tape/linux_list_drives.rs
+++ b/src/tape/linux_list_drives.rs
@@ -12,14 +12,14 @@ use crate::{
tools::fs::scan_subdir,
};
+lazy_static::lazy_static!{
+ static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
+ regex::Regex::new(r"^sg\d+$").unwrap();
+}
+
/// List linux tape changer devices
pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
- lazy_static::lazy_static!{
- static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
- regex::Regex::new(r"^sg\d+$").unwrap();
- }
-
let mut list = Vec::new();
let dir_iter = match scan_subdir(
@@ -111,20 +111,15 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
list
}
-/// List linux tape devices (non-rewinding)
-pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
-
- lazy_static::lazy_static!{
- static ref NST_TAPE_NAME_REGEX: regex::Regex =
- regex::Regex::new(r"^nst\d+$").unwrap();
- }
+/// List LTO drives
+pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
let mut list = Vec::new();
let dir_iter = match scan_subdir(
libc::AT_FDCWD,
- "/sys/class/scsi_tape",
- &NST_TAPE_NAME_REGEX)
+ "/sys/class/scsi_generic",
+ &SCSI_GENERIC_NAME_REGEX)
{
Err(_) => return list,
Ok(iter) => iter,
@@ -138,7 +133,7 @@ pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
let name = item.file_name().to_str().unwrap().to_string();
- let mut sys_path = PathBuf::from("/sys/class/scsi_tape");
+ let mut sys_path = PathBuf::from("/sys/class/scsi_generic");
sys_path.push(&name);
let device = match udev::Device::from_syspath(&sys_path) {
@@ -151,6 +146,24 @@ pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
Some(devnum) => devnum,
};
+ let parent = match device.parent() {
+ None => continue,
+ Some(parent) => parent,
+ };
+
+ match parent.attribute_value("type") {
+ Some(type_osstr) => {
+ if type_osstr != "1" {
+ continue;
+ }
+ }
+ _ => { continue; }
+ }
+
+ // let mut test_path = sys_path.clone();
+ // test_path.push("device/scsi_tape");
+ // if !test_path.exists() { continue; }
+
let _dev_path = match device.devnode().map(Path::to_owned) {
None => continue,
Some(dev_path) => dev_path,
@@ -174,7 +187,7 @@ pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
.and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
.unwrap_or_else(|| String::from("unknown"));
- let dev_path = format!("/dev/tape/by-id/scsi-{}-nst", serial);
+ let dev_path = format!("/dev/tape/by-id/scsi-{}-sg", serial);
if PathBuf::from(&dev_path).exists() {
list.push(TapeDeviceInfo {
@@ -230,13 +243,13 @@ pub fn lookup_device_identification<'a>(
}
}
-/// Make sure path is a linux tape device
+/// Make sure path is a lto tape device
pub fn check_drive_path(
drives: &[TapeDeviceInfo],
path: &str,
) -> Result<(), Error> {
if lookup_device(drives, path).is_none() {
- bail!("path '{}' is not a linux (non-rewinding) tape device", path);
+ bail!("path '{}' is not a lto SCSI-generic tape device", path);
}
Ok(())
}
@@ -250,5 +263,5 @@ pub fn complete_changer_path(_arg: &str, _param: &HashMap<String, String>) -> Ve
/// List tape device paths
pub fn complete_drive_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
- linux_tape_device_list().iter().map(|v| v.path.clone()).collect()
+ lto_tape_device_list().iter().map(|v| v.path.clone()).collect()
}
--
2.20.1
More information about the pbs-devel
mailing list