From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 6783F7121A for ; Wed, 7 Apr 2021 12:24:50 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5D2E5F55C for ; Wed, 7 Apr 2021 12:24:20 +0200 (CEST) Received: from elsa.proxmox.com (unknown [94.136.29.99]) by firstgate.proxmox.com (Proxmox) with ESMTP id 30DBBF346 for ; Wed, 7 Apr 2021 12:24:07 +0200 (CEST) Received: by elsa.proxmox.com (Postfix, from userid 0) id 0978DAEAF22; Wed, 7 Apr 2021 12:24:07 +0200 (CEST) From: Dietmar Maurer To: pbs-devel@lists.proxmox.com Date: Wed, 7 Apr 2021 12:23:00 +0200 Message-Id: <20210407102308.9750-4-dietmar@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210407102308.9750-1-dietmar@proxmox.com> References: <20210407102308.9750-1-dietmar@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 1 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RDNS_NONE 1.274 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [pmt.rs, changer.rs, drive.rs, drive.name, sg-tape-cmd.rs, pmtx.rs, mod.rs, item.name] X-Mailman-Approved-At: Wed, 07 Apr 2021 12:31:00 +0200 Subject: [pbs-devel] [PATCH 03/11] tape: implement LTO userspace driver X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 07 Apr 2021 10:24:50 -0000 --- debian/proxmox-backup-server.udev | 18 ++ src/api2/config/changer.rs | 4 +- src/api2/config/drive.rs | 44 +-- src/api2/tape/changer.rs | 4 +- src/api2/tape/drive.rs | 36 +-- src/api2/tape/mod.rs | 4 +- src/api2/types/tape/drive.rs | 24 +- src/bin/pmt.rs | 314 ++++----------------- src/bin/pmtx.rs | 4 +- src/bin/proxmox_tape/drive.rs | 8 +- src/bin/sg-tape-cmd.rs | 189 ++----------- src/config/drive.rs | 18 +- src/tape/changer/mod.rs | 4 +- src/tape/drive/lto/mod.rs | 420 ++++++++++++++++++++++++++++ src/tape/drive/lto/sg_tape.rs | 445 ++++++++++++++++++++++++++++++ src/tape/drive/mod.rs | 22 +- src/tape/linux_list_drives.rs | 51 ++-- 17 files changed, 1078 insertions(+), 531 deletions(-) create mode 100644 debian/proxmox-backup-server.udev create mode 100644 src/tape/drive/lto/mod.rs create mode 100644 src/tape/drive/lto/sg_tape.rs diff --git a/debian/proxmox-backup-server.udev b/debian/proxmox-backup-server.udev new file mode 100644 index 00000000..afdfb2bc --- /dev/null +++ b/debian/proxmox-backup-server.udev @@ -0,0 +1,18 @@ +# do not edit this file, it will be overwritten on update + +# persistent storage links: /dev/tape/{by-id,by-path} + +ACTION=="remove", GOTO="persistent_storage_tape_end" +ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end" + +# also see: /lib/udev/rules.d/60-persistent-storage-tape.rules + +SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="1", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \ + SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}-sg" + +# iSCSI devices from the same host have all the same ID_SERIAL, +# but additionally a property named ID_SCSI_SERIAL. +SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="1", ENV{ID_SCSI_SERIAL}=="?*", \ + SYMLINK+="tape/by-id/scsi-$env{ID_SCSI_SERIAL}-sg" + +LABEL="persistent_storage_tape_end" diff --git a/src/api2/config/changer.rs b/src/api2/config/changer.rs index 380eb089..62a0fba4 100644 --- a/src/api2/config/changer.rs +++ b/src/api2/config/changer.rs @@ -27,7 +27,7 @@ use crate::{ SLOT_ARRAY_SCHEMA, EXPORT_SLOT_LIST_SCHEMA, ScsiTapeChanger, - LinuxTapeDrive, + LtoTapeDrive, }, tape::{ linux_tape_changer_list, @@ -303,7 +303,7 @@ pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> { None => bail!("Delete changer '{}' failed - no such entry", name), } - let drive_list: Vec = config.convert_to_typed_array("linux")?; + let drive_list: Vec = config.convert_to_typed_array("lto")?; for drive in drive_list { if let Some(changer) = drive.changer { if changer == name { diff --git a/src/api2/config/drive.rs b/src/api2/config/drive.rs index 98337024..e2626e14 100644 --- a/src/api2/config/drive.rs +++ b/src/api2/config/drive.rs @@ -19,12 +19,12 @@ use crate::{ DRIVE_NAME_SCHEMA, CHANGER_NAME_SCHEMA, CHANGER_DRIVENUM_SCHEMA, - LINUX_DRIVE_PATH_SCHEMA, - LinuxTapeDrive, + LTO_DRIVE_PATH_SCHEMA, + LtoTapeDrive, ScsiTapeChanger, }, tape::{ - linux_tape_device_list, + lto_tape_device_list, check_drive_path, }, }; @@ -37,7 +37,7 @@ use crate::{ schema: DRIVE_NAME_SCHEMA, }, path: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, }, changer: { schema: CHANGER_NAME_SCHEMA, @@ -60,13 +60,13 @@ pub fn create_drive(param: Value) -> Result<(), Error> { let (mut config, _digest) = config::drive::config()?; - let item: LinuxTapeDrive = serde_json::from_value(param)?; + let item: LtoTapeDrive = serde_json::from_value(param)?; - let linux_drives = linux_tape_device_list(); + let lto_drives = lto_tape_device_list(); - check_drive_path(&linux_drives, &item.path)?; + check_drive_path(<o_drives, &item.path)?; - let existing: Vec = config.convert_to_typed_array("linux")?; + let existing: Vec = config.convert_to_typed_array("lto")?; for drive in existing { if drive.name == item.name { @@ -77,7 +77,7 @@ pub fn create_drive(param: Value) -> Result<(), Error> { } } - config.set_data(&item.name, "linux", &item)?; + config.set_data(&item.name, "lto", &item)?; config::drive::save_config(&config)?; @@ -93,7 +93,7 @@ pub fn create_drive(param: Value) -> Result<(), Error> { }, }, returns: { - type: LinuxTapeDrive, + type: LtoTapeDrive, }, access: { permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false), @@ -104,11 +104,11 @@ pub fn get_config( name: String, _param: Value, mut rpcenv: &mut dyn RpcEnvironment, -) -> Result { +) -> Result { let (config, digest) = config::drive::config()?; - let data: LinuxTapeDrive = config.lookup("linux", &name)?; + let data: LtoTapeDrive = config.lookup("lto", &name)?; rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); @@ -123,7 +123,7 @@ pub fn get_config( description: "The list of configured drives (with config digest).", type: Array, items: { - type: LinuxTapeDrive, + type: LtoTapeDrive, }, }, access: { @@ -135,13 +135,13 @@ pub fn get_config( pub fn list_drives( _param: Value, mut rpcenv: &mut dyn RpcEnvironment, -) -> Result, Error> { +) -> Result, Error> { let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let user_info = CachedUserInfo::new()?; let (config, digest) = config::drive::config()?; - let drive_list: Vec = config.convert_to_typed_array("linux")?; + let drive_list: Vec = config.convert_to_typed_array("lto")?; let drive_list = drive_list .into_iter() @@ -176,7 +176,7 @@ pub enum DeletableProperty { schema: DRIVE_NAME_SCHEMA, }, path: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, changer: { @@ -225,7 +225,7 @@ pub fn update_drive( crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; } - let mut data: LinuxTapeDrive = config.lookup("linux", &name)?; + let mut data: LtoTapeDrive = config.lookup("lto", &name)?; if let Some(delete) = delete { for delete_prop in delete { @@ -240,8 +240,8 @@ pub fn update_drive( } if let Some(path) = path { - let linux_drives = linux_tape_device_list(); - check_drive_path(&linux_drives, &path)?; + let lto_drives = lto_tape_device_list(); + check_drive_path(<o_drives, &path)?; data.path = path; } @@ -261,7 +261,7 @@ pub fn update_drive( } } - config.set_data(&name, "linux", &data)?; + config.set_data(&name, "lto", &data)?; config::drive::save_config(&config)?; @@ -290,8 +290,8 @@ pub fn delete_drive(name: String, _param: Value) -> Result<(), Error> { match config.sections.get(&name) { Some((section_type, _)) => { - if section_type != "linux" { - bail!("Entry '{}' exists, but is not a linux tape drive", name); + if section_type != "lto" { + bail!("Entry '{}' exists, but is not a lto tape drive", name); } config.sections.remove(&name); }, diff --git a/src/api2/tape/changer.rs b/src/api2/tape/changer.rs index e2d1edc0..59dfe044 100644 --- a/src/api2/tape/changer.rs +++ b/src/api2/tape/changer.rs @@ -20,7 +20,7 @@ use crate::{ Authid, CHANGER_NAME_SCHEMA, ChangerListEntry, - LinuxTapeDrive, + LtoTapeDrive, MtxEntryKind, MtxStatusEntry, ScsiTapeChanger, @@ -88,7 +88,7 @@ pub async fn get_status( inventory.update_online_status(&map)?; - let drive_list: Vec = config.convert_to_typed_array("linux")?; + let drive_list: Vec = config.convert_to_typed_array("lto")?; let mut drive_map: HashMap = HashMap::new(); for drive in drive_list { diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index b17f4203..80d17a27 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -42,11 +42,11 @@ use crate::{ MEDIA_POOL_NAME_SCHEMA, Authid, DriveListEntry, - LinuxTapeDrive, + LtoTapeDrive, MediaIdFlat, LabelUuidMap, MamAttribute, - LinuxDriveAndMediaStatus, + LtoDriveAndMediaStatus, }, tape::restore::{ fast_catalog_restore, @@ -62,7 +62,7 @@ use crate::{ lock_media_set, lock_media_pool, lock_unassigned_media_pool, - linux_tape_device_list, + lto_tape_device_list, lookup_device_identification, file_formats::{ MediaLabel, @@ -70,9 +70,9 @@ use crate::{ }, drive::{ TapeDriver, - LinuxTapeHandle, + LtoTapeHandle, Lp17VolumeStatistics, - open_linux_tape_device, + open_lto_tape_device, media_changer, required_media_changer, open_drive, @@ -794,9 +794,9 @@ pub fn clean_drive( changer.clean_drive()?; - if let Ok(drive_config) = config.lookup::("linux", &drive) { + if let Ok(drive_config) = config.lookup::("lto", &drive) { // Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open - let mut handle = LinuxTapeHandle::new(open_linux_tape_device(&drive_config.path)?); + let mut handle = LtoTapeHandle::new(open_lto_tape_device(&drive_config.path)?)?; // test for critical tape alert flags if let Ok(alert_flags) = handle.tape_alert_flags() { @@ -1144,7 +1144,7 @@ pub async fn cartridge_memory(drive: String) -> Result, Error> drive.clone(), "reading cartridge memory".to_string(), move |config| { - let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; + let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?; let mut handle = drive_config.open()?; handle.cartridge_memory() @@ -1174,7 +1174,7 @@ pub async fn volume_statistics(drive: String) -> Result Result Result { +pub async fn status(drive: String) -> Result { run_drive_blocking_task( drive.clone(), "reading drive status".to_string(), move |config| { - let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; + let drive_config: LtoTapeDrive = config.lookup("lto", &drive)?; - // Note: use open_linux_tape_device, because this also works if no medium loaded - let file = open_linux_tape_device(&drive_config.path)?; + // Note: use open_lto_tape_device, because this also works if no medium loaded + let file = open_lto_tape_device(&drive_config.path)?; - let mut handle = LinuxTapeHandle::new(file); + let mut handle = LtoTapeHandle::new(file)?; handle.get_drive_and_media_status() } @@ -1382,9 +1382,9 @@ pub fn list_drives( let (config, _) = config::drive::config()?; - let linux_drives = linux_tape_device_list(); + let lto_drives = lto_tape_device_list(); - let drive_list: Vec = config.convert_to_typed_array("linux")?; + let drive_list: Vec = config.convert_to_typed_array("lto")?; let mut list = Vec::new(); @@ -1398,7 +1398,7 @@ pub fn list_drives( continue; } - let info = lookup_device_identification(&linux_drives, &drive.path); + let info = lookup_device_identification(<o_drives, &drive.path); let state = get_tape_device_state(&config, &drive.name)?; let entry = DriveListEntry { config: drive, info, state }; list.push(entry); diff --git a/src/api2/tape/mod.rs b/src/api2/tape/mod.rs index b560f077..219a721b 100644 --- a/src/api2/tape/mod.rs +++ b/src/api2/tape/mod.rs @@ -15,7 +15,7 @@ use proxmox::{ use crate::{ api2::types::TapeDeviceInfo, tape::{ - linux_tape_device_list, + lto_tape_device_list, linux_tape_changer_list, }, }; @@ -41,7 +41,7 @@ pub mod restore; /// Scan tape drives pub fn scan_drives(_param: Value) -> Result, Error> { - let list = linux_tape_device_list(); + let list = lto_tape_device_list(); Ok(list) } diff --git a/src/api2/types/tape/drive.rs b/src/api2/types/tape/drive.rs index 2fd480ac..058e544f 100644 --- a/src/api2/types/tape/drive.rs +++ b/src/api2/types/tape/drive.rs @@ -21,8 +21,8 @@ pub const DRIVE_NAME_SCHEMA: Schema = StringSchema::new("Drive Identifier.") .max_length(32) .schema(); -pub const LINUX_DRIVE_PATH_SCHEMA: Schema = StringSchema::new( - "The path to a LINUX non-rewinding SCSI tape device (i.e. '/dev/nst0')") +pub const LTO_DRIVE_PATH_SCHEMA: Schema = StringSchema::new( + "The path to a LTO SCSI-generic tape device (i.e. '/dev/sg0')") .schema(); pub const CHANGER_DRIVENUM_SCHEMA: Schema = IntegerSchema::new( @@ -57,7 +57,7 @@ pub struct VirtualTapeDrive { schema: DRIVE_NAME_SCHEMA, }, path: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, }, changer: { schema: CHANGER_NAME_SCHEMA, @@ -71,8 +71,8 @@ pub struct VirtualTapeDrive { )] #[derive(Serialize,Deserialize)] #[serde(rename_all = "kebab-case")] -/// Linux SCSI tape driver -pub struct LinuxTapeDrive { +/// Lto SCSI tape driver +pub struct LtoTapeDrive { pub name: String, pub path: String, #[serde(skip_serializing_if="Option::is_none")] @@ -84,7 +84,7 @@ pub struct LinuxTapeDrive { #[api( properties: { config: { - type: LinuxTapeDrive, + type: LtoTapeDrive, }, info: { type: OptionalDeviceIdentification, @@ -96,7 +96,7 @@ pub struct LinuxTapeDrive { /// Drive list entry pub struct DriveListEntry { #[serde(flatten)] - pub config: LinuxTapeDrive, + pub config: LtoTapeDrive, #[serde(flatten)] pub info: OptionalDeviceIdentification, /// the state of the drive if locked @@ -169,11 +169,11 @@ impl TryFrom for TapeDensity { )] #[derive(Serialize,Deserialize)] #[serde(rename_all = "kebab-case")] -/// Drive/Media status for Linux SCSI drives. +/// Drive/Media status for Lto SCSI drives. /// /// Media related data is optional - only set if there is a medium /// loaded. -pub struct LinuxDriveAndMediaStatus { +pub struct LtoDriveAndMediaStatus { /// Block size (0 is variable size) pub blocksize: u32, /// Tape density @@ -181,17 +181,17 @@ pub struct LinuxDriveAndMediaStatus { pub density: Option, /// Status flags pub status: String, - /// Linux Driver Options + /// Lto Driver Options pub options: String, /// Tape Alert Flags #[serde(skip_serializing_if="Option::is_none")] pub alert_flags: Option, /// Current file number #[serde(skip_serializing_if="Option::is_none")] - pub file_number: Option, + pub file_number: Option, /// Current block number #[serde(skip_serializing_if="Option::is_none")] - pub block_number: Option, + pub block_number: Option, /// Medium Manufacture Date (epoch) #[serde(skip_serializing_if="Option::is_none")] pub manufactured: Option, diff --git a/src/bin/pmt.rs b/src/bin/pmt.rs index a097df2c..df3ad9ec 100644 --- a/src/bin/pmt.rs +++ b/src/bin/pmt.rs @@ -1,18 +1,18 @@ /// Control magnetic tape drive operation /// -/// This is a Rust implementation, meant to replace the 'mt' command -/// line tool. +/// This is a Rust implementation, using the Proxmox userspace tape +/// driver. This is meant as replacement fot the 'mt' command line +/// tool. /// /// Features: /// /// - written in Rust +/// - use Proxmox userspace driver (using SG_IO) /// - optional json output format /// - support tape alert flags /// - support volume statistics /// - read cartridge memory -use std::collections::HashMap; - use anyhow::{bail, Error}; use serde_json::Value; @@ -43,7 +43,7 @@ pub const RECORD_COUNT_SCHEMA: Schema = .schema(); pub const DRIVE_OPTION_SCHEMA: Schema = StringSchema::new( - "Linux Tape Driver Option, either numeric value or option name.") + "Lto Tape Driver Option, either numeric value or option name.") .schema(); pub const DRIVE_OPTION_LIST_SCHEMA: Schema = @@ -57,103 +57,60 @@ use proxmox_backup::{ drive::complete_drive_name, }, api2::types::{ - LINUX_DRIVE_PATH_SCHEMA, + LTO_DRIVE_PATH_SCHEMA, DRIVE_NAME_SCHEMA, - LinuxTapeDrive, + LtoTapeDrive, }, tape::{ complete_drive_path, - linux_tape_device_list, + lto_tape_device_list, drive::{ - linux_mtio::{MTCmd, SetDrvBufferOptions}, TapeDriver, - LinuxTapeHandle, - open_linux_tape_device, + LtoTapeHandle, + open_lto_tape_device, }, }, }; -lazy_static::lazy_static!{ - - static ref DRIVE_OPTIONS: HashMap = { - 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) -> Result { - - let mut value = SetDrvBufferOptions::empty(); - - for option in options.iter() { - if let Ok::(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 { +fn get_tape_handle(param: &Value) -> Result { if let Some(name) = param["drive"].as_str() { let (config, _digest) = config::drive::config()?; - let drive: LinuxTapeDrive = config.lookup("linux", &name)?; + let drive: LtoTapeDrive = config.lookup("lto", &name)?; eprintln!("using device {}", drive.path); - return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?)) + return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?); } if let Some(device) = param["device"].as_str() { eprintln!("using device {}", device); - return Ok(LinuxTapeHandle::new(open_linux_tape_device(&device)?)) + return LtoTapeHandle::new(open_lto_tape_device(&device)?); } if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") { let (config, _digest) = config::drive::config()?; - let drive: LinuxTapeDrive = config.lookup("linux", &name)?; + let drive: LtoTapeDrive = config.lookup("lto", &name)?; eprintln!("using device {}", drive.path); - return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?)) + return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?); } if let Ok(device) = std::env::var("TAPE") { eprintln!("using device {}", device); - return Ok(LinuxTapeHandle::new(open_linux_tape_device(&device)?)) + return LtoTapeHandle::new(open_lto_tape_device(&device)?); } let (config, _digest) = config::drive::config()?; let mut drive_names = Vec::new(); for (name, (section_type, _)) in config.sections.iter() { - if section_type != "linux" { continue; } + if section_type != "lto" { continue; } drive_names.push(name); } if drive_names.len() == 1 { let name = drive_names[0]; - let drive: LinuxTapeDrive = config.lookup("linux", &name)?; + let drive: LtoTapeDrive = config.lookup("lto", &name)?; eprintln!("using device {}", drive.path); - return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?)) + return LtoTapeHandle::new(open_lto_tape_device(&drive.path)?); } bail!("no drive/device specified"); @@ -167,7 +124,7 @@ fn get_tape_handle(param: &Value) -> Result { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, count: { @@ -200,7 +157,7 @@ fn asf(count: usize, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, count: { @@ -230,7 +187,7 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, count: { @@ -243,11 +200,12 @@ fn bsf(count: usize, param: Value) -> Result<(), Error> { /// /// This leaves the tape positioned at the first block of the file /// that is count - 1 files before the current file. -fn bsfm(count: i32, param: Value) -> Result<(), Error> { +fn bsfm(count: usize, param: Value) -> Result<(), Error> { let mut handle = get_tape_handle(¶m)?; - handle.mtop(MTCmd::MTBSFM, count, "bsfm")?; + handle.backward_space_count_files(count)?; + handle.forward_space_count_files(1)?; Ok(()) } @@ -261,7 +219,7 @@ fn bsfm(count: i32, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, count: { @@ -275,7 +233,9 @@ fn bsr(count: i32, param: Value) -> Result<(), Error> { let mut handle = get_tape_handle(¶m)?; - handle.mtop(MTCmd::MTBSR, count, "backward space records")?; + unimplemented!(); + + // fixme: handle.mtop(MTCmd::MTBSR, count, "backward space records")?; Ok(()) } @@ -289,7 +249,7 @@ fn bsr(count: i32, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, "output-format": { @@ -340,7 +300,7 @@ fn cartridge_memory(param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, "output-format": { @@ -389,7 +349,7 @@ fn tape_alert_flags(param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, }, @@ -413,7 +373,7 @@ fn eject(param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, }, @@ -437,7 +397,7 @@ fn eod(param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, fast: { @@ -466,7 +426,7 @@ fn erase(fast: Option, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, count: { @@ -495,7 +455,7 @@ fn fsf(count: usize, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, count: { @@ -508,11 +468,12 @@ fn fsf(count: usize, param: Value) -> Result<(), Error> { /// /// This leaves the tape positioned at the last block of the file that /// is count - 1 files past the current file. -fn fsfm(count: i32, param: Value) -> Result<(), Error> { +fn fsfm(count: usize, param: Value) -> Result<(), Error> { let mut handle = get_tape_handle(¶m)?; - handle.mtop(MTCmd::MTFSFM, count, "fsfm")?; + handle.forward_space_count_files(count)?; + handle.backward_space_count_files(1)?; Ok(()) } @@ -526,7 +487,7 @@ fn fsfm(count: i32, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, count: { @@ -540,7 +501,8 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> { let mut handle = get_tape_handle(¶m)?; - handle.mtop(MTCmd::MTFSR, count, "forward space records")?; + unimplemented!(); + // fixme: handle.mtop(MTCmd::MTFSR, count, "forward space records")?; Ok(()) } @@ -554,7 +516,7 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, }, @@ -564,7 +526,7 @@ fn fsr(count: i32, param: Value) -> Result<(), Error> { fn load(param: Value) -> Result<(), Error> { let mut handle = get_tape_handle(¶m)?; - handle.mtload()?; + handle.load()?; Ok(()) } @@ -578,7 +540,7 @@ fn load(param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, }, @@ -589,7 +551,8 @@ fn lock(param: Value) -> Result<(), Error> { let mut handle = get_tape_handle(¶m)?; - handle.mtop(MTCmd::MTLOCK, 1, "lock tape drive door")?; + unimplemented!(); + // fixme: handle.mtop(MTCmd::MTLOCK, 1, "lock tape drive door")?; Ok(()) } @@ -603,7 +566,7 @@ fn lock(param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, }, @@ -634,7 +597,7 @@ fn scan(param: Value) -> Result<(), Error> { let output_format = get_output_format(¶m); - let list = linux_tape_device_list(); + let list = lto_tape_device_list(); if output_format == "json-pretty" { println!("{}", serde_json::to_string_pretty(&list)?); @@ -657,36 +620,6 @@ fn scan(param: Value) -> Result<(), Error> { Ok(()) } - -#[api( - input: { - properties: { - drive: { - schema: DRIVE_NAME_SCHEMA, - optional: true, - }, - device: { - schema: LINUX_DRIVE_PATH_SCHEMA, - optional: true, - }, - size: { - description: "Block size in bytes.", - minimum: 0, - }, - }, - }, -)] -/// Set the block size of the drive -fn setblk(size: i32, param: Value) -> Result<(), Error> { - - let mut handle = get_tape_handle(¶m)?; - - handle.mtop(MTCmd::MTSETBLK, size, "set block size")?; - - Ok(()) -} - - #[api( input: { properties: { @@ -695,7 +628,7 @@ fn setblk(size: i32, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, "output-format": { @@ -737,122 +670,6 @@ fn status(param: Value) -> Result<(), Error> { } -#[api( - input: { - properties: { - drive: { - schema: DRIVE_NAME_SCHEMA, - optional: true, - }, - device: { - schema: LINUX_DRIVE_PATH_SCHEMA, - optional: true, - }, - options: { - schema: DRIVE_OPTION_LIST_SCHEMA, - optional: true, - }, - defaults: { - description: "Set default options (buffer-writes async-writes read-ahead can-bsr).", - type: bool, - optional: true, - }, - }, - }, -)] -/// Set device driver options (root only) -fn st_options( - options: Option>, - defaults: Option, - 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, 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, param: Value) -> Result<(), Error> { - - let handle = get_tape_handle(¶m)?; - - let value = parse_drive_options(options)?; - - handle.drive_buffer_clear_options(value)?; - - Ok(()) -} - - #[api( input: { properties: { @@ -861,7 +678,7 @@ fn st_clear_options(options: Vec, param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, }, @@ -872,7 +689,8 @@ fn unlock(param: Value) -> Result<(), Error> { let mut handle = get_tape_handle(¶m)?; - handle.mtop(MTCmd::MTUNLOCK, 1, "unlock tape drive door")?; + unimplemented!(); + //handle.mtop(MTCmd::MTUNLOCK, 1, "unlock tape drive door")?; Ok(()) } @@ -886,7 +704,7 @@ fn unlock(param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, "output-format": { @@ -935,7 +753,7 @@ fn volume_statistics(param: Value) -> Result<(), Error> { optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, count: { @@ -946,10 +764,13 @@ fn volume_statistics(param: Value) -> Result<(), Error> { }, )] /// Write count (default 1) EOF marks at current position. -fn weof(count: Option, param: Value) -> Result<(), Error> { +fn weof(count: Option, param: Value) -> Result<(), Error> { + + let count = count.unwrap_or(1); let mut handle = get_tape_handle(¶m)?; - handle.mtop(MTCmd::MTWEOF, count.unwrap_or(1), "write EOF mark")?; + + handle.write_filemarks(count)?; Ok(()) } @@ -967,7 +788,6 @@ fn main() -> Result<(), Error> { CliCommand::new(method) .completion_cb("drive", complete_drive_name) .completion_cb("device", complete_drive_path) - .completion_cb("options", complete_option_name) }; let cmd_def = CliCommandMap::new() @@ -987,11 +807,7 @@ fn main() -> Result<(), Error> { .insert("lock", std_cmd(&API_METHOD_LOCK)) .insert("rewind", std_cmd(&API_METHOD_REWIND)) .insert("scan", CliCommand::new(&API_METHOD_SCAN)) - .insert("setblk", CliCommand::new(&API_METHOD_SETBLK).arg_param(&["size"])) .insert("status", std_cmd(&API_METHOD_STATUS)) - .insert("stoptions", std_cmd(&API_METHOD_ST_OPTIONS).arg_param(&["options"])) - .insert("stsetoptions", std_cmd(&API_METHOD_ST_SET_OPTIONS).arg_param(&["options"])) - .insert("stclearoptions", std_cmd(&API_METHOD_ST_CLEAR_OPTIONS).arg_param(&["options"])) .insert("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS)) .insert("unlock", std_cmd(&API_METHOD_UNLOCK)) .insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS)) @@ -1005,11 +821,3 @@ fn main() -> Result<(), Error> { Ok(()) } - -// Completion helpers -pub fn complete_option_name(_arg: &str, _param: &HashMap) -> Vec { - DRIVE_OPTIONS - .keys() - .map(String::from) - .collect() -} diff --git a/src/bin/pmtx.rs b/src/bin/pmtx.rs index 85114811..88074002 100644 --- a/src/bin/pmtx.rs +++ b/src/bin/pmtx.rs @@ -33,7 +33,7 @@ use proxmox_backup::{ SCSI_CHANGER_PATH_SCHEMA, CHANGER_NAME_SCHEMA, ScsiTapeChanger, - LinuxTapeDrive, + LtoTapeDrive, }, tape::{ linux_tape_changer_list, @@ -67,7 +67,7 @@ fn get_changer_handle(param: &Value) -> Result { if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") { let (config, _digest) = config::drive::config()?; - let drive: LinuxTapeDrive = config.lookup("linux", &name)?; + let drive: LtoTapeDrive = config.lookup("lto", &name)?; if let Some(changer) = drive.changer { let changer_config: ScsiTapeChanger = config.lookup("changer", &changer)?; eprintln!("using device {}", changer_config.path); diff --git a/src/bin/proxmox_tape/drive.rs b/src/bin/proxmox_tape/drive.rs index 84bdb524..f8831aec 100644 --- a/src/bin/proxmox_tape/drive.rs +++ b/src/bin/proxmox_tape/drive.rs @@ -21,7 +21,7 @@ use proxmox_backup::{ config::drive::{ complete_drive_name, complete_changer_name, - complete_linux_drive_name, + complete_lto_drive_name, }, }; @@ -33,13 +33,13 @@ pub fn drive_commands() -> CommandLineInterface { .insert("config", CliCommand::new(&API_METHOD_GET_CONFIG) .arg_param(&["name"]) - .completion_cb("name", complete_linux_drive_name) + .completion_cb("name", complete_lto_drive_name) ) .insert( "remove", CliCommand::new(&api2::config::drive::API_METHOD_DELETE_DRIVE) .arg_param(&["name"]) - .completion_cb("name", complete_linux_drive_name) + .completion_cb("name", complete_lto_drive_name) ) .insert( "create", @@ -53,7 +53,7 @@ pub fn drive_commands() -> CommandLineInterface { "update", CliCommand::new(&api2::config::drive::API_METHOD_UPDATE_DRIVE) .arg_param(&["name"]) - .completion_cb("name", complete_linux_drive_name) + .completion_cb("name", complete_lto_drive_name) .completion_cb("path", complete_drive_path) .completion_cb("changer", complete_changer_name) ) diff --git a/src/bin/sg-tape-cmd.rs b/src/bin/sg-tape-cmd.rs index 86998972..a2f0283d 100644 --- a/src/bin/sg-tape-cmd.rs +++ b/src/bin/sg-tape-cmd.rs @@ -1,7 +1,5 @@ -/// Tape command implemented using scsi-generic raw commands -/// -/// SCSI-generic command needs root privileges, so this binary need -/// to be setuid root. +/// Helper to run tape commands as root. Currently only required +/// to read and set the encryption key. /// /// This command can use STDIN as tape device handle. @@ -24,41 +22,41 @@ use proxmox_backup::{ config, backup::Fingerprint, api2::types::{ - LINUX_DRIVE_PATH_SCHEMA, + LTO_DRIVE_PATH_SCHEMA, DRIVE_NAME_SCHEMA, TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA, MEDIA_SET_UUID_SCHEMA, - LinuxTapeDrive, + LtoTapeDrive, }, tape::{ drive::{ TapeDriver, - LinuxTapeHandle, - open_linux_tape_device, - check_tape_is_linux_tape_device, + LtoTapeHandle, + open_lto_tape_device, + check_tape_is_lto_tape_device, }, }, }; -fn get_tape_handle(param: &Value) -> Result { +fn get_tape_handle(param: &Value) -> Result { let handle = if let Some(name) = param["drive"].as_str() { let (config, _digest) = config::drive::config()?; - let drive: LinuxTapeDrive = config.lookup("linux", &name)?; + let drive: LtoTapeDrive = config.lookup("lto", &name)?; eprintln!("using device {}", drive.path); drive.open()? } else if let Some(device) = param["device"].as_str() { eprintln!("using device {}", device); - LinuxTapeHandle::new(open_linux_tape_device(&device)?) + LtoTapeHandle::new(open_lto_tape_device(&device)?)? } else if let Some(true) = param["stdin"].as_bool() { eprintln!("using stdin"); let fd = std::io::stdin().as_raw_fd(); let file = unsafe { File::from_raw_fd(fd) }; - check_tape_is_linux_tape_device(&file)?; - LinuxTapeHandle::new(file) + check_tape_is_lto_tape_device(&file)?; + LtoTapeHandle::new(file)? } else if let Ok(name) = std::env::var("PROXMOX_TAPE_DRIVE") { let (config, _digest) = config::drive::config()?; - let drive: LinuxTapeDrive = config.lookup("linux", &name)?; + let drive: LtoTapeDrive = config.lookup("lto", &name)?; eprintln!("using device {}", drive.path); drive.open()? } else { @@ -66,13 +64,13 @@ fn get_tape_handle(param: &Value) -> Result { let mut drive_names = Vec::new(); for (name, (section_type, _)) in config.sections.iter() { - if section_type != "linux" { continue; } + if section_type != "lto" { continue; } drive_names.push(name); } if drive_names.len() == 1 { let name = drive_names[0]; - let drive: LinuxTapeDrive = config.lookup("linux", &name)?; + let drive: LtoTapeDrive = config.lookup("lto", &name)?; eprintln!("using device {}", drive.path); drive.open()? } else { @@ -83,111 +81,6 @@ fn get_tape_handle(param: &Value) -> Result { Ok(handle) } -#[api( - input: { - properties: { - drive: { - schema: DRIVE_NAME_SCHEMA, - optional: true, - }, - device: { - schema: LINUX_DRIVE_PATH_SCHEMA, - optional: true, - }, - stdin: { - description: "Use standard input as device handle.", - type: bool, - optional: true, - }, - }, - }, -)] -/// Tape/Media Status -fn status( - param: Value, -) -> Result<(), Error> { - - let result = proxmox::try_block!({ - let mut handle = get_tape_handle(¶m)?; - handle.get_drive_and_media_status() - }).map_err(|err: Error| err.to_string()); - - println!("{}", serde_json::to_string_pretty(&result)?); - - Ok(()) -} - -#[api( - input: { - properties: { - drive: { - schema: DRIVE_NAME_SCHEMA, - optional: true, - }, - device: { - schema: LINUX_DRIVE_PATH_SCHEMA, - optional: true, - }, - stdin: { - description: "Use standard input as device handle.", - type: bool, - optional: true, - }, - }, - }, -)] -/// Read Cartridge Memory (Medium auxiliary memory attributes) -fn cartridge_memory( - param: Value, -) -> Result<(), Error> { - - let result = proxmox::try_block!({ - let mut handle = get_tape_handle(¶m)?; - - handle.cartridge_memory() - }).map_err(|err| err.to_string()); - - println!("{}", serde_json::to_string_pretty(&result)?); - - Ok(()) -} - -#[api( - input: { - properties: { - drive: { - schema: DRIVE_NAME_SCHEMA, - optional: true, - }, - device: { - schema: LINUX_DRIVE_PATH_SCHEMA, - optional: true, - }, - stdin: { - description: "Use standard input as device handle.", - type: bool, - optional: true, - }, - }, - }, -)] -/// Read Tape Alert Flags -fn tape_alert_flags( - param: Value, -) -> Result<(), Error> { - - let result = proxmox::try_block!({ - let mut handle = get_tape_handle(¶m)?; - - let flags = handle.tape_alert_flags()?; - Ok(flags.bits()) - }).map_err(|err: Error| err.to_string()); - - println!("{}", serde_json::to_string_pretty(&result)?); - - Ok(()) -} - #[api( input: { properties: { @@ -204,7 +97,7 @@ fn tape_alert_flags( optional: true, }, device: { - schema: LINUX_DRIVE_PATH_SCHEMA, + schema: LTO_DRIVE_PATH_SCHEMA, optional: true, }, stdin: { @@ -245,40 +138,6 @@ fn set_encryption( Ok(()) } -#[api( - input: { - properties: { - drive: { - schema: DRIVE_NAME_SCHEMA, - optional: true, - }, - device: { - schema: LINUX_DRIVE_PATH_SCHEMA, - optional: true, - }, - stdin: { - description: "Use standard input as device handle.", - type: bool, - optional: true, - }, - }, - }, -)] -/// Read volume statistics -fn volume_statistics( - param: Value, -) -> Result<(), Error> { - - let result = proxmox::try_block!({ - let mut handle = get_tape_handle(¶m)?; - handle.volume_statistics() - }).map_err(|err: Error| err.to_string()); - - println!("{}", serde_json::to_string_pretty(&result)?); - - Ok(()) -} - fn main() -> Result<(), Error> { // check if we are user root or backup @@ -300,22 +159,6 @@ fn main() -> Result<(), Error> { } let cmd_def = CliCommandMap::new() - .insert( - "status", - CliCommand::new(&API_METHOD_STATUS) - ) - .insert( - "cartridge-memory", - CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY) - ) - .insert( - "tape-alert-flags", - CliCommand::new(&API_METHOD_TAPE_ALERT_FLAGS) - ) - .insert( - "volume-statistics", - CliCommand::new(&API_METHOD_VOLUME_STATISTICS) - ) .insert( "encryption", CliCommand::new(&API_METHOD_SET_ENCRYPTION) diff --git a/src/config/drive.rs b/src/config/drive.rs index 63839d0d..57f6911f 100644 --- a/src/config/drive.rs +++ b/src/config/drive.rs @@ -1,12 +1,12 @@ //! Tape drive/changer configuration //! //! This configuration module is based on [`SectionConfig`], and -//! provides a type safe interface to store [`LinuxTapeDrive`], +//! provides a type safe interface to store [`LtoTapeDrive`], //! [`VirtualTapeDrive`] and [`ScsiTapeChanger`] configurations. //! //! Drive type [`VirtualTapeDrive`] is only useful for debugging. //! -//! [LinuxTapeDrive]: crate::api2::types::LinuxTapeDrive +//! [LtoTapeDrive]: crate::api2::types::LtoTapeDrive //! [VirtualTapeDrive]: crate::api2::types::VirtualTapeDrive //! [ScsiTapeChanger]: crate::api2::types::ScsiTapeChanger //! [SectionConfig]: proxmox::api::section_config::SectionConfig @@ -36,7 +36,7 @@ use crate::{ api2::types::{ DRIVE_NAME_SCHEMA, VirtualTapeDrive, - LinuxTapeDrive, + LtoTapeDrive, ScsiTapeChanger, }, }; @@ -57,11 +57,11 @@ fn init() -> SectionConfig { let plugin = SectionConfigPlugin::new("virtual".to_string(), Some("name".to_string()), obj_schema); config.register_plugin(plugin); - let obj_schema = match LinuxTapeDrive::API_SCHEMA { + let obj_schema = match LtoTapeDrive::API_SCHEMA { Schema::Object(ref obj_schema) => obj_schema, _ => unreachable!(), }; - let plugin = SectionConfigPlugin::new("linux".to_string(), Some("name".to_string()), obj_schema); + let plugin = SectionConfigPlugin::new("lto".to_string(), Some("name".to_string()), obj_schema); config.register_plugin(plugin); let obj_schema = match ScsiTapeChanger::API_SCHEMA { @@ -116,7 +116,7 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> { pub fn check_drive_exists(config: &SectionConfigData, drive: &str) -> Result<(), Error> { match config.sections.get(drive) { Some((section_type, _)) => { - if !(section_type == "linux" || section_type == "virtual") { + if !(section_type == "lto" || section_type == "virtual") { bail!("Entry '{}' exists, but is not a tape drive", drive); } } @@ -138,12 +138,12 @@ pub fn complete_drive_name(_arg: &str, _param: &HashMap) -> Vec< } } -/// List Linux tape drives -pub fn complete_linux_drive_name(_arg: &str, _param: &HashMap) -> Vec { +/// List Lto tape drives +pub fn complete_lto_drive_name(_arg: &str, _param: &HashMap) -> Vec { match config() { Ok((data, _digest)) => data.sections.iter() .filter(|(_id, (section_type, _))| { - section_type == "linux" + section_type == "lto" }) .map(|(id, _)| id.to_string()) .collect(), diff --git a/src/tape/changer/mod.rs b/src/tape/changer/mod.rs index a25df49b..1fc0d435 100644 --- a/src/tape/changer/mod.rs +++ b/src/tape/changer/mod.rs @@ -26,7 +26,7 @@ use proxmox::{ use crate::api2::types::{ SLOT_ARRAY_SCHEMA, ScsiTapeChanger, - LinuxTapeDrive, + LtoTapeDrive, }; /// Changer element status. @@ -523,7 +523,7 @@ pub struct MtxMediaChanger { impl MtxMediaChanger { - pub fn with_drive_config(drive_config: &LinuxTapeDrive) -> Result { + pub fn with_drive_config(drive_config: &LtoTapeDrive) -> Result { let (config, _digest) = crate::config::drive::config()?; let changer_config: ScsiTapeChanger = match drive_config.changer { Some(ref changer) => config.lookup("changer", changer)?, diff --git a/src/tape/drive/lto/mod.rs b/src/tape/drive/lto/mod.rs new file mode 100644 index 00000000..becbad50 --- /dev/null +++ b/src/tape/drive/lto/mod.rs @@ -0,0 +1,420 @@ +//! Driver for LTO SCSI tapes +//! +//! This is a userspace drive implementation using SG_IO. +//! +//! Why we do not use the Linux tape driver: +//! +//! - missing features (MAM, Encryption, ...) +//! +//! - strange permission handling - only root (or CAP_SYS_RAWIO) can +//! do SG_IO (SYS_RAW_IO) +//! +//! - unability to detect EOT (you just get EIO) + +mod sg_tape; +pub use sg_tape::*; + +use std::fs::{OpenOptions, File}; +use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::convert::TryFrom; + +use anyhow::{bail, format_err, Error}; +use nix::fcntl::{fcntl, FcntlArg, OFlag}; + +use proxmox::{ + tools::Uuid, + sys::error::SysResult, +}; + +use crate::{ + config, + tools::run_command, + backup::{ + Fingerprint, + KeyConfig, + }, + api2::types::{ + MamAttribute, + LtoDriveAndMediaStatus, + LtoTapeDrive, + }, + tape::{ + TapeRead, + TapeWrite, + drive::{ + TapeDriver, + TapeAlertFlags, + Lp17VolumeStatistics, + mam_extract_media_usage, + }, + file_formats::{ + PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, + MediaSetLabel, + MediaContentHeader, + }, + }, +}; + +impl LtoTapeDrive { + + /// Open a tape device + /// + /// This does additional checks: + /// + /// - check if it is a non-rewinding tape device + /// - check if drive is ready (tape loaded) + /// - check block size + /// - for autoloader only, try to reload ejected tapes + pub fn open(&self) -> Result { + + 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 { + 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 { + + 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, Error> { + self.sg_tape.cartridge_memory() + } + + /// Read Volume Statistics + pub fn volume_statistics(&mut self) -> Result { + 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 { + 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>, std::io::Error> { + let reader = self.sg_tape.open_reader()?; + let handle = match reader { + Some(reader) => { + let reader: Box = Box::new(reader); + Some(reader) + } + None => None, + }; + + Ok(handle) + } + + fn write_file<'a>(&'a mut self) -> Result, 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 { + 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 { + + 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 { + let mut command = std::process::Command::new( + "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd"); + command.args(&[subcmd]); + command.args(&["--stdin"]); + command.args(args); + let device_fd = nix::unistd::dup(fd)?; + command.stdin(unsafe { std::process::Stdio::from_raw_fd(device_fd)}); + run_command(command, None) +} diff --git a/src/tape/drive/lto/sg_tape.rs b/src/tape/drive/lto/sg_tape.rs new file mode 100644 index 00000000..802756fa --- /dev/null +++ b/src/tape/drive/lto/sg_tape.rs @@ -0,0 +1,445 @@ +use std::time::SystemTime; +use std::fs::{File, OpenOptions}; +use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::io::AsRawFd; +use std::path::Path; + +use anyhow::{bail, format_err, Error}; +use endian_trait::Endian; +use nix::fcntl::{fcntl, FcntlArg, OFlag}; + +use proxmox::{ + sys::error::SysResult, + tools::io::ReadExt, +}; + +use crate::{ + api2::types::{ + MamAttribute, + }, + tape::{ + BlockRead, + BlockReadStatus, + BlockWrite, + file_formats::{ + BlockedWriter, + BlockedReader, + }, + drive::{ + TapeAlertFlags, + Lp17VolumeStatistics, + read_mam_attributes, + read_tape_alert_flags, + read_volume_statistics, + set_encryption, + }, + }, + tools::sgutils2::{ + SgRaw, + SenseInfo, + ScsiError, + InquiryInfo, + scsi_inquiry, + }, +}; + +#[repr(C, packed)] +#[derive(Endian, Debug, Copy, Clone)] +pub struct ReadPositionLongPage { + flags: u8, + reserved: [u8;3], + partition_number: u32, + pub logical_object_number: u64, + pub logical_file_id: u64, + obsolete: [u8;8], +} + +pub struct SgTape { + file: File, +} + +impl SgTape { + + const SCSI_TAPE_DEFAULT_TIMEOUT: usize = 60*2; // 2 minutes + + /// Create a new instance + /// + /// Uses scsi_inquiry to check the device type. + pub fn new(mut file: File) -> Result { + + 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>(path: P) -> Result { + // 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 { + 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 { + + let expected_size = std::mem::size_of::(); + + 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 { + 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 { + + 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 { + read_tape_alert_flags(&mut self.file) + } + + /// Read Cartridge Memory (MAM Attributes) + pub fn cartridge_memory(&mut self) -> Result, Error> { + read_mam_attributes(&mut self.file) + } + + /// Read Volume Statistics + pub fn volume_statistics(&mut self) -> Result { + 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 { + + 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 { + 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 { + let writer = SgTapeWriter::new(self); + BlockedWriter::new(writer) + } + + pub fn open_reader(&mut self) -> Result>, 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 { + 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 { + self.sg_tape.write_block(buffer) + } + + fn write_filemark(&mut self) -> Result<(), std::io::Error> { + self.sg_tape.write_filemarks(1, true) + } +} diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs index 5509728c..71f61642 100644 --- a/src/tape/drive/mod.rs +++ b/src/tape/drive/mod.rs @@ -13,8 +13,8 @@ pub use volume_statistics::*; mod encryption; pub use encryption::*; -mod linux_tape; -pub use linux_tape::*; +mod lto; +pub use lto::*; mod mam; pub use mam::*; @@ -49,7 +49,7 @@ use crate::{ }, api2::types::{ VirtualTapeDrive, - LinuxTapeDrive, + LtoTapeDrive, }, server::{ send_load_media_email, @@ -263,8 +263,8 @@ pub fn media_changer( let tape = VirtualTapeDrive::deserialize(config)?; Ok(Some((Box::new(tape), drive.to_string()))) } - "linux" => { - let drive_config = LinuxTapeDrive::deserialize(config)?; + "lto" => { + let drive_config = LtoTapeDrive::deserialize(config)?; match drive_config.changer { Some(ref changer_name) => { let changer = MtxMediaChanger::with_drive_config(&drive_config)?; @@ -317,8 +317,8 @@ pub fn open_drive( let handle = tape.open()?; Ok(Box::new(handle)) } - "linux" => { - let tape = LinuxTapeDrive::deserialize(config)?; + "lto" => { + let tape = LtoTapeDrive::deserialize(config)?; let handle = tape.open()?; Ok(Box::new(handle)) } @@ -379,8 +379,8 @@ pub fn request_and_load_media( Ok((handle, media_id)) } - "linux" => { - let drive_config = LinuxTapeDrive::deserialize(config)?; + "lto" => { + let drive_config = LtoTapeDrive::deserialize(config)?; let label_text = label.label_text.clone(); @@ -546,8 +546,8 @@ fn tape_device_path( "virtual" => { VirtualTapeDrive::deserialize(config)?.path } - "linux" => { - LinuxTapeDrive::deserialize(config)?.path + "lto" => { + LtoTapeDrive::deserialize(config)?.path } _ => bail!("unknown drive type '{}' - internal error"), }; diff --git a/src/tape/linux_list_drives.rs b/src/tape/linux_list_drives.rs index dacbda2c..78ee6e42 100644 --- a/src/tape/linux_list_drives.rs +++ b/src/tape/linux_list_drives.rs @@ -12,14 +12,14 @@ use crate::{ tools::fs::scan_subdir, }; +lazy_static::lazy_static!{ + static ref SCSI_GENERIC_NAME_REGEX: regex::Regex = + regex::Regex::new(r"^sg\d+$").unwrap(); +} + /// List linux tape changer devices pub fn linux_tape_changer_list() -> Vec { - lazy_static::lazy_static!{ - static ref SCSI_GENERIC_NAME_REGEX: regex::Regex = - regex::Regex::new(r"^sg\d+$").unwrap(); - } - let mut list = Vec::new(); let dir_iter = match scan_subdir( @@ -111,20 +111,15 @@ pub fn linux_tape_changer_list() -> Vec { list } -/// List linux tape devices (non-rewinding) -pub fn linux_tape_device_list() -> Vec { - - lazy_static::lazy_static!{ - static ref NST_TAPE_NAME_REGEX: regex::Regex = - regex::Regex::new(r"^nst\d+$").unwrap(); - } +/// List LTO drives +pub fn lto_tape_device_list() -> Vec { let mut list = Vec::new(); let dir_iter = match scan_subdir( libc::AT_FDCWD, - "/sys/class/scsi_tape", - &NST_TAPE_NAME_REGEX) + "/sys/class/scsi_generic", + &SCSI_GENERIC_NAME_REGEX) { Err(_) => return list, Ok(iter) => iter, @@ -138,7 +133,7 @@ pub fn linux_tape_device_list() -> Vec { let name = item.file_name().to_str().unwrap().to_string(); - let mut sys_path = PathBuf::from("/sys/class/scsi_tape"); + let mut sys_path = PathBuf::from("/sys/class/scsi_generic"); sys_path.push(&name); let device = match udev::Device::from_syspath(&sys_path) { @@ -151,6 +146,24 @@ pub fn linux_tape_device_list() -> Vec { Some(devnum) => devnum, }; + let parent = match device.parent() { + None => continue, + Some(parent) => parent, + }; + + match parent.attribute_value("type") { + Some(type_osstr) => { + if type_osstr != "1" { + continue; + } + } + _ => { continue; } + } + + // let mut test_path = sys_path.clone(); + // test_path.push("device/scsi_tape"); + // if !test_path.exists() { continue; } + let _dev_path = match device.devnode().map(Path::to_owned) { None => continue, Some(dev_path) => dev_path, @@ -174,7 +187,7 @@ pub fn linux_tape_device_list() -> Vec { .and_then(|s| if let Ok(s) = s.into_string() { Some(s) } else { None }) .unwrap_or_else(|| String::from("unknown")); - let dev_path = format!("/dev/tape/by-id/scsi-{}-nst", serial); + let dev_path = format!("/dev/tape/by-id/scsi-{}-sg", serial); if PathBuf::from(&dev_path).exists() { list.push(TapeDeviceInfo { @@ -230,13 +243,13 @@ pub fn lookup_device_identification<'a>( } } -/// Make sure path is a linux tape device +/// Make sure path is a lto tape device pub fn check_drive_path( drives: &[TapeDeviceInfo], path: &str, ) -> Result<(), Error> { if lookup_device(drives, path).is_none() { - bail!("path '{}' is not a linux (non-rewinding) tape device", path); + bail!("path '{}' is not a lto SCSI-generic tape device", path); } Ok(()) } @@ -250,5 +263,5 @@ pub fn complete_changer_path(_arg: &str, _param: &HashMap) -> Ve /// List tape device paths pub fn complete_drive_path(_arg: &str, _param: &HashMap) -> Vec { - linux_tape_device_list().iter().map(|v| v.path.clone()).collect() + lto_tape_device_list().iter().map(|v| v.path.clone()).collect() } -- 2.20.1