use camino::{Utf8Path, Utf8PathBuf};
use std::path::Path;
use anyhow::{Context, Result};
use bootc_internal_blockdev::PartitionTable;
use fn_error_context::context;
#[context("get parent devices from mount point boot or sysroot")]
pub fn get_devices<P: AsRef<Path>>(target_root: P) -> Result<Vec<String>> {
let target_root = target_root.as_ref();
let mut source = None;
for path in ["boot", "sysroot"] {
let target_path = target_root.join(path);
if !target_path.exists() {
continue;
}
let target_dir = openat::Dir::open(&target_path)
.with_context(|| format!("Opening {}", target_path.display()))?;
if let Ok(fsinfo) = crate::filesystem::inspect_filesystem(&target_dir, ".") {
source = Some(fsinfo.source);
break;
}
}
let source = match source {
Some(s) => s,
None => anyhow::bail!("Failed to inspect filesystem from boot or sysroot"),
};
let parent_devices = bootc_internal_blockdev::find_parent_devices(&source)
.with_context(|| format!("While looking for backing devices of {}", source))?;
log::debug!("Found parent devices: {parent_devices:?}");
Ok(parent_devices)
}
pub fn get_esp_partition(device: &str) -> Result<Option<String>> {
const ESP_TYPE_GUID: &str = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B";
let device_info: PartitionTable =
bootc_internal_blockdev::partitions_of(Utf8Path::new(device))?;
let esp = device_info
.partitions
.into_iter()
.find(|p| p.parttype.as_str() == ESP_TYPE_GUID);
if let Some(esp) = esp {
return Ok(Some(esp.node));
}
Ok(None)
}
pub fn find_colocated_esps(devices: &Vec<String>) -> Result<Option<Vec<String>>> {
let mut esps = Vec::new();
for device in devices {
if let Some(esp) = get_esp_partition(&device)? {
esps.push(esp)
}
}
if esps.is_empty() {
return Ok(None);
}
log::debug!("Found esp partitions: {esps:?}");
Ok(Some(esps))
}
#[cfg(any(target_arch = "x86_64", target_arch = "powerpc64"))]
pub fn get_bios_boot_partition(device: &str) -> Result<Option<String>> {
const BIOS_BOOT_TYPE_GUID: &str = "21686148-6449-6E6F-744E-656564454649";
let device_info = bootc_internal_blockdev::partitions_of(Utf8Path::new(device))?;
let bios_boot = device_info
.partitions
.into_iter()
.find(|p| p.parttype.as_str() == BIOS_BOOT_TYPE_GUID);
if let Some(bios_boot) = bios_boot {
return Ok(Some(bios_boot.node));
}
Ok(None)
}
#[cfg(any(target_arch = "x86_64", target_arch = "powerpc64"))]
pub fn find_colocated_bios_boot(devices: &Vec<String>) -> Result<Option<Vec<String>>> {
let mut bios_boots = Vec::new();
for device in devices {
if let Some(bios) = get_bios_boot_partition(&device)? {
bios_boots.push(bios)
}
}
if bios_boots.is_empty() {
return Ok(None);
}
log::debug!("Found bios_boot partitions: {bios_boots:?}");
Ok(Some(bios_boots))
}
fn is_mpath(device: &str) -> Result<bool> {
let dm_path = Utf8PathBuf::from_path_buf(std::fs::canonicalize(device)?)
.map_err(|_| anyhow::anyhow!("Non-UTF8 path"))?;
let dm_name = dm_path.file_name().unwrap_or("");
let uuid_path = Utf8PathBuf::from(format!("/sys/class/block/{dm_name}/dm/uuid"));
if uuid_path.exists() {
let uuid = std::fs::read_to_string(&uuid_path)
.with_context(|| format!("Failed to read {uuid_path}"))?;
if uuid.trim_start().starts_with("mpath-") {
return Ok(true);
}
}
Ok(false)
}
pub fn get_esp_partition_number(device: &str) -> Result<String> {
let esp_device =
get_esp_partition(device)?.ok_or_else(|| anyhow::anyhow!("Failed to find ESP device"))?;
let devname = esp_device
.rsplit_once('/')
.ok_or_else(|| anyhow::anyhow!("Failed to parse {esp_device}"))?
.1;
let partition_path = Utf8PathBuf::from(format!("/sys/class/block/{devname}/partition"));
if partition_path.exists() {
return std::fs::read_to_string(&partition_path)
.with_context(|| format!("Failed to read {partition_path}"));
}
if is_mpath(device)? {
if let Some(esp) = esp_device.strip_prefix(device) {
let esp_num = esp.trim_start_matches(|c: char| !c.is_ascii_digit());
return Ok(esp_num.to_string());
}
}
anyhow::bail!("Not supported for {device}")
}