1use crate::Error;
7use crate::FilePathArg;
8use crate::KeyPathArg;
9use crate::OpenMode;
10use crate::storage_backend::VmgsStorageBackend;
11use crate::vmgs_file_open;
12use crate::vmgs_json;
13use anyhow::Result;
14use clap::Args;
15use clap::Subcommand;
16use fs_err::File;
17use guid::Guid;
18use hcl_compat_uefi_nvram_storage::HclCompatNvram;
19use std::io::Write;
20use std::ops::Deref;
21use std::path::Path;
22use std::path::PathBuf;
23use std::str::FromStr;
24use ucs2::Ucs2LeVec;
25use uefi_nvram_specvars::ParsedNvramEntry;
26use uefi_nvram_specvars::boot_order;
27use uefi_nvram_specvars::parse_nvram_entry;
28use uefi_nvram_specvars::signature_list::SignatureList;
29use uefi_nvram_storage::NvramStorage;
30use uefi_specs::uefi::nvram::vars::EFI_GLOBAL_VARIABLE;
31use uefi_specs::uefi::time::EFI_TIME;
32use vmgs::Vmgs;
33
34#[derive(Args)]
35pub(crate) struct OutputArgs {
36 #[clap(short = 'o', long, alias = "outpath")]
38 output_path: Option<PathBuf>,
39 #[clap(short = 't', long)]
41 truncate: bool,
42}
43
44#[derive(Subcommand)]
45pub(crate) enum UefiNvramOperation {
46 Dump {
48 #[command(flatten)]
49 file_path: FilePathArg,
50 #[command(flatten)]
51 key_path: KeyPathArg,
52 #[command(flatten)]
53 output: OutputArgs,
54 },
55 DumpFromJson {
58 #[clap(short = 'f', long, alias = "filepath")]
60 file_path: PathBuf,
61 #[command(flatten)]
62 output: OutputArgs,
63 },
64 RemoveBootEntries {
66 #[command(flatten)]
67 file_path: FilePathArg,
68 #[command(flatten)]
69 key_path: KeyPathArg,
70 #[clap(short = 'n', long)]
72 dry_run: bool,
73 },
74 RemoveEntry {
76 #[command(flatten)]
77 file_path: FilePathArg,
78 #[command(flatten)]
79 key_path: KeyPathArg,
80 #[clap(short = 'n', long)]
82 name: String,
83 #[clap(short = 'v', long)]
85 vendor: String,
86 },
87}
88
89pub(crate) async fn do_command(operation: UefiNvramOperation) -> Result<(), Error> {
90 match operation {
91 UefiNvramOperation::Dump {
92 file_path,
93 key_path,
94 output,
95 } => {
96 vmgs_file_dump_nvram(
97 file_path.file_path,
98 output.output_path,
99 key_path.key_path,
100 output.truncate,
101 )
102 .await
103 }
104 UefiNvramOperation::DumpFromJson { file_path, output } => {
105 dump_nvram_from_json(file_path, output.output_path, output.truncate)
106 }
107 UefiNvramOperation::RemoveBootEntries {
108 file_path,
109 key_path,
110 dry_run,
111 } => vmgs_file_remove_boot_entries(file_path.file_path, key_path.key_path, dry_run).await,
112 UefiNvramOperation::RemoveEntry {
113 file_path,
114 key_path,
115 name,
116 vendor,
117 } => {
118 vmgs_file_remove_nvram_entry(file_path.file_path, key_path.key_path, name, vendor).await
119 }
120 }
121}
122
123async fn vmgs_file_dump_nvram(
125 file_path: impl AsRef<Path>,
126 output_path: Option<impl AsRef<Path>>,
127 key_path: Option<impl AsRef<Path>>,
128 truncate: bool,
129) -> Result<(), Error> {
130 let mut nvram_storage = vmgs_file_open_nvram(file_path, key_path, OpenMode::ReadOnly).await?;
131
132 let mut out: Box<dyn Write + Send> = if let Some(path) = output_path {
133 Box::new(File::create(path.as_ref()).map_err(Error::DataFile)?)
134 } else {
135 Box::new(std::io::stdout())
136 };
137
138 dump_nvram(&mut nvram_storage, &mut out, truncate).await
139}
140
141async fn dump_nvram(
142 nvram_storage: &mut HclCompatNvram<VmgsStorageBackend>,
143 out: &mut impl Write,
144 truncate: bool,
145) -> Result<(), Error> {
146 let mut count = 0;
147 for entry in nvram_storage.iter() {
148 let meta = NvramEntryMetadata {
149 vendor: entry.vendor.to_string(),
150 name: entry.name.to_string(),
151 timestamp: Some(entry.timestamp),
152 attr: entry.attr,
153 size: entry.data.len(),
154 };
155 let entry = parse_nvram_entry(&meta.name, entry.data)?;
156 print_nvram_entry(out, &meta, &entry, truncate).map_err(Error::DataFile)?;
157 count += 1;
158 }
159
160 eprintln!("Retrieved {count} NVRAM entries");
161 Ok(())
162}
163
164fn dump_nvram_from_json(
166 file_path: impl AsRef<Path>,
167 output_path: Option<impl AsRef<Path>>,
168 truncate: bool,
169) -> Result<(), Error> {
170 eprintln!("Opening JSON file: {}", file_path.as_ref().display());
171 let file = File::open(file_path.as_ref()).map_err(Error::VmgsFile)?;
172
173 let runtime_state: vmgs_json::RuntimeState = serde_json::from_reader(file)?;
174
175 let nvram_state = runtime_state
176 .devices
177 .get(vmgs_json::BIOS_LOADER_DEVICE_ID)
178 .ok_or(Error::Json("Missing BIOS_LOADER_DEVICE_ID".to_string()))?
179 .states
180 .get("Nvram")
181 .ok_or(Error::Json("Missing Nvram".to_string()))?;
182
183 let vendors = match nvram_state {
184 vmgs_json::State::Nvram { vendors, .. } => vendors,
185 _ => return Err(Error::Json("Nvram state invalid".to_string())),
186 };
187
188 let mut out: Box<dyn Write> = if let Some(path) = output_path {
189 Box::new(File::create(path.as_ref()).map_err(Error::DataFile)?)
190 } else {
191 Box::new(std::io::stdout())
192 };
193
194 let mut count = 0;
195 for (vendor, val) in vendors.iter() {
196 for (name, var) in val.variables.iter() {
197 let meta = NvramEntryMetadata {
198 vendor: vendor.clone(),
199 name: name.clone(),
200 timestamp: None,
201 attr: var.attributes,
202 size: var.data.len(),
203 };
204 let entry = parse_nvram_entry(&meta.name, &var.data)?;
205 print_nvram_entry(&mut out, &meta, &entry, truncate).map_err(Error::DataFile)?;
206 count += 1;
207 }
208 }
209
210 eprintln!("Retrieved {count} NVRAM entries");
211 Ok(())
212}
213
214struct NvramEntryMetadata {
217 pub vendor: String,
218 pub name: String,
219 pub timestamp: Option<EFI_TIME>,
220 pub attr: u32,
221 pub size: usize,
222}
223
224fn print_nvram_entry(
225 out: &mut impl Write,
226 meta: &NvramEntryMetadata,
227 entry: &ParsedNvramEntry<'_>,
228 truncate: bool,
229) -> std::io::Result<()> {
230 const LINE_WIDTH: usize = 80;
231
232 write!(
233 out,
234 "Vendor: {:?}\nName: {:?}\nAttributes: {:#x}\nSize: {:#x}\n",
235 meta.vendor, meta.name, meta.attr, meta.size,
236 )?;
237
238 if let Some(timestamp) = meta.timestamp {
239 writeln!(out, "Timestamp: {}", timestamp)?;
240 }
241
242 match entry {
243 ParsedNvramEntry::BootOrder(boot_order) => {
244 write!(out, "Boot Order:")?;
245 for x in boot_order {
246 write!(out, " {}", x)?;
247 }
248 writeln!(out)?;
249 }
250 ParsedNvramEntry::Boot(load_option) => {
251 writeln!(
252 out,
253 "Load Option: attributes: {:x}, description: {}",
254 load_option.attributes, load_option.description
255 )?;
256 for path in &load_option.device_paths {
257 writeln!(out, " - {:x?}", path)?;
258 }
259 if let Some(opt) = load_option.opt {
260 let prefix = " - opt: ";
261 write!(out, "{}", prefix)?;
262 print_hex_compact(out, opt, truncate.then(|| LINE_WIDTH - prefix.len()))?;
263 writeln!(out)?;
264 }
265 }
266 ParsedNvramEntry::SignatureList(sig_lists) => {
267 writeln!(out, "Signature Lists:")?;
268 for sig in sig_lists {
269 match sig {
270 SignatureList::Sha256(list) => {
271 writeln!(out, " - [Sha256]")?;
272 for sig in list {
273 let prefix = format!(
274 " - Signature Owner: {} Data: ",
275 sig.header.signature_owner
276 );
277 write!(out, "{}", &prefix)?;
278 print_hex_compact(
279 out,
280 sig.data.0.deref(),
281 truncate.then(|| LINE_WIDTH - prefix.len()),
282 )?;
283 writeln!(out)?;
284 }
285 }
286 SignatureList::X509(sig) => {
287 let prefix = format!(
288 " - [X509] Signature Owner: {} Data: ",
289 sig.header.signature_owner
290 );
291 write!(out, "{}", &prefix)?;
292 print_hex_compact(
293 out,
294 sig.data.0.deref(),
295 truncate.then(|| LINE_WIDTH - prefix.len()),
296 )?;
297 writeln!(out)?;
298 }
299 }
300 }
301 }
302 ParsedNvramEntry::Unknown(data) => {
303 let prefix = "data: ";
304 write!(out, "{}", prefix)?;
305 print_hex_compact(out, data, truncate.then(|| LINE_WIDTH - prefix.len()))?;
306 writeln!(out)?;
307 }
308 }
309
310 writeln!(out)?;
311
312 Ok(())
313}
314
315fn print_hex_compact(
316 out: &mut impl Write,
317 data: &[u8],
318 truncate: Option<usize>,
319) -> std::io::Result<()> {
320 if let Some(truncate) = truncate {
321 let ellipsis = "...";
322 let num_bytes = (truncate - ellipsis.len()) / 2;
323 for byte in data.iter().take(num_bytes) {
324 write!(out, "{:02x}", byte)?;
325 }
326 if data.len() > num_bytes {
327 write!(out, "{}", ellipsis)?;
328 }
329 } else {
330 for byte in data {
331 write!(out, "{:02x}", byte)?;
332 }
333 }
334
335 Ok(())
336}
337
338async fn vmgs_file_open_nvram(
339 file_path: impl AsRef<Path>,
340 key_path: Option<impl AsRef<Path>>,
341 open_mode: OpenMode,
342) -> Result<HclCompatNvram<VmgsStorageBackend>, Error> {
343 let vmgs = vmgs_file_open(file_path, key_path, open_mode).await?;
344 let encrypted = vmgs.is_encrypted();
345
346 open_nvram(vmgs, encrypted).await
347}
348
349async fn open_nvram(
350 vmgs: Vmgs,
351 encrypted: bool,
352) -> Result<HclCompatNvram<VmgsStorageBackend>, Error> {
353 Ok(HclCompatNvram::new(
354 VmgsStorageBackend::new(vmgs, vmgs::FileId::BIOS_NVRAM, encrypted)
355 .map_err(Error::VmgsStorageBackend)?,
356 None,
357 false,
358 )
359 .await?)
360}
361
362async fn vmgs_file_remove_boot_entries(
365 file_path: impl AsRef<Path>,
366 key_path: Option<impl AsRef<Path>>,
367 dry_run: bool,
368) -> Result<(), Error> {
369 let mut nvram_storage = vmgs_file_open_nvram(file_path, key_path, OpenMode::ReadWrite).await?;
370
371 if dry_run {
372 eprintln!("Printing Boot Entries (Dry-run)");
373 } else {
374 eprintln!("Deleting Boot Entries");
375 }
376
377 let name = Ucs2LeVec::from("BootOrder".to_string());
378 let (_, boot_order_bytes, _) = nvram_storage
379 .get_variable(&name, EFI_GLOBAL_VARIABLE)
380 .await?
381 .ok_or(Error::MissingNvramEntry(name.clone()))?;
382 let boot_order = boot_order::parse_boot_order(&boot_order_bytes)
383 .map_err(uefi_nvram_specvars::ParseError::BootOrder)?;
384
385 if !dry_run {
386 if !nvram_storage
387 .remove_variable(&name, EFI_GLOBAL_VARIABLE)
388 .await?
389 {
390 return Err(Error::MissingNvramEntry(name));
391 }
392 }
393
394 for (i, boot_option_num) in boot_order.enumerate() {
395 let name = Ucs2LeVec::from(format!("Boot{:04x}", boot_option_num));
396 let (_, boot_option_bytes, _) = nvram_storage
397 .get_variable(&name, EFI_GLOBAL_VARIABLE)
398 .await?
399 .ok_or(Error::MissingNvramEntry(name.clone()))?;
400 let boot_option = boot_order::EfiLoadOption::parse(&boot_option_bytes)
401 .map_err(uefi_nvram_specvars::ParseError::BootOrder)?;
402
403 println!("{i}: {}: {:x?}", &name, boot_option);
404
405 if !dry_run {
406 if !nvram_storage
407 .remove_variable(&name, EFI_GLOBAL_VARIABLE)
408 .await?
409 {
410 return Err(Error::MissingNvramEntry(name));
411 }
412 }
413 }
414
415 Ok(())
416}
417
418async fn vmgs_file_remove_nvram_entry(
420 file_path: impl AsRef<Path>,
421 key_path: Option<impl AsRef<Path>>,
422 name: String,
423 vendor: String,
424) -> Result<(), Error> {
425 let mut nvram_storage = vmgs_file_open_nvram(file_path, key_path, OpenMode::ReadWrite).await?;
426
427 eprintln!("Removing variable with name {name} and vendor {vendor}");
428
429 let name = Ucs2LeVec::from(name);
430 let vendor = Guid::from_str(&vendor)?;
431
432 if !nvram_storage.remove_variable(&name, vendor).await? {
433 return Err(Error::MissingNvramEntry(name));
434 }
435
436 Ok(())
437}