use std::{ffi::OsStr, fmt, path::Path};
use crate::{
engine::types::UdevEngineDevice,
stratis::{StratisError, StratisResult},
};
pub const FS_TYPE_KEY: &str = "ID_FS_TYPE";
pub const STRATIS_FS_TYPE: &str = "stratis";
pub const CRYPTO_FS_TYPE: &str = "crypto_LUKS";
pub const SUBSYSTEM_BLOCK: &str = "block";
pub fn block_enumerator(context: &libudev::Context) -> libudev::Result<libudev::Enumerator> {
let mut enumerator = libudev::Enumerator::new(context)?;
enumerator.match_subsystem(SUBSYSTEM_BLOCK)?;
Ok(enumerator)
}
pub fn get_udev_property<T: AsRef<OsStr>>(
device: &UdevEngineDevice,
property_name: T,
) -> Option<StratisResult<String>>
where
T: fmt::Display,
{
device.property_value(&property_name).map(|value| {
value
.to_str()
.ok_or_else(|| {
StratisError::Error(format!(
"Unable to convert udev property value with key {} to a string, lossy value is {}",
property_name,
value.to_string_lossy()
))
})
.map(|value| value.into())
})
}
fn is_multipath_member(device: &UdevEngineDevice) -> StratisResult<bool> {
match get_udev_property(device, "DM_MULTIPATH_DEVICE_PATH") {
None => Ok(false),
Some(Ok(value)) => Ok(value == "1"),
Some(Err(err)) => Err(err),
}
}
fn is_unclaimed(device: &UdevEngineDevice) -> bool {
(get_udev_property(device, "ID_PART_TABLE_TYPE").is_none()
|| get_udev_property(device, "ID_PART_ENTRY_DISK").is_some())
&& get_udev_property(device, "ID_FS_USAGE").is_none()
}
fn is_stratis(device: &UdevEngineDevice) -> StratisResult<bool> {
match get_udev_property(device, FS_TYPE_KEY) {
None => Ok(false),
Some(Ok(value)) => Ok(value == STRATIS_FS_TYPE),
Some(Err(err)) => Err(err),
}
}
fn is_luks(device: &UdevEngineDevice) -> StratisResult<bool> {
match get_udev_property(device, FS_TYPE_KEY) {
None => Ok(false),
Some(Ok(value)) => Ok(value == CRYPTO_FS_TYPE),
Some(Err(err)) => Err(err),
}
}
#[derive(Debug, Eq, PartialEq)]
pub enum UdevOwnership {
Luks,
MultipathMember,
Stratis,
Theirs,
Unowned,
}
impl fmt::Display for UdevOwnership {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
UdevOwnership::Luks => write!(f, "LUKS encrypted block device"),
UdevOwnership::MultipathMember => write!(f, "member of a multipath block device"),
UdevOwnership::Stratis => write!(f, "Stratis block device"),
UdevOwnership::Theirs => write!(f, "block device which appears to be owned"),
UdevOwnership::Unowned => write!(f, "block device which appears to be unowned"),
}
}
}
pub fn decide_ownership(device: &UdevEngineDevice) -> StratisResult<UdevOwnership> {
|| -> StratisResult<UdevOwnership> {
if is_multipath_member(device)? {
return Ok(UdevOwnership::MultipathMember);
}
Ok(if is_stratis(device)? {
UdevOwnership::Stratis
} else if is_luks(device)? {
UdevOwnership::Luks
} else if is_unclaimed(device) {
UdevOwnership::Unowned
} else {
UdevOwnership::Theirs
})
}()
.map_err(|err| {
StratisError::Error(format!(
"Could not determine ownership of a device from a udev database entry: {}",
err
))
})
}
pub fn block_device_apply<F, U>(devnode: &Path, f: F) -> StratisResult<Option<U>>
where
F: FnOnce(&UdevEngineDevice) -> U,
{
let canonical = devnode.canonicalize()?;
let context = libudev::Context::new()?;
let mut enumerator = block_enumerator(&context)?;
Ok(enumerator
.scan_devices()?
.filter(|dev| dev.is_initialized())
.find(|x| x.devnode().map_or(false, |d| canonical == d))
.map(|ref d| f(&UdevEngineDevice::from(d))))
}