Skip to content

Commit 000228b

Browse files
committed
Implement UEFI argument parsing
Implemented custom argument parsing for UEFI based on Section 3.4 of UEFI shell specification Signed-off-by: Ayush Singh <[email protected]>
1 parent f89fceb commit 000228b

File tree

3 files changed

+128
-9
lines changed

3 files changed

+128
-9
lines changed

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

+98-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,25 @@ use super::common;
44
use crate::env::current_exe;
55
use crate::ffi::OsString;
66
use crate::fmt;
7+
use crate::num::NonZeroU16;
8+
use crate::os::uefi::ffi::OsStringExt;
79
use crate::path::PathBuf;
810
use crate::sync::OnceLock;
911
use crate::sys_common::wstr::WStrUnits;
1012
use crate::vec;
1113
use r_efi::efi::protocols::loaded_image;
1214

15+
/// This is the const equivalent to `NonZeroU16::new(n).unwrap()`
16+
///
17+
/// FIXME: This can be removed once `Option::unwrap` is stably const.
18+
/// See the `const_option` feature (#67441).
19+
const fn non_zero_u16(n: u16) -> NonZeroU16 {
20+
match NonZeroU16::new(n) {
21+
Some(n) => n,
22+
None => panic!("called `unwrap` on a `None` value"),
23+
}
24+
}
25+
1326
pub struct Args {
1427
parsed_args_list: vec::IntoIter<OsString>,
1528
}
@@ -35,11 +48,93 @@ pub fn args() -> Args {
3548
Args { parsed_args_list: vec_args.clone().into_iter() }
3649
}
3750

51+
/// Implements the UEFI command-line argument parsing algorithm.
52+
///
53+
/// While this sounds good in theory, I have not really found any concrete implementation of
54+
/// argument parsing in UEFI. Thus I have created thisimplementation based on what is defined in
55+
/// Section 3.2 of [UEFI Shell Specification](https://uefi.org/sites/default/files/resources/UEFI_Shell_Spec_2_0.pdf)
3856
pub(crate) fn parse_lp_cmd_line<'a, F: Fn() -> OsString>(
39-
_lp_cmd_line: Option<WStrUnits<'a>>,
40-
_exe_name: F,
57+
lp_cmd_line: Option<WStrUnits<'a>>,
58+
exe_name: F,
4159
) -> Vec<OsString> {
42-
todo!()
60+
const QUOTE: NonZeroU16 = non_zero_u16(b'"' as u16);
61+
const SPACE: NonZeroU16 = non_zero_u16(b' ' as u16);
62+
const CARET: NonZeroU16 = non_zero_u16(b'^' as u16);
63+
64+
let mut ret_val = Vec::new();
65+
// If the cmd line pointer is null or it points to an empty string then
66+
// return the name of the executable as argv[0].
67+
if lp_cmd_line.as_ref().and_then(|cmd| cmd.peek()).is_none() {
68+
ret_val.push(exe_name());
69+
return ret_val;
70+
}
71+
let mut code_units = lp_cmd_line.unwrap();
72+
73+
// The executable name at the beginning is special.
74+
let mut in_quotes = false;
75+
let mut cur = Vec::new();
76+
for w in &mut code_units {
77+
match w {
78+
// A quote mark always toggles `in_quotes` no matter what because
79+
// there are no escape characters when parsing the executable name.
80+
QUOTE => in_quotes = !in_quotes,
81+
// If not `in_quotes` then whitespace ends argv[0].
82+
SPACE if !in_quotes => break,
83+
// In all other cases the code unit is taken literally.
84+
_ => cur.push(w.get()),
85+
}
86+
}
87+
88+
// Skip whitespace.
89+
code_units.advance_while(|w| w == SPACE);
90+
ret_val.push(OsString::from_wide(&cur));
91+
92+
// Parse the arguments according to these rules:
93+
// * All code units are taken literally except space, quote and caret.
94+
// * When not `in_quotes`, space separate arguments. Consecutive spaces are
95+
// treated as a single separator.
96+
// * A space `in_quotes` is taken literally.
97+
// * A quote toggles `in_quotes` mode unless it's escaped. An escaped quote is taken literally.
98+
// * A quote can be escaped if preceded by caret.
99+
// * A caret can be escaped if preceded by caret.
100+
let mut cur = Vec::new();
101+
let mut in_quotes = false;
102+
while let Some(w) = code_units.next() {
103+
match w {
104+
// If not `in_quotes`, a space or tab ends the argument.
105+
SPACE if !in_quotes => {
106+
ret_val.push(OsString::from_wide(&cur[..]));
107+
cur.truncate(0);
108+
109+
// Skip whitespace.
110+
code_units.advance_while(|w| w == SPACE);
111+
}
112+
// Caret can escape quotes or carets
113+
CARET if in_quotes => {
114+
if let Some(x) = code_units.next() {
115+
cur.push(x.get())
116+
}
117+
}
118+
// If `in_quotes` and not backslash escaped (see above) then a quote either
119+
// unsets `in_quote` or is escaped by another quote.
120+
QUOTE if in_quotes => match code_units.peek() {
121+
// Otherwise set `in_quotes`.
122+
Some(_) => in_quotes = false,
123+
// The end of the command line.
124+
// Push `cur` even if empty, which we do by breaking while `in_quotes` is still set.
125+
None => break,
126+
},
127+
// If not `in_quotes` and not BACKSLASH escaped (see above) then a quote sets `in_quote`.
128+
QUOTE => in_quotes = true,
129+
// Everything else is always taken literally.
130+
_ => cur.push(w.get()),
131+
}
132+
}
133+
// Push the final argument, if any.
134+
if !cur.is_empty() || in_quotes {
135+
ret_val.push(OsString::from_wide(&cur[..]));
136+
}
137+
ret_val
43138
}
44139

