use std::{collections::HashSet, fmt, path::PathBuf, str::FromStr};
use crate::{
core::{DevId, Device, DeviceInfo, DmName, DmOptions, DmUuid, DM},
result::{DmError, DmResult, ErrorEnum},
shared::{
device_create, device_exists, device_match, parse_device, parse_value, DmDevice,
TargetLine, TargetParams, TargetTable, TargetTypeBuf,
},
units::Sectors,
};
const FLAKEY_TARGET_NAME: &str = "flakey";
const LINEAR_TARGET_NAME: &str = "linear";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LinearTargetParams {
pub device: Device,
pub start_offset: Sectors,
}
impl LinearTargetParams {
pub fn new(device: Device, start_offset: Sectors) -> LinearTargetParams {
LinearTargetParams {
device,
start_offset,
}
}
}
impl fmt::Display for LinearTargetParams {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", LINEAR_TARGET_NAME, self.param_str())
}
}
impl FromStr for LinearTargetParams {
type Err = DmError;
fn from_str(s: &str) -> DmResult<LinearTargetParams> {
let vals = s.split(' ').collect::<Vec<_>>();
if vals.len() != 3 {
let err_msg = format!(
"expected 3 values in params string \"{}\", found {}",
s,
vals.len()
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
if vals[0] != LINEAR_TARGET_NAME {
let err_msg = format!(
"Expected a linear target entry but found target type {}",
vals[0]
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
let device = parse_device(vals[1], "block device for linear target")?;
let start = Sectors(parse_value(vals[2], "physical start offset")?);
Ok(LinearTargetParams::new(device, start))
}
}
impl TargetParams for LinearTargetParams {
fn param_str(&self) -> String {
format!("{} {}", self.device, *self.start_offset)
}
fn target_type(&self) -> TargetTypeBuf {
TargetTypeBuf::new(LINEAR_TARGET_NAME.into()).expect("LINEAR_TARGET_NAME is valid")
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FlakeyTargetParams {
pub device: Device,
pub start_offset: Sectors,
pub up_interval: u32,
pub down_interval: u32,
pub feature_args: HashSet<String>,
}
impl FlakeyTargetParams {
pub fn new(
device: Device,
start_offset: Sectors,
up_interval: u32,
down_interval: u32,
feature_args: Vec<String>,
) -> FlakeyTargetParams {
FlakeyTargetParams {
device,
start_offset,
up_interval,
down_interval,
feature_args: feature_args.into_iter().collect::<HashSet<_>>(),
}
}
}
impl fmt::Display for FlakeyTargetParams {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", FLAKEY_TARGET_NAME, self.param_str())
}
}
impl FromStr for FlakeyTargetParams {
type Err = DmError;
fn from_str(s: &str) -> DmResult<FlakeyTargetParams> {
let vals = s.split(' ').collect::<Vec<_>>();
if vals.len() < 5 {
let err_msg = format!(
"expected at least five values in params string \"{}\", found {}",
s,
vals.len()
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
if vals[0] != FLAKEY_TARGET_NAME {
let err_msg = format!(
"Expected a flakey target entry but found target type {}",
vals[0]
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
let device = parse_device(vals[1], "block device for flakey target")?;
let start_offset = Sectors(parse_value(vals[2], "physical start offset")?);
let up_interval = parse_value(vals[3], "up interval")?;
let down_interval = parse_value(vals[4], "down interval")?;
let num_feature_args: usize = parse_value(vals[5], "number of feature args")?;
let feature_args: Vec<String> = vals[6..6 + num_feature_args]
.iter()
.map(|x| x.to_string())
.collect();
Ok(FlakeyTargetParams::new(
device,
start_offset,
up_interval,
down_interval,
feature_args,
))
}
}
impl TargetParams for FlakeyTargetParams {
fn param_str(&self) -> String {
let feature_args = if self.feature_args.is_empty() {
"0".to_owned()
} else {
format!(
"{} {}",
self.feature_args.len(),
self.feature_args
.iter()
.cloned()
.collect::<Vec<_>>()
.join(" ")
)
};
format!(
"{} {} {} {} {}",
self.device, *self.start_offset, self.up_interval, self.down_interval, feature_args
)
}
fn target_type(&self) -> TargetTypeBuf {
TargetTypeBuf::new(FLAKEY_TARGET_NAME.into()).expect("FLAKEY_TARGET_NAME is valid")
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum LinearDevTargetParams {
Flakey(FlakeyTargetParams),
Linear(LinearTargetParams),
}
impl fmt::Display for LinearDevTargetParams {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
LinearDevTargetParams::Flakey(ref flakey) => flakey.fmt(f),
LinearDevTargetParams::Linear(ref linear) => linear.fmt(f),
}
}
}
impl FromStr for LinearDevTargetParams {
type Err = DmError;
fn from_str(s: &str) -> DmResult<LinearDevTargetParams> {
let target_type = s.splitn(2, ' ').next().ok_or_else(|| {
DmError::Dm(
ErrorEnum::Invalid,
format!("target line string \"{}\" did not contain any values", s),
)
})?;
if target_type == FLAKEY_TARGET_NAME {
Ok(LinearDevTargetParams::Flakey(
s.parse::<FlakeyTargetParams>()?,
))
} else if target_type == LINEAR_TARGET_NAME {
Ok(LinearDevTargetParams::Linear(
s.parse::<LinearTargetParams>()?,
))
} else {
Err(DmError::Dm(
ErrorEnum::Invalid,
format!("unexpected target type \"{}\"", target_type),
))
}
}
}
impl TargetParams for LinearDevTargetParams {
fn param_str(&self) -> String {
match *self {
LinearDevTargetParams::Flakey(ref flakey) => flakey.param_str(),
LinearDevTargetParams::Linear(ref linear) => linear.param_str(),
}
}
fn target_type(&self) -> TargetTypeBuf {
match *self {
LinearDevTargetParams::Flakey(ref flakey) => flakey.target_type(),
LinearDevTargetParams::Linear(ref linear) => linear.target_type(),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LinearDevTargetTable {
pub table: Vec<TargetLine<LinearDevTargetParams>>,
}
impl LinearDevTargetTable {
pub fn new(table: Vec<TargetLine<LinearDevTargetParams>>) -> LinearDevTargetTable {
LinearDevTargetTable { table }
}
}
impl fmt::Display for LinearDevTargetTable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for line in &self.table {
writeln!(f, "{} {} {}", *line.start, *line.length, line.params)?;
}
Ok(())
}
}
impl TargetTable for LinearDevTargetTable {
fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<LinearDevTargetTable> {
Ok(LinearDevTargetTable {
table: table
.iter()
.map(|x| -> DmResult<TargetLine<LinearDevTargetParams>> {
Ok(TargetLine::new(
Sectors(x.0),
Sectors(x.1),
format!("{} {}", x.2, x.3).parse::<LinearDevTargetParams>()?,
))
})
.collect::<DmResult<Vec<_>>>()?,
})
}
fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> {
self.table
.iter()
.map(|x| {
(
*x.start,
*x.length,
x.params.target_type().to_string(),
x.params.param_str(),
)
})
.collect::<Vec<_>>()
}
}
#[derive(Debug)]
pub struct LinearDev {
dev_info: Box<DeviceInfo>,
table: LinearDevTargetTable,
}
impl DmDevice<LinearDevTargetTable> for LinearDev {
fn device(&self) -> Device {
device!(self)
}
fn devnode(&self) -> PathBuf {
devnode!(self)
}
fn equivalent_tables(
left: &LinearDevTargetTable,
right: &LinearDevTargetTable,
) -> DmResult<bool> {
Ok(left == right)
}
fn name(&self) -> &DmName {
name!(self)
}
fn size(&self) -> Sectors {
self.table.table.iter().map(|l| l.length).sum()
}
fn table(&self) -> &LinearDevTargetTable {
table!(self)
}
fn teardown(&mut self, dm: &DM) -> DmResult<()> {
dm.device_remove(&DevId::Name(self.name()), &DmOptions::new())?;
Ok(())
}
fn uuid(&self) -> Option<&DmUuid> {
uuid!(self)
}
}
impl LinearDev {
pub fn setup(
dm: &DM,
name: &DmName,
uuid: Option<&DmUuid>,
table: Vec<TargetLine<LinearDevTargetParams>>,
) -> DmResult<LinearDev> {
let table = LinearDevTargetTable::new(table);
let dev = if device_exists(dm, name)? {
let dev_info = dm.device_info(&DevId::Name(name))?;
let dev = LinearDev {
dev_info: Box::new(dev_info),
table,
};
device_match(dm, &dev, uuid)?;
dev
} else {
let dev_info = device_create(dm, name, uuid, &table, &DmOptions::new())?;
LinearDev {
dev_info: Box::new(dev_info),
table,
}
};
Ok(dev)
}
pub fn set_table(
&mut self,
dm: &DM,
table: Vec<TargetLine<LinearDevTargetParams>>,
) -> DmResult<()> {
let table = LinearDevTargetTable::new(table);
self.suspend(dm, false)?;
self.table_load(dm, &table)?;
self.table = table;
Ok(())
}
pub fn set_name(&mut self, dm: &DM, name: &DmName) -> DmResult<()> {
if self.name() == name {
return Ok(());
}
dm.device_rename(self.name(), &DevId::Name(name))?;
self.dev_info = Box::new(dm.device_info(&DevId::Name(name))?);
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::{clone::Clone, fs::OpenOptions, path::Path};
use crate::{
core::{devnode_to_devno, Device},
testing::{blkdev_size, test_name, test_with_spec},
};
use super::*;
fn test_empty(_paths: &[&Path]) {
assert!(LinearDev::setup(
&DM::new().unwrap(),
&test_name("new").expect("valid format"),
None,
vec![],
)
.is_err());
}
fn test_empty_table_set(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let name = test_name("name").expect("valid format");
let dev = Device::from(devnode_to_devno(&paths[0]).unwrap().unwrap());
let params = LinearTargetParams::new(dev, Sectors(0));
let table = vec![TargetLine::new(
Sectors(0),
Sectors(1),
LinearDevTargetParams::Linear(params),
)];
let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
assert!(ld.set_table(&dm, vec![]).is_err());
ld.resume(&dm).unwrap();
ld.teardown(&dm).unwrap();
}
fn test_rename_id(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let name = test_name("name").expect("valid format");
let dev = Device::from(devnode_to_devno(&paths[0]).unwrap().unwrap());
let params = LinearTargetParams::new(dev, Sectors(0));
let table = vec![TargetLine::new(
Sectors(0),
Sectors(1),
LinearDevTargetParams::Linear(params),
)];
let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
ld.set_name(&dm, &name).unwrap();
assert_eq!(ld.name(), &*name);
ld.teardown(&dm).unwrap();
}
fn test_rename(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let name = test_name("name").expect("valid format");
let dev = Device::from(devnode_to_devno(&paths[0]).unwrap().unwrap());
let params = LinearTargetParams::new(dev, Sectors(0));
let table = vec![TargetLine::new(
Sectors(0),
Sectors(1),
LinearDevTargetParams::Linear(params),
)];
let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
let new_name = test_name("new_name").expect("valid format");
ld.set_name(&dm, &new_name).unwrap();
assert_eq!(ld.name(), &*new_name);
ld.teardown(&dm).unwrap();
}
fn test_duplicate_segments(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let name = test_name("name").expect("valid format");
let dev = Device::from(devnode_to_devno(&paths[0]).unwrap().unwrap());
let params = LinearTargetParams::new(dev, Sectors(0));
let table = vec![
TargetLine::new(
Sectors(0),
Sectors(1),
LinearDevTargetParams::Linear(params.clone()),
),
TargetLine::new(
Sectors(1),
Sectors(1),
LinearDevTargetParams::Linear(params),
),
];
let range: Sectors = table.iter().map(|s| s.length).sum();
let count = table.len();
let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
let table = LinearDev::read_kernel_table(&dm, &DevId::Name(ld.name()))
.unwrap()
.table;
assert_eq!(table.len(), count);
match table[0].params {
LinearDevTargetParams::Linear(ref device) => assert_eq!(device.device, dev),
_ => panic!("unexpected param type"),
}
match table[1].params {
LinearDevTargetParams::Linear(ref device) => assert_eq!(device.device, dev),
_ => panic!("unexpected param type"),
}
assert_eq!(
blkdev_size(&OpenOptions::new().read(true).open(ld.devnode()).unwrap()).sectors(),
range
);
ld.teardown(&dm).unwrap();
}
fn test_several_segments(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let name = test_name("name").expect("valid format");
let dev = Device::from(devnode_to_devno(&paths[0]).unwrap().unwrap());
let table = (0..5)
.map(|n| {
TargetLine::new(
Sectors(n),
Sectors(1),
LinearDevTargetParams::Linear(LinearTargetParams::new(dev, Sectors(n))),
)
})
.collect::<Vec<_>>();
let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap();
let loaded_table = LinearDev::read_kernel_table(&dm, &DevId::Name(ld.name())).unwrap();
assert!(
LinearDev::equivalent_tables(&LinearDevTargetTable::new(table), &loaded_table).unwrap()
);
ld.teardown(&dm).unwrap();
}
fn test_same_name(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let name = test_name("name").expect("valid format");
let dev = Device::from(devnode_to_devno(&paths[0]).unwrap().unwrap());
let params = LinearTargetParams::new(dev, Sectors(0));
let table = vec![TargetLine::new(
Sectors(0),
Sectors(1),
LinearDevTargetParams::Linear(params),
)];
let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap();
let params2 = LinearTargetParams::new(dev, Sectors(1));
let table2 = vec![TargetLine::new(
Sectors(0),
Sectors(1),
LinearDevTargetParams::Linear(params2),
)];
assert!(LinearDev::setup(&dm, &name, None, table2).is_err());
assert!(LinearDev::setup(&dm, &name, None, table).is_ok());
ld.teardown(&dm).unwrap();
}
fn test_same_segment(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let name = test_name("name").expect("valid format");
let ersatz = test_name("ersatz").expect("valid format");
let dev = Device::from(devnode_to_devno(&paths[0]).unwrap().unwrap());
let params = LinearTargetParams::new(dev, Sectors(0));
let table = vec![TargetLine::new(
Sectors(0),
Sectors(1),
LinearDevTargetParams::Linear(params),
)];
let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap();
let ld2 = LinearDev::setup(&dm, &ersatz, None, table);
assert!(ld2.is_ok());
ld2.unwrap().teardown(&dm).unwrap();
ld.teardown(&dm).unwrap();
}
fn test_suspend(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let name = test_name("name").expect("valid format");
let dev = Device::from(devnode_to_devno(&paths[0]).unwrap().unwrap());
let params = LinearTargetParams::new(dev, Sectors(0));
let table = vec![TargetLine::new(
Sectors(0),
Sectors(1),
LinearDevTargetParams::Linear(params),
)];
let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
ld.suspend(&dm, false).unwrap();
ld.suspend(&dm, false).unwrap();
ld.resume(&dm).unwrap();
ld.resume(&dm).unwrap();
ld.teardown(&dm).unwrap();
}
#[test]
fn loop_test_duplicate_segments() {
test_with_spec(1, test_duplicate_segments);
}
#[test]
fn loop_test_empty() {
test_with_spec(0, test_empty);
}
#[test]
fn loop_test_empty_table_set() {
test_with_spec(1, test_empty_table_set);
}
#[test]
fn loop_test_rename() {
test_with_spec(1, test_rename);
}
#[test]
fn loop_test_rename_id() {
test_with_spec(1, test_rename_id);
}
#[test]
fn loop_test_same_name() {
test_with_spec(1, test_same_name);
}
#[test]
fn loop_test_segment() {
test_with_spec(1, test_same_segment);
}
#[test]
fn loop_test_several_segments() {
test_with_spec(1, test_several_segments);
}
#[test]
fn loop_test_suspend() {
test_with_spec(1, test_suspend);
}
}