diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb27d37..efcb7b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: # The specification is taken from the branch of the same name where one # exists, so a change spanning both repositories is reviewed as a whole. OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }} - MCPP_VERSION: 2026.11.12.1 + MCPP_VERSION: 2026.11.15.1 XLINGS_VERSION: v2026.10.26.1 XLINGS_NON_INTERACTIVE: '1' steps: @@ -202,8 +202,11 @@ jobs: } subst mcpp.toml .spec subst examples/hello/mcpp.toml ../../.spec - - name: Install OVMF and qemu - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ovmf qemu-system-x86 + subst examples/std/mcpp.toml ../../.spec + # mtools and dosfstools make the writable FAT volume the Shell runs from (0.11.0), and read + # back what the program left on it. + - name: Install OVMF, qemu and the FAT tools + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ovmf qemu-system-x86 mtools dosfstools - name: The example is a real EFI application run: | @@ -236,6 +239,69 @@ jobs: grep -q "all held" uefi.log [ "$status" -eq 85 ] || { echo "::error::QEMU ended with $status, not 85 (42 << 1 | 1)"; exit 1; } + # THROUGH THE UEFI SHELL (0.11.0). With no default boot file, OVMF starts its Shell, which runs + # startup.nsh from the volume: the program is started by name with a quoted argument, on a + # writable FAT volume, with a virtual random device for the firmware's RNG protocol. The + # example checks the Shell's arguments (`"a b"' is one), writes, reads, renames, lists and + # removes on its own volume, and fills bytes from the random source; the host then reads the + # file it left. + - name: Started by the UEFI Shell, it receives the Shell's arguments and uses its volume + run: | + set -euo pipefail + cd examples/hello + EFI=$(find target -name "hello.efi" | head -1) + rm -f shell.img + mkfs.vfat -C -n OKUEFI shell.img 65536 > /dev/null + printf '@echo -off\r\nfs0:\r\nhello.efi --shell "a b" c\r\n' > startup.nsh + mcopy -i shell.img "$EFI" ::/hello.efi + mcopy -i shell.img startup.nsh ::/startup.nsh + set +e + timeout 180 qemu-system-x86_64 -machine q35 -m 256 -nographic -no-reboot \ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ + -drive if=virtio,format=raw,file=shell.img -device virtio-rng-pci \ + -device isa-debug-exit,iobase=0xf4,iosize=0x04 -net none < /dev/null > shell.log 2>&1 + status=$? + set -e + tr -d '\000' < shell.log | sed 's/\x1b\[[0-9;]*[A-Za-z]//g' | grep -v '^Press ESC' | tail -70 + grep -q "a quoted argument with a space is one argument" shell.log + grep -q "the firmware's random source fills bytes" shell.log + grep -q "all held" shell.log + [ "$status" -eq 85 ] || { echo "::error::QEMU ended with $status, not 85 (42 << 1 | 1)"; exit 1; } + mdir -i shell.img ::/okfs + mtype -i shell.img ::/okfs/kept.txt | tr -d '\r' | grep -qx "kept by openkal-uefi" \ + || { echo "::error::okfs/kept.txt was not left on the volume as written"; exit 1; } + + # BENEATH THE C LIBRARY (0.11.0): examples/std, `import std' on openkal-platform's row with + # this implementation in place of the released one. The Shell's arguments reach `main', files + # are written through the standard library, and std::random_device draws on the firmware's + # source; mimalloc, the C library's allocator, warns when it finds no secure random source, + # and the warning is required to be absent. + - name: Beneath the C library, through the Shell, with no random-source warning + run: | + set -euo pipefail + cd examples/std + mcpp build --target x86_64-uefi + EFI=$(find target -name "std.efi" | head -1) + rm -f std.img + mkfs.vfat -C -n OKSTD std.img 65536 > /dev/null + printf '@echo -off\r\nfs0:\r\nstd.efi --shell "a b" c\r\n' > startup.nsh + mcopy -i std.img "$EFI" ::/std.efi + mcopy -i std.img startup.nsh ::/startup.nsh + set +e + timeout 180 qemu-system-x86_64 -machine q35 -m 1024 -nographic -no-reboot \ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ + -drive if=virtio,format=raw,file=std.img -device virtio-rng-pci \ + -device isa-debug-exit,iobase=0xf4,iosize=0x04 -net none < /dev/null > std.log 2>&1 + status=$? + set -e + tr -d '\000' < std.log | sed 's/\x1b\[[0-9;]*[A-Za-z]//g' | grep -v '^Press ESC' | tail -30 + if grep -qi "secure randomness" std.log; then + echo "::error::the C library's allocator found no secure random source"; exit 1 + fi + grep -q "all held" std.log + [ "$status" -eq 85 ] || { echo "::error::QEMU ended with $status, not 85 (42 << 1 | 1)"; exit 1; } + mtype -i std.img ::/okstd/written.txt | tr -d '\r' | grep -qx "written through the C library" + # --------------------------------------------------------------------------- # The cross-compilation is performed FROM three systems, not only from Linux. # @@ -274,7 +340,7 @@ jobs: # The specification is taken from the branch of the same name where one # exists, so a change spanning both repositories is reviewed as a whole. OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }} - MCPP_VERSION: 2026.11.12.1 + MCPP_VERSION: 2026.11.15.1 XLINGS_VERSION: v2026.10.26.1 XLINGS_NON_INTERACTIVE: '1' steps: diff --git a/README.md b/README.md index 3a9075a..73b7db2 100644 --- a/README.md +++ b/README.md @@ -35,21 +35,66 @@ C library states `c-abi = false` for the row and calls openkal directly. The firmware calls `efi_main`; this implementation records its tables, turns off the boot manager's watchdog, runs the program's static initialisers (read from the image's own `.ctors` and `.CRT` sections, there being no C runtime) and calls -`main(argc, argv, envp)`. The arguments are the image's load options split at -spaces; firmware that gave none gives one empty name. +`main(argc, argv, envp)`. + +The arguments (0.11.0) are the UEFI Shell's where the Shell started the program: +it installs `EFI_SHELL_PARAMETERS_PROTOCOL` on the image handle with the command +line already divided, its quotes and its escape character `^` removed, and each +argument is made UTF-8. Started otherwise, the arguments are the image's load +options divided by the same rules: spaces and tabs separate, a double quote opens +or closes a part in which they do not, `^` takes the next character as it is. +0.10.0 divided the load options at spaces alone, so `"a b"` arrived as two +arguments with their quotes. Firmware that gave none gives one empty name. | openkal | UEFI | |---|---| | `stream` | `SIMPLE_TEXT_OUTPUT_PROTOCOL` (UCS-2, `\n` as CR LF); input reports its end | | `memory` | `AllocatePool` / `FreePool`, over-allocated for an alignment below a page; pages for a page or more, placed at the alignment from the memory map (0.10.0) | -| `env` | the load options; no variables, image, loader or system name (absent) | +| `env` | the Shell's `EFI_SHELL_PARAMETERS_PROTOCOL`, else the load options divided with quotes (0.11.0); no variables, image, loader or system name (absent) | +| `fs` | `SIMPLE_FILE_SYSTEM_PROTOCOL` of the volume the image was loaded from (`LoadedImage->DeviceHandle`), its root the one directory supplied, named `/`; `FILE_PROTOCOL` for opening, transfers, `GetInfo`/`SetInfo` (size, time, rename), `Delete`, and a directory's reads for a listing (0.11.0) | +| `random` | `RNG_PROTOCOL` (`LocateProtocol`, the source's default algorithm); firmware without one answers `kal_err_not_supported` (0.11.0) | | `time` | `GetTime` for the wall clock; the processor's counter, measured against `Stall`, for the monotonic one; `Stall` for a sleep | | `abort` | `Exit` with the status as a warning code, so a starter reads the number | -`process` waits for `fs` over the firmware's file system protocol, since a -program is started by name through a directory; the specification's property for -an environment with one task (`KAL_PROCESS_PROP_RUNS_TO_END`, openkal 0.25.1 -clause 7.19) is what it will claim. The rest have no firmware service beneath them. +`process` is not provided. What it needs of `fs` is present from 0.11.0: a +program is found by name beneath a directory, and `LoadImage` and `StartImage` +start it, the second returning when the started image returns, which is the +specification's property for an environment with one task +(`KAL_PROCESS_PROP_RUNS_TO_END`, openkal 0.25.1 clause 7.19). What remains is the +interface itself: the vector passed as the started image's load options, quoted +so that the division above recovers it (clause 7.6); the three streams, which +`StartImage` cannot redirect; and the grants of clause 7.13. The rest have no +firmware service beneath them. + +## The volume (0.11.0) + +A name is openkal's, `/`-separated UTF-8, and becomes the firmware's, +`\`-separated UCS-2, one unit at a time; `.` is the directory itself. A handle is a +slot of a table and a generation, so a released word and a word never issued are +refused (clause 7.2); the table holds the firmware's file handle and what the +firmware has no place for (how a file was opened, a directory's name from the +root, which a rename names its destination by). + +What the firmware has no service for is answered before it is asked (clause 6.2). +`kal_fs_props` claims `MODIFIED_TIME`, `CAPACITY` and `RENAME_NO_REPLACE`, and no +other position: a FAT volume compares names without regard to case and has no +links, locks, executable property or identity of a node. Links, hard links, +locks, the executable property, the resolved name and attenuation report +`kal_err_not_supported`, and an identity is left clear in `present`. A rename +over an existing name removes it first, so a rename is not claimed atomic; the +firmware runs one task, and nothing comes between the two, which is also what +makes an exclusive create, an append and a rename that does not replace one act +each. A name removed while another handle holds it is removed at once and the +held file then fails its transfers, so `REMOVE_OPEN` is not claimed. FAT records +a modification time to two seconds. + +The firmware's source of random bytes is a protocol a driver installs, over the +processor's instruction or a virtual device, so whether there is one is known +only when the program runs. Clause 6.1 states an absent source as an absent +interface, which an application built once for every machine cannot be; the +interface is provided, and firmware without the protocol answers +`kal_err_not_supported` and fills nothing --- neither `kal_err_again`, which +states a source that will fill later, nor bytes from a clock. ## The board's exit @@ -70,17 +115,27 @@ this repository's (examples/board), define it. These structures are ABI. A field one slot out reads a neighbouring function pointer, which calls something that exists with the wrong arguments. Every member -this implementation reaches is pinned by a `static_assert` on its offset. +this implementation reaches is pinned by a `static_assert` on its offset, the file +system, file, RNG and Shell parameters protocols of 0.11.0 included, with their +GUIDs from the UEFI and UEFI Shell specifications. -## Interfaces (0.10.0) +## Interfaces (0.11.0) `[kernel-abi] provides-interfaces` states `openkal.abort`, `openkal.env`, -`openkal.exec`, `openkal.memory`, `openkal.stream` and `openkal.time` (0.9.0 -stated the list as a key of `[package]`, where the engine does not read it). -`openkal.exec` is pages of type `EfiLoaderCode`, protected through the memory -attribute protocol where the firmware offers it: what a loader of images (the -ok-image runner carried as a UEFI application) places code in. A file cannot be -mapped (no `openkal.fs`), so `kal_exec_map_part` answers not supported. +`openkal.exec`, `openkal.fs`, `openkal.memory`, `openkal.random`, +`openkal.stream` and `openkal.time`, and `kal_interfaces()` answers the same +(0.9.0 stated the list as a key of `[package]`, where the engine does not read +it; 0.11.0 adds `openkal.fs` and `openkal.random`). `openkal.exec` is pages of +type `EfiLoaderCode`, protected through the memory attribute protocol where the +firmware offers it: what a loader of images (the ok-image runner carried as a +UEFI application) places code in. The firmware maps no file, so +`KAL_EXEC_PROP_FILE` is not claimed and `kal_exec_map_part` answers not +supported; a loader reads the file through `openkal.fs` and copies it. + +The sources are compiled without stack probes (0.11.0): the firmware's stack is +committed in whole and nothing beneath this implementation defines `__chkstk`, +which a frame larger than a page otherwise calls on a row that does not state +`-mno-stack-arg-probe` for the program. ## Verified @@ -101,5 +156,26 @@ error, throws and catches, and returns its status in 0.4 s with 1 GiB or more: the carrier's allocator and the image's each place a 32 MiB arena at a 256 MiB alignment. +0.11.0, measured with mcpp 2026.11.15.1 under QEMU's q35 with the edk2 firmware +(the same steps run in CI with Ubuntu's OVMF). Booted as `BOOTX64.EFI` from a +read-only volume, `examples/hello` lists the volume's root and reads its own +image through a file's stream; the firmware had no RNG protocol there, and the +source answered `kal_err_not_supported`. Started by the UEFI Shell from +`startup.nsh` as `hello.efi --shell "a b" c` on a writable FAT volume with a +virtual random device, it receives four arguments with `a b` whole; makes, +opens, lists and removes a directory; writes a file and reads it back, at an +offset and through its stream, shortens it, renames it within a directory and +into another, refuses a rename that does not replace and an exclusive create +onto names that exist, sets and reads a modification time, reads the volume's +capacity, and leaves `okfs/kept.txt`, which the host reads; the random source +fills bytes. `examples/std`, `import std` beneath openkal-musl 0.29.6 and +openkal-llvm-runtime 0.20.3 on openkal-platform 0.28.0's row with this +implementation in place of the released one, receives the Shell's arguments in +`main`, reports `/` as its working directory, lists it through +`std::filesystem`, writes and reads a file through `std::ofstream` and +`std::ifstream`, and draws from `std::random_device`; mimalloc's warning that it +is "unable to use secure randomness", printed above 0.10.0, is absent, and +appears again, with `std::random_device` failing, on firmware with no source. + [kal]: https://github.com/mcpplibs/openkal [plat]: https://github.com/mcpplibs/openkal-platform diff --git a/examples/board/mcpp.toml b/examples/board/mcpp.toml index ab952a4..8a3e50c 100644 --- a/examples/board/mcpp.toml +++ b/examples/board/mcpp.toml @@ -62,7 +62,7 @@ driver = "compiler" inputs = [{ file = "board/qemu-exit.S" }, { file = "runtime/mem.S" }] [platform.layers] -"kernel-abi" = "openkal-uefi@0.10.0" +"kernel-abi" = "openkal-uefi@0.11.0" "c-abi" = false "c++-abi" = false "compiler-runtime" = false diff --git a/examples/hello/src/main.cpp b/examples/hello/src/main.cpp index 0be1183..9362bf9 100644 --- a/examples/hello/src/main.cpp +++ b/examples/hello/src/main.cpp @@ -1,17 +1,27 @@ // A UEFI application written against openkal, with no UEFI type in sight: a static initialiser, -// the arguments, the two clocks, two over-aligned allocations, and a status the board carries out. +// the arguments, the two clocks, two over-aligned allocations, the volume it was loaded from, a +// source of random bytes, and a status the board carries out. +// +// Started as the firmware's default boot file, it reads its own volume (read-only under the +// board's runner). Started by the UEFI Shell as `hello.efi --shell "a b" c' (CI's startup.nsh, on +// a writable FAT volume), it also checks the Shell's arguments, writes, renames and removes files +// and directories, leaves okfs/kept.txt for the host to read, and requires a random source. import openkal.stream; import openkal.abort; import openkal.memory; import openkal.env; import openkal.time; +import openkal.fs; +import openkal.random; namespace { -void say(const char* s) { +kal_uintptr length(const char* s) { kal_uintptr n = 0; while (s[n]) ++n; - kal_stream_write(kal_stdout(), s, n); + return n; } +void say(const char* s) { kal_stream_write(kal_stdout(), s, length(s)); } +void say(const char* s, kal_uintptr n) { kal_stream_write(kal_stdout(), s, n); } int failures = 0; void check(bool ok, const char* what) { say(ok ? " ok " : " FAIL "); @@ -19,16 +29,170 @@ void check(bool ok, const char* what) { say("\n"); if (!ok) ++failures; } +bool same(const char* a, kal_uintptr n, const char* b) { + if (n != length(b)) return false; + for (kal_uintptr i = 0; i < n; ++i) + if (a[i] != b[i]) return false; + return true; +} +// FAT compares names without regard to case, and keeps the case a name was written in. +bool same_name(const char* a, kal_uintptr n, const char* b) { + if (n != length(b)) return false; + const auto fold = [](char c) { return c >= 'a' && c <= 'z' ? static_cast(c - 32) : c; }; + for (kal_uintptr i = 0; i < n; ++i) + if (fold(a[i]) != fold(b[i])) return false; + return true; +} struct initialised { int value = 7; initialised() { value = 42; } } g_initialised; + +// Whether a listing of `d' holds `name', of kind `kind'. +bool lists(kal_dir d, const char* name, int kind) { + kal_uintptr it = 0; + if (kal_fs_list_begin(d, &it) != kal_ok) return false; + bool found = false; + char buf[256]; + while (it) { + kal_uintptr n = 0; + int k = 0; + if (kal_fs_list_next(d, &it, buf, sizeof buf, &n, &k) != kal_ok) return false; + if (!it) break; + say(" "); + say(buf, n); + say(k == kal_node_directory ? "/\n" : "\n"); + if (same_name(buf, n, name) && k == kind) found = true; + } + return found; +} + +int kind_of(kal_dir d, const char* name) { + kal_node_info i{}; + i.self_size = sizeof i; + if (kal_fs_info(d, name, length(name), 0, kal::fs::field::kind, &i) != kal_ok) return -1; + return (i.present & kal::fs::field::kind) ? i.kind : -1; +} + +bool put(kal_dir d, const char* name, const char* text) { + kal_file f{}; + const kal_uintptr how = (kal::fs::open::write | kal::fs::open::create | kal::fs::open::truncate).bits; + if (kal_fs_open(d, name, length(name), how, &f) != kal_ok) return false; + const bool ok = kal_stream_write(kal_fs_stream(f), text, length(text)) == static_cast(length(text)); + kal_fs_close_file(f); + return ok; +} + +bool holds(kal_dir d, const char* name, const char* text) { + kal_file f{}; + if (kal_fs_open(d, name, length(name), kal::fs::open::read.bits, &f) != kal_ok) return false; + char buf[128]; + kal_uintptr got = 0; + for (;;) { + const kal_intptr r = kal_stream_read(kal_fs_stream(f), buf + got, sizeof buf - got); + if (r <= 0) break; + got += static_cast(r); + } + kal_fs_close_file(f); + return same(buf, got, text); +} + +// What every start can check of the volume: the supplied directory, a listing of it, and the first +// bytes of the program's own image, read through a file's stream. +void read_the_volume(kal_dir root, const char* own_image, const char* listed, int listed_kind) { + say("the volume, listed:\n"); + check(lists(root, listed, listed_kind), "the program's volume is listed"); + kal_file f{}; + char mz[2] = {}; + const bool opened = kal_fs_open(root, own_image, length(own_image), kal::fs::open::read.bits, &f) == kal_ok; + if (opened) { + kal_stream_read(kal_fs_stream(f), mz, 2); + kal_fs_close_file(f); + } + check(opened && mz[0] == 'M' && mz[1] == 'Z', "the program's own image is read from its volume"); + kal_node_info i{}; + i.self_size = sizeof i; + check(kal_fs_info(root, "no-such-name", 12, 0, kal::fs::field::kind, &i) == kal_ok && i.kind == kal_node_absent, + "an absent name is an answer, not an error"); + check(kal_fs_open(root, "no-such-name", 12, kal::fs::open::read.bits, &f) == kal_err_not_found, + "opening an absent name is kal_err_not_found"); +} + +void write_the_volume(kal_dir root) { + const char* dir = "okfs"; + kal_fs_mkdir(root, dir, length(dir)); // a previous run may have left it + check(kind_of(root, dir) == kal_node_directory, "a directory is made"); + check(kal_fs_mkdir(root, dir, length(dir)) == kal_err_exists, "making it again is kal_err_exists"); + kal_dir d{}; + check(kal_fs_open_dir(root, dir, length(dir), &d) == kal_ok, "the directory is opened"); + + check(put(d, "note.txt", "written on the volume\n"), "a file is written"); + check(holds(d, "note.txt", "written on the volume\n"), "and read back as written"); + check(put(root, "okfs/kept.txt", "kept by openkal-uefi\n"), "a file is written through a name of two components"); + + kal_file f{}; + const kal_uintptr rw = (kal::fs::open::read | kal::fs::open::write).bits; + check(kal_fs_open(d, "note.txt", 8, rw, &f) == kal_ok, "a file is opened to read and write"); + char at[6] = {}; + check(kal_fs_read_at(f, at, 6, 8) == 6 && same(at, 6, "on the"), "a read at an offset"); + check(kal_fs_write_at(f, "ON", 2, 8) == 2, "a write at an offset"); + kal_u64 pos = 1; + check(kal_fs_seek(f, 0, kal::fs::seek_current, &pos) == kal_ok && pos == 0, "neither moved the position"); + kal_node_info i{}; + i.self_size = sizeof i; + check(kal_fs_file_info(f, kal::fs::field::size, &i) == kal_ok && (i.present & kal::fs::field::size) && i.size == 22, + "an open file's size"); + check(kal_fs_truncate(f, 7) == kal_ok, "a file is shortened"); + kal_fs_close_file(f); + check(holds(d, "note.txt", "written"), "and holds what was left"); + + const kal_uintptr excl = (kal::fs::open::write | kal::fs::open::create | kal::fs::open::exclusive).bits; + check(kal_fs_open(d, "note.txt", 8, excl, &f) == kal_err_exists, "an exclusive create of a name that exists"); + + kal_fs_remove(d, "renamed.txt", 11); + check(kal_fs_rename(d, "note.txt", 8, d, "renamed.txt", 11) == kal_ok, "a file is renamed"); + check(kind_of(d, "note.txt") == kal_node_absent && kind_of(d, "renamed.txt") == kal_node_file, + "the old name is absent and the new one present"); + check(put(d, "other.txt", "other\n") && + kal_fs_rename_flags(d, "other.txt", 9, d, "renamed.txt", 11, kal::fs::no_replace.bits) == kal_err_exists, + "a rename that does not replace is refused onto a name that exists"); + check(kal_fs_rename(d, "other.txt", 9, root, "moved.txt", 9) == kal_ok && kind_of(root, "moved.txt") == kal_node_file, + "a file is renamed into another directory"); + check(kal_fs_remove(root, "moved.txt", 9) == kal_ok && kind_of(root, "moved.txt") == kal_node_absent, + "a file is removed"); + say("okfs, listed:\n"); + check(lists(d, "renamed.txt", kal_node_file), "the directory is listed"); + check(kal_fs_remove(root, dir, length(dir)) == kal_err_not_empty, "a directory that is not empty is not removed"); + kal_fs_mkdir(d, "empty", 5); + check(kal_fs_remove(d, "empty", 5) == kal_ok, "an empty directory is removed"); + check(kal_fs_remove(d, "renamed.txt", 11) == kal_ok, "a file is removed by its directory"); + + const kal_u64 when = 1767225600ull * 1000000000ull; // 2026-01-01T00:00:00Z + check(kal_fs_set_modified_at(d, "kept.txt", 8, when) == kal_ok, "a modification time is set"); + check(kal_fs_info(d, "kept.txt", 8, 0, kal::fs::field::modified, &i) == kal_ok && (i.present & kal::fs::field::modified) && + i.modified_ns / 1000000000ull == when / 1000000000ull, + "and reads back"); + kal_u64 total = 0, available = 0; + check(kal_fs_capacity(root, &total, &available) == kal_ok && total > 0 && available <= total, + "the volume's capacity"); + check(kal_fs_link_create(d, "l", 1, "kept.txt", 8, 0) == kal_err_not_supported && + !kal::fs::has(root, kal::fs::links), + "links are not claimed, and not made"); + kal_fs_close_dir(d); + check(kal_fs_list_begin(d, &pos) == kal_err_invalid, "a closed directory is refused"); +} } // namespace -int main(int argc, char**, char**) { +int main(int argc, char** argv, char**) { say("hello from openkal over UEFI\n"); check(g_initialised.value == 42, "a static initialiser ran before main"); check(argc >= 1 && kal_env_arg_count() == static_cast(argc), "the arguments are counted alike"); + const bool shell = argc > 1 && same(argv[1], length(argv[1]), "--shell"); + if (shell) { + check(argc == 4, "the Shell gave four arguments"); + check(argc == 4 && same(argv[2], length(argv[2]), "a b"), "a quoted argument with a space is one argument"); + check(argc == 4 && same(argv[3], length(argv[3]), "c"), "and the one after it is the next"); + } void* p = kal_alloc(4096, 64); check(p != nullptr && (reinterpret_cast(p) & 63) == 0, "an over-aligned allocation"); kal_free(p, 4096, 64); @@ -44,6 +208,36 @@ int main(int argc, char**, char**) { const kal_duration t1 = kal_time_monotonic(); check(t1 - t0 >= 19u * 1000u * 1000u && t1 - t0 < 2000u * 1000u * 1000u, "a sleep of 20 ms is measured as such"); check(kal_time_wall() > 1700000000ull * 1000000000ull, "the wall clock reads the present"); + + // openkal.fs (0.11.0): the volume the program was loaded from, supplied as "/". + kal_dir root{}; + char name[8]; + kal_uintptr name_len = 0; + check(kal_fs_preopen_count() == 1 && kal_fs_preopen(0, &root, name, sizeof name, &name_len) == kal_ok && + same(name, name_len, "/"), + "one directory is supplied, the volume's root, named /"); + if (shell) { + read_the_volume(root, "hello.efi", "startup.nsh", kal_node_file); + write_the_volume(root); + } else { + read_the_volume(root, "EFI/BOOT/BOOTX64.EFI", "EFI", kal_node_directory); + } + + // openkal.random (0.11.0): the firmware's source, where it has one. + unsigned char a[32] = {}, b[32] = {}; + const int ra = kal_random_fill(a, sizeof a); + const int rb = kal_random_fill(b, sizeof b); + if (ra == kal_err_not_supported && !shell) { + say(" -- the firmware has no random source (EFI_RNG_PROTOCOL), reported as kal_err_not_supported\n"); + } else { + bool differ = false, nonzero = false; + for (unsigned i = 0; i < sizeof a; ++i) { + if (a[i] != b[i]) differ = true; + if (a[i]) nonzero = true; + } + check(ra == kal_ok && rb == kal_ok && differ && nonzero, "the firmware's random source fills bytes"); + } + say(failures ? "FAILED\n" : "all held\n"); return failures ? 1 : 42; } diff --git a/examples/std/mcpp.toml b/examples/std/mcpp.toml new file mode 100644 index 0000000..35f9557 --- /dev/null +++ b/examples/std/mcpp.toml @@ -0,0 +1,14 @@ +[package] +name = "std" +version = "0.1.0" + +[targets.std] +kind = "bin" +main = "src/main.cpp" + +[dependencies] +openkal = "^0.25.0" +openkal-uefi = { path = "../..", override = true } + +[target.x86_64-uefi] +platform = "openkal-platform@0.28.0" diff --git a/examples/std/src/main.cpp b/examples/std/src/main.cpp new file mode 100644 index 0000000..865691c --- /dev/null +++ b/examples/std/src/main.cpp @@ -0,0 +1,51 @@ +// A UEFI application in C++23 beneath the C library and C++ run-time of openkal-platform's row, +// over this implementation: the Shell's arguments as `main' receives them, files and a directory +// through the standard library (openkal.fs), and std::random_device, which with the C library's +// allocator draws on openkal.random. Started by the UEFI Shell as `std.efi --shell "a b" c'. +import std; + +namespace { +int failures = 0; +void check(bool ok, std::string_view what) { + std::println(" {} {}", ok ? "ok " : "FAIL", what); + if (!ok) ++failures; +} +} // namespace + +int main(int argc, char** argv) { + std::println("hello from the C library over openkal-uefi"); + const std::vector args(argv, argv + argc); + check(args.size() == 4 && args[1] == "--shell" && args[2] == "a b" && args[3] == "c", + "main receives the Shell's arguments, a quoted one whole"); + + namespace fs = std::filesystem; + check(fs::current_path() == fs::path("/"), "the working directory is the volume's root"); + std::vector names; + for (const auto& e : fs::directory_iterator("/")) names.push_back(e.path().filename().string()); + std::ranges::sort(names); + std::println(" the volume: {}", names); + // FAT keeps a name's case where it records one; the comparison is the volume's, without case. + const auto lower = [](std::string s) { for (auto& c : s) c = static_cast(std::tolower(static_cast(c))); return s; }; + check(std::ranges::any_of(names, [&](const std::string& n) { return lower(n) == "std.efi"; }), "the root is listed"); + { + std::ofstream out("/okstd/written.txt"); + check(!out, "a file beneath a directory that does not exist is not created"); + } + check(fs::create_directory("/okstd"), "a directory is made"); + { + std::ofstream out("/okstd/written.txt"); + out << "written through the C library\n"; + } + std::ifstream in("/okstd/written.txt"); + std::string line; + std::getline(in, line); + check(line == "written through the C library", "a file is written and read back"); + check(fs::file_size("/okstd/written.txt") == 30, "its size"); + + std::random_device rd; + const auto a = rd(), b = rd(), c = rd(); + check(!(a == b && b == c), "std::random_device draws on the firmware's source"); + + std::println("{}", failures ? "FAILED" : "all held"); + return failures ? 1 : 42; +} diff --git a/mcpp.toml b/mcpp.toml index b755101..9b02d60 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -15,7 +15,7 @@ [package] namespace = "mcpplibs" name = "openkal-uefi" -version = "0.10.0" +version = "0.11.0" description = "An implementation of openkal on UEFI Boot Services, for applications the firmware loads before an operating system exists" license = "Apache-2.0" @@ -36,7 +36,8 @@ repo = "https://github.com/mcpplibs/openkal-uefi" # it, so a consumer bound every interface (openkal-run then referred to fifteen this # implementation does not have); the list belongs to [kernel-abi]. [kernel-abi] -provides-interfaces = ["openkal.abort", "openkal.env", "openkal.exec", "openkal.memory", "openkal.stream", "openkal.time"] +provides-interfaces = ["openkal.abort", "openkal.env", "openkal.exec", "openkal.fs", "openkal.memory", + "openkal.random", "openkal.stream", "openkal.time"] [dependencies] # Every patch release of the specification's minor version (openkal clause 8). @@ -50,9 +51,14 @@ openkal = "^0.25.0" [features] standalone = { defines = ["OKUEFI_STANDALONE"] } +# No stack probes (0.11.0): the firmware's stack is committed in whole, and no run-time beneath +# this implementation defines `__chkstk', which a frame larger than a page otherwise calls (a name +# of openkal.fs is converted in a buffer of 2 KiB on the stack). The example's board states it for +# the whole program; a row that does not still links this implementation. [build] flags = [ { glob = "src/**", cxxflags = ["-fno-exceptions", "-fno-rtti", "-fno-stack-protector", - "-fno-asynchronous-unwind-tables"] }, + "-fno-asynchronous-unwind-tables", + "-mno-stack-arg-probe"] }, ] diff --git a/src/exec.cpp b/src/exec.cpp index e624bc6..4bff33f 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -8,8 +8,9 @@ // Firmware that offers no such protocol maps every page writable and executable, and has nothing to // change: publishing is then a statement the caller makes, as the interface allows. // -// A file cannot be mapped (there is no openkal.fs here), so kal_exec_map_part answers -// kal_err_not_supported, a property of the environment. +// A part is not made a file's: the firmware maps nothing, and openkal.fs (0.11.0) reads a file's +// bytes, which a loader copies into the region itself. KAL_EXEC_PROP_FILE is not claimed, and +// kal_exec_map_part answers kal_err_not_supported, a property of the environment. #include #include diff --git a/src/fs.cpp b/src/fs.cpp new file mode 100644 index 0000000..d53c98c --- /dev/null +++ b/src/fs.cpp @@ -0,0 +1,832 @@ +// openkal.fs on Boot Services (0.11.0): the volume the program was loaded from, through the simple +// file system protocol and the file protocol of its handles. +// +// THE PREOPEN IS THE VOLUME'S ROOT. The firmware has no namespace of paths a program shares: a +// volume is a device handle carrying EFI_SIMPLE_FILE_SYSTEM_PROTOCOL, opened for its root +// directory. The one directory supplied is the root of the volume the image was loaded from +// (EFI_LOADED_IMAGE_PROTOCOL.DeviceHandle), named "/", so that a C library above resolves an +// absolute name against it and its working directory is the root. +// +// A HANDLE IS A SLOT AND A GENERATION. Each directory, file and listing is a slot of a table here +// holding the firmware's EFI_FILE_PROTOCOL; the word is the slot's index with its generation in the +// high half, so that a released word, or a word this implementation never issued, is refused +// (clause 7.2). The table holds what the firmware has no place for --- how a file was opened, a +// directory's name from the root --- and translates nothing. +// +// A NAME is openkal's, `/'-separated UTF-8, and becomes the firmware's, `\'-separated UCS-2, one +// unit at a time; "." is the directory itself, which the firmware's own naming reserves as well. +// +// WHAT THE FIRMWARE HAS NO SERVICE FOR is answered before it is asked (clause 6.2): kal_fs_props +// claims capacity, a modification time and a rename that does not replace, and no other position. +// A FAT volume has no links, no locks, no executable property and no identity of a node; the +// operations those positions govern report kal_err_not_supported, and a node's identity is left +// clear in `present'. Attenuation is not offered. A rename over an existing name removes it first, +// so a rename is not claimed atomic; one environment task runs, so nothing comes between the two. +#include +#include +#include + +#include "uefi.h" +#include "state.h" +#include "text.h" + +namespace { + +// ── the table ─────────────────────────────────────────────────────────────── + +enum : unsigned char { kFree = 0, kDir, kFile, kList }; + +struct node { + efi_file_protocol* f; + efi_char16* path; // a directory's name from the root, `\'-separated; "" for the root + kal_uintptr flags; // a file's KAL_OPEN_* flags + kal_uintptr owner; // a listing's directory + unsigned generation; + unsigned char kind; +}; + +constexpr unsigned kSlots = 1024; +node g_nodes[kSlots]; +kal_uintptr g_root = 0; // the preopen's word while it is held +bool g_read_only = false; // the volume states it is read-only + +kal_uintptr word_of(unsigned i) { + return (static_cast(g_nodes[i].generation) << 32) | (i + 1); +} + +node* take(kal_uintptr h, unsigned char kind) { + const kal_uintptr i = (h & 0xFFFFFFFFu) - 1; + if (i >= kSlots) return nullptr; + node& n = g_nodes[i]; + if (n.kind != kind || n.generation != static_cast(h >> 32)) return nullptr; + return &n; +} + +kal_uintptr issue(unsigned char kind, efi_file_protocol* f) { + for (unsigned i = 0; i < kSlots; ++i) { + node& n = g_nodes[i]; + if (n.kind != kFree) continue; + if (n.generation == 0) n.generation = 1; + n.kind = kind; + n.f = f; + n.path = nullptr; + n.flags = 0; + n.owner = 0; + return word_of(i); + } + return 0; +} + +void retire(node& n) { + if (n.path) kal_free(n.path, 0, 0); + n.path = nullptr; + n.f = nullptr; + n.kind = kFree; + if (++n.generation == 0) n.generation = 1; +} + +int error_of(efi_status s) { + switch (s) { + case EFI_NOT_FOUND: return kal_err_not_found; + case EFI_ACCESS_DENIED: + case EFI_WRITE_PROTECTED: return kal_err_permission; + case EFI_VOLUME_FULL: return kal_err_no_space; + case EFI_OUT_OF_RESOURCES: return kal_err_no_memory; + case EFI_INVALID_PARAMETER: + case EFI_BAD_BUFFER_SIZE: return kal_err_invalid; + case EFI_UNSUPPORTED: return kal_err_not_supported; + default: return kal_err_io; + } +} + +// ── names ─────────────────────────────────────────────────────────────────── + +constexpr kal_uintptr kMaxName = 1024; + +// A name this interface accepts (fs.h): not empty, not beginning with a separator, no empty +// component and none that ascends; and no `\', which the firmware reads as a separator. +struct name16 { + efi_char16 w[kMaxName + 1]; + long long n = -1; + name16(const char* s, kal_uintptr len) { + if (!s || len == 0 || len > kMaxName || s[0] == '/') return; + kal_uintptr start = 0; + for (kal_uintptr i = 0; i <= len; ++i) { + if (i < len && s[i] == '\\') return; + if (i == len || s[i] == '/') { + const kal_uintptr k = i - start; + if (k == 0) return; + if (k == 2 && s[start] == '.' && s[start + 1] == '.') return; + start = i + 1; + } + } + n = okuefi::to_ucs2(s, len, w, kMaxName); + for (long long i = 0; i < n; ++i) + if (w[i] == u'/') w[i] = u'\\'; + } + bool ok() const { return n > 0; } + bool self() const { return n == 1 && w[0] == u'.'; } +}; + +kal_uintptr length16(const efi_char16* w) { kal_uintptr n = 0; while (w[n]) ++n; return n; } + +// `base\name', the name of a node from the root; null where there is no memory for it. +efi_char16* joined(const efi_char16* base, const efi_char16* name) { + const kal_uintptr a = base ? length16(base) : 0, b = length16(name); + auto* p = static_cast(kal_alloc((a + b + 2) * sizeof(efi_char16), alignof(efi_char16))); + if (!p) return nullptr; + kal_uintptr o = 0; + for (kal_uintptr i = 0; i < a; ++i) p[o++] = base[i]; + if (a) p[o++] = u'\\'; + for (kal_uintptr i = 0; i < b; ++i) p[o++] = name[i]; + p[o] = 0; + return p; +} + +// ── what the firmware states of a node ────────────────────────────────────── + +// EFI_FILE_INFO of a handle, in `small' where it fits and in an allocation where it does not. +struct file_info { + alignas(8) unsigned char small[kFileInfoFixed + 2 * 260]; + efi_file_info* info = nullptr; + efi_uintn size = 0; + void* big = nullptr; + efi_status status = EFI_DEVICE_ERROR; + explicit file_info(efi_file_protocol* f) { + size = sizeof small; + status = f->get_info(f, &kFileInfo, &size, small); + if (status == EFI_SUCCESS) { info = reinterpret_cast(small); return; } + if (status != EFI_BUFFER_TOO_SMALL) return; + big = kal_alloc(size, 8); + if (!big) { status = EFI_OUT_OF_RESOURCES; return; } + status = f->get_info(f, &kFileInfo, &size, big); + if (status == EFI_SUCCESS) info = static_cast(big); + } + ~file_info() { if (big) kal_free(big, 0, 0); } + file_info(const file_info&) = delete; + file_info& operator=(const file_info&) = delete; +}; + +bool is_directory(const efi_file_info* i) { return (i->attribute & EFI_FILE_DIRECTORY) != 0; } + +// The fields of kal_node_info that EFI_FILE_INFO gives: kind, size, modification time and whether +// the node may be written. A node has no identity the firmware states, and no executable property. +// No more of `out' than its self_size is written, and a position is stated only for a field that is +// written. +int describe(const efi_file_info* i, kal_u32 wanted, kal_node_info* out) { + if (!out || out->self_size < 2 * sizeof(kal_u32)) return kal_err_invalid; + kal_node_info v{}; + v.self_size = out->self_size; + v.kind = is_directory(i) ? kal_node_directory : kal_node_file; + v.size = i->file_size; + v.modified_ns = okuefi::ns_of(i->modification_time); + v.writable = !g_read_only && !(i->attribute & EFI_FILE_READ_ONLY); + kal_u32 present = KAL_INFO_KIND | KAL_INFO_SIZE | KAL_INFO_WRITABLE; + if (v.modified_ns) present |= KAL_INFO_MODIFIED; + (void)wanted; // everything the firmware gives is in one answer + const kal_uintptr room = out->self_size < sizeof v ? out->self_size : sizeof v; + const auto fits = [&](kal_uintptr offset, kal_uintptr size) { return offset + size <= room; }; + if (!fits(__builtin_offsetof(kal_node_info, kind), sizeof v.kind)) present &= ~KAL_INFO_KIND; + if (!fits(__builtin_offsetof(kal_node_info, size), sizeof v.size)) present &= ~KAL_INFO_SIZE; + if (!fits(__builtin_offsetof(kal_node_info, modified_ns), sizeof v.modified_ns)) present &= ~KAL_INFO_MODIFIED; + if (!fits(__builtin_offsetof(kal_node_info, writable), sizeof v.writable)) present &= ~KAL_INFO_WRITABLE; + v.present = present; + const auto* from = reinterpret_cast(&v); + auto* to = reinterpret_cast(out); + for (kal_uintptr k = 0; k < room; ++k) to[k] = from[k]; + return kal_ok; +} + +// Sets what a handle's EFI_FILE_INFO states, through a copy: the times are cleared, which the +// firmware reads as "keep them", unless `modified' is given; the name is kept unless `rename' is. +efi_status amend(efi_file_protocol* f, const unsigned long long* size, const efi_time* modified, + const efi_char16* rename) { + file_info fi(f); + if (!fi.info) return fi.status; + const kal_uintptr name = rename ? length16(rename) : length16(fi.info->file_name); + const efi_uintn bytes = kFileInfoFixed + (name + 1) * sizeof(efi_char16); + auto* p = static_cast(kal_alloc(bytes, 8)); + if (!p) return EFI_OUT_OF_RESOURCES; + const auto* src = reinterpret_cast(fi.info); + auto* dst = reinterpret_cast(p); + for (efi_uintn k = 0; k < kFileInfoFixed; ++k) dst[k] = src[k]; + const efi_char16* n = rename ? rename : fi.info->file_name; + for (kal_uintptr k = 0; k <= name; ++k) p->file_name[k] = n[k]; + p->size = bytes; + p->create_time = efi_time{}; + p->last_access_time = efi_time{}; + p->modification_time = modified ? *modified : efi_time{}; + if (size) p->file_size = *size; + const efi_status s = f->set_info(f, &kFileInfo, bytes, p); + kal_free(p, 0, 0); + return s; +} + +// ── the volume ────────────────────────────────────────────────────────────── + +kal_uintptr root() { + if (g_root && take(g_root, kDir)) return g_root; + g_root = 0; + const okuefi::state& s = okuefi::self(); + if (!s.st || !s.device) return 0; + void* p = nullptr; + if (s.st->boot_services->handle_protocol(s.device, &kSimpleFileSystemProtocol, &p) != EFI_SUCCESS || !p) + return 0; + auto* sfs = static_cast(p); + efi_file_protocol* f = nullptr; + if (sfs->open_volume(sfs, &f) != EFI_SUCCESS || !f) return 0; + alignas(8) unsigned char buf[256]; + efi_uintn size = sizeof buf; + if (f->get_info(f, &kFileSystemInfo, &size, buf) == EFI_SUCCESS) + g_read_only = reinterpret_cast(buf)->read_only != 0; + const kal_uintptr h = issue(kDir, f); + if (!h) { f->close(f); return 0; } + node* n = take(h, kDir); + n->path = joined(nullptr, u""); + if (!n->path) { f->close(f); retire(*n); return 0; } + return g_root = h; +} + +// Opens `name' beneath a directory with the firmware's mode; "." is the directory itself. +efi_status open_beneath(node* d, const name16& name, unsigned long long mode, unsigned long long attributes, + efi_file_protocol** out) { + *out = nullptr; + return d->f->open(d->f, out, name.w, mode, attributes); +} + +// Whether a directory holds anything but its own two entries. +int emptiness(efi_file_protocol* dir, bool* empty) { + efi_file_protocol* h = nullptr; + efi_status s = dir->open(dir, &h, u".", EFI_FILE_MODE_READ, 0); + if (s != EFI_SUCCESS) return error_of(s); + *empty = true; + alignas(8) unsigned char buf[kFileInfoFixed + 2 * 260]; + for (;;) { + efi_uintn size = sizeof buf; + s = h->read(h, &size, buf); + if (s == EFI_BUFFER_TOO_SMALL) { *empty = false; break; } // a long name is an entry + if (s != EFI_SUCCESS) { h->close(h); return error_of(s); } + if (size == 0) break; + const efi_char16* n = reinterpret_cast(buf)->file_name; + if ((n[0] == u'.' && n[1] == 0) || (n[0] == u'.' && n[1] == u'.' && n[2] == 0)) continue; + *empty = false; + break; + } + h->close(h); + return kal_ok; +} + +// ── a file's transfers ────────────────────────────────────────────────────── + +kal_intptr read_file(node* n, void* buf, kal_uintptr len) { + if (!(n->flags & KAL_OPEN_READ)) return -kal_err_invalid; + if (len == 0) return 0; + if (!buf) return -kal_err_invalid; + efi_uintn size = len; + const efi_status s = n->f->read(n->f, &size, buf); + if (s == EFI_SUCCESS) return static_cast(size); + // A position beyond the end is the end, which the firmware reports as a device's failure. + if (s == EFI_DEVICE_ERROR) { + unsigned long long at = 0; + file_info fi(n->f); + if (fi.info && n->f->get_position(n->f, &at) == EFI_SUCCESS && at >= fi.info->file_size) return 0; + } + return -error_of(s); +} + +kal_intptr write_file(node* n, const void* buf, kal_uintptr len) { + if (!(n->flags & KAL_OPEN_WRITE)) return -kal_err_invalid; + if (len == 0) return 0; + if (!buf) return -kal_err_invalid; + // Appending: one task runs, so the end found here is the end written at. + if (n->flags & KAL_OPEN_APPEND) n->f->set_position(n->f, EFI_FILE_POSITION_END); + const auto* p = static_cast(buf); + kal_uintptr done = 0; + while (done < len) { + efi_uintn size = len - done; + const efi_status s = n->f->write(n->f, &size, p + done); + if (s != EFI_SUCCESS) return done ? static_cast(done) : -error_of(s); + if (size == 0) return done ? static_cast(done) : -kal_err_io; + done += size; + } + return static_cast(done); +} + +} // namespace + +// ── openkal.stream, for a file's stream ───────────────────────────────────── + +namespace okuefi { +bool file_stream(kal_uintptr h) { return take(h, kFile) != nullptr; } +kal_intptr file_write(kal_uintptr h, const void* buf, kal_uintptr n) { + node* f = take(h, kFile); + return f ? write_file(f, buf, n) : -kal_err_invalid; +} +kal_intptr file_read(kal_uintptr h, void* buf, kal_uintptr n) { + node* f = take(h, kFile); + return f ? read_file(f, buf, n) : -kal_err_invalid; +} +int file_flush(kal_uintptr h) { + node* f = take(h, kFile); + if (!f) return kal_err_invalid; + if (!(f->flags & KAL_OPEN_WRITE)) return kal_ok; + const efi_status s = f->f->flush(f->f); + return s == EFI_SUCCESS ? kal_ok : error_of(s); +} +} // namespace okuefi + +extern "C" { + +// ── directories ───────────────────────────────────────────────────────────── + +kal_uintptr kal_fs_preopen_count(void) { return root() ? 1 : 0; } + +int kal_fs_preopen(kal_uintptr index, kal_dir* out, char* name_out, kal_uintptr name_cap, + kal_uintptr* name_len) { + if (index != 0 || !out) return kal_err_invalid; + const kal_uintptr h = root(); + if (!h) return kal_err_invalid; + *out = kal_dir{h}; + if (name_out && name_cap) name_out[0] = '/'; + if (name_len) *name_len = 1; + return kal_ok; +} + +// FAT compares names without regard to case and has no links; it records a modification time, its +// capacity is stated (EFI_FILE_SYSTEM_INFO), and a rename into a name that exists is refused by +// the firmware, which is the rename that does not replace. +kal_uintptr kal_fs_props(kal_dir d) { + if (!take(d.h, kDir)) return 0; + return KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_CAPACITY | KAL_FS_PROP_RENAME_NO_REPLACE; +} + +int kal_fs_open_dir(kal_dir base, const char* name, kal_uintptr len, kal_dir* out) { + node* d = take(base.h, kDir); + const name16 n(name, len); + if (!d || !out || !n.ok()) return kal_err_invalid; + efi_file_protocol* f = nullptr; + const efi_status s = open_beneath(d, n, EFI_FILE_MODE_READ, 0, &f); + if (s != EFI_SUCCESS) return error_of(s); + { + file_info fi(f); + if (!fi.info) { f->close(f); return error_of(fi.status); } + if (!is_directory(fi.info)) { f->close(f); return kal_err_not_directory; } + } + efi_char16* path = n.self() ? joined(nullptr, d->path) : joined(d->path, n.w); + const kal_uintptr h = path ? issue(kDir, f) : 0; + if (!h) { if (path) kal_free(path, 0, 0); f->close(f); return kal_err_no_memory; } + take(h, kDir)->path = path; + *out = kal_dir{h}; + return kal_ok; +} + +int kal_fs_open(kal_dir base, const char* name, kal_uintptr len, kal_uintptr flags, kal_file* out) { + node* d = take(base.h, kDir); + const name16 n(name, len); + constexpr kal_uintptr known = KAL_OPEN_READ | KAL_OPEN_WRITE | KAL_OPEN_CREATE | KAL_OPEN_EXCLUSIVE | + KAL_OPEN_TRUNCATE | KAL_OPEN_APPEND; + if (!d || !out || !n.ok() || (flags & ~known) || !(flags & (KAL_OPEN_READ | KAL_OPEN_WRITE))) + return kal_err_invalid; + // The firmware's modes all read; writing is asked for by any intent that writes. + if (flags & (KAL_OPEN_CREATE | KAL_OPEN_TRUNCATE | KAL_OPEN_APPEND)) flags |= KAL_OPEN_WRITE; + unsigned long long mode = EFI_FILE_MODE_READ; + if (flags & KAL_OPEN_WRITE) mode |= EFI_FILE_MODE_WRITE; + efi_file_protocol* f = nullptr; + // Exclusion: the firmware's create opens what exists, so existence is asked first. One task + // runs, and nothing creates the name between the question and the creation. + if ((flags & KAL_OPEN_CREATE) && (flags & KAL_OPEN_EXCLUSIVE)) { + if (open_beneath(d, n, EFI_FILE_MODE_READ, 0, &f) == EFI_SUCCESS) { f->close(f); return kal_err_exists; } + } + if (flags & KAL_OPEN_CREATE) mode |= EFI_FILE_MODE_CREATE; + efi_status s = open_beneath(d, n, mode, 0, &f); + if (s != EFI_SUCCESS) return error_of(s); + { + file_info fi(f); + if (!fi.info) { f->close(f); return error_of(fi.status); } + if (is_directory(fi.info)) { f->close(f); return kal_err_is_directory; } + if ((flags & KAL_OPEN_TRUNCATE) && fi.info->file_size != 0) { + const unsigned long long zero = 0; + s = amend(f, &zero, nullptr, nullptr); + if (s != EFI_SUCCESS) { f->close(f); return error_of(s); } + } + } + const kal_uintptr h = issue(kFile, f); + if (!h) { f->close(f); return kal_err_no_memory; } + take(h, kFile)->flags = flags; + *out = kal_file{h}; + return kal_ok; +} + +kal_uintptr kal_fs_max_name(void) { return kMaxName; } + +// Closing a directory releases the listings it produced (fs.h). +void kal_fs_close_dir(kal_dir d) { + node* n = take(d.h, kDir); + if (!n) return; + for (unsigned i = 0; i < kSlots; ++i) { + node& l = g_nodes[i]; + if (l.kind == kList && l.owner == d.h) { l.f->close(l.f); retire(l); } + } + n->f->close(n->f); + retire(*n); + if (d.h == g_root) g_root = 0; +} + +void kal_fs_close_file(kal_file file) { + node* n = take(file.h, kFile); + if (!n) return; + n->f->close(n->f); + retire(*n); +} + +kal_stream kal_fs_stream(kal_file file) { + return take(file.h, kFile) ? kal_stream{file.h} : kal_stream{~kal_uintptr{0}}; +} + +int kal_fs_seek(kal_file file, kal_i64 offset, int whence, kal_u64* result) { + node* n = take(file.h, kFile); + if (!n) return kal_err_invalid; + long long from = 0; + if (whence == KAL_SEEK_CURRENT) { + unsigned long long at = 0; + const efi_status s = n->f->get_position(n->f, &at); + if (s != EFI_SUCCESS) return error_of(s); + from = static_cast(at); + } else if (whence == KAL_SEEK_END) { + file_info fi(n->f); + if (!fi.info) return error_of(fi.status); + from = static_cast(fi.info->file_size); + } else if (whence != KAL_SEEK_SET) { + return kal_err_invalid; + } + const long long to = from + offset; + if (to < 0) return kal_err_invalid; + const efi_status s = n->f->set_position(n->f, static_cast(to)); + if (s != EFI_SUCCESS) return error_of(s); + if (result) *result = static_cast(to); + return kal_ok; +} + +// Extending writes zero bytes (the firmware's own extension does), and discarding releases them. +int kal_fs_truncate(kal_file file, kal_u64 size) { + node* n = take(file.h, kFile); + if (!n || !(n->flags & KAL_OPEN_WRITE)) return kal_err_invalid; + const unsigned long long to = size; + const efi_status s = amend(n->f, &to, nullptr, nullptr); + return s == EFI_SUCCESS ? kal_ok : error_of(s); +} + +// At an offset: the position is kept and restored around the transfer. One task runs, so no other +// transfer observes the position in between. +kal_intptr kal_fs_read_at(kal_file file, void* buf, kal_uintptr len, kal_u64 offset) { + node* n = take(file.h, kFile); + if (!n) return -kal_err_invalid; + if (!(n->flags & KAL_OPEN_READ)) return -kal_err_invalid; + { + file_info fi(n->f); + if (!fi.info) return -error_of(fi.status); + if (offset >= fi.info->file_size) return 0; + } + unsigned long long was = 0; + efi_status s = n->f->get_position(n->f, &was); + if (s != EFI_SUCCESS) return -error_of(s); + s = n->f->set_position(n->f, offset); + if (s != EFI_SUCCESS) return -error_of(s); + const kal_intptr r = read_file(n, buf, len); + n->f->set_position(n->f, was); + return r; +} + +kal_intptr kal_fs_write_at(kal_file file, const void* buf, kal_uintptr len, kal_u64 offset) { + node* n = take(file.h, kFile); + if (!n) return -kal_err_invalid; + if (!(n->flags & KAL_OPEN_WRITE)) return -kal_err_invalid; + if (len == 0) return 0; + { + // A write beyond the end leaves zero bytes between: the file is extended to the offset + // first, which the firmware fills. + file_info fi(n->f); + if (!fi.info) return -error_of(fi.status); + if (offset > fi.info->file_size) { + const unsigned long long to = offset; + const efi_status s = amend(n->f, &to, nullptr, nullptr); + if (s != EFI_SUCCESS) return -error_of(s); + } + } + unsigned long long was = 0; + efi_status s = n->f->get_position(n->f, &was); + if (s != EFI_SUCCESS) return -error_of(s); + s = n->f->set_position(n->f, offset); + if (s != EFI_SUCCESS) return -error_of(s); + const kal_uintptr keep = n->flags; + n->flags &= ~KAL_OPEN_APPEND; // at the offset (fs.h permits either) + const kal_intptr r = write_file(n, buf, len); + n->flags = keep; + n->f->set_position(n->f, was); + return r; +} + +// ── enquiry, creation and removal ─────────────────────────────────────────── + +// There are no links, so the name and what it finally refers to are one node and KAL_FS_NO_RESOLVE +// changes nothing. A name that is absent, or beneath a component that is not a directory, is an +// answer (clause 7.7). +int kal_fs_info(kal_dir base, const char* name, kal_uintptr len, kal_uintptr flags, kal_u32 wanted, + kal_node_info* out) { + node* d = take(base.h, kDir); + const name16 n(name, len); + if (!d || !n.ok() || (flags & ~KAL_FS_NO_RESOLVE) || !out || out->self_size < 2 * sizeof(kal_u32)) + return kal_err_invalid; + if (n.self()) { + file_info fi(d->f); + return fi.info ? describe(fi.info, wanted, out) : error_of(fi.status); + } + efi_file_protocol* f = nullptr; + const efi_status s = open_beneath(d, n, EFI_FILE_MODE_READ, 0, &f); + if (s == EFI_NOT_FOUND) { + out->present = 0; + if (out->self_size >= __builtin_offsetof(kal_node_info, kind) + sizeof(int)) { + out->kind = kal_node_absent; + out->present = KAL_INFO_KIND; + } + return kal_ok; + } + if (s != EFI_SUCCESS) return error_of(s); + int r; + { + file_info fi(f); + r = fi.info ? describe(fi.info, wanted, out) : error_of(fi.status); + } + f->close(f); + return r; +} + +int kal_fs_mkdir(kal_dir base, const char* name, kal_uintptr len) { + node* d = take(base.h, kDir); + const name16 n(name, len); + if (!d || !n.ok()) return kal_err_invalid; + if (n.self()) return kal_err_exists; + efi_file_protocol* f = nullptr; + if (open_beneath(d, n, EFI_FILE_MODE_READ, 0, &f) == EFI_SUCCESS) { f->close(f); return kal_err_exists; } + const efi_status s = open_beneath(d, n, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, + EFI_FILE_DIRECTORY, &f); + if (s != EFI_SUCCESS) return error_of(s); + f->close(f); + return kal_ok; +} + +// A directory must be empty; the firmware's Delete reports a refusal as a warning that does not +// say why, so emptiness is asked first. +int kal_fs_remove(kal_dir base, const char* name, kal_uintptr len) { + node* d = take(base.h, kDir); + const name16 n(name, len); + if (!d || !n.ok() || n.self()) return kal_err_invalid; + efi_file_protocol* f = nullptr; + efi_status s = open_beneath(d, n, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0, &f); + if (s != EFI_SUCCESS) return error_of(s); + { + file_info fi(f); + if (!fi.info) { f->close(f); return error_of(fi.status); } + if (is_directory(fi.info)) { + bool empty = false; + const int e = emptiness(f, &empty); + if (e != kal_ok) { f->close(f); return e; } + if (!empty) { f->close(f); return kal_err_not_empty; } + } + } + s = f->remove(f); // closes the handle in every case + if (s == EFI_SUCCESS) return kal_ok; + return s == EFI_WARN_DELETE_FAILURE ? kal_err_permission : error_of(s); +} + +// Through the firmware's own rename: the name in EFI_FILE_INFO, set to the destination's name from +// the root. A destination that exists is removed first, as a rename replaces it, unless the caller +// asked that it not be (KAL_RENAME_NO_REPLACE), which the firmware's refusal answers. +int kal_fs_rename_flags(kal_dir from, const char* a, kal_uintptr alen, kal_dir to, const char* b, + kal_uintptr blen, kal_uintptr flags) { + node* df = take(from.h, kDir); + node* dt = take(to.h, kDir); + const name16 na(a, alen), nb(b, blen); + if (!df || !dt || !na.ok() || !nb.ok() || na.self() || nb.self() || (flags & ~KAL_RENAME_NO_REPLACE)) + return kal_err_invalid; + efi_file_protocol* f = nullptr; + efi_status s = open_beneath(df, na, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0, &f); + if (s != EFI_SUCCESS) return error_of(s); + bool source_is_directory = false; + { + file_info fi(f); + if (!fi.info) { f->close(f); return error_of(fi.status); } + source_is_directory = is_directory(fi.info); + } + efi_char16* target = joined(dt->path, nb.w); + if (!target) { f->close(f); return kal_err_no_memory; } + // An absolute name, from the root: the firmware reads a name beginning with `\' so. + const kal_uintptr tl = length16(target); + auto* absolute = static_cast(kal_alloc((tl + 2) * sizeof(efi_char16), alignof(efi_char16))); + if (!absolute) { kal_free(target, 0, 0); f->close(f); return kal_err_no_memory; } + absolute[0] = u'\\'; + for (kal_uintptr k = 0; k <= tl; ++k) absolute[k + 1] = target[k]; + kal_free(target, 0, 0); + + int result = kal_ok; + efi_file_protocol* g = nullptr; + if (open_beneath(dt, nb, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0, &g) == EFI_SUCCESS) { + // The destination exists. Renaming a node onto itself changes nothing. + file_info ga(g); + file_info fa(f); + bool same = false; + if (ga.info && fa.info) { + // The firmware gives no identity; the same node is the same name from the root, which + // the two handles were opened by (a case-insensitive volume compares it so). + efi_char16* src = joined(df->path, na.w); + if (src) { + const auto fold = [](efi_char16 c) { return c >= u'a' && c <= u'z' ? static_cast(c - 32) : c; }; + kal_uintptr k = 0; + same = true; + for (;; ++k) { + if (fold(src[k]) != fold(absolute[k + 1])) { same = false; break; } + if (!src[k]) break; + } + kal_free(src, 0, 0); + } + } + if (same) { g->close(g); f->close(f); kal_free(absolute, 0, 0); return kal_ok; } + if (flags & KAL_RENAME_NO_REPLACE) result = kal_err_exists; + else if (!ga.info) result = error_of(ga.status); + else if (is_directory(ga.info) && !source_is_directory) result = kal_err_is_directory; + else if (!is_directory(ga.info) && source_is_directory) result = kal_err_not_directory; + else if (is_directory(ga.info)) { + bool empty = false; + result = emptiness(g, &empty); + if (result == kal_ok && !empty) result = kal_err_not_empty; + } + if (result != kal_ok) { g->close(g); f->close(f); kal_free(absolute, 0, 0); return result; } + s = g->remove(g); + if (s != EFI_SUCCESS) { + f->close(f); + kal_free(absolute, 0, 0); + return s == EFI_WARN_DELETE_FAILURE ? kal_err_permission : error_of(s); + } + } + s = amend(f, nullptr, nullptr, absolute); + kal_free(absolute, 0, 0); + f->close(f); + if (s == EFI_SUCCESS) return kal_ok; + return s == EFI_ACCESS_DENIED && (flags & KAL_RENAME_NO_REPLACE) ? kal_err_exists : error_of(s); +} + +int kal_fs_rename(kal_dir from, const char* a, kal_uintptr alen, kal_dir to, const char* b, kal_uintptr blen) { + return kal_fs_rename_flags(from, a, alen, to, b, blen, 0); +} + +int kal_fs_file_info(kal_file file, kal_u32 wanted, kal_node_info* out) { + node* n = take(file.h, kFile); + if (!n || !out || out->self_size < 2 * sizeof(kal_u32)) return kal_err_invalid; + file_info fi(n->f); + return fi.info ? describe(fi.info, wanted, out) : error_of(fi.status); +} + +int kal_fs_set_modified(kal_file file, kal_u64 modified_ns) { + node* n = take(file.h, kFile); + if (!n || !(n->flags & KAL_OPEN_WRITE)) return kal_err_invalid; + const efi_time t = okuefi::time_of(modified_ns); + const efi_status s = amend(n->f, nullptr, &t, nullptr); + return s == EFI_SUCCESS ? kal_ok : error_of(s); +} + +int kal_fs_set_modified_at(kal_dir base, const char* name, kal_uintptr len, kal_u64 modified_ns) { + node* d = take(base.h, kDir); + const name16 n(name, len); + if (!d || !n.ok()) return kal_err_invalid; + efi_file_protocol* f = nullptr; + efi_status s = open_beneath(d, n, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0, &f); + if (s != EFI_SUCCESS) return error_of(s); + const efi_time t = okuefi::time_of(modified_ns); + s = amend(f, nullptr, &t, nullptr); + f->close(f); + return s == EFI_SUCCESS ? kal_ok : error_of(s); +} + +// ── what the volume does not have (clause 6.2: not claimed in kal_fs_props) ── + +int kal_fs_set_executable_at(kal_dir base, const char*, kal_uintptr, int) { + return take(base.h, kDir) ? kal_err_not_supported : kal_err_invalid; +} +int kal_fs_lock(kal_file file, kal_u64, kal_u64, kal_uintptr) { + return take(file.h, kFile) ? kal_err_not_supported : kal_err_invalid; +} +int kal_fs_unlock(kal_file file, kal_u64, kal_u64) { + return take(file.h, kFile) ? kal_err_not_supported : kal_err_invalid; +} +int kal_fs_link_create(kal_dir base, const char*, kal_uintptr, const char*, kal_uintptr, kal_uintptr) { + return take(base.h, kDir) ? kal_err_not_supported : kal_err_invalid; +} +kal_intptr kal_fs_link_read(kal_dir base, const char*, kal_uintptr, char*, kal_uintptr) { + return take(base.h, kDir) ? -kal_err_not_supported : -kal_err_invalid; +} +int kal_fs_hard_link(kal_dir from, const char*, kal_uintptr, kal_dir to, const char*, kal_uintptr) { + return take(from.h, kDir) && take(to.h, kDir) ? kal_err_not_supported : kal_err_invalid; +} +kal_intptr kal_fs_resolved_name(kal_dir base, const char*, kal_uintptr, char*, kal_uintptr) { + return take(base.h, kDir) ? -kal_err_not_supported : -kal_err_invalid; +} +int kal_fs_dir_attenuate(kal_dir d, const kal_dir_rights*, kal_dir*) { + return take(d.h, kDir) ? kal_err_not_supported : kal_err_invalid; +} + +// ── capacity ──────────────────────────────────────────────────────────────── + +int kal_fs_capacity(kal_dir d, kal_u64* total, kal_u64* available) { + node* n = take(d.h, kDir); + if (!n) return kal_err_invalid; + alignas(8) unsigned char small[256]; + void* buf = small; + efi_uintn size = sizeof small; + efi_status s = n->f->get_info(n->f, &kFileSystemInfo, &size, buf); + if (s == EFI_BUFFER_TOO_SMALL) { + buf = kal_alloc(size, 8); + if (!buf) return kal_err_no_memory; + s = n->f->get_info(n->f, &kFileSystemInfo, &size, buf); + } + if (s == EFI_SUCCESS) { + const auto* i = static_cast(buf); + if (total) *total = i->volume_size; + if (available) *available = i->free_space; + } + if (buf != small) kal_free(buf, 0, 0); + return s == EFI_SUCCESS ? kal_ok : error_of(s); +} + +// ── enumeration ───────────────────────────────────────────────────────────── +// +// A listing is a second handle on the directory ("."), whose reads give an EFI_FILE_INFO for each +// entry; the directory's own two entries are not reported. The listing is a slot, released at its +// end or with its directory. + +int kal_fs_list_begin(kal_dir d, kal_uintptr* iter) { + node* n = take(d.h, kDir); + if (!n || !iter) return kal_err_invalid; + efi_file_protocol* f = nullptr; + const efi_status s = n->f->open(n->f, &f, u".", EFI_FILE_MODE_READ, 0); + if (s != EFI_SUCCESS) return error_of(s); + const kal_uintptr h = issue(kList, f); + if (!h) { f->close(f); return kal_err_no_memory; } + take(h, kList)->owner = d.h; + *iter = h; + return kal_ok; +} + +int kal_fs_list_next_info(kal_dir d, kal_uintptr* iter, char* name_out, kal_uintptr name_cap, + kal_uintptr* name_len, kal_u32 wanted, kal_node_info* info) { + if (!take(d.h, kDir) || !iter || *iter == 0) return kal_err_invalid; + node* l = take(*iter, kList); + if (!l || l->owner != d.h) return kal_err_invalid; + if (info && info->self_size < 2 * sizeof(kal_u32)) return kal_err_invalid; + alignas(8) unsigned char small[kFileInfoFixed + 2 * 260]; + void* buf = small; + efi_uintn cap = sizeof small; + for (;;) { + efi_uintn size = cap; + efi_status s = l->f->read(l->f, &size, buf); + if (s == EFI_BUFFER_TOO_SMALL) { + if (buf != small) kal_free(buf, 0, 0); + buf = kal_alloc(size, 8); + if (!buf) return kal_err_no_memory; + cap = size; + s = l->f->read(l->f, &size, buf); + } + if (s != EFI_SUCCESS) { if (buf != small) kal_free(buf, 0, 0); return error_of(s); } + if (size == 0) { + // The end: the iterator is released and becomes zero, with a name of length zero. + l->f->close(l->f); + retire(*l); + *iter = 0; + if (name_len) *name_len = 0; + if (info) { + info->present = 0; + } + if (buf != small) kal_free(buf, 0, 0); + return kal_ok; + } + const auto* e = static_cast(buf); + const efi_char16* w = e->file_name; + if ((w[0] == u'.' && w[1] == 0) || (w[0] == u'.' && w[1] == u'.' && w[2] == 0)) continue; + const kal_uintptr n = okuefi::to_utf8(w, length16(w), name_out, name_cap); + if (name_len) *name_len = n; + int r = kal_ok; + if (info) r = describe(e, wanted, info); + if (buf != small) kal_free(buf, 0, 0); + return r; + } +} + +int kal_fs_list_next(kal_dir d, kal_uintptr* iter, char* name_out, kal_uintptr name_cap, + kal_uintptr* name_len, int* kind) { + kal_node_info i{}; + i.self_size = sizeof i; + const int r = kal_fs_list_next_info(d, iter, name_out, name_cap, name_len, KAL_INFO_KIND, &i); + if (r == kal_ok && kind) *kind = *iter ? i.kind : kal_node_absent; + return r; +} + +} // extern "C" diff --git a/src/kal.cpp b/src/kal.cpp index 711c65d..49a4b63 100644 --- a/src/kal.cpp +++ b/src/kal.cpp @@ -10,6 +10,8 @@ // openkal.stream SIMPLE_TEXT_OUTPUT_PROTOCOL / SIMPLE_TEXT_INPUT_PROTOCOL // openkal.memory AllocatePool / FreePool // openkal.abort Exit +// openkal.fs SIMPLE_FILE_SYSTEM_PROTOCOL / FILE_PROTOCOL (fs.cpp, 0.11.0) +// openkal.random RNG_PROTOCOL (random.cpp, 0.11.0) // // `process` and `task` are absent because UEFI has no process model — an // application is the only thing running, and an interface provided in part @@ -131,6 +133,7 @@ kal_stream kal_stderr(void) { return kal_stream{kStderr}; } kal_intptr kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) { if (!g_st) return -kal_err_io; + if (okuefi::file_stream(s.h)) return okuefi::file_write(s.h, buf, n); efi_simple_text_output_protocol* out = s.h == kStdout ? g_st->con_out : s.h == kStderr ? error_console() @@ -145,19 +148,24 @@ kal_intptr kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) { // loses every key that is not a plain character. Returning "no bytes, no error" // is end of input, which is the truthful reading of a console this // implementation does not attempt to interpret. -kal_intptr kal_stream_read(kal_stream s, void*, kal_uintptr) { +// +// A file's stream (openkal.fs, 0.11.0) is read from the file. +kal_intptr kal_stream_read(kal_stream s, void* buf, kal_uintptr n) { + if (okuefi::file_stream(s.h)) return okuefi::file_read(s.h, buf, n); if (s.h != kStdin) return -kal_err_invalid; return 0; } // The console is unbuffered by this implementation; the bytes were handed to -// firmware when the write returned. +// firmware when the write returned. A file's stream is flushed by the firmware. int kal_stream_flush(kal_stream s) { + if (okuefi::file_stream(s.h)) return okuefi::file_flush(s.h); if (s.h != kStdin && s.h != kStdout && s.h != kStderr) return kal_err_invalid; return kal_ok; } kal_uintptr kal_stream_props(kal_stream s) { + if (okuefi::file_stream(s.h)) return 0; if (s.h != kStdin && s.h != kStdout && s.h != kStderr) return 0; return KAL_STREAM_PROP_INTERACTIVE; } diff --git a/src/random.cpp b/src/random.cpp new file mode 100644 index 0000000..f59bed0 --- /dev/null +++ b/src/random.cpp @@ -0,0 +1,58 @@ +// openkal.random on Boot Services (0.11.0): EFI_RNG_PROTOCOL, the firmware's source, found with +// LocateProtocol and asked for its default algorithm. +// +// WHETHER THE FIRMWARE HAS A SOURCE IS KNOWN ONLY WHEN THE PROGRAM RUNS. Clause 6.1 states the +// absence of a source as the absence of this interface at the link; an application built once for +// x86_64-uefi meets firmware with a source (a driver over the processor's instruction, or over a +// virtual device) and firmware without one, and the link cannot know which. The interface is +// therefore provided, and firmware that installs no RNG protocol is answered with +// kal_err_not_supported and no byte filled: not kal_err_again, which states a source that will +// fill later, and not bytes from a clock, which are predictable to an adversary (random.h). +// +// No position of kal_random_props is claimed. The firmware's source answers at once or reports +// that it is not ready (kal_err_again), so it does not block; whether it is a device of the +// processor or of a board is not stated by the protocol, so HARDWARE is not claimed. +#include + +#include "uefi.h" +#include "state.h" + +namespace { + +efi_rng_protocol* source() { + static efi_rng_protocol* rng = nullptr; + if (!rng) { + const okuefi::state& s = okuefi::self(); + void* p = nullptr; + if (s.st && s.st->boot_services->locate_protocol(&kRngProtocol, nullptr, &p) == EFI_SUCCESS) + rng = static_cast(p); + } + return rng; +} + +} // namespace + +extern "C" { + +int kal_random_fill(void* out, kal_uintptr len) { + if (len == 0) return kal_ok; + if (!out) return kal_err_invalid; + efi_rng_protocol* rng = source(); + if (!rng) return kal_err_not_supported; + // In parts, since a source may bound one request; every part or none is a success. + auto* p = static_cast(out); + constexpr kal_uintptr kPart = 4096; + for (kal_uintptr done = 0; done < len;) { + const kal_uintptr n = len - done < kPart ? len - done : kPart; + const efi_status s = rng->get_rng(rng, nullptr, n, p + done); + if (s == EFI_NOT_READY) return kal_err_again; + if (s == EFI_UNSUPPORTED) return kal_err_not_supported; + if (s != EFI_SUCCESS) return kal_err_io; + done += n; + } + return kal_ok; +} + +kal_uintptr kal_random_props(void) { return 0; } + +} // extern "C" diff --git a/src/start.cpp b/src/start.cpp index bb03d32..23ba1ad 100644 --- a/src/start.cpp +++ b/src/start.cpp @@ -8,6 +8,7 @@ #include "uefi.h" #include "state.h" +#include "text.h" extern "C" int main(int, char**, char**); @@ -64,51 +65,99 @@ void run_initialisers(const void* image_base) { run_section(base, ".CRT\0\0\0\0"); } -// The options the image was started with, as the arguments: UCS-2 text split at spaces, each -// word UTF-8. Options that are not text (a boot option's binary data) are none. +// THE ARGUMENTS (0.11.0). A program the UEFI Shell starts finds EFI_SHELL_PARAMETERS_PROTOCOL on its +// own image handle, holding the command line already divided by the Shell's rules --- quotes and +// the Shell's escape character removed --- and those are the arguments, each UCS-2 made UTF-8. +// Started otherwise (a boot option, another application), the arguments are the image's load +// options, divided here by the same rules: spaces and tabs separate, a double quote begins or ends +// a part in which they do not, and `^' takes the character after it as it is. 0.10.0 divided the +// options at spaces alone, so `"a b"' arrived as two arguments with their quotes. Options that are +// not text (a boot option's binary data) are none. bool printable(const efi_char16* w, efi_uintn n) { for (efi_uintn i = 0; i < n; ++i) - if (w[i] != 0 && (w[i] < 0x20 || w[i] == 0x7f)) return false; + if (w[i] != 0 && w[i] != u'\t' && (w[i] < 0x20 || w[i] == 0x7f)) return false; return true; } -void record_arguments(const efi_loaded_image_protocol* li) { +template +T* pool(efi_uintn count) { + void* p = nullptr; + if (okuefi::self().st->boot_services->allocate_pool(EfiLoaderData, count * sizeof(T), &p) != EFI_SUCCESS) + return nullptr; + return static_cast(p); +} + +void from_shell(const efi_shell_parameters_protocol* sp) { + okuefi::state& s = okuefi::self(); + if (!sp || !sp->argv || sp->argc == 0) return; + efi_uintn bytes = 0; + for (efi_uintn i = 0; i < sp->argc; ++i) { + const efi_char16* a = sp->argv[i] ? sp->argv[i] : u""; + kal_uintptr n = 0; + while (a[n]) ++n; + bytes += okuefi::utf8_length(a, n) + 1; + } + char* text = pool(bytes); + char** argv = pool(sp->argc + 1); + if (!text || !argv) return; + char* o = text; + for (efi_uintn i = 0; i < sp->argc; ++i) { + const efi_char16* a = sp->argv[i] ? sp->argv[i] : u""; + kal_uintptr n = 0; + while (a[n]) ++n; + argv[i] = o; + o += okuefi::to_utf8(a, n, o, okuefi::utf8_length(a, n)); + *o++ = '\0'; + } + argv[sp->argc] = nullptr; + s.argc = static_cast(sp->argc); + s.argv = argv; +} + +void from_load_options(const efi_loaded_image_protocol* li) { okuefi::state& s = okuefi::self(); if (!li) return; const auto* w = static_cast(li->load_options); efi_uintn n = li->load_options_size / sizeof(efi_char16); if (!w || n == 0 || (li->load_options_size & 1u) || !printable(w, n)) return; - while (n && w[n - 1] == 0) --n; - // At most three bytes per UCS-2 unit, one terminator per word. - char* text = nullptr; - if (s.st->boot_services->allocate_pool(EfiLoaderData, n * 3 + n + 1, - reinterpret_cast(&text)) != EFI_SUCCESS) - return; - char** argv = nullptr; - if (s.st->boot_services->allocate_pool(EfiLoaderData, (n / 2 + 2) * sizeof(char*), - reinterpret_cast(&argv)) != EFI_SUCCESS) - return; + for (efi_uintn i = 0; i < n; ++i) + if (w[i] == 0) { n = i; break; } + // An argument is at most its units, with one terminator; there is at most one per two units. + efi_char16* part = pool(n + 1); + char* text = pool(okuefi::utf8_length(w, n) + n + 1); + char** argv = pool(n / 2 + 2); + if (!part || !text || !argv) return; int argc = 0; efi_uintn at = 0; char* o = text; + const auto blank = [](efi_char16 c) { return c == u' ' || c == u'\t'; }; while (at < n) { - while (at < n && w[at] == u' ') ++at; + while (at < n && blank(w[at])) ++at; if (at >= n) break; - argv[argc++] = o; - for (; at < n && w[at] != u' '; ++at) { - const unsigned c = w[at]; - if (c < 0x80) *o++ = static_cast(c); - else if (c < 0x800) { *o++ = static_cast(0xC0 | (c >> 6)); *o++ = static_cast(0x80 | (c & 0x3F)); } - else { *o++ = static_cast(0xE0 | (c >> 12)); *o++ = static_cast(0x80 | ((c >> 6) & 0x3F)); - *o++ = static_cast(0x80 | (c & 0x3F)); } + kal_uintptr k = 0; + bool quoted = false; + for (; at < n && (quoted || !blank(w[at])); ++at) { + if (w[at] == u'^' && at + 1 < n) { part[k++] = w[++at]; continue; } + if (w[at] == u'"') { quoted = !quoted; continue; } + part[k++] = w[at]; } + argv[argc++] = o; + o += okuefi::to_utf8(part, k, o, okuefi::utf8_length(part, k)); *o++ = '\0'; } + s.st->boot_services->free_pool(part); argv[argc] = nullptr; s.argc = argc; s.argv = argv; } +void record_arguments(efi_handle image, const efi_loaded_image_protocol* li) { + void* p = nullptr; + if (okuefi::self().st->boot_services->handle_protocol(image, &kShellParametersProtocol, &p) == EFI_SUCCESS && p) + from_shell(static_cast(p)); + if (okuefi::self().argc == 0) from_load_options(li); +} + } // namespace namespace okuefi { @@ -129,7 +178,8 @@ extern "C" efi_status efi_main(efi_handle image, efi_system_table* st) { efi_loaded_image_protocol* li = nullptr; if (st->boot_services->handle_protocol(image, &kLoadedImageProtocol, reinterpret_cast(&li)) != EFI_SUCCESS) li = nullptr; - record_arguments(li); + s.device = li ? li->device_handle : nullptr; + record_arguments(image, li); // Position zero is the name the program was started by; firmware that gave none gives an empty // name rather than none (openkal.env), in both arrangements. static char name[] = ""; diff --git a/src/state.h b/src/state.h index b75a74c..8222dba 100644 --- a/src/state.h +++ b/src/state.h @@ -1,5 +1,7 @@ // The firmware's handles, kept for the operations that need them. #pragma once +#include + #include "uefi.h" namespace okuefi { @@ -10,9 +12,21 @@ struct state { int argc; char** argv; unsigned long long tsc_per_ms; // the processor's counter, measured against Stall + efi_handle device; // the volume the image was loaded from (openkal.fs, 0.11.0) }; state& self(); void calibrate_clock(); +// A civil time of the firmware as nanoseconds of the wall clock, and the reverse (time.cpp). +kal_u64 ns_of(const efi_time&); +efi_time time_of(kal_u64 ns); + +// The stream of an open file (openkal.fs, fs.cpp): whether a stream's word is one, and its +// transfers. openkal.stream (kal.cpp) carries the console's three and passes the rest here. +bool file_stream(kal_uintptr h); +kal_intptr file_write(kal_uintptr h, const void* buf, kal_uintptr n); +kal_intptr file_read(kal_uintptr h, void* buf, kal_uintptr n); +int file_flush(kal_uintptr h); + } // namespace okuefi diff --git a/src/text.h b/src/text.h new file mode 100644 index 0000000..04e7f79 --- /dev/null +++ b/src/text.h @@ -0,0 +1,82 @@ +// The firmware's text and openkal's: UCS-2 (UTF-16 where a pair occurs) and UTF-8. +// +// A conversion of representation and nothing more. The firmware names files and passes arguments in +// sixteen-bit units; openkal names them in bytes. Each direction is one pass with no table. +#pragma once +#include + +#include "uefi.h" + +namespace okuefi { + +// The UTF-8 length of `n' units of firmware text. A pair of surrogates is one character of four +// bytes; a surrogate alone is encoded as itself, three bytes, rather than refused --- a name the +// firmware gave is reported, not judged. +inline kal_uintptr utf8_length(const efi_char16* w, kal_uintptr n) { + kal_uintptr bytes = 0; + for (kal_uintptr i = 0; i < n; ++i) { + const unsigned c = w[i]; + if (c < 0x80) bytes += 1; + else if (c < 0x800) bytes += 2; + else if (c >= 0xD800 && c < 0xDC00 && i + 1 < n && w[i + 1] >= 0xDC00 && w[i + 1] < 0xE000) { bytes += 4; ++i; } + else bytes += 3; + } + return bytes; +} + +// Writes the UTF-8 form of `n' units at `out', at most `cap' bytes of it, and returns the whole +// length (utf8_length), so a caller with a short buffer learns how long the text is. +inline kal_uintptr to_utf8(const efi_char16* w, kal_uintptr n, char* out, kal_uintptr cap) { + kal_uintptr o = 0; + const auto put = [&](unsigned b) { if (out && o < cap) out[o] = static_cast(b); ++o; }; + for (kal_uintptr i = 0; i < n; ++i) { + unsigned c = w[i]; + if (c >= 0xD800 && c < 0xDC00 && i + 1 < n && w[i + 1] >= 0xDC00 && w[i + 1] < 0xE000) { + c = 0x10000 + ((c - 0xD800) << 10) + (w[i + 1] - 0xDC00); + ++i; + } + if (c < 0x80) put(c); + else if (c < 0x800) { put(0xC0 | (c >> 6)); put(0x80 | (c & 0x3F)); } + else if (c < 0x10000) { put(0xE0 | (c >> 12)); put(0x80 | ((c >> 6) & 0x3F)); put(0x80 | (c & 0x3F)); } + else { put(0xF0 | (c >> 18)); put(0x80 | ((c >> 12) & 0x3F)); put(0x80 | ((c >> 6) & 0x3F)); put(0x80 | (c & 0x3F)); } + } + return o; +} + +// The firmware's form of `n' bytes of UTF-8 at `s', into `out' (room for `cap' units and a NUL). +// Returns the number of units, or -1 for text that is not UTF-8 or does not fit. A character +// beyond the sixteen-bit plane is a pair of surrogates, which a volume that stores UCS-2 keeps +// as two units. +inline long long to_ucs2(const char* s, kal_uintptr n, efi_char16* out, kal_uintptr cap) { + kal_uintptr o = 0; + for (kal_uintptr i = 0; i < n;) { + const unsigned b = static_cast(s[i]); + unsigned c = 0, more = 0; + if (b < 0x80) { c = b; } + else if ((b & 0xE0) == 0xC0) { c = b & 0x1F; more = 1; } + else if ((b & 0xF0) == 0xE0) { c = b & 0x0F; more = 2; } + else if ((b & 0xF8) == 0xF0) { c = b & 0x07; more = 3; } + else return -1; + if (more && i + more >= n) return -1; + for (unsigned k = 1; k <= more; ++k) { + const unsigned t = static_cast(s[i + k]); + if ((t & 0xC0) != 0x80) return -1; + c = (c << 6) | (t & 0x3F); + } + i += 1 + more; + if (c == 0) return -1; + if (c < 0x10000) { + if (o + 1 > cap) return -1; + out[o++] = static_cast(c); + } else { + if (c > 0x10FFFF || o + 2 > cap) return -1; + c -= 0x10000; + out[o++] = static_cast(0xD800 + (c >> 10)); + out[o++] = static_cast(0xDC00 + (c & 0x3FF)); + } + } + out[o] = 0; + return static_cast(o); +} + +} // namespace okuefi diff --git a/src/time.cpp b/src/time.cpp index 8cbc258..6dc8519 100644 --- a/src/time.cpp +++ b/src/time.cpp @@ -31,9 +31,52 @@ long long days_from_civil(long long y, unsigned m, unsigned d) { return era * 146097 + static_cast(doe) - 719468; } +// The civil date of a count of days from 1970-01-01, the inverse of the above. +void civil_from_days(long long z, long long& y, unsigned& m, unsigned& d) { + z += 719468; + const long long era = (z >= 0 ? z : z - 146096) / 146097; + const unsigned doe = static_cast(z - era * 146097); + const unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + const unsigned mp = (5 * doy + 2) / 153; + d = doy - (153 * mp + 2) / 5 + 1; + m = mp < 10 ? mp + 3 : mp - 9; + y = static_cast(yoe) + era * 400 + (m <= 2); +} + } // namespace namespace okuefi { + +// THE FIRMWARE'S TIME AND openkal's. EFI_TIME is a civil date in a zone the time states, or in the +// firmware's local zone when it states none (2047), which is read as UTC: the wall clock and a +// volume's times (FAT records local time and states no zone) are then read alike, and a time +// written to a file reads back as written. +kal_u64 ns_of(const efi_time& t) { + if (t.year == 0 || t.month == 0 || t.day == 0) return 0; + long long secs = days_from_civil(t.year, t.month, t.day) * 86400 + t.hour * 3600 + t.minute * 60 + t.second; + if (t.time_zone != 2047) secs += static_cast(t.time_zone) * 60; // to UTC + return secs < 0 ? 0 : static_cast(secs) * 1000000000ull + t.nanosecond; +} + +efi_time time_of(kal_u64 ns) { + const long long secs = static_cast(ns / 1000000000ull); + long long y = 0; + unsigned m = 0, d = 0; + civil_from_days(secs / 86400, y, m, d); + const long long rest = secs % 86400; + efi_time t{}; + t.year = static_cast(y); + t.month = static_cast(m); + t.day = static_cast(d); + t.hour = static_cast(rest / 3600); + t.minute = static_cast(rest / 60 % 60); + t.second = static_cast(rest % 60); + t.nanosecond = static_cast(ns % 1000000000ull); + t.time_zone = 2047; + return t; +} + void calibrate_clock() { state& s = self(); const unsigned long long a = counter(); @@ -59,9 +102,7 @@ kal_duration kal_time_wall(void) { efi_time t{}; if (!s.st || !s.st->runtime_services || s.st->runtime_services->get_time(&t, nullptr) != EFI_SUCCESS) return 0; - long long secs = days_from_civil(t.year, t.month, t.day) * 86400 + t.hour * 3600 + t.minute * 60 + t.second; - if (t.time_zone != 2047) secs += static_cast(t.time_zone) * 60; // to UTC - return secs < 0 ? 0 : static_cast(secs) * 1000000000ull + t.nanosecond; + return okuefi::ns_of(t); } void kal_time_sleep(kal_duration ns) { diff --git a/src/uefi.h b/src/uefi.h index 3d2d2c7..593445f 100644 --- a/src/uefi.h +++ b/src/uefi.h @@ -167,7 +167,8 @@ static_assert(__builtin_offsetof(efi_runtime_services, get_time) == 24); /* EFI_LOADED_IMAGE_PROTOCOL: what the firmware recorded about this image, and * among it the options it was started with (UCS-2 text when a shell or a boot - * option gives a command line). */ + * option gives a command line) and the device it was loaded from (the volume + * openkal.fs supplies). */ struct efi_loaded_image_protocol { efi_u32 revision; efi_handle parent_handle; @@ -180,6 +181,7 @@ struct efi_loaded_image_protocol { void* image_base; unsigned long long image_size; }; +static_assert(__builtin_offsetof(efi_loaded_image_protocol, device_handle) == 24); static_assert(__builtin_offsetof(efi_loaded_image_protocol, load_options_size) == 48); static_assert(__builtin_offsetof(efi_loaded_image_protocol, load_options) == 56); @@ -223,4 +225,133 @@ inline constexpr efi_guid kMemoryAttributeProtocol = { inline constexpr unsigned long long EFI_MEMORY_XP = 0x0000000000004000ull; inline constexpr unsigned long long EFI_MEMORY_RO = 0x0000000000020000ull; +/* EFI status codes this implementation distinguishes (UEFI 2.10, appendix D). The high bit marks an + * error; a warning has it clear and is not a failure. */ +inline constexpr efi_status EFI_INVALID_PARAMETER = 0x8000000000000002ULL; +inline constexpr efi_status EFI_UNSUPPORTED = 0x8000000000000003ULL; +inline constexpr efi_status EFI_BAD_BUFFER_SIZE = 0x8000000000000004ULL; +inline constexpr efi_status EFI_NOT_READY = 0x8000000000000006ULL; +inline constexpr efi_status EFI_DEVICE_ERROR = 0x8000000000000007ULL; +inline constexpr efi_status EFI_WRITE_PROTECTED = 0x8000000000000008ULL; +inline constexpr efi_status EFI_OUT_OF_RESOURCES = 0x8000000000000009ULL; +inline constexpr efi_status EFI_VOLUME_CORRUPTED = 0x800000000000000AULL; +inline constexpr efi_status EFI_VOLUME_FULL = 0x800000000000000BULL; +inline constexpr efi_status EFI_NO_MEDIA = 0x800000000000000CULL; +inline constexpr efi_status EFI_MEDIA_CHANGED = 0x800000000000000DULL; +inline constexpr efi_status EFI_NOT_FOUND = 0x800000000000000EULL; +inline constexpr efi_status EFI_ACCESS_DENIED = 0x800000000000000FULL; +/* Delete closed the handle and did not delete the file (EFI_FILE_PROTOCOL.Delete). */ +inline constexpr efi_status EFI_WARN_DELETE_FAILURE = 2; +inline constexpr bool efi_error(efi_status s) { return (s >> 63) != 0; } + +/* EFI_SIMPLE_FILE_SYSTEM_PROTOCOL (UEFI 2.10, 13.4): a volume, opened once for its root directory. */ +struct efi_file_protocol; +struct efi_simple_file_system_protocol { + unsigned long long revision; /* 0 */ + efi_status (*open_volume)(efi_simple_file_system_protocol*, efi_file_protocol** root); /* 8 */ +}; +static_assert(__builtin_offsetof(efi_simple_file_system_protocol, open_volume) == 8); +inline constexpr efi_guid kSimpleFileSystemProtocol = { + 0x964E5B22, 0x6459, 0x11D2, {0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B}}; + +/* EFI_FILE_PROTOCOL (UEFI 2.10, 13.5): a file or a directory open on a volume, each operation a + * member of the handle itself. As far as Flush; the revision-2 members (OpenEx and those after it) + * are asynchronous forms this implementation does not use. */ +struct efi_file_protocol { + unsigned long long revision; /* 0 */ + efi_status (*open)(efi_file_protocol*, efi_file_protocol** out, const efi_char16* name, + unsigned long long mode, unsigned long long attributes); /* 8 */ + efi_status (*close)(efi_file_protocol*); /* 16 */ + efi_status (*remove)(efi_file_protocol*); /* Delete: closes the handle in every case 24 */ + efi_status (*read)(efi_file_protocol*, efi_uintn* size, void* buffer); /* 32 */ + efi_status (*write)(efi_file_protocol*, efi_uintn* size, const void* buffer); /* 40 */ + efi_status (*get_position)(efi_file_protocol*, unsigned long long* position); /* 48 */ + efi_status (*set_position)(efi_file_protocol*, unsigned long long position); /* 56 */ + efi_status (*get_info)(efi_file_protocol*, const efi_guid* type, efi_uintn* size, void* buffer); /* 64 */ + efi_status (*set_info)(efi_file_protocol*, const efi_guid* type, efi_uintn size, const void* buffer); /* 72 */ + efi_status (*flush)(efi_file_protocol*); /* 80 */ +}; +static_assert(__builtin_offsetof(efi_file_protocol, open) == 8); +static_assert(__builtin_offsetof(efi_file_protocol, close) == 16); +static_assert(__builtin_offsetof(efi_file_protocol, remove) == 24); +static_assert(__builtin_offsetof(efi_file_protocol, read) == 32); +static_assert(__builtin_offsetof(efi_file_protocol, write) == 40); +static_assert(__builtin_offsetof(efi_file_protocol, get_position) == 48); +static_assert(__builtin_offsetof(efi_file_protocol, set_position) == 56); +static_assert(__builtin_offsetof(efi_file_protocol, get_info) == 64); +static_assert(__builtin_offsetof(efi_file_protocol, set_info) == 72); +static_assert(__builtin_offsetof(efi_file_protocol, flush) == 80); + +/* Open modes and attributes of EFI_FILE_PROTOCOL.Open. A mode is READ, READ|WRITE or + * READ|WRITE|CREATE; writing alone is not one. */ +inline constexpr unsigned long long EFI_FILE_MODE_READ = 0x0000000000000001ULL; +inline constexpr unsigned long long EFI_FILE_MODE_WRITE = 0x0000000000000002ULL; +inline constexpr unsigned long long EFI_FILE_MODE_CREATE = 0x8000000000000000ULL; +inline constexpr unsigned long long EFI_FILE_READ_ONLY = 0x0000000000000001ULL; +inline constexpr unsigned long long EFI_FILE_DIRECTORY = 0x0000000000000010ULL; +/* SetPosition to this value places the position at the end of the file. */ +inline constexpr unsigned long long EFI_FILE_POSITION_END = 0xFFFFFFFFFFFFFFFFULL; + +/* EFI_FILE_INFO (UEFI 2.10, 13.5.16): what GetInfo answers of a node, and what a directory's Read + * gives for each entry. The name follows the fixed part and ends in a NUL; `size' counts both. */ +struct efi_file_info { + unsigned long long size; /* 0 */ + unsigned long long file_size; /* 8 */ + unsigned long long physical_size; /* 16 */ + efi_time create_time; /* 24 */ + efi_time last_access_time; /* 40 */ + efi_time modification_time; /* 56 */ + unsigned long long attribute; /* 72 */ + efi_char16 file_name[1]; /* 80 */ +}; +static_assert(__builtin_offsetof(efi_file_info, file_size) == 8); +static_assert(__builtin_offsetof(efi_file_info, modification_time) == 56); +static_assert(__builtin_offsetof(efi_file_info, attribute) == 72); +static_assert(__builtin_offsetof(efi_file_info, file_name) == 80, "SIZE_OF_EFI_FILE_INFO is 80"); +inline constexpr efi_uintn kFileInfoFixed = 80; +inline constexpr efi_guid kFileInfo = { + 0x09576E92, 0x6D3F, 0x11D2, {0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B}}; + +/* EFI_FILE_SYSTEM_INFO (UEFI 2.10, 13.5.17): the volume a handle is on. */ +struct efi_file_system_info { + unsigned long long size; /* 0 */ + efi_u8 read_only; /* 8 */ + unsigned long long volume_size; /* 16 */ + unsigned long long free_space; /* 24 */ + efi_u32 block_size; /* 32 */ + efi_char16 volume_label[1]; /* 36 */ +}; +static_assert(__builtin_offsetof(efi_file_system_info, read_only) == 8); +static_assert(__builtin_offsetof(efi_file_system_info, volume_size) == 16); +static_assert(__builtin_offsetof(efi_file_system_info, free_space) == 24); +static_assert(__builtin_offsetof(efi_file_system_info, volume_label) == 36); +inline constexpr efi_guid kFileSystemInfo = { + 0x09576E93, 0x6D3F, 0x11D2, {0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B}}; + +/* EFI_RNG_PROTOCOL (UEFI 2.10, 37.5): the firmware's source of random bytes. A null algorithm asks + * for the source's default. */ +struct efi_rng_protocol { + efi_status (*get_info)(efi_rng_protocol*, efi_uintn* list_size, efi_guid* list); /* 0 */ + efi_status (*get_rng)(efi_rng_protocol*, const efi_guid* algorithm, efi_uintn length, + efi_u8* value); /* 8 */ +}; +static_assert(__builtin_offsetof(efi_rng_protocol, get_rng) == 8); +inline constexpr efi_guid kRngProtocol = { + 0x3152BCA5, 0xEADE, 0x433D, {0x86, 0x2E, 0xC0, 0x1C, 0xDC, 0x29, 0x1F, 0x44}}; + +/* EFI_SHELL_PARAMETERS_PROTOCOL (UEFI Shell 2.2, 2.3): what the UEFI Shell installs on the image + * handle of an application it starts --- the command line already divided into arguments by the + * Shell's own rules (quotes and its escape character removed), each UCS-2. */ +struct efi_shell_parameters_protocol { + efi_char16** argv; /* 0 */ + efi_uintn argc; /* 8 */ + void* std_in; /* 16 */ + void* std_out; /* 24 */ + void* std_err; /* 32 */ +}; +static_assert(__builtin_offsetof(efi_shell_parameters_protocol, argv) == 0); +static_assert(__builtin_offsetof(efi_shell_parameters_protocol, argc) == 8); +inline constexpr efi_guid kShellParametersProtocol = { + 0x752F3136, 0x4E16, 0x4FDC, {0xA2, 0x2A, 0xE5, 0xF4, 0x68, 0x12, 0xF4, 0xCA}}; + #endif /* OPENKAL_UEFI_H */ diff --git a/src/version.cpp b/src/version.cpp index b92c484..b8b6b44 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -6,13 +6,15 @@ extern "C" { kal_u64 kal_version(void) { return KAL_VERSION; } -// Firmware supplies a console, a pool, a clock, the options an image was started with, and pages -// of code (openkal.exec, 0.10.0: what a loader of images places their code in). -// `openkal.process' waits for `openkal.fs' (a program is named through a directory handle); -// the rest have no firmware service beneath them here. +// Firmware supplies a console, a pool, a clock, the options an image was started with, pages of code +// (openkal.exec, 0.10.0: what a loader of images places their code in), the volume the image was +// loaded from and a source of random bytes (openkal.fs and openkal.random, 0.11.0). +// `openkal.process' is not provided yet: StartImage of an image found through openkal.fs is what +// it will be, a started program running to its end first (clause 7.19); the rest have no firmware +// service beneath them here. kal_u64 kal_interfaces(void) { return KAL_IFACE_ABORT | KAL_IFACE_STREAM | KAL_IFACE_MEMORY | KAL_IFACE_ENV | KAL_IFACE_TIME | - KAL_IFACE_EXEC; + KAL_IFACE_EXEC | KAL_IFACE_FS | KAL_IFACE_RANDOM; } }