45140
impl fmt::Debug for Args {

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

+28-5
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ use super::{
22
common::{self, status_to_io_error},
33
unsupported,
44
};
5+
use crate::error::Error as StdError;
56
use crate::ffi::{OsStr, OsString};
67
use crate::fmt;
78
use crate::io;
89
use crate::marker::PhantomData;
910
use crate::os::uefi;
11+
use crate::os::uefi::ffi::{OsStrExt, OsStringExt};
1012
use crate::path::{self, PathBuf};
11-
use crate::{error::Error as StdError, os::uefi::ffi::OsStringExt};
1213

1314
// Return EFI_SUCCESS as Status
1415
pub fn errno() -> i32 {
@@ -47,24 +48,46 @@ impl<'a> Iterator for SplitPaths<'a> {
4748
#[derive(Debug)]
4849
pub struct JoinPathsError;
4950

50-
pub fn join_paths<I, T>(_paths: I) -> Result<OsString, JoinPathsError>
51+
// This is based on windows implementation since the path variable mentioned in Section 3.6.1
52+
// [UEFI Shell Specification](https://uefi.org/sites/default/files/resources/UEFI_Shell_Spec_2_0.pdf).
53+
pub fn join_paths<I, T>(paths: I) -> Result<OsString, JoinPathsError>
5154
where
5255
I: Iterator<Item = T>,
5356
T: AsRef<OsStr>,
5457
{
55-
Err(JoinPathsError)
58+
let mut joined = Vec::new();
59+
let sep = b';' as u16;
60+
61+
for (i, path) in paths.enumerate() {
62+
let path = path.as_ref();
63+
if i > 0 {
64+
joined.push(sep)
65+
}
66+
let v = path.encode_wide().collect::<Vec<u16>>();
67+
if v.contains(&(b'"' as u16)) {
68+
return Err(JoinPathsError);
69+
} else if v.contains(&sep) {
70+
joined.push(b'"' as u16);
71+
joined.extend_from_slice(&v[..]);
72+
joined.push(b'"' as u16);
73+
} else {
74+
joined.extend_from_slice(&v[..]);
75+
}
76+
}
77+
78+
Ok(OsStringExt::from_wide(&joined[..]))
5679
}
5780

5881
impl fmt::Display for JoinPathsError {
5982
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60-
"not supported on this platform yet".fmt(f)
83+
"path segment contains `\"`".fmt(f)
6184
}
6285
}
6386

6487
impl StdError for JoinPathsError {
6588
#[allow(deprecated)]
6689
fn description(&self) -> &str {
67-
"not supported on this platform yet"
90+
"failed to join paths"
6891
}
6992
}
7093

src/tools/remote-test-server/src/main.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ fn handle_run(socket: TcpStream, work: &Path, tmp: &Path, lock: &Mutex<()>, conf
268268
// On windows, libraries are just searched in the executable directory,
269269
// system directories, PWD, and PATH, in that order. PATH is the only one
270270
// we can change for this.
271-
let library_path = if cfg!(windows) { "PATH" } else { "LD_LIBRARY_PATH" };
271+
let library_path =
272+
if cfg!(any(windows, target_os = "uefi")) { "PATH" } else { "LD_LIBRARY_PATH" };
272273

273274
// Support libraries were uploaded to `work` earlier, so make sure that's
274275
// in `LD_LIBRARY_PATH`. Also include our own current dir which may have

0 commit comments

Comments
 (0)