Files
libakstdlib/include/akstdlib.h
Andrew Kesterson 125eeb2109 Version at 0.2.0: complete the wishlist, document it, gate the docs
Closes what was left of TODO.md sections 1, 2 and 3, and rewrites that
file to hold outstanding items only.

The API break gets a minor bump, because pre-1.0 the soname carries
MAJOR.MINOR and 0.1 and 0.2 are therefore different ABIs. Five
signatures changed and the ato* contract with them; UPGRADING.md is new
and lists every one, with the before/after for the cases the compiler
cannot warn about.

Section 3.1 is finished: reallocarray with the multiplication checked,
aligned_alloc and posix_memalign, asprintf/vasprintf, scanf/vscanf.
Four functions on that list are deliberately absent rather than missing
-- sprintf, strtok, setbuf and perror -- and TODO.md now says which and
why, so nobody adds them thinking they were forgotten.

Section 1.9, the cross-cutting tests:

  tests/test_pool.c    drives every failure path AKERR_MAX_ARRAY_ERROR
                       + 10 times and checks the pool after each round,
                       because a wrapper that leaks a slot fails a
                       hundred calls later in unrelated code. It also
                       asserts that each error names the function and
                       file it was raised from, which is what catches a
                       FAIL that migrates into a helper during a
                       refactor: status right, message right, origin
                       quietly lying.
  tests/negative/      two sources that must FAIL to compile, built with
                       -Werror and registered WILL_FAIL. AKERR_NOIGNORE
                       and the format attributes are enforced by the
                       compiler and by nothing else; drop either and
                       every ordinary test still passes.

Thread safety is answered rather than tested: the library is not
thread-safe and cannot be made so from here, because libakerror's error
pool is an unlocked process-global array. README.md says so plainly and
TODO.md carries it as the item blocking any future pthread wrappers.

Doxygen is configured and gated. All 147 public functions have @brief,
a @param each, @throws per status and @return; EXTRACT_ALL is off and
WARN_NO_PARAMDOC on, so `cmake --build build --target docs` fails on an
undocumented entity. It ran to 0 warnings. The Doxyfile carries no
version -- cmake/RunDoxygen.cmake feeds PROJECT_NUMBER in from
project(), so that stays the one place a version is written.

CI now builds against the submodule it pins instead of also installing
libakerror@main and never linking it, adds -Werror, and gains a
sanitizer job. The pre-push hook matches, and runs the docs check too.

Coverage: 99.5% of lines (1643/1651), 100% of functions (147/147). The
eight uncovered lines are each uncovered on purpose and TODO.md says
which and why.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:00:16 -04:00

2255 lines
103 KiB
C

