use thiserror::Error;
use uefi::{
CStr16, CString16, Status, guid,
runtime::{self, VariableAttributes, VariableVendor},
};
use crate::BootResult;
const BOOTMGR_GUID: uefi::Guid = guid!("23600d08-561e-4e68-a024-1d7d6e04ee4e");
const MAX_SIZE: usize = size_of::<u64>();
#[derive(Error, Debug)]
pub enum VarError {
#[error("Failed to get variable: {0}")]
GetErr(#[from] uefi::Error<Option<usize>>),
#[error("Failed to cast variable to u16: {0}")]
CastErr(bytemuck::PodCastError),
#[error("Failed to get string variable: {0}")]
StrErr(#[from] uefi::data_types::FromSliceWithNulError),
}
trait UefiVariableStorage {
fn get_variable<T: UefiVariable + 'static>(
name: &CStr16,
vendor: &VariableVendor,
) -> BootResult<T>;
fn set_variable<T: UefiVariable + 'static>(
name: &CStr16,
vendor: &VariableVendor,
attributes: VariableAttributes,
num: Option<T>,
) -> BootResult<()>;
}
struct RuntimeUefiVariableStorage;
impl UefiVariableStorage for RuntimeUefiVariableStorage {
fn get_variable<T: UefiVariable>(name: &CStr16, vendor: &VariableVendor) -> BootResult<T> {
let mut buf = [0; MAX_SIZE];
match runtime::get_variable(name, vendor, &mut buf) {
Ok((var, _)) => Ok(T::from_bytes(var)),
Err(e) if e.status() == Status::NOT_FOUND => Ok(T::default()), Err(e) => Err(VarError::GetErr(e).into()),
}
}
fn set_variable<T: UefiVariable>(
name: &CStr16,
vendor: &VariableVendor,
attributes: VariableAttributes,
num: Option<T>,
) -> BootResult<()> {
let mut bytes = 0;
let mut buf = [0; MAX_SIZE];
if let Some(num) = num {
bytes = num.to_bytes(&mut buf);
}
Ok(runtime::set_variable(
name,
vendor,
attributes,
&buf[0..bytes],
)?)
}
}
pub trait UefiVariable: Sized {
const SIZE: usize;
fn to_bytes(&self, out: &mut [u8]) -> usize;
fn from_bytes(bytes: &[u8]) -> Self;
fn default() -> Self;
}
impl UefiVariable for usize {
const SIZE: Self = size_of::<Self>();
fn to_bytes(&self, out: &mut [u8]) -> usize {
let bytes = self.to_le_bytes();
out[..bytes.len()].copy_from_slice(&bytes);
bytes.len()
}
fn from_bytes(bytes: &[u8]) -> Self {
let mut array = [0; size_of::<Self>()];
array.copy_from_slice(bytes);
Self::from_le_bytes(array)
}
fn default() -> Self {
0
}
}
impl UefiVariable for u64 {
const SIZE: usize = size_of::<Self>();
fn to_bytes(&self, out: &mut [u8]) -> usize {
let bytes = self.to_le_bytes();
out[..bytes.len()].copy_from_slice(&bytes);
bytes.len()
}
fn from_bytes(bytes: &[u8]) -> Self {
let mut array = [0; size_of::<Self>()];
array.copy_from_slice(bytes);
Self::from_le_bytes(array)
}
fn default() -> Self {
0
}
}
impl UefiVariable for u32 {
const SIZE: usize = size_of::<Self>();
fn to_bytes(&self, out: &mut [u8]) -> usize {
let bytes = self.to_le_bytes();
out[..bytes.len()].copy_from_slice(&bytes);
bytes.len()
}
fn from_bytes(bytes: &[u8]) -> Self {
let mut array = [0; size_of::<Self>()];
array.copy_from_slice(bytes);
Self::from_le_bytes(array)
}
fn default() -> Self {
0
}
}
impl UefiVariable for u16 {
const SIZE: usize = size_of::<Self>();
fn to_bytes(&self, out: &mut [u8]) -> usize {
let bytes = self.to_le_bytes();
out[..bytes.len()].copy_from_slice(&bytes);
bytes.len()
}
fn from_bytes(bytes: &[u8]) -> Self {
let mut array = [0; size_of::<Self>()];
array.copy_from_slice(bytes);
Self::from_le_bytes(array)
}
fn default() -> Self {
0
}
}
impl UefiVariable for u8 {
const SIZE: usize = size_of::<Self>();
fn to_bytes(&self, out: &mut [u8]) -> usize {
let bytes = self.to_le_bytes();
out[..bytes.len()].copy_from_slice(&bytes);
bytes.len()
}
fn from_bytes(bytes: &[u8]) -> Self {
let mut array = [0; size_of::<Self>()];
array.copy_from_slice(bytes);
Self::from_le_bytes(array)
}
fn default() -> Self {
0
}
}
impl UefiVariable for bool {
const SIZE: usize = size_of::<Self>();
fn to_bytes(&self, out: &mut [u8]) -> usize {
let bytes = u8::from(*self).to_le_bytes();
out[..bytes.len()].copy_from_slice(&bytes);
bytes.len()
}
fn from_bytes(bytes: &[u8]) -> Self {
let mut array = [0; size_of::<Self>()];
array.copy_from_slice(bytes);
u8::from_le_bytes(array) > 0
}
fn default() -> Self {
false
}
}
pub fn set_variable<T: UefiVariable + 'static>(
name: &CStr16,
vendor: Option<VariableVendor>,
attrs: Option<VariableAttributes>,
num: Option<T>,
) -> BootResult<()> {
let vendor = vendor.unwrap_or(runtime::VariableVendor(BOOTMGR_GUID));
let attrs = attrs.map_or_else(
|| {
VariableAttributes::NON_VOLATILE
| VariableAttributes::BOOTSERVICE_ACCESS
| VariableAttributes::RUNTIME_ACCESS
},
|x| x,
);
RuntimeUefiVariableStorage::set_variable(name, &vendor, attrs, num)
}
pub fn get_variable<T: UefiVariable + 'static>(
name: &CStr16,
vendor: Option<VariableVendor>,
) -> BootResult<T> {
let vendor = vendor.unwrap_or(runtime::VariableVendor(BOOTMGR_GUID));
RuntimeUefiVariableStorage::get_variable(name, &vendor)
}
pub fn set_variable_u16_slice(
name: &CStr16,
vendor: Option<VariableVendor>,
attrs: Option<VariableAttributes>,
bytes: Option<&[u16]>,
) -> BootResult<()> {
let vendor = vendor.unwrap_or(runtime::VariableVendor(BOOTMGR_GUID));
let attrs = attrs.map_or_else(
|| {
VariableAttributes::NON_VOLATILE
| VariableAttributes::BOOTSERVICE_ACCESS
| VariableAttributes::RUNTIME_ACCESS
},
|x| x,
);
let bytes = bytes.unwrap_or(&[] as &[u16]);
Ok(runtime::set_variable(
name,
&vendor,
attrs,
bytemuck::cast_slice(bytes),
)?)
}
pub fn set_variable_str(
name: &CStr16,
vendor: Option<VariableVendor>,
attrs: Option<VariableAttributes>,
str: Option<&CStr16>,
) -> BootResult<()> {
let str = str.map(CStr16::to_u16_slice_with_nul);
set_variable_u16_slice(name, vendor, attrs, str)
}
pub fn get_variable_str(name: &CStr16, vendor: Option<VariableVendor>) -> BootResult<CString16> {
let vendor = vendor.unwrap_or(runtime::VariableVendor(BOOTMGR_GUID));
let var = match runtime::get_variable_boxed(name, &vendor) {
Ok((var, _)) => var,
Err(e) if e.status() == Status::NOT_FOUND => return Ok(CString16::new()),
Err(e) => return Err(e.into()),
};
let str = bytemuck::try_cast_slice(&var).map_err(VarError::CastErr)?;
Ok(CString16::try_from(str.to_vec()).map_err(VarError::StrErr)?)
}