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

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) == 0) {
    // 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_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:

0 on success, -1 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:

0 on success, -1 on error.

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:

0 on success, -1 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

AxlFs — filesystem operations

Typedefs

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 AxlDirEntry *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 AxlDirEntry, including name, size, is_dir

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.

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.

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.

int axl_file_info(const char *path, AxlFileInfo *info)

Get file metadata. Wraps UEFI EFI_FILE_INFO.

Parameters:
  • path – file path (UTF-8)

  • info – [out] receives file metadata

Returns:

0 on success, -1 on error.

int axl_file_delete(const char *path)

Delete a file.

Parameters:
  • path – file path (UTF-8)

Returns:

0 on success, -1 on error.

int axl_file_rename(const char *old_path, const char *new_path)

Rename or move a file.

Parameters:
  • old_path – current path (UTF-8)

  • new_path – new path (UTF-8)

Returns:

0 on success, -1 on error.

int axl_dir_mkdir(const char *path)

Create a directory.

Parameters:
  • path – directory path (UTF-8)

Returns:

0 on success, -1 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:

0 on success, -1 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, AxlDirEntry *entry)

Read the next directory entry.

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 AxlDirEntry *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:

0 on success, -1 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_service_enumerate(“simple-fs”, …). Returns a UTF-8 copy of the label. Caller frees with axl_free().

Parameters:
  • handle – filesystem handle from axl_service_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:

0 on success, -1 on error.

struct AxlFileInfo
#include <axl-fs.h>

File metadata (UEFI EFI_FILE_INFO equivalent).

Public Members

uint64_t size

file size in bytes

uint64_t alloc_size

physical allocation size on disk

bool is_dir

true if directory

bool read_only

true if read-only attribute set

struct AxlDirEntry
#include <axl-fs.h>

Directory entry returned by axl_dir_read.

Public Members

char name[256]

filename (UTF-8, not full path)

uint64_t size

file size in bytes (0 for directories)

bool is_dir

true if this entry is a directory

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.