├── .github
└── workflows
│ ├── build.yml
│ └── release.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE.md
├── README.md
├── extra
├── 8A14_GetMem.uvt
├── 8A14_Set_CL34.uvt
├── 8A14_Set_CL34_SAGV.uvt
├── README.md
└── uvt-logo.png
└── src
├── config.rs
├── config
└── locale_en.rs
├── data.rs
├── error.rs
├── firmware.rs
├── main.rs
├── parse.rs
└── string.rs
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | #
2 | # -|-
3 | # | || /| UEFI Variable Tool (UVT) * Build Workflow
4 | # | || / | https://github.com/GeographicCone/UefiVarTool
5 | # `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 | #
7 | # Builds the application with the specified version data
8 |
9 | name: "UVT Build"
10 | run-name: "UEFI Variable Tool (UVT) Build by ${{ github.actor }}"
11 |
12 | on:
13 | workflow_call:
14 | inputs:
15 | version_number:
16 | description: "Version number (three dot-separated non-negative integers)"
17 | required: true
18 | type: string
19 | version_word:
20 | default: "Snapshot"
21 | description: "Build type (Preview, Release, Snapshot)"
22 | required: false
23 | type: string
24 |
25 | workflow_dispatch:
26 | inputs:
27 | version_number:
28 | default: "0.0.0"
29 | description: "Version number"
30 | required: true
31 | type: string
32 | version_word:
33 | default: "Snapshot"
34 | description: "Build type"
35 | options:
36 | - "Preview"
37 | - "Release"
38 | - "Snapshot"
39 | required: true
40 | type: choice
41 |
42 | env:
43 | CARGO_BUILD_TARGET: x86_64-unknown-uefi
44 | CARGO_TERM_COLOR: always
45 | CARGO_TOML: Cargo.toml
46 |
47 | permissions:
48 | contents: read
49 |
50 | jobs:
51 | build:
52 | name: "Build UVT"
53 | runs-on: ubuntu-latest
54 |
55 | strategy:
56 | matrix:
57 | profile: ["release"]
58 |
59 | steps:
60 | - name: "Checkout UVT"
61 | uses: actions/checkout@v4
62 | with:
63 | fetch-depth: 1
64 |
65 | - name: "Add Target"
66 | working-directory: ${{ github.workspace }}
67 | run: rustup target add "${{ env.CARGO_BUILD_TARGET }}"
68 |
69 | - name: "Update Version Number"
70 | working-directory: ${{ github.workspace }}
71 | run: "sed -e 's/^version = \"0.0.0\".*$/version = \"${{ inputs.version_number }}\" # Automatically overriden/' -i \"${{ env.CARGO_TOML }}\""
72 |
73 | - name: "Run Cargo Build"
74 | working-directory: ${{ github.workspace }}
75 | run: BUILD_TYPE=${{ inputs.version_word }} cargo build --profile ${{ matrix.profile }} --verbose
76 |
77 | - name: "Upload UVT"
78 | uses: actions/upload-artifact@v3
79 | with:
80 | if-no-files-found: error
81 | name: ${{ github.event.repository.name }}
82 | path: target/${{ env.CARGO_BUILD_TARGET }}/${{ matrix.profile }}/${{ vars.BIN_FILE }}
83 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | #
2 | # -|-
3 | # | || /| UEFI Variable Tool (UVT) * Release Workflow
4 | # | || / | https://github.com/GeographicCone/UefiVarTool
5 | # `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 | #
7 | # Triggers a build and uploads artifacts as release assets
8 |
9 | name: "UVT Release"
10 | run-name: "UEFI Variable Tool (UVT) Release by ${{ github.actor }}"
11 |
12 | on:
13 | release:
14 | types: [published]
15 |
16 | permissions:
17 | contents: write
18 |
19 | jobs:
20 | call-build:
21 | name: "Call Build"
22 | secrets: inherit
23 | uses: ./.github/workflows/build.yml
24 | with:
25 | version_number: ${{ github.event.release.tag_name }}
26 | version_word: ${{ github.event.release.prerelease && 'Preview' || 'Release' }}
27 |
28 | release:
29 | name: "Release UVT"
30 | needs: call-build
31 | runs-on: ubuntu-latest
32 |
33 | steps:
34 | - id: download
35 | name: "Download Artifacts"
36 | uses: actions/download-artifact@v3
37 | with:
38 | name: ${{ github.event.repository.name }}
39 |
40 | - name: 'List Artifacts'
41 | run: ls -1R ${{ steps.download.outputs.download-path }}
42 |
43 | - name: "Create Archive"
44 | run: zip -9 -r UVT.zip ./*
45 |
46 | - name: "Create Archive Comment"
47 | run: |
48 | cat >UVT.zip.txt <
2 |
3 | # UEFI Variable Tool (UVT)
4 |
5 | **UEFI Variable Tool (UVT)** is a command-line application that runs from the UEFI shell. It can be launched in seconds from any FAT flash drive with no prior machine-specific setup, and lets you view and modify the content of individual UEFI variables at a byte level.
6 |
7 | **UVT**'s purpose is to allow changing all the hidden _UEFI (BIOS) Setup_ hardware settings. It is well-suited for situations when custom firmware, such as a modified BIOS that would unhide all the menu forms, cannot be flashed due to restrictive anti-features such as _Boot Guard_ being enabled.
8 |
9 | While various utilities have existed for a long time to allow doing just the same, making the functionality as such hardly a novelty, **UVT** aims to make the process as efficient and unencumbered as possible. To that effect:
10 | * It greatly streamlines the command-line **argument syntax**
11 | * It can also work in **scripted mode** to execute an arbitrarily-long series of operations from the **standard input** (_stdin_)
12 | * While in this mode, it allows **defining aliases** to identify data of interest (variable name, offset, and size), and **referencing** these later for read and write operations
13 | * The **output format follows the input**, which means it can be saved to a file and fed back as standard input to restore the **saved settings** later
14 |
15 | ----
16 |
17 | _**UVT** is free software, and full source code is available for anyone to tinker with. It is a quite heavily modified version of [setup_var.efi](https://github.com/datasone/setup_var.efi) by **[@datasone](https://github.com/datasone)**, to whom I originally made some [improvement suggestions](https://github.com/datasone/setup_var.efi/issues/14#issue-1863056272). He [followed up](https://github.com/datasone/setup_var.efi/issues/14#issuecomment-1727910880) on them and even graciously included me as a co-author in his commit, never mind that I had not at that point written a single line of code._
18 |
19 | _**@datasone**'s [last-posted version](https://github.com/datasone/setup_var.efi/commit/8c72429113f6fc5e7a4aac63a323d51a2d9f9dd8) seemed at least 90% there but wasn't working for me in a couple of ways. Since he's probably busy with other stuff in his life, I decided it was my turn to contribute something. Hence this fork, which although is completely refactored to the point it might not look very much like the original, would have never been possible without all of **@datasone**'s work, which he generously shared with the world. For that I am eternally grateful to him, and want everyone to know he deserves all the credit as the **original author** of this utility._
20 |
21 | ## How to Use
22 |
23 | The in-application usage information summary is reproduced below for reference:
24 |
25 | ````
26 | Usage: uvt[.efi] [] [ [... []]
27 | - or - uvt[.efi] <
28 | Where:
29 | : Optional global-scope application settings
30 | -f --force Force-write values even if already set as requested
31 | -h --help Show usage information (precludes other operations)
32 | -r --restart Upon successful completion, perform a system restart
33 | -s --simulate Do not write, only simulate actions (will still read)
34 | : Operation(s) to perform, can be multiple, each in the format:
35 | [()]:[()][=]
36 | Arg Overview:
37 | UEFI variable name to read or write to, case-sensitive
38 | If two variables share a name, will prompt to use this
39 | Data starting position within the given UEFI variable
40 | Optional, a byte (1) by default if omitted; little-endian
41 | Value to write, 8 bytes (64 bits) maximum; read if absent
42 | Script to run, same base format as arguments + see below
43 | File Overview:
44 | # Comment, ignored until end of line
45 | ! Set options, same as above arguments
46 | ,:[()] Define a variable to reference later
47 | @[=] Assign to a referenced variable
48 | Example Command Line:
49 | uvt -s Lang:0x00 Lang:0x00(4)=0x01020304 Lang:0x00(4)
50 | Read byte at offset 0, simulate-set the dword (4 bytes), then read again
51 | Example Input File:
52 | !simulate # Simulate only, do not perform actual writes
53 | Language,Lang:0x00(4) # Define a reference under the alias "Language"
54 | @Language=0x01020304 # Write to the target referred to by "Language"
55 |
56 | , and can be decimal or hexadecimal: use prefix "0x"
57 | File should be a UTF-16 LE text, UEFI firmware and shell version-dependent
58 | Output saved to a file can be re-used as input again: format is the same
59 | ````
60 |
61 | ### Prerequisites
62 |
63 | You need to boot into UEFI shell on the machine where you want to use the utility. This typically involves setting up a FAT flash drive and placing an EFI shell binary under the path `efi/boot/bootx64.efi`. You can then place the **UVT** binary under `efi/tools/uvt.efi` and run it as `uvt` regardless of what the current directory is. More on this in the _Background_ section.
64 |
65 | On a broader note, you need to know the variable layout, which is specific to your particular hardware, and possibly even the firmware version. How to obtain this information is also addressed in the _Background_ section.
66 |
67 | Separately, it is possible to run **UVT** in an emulator. As this would be mostly of interest to a developer, it is described in the _Building_ section.
68 |
69 | ### Command Line
70 |
71 | There are two ways to use **UVT**. The first one is to run it with command-line arguments.
72 |
73 | **UVT** accepts two kinds of command-line arguments: _operations_ and _options_. Arguments are separated with ` ` spaces. There is no limit on the number of arguments.
74 |
75 | #### Options
76 |
77 | _Options_ start with a `-` (minus) sign and are used to define global-scope settings. Each option has a short and a long form, taking a single `-` and a letter or a double `--` and a keyword respectively. The options are:
78 | * `-f` or `--force` Force-write values where the current values is equal to the new one. The default behavior is to skip such operations, and annotate such entries with an `# Already` comment in the output.
79 | * `-h` or `--help` Shows the usage information. If this option is selected, no other operations will be performed.
80 | * `-r` or `--restart` Reboots the system upon successful completion. No restart will be performed if any of the operations failed.
81 | * `-s` or `--simulate` If set, no changes will be made to UEFI variables. All the other aspects of the application will still be functioning exactly in the same way. This might be useful for checking what an operation would do, or whether the arguments are syntactically correct. If `-f` or `--force` is specified together with this option, no writing will happen regardless: the simulation takes precedence.
82 |
83 | #### Operations
84 |
85 | _Operations_ define either reading (querying, or getting) or writing (assigning, or setting) a value. The syntax is:
86 |
87 | ````
88 | [()]:[()][=]
89 | ````
90 | Where:
91 | * `` is the UEFI variable name. It is case-sensitive and mandatory: there is no default.
92 | * `` is an optional identifier to distinguish between variables in a situation when two or more share the same name. In the unlikely scenario this happens, the application will automatically list all the variables with the matching name, alongside with their respective identifiers.
93 | * `` is the position of data within the variable where the value data starts. Remember the count starts from 0, not 1.
94 | * `` is the optional size of the variable: it defaults to a single byte, i.e. `(1)`, which can also be specified, although that's unnecessary. The application can write at most 8 bytes (or 64 bits) at a time.
95 | * `` is the _new_ value to be written at the given offset. The value must fit within the `` constraint, which is checked. Multi-byte values are little-endian, which means that if you write `0x01` to 4 bytes starting at offset `0x00`, the value of `0x01` will be at the offset of `0x00` and not `0x03`, although if you _read_ these 4 bytes again, the result will also be shown as `0x00000001`. If you are unfamiliar with the concept or do not understand its implications, it's best to write individual bytes, and that's what the vast majority of _UEFI Setup_ settings are anyway. This part, alongside the `=` assignment operator, is optional: if absent, the default action is to query and output the _current_ value.
96 |
97 | For example:
98 | * `uvt Lang:0x00` reads the byte value at offset `0x00` in the variable `Lang`
99 | * `uvt -s Lang:0x00(4)=0x01020304` _simulates_ writing a double-word (four-byte) value to an offset starting at `0x00` in the variable `Lang`
100 | * `uvt Lang:0x00(4)` reads again the double word that has just been written with the preceding command
101 |
102 | An arbitrary number of command-line operations can be specified. They will be executed in the order entered. An error interrupts the processing of any further operations and arguments, terminating the application.
103 |
104 | #### Numerical Values
105 |
106 | Any number can be specified as either _decimal_ (base 10) or _hexadecimal_ (base 16). Hexadecimal values should be preceded by `0x` or `0X`, otherwise they will be parsed as decimal. Only digits `0-9` are allowed in decimal values. The additional digits `a-f` and `A-F` in hexadecimal values are case-insensitive.
107 |
108 | _Offsets_ and _values_ are output in hexadecimal, while _sizes_ are shown in decimal. When printed, hexadecimal values for _offsets_ will be zero-padded to 2 bytes. _Values_ will be zero-padded to their _size_. The padding does not have to be preserved in input, i.e. you can type `0x1` for a word-sized (two-byte) value, instead of writing `0x0001`.
109 |
110 | #### Output
111 |
112 | **UVT**'s output follows the same syntax as the input it accepts. This way, nearly everything it spits out can be fed back to it, for example to restore some previously-saved settings.
113 |
114 | The application prints out a header as the first thing it does after it launches, which provides some useful information. It might look like that:
115 |
116 | ````
117 | # UEFI Variable Tool (uvt) Version 0.0.0 @ American Megatrends 5.24 UEFI 2.8
118 | ````
119 |
120 | This prompt starting with `#` is also a valid comment, which means it will not interfere if you decided to feed the same file back to **UVT**. The three items following the `@` sign are: the firmware _vendor_, _firmware version_ (_major_._minor_) and the _UEFI revision_ (specification version compatibility).
121 |
122 | ### Input Stream
123 |
124 | **UVT**'s other mode of operation is to take an arbitrarily-long list of commands from the standard input (_stdin_). To use the application in this mode, make sure _not_ to provide _any_ command-line arguments, other than the redirection operator, which is however handled by the shell.
125 |
126 | #### Redirection
127 |
128 | **UVT** does not read or write files directly: it depends on the UEFI shell redirection. This comes with some quirks, which might also be implementation-dependent.
129 |
130 | To feed data to the application's _standard input_, use the following shell command:
131 |
132 | ````shell
133 | uvt < in.txt
134 | ````
135 |
136 | The file `in.txt` should be properly formatted, as discussed in the next section. You can also save the application's output:
137 |
138 | ````shell
139 | uvt > out.txt
140 | ````
141 |
142 | To combine both operations:
143 |
144 | ````shell
145 | uvt < in.txt > out.txt
146 | ````
147 |
148 | The quirks mentioned are as follows:
149 | * The ` ` space between the filename and the redirection sign is mandatory: neither `uvt a` despite being specified in the [UEFI Shell Manual](https://uefi.org/sites/default/files/resources/UEFI_Shell_2_2.pdf) do not seem to work properly in the latest _EFI Shell_ 2.2 (as of August 2023). Do not use them.
151 | * Input redirection via the `|` pipe operator does not seem to work either with built-in commands such as `echo` or `type`. The standard-input stream received by **UVT** is empty.
152 |
153 | #### File Format
154 |
155 | As the UEFI shell operates internally with UCS-2 encoding, the accepted standard input file format is _Unicode UTF-16 Little-Endian (LE)_: standard _ASCII_ text files will not work. This is a minor inconvenience, although even the _Notepad_ application bundled with Windows can save text in this format, as long as it is specified explicitly.
156 |
157 | Any output files produced by a redirection will also be in the same format.
158 |
159 | The _Byte Order Mark_ (BOM), mandated by the UTF-16 specification, is optional as far as the application is concerned. In fact, any BOM instances will be filtered out at an early parsing stage.
160 |
161 | The input file is split into individual _entries_, which are rows separated by the _Line Feed_ character `LF` or `\n`. Each line can contain at most a single operation and is self-contained, i.e. no operation can span multiple lines. The _Carriage Return_ character `CR` or `\r` may be present and will be discarded if that's the case.
162 |
163 | The format to define _operations_ is just the same as for the command-line arguments. _Options_ can also be defined, however with a different syntax (read on). Beyond that, there are also _definitions_, which can be referenced by _operations_, and _comments_.
164 |
165 | ##### Comments
166 |
167 | Comments are marked with the pound sign `#`. Anything to the right of that sign is discarded. Comments do not have to be separate lines, they can appear on the same line as an _operation_, an _option_ or a _definition_:
168 |
169 | ````
170 | # This is an example comment on a separate line
171 | !simulate # Simulate only, do not write
172 | Lang:0x00 # Retrieve the byte at offset 0 in "Lang"
173 | ````
174 |
175 | When the input is parsed, after filtering out the comments, an entry is trimmed of any leading and trailing whitespace characters. Entries that end up blank are at this point entirely discarded.
176 |
177 | ##### Definitions & References
178 |
179 | A target for an _operation_, consisting of a _variable name_, _offset_ and, optionally, _size_, can be defined to be referenced elsewhere in the file. The syntax is:
180 |
181 | ````
182 | ,:[()]
183 | ````
184 |
185 | Where ``, `` and `` have the same interpretation as discussed in the command-line arguments section, and `` is an identifier that can be reused later to identify the target. For example:
186 |
187 | ````
188 | Language,Lang:0x00(4)
189 | ````
190 |
191 | Note that any amount of whitespace can appear on either side of the `,` comma separator, which allows for better legibility in script formatting. For example:
192 |
193 | ````
194 | Language9 , Lang:0x09
195 | Language10, Lang:0x0a
196 | ````
197 |
198 | The syntax for _operations_ in the input stream is extended to include the following:
199 |
200 | ````
201 | @[=]
202 | ````
203 |
204 | The following example illustrates accessing a value by reference for the purposes of reading and writing respectively:
205 |
206 | ````
207 | @Language
208 | @Language=0x01020304
209 | ````
210 |
211 | #### Options
212 |
213 | Some of the _options_ (excluding usage information) can be defined in the input stream as well but the syntax for that is different. Namely, it's the `!` bang (exclamation mark) followed by the option keyword:
214 |
215 | ````
216 | !
217 | ````
218 |
219 | The available _options_ are `!force`, `!restart` and `!simulate`, and their interpretation is the same as discussed in the command-line arguments section.
220 |
221 | ## Background
222 |
223 | ### Setup
224 |
225 | To run **UVT** you need to boot into UEFI shell. The most straightforward way of setting it up is to use an empty flash drive you can boot from:
226 |
227 | * Start with an empty USB flash drive, formatted to a _FAT16_ or _FAT32_ filesystem
228 | * It does not actually _have_ to be empty, as long as you ensure any other files don't get in the way of the UEFI boot process
229 | * Download a compiled UEFI shell binary
230 | * Recent [official releases](https://github.com/tianocore/edk2/releases) do not provide binaries, those in the official repository are hopelessly out of date
231 | * One option is to use _Arch Linux_'s [edk2-shell](https://archlinux.org/packages/extra/any/edk2-shell/) package, which is regularly updated: you only need the file `usr/share/edk2-shell/x64/Shell_Full.efi` from inside the package archive `edk2-shell-YYYYMMDD-#-any.pkg.tar.zst`
232 | * The package is compressed with [zstd](https://en.wikipedia.org/wiki/Zstd) and can be decompressed using for example [7-Zip Z-Standard](https://github.com/mcmilk/7-Zip-zstd)
233 | * Another option is to use a build provided by **[@pbatard](https://github.com/pbatard)** in his [UEFI-Shell](https://github.com/pbatard/UEFI-Shell/releases/) repository, which are updated every half a year: in this case, download the file `UEFI-Shell-2.2-YYH#-RELEASE.iso` which can be opened with any archiving utility, and extract the file `efi/boot/bootx64.efi` from it
234 | * Whichever way you obtained the UEFI shell binary, rename it (if need be) to `bootx64.efi` and place it on the flash drive in the `efi/boot` directory.
235 | * While at it, also create the directory `efi/tools` and put `uvt.efi` downloaded from the [latest release](https://github.com/GeographicCone/UefiVarTool/releases/latest) in it: you're now ready to roll
236 | * Insert the flash drive into a USB port and boot from it, which might involve pressing one of the function keys to override the boot process, as well as possibly disabling _Secure Boot_, if you have it enabled.
237 |
238 | #### Automation
239 |
240 | You can use a startup script named `startup.nsh` placed in the `efi/boot` directory. An example script would look as follows:
241 | ````batch
242 | @echo -off
243 | fs0:
244 | alias -v so "shellopt.efi"
245 | alias -v v "uvt.efi"
246 | so -s -delay 0
247 | v --help
248 | ````
249 |
250 | You can now refer to `uvt` as `v`, which saves having to type the extra two letters every time. Furthermore, you can place any commands you want to run automatically on startup below in the file.
251 |
252 | One remaining annoyance is that the UEFI shell will have you wait five seconds or press a key before it processes the startup script. This behavior can be changed by passing a command-line argument to the shell: however, it's a chicken-and-egg problem since arguments cannot be passed to the shell directly, only by means of an environment variable.
253 |
254 | This problem is discussed in detail in **[@fpmurphy](https://github.com/fpmurphy)**'s blog post from a long while ago: [Problems with UEFI Shell Options](https://blog.fpmurphy.com/2012/07/problems-with-uefi-shell-options.html). He also came up with a solution to it, and that is the `shellopt.efi` script that appears in the example `startup.nsh` above. A more recent, updated build has been made available by BIOS developer **ChiChen** in his post: [Passing Parameters to BootX64.efi](https://chichen.tw/post/2020-06-01-pass-parameters-to-bootx64.efi/). If you use this functionality a lot, these five-second delays add up, and you might want to consider this workaround, as cumbersome as it sounds.
255 |
256 | ### Variable Information
257 |
258 | While **UVT** gives you all the means to access and modify the _UEFI Setup_ settings, no matter if they are hidden from the menu, you still have to know what you're looking at, and what can be done with it. This information depends on your specific hardware, and might possibly also change between different firmware (i.e. UEFI BIOS) versions, and has to be figured out separately. Here is a quick summary of the process:
259 |
260 | * Obtain the UEFI BIOS image for the device
261 | * Download it from the manufacturer's website: this is the easiest way, but double-check it's the correct version; the only catch is that updates are often distributed as Windows executable files: you might need a tool such as [InnoExtract](https://github.com/dscharrer/innoextract), [InnoUnp](https://github.com/WhatTheBlock/innounp) or the like, however the mentioned [7-Zip Z-Standard](https://github.com/mcmilk/7-Zip-zstd) might be enough to deal with it: you can use 7-Zip's _Open Inside #_ context-menu option to extract payloads from `.exe` files, and, if all else fails, visit **@[platomav](https://github.com/platomav)**'s wonderland of [BIOSUtilities](https://github.com/platomav/BIOSUtilities/), remembering to also grab [TianoCompress.exe](https://github.com/tianocore/edk2-BaseTools-win32/blob/master/TianoCompress.exe) from the official [ed2k-BaseTools-win32](https://github.com/tianocore/edk2-BaseTools-win32) repository as a dependency
262 | * Dump it with a software tool, such as Intel's **Flash Programming Tool** (_FPT_ or _FPT64W_), not officially available from _Intel_ but widely redistributed by vendors with a number of UEFI BIOS updates for commercial-grade hardware, and unofficially also obtainable from a number of places, including the [Win-RAID Forums](https://winraid.level1techs.com/t/89959/1): make sure you use the version matching your _Management Engine_ firmware
263 | * Dump it with a hardware programmer such as the widely-popular **CH341A** armed with a _Pomona 5250_ SOIC-8 clip and a tool like [FlashROM](https://github.com/flashrom/flashrom)
264 | * Either way, once you have obtained the correct BIOS image, open it with [UEFI Tool](https://github.com/LongSoft/UEFITool/releases/) by pressing Ctrl -O (command-line _UEFI Extract_ from the same repository can also be used)
265 | * Search for the _Setup_ or _SetupUtility_ EFI module by pressing Ctrl -F : the best way is to look for it by its GUID: `899407D7-99FE-43D8-9A21-79EC328CAC21` in case of an AMI BIOS, or `FE3542FE-C1D3-4EF8-657C-8048606FF670` for Insyde; in rare cases, the data might be stored elsewhere, in which case a list of common [guids.csv](https://github.com/LongSoft/UEFITool/blob/new_engine/common/guids.csv) might also come handy
266 | * Once located, right-click on the _PE32 Image Section_ under _Setup_ and choose _Extract Body…_ to extract the file `Section_PE32_Image_Setup_Body.efi` (or similarly named)
267 | * Download [IFR Extractor](https://github.com/LongSoft/IFRExtractor-RS/releases/latest) from the same repository, a command-line utility. Run it as follows: `ifrextractor Section_PE32_Image_Setup_Body.efi verbose`
268 | * The resulting _Internal Forms Representation_ dump file `Section_PE32_Image_Setup_Body.efi.0.0.en-US.ifr.txt` already contains all the information you need about all the settings, however it's a bit cryptic. Thus, I recommend running it through the [SlimIFR](https://github.com/GeographicCone/SlimIFR) script by yours truly to streamline the formatting and make it more human-readable:
269 | ````shell
270 | node SlimIFR.js Section_PE32_Image_Setup_Body.efi.0.0.en-US.ifr.txt Setup.txt
271 | ````
272 | * In the end, you should end up with entries like the one down below, conveniently already exactly in the same format accepted as input by **UVT**:
273 |
274 | ````
275 | AMITSESetup:0x0040 # Boot: Quiet Boot [0x00 / 0x01]
276 | ````
277 |
278 | Or if you skipped the last step
279 |
280 | The untransformed end result would look like this:
281 | ````
282 | CheckBox Prompt: "Quiet Boot", Help: "Enables or disables Quiet Boot option", QuestionFlags: 0x0, QuestionId: 0x106E, VarStoreId: 0xF013, VarOffset: 0x40, Flags: 0x0, Default: Disabled, MfgDefault: Disabled
283 | Default DefaultId: 0x0 Value: 1
284 | Default DefaultId: 0x1 Value: 1
285 | End
286 | ````
287 |
288 |
289 | Either way, with all this information at hand, you're now ready to change any hidden settings. Be careful though, changing some of these may brick (or, more likely, _soft_-brick) your hardware. The usual disclaimers apply: if things go south, you're on your own, so make sure to plan for that contingency.
290 |
291 | ## Building
292 |
293 | Once you have the environment set up (if not, read on), building should be fairly straightforward by running the following command in the source directory:
294 |
295 | ````
296 | CARGO_BUILD_TARGET=x86_64-unknown-uefi cargo build --release
297 | ````
298 |
299 | Remove the `--release` flag if you want a debug build which is also much faster to produce.
300 |
301 | ### Environment
302 |
303 | **UVT** is written in [Rust](https://www.rust-lang.org/). To build it, you will need `rustc` (compiler), `rustup` (toolchain installer), and `cargo` (package manager and build automation tool). Make sure these are all installed and in the `PATH`. On Windows, you can use [MSys2](https://www.msys2.org/).
304 |
305 | You will need to install the appropriate build target first by running: `rustup target add x86_64-unknown-uefi`. And you will also need an Internet connection, since **UVT** has some external dependencies that have to be resolved at build time: the [uefi](https://crates.io/crates/uefi) and [uefi-services](https://crates.io/crates/uefi-services) _crates_, as well as everything they depend on.
306 |
307 | ### Firmware Emulator
308 |
309 | An optional but recommended step is having an emulator set up as well, so that you can immediately run the application as you build it. This is possible with [QEMU](https://www.qemu.org/) and _Open Virtual Machine Firmware_, [OVMF](https://github.com/tianocore/tianocore.github.io/wiki/OVMF), for which the official repository (again) does not offer binary releases: these are helpfully provided by [Gerd Hoffmann](https://www.kraxel.org) and can be downloaded from [his website](https://www.kraxel.org/repos/jenkins/edk2/).
310 |
311 | Once you have downloaded _QEMU_ and extracted the _OVMF_ archives, you can run the emulator as follows:
312 |
313 | ````batch
314 | @echo off
315 | start /min "" "%TOOLS%\MSys64\mingw64\bin\qemu-system-x86_64w.exe" ^
316 | -device isa-debug-exit,iobase=0xf4,iosize=0x04 ^
317 | -device virtio-rng-pci ^
318 | -drive format=raw,file=fat:rw:Filesystem ^
319 | -drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd ^
320 | -drive if=pflash,format=raw,file=OVMF_VARS.fd ^
321 | -m 256M ^
322 | -machine q35,accel=kvm:tcg ^
323 | -net none ^
324 | -nodefaults ^
325 | -smp 4 ^
326 | -vga std
327 | ````
328 |
329 | The above example is a _Windows_ batch file but the _UN*X_ `sh(1)` syntax is similar enough. Adjust the paths as necessary. Before running the emulator, you also need to create the `Filesystem` directory and set it up with the files as described in the _Setup_ section above. Note that the `shellopt` workaround to bypass the five-second wait on boot before executing the `startup.nsh` script does not work in the emulator.
330 |
331 | On Windows, you generally want to use the `qemu-system-x86_64w.exe` binary with the trailing `w` which does not keep the console window open as it is running. Alternatively, it is also possible to make _QEMU_ attach itself to a console window it is being executed from with the `-nographic` switch. While running _QEMU_ in this mode, it is good to know that you can press Ctrl -A , X to terminate it at any time.
332 |
333 | In the standard, windowed mode it's helpful to be aware that Ctrl -Alt -F toggles full-screen mode, and you can press Ctrl -Alt -G anytime to stop the mouse events from being captured by the client: use this if your cursor has suddenly disappeared.
334 |
335 | #### Project Layout
336 |
337 | The source files are organized as follows. In the project root directory:
338 |
339 | * `Cargo.toml` contains the project metadata and build settings being used by `cargo` and the compiler toolchain
340 | * `Cargo.lock` is an automatically-generated file that stores the information about package dependency versions used by the project; if deleted, it will be regenerated but if the information there changes, the project might no longer build, or it might introduce unpredictable errors in **UVT**'s operations due to changes upstream: consider yourself warned
341 | * `target` is the directory where all the objects and information generated during the build process is stored, alongside the resulting executable in `target/x86_64-uknown-uefi/{debug,release}/uvt.efi`: all of this can be safely deleted at any time
342 | * `src` is where all the source files are located, and the directory is discussed separately below
343 |
344 | ##### Source Files
345 |
346 | The source files (all with the `*.rs` extension) are organized as follows:
347 |
348 | * `main.rs` is the main file that provides the entry point and launches all operations
349 |
350 | Most of the logic (code) is located in the following three files:
351 |
352 | * `firmware.rs` performs UEFI operations such as querying and setting UEFI variables
353 | * `parse.rs` processes command-line and stream (standard) input into data structures
354 | * `string.rs` provides string manipulation routines, including an extension to `CStr16` (UEFI-specific equivalent to `str`)
355 |
356 | The following files contain primarily data, with very little code:
357 |
358 | * `config.rs` stores configurable parameters together for easy adjustment
359 | * `config/locale_en.rs` stores translateable user interface messages
360 | * `data.rs` defines data types and structures used throughout the application
361 | * `error.rs` allows for error handling in a single centralized manner
362 |
363 | The application can easily be translated to other languages by making a copy of `locale_en.rs` as `locale_XX.rs`, where `XX` is a two-letter [ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). The entry for `locale_en` can then be replaced with `locale_XX` in `config.rs`.
364 |
365 | ## License
366 |
367 | * Copyright © 2022 [@datasone](https://github.com/datasone)
368 | * Copyright © 2023 [Piotr Szczepański](https://piotr.szczepanski.name/) ([@GeographicCone](https://github.com/GeographicCone))
369 |
370 | **UVT** is _free software_: you can redistribute it and/or modify it under the terms of the [GNU General Public License Version 3](https://www.gnu.org/licenses/gpl-3.0.html#license-text) as published by the [Free Software Foundation](https://www.fsf.org/). The full text of the license is available as `LICENSE.md` in this repository.
371 |
372 | **UVT** is a fork of **[setup_var.efi](https://github.com/datasone/setup_var.efi)** based on the [last-posted version](https://github.com/datasone/setup_var.efi/commit/8c72429113f6fc5e7a4aac63a323d51a2d9f9dd8) at the time of the initial release. The original author of this software is **[@datasone](https://github.com/datasone)** who has generously made his work available under the terms of either the _Apache 2.0_ or _MIT)_ license.
373 |
374 | I am eternally grateful to **[@datasone](https://github.com/datasone)** for all his work, and implementing the ideas I previously suggested to him. I mostly stepped in to fix the issues that prevented me from using the newest version in his absence. Even if the original did not work for me, once the errors were addressed, I believe it contained about 90% of the current functionality of **UVT** at the time of the initial release. This is why it is extremely important for me to give credit where credit is due. **UVT** as it is would have never been possible without all of **@datasone**'s work, which he generously shared with the world.
375 | For that I am eternally grateful to him, and want everyone to know he deserves all the credit as the **original author** of this utility.
376 |
377 | That being said, the source has been completely refactored. As a result, all errors and issues within are mine to own. Please do not bother the original author **@datasone** about any issues you encounter when running **UVT**.
378 |
379 | The [original license terms](https://github.com/datasone/setup_var.efi/commit/a535a524a6f5282f84c01e76c13d3cb2f39c14e9) for the portions authored by **@datasone** are reproduced below:
380 |
381 | > MIT License
382 | >
383 | > Copyright (c) 2022 datasone
384 | >
385 | > 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:
386 | >
387 | > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
388 | >
389 | > 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.
390 |
391 | Note that this only applies to the code from [setup_var.efi](https://github.com/datasone/setup_var.efi) in its original repository. **UVT** in its entirety as a fork of it is solely available under the terms of the _GNU General Public License Version 3_, as indicated in the `LICENSE.md` file in this repository.
392 |
393 | Please also note that the files in the `extra` directory are not covered by the repository-wide license. In particular, the **UVT** logo is made available under the terms of [CC BY-NC-ND 4.0](http://creativecommons.org/licenses/by-nc-nd/4.0/).
394 |
395 | ## Version History
396 |
397 | ### 1.0.0 Initial Public Release
398 |
399 | * Refactor and redistribute the code between the source files
400 | * Gather all configurable options in a single source file
401 | * Provide the option for user interface messages to be localized (translated) to different languages
402 | * Fix the issue where the application would try to parse its own name as the first argument and fail unless the `.efi` extension in lower case was explicitly specified as part of the command line
403 | * Fix the issue where the application would never reach the state where it should read and parse the standard input, regardless if standard input was provided
404 | * Fix the issue where no variable identifier or value size could be successfully entered because the trailing bracket was not stripped from the relevant part of a string, and parsing any of the provided values as a number, whether decimal or hexadecimal, would fail for this reason
405 | * Simplify the input-stream parser by applying prior sanitization at the initial reading
406 | * Change `--write_on_demand` to `--force` (or `-f` in short) and invert the meaning, also change `--reboot` to `--restart`
407 | * Add `--simulate` (or `-s` in short) simulation mode, where no actual write operations are performed
408 | * Change the special characters to use `,` (comma) instead of `:=` (Algol/Pascal-style assignment operator) for _definitions_, `!` instead of `@` for _options_, and `@` instead of `$` for references in _operations_
409 | * Provide a header with application version, as well as firmware vendor, version, and UEFI revision number
410 | * Provide more detailed and meaningful error messages when encountering errors throughout the application, particularly parsing errors
411 | * Dynamically update the application executable image name when displaying usage information
412 | * Add the functionality that references can also be used for read operations, which originally resulted in a (perhaps unintended) parse error
413 | * Allow whitespace in a _definition_ on each side of the `,` comma separator, which provides for better legibility in script formatting
414 | * Update the `uefi` and `uefi_services` external package dependencies to their latest versions
415 | * Will now default to showing usage information on parser errors (but not other errors)
416 |
--------------------------------------------------------------------------------
/extra/8A14_GetMem.uvt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeographicCone/UefiVarTool/3c44cc93c500a7ca4ae3a28c66cfd51097fc3112/extra/8A14_GetMem.uvt
--------------------------------------------------------------------------------
/extra/8A14_Set_CL34.uvt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeographicCone/UefiVarTool/3c44cc93c500a7ca4ae3a28c66cfd51097fc3112/extra/8A14_Set_CL34.uvt
--------------------------------------------------------------------------------
/extra/8A14_Set_CL34_SAGV.uvt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeographicCone/UefiVarTool/3c44cc93c500a7ca4ae3a28c66cfd51097fc3112/extra/8A14_Set_CL34_SAGV.uvt
--------------------------------------------------------------------------------
/extra/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # UEFI Variable Tool (UVT) Extra Resources
4 |
5 | The files in this directory are not licensed under the _GNU General Public License Version 3_.
6 |
7 | Instead, each of the files made available here is distributed subject to its own specific license.
8 |
9 | None of them are required for the application to work, nor does the absence of any of them inhibit the use in any way.
10 |
11 | _For information about **UEFI Variable Tool (UVT)**, see the file `README.md` in the parent directory._
12 |
13 | # 8A14_*.uvt
14 |
15 | **Custom memory timing and other UEFI setting examples with UVT**
16 |
17 | These files demonstrate the workflows for accessing and modifying hidden UEFI settings on an _HP Omen 16-b1xxx_ (8A14) laptop:
18 |
19 | * `8A14_GetMem.uvt` retrieves the values of all memory and memory-related settings
20 | * `8A14_Set_CL34.uvt` sets the performance-optimized settings for use with _System Agent Geyserville_ set to **off**
21 | * `8A14_Set_CL34_SAGV.uvt` sets the performance-optimized settings for use with _System Agent Geyserville_ left **on**
22 |
23 | # uvt-logo.png
24 |
25 | **UEFI Variable Tool (UVT) logo artwork**
26 |
27 | * Copyright © 2023 [Piotr Szczepański](https://piotr.szczepanski.name)
28 | * Licensed under the terms of the [CC BY-NC-ND 4.0](http://creativecommons.org/licenses/by-nc-nd/4.0/)
29 | * Artwork designed in [Inkscape](https://inkscape.org/)
30 | * Typefaces used:
31 | * [Iosevka](https://be5invis.github.io/Iosevka) by [Renzhi Li](https://typeof.net/) (aka Belleve Invis)
32 | * [Charis SIL](https://software.sil.org/charis/) by [SIL International](https://www.sil.org/)
33 | * Both available under the terms of the [SIL Open Font License 1.1](https://scripts.sil.org/OFL)
34 |
--------------------------------------------------------------------------------
/extra/uvt-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GeographicCone/UefiVarTool/3c44cc93c500a7ca4ae3a28c66cfd51097fc3112/extra/uvt-logo.png
--------------------------------------------------------------------------------
/src/config.rs:
--------------------------------------------------------------------------------
1 |
2 | // -|-
3 | // | || /| UEFI Variable Tool (UVT) * Module: Configuration
4 | // | || / | https://github.com/GeographicCone/UefiVarTool
5 | // `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 |
7 | // Stores configurable parameters together for easy adjustment
8 |
9 | // Symbols from other modules
10 | pub(crate) mod locale_en;
11 | pub(crate) use locale_en as locale; // Interface messages in English
12 |
13 | // Application metadata
14 | pub const APP_NAME: Option<&str> = option_env!("CARGO_PKG_NAME");
15 | pub const APP_TITLE: Option<&str> = option_env!("CARGO_PKG_DESCRIPTION");
16 | pub const APP_VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
17 | pub const BUILD_TYPE: Option<&str> = option_env!("BUILD_TYPE");
18 |
19 | // Character definitions
20 | pub const CHAR_ARG_ASS: char = '='; // Argument assignment operator
21 | pub const CHAR_ARG_BKT_L: char = '('; // Opening bracket for optional variable identifier or size
22 | pub const CHAR_ARG_BKT_R: char = ')'; // Closing bracket for optional variable identifier or size
23 | pub const CHAR_ARG_OPT: char = '-'; // Argument option prefix
24 | pub const CHAR_ARG_POS: char = ':'; // Argument offset indicator for variables
25 | pub const CHAR_ARG_SEP: char = ' '; // Argument separator
26 | pub const CHAR_BLANK_SPACE: char = ' '; // Space (SP) whitespace character
27 | pub const CHAR_BLANK_TAB: char = '\t'; // Horizontal tabulation (HT) whitespace character
28 | pub const CHAR_FILE_EXT: char = '.'; // File extension separator
29 | pub const CHAR_FILE_PATH: char = '\\'; // File path separator (single backlash, escaped)
30 | pub const CHAR_INPUT_COMMENT: char = '#'; // Comment prefix, rest of the line is ignored
31 | pub const CHAR_INPUT_DEF: char = ','; // Input definition separator
32 | pub const CHAR_INPUT_OPT: char = '!'; // Input option prefix
33 | pub const CHAR_INPUT_REF: char = '@'; // Input reference prefix
34 | pub const CHAR_CTL_BOM: char = '\u{FEFF}'; // Byte Order Mark (BOM) control character
35 | pub const CHAR_CTL_CR: char = '\r'; // Carriage Return (CR) control character
36 | pub const CHAR_CTL_LF: char = '\n'; // Line Feed (LF) control character
37 |
38 | // Command-line options
39 | pub const OPT_ARG_FORCE: &str = "-f";
40 | pub const OPT_ARG_FORCE_LONG: &str = "--force";
41 | pub const OPT_ARG_RESTART: &str = "-r";
42 | pub const OPT_ARG_RESTART_LONG: &str = "--restart";
43 | pub const OPT_ARG_SIMULATE: &str = "-s";
44 | pub const OPT_ARG_SIMULATE_LONG: &str = "--simulate";
45 | pub const OPT_ARG_USAGE: &str = "-h";
46 | pub const OPT_ARG_USAGE_LONG: &str = "--help";
47 |
48 | // Input options (prefixed with CHAR_INPUT_OPT)
49 | pub const OPT_INPUT_FORCE: &str = "force";
50 | pub const OPT_INPUT_RESTART: &str = "restart";
51 | pub const OPT_INPUT_SIMULATE: &str = "simulate";
52 |
--------------------------------------------------------------------------------
/src/config/locale_en.rs:
--------------------------------------------------------------------------------
1 |
2 | // -|-
3 | // | || /| UEFI Variable Tool (UVT) * Module: Localization (English)
4 | // | || / | https://github.com/GeographicCone/UefiVarTool
5 | // `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 |
7 | // Stores translatable user interface messages
8 |
9 | // To translate, copy this file as locale_XX.rs.
10 | // where XX is a two-letter ISO 639-1 language code,
11 | // and replace the entry for locale in ../config.rs
12 |
13 | // Application metadata defaults
14 | pub const APP_NAME: &str = "UVT";
15 | pub const APP_TITLE: &str = "UEFI Variable Tool";
16 | pub const BUILD_TYPE: &str = "Manual";
17 |
18 | // Error message prefixes and suffixes
19 | pub const ERR_PREFIX_ARG: &str = "Argument error";
20 | pub const ERR_PREFIX_INPUT: &str = "Input error";
21 | pub const ERR_PREFIX_OP_GET: &str = "Get variable error";
22 | pub const ERR_PREFIX_OP_SET: &str = "Set variable error";
23 |
24 | // Error messages
25 | pub const ERR_ARG: &str = "Failed to parse";
26 | pub const ERR_ARG_ASS: [&'static str; 2] = ["Must have at most a single assignment operator", "followed by a value"];
27 | pub const ERR_ARG_MORE: &str = "Premature end of string";
28 | pub const ERR_ARG_NUM_DEC: &str = "Only digits 0-9 should appear in decimal value";
29 | pub const ERR_ARG_NUM_HEX: &str = "Only digits 0-9, a-f or A-F should appear in hexadecimal value";
30 | pub const ERR_ARG_NUM_HEX_PREFIX: &str = "Use prefix \"0x\" or \"0X\" for hexadecimal value";
31 | pub const ERR_ARG_OPT: &str = "Unrecognized option";
32 | pub const ERR_ARG_POS: [&'static str; 2] = ["Must have exactly one offset indicator", "followed by a value"];
33 | pub const ERR_ARG_POS_BKT_L: &str = "Surplus opening bracket in offset identifier";
34 | pub const ERR_ARG_POS_BKT_R: &str = "Missing closing bracket in offset identifier";
35 | pub const ERR_ARG_SIZE_LIMIT: [&'static str; 2] = ["Number", "is too large (64 bits or 8 bytes maximum)"];
36 | pub const ERR_ARG_SIZE_MISMATCH: [&'static str; 3] = ["Value", "too large to fit into", "bytes"];
37 | pub const ERR_ARG_VAR_BKT_L: &str = "Surplus opening bracket in variable identifier";
38 | pub const ERR_ARG_VAR_BKT_R: &str = "Missing closing bracket in variable identifier";
39 | pub const ERR_INPUT: &str = "Parse error in input";
40 | pub const ERR_INPUT_DEF: &str = "Malformed definition";
41 | pub const ERR_INPUT_DEF_SET: [&'static str; 2] = ["Definition for", "must not specify new value to set"];
42 | pub const ERR_INPUT_NONE: &str = "No command-line arguments or standard input: use -h or --help for usage information";
43 | pub const ERR_INPUT_OPT: &str = "Unrecognized input option";
44 | pub const ERR_INPUT_READ: &str = "Failed to read standard input";
45 | pub const ERR_INPUT_REF: &str = "Malformed reference";
46 | pub const ERR_INPUT_REF_NONE: &str = "Failed to resolve reference";
47 | pub const ERR_INT_DEF: &str = "Internal parser error: definition retrieval attempted on wrong entry type";
48 | pub const ERR_INT_OP: &str = "Internal parser error: operation retrieval attempted on wrong entry type";
49 | pub const ERR_INT_SPLIT: &str = "Internal error: failed to split string into parts";
50 | pub const ERR_UEFI_INIT: &str = "Failed to initialize UEFI services";
51 | pub const ERR_UEFI_LOAD: &str = "Failed to initialize UEFI loaded image protocol";
52 | pub const ERR_UEFI_LOAD_OPT: &str = "Failed to obtain UEFI image load options";
53 | pub const ERR_UEFI_PATH_CONV: &str = "Failed to convert device image path";
54 | pub const ERR_UEFI_PATH_FIND: &str = "Failed to locate UEFI device path protocol";
55 | pub const ERR_UEFI_PATH_NONE: &str = "Device image path is empty";
56 | pub const ERR_UEFI_PATH_OPEN: &str = "Failed to initialize UEFI device path protocol";
57 | pub const ERR_UEFI_VAR_CONV: &str = "Internal error: failed to convert UEFI variable name";
58 | pub const ERR_UEFI_VAR_GET: &str = "Failed to get variable";
59 | pub const ERR_UEFI_VAR_GET_MANY: &str = "Use one of the above identifiers";
60 | pub const ERR_UEFI_VAR_GET_MANY_HEAD: &str = "Which one do you mean?";
61 | pub const ERR_UEFI_VAR_GET_MANY_ITEM: &str = " # Size: ";
62 | pub const ERR_UEFI_VAR_GET_NONE: &str = "No such variable";
63 | pub const ERR_UEFI_VAR_LIST: &str = "Error while enumerating UEFI variables";
64 | pub const ERR_UEFI_VAR_SET: &str = "Failed to set variable";
65 | pub const ERR_UEFI_VAR_SIZE: [&'static str; 3] = ["Variable size", "less than offset", "and value size"];
66 | pub const ERR_UEFI_VAR_SIZE_GET: &str = "Failed to get variable size";
67 |
68 | // Operations
69 | pub const OP_SKIPPED: &str = " # Already";
70 |
71 | // Version prompt in application header
72 | pub const VERSION: &str = "Version";
73 | pub const VERSION_UNKNOWN: &str = "Unknown";
74 |
75 | // Usage information
76 | pub const USAGE: [&'static str; 4] = ["Usage: ", "[.efi] [] [ [... []]
77 | - or - ", "[.efi] <
78 | Where:
79 | : Optional global-scope application settings
80 | -f --force Force-write values even if already set as requested
81 | -h --help Show usage information (precludes other operations)
82 | -r --restart Upon successful completion, perform a system restart
83 | -s --simulate Do not write, only simulate actions (will still read)
84 | : Operation(s) to perform, can be multiple, each in the format:
85 | [()]:[()][=]
86 | Arg Overview:
87 | UEFI variable name to read or write to, case-sensitive
88 | If two variables share a name, will prompt to use this
89 | Data starting position within the given UEFI variable
90 | Optional, a byte (1) by default if omitted; little-endian
91 | Value to write, 8 bytes (64 bits) maximum; read if absent
92 | Script to run, same base format as arguments + see below
93 | File Overview:
94 | # Comment, ignored until end of line
95 | ! Set options, same as above arguments
96 | ,:[()] Define a variable to reference later
97 | @[=] Assign to a referenced variable
98 | Example Command Line:
99 | ", " -s Lang:0x00 Lang:0x00(4)=0x01020304 Lang:0x00(4)
100 | Read byte at offset 0, simulate-set the dword (4 bytes), then read again
101 | Example Input File:
102 | !simulate # Simulate only, do not perform actual writes
103 | Language,Lang:0x00(4) # Define a reference under the alias \"Language\"
104 | @Language=0x01020304 # Write to the target referred to by \"Language\"
105 |
106 | , and can be decimal or hexadecimal: use prefix \"0x\"
107 | File should be a UTF-16 LE text, UEFI firmware and shell version-dependent
108 | Output saved to a file can be re-used as input again: format is the same"];
109 |
--------------------------------------------------------------------------------
/src/data.rs:
--------------------------------------------------------------------------------
1 |
2 | // -|-
3 | // | || /| UEFI Variable Tool (UVT) * Module: Data
4 | // | || / | https://github.com/GeographicCone/UefiVarTool
5 | // `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 |
7 | // Defines data types and structures used throughout the application
8 |
9 | // Declare fully-qualified symbols
10 | // to be used in the local scope
11 | use alloc::{borrow::ToOwned, format, string::String, vec::Vec};
12 | use core::fmt::{Display, Formatter, Result as FmtResult};
13 | use uefi::{CString16, table::runtime::{VariableAttributes, VariableVendor}};
14 |
15 | // Symbols from other modules
16 | use crate::config;
17 | use crate::config::locale as msg;
18 | use crate::error::AppError;
19 |
20 | // Operation target
21 | // Structure identifying a value in a UEFI variable
22 |
23 | #[derive(Clone, Debug, Default)]
24 | pub struct OperationTarget {
25 |
26 | pub id: Option, // Optional to tell namesakes
27 | pub name: CString16, // Name of the UEFI variable
28 | pub offset: usize, // Offset within the variable
29 | pub size: usize, // Value data length from offset
30 |
31 | }
32 |
33 | // Operation type
34 | // Whether the value is being retrieved or written
35 |
36 | #[derive(Clone, Copy, Debug, Default)]
37 | pub enum OperationType {
38 |
39 | #[default]
40 | Get, // Query the current value
41 | Set(usize), // Assign a different value
42 |
43 | }
44 |
45 | // Operation argument
46 | // Structure identifying the action to take,
47 | // as well as the target of an operation
48 |
49 | #[derive(Clone, Debug, Default)]
50 | pub struct ArgOperation {
51 | pub action: OperationType, // Get or set
52 | pub target: OperationTarget, // Variable, offset, length
53 | }
54 |
55 | // Implementation
56 | impl ArgOperation {
57 |
58 | // Operation argument validation, with error handling
59 | pub fn validate(&self) -> Result<(), AppError> {
60 |
61 | // For an assignment operation
62 | if let OperationType::Set(value) = self.action {
63 |
64 | // Make sure that the value to be assigned
65 | // fits within the specified size of the value
66 | if value > (1 << (self.target.size * 8)) {
67 |
68 | // Return an error if that's not the case
69 | Err(AppError::ArgSizeMismatch(value, self.target.size))
70 |
71 | // Fits
72 | } else {
73 |
74 | // Pass
75 | Ok(())
76 |
77 | }
78 |
79 | // For a query
80 | } else {
81 |
82 | // Pass
83 | Ok(())
84 |
85 | }
86 |
87 | }
88 |
89 | // Retrieval as a string, together with the current value
90 | pub fn to_string_with_val(&self, value: &UefiValue) -> String {
91 |
92 | // Retrieve the variable name
93 | let name = &self.target.name;
94 |
95 | // Retrieve the identifier, if present
96 | let id_string = match self.target.id {
97 | None => "".to_owned(),
98 | Some(id) => format!("{}{id}{}", config::CHAR_ARG_BKT_L, config::CHAR_ARG_BKT_R),
99 | };
100 |
101 | // Retrieve the offset
102 | let offset = self.target.offset;
103 |
104 | // If value size is more than a single byte
105 | let size_string = if self.target.size == 1 {
106 | "".to_owned()
107 | } else {
108 |
109 | // Retrieve the optional size (if not a byte)
110 | format!("{}{}{}", config::CHAR_ARG_BKT_L, self.target.size, config::CHAR_ARG_BKT_R)
111 |
112 | };
113 |
114 | // Retrieve the current value, using UefiValue's implementation
115 | let value_string = value.to_string_with_size(self.target.size);
116 |
117 | // Format the resulting information and return
118 | format!("{name}{id_string}{}{offset:#06x}{size_string}{}{value_string}",
119 | config::CHAR_ARG_POS, config::CHAR_ARG_ASS)
120 |
121 | }
122 |
123 | }
124 |
125 | // Option argument list
126 | #[derive(Debug)]
127 | pub enum ArgOption {
128 |
129 | Force, // Force-write identical values
130 | Restart, // Restart system when done
131 | Simulate, // Simulate, do not write
132 | Usage, // Show usage information
133 |
134 | }
135 |
136 | // Every argument is either
137 | // an operation, or an option
138 | #[derive(Debug)]
139 | pub enum Arg {
140 |
141 | Operation(ArgOperation), // Get or set a given UEFI value
142 | Option(ArgOption), // Set state in application scope
143 |
144 | }
145 |
146 | // Argument structure
147 | // holds all arguments
148 | #[derive(Debug, Default)]
149 | pub struct Args {
150 |
151 | // Operation arguments
152 | pub op: Vec,
153 |
154 | // Option arguments
155 | // Application-scope state
156 | pub force: bool,
157 | pub restart: bool,
158 | pub simulate: bool,
159 | pub usage: bool,
160 |
161 | }
162 |
163 | // Implementation of
164 | // argument validation
165 | impl Args {
166 |
167 | pub fn validate(&self) -> Result<(), AppError> {
168 |
169 | // If asked to show usage information
170 | if self.usage {
171 |
172 | // Do not validate, since
173 | // nothing else will be done
174 | Ok(())
175 |
176 | } else {
177 |
178 | // Iterate through the operations,
179 | // attempting to validate each of them
180 | self.op.iter().try_for_each(|i| i.validate())
181 |
182 | }
183 |
184 | }
185 |
186 | }
187 |
188 | // Input entry types
189 | #[derive(Debug)]
190 | pub enum InputEntry {
191 |
192 | Operation(ArgOperation), // Get or set a given UEFI value
193 | Option(ArgOption), // Set state in application scope
194 |
195 | // Define a target to be referenced elsewhere in the file
196 | TargetDefinition { name: CString16, target: OperationTarget },
197 |
198 | // Reference of a target defined elsewhere in the file
199 | TargetReference { name: CString16, action: OperationType },
200 | }
201 |
202 | // Implementation
203 | // for input entries
204 | impl InputEntry {
205 |
206 | // Retrieval of the target definition for the operation
207 | pub fn as_def(&self) -> (&CString16, &OperationTarget) {
208 |
209 | match self {
210 | Self::TargetDefinition { name, target } => (name, target),
211 |
212 | // Fail if attempted on an incorrect entry type
213 | _ => panic!("{}", msg::ERR_INT_DEF)
214 | }
215 |
216 | }
217 |
218 | // Retrieval of an argument for the operation
219 | pub fn as_op(&self) -> &ArgOperation {
220 |
221 | match self {
222 | Self::Operation(op) => op,
223 |
224 | // Fail if attempted on an incorrect entry type
225 | _ => panic!("{}", msg::ERR_INT_OP)
226 |
227 | }
228 |
229 | }
230 |
231 | }
232 |
233 | // UEFI Value
234 | // Byte array at a given offset within a UEFI variable
235 |
236 | pub struct UefiValue(pub Vec);
237 |
238 | // Implementation
239 | impl UefiValue {
240 |
241 | // Assignment from a given value and length
242 | pub fn from_usize(value: usize, length: usize) -> Self {
243 | let value = value & ((1 << (length * 8)) - 1);
244 | Self(value.to_le_bytes()[0 .. length].to_vec())
245 | }
246 |
247 | // Retrieval as a string of a given length
248 | pub fn to_string_with_size(&self, length: usize) -> String {
249 | let mut bytes = [0; 8];
250 | bytes[0 .. self.0.len()].copy_from_slice(&self.0);
251 |
252 | format!("{:#0size$x}", usize::from_ne_bytes(bytes), size = 2 + length * 2)
253 |
254 | }
255 | }
256 |
257 | // Implementation: formatting for display
258 | // (used by to_string(), currently never called)
259 | impl Display for UefiValue {
260 |
261 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
262 | let mut bytes = [0; 8];
263 | bytes[0 .. self.0.len()].copy_from_slice(&self.0);
264 |
265 | write!(f, "{:#04x}", usize::from_ne_bytes(bytes))
266 |
267 | }
268 |
269 | }
270 |
271 | // UEFI Variable
272 | // A configuration-data storage unit implemented by UEFI
273 | // Each variable can store numerous configuration settings
274 |
275 | pub struct UefiVariable {
276 | pub attributes: VariableAttributes,
277 | pub content: Vec,
278 | pub name: CString16,
279 | pub vendor: VariableVendor,
280 | }
281 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 |
2 | // -|-
3 | // | || /| UEFI Variable Tool (UVT) * Module: Error
4 | // | || / | https://github.com/GeographicCone/UefiVarTool
5 | // `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 |
7 | // Allows for error handling in a single centralized manner
8 |
9 | // Declare fully-qualified symbols
10 | // to be used in the local scope
11 | use alloc::string::String;
12 | use core::fmt::{Display, Formatter, Result as FmtResult};
13 | use uefi::{data_types::FromSliceWithNulError, proto::loaded_image::LoadOptionsError, Status};
14 |
15 | // Symbols from other modules
16 | use crate::config;
17 | use crate::config::locale as msg;
18 |
19 | // Error list
20 | // Note: some errors handled directly,
21 | // listed for completeness only
22 | #[derive(Debug)]
23 | pub enum AppError {
24 |
25 | // Args
26 | Arg(String),
27 | ArgAss,
28 | ArgMore(String),
29 | ArgNone,
30 | ArgNumDec(String),
31 | ArgNumHex(String),
32 | ArgNumHexPrefix(String),
33 | ArgOpt,
34 | ArgPos,
35 | ArgPosBktL,
36 | ArgPosBktR,
37 | ArgSizeLimit(String),
38 | ArgSizeMismatch(usize, usize),
39 | ArgVarBktL,
40 | ArgVarBktR,
41 |
42 | // Input
43 | Input(String),
44 | InputDef(String),
45 | InputDefSet(String),
46 | InputNone,
47 | InputOpt(String),
48 | //InputRead, // firmware::read_stream()
49 | InputRef(String),
50 | InputRefNone(String),
51 |
52 | // Internal
53 | //IntDef, // data::InputEntry::as_def()
54 | //IntOp, // data::InputEntry::as_op()
55 | //IntSplit, // string::CStr16Ext::split()
56 |
57 | // UEFI
58 | //UefiInit, // main::main()
59 | UefiLoad,
60 | UefiLoadOpt(LoadOptionsError),
61 | UefiPathConv,
62 | UefiPathFind,
63 | UefiPathNone,
64 | UefiPathOpen,
65 | UefiVarConv(FromSliceWithNulError),
66 | UefiVarGet(String, Status),
67 | UefiVarGetMany,
68 | UefiVarGetNone(String),
69 | UefiVarList(Status),
70 | UefiVarSet(String, Status),
71 | UefiVarSize((usize, usize), usize),
72 | UefiVarSizeGet(String, Status),
73 |
74 | }
75 |
76 | // Error display implementation
77 | impl Display for AppError {
78 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
79 | match self {
80 |
81 | // Argument
82 |
83 | // Parse error (argument)
84 | Self::Arg(string) => {
85 | write!(f, "{}: {string}", msg::ERR_ARG)
86 | }
87 |
88 | // Surplus assignment
89 | Self::ArgAss => {
90 | write!(f, "{} ({}) {}", msg::ERR_ARG_ASS[0], config::CHAR_ARG_ASS, msg::ERR_ARG_POS[1])
91 | }
92 |
93 | // More expected
94 | Self::ArgMore(string) => {
95 | write!(f, "{}: {string}", msg::ERR_ARG_MORE)
96 | }
97 |
98 | // Empty argument list (not actual error)
99 | Self::ArgNone => {
100 | write!(f, "")
101 | }
102 |
103 | // Decimal number format
104 | Self::ArgNumDec(string) => {
105 | write!(f, "{} {string}", msg::ERR_ARG_NUM_DEC)
106 | }
107 |
108 | // Hexadecimal number format
109 | Self::ArgNumHex(string) => {
110 | write!(f, "{} {string}", msg::ERR_ARG_NUM_HEX)
111 | }
112 |
113 | // Hexadecimal number prefix
114 | Self::ArgNumHexPrefix(string) => {
115 | write!(f, "{} {string}", msg::ERR_ARG_NUM_HEX_PREFIX)
116 | }
117 |
118 | // Offset left bracket
119 | Self::ArgPosBktL => {
120 | write!(f, "{}", msg::ERR_ARG_POS_BKT_L)
121 | }
122 |
123 | // Offset right bracket
124 | Self::ArgPosBktR => {
125 | write!(f, "{}", msg::ERR_ARG_POS_BKT_R)
126 | }
127 |
128 | // Unrecognized option
129 | Self::ArgOpt => {
130 | write!(f, "{}", msg::ERR_ARG_OPT)
131 | }
132 |
133 | // Incorrect offset specification
134 | Self::ArgPos => {
135 | write!(f, "{} ({}) {}", msg::ERR_ARG_POS[0], config::CHAR_ARG_POS, msg::ERR_ARG_POS[1])
136 | }
137 |
138 | // Variable identifier left bracket
139 | Self::ArgVarBktL => {
140 | write!(f, "{}", msg::ERR_ARG_VAR_BKT_L)
141 | }
142 |
143 | // Variable identifier right bracket
144 | Self::ArgVarBktR => {
145 | write!(f, "{}", msg::ERR_ARG_VAR_BKT_R)
146 | }
147 |
148 | // Number too large
149 | Self::ArgSizeLimit(string) => {
150 | write!(f, "{} {string} {}",
151 | msg::ERR_ARG_SIZE_LIMIT[0], msg::ERR_ARG_SIZE_LIMIT[1])
152 | }
153 |
154 | // New value larger than size specified
155 | Self::ArgSizeMismatch(value, size) => {
156 | write!(f, "{} {value:#0width$x} {} {size} {}",
157 | msg::ERR_ARG_SIZE_MISMATCH[0], msg::ERR_ARG_SIZE_MISMATCH[1],
158 | msg::ERR_ARG_SIZE_MISMATCH[2], width = 2 + size * 2)
159 | }
160 |
161 | // Input
162 |
163 | // Parse error (input)
164 | Self::Input(string) => {
165 | write!(f, "{}: {string}", msg::ERR_INPUT)
166 | }
167 |
168 | // Input definition malformed
169 | Self::InputDef(string) => {
170 | write!(f, "{} \"{string}\"", msg::ERR_INPUT_DEF)
171 | }
172 |
173 | // Definition attempts a set operation
174 | Self::InputDefSet(string) => {
175 | write!(f, "{} \"{string}\" {}",
176 | msg::ERR_INPUT_DEF_SET[0], msg::ERR_INPUT_DEF_SET[1])
177 | }
178 |
179 | // No input or command-line arguments
180 | Self::InputNone => {
181 | write!(f, "{}", msg::ERR_INPUT_NONE)
182 | }
183 |
184 | // Input option unrecognized
185 | Self::InputOpt(string) => {
186 | write!(f, "{} \"{string}\"", msg::ERR_INPUT_OPT)
187 | }
188 |
189 | // Input reference
190 | Self::InputRef(string) => {
191 | write!(f, "{} \"{string}\"", msg::ERR_INPUT_REF)
192 | }
193 |
194 | // Input reference not found
195 | Self::InputRefNone(string) => {
196 | write!(f, "{} \"{string}\"", msg::ERR_INPUT_REF_NONE)
197 | }
198 |
199 | // UEFI
200 |
201 | // Failed to initialize loaded-image protocol
202 | Self::UefiLoad => {
203 | write!(f, "{}", msg::ERR_UEFI_LOAD)
204 | }
205 |
206 | // Failed to obtain loaded image options
207 | Self::UefiLoadOpt(e) => {
208 | write!(f, "{}: {e:?}", msg::ERR_UEFI_LOAD_OPT)
209 | }
210 |
211 | // Failed to convert device image path
212 | Self::UefiPathConv => {
213 | write!(f, "{}", msg::ERR_UEFI_PATH_CONV)
214 | }
215 |
216 | // Failed to locate device-path protocol
217 | Self::UefiPathFind => {
218 | write!(f, "{}", msg::ERR_UEFI_PATH_FIND)
219 | }
220 |
221 | // Device image path empty
222 | Self::UefiPathNone => {
223 | write!(f, "{}", msg::ERR_UEFI_PATH_NONE)
224 | }
225 |
226 | // Failed to initialize device-path protocol
227 | Self::UefiPathOpen => {
228 | write!(f, "{}", msg::ERR_UEFI_PATH_OPEN)
229 | }
230 |
231 | // Failed to convert variable name
232 | Self::UefiVarConv(details) => {
233 | write!(f, "{}: {details:?}", msg::ERR_UEFI_VAR_CONV)
234 | }
235 |
236 | // Failed to get variable
237 | Self::UefiVarGet(name, status) => {
238 | write!(f, "{}: \"{name}\" ({status:?})", msg::ERR_UEFI_VAR_GET)
239 | }
240 |
241 | // Ambiguous variable reference
242 | Self::UefiVarGetMany => {
243 | write!(f, "{}", msg::ERR_UEFI_VAR_GET_MANY)
244 | }
245 |
246 | // No such variable
247 | Self::UefiVarGetNone(name) => {
248 | write!(f, "{}: \"{name}\"", msg::ERR_UEFI_VAR_GET_NONE)
249 | }
250 |
251 | // Failed to list variables
252 | Self::UefiVarList(status) => {
253 | write!(f, "{} ({status:?})", msg::ERR_UEFI_VAR_LIST)
254 | }
255 |
256 | // Failed to set variable
257 | Self::UefiVarSet(name, status) => {
258 | write!(f, "{}: \"{name}\" ({status:?})", msg::ERR_UEFI_VAR_SET)
259 | }
260 |
261 | // Size too small given offset and length
262 | Self::UefiVarSize((offset, length), size) => {
263 | write!(f, "{} {size:#06x} {} {offset:#06x} {} {length}",
264 | msg::ERR_UEFI_VAR_SIZE[0], msg::ERR_UEFI_VAR_SIZE[1], msg::ERR_UEFI_VAR_SIZE[2])
265 | }
266 |
267 | // Failed to get variable size
268 | Self::UefiVarSizeGet(name, status) => {
269 | write!(f, "{}: \"{name}\" ({status:?})", msg::ERR_UEFI_VAR_SIZE_GET)
270 | }
271 |
272 | }
273 |
274 | }
275 |
276 | }
277 |
278 | // Implementation of a UEFI-specific conversion error
279 | impl From for AppError {
280 |
281 | fn from(value: FromSliceWithNulError) -> Self {
282 |
283 | // Use default error handling
284 | Self::UefiVarConv(value)
285 |
286 | }
287 |
288 | }
289 |
--------------------------------------------------------------------------------
/src/firmware.rs:
--------------------------------------------------------------------------------
1 |
2 | // -|-
3 | // | || /| UEFI Variable Tool (UVT) * Module: Firmware
4 | // | || / | https://github.com/GeographicCone/UefiVarTool
5 | // `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 |
7 | // Performs UEFI operations such as querying and setting UEFI variables
8 |
9 | // Declare fully-qualified symbols to be used in the local scope
10 | use alloc::{borrow::ToOwned, string::ToString, vec, vec::Vec};
11 | use core::ptr::null_mut;
12 | use uefi::{Char16, CStr16, CString16, Identify, Status,
13 | proto::{ // Protocols
14 | console::text::{Input, Key},
15 | device_path::text::{AllowShortcuts, DevicePathToText, DisplayOnly},
16 | loaded_image::LoadedImage},
17 | table::{ // Tables
18 | Boot, SystemTable,
19 | boot::{BootServices, SearchType},
20 | runtime::{ResetType, VariableKey}}};
21 | use uefi_services::println;
22 |
23 | // Symbols from other modules
24 | use crate::config;
25 | use crate::config::locale as msg;
26 | use crate::data::{UefiValue, UefiVariable};
27 | use crate::error::AppError;
28 | use crate::string::CStr16Ext;
29 |
30 | // Public System Functions
31 |
32 | // Exits the application and passes the control back to the shell
33 | pub fn exit(system_table: &SystemTable, status: Status) {
34 | unsafe {
35 |
36 | // Call boot services to exit the application
37 | BootServices::exit(system_table.boot_services(),
38 | system_table.boot_services().image_handle(),
39 | status, 0, null_mut());
40 |
41 | }
42 |
43 | }
44 |
45 | // Retrieves the image (application executable) name
46 | // by stripping the path and extension from the image path)
47 | pub fn get_image_name(system_table: &SystemTable) -> CString16 {
48 |
49 | // Retrieve the image path
50 | let image_path = get_image_path(&system_table)
51 | .unwrap_or_else(|_| CString16::try_from(msg::APP_NAME).unwrap());
52 |
53 | // Return a substring starting from the last path separator (\)
54 | // until the first file extension separator (.), or from the beginning
55 | // to the end of the string respectively, if either of these are absent
56 | image_path.substring(
57 | match image_path.find_last(config::CHAR_FILE_PATH) {
58 | Some(0) => 0,
59 | Some(i) => i + 1,
60 | None => 0 },
61 | match image_path.find_last(config::CHAR_FILE_EXT) {
62 | Some(0) => 0,
63 | Some(i) => i - 1,
64 | None => image_path.num_chars() - 1 })
65 |
66 | }
67 |
68 | // Retrieves the image path (path and name of the application executable)
69 | pub fn get_image_path(system_table: &SystemTable) -> Result {
70 |
71 | // Store a reference to UEFI Boot Services,
72 | // called numerous times within this function
73 | let boot_services = system_table.boot_services();
74 |
75 | // Open the loaded-image protocol
76 | let loaded_image = boot_services
77 | .open_protocol_exclusive::(boot_services.image_handle())
78 | .map_err(|_| AppError::UefiLoad)?;
79 |
80 | // Obtain a device-path-to-text protocol handle
81 | let device_path_to_text_handle = *boot_services
82 | .locate_handle_buffer(SearchType::ByProtocol(&DevicePathToText::GUID))
83 | .map_err(|_| AppError::UefiPathFind)?
84 | .first().unwrap();
85 |
86 | // Open the device-path-to-text protocol
87 | let device_path_to_text = boot_services
88 | .open_protocol_exclusive::(device_path_to_text_handle)
89 | .map_err(|_| AppError::UefiPathOpen)?;
90 |
91 | // Retrieve the image device path, error out if empty
92 | let image_device_path = match loaded_image.file_path() {
93 | Some(image_device_path) => image_device_path,
94 | None => return Err(AppError::UefiPathNone)
95 | };
96 |
97 | // Convert the image device path to text
98 | let image_device_path_text =
99 | device_path_to_text.convert_device_path_to_text(
100 | boot_services, image_device_path,
101 | DisplayOnly(true), AllowShortcuts(false))
102 | .map_err(|_| AppError::UefiPathConv)?;
103 |
104 | // Return the converted text as a string
105 | Ok(CString16::from(&*image_device_path_text))
106 |
107 | }
108 |
109 | // Loads the options (command-line arguments)
110 | pub fn load_options(system_table: &SystemTable) -> Result, AppError> {
111 |
112 | // Open the loaded-image protocol
113 | let image = system_table.boot_services()
114 | .open_protocol_exclusive::(system_table.boot_services().image_handle())
115 | .map_err(|_| AppError::UefiLoad)?;
116 |
117 | // Obtain image load options
118 | let options = image.load_options_as_cstr16()
119 | .map_err(AppError::UefiLoadOpt)?;
120 |
121 | // Consider the possibility that the first argument (#0)
122 | // is not the executing image name but already a parameter
123 |
124 | // Note: the use case where this applies is unclear to me (@GeographicCone)
125 | // but since it was implemented by the original author (@datasone), he must
126 | // have had his reasons, so I am keeping this functionality, only making it
127 | // better handle different scenarios
128 |
129 | // Separately, if the image name is present in load options, it might include an arbitrary
130 | // number of quote marks ("), possibly with spaces inside, messing up argument processing
131 | // Example: "\EFI\Tools\uvt.efi" but also \EFI\Tools\"u v t.efi" or \"EFI"\"Tools"\uvt.efi
132 |
133 | // To mitigate both cases, we sanitize the loaded options, only keeping
134 | // those that appear vaguely legitimate, which is determined as follows:
135 |
136 | // All option arguments must begin with a CHAR_ARG_OPT, i.e. minus (-) sign
137 | // All operation arguments must contain a CHAR_ARG_POS, i.e. colon (:) sign
138 | // Non-conforming arguments will be silently discarded
139 |
140 | // Return the options, split into a CString16 vector
141 | Ok(options.split(config::CHAR_ARG_SEP).into_iter().filter(
142 | |s| s.has_first(config::CHAR_ARG_OPT)
143 | || s.has(config::CHAR_ARG_POS)).collect())
144 |
145 | }
146 |
147 | // Reads data from an input stream
148 | // (currently used for standard input)
149 | pub fn read_stream(input: &mut Input) -> CString16 {
150 |
151 | // Define ignored chars
152 | let is_ignored = |c|
153 |
154 | // Byte Order Mark (BOM)
155 | c == Char16::try_from(config::CHAR_CTL_BOM).unwrap()
156 |
157 | // Carriage Return (CR)
158 | || c == Char16::try_from(config::CHAR_CTL_CR).unwrap();
159 |
160 | // Set up the string for returned data
161 | let mut data = CString16::new();
162 |
163 | // Keep reading, while ignoring
164 | // special scancodes or BOM and CR
165 | while let Some(key) = input.read_key().expect(msg::ERR_INPUT_READ) {
166 | if let Key::Printable(c) = key {
167 |
168 | // If not ignored
169 | if !is_ignored(c) {
170 |
171 | // Add to data
172 | data.push(c)
173 |
174 | }
175 |
176 | }
177 |
178 | }
179 |
180 | // Return
181 | data
182 |
183 | }
184 |
185 | // Restarts the system
186 | pub fn restart_system(system_table: &SystemTable) -> () {
187 | system_table.runtime_services()
188 | .reset(ResetType::WARM, Status::SUCCESS, None)
189 | }
190 |
191 | // Public Variable Functions
192 |
193 | // Queries a UEFI variable at a given offset and size,
194 | // returns the value and the operation error status
195 | pub fn get_value(system_table: &SystemTable,
196 | var_name: &CStr16, var_id: Option,
197 | offset: usize, length: usize) -> Result {
198 |
199 | // Attempt to retrieve the specified variable
200 | let var = get_variable(&system_table, var_name, var_id)?;
201 |
202 | // If the specified variable is too short
203 | // to hold data at given offset and length
204 | if offset + length > var.content.len() {
205 |
206 | // Return an error
207 | return Err(
208 | AppError::UefiVarSize(
209 | (offset, length), var.content.len()));
210 |
211 | }
212 |
213 | // Retrieve the given slice of the variable
214 | let slice = &var.content[offset .. offset + length];
215 |
216 | // Return the result
217 | Ok(UefiValue(slice.to_vec()))
218 |
219 | }
220 |
221 | // Modifies a UEFI variable at a given offset and size,
222 | // returns a flag whether changes were made, and error status
223 | pub fn set_value(system_table: &SystemTable,
224 | var_name: &CStr16, var_id: Option,
225 | offset: usize, length: usize, value: &UefiValue,
226 | force: bool, simulate: bool) -> Result {
227 |
228 | // Attempt to retrieve the specified variable
229 | let mut var = get_variable(&system_table, var_name, var_id)?;
230 |
231 | // If the specified variable is too short
232 | // to hold data at given offset and length
233 | if offset + length > var.content.len() {
234 |
235 | // Return an error
236 | return Err(AppError::UefiVarSize(
237 | (offset, length), var.content.len()));
238 |
239 | }
240 |
241 | // Retrieve the given slice of the variable
242 | let slice = &mut var.content[offset .. offset + length];
243 |
244 | // If the value is already as requested
245 | // and we are not being forced to write
246 | if !force && slice == value.0 {
247 |
248 | // Return with
249 | // no changes made
250 | Ok(false)
251 |
252 | // Otherwise
253 | } else {
254 |
255 | // Copy the new value into the slice
256 | slice.copy_from_slice(&value.0);
257 |
258 | // Unless simulating
259 | if !simulate {
260 |
261 | // Attempt to set the variable, handling a possible error
262 | system_table.runtime_services()
263 | .set_variable(&var.name, &var.vendor, var.attributes, &var.content)
264 | .map_err(|e| AppError::UefiVarSet(var.name.to_string(), e.status()))?;
265 |
266 | }
267 |
268 | // Return with
269 | // changes made
270 | Ok(true)
271 |
272 | }
273 |
274 | }
275 |
276 | // Private Functions
277 |
278 | // A wrapper for UEFI Runtime Services'
279 | // variable retrieval function, used internally
280 | fn get_variable(system_table: &SystemTable,
281 | var_name: &CStr16, var_id: Option)
282 | -> Result {
283 |
284 | // Store a reference to UEFI Runtime Services,
285 | // which are called numerous times within this function
286 | let runtime_services = system_table.runtime_services();
287 |
288 | // Retrieve variable name list from UEFI Runtime Services
289 | let keys = runtime_services.variable_keys()
290 | .map_err(|e| AppError::UefiVarList(e.status()))?;
291 |
292 | // Filter the retrieved list for matching variable names
293 | let mut keys = keys
294 | .into_iter()
295 | .filter(|k| {
296 | if let Ok(name) = k.name() {
297 | name == var_name
298 | } else {
299 | false
300 | }
301 | }).collect::>();
302 |
303 | // Sort the filtered list by vendor
304 | keys.sort_by_key(|k| k.vendor.0);
305 |
306 | // If no matches were found, report an error
307 | if keys.is_empty() {
308 | return Err(AppError::UefiVarGetNone(var_name.to_string()));
309 | }
310 |
311 | // If name matched more than once, and no identifier,
312 | // output an identifier list and report an error
313 | if keys.len() > 1 && var_id.is_none() {
314 | get_variable_ambiguous(&system_table, keys)?;
315 | return Err(AppError::UefiVarGetMany);
316 | }
317 |
318 | // Pick the correct variable depending on the arguments
319 | let var_key =
320 | if keys.len() == 1 {
321 | // If only a single variable was found with the name,
322 | // it is obviously the first entry on the list
323 | &keys[0]
324 | } else {
325 | // If multiple variables were found, use the identifier
326 | &keys[var_id.unwrap()]
327 | };
328 |
329 | // Set the variable name from the name associated
330 | // with the key, or return a rare conversion error
331 | let var_name = var_key.name()?;
332 |
333 | // Retrieve the size of the variable, allowing for an error
334 | let size = runtime_services.get_variable_size(var_name, &var_key.vendor)
335 | .map_err(|e| AppError::UefiVarSizeGet(var_name.to_string(), e.status()))?;
336 |
337 | // Allocate a buffer the size of the variable
338 | let mut buffer = vec![0; size];
339 |
340 | // Retrieve the variable into the buffer using UEFI Runtime Services
341 | let (_, var_attr) = runtime_services.get_variable(var_name, &var_key.vendor, &mut buffer)
342 | .map_err(|e| AppError::UefiVarGet(var_name.to_string(), e.status()))?;
343 |
344 | // Return the variable
345 | Ok(UefiVariable {
346 | name: var_name.to_owned(),
347 | vendor: var_key.vendor,
348 | attributes: var_attr,
349 | content: buffer,
350 | })
351 |
352 | }
353 |
354 | // Handles the case where UEFI variable cannot be identified by its name,
355 | // asks the user to reattempt the operation providing a unique identifier
356 | fn get_variable_ambiguous(system_table: &SystemTable, keys: Vec)
357 | -> Result<(), AppError> {
358 |
359 | println!("{}", msg::ERR_UEFI_VAR_GET_MANY_HEAD);
360 |
361 | // Iterate through variables with the matching name
362 | for(i, key) in keys.into_iter().enumerate() {
363 |
364 | // Retrieve metadata for each variable
365 | let id = i;
366 | let name = key.name()?;
367 | let size = system_table.runtime_services()
368 | .get_variable_size(name, &key.vendor)
369 | .map_err(|e| AppError::UefiVarSizeGet(name.to_string(), e.status()))?;
370 |
371 | // Output the resulting information
372 | println!("{}({:#04x}){}{:#06x}", name, id, msg::ERR_UEFI_VAR_GET_MANY_ITEM, size);
373 |
374 | }
375 |
376 | // Return
377 | Ok(())
378 |
379 | }
380 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 |
2 | // -|-
3 | // | || /| UEFI Variable Tool (UVT) * Base Application
4 | // | || / | https://github.com/GeographicCone/UefiVarTool
5 | // `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 |
7 | // Provides the entry point and launches all operations
8 |
9 | #![no_main] // No default main entry point
10 | #![no_std] // No underlying standard library
11 |
12 | // Need to explicitly import alloc crate
13 | // to use allocation in a no_std context
14 | extern crate alloc;
15 |
16 | // Modules
17 | mod config; // Stores configurable parameters together for easy adjustment
18 | mod data; // Defines data types and structures used throughout the application
19 | mod error; // Allows for error handling in a single centralized manner
20 | mod firmware; // Performs UEFI operations such as querying and setting UEFI variables
21 | mod parse; // Processes command-line and stream (standard) input into data structures
22 | mod string; // Provides string manipulation routines, including an extension to CStr16
23 |
24 | // Declare fully-qualified symbols
25 | // to be used in the local scope
26 | use uefi::prelude::*;
27 | use uefi_services::println;
28 |
29 | // Symbols from other modules
30 | use config::locale as msg;
31 | use data::Args;
32 | use error::AppError;
33 | use firmware::{exit, get_image_name, get_value, load_options, read_stream, restart_system, set_value};
34 | use parse::{parse_args, parse_input};
35 |
36 | #[entry] // Main entry point to the application
37 | fn main(_handle: Handle, mut system_table: SystemTable) -> Status {
38 |
39 | // Initialize UEFI services
40 | uefi_services::init(&mut system_table)
41 | .expect(msg::ERR_UEFI_INIT);
42 |
43 | // Print name and version header, including
44 | // firmware vendor and version, and UEFI revision
45 | println!("# {} ({}) {} {}-{} @ {} {}.{:02} UEFI {}",
46 | config::APP_TITLE.unwrap_or_else(|| msg::APP_TITLE), config::APP_NAME.unwrap_or_else(|| msg::APP_NAME),
47 | msg::VERSION, config::APP_VERSION.unwrap_or_else(|| msg::VERSION_UNKNOWN),
48 | config::BUILD_TYPE.unwrap_or_else(|| msg::BUILD_TYPE), system_table.firmware_vendor(),
49 | system_table.firmware_revision() >> 16, system_table.firmware_revision() & 0xFFFFu32,
50 | system_table.uefi_revision());
51 |
52 | // Set the default exit status
53 | let mut status = Status::SUCCESS;
54 |
55 | // Attempt to load and parse command-line arguments,
56 | // determine subsequent actions based on the outcome
57 | let args = match parse_args(load_options(&system_table).unwrap()) {
58 |
59 | // Parse success
60 | // Continue with arguments
61 | Ok(args) => args,
62 |
63 | // Empty argument list
64 | Err(AppError::ArgNone) => {
65 |
66 | // Parse the data from standard input
67 | match parse_input(read_stream(system_table.stdin())) {
68 |
69 | // If success, use
70 | // data as arguments
71 | Ok(args) => args,
72 |
73 | // Nothing to do
74 | Err(AppError::InputNone) => {
75 |
76 | // Show message
77 | println!("{}", AppError::InputNone);
78 | return Status::SUCCESS;
79 |
80 | }
81 |
82 | // Failure
83 | Err(e) => {
84 |
85 | // Show input file parser error
86 | println!("{}: {e}", msg::ERR_PREFIX_INPUT);
87 |
88 | // Exit after showing usage information
89 | status = Status::INVALID_PARAMETER;
90 | Args { usage: true, ..Default::default() }
91 |
92 | }
93 |
94 | }
95 |
96 | }
97 |
98 | // Failure
99 | Err(e) => {
100 |
101 | // Show argument parser error
102 | println!("{}: {e}", msg::ERR_PREFIX_ARG);
103 |
104 | // Exit after showing usage information
105 | status = Status::INVALID_PARAMETER;
106 | Args { usage: true, ..Default::default() }
107 |
108 | }
109 |
110 | };
111 |
112 | // If show usage
113 | if args.usage {
114 |
115 | // Only output usage and exit
116 | show_usage(&system_table, status);
117 |
118 | }
119 |
120 | // Iterate through operations
121 | for op in args.op {
122 |
123 | // Process each operation and retain its status
124 | let status = process_op(&system_table,
125 | &op, args.force, args.simulate);
126 |
127 | // If an operation failed
128 | if status != Status::SUCCESS {
129 |
130 | // Interrupt
131 | return status;
132 |
133 | }
134 |
135 | }
136 |
137 | // If restart requested
138 | if args.restart {
139 |
140 | // Restart the system upon succesful completion
141 | restart_system(&system_table);
142 |
143 | }
144 |
145 | // Return no error
146 | Status::SUCCESS
147 |
148 | }
149 |
150 | // Process an argument operation, returning its status
151 | fn process_op(system_table: &SystemTable,
152 | op: &data::ArgOperation, force: bool, simulate: bool) -> Status {
153 |
154 | // Variable name, offset and size
155 | let name = &op.target.name;
156 | let size = op.target.size;
157 | let offset = op.target.offset;
158 |
159 | // Operation type
160 | match op.action {
161 |
162 | // Get current value
163 | data::OperationType::Get => {
164 |
165 | // Perform retrieval
166 | match get_value(&system_table,
167 | name, op.target.id, offset, size) {
168 |
169 | // Success
170 | Ok(value) =>
171 |
172 | // Output the formatted value
173 | println!("{}", op.to_string_with_val(&value)),
174 |
175 | // Failure
176 | Err(e) => {
177 |
178 | // Show error message and interrupt processing
179 | println!("{}: {e}", msg::ERR_PREFIX_OP_GET);
180 | return Status::ABORTED;
181 |
182 | }
183 |
184 | }
185 |
186 | }
187 |
188 | // Set new value
189 | data::OperationType::Set(value) => {
190 |
191 | // Initialize the new value
192 | let value = data::UefiValue::from_usize(value, size);
193 |
194 | // Perform the assignment
195 | match set_value(&system_table,
196 | name, op.target.id, offset, size, &value, force, simulate) {
197 |
198 | // Success
199 | Ok(written) => {
200 |
201 | // Output the formatted value,
202 | // adding a comment if no writing occurred
203 | println!("{}{}", op.to_string_with_val(&value),
204 | if let false = written {
205 | msg::OP_SKIPPED
206 | } else {
207 | ""
208 | });
209 |
210 | }
211 |
212 | // Failure
213 | Err(e) => {
214 |
215 | // Show error message and interrupt processing
216 | println!("{}: {e}", msg::ERR_PREFIX_OP_SET);
217 | return Status::ABORTED;
218 |
219 | }
220 |
221 | }
222 |
223 | }
224 |
225 | }
226 |
227 | // Return no error
228 | Status::SUCCESS
229 |
230 | }
231 |
232 | // Shows the usage information and exits the application
233 | fn show_usage(system_table: &SystemTable, status: Status) {
234 |
235 | // Obtain the application image name
236 | let image_name = get_image_name(&system_table);
237 |
238 | // Output the usage information, substituting the image name
239 | println!("{}{image_name}{}{image_name}{}{image_name}{}",
240 | msg::USAGE[0], msg::USAGE[1], msg::USAGE[2], msg::USAGE[3]);
241 |
242 | // Make a call to UEFI boot services to exit
243 | exit(&system_table, status);
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/src/parse.rs:
--------------------------------------------------------------------------------
1 |
2 | // -|-
3 | // | || /| UEFI Variable Tool (UVT) * Module: Parse
4 | // | || / | https://github.com/GeographicCone/UefiVarTool
5 | // `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 |
7 | // Processes command-line and stream input into data structures
8 |
9 | // Declare fully-qualified symbols to be used in the local scope
10 | use alloc::{borrow::{Cow, ToOwned}, format, string::ToString, vec::Vec};
11 | use uefi::{CStr16, CString16, data_types::EqStrUntilNul};
12 |
13 | // Symbols from other modules
14 | use crate::config;
15 | use crate::data::{
16 | Arg, Args, ArgOperation, ArgOption,
17 | InputEntry, OperationTarget, OperationType};
18 | use crate::error::AppError;
19 | use crate::parse_multiple;
20 | use crate::string::{CStr16Ext, try_next_char};
21 |
22 | // Command-Line Arguments
23 |
24 | // Parses the command-line arguments received from load options
25 | pub fn parse_args(args: Vec) -> Result {
26 |
27 | // Skip if nothing to parse
28 | if args.len() == 0 {
29 | return Err(AppError::ArgNone);
30 | }
31 |
32 | // Iterate through arguments, trying to parse each
33 | // with either the operation or the option parser
34 | let args = args.into_iter().map(|s| {
35 | parse_multiple!(&s, parse_arg_option, parse_arg_operation)
36 |
37 | // Interrupt on error
38 | .map_err(|e| AppError::Arg(format!("\"{s}\" - {e}")))
39 |
40 | // Collect into a common Arg vector
41 | }).collect::, _>>()?;
42 |
43 | // Collect operations
44 | let operations = args.iter().filter(|arg| matches!(arg, Arg::Operation(_)))
45 | .map(|arg| {
46 | if let Arg::Operation(arg) = arg { arg.clone() } else { unreachable!() }
47 | }).collect::>();
48 |
49 | // Collect options
50 | let options = args.iter().filter(|arg| matches!(arg, Arg::Option(_)))
51 | .map(|arg| {
52 | if let Arg::Option(arg) = arg { arg } else { unreachable!() }
53 | }).collect::>();
54 |
55 | // Initialize the argument structure
56 | let mut args = Args::default();
57 |
58 | // Assign the options
59 | for option in options {
60 | match option {
61 |
62 | // Force-write even if already set
63 | ArgOption::Force => args.force = true,
64 |
65 | // Restart system on completion
66 | ArgOption::Restart => args.restart = true,
67 |
68 | // Simulate, do not write
69 | ArgOption::Simulate => args.simulate = true,
70 |
71 | // Show usage information
72 | ArgOption::Usage => args.usage = true,
73 |
74 | }
75 |
76 | }
77 |
78 | // Assign the operations
79 | args.op = operations;
80 |
81 | // Validate arguments
82 | // Also handles errors
83 | args.validate()?;
84 |
85 | // Ready
86 | Ok(args)
87 |
88 | }
89 |
90 | // Attempts to parse a command-line operation as a general argument
91 | fn parse_arg_operation(arg: &CStr16) -> Result {
92 |
93 | // This function is just a wrapper that returns
94 | // a general instead of an operation argument
95 | Ok(Arg::Operation(parse_operation(arg)?))
96 |
97 | }
98 |
99 | // Attempts to parse a command-line option as a general argument
100 | fn parse_arg_option(key: &CStr16) -> Result {
101 |
102 | // Force-write even if already set
103 | if key.eq_str_until_nul(config::OPT_ARG_FORCE)
104 | || key.eq_str_until_nul(config::OPT_ARG_FORCE_LONG) {
105 |
106 | Ok(Arg::Option(ArgOption::Force))
107 |
108 | // Restart system on completion
109 | } else if key.eq_str_until_nul(config::OPT_ARG_RESTART)
110 | || key.eq_str_until_nul(config::OPT_ARG_RESTART_LONG) {
111 |
112 | Ok(Arg::Option(ArgOption::Restart))
113 |
114 | // Simulate, do not write
115 | } else if key.eq_str_until_nul(config::OPT_ARG_SIMULATE)
116 | || key.eq_str_until_nul(config::OPT_ARG_SIMULATE_LONG) {
117 |
118 | Ok(Arg::Option(ArgOption::Simulate))
119 |
120 | // Show usage information
121 | } else if key.eq_str_until_nul(config::OPT_ARG_USAGE)
122 | || key.eq_str_until_nul(config::OPT_ARG_USAGE_LONG) {
123 |
124 | Ok(Arg::Option(ArgOption::Usage))
125 |
126 | // Unknown
127 | } else {
128 |
129 | // Return an error
130 | Err(AppError::ArgOpt)
131 |
132 | // Note: currently obstructed
133 | // due to parser limitations
134 |
135 | }
136 |
137 | }
138 |
139 | // Input Stream (Standard Input)
140 |
141 | // Parses an input script read from a stream such as standard input
142 | pub fn parse_input(input: CString16) -> Result {
143 |
144 | // Error if nothing to parse
145 | if input.is_empty() {
146 | return Err(AppError::InputNone);
147 | }
148 |
149 | // Split input into lines
150 | let lines = input.split(config::CHAR_CTL_LF);
151 |
152 | // Remove comments and whitespace from each line
153 | let lines = lines.into_iter().filter_map(|s| {
154 |
155 | // Set up a helper variable
156 | let mut line_filtered = s.clone();
157 |
158 | // If the line has a comment
159 | if s.has(config::CHAR_INPUT_COMMENT) {
160 |
161 | // Find out where the comment begins
162 | let comment_start = s.find_first(config::CHAR_INPUT_COMMENT).unwrap();
163 |
164 | // If at first character,
165 | // ignore the entire line
166 | if comment_start == 0 {
167 | return None;
168 | }
169 |
170 | // Filter out the comment portion
171 | line_filtered = s.substring(0, comment_start - 1);
172 |
173 | }
174 |
175 | // Trim leading and trailing whitespace
176 | line_filtered = line_filtered.trim();
177 |
178 | // Skip line if completely empty
179 | if line_filtered.is_empty() {
180 | return None;
181 | }
182 |
183 | // Return the result
184 | Some(line_filtered)
185 |
186 | });
187 |
188 | // Parse the input into the entries
189 | let entries = lines.map(|s| {
190 | parse_multiple!(&s,
191 | parse_input_option, parse_target_def,
192 | parse_input_operation, parse_target_ref) })
193 | .collect::, _>>()?;
194 |
195 | // Collect parsed target definitions
196 | let target_defs = entries.iter().filter(
197 | |e| matches!(e, InputEntry::TargetDefinition { .. }))
198 | .map(|e| e.as_def()).collect::>();
199 |
200 | // Collect parsed input operations
201 | let mut operations = entries.iter().filter(
202 | |e| matches!(e, InputEntry::Operation(_))).map(
203 | |e| e.as_op()).cloned().collect::>();
204 |
205 | // Collect parsed target references
206 | let target_refs = entries.iter().filter(
207 | |e| matches!(e, InputEntry::TargetReference { .. }));
208 |
209 | // Collect parsed input operations with references
210 | let mut operations_ref = target_refs.map(|e| {
211 | match e {
212 | InputEntry::TargetReference { name, action } => {
213 |
214 | // Find the target definition for the reference
215 | let (_, target) = target_defs.iter().find(
216 | |(def_name, _)| name == *def_name).ok_or_else(
217 | || AppError::InputRefNone(name.to_string()))?;
218 | Ok(ArgOperation {
219 | target: (*target).clone(), action: *action })
220 | }
221 | _ => unreachable!() }})
222 | .collect::, AppError>>()?;
223 |
224 | // Include the operations with references
225 | // together with those defined directly
226 | operations.append(&mut operations_ref);
227 |
228 | // Set option arguments from input
229 | let force = entries.iter().any( // Force write
230 | |e| matches!(e, InputEntry::Option(ArgOption::Force)));
231 |
232 | let restart = entries.iter().any( // Restart when done
233 | |e| matches!(e, InputEntry::Option(ArgOption::Restart)));
234 |
235 | let simulate = entries.iter().any( // Simulate, do not write
236 | |e| matches!(e, InputEntry::Option(ArgOption::Simulate)));
237 |
238 | // Return the complete argument structure
239 | Ok(Args { op: operations, force, restart, simulate, usage: false })
240 |
241 | }
242 |
243 | // Attempts to parse a command-line operation as an input entry
244 | fn parse_input_operation(arg: &CStr16) -> Result {
245 |
246 | // This function is just a wrapper that returns
247 | // an input entry instead of an operation argument
248 | Ok(InputEntry::Operation(crate::parse::parse_operation(arg)?))
249 |
250 | }
251 |
252 | // Attempts to parse a command-line option as an input entry
253 | fn parse_input_option(arg: &CStr16) -> Result {
254 |
255 | // Remove the option prefix
256 | let named_arg = arg.strip_first(config::CHAR_INPUT_OPT)
257 | .ok_or_else(|| AppError::InputOpt(arg.to_string()))?;
258 |
259 | // Parse the option
260 | if named_arg.eq_str_until_nul(config::OPT_INPUT_FORCE) {
261 |
262 | // Force-write even if already set
263 | Ok(InputEntry::Option(ArgOption::Force))
264 |
265 | } else if named_arg.eq_str_until_nul(config::OPT_INPUT_RESTART) {
266 |
267 | // Restart system on completion
268 | Ok(InputEntry::Option(ArgOption::Restart))
269 |
270 | } else if named_arg.eq_str_until_nul(config::OPT_INPUT_SIMULATE) {
271 |
272 | // Simulate, do not write
273 | Ok(InputEntry::Option(ArgOption::Simulate))
274 |
275 | } else {
276 |
277 | // Unrecognized option error
278 | Err(AppError::InputOpt(arg.to_string()))
279 |
280 | }
281 |
282 | }
283 |
284 | // Common (Command-Line & Input Stream)
285 |
286 | // Attempts to parse a command-line argument as an operation argument
287 | fn parse_operation(arg: &CStr16) -> Result {
288 |
289 | // Split the argument at the offset specification
290 | let mut arg_split = arg.split(config::CHAR_ARG_POS);
291 |
292 | // Every argument must have
293 | // exactly one offset indicator
294 | if arg_split.len() != 2 {
295 | Err(AppError::ArgPos)?
296 | }
297 |
298 | // Determine the variable name
299 | // Note: swap_remove() is O(1), remove is O(n)
300 | let mut name = arg_split.swap_remove(0);
301 |
302 | // Determine the variable identifier
303 | // Empty by default, can be defined in brackets
304 | let mut id = None;
305 | if name.has(config::CHAR_ARG_BKT_L) {
306 |
307 | // Split the variable name at the opening bracket
308 | let mut arg_split = name.split(config::CHAR_ARG_BKT_L);
309 |
310 | // Part left of offset may have
311 | // at most a single bracket
312 | if arg_split.len() != 2 {
313 | Err(AppError::ArgVarBktL)?
314 | }
315 |
316 | // Remove the matching closing bracket
317 | // Error out if no closing bracket present
318 | let id_string = arg_split[1]
319 | .strip_last(config::CHAR_ARG_BKT_R)
320 | .ok_or(AppError::ArgVarBktR)?;
321 |
322 | // Parse the variable identifier as
323 | // either a decimal or a hexadecimal number
324 | id = Some(parse_multiple!(&id_string, parse_value_dec, parse_value_hex)?);
325 |
326 | // Update the variable name
327 | // to remove the part in brackets
328 | name = arg_split.swap_remove(0);
329 |
330 | }
331 |
332 | // Determine the offset (position within variable)
333 | let offset = arg_split.swap_remove(0);
334 |
335 | // Determine the operation type and the new data
336 | // to be assigned if operation type is to set the value
337 | let (mut offset, op_type) = parse_operation_type(&offset)?;
338 |
339 | // Determine the value size
340 | // Defaults to a byte (1), can be defined in brackets
341 | let mut size = 1;
342 | if offset.has(config::CHAR_ARG_BKT_L) {
343 |
344 | // Split the offset at the opening bracket
345 | let mut arg_split = offset.split(config::CHAR_ARG_BKT_L);
346 |
347 | // Part right of offset may have
348 | // at most a single bracket
349 | if arg_split.len() != 2 {
350 | Err(AppError::ArgPosBktL)?
351 | }
352 |
353 | // Remove the matching closing bracket
354 | // Error out if no closing bracket present
355 | let size_string = arg_split[1]
356 | .strip_last(config::CHAR_ARG_BKT_R)
357 | .ok_or(AppError::ArgPosBktR)?;
358 |
359 | // Parse the size as either a decimal or a hexadecimal number
360 | size = parse_multiple!(&size_string, parse_value_dec, parse_value_hex)?;
361 |
362 | // Update the offset to remove the part in brackets
363 | offset = Cow::Owned(arg_split.swap_remove(0));
364 |
365 | }
366 |
367 | // Parse the offset value as either a decimal or a hexadecimal number
368 | let offset = parse_multiple!(&offset, parse_value_hex, parse_value_dec)?;
369 |
370 | // Return the populated data structure
371 | Ok(ArgOperation { action: op_type,
372 | target: OperationTarget { id, name, offset, size }})
373 |
374 | }
375 |
376 | // Attempts to parse operation type,
377 | // and optionally the new data to be set
378 | fn parse_operation_type(arg: &CStr16)
379 | -> Result<(Cow, OperationType), AppError> {
380 |
381 | // If operation is an assignment
382 | if arg.has(config::CHAR_ARG_ASS) {
383 |
384 | // Split the string at the opening bracket
385 | let mut arg_split = arg.split(config::CHAR_ARG_ASS);
386 |
387 | // There can only be
388 | // a single assignment operator
389 | if arg_split.len() != 2 {
390 | Err(AppError::ArgAss)?
391 | }
392 |
393 | // Parse the value to be assigned as either a decimal or a hexadecimal number
394 | let value = parse_multiple!(&arg_split[1], parse_value_hex, parse_value_dec)?;
395 |
396 | // Set the operation type to assignment (set) and the new
397 | // value to be assigned, update the offset to only the part
398 | // on the left-hand size of the assignment operator
399 | Ok((Cow::Owned(arg_split.swap_remove(0)), OperationType::Set(value)))
400 |
401 | } else {
402 |
403 | // Set the operation type to retrieval (get)
404 | // Return the offset argument as received
405 | Ok((Cow::Borrowed(arg), OperationType::Get))
406 |
407 | }
408 |
409 | }
410 |
411 | // Attempts to parse a decimal value
412 | fn parse_value_dec(value: &CStr16) -> Result {
413 |
414 | // Split the input string into a character vector
415 | let chars = value.iter().map(|&c| char::from(c)).collect::>();
416 |
417 | // Only ASCII digits are allowed in a decimal value
418 | if chars.iter().any(|c| !c.is_ascii_digit()) {
419 | Err(AppError::ArgNumDec(format!("\"{}\"", value.to_string())))?
420 | }
421 |
422 | // Iterate through the characters:
423 | // Take the ASCII value of each (48-57), and deduct the value of 0 (48)
424 | // Multiply the accumulator by 10 at each step and add to the total
425 | let value = chars.into_iter()
426 | .fold(0usize, |acc, n| acc * 10 + (n as u8 - b'0') as usize);
427 |
428 | // Return
429 | Ok(value)
430 |
431 | }
432 |
433 | // Attempts to parse a hexadecimal value
434 | fn parse_value_hex(value: &CStr16) -> Result {
435 |
436 | // Define an iterator over the input string
437 | let mut str_iter = value.iter().map(|&c| char::from(c));
438 |
439 | // Retrieve the initial two characters of the string
440 | let c0 = try_next_char(&mut str_iter, value)?;
441 | let c1 = try_next_char(&mut str_iter, value)?;
442 |
443 | // Check the prefix
444 | match (c0, c1) {
445 |
446 | // The hexadecimal
447 | // prefix is present
448 | ('0', 'x') => {}
449 | ('0', 'X') => {}
450 |
451 | // Report an error in hexadecimal string formatting
452 | _ => return Err(AppError::ArgNumHexPrefix(format!("\"{}\"", value.to_string()))),
453 |
454 | }
455 |
456 | // Check if within the size constraint
457 | if value.num_bytes() > 2 * (18 + 1) {
458 | return Err(AppError::ArgSizeLimit(value.to_string()));
459 | }
460 |
461 | // Convert each byte (char) to its hexadecimal value
462 | let value = str_iter
463 | .map(|c| c.to_digit(16).map(|n| n as u8))
464 | .collect::>>()
465 | .ok_or_else(|| AppError::ArgNumHex(format!("\"{}\"", value.to_string())))?;
466 |
467 | // Sum all the values, multiplied according their respective positions
468 | let length = value.len();
469 | let value = value.iter().enumerate().fold(0usize, |acc, (i, &n)| {
470 | acc + ((n as usize) << (4 * (length - i - 1)))
471 | });
472 |
473 | // Return
474 | Ok(value)
475 |
476 | }
477 |
478 | // Definitions & References
479 | // (Input Stream Only)
480 |
481 | // Attempts to parse a target definition
482 | pub fn parse_target_def(arg: &CStr16) -> Result {
483 |
484 | // Split into two at input definition sepator
485 | let (name, target) = arg.split_once(config::CHAR_INPUT_DEF)
486 | .ok_or_else(|| AppError::InputDef(arg.to_string()))?;
487 |
488 | // Attempt to parse the target as an operation
489 | let operation = parse_operation(&target)?;
490 |
491 | // Check operation type
492 | if let OperationType::Get = operation.action {
493 |
494 | // Add the target definition entry
495 | Ok(InputEntry::TargetDefinition {
496 | name, target: operation.target })
497 |
498 | } else {
499 |
500 | // Value setting not allowed in a definition
501 | Err(AppError::InputDefSet(target.to_string()))
502 |
503 | }
504 |
505 | }
506 |
507 | // Attempts to parse a target reference
508 | fn parse_target_ref(arg: &CStr16)
509 | -> Result {
510 |
511 | // Check if prefix is the first character in reference
512 | let arg = if arg.has_first(config::CHAR_INPUT_REF) {
513 |
514 | // Remove the prefix from reference
515 | arg.strip_first(config::CHAR_INPUT_REF)
516 | .ok_or_else(|| AppError::Input(arg.to_string()))?
517 |
518 | } else {
519 |
520 | // Report an error: prefix is mandatory
521 | Err(AppError::InputRef(arg.to_string()))?
522 |
523 | };
524 |
525 | // Retrieval is the default,
526 | // name is the whole argument
527 | let mut action = OperationType::Get;
528 | let mut name = CString16::from(arg);
529 |
530 | // Override the settings for an assignment operation
531 | // if assignment operator appears in the argument
532 | if arg.has(config::CHAR_ARG_ASS) {
533 |
534 | // Split into two at argument assignment operator
535 | // keep the operator in the value as it is required
536 | let ass = arg.find_first(config::CHAR_ARG_ASS).unwrap();
537 | name = arg.substring(0, ass - 1);
538 | let value = arg.substring(ass, arg.num_chars() - 1);
539 |
540 | // Retrieve the operation type (and the new value if set)
541 | action = parse_target_ref_operation_type(&value)?;
542 |
543 | }
544 |
545 | // Return the reference
546 | Ok(InputEntry::TargetReference { action, name: name.to_owned() })
547 |
548 | }
549 |
550 | // Attempts to parse operation type,
551 | // and optionally the new data to be set
552 | fn parse_target_ref_operation_type(arg: &CStr16) -> Result {
553 |
554 | // This function is just a wrapper that returns
555 | // only the operation type and discards the offset
556 | Ok(parse_operation_type(arg)?.1)
557 |
558 | }
559 |
--------------------------------------------------------------------------------
/src/string.rs:
--------------------------------------------------------------------------------
1 |
2 | // -|-
3 | // | || /| UEFI Variable Tool (UVT) * Module: String
4 | // | || / | https://github.com/GeographicCone/UefiVarTool
5 | // `---'`-' `- Copyright © 2022 Datasone, © 2023 Piotr Szczepański
6 |
7 | // Provides string manipulation routines, including an extension to CStr16
8 |
9 | // Declare fully-qualified symbols
10 | // to be used in the local scope
11 | use alloc::{string::ToString, vec::Vec};
12 | use uefi::{Char16, CStr16, CString16, data_types::chars::NUL_16};
13 |
14 | // Symbols from other modules
15 | use crate::config;
16 | use crate::config::locale as msg;
17 | use crate::error::AppError;
18 |
19 | // Converts a Char16 vector to CString16
20 | pub fn char16_vec_to_cstring16(string: Vec) -> CString16 {
21 |
22 | // A copy and char validity check is required
23 | CString16::try_from(
24 | string.into_iter().map(u16::from).collect::>()).unwrap()
25 |
26 | // Note: CString16 does not use repr(transparent), which means it cannot
27 | // be constructed directly, even with unsafe functions, hence the workaround
28 |
29 | }
30 |
31 | // Tries to retrieve the next char of a string
32 | pub fn try_next_char(
33 | iter: &mut impl Iterator- ,
34 | string: &CStr16) -> Result
{
35 |
36 | // Error if no next char
37 | iter.next().ok_or_else(|| AppError::ArgMore(string.to_string()))
38 |
39 | }
40 |
41 | // A recursive variadic macro definition
42 | // to apply an arbitrary number of parsers
43 | #[macro_export]
44 | macro_rules! parse_multiple {
45 |
46 | // Base case with just a single parser
47 | ($input:expr, $parser:expr) => {{
48 |
49 | // Call the parser
50 | // to process the input
51 | $parser($input)
52 |
53 | }};
54 |
55 | // Variadic case with an arbitrary number of parsers
56 | ($input:expr, $parser:expr, $($parsers:expr),* $(,)? ) => {{
57 |
58 | // Invoke the next parser and check status
59 | if let Ok(val) = $parser($input) {
60 |
61 | // Match
62 | Ok(val)
63 |
64 | } else {
65 |
66 | // Continue with the remaining parsers
67 | parse_multiple!($input, $($parsers),*)
68 |
69 | }
70 |
71 | }};
72 |
73 | }
74 |
75 | // Trait (interface) for an extension to CStr16
76 | // which is the UEFI-specific equivalent to str
77 | pub trait CStr16Ext {
78 |
79 | // Finds the first location of the given char
80 | fn find_first(&self, search: char) -> Option;
81 |
82 | // Finds the last location of the given char
83 | fn find_last(&self, search: char) -> Option;
84 |
85 | // Checks for the presence of the given char
86 | fn has(&self, search: char) -> bool;
87 |
88 | // Checks if the string starts with the given char
89 | fn has_first(&self, search: char) -> bool;
90 |
91 | // Splits the string into parts separated by the given char
92 | fn split(&self, search: char) -> Vec;
93 |
94 | // Splits the string into two parts separated by the given char
95 | fn split_once(&self, search: char) -> Option<(CString16, CString16)>;
96 |
97 | // Removes the specified leading char from the string
98 | fn strip_first(&self, search: char) -> Option<&CStr16>;
99 |
100 | // Removes the specified trailing char from the string
101 | fn strip_last(&self, search: char) -> Option;
102 |
103 | // Returns a substring given start and end indices
104 | fn substring(&self, start: usize, end: usize) -> CString16;
105 |
106 | // Removes whitespace on both ends
107 | fn trim(&self) -> CString16;
108 |
109 | }
110 |
111 | // Implementation on top of CStr16
112 | impl CStr16Ext for CStr16 {
113 |
114 | // Finds the first location of the given char
115 | fn find_first(&self, search: char) -> Option {
116 |
117 | // Convert the search character to match data type
118 | let search = Char16::try_from(search).unwrap();
119 |
120 | // Find the location of the given char, error if none
121 | let index = self.as_slice().iter().position(|&c| c == search)?;
122 |
123 | // Return
124 | Some(index)
125 |
126 | }
127 |
128 | // Finds the last location of the given char
129 | fn find_last(&self, search: char) -> Option {
130 |
131 | // Convert the search character to match data type
132 | let search = Char16::try_from(search).unwrap();
133 |
134 | // Find the location of the given char, error if none
135 | let index = self.as_slice().iter().rposition(|&c| c == search)?;
136 |
137 | // Return
138 | Some(index)
139 |
140 | }
141 |
142 | // Checks for the presence of the given char
143 | fn has(&self, search: char) -> bool {
144 |
145 | // Convert the search character to match data type
146 | let search = Char16::try_from(search).unwrap();
147 |
148 | // Iterate through the chars
149 | // return true if any matches
150 | self.iter().any(|&c| c == search)
151 |
152 | }
153 |
154 | // Checks if the string starts with the given char
155 | fn has_first(&self, search: char) -> bool {
156 |
157 | // Also returns false for empty strings
158 | *self.iter().next().unwrap_or(&NUL_16) == search.try_into().unwrap()
159 |
160 | }
161 |
162 | // Splits the string into parts separated by the given char
163 | fn split(&self, search: char) -> Vec {
164 |
165 | // Convert the search character to match data type
166 | let search = Char16::try_from(search).unwrap();
167 |
168 | // Set up output and helper variables
169 | let mut split_strings = Vec::new();
170 | let mut current_string = Vec::new();
171 |
172 | // Iterate through input string
173 | for c in self.iter() {
174 |
175 | // If not a split char
176 | if *c != search {
177 |
178 | // Append it to the current string
179 | current_string.push(u16::from(*c))
180 |
181 | // Split
182 | } else {
183 |
184 | // Terminate the current string
185 | current_string.push(0u16);
186 |
187 | // Append the current string to the array
188 | split_strings.push(current_string.try_into()
189 | .expect(msg::ERR_INT_SPLIT));
190 |
191 | // Reset the current string
192 | current_string = Vec::new()
193 |
194 | }
195 |
196 | }
197 |
198 | // Deal with the leftover portion
199 | if !current_string.is_empty() {
200 |
201 | // Terminate the leftover
202 | current_string.push(0u16);
203 |
204 | // Append the leftover string to the array
205 | split_strings.push(current_string.try_into()
206 | .expect(msg::ERR_INT_SPLIT));
207 |
208 | }
209 |
210 | // Return the result
211 | split_strings
212 |
213 | }
214 |
215 | // Splits the string into two parts at the first
216 | // occurrence of the given character, trims both parts
217 | fn split_once(&self, search: char) -> Option<(CString16, CString16)> {
218 |
219 | // Convert the search character to match data type
220 | let search = Char16::try_from(search).unwrap();
221 |
222 | // Find the location of the given char, error if none
223 | let index = self.iter().position(|&c| c == search)?;
224 |
225 | // Save a slice on each side of the split as a vector
226 | let mut former = self.as_slice()[.. index].to_vec();
227 | let mut latter = self.as_slice()[index + 1 ..].to_vec();
228 |
229 | // Terminate both
230 | former.push(NUL_16);
231 | latter.push(NUL_16);
232 |
233 | // Convert each slice to a CString16
234 | let former = char16_vec_to_cstring16(former);
235 | let latter = char16_vec_to_cstring16(latter);
236 |
237 | // Return the result, trimmed
238 | Some((former.trim(), latter.trim()))
239 |
240 | }
241 |
242 | // Removes the specified leading char from the string
243 | fn strip_first(&self, search: char) -> Option<&CStr16> {
244 |
245 | // Check if the char is indeed at the beginning
246 | if *self.iter().next()? == Char16::try_from(search).unwrap() {
247 |
248 | // Create a reference
249 | let reference = unsafe {
250 |
251 | // Point to a substring bypassing the first char
252 | CStr16::from_u16_with_nul_unchecked(
253 | &*(&self.as_slice_with_nul()[1 .. ] as *const [Char16] as *const [u16]))
254 |
255 | };
256 |
257 | // Return the reference
258 | Some(reference)
259 |
260 | } else {
261 |
262 | // Return nothing
263 | None
264 |
265 | }
266 |
267 | }
268 |
269 | // Removes the specified trailing char from the string
270 | fn strip_last(&self, search: char) -> Option {
271 |
272 | // Set up a helper variable
273 | let str = self.to_u16_slice();
274 |
275 | // Check if the char is indeed the suffix
276 | if *str.last()? == u16::from(Char16::try_from(search).unwrap()) {
277 |
278 | // Create a mutable buffer
279 | let mut buffer = self
280 | .as_slice() // Without the terminating NUL_16
281 | .iter()
282 | .map(|&c| u16::from(c))
283 | .collect::>();
284 |
285 | // Change the last element to zero
286 | *buffer.last_mut()? = 0;
287 |
288 | // Return the substring
289 | Some(CString16::try_from(buffer).unwrap())
290 |
291 | } else {
292 |
293 | // Return nothing
294 | None
295 |
296 | }
297 |
298 | }
299 |
300 | // Returns a substring given start and end indices
301 | fn substring(&self, start: usize, end: usize) -> CString16 {
302 |
303 | // Take a slice without the whitespace characters
304 | let mut result = self.as_slice()[start ..= end].to_vec();
305 |
306 | // Terminate the slice
307 | result.push(NUL_16);
308 |
309 | // Convert character vector to string
310 | char16_vec_to_cstring16(result)
311 |
312 | }
313 |
314 | // Removes whitespace on both ends
315 | fn trim(&self) -> CString16 {
316 |
317 | // Do not trim empty strings
318 | if self.is_empty() {
319 | return CString16::new();
320 | }
321 |
322 | // Define a way for determining whitespace chars
323 | let is_whitespace = |&c|
324 | c == Char16::try_from(config::CHAR_BLANK_SPACE).unwrap() // Space
325 | || c == Char16::try_from(config::CHAR_BLANK_TAB).unwrap(); // Tab
326 |
327 | // Locate the first and last
328 | // non-whitespace characters
329 | let length = self.num_chars() - 1;
330 | let mut start = length;
331 | let mut end = 0;
332 |
333 | // Find the first non-whitespace character
334 | for (i, c) in self.iter().enumerate() {
335 | if !is_whitespace(c) {
336 | start = i;
337 | break;
338 | }
339 | }
340 |
341 | // Find the last non-whitespace character
342 | for (i, c) in self.as_slice().iter().rev().enumerate() {
343 | if !is_whitespace(c) {
344 | end = length - i;
345 | break;
346 | }
347 | }
348 |
349 | // Result
350 | if start > end {
351 | // Empty string
352 | CString16::new()
353 | } else {
354 | // Actual substring
355 | self.substring(start, end)
356 | }
357 |
358 | }
359 |
360 | }
361 |
--------------------------------------------------------------------------------