use std::{
fmt,
ops::Deref,
path::{Path, PathBuf},
str::FromStr,
};
use crate::{
core::{devnode_to_devno, DevId, Device, DeviceInfo, DmFlags, DmName, DmOptions, DmUuid, DM},
result::{DmError, DmResult, ErrorEnum},
units::Sectors,
};
fn err_func(err_msg: &str) -> DmError {
DmError::Dm(ErrorEnum::Invalid, err_msg.into())
}
const DM_TARGET_TYPE_LEN: usize = 16;
str_id!(TargetType, TargetTypeBuf, DM_TARGET_TYPE_LEN, err_func);
pub trait TargetParams: Clone + fmt::Debug + fmt::Display + Eq + FromStr + PartialEq {
fn param_str(&self) -> String;
fn target_type(&self) -> TargetTypeBuf;
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TargetLine<T: TargetParams> {
pub start: Sectors,
pub length: Sectors,
pub params: T,
}
impl<T: TargetParams> TargetLine<T> {
pub fn new(start: Sectors, length: Sectors, params: T) -> TargetLine<T> {
TargetLine {
start,
length,
params,
}
}
}
pub trait TargetTable: Clone + fmt::Debug + fmt::Display + Eq + PartialEq + Sized {
fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<Self>;
fn to_raw_table(&self) -> Vec<(u64, u64, String, String)>;
}
pub trait DmDevice<T: TargetTable> {
fn device(&self) -> Device;
fn devnode(&self) -> PathBuf;
fn equivalent_tables(left: &T, right: &T) -> DmResult<bool>;
fn read_kernel_table(dm: &DM, id: &DevId<'_>) -> DmResult<T> {
let (_, table) =
dm.table_status(id, DmOptions::default().set_flags(DmFlags::DM_STATUS_TABLE))?;
T::from_raw_table(&table)
}
fn name(&self) -> &DmName;
fn resume(&mut self, dm: &DM) -> DmResult<()> {
dm.device_suspend(&DevId::Name(self.name()), DmOptions::default())?;
Ok(())
}
fn size(&self) -> Sectors;
fn suspend(&mut self, dm: &DM, options: DmOptions) -> DmResult<()> {
dm.device_suspend(
&DevId::Name(self.name()),
DmOptions::default()
.set_flags(DmFlags::DM_SUSPEND | options.flags())
.set_cookie(options.cookie()),
)?;
Ok(())
}
fn table(&self) -> &T;
fn table_load(&self, dm: &DM, table: &T, options: DmOptions) -> DmResult<()> {
dm.table_load(&DevId::Name(self.name()), &table.to_raw_table(), options)?;
Ok(())
}
fn teardown(&mut self, dm: &DM) -> DmResult<()>;
fn uuid(&self) -> Option<&DmUuid>;
}
pub fn message<T: TargetTable, D: DmDevice<T>>(dm: &DM, target: &D, msg: &str) -> DmResult<()> {
dm.target_msg(&DevId::Name(target.name()), None, msg)?;
Ok(())
}
pub fn device_create<T: TargetTable>(
dm: &DM,
name: &DmName,
uuid: Option<&DmUuid>,
table: &T,
suspend_options: DmOptions,
) -> DmResult<DeviceInfo> {
dm.device_create(name, uuid, DmOptions::default())?;
let id = DevId::Name(name);
let dev_info = match dm.table_load(&id, &table.to_raw_table(), DmOptions::default()) {
Err(e) => {
dm.device_remove(&id, DmOptions::default())?;
return Err(e);
}
Ok(dev_info) => dev_info,
};
dm.device_suspend(&id, suspend_options)?;
Ok(dev_info)
}
pub fn device_match<T: TargetTable, D: DmDevice<T>>(
dm: &DM,
dev: &D,
uuid: Option<&DmUuid>,
) -> DmResult<()> {
let kernel_table = D::read_kernel_table(dm, &DevId::Name(dev.name()))?;
let device_table = dev.table();
if !D::equivalent_tables(&kernel_table, device_table)? {
let err_msg = format!(
"Specified new table \"{:?}\" does not match kernel table \"{:?}\"",
device_table, kernel_table
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
if dev.uuid() != uuid {
let err_msg = format!(
"Specified uuid \"{:?}\" does not match kernel uuuid \"{:?}\"",
uuid,
dev.uuid()
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
Ok(())
}
pub fn device_exists(dm: &DM, name: &DmName) -> DmResult<bool> {
dm.list_devices()
.map(|l| l.iter().any(|&(ref n, _, _)| &**n == name))
}
pub fn parse_device(val: &str, desc: &str) -> DmResult<Device> {
let device = if val.starts_with('/') {
devnode_to_devno(Path::new(val))?
.ok_or_else(|| {
DmError::Dm(
ErrorEnum::Invalid,
format!("Failed to parse \"{}\" from input \"{}\"", desc, val),
)
})?
.into()
} else {
val.parse::<Device>()?
};
Ok(device)
}
pub fn parse_value<T>(val: &str, desc: &str) -> DmResult<T>
where
T: FromStr,
{
val.parse::<T>().map_err(|_| {
DmError::Dm(
ErrorEnum::Invalid,
format!(
"Failed to parse value for \"{}\" from input \"{}\"",
desc, val
),
)
})
}
pub fn get_status_line_fields(status_line: &str, number_required: usize) -> DmResult<Vec<&str>> {
let status_vals = status_line.split(' ').collect::<Vec<_>>();
let length = status_vals.len();
if length < number_required {
return Err(DmError::Dm(
ErrorEnum::Invalid,
format!(
"Insufficient number of fields for status; requires at least {}, found only {} in status line \"{}\"",
number_required, length, status_line
),
));
}
Ok(status_vals)
}
pub fn get_status(status_lines: &[(u64, u64, String, String)]) -> DmResult<String> {
let length = status_lines.len();
if length != 1 {
return Err(DmError::Dm(
ErrorEnum::Invalid,
format!(
"Incorrect number of lines for status; expected 1, found {} in status result \"{}\"",
length,
status_lines.iter().map(|(s, l, t, v)| format!("{} {} {} {}", s, l, t, v)).collect::<Vec<String>>().join(", ")
),
));
}
Ok(status_lines
.first()
.expect("if length != 1, already returned")
.3
.to_owned())
}
pub fn make_unexpected_value_error(value_index: usize, value: &str, item_name: &str) -> DmError {
DmError::Dm(
ErrorEnum::Invalid,
format!(
"Kernel returned unexpected {}th value \"{}\" for item representing {} in status result",
value_index, value, item_name
),
)
}