@@ -4,12 +4,25 @@ use super::common;
4
4
use crate :: env:: current_exe;
5
5
use crate :: ffi:: OsString ;
6
6
use crate :: fmt;
7
+ use crate :: num:: NonZeroU16 ;
8
+ use crate :: os:: uefi:: ffi:: OsStringExt ;
7
9
use crate :: path:: PathBuf ;
8
10
use crate :: sync:: OnceLock ;
9
11
use crate :: sys_common:: wstr:: WStrUnits ;
10
12
use crate :: vec;
11
13
use r_efi:: efi:: protocols:: loaded_image;
12
14
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
+
13
26
pub struct Args {
14
27
parsed_args_list : vec:: IntoIter < OsString > ,
15
28
}
@@ -35,11 +48,93 @@ pub fn args() -> Args {
35
48
Args { parsed_args_list : vec_args. clone ( ) . into_iter ( ) }
36
49
}
37
50
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)
38
56
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 ,
41
59
) -> 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
43
138
}
44
139
45
140
impl fmt:: Debug for Args {
0 commit comments