diff --git a/src/bin/pmt.rs b/src/bin/pmt.rs index fc673996..8d651064 100644 --- a/src/bin/pmt.rs +++ b/src/bin/pmt.rs @@ -11,6 +11,8 @@ /// - support volume statistics /// - read cartridge memory +use std::collections::HashMap; + use anyhow::{bail, Error}; use serde_json::Value; @@ -21,6 +23,8 @@ use proxmox::{ schema::{ Schema, IntegerSchema, + StringSchema, + ArraySchema, }, RpcEnvironment, }, @@ -38,6 +42,14 @@ pub const RECORD_COUNT_SCHEMA: Schema = .maximum(i32::MAX as isize) .schema(); +pub const DRIVE_OPTION_SCHEMA: Schema = StringSchema::new( + "Linux Tape Driver Option, either numeric value or option name.") + .schema(); + +pub const DRIVE_OPTION_LIST_SCHEMA: Schema = + ArraySchema::new("Drive Option List.", &DRIVE_OPTION_SCHEMA) + .schema(); + use proxmox_backup::{ config::{ self, @@ -52,7 +64,7 @@ use proxmox_backup::{ complete_drive_path, linux_tape_device_list, drive::{ - linux_mtio::MTCmd, + linux_mtio::{MTCmd, SetDrvBufferOptions}, TapeDriver, LinuxTapeHandle, open_linux_tape_device, @@ -60,6 +72,48 @@ use proxmox_backup::{ }, }; +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 { if let Some(name) = param["drive"].as_str() { @@ -582,6 +636,7 @@ fn setblk(size: i32, param: Value) -> Result<(), Error> { Ok(()) } + #[api( input: { properties: { @@ -632,6 +687,109 @@ 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, + }, + }, + }, +)] +/// Set device driver options (root only) +/// +/// If no options specified, we reset to default options +/// (buffer-writes async-writes read-ahead can-bsr) +fn st_options(options: Option>, param: Value) -> Result<(), Error> { + + let handle = get_tape_handle(¶m)?; + + let options = options.unwrap_or_else(|| { + 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 + }); + + 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: { @@ -766,6 +924,9 @@ fn main() -> Result<(), Error> { .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("unlock", std_cmd(&API_METHOD_UNLOCK)) .insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS)) .insert("weof", std_cmd(&API_METHOD_WEOF).arg_param(&["count"])) diff --git a/src/tape/drive/linux_tape.rs b/src/tape/drive/linux_tape.rs index 2e499531..4b7684a5 100644 --- a/src/tape/drive/linux_tape.rs +++ b/src/tape/drive/linux_tape.rs @@ -180,6 +180,36 @@ impl LinuxTapeHandle { Ok(()) } + /// call MTSETDRVBUFFER to set boolean options + /// + /// Note: this uses MT_ST_SETBOOLEANS + pub fn drive_buffer_set_options(&self, opts: SetDrvBufferOptions) -> Result<(), Error> { + + let cmd = mtop { + mt_op: MTCmd::MTSETDRVBUFFER, + mt_count: (SetDrvBufferCmd::MT_ST_SETBOOLEANS as i32) | opts.bits(), + }; + unsafe { + mtioctop(self.file.as_raw_fd(), &cmd) + }.map_err(|err| format_err!("MTSETDRVBUFFER options failed - {}", err))?; + + Ok(()) + } + + /// call MTSETDRVBUFFER to clear boolean options + pub fn drive_buffer_clear_options(&self, opts: SetDrvBufferOptions) -> Result<(), Error> { + + let cmd = mtop { + mt_op: MTCmd::MTSETDRVBUFFER, + mt_count: (SetDrvBufferCmd::MT_ST_CLEARBOOLEANS as i32) | opts.bits(), + }; + unsafe { + mtioctop(self.file.as_raw_fd(), &cmd) + }.map_err(|err| format_err!("MTSETDRVBUFFER options failed - {}", err))?; + + Ok(()) + } + /// This flushes the driver's buffer as a side effect. Should be /// used before reading status with MTIOCGET. fn mtnop(&self) -> Result<(), Error> {