Skip to content

Commit 75f22a0

Browse files
RenTrieuphip1611
andcommitted
uefi: Implementing var(), vars(), and set_var() wrapper functions
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 31894f4 commit 75f22a0

File tree

2 files changed

+210
-90
lines changed

2 files changed

+210
-90
lines changed

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

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use uefi::boot::ScopedProtocol;
44
use uefi::proto::shell::Shell;
5-
use uefi::{Error, Status, boot, cstr16, CStr16};
5+
use uefi::{Error, Status, boot, cstr16};
66

77
/// Test `current_dir()` and `set_current_dir()`
88
pub fn test_current_dir(shell: &ScopedProtocol<Shell>) {
@@ -100,43 +100,46 @@ pub fn test_current_dir(shell: &ScopedProtocol<Shell>) {
100100
assert_eq!(cur_fs_str, expected_fs_str);
101101
}
102102

103-
/// Test ``get_env()`` and ``set_env()``
104-
pub fn test_env(shell: &ScopedProtocol<Shell>) {
105-
let mut test_buf = [0u16; 128];
106-
107-
/* Test retrieving list of environment variable names (null input) */
108-
let cur_env_vec = shell
109-
.get_env(None)
110-
.expect("Could not get environment variable");
111-
assert_eq!(
112-
*cur_env_vec.first().unwrap(),
113-
CStr16::from_str_with_buf("path", &mut test_buf).unwrap()
114-
);
115-
assert_eq!(
116-
*cur_env_vec.get(1).unwrap(),
117-
CStr16::from_str_with_buf("nonesting", &mut test_buf).unwrap()
118-
);
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();
119112

120113
/* Test setting and getting a specific environment variable */
121-
let mut test_env_buf = [0u16; 32];
122-
let test_var = CStr16::from_str_with_buf("test_var", &mut test_env_buf).unwrap();
123-
let mut test_val_buf = [0u16; 32];
124-
let test_val = CStr16::from_str_with_buf("test_val", &mut test_val_buf).unwrap();
125-
assert!(shell.get_env(Some(test_var)).is_none());
126-
let status = shell.set_env(test_var, test_val, false);
127-
assert_eq!(status, Status::SUCCESS);
128-
let cur_env_str = *shell
129-
.get_env(Some(test_var))
130-
.expect("Could not get environment variable")
131-
.first()
132-
.unwrap();
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");
133126
assert_eq!(cur_env_str, test_val);
134127

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+
135133
/* Test deleting environment variable */
136-
let test_val = CStr16::from_str_with_buf("", &mut test_val_buf).unwrap();
137-
let status = shell.set_env(test_var, test_val, false);
138-
assert_eq!(status, Status::SUCCESS);
139-
assert!(shell.get_env(Some(test_var)).is_none());
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);
140143
}
141144

142145
pub fn test() {
@@ -148,5 +151,5 @@ pub fn test() {
148151
boot::open_protocol_exclusive::<Shell>(handle).expect("Failed to open Shell protocol");
149152

150153
test_current_dir(&shell);
151-
test_env(&shell);
154+
test_var(&shell);
152155
}

uefi/src/proto/shell/mod.rs

Lines changed: 173 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,56 @@
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;
9-
use alloc::vec::Vec;
1011

1112
/// Shell Protocol
1213
#[derive(Debug)]
1314
#[repr(transparent)]
1415
#[unsafe_protocol(ShellProtocol::GUID)]
1516
pub struct Shell(ShellProtocol);
1617

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+
1757
impl Shell {
1858
/// Returns the current directory on the specified device.
1959
///
@@ -56,64 +96,42 @@ impl Shell {
5696
unsafe { (self.0.set_cur_dir)(fs_ptr.cast(), dir_ptr.cast()) }.to_result()
5797
}
5898

59-
/// Gets the environment variable or list of environment variables
99+
/// Gets the value of the specified environment variable
60100
///
61101
/// # Arguments
62102
///
63103
/// * `name` - The environment variable name of which to retrieve the
64-
/// value
65-
/// If None, will return all defined shell environment
66-
/// variables
104+
/// value.
67105
///
68106
/// # Returns
69107
///
70-
/// * `Some(Vec<env_value>)` - Value of the environment variable
71-
/// * `Some(Vec<env_names>)` - Vector of environment variable names
72-
/// * `None` - Environment variable doesn't exist
108+
/// * `Some(<env_value>)` - &CStr16 containing the value of the
109+
/// environment variable
110+
/// * `None` - If environment variable does not exist
73111
#[must_use]
74-
pub fn get_env<'a>(&'a self, name: Option<&CStr16>) -> Option<Vec<&'a CStr16>> {
75-
let mut env_vec = Vec::new();
76-
match name {
77-
Some(n) => {
78-
let name_ptr: *const Char16 = core::ptr::from_ref::<CStr16>(n).cast();
79-
let var_val = unsafe { (self.0.get_env)(name_ptr.cast()) };
80-
if var_val.is_null() {
81-
return None;
82-
} else {
83-
unsafe { env_vec.push(CStr16::from_ptr(var_val.cast())) };
84-
}
85-
}
86-
None => {
87-
let cur_env_ptr = unsafe { (self.0.get_env)(ptr::null()) };
88-
89-
let mut cur_start = cur_env_ptr;
90-
let mut cur_len = 0;
91-
92-
let mut i = 0;
93-
let mut null_count = 0;
94-
unsafe {
95-
while null_count <= 1 {
96-
if (*(cur_env_ptr.add(i))) == Char16::from_u16_unchecked(0).into() {
97-
if cur_len > 0 {
98-
env_vec.push(CStr16::from_char16_with_nul_unchecked(
99-
&(*ptr::slice_from_raw_parts(cur_start.cast(), cur_len + 1)),
100-
));
101-
}
102-
cur_len = 0;
103-
null_count += 1;
104-
} else {
105-
if null_count > 0 {
106-
cur_start = cur_env_ptr.add(i);
107-
}
108-
null_count = 0;
109-
cur_len += 1;
110-
}
111-
i += 1;
112-
}
113-
}
114-
}
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,
115134
}
116-
Some(env_vec)
117135
}
118136

