@@ -4,12 +4,25 @@ use super::common;
44use crate :: env:: current_exe;
55use crate :: ffi:: OsString ;
66use crate :: fmt;
7+ use crate :: num:: NonZeroU16 ;
8+ use crate :: os:: uefi:: ffi:: OsStringExt ;
79use crate :: path:: PathBuf ;
810use crate :: sync:: OnceLock ;
911use crate :: sys_common:: wstr:: WStrUnits ;
1012use crate :: vec;
1113use 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+
1326pub 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)
3856pub ( 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
45140impl fmt:: Debug for Args {
0 commit comments