Skip to main content

patina/runtime_services/
variable_services.rs

1use core::mem;
2
3use alloc::{vec, vec::Vec};
4use fallible_streaming_iterator::FallibleStreamingIterator;
5use r_efi::efi::{self, Guid};
6
7use super::RuntimeServices;
8
9/// Status information returned by [`RuntimeServices::get_variable_unchecked`]
10#[derive(Debug)]
11pub enum GetVariableStatus {
12    /// The variable was unable to be retrieved
13    Error(efi::Status),
14    /// The variable was found, but the buffer provided wasn't large enough
15    BufferTooSmall {
16        /// The size of a buffer needed to retrieve the variable data
17        data_size: usize,
18        /// The attributes of the variable
19        attributes: u32,
20    },
21    /// The variable was successfully retrieved
22    Success {
23        /// The size of the variable data retrieved
24        data_size: usize,
25        /// The attributes of the variable
26        attributes: u32,
27    },
28}
29
30/// Variable information returned by [`RuntimeServices::query_variable_info`]
31#[derive(Debug)]
32pub struct VariableInfo {
33    /// The maximum size of the storage space available for the EFI variables associated with the attributes specified
34    pub maximum_variable_storage_size: u64,
35    /// The remaining size of the storage space available for EFI variables associated with the attributes specified
36    pub remaining_variable_storage_size: u64,
37    /// The maximum size of an individual EFI variable associated with the attributes specified
38    pub maximum_variable_size: u64,
39}
40
41/// Uniquely identifies a UEFI variable
42#[derive(Debug)]
43pub struct VariableIdentifier {
44    /// The name of a UEFI variable
45    name: Vec<u16>,
46    /// The namespace of a UEFI variable
47    namespace: efi::Guid,
48}
49
50/// Provides a [`FallibleStreamingIterator`] over UEFI variable names
51///
52/// Produces an EFI status on error.
53///
54/// # Examples
55///
56/// ## Iterating through all UEFI variable names
57/// ```ignore
58/// pub static RUNTIME_SERVICES: StandardRuntimeServices =
59///     StandardRuntimeServices::new(&(*runtime_services_ptr));
60/// let mut iter = VariableNameIterator::new_from_first(runtime_services);
61/// while let Some(variable_identifier) = iter.next()? {
62///     some_function(variable_identifier.name, variable_identifier.namespace);
63/// }
64/// ```
65///
66/// ## Iterating through UEFI variable names, starting with a known one
67/// ```ignore
68/// let mut iter = VariableNameIterator::new_from_variable(
69///     &SOME_VARIABLE_NAME,
70///     &SOME_VARIABLE_NAMESPACE,
71///     runtime_services
72/// );
73///
74/// while let Some(variable_identifier) = iter.next()? {
75///     some_function(variable_identifier.name, variable_identifier.namespace);
76/// }
77/// ```
78#[derive(Debug)]
79pub struct VariableNameIterator<'a, R: RuntimeServices> {
80    rs: &'a R,
81
82    current: VariableIdentifier,
83    next: VariableIdentifier,
84    finished: bool,
85}
86
87impl<'a, R: RuntimeServices> VariableNameIterator<'a, R> {
88    /// Produce a new iterator from the beginning of the UEFI variable list
89    pub fn new_from_first(runtime_services: &'a R) -> Self {
90        Self {
91            rs: runtime_services,
92            current: VariableIdentifier {
93                name: {
94                    // Previous name should be an empty string to get the first variable
95                    let prev_name = vec![0; 1];
96
97                    prev_name
98                },
99                // When calling with an empty name, the GUID is ignored.
100                // We can just set it to zero.
101                namespace: Guid::from_bytes(&[0x0; 16]),
102            },
103            next: VariableIdentifier { name: Vec::<u16>::new(), namespace: Guid::from_bytes(&[0x0; 16]) },
104            finished: false,
105        }
106    }
107
108    /// Produce a new iterator, starting from a given variable
109    pub fn new_from_variable(name: &[u16], namespace: &efi::Guid, runtime_services: &'a R) -> Self {
110        Self {
111            rs: runtime_services,
112            current: VariableIdentifier { name: name.to_vec(), namespace: *namespace },
113            next: VariableIdentifier { name: Vec::<u16>::new(), namespace: Guid::from_bytes(&[0x0; 16]) },
114            finished: false,
115        }
116    }
117}
118
119impl<R: RuntimeServices> FallibleStreamingIterator for VariableNameIterator<'_, R> {
120    type Item = VariableIdentifier;
121    type Error = efi::Status;
122
123    fn advance(&mut self) -> Result<(), Self::Error> {
124        // SAFETY: get_next_variable_name_unchecked is called with valid variable name and namespace references.
125        // The iterator manages the state and ensures proper sequencing through the variable list.
126        unsafe {
127            // Don't do anything if we've reached the end already
128            if self.finished {
129                return Ok(());
130            }
131
132            let status = self.rs.get_next_variable_name_unchecked(
133                &self.current.name,
134                &self.current.namespace,
135                &mut self.next.name,
136                &mut self.next.namespace,
137            );
138
139            mem::swap(&mut self.current, &mut self.next);
140
141            if status.is_err() && status.unwrap_err() == efi::Status::NOT_FOUND {
142                self.finished = true;
143                Ok(())
144            } else {
145                status
146            }
147        }
148    }
149
150    fn get(&self) -> Option<&Self::Item> {
151        if self.finished { None } else { Some(&self.current) }
152    }
153}
154
155#[cfg(test)]
156#[coverage(off)]
157mod test {
158    use r_efi::efi;
159
160    use super::*;
161    use crate::runtime_services::{
162        StandardRuntimeServices,
163        test::{
164            DUMMY_FIRST_NAME, DUMMY_FIRST_NAMESPACE, DUMMY_SECOND_NAME, DUMMY_SECOND_NAMESPACE,
165            mock_efi_get_next_variable_name, runtime_services,
166        },
167    };
168    use std::mem;
169
170    #[test]
171    fn test_variable_name_iterator_from_first() {
172        let rs = runtime_services!(get_next_variable_name = mock_efi_get_next_variable_name);
173
174        let mut iter = VariableNameIterator::new_from_first(&rs);
175
176        // Make sure the first result corresponds to DUMMY_FIRST_NAME
177        let mut status = iter.next();
178        assert!(status.is_ok());
179        assert!(status.unwrap().is_some());
180        let mut variable_identifier = status.unwrap().unwrap();
181        assert_eq!(variable_identifier.name, DUMMY_FIRST_NAME);
182        assert_eq!(variable_identifier.namespace, DUMMY_FIRST_NAMESPACE);
183
184        // Make sure the second result corresponds to DUMMY_SECOND_NAME
185        status = iter.next();
186        assert!(status.is_ok());
187        assert!(status.unwrap().is_some());
188        variable_identifier = status.unwrap().unwrap();
189        assert_eq!(variable_identifier.name, DUMMY_SECOND_NAME);
190        assert_eq!(variable_identifier.namespace, DUMMY_SECOND_NAMESPACE);
191
192        // Make sure the third result indicates we've reached the end
193        status = iter.next();
194        assert!(status.is_ok());
195        assert!(status.unwrap().is_none());
196    }
197
198    #[test]
199    fn test_variable_name_iterator_from_second() {
200        let rs = runtime_services!(get_next_variable_name = mock_efi_get_next_variable_name);
201
202        let mut iter = VariableNameIterator::new_from_variable(&DUMMY_FIRST_NAME, &DUMMY_FIRST_NAMESPACE, &rs);
203
204        // Make sure the first result corresponds to DUMMY_SECOND_NAME
205        let mut status = iter.next();
206        assert!(status.is_ok());
207        assert!(status.unwrap().is_some());
208        let variable_identifier = status.unwrap().unwrap();
209        assert_eq!(variable_identifier.name, DUMMY_SECOND_NAME);
210        assert_eq!(variable_identifier.namespace, DUMMY_SECOND_NAMESPACE);
211
212        // Make sure the second result indicates we've reached the end
213        status = iter.next();
214        assert!(status.is_ok());
215        assert!(status.unwrap().is_none());
216    }
217}