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>
2255 lines
103 KiB
C
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
|