/**
* @file akstdlib.h
* @brief libc wrappers that report failures through libakerror error contexts.
*
* Every entry point here returns `akerr_ErrorContext *` and is marked
* `AKERR_NOIGNORE`: NULL means success, anything else is an error the caller
* owns and must release. Results come back through out-params, because the
* return value is spoken for.
*
* Four conventions run through the whole library, and knowing them removes most
* of the per-function reading:
*
* - **A NULL out-param is a caller error**, not "don't care". If a function can
* tell you something, it insists on somewhere to put it.
* - **Finding nothing is success.** Searching functions write NULL or zero and
* return NULL. Absent is an ordinary answer to "where is this".
* - **Truncation is a failure.** Every function that writes into a caller's
* buffer takes that buffer's size and raises AKERR_OUTOFBOUNDS rather than
* quietly writing less, and writes nothing at all when it does.
* - **`errno` is cleared before each wrapped call** and read back with a
* fallback, so no error can carry status 0 -- which every `DETECT` and `CATCH`
* downstream would read as success.
*
* @warning This library is **not thread-safe**. libakerror hands out error
* contexts from a process-global array with no locking, and every function here
* takes a slot from it on any failure path. See README.md.
*
* @see README.md for the deviations from libc semantics, UPGRADING.md if you are
* coming from 0.1.0, and TODO.md for what is still open.
*/
#ifndef _AKSTDLIB_H_
#define _AKSTDLIB_H_
#include <akerror.h>
/*
* libakerror 1.0.0 is the floor. That release moved the status-name table into a
* private registry -- AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, the
* registry entry points raise akerr_ErrorContext * instead of returning int, and
* the library gained an soname -- so a translation unit that pairs this header
* with a pre-1.0.0 akerror.h is an ABI mismatch, not just a compile problem.
*
* libakerror publishes no version macro, so this feature-tests on
* AKERR_FIRST_CONSUMER_STATUS, which that release introduced, rather than on a
* version number that does not exist. Without the guard a stale installed header
* fails much further in, as a pile of unrelated errors inside src/stdlib.c.
*
* See deps/libakerror/UPGRADING.md.
*/
#ifndef AKERR_FIRST_CONSUMER_STATUS
#error "libakstdlib requires libakerror >= 1.0.0: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror."
#endif
/*
* AKSL_VERSION_MAJOR/MINOR/PATCH/STRING/NUMBER and AKSL_VERSION_SONAME.
* Generated by CMake from include/akstdlib_version.h.in, which is why there is
* no such file in the source tree -- it is configured into the build tree and
* installed alongside this header.
*/
#include <akstdlib_version.h>
/*
* What this header needs in its own declarations, and no more:
* stdio.h FILE
* stddef.h size_t
* stdint.h uint32_t
* stdarg.h va_list, for the v* forms of the formatted-output wrappers
*
* It used to pull in stdlib.h and string.h as well, which nothing here needs and
* which every consumer then got whether it wanted them or not. stddef.h in place
* of stdlib.h is the same size_t at a fraction of the namespace. TODO.md 2.2.16.
*/
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
/* off_t, for the aksl_fseeko/aksl_ftello pair. POSIX, like aksl_realpath. */
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Restore the compile-time format/argument checking that callers otherwise lose
* by going through a variadic wrapper: without it, printf("%d", "str") is caught
* and aksl_printf(&n, "%d", "str") is not. TODO.md 2.2.5.
*
* tests/negative/format_mismatch.c is a compile that must fail, which is what
* proves these are still attached.
*/
#if defined(__GNUC__) || defined(__clang__)
/** @brief Mark a printf-style format argument for compile-time checking. */
#define AKSL_PRINTF_FORMAT(__fmt_index, __first_arg) \
__attribute__((format(printf, __fmt_index, __first_arg)))
/** @brief Mark a scanf-style format argument for compile-time checking. */
#define AKSL_SCANF_FORMAT(__fmt_index, __first_arg) \
__attribute__((format(scanf, __fmt_index, __first_arg)))
#else
/** @brief No-op where the compiler has no format attribute. */
#define AKSL_PRINTF_FORMAT(__fmt_index, __first_arg)
/** @brief No-op where the compiler has no format attribute. */
#define AKSL_SCANF_FORMAT(__fmt_index, __first_arg)
#endif
/* ====================================================================== */
/** @name Types
* @{
*/
/* ====================================================================== */
/** @brief A node in a doubly-linked list. Initialise with aksl_list_node_init. */
typedef struct aksl_ListNode {
void *data; /**< Whatever the caller is storing. Never read by this library. */
struct aksl_ListNode *next; /**< The next node, or NULL at the tail. */
struct aksl_ListNode *prev; /**< The previous node, or NULL at the head. */
} aksl_ListNode;
/** @brief A node in a binary tree. Initialise with aksl_tree_node_init. */
typedef struct aksl_TreeNode {
struct aksl_TreeNode *parent; /**< Set and read by the aksl_tree_insert/remove family only. */
struct aksl_TreeNode *left; /**< Left child, or NULL. */
struct aksl_TreeNode *right; /**< Right child, or NULL. */
void *leaf; /**< Whatever the caller is storing; the comparator's operand. */
} aksl_TreeNode;
#define AKSL_TREE_SEARCH_BFS 0 /**< Breadth-first, left child before right */
#define AKSL_TREE_SEARCH_BFS_RIGHT 1 /**< Breadth-first, right child before left */
#define AKSL_TREE_SEARCH_DFS 2 /**< Alias for AKSL_TREE_SEARCH_DFS_PREORDER */
#define AKSL_TREE_SEARCH_DFS_PREORDER 2 /**< Depth-first pre-order: root, left, right */
#define AKSL_TREE_SEARCH_DFS_INORDER 3 /**< Depth-first in-order: left, root, right */
#define AKSL_TREE_SEARCH_DFS_POSTORDER 4 /**< Depth-first post-order: left, right, root */
#define AKSL_TREE_SEARCH_VISIT 5 /**< Visit the node and stop; do not traverse the children */
/**
* @brief How deep a tree walk goes before raising AKERR_OUTOFBOUNDS.
*
* A real limit, not a formality: the depth-first walk is recursive, so without
* it a degenerate chain overflows the stack and a child pointing back at an
* ancestor recurses until the process dies. 256 is far past any balanced tree
* that fits in memory (2^256 nodes) and comfortably short of a stack overflow,
* so in practice it only rejects the degenerate shapes it exists to reject.
*
* It also sizes the ancestor buffer aksl_tree_iterate keeps on its own stack --
* one pointer per level, so raising it costs 8 bytes of stack per level.
*/
#define AKSL_TREE_MAX_DEPTH 256
/**
* @brief A list that tracks its own head, tail and length.
*
* aksl_list_append has to walk the list to find its tail, so building n nodes
* with it is O(n^2); this makes that O(1) and makes the length free. It owns no
* memory -- the nodes are still the caller's.
*/
typedef struct aksl_List {
aksl_ListNode *head; /**< First node, or NULL when empty. */
aksl_ListNode *tail; /**< Last node, or NULL when empty. */
size_t length; /**< Number of nodes. */
} aksl_List;
/** @brief Called once per node by aksl_list_iterate. Raise AKERR_ITERATOR_BREAK to stop. */
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_ListNodeIterator)(aksl_ListNode *node, void *data);
/** @brief Called once per node by aksl_tree_iterate. Raise AKERR_ITERATOR_BREAK to stop. */
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_TreeNodeIterator)(aksl_TreeNode *node, void *data);
/** @brief Allocator, shaped like aksl_malloc. */
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_AllocFunc)(size_t size, void **dest);
/** @brief Deallocator, shaped like aksl_free. */
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_FreeFunc)(void *ptr);
/** @brief Accepts or rejects a node for aksl_list_find, through *matched (non-zero to accept). May raise. */
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_ListNodePredicate)(aksl_ListNode *node, void *data, int *matched);
/** @brief Orders two leaves for the tree functions: negative, zero or positive through *dest, as strcmp(3). May raise. */
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_TreeCompareFunc)(void *a, void *b, int *dest);
/** @brief Called once per live entry by aksl_hashmap_iterate. Raise AKERR_ITERATOR_BREAK to stop. */
typedef akerr_ErrorContext AKERR_NOIGNORE *(*aksl_HashMapIterator)(const char *key, void *value, void *data);
/** @} */
/* ====================================================================== */
/** @name Version
*
* The AKSL_VERSION_* macros record what a caller was *compiled* against. These
* report the shared library actually *loaded*. The two differ exactly when a
* stale libakstdlib.so is on the loader path, which is the failure they exist to
* name.
* @{
*/
/* ====================================================================== */
/**
* @brief The loaded library's version.
* @param[out] major Major version. Required.
* @param[out] minor Minor version. Required.
* @param[out] patch Patch version. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL. All three are required; this
* library treats a NULL out-param as a caller error rather than as
* "don't care".
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_version(int *major, int *minor, int *patch);
/**
* @brief The loaded library's version as "MAJOR.MINOR.PATCH".
*
* Cannot fail -- it returns a pointer to a string literal in the library's own
* .rodata -- so it returns the string directly rather than an error context, the
* same exception akerr_name_for_status() makes for the same reason.
*
* @return A string owned by the library. Do not free it.
*/
const char *aksl_version_string(void);
/**
* @brief The loaded library's soname, as "MAJOR.MINOR" pre-1.0 and "MAJOR" from 1.0.
* @return A string owned by the library. Do not free it.
*/
const char *aksl_version_soname(void);
/**
* @brief Compare the caller's compiled-in version against the loaded library's.
*
* Call it through AKSL_VERSION_CHECK() rather than directly. That macro expands
* at *your* call site, so it captures the AKSL_VERSION_* you were built with;
* this function compares them against the values baked into the library. Passing
* the numbers by hand defeats the entire mechanism.
*
* Compatibility is "same soname": pre-1.0 both major and minor must match and
* patch is ignored; from 1.0 only major will matter.
*
* @param[in] major The caller's compiled-in major version.
* @param[in] minor The caller's compiled-in minor version.
* @param[in] patch The caller's compiled-in patch version. Not compared; it is
* here so the error message can name the caller's full version.
* @throws AKERR_VALUE On a mismatch, naming both versions.
* @return NULL when the pairing is compatible, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_version_check(int major, int minor, int patch);
/** @brief Check the loaded library against the one this translation unit was built against. */
#define AKSL_VERSION_CHECK() \
aksl_version_check(AKSL_VERSION_MAJOR, AKSL_VERSION_MINOR, AKSL_VERSION_PATCH)
/** @} */
/* ====================================================================== */
/** @name Memory
*
* aksl_malloc and aksl_calloc reject a zero-size request outright rather than
* reporting whatever errno happened to hold when malloc(0) returned NULL.
* aksl_realloc takes the pointer by reference and leaves it valid and untouched
* on failure, which is realloc(3)'s classic leak closed off.
* @{
*/
/* ====================================================================== */
/**
* @brief Allocate `size` bytes.
* @param[in] size Bytes to allocate. Must be non-zero.
* @param[out] dst Receives the allocation, or NULL on any failure. Required.
* @throws AKERR_NULLPOINTER If dst is NULL.
* @throws AKERR_VALUE If size is 0. There is nothing useful to hand back either
* way, and malloc(0) is permitted to return NULL without setting errno --
* which is how an error with status 0 used to get raised.
* @throws ENOMEM If the allocation fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_malloc(size_t size, void **dst);
/**
* @brief Allocate `nmemb` * `size` zeroed bytes.
* @param[in] nmemb Number of members. Must be non-zero.
* @param[in] size Bytes per member. Must be non-zero.
* @param[out] dst Receives the allocation, or NULL on any failure. Required.
* @throws AKERR_NULLPOINTER If dst is NULL.
* @throws AKERR_VALUE If either nmemb or size is 0.
* @throws ENOMEM If the allocation fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_calloc(size_t nmemb, size_t size, void **dst);
/**
* @brief Resize an allocation in place, through the same pointer.
*
* realloc(3)'s trap, hidden: on failure it returns NULL *and leaves the original
* block valid*, so the near-universal `p = realloc(p, n)` leaks the original
* every time it fails. Here the pointer goes in and out through the same
* out-param and is left untouched -- still valid, still yours to free -- whenever
* an error is raised.
*
* @param[in,out] ptr The block to resize, updated on success. `*ptr` may be
* NULL, in which case this allocates. Required.
* @param[in] size New size in bytes. Must be non-zero.
* @throws AKERR_NULLPOINTER If ptr is NULL.
* @throws AKERR_VALUE If size is 0; use aksl_free to release the block.
* @throws ENOMEM If the allocation fails. `*ptr` still points at the original,
* still-valid block.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_realloc(void **ptr, size_t size);
/**
* @brief Resize an array allocation, checking the multiplication for overflow.
*
* `realloc(p, n * size)` is a heap overflow waiting for an n large enough to
* wrap, and the multiplication is exactly the place a caller does not think to
* look.
*
* @param[in,out] ptr The block to resize, updated on success. Required.
* @param[in] nmemb Number of members. Must be non-zero.
* @param[in] size Bytes per member. Must be non-zero.
* @throws AKERR_NULLPOINTER If ptr is NULL.
* @throws AKERR_VALUE If either nmemb or size is 0.
* @throws AKERR_OUTOFBOUNDS If `nmemb * size` overflows size_t.
* @throws ENOMEM If the allocation fails. `*ptr` still points at the original.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_reallocarray(void **ptr, size_t nmemb, size_t size);
/**
* @brief aligned_alloc(3), with its two preconditions checked.
* @param[in] alignment Required alignment. Must be a power of two.
* @param[in] size Bytes to allocate. Must be a non-zero multiple of
* `alignment`, which aligned_alloc(3) requires and does not check.
* @param[out] dst Receives the allocation, or NULL on failure. Required.
* @throws AKERR_NULLPOINTER If dst is NULL.
* @throws AKERR_VALUE If alignment is 0 or not a power of two, if size is 0, or
* if size is not a multiple of alignment -- all undefined behaviour that
* usually just returns NULL.
* @throws ENOMEM If the allocation fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_aligned_alloc(size_t alignment, size_t size, void **dst);
/**
* @brief posix_memalign(3), the form that does not constrain `size`.
*
* posix_memalign(3) breaks the errno convention -- it *returns* the error number
* and leaves errno alone -- which is the kind of local irregularity a caller
* mis-handles once and never notices.
*
* @param[out] dst Receives the allocation, or NULL on failure. Required.
* @param[in] alignment Required alignment: a power of two, and a multiple of
* sizeof(void *).
* @param[in] size Bytes to allocate. Must be non-zero.
* @throws AKERR_NULLPOINTER If dst is NULL.
* @throws AKERR_VALUE If size is 0.
* @throws EINVAL If the alignment is unacceptable.
* @throws ENOMEM If the allocation fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_posix_memalign(void **dst, size_t alignment, size_t size);
/**
* @brief Release an allocation.
* @param[in] ptr The block to release.
* @throws AKERR_NULLPOINTER If ptr is NULL. This is deliberate and against
* libc: free(NULL) is legal and does nothing, but in a codebase that
* routes every allocation through aksl_malloc, freeing a pointer you
* believed was live and finding it NULL means something upstream did not
* happen -- and silence there is how a lost allocation is found three
* weeks later.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_free(void *ptr);
/**
* @brief Release an allocation and clear the caller's pointer.
*
* Prefer this to aksl_free where the pointer outlives the call: a cleared
* pointer cannot be used or freed twice.
*
* @param[in,out] ptr The block to release; set to NULL on success. Required.
* @throws AKERR_NULLPOINTER If ptr or *ptr is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_freep(void **ptr);
/**
* @brief Fill `n` bytes of `s` with `c`.
* @param[out] s Destination. Required.
* @param[in] c Byte to write, converted to unsigned char.
* @param[in] n Number of bytes. 0 is a no-op, not an error.
* @throws AKERR_NULLPOINTER If s is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_memset(void *s, int c, size_t n);
/**
* @brief Copy `n` bytes from `s` to `d`, which must not overlap.
* @param[out] d Destination. Required.
* @param[in] s Source. Required.
* @param[in] n Number of bytes. 0 is a no-op, not an error.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If the ranges overlap. memcpy(3) calls that undefined
* behaviour, which in practice means "works until a compiler version or
* a length changes". Use aksl_memmove when you mean to overlap.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_memcpy(void *d, const void *s, size_t n);
/**
* @brief Copy `n` bytes from `s` to `d`, which may overlap.
* @param[out] d Destination. Required.
* @param[in] s Source. Required.
* @param[in] n Number of bytes. 0 is a no-op, not an error.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_memmove(void *d, const void *s, size_t n);
/**
* @brief Compare `n` bytes of `a` and `b`.
* @param[in] a First buffer. Required.
* @param[in] b Second buffer. Required.
* @param[in] n Number of bytes to compare.
* @param[out] dest Negative, zero or positive as a sorts before, equal to, or
* after b. Required -- the return value is the error context.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_memcmp(const void *a, const void *b, size_t n, int *dest);
/**
* @brief Find the first `c` in the first `n` bytes of `s`.
* @param[in] s Buffer to search. Required.
* @param[in] c Byte to find, converted to unsigned char.
* @param[in] n Number of bytes to search.
* @param[out] dest The match, or NULL if there is none. Required. Not finding
* it is a successful answer, not an error.
* @throws AKERR_NULLPOINTER If s or dest is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_memchr(const void *s, int c, size_t n, void **dest);
/** @} */
/* ====================================================================== */
/** @name Formatted output
*
* `*count` is the byte count written excluding the terminating NUL, and is 0 on
* every failure path -- never vsnprintf's -1, and never the length the output
* *would* have been.
*
* There is no aksl_sprintf. It wrapped vsprintf, which cannot be bounded, and an
* error-handling wrapper around an unbounded write is the sharp edge this
* library exists to remove. aksl_snprintf replaces it.
* @{
*/
/* ====================================================================== */
/**
* @brief printf(3) to stdout.
* @param[out] count Bytes written; 0 on failure. Required.
* @param[in] format printf format string. Required. Checked at compile time.
* @throws AKERR_NULLPOINTER If count or format is NULL.
* @throws AKERR_IO Or the errno the C library saw, if the write fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_printf(int *count, const char *restrict format, ...) AKSL_PRINTF_FORMAT(2, 3);
/**
* @brief fprintf(3) to a stream.
* @param[out] count Bytes written; 0 on failure. Required.
* @param[in] stream Destination stream. Required.
* @param[in] format printf format string. Required. Checked at compile time.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_IO Or the errno the C library saw, if the write fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fprintf(int *count, FILE *restrict stream, const char *restrict format, ...) AKSL_PRINTF_FORMAT(3, 4);
/**
* @brief snprintf(3) into a bounded buffer, treating truncation as an error.
*
* snprintf(3) truncates, terminates and reports the length it *would* have
* written, leaving the caller to notice by comparing that against the buffer
* size -- the check this library exists to stop people forgetting.
*
* @param[out] count Bytes written excluding the NUL; 0 on failure. Required.
* @param[out] str Destination buffer. Required.
* @param[in] size Size of `str` including the terminator. Must be non-zero.
* @param[in] format printf format string. Required. Checked at compile time.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_VALUE If size is 0.
* @throws AKERR_OUTOFBOUNDS If the output does not fit, naming both lengths.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_snprintf(int *count, char *restrict str, size_t size, const char *restrict format, ...) AKSL_PRINTF_FORMAT(4, 5);
/**
* @brief Format into a freshly allocated string.
*
* Where aksl_snprintf is right for a fixed destination, this is right when the
* length is not knowable in advance and the result is short-lived enough not to
* want a whole aksl_StrBuf. There is no truncation case, because there is no
* fixed buffer to truncate against.
*
* @param[out] count Bytes written excluding the NUL; 0 on failure. Required.
* @param[out] dest The formatted string, or NULL on failure. Required. It is
* yours; release it with aksl_free or aksl_freep.
* @param[in] format printf format string. Required. Checked at compile time.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_IO If the arguments cannot be formatted.
* @throws ENOMEM If the allocation fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_asprintf(int *count, char **dest, const char *restrict format, ...) AKSL_PRINTF_FORMAT(3, 4);
/**
* @brief Format into a freshly allocated string. The va_list form of aksl_asprintf.
* @param[out] count Bytes written excluding the NUL; 0 on failure. Required.
* @param[out] dest The formatted string, or NULL on failure. Required.
* @param[in] format printf format string. Required.
* @param[in] args Arguments. The caller owns it and must va_end it.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_IO If the arguments cannot be formatted.
* @throws ENOMEM If the allocation fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_vasprintf(int *count, char **dest, const char *restrict format, va_list args);
/**
* @brief vprintf(3) to stdout. The va_list form of aksl_printf.
* @param[out] count Bytes written; 0 on failure. Required.
* @param[in] format printf format string. Required.
* @param[in] args Arguments. The caller owns it and must va_end it.
* @throws AKERR_NULLPOINTER If count or format is NULL.
* @throws AKERR_IO Or the errno the C library saw, if the write fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_vprintf(int *count, const char *restrict format, va_list args);
/**
* @brief vfprintf(3) to a stream. The va_list form of aksl_fprintf.
* @param[out] count Bytes written; 0 on failure. Required.
* @param[in] stream Destination stream. Required.
* @param[in] format printf format string. Required.
* @param[in] args Arguments. The caller owns it and must va_end it.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_IO Or the errno the C library saw, if the write fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_vfprintf(int *count, FILE *restrict stream, const char *restrict format, va_list args);
/**
* @brief vsnprintf(3) into a bounded buffer. The va_list form of aksl_snprintf.
* @param[out] count Bytes written excluding the NUL; 0 on failure. Required.
* @param[out] str Destination buffer. Required.
* @param[in] size Size of `str` including the terminator. Must be non-zero.
* @param[in] format printf format string. Required.
* @param[in] args Arguments. The caller owns it and must va_end it.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_VALUE If size is 0.
* @throws AKERR_OUTOFBOUNDS If the output does not fit.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_vsnprintf(int *count, char *restrict str, size_t size, const char *restrict format, va_list args);
/** @} */
/* ====================================================================== */
/** @name String to number
*
* These are strict, unlike the libc functions they are named for:
*
* | condition | status |
* |---|---|
* | no digits consumed | AKERR_VALUE |
* | trailing junk, endptr NULL | AKERR_VALUE |
* | value outside the type | ERANGE |
*
* and `*dest` is 0 on every failure. Pass a non-NULL endptr to opt out of the
* trailing-junk check and read a number off the front of a longer string.
*
* The ato* forms are the strto* ones with base 10 and a NULL endptr, so the
* whole string must be a number. "0x10" is therefore AKERR_VALUE through
* aksl_atoi -- base 10 stops at the 'x' -- and 16 through
* `aksl_strtol(nptr, NULL, 0, &dest)`.
* @{
*/
/* ====================================================================== */
/**
* @brief strtol(3) with an error channel.
* @param[in] nptr String to parse. Leading whitespace is accepted. Required.
* @param[out] endptr If non-NULL, receives the first unconsumed character and
* trailing junk is not an error. Pass NULL to require that the whole
* string be a number.
* @param[in] base 2 to 36, or 0 to auto-detect the 0x and 0 prefixes.
* @param[out] dest The value; 0 on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE If no digits were consumed, or on trailing junk when
* endptr is NULL.
* @throws ERANGE If the value does not fit a long.
* @throws EINVAL If the base is invalid.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtol(const char *nptr, char **endptr, int base, long *dest);
/**
* @brief strtoll(3) with an error channel.
* @param[in] nptr String to parse. Required.
* @param[out] endptr First unconsumed character, or NULL to reject trailing junk.
* @param[in] base 2 to 36, or 0 to auto-detect the prefix.
* @param[out] dest The value; 0 on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits, or trailing junk with a NULL endptr.
* @throws ERANGE If the value does not fit a long long.
* @throws EINVAL If the base is invalid.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtoll(const char *nptr, char **endptr, int base, long long *dest);
/**
* @brief strtoul(3) with an error channel, and without its sign surprise.
*
* strtoul(3) accepts a leading '-' and hands back the negation wrapped into the
* unsigned range, so "-1" parses as ULONG_MAX and reports nothing at all. The
* sign is rejected here before the call.
*
* @param[in] nptr String to parse. Required.
* @param[out] endptr First unconsumed character, or NULL to reject trailing junk.
* @param[in] base 2 to 36, or 0 to auto-detect the prefix.
* @param[out] dest The value; 0 on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits, trailing junk with a NULL endptr, or a
* negative input.
* @throws ERANGE If the value does not fit an unsigned long.
* @throws EINVAL If the base is invalid.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtoul(const char *nptr, char **endptr, int base, unsigned long *dest);
/**
* @brief strtoull(3) with an error channel, and without its sign surprise.
* @param[in] nptr String to parse. Required.
* @param[out] endptr First unconsumed character, or NULL to reject trailing junk.
* @param[in] base 2 to 36, or 0 to auto-detect the prefix.
* @param[out] dest The value; 0 on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits, trailing junk with a NULL endptr, or a
* negative input.
* @throws ERANGE If the value does not fit an unsigned long long.
* @throws EINVAL If the base is invalid.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtoull(const char *nptr, char **endptr, int base, unsigned long long *dest);
/**
* @brief strtod(3) with an error channel.
*
* "inf", "infinity" and "nan" are accepted in any case, because strtod(3)
* accepts them and they are exact round-trips rather than approximations of
* something else. Underflow is ERANGE as well as overflow: it is the only signal
* strtod gives, and losing every significant digit of "1e-400" is a conversion
* failure however it is spelled.
*
* @param[in] nptr String to parse. Required.
* @param[out] endptr First unconsumed character, or NULL to reject trailing junk.
* @param[out] dest The value; 0.0 on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits, or trailing junk with a NULL endptr.
* @throws ERANGE On overflow or underflow.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtod(const char *nptr, char **endptr, double *dest);
/**
* @brief strtof(3) with an error channel.
* @param[in] nptr String to parse. Required.
* @param[out] endptr First unconsumed character, or NULL to reject trailing junk.
* @param[out] dest The value; 0.0f on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits, or trailing junk with a NULL endptr.
* @throws ERANGE On overflow or underflow. float's range is far narrower than
* double's, so "1e300" is ERANGE here and fine through aksl_strtod.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtof(const char *nptr, char **endptr, float *dest);
/**
* @brief strtold(3) with an error channel.
* @param[in] nptr String to parse. Required.
* @param[out] endptr First unconsumed character, or NULL to reject trailing junk.
* @param[out] dest The value; 0.0L on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits, or trailing junk with a NULL endptr.
* @throws ERANGE On overflow or underflow.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtold(const char *nptr, char **endptr, long double *dest);
/**
* @brief atoi(3) with an error channel: base 10, whole string.
*
* atoi(3) has no error channel at all -- "not a number" converts to 0 and an
* overflowing literal wraps -- which made this the one place in the library a
* libc failure passed unnoticed.
*
* @param[in] nptr String to parse. Required.
* @param[out] dest The value; 0 on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits or trailing junk, including "0x10", where
* base 10 stops at the 'x'.
* @throws ERANGE If the value does not fit an int.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_atoi(const char *nptr, int *dest);
/**
* @brief atol(3) with an error channel: base 10, whole string.
* @param[in] nptr String to parse. Required.
* @param[out] dest The value; 0 on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits or trailing junk.
* @throws ERANGE If the value does not fit a long.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_atol(const char *nptr, long *dest);
/**
* @brief atoll(3) with an error channel: base 10, whole string.
* @param[in] nptr String to parse. Required.
* @param[out] dest The value; 0 on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits or trailing junk.
* @throws ERANGE If the value does not fit a long long.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_atoll(const char *nptr, long long *dest);
/**
* @brief atof(3) with an error channel: whole string.
* @param[in] nptr String to parse. Required.
* @param[out] dest The value; 0.0 on any failure. Required.
* @throws AKERR_NULLPOINTER If nptr or dest is NULL.
* @throws AKERR_VALUE On no digits or trailing junk.
* @throws ERANGE On overflow or underflow.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_atof(const char *nptr, double *dest);
/** @} */
/* ====================================================================== */
/** @name Paths and hashing
* @{
*/
/* ====================================================================== */
/**
* @brief realpath(3) into a caller-supplied buffer.
*
* The error message names the input path only. realpath(3) leaves the
* destination unspecified on failure, so reading it back to report on it -- as
* this used to -- reads uninitialised memory in the error path itself.
*
* @param[in] path Path to resolve. Required.
* @param[out] resolved_path Destination. Required, and cleared before the call.
* @param[in] buflen Size of `resolved_path`. Must be at least PATH_MAX.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_OUTOFBOUNDS If buflen is below PATH_MAX. realpath(3) offers no
* way to bound its own write, so a shorter buffer cannot be used safely
* and is refused rather than overflowed.
* @throws ENOENT, ENOTDIR, ELOOP, EACCES Or whatever else realpath(3) reports.
* @return NULL on success, an error context otherwise.
* @see aksl_realpath_alloc if you have no PATH_MAX-sized buffer to hand.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_realpath(const char *restrict path, char *restrict resolved_path, size_t buflen);
/**
* @brief realpath(3), allocating the result.
* @param[in] path Path to resolve. Required.
* @param[out] dest Receives the allocated result, or NULL on failure. Required.
* The buffer is yours; release it with aksl_free or aksl_freep.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws ENOENT, ENOTDIR, ELOOP, EACCES Or whatever else realpath(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_realpath_alloc(const char *restrict path, char **dest);
/**
* @brief djb2 hash over an explicit length.
*
* Length-driven rather than NUL-driven, so an embedded NUL hashes like any other
* byte. Bytes are read unsigned, so the result matches canonical djb2 and does
* not depend on whether plain char is signed on the target -- which it does on
* x86 and ARM Linux, and did not use to.
*
* @param[in] str Bytes to hash. Required.
* @param[in] len Number of bytes. 0 yields the seed, 5381.
* @param[out] hashval The hash. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2(const char *str, size_t len, uint32_t *hashval);
/**
* @brief djb2 hash of a NUL-terminated string.
* @param[in] str String to hash. Required.
* @param[out] hashval The hash. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_djb2_str(const char *str, uint32_t *hashval);
/** @} */
/* ====================================================================== */
/** @name Streams: open, read, write, close
*
* aksl_fread and aksl_fwrite take a required `size_t *nmemb_out`. It is always
* written -- including on the failure paths -- so a caller who gets AKERR_EOF can
* find out how much data arrived before the stream ran out, which is the whole
* point of distinguishing EOF from an error. A transfer that comes up short with
* no stream error set is AKERR_IO, not silent success.
* @{
*/
/* ====================================================================== */
/**
* @brief fopen(3).
* @param[in] pathname Path to open. Required; fopen(NULL, ...) is undefined
* behaviour and this used to hand it straight through.
* @param[in] mode fopen mode string. Required.
* @param[out] fp The stream, or NULL on failure. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws ENOENT, EACCES Or whatever else fopen(3) reports, with the pathname in
* the message.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fopen(const char *pathname, const char *mode, FILE **fp);
/**
* @brief fread(3), reporting how much was actually read.
* @param[out] ptr Destination buffer. Required.
* @param[in] size Bytes per member.
* @param[in] nmemb Members to read.
* @param[in] stream Stream to read from. Required.
* @param[out] nmemb_out Members actually read. Required, and written on every
* path including the failures.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_EOF If the stream ran out first. `*nmemb_out` says how much
* arrived, which is the reason this is distinguished from an error.
* @throws AKERR_IO Or the errno the C library saw, on a stream error -- and on a
* short read with neither EOF nor an error set, which the standard
* permits and which used to be reported as complete success.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fread(void *ptr, size_t size, size_t nmemb, FILE *stream, size_t *nmemb_out);
/**
* @brief fwrite(3), reporting how much was actually written.
* @param[in] ptr Source buffer. Required.
* @param[in] size Bytes per member.
* @param[in] nmemb Members to write.
* @param[in] fp Stream to write to. Required.
* @param[out] nmemb_out Members actually written. Required, and written on every
* path including the failures.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_IO Or the errno the C library saw, on a stream error or a short
* write.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp, size_t *nmemb_out);
/**
* @brief fclose(3), surfacing a failed flush.
*
* A caller who checks every write and ignores the close loses buffered data
* silently, because the failure can only appear here.
*
* @param[in] stream Stream to close. Required.
* @throws AKERR_NULLPOINTER If stream is NULL.
* @throws AKERR_IO Or the errno the C library saw. Any buffered data is lost.
* @return NULL on success, an error context otherwise.
* @warning Closing an already-closed stream is undefined behaviour and cannot be
* detected from here -- the FILE * is freed, so even reading it to check
* is the bug. This wrapper cannot help; do not do it.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fclose(FILE *stream);
/** @} */
/* ====================================================================== */
/** @name Strings
*
* Two conventions run through this whole section.
*
* The copying and concatenating functions take the size of the destination
* buffer, even the ones named after libc functions that do not. strcpy(3) and
* strcat(3) cannot be called safely without knowing how much room there is, so a
* wrapper taking the same arguments would be an error-handling wrapper around a
* buffer overflow. `dstsize` is the whole buffer including the terminator --
* pair `char buf[64]` with `sizeof(buf)`. Truncation is AKERR_OUTOFBOUNDS and
* nothing is written when it happens, so ignoring the status leaves an empty
* string rather than a plausible-looking prefix.
*
* The searching functions answer through an out-param, and finding nothing is a
* successful answer of NULL. The result points into the caller's own string, so
* it lives exactly as long as that string does and must not be freed.
* @{
*/
/* ====================================================================== */
/**
* @brief strlen(3).
* @param[in] s String to measure. Required.
* @param[out] dest The length, excluding the terminator. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strlen(const char *s, size_t *dest);
/**
* @brief strnlen(3): the length, or `maxlen`, whichever is smaller.
* @param[in] s Buffer to measure. Required.
* @param[in] maxlen Upper bound on the scan.
* @param[out] dest The length. A result equal to maxlen means "at least this
* long" rather than "this long", which is the ambiguity that makes
* strnlen the right tool for a buffer that may not be terminated.
* Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strnlen(const char *s, size_t maxlen, size_t *dest);
/**
* @brief Bounded strcpy(3).
* @param[out] dst Destination. Required, and emptied before the copy.
* @param[in] dstsize Size of `dst` including the terminator. Must be non-zero.
* @param[in] src Source string. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If dstsize is 0.
* @throws AKERR_OUTOFBOUNDS If `src` does not fit. `dst` is left empty rather
* than truncated: a caller who ignores the status gets nothing, which is
* far easier to notice than a plausible prefix.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcpy(char *dst, size_t dstsize, const char *src);
/**
* @brief Bounded strncpy(3) that always terminates and never NUL-pads.
*
* strncpy(3) does two surprising things this does not: it leaves the destination
* unterminated when the source is at least n bytes long, and it pads the
* remainder with NULs when the source is shorter, turning a short copy into a
* full-length write.
*
* @param[out] dst Destination. Required, and emptied before the copy.
* @param[in] dstsize Size of `dst` including the terminator. Must be non-zero.
* @param[in] src Source string. Required.
* @param[in] n At most this many bytes of `src` are considered.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If dstsize is 0.
* @throws AKERR_OUTOFBOUNDS If the bytes to copy do not fit.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncpy(char *dst, size_t dstsize, const char *src, size_t n);
/**
* @brief Bounded strcat(3).
* @param[in,out] dst Destination, already holding a terminated string.
* Required.
* @param[in] dstsize Size of `dst` including the terminator. Must be
* non-zero.
* @param[in] src String to append. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If dstsize is 0, or if `dst` is not terminated within
* dstsize -- refused rather than walked off the end of looking for a NUL
* that is not there.
* @throws AKERR_OUTOFBOUNDS If the result does not fit. `dst` is unchanged.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcat(char *dst, size_t dstsize, const char *src);
/**
* @brief Bounded strncat(3).
* @param[in,out] dst Destination, already holding a terminated string.
* Required.
* @param[in] dstsize Size of `dst` including the terminator. Must be
* non-zero.
* @param[in] src String to append. Required.
* @param[in] n At most this many bytes of `src` are appended.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If dstsize is 0, or `dst` is not terminated within it.
* @throws AKERR_OUTOFBOUNDS If the result does not fit. `dst` is unchanged.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncat(char *dst, size_t dstsize, const char *src, size_t n);
/**
* @brief strdup(3).
* @param[in] s String to copy. Required.
* @param[out] dest The copy, or NULL on failure. Required. It is yours; release
* it with aksl_free or aksl_freep.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws ENOMEM If the allocation fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strdup(const char *s, char **dest);
/**
* @brief strndup(3): a copy of at most `n` bytes, always terminated.
* @param[in] s String to copy. Required.
* @param[in] n Maximum bytes to copy.
* @param[out] dest The copy, or NULL on failure. Required. It is yours.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws ENOMEM If the allocation fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strndup(const char *s, size_t n, char **dest);
/**
* @brief strcmp(3).
* @param[in] a First string. Required.
* @param[in] b Second string. Required.
* @param[out] dest Negative, zero or positive as a sorts before, equal to, or
* after b. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcmp(const char *a, const char *b, int *dest);
/**
* @brief strncmp(3): compare at most `n` bytes.
* @param[in] a First string. Required.
* @param[in] b Second string. Required.
* @param[in] n Maximum bytes to compare.
* @param[out] dest The ordering. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncmp(const char *a, const char *b, size_t n, int *dest);
/**
* @brief strcasecmp(3): compare ignoring case.
* @param[in] a First string. Required.
* @param[in] b Second string. Required.
* @param[out] dest The ordering. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcasecmp(const char *a, const char *b, int *dest);
/**
* @brief strncasecmp(3): compare at most `n` bytes, ignoring case.
* @param[in] a First string. Required.
* @param[in] b Second string. Required.
* @param[in] n Maximum bytes to compare.
* @param[out] dest The ordering. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strncasecmp(const char *a, const char *b, size_t n, int *dest);
/**
* @brief strcoll(3): compare according to the current locale.
* @param[in] a First string. Required.
* @param[in] b Second string. Required.
* @param[out] dest The ordering. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws EINVAL Or whatever errno strcoll(3) sets. It has no error return of
* its own, so a malformed multibyte sequence in the current locale shows
* up only as errno -- which is why errno is cleared first and consulted
* after.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcoll(const char *a, const char *b, int *dest);
/**
* @brief strchr(3): the first `c` in `s`.
* @param[in] s String to search. Required.
* @param[in] c Character to find.
* @param[out] dest The match, or NULL if there is none. Required. Not finding it
* is a successful answer, not an error.
* @throws AKERR_NULLPOINTER If s or dest is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strchr(const char *s, int c, char **dest);
/**
* @brief strrchr(3): the last `c` in `s`.
* @param[in] s String to search. Required.
* @param[in] c Character to find.
* @param[out] dest The match, or NULL if there is none. Required.
* @throws AKERR_NULLPOINTER If s or dest is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strrchr(const char *s, int c, char **dest);
/**
* @brief strstr(3): the first `needle` in `haystack`.
* @param[in] haystack String to search. Required.
* @param[in] needle Substring to find. An empty needle matches at the start.
* Required.
* @param[out] dest The match, or NULL if there is none. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strstr(const char *haystack, const char *needle, char **dest);
/**
* @brief The first `needle` in `haystack`, ignoring case.
*
* Open-coded rather than wrapping strcasestr(3), which is a GNU extension in no
* standard, so that it does not depend on _GNU_SOURCE reaching every consumer's
* build.
*
* @param[in] haystack String to search. Required.
* @param[in] needle Substring to find. An empty needle matches at the start.
* Required.
* @param[out] dest The match, or NULL if there is none. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcasestr(const char *haystack, const char *needle, char **dest);
/**
* @brief strpbrk(3): the first character of `s` that appears in `accept`.
* @param[in] s String to search. Required.
* @param[in] accept Characters to look for. Required.
* @param[out] dest The match, or NULL if there is none. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strpbrk(const char *s, const char *accept, char **dest);
/**
* @brief strspn(3): length of the prefix of `s` made only of `accept` characters.
* @param[in] s String to measure. Required.
* @param[in] accept Characters that may appear in the prefix. Required.
* @param[out] dest The length. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strspn(const char *s, const char *accept, size_t *dest);
/**
* @brief strcspn(3): length of the prefix of `s` containing no `reject` character.
* @param[in] s String to measure. Required.
* @param[in] reject Characters that end the prefix. Required.
* @param[out] dest The length. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strcspn(const char *s, const char *reject, size_t *dest);
/**
* @brief strtok_r(3): the next token, with the state in the caller's `saveptr`.
*
* The reentrant tokeniser only. strtok(3) keeps its state in a hidden static, so
* two interleaved tokenisations corrupt each other silently and any use from a
* thread is a bug; it is not wrapped at all.
*
* Adjacent delimiters are collapsed, so "a::b" is two tokens. Use aksl_strsep if
* you want the empty field between them.
*
* @param[in] str The string on the first call, NULL on every call after.
* @param[in] delim Delimiter characters. Required.
* @param[in,out] saveptr Tokeniser state, owned by the caller. Required.
* @param[out] dest The token, or NULL when there are none left. Required.
* Running out is how the loop ends, not a fault.
* @throws AKERR_NULLPOINTER If delim, saveptr or dest is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strtok_r(char *str, const char *delim, char **saveptr, char **dest);
/**
* @brief strsep(3): the next field, keeping empty ones.
*
* Differs from aksl_strtok_r in returning the empty field between adjacent
* delimiters, which is what you want for parsing "a::b" as three fields.
*
* @param[in,out] stringp The remaining input, advanced past the field and set to
* NULL when done. Required.
* @param[in] delim Delimiter characters. Required.
* @param[out] dest The field, or NULL when there are none left. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strsep(char **stringp, const char *delim, char **dest);
/**
* @brief The message for a status, into the caller's buffer.
*
* Deliberately not strerror_r(3): there are two incompatible functions by that
* name and which one a translation unit gets depends on feature-test macros a
* consumer cannot influence from here. Going through libakerror's registry also
* names this library's own non-errno statuses -- AKERR_NULLPOINTER and the rest --
* which strerror_r could never do.
*
* @param[in] status Status to describe: an errno value or an AKERR_* one.
* @param[out] buf Destination. Required, and emptied first.
* @param[in] buflen Size of `buf` including the terminator. Must be non-zero.
* @throws AKERR_NULLPOINTER If buf is NULL.
* @throws AKERR_VALUE If buflen is 0.
* @throws AKERR_OUTOFBOUNDS If the message does not fit. `buf` is left empty.
* @return NULL on success, an error context otherwise. A status nothing
* recognises is rendered as its own number, not as an empty string.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strerror(int status, char *buf, size_t buflen);
/** @} */
/* ====================================================================== */
/** @name Streams: everything else
*
* Where a function has a genuine "nothing more to read" outcome, that is
* AKERR_EOF rather than AKERR_IO, so a read loop can tell the end of its input
* from the failure of it. That applies to aksl_fgetc, aksl_fgets, aksl_getline,
* aksl_getdelim and aksl_fscanf.
* @{
*/
/* ====================================================================== */
/**
* @brief fseek(3).
* @param[in] stream Stream to reposition. Required.
* @param[in] offset Offset in bytes.
* @param[in] whence SEEK_SET, SEEK_CUR or SEEK_END.
* @throws AKERR_NULLPOINTER If stream is NULL.
* @throws ESPIPE, EINVAL Or whatever else fseek(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fseek(FILE *stream, long offset, int whence);
/**
* @brief ftell(3): the current position.
* @param[in] stream Stream to query. Required.
* @param[out] dest The position; 0 on failure. Required -- ftell(3) reports
* failure as -1L, which is also a perfectly ordinary thing for an
* arithmetic type to hold.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws ESPIPE Or whatever else ftell(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_ftell(FILE *stream, long *dest);
/**
* @brief Seek to the start of a stream, reporting failure.
*
* rewind(3) is the one positioning call with no error return at all: it is
* fseek(stream, 0, SEEK_SET) with the result thrown away, and it clears the error
* indicator on the way past so even that evidence is gone.
*
* @param[in] stream Stream to rewind. Required.
* @throws AKERR_NULLPOINTER If stream is NULL.
* @throws ESPIPE Or whatever else the underlying seek reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_rewind(FILE *stream);
/**
* @brief fseeko(3): fseek with an off_t offset.
* @param[in] stream Stream to reposition. Required.
* @param[in] offset Offset in bytes.
* @param[in] whence SEEK_SET, SEEK_CUR or SEEK_END.
* @throws AKERR_NULLPOINTER If stream is NULL.
* @throws ESPIPE, EINVAL Or whatever else fseeko(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fseeko(FILE *stream, off_t offset, int whence);
/**
* @brief ftello(3): the current position as an off_t.
* @param[in] stream Stream to query. Required.
* @param[out] dest The position; 0 on failure. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws ESPIPE Or whatever else ftello(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_ftello(FILE *stream, off_t *dest);
/**
* @brief fgetpos(3): save an opaque stream position.
*
* Carries an fpos_t rather than a byte offset, which is what makes this the right
* pair for a stream in a multibyte locale where a byte offset is not enough to
* restore the conversion state.
*
* @param[in] stream Stream to query. Required.
* @param[out] pos The saved position. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws ESPIPE Or whatever else fgetpos(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fgetpos(FILE *stream, fpos_t *pos);
/**
* @brief fsetpos(3): restore a position saved by aksl_fgetpos.
* @param[in] stream Stream to reposition. Required.
* @param[in] pos A position from aksl_fgetpos. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws ESPIPE, EINVAL Or whatever else fsetpos(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fsetpos(FILE *stream, const fpos_t *pos);
/**
* @brief fflush(3).
* @param[in] stream Stream to flush, or **NULL for every output stream** -- which
* is fflush(3)'s own documented behaviour and genuinely useful before
* a fork or an abort, so unlike almost everywhere else in this
* library a NULL argument here is not an error.
* @throws AKERR_IO Or the errno the C library saw. The buffered data is lost.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fflush(FILE *stream);
/**
* @brief setvbuf(3).
* @param[in] stream Stream to configure. Required.
* @param[in] buf Caller-supplied buffer, or NULL to let stdio allocate one.
* @param[in] mode _IOFBF, _IOLBF or _IONBF.
* @param[in] size Size of `buf`.
* @throws AKERR_NULLPOINTER If stream is NULL.
* @throws AKERR_VALUE Or the errno the C library saw, if the call is refused.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_setvbuf(FILE *stream, char *buf, int mode, size_t size);
/**
* @brief fgetc(3), with EOF and error told apart.
*
* fgetc(3) folds three outcomes into one int: a byte, the end of the file, and a
* read error -- the last two both spelled EOF. Split apart, a read loop needs no
* ferror/feof dance at the bottom of it.
*
* @param[in] stream Stream to read from. Required.
* @param[out] dest The byte; 0 on failure. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_EOF At the end of the stream.
* @throws AKERR_IO Or the errno the C library saw, on a read error.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fgetc(FILE *stream, int *dest);
/**
* @brief fputc(3).
* @param[in] c Byte to write.
* @param[in] stream Stream to write to. Required.
* @throws AKERR_NULLPOINTER If stream is NULL.
* @throws AKERR_IO Or the errno the C library saw.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fputc(int c, FILE *stream);
/**
* @brief ungetc(3): push one byte back so the next read returns it.
* @param[in] c Byte to push back.
* @param[in] stream Stream to push it onto. Required.
* @throws AKERR_NULLPOINTER If stream is NULL.
* @throws AKERR_IO If the push-back is refused. One byte is all that is
* guaranteed; a second without an intervening read may well fail.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_ungetc(int c, FILE *stream);
/**
* @brief fgets(3) into a bounded buffer, reporting the length read.
* @param[out] s Destination. Required, and emptied before the read.
* @param[in] size Size of `s` including the terminator. Must be non-zero and
* within int range, which is what fgets(3) takes.
* @param[in] stream Stream to read from. Required.
* @param[out] len_out Bytes read, newline included if there was one; 0 on
* failure. Required. A full buffer with no trailing newline is how a
* caller spots a line longer than the buffer -- which is a short
* read, not an error: the rest of the line is still in the stream.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_VALUE If size is 0 or beyond INT_MAX.
* @throws AKERR_EOF At the end of the stream.
* @throws AKERR_IO Or the errno the C library saw, on a read error.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fgets(char *s, size_t size, FILE *stream, size_t *len_out);
/**
* @brief fputs(3).
* @param[in] s String to write. Required.
* @param[in] stream Stream to write to. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_IO Or the errno the C library saw.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fputs(const char *s, FILE *stream);
/**
* @brief getline(3): a whole line, growing the buffer as needed.
*
* Start with `char *line = NULL; size_t cap = 0;` and release the buffer once
* the loop is done -- not once per line.
*
* @param[in,out] lineptr The buffer, grown as needed. Required. `*lineptr` may
* be NULL on the first call. Release it with aksl_free.
* @param[in,out] n Capacity of `*lineptr`, updated when it grows. Required.
* @param[in] stream Stream to read from. Required.
* @param[out] len_out Bytes read, newline included; 0 on failure. Required.
* This is what distinguishes an embedded NUL from the end of the
* line, which strlen cannot.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_EOF At the end of the stream.
* @throws AKERR_IO Or the errno the C library saw, on a read error.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_getline(char **lineptr, size_t *n, FILE *stream, size_t *len_out);
/**
* @brief getdelim(3): read up to `delim`, growing the buffer as needed.
* @param[in,out] lineptr The buffer, grown as needed. Required.
* @param[in,out] n Capacity of `*lineptr`, updated when it grows. Required.
* @param[in] delim Byte to stop at.
* @param[in] stream Stream to read from. Required.
* @param[out] len_out Bytes read, delimiter included; 0 on failure. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_EOF At the end of the stream.
* @throws AKERR_IO Or the errno the C library saw, on a read error.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_getdelim(char **lineptr, size_t *n, int delim, FILE *stream, size_t *len_out);
/**
* @brief feof(3): whether the end-of-file indicator is set.
* @param[in] stream Stream to query. Required.
* @param[out] dest Non-zero if the indicator is set. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_feof(FILE *stream, int *dest);
/**
* @brief ferror(3): whether the error indicator is set.
* @param[in] stream Stream to query. Required.
* @param[out] dest Non-zero if the indicator is set. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_ferror(FILE *stream, int *dest);
/**
* @brief clearerr(3): clear both the EOF and error indicators.
* @param[in] stream Stream to clear. Required.
* @throws AKERR_NULLPOINTER If stream is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_clearerr(FILE *stream);
/**
* @brief fileno(3): the descriptor behind a stream.
* @param[in] stream Stream to query. Required.
* @param[out] dest The descriptor; -1 on failure. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws EBADF If the stream has no descriptor.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fileno(FILE *stream, int *dest);
/**
* @brief freopen(3): point an existing stream at a different file.
* @param[in] pathname File to open, or **NULL to reopen the same file with a
* new mode** -- the one thing freopen can do that fopen cannot, so
* unlike aksl_fopen's this argument is not checked.
* @param[in] mode fopen mode string. Required.
* @param[in] stream Stream to reuse. Required.
* @param[out] fp The reopened stream, or NULL on failure. Required.
* @throws AKERR_NULLPOINTER If mode, stream or fp is NULL.
* @throws ENOENT, EACCES Or whatever else freopen(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_freopen(const char *pathname, const char *mode, FILE *stream, FILE **fp);
/**
* @brief fdopen(3): wrap an open descriptor in a stream.
* @param[in] fd Descriptor to wrap. Must not be negative. Closing the
* resulting stream closes it.
* @param[in] mode fopen mode string, which must agree with how `fd` was opened.
* Required.
* @param[out] fp The stream, or NULL on failure. Required.
* @throws AKERR_NULLPOINTER If mode or fp is NULL.
* @throws AKERR_VALUE If fd is negative.
* @throws EINVAL, EBADF Or whatever else fdopen(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fdopen(int fd, const char *mode, FILE **fp);
/**
* @brief tmpfile(3): an unnamed temporary file, removed when it is closed.
* @param[out] fp The stream, or NULL on failure. Required.
* @throws AKERR_NULLPOINTER If fp is NULL.
* @throws AKERR_IO Or the errno the C library saw.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_tmpfile(FILE **fp);
/**
* @brief sscanf(3), enforcing the number of conversions you expect.
*
* scanf(3) returns how many conversions succeeded, and the caller is expected to
* compare that against the number it wrote in the format string -- by hand, from
* memory, at every call site. Getting it wrong leaves the unassigned arguments
* holding whatever they held before, which for the usual uninitialised local is
* anything at all.
*
* @param[in] str String to parse. Required.
* @param[in] format scanf format string. Required. Checked at compile time.
* @param[in] expected Conversions that must succeed, or 0 to opt out and judge
* `*assigned` yourself.
* @param[out] assigned Conversions that did succeed; 0 on failure. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_VALUE If fewer than `expected` conversions succeeded, naming both
* counts, or if no conversion was possible at all.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_sscanf(const char *str, const char *format, int expected, int *assigned, ...) AKSL_SCANF_FORMAT(2, 5);
/**
* @brief fscanf(3), enforcing the number of conversions you expect.
* @param[in] stream Stream to parse. Required.
* @param[in] format scanf format string. Required. Checked at compile time.
* @param[in] expected Conversions that must succeed, or 0 to opt out.
* @param[out] assigned Conversions that did succeed; 0 on failure. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_EOF If the stream ended before any conversion.
* @throws AKERR_VALUE If fewer than `expected` conversions succeeded.
* @throws AKERR_IO Or the errno the C library saw, on a read error.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_fscanf(FILE *stream, const char *format, int expected, int *assigned, ...) AKSL_SCANF_FORMAT(2, 5);
/**
* @brief scanf(3) from stdin, enforcing the number of conversions you expect.
* @param[in] format scanf format string. Required. Checked at compile time.
* @param[in] expected Conversions that must succeed, or 0 to opt out.
* @param[out] assigned Conversions that did succeed; 0 on failure. Required.
* @throws AKERR_NULLPOINTER If format or assigned is NULL.
* @throws AKERR_EOF If stdin ended before any conversion.
* @throws AKERR_VALUE If fewer than `expected` conversions succeeded.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_scanf(const char *format, int expected, int *assigned, ...) AKSL_SCANF_FORMAT(1, 4);
/**
* @brief scanf(3) from stdin. The va_list form of aksl_scanf.
* @param[in] format scanf format string. Required.
* @param[in] expected Conversions that must succeed, or 0 to opt out.
* @param[out] assigned Conversions that did succeed; 0 on failure. Required.
* @param[in] args Arguments. The caller owns it and must va_end it.
* @throws AKERR_NULLPOINTER If format or assigned is NULL.
* @throws AKERR_EOF If stdin ended before any conversion.
* @throws AKERR_VALUE If fewer than `expected` conversions succeeded.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_vscanf(const char *format, int expected, int *assigned, va_list args);
/**
* @brief vsscanf(3). The va_list form of aksl_sscanf.
* @param[in] str String to parse. Required.
* @param[in] format scanf format string. Required.
* @param[in] expected Conversions that must succeed, or 0 to opt out.
* @param[out] assigned Conversions that did succeed; 0 on failure. Required.
* @param[in] args Arguments. The caller owns it and must va_end it.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_VALUE If fewer than `expected` conversions succeeded.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_vsscanf(const char *str, const char *format, int expected, int *assigned, va_list args);
/**
* @brief vfscanf(3). The va_list form of aksl_fscanf.
* @param[in] stream Stream to parse. Required.
* @param[in] format scanf format string. Required.
* @param[in] expected Conversions that must succeed, or 0 to opt out.
* @param[out] assigned Conversions that did succeed; 0 on failure. Required.
* @param[in] args Arguments. The caller owns it and must va_end it.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_EOF If the stream ended before any conversion.
* @throws AKERR_VALUE If fewer than `expected` conversions succeeded.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_vfscanf(FILE *stream, const char *format, int expected, int *assigned, va_list args);
/**
* @brief remove(3): delete a file or an empty directory.
* @param[in] pathname Path to remove. Required.
* @throws AKERR_NULLPOINTER If pathname is NULL.
* @throws ENOENT, EACCES, ENOTEMPTY Or whatever else remove(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_remove(const char *pathname);
/**
* @brief rename(3).
* @param[in] oldpath Existing path. Required.
* @param[in] newpath New path. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws ENOENT, EACCES, EXDEV Or whatever else rename(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_rename(const char *oldpath, const char *newpath);
/**
* @brief mkstemp(3): create and open a uniquely named temporary file.
* @param[in,out] template_ Path template, **rewritten in place**, so it must be a
* writable buffer ending in six literal X characters. A string
* literal is a segfault. Required. The file is yours, including
* removing it.
* @param[out] fd The open descriptor; -1 on failure. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If the template does not end in six X characters --
* checked here rather than left to the kernel.
* @throws EACCES, EEXIST Or whatever else mkstemp(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_mkstemp(char *template_, int *fd);
/**
* @brief mkdtemp(3): create a uniquely named temporary directory.
* @param[in,out] template_ Path template, rewritten in place; must end in six
* literal X characters. Required. The directory is yours.
* @throws AKERR_NULLPOINTER If template_ is NULL.
* @throws AKERR_VALUE If the template does not end in six X characters.
* @throws EACCES, EEXIST Or whatever else mkdtemp(3) reports.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_mkdtemp(char *template_);
/** @} */
/* ====================================================================== */
/** @name Linked list
*
* Nothing here allocates. These relink nodes the caller already owns, so a
* caller drawing from a fixed pool can use all of them; aksl_list_free_all is
* the exception and it takes the free function to use.
*
* The bare-node functions take the head by reference wherever the head itself
* can move, which is the case a caller doing it by hand gets wrong.
* @{
*/
/* ====================================================================== */
/**
* @brief Initialise a list node.
*
* Every caller used to have to remember to memset a node before its first use,
* and a stack node that skipped it walked into garbage.
*
* @param[out] node Node to initialise. Required.
* @param[in] data Caller payload, or NULL. Never read by this library.
* @throws AKERR_NULLPOINTER If node is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_node_init(aksl_ListNode *node, void *data);
/**
* @brief Append a node to the end of a list. O(n).
* @param[in,out] list The list's head. Required.
* @param[in,out] obj Node to append. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_CIRCULAR_REFERENCE If the list contains a cycle.
* @throws AKERR_VALUE If `obj` is already in the list -- relinking it would
* orphan everything between its old position and the tail.
* @return NULL on success, an error context otherwise.
* @see aksl_list_push for the O(1) form, via the aksl_List container.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_append(aksl_ListNode *list, aksl_ListNode *obj);
/**
* @brief Unlink a node from a list.
* @param[in,out] head The list's head, updated when `node` was the head.
* Required: there is no way to move the caller's head pointer
* from a node pointer alone, and the one-argument form left the
* caller aimed at a detached node.
* @param[in,out] node Node to unlink; its own links are cleared. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_pop(aksl_ListNode **head, aksl_ListNode *node);
/**
* @brief Call `iter` for each node, head first.
* @param[in] list The list's head. Required.
* @param[in] iter Callback. Required.
* @param[in] data Passed through to every invocation of `iter`.
* @throws AKERR_NULLPOINTER If list or iter is NULL.
* @throws AKERR_CIRCULAR_REFERENCE If the list contains a cycle. Checked before
* any node is visited.
* @return NULL on success -- including when the callback stopped the walk early
* with AKERR_ITERATOR_BREAK, which is a control signal and not a failure.
* Any other status the callback raises propagates out unchanged.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate(aksl_ListNode *list, aksl_ListNodeIterator iter, void *data);
/**
* @brief Insert a node at the front of a list.
* @param[in,out] head The list's head, updated to `obj`. Required. `*head` may
* be NULL, making a one-node list.
* @param[in,out] obj Node to insert. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If `obj` is already the head.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_prepend(aksl_ListNode **head, aksl_ListNode *obj);
/**
* @brief Insert `obj` directly after `node`.
* @param[in,out] node Node to insert after. Required.
* @param[in,out] obj Node to insert. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If `node` and `obj` are the same node.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_insert_after(aksl_ListNode *node, aksl_ListNode *obj);
/**
* @brief Insert `obj` directly before `node`.
* @param[in,out] head The list's head, updated when `node` was the head.
* Required for exactly that reason.
* @param[in,out] node Node to insert before. Required.
* @param[in,out] obj Node to insert. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_VALUE If `node` and `obj` are the same node.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_insert_before(aksl_ListNode **head, aksl_ListNode *node, aksl_ListNode *obj);
/**
* @brief Count the nodes in a list. O(n).
* @param[in] head The list's head, or NULL for an empty list -- which is length
* 0, not an error.
* @param[out] dest The count. Required.
* @throws AKERR_NULLPOINTER If dest is NULL.
* @throws AKERR_CIRCULAR_REFERENCE If the list contains a cycle.
* @return NULL on success, an error context otherwise.
* @see aksl_List, whose length is a field rather than a walk.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_length(aksl_ListNode *head, size_t *dest);
/**
* @brief The first node a predicate accepts.
* @param[in] head The list's head, or NULL for an empty list.
* @param[in] pred Predicate. Required.
* @param[in] data Passed through to every invocation of `pred`.
* @param[out] dest The match, or NULL if nothing matched. Required. Not finding
* one is a successful answer.
* @throws AKERR_NULLPOINTER If pred or dest is NULL.
* @throws AKERR_CIRCULAR_REFERENCE If the list contains a cycle.
* @return NULL on success, or whatever the predicate raised -- the search stops
* there and the error propagates with its message intact.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_find(aksl_ListNode *head, aksl_ListNodePredicate pred, void *data, aksl_ListNode **dest);
/**
* @brief Reverse a list in place, swapping every node's links.
* @param[in,out] head The list's head, updated to the old tail. Required.
* `*head` may be NULL, which is a no-op.
* @throws AKERR_NULLPOINTER If head is NULL.
* @throws AKERR_CIRCULAR_REFERENCE If the list contains a cycle.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_reverse(aksl_ListNode **head);
/**
* @brief Link `other` onto the end of `head`.
* @param[in,out] head The destination list's head. Required.
* @param[in,out] other The list to append, or NULL for a no-op.
* @throws AKERR_NULLPOINTER If head is NULL.
* @throws AKERR_CIRCULAR_REFERENCE If either list contains a cycle.
* @throws AKERR_VALUE If the two lists are the same, or `other` is already
* inside `head` -- either would make a cycle.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_concat(aksl_ListNode *head, aksl_ListNode *other);
/**
* @brief Release every node in a list and clear the caller's head.
*
* A failure part-way through does not abandon the rest of the list: the first
* error is kept and returned once the walk is finished, so one bad free cannot
* become a leak of everything after it.
*
* @param[in,out] head The list's head, set to NULL. Required.
* @param[in] lfree Deallocator, or NULL for aksl_free.
* @throws AKERR_NULLPOINTER If head is NULL.
* @throws AKERR_CIRCULAR_REFERENCE If the list contains a cycle.
* @return NULL on success, or the first error `lfree` raised.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_free_all(aksl_ListNode **head, aksl_FreeFunc lfree);
/**
* @brief Call `iter` for each node, tail first.
*
* Takes a tail rather than a head because that is what the caller has after a
* walk forwards -- and because a doubly-linked list that cannot be read backwards
* is just a linked list.
*
* @param[in] tail The list's last node. Required.
* @param[in] iter Callback. Required.
* @param[in] data Passed through to every invocation of `iter`.
* @throws AKERR_NULLPOINTER If tail or iter is NULL.
* @throws AKERR_CIRCULAR_REFERENCE If the prev links contain a cycle.
* @return NULL on success, including on AKERR_ITERATOR_BREAK.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_iterate_reverse(aksl_ListNode *tail, aksl_ListNodeIterator iter, void *data);
/**
* @brief Initialise an empty tracked list.
* @param[out] list Container to initialise. Required.
* @throws AKERR_NULLPOINTER If list is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_init(aksl_List *list);
/**
* @brief Append a node to a tracked list. O(1).
* @param[in,out] list Container. Required.
* @param[in,out] obj Node to append. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_push(aksl_List *list, aksl_ListNode *obj);
/**
* @brief Insert a node at the front of a tracked list. O(1).
* @param[in,out] list Container. Required.
* @param[in,out] obj Node to insert. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_unshift(aksl_List *list, aksl_ListNode *obj);
/**
* @brief Unlink a node from a tracked list, keeping head, tail and length right.
* @param[in,out] list Container. Required.
* @param[in,out] node Node to unlink; its own links are cleared. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If `node` is not in this list -- the container's length and
* endpoints would otherwise silently stop describing it.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_remove(aksl_List *list, aksl_ListNode *node);
/**
* @brief Empty a tracked list, releasing every node.
* @param[in,out] list Container. Required. Emptied whether or not every node
* released cleanly.
* @param[in] lfree Deallocator, or NULL for aksl_free.
* @throws AKERR_NULLPOINTER If list is NULL.
* @return NULL on success, or the first error `lfree` raised.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_list_clear(aksl_List *list, aksl_FreeFunc lfree);
/** @} */
/* ====================================================================== */
/** @name Tree
*
* A traversal that works on any binary tree, plus an unbalanced binary *search*
* tree built on top of it. Inserting already-sorted data into the latter gives a
* degenerate chain, which the traversal then refuses past AKSL_TREE_MAX_DEPTH --
* bounded rather than dangerous, but this is not a balanced tree and does not
* claim to be.
*
* The insert/remove pair are the functions that set and read
* aksl_TreeNode::parent; nothing else in the library touches it.
* @{
*/
/* ====================================================================== */
/**
* @brief Initialise a tree node.
* @param[out] node Node to initialise. Required.
* @param[in] leaf Caller payload, or NULL. The comparator's operand.
* @throws AKERR_NULLPOINTER If node is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_node_init(aksl_TreeNode *node, void *leaf);
/**
* @brief Walk a tree, calling `iter` on each node.
*
* The callback may stop the walk early by raising AKERR_ITERATOR_BREAK; this
* absorbs that one status and returns success, so an early exit is not an error.
* Any other status propagates out unchanged, with its message and stack trace
* intact.
*
* @param[in] root Root of the tree to walk. Required.
* @param[in] iter Callback. Required.
* @param[in] lalloc Allocator for the internal traversal queue, or NULL for
* aksl_malloc. **Breadth-first modes only**; the depth-first modes
* allocate nothing at all.
* @param[in] lfree Deallocator for what `lalloc` returned, or NULL for
* aksl_free.
* @param[in] searchmode One of the AKSL_TREE_SEARCH_* values.
* @param[in] data Passed through to every invocation of `iter`.
* @throws AKERR_NULLPOINTER If root or iter is NULL.
* @throws AKERR_VALUE On an unrecognised searchmode. This used to return success
* having visited nothing.
* @throws AKERR_OUTOFBOUNDS If the tree is deeper than AKSL_TREE_MAX_DEPTH.
* @throws AKERR_CIRCULAR_REFERENCE If a *depth-first* walk reaches a node that is
* its own ancestor. A breadth-first walk has no ancestor chain to compare
* against, so the same tree comes back as AKERR_OUTOFBOUNDS through the
* depth cap instead.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_iterate(aksl_TreeNode *root, aksl_TreeNodeIterator iter, aksl_AllocFunc lalloc, aksl_FreeFunc lfree, uint8_t searchmode, void *data);
/**
* @brief Insert a node into a binary search tree.
* @param[in,out] root Root of the tree, set when the tree was empty. Required.
* @param[in,out] node Node to insert; its links are reset first. Required.
* @param[in] cmp Comparator over the nodes' `leaf` pointers. Required.
* @throws AKERR_NULLPOINTER If any pointer is NULL.
* @throws AKERR_OUTOFBOUNDS If the tree is already AKSL_TREE_MAX_DEPTH deep.
* @return NULL on success, or whatever the comparator raised.
* @note Equal keys go right, so insertion order is preserved among them and a
* duplicate is stored rather than refused. Check with aksl_tree_find first
* if you want uniqueness.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_insert(aksl_TreeNode **root, aksl_TreeNode *node, aksl_TreeCompareFunc cmp);
/**
* @brief Find the node matching `leaf`.
* @param[in] root Root of the tree, or NULL for an empty one.
* @param[in] leaf The key to look for, as the comparator understands it.
* @param[in] cmp Comparator. Required.
* @param[out] dest The match, or NULL if there is none. Required. Not finding
* one is a successful answer.
* @throws AKERR_NULLPOINTER If cmp or dest is NULL.
* @throws AKERR_OUTOFBOUNDS If the tree is deeper than AKSL_TREE_MAX_DEPTH.
* @return NULL on success, or whatever the comparator raised.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_find(aksl_TreeNode *root, void *leaf, aksl_TreeCompareFunc cmp, aksl_TreeNode **dest);
/**
* @brief Remove a node from a binary search tree, keeping it ordered.
*
* The removed node's own links are cleared, so it can go straight back into a
* pool or be inserted somewhere else without carrying stale pointers.
*
* @param[in,out] root Root of the tree, updated when the root itself is removed.
* Required.
* @param[in,out] node Node to remove. Required, and must be in this tree.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @throws AKERR_VALUE If the tree is empty.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_remove(aksl_TreeNode **root, aksl_TreeNode *node);
/**
* @brief The height of a tree in nodes: 0 when empty, 1 for a single node.
* @param[in] root Root of the tree, or NULL.
* @param[out] dest The height. Required.
* @throws AKERR_NULLPOINTER If dest is NULL.
* @throws AKERR_OUTOFBOUNDS If the tree is deeper than AKSL_TREE_MAX_DEPTH.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_height(aksl_TreeNode *root, int *dest);
/**
* @brief The number of nodes in a tree.
* @param[in] root Root of the tree, or NULL.
* @param[out] dest The count. Required.
* @throws AKERR_NULLPOINTER If dest is NULL.
* @throws AKERR_OUTOFBOUNDS If the tree is deeper than AKSL_TREE_MAX_DEPTH.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_count(aksl_TreeNode *root, size_t *dest);
/**
* @brief Release every node in a tree and clear the caller's root.
*
* Post-order, necessarily: a node's children have to be released before the node
* that points at them, and any other order reads freed memory to find the second
* subtree. The first failure is kept and returned at the end rather than
* abandoning the rest of the tree.
*
* @param[in,out] root Root of the tree, set to NULL. Required.
* @param[in] lfree Deallocator, or NULL for aksl_free.
* @throws AKERR_NULLPOINTER If root is NULL.
* @throws AKERR_OUTOFBOUNDS If the tree is deeper than AKSL_TREE_MAX_DEPTH.
* @return NULL on success, or the first error `lfree` raised.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_tree_free_all(aksl_TreeNode **root, aksl_FreeFunc lfree);
/** @} */
/* ====================================================================== */
/** @name Hash map
*
* Fixed-capacity, open-addressed, linear-probing, string-keyed. The caller
* supplies the slot array, keys are copied into fixed-size slots so the map owns
* them, and deletion leaves a tombstone rather than an empty slot -- clearing the
* slot outright would cut the probe chain of anything that hashed past it.
*
* It refuses rather than resizes when full, deliberately. A table that
* reallocates is a table whose entry pointers move underneath anything holding
* one; a table that cannot grow has a worst case you can state. The cost is that
* the caller sizes it, which is why init takes the array rather than making one.
* @{
*/
/* ====================================================================== */
#define AKSL_HASHMAP_MAX_KEY 64 /**< Longest key, terminator included */
#define AKSL_HASHMAP_SLOT_EMPTY 0 /**< Never used; ends a probe chain */
#define AKSL_HASHMAP_SLOT_OCCUPIED 1 /**< Holds a live key and value */
#define AKSL_HASHMAP_SLOT_DELETED 2 /**< Tombstone; reusable, but does not end a chain */
/** @brief One slot in an aksl_HashMap. Allocate an array of these and hand it to aksl_hashmap_init. */
typedef struct aksl_HashEntry {
char key[AKSL_HASHMAP_MAX_KEY]; /**< The key, copied in; the map owns it. */
void *value; /**< Caller payload. */
uint8_t state; /**< One of AKSL_HASHMAP_SLOT_*. */
} aksl_HashEntry;
/** @brief A fixed-capacity string-keyed hash map over a caller-supplied slot array. */
typedef struct aksl_HashMap {
aksl_HashEntry *slots; /**< The caller's slot array. */
size_t capacity; /**< Number of slots. */
size_t count; /**< Live entries, tombstones excluded. */
} aksl_HashMap;
/**
* @brief Initialise a map over a caller-supplied slot array.
* @param[out] map Map to initialise. Required.
* @param[out] slots Slot array, which the caller owns and must keep alive for
* as long as the map. Required, and cleared here.
* @param[in] capacity Number of slots. Must be non-zero.
* @throws AKERR_NULLPOINTER If map or slots is NULL.
* @throws AKERR_VALUE If capacity is 0.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_init(aksl_HashMap *map, aksl_HashEntry *slots, size_t capacity);
/**
* @brief Insert a key, or replace an existing key's value.
* @param[in,out] map Map. Required, and initialised.
* @param[in] key Key, copied into the map. Required.
* @param[in] value Caller payload, which may be NULL.
* @throws AKERR_NULLPOINTER If map or key is NULL, or the map is uninitialised.
* @throws AKERR_OUTOFBOUNDS If the key is at least AKSL_HASHMAP_MAX_KEY bytes --
* refused rather than truncated, since a truncated key would silently
* collide with a different one sharing its prefix. Also if the map is
* full, naming the capacity it hit.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_put(aksl_HashMap *map, const char *key, void *value);
/**
* @brief Look a key up.
* @param[in] map Map. Required, and initialised.
* @param[in] key Key to find. Required.
* @param[out] value The value, written only when the key is present. May be NULL
* if you only want to know whether the key is there.
* @param[out] found Non-zero when the key was present. Required.
* @throws AKERR_NULLPOINTER If map, key or found is NULL, or the map is
* uninitialised.
* @throws AKERR_OUTOFBOUNDS If the key is too long to be in the map at all.
* @return NULL on success. A key that is not there is `*found = 0` and success,
* not an error: looking something up and not finding it is the ordinary
* case in a symbol table.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_get(aksl_HashMap *map, const char *key, void **value, int *found);
/**
* @brief Remove a key, leaving a tombstone.
* @param[in,out] map Map. Required, and initialised.
* @param[in] key Key to remove. Required.
* @param[out] removed Non-zero when the key was present. Optional.
* @throws AKERR_NULLPOINTER If map or key is NULL, or the map is uninitialised.
* @throws AKERR_OUTOFBOUNDS If the key is too long to be in the map at all.
* @return NULL on success. Removing a key that is not there is success with
* `*removed = 0`.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_remove(aksl_HashMap *map, const char *key, int *removed);
/**
* @brief Call `iter` for each live entry.
* @param[in] map Map. Required, and initialised.
* @param[in] iter Callback. Required.
* @param[in] data Passed through to every invocation of `iter`.
* @throws AKERR_NULLPOINTER If map or iter is NULL, or the map is uninitialised.
* @return NULL on success, including on AKERR_ITERATOR_BREAK.
* @note Entries are visited in slot order, which is to say in no order a caller
* can predict or should rely on.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_hashmap_iterate(aksl_HashMap *map, aksl_HashMapIterator iter, void *data);
/**
* @brief FNV-1a hash over an explicit length.
*
* Alongside djb2 rather than instead of it: FNV-1a XORs then multiplies rather
* than multiplying then adding, which mixes the low bits better on short keys
* that share a prefix -- which is what identifiers in a symbol table look like.
*
* @param[in] str Bytes to hash. Required.
* @param[in] len Number of bytes. 0 yields the offset basis, 2166136261.
* @param[out] hashval The hash. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_fnv1a(const char *str, size_t len, uint32_t *hashval);
/**
* @brief FNV-1a hash of a NUL-terminated string.
* @param[in] str String to hash. Required.
* @param[out] hashval The hash. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strhash_fnv1a_str(const char *str, uint32_t *hashval);
/** @} */
/* ====================================================================== */
/** @name Growable string buffer
*
* The one thing in this library that owns memory. The bounded formatting
* wrappers are the right answer when the destination is a fixed buffer and no
* answer at all when the output length is not known in advance; this is that
* answer.
*
* Capacity doubles, so n appends cost O(n) amortised. The contents are always
* NUL-terminated, so aksl_strbuf_cstr is valid at any point without a finalise
* step.
* @{
*/
/* ====================================================================== */
/** @brief A growable, always-terminated character buffer. Initialise with aksl_strbuf_init. */
typedef struct aksl_StrBuf {
char *data; /**< The characters. NULL before init and after free. */
size_t length; /**< Bytes in use, terminator not counted. */
size_t capacity; /**< Bytes allocated, terminator included. */
} aksl_StrBuf;
/**
* @brief Allocate a string buffer.
* @param[out] buf Buffer to initialise. Required.
* @param[in] initial Requested capacity in bytes. Raised to a small minimum if
* it is lower; 0 is fine and means "you decide".
* @throws AKERR_NULLPOINTER If buf is NULL.
* @throws ENOMEM If the allocation fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_init(aksl_StrBuf *buf, size_t initial);
/**
* @brief Append a NUL-terminated string.
* @param[in,out] buf Buffer. Required, and initialised.
* @param[in] s String to append. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL, or the buffer is
* uninitialised.
* @throws AKERR_OUTOFBOUNDS If the required capacity overflows size_t.
* @throws ENOMEM If growing the buffer fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_append(aksl_StrBuf *buf, const char *s);
/**
* @brief Append `n` bytes, which may include NULs.
* @param[in,out] buf Buffer. Required, and initialised.
* @param[in] s Bytes to append. Required.
* @param[in] n Number of bytes.
* @throws AKERR_NULLPOINTER If either pointer is NULL, or the buffer is
* uninitialised.
* @throws AKERR_OUTOFBOUNDS If the required capacity overflows size_t.
* @throws ENOMEM If growing the buffer fails.
* @return NULL on success, an error context otherwise.
* @note An embedded NUL is stored and counted in `length`, but the C string view
* from aksl_strbuf_cstr stops at the first one.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_append_bytes(aksl_StrBuf *buf, const char *s, size_t n);
/**
* @brief Append one character.
* @param[in,out] buf Buffer. Required, and initialised.
* @param[in] c Character to append.
* @throws AKERR_NULLPOINTER If buf is NULL or uninitialised.
* @throws ENOMEM If growing the buffer fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_append_char(aksl_StrBuf *buf, char c);
/**
* @brief Append formatted output, growing the buffer to fit.
*
* Unlike aksl_snprintf into a fixed array, a long result grows the destination
* rather than being truncation.
*
* @param[in,out] buf Buffer. Required, and initialised.
* @param[in] format printf format string. Required. Checked at compile time.
* @throws AKERR_NULLPOINTER If either pointer is NULL, or the buffer is
* uninitialised.
* @throws AKERR_IO If the arguments cannot be formatted.
* @throws ENOMEM If growing the buffer fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_appendf(aksl_StrBuf *buf, const char *format, ...) AKSL_PRINTF_FORMAT(2, 3);
/**
* @brief Append formatted output. The va_list form of aksl_strbuf_appendf.
* @param[in,out] buf Buffer. Required, and initialised.
* @param[in] format printf format string. Required.
* @param[in] args Arguments. The caller owns it and must va_end it; this
* copies it internally rather than consuming it twice.
* @throws AKERR_NULLPOINTER If either pointer is NULL, or the buffer is
* uninitialised.
* @throws AKERR_IO If the arguments cannot be formatted.
* @throws ENOMEM If growing the buffer fails.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_vappendf(aksl_StrBuf *buf, const char *format, va_list args);
/**
* @brief Empty a buffer without releasing its storage, so the capacity is reused.
* @param[in,out] buf Buffer. Required, and initialised.
* @throws AKERR_NULLPOINTER If buf is NULL or uninitialised.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_reset(aksl_StrBuf *buf);
/**
* @brief The buffer's contents as a C string.
* @param[in] buf Buffer. Required, and initialised.
* @param[out] dest The contents. Required.
* @throws AKERR_NULLPOINTER If either pointer is NULL, or the buffer is
* uninitialised.
* @return NULL on success, an error context otherwise.
* @warning `*dest` points into the buffer, so the next append invalidates it.
* Copy it with aksl_strdup if it has to outlive one.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_cstr(aksl_StrBuf *buf, const char **dest);
/**
* @brief Release a buffer's storage and zero it.
*
* Leaves the buffer in the state that makes a second free an error rather than a
* double free, exactly as aksl_freep arranges for a bare pointer.
*
* @param[in,out] buf Buffer. Required, and initialised.
* @throws AKERR_NULLPOINTER If buf is NULL or already released.
* @return NULL on success, an error context otherwise.
*/
akerr_ErrorContext AKERR_NOIGNORE *aksl_strbuf_free(aksl_StrBuf *buf);
/** @} */
#ifdef __cplusplus
}
#endif
#endif