Skip to content

Commit f1776d1

Browse files
runtime: Add variable key functions
The interface is slightly different than in `uefi/src/table/runtime.rs`, where we just have a function to get all keys at once in a `Vec<VariableKey>`. For the new implementation, two interfaces are provided: `get_next_variable_key` is a low-level interface that does not require the `alloc` feature; it's a simple wrapper around `GetNextVariableName`. `variable_keys` is a high-level interface that returns an iterator. (Following the conventions of Rust's std lib, the iterator type is named `VariableKeys`.) This requires `alloc`.
1 parent 80a8923 commit f1776d1

File tree

1 file changed

+146
-1
lines changed

1 file changed

+146
-1
lines changed

uefi/src/runtime.rs

+146-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use crate::{table, CStr16, Error, Result, Status, StatusExt};
99
use core::ptr::{self, NonNull};
1010

1111
#[cfg(feature = "alloc")]
12-
use {crate::mem::make_boxed, alloc::boxed::Box};
12+
use {
13+
crate::mem::make_boxed, crate::Guid, alloc::borrow::ToOwned, alloc::boxed::Box,
14+
alloc::vec::Vec, core::mem,
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,145 @@ 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<'n, 'v>(
177+
name: &'n mut [u16],
178+
vendor: &'v 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 = name.len() * mem::size_of::<u16>();
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+
pub fn variable_keys() -> VariableKeys {
203+
VariableKeys::new()
204+
}
205+
206+
/// Iterator over all UEFI variables.
207+
///
208+
/// Each iteration yields a `Result<`[`VariableKey`]`>`. Error values:
209+
///
210+
/// * [`Status::DEVICE_ERROR`]: variable could not be read due to a hardware error.
211+
/// * [`Status::UNSUPPORTED`]: this platform does not support variable storage
212+
/// after exiting boot services.
213+
#[cfg(feature = "alloc")]
214+
#[derive(Debug)]
215+
pub struct VariableKeys {
216+
name: Vec<u16>,
217+
vendor: VariableVendor,
218+
is_done: bool,
219+
}
220+
221+
#[cfg(feature = "alloc")]
222+
impl VariableKeys {
223+
fn new() -> Self {
224+
// Create a the name buffer with a reasonable default capacity, and
225+
// initialize it to an empty null-terminated string.
226+
let mut name = Vec::with_capacity(32);
227+
name.push(0);
228+
229+
Self {
230+
// Give the name buffer a reasonable default capacity.
231+
name,
232+
// The initial vendor GUID is arbitrary.
233+
vendor: VariableVendor(Guid::default()),
234+
is_done: false,
235+
}
236+
}
237+
}
238+
239+
#[cfg(feature = "alloc")]
240+
impl Iterator for VariableKeys {
241+
type Item = Result<VariableKey>;
242+
243+
fn next(&mut self) -> Option<Result<VariableKey>> {
244+
if self.is_done {
245+
return None;
246+
}
247+
248+
let mut result = get_next_variable_key(&mut self.name, &mut self.vendor);
249+
250+
// If the name buffer was too small, resize it to be big enough and call
251+
// `get_next_variable_key` again.
252+
if let Err(err) = &result {
253+
if let Some(required_size) = err.data() {
254+
self.name.resize(*required_size, 0u16);
255+
result = get_next_variable_key(&mut self.name, &mut self.vendor);
256+
}
257+
}
258+
259+
match result {
260+
Ok(()) => {
261+
// Copy the name buffer, truncated after the first null
262+
// character (if one is present).
263+
let name = if let Some(nul_pos) = self.name.iter().position(|c| *c == 0) {
264+
self.name[..=nul_pos].to_owned()
265+
} else {
266+
self.name.clone()
267+
};
268+
Some(Ok(VariableKey {
269+
name,
270+
vendor: self.vendor,
271+
}))
272+
}
273+
Err(err) => {
274+
if err.status() == Status::NOT_FOUND {
275+
// This status indicates the end of the list. The final variable
276+
// has already been yielded at this point, so return `None`.
277+
self.is_done;
278+
None
279+
} else {
280+
// Return the error and end iteration.
281+
self.is_done = true;
282+
Some(Err(err.to_err_without_payload()))
283+
}
284+
}
285+
}
286+
}
287+
}
288+
144289
/// Sets the value of a variable. This can be used to create a new variable,
145290
/// update an existing variable, or (when the size of `data` is zero)
146291
/// delete a variable.

0 commit comments

Comments
 (0)