use std::{fmt, path::PathBuf, str::FromStr};
use crate::{
core::{DevId, Device, DeviceInfo, DmCookie, DmFlags, DmName, DmOptions, DmUuid, DM},
result::{DmError, DmResult, ErrorEnum},
shared::{
device_create, device_exists, device_match, get_status, get_status_line_fields, message,
parse_device, parse_value, DmDevice, TargetLine, TargetParams, TargetTable, TargetTypeBuf,
},
thindevid::ThinDevId,
thinpooldev::ThinPoolDev,
units::Sectors,
};
const THIN_TARGET_NAME: &str = "thin";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ThinTargetParams {
pub pool: Device,
pub thin_id: ThinDevId,
pub external_origin_dev: Option<Device>,
}
impl ThinTargetParams {
pub fn new(
pool: Device,
thin_id: ThinDevId,
external_origin_dev: Option<Device>,
) -> ThinTargetParams {
ThinTargetParams {
pool,
thin_id,
external_origin_dev,
}
}
}
impl fmt::Display for ThinTargetParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", THIN_TARGET_NAME, self.param_str())
}
}
impl FromStr for ThinTargetParams {
type Err = DmError;
fn from_str(s: &str) -> DmResult<ThinTargetParams> {
let vals = s.split(' ').collect::<Vec<_>>();
let len = vals.len();
if !(3..=4).contains(&len) {
let err_msg = format!(
"expected 3 or 4 values in params string \"{}\", found {}",
s, len
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
if vals[0] != THIN_TARGET_NAME {
let err_msg = format!(
"Expected a thin target entry but found target type {}",
vals[0]
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
Ok(ThinTargetParams::new(
parse_device(vals[1], "thinpool device for thin target")?,
vals[2].parse::<ThinDevId>()?,
if len == 3 {
None
} else {
Some(parse_device(
vals[3],
"external origin device for thin snapshot",
)?)
},
))
}
}
impl TargetParams for ThinTargetParams {
fn param_str(&self) -> String {
match self.external_origin_dev {
None => format!("{} {}", self.pool, self.thin_id),
Some(dev) => format!("{} {} {}", self.pool, self.thin_id, dev),
}
}
fn target_type(&self) -> TargetTypeBuf {
TargetTypeBuf::new(THIN_TARGET_NAME.into()).expect("THIN_TARGET_NAME is valid")
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ThinDevTargetTable {
pub table: TargetLine<ThinTargetParams>,
}
impl ThinDevTargetTable {
pub fn new(start: Sectors, length: Sectors, params: ThinTargetParams) -> ThinDevTargetTable {
ThinDevTargetTable {
table: TargetLine::new(start, length, params),
}
}
}
impl fmt::Display for ThinDevTargetTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let table = &self.table;
writeln!(f, "{} {} {}", *table.start, *table.length, table.params)
}
}
impl TargetTable for ThinDevTargetTable {
fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<ThinDevTargetTable> {
if table.len() != 1 {
let err_msg = format!(
"ThinDev table should have exactly one line, has {} lines",
table.len()
);
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
}
let line = table.first().expect("table.len() == 1");
Ok(ThinDevTargetTable::new(
Sectors(line.0),
Sectors(line.1),
format!("{} {}", line.2, line.3).parse::<ThinTargetParams>()?,
))
}
fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> {
to_raw_table_unique!(self)
}
}
#[derive(Debug)]
pub struct ThinDev {
dev_info: Box<DeviceInfo>,
table: ThinDevTargetTable,
}
impl DmDevice<ThinDevTargetTable> for ThinDev {
fn device(&self) -> Device {
device!(self)
}
fn devnode(&self) -> PathBuf {
devnode!(self)
}
fn equivalent_tables(left: &ThinDevTargetTable, right: &ThinDevTargetTable) -> DmResult<bool> {
Ok(left == right)
}
fn name(&self) -> &DmName {
name!(self)
}
fn size(&self) -> Sectors {
self.table.table.length
}
fn table(&self) -> &ThinDevTargetTable {
table!(self)
}
fn teardown(&mut self, dm: &DM) -> DmResult<()> {
dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?;
Ok(())
}
fn uuid(&self) -> Option<&DmUuid> {
uuid!(self)
}
}
#[derive(Clone, Debug)]
pub struct ThinDevWorkingStatus {
pub nr_mapped_sectors: Sectors,
pub highest_mapped_sector: Option<Sectors>,
}
impl ThinDevWorkingStatus {
pub fn new(
nr_mapped_sectors: Sectors,
highest_mapped_sector: Option<Sectors>,
) -> ThinDevWorkingStatus {
ThinDevWorkingStatus {
nr_mapped_sectors,
highest_mapped_sector,
}
}
}
#[derive(Clone, Debug)]
pub enum ThinStatus {
Working(Box<ThinDevWorkingStatus>),
Error,
Fail,
}
impl FromStr for ThinStatus {
type Err = DmError;
fn from_str(status_line: &str) -> DmResult<ThinStatus> {
if status_line.starts_with("Error") {
return Ok(ThinStatus::Error);
}
if status_line.starts_with("Fail") {
return Ok(ThinStatus::Fail);
}
let status_vals = get_status_line_fields(status_line, 2)?;
let count = Sectors(parse_value(status_vals[0], "sector count")?);
let highest = if count == Sectors(0) {
None
} else {
Some(Sectors(parse_value(status_vals[1], "highest used sector")?))
};
Ok(ThinStatus::Working(Box::new(ThinDevWorkingStatus::new(
count, highest,
))))
}
}
impl ThinDev {
pub fn new(
dm: &DM,
name: &DmName,
uuid: Option<&DmUuid>,
length: Sectors,
thin_pool: &ThinPoolDev,
thin_id: ThinDevId,
) -> DmResult<ThinDev> {
message(dm, thin_pool, &format!("create_thin {}", thin_id))?;
if device_exists(dm, name)? {
let err_msg = "Uncreated device should not be known to kernel";
return Err(DmError::Dm(ErrorEnum::Invalid, err_msg.into()));
}
let thin_pool_device = thin_pool.device();
let table = ThinDev::gen_default_table(length, thin_pool_device, thin_id);
let dev_info = device_create(
dm,
name,
uuid,
&table,
DmOptions::default().set_cookie(DmCookie::DM_UDEV_PRIMARY_SOURCE_FLAG),
)?;
Ok(ThinDev {
dev_info: Box::new(dev_info),
table,
})
}
pub fn setup(
dm: &DM,
name: &DmName,
uuid: Option<&DmUuid>,
length: Sectors,
thin_pool: &ThinPoolDev,
thin_id: ThinDevId,
) -> DmResult<ThinDev> {
let thin_pool_device = thin_pool.device();
let table = ThinDev::gen_default_table(length, thin_pool_device, thin_id);
let dev = if device_exists(dm, name)? {
let dev_info = dm.device_info(&DevId::Name(name))?;
let dev = ThinDev {
dev_info: Box::new(dev_info),
table,
};
device_match(dm, &dev, uuid)?;
dev
} else {
let dev_info = device_create(
dm,
name,
uuid,
&table,
DmOptions::default().set_cookie(DmCookie::DM_UDEV_PRIMARY_SOURCE_FLAG),
)?;
ThinDev {
dev_info: Box::new(dev_info),
table,
}
};
Ok(dev)
}
pub fn snapshot(
&self,
dm: &DM,
snapshot_name: &DmName,
snapshot_uuid: Option<&DmUuid>,
thin_pool: &ThinPoolDev,
snapshot_thin_id: ThinDevId,
) -> DmResult<ThinDev> {
let source_id = DevId::Name(self.name());
dm.device_suspend(
&source_id,
DmOptions::default().set_flags(DmFlags::DM_SUSPEND),
)?;
message(
dm,
thin_pool,
&format!(
"create_snap {} {}",
snapshot_thin_id, self.table.table.params.thin_id
),
)?;
dm.device_suspend(&source_id, DmOptions::default())?;
let table = ThinDev::gen_default_table(self.size(), thin_pool.device(), snapshot_thin_id);
let dev_info = Box::new(device_create(
dm,
snapshot_name,
snapshot_uuid,
&table,
DmOptions::default().set_cookie(DmCookie::DM_UDEV_PRIMARY_SOURCE_FLAG),
)?);
Ok(ThinDev { dev_info, table })
}
fn gen_default_table(
length: Sectors,
thin_pool: Device,
thin_id: ThinDevId,
) -> ThinDevTargetTable {
ThinDevTargetTable::new(
Sectors::default(),
length,
ThinTargetParams::new(thin_pool, thin_id, None),
)
}
pub fn id(&self) -> ThinDevId {
self.table.table.params.thin_id
}
pub fn status(&self, dm: &DM, options: DmOptions) -> DmResult<ThinStatus> {
status!(self, dm, options)
}
pub fn set_table(&mut self, dm: &DM, table: TargetLine<ThinTargetParams>) -> DmResult<()> {
let table = ThinDevTargetTable::new(table.start, table.length, table.params);
self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?;
self.table_load(dm, &table, DmOptions::default())?;
self.resume(dm)?;
self.table = table;
Ok(())
}
pub fn destroy(&mut self, dm: &DM, thin_pool: &ThinPoolDev) -> DmResult<()> {
let thin_id = self.table.table.params.thin_id;
self.teardown(dm)?;
message(dm, thin_pool, &format!("delete {}", thin_id))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::{
collections::HashMap,
fs::{canonicalize, OpenOptions},
io::Write,
path::Path,
};
use nix::mount::{mount, umount2, MntFlags, MsFlags};
use uuid::Uuid;
use crate::{
consts::IEC,
core::errors::Error,
shared::DmDevice,
testing::{
blkdev_size, test_name, test_string, test_uuid, test_with_spec, udev_settle,
xfs_create_fs, xfs_set_uuid,
},
thinpooldev::{minimal_thinpool, ThinPoolStatus},
units::DataBlocks,
};
use super::*;
const MIN_THIN_DEV_SIZE: Sectors = Sectors(1);
fn get_udev_db_entry(dev_node_search: &Path) -> Option<HashMap<String, String>> {
fn device_as_map(device: &libudev::Device<'_>) -> HashMap<String, String> {
let rc: HashMap<_, _> = device
.properties()
.map(|i| {
(
String::from(i.name().to_str().unwrap()),
String::from(i.value().to_str().unwrap()),
)
})
.collect();
rc
}
let context = libudev::Context::new().unwrap();
let mut enumerator = libudev::Enumerator::new(&context).unwrap();
enumerator.match_subsystem("block").unwrap();
enumerator
.scan_devices()
.unwrap()
.find(|x| x.devnode().map_or(false, |d| dev_node_search == d))
.map(|dev| device_as_map(&dev))
}
fn test_zero_size(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
assert_matches!(
ThinDev::new(
&dm,
&test_name("name").expect("is valid DM name"),
None,
Sectors(0),
&tp,
ThinDevId::new_u64(0).expect("is below limit")
),
Err(_)
);
udev_settle().unwrap();
tp.teardown(&dm).unwrap();
}
fn test_setup_without_new(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
let td_size = MIN_THIN_DEV_SIZE;
assert_matches!(
ThinDev::setup(
&dm,
&test_name("name").expect("is valid DM name"),
None,
td_size,
&tp,
ThinDevId::new_u64(0).expect("is below limit")
),
Err(DmError::Core(Error::Ioctl(_, _, _, _)))
);
tp.teardown(&dm).unwrap();
}
fn test_basic(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
let thin_id = ThinDevId::new_u64(0).expect("is below limit");
let id = test_name("name").expect("is valid DM name");
let td_size = MIN_THIN_DEV_SIZE;
let mut td = ThinDev::new(&dm, &id, None, td_size, &tp, thin_id).unwrap();
udev_settle().unwrap();
let table = ThinDev::read_kernel_table(&dm, &DevId::Name(td.name()))
.unwrap()
.table;
assert_eq!(table.params.pool, tp.device());
assert_eq!(table.params.thin_id, thin_id);
assert_matches!(
td.status(&dm, DmOptions::default()).unwrap(),
ThinStatus::Error | ThinStatus::Working(_)
);
assert_eq!(
blkdev_size(&OpenOptions::new().read(true).open(td.devnode()).unwrap()),
td_size.bytes()
);
assert_matches!(
ThinDev::new(&dm, &id, None, td_size, &tp, thin_id),
Err(DmError::Core(Error::Ioctl(_, _, _, _)))
);
assert!(device_exists(&dm, &id).unwrap());
assert_matches!(ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id), Ok(_));
udev_settle().unwrap();
assert_matches!(ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id), Ok(_));
td.teardown(&dm).unwrap();
let mut td = ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id).unwrap();
udev_settle().unwrap();
td.destroy(&dm, &tp).unwrap();
tp.teardown(&dm).unwrap();
}
fn test_udev_userspace(paths: &[&Path]) {
fn validate(path_uuid: &Uuid, devnode: &Path) {
udev_settle().unwrap();
let symlink = PathBuf::from(format!("/dev/disk/by-uuid/{}", path_uuid));
assert!(symlink.exists());
let uuid_sym = canonicalize(symlink).unwrap();
assert_eq!(*devnode, uuid_sym);
let entry = get_udev_db_entry(devnode).unwrap();
assert!(!entry.contains_key("SYSTEMD_READY"));
}
fn set_new_fs_uuid(devnode: &Path) -> Uuid {
let tmp_dir = tempfile::Builder::new()
.prefix(&test_string("test_udev_userspace_mp"))
.tempdir()
.unwrap();
mount(
Some(devnode),
tmp_dir.path(),
Some("xfs"),
MsFlags::empty(),
None as Option<&str>,
)
.unwrap();
umount2(tmp_dir.path(), MntFlags::MNT_DETACH).unwrap();
let new_uuid = Uuid::new_v4();
xfs_set_uuid(devnode, &new_uuid).unwrap();
new_uuid
}
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
let thin_id = ThinDevId::new_u64(0).expect("is below limit");
let id = test_name("udev_test_thin_dev").expect("is valid DM name");
let mut td = ThinDev::new(&dm, &id, None, tp.size(), &tp, thin_id).unwrap();
udev_settle().unwrap();
xfs_create_fs(&td.devnode()).unwrap();
let uuid = set_new_fs_uuid(&td.devnode());
validate(&uuid, &td.devnode());
td.teardown(&dm).unwrap();
td = ThinDev::setup(&dm, &id, None, tp.size(), &tp, thin_id).unwrap();
validate(&uuid, &td.devnode());
let ss_id = ThinDevId::new_u64(1).expect("is below limit");
let ss_name = test_name("snap_name").expect("is valid DM name");
let mut ss = td.snapshot(&dm, &ss_name, None, &tp, ss_id).unwrap();
udev_settle().unwrap();
let ss_new_uuid = set_new_fs_uuid(&ss.devnode());
validate(&ss_new_uuid, &ss.devnode());
validate(&uuid, &td.devnode());
ss.destroy(&dm, &tp).unwrap();
td.destroy(&dm, &tp).unwrap();
tp.teardown(&dm).unwrap();
}
fn test_snapshot(paths: &[&Path]) {
assert!(!paths.is_empty());
let td_size = MIN_THIN_DEV_SIZE;
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
let orig_data_usage = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert_eq!(orig_data_usage, DataBlocks(0));
let thin_id = ThinDevId::new_u64(0).expect("is below limit");
let thin_name = test_name("name").expect("is valid DM name");
let mut td = ThinDev::new(&dm, &thin_name, None, td_size, &tp, thin_id).unwrap();
udev_settle().unwrap();
let data_usage_1 = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert_eq!(data_usage_1, DataBlocks(0));
let ss_id = ThinDevId::new_u64(1).expect("is below limit");
let ss_name = test_name("snap_name").expect("is valid DM name");
let mut ss = td.snapshot(&dm, &ss_name, None, &tp, ss_id).unwrap();
udev_settle().unwrap();
let data_usage_2 = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert_eq!(data_usage_2, DataBlocks(0));
assert_eq!(td.size(), ss.size());
ss.destroy(&dm, &tp).unwrap();
td.destroy(&dm, &tp).unwrap();
tp.teardown(&dm).unwrap();
}
fn test_filesystem(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
let thin_id = ThinDevId::new_u64(0).expect("is below limit");
let thin_name = test_name("name").expect("is valid DM name");
let mut td = ThinDev::new(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap();
udev_settle().unwrap();
let orig_data_usage = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert_eq!(orig_data_usage, DataBlocks(0));
xfs_create_fs(&td.devnode()).unwrap();
let data_usage_1 = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert!(data_usage_1 > DataBlocks(0));
let tmp_dir = tempfile::Builder::new()
.prefix(&test_string("devicemapper_testing"))
.tempdir()
.unwrap();
mount(
Some(&td.devnode()),
tmp_dir.path(),
Some("xfs"),
MsFlags::empty(),
None as Option<&str>,
)
.unwrap();
for i in 0..100 {
let file_path = tmp_dir.path().join(format!("devicemapper_test{}.txt", i));
writeln!(
&OpenOptions::new()
.create(true)
.write(true)
.open(file_path)
.unwrap(),
"data"
)
.unwrap();
}
umount2(tmp_dir.path(), MntFlags::MNT_DETACH).unwrap();
let data_usage_2 = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert!(data_usage_2 > data_usage_1);
td.destroy(&dm, &tp).unwrap();
tp.teardown(&dm).unwrap();
}
fn test_snapshot_usage(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
let thin_id = ThinDevId::new_u64(0).expect("is below limit");
let thin_name = test_name("name").expect("is valid DM name");
let mut td =
ThinDev::new(&dm, &thin_name, None, Sectors(2 * IEC::Mi), &tp, thin_id).unwrap();
udev_settle().unwrap();
let orig_data_usage = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert_eq!(orig_data_usage, DataBlocks(0));
xfs_create_fs(&td.devnode()).unwrap();
let data_usage_1 = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert!(data_usage_1 > DataBlocks(0));
let ss_id = ThinDevId::new_u64(1).expect("is below limit");
let ss_name = test_name("snap_name").expect("is valid DM name");
let ss_uuid = test_uuid("snap_uuid").expect("is valid DM uuid");
let mut ss = td
.snapshot(&dm, &ss_name, Some(&ss_uuid), &tp, ss_id)
.unwrap();
udev_settle().unwrap();
let data_usage_2 = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert_eq!(data_usage_2, data_usage_1);
xfs_set_uuid(&ss.devnode(), &Uuid::new_v4()).unwrap();
let data_usage_3 = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert!(data_usage_3 - data_usage_2 > DataBlocks(0));
assert!(data_usage_3 - data_usage_2 < data_usage_1);
assert!(data_usage_3 - data_usage_2 > data_usage_1 / 2usize);
let thin_id = ThinDevId::new_u64(2).expect("is below limit");
let thin_name = test_name("name1").expect("is valid DM name");
let mut td1 =
ThinDev::new(&dm, &thin_name, None, Sectors(2 * IEC::Gi), &tp, thin_id).unwrap();
udev_settle().unwrap();
let data_usage_4 = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert_eq!(data_usage_4, data_usage_3);
xfs_create_fs(&td1.devnode()).unwrap();
let data_usage_5 = match tp.status(&dm, DmOptions::default()).unwrap() {
ThinPoolStatus::Working(ref status) => status.usage.used_data,
ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
};
assert!(data_usage_5 - data_usage_4 > 32usize * data_usage_1);
ss.destroy(&dm, &tp).unwrap();
td1.destroy(&dm, &tp).unwrap();
td.destroy(&dm, &tp).unwrap();
tp.teardown(&dm).unwrap();
}
fn test_thindev_destroy(paths: &[&Path]) {
assert!(!paths.is_empty());
let dm = DM::new().unwrap();
let mut tp = minimal_thinpool(&dm, paths[0]);
let thin_id = ThinDevId::new_u64(0).expect("is below limit");
let thin_name = test_name("name").expect("is valid DM name");
let mut td = ThinDev::new(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap();
td.teardown(&dm).unwrap();
let mut td = ThinDev::setup(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap();
td.destroy(&dm, &tp).unwrap();
assert_matches!(
ThinDev::setup(&dm, &thin_name, None, tp.size(), &tp, thin_id),
Err(DmError::Core(Error::Ioctl(_, _, _, _)))
);
tp.teardown(&dm).unwrap();
}
#[test]
fn loop_test_basic() {
test_with_spec(1, test_basic);
}
#[test]
fn loop_test_basic_udev() {
test_with_spec(1, test_udev_userspace);
}
#[test]
fn loop_test_zero_size() {
test_with_spec(1, test_zero_size);
}
#[test]
fn loop_test_setup_without_new() {
test_with_spec(1, test_setup_without_new);
}
#[test]
fn loop_test_snapshot() {
test_with_spec(1, test_snapshot);
}
#[test]
fn loop_test_snapshot_usage() {
test_with_spec(1, test_snapshot_usage);
}
#[test]
fn loop_test_filesystem() {
test_with_spec(1, test_filesystem);
}
#[test]
fn loop_test_thindev_destroy() {
test_with_spec(1, test_thindev_destroy);
}
}