Skip to content

Commit 87587f0

Browse files
committed
Implement args for UEFI
- Uses `EFI_LOADED_IMAGE_PROTOCOL` Signed-off-by: Ayush Singh <[email protected]>
1 parent 95d7056 commit 87587f0

File tree

3 files changed

+155
-1
lines changed

3 files changed

+155
-1
lines changed

library/std/src/sys/uefi/args.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use r_efi::protocols::loaded_image;
2+
3+
use crate::env::current_exe;
4+
use crate::ffi::OsString;
5+
use crate::fmt;
6+
use crate::num::NonZeroU16;
7+
use crate::os::uefi::ffi::OsStringExt;
8+
use crate::sys::uefi::helpers;
9+
use crate::sys_common::wstr::WStrUnits;
10+
use crate::vec;
11+
12+
pub struct Args {
13+
parsed_args_list: vec::IntoIter<OsString>,
14+
}
15+
16+
pub fn args() -> Args {
17+
// SAFETY: Each loaded image has an image handle that supports EFI_LOADED_IMAGE_PROTOCOL
18+
let protocol =
19+
helpers::current_handle_protocol::<loaded_image::Protocol>(loaded_image::PROTOCOL_GUID)
20+
.unwrap();
21+
22+
let lp_cmd_line = unsafe { (*protocol.as_ptr()).load_options as *const u16 };
23+
let vec_args = match unsafe { WStrUnits::new(lp_cmd_line) } {
24+
Some(code_units) => parse_lp_cmd_line(code_units),
25+
None => Vec::from([current_exe().map(Into::into).unwrap_or_default()]),
26+
};
27+
28+
Args { parsed_args_list: vec_args.into_iter() }
29+
}
30+
31+
impl fmt::Debug for Args {
32+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33+
self.parsed_args_list.as_slice().fmt(f)
34+
}
35+
}
36+
37+
impl Iterator for Args {
38+
type Item = OsString;
39+
40+
fn next(&mut self) -> Option<OsString> {
41+
self.parsed_args_list.next()
42+
}
43+
44+
fn size_hint(&self) -> (usize, Option<usize>) {
45+
self.parsed_args_list.size_hint()
46+
}
47+
}
48+
49+
impl ExactSizeIterator for Args {
50+
fn len(&self) -> usize {
51+
self.parsed_args_list.len()
52+
}
53+
}
54+
55+
impl DoubleEndedIterator for Args {
56+
fn next_back(&mut self) -> Option<OsString> {
57+
self.parsed_args_list.next_back()
58+
}
59+
}
60+
61+
/// Implements the UEFI command-line argument parsing algorithm.
62+
///
63+
/// This implementation is based on what is defined in Section 3.4 of
64+
/// [UEFI Shell Specification](https://uefi.org/sites/default/files/resources/UEFI_Shell_Spec_2_0.pdf)
65+
fn parse_lp_cmd_line<'a>(mut code_units: WStrUnits<'a>) -> Vec<OsString> {
66+
const QUOTE: NonZeroU16 = non_zero_u16(b'"' as u16);
67+
const SPACE: NonZeroU16 = non_zero_u16(b' ' as u16);
68+
const CARET: NonZeroU16 = non_zero_u16(b'^' as u16);
69+
70+
let mut ret_val = Vec::new();
71+
72+
// The executable name at the beginning is special.
73+
let mut in_quotes = false;
74+
let mut cur = Vec::new();
75+
for w in &mut code_units {
76+
match w {
77+
// A quote mark always toggles `in_quotes` no matter what because
78+
// there are no escape characters when parsing the executable name.
79+
QUOTE => in_quotes = !in_quotes,
80+
// If not `in_quotes` then whitespace ends argv[0].
81+
SPACE if !in_quotes => break,
82+
// In all other cases the code unit is taken literally.
83+
_ => cur.push(w.get()),
84+
}
85+
}
86+
87+
// Skip whitespace.
88+
code_units.advance_while(|w| w == SPACE);
89+
ret_val.push(OsString::from_wide(&cur));
90+
91+
// Parse the arguments according to these rules:
92+
// * All code units are taken literally except space, quote and caret.
93+
// * When not `in_quotes`, space separate arguments. Consecutive spaces are
94+
// treated as a single separator.
95+
// * A space `in_quotes` is taken literally.
96+
// * A quote toggles `in_quotes` mode unless it's escaped. An escaped quote is taken literally.
97+
// * A quote can be escaped if preceded by caret.
98+
// * A caret can be escaped if preceded by caret.
99+
let mut cur = Vec::new();
100+
let mut in_quotes = false;
101+
while let Some(w) = code_units.next() {
102+
match w {
103+
// If not `in_quotes`, a space or tab ends the argument.
104+
SPACE if !in_quotes => {
105+
ret_val.push(OsString::from_wide(&cur[..]));
106+
cur.truncate(0);
107+
108+
// Skip whitespace.
109+
code_units.advance_while(|w| w == SPACE);
110+
}
111+
// Caret can escape quotes or carets
112+
CARET if in_quotes => {
113+
if let Some(x) = code_units.next() {
114+
cur.push(x.get())
115+
}
116+
}
117+
// If `in_quotes` and not backslash escaped (see above) then a quote either
118+
// unsets `in_quote` or is escaped by another quote.
119+
QUOTE if in_quotes => match code_units.peek() {
120+
// Otherwise set `in_quotes`.
121+
Some(_) => in_quotes = false,
122+
// The end of the command line.
123+
// Push `cur` even if empty, which we do by breaking while `in_quotes` is still set.
124+
None => break,
125+
},
126+
// If not `in_quotes` and not BACKSLASH escaped (see above) then a quote sets `in_quote`.
127+
QUOTE => in_quotes = true,
128+
// Everything else is always taken literally.
129+
_ => cur.push(w.get()),
130+
}
131+
}
132+
// Push the final argument, if any.
133+
if !cur.is_empty() || in_quotes {
134+
ret_val.push(OsString::from_wide(&cur[..]));
135+
}
136+
ret_val
137+
}
138+
139+
/// This is the const equivalent to `NonZeroU16::new(n).unwrap()`
140+
///
141+
/// FIXME: This can be removed once `Option::unwrap` is stably const.
142+
/// See the `const_option` feature (#67441).
143+
const fn non_zero_u16(n: u16) -> NonZeroU16 {
144+
match NonZeroU16::new(n) {
145+
Some(n) => n,
146+
None => panic!("called `unwrap` on a `None` value"),
147+
}
148+
}

library/std/src/sys/uefi/helpers.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,10 @@ pub(crate) unsafe fn close_event(evt: NonNull<crate::ffi::c_void>) -> io::Result
139139

140140
if r.is_error() { Err(crate::io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
141141
}
142+
143+
/// Get the Protocol for current system handle.
144+
/// Note: Some protocols need to be manually freed. It is the callers responsibility to do so.
145+
pub(crate) fn current_handle_protocol<T>(protocol_guid: Guid) -> Option<NonNull<T>> {
146+
let system_handle = uefi::env::try_image_handle()?;
147+
open_protocol(system_handle, protocol_guid).ok()
148+
}

library/std/src/sys/uefi/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
//! [`OsString`]: crate::ffi::OsString
1414
1515
pub mod alloc;
16-
#[path = "../unsupported/args.rs"]
1716
pub mod args;
1817
#[path = "../unix/cmath.rs"]
1918
pub mod cmath;

0 commit comments

Comments
 (0)