use std::{
fs::File, os::unix::io::AsRawFd, panic::catch_unwind, path::Path, process::Command, sync::Once,
};
use nix::mount::{umount2, MntFlags};
use uuid::Uuid;
use crate::{
core::{DevId, Device, DmNameBuf, DmOptions, DmUuidBuf, DM},
result::{DmError, DmResult, ErrorEnum},
units::Bytes,
};
static INIT: Once = Once::new();
static mut DM_CONTEXT: Option<DM> = None;
impl DM {
pub fn list_test_devices(&self) -> Result<Vec<(DmNameBuf, Device, Option<u32>)>> {
let mut test_devs = self.list_devices()?;
test_devs.retain(|x| x.0.as_bytes().ends_with(DM_TEST_ID.as_bytes()));
Ok(test_devs)
}
}
ioctl_read!(
blkgetsize64,
0x12,
114,
u64
);
pub fn blkdev_size(file: &File) -> Bytes {
let mut val: u64 = 0;
unsafe { blkgetsize64(file.as_raw_fd(), &mut val) }.unwrap();
Bytes(u128::from(val))
}
fn get_dm() -> &'static DM {
unsafe {
INIT.call_once(|| DM_CONTEXT = Some(DM::new().unwrap()));
match DM_CONTEXT {
Some(ref context) => context,
_ => panic!("DM_CONTEXT.is_some()"),
}
}
}
static DM_TEST_ID: &str = "_dm-rs_test_delme";
pub fn test_string(name: &str) -> String {
let mut namestr = String::from(name);
namestr.push_str(DM_TEST_ID);
namestr
}
fn execute_cmd(cmd: &mut Command) -> DmResult<()> {
match cmd.output() {
Err(err) => Err(DmError::Dm(
ErrorEnum::Error,
format!("cmd: {cmd:?}, error '{err}'"),
)),
Ok(result) => {
if result.status.success() {
Ok(())
} else {
let std_out_txt = String::from_utf8_lossy(&result.stdout);
let std_err_txt = String::from_utf8_lossy(&result.stderr);
let err_msg = format!("cmd: {cmd:?} stdout: {std_out_txt} stderr: {std_err_txt}");
Err(DmError::Dm(ErrorEnum::Error, err_msg))
}
}
}
}
pub fn xfs_create_fs(devnode: &Path, uuid: Option<Uuid>) -> DmResult<()> {
let mut command = Command::new("mkfs.xfs");
command.arg("-f");
command.arg("-q");
command.arg(devnode);
if let Some(uuid) = uuid {
command.arg("-m");
command.arg(format!("uuid={uuid}"));
}
execute_cmd(&mut command)
}
pub fn xfs_set_uuid(devnode: &Path, uuid: &Uuid) -> DmResult<()> {
execute_cmd(
Command::new("xfs_admin")
.arg("-U")
.arg(format!("{uuid}"))
.arg(devnode),
)
}
pub fn udev_settle() -> DmResult<()> {
execute_cmd(Command::new("udevadm").arg("settle"))
}
pub fn test_name(name: &str) -> DmResult<DmNameBuf> {
DmNameBuf::new(test_string(name))
}
pub fn test_uuid(name: &str) -> DmResult<DmUuidBuf> {
DmUuidBuf::new(test_string(name))
}
mod cleanup_errors {
use super::DmError;
#[derive(Debug)]
pub enum Error {
Ioe(std::io::Error),
Procfs(procfs::ProcError),
Nix(nix::Error),
Msg(String),
Chained(String, Box<Error>),
Dm(DmError),
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<nix::Error> for Error {
fn from(err: nix::Error) -> Error {
Error::Nix(err)
}
}
impl From<procfs::ProcError> for Error {
fn from(err: procfs::ProcError) -> Error {
Error::Procfs(err)
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Ioe(err)
}
}
impl From<String> for Error {
fn from(err: String) -> Error {
Error::Msg(err)
}
}
impl From<DmError> for Error {
fn from(err: DmError) -> Error {
Error::Dm(err)
}
}
}
use self::cleanup_errors::{Error, Result};
fn dm_test_devices_remove() -> Result<()> {
fn one_iteration() -> Result<(bool, Vec<String>)> {
let mut progress_made = false;
let mut remain = Vec::new();
for n in get_dm()
.list_test_devices()
.map_err(|e| {
Error::Chained(
"failed while listing DM devices, giving up".into(),
Box::new(e),
)
})?
.iter()
.map(|d| &d.0)
{
match get_dm().device_remove(&DevId::Name(n), DmOptions::default()) {
Ok(_) => progress_made = true,
Err(_) => remain.push(n.to_string()),
}
}
Ok((progress_made, remain))
}
fn do_while_progress() -> Result<Vec<String>> {
let mut result = one_iteration()?;
while result.0 {
result = one_iteration()?;
}
Ok(result.1)
}
|| -> Result<()> {
if catch_unwind(get_dm).is_err() {
return Err("Unable to initialize DM".to_string().into());
}
do_while_progress().and_then(|remain| {
if !remain.is_empty() {
let err_msg = format!("Some test-generated DM devices remaining: {remain:?}");
Err(err_msg.into())
} else {
Ok(())
}
})
}()
.map_err(|e| {
Error::Chained(
"Failed to ensure removal of all test-generated DM devices".into(),
Box::new(e),
)
})
}
fn dm_test_fs_unmount() -> Result<()> {
|| -> Result<()> {
for mount_point in procfs::process::Process::myself()?
.mountinfo()?
.into_iter()
.map(|i| i.mount_point)
.filter(|mp| mp.as_path().to_string_lossy().contains(DM_TEST_ID))
{
umount2(&mount_point, MntFlags::MNT_DETACH)?;
}
Ok(())
}()
.map_err(|e| {
Error::Chained(
"Failed to ensure all test-generated filesystems were unmounted".into(),
Box::new(e),
)
})
}
pub fn clean_up() -> Result<()> {
dm_test_fs_unmount().and_then(|_| dm_test_devices_remove())
}