Rollup merge of #116341 - Ayush1325:uefi-args, r=Mark-Simulacrum
Implement sys::args for UEFI - Uses `EFI_LOADED_IMAGE_PROTOCOL`, which is implemented for all loaded images. Tested on qemu with OVMF cc ``@nicholasbishop`` cc ``@dvdhrm``
This commit is contained in:
commit
835edc1397
4 changed files with 167 additions and 1 deletions
158
library/std/src/sys/uefi/args.rs
Normal file
158
library/std/src/sys/uefi/args.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
use r_efi::protocols::loaded_image;
|
||||
|
||||
use crate::env::current_exe;
|
||||
use crate::ffi::OsString;
|
||||
use crate::fmt;
|
||||
use crate::iter::Iterator;
|
||||
use crate::mem::size_of;
|
||||
use crate::sys::uefi::helpers;
|
||||
use crate::vec;
|
||||
|
||||
pub struct Args {
|
||||
parsed_args_list: vec::IntoIter<OsString>,
|
||||
}
|
||||
|
||||
pub fn args() -> Args {
|
||||
let lazy_current_exe = || Vec::from([current_exe().map(Into::into).unwrap_or_default()]);
|
||||
|
||||
// Each loaded image has an image handle that supports `EFI_LOADED_IMAGE_PROTOCOL`. Thus, this
|
||||
// will never fail.
|
||||
let protocol =
|
||||
helpers::image_handle_protocol::<loaded_image::Protocol>(loaded_image::PROTOCOL_GUID)
|
||||
.unwrap();
|
||||
|
||||
let lp_size = unsafe { (*protocol.as_ptr()).load_options_size } as usize;
|
||||
// Break if we are sure that it cannot be UTF-16
|
||||
if lp_size < size_of::<u16>() || lp_size % size_of::<u16>() != 0 {
|
||||
return Args { parsed_args_list: lazy_current_exe().into_iter() };
|
||||
}
|
||||
let lp_size = lp_size / size_of::<u16>();
|
||||
|
||||
let lp_cmd_line = unsafe { (*protocol.as_ptr()).load_options as *const u16 };
|
||||
if !lp_cmd_line.is_aligned() {
|
||||
return Args { parsed_args_list: lazy_current_exe().into_iter() };
|
||||
}
|
||||
let lp_cmd_line = unsafe { crate::slice::from_raw_parts(lp_cmd_line, lp_size) };
|
||||
|
||||
Args {
|
||||
parsed_args_list: parse_lp_cmd_line(lp_cmd_line)
|
||||
.unwrap_or_else(lazy_current_exe)
|
||||
.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Args {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.parsed_args_list.as_slice().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Args {
|
||||
type Item = OsString;
|
||||
|
||||
fn next(&mut self) -> Option<OsString> {
|
||||
self.parsed_args_list.next()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.parsed_args_list.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for Args {
|
||||
fn len(&self) -> usize {
|
||||
self.parsed_args_list.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleEndedIterator for Args {
|
||||
fn next_back(&mut self) -> Option<OsString> {
|
||||
self.parsed_args_list.next_back()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the UEFI command-line argument parsing algorithm.
|
||||
///
|
||||
/// This implementation is based on what is defined in Section 3.4 of
|
||||
/// [UEFI Shell Specification](https://uefi.org/sites/default/files/resources/UEFI_Shell_Spec_2_0.pdf)
|
||||
///
|
||||
/// Return None in the following cases:
|
||||
/// - Invalid UTF-16 (unpaired surrogate)
|
||||
/// - Empty/improper arguments
|
||||
fn parse_lp_cmd_line(code_units: &[u16]) -> Option<Vec<OsString>> {
|
||||
const QUOTE: char = '"';
|
||||
const SPACE: char = ' ';
|
||||
const CARET: char = '^';
|
||||
const NULL: char = '\0';
|
||||
|
||||
let mut ret_val = Vec::new();
|
||||
let mut code_units_iter = char::decode_utf16(code_units.iter().cloned()).peekable();
|
||||
|
||||
// The executable name at the beginning is special.
|
||||
let mut in_quotes = false;
|
||||
let mut cur = String::new();
|
||||
while let Some(w) = code_units_iter.next() {
|
||||
let w = w.ok()?;
|
||||
match w {
|
||||
// break on NULL
|
||||
NULL => break,
|
||||
// A quote mark always toggles `in_quotes` no matter what because
|
||||
// there are no escape characters when parsing the executable name.
|
||||
QUOTE => in_quotes = !in_quotes,
|
||||
// If not `in_quotes` then whitespace ends argv[0].
|
||||
SPACE if !in_quotes => break,
|
||||
// In all other cases the code unit is taken literally.
|
||||
_ => cur.push(w),
|
||||
}
|
||||
}
|
||||
|
||||
// If exe name is missing, the cli args are invalid
|
||||
if cur.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
ret_val.push(OsString::from(cur));
|
||||
// Skip whitespace.
|
||||
while code_units_iter.next_if_eq(&Ok(SPACE)).is_some() {}
|
||||
|
||||
// Parse the arguments according to these rules:
|
||||
// * All code units are taken literally except space, quote and caret.
|
||||
// * When not `in_quotes`, space separate arguments. Consecutive spaces are
|
||||
// treated as a single separator.
|
||||
// * A space `in_quotes` is taken literally.
|
||||
// * A quote toggles `in_quotes` mode unless it's escaped. An escaped quote is taken literally.
|
||||
// * A quote can be escaped if preceded by caret.
|
||||
// * A caret can be escaped if preceded by caret.
|
||||
let mut cur = String::new();
|
||||
let mut in_quotes = false;
|
||||
while let Some(w) = code_units_iter.next() {
|
||||
let w = w.ok()?;
|
||||
match w {
|
||||
// break on NULL
|
||||
NULL => break,
|
||||
// If not `in_quotes`, a space or tab ends the argument.
|
||||
SPACE if !in_quotes => {
|
||||
ret_val.push(OsString::from(&cur[..]));
|
||||
cur.truncate(0);
|
||||
|
||||
// Skip whitespace.
|
||||
while code_units_iter.next_if_eq(&Ok(SPACE)).is_some() {}
|
||||
}
|
||||
// Caret can escape quotes or carets
|
||||
CARET if in_quotes => {
|
||||
if let Some(x) = code_units_iter.next() {
|
||||
cur.push(x.ok()?);
|
||||
}
|
||||
}
|
||||
// If quote then flip `in_quotes`
|
||||
QUOTE => in_quotes = !in_quotes,
|
||||
// Everything else is always taken literally.
|
||||
_ => cur.push(w),
|
||||
}
|
||||
}
|
||||
// Push the final argument, if any.
|
||||
if !cur.is_empty() || in_quotes {
|
||||
ret_val.push(OsString::from(cur));
|
||||
}
|
||||
Some(ret_val)
|
||||
}
|
|
@ -139,3 +139,10 @@ pub(crate) unsafe fn close_event(evt: NonNull<crate::ffi::c_void>) -> io::Result
|
|||
|
||||
if r.is_error() { Err(crate::io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
|
||||
}
|
||||
|
||||
/// Get the Protocol for current system handle.
|
||||
/// Note: Some protocols need to be manually freed. It is the callers responsibility to do so.
|
||||
pub(crate) fn image_handle_protocol<T>(protocol_guid: Guid) -> Option<NonNull<T>> {
|
||||
let system_handle = uefi::env::try_image_handle()?;
|
||||
open_protocol(system_handle, protocol_guid).ok()
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
//! [`OsString`]: crate::ffi::OsString
|
||||
|
||||
pub mod alloc;
|
||||
#[path = "../unsupported/args.rs"]
|
||||
pub mod args;
|
||||
#[path = "../unix/cmath.rs"]
|
||||
pub mod cmath;
|
||||
|
|
|
@ -268,6 +268,8 @@ cargo build --target x86_64-unknown-uefi -Zbuild-std=std,panic_abort
|
|||
#### stdio
|
||||
- Uses `Simple Text Input Protocol` and `Simple Text Output Protocol`.
|
||||
- Note: UEFI uses CRLF for new line. This means Enter key is registered as CR instead of LF.
|
||||
#### args
|
||||
- Uses `EFI_LOADED_IMAGE_PROTOCOL->LoadOptions`
|
||||
|
||||
## Example: Hello World With std
|
||||
The following code features a valid UEFI application, including `stdio` and `alloc` (`OsString` and `Vec`):
|
||||
|
|
Loading…
Add table
Reference in a new issue