tape: implement LTO userspace driver
This commit is contained in:
parent
1336ae8249
commit
a79082a0dd
|
@ -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"
|
|
@ -27,7 +27,7 @@ use crate::{
|
||||||
SLOT_ARRAY_SCHEMA,
|
SLOT_ARRAY_SCHEMA,
|
||||||
EXPORT_SLOT_LIST_SCHEMA,
|
EXPORT_SLOT_LIST_SCHEMA,
|
||||||
ScsiTapeChanger,
|
ScsiTapeChanger,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
},
|
},
|
||||||
tape::{
|
tape::{
|
||||||
linux_tape_changer_list,
|
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),
|
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 {
|
for drive in drive_list {
|
||||||
if let Some(changer) = drive.changer {
|
if let Some(changer) = drive.changer {
|
||||||
if changer == name {
|
if changer == name {
|
||||||
|
|
|
@ -19,12 +19,12 @@ use crate::{
|
||||||
DRIVE_NAME_SCHEMA,
|
DRIVE_NAME_SCHEMA,
|
||||||
CHANGER_NAME_SCHEMA,
|
CHANGER_NAME_SCHEMA,
|
||||||
CHANGER_DRIVENUM_SCHEMA,
|
CHANGER_DRIVENUM_SCHEMA,
|
||||||
LINUX_DRIVE_PATH_SCHEMA,
|
LTO_DRIVE_PATH_SCHEMA,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
ScsiTapeChanger,
|
ScsiTapeChanger,
|
||||||
},
|
},
|
||||||
tape::{
|
tape::{
|
||||||
linux_tape_device_list,
|
lto_tape_device_list,
|
||||||
check_drive_path,
|
check_drive_path,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@ use crate::{
|
||||||
schema: DRIVE_NAME_SCHEMA,
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
},
|
},
|
||||||
path: {
|
path: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
},
|
},
|
||||||
changer: {
|
changer: {
|
||||||
schema: CHANGER_NAME_SCHEMA,
|
schema: CHANGER_NAME_SCHEMA,
|
||||||
|
@ -60,13 +60,13 @@ pub fn create_drive(param: Value) -> Result<(), Error> {
|
||||||
|
|
||||||
let (mut config, _digest) = config::drive::config()?;
|
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 {
|
for drive in existing {
|
||||||
if drive.name == item.name {
|
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)?;
|
config::drive::save_config(&config)?;
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ pub fn create_drive(param: Value) -> Result<(), Error> {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
returns: {
|
returns: {
|
||||||
type: LinuxTapeDrive,
|
type: LtoTapeDrive,
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false),
|
permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false),
|
||||||
|
@ -104,11 +104,11 @@ pub fn get_config(
|
||||||
name: String,
|
name: String,
|
||||||
_param: Value,
|
_param: Value,
|
||||||
mut rpcenv: &mut dyn RpcEnvironment,
|
mut rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<LinuxTapeDrive, Error> {
|
) -> Result<LtoTapeDrive, Error> {
|
||||||
|
|
||||||
let (config, digest) = config::drive::config()?;
|
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();
|
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).",
|
description: "The list of configured drives (with config digest).",
|
||||||
type: Array,
|
type: Array,
|
||||||
items: {
|
items: {
|
||||||
type: LinuxTapeDrive,
|
type: LtoTapeDrive,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
|
@ -135,13 +135,13 @@ pub fn get_config(
|
||||||
pub fn list_drives(
|
pub fn list_drives(
|
||||||
_param: Value,
|
_param: Value,
|
||||||
mut rpcenv: &mut dyn RpcEnvironment,
|
mut rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Vec<LinuxTapeDrive>, Error> {
|
) -> Result<Vec<LtoTapeDrive>, Error> {
|
||||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||||
let user_info = CachedUserInfo::new()?;
|
let user_info = CachedUserInfo::new()?;
|
||||||
|
|
||||||
let (config, digest) = config::drive::config()?;
|
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
|
let drive_list = drive_list
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -176,7 +176,7 @@ pub enum DeletableProperty {
|
||||||
schema: DRIVE_NAME_SCHEMA,
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
},
|
},
|
||||||
path: {
|
path: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
changer: {
|
changer: {
|
||||||
|
@ -225,7 +225,7 @@ pub fn update_drive(
|
||||||
crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
|
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 {
|
if let Some(delete) = delete {
|
||||||
for delete_prop in delete {
|
for delete_prop in delete {
|
||||||
|
@ -240,8 +240,8 @@ pub fn update_drive(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(path) = path {
|
if let Some(path) = path {
|
||||||
let linux_drives = linux_tape_device_list();
|
let lto_drives = lto_tape_device_list();
|
||||||
check_drive_path(&linux_drives, &path)?;
|
check_drive_path(<o_drives, &path)?;
|
||||||
data.path = 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)?;
|
config::drive::save_config(&config)?;
|
||||||
|
|
||||||
|
@ -290,8 +290,8 @@ pub fn delete_drive(name: String, _param: Value) -> Result<(), Error> {
|
||||||
|
|
||||||
match config.sections.get(&name) {
|
match config.sections.get(&name) {
|
||||||
Some((section_type, _)) => {
|
Some((section_type, _)) => {
|
||||||
if section_type != "linux" {
|
if section_type != "lto" {
|
||||||
bail!("Entry '{}' exists, but is not a linux tape drive", name);
|
bail!("Entry '{}' exists, but is not a lto tape drive", name);
|
||||||
}
|
}
|
||||||
config.sections.remove(&name);
|
config.sections.remove(&name);
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::{
|
||||||
Authid,
|
Authid,
|
||||||
CHANGER_NAME_SCHEMA,
|
CHANGER_NAME_SCHEMA,
|
||||||
ChangerListEntry,
|
ChangerListEntry,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
MtxEntryKind,
|
MtxEntryKind,
|
||||||
MtxStatusEntry,
|
MtxStatusEntry,
|
||||||
ScsiTapeChanger,
|
ScsiTapeChanger,
|
||||||
|
@ -88,7 +88,7 @@ pub async fn get_status(
|
||||||
|
|
||||||
inventory.update_online_status(&map)?;
|
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();
|
let mut drive_map: HashMap<u64, String> = HashMap::new();
|
||||||
|
|
||||||
for drive in drive_list {
|
for drive in drive_list {
|
||||||
|
|
|
@ -42,11 +42,11 @@ use crate::{
|
||||||
MEDIA_POOL_NAME_SCHEMA,
|
MEDIA_POOL_NAME_SCHEMA,
|
||||||
Authid,
|
Authid,
|
||||||
DriveListEntry,
|
DriveListEntry,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
MediaIdFlat,
|
MediaIdFlat,
|
||||||
LabelUuidMap,
|
LabelUuidMap,
|
||||||
MamAttribute,
|
MamAttribute,
|
||||||
LinuxDriveAndMediaStatus,
|
LtoDriveAndMediaStatus,
|
||||||
},
|
},
|
||||||
tape::restore::{
|
tape::restore::{
|
||||||
fast_catalog_restore,
|
fast_catalog_restore,
|
||||||
|
@ -62,7 +62,7 @@ use crate::{
|
||||||
lock_media_set,
|
lock_media_set,
|
||||||
lock_media_pool,
|
lock_media_pool,
|
||||||
lock_unassigned_media_pool,
|
lock_unassigned_media_pool,
|
||||||
linux_tape_device_list,
|
lto_tape_device_list,
|
||||||
lookup_device_identification,
|
lookup_device_identification,
|
||||||
file_formats::{
|
file_formats::{
|
||||||
MediaLabel,
|
MediaLabel,
|
||||||
|
@ -70,9 +70,9 @@ use crate::{
|
||||||
},
|
},
|
||||||
drive::{
|
drive::{
|
||||||
TapeDriver,
|
TapeDriver,
|
||||||
LinuxTapeHandle,
|
LtoTapeHandle,
|
||||||
Lp17VolumeStatistics,
|
Lp17VolumeStatistics,
|
||||||
open_linux_tape_device,
|
open_lto_tape_device,
|
||||||
media_changer,
|
media_changer,
|
||||||
required_media_changer,
|
required_media_changer,
|
||||||
open_drive,
|
open_drive,
|
||||||
|
@ -794,9 +794,9 @@ pub fn clean_drive(
|
||||||
|
|
||||||
changer.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
|
// 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
|
// test for critical tape alert flags
|
||||||
if let Ok(alert_flags) = handle.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(),
|
drive.clone(),
|
||||||
"reading cartridge memory".to_string(),
|
"reading cartridge memory".to_string(),
|
||||||
move |config| {
|
move |config| {
|
||||||
let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
|
let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?;
|
||||||
let mut handle = drive_config.open()?;
|
let mut handle = drive_config.open()?;
|
||||||
|
|
||||||
handle.cartridge_memory()
|
handle.cartridge_memory()
|
||||||
|
@ -1174,7 +1174,7 @@ pub async fn volume_statistics(drive: String) -> Result<Lp17VolumeStatistics, Er
|
||||||
drive.clone(),
|
drive.clone(),
|
||||||
"reading volume statistics".to_string(),
|
"reading volume statistics".to_string(),
|
||||||
move |config| {
|
move |config| {
|
||||||
let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
|
let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?;
|
||||||
let mut handle = drive_config.open()?;
|
let mut handle = drive_config.open()?;
|
||||||
|
|
||||||
handle.volume_statistics()
|
handle.volume_statistics()
|
||||||
|
@ -1192,24 +1192,24 @@ pub async fn volume_statistics(drive: String) -> Result<Lp17VolumeStatistics, Er
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
returns: {
|
returns: {
|
||||||
type: LinuxDriveAndMediaStatus,
|
type: LtoDriveAndMediaStatus,
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT, false),
|
permission: &Permission::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT, false),
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
/// Get drive/media status
|
/// 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(
|
run_drive_blocking_task(
|
||||||
drive.clone(),
|
drive.clone(),
|
||||||
"reading drive status".to_string(),
|
"reading drive status".to_string(),
|
||||||
move |config| {
|
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
|
// Note: use open_lto_tape_device, because this also works if no medium loaded
|
||||||
let file = open_linux_tape_device(&drive_config.path)?;
|
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()
|
handle.get_drive_and_media_status()
|
||||||
}
|
}
|
||||||
|
@ -1382,9 +1382,9 @@ pub fn list_drives(
|
||||||
|
|
||||||
let (config, _) = config::drive::config()?;
|
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();
|
let mut list = Vec::new();
|
||||||
|
|
||||||
|
@ -1398,7 +1398,7 @@ pub fn list_drives(
|
||||||
continue;
|
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 state = get_tape_device_state(&config, &drive.name)?;
|
||||||
let entry = DriveListEntry { config: drive, info, state };
|
let entry = DriveListEntry { config: drive, info, state };
|
||||||
list.push(entry);
|
list.push(entry);
|
||||||
|
|
|
@ -15,7 +15,7 @@ use proxmox::{
|
||||||
use crate::{
|
use crate::{
|
||||||
api2::types::TapeDeviceInfo,
|
api2::types::TapeDeviceInfo,
|
||||||
tape::{
|
tape::{
|
||||||
linux_tape_device_list,
|
lto_tape_device_list,
|
||||||
linux_tape_changer_list,
|
linux_tape_changer_list,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -41,7 +41,7 @@ pub mod restore;
|
||||||
/// Scan tape drives
|
/// Scan tape drives
|
||||||
pub fn scan_drives(_param: Value) -> Result<Vec<TapeDeviceInfo>, Error> {
|
pub fn scan_drives(_param: Value) -> Result<Vec<TapeDeviceInfo>, Error> {
|
||||||
|
|
||||||
let list = linux_tape_device_list();
|
let list = lto_tape_device_list();
|
||||||
|
|
||||||
Ok(list)
|
Ok(list)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,8 @@ pub const DRIVE_NAME_SCHEMA: Schema = StringSchema::new("Drive Identifier.")
|
||||||
.max_length(32)
|
.max_length(32)
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
pub const LINUX_DRIVE_PATH_SCHEMA: Schema = StringSchema::new(
|
pub const LTO_DRIVE_PATH_SCHEMA: Schema = StringSchema::new(
|
||||||
"The path to a LINUX non-rewinding SCSI tape device (i.e. '/dev/nst0')")
|
"The path to a LTO SCSI-generic tape device (i.e. '/dev/sg0')")
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
pub const CHANGER_DRIVENUM_SCHEMA: Schema = IntegerSchema::new(
|
pub const CHANGER_DRIVENUM_SCHEMA: Schema = IntegerSchema::new(
|
||||||
|
@ -57,7 +57,7 @@ pub struct VirtualTapeDrive {
|
||||||
schema: DRIVE_NAME_SCHEMA,
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
},
|
},
|
||||||
path: {
|
path: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
},
|
},
|
||||||
changer: {
|
changer: {
|
||||||
schema: CHANGER_NAME_SCHEMA,
|
schema: CHANGER_NAME_SCHEMA,
|
||||||
|
@ -71,8 +71,8 @@ pub struct VirtualTapeDrive {
|
||||||
)]
|
)]
|
||||||
#[derive(Serialize,Deserialize)]
|
#[derive(Serialize,Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
/// Linux SCSI tape driver
|
/// Lto SCSI tape driver
|
||||||
pub struct LinuxTapeDrive {
|
pub struct LtoTapeDrive {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
@ -84,7 +84,7 @@ pub struct LinuxTapeDrive {
|
||||||
#[api(
|
#[api(
|
||||||
properties: {
|
properties: {
|
||||||
config: {
|
config: {
|
||||||
type: LinuxTapeDrive,
|
type: LtoTapeDrive,
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
type: OptionalDeviceIdentification,
|
type: OptionalDeviceIdentification,
|
||||||
|
@ -96,7 +96,7 @@ pub struct LinuxTapeDrive {
|
||||||
/// Drive list entry
|
/// Drive list entry
|
||||||
pub struct DriveListEntry {
|
pub struct DriveListEntry {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub config: LinuxTapeDrive,
|
pub config: LtoTapeDrive,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub info: OptionalDeviceIdentification,
|
pub info: OptionalDeviceIdentification,
|
||||||
/// the state of the drive if locked
|
/// the state of the drive if locked
|
||||||
|
@ -169,11 +169,11 @@ impl TryFrom<u8> for TapeDensity {
|
||||||
)]
|
)]
|
||||||
#[derive(Serialize,Deserialize)]
|
#[derive(Serialize,Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[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
|
/// Media related data is optional - only set if there is a medium
|
||||||
/// loaded.
|
/// loaded.
|
||||||
pub struct LinuxDriveAndMediaStatus {
|
pub struct LtoDriveAndMediaStatus {
|
||||||
/// Block size (0 is variable size)
|
/// Block size (0 is variable size)
|
||||||
pub blocksize: u32,
|
pub blocksize: u32,
|
||||||
/// Tape density
|
/// Tape density
|
||||||
|
@ -181,17 +181,17 @@ pub struct LinuxDriveAndMediaStatus {
|
||||||
pub density: Option<TapeDensity>,
|
pub density: Option<TapeDensity>,
|
||||||
/// Status flags
|
/// Status flags
|
||||||
pub status: String,
|
pub status: String,
|
||||||
/// Linux Driver Options
|
/// Lto Driver Options
|
||||||
pub options: String,
|
pub options: String,
|
||||||
/// Tape Alert Flags
|
/// Tape Alert Flags
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub alert_flags: Option<String>,
|
pub alert_flags: Option<String>,
|
||||||
/// Current file number
|
/// Current file number
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub file_number: Option<u32>,
|
pub file_number: Option<u64>,
|
||||||
/// Current block number
|
/// Current block number
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub block_number: Option<u32>,
|
pub block_number: Option<u64>,
|
||||||
/// Medium Manufacture Date (epoch)
|
/// Medium Manufacture Date (epoch)
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub manufactured: Option<i64>,
|
pub manufactured: Option<i64>,
|
||||||
|
|
314
src/bin/pmt.rs
314
src/bin/pmt.rs
|
@ -1,18 +1,18 @@
|
||||||
/// Control magnetic tape drive operation
|
/// Control magnetic tape drive operation
|
||||||
///
|
///
|
||||||
/// This is a Rust implementation, meant to replace the 'mt' command
|
/// This is a Rust implementation, using the Proxmox userspace tape
|
||||||
/// line tool.
|
/// driver. This is meant as replacement fot the 'mt' command line
|
||||||
|
/// tool.
|
||||||
///
|
///
|
||||||
/// Features:
|
/// Features:
|
||||||
///
|
///
|
||||||
/// - written in Rust
|
/// - written in Rust
|
||||||
|
/// - use Proxmox userspace driver (using SG_IO)
|
||||||
/// - optional json output format
|
/// - optional json output format
|
||||||
/// - support tape alert flags
|
/// - support tape alert flags
|
||||||
/// - support volume statistics
|
/// - support volume statistics
|
||||||
/// - read cartridge memory
|
/// - read cartridge memory
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ pub const RECORD_COUNT_SCHEMA: Schema =
|
||||||
.schema();
|
.schema();
|
||||||
|
|
||||||
pub const DRIVE_OPTION_SCHEMA: Schema = StringSchema::new(
|
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();
|
.schema();
|
||||||
|
|
||||||
pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
|
pub const DRIVE_OPTION_LIST_SCHEMA: Schema =
|
||||||
|
@ -57,103 +57,60 @@ use proxmox_backup::{
|
||||||
drive::complete_drive_name,
|
drive::complete_drive_name,
|
||||||
},
|
},
|
||||||
api2::types::{
|
api2::types::{
|
||||||
LINUX_DRIVE_PATH_SCHEMA,
|
LTO_DRIVE_PATH_SCHEMA,
|
||||||
DRIVE_NAME_SCHEMA,
|
DRIVE_NAME_SCHEMA,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
},
|
},
|
||||||
tape::{
|
tape::{
|
||||||
complete_drive_path,
|
complete_drive_path,
|
||||||
linux_tape_device_list,
|
lto_tape_device_list,
|
||||||
drive::{
|
drive::{
|
||||||
linux_mtio::{MTCmd, SetDrvBufferOptions},
|
|
||||||
TapeDriver,
|
TapeDriver,
|
||||||
LinuxTapeHandle,
|
LtoTapeHandle,
|
||||||
open_linux_tape_device,
|
open_lto_tape_device,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
lazy_static::lazy_static!{
|
fn get_tape_handle(param: &Value) -> Result<LtoTapeHandle, Error> {
|
||||||
|
|
||||||
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> {
|
|
||||||
|
|
||||||
if let Some(name) = param["drive"].as_str() {
|
if let Some(name) = param["drive"].as_str() {
|
||||||
let (config, _digest) = config::drive::config()?;
|
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);
|
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() {
|
if let Some(device) = param["device"].as_str() {
|
||||||
eprintln!("using device {}", device);
|
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") {
|
if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
|
||||||
let (config, _digest) = config::drive::config()?;
|
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);
|
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") {
|
if let Ok(device) = std::env::var("TAPE") {
|
||||||
eprintln!("using device {}", device);
|
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 (config, _digest) = config::drive::config()?;
|
||||||
|
|
||||||
let mut drive_names = Vec::new();
|
let mut drive_names = Vec::new();
|
||||||
for (name, (section_type, _)) in config.sections.iter() {
|
for (name, (section_type, _)) in config.sections.iter() {
|
||||||
if section_type != "linux" { continue; }
|
if section_type != "lto" { continue; }
|
||||||
drive_names.push(name);
|
drive_names.push(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if drive_names.len() == 1 {
|
if drive_names.len() == 1 {
|
||||||
let name = drive_names[0];
|
let name = drive_names[0];
|
||||||
let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
|
let drive: LtoTapeDrive = config.lookup("lto", &name)?;
|
||||||
eprintln!("using device {}", drive.path);
|
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");
|
bail!("no drive/device specified");
|
||||||
|
@ -167,7 +124,7 @@ fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
|
@ -200,7 +157,7 @@ fn asf(count: usize, param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
|
@ -230,7 +187,7 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
count: {
|
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
|
/// This leaves the tape positioned at the first block of the file
|
||||||
/// that is count - 1 files before the current 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)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -261,7 +219,7 @@ fn bsfm(count: i32, param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
|
@ -275,7 +233,9 @@ fn bsr(count: i32, param: Value) -> Result<(), Error> {
|
||||||
|
|
||||||
let mut handle = get_tape_handle(¶m)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -289,7 +249,7 @@ fn bsr(count: i32, param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
"output-format": {
|
"output-format": {
|
||||||
|
@ -340,7 +300,7 @@ fn cartridge_memory(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
"output-format": {
|
"output-format": {
|
||||||
|
@ -389,7 +349,7 @@ fn tape_alert_flags(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -413,7 +373,7 @@ fn eject(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -437,7 +397,7 @@ fn eod(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
fast: {
|
fast: {
|
||||||
|
@ -466,7 +426,7 @@ fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
|
@ -495,7 +455,7 @@ fn fsf(count: usize, param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
count: {
|
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
|
/// This leaves the tape positioned at the last block of the file that
|
||||||
/// is count - 1 files past the current file.
|
/// 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)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -526,7 +487,7 @@ fn fsfm(count: i32, param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
|
@ -540,7 +501,8 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> {
|
||||||
|
|
||||||
let mut handle = get_tape_handle(¶m)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -554,7 +516,7 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -564,7 +526,7 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> {
|
||||||
fn load(param: Value) -> Result<(), Error> {
|
fn load(param: Value) -> Result<(), Error> {
|
||||||
|
|
||||||
let mut handle = get_tape_handle(¶m)?;
|
let mut handle = get_tape_handle(¶m)?;
|
||||||
handle.mtload()?;
|
handle.load()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -578,7 +540,7 @@ fn load(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -589,7 +551,8 @@ fn lock(param: Value) -> Result<(), Error> {
|
||||||
|
|
||||||
let mut handle = get_tape_handle(¶m)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -603,7 +566,7 @@ fn lock(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -634,7 +597,7 @@ fn scan(param: Value) -> Result<(), Error> {
|
||||||
|
|
||||||
let output_format = get_output_format(¶m);
|
let output_format = get_output_format(¶m);
|
||||||
|
|
||||||
let list = linux_tape_device_list();
|
let list = lto_tape_device_list();
|
||||||
|
|
||||||
if output_format == "json-pretty" {
|
if output_format == "json-pretty" {
|
||||||
println!("{}", serde_json::to_string_pretty(&list)?);
|
println!("{}", serde_json::to_string_pretty(&list)?);
|
||||||
|
@ -657,7 +620,6 @@ fn scan(param: Value) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -666,36 +628,7 @@ fn scan(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_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: {
|
|
||||||
drive: {
|
|
||||||
schema: DRIVE_NAME_SCHEMA,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
device: {
|
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
"output-format": {
|
"output-format": {
|
||||||
|
@ -745,123 +678,7 @@ fn status(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_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: {
|
|
||||||
drive: {
|
|
||||||
schema: DRIVE_NAME_SCHEMA,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
device: {
|
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -872,7 +689,8 @@ fn unlock(param: Value) -> Result<(), Error> {
|
||||||
|
|
||||||
let mut handle = get_tape_handle(¶m)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -886,7 +704,7 @@ fn unlock(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
"output-format": {
|
"output-format": {
|
||||||
|
@ -935,7 +753,7 @@ fn volume_statistics(param: Value) -> Result<(), Error> {
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
|
@ -946,10 +764,13 @@ fn volume_statistics(param: Value) -> Result<(), Error> {
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
/// Write count (default 1) EOF marks at current position.
|
/// 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)?;
|
let mut handle = get_tape_handle(¶m)?;
|
||||||
handle.mtop(MTCmd::MTWEOF, count.unwrap_or(1), "write EOF mark")?;
|
|
||||||
|
handle.write_filemarks(count)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -967,7 +788,6 @@ fn main() -> Result<(), Error> {
|
||||||
CliCommand::new(method)
|
CliCommand::new(method)
|
||||||
.completion_cb("drive", complete_drive_name)
|
.completion_cb("drive", complete_drive_name)
|
||||||
.completion_cb("device", complete_drive_path)
|
.completion_cb("device", complete_drive_path)
|
||||||
.completion_cb("options", complete_option_name)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let cmd_def = CliCommandMap::new()
|
let cmd_def = CliCommandMap::new()
|
||||||
|
@ -987,11 +807,7 @@ fn main() -> Result<(), Error> {
|
||||||
.insert("lock", std_cmd(&API_METHOD_LOCK))
|
.insert("lock", std_cmd(&API_METHOD_LOCK))
|
||||||
.insert("rewind", std_cmd(&API_METHOD_REWIND))
|
.insert("rewind", std_cmd(&API_METHOD_REWIND))
|
||||||
.insert("scan", CliCommand::new(&API_METHOD_SCAN))
|
.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("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("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS))
|
||||||
.insert("unlock", std_cmd(&API_METHOD_UNLOCK))
|
.insert("unlock", std_cmd(&API_METHOD_UNLOCK))
|
||||||
.insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
|
.insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
|
||||||
|
@ -1005,11 +821,3 @@ fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Completion helpers
|
|
||||||
pub fn complete_option_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
|
|
||||||
DRIVE_OPTIONS
|
|
||||||
.keys()
|
|
||||||
.map(String::from)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ use proxmox_backup::{
|
||||||
SCSI_CHANGER_PATH_SCHEMA,
|
SCSI_CHANGER_PATH_SCHEMA,
|
||||||
CHANGER_NAME_SCHEMA,
|
CHANGER_NAME_SCHEMA,
|
||||||
ScsiTapeChanger,
|
ScsiTapeChanger,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
},
|
},
|
||||||
tape::{
|
tape::{
|
||||||
linux_tape_changer_list,
|
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") {
|
if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
|
||||||
let (config, _digest) = config::drive::config()?;
|
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 {
|
if let Some(changer) = drive.changer {
|
||||||
let changer_config: ScsiTapeChanger = config.lookup("changer", &changer)?;
|
let changer_config: ScsiTapeChanger = config.lookup("changer", &changer)?;
|
||||||
eprintln!("using device {}", changer_config.path);
|
eprintln!("using device {}", changer_config.path);
|
||||||
|
|
|
@ -21,7 +21,7 @@ use proxmox_backup::{
|
||||||
config::drive::{
|
config::drive::{
|
||||||
complete_drive_name,
|
complete_drive_name,
|
||||||
complete_changer_name,
|
complete_changer_name,
|
||||||
complete_linux_drive_name,
|
complete_lto_drive_name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,13 +33,13 @@ pub fn drive_commands() -> CommandLineInterface {
|
||||||
.insert("config",
|
.insert("config",
|
||||||
CliCommand::new(&API_METHOD_GET_CONFIG)
|
CliCommand::new(&API_METHOD_GET_CONFIG)
|
||||||
.arg_param(&["name"])
|
.arg_param(&["name"])
|
||||||
.completion_cb("name", complete_linux_drive_name)
|
.completion_cb("name", complete_lto_drive_name)
|
||||||
)
|
)
|
||||||
.insert(
|
.insert(
|
||||||
"remove",
|
"remove",
|
||||||
CliCommand::new(&api2::config::drive::API_METHOD_DELETE_DRIVE)
|
CliCommand::new(&api2::config::drive::API_METHOD_DELETE_DRIVE)
|
||||||
.arg_param(&["name"])
|
.arg_param(&["name"])
|
||||||
.completion_cb("name", complete_linux_drive_name)
|
.completion_cb("name", complete_lto_drive_name)
|
||||||
)
|
)
|
||||||
.insert(
|
.insert(
|
||||||
"create",
|
"create",
|
||||||
|
@ -53,7 +53,7 @@ pub fn drive_commands() -> CommandLineInterface {
|
||||||
"update",
|
"update",
|
||||||
CliCommand::new(&api2::config::drive::API_METHOD_UPDATE_DRIVE)
|
CliCommand::new(&api2::config::drive::API_METHOD_UPDATE_DRIVE)
|
||||||
.arg_param(&["name"])
|
.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("path", complete_drive_path)
|
||||||
.completion_cb("changer", complete_changer_name)
|
.completion_cb("changer", complete_changer_name)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
/// Tape command implemented using scsi-generic raw commands
|
/// Helper to run tape commands as root. Currently only required
|
||||||
///
|
/// to read and set the encryption key.
|
||||||
/// SCSI-generic command needs root privileges, so this binary need
|
|
||||||
/// to be setuid root.
|
|
||||||
///
|
///
|
||||||
/// This command can use STDIN as tape device handle.
|
/// This command can use STDIN as tape device handle.
|
||||||
|
|
||||||
|
@ -24,41 +22,41 @@ use proxmox_backup::{
|
||||||
config,
|
config,
|
||||||
backup::Fingerprint,
|
backup::Fingerprint,
|
||||||
api2::types::{
|
api2::types::{
|
||||||
LINUX_DRIVE_PATH_SCHEMA,
|
LTO_DRIVE_PATH_SCHEMA,
|
||||||
DRIVE_NAME_SCHEMA,
|
DRIVE_NAME_SCHEMA,
|
||||||
TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
|
TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA,
|
||||||
MEDIA_SET_UUID_SCHEMA,
|
MEDIA_SET_UUID_SCHEMA,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
},
|
},
|
||||||
tape::{
|
tape::{
|
||||||
drive::{
|
drive::{
|
||||||
TapeDriver,
|
TapeDriver,
|
||||||
LinuxTapeHandle,
|
LtoTapeHandle,
|
||||||
open_linux_tape_device,
|
open_lto_tape_device,
|
||||||
check_tape_is_linux_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 handle = if let Some(name) = param["drive"].as_str() {
|
||||||
let (config, _digest) = config::drive::config()?;
|
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);
|
eprintln!("using device {}", drive.path);
|
||||||
drive.open()?
|
drive.open()?
|
||||||
} else if let Some(device) = param["device"].as_str() {
|
} else if let Some(device) = param["device"].as_str() {
|
||||||
eprintln!("using device {}", device);
|
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() {
|
} else if let Some(true) = param["stdin"].as_bool() {
|
||||||
eprintln!("using stdin");
|
eprintln!("using stdin");
|
||||||
let fd = std::io::stdin().as_raw_fd();
|
let fd = std::io::stdin().as_raw_fd();
|
||||||
let file = unsafe { File::from_raw_fd(fd) };
|
let file = unsafe { File::from_raw_fd(fd) };
|
||||||
check_tape_is_linux_tape_device(&file)?;
|
check_tape_is_lto_tape_device(&file)?;
|
||||||
LinuxTapeHandle::new(file)
|
LtoTapeHandle::new(file)?
|
||||||
} else if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
|
} else if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") {
|
||||||
let (config, _digest) = config::drive::config()?;
|
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);
|
eprintln!("using device {}", drive.path);
|
||||||
drive.open()?
|
drive.open()?
|
||||||
} else {
|
} else {
|
||||||
|
@ -66,13 +64,13 @@ fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
|
||||||
|
|
||||||
let mut drive_names = Vec::new();
|
let mut drive_names = Vec::new();
|
||||||
for (name, (section_type, _)) in config.sections.iter() {
|
for (name, (section_type, _)) in config.sections.iter() {
|
||||||
if section_type != "linux" { continue; }
|
if section_type != "lto" { continue; }
|
||||||
drive_names.push(name);
|
drive_names.push(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if drive_names.len() == 1 {
|
if drive_names.len() == 1 {
|
||||||
let name = drive_names[0];
|
let name = drive_names[0];
|
||||||
let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
|
let drive: LtoTapeDrive = config.lookup("lto", &name)?;
|
||||||
eprintln!("using device {}", drive.path);
|
eprintln!("using device {}", drive.path);
|
||||||
drive.open()?
|
drive.open()?
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,111 +81,6 @@ fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
|
||||||
Ok(handle)
|
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(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -204,7 +97,7 @@ fn tape_alert_flags(
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
device: {
|
device: {
|
||||||
schema: LINUX_DRIVE_PATH_SCHEMA,
|
schema: LTO_DRIVE_PATH_SCHEMA,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
stdin: {
|
stdin: {
|
||||||
|
@ -245,40 +138,6 @@ fn set_encryption(
|
||||||
Ok(())
|
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> {
|
fn main() -> Result<(), Error> {
|
||||||
|
|
||||||
// check if we are user root or backup
|
// check if we are user root or backup
|
||||||
|
@ -300,22 +159,6 @@ fn main() -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmd_def = CliCommandMap::new()
|
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(
|
.insert(
|
||||||
"encryption",
|
"encryption",
|
||||||
CliCommand::new(&API_METHOD_SET_ENCRYPTION)
|
CliCommand::new(&API_METHOD_SET_ENCRYPTION)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
//! Tape drive/changer configuration
|
//! Tape drive/changer configuration
|
||||||
//!
|
//!
|
||||||
//! This configuration module is based on [`SectionConfig`], and
|
//! 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.
|
//! [`VirtualTapeDrive`] and [`ScsiTapeChanger`] configurations.
|
||||||
//!
|
//!
|
||||||
//! Drive type [`VirtualTapeDrive`] is only useful for debugging.
|
//! Drive type [`VirtualTapeDrive`] is only useful for debugging.
|
||||||
//!
|
//!
|
||||||
//! [LinuxTapeDrive]: crate::api2::types::LinuxTapeDrive
|
//! [LtoTapeDrive]: crate::api2::types::LtoTapeDrive
|
||||||
//! [VirtualTapeDrive]: crate::api2::types::VirtualTapeDrive
|
//! [VirtualTapeDrive]: crate::api2::types::VirtualTapeDrive
|
||||||
//! [ScsiTapeChanger]: crate::api2::types::ScsiTapeChanger
|
//! [ScsiTapeChanger]: crate::api2::types::ScsiTapeChanger
|
||||||
//! [SectionConfig]: proxmox::api::section_config::SectionConfig
|
//! [SectionConfig]: proxmox::api::section_config::SectionConfig
|
||||||
|
@ -36,7 +36,7 @@ use crate::{
|
||||||
api2::types::{
|
api2::types::{
|
||||||
DRIVE_NAME_SCHEMA,
|
DRIVE_NAME_SCHEMA,
|
||||||
VirtualTapeDrive,
|
VirtualTapeDrive,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
ScsiTapeChanger,
|
ScsiTapeChanger,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -57,11 +57,11 @@ fn init() -> SectionConfig {
|
||||||
let plugin = SectionConfigPlugin::new("virtual".to_string(), Some("name".to_string()), obj_schema);
|
let plugin = SectionConfigPlugin::new("virtual".to_string(), Some("name".to_string()), obj_schema);
|
||||||
config.register_plugin(plugin);
|
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,
|
Schema::Object(ref obj_schema) => obj_schema,
|
||||||
_ => unreachable!(),
|
_ => 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);
|
config.register_plugin(plugin);
|
||||||
|
|
||||||
let obj_schema = match ScsiTapeChanger::API_SCHEMA {
|
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> {
|
pub fn check_drive_exists(config: &SectionConfigData, drive: &str) -> Result<(), Error> {
|
||||||
match config.sections.get(drive) {
|
match config.sections.get(drive) {
|
||||||
Some((section_type, _)) => {
|
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);
|
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
|
/// List Lto tape drives
|
||||||
pub fn complete_linux_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
|
pub fn complete_lto_drive_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
|
||||||
match config() {
|
match config() {
|
||||||
Ok((data, _digest)) => data.sections.iter()
|
Ok((data, _digest)) => data.sections.iter()
|
||||||
.filter(|(_id, (section_type, _))| {
|
.filter(|(_id, (section_type, _))| {
|
||||||
section_type == "linux"
|
section_type == "lto"
|
||||||
})
|
})
|
||||||
.map(|(id, _)| id.to_string())
|
.map(|(id, _)| id.to_string())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
|
@ -26,7 +26,7 @@ use proxmox::{
|
||||||
use crate::api2::types::{
|
use crate::api2::types::{
|
||||||
SLOT_ARRAY_SCHEMA,
|
SLOT_ARRAY_SCHEMA,
|
||||||
ScsiTapeChanger,
|
ScsiTapeChanger,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Changer element status.
|
/// Changer element status.
|
||||||
|
@ -523,7 +523,7 @@ pub struct MtxMediaChanger {
|
||||||
|
|
||||||
impl 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 (config, _digest) = crate::config::drive::config()?;
|
||||||
let changer_config: ScsiTapeChanger = match drive_config.changer {
|
let changer_config: ScsiTapeChanger = match drive_config.changer {
|
||||||
Some(ref changer) => config.lookup("changer", changer)?,
|
Some(ref changer) => config.lookup("changer", changer)?,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,8 +13,8 @@ pub use volume_statistics::*;
|
||||||
mod encryption;
|
mod encryption;
|
||||||
pub use encryption::*;
|
pub use encryption::*;
|
||||||
|
|
||||||
mod linux_tape;
|
mod lto;
|
||||||
pub use linux_tape::*;
|
pub use lto::*;
|
||||||
|
|
||||||
mod mam;
|
mod mam;
|
||||||
pub use mam::*;
|
pub use mam::*;
|
||||||
|
@ -49,7 +49,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
api2::types::{
|
api2::types::{
|
||||||
VirtualTapeDrive,
|
VirtualTapeDrive,
|
||||||
LinuxTapeDrive,
|
LtoTapeDrive,
|
||||||
},
|
},
|
||||||
server::{
|
server::{
|
||||||
send_load_media_email,
|
send_load_media_email,
|
||||||
|
@ -263,8 +263,8 @@ pub fn media_changer(
|
||||||
let tape = VirtualTapeDrive::deserialize(config)?;
|
let tape = VirtualTapeDrive::deserialize(config)?;
|
||||||
Ok(Some((Box::new(tape), drive.to_string())))
|
Ok(Some((Box::new(tape), drive.to_string())))
|
||||||
}
|
}
|
||||||
"linux" => {
|
"lto" => {
|
||||||
let drive_config = LinuxTapeDrive::deserialize(config)?;
|
let drive_config = LtoTapeDrive::deserialize(config)?;
|
||||||
match drive_config.changer {
|
match drive_config.changer {
|
||||||
Some(ref changer_name) => {
|
Some(ref changer_name) => {
|
||||||
let changer = MtxMediaChanger::with_drive_config(&drive_config)?;
|
let changer = MtxMediaChanger::with_drive_config(&drive_config)?;
|
||||||
|
@ -317,8 +317,8 @@ pub fn open_drive(
|
||||||
let handle = tape.open()?;
|
let handle = tape.open()?;
|
||||||
Ok(Box::new(handle))
|
Ok(Box::new(handle))
|
||||||
}
|
}
|
||||||
"linux" => {
|
"lto" => {
|
||||||
let tape = LinuxTapeDrive::deserialize(config)?;
|
let tape = LtoTapeDrive::deserialize(config)?;
|
||||||
let handle = tape.open()?;
|
let handle = tape.open()?;
|
||||||
Ok(Box::new(handle))
|
Ok(Box::new(handle))
|
||||||
}
|
}
|
||||||
|
@ -379,8 +379,8 @@ pub fn request_and_load_media(
|
||||||
|
|
||||||
Ok((handle, media_id))
|
Ok((handle, media_id))
|
||||||
}
|
}
|
||||||
"linux" => {
|
"lto" => {
|
||||||
let drive_config = LinuxTapeDrive::deserialize(config)?;
|
let drive_config = LtoTapeDrive::deserialize(config)?;
|
||||||
|
|
||||||
let label_text = label.label_text.clone();
|
let label_text = label.label_text.clone();
|
||||||
|
|
||||||
|
@ -546,8 +546,8 @@ fn tape_device_path(
|
||||||
"virtual" => {
|
"virtual" => {
|
||||||
VirtualTapeDrive::deserialize(config)?.path
|
VirtualTapeDrive::deserialize(config)?.path
|
||||||
}
|
}
|
||||||
"linux" => {
|
"lto" => {
|
||||||
LinuxTapeDrive::deserialize(config)?.path
|
LtoTapeDrive::deserialize(config)?.path
|
||||||
}
|
}
|
||||||
_ => bail!("unknown drive type '{}' - internal error"),
|
_ => bail!("unknown drive type '{}' - internal error"),
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,14 +12,14 @@ use crate::{
|
||||||
tools::fs::scan_subdir,
|
tools::fs::scan_subdir,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// List linux tape changer devices
|
|
||||||
pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
|
|
||||||
|
|
||||||
lazy_static::lazy_static!{
|
lazy_static::lazy_static!{
|
||||||
static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
|
static ref SCSI_GENERIC_NAME_REGEX: regex::Regex =
|
||||||
regex::Regex::new(r"^sg\d+$").unwrap();
|
regex::Regex::new(r"^sg\d+$").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List linux tape changer devices
|
||||||
|
pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
|
||||||
|
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
|
|
||||||
let dir_iter = match scan_subdir(
|
let dir_iter = match scan_subdir(
|
||||||
|
@ -111,20 +111,15 @@ pub fn linux_tape_changer_list() -> Vec<TapeDeviceInfo> {
|
||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List linux tape devices (non-rewinding)
|
/// List LTO drives
|
||||||
pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
|
pub fn lto_tape_device_list() -> Vec<TapeDeviceInfo> {
|
||||||
|
|
||||||
lazy_static::lazy_static!{
|
|
||||||
static ref NST_TAPE_NAME_REGEX: regex::Regex =
|
|
||||||
regex::Regex::new(r"^nst\d+$").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
|
|
||||||
let dir_iter = match scan_subdir(
|
let dir_iter = match scan_subdir(
|
||||||
libc::AT_FDCWD,
|
libc::AT_FDCWD,
|
||||||
"/sys/class/scsi_tape",
|
"/sys/class/scsi_generic",
|
||||||
&NST_TAPE_NAME_REGEX)
|
&SCSI_GENERIC_NAME_REGEX)
|
||||||
{
|
{
|
||||||
Err(_) => return list,
|
Err(_) => return list,
|
||||||
Ok(iter) => iter,
|
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 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);
|
sys_path.push(&name);
|
||||||
|
|
||||||
let device = match udev::Device::from_syspath(&sys_path) {
|
let device = match udev::Device::from_syspath(&sys_path) {
|
||||||
|
@ -151,6 +146,24 @@ pub fn linux_tape_device_list() -> Vec<TapeDeviceInfo> {
|
||||||
Some(devnum) => devnum,
|
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) {
|
let _dev_path = match device.devnode().map(Path::to_owned) {
|
||||||
None => continue,
|
None => continue,
|
||||||
Some(dev_path) => dev_path,
|
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 })
|
.and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None })
|
||||||
.unwrap_or_else(|| String::from("unknown"));
|
.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() {
|
if PathBuf::from(&dev_path).exists() {
|
||||||
list.push(TapeDeviceInfo {
|
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(
|
pub fn check_drive_path(
|
||||||
drives: &[TapeDeviceInfo],
|
drives: &[TapeDeviceInfo],
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if lookup_device(drives, path).is_none() {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -250,5 +263,5 @@ pub fn complete_changer_path(_arg: &str, _param: &HashMap<String, String>) -> Ve
|
||||||
|
|
||||||
/// List tape device paths
|
/// List tape device paths
|
||||||
pub fn complete_drive_path(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
|
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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue