tape: add/use rust scsi changer implementation using libsgutil2
This commit is contained in:
7
src/tape/changer/mtx/mod.rs
Normal file
7
src/tape/changer/mtx/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//! Wrapper around expernal `mtx` command line tool
|
||||
|
||||
mod parse_mtx_status;
|
||||
pub use parse_mtx_status::*;
|
||||
|
||||
mod mtx_wrapper;
|
||||
pub use mtx_wrapper::*;
|
101
src/tape/changer/mtx/mtx_wrapper.rs
Normal file
101
src/tape/changer/mtx/mtx_wrapper.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::Error;
|
||||
use serde_json::Value;
|
||||
|
||||
use proxmox::{
|
||||
api::schema::parse_property_string,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
tools::run_command,
|
||||
api2::types::{
|
||||
SLOT_ARRAY_SCHEMA,
|
||||
ScsiTapeChanger,
|
||||
},
|
||||
tape::{
|
||||
changer::{
|
||||
MtxStatus,
|
||||
mtx::{
|
||||
parse_mtx_status,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// Run 'mtx status' and return parsed result.
|
||||
pub fn mtx_status(config: &ScsiTapeChanger) -> Result<MtxStatus, Error> {
|
||||
|
||||
let path = &config.path;
|
||||
|
||||
let mut export_slots: HashSet<u64> = HashSet::new();
|
||||
|
||||
if let Some(slots) = &config.export_slots {
|
||||
let slots: Value = parse_property_string(&slots, &SLOT_ARRAY_SCHEMA)?;
|
||||
export_slots = slots
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|v| v.as_u64())
|
||||
.collect();
|
||||
}
|
||||
|
||||
let mut command = std::process::Command::new("mtx");
|
||||
command.args(&["-f", path, "status"]);
|
||||
|
||||
let output = run_command(command, None)?;
|
||||
|
||||
let mut status = parse_mtx_status(&output)?;
|
||||
|
||||
for (i, entry) in status.slots.iter_mut().enumerate() {
|
||||
let slot = i as u64 + 1;
|
||||
if export_slots.contains(&slot) {
|
||||
entry.import_export = true; // mark as IMPORT/EXPORT
|
||||
}
|
||||
}
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Run 'mtx load'
|
||||
pub fn mtx_load(
|
||||
path: &str,
|
||||
slot: u64,
|
||||
drivenum: u64,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let mut command = std::process::Command::new("mtx");
|
||||
command.args(&["-f", path, "load", &slot.to_string(), &drivenum.to_string()]);
|
||||
run_command(command, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run 'mtx unload'
|
||||
pub fn mtx_unload(
|
||||
path: &str,
|
||||
slot: u64,
|
||||
drivenum: u64,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let mut command = std::process::Command::new("mtx");
|
||||
command.args(&["-f", path, "unload", &slot.to_string(), &drivenum.to_string()]);
|
||||
run_command(command, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run 'mtx transfer'
|
||||
pub fn mtx_transfer(
|
||||
path: &str,
|
||||
from_slot: u64,
|
||||
to_slot: u64,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let mut command = std::process::Command::new("mtx");
|
||||
command.args(&["-f", path, "transfer", &from_slot.to_string(), &to_slot.to_string()]);
|
||||
|
||||
run_command(command, None)?;
|
||||
|
||||
Ok(())
|
||||
}
|
174
src/tape/changer/mtx/parse_mtx_status.rs
Normal file
174
src/tape/changer/mtx/parse_mtx_status.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use anyhow::Error;
|
||||
|
||||
use nom::{
|
||||
bytes::complete::{take_while, tag},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
tools::nom::{
|
||||
parse_complete, multispace0, multispace1, parse_u64,
|
||||
parse_failure, parse_error, IResult,
|
||||
},
|
||||
tape::changer::{
|
||||
ElementStatus,
|
||||
MtxStatus,
|
||||
DriveStatus,
|
||||
StorageElementStatus,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// Recognizes one line
|
||||
fn next_line(i: &str) -> IResult<&str, &str> {
|
||||
let (i, line) = take_while(|c| (c != '\n'))(i)?;
|
||||
if i.is_empty() {
|
||||
Ok((i, line))
|
||||
} else {
|
||||
Ok((&i[1..], line))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_storage_changer(i: &str) -> IResult<&str, ()> {
|
||||
|
||||
let (i, _) = multispace0(i)?;
|
||||
let (i, _) = tag("Storage Changer")(i)?;
|
||||
let (i, _) = next_line(i)?; // skip
|
||||
|
||||
Ok((i, ()))
|
||||
}
|
||||
|
||||
fn parse_drive_status(i: &str, id: u64) -> IResult<&str, DriveStatus> {
|
||||
|
||||
let mut loaded_slot = None;
|
||||
|
||||
if let Some(empty) = i.strip_prefix("Empty") {
|
||||
let status = DriveStatus {
|
||||
loaded_slot,
|
||||
status: ElementStatus::Empty,
|
||||
drive_serial_number: None,
|
||||
element_address: id as u16,
|
||||
};
|
||||
return Ok((empty, status));
|
||||
}
|
||||
let (mut i, _) = tag("Full (")(i)?;
|
||||
|
||||
if let Some(n) = i.strip_prefix("Storage Element ") {
|
||||
let (n, id) = parse_u64(n)?;
|
||||
loaded_slot = Some(id);
|
||||
let (n, _) = tag(" Loaded")(n)?;
|
||||
i = n;
|
||||
} else {
|
||||
let (n, _) = take_while(|c| !(c == ')' || c == '\n'))(i)?; // skip to ')'
|
||||
i = n;
|
||||
}
|
||||
|
||||
let (i, _) = tag(")")(i)?;
|
||||
|
||||
if let Some(i) = i.strip_prefix(":VolumeTag = ") {
|
||||
let (i, tag) = take_while(|c| !(c == ' ' || c == ':' || c == '\n'))(i)?;
|
||||
let (i, _) = take_while(|c| c != '\n')(i)?; // skip to eol
|
||||
let status = DriveStatus {
|
||||
loaded_slot,
|
||||
status: ElementStatus::VolumeTag(tag.to_string()),
|
||||
drive_serial_number: None,
|
||||
element_address: id as u16,
|
||||
};
|
||||
return Ok((i, status));
|
||||
}
|
||||
|
||||
let (i, _) = take_while(|c| c != '\n')(i)?; // skip
|
||||
|
||||
let status = DriveStatus {
|
||||
loaded_slot,
|
||||
status: ElementStatus::Full,
|
||||
drive_serial_number: None,
|
||||
element_address: id as u16,
|
||||
};
|
||||
Ok((i, status))
|
||||
}
|
||||
|
||||
fn parse_slot_status(i: &str) -> IResult<&str, ElementStatus> {
|
||||
if let Some(empty) = i.strip_prefix("Empty") {
|
||||
return Ok((empty, ElementStatus::Empty));
|
||||
}
|
||||
if let Some(n) = i.strip_prefix("Full ") {
|
||||
if let Some(n) = n.strip_prefix(":VolumeTag=") {
|
||||
let (n, tag) = take_while(|c| !(c == ' ' || c == ':' || c == '\n'))(n)?;
|
||||
let (n, _) = take_while(|c| c != '\n')(n)?; // skip to eol
|
||||
return Ok((n, ElementStatus::VolumeTag(tag.to_string())));
|
||||
|
||||
}
|
||||
let (n, _) = take_while(|c| c != '\n')(n)?; // skip
|
||||
|
||||
return Ok((n, ElementStatus::Full));
|
||||
}
|
||||
|
||||
Err(parse_error(i, "unexptected element status"))
|
||||
}
|
||||
|
||||
fn parse_data_transfer_element(i: &str) -> IResult<&str, (u64, DriveStatus)> {
|
||||
|
||||
let (i, _) = tag("Data Transfer Element")(i)?;
|
||||
let (i, _) = multispace1(i)?;
|
||||
let (i, id) = parse_u64(i)?;
|
||||
let (i, _) = nom::character::complete::char(':')(i)?;
|
||||
let (i, element_status) = parse_drive_status(i, id)?;
|
||||
let (i, _) = nom::character::complete::newline(i)?;
|
||||
|
||||
Ok((i, (id, element_status)))
|
||||
}
|
||||
|
||||
fn parse_storage_element(i: &str) -> IResult<&str, (u64, bool, ElementStatus)> {
|
||||
|
||||
let (i, _) = multispace1(i)?;
|
||||
let (i, _) = tag("Storage Element")(i)?;
|
||||
let (i, _) = multispace1(i)?;
|
||||
let (i, id) = parse_u64(i)?;
|
||||
let (i, opt_ie) = nom::combinator::opt(tag(" IMPORT/EXPORT"))(i)?;
|
||||
let import_export = opt_ie.is_some();
|
||||
let (i, _) = nom::character::complete::char(':')(i)?;
|
||||
let (i, element_status) = parse_slot_status(i)?;
|
||||
let (i, _) = nom::character::complete::newline(i)?;
|
||||
|
||||
Ok((i, (id, import_export, element_status)))
|
||||
}
|
||||
|
||||
fn parse_status(i: &str) -> IResult<&str, MtxStatus> {
|
||||
|
||||
let (mut i, _) = parse_storage_changer(i)?;
|
||||
|
||||
let mut drives = Vec::new();
|
||||
while let Ok((n, (id, drive_status))) = parse_data_transfer_element(i) {
|
||||
if id != drives.len() as u64 {
|
||||
return Err(parse_failure(i, "unexpected drive number"));
|
||||
}
|
||||
i = n;
|
||||
drives.push(drive_status);
|
||||
}
|
||||
|
||||
let mut slots = Vec::new();
|
||||
while let Ok((n, (id, import_export, element_status))) = parse_storage_element(i) {
|
||||
if id != (slots.len() as u64 + 1) {
|
||||
return Err(parse_failure(i, "unexpected slot number"));
|
||||
}
|
||||
i = n;
|
||||
let status = StorageElementStatus {
|
||||
import_export,
|
||||
status: element_status,
|
||||
element_address: id as u16,
|
||||
};
|
||||
slots.push(status);
|
||||
}
|
||||
|
||||
let status = MtxStatus { drives, slots, transports: Vec::new() };
|
||||
|
||||
Ok((i, status))
|
||||
}
|
||||
|
||||
/// Parses the output from 'mtx status'
|
||||
pub fn parse_mtx_status(i: &str) -> Result<MtxStatus, Error> {
|
||||
|
||||
let status = parse_complete("mtx status", i, parse_status)?;
|
||||
|
||||
Ok(status)
|
||||
}
|
Reference in New Issue
Block a user