AxlStream — byte-stream abstraction
AxlStream — byte-stream abstraction
AxlStream is the polymorphic byte-source/sink — modeled on POSIX
<stdio.h>’s FILE *. It wraps a vtable over file handles, memory
buffers, the console, the BOM-detecting text decoder, etc. All
public functions that take AxlStream * live in this module.
For path-based filesystem operations (read whole file, dir walk,
volume enumerate, stat), see the sibling AxlFs module
(<axl/axl-fs.h>, source in src/fs/).
Three-layer API shape:
Simple helpers (GLib-style):
axl_print,axl_printerrStream I/O (POSIX-style):
axl_fopen,axl_fread,axl_fprintf,axl_fgets,axl_readline,axl_walk_linesLow-level:
axl_read,axl_write,axl_pread,axl_pwrite
All strings are UTF-8. Paths in axl_fopen are converted to UCS-2
internally. Per-stream wire encoding (UCS-2 LE/BE/ASCII) is
available via axl_stream_set_encoding.
Header: <axl/axl-stream.h>
Overview
AXL provides familiar POSIX-style I/O on top of UEFI’s file protocols.
Paths use UTF-8 with forward slashes (fs0:/path/to/file.txt); AXL
converts to UCS-2 and backslashes internally.
Console Output
axl_printf writes to the UEFI console (ConOut). It’s available
immediately after AXL_APP or axl_driver_init:
axl_printf("Hello %s, value=%d\n", name, 42);
axl_printerr("Error: %s\n", msg); // writes to ConErr
Tee (-o:<file> log support)
axl_stream_set_stdout_tee installs an additional stream that
receives every byte written to axl_stdout — axl_print,
axl_printf, direct axl_write(axl_stdout, ...). Pass NULL to
clear; multiple calls replace the previous tee with no chaining.
The caller owns the tee stream and is responsible for closing it
(typical pairing: axl_atexit):
AxlStream *log = axl_fopen("fs0:\\app.log", "a");
axl_stream_set_stdout_tee(log);
axl_atexit(close_log_stream, log);
axl_printf("greet %s\n", who); /* lands in the console AND log */
A symmetric axl_stream_set_stderr_tee covers axl_printerr.
Tee write errors are swallowed — a broken log file must not break
the primary console.
Interactive console input
axl_stdin (above) is shell-pipe input — bytes the shell captured
from the left-hand side of a |. For interactive prompts where
the tool needs to wait on a real keystroke (y/n confirmations,
“press any key”, arrow-key menus), use <axl/axl-console.h>:
#include <axl/axl-console.h>
axl_print("Continue? [y/n]: ");
AxlKey k;
if (axl_console_read_key(5000, &k) == 0
&& (k.unicode_char == 'y' || k.unicode_char == 'Y'))
{
/* user said yes */
}
Three timeout modes: 0 is non-blocking (returns -1 immediately
if no key is buffered), UINT64_MAX blocks forever,
anything else is a millisecond bound. Pair with
axl_console_flush_input() before a prompt to eat type-ahead.
File Read/Write
The simplest way to read or write files:
// Read entire file into memory
void *data;
size_t len;
if (axl_file_get_contents("fs0:/config.json", &data, &len) == AXL_OK) {
// process data...
axl_free(data);
}
// Write entire file
axl_file_set_contents("fs0:/output.txt", buf, buf_len);
Stream I/O
For line-by-line reading or incremental writes:
AxlStream *f = axl_fopen("fs0:/log.txt", "r");
if (f != NULL) {
char *line;
while ((line = axl_readline(f)) != NULL) {
axl_printf(" %s\n", line);
axl_free(line);
}
axl_fclose(f);
}
Buffer Streams
In-memory streams for building data without files:
AxlStream *buf = axl_open_buffer();
axl_fprintf(buf, "name=%s\n", name);
axl_fprintf(buf, "value=%d\n", value);
void *data;
size_t len;
axl_stream_get_bufdata(buf, &data, &len);
// data contains the formatted text
axl_fclose(buf);
Standard Streams
axl_stream_init populates four globals:
Stream |
Direction |
Encoding |
Backed by |
|---|---|---|---|
|
out |
text (UTF-8 in → UCS-2 to console) |
firmware console (ConOut) |
|
out |
text (same path as stdout) |
firmware console |
|
in |
raw bytes |
|
|
out |
raw bytes |
|
For shell pipe invocations (tool1 | tool2) the LHS output is
captured by the shell into a stream that becomes the RHS’s StdIn, so
axl_read(axl_stdin, ...) consumes the piped bytes.
When the shell-params protocol isn’t published (cross-volume
launches, BDS contexts, non-Shell-2.0 launches), axl_stdin reads
return EOF (0 bytes) and axl_stdout_raw writes return -1 — tools
that opt in should fall back to a file argument or print a clear
error.
Output: text vs binary
The split is symmetric with stdin (raw bytes vs axl_text_stream_wrap
for text decoding):
Use case |
API |
|---|---|
Text output (the common case) |
|
Binary output (RAM-disk dump, captured SPD blob, etc.) |
|
Don’t use axl_stdout_raw for text; the firmware console only knows
UCS-2, so writing raw 8-bit bytes to it (when no shell redirection
is in play) would mangle the display. The raw path is only useful
when the caller knows the shell wired their StdOut to a file or pipe.
Text-Decoding Stream Wrapper
UEFI Shell pipes carry text as UCS-2 LE with a FF FE BOM (because
shell built-ins write to console handles that wrap text that way).
A tool reading axl_stdin directly sees raw UCS-2 bytes. To get
UTF-8 text regardless of source encoding, wrap any byte stream with
axl_text_stream_wrap:
AxlStream *in = axl_text_stream_wrap(axl_stdin); /* or any other AxlStream */
char *line;
while ((line = axl_readline(in)) != NULL) {
/* `line` is UTF-8 — search, parse, etc. */
axl_free(line);
}
axl_fclose(in); /* does NOT close the wrapped src */
BOM detection happens lazily on the first read:
Leading bytes |
Mode |
Behavior |
|---|---|---|
|
UTF-16 LE |
BOM consumed, body transcoded to UTF-8 |
|
UTF-16 BE |
BOM consumed, body transcoded to UTF-8 |
|
UTF-8 BOM |
BOM stripped, body returned verbatim |
anything else |
passthrough |
raw bytes returned as-is |
Transcoding is incremental and bounded-memory regardless of source size — wrap a multi-MB pipe and read line by line. The wrapper holds back partial transcoded sequences when the caller’s buffer cuts mid-character, so reads return valid UTF-8 even for tiny buffers.
The wrapper does not take ownership of src — the caller closes
both eventually. Use this when the source might be UCS-2; pass
binary data through the raw stream (e.g. hexdump reads axl_stdin
directly so it shows wire bytes including the BOM).
Codepoints above U+FFFF (surrogate pairs) round-trip as their UTF-16 code-unit shape, not the proper UTF-8 4-byte form. Almost all real-world UEFI Shell content is BMP-only ASCII / Latin-1 / common scripts; a follow-up can add full SMP support if a real consumer needs it.
File Operations
bool exists = axl_file_exists("fs0:/data.bin");
bool is_dir = axl_file_is_dir("fs0:/logs");
axl_file_delete("fs0:/temp.txt");
axl_file_rename("fs0:/old.txt", "fs0:/new.txt");
axl_dir_mkdir("fs0:/output");
API Reference
Defines
-
axl_printf
Alias for axl_print. Matches the design-doc name.
-
AXL_SEEK_SET
seek from beginning
-
AXL_SEEK_CUR
seek from current position
-
AXL_SEEK_END
seek from end of file
Typedefs
-
typedef struct AxlStream AxlStream
axl-stream.h:
AxlStream — the byte-stream abstraction. Modeled on POSIX
<stdio.h>’sFILE *: a polymorphic handle that backs onto a file, a memory buffer, the console, or any other byte sink/source via an internal vtable. All public functions that take or returnAxlStream *live here.Three-layer API shape:
Simple helpers (GLib-style): axl_print, axl_printerr.
Stream I/O (POSIX-style): axl_fopen, axl_fread, axl_fprintf, axl_fgets, axl_readline, etc.
Low-level: axl_read, axl_write, axl_pread, axl_pwrite.
Filesystem operations (read-whole-file, dir walk, volume enumerate, stat) live in
<axl/axl-fs.h>— they’re path-based, not stream-based. AxlStream only knows about bytes.All strings are UTF-8. Paths in axl_fopen are converted to UCS-2 internally. Per-stream wire encoding (UCS-2 LE/BE/ASCII) is available via axl_stream_set_encoding.
-
typedef long long axl_ssize_t
-
typedef int (*AxlLineFn)(const char *line, size_t len, bool truncated, void *user)
Per-line callback for axl_walk_lines.
linepoints into the caller’s working buffer — valid only for the duration of this callback invocation.lenexcludes the trailing\\n; a\\rimmediately before is left inlinefor callers that want to keep CRLF context (most strip it).truncatedis true when the logical line exceeded the working buffer; in that caselineholds the leadinglenbytes and the rest of the line was discarded from the stream before this callback fired.Return 0 to continue, any non-zero value to stop iteration — the value is propagated back from
axl_walk_lines.
Enums
-
enum AxlEncoding
Wire-side encoding for a stream. The caller always works in UTF-8 —
axl_readreturns UTF-8,axl_writeaccepts UTF-8 — andaxl_stream_set_encodingdeclares what’s on the wire underneath.Default is AXL_ENC_UTF8, which is a passthrough (no transcoding) — existing behavior is unchanged for any stream the consumer doesn’t explicitly configure.
Transcoding is permissive — bad input never produces an error:
Invalid UTF-8 in writes → encoded byte-for-byte as Latin-1.
Invalid wire bytes / surrogate halves in reads → transcoded in their BMP shape (U+D800..U+DFFF round-trip as a 3-byte UTF-8 sequence; lone wire byte at end of stream is dropped).
Codepoints above U+FFFF on encode to UCS-2/ASCII → replaced with
?.
Values:
-
enumerator AXL_ENC_UTF8
default — passthrough, no transcoding
-
enumerator AXL_ENC_UCS2_LE
native UEFI; what the shell pipes use
-
enumerator AXL_ENC_UCS2_BE
network-byte-order UCS-2; rare
-
enumerator AXL_ENC_ASCII
7-bit; high bytes replaced with
?
Functions
-
void axl_stream_init(void)
Initialize the standard stream globals.
Sets up axl_stdout, axl_stderr, axl_stdin, and axl_stdout_raw. Call once at startup (before any axl_print/axl_fprintf or axl_read on axl_stdin). Invoked automatically by axl_runtime_init — most consumers don’t call it directly.
axl_stdin is backed by
EFI_SHELL_PARAMETERS_PROTOCOL.StdInwhen the shell publishes it (the typical case for shell-launched apps including the right-hand side of a|pipe). Reading from axl_stdin then consumes the bytes the shell captured from the left-hand side of the pipe.If the shell-params protocol isn’t published on this image (cross-volume launches, BDS contexts, non-Shell-2.0 launches), axl_stdin reads return EOF (0 bytes). Callers that need to detect “stdin not connected” can issue a single zero-length read and check the result.
-
int axl_stream_set_stdout_tee(AxlStream *extra)
Tee subsequent writes to
extraalongside the console.After this call, every byte written to axl_stdout via
axl_print,axl_printf,axl_fprintf(axl_stdout, ...), or directaxl_write(axl_stdout, ...)is also written toextra. Pass NULL to clear an active tee. Multiple calls replace the previous tee — there is no chain (theextrastream’s ownteefield is intentionally ignored, so an accidental loop just double-writes once instead of recursing).Lifetime: the caller owns
extraand is responsible for closing it. axl-sdk does not close the tee on exit. The typical idiom for a-o:<file>log option is:AxlStream *log = axl_fopen(path, "a"); axl_stream_set_stdout_tee(log); axl_atexit(close_log_stream, log);
The tee target is written with the same UTF-8 caller bytes passed to
axl_write— NOT the post-transcode wire bytes the source produced. If the tee target itself has a non-UTF-8 encoding set via axl_stream_set_encoding, the tee transcodes those caller bytes through its own encoding on the way out (so a UTF-8 source teeing to a UCS-2-LE log file produces a UCS-2-LE log file, not a UTF-8 one).If the primary write fails (returns -1), the tee still fires with the caller bytes — log-on-best-effort. A broken primary console must not cost you the log.
- Parameters:
extra – stream to tee to (NULL clears)
- Returns:
AXL_OK on success, AXL_ERR if axl_stdout isn’t initialized.
-
int axl_stream_set_stderr_tee(AxlStream *extra)
Tee subsequent writes to
extraalongside the stderr console.Symmetric to axl_stream_set_stdout_tee but for axl_stderr.
- Parameters:
extra – stream to tee to (NULL clears)
- Returns:
AXL_OK on success, AXL_ERR if axl_stderr isn’t initialized.
-
int axl_print(const char *fmt, ...)
Print to stdout. Like g_print().
- Parameters:
fmt – printf-style format string
- Returns:
number of bytes written, or -1 on error.
-
int axl_printerr(const char *fmt, ...)
Print to stderr. Like g_printerr().
- Parameters:
fmt – printf-style format string
- Returns:
number of bytes written, or -1 on error.
-
AxlStream *axl_fopen(const char *path, const char *mode)
Open a file stream.
Path is converted to UCS-2 internally.
- Parameters:
path – file path (UTF-8, e.g. “fs0:/data.txt”)
mode – “r” (read), “w” (write/create), “a” (append)
- Returns:
stream, or NULL on error. Close with axl_fclose().
-
void axl_fclose(AxlStream *s)
Close a stream and free resources. NULL-safe.
- Parameters:
s – stream, or NULL
-
size_t axl_fread(void *buf, size_t size, size_t count, AxlStream *s)
Read size*count bytes from stream.
Returns number of complete items read (may be less than count at EOF or on error). Returns 0 on both EOF and error — use axl_read() if you need to distinguish them (-1 = error, 0 = EOF).
- Parameters:
buf – destination buffer
size – item size in bytes
count – number of items
s – stream
-
size_t axl_fwrite(const void *buf, size_t size, size_t count, AxlStream *s)
Write size*count bytes to stream.
Returns number of complete items written.
- Parameters:
buf – source buffer
size – item size in bytes
count – number of items
s – stream
-
int axl_fprintf(AxlStream *s, const char *fmt, ...)
Write formatted text to a stream.
- Parameters:
s – stream
fmt – printf-style format string
- Returns:
number of bytes written, or -1 on error.
-
char *axl_readline(AxlStream *s)
Read one line (up to and including ‘\n’).
Unbounded — grows the internal buffer until ‘\n’ or EOF. For arbitrary input (untrusted files, network streams, output piped from another tool) prefer axl_readline_max so a single oversized line cannot exhaust memory.
Caller frees with axl_free(). Returns NULL at EOF or on error.
- Parameters:
s – stream
-
char *axl_readline_max(AxlStream *s, size_t max_bytes)
Read one line with a memory cap.
Like axl_readline but bounded: stops appending after
max_bytes-1bytes have been buffered. The returned string is always NUL-terminated.Truncation semantics: when the cap is hit before a
\\n, the remaining bytes of that logical line are silently consumed from the stream up to (and including) the next\\nor EOF. The nextaxl_readline_maxcall therefore reads the next logical line — line counting stays meaningful even when individual lines are truncated.Detect truncation via “last char != `\n`”: a complete line ends in
\\n; a truncated one doesn’t (and the byte count equalsmax_bytes-1).Caller frees with axl_free(). Returns NULL at EOF (no bytes read) or on error.
- Parameters:
s – stream
max_bytes – maximum heap-buffered bytes (incl. trailing NUL)
-
void axl_line_reader_init(AxlLineReader *r, AxlStream *s, char *buf, size_t buf_size)
Initialize a line reader.
- Parameters:
r – caller-allocated reader struct
s – source stream (caller-owned)
buf – working buffer (caller-owned, must outlive the reader)
buf_size – buffer size; must be ≥ 2 and is the max line length
-
bool axl_line_reader_next(AxlLineReader *r, const char **line, size_t *len, bool *truncated)
Read the next line from the stream.
On a successful return,
*linepoints into the reader’s working buffer (valid until the next_nextcall) and*lenis the byte count, excluding any trailing\n(a\rfrom a CRLF pair is left for the caller to strip if desired). On truncation,*truncatedis set to true and the rest of the logical line has already been drained from the stream — line counts in the caller stay consistent.- Returns:
true if a line was read, false at EOF or on backend read error. Use axl_line_reader_error to distinguish.
-
bool axl_line_reader_error(const AxlLineReader *r)
True if the most recent read returned a backend error.
Distinguishes EOF (false return + this returns false) from a real read failure (false return + this returns true).
-
int axl_walk_lines(AxlStream *s, char *buf, size_t buf_size, AxlLineFn fn, void *user)
Callback wrapper around AxlLineReader.
Convenience for callers that prefer callback dispatch over iterator-style
while (next(...))loops. Equivalent to:AxlLineReader r; axl_line_reader_init(&r, s, buf, buf_size); while (axl_line_reader_next(&r, &line, &len, &truncated)) { int rc = fn(line, len, truncated, user); if (rc != 0) return rc; } return axl_line_reader_error(&r) ? -1 : 0;Most callers should prefer the reader-struct form for its normal-scope local variables and standard control flow. Use this wrapper only when the per-line work is genuinely stateless or when the dispatch shape simplifies a generic API.
- Parameters:
s – stream
buf – working buffer (caller-owned)
buf_size – buffer capacity (>= 2; doubles as max line length)
fn – per-line callback
user – opaque user pointer for the callback
- Returns:
0 on full traversal to EOF, the callback’s non-zero return if it stopped, or -1 on backend read error or invalid arguments.
-
int axl_fseek(AxlStream *s, int64_t offset, int whence)
Set the stream position.
- Parameters:
s – stream
offset – byte offset (may be negative for CUR/END)
whence – AXL_SEEK_SET, AXL_SEEK_CUR, or AXL_SEEK_END
- Returns:
AXL_OK on success, AXL_ERR on error or if not supported.
-
int64_t axl_ftell(AxlStream *s)
Get the current stream position.
- Parameters:
s – stream
- Returns:
position in bytes, or -1 on error.
-
bool axl_feof(AxlStream *s)
Check if the stream has reached end-of-file.
Set when read returns 0 bytes. Cleared by axl_fseek.
- Parameters:
s – stream
- Returns:
true if at EOF.
-
int axl_fflush(AxlStream *s)
Flush pending writes to the underlying file. NULL-safe.
- Parameters:
s – stream
- Returns:
AXL_OK on success, AXL_ERR on error.
-
AxlEncoding axl_detect_encoding(const void *prefix, size_t len, bool *out_has_bom)
Sniff a file’s text encoding from a leading byte sample.
Recognizes a UTF-8 BOM (EF BB BF), UTF-16 LE BOM (FF FE), and UTF-16 BE BOM (FE FF); failing a BOM, applies a light BOM-less heuristic (interleaved NUL bytes ⇒ UCS-2 LE/BE) and otherwise reports UTF-8.
out_has_bom(optional) is set true when a BOM was present, so a caller can round-trip it on save.- Parameters:
prefix – leading bytes of the file
len – number of bytes available
out_has_bom – [out, optional] BOM present
- Returns:
the detected encoding (UCS-2 variants for UTF-16; UTF-8 is the default).
-
int axl_stream_set_encoding(AxlStream *s, AxlEncoding enc)
Set the wire-side encoding for a stream.
Applies to the byte-I/O primitives — axl_read, axl_write, axl_fread, axl_fwrite, axl_readline, axl_fgets. Does not affect axl_print / axl_printf / axl_printerr — those go through the console_write path which does its own UTF-8→UCS-2 conversion.
Switching encoding mid-stream discards any partial multi-byte sequence that was being buffered under the previous encoding. That avoids silently splicing stale partial bytes onto the new encoding’s byte stream. Likewise, axl_fseek discards transcode buffers — they describe state at the pre-seek position.
- Parameters:
s – stream
enc – wire-side encoding
- Returns:
AXL_OK on success, AXL_ERR if
sis NULL orencis out of range.
-
AxlEncoding axl_stream_get_encoding(AxlStream *s)
Get the current wire-side encoding for a stream.
- Parameters:
s – stream
- Returns:
current encoding (defaults to AXL_ENC_UTF8).
-
char *axl_fgets(char *buf, int size, AxlStream *stream)
Read up to
size-1bytes fromstreamintobuf, stopping at end-of-line or EOF, and NUL-terminate.Like POSIX
fgets(): reads at most one less thansizebytes, stopping after the first newline (which is included inbuf), at EOF, or on error. The buffer is always NUL-terminated when a non-NULL return is delivered.- Parameters:
buf – destination buffer (must be at least
sizebytes)size – buffer size in bytes (incl. NUL)
stream – source stream
- Returns:
bufon success, NULL at EOF (with no bytes read) or on error (use axl_ferror to distinguish).
-
int axl_vfprintf(AxlStream *stream, const char *fmt, va_list ap)
Write formatted text to a stream (va_list variant).
Like POSIX
vfprintf(). The axl_fprintf entry point wraps this for the variadic case.- Parameters:
stream – stream
fmt – printf-style format string
ap – argument list
- Returns:
number of bytes written, or -1 on error.
-
bool axl_ferror(AxlStream *stream)
Test the sticky error indicator on a stream.
Set by any backend read/write/seek error. Mirror of POSIX
ferror(). Cleared by axl_clearerr.- Parameters:
stream – stream
- Returns:
true if an error has been signaled on
stream.
-
void axl_clearerr(AxlStream *stream)
Clear both the EOF and error indicators on
stream.Mirror of POSIX
clearerr().- Parameters:
stream – stream
-
AxlStream *axl_text_stream_wrap(AxlStream *src)
Wrap a raw byte stream as a UTF-8 text stream.
Source encoding is classified at construction time. In priority:
BOM:
FF FE→ UTF-16 LE; BOM consumed, body transcoded to UTF-8FE FF→ UTF-16 BE; BOM consumed, body transcoded to UTF-8EF BB BF→ UTF-8 BOM; consumed, body returned verbatim
Headerless UCS-2 sniff (≥16 bytes available, no BOM): if every odd-position byte is 0x00 the source is treated as UCS-2 LE; if every even-position byte is 0x00, UCS-2 BE. The sniffed bytes are non-consuming and re-emerge on the first read. This catches UEFI shells that write UCS-2 LE without a BOM (
some-cmd > out.txt). UTF-8 ASCII text never matches (no NULs anywhere); the remaining false-positive risk is binary content with NULs at every alternate byte — wrap such streams only if they’re known to be text.Otherwise → passthrough (raw bytes returned as-is).
Transcoding is incremental and bounded-memory regardless of source size — wrap a multi-MB pipe and read line by line. Returned reads are valid UTF-8 even when the caller’s buffer cuts mid-character (the wrapper holds back partial transcoded sequences for the next call).
Useful for shell-pipe consumers in UEFI: the shell wraps text output as UCS-2 LE, so wrapping
axl_stdingives every text- oriented tool transparent UTF-8 input regardless of whether the upstream is a built-in (UCS-2), an AXL tool that wrote viaaxl_print(UCS-2 after console conversion), or a binary tool that wrote raw UTF-8 (passthrough).The wrapper does not take ownership of
src— the caller is responsible for closing both eventually.Surrogate-half caveat. Codepoints above U+FFFF are encoded in UTF-16 as a pair of surrogate code units (U+D800-U+DFFF). This wrapper transcodes each code unit independently as a 3-byte UTF-8 sequence rather than combining the pair into the proper UTF-8 4-byte form. The output is not strictly valid UTF-8 for codepoints > U+FFFF: lone-surrogate sequences will be rejected by strict UTF-8 validators (axl_utf8_validate, JSON encoders, MultiByteToWideChar-style decoders). Lenient consumers (grep, substring search, console display) tolerate it. Almost all real-world UEFI Shell content is BMP-only ASCII / Latin-1 / common scripts, so this hasn’t bitten in practice; if a real consumer needs proper SMP support, a follow-up can add the surrogate-pair combiner.
- Parameters:
src – source byte stream (caller-owned)
- Returns:
wrapper stream (free with axl_fclose), or NULL on OOM or NULL
src.
-
AxlStream *axl_bufopen(void)
Create an in-memory buffer stream.
Supports read, write, pread, pwrite.
- Returns:
stream, or NULL on allocation failure.
-
const void *axl_bufdata(AxlStream *s, size_t *size)
Peek at buffer contents without consuming.
The returned pointer is owned by the stream and invalidated by writes or close.
- Parameters:
s – buffer stream
size – (out, optional): buffer size
-
void *axl_bufsteal(AxlStream *s, size_t *size)
Transfer ownership of buffer to caller.
Stream becomes empty. Caller frees with axl_free().
- Parameters:
s – buffer stream
size – (out, optional): buffer size
-
axl_ssize_t axl_read(AxlStream *s, void *buf, size_t count)
Read up to count bytes from stream at current position.
- Parameters:
s – stream
buf – destination buffer
count – max bytes to read
- Returns:
bytes read, 0 at EOF, -1 on error.
-
axl_ssize_t axl_write(AxlStream *s, const void *buf, size_t count)
Write count bytes to stream at current position.
- Parameters:
s – stream
buf – source buffer
count – bytes to write
- Returns:
bytes written, -1 on error.
-
axl_ssize_t axl_pread(AxlStream *s, void *buf, size_t count, size_t offset)
Read up to count bytes at offset without changing stream position.
- Parameters:
s – stream
buf – destination buffer
count – max bytes to read
offset – byte offset to read from
- Returns:
bytes read, -1 on error or if not supported.
-
axl_ssize_t axl_pwrite(AxlStream *s, const void *buf, size_t count, size_t offset)
Write count bytes at offset without changing stream position.
- Parameters:
s – stream
buf – source buffer
count – bytes to write
offset – byte offset to write at
- Returns:
bytes written, -1 on error or if not supported.
Variables
-
AxlStream *axl_stdout_raw
axl_stdout_raw — sibling of axl_stdout for binary output. Writes via
EFI_SHELL_PARAMETERS_PROTOCOL.StdOut->WriteFiledirectly, bypassing the UTF-8→UCS-2 conversion that axl_stdout (and axl_print / axl_fprintf) does for console output. Use when a tool needs bytes to traverse a pipe intact (dumping a RAM-disk image, SPD blob, etc.).Symmetric with axl_stdin (which is also raw bytes); axl_stdout remains the text-output path.
axl_write(axl_stdout_raw, ...)returns -1 if the shell-params protocol isn’t published — there’s no sensible console fallback for binary bytes (the firmware console mangles non-CHAR16 input). Tools that opt in should print a clear error in that case.
-
struct AxlLineReader
- #include <axl-stream.h>
Stateful line reader for chunk-buffered streams.
Iterate line-by-line over a stream using a caller-supplied working buffer. Constant memory regardless of input size; the line slice on each
nextcall points into the working buffer and is invalidated by the next call.AxlLineReader r; char buf[64 * 1024]; axl_line_reader_init(&r, stream, buf, sizeof(buf)); const char *line; size_t len; bool truncated; while (axl_line_reader_next(&r, &line, &len, &truncated)) { // use line[0..len) — invalidated by next call }Lines longer than
buf_size-1fire onenextcall withtruncated == truecarrying the prefix; the rest of the logical line is consumed before the next call. The buffer doubles as the maximum line length.Fields are internal — callers must not touch them. Stack- allocate the struct and pass &reader to the API. No teardown function is needed; ownership of the working buffer stays with the caller.
AxlConsole — interactive console input
Interactive console input — single-keystroke read with timeout.
axl_stdin (in axl-stream.h) is shell-pipe input only: bytes the shell captured from the left-hand side of a |. Tools that need to wait on a real keystroke (y / n
prompts, “press
any key”, arrow-key menus) reach for the SimpleTextInputProtocol
ReadKeyStroke path, which axl-console wraps with a timeout so callers don’t have to manage timer events by hand.
AxlKey k;
axl_print("Continue? [y/n]: ");
int rc = axl_console_read_key(5000, &k); // 5-second timeout
if (rc == 0 && (k.unicode_char == 'y' || k.unicode_char == 'Y')) {
// proceed
}
Functions
-
int axl_console_read_key(uint64_t timeout_ms, AxlKey *out)
Read one keystroke from the console, blocking up to
timeout_ms.Three timeout modes:
0— non-blocking. Returns -1 immediately if no key is already buffered.UINT64_MAX— block forever until a keystroke arrives.any other — block at most
timeout_msmilliseconds; returns -1 on timeout withoutuntouched.
Internally creates a timer event (when
timeout_msis finite), waits on the union of {ConIn WaitForKey, timer}, then reads one keystroke. The timer is closed before return.- Parameters:
timeout_ms – 0 / UINT64_MAX / millisecond bound
out – [out] decoded keystroke (must be non-NULL)
- Returns:
AXL_OK on key read (with
outpopulated), -1 on timeout, no console available, or backend error.
-
void axl_console_flush_input(void)
Drain any buffered keystrokes.
Discards every keystroke currently in the ConIn queue. Useful before a prompt to eat type-ahead, or after a long-running operation that may have accumulated stray keys. NULL-safe and no-op on consoles where ConIn is unavailable.
-
struct AxlKey
- #include <axl-console.h>
One decoded keystroke, in the shape the UEFI Simple Text Input Protocol delivers it. Exactly one of scan_code and unicode_char carries the user’s intent: printable keys leave scan_code = 0; special keys (arrows, F-keys, Esc, Home/End, etc.) leave unicode_char = 0.
AxlFs — filesystem operations
Defines
-
AXL_FILE_WRITER_APPEND
Append to an existing file instead of replacing it: writes go at the current end of file rather than truncating it to empty first.
-
AXL_FILE_WRITER_EXCL
Exclusive create: fail (return NULL) if
pathalready exists. Backs a PUT withIf-None-Match: *(create-only). Ignored together with APPEND would contradict; pass at most one of the two.
-
AXL_FS_OPEN_READ
open for reading
Open-mode flags for
axl_fopen(axl-stream) and the<axl/axl-fs-provider.h>opencallback.READ (0x1) and WRITE (0x2) are bit-identical to the corresponding
EFI_FILE_MODE_*constants; CREATE is renumbered (0x4 here vs0x8000000000000000forEFI_FILE_MODE_CREATE). The SDK thunk at the AxlFsProvider boundary translates explicitly.
-
AXL_FS_OPEN_WRITE
open for writing (requires READ)
-
AXL_FS_OPEN_CREATE
create if missing (requires WRITE)
-
AXL_FS_ATTR_READ_ONLY
File / directory attribute bits — mirror EFI_FILE_* attribute bits in axl shape. Used in
AxlFsEntry.attributesand theattributesparameter to the fs-provider open callback.
-
AXL_FS_ATTR_HIDDEN
-
AXL_FS_ATTR_SYSTEM
-
AXL_FS_ATTR_DIRECTORY
-
AXL_FS_ATTR_ARCHIVE
-
AXL_FS_ENTRY_VERSION
Current
AxlFsEntry.versionvalue emitted by the SDK. Bumped when the struct gains a new field. Forward-compat: callers testentry.struct_size >= offsetof(AxlFsEntry, new_field) + sizeof(new_field)before reading anything added in version > 1.
Typedefs
-
typedef struct AxlFileWriter AxlFileWriter
Streaming file writer — the write peer of AxlFileView.
Writes a file incrementally without buffering the whole payload in memory, the way
axl_file_viewreads out-of-core. This is what backs a WebDAV PUT or any large upload that can’t fitaxl_file_set_contents’s whole-buffer model (a BMC mounting a multi-GB ISO, say). Eachaxl_file_writer_writegoes straight to the file; nothing is held in RAM between calls.Opaque handle; create with
axl_file_writer_open, finalize withaxl_file_writer_close(which flushes). Not thread-safe (UEFI is single-threaded for file I/O).
-
typedef void (*AxlProgressFunc)(uint64_t done, uint64_t total, void *ctx)
Progress callback for long-running I/O operations.
- Param done:
bytes transferred so far
- Param total:
total bytes (0 if unknown)
- Param ctx:
caller context pointer
-
typedef int (*AxlDirWalkFn)(const char *full_path, const AxlFsEntry *entry, void *user)
Per-entry callback for axl_dir_walk.
Return codes:
0 continue walking
>0 stop (propagated as the walk’s return value)
<0 stop with error (propagated)
- Param full_path:
full path to the entry (root + separator + name)
- Param entry:
the AxlFsEntry, including name, size, attributes
- Param user:
opaque user pointer passed through from caller
Functions
-
int axl_file_get_contents(const char *path, void **buf, size_t *len)
Read entire file into memory. Like g_file_get_contents().
axl-fs.h:
Filesystem operations — path-based file and directory APIs, volume enumeration, file metadata. Mirrors the POSIX split:
<axl/axl-stream.h>is the<stdio.h>analog (FILE * / streams); this header is the<sys/stat.h>+<dirent.h>+<sys/statvfs.h>analog.All paths are UTF-8; backend converts to UCS-2 internally. High-level convenience wrappers (
axl_file_get_contents) layer on top of axl_fopen — they’re path-based shortcuts, not stream primitives.- Parameters:
path – file path (UTF-8)
buf – (out): file contents (caller frees with axl_free)
len – (out): file size in bytes
- Returns:
AXL_OK on success, AXL_ERR on error.
-
AxlBytes *axl_file_get_bytes(const char *path)
Read an entire file into an immutable AxlBytes.
Like axl_file_get_contents but returns the contents as a reference-counted AxlBytes — the shareable currency for passing file data to parsers, hashers, or multiple readers without copying. The read buffer is wrapped without an extra copy (axl_bytes_new_take). An empty file yields a valid empty AxlBytes (size 0).
- Parameters:
path – file path (UTF-8)
- Returns:
a new AxlBytes (release with axl_bytes_unref), or NULL on read error or allocation failure.
-
int axl_file_set_contents(const char *path, const void *buf, size_t len)
Write entire buffer to file (creates or overwrites).
Like g_file_set_contents().
- Parameters:
path – file path (UTF-8)
buf – data to write
len – data size in bytes
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_file_write_atomic(const char *path, const void *buf, size_t len)
Crash-safely write an entire buffer to a file.
Writes
bufto a temporary sibling (“<path>.tmp”), flushes it (close implies flush on the UEFI FAT driver), then replacespathwith it via rename. Unlike axl_file_set_contents — which truncates the target and writes in place, so a power loss mid-write leaves the target half-written — this never modifies the target until the full, flushed contents exist in the temp file.On UEFI/FAT a rename cannot atomically replace an existing file, so when
pathalready exists the replace is delete-then-rename: a power loss in that window leaves the target momentarily absent but the complete data still present in “<path>.tmp” (recoverable) — never a half-written target. The temp file is removed on any failure.pathmust be in a writable directory; the temp sibling is created in the same directory (same-directory rename is the FAT atomic case).- Parameters:
path – target file path (UTF-8)
buf – data to write
len – data size in bytes
- Returns:
AXL_OK on success, AXL_ERR on any failure (the target is left untouched if the temp write fails).
-
AxlFileWriter *axl_file_writer_open(const char *path, uint32_t flags)
Open a file for incremental writing.
Creates
pathif it does not exist. By default (flags 0) an existing file is truncated to empty first, so the writer replaces its contents (WebDAV PUT semantics) with no stale tail when the new content is shorter. WithAXL_FILE_WRITER_APPEND, existing content is kept and writes go at the current end of file. WithAXL_FILE_WRITER_EXCL, the open fails ifpathalready exists.Not atomic: unlike
axl_file_write_atomic, the target is written in place (truncated up front), so an aborted stream or a power loss mid-write leaves it partially written. Out-of-core streaming precludes the temp-file-then-rename trick — a caller that needs all-or-nothing must write to a temp path and rename on close itself.- Parameters:
path – file path (UTF-8)
flags – AXL_FILE_WRITER_* (0 = create / replace)
- Returns:
new writer, or NULL on open failure / OOM / EXCL-and-exists. Free with
axl_file_writer_close.
-
int axl_file_writer_write(AxlFileWriter *w, const void *buf, size_t len)
Append
lenbytes to the writer.Writes straight through to the file. A short write (fewer bytes accepted by the firmware than requested) is reported as AXL_ERR and puts the writer in a failed state: subsequent writes return AXL_ERR without further I/O, and the file is left partially written — the caller should stop and
axl_file_writer_closeit. A failed write may already have advanced the file by some bytes.- Parameters:
w – writer
buf – data to append
len – number of bytes (0 is a no-op success)
- Returns:
AXL_OK on success, AXL_ERR on write error, a prior failed state, or NULL args.
-
uint64_t axl_file_writer_tell(const AxlFileWriter *w)
Bytes written through this writer so far.
Total bytes accepted by successful
axl_file_writer_writecalls (plus the pre-existing length when opened with APPEND). Lets a PUT handler verify it received the advertisedContent-Length.- Parameters:
w – writer
- Returns:
byte count, or 0 if
wis NULL.
-
int axl_file_writer_close(AxlFileWriter *w)
Flush and close the writer, releasing it. NULL-safe.
Closing flushes outstanding data to the underlying volume (UEFI FAT close implies flush). The writer is freed regardless of the flush result, so a PUT handler that needs durability MUST check this return (a failed flush means report 5xx, not 201). There is deliberately no AXL_AUTOPTR cleanup binding for AxlFileWriter: an implicit close would discard this load-bearing flush status. C++ callers close explicitly.
- Parameters:
w – writer (NULL-safe)
- Returns:
AXL_OK on success (or
w== NULL), AXL_ERR if the final flush/close failed or the writer was already in a failed state.
-
bool axl_file_is_dir(const char *path)
Check if a path refers to a directory.
- Parameters:
path – file path (UTF-8)
- Returns:
true if directory, false otherwise or on error.
-
static inline bool axl_fs_entry_is_dir(const AxlFsEntry *e)
Convenience: test the DIRECTORY attribute bit.
-
static inline bool axl_fs_entry_is_read_only(const AxlFsEntry *e)
Convenience: test the READ_ONLY attribute bit.
-
int axl_file_info(const char *path, AxlFsEntry *entry)
Get file metadata for a path. Wraps UEFI EFI_FILE_INFO.
entry->nameis populated with the basename; the rest of the fields hold the file’s stat data.- Parameters:
path – file path (UTF-8)
entry – [out] receives file metadata
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_file_delete(const char *path)
Delete a file.
- Parameters:
path – file path (UTF-8)
- Returns:
AXL_OK on success, AXL_ERR on error.
-
int axl_file_rename(const char *old_path, const char *new_path)
Rename a file within its current directory.
new_pathmay be a basename only (“bar.txt”) or a full path with the same directory prefix asold_path(“fs0:\dir\bar.txt” given “fs0:\dir\foo.txt”). Cross-directory renames are refused with AXL_ERR — most UEFI FAT drivers can’t move a file across directories via SetFileInfo. Use axl_file_move for cross-directory cases (it falls back to copy + delete).- Parameters:
old_path – current path (UTF-8)
new_path – new path or basename (UTF-8); same dir as
old_path
- Returns:
AXL_OK on success, AXL_ERR on cross-directory request, missing source, or backend failure.
-
int axl_file_move(const char *old_path, const char *new_path)
Move a file. Same-directory case is an atomic rename; cross-directory falls back to copy + delete.
Tries axl_file_rename first (atomic on FAT for same-directory). On refusal — typically because
new_path'sdirectory differs fromold_path's— falls back to chunked stream copy followed by source delete.Overwrite semantics: an existing file at
new_pathis replaced (matches POSIXrename(2)). The implementation removesnew_patheagerly before attempting the rename / copy — this is NOT atomic. If the subsequent move then fails, the prior destination is gone; the source remains for retry. Callers that want “fail-if-exists” must probe with axl_file_info first.Failure modes (the fallback is NOT atomic — no rollback):
Copy fails mid-stream → partial destination file exists; source is untouched. Caller can retry or clean up
new_path.Copy succeeds but delete fails → both files exist. Caller can retry the delete.
Callers needing atomicity across directories must orchestrate temp-file + rename themselves at a higher layer.
- Parameters:
old_path – current path (UTF-8)
new_path – new path (UTF-8); may be in a different directory
- Returns:
AXL_OK on success; AXL_ERR if either source missing, destination unwritable, copy fails, or delete fails.
-
int axl_dir_mkdir(const char *path)
Create a directory.
- Parameters:
path – directory path (UTF-8)
- Returns:
AXL_OK on success, AXL_ERR on error (including if it already exists).
-
int axl_dir_rmdir(const char *path)
Remove an empty directory.
- Parameters:
path – directory path (UTF-8)
- Returns:
AXL_OK on success, AXL_ERR on error (including if not empty).
-
AxlDir *axl_dir_open(const char *path)
Open a directory for iteration.
- Parameters:
path – directory path (UTF-8)
- Returns:
directory handle, or NULL on error.
-
bool axl_dir_read(AxlDir *dir, AxlFsEntry *entry)
Read the next directory entry.
entry->nameis populated with the entry’s basename;entry->attributescarries the kind bits (AXL_FS_ATTR_DIRECTORYfor sub-dirs, etc.).- Parameters:
dir – directory handle
entry – [out] receives entry
- Returns:
true if an entry was read, false at end of directory.
-
void axl_dir_close(AxlDir *dir)
Close a directory handle. NULL-safe.
- Parameters:
dir – directory handle
-
int axl_dir_walk(const char *root, AxlDirWalkFn fn, void *user, int max_depth)
Recursively walk a directory tree.
Calls
fnon every entry (excluding.and..) underroot, descending into subdirectories automatically. Each entry’s full path is constructed with separator deduplication so the callback sees clean paths regardless of whetherroothas a trailing/or\. Recursion is post-callback — the walker invokesfnon a directory entry first, then descends into it.max_depthmatches POSIXfind -maxdepth: it caps the deepest level the callback runs at, where root’s immediate children are level 1, their children are level 2, and so on.max_depth = 1lists root’s immediate children only.max_depth = Nlists at most N levels of nesting below root.max_depth <= 0is rejected (returns -1).
- Parameters:
root – starting directory
fn – per-entry callback
user – opaque user pointer for the callback
max_depth – maximum nesting level visited (>=1)
- Returns:
0 on a clean traversal, the callback’s non-zero return value if it stopped the walk, or -1 if
rootcould not be opened or arguments are invalid.
-
int axl_dir_list_json(const AxlFsEntry *entries, size_t count, char *buf, size_t buf_size)
Serialize directory entries to a JSON array.
Writes a JSON array of objects into
buf. Each object has: “name” (string), “size” (uint64), “dir” (boolean).Example output: [{“name”:”foo.txt”,”size”:1024,”dir”:false}]
- Parameters:
entries – array of directory entries
count – number of entries
buf – output buffer
buf_size – output buffer size
- Returns:
AXL_OK on success, AXL_ERR on error or buffer overflow.
-
char *axl_volume_get_label(const char *path)
Get the filesystem volume label for a path.
Returns a UTF-8 copy of the label. Caller frees with axl_free().
- Parameters:
path – filesystem path (e.g., “fs0:”, “fs1:\”)
- Returns:
label string, or NULL on error.
-
char *axl_volume_get_label_by_handle(void *handle)
Get the filesystem volume label for a handle.
Use with handles from axl_protocol_enumerate(“simple-fs”, …). Returns a UTF-8 copy of the label. Caller frees with axl_free().
- Parameters:
handle – filesystem handle from axl_protocol_enumerate
- Returns:
label string, or NULL on error.
-
int axl_volume_enumerate(AxlVolume *out, size_t max, size_t *count)
Enumerate mounted filesystem volumes.
Fills
outwith up tomaxdescriptors, each with a stable name (“fs0”, “fs1”, …) and an opaque handle. On return,countreceives the number of entries filled.- Parameters:
out – output array (may be NULL to query count)
max – capacity of
outcount – [out] number of volumes found
- Returns:
AXL_OK on success, AXL_ERR on error.
-
struct AxlFsEntry
- #include <axl-fs.h>
Canonical file / directory metadata.
One struct, three uses:
axl_file_info(path, &entry)— path-based stat.axl_dir_read(dir, &entry)— next directory entry;namepopulated with the basename only.<axl/axl-fs-provider.h>callbacksget_info,read_dir,set_info— provider authors fill / read this same struct.
Pre-Phase-C this was three different structs (
AxlFileInfo,AxlDirEntry,AxlFsProviderInfo) carrying the same data in different shapes; collapsed in Phase C cleanup.Public Members
-
uint32_t struct_size
sizeof(AxlFsEntry) at write time
-
uint32_t version
AXL_FS_ENTRY_VERSION at write time.
-
char name[256]
basename UTF-8; empty for path-stat / root
-
uint64_t size
file size in bytes (0 for directories)
-
uint64_t alloc_size
physical size on disk; 0 if unknown
-
uint64_t mtime_unix
modification time, Unix epoch seconds (0 = unknown)
-
uint32_t attributes
AXL_FS_ATTR_* bitmask.
-
struct AxlVolume
- #include <axl-fs.h>
Volume descriptor for axl_volume_enumerate.
AxlFileView — mmap-like windowed view over a file
Typedefs
-
typedef struct AxlFileView AxlFileView
axl-file-view.h:
An mmap-like windowed view over a file.
The file is NEVER loaded whole. Reads are served from a small, fixed set of page frames (an AxlPageCache) backed by positional reads (axl_pread); only the hot pages stay resident. This gives the out-of-core property real mmap would — without MMU demand paging, which is unworkable in UEFI boot services (servicing a page fault means blocking filesystem I/O from an exception handler, outside the TPL model — and a FAT file has no physical backing to zero-copy map, so even “real” mmap would copy each page in through the FS driver anyway).
Two access modes:
axl_file_view_read — copy a [offset, offset+len) range out (spans pages transparently). Always safe.
axl_file_view_page — borrow a pointer into the resident frame for the page containing an offset (zero-copy, single page only; valid until the next view call that may evict).
Read-only: the view never writes back. Intended for windowing large files (logs, the original text of an out-of-core editor buffer) where loading the whole file is undesirable.
-
typedef struct AxlPageCache AxlPageCache
Functions
-
AxlFileView *axl_file_view_open(const char *path, size_t page_size, size_t max_frames)
Open a windowed, page-cached view over a file.
The file is opened read-only; its size is queried once. No file content is read until the first access.
- Parameters:
path – file path (UTF-8)
page_size – frame size in bytes (0 = 64 KiB default; rounded up to a power of two)
max_frames – number of resident frames (LRU capacity; min 1)
- Returns:
new view, or NULL on open failure / OOM. Free with axl_file_view_close().
-
AxlFileView *axl_file_view_open_cached(const char *path, AxlPageCache *cache)
Open a view that shares an existing page cache.
Like axl_file_view_open but instead of allocating its own frame pool, the view borrows
cache(created with axl_page_cache_new_shared) and keys its pages by this view’s identity, so one bounded frame budget is shared across many open files. The view adopts the cache’s page size (which must be a power of two). The cache is NOT freed on close — the view drops only its own frames (axl_page_cache_drop_owner); the caller owns the cache and frees it after all sharing views are closed.- Parameters:
path – file path (UTF-8)
cache – shared cache to borrow (caller-owned)
- Returns:
new view, or NULL on open failure / OOM / a non-power-of-two cache page size. Free with axl_file_view_close().
-
void axl_file_view_close(AxlFileView *v)
Close a view and release its frames. NULL-safe.
- Parameters:
v – view (NULL-safe)
-
size_t axl_file_view_size(const AxlFileView *v)
Total byte length of the underlying file.
- Parameters:
v – view
-
size_t axl_file_view_read(AxlFileView *v, size_t offset, void *out, size_t len)
Copy a byte range out of the view.
Copies up to
lenbytes starting atoffsetintoout, faulting in (and caching) whichever pages the range touches. The range may span any number of pages. Clamped to the file size: a range starting at or past EOF copies 0 bytes.- Parameters:
v – view
offset – byte offset to read from
out – destination buffer
len – bytes requested
- Returns:
number of bytes actually copied (<
lennear EOF).
-
const void *axl_file_view_page(AxlFileView *v, size_t offset, size_t *avail)
Borrow a zero-copy pointer into the page containing
offset.Ensures the page is resident and returns a pointer to
offsetwithin that frame, setting*availto the number of contiguous valid bytes available fromoffsetto the end of that page’s data. To cross a page boundary, call again atoffset+*avail.The pointer is valid only until the next call that may evict (any read/page call). Do not free it.
- Parameters:
v – view
offset – byte offset
avail – [out] contiguous valid bytes from
offsetin this page
- Returns:
pointer into a resident frame, or NULL if
offsetis at or past EOF (in which case*availis set to 0).
-
void axl_file_view_stats(const AxlFileView *v, AxlFileViewStats *out)
Snapshot the cache counters (spike instrumentation).
- Parameters:
v – view
out – [out] counters
-
struct AxlFileViewStats
- #include <axl-file-view.h>
Cache counters, for spike validation (hit rate, that we never pread more than necessary, eviction actually happens).
AxlFsProvider — publish a UEFI-visible filesystem
Defines
-
AXL_FS_PROVIDER_VERSION
axl-fs-provider.h:
Filesystem-publisher abstraction. Lets a consumer publish a UEFI-visible filesystem (Shell
dir fsN:, LoadImage fromfsN:\\foo.efi, the Boot Manager’s volume picker) without writing a singleEFI_*identifier.The consumer fills an
AxlFsProvidervtable in pure UTF-8 / snake_case /AxlFsStatusterms.axl_fs_provider_publishsynthesizes the matchingEFI_SIMPLE_FILE_SYSTEM_PROTOCOL+EFI_FILE_PROTOCOLvtables, marshals UCS-2 ↔ UTF-8 at the boundary, lays outEFI_FILE_INFO/EFI_FILE_SYSTEM_INFOtrailers in caller-supplied buffers (with the spec’s probe-then-resizeEFI_BUFFER_TOO_SMALLsemantics), mapsAxlFsStatus→ spec-mandatedEFI_STATUScodes, and installs both protocols on a freshly-created handle.Design choices documented in
docs/AXL-EFI-Encapsulation-Plan.md(Phase C, kickoff deltas #1–#9).Currentstatic AxlFsStatus my_open(void *ctx, const char *path, unsigned mode, AxlFsProviderFile **out, bool *is_dir) { ... } static AxlFsStatus my_read(AxlFsProviderFile *f, void *buf, size_t *inout) { ... } // ... rest of the vtable ... static const AxlFsProvider provider = { .struct_size = sizeof(AxlFsProvider), .version = AXL_FS_PROVIDER_VERSION, .open = my_open, .close = my_close, .read = my_read, .read_dir = my_read_dir, .get_info = my_get_info, .default_label = "MyFs", .backend_ctx = &my_state, }; static AxlGuid my_guid = AXL_GUID(0x..., 0x..., 0x..., 0x..,0x..,0x..,0x..,0x..,0x..,0x..,0x..); void *handle = NULL; axl_fs_provider_publish(&provider, &my_guid, &handle); // ... later, on driver unload: axl_fs_provider_unpublish(handle);
AxlFsProvider.versionandAxlFsEntry.versionvalue emitted by the SDK. Bumped when either struct gains a new field. Forward-compat: consumers and providers testinstance.struct_size >= offsetof(struct, new_field) + sizeof(new_field)before reading any field added in a version later than the one they were built against.
Typedefs
-
typedef struct AxlFsProviderFile AxlFsProviderFile
Opaque per-open-file handle owned by the provider.
-
typedef AxlFsStatus (*AxlFsProviderOpen)(void *backend_ctx, const char *utf8_path, unsigned mode, unsigned attributes, AxlFsProviderFile **out, bool *out_is_dir)
Open a path on the provider’s filesystem.
The thunk has already resolved the EFI-supplied UCS-2 path to absolute UTF-8 with
/separators, including “.” / “..” / “” (open-self) handling and\\↔/translation. Providers always see an absolute path rooted at/.modeis anAXL_FS_OPEN_*bitmask.attributesis anAXL_FS_ATTR_*bitmask interpreted only whenAXL_FS_OPEN_CREATEis set: passAXL_FS_ATTR_DIRECTORYto request mkdir, otherwise the provider creates a regular file (matches UEFI 2.11 §13.5.2’sAttributesparameter toEFI_FILE_OPEN). Providers that don’t support directory creation should returnAXL_FS_ERR_UNSUPPORTEDfor that case.out_is_dirtells the thunk whether to dispatch subsequentReadcalls toread(file bytes) orread_dir(entries). The provider already knows because it just looked up the entry, so reporting it here saves a stat round trip.Lifetime.
utf8_pathis owned by the SDK thunk and is valid only for the duration of this call. Providers that need to retain the path (most do — for child Open resolution, GetInfo “is root?” checks, rename source path) must copy it into theAxlFsProviderFilethey return.- Return:
AXL_FS_OK on success; AXL_FS_ERR_* otherwise.
-
typedef AxlFsStatus (*AxlFsProviderClose)(AxlFsProviderFile *file)
Close a previously-opened file handle.
The thunk calls
closeexactly once per successfulopen(and duringaxl_fs_provider_unpublishon every still-open handle, per the force-close-on-unpublish contract).Errors are logged but otherwise ignored — the caller’s
EFI_FILE_CLOSEreturnsEFI_SUCCESSregardless, per UEFI 2.11 §13.5.4.
-
typedef AxlFsStatus (*AxlFsProviderRead)(AxlFsProviderFile *file, void *buf, size_t *inout_size)
Read bytes from a regular-file handle.
inout_sizeis the requested byte count on entry, the actually-read byte count on exit. A successful read of zero bytes signals EOF (mirrorsEFI_FILE_READspec).Not called for directory handles — see
read_dir.
-
typedef AxlFsStatus (*AxlFsProviderReadDir)(AxlFsProviderFile *file, AxlFsEntry *out, bool *out_end)
Read one directory entry.
Sets
*out_end = true(with statusAXL_FS_OK) when there are no more entries. Otherwise populatesoutand sets*out_end = false. Iteration order is implementation-defined but stable across calls within a single open.The thunk re-marshals the populated
AxlFsEntryintoEFI_FILE_INFO(header + UCS-2 trailer) per call.
-
typedef AxlFsStatus (*AxlFsProviderWrite)(AxlFsProviderFile *file, const void *buf, size_t *inout_size)
Write bytes to a regular-file handle.
inout_sizeis the requested byte count on entry, the actually-written byte count on exit. NULL in the vtable means the filesystem is read-only (thunk returnsEFI_WRITE_PROTECTEDfor all writes).
-
typedef AxlFsStatus (*AxlFsProviderSeek)(AxlFsProviderFile *file, uint64_t position)
Seek to an absolute byte offset.
(uint64_t)-1means seek-to-EOF (mirrors EFI’s0xFFFFFFFFFFFFFFFFconvention). Directoryseek(0)resets iteration to the start; other directory seeks areAXL_FS_ERR_UNSUPPORTED.
-
typedef AxlFsStatus (*AxlFsProviderDelete)(AxlFsProviderFile *file)
Delete the file referenced by
file.Per UEFI 2.11 §13.5.5, the file handle is closed regardless of whether the delete succeeded. The thunk takes care of calling
closeafter this returns; the provider just deletes the backing object.NULL in the vtable means delete is unsupported (thunk returns
EFI_WARN_DELETE_FAILURE).
-
typedef AxlFsStatus (*AxlFsProviderFlush)(AxlFsProviderFile *file)
Flush pending writes for
file.NULL in the vtable means flush is a no-op (thunk returns
EFI_SUCCESS).
-
typedef AxlFsStatus (*AxlFsProviderGetInfo)(AxlFsProviderFile *file, AxlFsEntry *out)
Populate
outwith this file’s metadata.The thunk converts to
EFI_FILE_INFOfor UEFI consumers, including the UCS-2 trailer with the UTF-8-decodedAxlFsEntry.name.
-
typedef AxlFsStatus (*AxlFsProviderSetInfo)(AxlFsProviderFile *file, const AxlFsEntry *in)
Apply changes to this file’s metadata.
EFI consumers use
SetInfo(EFI_FILE_INFO)for two purposes: (1) renaming (the trailing UCS-2 name in the buffer differs from the file’s current basename), and (2) attribute changes. The thunk decodes both and presents them as a normalizedAxlFsEntrywith the new name and attributes already in axl form.NULL in the vtable means SetInfo is unsupported (thunk returns
EFI_WRITE_PROTECTED).
-
typedef AxlFsStatus (*AxlFsProviderVolumeInfoFn)(void *backend_ctx, AxlFsProviderVolumeInfo *out)
Optional volume-level info callback.
UEFI’s
EFI_FILE_GET_INFOaccepts both per-file (gEfiFileInfoGuid) and per-volume (gEfiFileSystemInfoGuid/gEfiFileSystemVolumeLabelInfoIdGuid) GUIDs. Per-file goes throughget_info; per-volume goes through this callback. NULL means “use AxlFsProvider.default_label and
report (uint64_t)-1 for volume_size / free_space”.
Enums
-
enum AxlFsStatus
Typed status returned by every provider callback.
The SDK thunks map these onto the spec-mandated
EFI_STATUSfor eachEFI_FILE_PROTOCOLentry point. Providers stay in pure snake_case-land; UEFI consumers (Shell, Boot Manager) still see the right error code (EFI_NOT_FOUNDvsEFI_DEVICE_ERRORvsEFI_WRITE_PROTECTEDetc.).Values:
-
enumerator AXL_FS_OK
-
enumerator AXL_FS_ERR_NOT_FOUND
→ EFI_NOT_FOUND
-
enumerator AXL_FS_ERR_ACCESS_DENIED
→ EFI_ACCESS_DENIED
-
enumerator AXL_FS_ERR_WRITE_PROTECTED
→ EFI_WRITE_PROTECTED
-
enumerator AXL_FS_ERR_NO_SPACE
→ EFI_VOLUME_FULL
-
enumerator AXL_FS_ERR_NOT_DIR
→ EFI_INVALID_PARAMETER (open mode mismatch)
-
enumerator AXL_FS_ERR_IS_DIR
→ EFI_INVALID_PARAMETER (write to dir)
-
enumerator AXL_FS_ERR_INVALID
→ EFI_INVALID_PARAMETER
-
enumerator AXL_FS_ERR_NO_MEMORY
→ EFI_OUT_OF_RESOURCES
-
enumerator AXL_FS_ERR_IO
→ EFI_DEVICE_ERROR
-
enumerator AXL_FS_ERR_UNSUPPORTED
→ EFI_UNSUPPORTED
-
enumerator AXL_FS_ERR_END_OF_FILE
→ EFI_END_OF_FILE
-
enumerator AXL_FS_ERR_VOLUME_CORRUPTED
→ EFI_VOLUME_CORRUPTED
-
enumerator AXL_FS_OK
Functions
-
int axl_fs_provider_publish(const AxlFsProvider *provider, const AxlGuid *vendor_guid, void **out_handle)
Publish a filesystem on a new UEFI handle.
Synthesizes
EFI_SIMPLE_FILE_SYSTEM_PROTOCOLandEFI_FILE_PROTOCOLvtables that forward intoprovider, marshals UCS-2 ↔ UTF-8 at the boundary, builds a vendor device-path withvendor_guid, and installs the protocols on a freshly-created handle. The returnedout_handleis opaque to consumers (treat as a token); internally it is theEFI_HANDLEthe protocols were installed on, so AXL primitives likeaxl_protocol_find_guid/axl_driver_connect_handlework against it without further bookkeeping. Pass it toaxl_fs_provider_unpublishto tear down.UEFI consumers (Shell, Boot Manager, LoadImage) see a spec-conformant filesystem they can
dir/cd/LoadImageagainst without knowing it’s a thunked provider.vendor_guidshould be unique to the provider kind so device paths from multiple instances don’t collide. Multiple concurrent publish calls with the same GUID are allowed (each gets its own handle).- Parameters:
provider – caller-owned vtable
vendor_guid – identifies provider kind
out_handle – [out] opaque, for unpublish
- Returns:
AXL_OK on success, AXL_ERR if
provideris malformed (missing required callback, wrong struct_size, unsupported version) or the protocol install fails.
-
int axl_fs_provider_unpublish(void *handle)
Tear down a previously-published filesystem.
Force-closes every still-open
AxlFsProviderFile *(calling the provider’sclosecallback on each), uninstalls both protocols from the published handle, and frees all SDK-side thunk state.UEFI consumers that hold a stale
EFI_FILE_PROTOCOL *after unpublish receiveEFI_DEVICE_ERRORfrom the next call (the thunk retains a small “dead handle” record so the deref doesn’t fault). This is the right shape for AXL_SERVICE_DRIVER teardown, where the driver image (and the vtable function pointers in it) is going away — the alternative (“refuse if files open”) would leak the protocols permanently.NULL
handleis a no-op.- Returns:
AXL_OK on success, AXL_ERR if
handlewas never returned byaxl_fs_provider_publishor has already been unpublished.
-
struct AxlFsProviderVolumeInfo
- #include <axl-fs-provider.h>
Volume-level metadata.
Returned by the optional
AxlFsProviderVolumeInfoFncallback. If the provider doesn’t supply one, the thunk synthesizes a default with(uint64_t)-1sizes and the staticAxlFsProvider.default_labelstring.Public Members
-
uint32_t struct_size
sizeof(AxlFsProviderVolumeInfo) at write time
-
uint32_t version
AXL_FS_PROVIDER_VERSION at write time.
-
bool read_only
true if volume is read-only
-
uint64_t volume_size
total bytes; (uint64_t)-1 if unknown
-
uint64_t free_space
free bytes; (uint64_t)-1 if unknown
-
uint32_t block_size
usually 512
-
char label[64]
UTF-8 volume label.
-
uint32_t struct_size
-
struct AxlFsProvider
- #include <axl-fs-provider.h>
A filesystem-provider vtable.
The consumer fills this once and passes a pointer to
axl_fs_provider_publish. The pointer must remain valid for as long as the publication lives — typicallystatic constin the driver image.The struct_size + version prefix lets the SDK extend the vtable without breaking existing providers. Always set:
.struct_size = sizeof(AxlFsProvider),.version = AXL_FS_PROVIDER_VERSION.Optional callbacks (set to NULL to opt out):
write— NULL → EFI_WRITE_PROTECTED for all writesdel— NULL → EFI_WARN_DELETE_FAILURE for all deletesset_info— NULL → EFI_WRITE_PROTECTED for SetInfoflush— NULL → EFI_SUCCESS no-opvolume_info— NULL → thunk synthesizes fromdefault_label
Required callbacks:
open,close,read,read_dir,seek,get_info. Missing any of these is a publish-time validation error (returns AXL_ERR).Public Members
-
uint32_t struct_size
sizeof(AxlFsProvider)
-
uint32_t version
AXL_FS_PROVIDER_VERSION.
-
AxlFsProviderOpen open
-
AxlFsProviderClose close
-
AxlFsProviderRead read
-
AxlFsProviderReadDir read_dir
-
AxlFsProviderWrite write
optional
-
AxlFsProviderSeek seek
-
AxlFsProviderDelete del
optional (‘delete’ is a C++ keyword)
-
AxlFsProviderFlush flush
optional
-
AxlFsProviderGetInfo get_info
-
AxlFsProviderSetInfo set_info
optional
-
AxlFsProviderVolumeInfoFn volume_info
optional
-
const char *default_label
used when volume_info NULL; “” allowed
-
void *backend_ctx
passed to open / volume_info
AxlDevicePath — UEFI device-path constructors
Typedefs
-
typedef struct AxlDevicePath AxlDevicePath
axl-device-path.h:
Surfaces the small handful of UEFI-device-path primitives that consumers building publishers (filesystem, block-io, vendor protocols) need to construct fresh device-path chains, without having to
#include <uefi/...>.Today this header only ships the vendor-path constructor — historically the one chunk of
EFI_DEVICE_PATH_PROTOCOLplumbing every consumer hand-rolled. Future additions (PCI-path, file-path, fan-out helpers) live here. Opaque handle to an EFI_DEVICE_PATH_PROTOCOL chain. The pointer returned by the constructors below is suitable to pass toaxl_protocol_register("device-path", ...)or toaxl_protocol_install(&EFI_DEVICE_PATH_PROTOCOL_GUID, ...).
Functions
-
int axl_device_path_make_vendor(const AxlGuid *vendor_guid, AxlDevicePath **out)
Allocate a vendor device-path node + END terminator.
Builds the two-node chain that vendor-defined protocols (axl-webfs, crashhandler reports, RAM-disk publishers, …) install on the handle they create:
[HARDWARE_DEVICE_PATH / HW_VENDOR_DP] ← carries vendor_guid [END_DEVICE_PATH_TYPE / END_ENTIRE] ← terminator
The two nodes are laid out in a single contiguous allocation; caller frees with
axl_free(*out).- Parameters:
vendor_guid – identifies the vendor / instance
out – [out] freshly-allocated chain
- Returns:
AXL_OK on success; AXL_ERR if
vendor_guidoroutis NULL or allocation fails.