use core::str::FromStr;
use alloc::format;
use alloc::string::String;
use alloc::string::ToString;
use alloc::vec::Vec;
use log::error;
use uefi::boot::OpenProtocolParams;
use uefi::proto::ProtocolPointer;
use uefi::proto::device_path::{DevicePath, PoolDevicePath};
use uefi::proto::loaded_image::LoadedImage;
use uefi::proto::media::partition::{GptPartitionEntry, MbrPartitionRecord};
use uefi::{CString16, Handle, Identify};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathReference {
pub location: PartitionReference,
pub path: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PartitionReference {
Boot,
Guid(uefi::Guid),
#[cfg(feature = "iso")]
Iso(String),
}
impl PathReference {
pub fn parse(s: &str) -> Result<Self, PathRefParseError> {
let (resource, path) = s
.split_once(':')
.ok_or(PathRefParseError::MissingDelimiter)?;
let location = PartitionReference::parse(resource)?;
Ok(PathReference {
location,
path: path.to_string(),
})
}
pub fn to_uri(&self) -> String {
format!("{}{}", self.location.to_uri_prefix(), self.path)
}
}
impl PartitionReference {
pub fn parse(s: &str) -> Result<Self, PathRefParseError> {
let Some(lparen) = s.find('(') else {
return Err(PathRefParseError::InvalidSyntax);
};
let scheme = &s[..lparen];
let arg = s[lparen + 1..]
.strip_suffix(')')
.ok_or(PathRefParseError::MissingDelimiter)?;
match scheme {
"boot" => Ok(PartitionReference::Boot),
"guid" => Ok(PartitionReference::Guid(
uefi::Guid::from_str(arg).map_err(|_| PathRefParseError::InvalidGuid)?,
)),
#[cfg(feature = "iso")]
"iso" => Ok(PartitionReference::Iso(arg.to_string())),
_ => Err(PathRefParseError::UnknownResource(scheme.to_string())),
}
}
pub fn to_uri_prefix(&self) -> String {
match self {
PartitionReference::Boot => String::from("boot():"),
PartitionReference::Guid(guid) => {
format!("guid({}):", guid)
}
#[cfg(feature = "iso")]
PartitionReference::Iso(iso) => {
format!("iso({}):", iso)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror_no_std::Error)]
pub enum PathRefParseError {
#[error("Missing Delimiter")]
MissingDelimiter,
#[error("Invalid Path")]
InvalidPath,
#[error("Unknown Resource: {0}")]
UnknownResource(String),
#[error("Invalid Guid")]
InvalidGuid,
#[error("Invalid Syntax")]
InvalidSyntax,
}
pub struct DiskManager {
partitions: Vec<Partition>,
}
impl DiskManager {
pub fn new(boot_handle: Handle) -> uefi::Result<Self> {
use uefi::proto::media::partition::PartitionInfo;
let mut partitions = Vec::new();
let boot_device_handle = open_protocol_get::<LoadedImage>(boot_handle)?.device();
let partition_handles = uefi::boot::locate_handle_buffer(
uefi::boot::SearchType::ByProtocol(&uefi::proto::media::partition::PartitionInfo::GUID),
)?;
for handle in partition_handles.iter() {
match uefi::boot::open_protocol_exclusive::<PartitionInfo>(*handle) {
Ok(partition_info) => {
partitions.push(Partition {
handle: *handle,
mbr_partition_info: partition_info.mbr_partition_record().cloned(),
gpt_partition_info: partition_info.gpt_partition_entry().cloned(),
is_system: partition_info.is_system(),
is_boot: boot_device_handle == Some(*handle),
#[cfg(feature = "iso")]
iso_path: None,
});
}
Err(e) => {
error!("failed to open protocol on a partition: {:?}", e)
}
}
}
Ok(DiskManager { partitions })
}
pub fn resolve_path(&self, reference: &PathReference) -> uefi::Result<PoolDevicePath> {
match self
.partitions
.iter()
.find(|part| reference.location.matches(part))
{
Some(partition) => {
let device_path = open_protocol_get::<DevicePath>(partition.handle)?;
let mut v = Vec::new();
let root_to_executable =
uefi::proto::device_path::build::DevicePathBuilder::with_vec(&mut v)
.push(&uefi::proto::device_path::build::media::FilePath {
path_name: &CString16::try_from(reference.path.as_str())
.map_err(|_| uefi::Error::new(uefi::Status::NOT_FOUND, ()))?,
})
.map_err(|_| uefi::Error::new(uefi::Status::NOT_FOUND, ()))?
.finalize()
.map_err(|_| uefi::Error::new(uefi::Status::NOT_FOUND, ()))?;
Ok(device_path
.append_path(root_to_executable)
.map_err(|_| uefi::Error::new(uefi::Status::NOT_FOUND, ()))?)
}
None => Err(uefi::Error::new(uefi::Status::NOT_FOUND, ())),
}
}
}
#[derive(Debug)]
pub struct Partition {
pub handle: Handle,
pub gpt_partition_info: Option<GptPartitionEntry>,
#[allow(dead_code)]
pub mbr_partition_info: Option<MbrPartitionRecord>,
#[allow(dead_code)]
pub is_system: bool,
pub is_boot: bool,
#[cfg(feature = "iso")]
pub iso_path: Option<String>,
}
impl Partition {
const fn guid(&self) -> Option<uefi::Guid> {
if let Some(gpt) = self.gpt_partition_info {
Some(gpt.unique_partition_guid)
} else {
None
}
}
}
impl PartitionReference {
fn matches(&self, p: &Partition) -> bool {
match &self {
PartitionReference::Boot => p.is_boot,
PartitionReference::Guid(id) => p.guid().as_ref() == Some(id),
#[cfg(feature = "iso")]
PartitionReference::Iso(iso) => p.iso_path.as_ref() == Some(iso),
}
}
}
pub fn open_protocol_get<P: ProtocolPointer + ?Sized>(
handle: Handle,
) -> Result<uefi::boot::ScopedProtocol<P>, uefi::Error> {
unsafe {
uefi::boot::open_protocol::<P>(
OpenProtocolParams {
handle,
agent: uefi::boot::image_handle(),
controller: None,
},
uefi::boot::OpenProtocolAttributes::GetProtocol,
)
}
}