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:

  1. Simple helpers (GLib-style): axl_print, axl_printerr

  2. Stream I/O (POSIX-style): axl_fopen, axl_fread, axl_fprintf, axl_fgets, axl_readline, axl_walk_lines

  3. Low-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_stdoutaxl_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

axl_stdout

out

text (UTF-8 in → UCS-2 to console)

firmware console (ConOut)

axl_stderr

out

text (same path as stdout)

firmware console

axl_stdin

in

raw bytes

EFI_SHELL_PARAMETERS_PROTOCOL.StdIn

axl_stdout_raw

out

raw bytes

EFI_SHELL_PARAMETERS_PROTOCOL.StdOut (direct WriteFile)

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)

axl_print / axl_printf / axl_write(axl_stdout, ...) — UTF-8 in, UCS-2 to console, captured-as-UCS-2 by the shell on > / `

Binary output (RAM-disk dump, captured SPD blob, etc.)

axl_write(axl_stdout_raw, ...) — raw bytes, bypasses the CHAR16 console path so they survive a pipe intact

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

FF FE

UTF-16 LE

BOM consumed, body transcoded to UTF-8

FE FF

UTF-16 BE

BOM consumed, body transcoded to UTF-8

EF BB BF

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>’s FILE *: 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 return AxlStream * live here.

Three-layer API shape:

  1. Simple helpers (GLib-style): axl_print, axl_printerr.

  2. Stream I/O (POSIX-style): axl_fopen, axl_fread, axl_fprintf, axl_fgets, axl_readline, etc.

  3. 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.

line points into the caller’s working buffer — valid only for the duration of this callback invocation. len excludes the trailing \\n; a \\r immediately before is left in line for callers that want to keep CRLF context (most strip it).

truncated is true when the logical line exceeded the working buffer; in that case line holds the leading len bytes 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_read returns UTF-8, axl_write accepts UTF-8 — and axl_stream_set_encoding declares 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.StdIn when 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 extra alongside the console.

After this call, every byte written to axl_stdout via axl_print, axl_printf, axl_fprintf(axl_stdout, ...), or direct axl_write(axl_stdout, ...) is also written to extra. Pass NULL to clear an active tee. Multiple calls replace the previous tee — there is no chain (the extra stream’s own tee field is intentionally ignored, so an accidental loop just double-writes once instead of recursing).

Lifetime: the caller owns extra and 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 extra alongside 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-1 bytes 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 \\n or EOF. The next axl_readline_max call 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 equals max_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, *line points into the reader’s working buffer (valid until the next _next call) and *len is the byte count, excluding any trailing \n (a \r from a CRLF pair is left for the caller to strip if desired). On truncation, *truncated is 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 s is NULL or enc is 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-1 bytes from stream into buf, stopping at end-of-line or EOF, and NUL-terminate.

Like POSIX fgets(): reads at most one less than size bytes, stopping after the first newline (which is included in buf), 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 size bytes)

  • size – buffer size in bytes (incl. NUL)

  • stream – source stream

Returns:

buf on 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:

  1. BOM:

    • FF FE → UTF-16 LE; BOM consumed, body transcoded to UTF-8

    • FE FF → UTF-16 BE; BOM consumed, body transcoded to UTF-8

    • EF BB BF → UTF-8 BOM; consumed, body returned verbatim

  2. 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.

  3. 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_stdin gives every text- oriented tool transparent UTF-8 input regardless of whether the upstream is a built-in (UCS-2), an AXL tool that wrote via axl_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
AxlStream *axl_stderr
AxlStream *axl_stdin
AxlStream *axl_stdout_raw

axl_stdout_raw — sibling of axl_stdout for binary output. Writes via EFI_SHELL_PARAMETERS_PROTOCOL.StdOut->WriteFile directly, 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 next call 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-1 fire one next call with truncated == true carrying 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.

Public Members

AxlStream *_stream
char *_buf
size_t _buf_size
size_t _fill
size_t _last_consumed
bool _discard
bool _eof
bool _err

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_ms milliseconds; returns -1 on timeout with out untouched.

Internally creates a timer event (when timeout_ms is 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 out populated), -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.

Public Members

uint16_t scan_code

UEFI scan code (0 for printable keys)

uint16_t unicode_char

UCS-2 character (0 for special keys)

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 path already exists. Backs a PUT with If-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> open callback.

READ (0x1) and WRITE (0x2) are bit-identical to the corresponding EFI_FILE_MODE_* constants; CREATE is renumbered (0x4 here vs 0x8000000000000000 for EFI_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.attributes and the attributes parameter 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.version value emitted by the SDK. Bumped when the struct gains a new field. Forward-compat: callers test entry.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_view reads out-of-core. This is what backs a WebDAV PUT or any large upload that can’t fit axl_file_set_contents’s whole-buffer model (a BMC mounting a multi-GB ISO, say). Each axl_file_writer_write goes straight to the file; nothing is held in RAM between calls.

Opaque handle; create with axl_file_writer_open, finalize with axl_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 struct AxlDir AxlDir
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 buf to a temporary sibling (“<path>.tmp”), flushes it (close implies flush on the UEFI FAT driver), then replaces path with 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 path already 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.

path must 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 path if 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. With AXL_FILE_WRITER_APPEND, existing content is kept and writes go at the current end of file. With AXL_FILE_WRITER_EXCL, the open fails if path already 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 len bytes 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_close it. 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_write calls (plus the pre-existing length when opened with APPEND). Lets a PUT handler verify it received the advertised Content-Length.

Parameters:
  • w – writer

Returns:

byte count, or 0 if w is 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->name is 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_path may be a basename only (“bar.txt”) or a full path with the same directory prefix as old_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's directory differs from old_path's — falls back to chunked stream copy followed by source delete.

Overwrite semantics: an existing file at new_path is replaced (matches POSIX rename(2)). The implementation removes new_path eagerly 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->name is populated with the entry’s basename; entry->attributes carries the kind bits (AXL_FS_ATTR_DIRECTORY for 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 fn on every entry (excluding . and ..) under root, descending into subdirectories automatically. Each entry’s full path is constructed with separator deduplication so the callback sees clean paths regardless of whether root has a trailing / or \. Recursion is post-callback — the walker invokes fn on a directory entry first, then descends into it.

max_depth matches POSIX find -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 = 1 lists root’s immediate children only.

  • max_depth = N lists at most N levels of nesting below root.

  • max_depth <= 0 is 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 root could 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 out with up to max descriptors, each with a stable name (“fs0”, “fs1”, …) and an opaque handle. On return, count receives the number of entries filled.

Parameters:
  • out – output array (may be NULL to query count)

  • max – capacity of out

  • count – [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; name populated with the basename only.

  • <axl/axl-fs-provider.h> callbacks get_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.

Public Members

void *handle

opaque filesystem handle

char name[16]

stable name (“fs0”, “fs1”, …)

void *device_path

opaque EFI_DEVICE_PATH_PROTOCOL — caller may pass to axl_device_path_find / _for_each. The pointer is firmware-owned; callers must not free it.

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 len bytes starting at offset into out, 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 (< len near 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 offset within that frame, setting *avail to the number of contiguous valid bytes available from offset to the end of that page’s data. To cross a page boundary, call again at offset + *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 offset in this page

Returns:

pointer into a resident frame, or NULL if offset is at or past EOF (in which case *avail is 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).

Public Members

uint64_t hits

page lookups served from a resident frame

uint64_t misses

page lookups that had to read from the file

uint64_t evictions

resident pages discarded to make room

uint64_t preads

underlying axl_pread calls (one per successful fill; == misses when no read errors)

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 from fsN:\\foo.efi, the Boot Manager’s volume picker) without writing a single EFI_* identifier.

The consumer fills an AxlFsProvider vtable in pure UTF-8 / snake_case / AxlFsStatus terms. axl_fs_provider_publish synthesizes the matching EFI_SIMPLE_FILE_SYSTEM_PROTOCOL + EFI_FILE_PROTOCOL vtables, marshals UCS-2 ↔ UTF-8 at the boundary, lays out EFI_FILE_INFO / EFI_FILE_SYSTEM_INFO trailers in caller-supplied buffers (with the spec’s probe-then-resize EFI_BUFFER_TOO_SMALL semantics), maps AxlFsStatus → spec-mandated EFI_STATUS codes, 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).

static 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);
Current AxlFsProvider.version and AxlFsEntry.version value emitted by the SDK. Bumped when either struct gains a new field. Forward-compat: consumers and providers test instance.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 /.

mode is an AXL_FS_OPEN_* bitmask. attributes is an AXL_FS_ATTR_* bitmask interpreted only when AXL_FS_OPEN_CREATE is set: pass AXL_FS_ATTR_DIRECTORY to request mkdir, otherwise the provider creates a regular file (matches UEFI 2.11 §13.5.2’s Attributes parameter to EFI_FILE_OPEN). Providers that don’t support directory creation should return AXL_FS_ERR_UNSUPPORTED for that case.

out_is_dir tells the thunk whether to dispatch subsequent Read calls to read (file bytes) or read_dir (entries). The provider already knows because it just looked up the entry, so reporting it here saves a stat round trip.

Lifetime. utf8_path is 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 the AxlFsProviderFile they 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 close exactly once per successful open (and during axl_fs_provider_unpublish on every still-open handle, per the force-close-on-unpublish contract).

Errors are logged but otherwise ignored — the caller’s EFI_FILE_CLOSE returns EFI_SUCCESS regardless, 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_size is the requested byte count on entry, the actually-read byte count on exit. A successful read of zero bytes signals EOF (mirrors EFI_FILE_READ spec).

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 status AXL_FS_OK) when there are no more entries. Otherwise populates out and sets *out_end = false. Iteration order is implementation-defined but stable across calls within a single open.

The thunk re-marshals the populated AxlFsEntry into EFI_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_size is the requested byte count on entry, the actually-written byte count on exit. NULL in the vtable means the filesystem is read-only (thunk returns EFI_WRITE_PROTECTED for all writes).

typedef AxlFsStatus (*AxlFsProviderSeek)(AxlFsProviderFile *file, uint64_t position)

Seek to an absolute byte offset.

(uint64_t)-1 means seek-to-EOF (mirrors EFI’s 0xFFFFFFFFFFFFFFFF convention). Directory seek(0) resets iteration to the start; other directory seeks are AXL_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 close after 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 out with this file’s metadata.

The thunk converts to EFI_FILE_INFO for UEFI consumers, including the UCS-2 trailer with the UTF-8-decoded AxlFsEntry.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 normalized AxlFsEntry with 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_INFO accepts both per-file (gEfiFileInfoGuid) and per-volume (gEfiFileSystemInfoGuid / gEfiFileSystemVolumeLabelInfoIdGuid) GUIDs. Per-file goes through get_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_STATUS for each EFI_FILE_PROTOCOL entry point. Providers stay in pure snake_case-land; UEFI consumers (Shell, Boot Manager) still see the right error code (EFI_NOT_FOUND vs EFI_DEVICE_ERROR vs EFI_WRITE_PROTECTED etc.).

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

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_PROTOCOL and EFI_FILE_PROTOCOL vtables that forward into provider, marshals UCS-2 ↔ UTF-8 at the boundary, builds a vendor device-path with vendor_guid, and installs the protocols on a freshly-created handle. The returned out_handle is opaque to consumers (treat as a token); internally it is the EFI_HANDLE the protocols were installed on, so AXL primitives like axl_protocol_find_guid / axl_driver_connect_handle work against it without further bookkeeping. Pass it to axl_fs_provider_unpublish to tear down.

UEFI consumers (Shell, Boot Manager, LoadImage) see a spec-conformant filesystem they can dir / cd / LoadImage against without knowing it’s a thunked provider.

vendor_guid should 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 provider is 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’s close callback 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 receive EFI_DEVICE_ERROR from 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 handle is a no-op.

Returns:

AXL_OK on success, AXL_ERR if handle was never returned by axl_fs_provider_publish or has already been unpublished.

struct AxlFsProviderVolumeInfo
#include <axl-fs-provider.h>

Volume-level metadata.

Returned by the optional AxlFsProviderVolumeInfoFn callback. If the provider doesn’t supply one, the thunk synthesizes a default with (uint64_t)-1 sizes and the static AxlFsProvider.default_label string.

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.

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 — typically static const in 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 writes

  • del — NULL → EFI_WARN_DELETE_FAILURE for all deletes

  • set_info — NULL → EFI_WRITE_PROTECTED for SetInfo

  • flush — NULL → EFI_SUCCESS no-op

  • volume_info — NULL → thunk synthesizes from default_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_PROTOCOL plumbing 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 to axl_protocol_register("device-path", ...) or to axl_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_guid or out is NULL or allocation fails.