2021-01-31 15:19:53 +00:00
|
|
|
/// Control magnetic tape drive operation
|
|
|
|
///
|
|
|
|
/// This is a Rust implementation, meant to replace the 'mt' command
|
|
|
|
/// line tool.
|
|
|
|
///
|
|
|
|
/// Features:
|
|
|
|
///
|
|
|
|
/// - written in Rust
|
|
|
|
/// - optional json output format
|
|
|
|
/// - support tape alert flags
|
|
|
|
/// - support volume statistics
|
|
|
|
/// - read cartridge memory
|
|
|
|
|
2021-02-02 07:58:02 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2021-01-31 15:19:53 +00:00
|
|
|
use anyhow::{bail, Error};
|
|
|
|
use serde_json::Value;
|
|
|
|
|
|
|
|
use proxmox::{
|
|
|
|
api::{
|
|
|
|
api,
|
|
|
|
cli::*,
|
2021-01-31 16:33:07 +00:00
|
|
|
schema::{
|
|
|
|
Schema,
|
|
|
|
IntegerSchema,
|
2021-02-02 07:58:02 +00:00
|
|
|
StringSchema,
|
|
|
|
ArraySchema,
|
2021-01-31 16:33:07 +00:00
|
|
|
},
|
2021-01-31 15:19:53 +00:00
|
|
|
RpcEnvironment,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2021-01-31 16:33:07 +00:00
|
|
|
pub const FILE_MARK_COUNT_SCHEMA: Schema =
|
|
|
|
IntegerSchema::new("File mark count.")
|
|
|
|
.minimum(1)
|
2021-02-01 09:21:25 +00:00
|
|
|
.maximum(i32::MAX as isize)
|
2021-01-31 16:33:07 +00:00
|
|
|
.schema();
|
|
|
|
|
2021-02-01 09:39:04 +00:00
|
|
|
pub const RECORD_COUNT_SCHEMA: Schema =
|
|
|
|
IntegerSchema::new("Record count.")
|
|
|
|
.minimum(1)
|
|
|
|
.maximum(i32::MAX as isize)
|
|
|
|
.schema();
|
|
|
|
|
2021-02-02 07:58:02 +00:00
|
|
|
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)
|
2021-02-03 07:54:12 +00:00
|
|
|
.min_length(1)
|
2021-02-02 07:58:02 +00:00
|
|
|
.schema();
|
|
|
|
|
2021-01-31 15:19:53 +00:00
|
|
|
use proxmox_backup::{
|
|
|
|
config::{
|
|
|
|
self,
|
|
|
|
drive::complete_drive_name,
|
|
|
|
},
|
|
|
|
api2::types::{
|
|
|
|
LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
DRIVE_NAME_SCHEMA,
|
|
|
|
LinuxTapeDrive,
|
|
|
|
},
|
|
|
|
tape::{
|
|
|
|
complete_drive_path,
|
|
|
|
linux_tape_device_list,
|
|
|
|
drive::{
|
2021-02-02 07:58:02 +00:00
|
|
|
linux_mtio::{MTCmd, SetDrvBufferOptions},
|
2021-01-31 15:19:53 +00:00
|
|
|
TapeDriver,
|
|
|
|
LinuxTapeHandle,
|
|
|
|
open_linux_tape_device,
|
2021-01-31 16:02:55 +00:00
|
|
|
},
|
2021-01-31 15:19:53 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2021-02-02 07:58:02 +00:00
|
|
|
lazy_static::lazy_static!{
|
|
|
|
|
|
|
|
static ref DRIVE_OPTIONS: HashMap<String, SetDrvBufferOptions> = {
|
|
|
|
let mut map = HashMap::new();
|
|
|
|
|
|
|
|
for i in 0..31 {
|
|
|
|
let bit: i32 = 1 << i;
|
|
|
|
let flag = SetDrvBufferOptions::from_bits_truncate(bit);
|
|
|
|
if flag.bits() == 0 { continue; }
|
|
|
|
let name = format!("{:?}", flag)
|
|
|
|
.to_lowercase()
|
|
|
|
.replace("_", "-");
|
|
|
|
|
|
|
|
map.insert(name, flag);
|
|
|
|
}
|
|
|
|
map
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_drive_options(options: Vec<String>) -> Result<SetDrvBufferOptions, Error> {
|
|
|
|
|
|
|
|
let mut value = SetDrvBufferOptions::empty();
|
|
|
|
|
|
|
|
for option in options.iter() {
|
|
|
|
if let Ok::<i32,_>(v) = option.parse() {
|
|
|
|
value |= SetDrvBufferOptions::from_bits_truncate(v);
|
|
|
|
} else if let Some(v) = DRIVE_OPTIONS.get(option) {
|
|
|
|
value |= *v;
|
|
|
|
} else {
|
|
|
|
let option = option.to_lowercase().replace("_", "-");
|
|
|
|
if let Some(v) = DRIVE_OPTIONS.get(&option) {
|
|
|
|
value |= *v;
|
|
|
|
} else {
|
|
|
|
bail!("unknown drive option {}", option);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(value)
|
|
|
|
}
|
|
|
|
|
2021-01-31 15:19:53 +00:00
|
|
|
fn get_tape_handle(param: &Value) -> Result<LinuxTapeHandle, Error> {
|
|
|
|
|
|
|
|
if let Some(name) = param["drive"].as_str() {
|
|
|
|
let (config, _digest) = config::drive::config()?;
|
|
|
|
let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
|
|
|
|
eprintln!("using device {}", drive.path);
|
2021-02-01 11:39:50 +00:00
|
|
|
return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?))
|
2021-01-31 15:19:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(device) = param["device"].as_str() {
|
|
|
|
eprintln!("using device {}", device);
|
|
|
|
return Ok(LinuxTapeHandle::new(open_linux_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)?;
|
|
|
|
eprintln!("using device {}", drive.path);
|
2021-02-01 11:39:50 +00:00
|
|
|
return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?))
|
2021-01-31 15:19:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Ok(device) = std::env::var("TAPE") {
|
|
|
|
eprintln!("using device {}", device);
|
|
|
|
return Ok(LinuxTapeHandle::new(open_linux_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; }
|
|
|
|
drive_names.push(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if drive_names.len() == 1 {
|
|
|
|
let name = drive_names[0];
|
|
|
|
let drive: LinuxTapeDrive = config.lookup("linux", &name)?;
|
|
|
|
eprintln!("using device {}", drive.path);
|
2021-02-01 11:39:50 +00:00
|
|
|
return Ok(LinuxTapeHandle::new(open_linux_tape_device(&drive.path)?))
|
2021-01-31 15:19:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bail!("no drive/device specified");
|
|
|
|
}
|
|
|
|
|
2021-02-01 09:32:21 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
count: {
|
|
|
|
schema: FILE_MARK_COUNT_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Position the tape at the beginning of the count file.
|
|
|
|
///
|
|
|
|
/// Positioning is done by first rewinding the tape and then spacing
|
|
|
|
/// forward over count file marks.
|
|
|
|
fn asf(count: i32, param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
|
|
|
|
handle.rewind()?;
|
|
|
|
|
|
|
|
handle.forward_space_count_files(count)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-31 16:00:15 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
count: {
|
2021-01-31 16:33:07 +00:00
|
|
|
schema: FILE_MARK_COUNT_SCHEMA,
|
2021-01-31 16:00:15 +00:00
|
|
|
},
|
2021-01-31 16:33:07 +00:00
|
|
|
},
|
2021-01-31 16:00:15 +00:00
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Backward space count files (position before file mark).
|
|
|
|
///
|
|
|
|
/// The tape is positioned on the last block of the previous file.
|
|
|
|
fn bsf(count: i32, param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
|
|
|
|
handle.backward_space_count_files(count)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-01 09:18:18 +00:00
|
|
|
|
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
count: {
|
|
|
|
schema: FILE_MARK_COUNT_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Backward space count files, then forward space one record (position after file mark).
|
|
|
|
///
|
|
|
|
/// 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> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
|
|
|
|
handle.mtop(MTCmd::MTBSFM, count, "bsfm")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-01 09:39:04 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
count: {
|
|
|
|
schema: RECORD_COUNT_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Backward space records.
|
|
|
|
fn bsr(count: i32, param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
|
|
|
|
handle.mtop(MTCmd::MTBSR, count, "backward space records")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-31 15:19:53 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
"output-format": {
|
|
|
|
schema: OUTPUT_FORMAT,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Read Cartridge Memory
|
|
|
|
fn cartridge_memory(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let output_format = get_output_format(¶m);
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
let result = handle.cartridge_memory();
|
|
|
|
|
|
|
|
if output_format == "json-pretty" {
|
|
|
|
let result = result.map_err(|err: Error| err.to_string());
|
|
|
|
println!("{}", serde_json::to_string_pretty(&result)?);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if output_format == "json" {
|
|
|
|
let result = result.map_err(|err: Error| err.to_string());
|
|
|
|
println!("{}", serde_json::to_string(&result)?);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if output_format != "text" {
|
|
|
|
bail!("unknown output format '{}'", output_format);
|
|
|
|
}
|
|
|
|
|
|
|
|
let list = result?;
|
|
|
|
|
|
|
|
for item in list {
|
|
|
|
println!("{}|{}|{}", item.id, item.name, item.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Eject drive media
|
|
|
|
fn eject(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
handle.eject_media()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Move to end of media
|
|
|
|
fn eod(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
handle.move_to_eom()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-31 15:34:10 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
fast: {
|
|
|
|
description: "Use fast erase.",
|
|
|
|
type: bool,
|
|
|
|
optional: true,
|
|
|
|
default: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Erase media
|
|
|
|
fn erase(fast: Option<bool>, param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
handle.erase_media(fast.unwrap_or(true))?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-01-31 15:49:48 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
count: {
|
2021-01-31 16:33:07 +00:00
|
|
|
schema: FILE_MARK_COUNT_SCHEMA,
|
2021-01-31 15:49:48 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Forward space count files (position after file mark).
|
|
|
|
///
|
|
|
|
/// The tape is positioned on the first block of the next file.
|
|
|
|
fn fsf(count: i32, param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
|
|
|
|
handle.forward_space_count_files(count)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-01 09:18:18 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
count: {
|
|
|
|
schema: FILE_MARK_COUNT_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Forward space count files, then backward space one record (position before file mark).
|
|
|
|
///
|
|
|
|
/// 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> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
|
|
|
|
handle.mtop(MTCmd::MTFSFM, count, "fsfm")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-01-31 15:34:10 +00:00
|
|
|
|
2021-02-01 09:39:04 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
count: {
|
|
|
|
schema: RECORD_COUNT_SCHEMA,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Forward space records.
|
|
|
|
fn fsr(count: i32, param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
|
|
|
|
handle.mtop(MTCmd::MTFSR, count, "forward space records")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-31 15:19:53 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Load media
|
|
|
|
fn load(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
handle.mtload()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-01 11:18:20 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Lock the tape drive door
|
|
|
|
fn lock(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
|
|
|
|
handle.mtop(MTCmd::MTLOCK, 1, "lock tape drive door")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-31 15:19:53 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Rewind the tape
|
|
|
|
fn rewind(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
handle.rewind()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
"output-format": {
|
|
|
|
schema: OUTPUT_FORMAT,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Scan for existing tape changer devices
|
|
|
|
fn scan(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let output_format = get_output_format(¶m);
|
|
|
|
|
|
|
|
let list = linux_tape_device_list();
|
|
|
|
|
|
|
|
if output_format == "json-pretty" {
|
|
|
|
println!("{}", serde_json::to_string_pretty(&list)?);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if output_format == "json" {
|
|
|
|
println!("{}", serde_json::to_string(&list)?);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if output_format != "text" {
|
|
|
|
bail!("unknown output format '{}'", output_format);
|
|
|
|
}
|
|
|
|
|
|
|
|
for item in list.iter() {
|
|
|
|
println!("{} ({}/{}/{})", item.path, item.vendor, item.model, item.serial);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-02 06:19:54 +00:00
|
|
|
|
|
|
|
#[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(())
|
|
|
|
}
|
|
|
|
|
2021-02-02 07:58:02 +00:00
|
|
|
|
2021-01-31 15:19:53 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
"output-format": {
|
|
|
|
schema: OUTPUT_FORMAT,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Drive Status
|
|
|
|
fn status(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let output_format = get_output_format(¶m);
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
let result = handle.get_drive_and_media_status();
|
|
|
|
|
|
|
|
if output_format == "json-pretty" {
|
|
|
|
let result = result.map_err(|err: Error| err.to_string());
|
|
|
|
println!("{}", serde_json::to_string_pretty(&result)?);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if output_format == "json" {
|
|
|
|
let result = result.map_err(|err: Error| err.to_string());
|
|
|
|
println!("{}", serde_json::to_string(&result)?);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if output_format != "text" {
|
|
|
|
bail!("unknown output format '{}'", output_format);
|
|
|
|
}
|
|
|
|
|
|
|
|
let status = result?;
|
|
|
|
|
|
|
|
println!("{}", serde_json::to_string_pretty(&status)?);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-02-01 11:18:20 +00:00
|
|
|
|
2021-02-02 07:58:02 +00:00
|
|
|
#[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,
|
|
|
|
},
|
2021-02-03 07:54:12 +00:00
|
|
|
defaults: {
|
|
|
|
description: "Set default options (buffer-writes async-writes read-ahead can-bsr).",
|
|
|
|
type: bool,
|
|
|
|
optional: true,
|
|
|
|
},
|
2021-02-02 07:58:02 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Set device driver options (root only)
|
2021-02-03 07:54:12 +00:00
|
|
|
fn st_options(
|
|
|
|
options: Option<Vec<String>>,
|
|
|
|
defaults: Option<bool>,
|
|
|
|
param: Value) -> Result<(), Error> {
|
2021-02-02 07:58:02 +00:00
|
|
|
|
|
|
|
let handle = get_tape_handle(¶m)?;
|
|
|
|
|
2021-02-03 07:54:12 +00:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
};
|
2021-02-02 07:58:02 +00:00
|
|
|
|
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-01 11:18:20 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Unlock the tape drive door
|
|
|
|
fn unlock(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
|
|
|
|
handle.mtop(MTCmd::MTUNLOCK, 1, "unlock tape drive door")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-31 15:19:53 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
"output-format": {
|
|
|
|
schema: OUTPUT_FORMAT,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Volume Statistics
|
|
|
|
fn volume_statistics(param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let output_format = get_output_format(¶m);
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
let result = handle.volume_statistics();
|
|
|
|
|
|
|
|
if output_format == "json-pretty" {
|
|
|
|
let result = result.map_err(|err: Error| err.to_string());
|
|
|
|
println!("{}", serde_json::to_string_pretty(&result)?);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if output_format == "json" {
|
|
|
|
let result = result.map_err(|err: Error| err.to_string());
|
|
|
|
println!("{}", serde_json::to_string(&result)?);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
if output_format != "text" {
|
|
|
|
bail!("unknown output format '{}'", output_format);
|
|
|
|
}
|
|
|
|
|
|
|
|
let data = result?;
|
|
|
|
|
|
|
|
println!("{}", serde_json::to_string_pretty(&data)?);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-01-31 16:33:07 +00:00
|
|
|
#[api(
|
|
|
|
input: {
|
|
|
|
properties: {
|
|
|
|
drive: {
|
|
|
|
schema: DRIVE_NAME_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
device: {
|
|
|
|
schema: LINUX_DRIVE_PATH_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
count: {
|
|
|
|
schema: FILE_MARK_COUNT_SCHEMA,
|
|
|
|
optional: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)]
|
|
|
|
/// Write count (default 1) EOF marks at current position.
|
|
|
|
fn weof(count: Option<i32>, param: Value) -> Result<(), Error> {
|
|
|
|
|
|
|
|
let mut handle = get_tape_handle(¶m)?;
|
|
|
|
handle.mtop(MTCmd::MTWEOF, count.unwrap_or(1), "write EOF mark")?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-01-31 15:19:53 +00:00
|
|
|
fn main() -> Result<(), Error> {
|
|
|
|
|
|
|
|
let uid = nix::unistd::Uid::current();
|
|
|
|
|
|
|
|
let username = match nix::unistd::User::from_uid(uid)? {
|
|
|
|
Some(user) => user.name,
|
|
|
|
None => bail!("unable to get user name"),
|
|
|
|
};
|
|
|
|
|
|
|
|
let std_cmd = |method| {
|
|
|
|
CliCommand::new(method)
|
|
|
|
.completion_cb("drive", complete_drive_name)
|
|
|
|
.completion_cb("device", complete_drive_path)
|
2021-02-03 07:54:12 +00:00
|
|
|
.completion_cb("options", complete_option_name)
|
2021-01-31 15:19:53 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let cmd_def = CliCommandMap::new()
|
2021-02-01 09:32:21 +00:00
|
|
|
.insert("asf", std_cmd(&API_METHOD_ASF).arg_param(&["count"]))
|
2021-02-01 09:18:18 +00:00
|
|
|
.insert("bsf", std_cmd(&API_METHOD_BSF).arg_param(&["count"]))
|
|
|
|
.insert("bsfm", std_cmd(&API_METHOD_BSFM).arg_param(&["count"]))
|
2021-02-01 09:39:04 +00:00
|
|
|
.insert("bsr", std_cmd(&API_METHOD_BSR).arg_param(&["count"]))
|
2021-01-31 15:19:53 +00:00
|
|
|
.insert("cartridge-memory", std_cmd(&API_METHOD_CARTRIDGE_MEMORY))
|
|
|
|
.insert("eject", std_cmd(&API_METHOD_EJECT))
|
|
|
|
.insert("eod", std_cmd(&API_METHOD_EOD))
|
2021-01-31 15:34:10 +00:00
|
|
|
.insert("erase", std_cmd(&API_METHOD_ERASE))
|
2021-02-01 09:18:18 +00:00
|
|
|
.insert("fsf", std_cmd(&API_METHOD_FSF).arg_param(&["count"]))
|
|
|
|
.insert("fsfm", std_cmd(&API_METHOD_FSFM).arg_param(&["count"]))
|
2021-02-01 09:39:04 +00:00
|
|
|
.insert("fsr", std_cmd(&API_METHOD_FSR).arg_param(&["count"]))
|
2021-01-31 15:19:53 +00:00
|
|
|
.insert("load", std_cmd(&API_METHOD_LOAD))
|
2021-02-01 11:18:20 +00:00
|
|
|
.insert("lock", std_cmd(&API_METHOD_LOCK))
|
2021-01-31 15:19:53 +00:00
|
|
|
.insert("rewind", std_cmd(&API_METHOD_REWIND))
|
|
|
|
.insert("scan", CliCommand::new(&API_METHOD_SCAN))
|
2021-02-02 06:19:54 +00:00
|
|
|
.insert("setblk", CliCommand::new(&API_METHOD_SETBLK).arg_param(&["size"]))
|
2021-01-31 15:19:53 +00:00
|
|
|
.insert("status", std_cmd(&API_METHOD_STATUS))
|
2021-02-02 07:58:02 +00:00
|
|
|
.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"]))
|
2021-02-01 11:18:20 +00:00
|
|
|
.insert("unlock", std_cmd(&API_METHOD_UNLOCK))
|
2021-01-31 15:19:53 +00:00
|
|
|
.insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS))
|
2021-02-01 09:18:18 +00:00
|
|
|
.insert("weof", std_cmd(&API_METHOD_WEOF).arg_param(&["count"]))
|
2021-01-31 15:19:53 +00:00
|
|
|
;
|
|
|
|
|
|
|
|
let mut rpcenv = CliEnvironment::new();
|
|
|
|
rpcenv.set_auth_id(Some(format!("{}@pam", username)));
|
|
|
|
|
|
|
|
run_cli_command(cmd_def, rpcenv, None);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-03 07:54:12 +00:00
|
|
|
|
|
|
|
// Completion helpers
|
|
|
|
pub fn complete_option_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
|
|
|
|
DRIVE_OPTIONS
|
|
|
|
.keys()
|
|
|
|
.map(String::from)
|
|
|
|
.collect()
|
|
|
|
}
|