Skip to content

Commit 4fd9aed

Browse files
authored
Merge pull request #1252 from rust-osdev/bishop-runtime-mod-varkeys-2
Add variable key functions to `uefi::runtime`
2 parents 0212d43 + bea0fd9 commit 4fd9aed

File tree

4 files changed

+170
-3
lines changed

4 files changed

+170
-3
lines changed

uefi-test-runner/src/runtime/vars.rs

+20
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ fn test_variables(rt: &RuntimeServices) {
5151
info!("First variable: {}", key);
5252
}
5353

54+
// Test that the `runtime::variable_keys` iterator gives exactly the same
55+
// list as the `RuntimeServices::variable_keys` function.
56+
assert_eq!(
57+
runtime::variable_keys()
58+
.map(|k| k.unwrap())
59+
.collect::<alloc::vec::Vec<_>>(),
60+
variable_keys
61+
);
62+
5463
info!("Testing delete_variable()");
5564
rt.delete_variable(NAME, VENDOR)
5665
.expect("failed to delete variable");
@@ -86,6 +95,15 @@ fn test_variables_freestanding() {
8695
assert_eq!(&*data, VALUE);
8796
assert_eq!(attrs, ATTRS);
8897

98+
// Test that the variable is present in the `variable_keys` iterator.
99+
let find_by_key = || {
100+
runtime::variable_keys().any(|k| {
101+
let k = k.as_ref().unwrap();
102+
k.name().unwrap() == NAME && &k.vendor == VENDOR
103+
})
104+
};
105+
assert!(find_by_key());
106+
89107
// Delete the variable and verify it can no longer be read.
90108
runtime::delete_variable(NAME, VENDOR).expect("failed to delete variable");
91109
assert_eq!(
@@ -94,6 +112,8 @@ fn test_variables_freestanding() {
94112
.status(),
95113
Status::NOT_FOUND
96114
);
115+
// Variable is no longer present in the `variable_keys` iterator.
116+
assert!(!find_by_key());
97117
}
98118

99119
fn test_variable_info(rt: &RuntimeServices) {

uefi/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Add standard derives for `ConfigTableEntry`.
99
- `PcrEvent`/`PcrEventInputs` impl `Align`, `Eq`, and `PartialEq`.
1010
- Added `PcrEvent::new_in_box` and `PcrEventInputs::new_in_box`.
11+
- `VariableKey` impls `Clone`, `Eq`, `PartialEq`, `Ord`, `PartialOrd`, and `Hash`.
1112

1213
## Changed
1314
- **Breaking:** `uefi::helpers::init` no longer takes an argument.

uefi/src/runtime.rs

+147-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
//! of the UEFI specification for details.
77
88
use crate::{table, CStr16, Error, Result, Status, StatusExt};
9+
use core::mem;
910
use core::ptr::{self, NonNull};
1011

1112
#[cfg(feature = "alloc")]
12-
use {crate::mem::make_boxed, alloc::boxed::Box};
13+
use {
14+
crate::mem::make_boxed, crate::Guid, alloc::borrow::ToOwned, alloc::boxed::Box, alloc::vec::Vec,
15+
};
1316

1417
#[cfg(all(feature = "unstable", feature = "alloc"))]
1518
use alloc::alloc::Global;
@@ -18,6 +21,9 @@ pub use crate::table::runtime::{Daylight, Time, TimeCapabilities, TimeError, Tim
1821
pub use uefi_raw::capsule::{CapsuleBlockDescriptor, CapsuleFlags, CapsuleHeader};
1922
pub use uefi_raw::table::runtime::{ResetType, VariableAttributes, VariableVendor};
2023

24+
#[cfg(feature = "alloc")]
25+
pub use crate::table::runtime::VariableKey;
26+
2127
fn runtime_services_raw_panicking() -> NonNull<uefi_raw::table::runtime::RuntimeServices> {
2228
let st = table::system_table_raw_panicking();
2329
// SAFETY: valid per requirements of `set_system_table`.
@@ -141,6 +147,146 @@ pub fn get_variable_boxed(
141147
}
142148
}
143149

150+
/// Gets each variable key (name and vendor) one at a time.
151+
///
152+
/// This is used to iterate over variable keys. See [`variable_keys`] for a more
153+
/// convenient interface that requires the `alloc` feature.
154+
///
155+
/// To get the first variable key, `name` must be initialized to start with a
156+
/// null character. The `vendor` value is arbitrary. On success, the first
157+
/// variable's name and vendor will be written out to `name` and `vendor`. Keep
158+
/// calling `get_next_variable_key` with the same `name` and `vendor` references
159+
/// to get the remaining variable keys.
160+
///
161+
/// All variable names should be valid strings, but this may not be enforced by
162+
/// firmware. To convert to a string, truncate at the first null and call
163+
/// [`CStr16::from_u16_with_nul`].
164+
///
165+
/// # Errors
166+
///
167+
/// * [`Status::NOT_FOUND`]: indicates end of iteration, the last variable keys
168+
/// was retrieved by the previous call to `get_next_variable_key`.
169+
/// * [`Status::BUFFER_TOO_SMALL`]: `name` is not large enough. The required
170+
/// size (in `u16` characters, not bytes) will be returned in the error data.
171+
/// * [`Status::INVALID_PARAMETER`]: `name` does not contain a null character, or
172+
/// the `name` and `vendor` are not an existing variable.
173+
/// * [`Status::DEVICE_ERROR`]: variable could not be read due to a hardware error.
174+
/// * [`Status::UNSUPPORTED`]: this platform does not support variable storage
175+
/// after exiting boot services.
176+
pub fn get_next_variable_key(
177+
name: &mut [u16],
178+
vendor: &mut VariableVendor,
179+
) -> Result<(), Option<usize>> {
180+
let rt = runtime_services_raw_panicking();
181+
let rt = unsafe { rt.as_ref() };
182+
183+
let mut name_size_in_bytes = mem::size_of_val(name);
184+
185+
let status = unsafe {
186+
(rt.get_next_variable_name)(&mut name_size_in_bytes, name.as_mut_ptr(), &mut vendor.0)
187+
};
188+
match status {
189+
Status::SUCCESS => Ok(()),
190+
Status::BUFFER_TOO_SMALL => Err(Error::new(
191+
status,
192+
Some(name_size_in_bytes / mem::size_of::<u16>()),
193+
)),
194+
_ => Err(Error::new(status, None)),
195+
}
196+
}
197+
198+
/// Get an iterator over all UEFI variables.
199+
///
200+
/// See [`VariableKeys`] for details.
201+
#[cfg(feature = "alloc")]
202+
#[must_use]
203+
pub fn variable_keys() -> VariableKeys {
204+
VariableKeys::new()
205+
}
206+
207+
/// Iterator over all UEFI variables.
208+
///
209+
/// Each iteration yields a `Result<`[`VariableKey`]`>`. Error values:
210+
///
211+
/// * [`Status::DEVICE_ERROR`]: variable could not be read due to a hardware error.
212+
/// * [`Status::UNSUPPORTED`]: this platform does not support variable storage
213+
/// after exiting boot services.
214+
#[cfg(feature = "alloc")]
215+
#[derive(Debug)]
216+
pub struct VariableKeys {
217+
name: Vec<u16>,
218+
vendor: VariableVendor,
219+
is_done: bool,
220+
}
221+
222+
#[cfg(feature = "alloc")]
223+
impl VariableKeys {
224+
fn new() -> Self {
225+
// Create a the name buffer with a reasonable default capacity, and
226+
// initialize it to an empty null-terminated string.
227+
let mut name = Vec::with_capacity(32);
228+
name.push(0);
229+
230+
Self {
231+
// Give the name buffer a reasonable default capacity.
232+
name,
233+
// The initial vendor GUID is arbitrary.
234+
vendor: VariableVendor(Guid::default()),
235+
is_done: false,
236+
}
237+
}
238+
}
239+
240+
#[cfg(feature = "alloc")]
241+
impl Iterator for VariableKeys {
242+
type Item = Result<VariableKey>;
243+
244+
fn next(&mut self) -> Option<Result<VariableKey>> {
245+
if self.is_done {
246+
return None;
247+
}
248+
249+
let mut result = get_next_variable_key(&mut self.name, &mut self.vendor);
250+
251+
// If the name buffer was too small, resize it to be big enough and call
252+
// `get_next_variable_key` again.
253+
if let Err(err) = &result {
254+
if let Some(required_size) = err.data() {
255+
self.name.resize(*required_size, 0u16);
256+
result = get_next_variable_key(&mut self.name, &mut self.vendor);
257+
}
258+
}
259+
260+
match result {
261+
Ok(()) => {
262+
// Copy the name buffer, truncated after the first null
263+
// character (if one is present).
264+
let name = if let Some(nul_pos) = self.name.iter().position(|c| *c == 0) {
265+
self.name[..=nul_pos].to_owned()
266+
} else {
267+
self.name.clone()
268+
};
269+
Some(Ok(VariableKey {
270+
name,
271+
vendor: self.vendor,
272+
}))
273+
}
274+
Err(err) => {
275+
if err.status() == Status::NOT_FOUND {
276+
// This status indicates the end of the list. The final variable
277+
// has already been yielded at this point, so return `None`.
278+
self.is_done = true;
279+
None
280+
} else {
281+
// Return the error and end iteration.
282+
self.is_done = true;
283+
Some(Err(err.to_err_without_payload()))
284+
}
285+
}
286+
}
287+
}
288+
}
289+
144290
/// Sets the value of a variable. This can be used to create a new variable,
145291
/// update an existing variable, or (when the size of `data` is zero)
146292
/// delete a variable.

uefi/src/table/runtime.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -647,9 +647,9 @@ impl TryFrom<&[u8]> for Time {
647647

648648
/// Unique key for a variable.
649649
#[cfg(feature = "alloc")]
650-
#[derive(Debug)]
650+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
651651
pub struct VariableKey {
652-
name: Vec<u16>,
652+
pub(crate) name: Vec<u16>,
653653
/// Unique identifier for the vendor.
654654
pub vendor: VariableVendor,
655655
}

0 commit comments

Comments
 (0)