119137
/// Sets the environment variable
@@ -122,15 +140,114 @@ impl Shell {
122140
///
123141
/// * `name` - The environment variable for which to set the value
124142
/// * `value` - The new value of the environment variable
125-
/// * `volatile` - Indicates whether or not the variable is volatile or
143+
/// * `volatile` - Indicates whether the variable is volatile or
126144
/// not
127145
///
128146
/// # Returns
129147
///
130-
/// * `Status::SUCCESS` The variable was successfully set
131-
pub fn set_env(&self, name: &CStr16, value: &CStr16, volatile: bool) -> Status {
132-
let name_ptr: *const Char16 = core::ptr::from_ref::<CStr16>(name).cast();
133-
let value_ptr: *const Char16 = core::ptr::from_ref::<CStr16>(value).cast();
134-
unsafe { (self.0.set_env)(name_ptr.cast(), value_ptr.cast(), volatile) }
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(names: Vec<&'a CStr16>, values: Vec<&'a CStr16>) -> ShellMock<'a> {
169+
let mut inner_map = BTreeMap::new();
170+
for (name, val) in names.iter().zip(values.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.push(0);
192+
vars_mock.push(0);
193+
let mut vars = Vars {
194+
names: vars_mock.as_ptr().cast(),
195+
protocol: &ShellMock::new(Vec::new(), Vec::new()),
196+
_marker: PhantomData,
197+
};
198+
199+
assert!(vars.next().is_none());
200+
201+
// One environment variable in Vars
202+
let mut vars_mock = Vec::<u16>::new();
203+
vars_mock.push(b'f' as u16);
204+
vars_mock.push(b'o' as u16);
205+
vars_mock.push(b'o' as u16);
206+
vars_mock.push(0);
207+
vars_mock.push(0);
208+
let vars = Vars {
209+
names: vars_mock.as_ptr().cast(),
210+
protocol: &ShellMock::new(Vec::from([cstr16!("foo")]), Vec::from([cstr16!("value")])),
211+
_marker: PhantomData,
212+
};
213+
assert_eq!(
214+
vars.collect::<Vec<_>>(),
215+
Vec::from([(cstr16!("foo"), Some(cstr16!("value")))])
216+
);
217+
218+
// Multiple environment variables in Vars
219+
let mut vars_mock = Vec::<u16>::new();
220+
vars_mock.push(b'f' as u16);
221+
vars_mock.push(b'o' as u16);
222+
vars_mock.push(b'o' as u16);
223+
vars_mock.push(b'1' as u16);
224+
vars_mock.push(0);
225+
vars_mock.push(b'b' as u16);
226+
vars_mock.push(b'a' as u16);
227+
vars_mock.push(b'r' as u16);
228+
vars_mock.push(0);
229+
vars_mock.push(b'b' as u16);
230+
vars_mock.push(b'a' as u16);
231+
vars_mock.push(b'z' as u16);
232+
vars_mock.push(b'2' as u16);
233+
vars_mock.push(0);
234+
vars_mock.push(0);
235+
236+
let vars = Vars {
237+
names: vars_mock.as_ptr().cast(),
238+
protocol: &ShellMock::new(
239+
Vec::from([cstr16!("foo1"), cstr16!("bar"), cstr16!("baz2")]),
240+
Vec::from([cstr16!("value"), cstr16!("one"), 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+
);
135252
}
136253
}

0 commit comments

Comments
 (0)