Repository: datasone/setup_var.efi
Branch: master
Commit: f36c98be3b2d
Files: 11
Total size: 67.0 KB
Directory structure:
gitextract_10qigmhq/
├── .cargo/
│ └── config.toml
├── .gitignore
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── rust-toolchain.toml
└── src/
├── args.rs
├── input.rs
├── main.rs
└── utils.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .cargo/config.toml
================================================
[build]
target = "x86_64-unknown-uefi"
================================================
FILE: .gitignore
================================================
/target
/.idea
================================================
FILE: Cargo.toml
================================================
[package]
name = "setup_var"
version = "0.3.1"
edition = "2024"
license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
uefi = { version = "0.34.1", features = ["alloc", "global_allocator", "panic_handler"] }
num_enum = { version = "0.7.3", default-features = false }
strum = { version = "0.27.1", default-features = false, features = ["derive"] }
================================================
FILE: LICENSE-APACHE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: LICENSE-MIT
================================================
MIT License
Copyright (c) 2022 datasone
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# UEFI Command Line Tool for Reading/Writing UEFI Variables
This tool is a rewritten version of the [modded grub with setup_var commands](https://github.com/datasone/grub-mod-setup_var), enhanced with much cleaner code, aarch64 support and ability to be automated. The tool is able to read/write UEFI variables, and is usually used for changing BIOS settings which are hidden from UI.
⚠ Use this tool with extreme caution as accessing wrong varstore or variable may completely brick your computer!
---
**The new usage document is for 0.3.0 version and later with the new syntax for specifying variables. For 0.2.x releases with the old syntax (0.2.x will still be maintained so you can continue to use that), refer to [document of that version](https://github.com/datasone/setup_var.efi/blob/0.2.x/README.md).**
---
Note: If you have been using the modded grub tool, you may want to refer to [the cheatsheet](#grub-cheatsheet).
## Term definitions
The legacy names used in grub version of the tool and some other related tools (e.g. IFR extractor) used *varstore* and *variable*. The former word is not used in UEFI specification, and is actually the **variable** in UEFI runtime services (sometimes referred to as "variable store", which is possibly where "varstore" came from). So to clarify this, this project uses "variable" and "value":
- **Variable** is the former *varstore*, which means a named storage with varying size from several to several thousands of bytes. Maybe used for storing many values of many BIOS setting items.
- **Value** is the former *variable*, which represents a value in the variable with few (often one to two) bytes, referred with variable and offset. While changing BIOS settings, this means the value of *one* BIOS setting item.
## Usage
Run `setup_var.efi -h` in UEFI shell to get this help:
```
setup_var.efi [-h/--help] [-r/--reboot(=auto)] [--write_on_demand] VALUE_ARG...
-r or --reboot: Reboot (warm reset) the computer after the program successfully finishes.
--reboot=auto: Reboot only if any value was actually written. This option automatically enables --write_on_demand.
--write_on_demand: If the value desired to be written is the same with storage, skip the unnecessary write.
VALUE_ARG represents the value needs to be read/written, and can be specified multiple times.
The format of VALUE_ARG is: <VAR_NAME>[(VAR_ID)]:<OFFSET>[(VALUE_SIZE)][=VALUE]
VAR_NAME: The name of UEFI variable to be altered, defaults to "Setup".
VAR_ID: Unique id for distinguishing variables with same name, which will be provided by setup_var.efi (when required).
OFFSET: The offset of value to be altered in the UEFI variable.
VALUE: The new value to write, capped at 64-bit. If not specified, the value at OFFSET will be read and shown.
VALUE_SIZE: Bytes of value to write, must be equal or larger than the size of <VALUE>, defaults to 1.
OFFSET, VALUE, VALUE_SIZE and VAR_ID are numbers, and can be specified in hexadecimal with prefix "0x", or decimal with no prefixes.
The program defaults to little endian for values ONLY while operating UEFI variables,
though it's recommended to only operate on one byte if you are not sure what this is or means.
Example: .\setup_var.efi -r CpuSetup:0x10E=0x1A
```
---
### Examples for `VALUE_ARG`
- `CpuSetup:0x10E=0x1A`: write one byte `0x1A` to offset `0x10E` in `CpuSetup`.
- `CpuSetup:0x10E(2)`: read one word (2 bytes) from offset `0x10E` in `CpuSetup`.
- `Setup(1):0x100(2)=0x1000`: write one word `0x1000` to offset `0x100` in `Setup` with variable id `1`.
---
For changing BIOS settings, you may use those following steps to obtain the variable name and value offset:
1. Obtain (raw) BIOS image, some motherboard OEMs provides BIOS image that can be directly opened but it's not commonly the case for branded PCs. For Intel CPU-based systems, you may use **Flash Programming Tool** from CSME System Tools to directly extract raw BIOS image.
2. Use [UEFITool](https://github.com/LongSoft/UEFITool) to open the BIOS image, and find `Setup` as string, there would be image sections named "Setup/*", navigate to that and extract body of the `Setup` folder to a file.
3. Use [IFR-Extractor](https://github.com/LongSoft/Universal-IFR-Extractor) to export setup info to a text file.
4. Find your desired setting in the text file, note the offset after `VarStoreInfo (VarOffset/VarName):` and the id after `VarStore:`.
5. Search for `VarStoreId: {id}`, where `{id}` is the id found earlier. And note the `Name` after it.
6. Change the value using noted variable(varstore) name, offset, and size.
## Input File
`setup_var.efi` now supports specifying with a input file (by UEFI shell redirection). All options that can be specified with arguments are available in input file.
Note: The text should be in UTF-16LE encoding, see [pinned issue #25](https://github.com/datasone/setup_var.efi/issues/25) for details.
The input file consists of three parts:
- Named arguments which are specified with a leading `@`.
- Defining a value address reference (to make the input file more readable) with `:=`.
- Specifying value arguments as how they are specified in progrm arguments.
Comments starting with `#` can be specified in the input file.
### Input File Example
```
# Named arguments
# @reboot # Reboot after executing setup_var.efi
@write_on_demand # Only write values when necessary
# Address Definition
ADLImonSlope:=CpuSetup:0x016E
ADLImonPrefix:=CpuSetup:0x0182
# Value Args
# Address can be referenced with leading '$'
$ADLImonSlope=0x14
$ADLImonPrefix=0x01
# Normal value argument also works
CpuSetup:0x0178(2)=0x4E20
```
The output of `setup.var.efi` has also be altered to match the format of the input file. So you can save your configurations with
```shell
setup_var.efi CpuSetup:0x10E=0x1A > vars.txt
```
and load them afterwards with
```shell
setup_var.efi < vars.txt
```
## GRUB Cheatsheet
The legacy grub commands can be mapped to these usages of this tool:
- `setup_var offset [value]`: `setup_var.efi Setup:offset[=value]`.
- `setup_var2 offset [value]`: This command searches for named `Custom`, so `setup_var.efi Custom:offset[=value]`.
- `setup_var_3 offset [value]`: This command discards variables that are too small, `setup_var.efi` has the `VAR_ID` argument to handle this. Though it's not tested as the problemed firmware used to test this command has been updated and the issue was solved.
- `setup_var_vs offset [value_size] [value]`: This command processes variable-sized values, and is mapped to `setup_var.efi Setup:offset(value_size)[=value]`.
- `setup_var_cv name offset [value_size] [value]`: This was the full-featured command, to use full features, use `setup_var.efi name:offset(value_size)[=value]`, in which the `(value_size)` can be omitted for 1-byte value.
## Build
```shell
cargo build [--release]
```
The default and tested target for this project is `x86_64-unknown-uefi`, it can also target `aarch64-unknown-uefi` for AArch64 UEFI and `i686-unknown-uefi` for 32-bit x86 EFI, though they are not tested and may contain glitches.
================================================
FILE: rust-toolchain.toml
================================================
[toolchain]
targets = ["aarch64-unknown-uefi", "i686-unknown-uefi", "x86_64-unknown-uefi"]
================================================
FILE: src/args.rs
================================================
use alloc::{
borrow::{Cow, ToOwned},
format,
string::{String, ToString},
vec,
vec::Vec,
};
use core::fmt::{Display, Formatter};
use uefi::{
CStr16, CString16, Char16,
data_types::EqStrUntilNul,
println,
proto::loaded_image::{LoadOptionsError, LoadedImage},
};
use crate::utils::{CStr16Ext, UEFIValue};
#[derive(Debug)]
pub enum ParseError {
LoadedImageProtocolError,
LoadOptionsError(LoadOptionsError),
InvalidValue(String),
NumberTooLarge(String),
InvalidArgs(ArgsError),
NoArgs,
NoInput,
LoadStdInError,
AddrDefWrite(String),
}
impl Display for ParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::LoadedImageProtocolError => {
write!(f, "Error while initializing UEFI LoadedImage protocol")
}
Self::LoadOptionsError(e) => {
write!(f, "Error loading options: {e:?}")
}
Self::InvalidValue(s) => {
write!(f, "Unexpected value: {s}")
}
Self::NumberTooLarge(s) => {
write!(f, "Specified number {s} is too large (larger than 64-bit)")
}
Self::InvalidArgs(e) => {
write!(f, "{e}")
}
Self::NoArgs => {
// This error is not meant to be returned to user
write!(f, "")
}
Self::NoInput => {
// This error is not meant to be returned to user as well
write!(f, "")
}
Self::LoadStdInError => {
write!(f, "Error while reading from standard input")
}
Self::AddrDefWrite(s) => {
write!(f, "Address definition in {s} cannot contain value write")
}
}
}
}
#[derive(Debug, Default, Clone)]
pub struct ValueAddr {
pub var_name: CString16,
pub var_id: Option<usize>,
pub offset: usize,
pub val_size: usize,
}
#[derive(Debug, Default, Copy, Clone)]
pub enum ValueOperation {
#[default]
Read,
Write(usize),
}
#[derive(Debug, Default, Clone)]
pub struct ValueArg {
pub addr: ValueAddr,
pub operation: ValueOperation,
}
impl ValueArg {
pub fn validate(&self) -> Result<(), ArgsError> {
if let ValueOperation::Write(val) = self.operation {
if val > (1 << (self.addr.val_size * 8)) {
Err(ArgsError::ValLargerThanSize(val, self.addr.val_size))
} else {
Ok(())
}
} else {
Ok(())
}
}
pub fn to_string_with_val(&self, value: &UEFIValue) -> String {
let var_name = &self.addr.var_name;
let var_id_str = match self.addr.var_id {
None => "".to_owned(),
Some(id) => format!("({id})"),
};
let var_offset = self.addr.offset;
let val_size_str = if self.addr.val_size == 1 {
"".to_owned()
} else {
format!("({})", self.addr.val_size)
};
let value_str = value.to_string_with_size(self.addr.val_size);
format!("{var_name}{var_id_str}:0x{var_offset:X}{val_size_str}={value_str}")
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum RebootMode {
Never,
Always,
Auto,
}
impl Default for RebootMode {
fn default() -> Self {
Self::Never
}
}
#[derive(Debug)]
pub enum NamedArg {
Help,
Reboot(RebootMode),
WriteOnDemand,
}
#[derive(Debug)]
enum Arg {
Named(NamedArg),
Value(ValueArg),
}
#[derive(Debug, Default)]
pub struct Args {
pub value_args: Vec<ValueArg>,
pub help_msg: bool,
pub write_on_demand: bool,
pub reboot: RebootMode,
}
impl Args {
fn validate(&self) -> Result<(), ArgsError> {
if self.help_msg {
Ok(())
} else {
self.value_args.iter().try_for_each(|va| va.validate())
}
}
}
#[derive(Debug)]
pub enum ArgsError {
ValLargerThanSize(usize, usize),
}
impl Display for ArgsError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::ValLargerThanSize(value, size) => {
write!(
f,
"Specified value to write (0x{:0width$X}) is larger than specified size \
{size} bytes",
value,
width = size * 2,
)
}
}
}
}
impl From<ArgsError> for ParseError {
fn from(value: ArgsError) -> Self {
Self::InvalidArgs(value)
}
}
pub const HELP_MSG: &str = r#"Usage:
setup_var.efi [-h/--help] [-r/--reboot(=auto)] [--write_on_demand] VALUE_ARG...
-r or --reboot: Reboot (warm reset) the computer after the program successfully finishes.
--reboot=auto: Reboot only if any value was actually written. This option automatically enables --write_on_demand.
--write_on_demand: If the value desired to be written is the same with storage, skip the unnecessary write.
VALUE_ARG represents the value needs to be read/written, and can be specified multiple times.
The format of VALUE_ARG is: <VAR_NAME>[(VAR_ID)]:<OFFSET>[(VALUE_SIZE)][=VALUE]
VAR_NAME: The name of UEFI variable to be altered, defaults to "Setup".
VAR_ID: Unique id for distinguishing variables with same name, which will be provided by setup_var.efi (when required).
OFFSET: The offset of value to be altered in the UEFI variable.
VALUE: The new value to write, capped at 64-bit. If not specified, the value at OFFSET will be read and shown.
VALUE_SIZE: Bytes of value to write, must be equal or larger than the size of <VALUE>, defaults to 1.
OFFSET, VALUE, VALUE_SIZE and VAR_ID are numbers, and can be specified in hexadecimal with prefix "0x", or decimal with no prefixes.
The program defaults to little endian for values ONLY while operating UEFI variables,
though it's recommended to only operate on one byte if you are not sure what this is or means.
Example: .\setup_var.efi -r CpuSetup:0x10E=0x1A"#;
pub fn parse_args() -> Result<Args, ParseError> {
let loaded_image =
uefi::boot::open_protocol_exclusive::<LoadedImage>(uefi::boot::image_handle())
.map_err(|_| ParseError::LoadedImageProtocolError)?;
let options = loaded_image
.load_options_as_cstr16()
.map_err(ParseError::LoadOptionsError)?;
parse_args_from_str(options)
}
fn drop_first_arg(arg: &CStr16) -> bool {
let rev_efi_ext: [Char16; 5] = [
'.'.try_into().unwrap(),
'e'.try_into().unwrap(),
'f'.try_into().unwrap(),
'i'.try_into().unwrap(),
'\0'.try_into().unwrap(),
];
let arg_chars = arg.as_slice_with_nul();
let has_efi_ext = if arg_chars.len() < 5 {
false
} else {
let arg_last_chars = &arg_chars[arg_chars.len() - 5..arg_chars.len()];
arg_last_chars == rev_efi_ext
};
#[allow(clippy::needless_bool)]
#[allow(clippy::if_same_then_else)]
if has_efi_ext {
true // efi extension, treated as the name of the executable file
} else if starts_with(arg, '-') {
false // Highly probably an option
} else {
// Now that we can't decide what is the first arg as they starts with the var
// name, defaults to keep it
false
}
}
macro_rules! try_parsers {
($input:expr, $parser:expr) => {{
$parser($input)
}};
($input:expr, $parser:expr, $( $parsers:expr ),* $(,)? ) => {{
if let Ok(val) = $parser($input) {
Ok(val)
} else {
crate::args::try_parsers!($input, $($parsers),*)
}
}};
}
pub(crate) use try_parsers;
fn parse_args_from_str(options: &CStr16) -> Result<Args, ParseError> {
let options = options.split_to_cstring(' '.try_into().unwrap());
let opt_len = options.len();
if opt_len == 0 {
return Err(ParseError::NoArgs);
}
let first_arg = options[0].clone();
let mut option_iter = options.into_iter();
if drop_first_arg(&first_arg) {
if opt_len == 1 {
return Err(ParseError::NoArgs);
}
option_iter.next(); // Skips executable argv
}
let args = option_iter
.filter(|s| s.as_slice_with_nul().len() != 1)
.map(|s| {
try_parsers!(&s, parse_named_arg, parse_value_arg_wrapped)
.map_err(|e| ParseError::InvalidValue(format!("{s}: {e}")))
})
.collect::<Result<Vec<_>, _>>()?;
let named_args = args
.iter()
.filter(|arg| matches!(arg, Arg::Named(_)))
.map(|arg| {
if let Arg::Named(arg) = arg {
arg
} else {
unreachable!()
}
})
.collect::<Vec<_>>();
let value_args = args
.iter()
.filter(|arg| matches!(arg, Arg::Value(_)))
.map(|arg| {
if let Arg::Value(arg) = arg {
arg.clone()
} else {
unreachable!()
}
})
.collect::<Vec<_>>();
let mut args = Args::default();
for named_arg in named_args {
match named_arg {
NamedArg::Help => args.help_msg = true,
NamedArg::Reboot(mode) => {
args.reboot = *mode;
if *mode == RebootMode::Auto {
args.write_on_demand = true;
}
}
NamedArg::WriteOnDemand => args.write_on_demand = true,
}
}
args.value_args = value_args;
args.validate()?;
Ok(args)
}
fn starts_with(s: &CStr16, c: char) -> bool {
match s.as_slice_with_nul().first() {
None => false,
Some(&c_h) => c_h == Char16::try_from(c).unwrap(),
}
}
fn try_next_char(iter: &mut impl Iterator<Item = char>, str: &CStr16) -> Result<char, ParseError> {
iter.next()
.ok_or_else(|| ParseError::InvalidValue(str.to_string()))
}
fn parse_hex_number(num_str: &CStr16) -> Result<usize, ParseError> {
let mut str_iter = num_str.iter().map(|&c| char::from(c));
// Prefix 0x(0X)
let c_h = try_next_char(&mut str_iter, num_str)?;
let c2_h = try_next_char(&mut str_iter, num_str)?;
match (c_h, c2_h) {
('0', 'x') => {}
('0', 'X') => {}
_ => return Err(ParseError::InvalidValue(num_str.to_string())),
}
if num_str.num_bytes() > 2 * (18 + 1) {
return Err(ParseError::NumberTooLarge(num_str.to_string()));
}
let value = str_iter
.map(|c| c.to_digit(16).map(|n| n as u8))
.collect::<Option<Vec<u8>>>()
.ok_or_else(|| ParseError::InvalidValue(num_str.to_string()))?;
let value_len = value.len();
let value = value.iter().enumerate().fold(0usize, |acc, (i, &n)| {
acc + ((n as usize) << (4 * (value_len - i - 1)))
});
Ok(value)
}
fn parse_number(num_str: &CStr16) -> Result<usize, ParseError> {
let chars = num_str.iter().map(|&c| char::from(c)).collect::<Vec<_>>();
if chars.iter().any(|c| !c.is_ascii_digit()) {
Err(ParseError::InvalidValue(num_str.to_string()))?
}
let value = chars
.into_iter()
.fold(0usize, |acc, n| acc * 10 + (n as u8 - b'0') as usize);
Ok(value)
}
fn parse_named_arg(key: &CStr16) -> Result<Arg, ParseError> {
if key.eq_str_until_nul(&"-h") || key.eq_str_until_nul(&"--help") {
Ok(Arg::Named(NamedArg::Help))
} else if key.eq_str_until_nul(&"-r=auto") || key.eq_str_until_nul(&"--reboot=auto") {
Ok(Arg::Named(NamedArg::Reboot(RebootMode::Auto)))
} else if key.eq_str_until_nul(&"-r") || key.eq_str_until_nul(&"--reboot") {
Ok(Arg::Named(NamedArg::Reboot(RebootMode::Always)))
} else if key.eq_str_until_nul(&"--write_on_demand") {
Ok(Arg::Named(NamedArg::WriteOnDemand))
} else {
Err(ParseError::InvalidValue(key.to_string()))
}
}
fn parse_value_arg_wrapped(arg: &CStr16) -> Result<Arg, ParseError> {
Ok(Arg::Value(parse_value_arg(arg)?))
}
pub fn parse_value_wrapped(arg: &CStr16) -> Result<ValueOperation, ParseError> {
Ok(parse_value(arg)?.1)
}
fn parse_value(arg: &CStr16) -> Result<(Cow<CStr16>, ValueOperation), ParseError> {
if arg.contains('='.try_into().unwrap()) {
let mut arg_split = arg.split_to_cstring('='.try_into().unwrap());
if arg_split.len() != 2 {
Err(ParseError::InvalidValue(arg.to_string()))?
}
let value = try_parsers!(&arg_split[1], parse_hex_number, parse_number)?;
Ok((
Cow::Owned(arg_split.swap_remove(0)),
ValueOperation::Write(value),
))
} else {
Ok((Cow::Borrowed(arg), ValueOperation::Read))
}
}
pub fn parse_value_arg(arg: &CStr16) -> Result<ValueArg, ParseError> {
let mut arg_split = arg.split_to_cstring(':'.try_into().unwrap());
if arg_split.len() != 2 {
Err(ParseError::InvalidValue(arg.to_string()))?
}
let mut var_name = arg_split.swap_remove(0);
let mut var_id = None;
if var_name.contains('('.try_into().unwrap()) {
let mut arg_split = var_name.split_to_cstring('('.try_into().unwrap());
if arg_split.len() != 2 {
Err(ParseError::InvalidValue(var_name.to_string()))?
}
let var_id_str = arg_split[1]
.strip_suffix(')'.try_into().unwrap())
.ok_or(ParseError::InvalidValue(var_name.to_string()))?;
var_id = Some(try_parsers!(&var_id_str, parse_number, parse_hex_number)?);
var_name = arg_split.swap_remove(0);
}
let var_offset = arg_split.swap_remove(0);
let mut val_size = 1;
let (mut var_offset, op_type) = parse_value(&var_offset)?;
if var_offset.contains('('.try_into().unwrap()) {
let mut arg_split = var_offset.split_to_cstring('('.try_into().unwrap());
if arg_split.len() != 2 {
Err(ParseError::InvalidValue(var_offset.to_string()))?
}
let val_size_str = arg_split[1]
.strip_suffix(')'.try_into().unwrap())
.ok_or(ParseError::InvalidValue(var_offset.to_string()))?;
val_size = try_parsers!(&val_size_str, parse_number, parse_hex_number)?;
var_offset = Cow::Owned(arg_split.swap_remove(0));
}
let offset = try_parsers!(&var_offset, parse_hex_number, parse_number)?;
Ok(ValueArg {
addr: ValueAddr {
var_name,
var_id,
offset,
val_size,
},
operation: op_type,
})
}
#[allow(dead_code)]
pub fn test_functions() {
println!("Testing parse_hex_number");
println!(
r#"parse_hex_number("0x1"), should be Ok({}), result is {:?}"#,
0x1,
parse_hex_number(&CString16::try_from("0x1").unwrap())
);
println!(
r#"parse_hex_number("0x01"), should be Ok({}), result is {:?}"#,
0x1,
parse_hex_number(&CString16::try_from("0x01").unwrap())
);
println!(
r#"parse_hex_number("0X12e"), should be Ok({}), result is {:?}"#,
0x12E,
parse_hex_number(&CString16::try_from("0X12e").unwrap())
);
println!(
r#"parse_hex_number("0x12eFAb"), should be Ok({}), result is {:?}"#,
0x12EFAB,
parse_hex_number(&CString16::try_from("0x12eFAb").unwrap())
);
println!(
r#"parse_hex_number("0xDEADBEEFDEADBEEF01"), should be Err({}), result is {:?}"#,
ParseError::NumberTooLarge("0xDEADBEEFDEADBEEF01".to_owned()),
parse_hex_number(&CString16::try_from("0xDEADBEEFDEADBEEF01").unwrap())
);
println!(
r#"parse_hex_number("0x12eFAs"), should be Err({:?}), result is {:?}"#,
ParseError::InvalidValue("0x12eFAs".to_owned()),
parse_hex_number(&CString16::try_from("0x12eFAs").unwrap())
);
println!(
r#"parse_hex_number("abc"), should be Err({:?}), result is {:?}"#,
ParseError::InvalidValue("abc".to_owned()),
parse_hex_number(&CString16::try_from("abc").unwrap())
);
println!("Testing parse_named_arg");
println!(
r#"parse_named_arg("-h", None), should be Ok({:?}), result is {:?}"#,
NamedArg::Help,
parse_named_arg(&CString16::try_from("-h").unwrap())
);
println!(
r#"parse_named_arg("-r", None), should be Ok({:?}), result is {:?}"#,
NamedArg::Reboot(RebootMode::Always),
parse_named_arg(&CString16::try_from("-r").unwrap())
);
println!(
r#"parse_named_arg("--reboot", None), should be Ok({:?}), result is {:?}"#,
NamedArg::Reboot(RebootMode::Always),
parse_named_arg(&CString16::try_from("--reboot").unwrap())
);
println!(
r#"parse_named_arg("--write_on_demand", None), should be Ok({:?}), result is {:?}"#,
NamedArg::WriteOnDemand,
parse_named_arg(&CString16::try_from("--write_on_demand").unwrap())
);
println!(
r#"parse_named_arg("--help", Some("abc")), should be Ok({:?}), result is {:?}"#,
NamedArg::Help,
parse_named_arg(&CString16::try_from("--help").unwrap())
);
println!(
r#"parse_named_arg("0x13", Some("CpuSetup")), should be Err({:?}), result is {:?}"#,
ParseError::InvalidValue("0x13".to_owned()),
parse_named_arg(&CString16::try_from("0x13").unwrap())
);
println!("Testing parse_value_arg");
println!(
r#"parse_value_arg("CpuSetup(2):0x19E"), should be Ok({:?}), result is {:?}"#,
ValueArg {
addr: ValueAddr {
var_name: CString16::try_from("CpuSetup").unwrap(),
var_id: Some(2),
offset: 0x19E,
val_size: 1,
},
operation: ValueOperation::Read,
},
parse_value_arg(&CString16::try_from("CpuSetup(2):0x19E").unwrap())
);
println!(
r#"parse_value_arg("CpuSetup:0x19E(3)=0x02"), should be Ok({:?}), result is {:?}"#,
ValueArg {
addr: ValueAddr {
var_name: CString16::try_from("CpuSetup").unwrap(),
var_id: None,
offset: 0x19E,
val_size: 3,
},
operation: ValueOperation::Write(0x02),
},
parse_value_arg(&CString16::try_from("CpuSetup:0x19E(3)=0x02").unwrap())
);
println!("Testing parse_args_from_str");
println!(
r#"parse_args_from_str(".\setup_var.efi"), should be Ok({:?}), result is {:?}"#,
Args {
help_msg: true,
..Default::default()
},
parse_args_from_str(&CString16::try_from(".\\setup_var.efi").unwrap())
);
println!(
r#"parse_args_from_str(".\setup_var.efi -h"), should be Ok({:?}), result is {:?}"#,
Args {
help_msg: true,
..Default::default()
},
parse_args_from_str(&CString16::try_from(".\\setup_var.efi -h").unwrap())
);
println!(
r#"parse_args_from_str(".\setup_var.efi VAR:0x12 -h"), should be Ok({:?}), result is {:?}"#,
Args {
help_msg: true,
value_args: vec![ValueArg {
addr: ValueAddr {
var_name: CString16::try_from("VAR").unwrap(),
var_id: None,
offset: 0x12,
val_size: 1,
},
operation: ValueOperation::Read,
},],
..Default::default()
},
parse_args_from_str(&CString16::try_from(".\\setup_var.efi VAR:0x12 -h").unwrap())
);
println!(
r#"parse_args_from_str(".\setup_var.efi VAR:0x12=0x12 --write_on_demand -h"), should be Ok({:?}), result is {:?}"#,
Args {
help_msg: true,
write_on_demand: true,
value_args: vec![ValueArg {
addr: ValueAddr {
var_name: CString16::try_from("VAR").unwrap(),
var_id: None,
offset: 0x12,
val_size: 1,
},
operation: ValueOperation::Write(0x12),
},],
..Default::default()
},
parse_args_from_str(
&CString16::try_from(".\\setup_var.efi VAR:0x12=0x12 --write_on_demand -h").unwrap()
)
);
println!(
r#"parse_args_from_str(".\setup_var.efi VAR:0x12=0x12e"), should be Err({:?}), result is {:?}"#,
ParseError::InvalidArgs(ArgsError::ValLargerThanSize(0x12e, 1)),
parse_args_from_str(&CString16::try_from(".\\setup_var.efi VAR:0x12=0x12e").unwrap())
);
println!(
r#"parse_args_from_str(".\setup_var.efi VAR:0x01(3) VAR2(2):0x03=0x05"), should be Ok({:?}), result is {:?}"#,
Args {
value_args: vec![
ValueArg {
addr: ValueAddr {
var_name: CString16::try_from("VAR").unwrap(),
var_id: None,
offset: 0x01,
val_size: 3,
},
operation: ValueOperation::Read,
},
ValueArg {
addr: ValueAddr {
var_name: CString16::try_from("VAR2").unwrap(),
var_id: Some(2),
offset: 0x03,
val_size: 1,
},
operation: ValueOperation::Write(0x05),
}
],
..Default::default()
},
parse_args_from_str(
&CString16::try_from(".\\setup_var.efi VAR:0x01(3) VAR2(2):0x03=0x05").unwrap()
)
);
// Add new test for auto reboot mode
println!(
r#"parse_args_from_str(".\setup_var.efi --reboot=auto VAR:0x12=0x12"), should be Ok({:?}), result is {:?}"#,
Args {
help_msg: false,
write_on_demand: true, // auto reboot enables write_on_demand
reboot: RebootMode::Auto,
value_args: vec![ValueArg {
addr: ValueAddr {
var_name: CString16::try_from("VAR").unwrap(),
var_id: None,
offset: 0x12,
val_size: 1,
},
operation: ValueOperation::Write(0x12),
},],
},
parse_args_from_str(
&CString16::try_from(".\\setup_var.efi --reboot=auto VAR:0x12=0x12").unwrap()
)
);
}
================================================
FILE: src/input.rs
================================================
use alloc::{
borrow::{Cow, ToOwned},
string::ToString,
vec::Vec,
};
use uefi::{
CStr16, CString16,
data_types::EqStrUntilNul,
proto::console::text::{Input, Key},
system::with_stdin,
};
use crate::{
args::{Args, NamedArg, ParseError, RebootMode, ValueAddr, ValueArg, ValueOperation},
utils::CStr16Ext,
};
fn read_to_string(stdin: &mut Input) -> uefi::Result<CString16> {
let mut str = CString16::new();
while let Some(key) = stdin.read_key()? {
if let Key::Printable(char) = key {
str.push(char)
}
}
Ok(str)
}
pub fn parse_input() -> Result<Args, ParseError> {
let content = with_stdin(read_to_string).map_err(|_| ParseError::LoadStdInError)?;
if content.is_empty() {
return Err(ParseError::NoInput);
}
let content = content
.strip_prefix('\u{feff}'.try_into().unwrap())
.unwrap_or(&content);
let lines = content.split_to_cstring('\n'.try_into().unwrap());
let lines = lines.into_iter().filter_map(|s| {
let s = s.trim();
if s.starts_with('#'.try_into().unwrap()) {
return None;
}
let res = s.strip_suffix('\r'.try_into().unwrap()).unwrap_or(s);
if res.is_empty() { None } else { Some(res) }
});
let entries = lines
.map(|s| {
crate::args::try_parsers!(
&s,
parser_with_comment(parse_named_arg),
parser_with_comment(parse_address_def),
parser_with_comment(parse_value_arg_wrapped),
parser_with_comment(parse_address_ref),
)
})
.collect::<Result<Vec<_>, _>>()?;
let address_defs = entries
.iter()
.filter(|e| matches!(e, FileInputEntry::AddressDef { .. }))
.map(|e| e.as_address_def())
.collect::<Vec<_>>();
let mut val_args = entries
.iter()
.filter(|e| matches!(e, FileInputEntry::ValueArg(_)))
.map(|e| e.as_value_arg())
.cloned()
.collect::<Vec<_>>();
let address_refs = entries
.iter()
.filter(|e| matches!(e, FileInputEntry::AddressRef { .. }));
let mut address_ref_args = address_refs
.map(|e| match e {
FileInputEntry::AddressRef { name, operation } => {
let (_, addr) = address_defs
.iter()
.find(|(def_name, _)| name == *def_name)
.ok_or_else(|| ParseError::InvalidValue(name.to_string()))?;
Ok(ValueArg {
addr: (*addr).clone(),
operation: *operation,
})
}
_ => unreachable!(),
})
.collect::<Result<Vec<_>, ParseError>>()?;
val_args.append(&mut address_ref_args);
// Here we choose to let the last occurred setting override previous ones
let reboot_entry = entries
.iter()
.filter(|e| matches!(e, FileInputEntry::NamedArg(NamedArg::Reboot(_))))
.next_back();
let reboot = match reboot_entry {
Some(FileInputEntry::NamedArg(NamedArg::Reboot(mode))) => *mode,
_ => RebootMode::Never,
};
let write_on_demand = entries
.iter()
.any(|e| matches!(e, FileInputEntry::NamedArg(NamedArg::WriteOnDemand)));
Ok(Args {
value_args: val_args,
help_msg: false,
write_on_demand,
reboot,
})
}
enum FileInputEntry {
AddressDef {
name: CString16,
addr: ValueAddr,
},
ValueArg(ValueArg),
AddressRef {
name: CString16,
operation: ValueOperation,
},
NamedArg(NamedArg),
}
impl FileInputEntry {
fn as_address_def(&self) -> (&CString16, &ValueAddr) {
match self {
Self::AddressDef { name, addr } => (name, addr),
_ => panic!("Invalid variant used on into_address_def"),
}
}
fn as_value_arg(&self) -> &ValueArg {
match self {
Self::ValueArg(val_arg) => val_arg,
_ => panic!("Invalid variant used on into_value_arg"),
}
}
}
fn parser_with_comment<T>(parser: T) -> impl Fn(&CStr16) -> Result<FileInputEntry, ParseError>
where
T: Fn(&CStr16) -> Result<FileInputEntry, ParseError>,
{
move |arg| {
let arg = if let Some((arg, _comment)) = arg.split_only_first('#'.try_into().unwrap()) {
Cow::Owned(arg.trim())
} else {
Cow::Borrowed(arg)
};
parser(&arg)
}
}
fn parse_named_arg(arg: &CStr16) -> Result<FileInputEntry, ParseError> {
let named_arg = arg
.strip_prefix('@'.try_into().unwrap())
.ok_or_else(|| ParseError::InvalidValue(arg.to_string()))?;
if named_arg.eq_str_until_nul("reboot=auto") {
Ok(FileInputEntry::NamedArg(NamedArg::Reboot(RebootMode::Auto)))
} else if named_arg.eq_str_until_nul("reboot") {
Ok(FileInputEntry::NamedArg(NamedArg::Reboot(
RebootMode::Always,
)))
} else if named_arg.eq_str_until_nul("write_on_demand") {
Ok(FileInputEntry::NamedArg(NamedArg::WriteOnDemand))
} else {
Err(ParseError::InvalidValue(arg.to_string()))
}
}
fn parse_address_def(arg: &CStr16) -> Result<FileInputEntry, ParseError> {
let (addr_name, addr_def) = arg
.split_only_first('='.try_into().unwrap())
.ok_or_else(|| ParseError::InvalidValue(arg.to_string()))?;
let addr_name = addr_name
.strip_suffix(':'.try_into().unwrap())
.ok_or_else(|| ParseError::InvalidValue(arg.to_string()))?;
let val_arg = crate::args::parse_value_arg(&addr_def)?;
if let ValueOperation::Write(_) = val_arg.operation {
Err(ParseError::AddrDefWrite(addr_def.to_string()))
} else {
Ok(FileInputEntry::AddressDef {
name: addr_name,
addr: val_arg.addr,
})
}
}
fn parse_value_arg_wrapped(arg: &CStr16) -> Result<FileInputEntry, ParseError> {
Ok(FileInputEntry::ValueArg(crate::args::parse_value_arg(arg)?))
}
fn parse_address_ref(arg: &CStr16) -> Result<FileInputEntry, ParseError> {
let (addr_name, _value_str) = arg
.split_only_first('='.try_into().unwrap())
.ok_or_else(|| ParseError::InvalidValue(arg.to_string()))?;
let addr_name = if addr_name.starts_with('$'.try_into().unwrap()) {
addr_name
.strip_prefix('$'.try_into().unwrap())
.ok_or_else(|| ParseError::InvalidValue(arg.to_string()))?
} else {
Err(ParseError::InvalidValue(addr_name.to_string()))?
};
let value_op = crate::args::parse_value_wrapped(arg)?;
Ok(FileInputEntry::AddressRef {
name: addr_name.to_owned(),
operation: value_op,
})
}
================================================
FILE: src/main.rs
================================================
#![no_main]
#![no_std]
extern crate alloc;
mod args;
mod input;
mod utils;
use args::{HELP_MSG, ParseError, RebootMode, ValueArg, ValueOperation};
use uefi::{prelude::*, println, runtime::ResetType};
use utils::{UEFIValue, WriteStatus};
use crate::input::parse_input;
#[entry]
fn main() -> Status {
uefi::helpers::init().expect("Unexpected error while initializing UEFI services");
let args = match args::parse_args() {
Ok(args) => {
if args.help_msg {
println!("{}", HELP_MSG);
return Status::INVALID_PARAMETER;
}
args
}
Err(ParseError::NoArgs) => match parse_input() {
Ok(args) => args,
Err(ParseError::NoInput) => {
println!("{}", HELP_MSG);
return Status::INVALID_PARAMETER;
}
Err(e) => {
println!("Error parsing input file:\n{e}");
return Status::INVALID_PARAMETER;
}
},
Err(e) => {
println!("Error parsing arguments:\n{e}");
return Status::INVALID_PARAMETER;
}
};
let mut arg_process_state = ArgProcessState::default();
for val_arg in args.value_args {
let (status, state) = process_arg(args.write_on_demand, &val_arg);
if status != Status::SUCCESS {
return status;
}
arg_process_state.bind(&state);
}
let reboot_required = matches!(arg_process_state.write_status, WriteStatus::Normal);
if args.reboot == RebootMode::Always || (args.reboot == RebootMode::Auto && reboot_required) {
runtime::reset(ResetType::WARM, Status::SUCCESS, None);
}
Status::SUCCESS
}
struct ArgProcessState {
write_status: WriteStatus,
}
impl ArgProcessState {
fn bind(&mut self, other: &Self) {
if let WriteStatus::Skipped = self.write_status {
if let WriteStatus::Normal = other.write_status {
self.write_status = WriteStatus::Normal;
}
}
}
fn nothing_written() -> Self {
Self {
write_status: WriteStatus::Skipped,
}
}
}
impl Default for ArgProcessState {
fn default() -> Self {
Self::nothing_written()
}
}
fn process_arg(write_on_demand: bool, val_arg: &ValueArg) -> (Status, ArgProcessState) {
let var_name = &val_arg.addr.var_name;
let val_size = val_arg.addr.val_size;
let offset = val_arg.addr.offset;
match val_arg.operation {
ValueOperation::Read => {
match utils::read_val(var_name, val_arg.addr.var_id, offset, val_size) {
Ok(value) => {
println!("{}", val_arg.to_string_with_val(&value));
(Status::SUCCESS, ArgProcessState::nothing_written())
}
Err(e) => {
println!("Error reading variable:\n{e}");
(Status::ABORTED, ArgProcessState::nothing_written())
}
}
}
ValueOperation::Write(value) => {
let value = UEFIValue::from_usize(value, val_size);
match utils::write_val(
var_name,
val_arg.addr.var_id,
offset,
&value,
val_size,
write_on_demand,
) {
Err(e) => {
println!("Error writing variable:\n{e}");
(Status::ABORTED, ArgProcessState::nothing_written())
}
Ok(write_status) => {
let skipped_msg = match write_status {
WriteStatus::Skipped => " # Writing skipped",
WriteStatus::Normal => "",
};
println!("{}{}", val_arg.to_string_with_val(&value), skipped_msg);
(Status::SUCCESS, ArgProcessState { write_status })
}
}
}
}
}
================================================
FILE: src/utils.rs
================================================
use alloc::{
borrow::ToOwned,
format,
string::{String, ToString},
vec,
vec::Vec,
};
use core::fmt::{Display, Formatter};
use num_enum::TryFromPrimitive;
use strum::EnumMessage;
use uefi::{
CStr16, CString16, Char16, ResultExt, Status,
data_types::{FromSliceWithNulError, chars::NUL_16},
print, println,
runtime::{VariableAttributes, VariableKey, VariableVendor},
};
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(TryFromPrimitive, EnumMessage, strum::Display)]
#[repr(u8)]
pub enum UEFIStatusErrorCode {
/// The image failed to load.
LOAD_ERROR = 1,
/// A parameter was incorrect.
INVALID_PARAMETER = 2,
/// The operation is not supported.
UNSUPPORTED = 3,
/// The buffer was not the proper size for the request.
BAD_BUFFER_SIZE = 4,
/// The buffer is not large enough to hold the requested data.
/// The required buffer size is returned in the appropriate parameter.
BUFFER_TOO_SMALL = 5,
/// There is no data pending upon return.
NOT_READY = 6,
/// The physical device reported an error while attempting the operation.
DEVICE_ERROR = 7,
/// The device cannot be written to.
WRITE_PROTECTED = 8,
/// A resource has run out.
OUT_OF_RESOURCES = 9,
/// An inconstency was detected on the file system.
VOLUME_CORRUPTED = 10,
/// There is no more space on the file system.
VOLUME_FULL = 11,
/// The device does not contain any medium to perform the operation.
NO_MEDIA = 12,
/// The medium in the device has changed since the last access.
MEDIA_CHANGED = 13,
/// The item was not found.
NOT_FOUND = 14,
/// Access was denied.
ACCESS_DENIED = 15,
/// The server was not found or did not respond to the request.
NO_RESPONSE = 16,
/// A mapping to a device does not exist.
NO_MAPPING = 17,
/// The timeout time expired.
TIMEOUT = 18,
/// The protocol has not been started.
NOT_STARTED = 19,
/// The protocol has already been started.
ALREADY_STARTED = 20,
/// The operation was aborted.
ABORTED = 21,
/// An ICMP error occurred during the network operation.
ICMP_ERROR = 22,
/// A TFTP error occurred during the network operation.
TFTP_ERROR = 23,
/// A protocol error occurred during the network operation.
PROTOCOL_ERROR = 24,
/// The function encountered an internal version that was
/// incompatible with a version requested by the caller.
INCOMPATIBLE_VERSION = 25,
/// The function was not performed due to a security violation.
SECURITY_VIOLATION = 26,
/// A CRC error was detected.
CRC_ERROR = 27,
/// Beginning or end of media was reached
END_OF_MEDIA = 28,
/// The end of the file was reached.
END_OF_FILE = 31,
/// The language specified was invalid.
INVALID_LANGUAGE = 32,
/// The security status of the data is unknown or compromised and
/// the data must be updated or replaced to restore a valid security status.
COMPROMISED_DATA = 33,
/// There is an address conflict address allocation
IP_ADDRESS_CONFLICT = 34,
/// A HTTP error occurred during the network operation.
HTTP_ERROR = 35,
}
pub enum UEFIStatusError {
Success,
Error(UEFIStatusErrorCode),
UnknownError(usize),
}
impl From<Status> for UEFIStatusError {
fn from(value: Status) -> Self {
if !value.is_error() {
Self::Success
} else {
match UEFIStatusErrorCode::try_from(value.0 as u8) {
Ok(error_code) => UEFIStatusError::Error(error_code),
Err(_) => UEFIStatusError::UnknownError(value.0),
}
}
}
}
impl Display for UEFIStatusError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
UEFIStatusError::Success => write!(f, "Success"),
UEFIStatusError::Error(error_code) => {
write!(
f,
"{} ({})",
error_code,
error_code.get_documentation().unwrap_or_default()
)
}
UEFIStatusError::UnknownError(code) => write!(f, "Unknown error with code {}", code),
}
}
}
pub enum UEFIVarError {
NoCorrespondingVar,
MultipleVarNoId,
InvalidVarName(FromSliceWithNulError),
GetVariableSize(String, UEFIStatusError),
GetVariable(String, UEFIStatusError),
OffsetOverflow((usize, usize), usize), // ((offset, val_size), var_size)
SetVariable(String, UEFIStatusError),
}
impl Display for UEFIVarError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::NoCorrespondingVar => {
write!(f, "No variable with specified name found")
}
Self::MultipleVarNoId => {
write!(
f,
"Please re-run with variable id as shown in the above table"
)
}
Self::InvalidVarName(e) => {
write!(f, "Unexpected invalid UEFI variable name obtained: {e:?}")
}
Self::GetVariableSize(s, st) => {
write!(f, "Error while getting size of variable {s}: {st}")
}
Self::GetVariable(s, st) => {
write!(f, "Error while getting content of variable {s}: {st}",)
}
Self::OffsetOverflow((offset, val_size), size) => {
write!(
f,
"Specified offset 0x{offset:X} and value size 0x{val_size:X} exceeds variable \
size 0x{size:X}"
)
}
Self::SetVariable(s, st) => {
write!(f, "Error while setting content of variable {s}: {st}")
}
}
}
}
impl From<FromSliceWithNulError> for UEFIVarError {
fn from(value: FromSliceWithNulError) -> Self {
Self::InvalidVarName(value)
}
}
struct UEFIVariable {
name: CString16,
vendor: VariableVendor,
attributes: VariableAttributes,
content: Vec<u8>,
}
pub struct UEFIValue(pub Vec<u8>);
impl UEFIValue {
pub fn from_usize(value: usize, val_size: usize) -> Self {
let value = value & ((1 << (val_size * 8)) - 1);
Self(value.to_le_bytes()[0..val_size].to_vec())
}
pub fn to_string_with_size(&self, val_size: usize) -> String {
let mut bytes = [0; 8];
bytes[0..self.0.len()].copy_from_slice(&self.0);
format!(
"0x{:0width$X}",
usize::from_ne_bytes(bytes),
width = val_size * 2
)
}
}
impl Display for UEFIValue {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let mut bytes = [0; 8];
bytes[0..self.0.len()].copy_from_slice(&self.0);
write!(f, "0x{:X}", usize::from_ne_bytes(bytes))
}
}
pub fn read_val(
var_name: &CStr16,
var_id: Option<usize>,
offset: usize,
val_size: usize,
) -> Result<UEFIValue, UEFIVarError> {
let var = read_var(var_name, var_id)?;
if offset + val_size > var.content.len() {
return Err(UEFIVarError::OffsetOverflow(
(offset, val_size),
var.content.len(),
));
}
let slice = &var.content[offset..offset + val_size];
Ok(UEFIValue(slice.to_vec()))
}
pub enum WriteStatus {
Normal,
Skipped,
}
pub fn write_val(
var_name: &CStr16,
var_id: Option<usize>,
offset: usize,
value: &UEFIValue,
val_size: usize,
write_on_demand: bool,
) -> Result<WriteStatus, UEFIVarError> {
let mut var = read_var(var_name, var_id)?;
if offset + val_size > var.content.len() {
return Err(UEFIVarError::OffsetOverflow(
(offset, val_size),
var.content.len(),
));
}
let slice = &mut var.content[offset..offset + val_size];
if write_on_demand && slice == value.0 {
Ok(WriteStatus::Skipped)
} else {
slice.copy_from_slice(&value.0);
uefi::runtime::set_variable(&var.name, &var.vendor, var.attributes, &var.content)
.map_err(|e| UEFIVarError::SetVariable(var.name.to_string(), e.status().into()))?;
Ok(WriteStatus::Normal)
}
}
fn read_var(var_name: &CStr16, var_id: Option<usize>) -> Result<UEFIVariable, UEFIVarError> {
let keys = uefi::runtime::variable_keys();
let mut keys = keys
.into_iter()
.filter_map(|k| k.ok())
.filter(|k| k.name == var_name)
.collect::<Vec<_>>();
keys.sort_by_key(|k| k.vendor.0);
if keys.is_empty() {
return Err(UEFIVarError::NoCorrespondingVar);
}
if keys.len() > 1 && var_id.is_none() {
print_keys(keys)?;
return Err(UEFIVarError::MultipleVarNoId);
}
let var_key = if keys.len() == 1 {
&keys[0]
} else {
&keys[var_id.unwrap()] // if keys.len() == 1, then branch above; if keys.len() > 1 && var_id is None, then above if
};
let var_name = &var_key.name;
let size = get_variable_size(var_name, &var_key.vendor)
.map_err(|e| UEFIVarError::GetVariableSize(var_name.to_string(), e.status().into()))?;
let mut buf = vec![0; size];
let (_, var_attr) = uefi::runtime::get_variable(var_name, &var_key.vendor, &mut buf)
.map_err(|e| UEFIVarError::GetVariable(var_name.to_string(), e.status().into()))?;
Ok(UEFIVariable {
name: var_name.to_owned(),
vendor: var_key.vendor,
attributes: var_attr,
content: buf,
})
}
fn print_keys(keys: Vec<VariableKey>) -> Result<(), UEFIVarError> {
println!("Multiple variables with same name detected:");
println!("ID\tVarName\t\t\t\tSize\t");
println!();
for (i, key) in keys.into_iter().enumerate() {
let id = i;
let name = &key.name;
let size = get_variable_size(name, &key.vendor)
.map_err(|e| UEFIVarError::GetVariableSize(name.to_string(), e.status().into()))?;
print!("0x{:X}\t", id);
print!("{name}");
match (name.num_bytes() / 2 - 1) / 8 {
0 => {
print!("\t\t\t\t")
}
1 => {
print!("\t\t\t")
}
2 => {
print!("\t\t")
}
_ => {
print!("\t")
}
}
println!("0x{:X}\t", size)
}
Ok(())
}
fn get_variable_size(name: &CStr16, vendor: &VariableVendor) -> uefi::Result<usize> {
let res = uefi::runtime::get_variable(name, vendor, &mut []);
if res.status() == Status::BUFFER_TOO_SMALL {
let err = res.unwrap_err();
Ok(err.data().unwrap_or_default())
} else {
Err(res.discard_errdata().unwrap_err())
}
}
pub trait CStr16Ext {
fn contains(&self, char: Char16) -> bool;
fn strip_prefix(&self, prefix: Char16) -> Option<&CStr16>;
fn strip_suffix(&self, suffix: Char16) -> Option<CString16>;
/// The CStr16 requires a NUL terminator, so we can only collect them back
/// into CString16
fn split_to_cstring(&self, split: Char16) -> Vec<CString16>;
fn split_only_first(&self, split: Char16) -> Option<(CString16, CString16)>;
fn trim(&self) -> CString16;
fn starts_with(&self, char: Char16) -> bool;
}
impl CStr16Ext for CStr16 {
fn contains(&self, char: Char16) -> bool {
self.iter().any(|&c| c == char)
}
fn strip_prefix(&self, prefix: Char16) -> Option<&CStr16> {
if *self.iter().next()? == prefix {
let str_ref = unsafe {
CStr16::from_u16_with_nul_unchecked(
&*(&self.as_slice_with_nul()[1..] as *const [Char16] as *const [u16]),
)
};
Some(str_ref)
} else {
None
}
}
fn strip_suffix(&self, suffix: Char16) -> Option<CString16> {
let str = self.to_u16_slice();
if *str.last()? == u16::from(suffix) {
let mut buf = self
.as_slice()
.iter()
.map(|&c| u16::from(c))
.collect::<Vec<_>>();
*buf.last_mut()? = 0;
Some(CString16::try_from(buf).unwrap())
} else {
None
}
}
fn split_to_cstring(&self, split: Char16) -> Vec<CString16> {
let mut split_strings = Vec::new();
let mut current_string = Vec::new();
for c in self.iter() {
if *c != split {
current_string.push(u16::from(*c))
} else {
current_string.push(0u16);
split_strings.push(
current_string
.try_into()
.expect("Invalid bytes in arguments"),
);
current_string = Vec::new()
}
}
if !current_string.is_empty() {
current_string.push(0u16);
split_strings.push(
current_string
.try_into()
.expect("Invalid bytes in arguments"),
)
}
split_strings
}
fn split_only_first(&self, split: Char16) -> Option<(CString16, CString16)> {
let index = self.iter().position(|&c| c == split)?;
// We have to copy twice and check validity again???
let mut first_str = self.as_slice()[..index].to_vec();
first_str.push(NUL_16);
let mut second_str = self.as_slice()[index + 1..].to_vec();
second_str.push(NUL_16);
let first_str = char16_vec_to_cstring16(first_str);
let second_str = char16_vec_to_cstring16(second_str);
Some((first_str, second_str))
}
fn trim(&self) -> CString16 {
let is_space =
|&c| c == Char16::try_from(' ').unwrap() || c == Char16::try_from('\t').unwrap();
let mut start = 0;
for (i, c) in self.iter().enumerate() {
if !is_space(c) {
start = i;
break;
}
}
let mut end = self.num_chars() - 1;
for (i, c) in self.as_slice().iter().rev().enumerate() {
if !is_space(c) {
end -= i;
break;
}
}
let mut str = self.as_slice()[start..=end].to_vec();
str.push(NUL_16);
char16_vec_to_cstring16(str)
}
fn starts_with(&self, char: Char16) -> bool {
*self.iter().next().unwrap_or(&NUL_16) == char
}
}
/// We have no way to directly construct CString16, and as it does NOT use
/// repr(transparent). We even cannot construct it with unsafe operations. A
/// copy and char validity check is required in this function.
fn char16_vec_to_cstring16(s: Vec<Char16>) -> CString16 {
CString16::try_from(s.into_iter().map(u16::from).collect::<Vec<_>>()).unwrap()
}
gitextract_10qigmhq/
├── .cargo/
│ └── config.toml
├── .gitignore
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── rust-toolchain.toml
└── src/
├── args.rs
├── input.rs
├── main.rs
└── utils.rs
SYMBOL INDEX (80 symbols across 4 files)
FILE: src/args.rs
type ParseError (line 20) | pub enum ParseError {
method from (line 188) | fn from(value: ArgsError) -> Self {
method fmt (line 33) | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
type ValueAddr (line 69) | pub struct ValueAddr {
type ValueOperation (line 77) | pub enum ValueOperation {
type ValueArg (line 84) | pub struct ValueArg {
method validate (line 90) | pub fn validate(&self) -> Result<(), ArgsError> {
method to_string_with_val (line 102) | pub fn to_string_with_val(&self, value: &UEFIValue) -> String {
type RebootMode (line 123) | pub enum RebootMode {
method default (line 130) | fn default() -> Self {
type NamedArg (line 136) | pub enum NamedArg {
type Arg (line 143) | enum Arg {
type Args (line 149) | pub struct Args {
method validate (line 157) | fn validate(&self) -> Result<(), ArgsError> {
type ArgsError (line 167) | pub enum ArgsError {
method fmt (line 172) | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
constant HELP_MSG (line 193) | pub const HELP_MSG: &str = r#"Usage:
function parse_args (line 216) | pub fn parse_args() -> Result<Args, ParseError> {
function drop_first_arg (line 228) | fn drop_first_arg(arg: &CStr16) -> bool {
function parse_args_from_str (line 273) | fn parse_args_from_str(options: &CStr16) -> Result<Args, ParseError> {
function starts_with (line 341) | fn starts_with(s: &CStr16, c: char) -> bool {
function try_next_char (line 348) | fn try_next_char(iter: &mut impl Iterator<Item = char>, str: &CStr16) ->...
function parse_hex_number (line 353) | fn parse_hex_number(num_str: &CStr16) -> Result<usize, ParseError> {
function parse_number (line 382) | fn parse_number(num_str: &CStr16) -> Result<usize, ParseError> {
function parse_named_arg (line 395) | fn parse_named_arg(key: &CStr16) -> Result<Arg, ParseError> {
function parse_value_arg_wrapped (line 409) | fn parse_value_arg_wrapped(arg: &CStr16) -> Result<Arg, ParseError> {
function parse_value_wrapped (line 413) | pub fn parse_value_wrapped(arg: &CStr16) -> Result<ValueOperation, Parse...
function parse_value (line 417) | fn parse_value(arg: &CStr16) -> Result<(Cow<CStr16>, ValueOperation), Pa...
function parse_value_arg (line 434) | pub fn parse_value_arg(arg: &CStr16) -> Result<ValueArg, ParseError> {
function test_functions (line 488) | pub fn test_functions() {
FILE: src/input.rs
function read_to_string (line 19) | fn read_to_string(stdin: &mut Input) -> uefi::Result<CString16> {
function parse_input (line 31) | pub fn parse_input() -> Result<Args, ParseError> {
type FileInputEntry (line 118) | enum FileInputEntry {
method as_address_def (line 132) | fn as_address_def(&self) -> (&CString16, &ValueAddr) {
method as_value_arg (line 139) | fn as_value_arg(&self) -> &ValueArg {
function parser_with_comment (line 147) | fn parser_with_comment<T>(parser: T) -> impl Fn(&CStr16) -> Result<FileI...
function parse_named_arg (line 162) | fn parse_named_arg(arg: &CStr16) -> Result<FileInputEntry, ParseError> {
function parse_address_def (line 180) | fn parse_address_def(arg: &CStr16) -> Result<FileInputEntry, ParseError> {
function parse_value_arg_wrapped (line 200) | fn parse_value_arg_wrapped(arg: &CStr16) -> Result<FileInputEntry, Parse...
function parse_address_ref (line 204) | fn parse_address_ref(arg: &CStr16) -> Result<FileInputEntry, ParseError> {
FILE: src/main.rs
function main (line 17) | fn main() -> Status {
type ArgProcessState (line 64) | struct ArgProcessState {
method bind (line 69) | fn bind(&mut self, other: &Self) {
method nothing_written (line 77) | fn nothing_written() -> Self {
method default (line 85) | fn default() -> Self {
function process_arg (line 90) | fn process_arg(write_on_demand: bool, val_arg: &ValueArg) -> (Status, Ar...
FILE: src/utils.rs
type UEFIStatusErrorCode (line 22) | pub enum UEFIStatusErrorCode {
type UEFIStatusError (line 94) | pub enum UEFIStatusError {
method from (line 101) | fn from(value: Status) -> Self {
method fmt (line 114) | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
type UEFIVarError (line 130) | pub enum UEFIVarError {
method from (line 176) | fn from(value: FromSliceWithNulError) -> Self {
method fmt (line 141) | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
type UEFIVariable (line 181) | struct UEFIVariable {
type UEFIValue (line 188) | pub struct UEFIValue(pub Vec<u8>);
method from_usize (line 191) | pub fn from_usize(value: usize, val_size: usize) -> Self {
method to_string_with_size (line 196) | pub fn to_string_with_size(&self, val_size: usize) -> String {
method fmt (line 209) | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
function read_val (line 217) | pub fn read_val(
type WriteStatus (line 235) | pub enum WriteStatus {
function write_val (line 240) | pub fn write_val(
function read_var (line 270) | fn read_var(var_name: &CStr16, var_id: Option<usize>) -> Result<UEFIVari...
function print_keys (line 309) | fn print_keys(keys: Vec<VariableKey>) -> Result<(), UEFIVarError> {
function get_variable_size (line 341) | fn get_variable_size(name: &CStr16, vendor: &VariableVendor) -> uefi::Re...
type CStr16Ext (line 351) | pub trait CStr16Ext {
method contains (line 352) | fn contains(&self, char: Char16) -> bool;
method strip_prefix (line 353) | fn strip_prefix(&self, prefix: Char16) -> Option<&CStr16>;
method strip_suffix (line 354) | fn strip_suffix(&self, suffix: Char16) -> Option<CString16>;
method split_to_cstring (line 357) | fn split_to_cstring(&self, split: Char16) -> Vec<CString16>;
method split_only_first (line 358) | fn split_only_first(&self, split: Char16) -> Option<(CString16, CStrin...
method trim (line 359) | fn trim(&self) -> CString16;
method starts_with (line 360) | fn starts_with(&self, char: Char16) -> bool;
method contains (line 364) | fn contains(&self, char: Char16) -> bool {
method strip_prefix (line 368) | fn strip_prefix(&self, prefix: Char16) -> Option<&CStr16> {
method strip_suffix (line 381) | fn strip_suffix(&self, suffix: Char16) -> Option<CString16> {
method split_to_cstring (line 396) | fn split_to_cstring(&self, split: Char16) -> Vec<CString16> {
method split_only_first (line 426) | fn split_only_first(&self, split: Char16) -> Option<(CString16, CStrin...
method trim (line 442) | fn trim(&self) -> CString16 {
method starts_with (line 468) | fn starts_with(&self, char: Char16) -> bool {
function char16_vec_to_cstring16 (line 476) | fn char16_vec_to_cstring16(s: Vec<Char16>) -> CString16 {
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (71K chars).
[
{
"path": ".cargo/config.toml",
"chars": 38,
"preview": "[build]\ntarget = \"x86_64-unknown-uefi\""
},
{
"path": ".gitignore",
"chars": 15,
"preview": "/target\n/.idea\n"
},
{
"path": "Cargo.toml",
"chars": 435,
"preview": "[package]\nname = \"setup_var\"\nversion = \"0.3.1\"\nedition = \"2024\"\nlicense = \"MIT OR Apache-2.0\"\n\n# See more keys and their"
},
{
"path": "LICENSE-APACHE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "LICENSE-MIT",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2022 datasone\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "README.md",
"chars": 7066,
"preview": "# UEFI Command Line Tool for Reading/Writing UEFI Variables\nThis tool is a rewritten version of the [modded grub with se"
},
{
"path": "rust-toolchain.toml",
"chars": 91,
"preview": "[toolchain]\ntargets = [\"aarch64-unknown-uefi\", \"i686-unknown-uefi\", \"x86_64-unknown-uefi\"]\n"
},
{
"path": "src/args.rs",
"chars": 22589,
"preview": "use alloc::{\n borrow::{Cow, ToOwned},\n format,\n string::{String, ToString},\n vec,\n vec::Vec,\n};\nuse core:"
},
{
"path": "src/input.rs",
"chars": 6769,
"preview": "use alloc::{\n borrow::{Cow, ToOwned},\n string::ToString,\n vec::Vec,\n};\n\nuse uefi::{\n CStr16, CString16,\n "
},
{
"path": "src/main.rs",
"chars": 4006,
"preview": "#![no_main]\n#![no_std]\n\nextern crate alloc;\n\nmod args;\nmod input;\nmod utils;\n\nuse args::{HELP_MSG, ParseError, RebootMod"
},
{
"path": "src/utils.rs",
"chars": 15191,
"preview": "use alloc::{\n borrow::ToOwned,\n format,\n string::{String, ToString},\n vec,\n vec::Vec,\n};\nuse core::fmt::{"
}
]
About this extraction
This page contains the full source code of the datasone/setup_var.efi GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (67.0 KB), approximately 16.9k tokens, and a symbol index with 80 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.