use crate::Error;
use crate::FilePathArg;
use crate::KeyPathArg;
use crate::OpenMode;
use crate::storage_backend::VmgsStorageBackend;
use crate::vmgs_file_open;
use crate::vmgs_json;
use anyhow::Result;
use clap::Args;
use clap::Subcommand;
use fs_err::File;
use guid::Guid;
use hcl_compat_uefi_nvram_storage::HclCompatNvram;
use std::io::Write;
use std::ops::Deref;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use ucs2::Ucs2LeVec;
use uefi_nvram_specvars::ParsedNvramEntry;
use uefi_nvram_specvars::boot_order;
use uefi_nvram_specvars::parse_nvram_entry;
use uefi_nvram_specvars::signature_list::SignatureList;
use uefi_nvram_storage::NvramStorage;
use uefi_specs::uefi::nvram::vars::EFI_GLOBAL_VARIABLE;
use uefi_specs::uefi::time::EFI_TIME;
use vmgs::Vmgs;
#[derive(Args)]
pub(crate) struct OutputArgs {
#[clap(short = 'o', long, alias = "outpath")]
output_path: Option<PathBuf>,
#[clap(short = 't', long)]
truncate: bool,
}
#[derive(Subcommand)]
pub(crate) enum UefiNvramOperation {
Dump {
#[command(flatten)]
file_path: FilePathArg,
#[command(flatten)]
key_path: KeyPathArg,
#[command(flatten)]
output: OutputArgs,
},
DumpFromJson {
#[clap(short = 'f', long, alias = "filepath")]
file_path: PathBuf,
#[command(flatten)]
output: OutputArgs,
},
RemoveBootEntries {
#[command(flatten)]
file_path: FilePathArg,
#[command(flatten)]
key_path: KeyPathArg,
#[clap(short = 'n', long)]
dry_run: bool,
},
RemoveEntry {
#[command(flatten)]
file_path: FilePathArg,
#[command(flatten)]
key_path: KeyPathArg,
#[clap(short = 'n', long)]
name: String,
#[clap(short = 'v', long)]
vendor: String,
},
}
pub(crate) async fn do_command(operation: UefiNvramOperation) -> Result<(), Error> {
match operation {
UefiNvramOperation::Dump {
file_path,
key_path,
output,
} => {
vmgs_file_dump_nvram(
file_path.file_path,
output.output_path,
key_path.key_path,
output.truncate,
)
.await
}
UefiNvramOperation::DumpFromJson { file_path, output } => {
dump_nvram_from_json(file_path, output.output_path, output.truncate)
}
UefiNvramOperation::RemoveBootEntries {
file_path,
key_path,
dry_run,
} => vmgs_file_remove_boot_entries(file_path.file_path, key_path.key_path, dry_run).await,
UefiNvramOperation::RemoveEntry {
file_path,
key_path,
name,
vendor,
} => {
vmgs_file_remove_nvram_entry(file_path.file_path, key_path.key_path, name, vendor).await
}
}
}
async fn vmgs_file_dump_nvram(
file_path: impl AsRef<Path>,
output_path: Option<impl AsRef<Path>>,
key_path: Option<impl AsRef<Path>>,
truncate: bool,
) -> Result<(), Error> {
let mut nvram_storage = vmgs_file_open_nvram(file_path, key_path, OpenMode::ReadOnly).await?;
let mut out: Box<dyn Write + Send> = if let Some(path) = output_path {
Box::new(File::create(path.as_ref()).map_err(Error::DataFile)?)
} else {
Box::new(std::io::stdout())
};
dump_nvram(&mut nvram_storage, &mut out, truncate).await
}
async fn dump_nvram(
nvram_storage: &mut HclCompatNvram<VmgsStorageBackend>,
out: &mut impl Write,
truncate: bool,
) -> Result<(), Error> {
let mut count = 0;
for entry in nvram_storage.iter().await? {
let meta = NvramEntryMetadata {
vendor: entry.vendor.to_string(),
name: entry.name.to_string(),
timestamp: Some(entry.timestamp),
attr: entry.attr,
size: entry.data.len(),
};
let entry = parse_nvram_entry(&meta.name, entry.data)?;
print_nvram_entry(out, &meta, &entry, truncate).map_err(Error::DataFile)?;
count += 1;
}
eprintln!("Retrieved {count} NVRAM entries");
Ok(())
}
fn dump_nvram_from_json(
file_path: impl AsRef<Path>,
output_path: Option<impl AsRef<Path>>,
truncate: bool,
) -> Result<(), Error> {
eprintln!("Opening JSON file: {}", file_path.as_ref().display());
let file = File::open(file_path.as_ref()).map_err(Error::VmgsFile)?;
let runtime_state: vmgs_json::RuntimeState = serde_json::from_reader(file)?;
let nvram_state = runtime_state
.devices
.get(vmgs_json::BIOS_LOADER_DEVICE_ID)
.ok_or(Error::Json("Missing BIOS_LOADER_DEVICE_ID".to_string()))?
.states
.get("Nvram")
.ok_or(Error::Json("Missing Nvram".to_string()))?;
let vendors = match nvram_state {
vmgs_json::State::Nvram { vendors, .. } => vendors,
_ => return Err(Error::Json("Nvram state invalid".to_string())),
};
let mut out: Box<dyn Write> = if let Some(path) = output_path {
Box::new(File::create(path.as_ref()).map_err(Error::DataFile)?)
} else {
Box::new(std::io::stdout())
};
let mut count = 0;
for (vendor, val) in vendors.iter() {
for (name, var) in val.variables.iter() {
let meta = NvramEntryMetadata {
vendor: vendor.clone(),
name: name.clone(),
timestamp: None,
attr: var.attributes,
size: var.data.len(),
};
let entry = parse_nvram_entry(&meta.name, &var.data)?;
print_nvram_entry(&mut out, &meta, &entry, truncate).map_err(Error::DataFile)?;
count += 1;
}
}
eprintln!("Retrieved {count} NVRAM entries");
Ok(())
}
struct NvramEntryMetadata {
pub vendor: String,
pub name: String,
pub timestamp: Option<EFI_TIME>,
pub attr: u32,
pub size: usize,
}
fn print_nvram_entry(
out: &mut impl Write,
meta: &NvramEntryMetadata,
entry: &ParsedNvramEntry<'_>,
truncate: bool,
) -> std::io::Result<()> {
const LINE_WIDTH: usize = 80;
write!(
out,
"Vendor: {:?}\nName: {:?}\nAttributes: {:#x}\nSize: {:#x}\n",
meta.vendor, meta.name, meta.attr, meta.size,
)?;
if let Some(timestamp) = meta.timestamp {
writeln!(out, "Timestamp: {}", timestamp)?;
}
match entry {
ParsedNvramEntry::BootOrder(boot_order) => {
write!(out, "Boot Order:")?;
for x in boot_order {
write!(out, " {}", x)?;
}
writeln!(out)?;
}
ParsedNvramEntry::Boot(load_option) => {
writeln!(
out,
"Load Option: attributes: {:x}, description: {}",
load_option.attributes, load_option.description
)?;
for path in &load_option.device_paths {
writeln!(out, " - {:x?}", path)?;
}
if let Some(opt) = load_option.opt {
let prefix = " - opt: ";
write!(out, "{}", prefix)?;
print_hex_compact(out, opt, truncate.then(|| LINE_WIDTH - prefix.len()))?;
writeln!(out)?;
}
}
ParsedNvramEntry::SignatureList(sig_lists) => {
writeln!(out, "Signature Lists:")?;
for sig in sig_lists {
match sig {
SignatureList::Sha256(list) => {
writeln!(out, " - [Sha256]")?;
for sig in list {
let prefix = format!(
" - Signature Owner: {} Data: ",
sig.header.signature_owner
);
write!(out, "{}", &prefix)?;
print_hex_compact(
out,
sig.data.0.deref(),
truncate.then(|| LINE_WIDTH - prefix.len()),
)?;
writeln!(out)?;
}
}
SignatureList::X509(sig) => {
let prefix = format!(
" - [X509] Signature Owner: {} Data: ",
sig.header.signature_owner
);
write!(out, "{}", &prefix)?;
print_hex_compact(
out,
sig.data.0.deref(),
truncate.then(|| LINE_WIDTH - prefix.len()),
)?;
writeln!(out)?;
}
}
}
}
ParsedNvramEntry::Unknown(data) => {
let prefix = "data: ";
write!(out, "{}", prefix)?;
print_hex_compact(out, data, truncate.then(|| LINE_WIDTH - prefix.len()))?;
writeln!(out)?;
}
}
writeln!(out)?;
Ok(())
}
fn print_hex_compact(
out: &mut impl Write,
data: &[u8],
truncate: Option<usize>,
) -> std::io::Result<()> {
if let Some(truncate) = truncate {
let ellipsis = "...";
let num_bytes = (truncate - ellipsis.len()) / 2;
for byte in data.iter().take(num_bytes) {
write!(out, "{:02x}", byte)?;
}
if data.len() > num_bytes {
write!(out, "{}", ellipsis)?;
}
} else {
for byte in data {
write!(out, "{:02x}", byte)?;
}
}
Ok(())
}
async fn vmgs_file_open_nvram(
file_path: impl AsRef<Path>,
key_path: Option<impl AsRef<Path>>,
open_mode: OpenMode,
) -> Result<HclCompatNvram<VmgsStorageBackend>, Error> {
let vmgs = vmgs_file_open(file_path, key_path, open_mode, false).await?;
let encrypted = vmgs.is_encrypted();
open_nvram(vmgs, encrypted)
}
fn open_nvram(vmgs: Vmgs, encrypted: bool) -> Result<HclCompatNvram<VmgsStorageBackend>, Error> {
let nvram_storage = HclCompatNvram::new(
VmgsStorageBackend::new(vmgs, vmgs::FileId::BIOS_NVRAM, encrypted)
.map_err(Error::VmgsStorageBackend)?,
None,
);
Ok(nvram_storage)
}
async fn vmgs_file_remove_boot_entries(
file_path: impl AsRef<Path>,
key_path: Option<impl AsRef<Path>>,
dry_run: bool,
) -> Result<(), Error> {
let mut nvram_storage = vmgs_file_open_nvram(file_path, key_path, OpenMode::ReadWrite).await?;
if dry_run {
eprintln!("Printing Boot Entries (Dry-run)");
} else {
eprintln!("Deleting Boot Entries");
}
let name = Ucs2LeVec::from("BootOrder".to_string());
let (_, boot_order_bytes, _) = nvram_storage
.get_variable(&name, EFI_GLOBAL_VARIABLE)
.await?
.ok_or(Error::MissingNvramEntry(name.clone()))?;
let boot_order = boot_order::parse_boot_order(&boot_order_bytes)
.map_err(uefi_nvram_specvars::ParseError::BootOrder)?;
if !dry_run {
if !nvram_storage
.remove_variable(&name, EFI_GLOBAL_VARIABLE)
.await?
{
return Err(Error::MissingNvramEntry(name));
}
}
for (i, boot_option_num) in boot_order.enumerate() {
let name = Ucs2LeVec::from(format!("Boot{:04x}", boot_option_num));
let (_, boot_option_bytes, _) = nvram_storage
.get_variable(&name, EFI_GLOBAL_VARIABLE)
.await?
.ok_or(Error::MissingNvramEntry(name.clone()))?;
let boot_option = boot_order::EfiLoadOption::parse(&boot_option_bytes)
.map_err(uefi_nvram_specvars::ParseError::BootOrder)?;
println!("{i}: {}: {:x?}", &name, boot_option);
if !dry_run {
if !nvram_storage
.remove_variable(&name, EFI_GLOBAL_VARIABLE)
.await?
{
return Err(Error::MissingNvramEntry(name));
}
}
}
Ok(())
}
async fn vmgs_file_remove_nvram_entry(
file_path: impl AsRef<Path>,
key_path: Option<impl AsRef<Path>>,
name: String,
vendor: String,
) -> Result<(), Error> {
let mut nvram_storage = vmgs_file_open_nvram(file_path, key_path, OpenMode::ReadWrite).await?;
eprintln!("Removing variable with name {name} and vendor {vendor}");
let name = Ucs2LeVec::from(name);
let vendor = Guid::from_str(&vendor)?;
if !nvram_storage.remove_variable(&name, vendor).await? {
return Err(Error::MissingNvramEntry(name));
}
Ok(())
}