Skip to content

Commit 216e002

Browse files
RenTrieuphip1611
andcommitted
uefi: Implementing var(), vars(), and set_var() for shell interface protocol
The var() and vars() functions wrap the UEFI get_env() implementation to retrieve a single and multiple environment variables respectively. The set_var() function wraps the UEFI set_env() variable to set the value of an environment variable. Co-authored-by: Philipp Schuster <phip1611@gmail.com>
1 parent 90c5ba4 commit 216e002

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

uefi-test-runner/src/proto/shell.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,48 @@ pub fn test_current_dir(shell: &ScopedProtocol<Shell>) {
100100
assert_eq!(cur_fs_str, expected_fs_str);
101101
}
102102

103+
/// Test `var()`, `vars()`, and `set_var()`
104+
pub fn test_var(shell: &ScopedProtocol<Shell>) {
105+
/* Test retrieving list of environment variable names */
106+
let mut cur_env_vec = shell.vars();
107+
assert_eq!(cur_env_vec.next().unwrap().0, cstr16!("path"));
108+
// check pre-defined shell variables; see UEFI Shell spec
109+
assert_eq!(cur_env_vec.next().unwrap().0, cstr16!("nonesting"));
110+
let cur_env_vec = shell.vars();
111+
let default_len = cur_env_vec.count();
112+
113+
/* Test setting and getting a specific environment variable */
114+
let test_var = cstr16!("test_var");
115+
let test_val = cstr16!("test_val");
116+
117+
let found_var = shell.vars().any(|(env_var, _)| env_var == test_var);
118+
assert!(!found_var);
119+
assert!(shell.var(test_var).is_none());
120+
121+
let status = shell.set_var(test_var, test_val, false);
122+
assert!(status.is_ok());
123+
let cur_env_str = shell
124+
.var(test_var)
125+
.expect("Could not get environment variable");
126+
assert_eq!(cur_env_str, test_val);
127+
128+
let found_var = shell.vars().any(|(env_var, _)| env_var == test_var);
129+
assert!(found_var);
130+
let cur_env_vec = shell.vars();
131+
assert_eq!(cur_env_vec.count(), default_len + 1);
132+
133+
/* Test deleting environment variable */
134+
let test_val = cstr16!("");
135+
let status = shell.set_var(test_var, test_val, false);
136+
assert!(status.is_ok());
137+
assert!(shell.var(test_var).is_none());
138+
139+
let found_var = shell.vars().any(|(env_var, _)| env_var == test_var);
140+
assert!(!found_var);
141+
let cur_env_vec = shell.vars();
142+
assert_eq!(cur_env_vec.count(), default_len);
143+
}
144+
103145
pub fn test() {
104146
info!("Running shell protocol tests");
105147

@@ -109,4 +151,5 @@ pub fn test() {
109151
boot::open_protocol_exclusive::<Shell>(handle).expect("Failed to open Shell protocol");
110152

111153
test_current_dir(&shell);
154+
test_var(&shell);
112155
}

uefi/src/proto/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub mod rng;
5151
#[cfg(feature = "alloc")]
5252
pub mod scsi;
5353
pub mod security;
54+
#[cfg(feature = "alloc")]
5455
pub mod shell;
5556
pub mod shell_params;
5657
pub mod shim;

uefi/src/proto/shell/mod.rs

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
55
use crate::proto::unsafe_protocol;
66
use crate::{CStr16, Char16, Error, Result, Status, StatusExt};
7+
8+
use core::marker::PhantomData;
79
use core::ptr;
810
use uefi_raw::protocol::shell::ShellProtocol;
911

@@ -13,6 +15,45 @@ use uefi_raw::protocol::shell::ShellProtocol;
1315
#[unsafe_protocol(ShellProtocol::GUID)]
1416
pub struct Shell(ShellProtocol);
1517

18+
/// Trait for implementing the var function
19+
pub trait ShellVarProvider {
20+
/// Gets the value of the specified environment variable
21+
fn var(&self, name: &CStr16) -> Option<&CStr16>;
22+
}
23+
24+
/// Iterator over the names of environmental variables obtained from the Shell protocol.
25+
#[derive(Debug)]
26+
pub struct Vars<'a, T: ShellVarProvider> {
27+
/// Char16 containing names of environment variables
28+
names: *const Char16,
29+
/// Reference to Shell Protocol
30+
protocol: *const T,
31+
/// Marker to attach a lifetime to `Vars`
32+
_marker: PhantomData<&'a CStr16>,
33+
}
34+
35+
impl<'a, T: ShellVarProvider + 'a> Iterator for Vars<'a, T> {
36+
type Item = (&'a CStr16, Option<&'a CStr16>);
37+
// We iterate a list of NUL terminated CStr16s.
38+
// The list is terminated with a double NUL.
39+
fn next(&mut self) -> Option<Self::Item> {
40+
let s = unsafe { CStr16::from_ptr(self.names) };
41+
if s.is_empty() {
42+
None
43+
} else {
44+
self.names = unsafe { self.names.add(s.num_chars() + 1) };
45+
Some((s, unsafe { self.protocol.as_ref().unwrap().var(s) }))
46+
}
47+
}
48+
}
49+
50+
impl ShellVarProvider for Shell {
51+
/// Gets the value of the specified environment variable
52+
fn var(&self, name: &CStr16) -> Option<&CStr16> {
53+
self.var(name)
54+
}
55+
}
56+
1657
impl Shell {
1758
/// Returns the current directory on the specified device.
1859
///
@@ -54,4 +95,159 @@ impl Shell {
5495
let dir_ptr: *const Char16 = directory.map_or(ptr::null(), |x| x.as_ptr());
5596
unsafe { (self.0.set_cur_dir)(fs_ptr.cast(), dir_ptr.cast()) }.to_result()
5697
}
98+
99+
/// Gets the value of the specified environment variable
100+
///
101+
/// # Arguments
102+
///
103+
/// * `name` - The environment variable name of which to retrieve the
104+
/// value.
105+
///
106+
/// # Returns
107+
///
108+
/// * `Some(<env_value>)` - &CStr16 containing the value of the
109+
/// environment variable
110+
/// * `None` - If environment variable does not exist
111+
#[must_use]
112+
pub fn var(&self, name: &CStr16) -> Option<&CStr16> {
113+
let name_ptr: *const Char16 = name.as_ptr();
114+
let var_val = unsafe { (self.0.get_env)(name_ptr.cast()) };
115+
if var_val.is_null() {
116+
None
117+
} else {
118+
unsafe { Some(CStr16::from_ptr(var_val.cast())) }
119+
}
120+
}
121+
122+
/// Gets an iterator over the names of all environment variables
123+
///
124+
/// # Returns
125+
///
126+
/// * `Vars` - Iterator over the names of the environment variables
127+
#[must_use]
128+
pub fn vars(&self) -> Vars<'_, Self> {
129+
let env_ptr = unsafe { (self.0.get_env)(ptr::null()) };
130+
Vars {
131+
names: env_ptr.cast::<Char16>(),
132+
protocol: self,
133+
_marker: PhantomData,
134+
}
135+
}
136+
137+
/// Sets the environment variable
138+
///
139+
/// # Arguments
140+
///
141+
/// * `name` - The environment variable for which to set the value
142+
/// * `value` - The new value of the environment variable
143+
/// * `volatile` - Indicates whether the variable is volatile or
144+
/// not
145+
///
146+
/// # Returns
147+
///
148+
/// * `Status::SUCCESS` - The variable was successfully set
149+
pub fn set_var(&self, name: &CStr16, value: &CStr16, volatile: bool) -> Result {
150+
let name_ptr: *const Char16 = name.as_ptr();
151+
let value_ptr: *const Char16 = value.as_ptr();
152+
unsafe { (self.0.set_env)(name_ptr.cast(), value_ptr.cast(), volatile) }.to_result()
153+
}
154+
}
155+
156+
#[cfg(test)]
157+
mod tests {
158+
use super::*;
159+
use alloc::collections::BTreeMap;
160+
use alloc::vec::Vec;
161+
use uefi::cstr16;
162+
163+
struct ShellMock<'a> {
164+
inner: BTreeMap<&'a CStr16, &'a CStr16>,
165+
}
166+
167+
impl<'a> ShellMock<'a> {
168+
fn new(pairs: impl IntoIterator<Item = (&'a CStr16, &'a CStr16)>) -> ShellMock<'a> {
169+
let mut inner_map = BTreeMap::new();
170+
for (name, val) in pairs.into_iter() {
171+
inner_map.insert(name, val);
172+
}
173+
ShellMock { inner: inner_map }
174+
}
175+
}
176+
impl<'a> ShellVarProvider for ShellMock<'a> {
177+
fn var(&self, name: &CStr16) -> Option<&CStr16> {
178+
if let Some(val) = self.inner.get(name) {
179+
Some(*val)
180+
} else {
181+
None
182+
}
183+
}
184+
}
185+
186+
/// Testing Vars struct
187+
#[test]
188+
fn test_vars() {
189+
// Empty Vars
190+
let mut vars_mock = Vec::<u16>::new();
191+
vars_mock.extend_from_slice(
192+
b"\0\0"
193+
.into_iter()
194+
.map(|&x| x as u16)
195+
.collect::<Vec<_>>()
196+
.as_slice(),
197+
);
198+
let mut vars = Vars {
199+
names: vars_mock.as_ptr().cast(),
200+
protocol: &ShellMock::new(Vec::new()),
201+
_marker: PhantomData,
202+
};
203+
204+
assert!(vars.next().is_none());
205+
206+
// One environment variable in Vars
207+
let mut vars_mock = Vec::<u16>::new();
208+
vars_mock.extend_from_slice(
209+
b"foo\0\0"
210+
.into_iter()
211+
.map(|&x| x as u16)
212+
.collect::<Vec<_>>()
213+
.as_slice(),
214+
);
215+
let vars = Vars {
216+
names: vars_mock.as_ptr().cast(),
217+
protocol: &ShellMock::new(Vec::from([(cstr16!("foo"), cstr16!("value"))])),
218+
_marker: PhantomData,
219+
};
220+
assert_eq!(
221+
vars.collect::<Vec<_>>(),
222+
Vec::from([(cstr16!("foo"), Some(cstr16!("value")))])
223+
);
224+
225+
// Multiple environment variables in Vars
226+
let mut vars_mock = Vec::<u16>::new();
227+
vars_mock.extend_from_slice(
228+
b"foo1\0bar\0baz2\0\0"
229+
.into_iter()
230+
.map(|&x| x as u16)
231+
.collect::<Vec<_>>()
232+
.as_slice(),
233+
);
234+
235+
let vars = Vars {
236+
names: vars_mock.as_ptr().cast(),
237+
protocol: &ShellMock::new(Vec::from([
238+
(cstr16!("foo1"), cstr16!("value")),
239+
(cstr16!("bar"), cstr16!("one")),
240+
(cstr16!("baz2"), cstr16!("two")),
241+
])),
242+
_marker: PhantomData,
243+
};
244+
assert_eq!(
245+
vars.collect::<Vec<_>>(),
246+
Vec::from([
247+
(cstr16!("foo1"), Some(cstr16!("value"))),
248+
(cstr16!("bar"), Some(cstr16!("one"))),
249+
(cstr16!("baz2"), Some(cstr16!("two")))
250+
])
251+
);
252+
}
57253
}

0 commit comments

Comments
 (0)