From: Dietmar Maurer <dietmar@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH 03/11] tape: implement LTO userspace driver
Date: Wed, 7 Apr 2021 12:23:00 +0200 [thread overview]
Message-ID: <20210407102308.9750-4-dietmar@proxmox.com> (raw)
In-Reply-To: <20210407102308.9750-1-dietmar@proxmox.com>
---
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
next prev parent reply other threads:[~2021-04-07 10:24 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-04-07 10:22 [pbs-devel] [PATCH 00/11] Userspace tape driver Dietmar Maurer
2021-04-07 10:22 ` [pbs-devel] [PATCH 01/11] tape: introduce trait BlockRead Dietmar Maurer
2021-04-07 10:22 ` [pbs-devel] [PATCH 02/11] tape: introduce trait BlockWrite Dietmar Maurer
2021-04-07 10:23 ` Dietmar Maurer [this message]
2021-04-07 10:23 ` [pbs-devel] [PATCH 04/11] tape: implement format/erase Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 05/11] tape: fix LEOM handling Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 06/11] tape: make fsf/bsf driver specific Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 07/11] tape: make sure there is a filemark at the end of the tape Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 08/11] sgutils2: add scsi_mode_sense helper Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 09/11] tape: correctly set/display drive option Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 10/11] tape: pmt - re-implement fsr/bsr Dietmar Maurer
2021-04-07 10:23 ` [pbs-devel] [PATCH 11/11] tape: pmt - re-implement lock/unlock command Dietmar Maurer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210407102308.9750-4-dietmar@proxmox.com \
--to=dietmar@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal