2025-11-03 02:04:21 -05:00
|
|
|
use alloc::string::{String, ToString};
|
|
|
|
|
use alloc::vec::Vec;
|
2026-09-26 20:07:58 -07:00
|
|
|
use anyhow::{Context, Result};
|
|
|
|
|
use uefi::proto::loaded_image::LoadedImage;
|
2025-11-03 02:04:21 -05:00
|
|
|
|
2025-11-03 23:45:35 -05:00
|
|
|
/// Loads the command-line arguments passed to the current image.
|
2025-11-03 02:04:21 -05:00
|
|
|
pub fn args() -> Result<Vec<String>> {
|
2025-11-03 23:45:35 -05:00
|
|
|
// Acquire the current image handle.
|
2025-11-03 02:04:21 -05:00
|
|
|
let handle = uefi::boot::image_handle();
|
|
|
|
|
|
2025-11-03 23:45:35 -05:00
|
|
|
// Open the LoadedImage protocol for the current image.
|
2025-11-03 02:04:21 -05:00
|
|
|
let loaded_image = uefi::boot::open_protocol_exclusive::<LoadedImage>(handle)
|
2025-11-03 23:45:35 -05:00
|
|
|
.context("unable to open loaded image protocol for current image")?;
|
2025-11-03 02:04:21 -05:00
|
|
|
|
|
|
|
|
// Load the command-line argument string.
|
2026-09-26 20:07:58 -07:00
|
|
|
// Load options are usually a null-terminated UCS-2 string, but firmware is not consistent
|
|
|
|
|
// about this. Some firmware passes an odd number of bytes, leaves off the null terminator,
|
|
|
|
|
// or pads the string with extra nulls. We decode everything up to the first null and
|
|
|
|
|
// replace anything that isn't valid UCS-2, instead of refusing to boot.
|
|
|
|
|
let Some(options) = loaded_image.load_options_as_bytes() else {
|
2025-11-03 02:04:21 -05:00
|
|
|
// No load options were passed. We will return an empty vector.
|
2026-09-26 20:07:58 -07:00
|
|
|
return Ok(Vec::new());
|
2025-11-03 02:04:21 -05:00
|
|
|
};
|
|
|
|
|
|
2026-09-26 20:07:58 -07:00
|
|
|
// Decode the options as little-endian UTF-16, stopping at the first null.
|
|
|
|
|
// A trailing odd byte can't be part of a character, so it is dropped.
|
|
|
|
|
let options = options
|
|
|
|
|
.as_chunks::<2>()
|
|
|
|
|
.0
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|pair| u16::from_le_bytes(*pair))
|
|
|
|
|
.take_while(|c| *c != 0)
|
|
|
|
|
.collect::<Vec<u16>>();
|
|
|
|
|
let options = String::from_utf16_lossy(&options);
|
2025-11-03 02:04:21 -05:00
|
|
|
|
|
|
|
|
// Use shlex to parse the options.
|
2025-11-03 23:45:35 -05:00
|
|
|
// If shlex fails, we will perform a simple whitespace split.
|
2025-11-03 02:04:21 -05:00
|
|
|
let mut args = shlex::split(&options).unwrap_or_else(|| {
|
|
|
|
|
options
|
|
|
|
|
.split_ascii_whitespace()
|
|
|
|
|
.map(|string| string.to_string())
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Correct firmware that may add invalid arguments at the start.
|
|
|
|
|
// Witnessed this on a Dell Precision 5690 when direct booting.
|
2025-11-03 23:58:48 -05:00
|
|
|
args = args
|
|
|
|
|
.into_iter()
|
|
|
|
|
.skip_while(|arg| {
|
|
|
|
|
arg.chars()
|
|
|
|
|
.next()
|
|
|
|
|
// Filter out unprintable characters and backticks.
|
|
|
|
|
// Both of which have been observed in the wild.
|
2026-09-26 18:12:21 -07:00
|
|
|
.map(|c| c.is_ascii_control() || c == '`')
|
2025-11-03 23:58:48 -05:00
|
|
|
.unwrap_or(false)
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
2025-11-03 02:04:21 -05:00
|
|
|
|
2025-11-03 23:45:35 -05:00
|
|
|
// If there is a first argument, check if it is not an option.
|
|
|
|
|
// If it is not, we will assume it is the path to the executable and remove it.
|
|
|
|
|
if let Some(arg) = args.first()
|
|
|
|
|
&& !arg.starts_with('-')
|
|
|
|
|
{
|
|
|
|
|
args.remove(0);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-03 02:04:21 -05:00
|
|
|
Ok(args)
|
|
|
|
|
}
|