/** * @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 /* * 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 /* * 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 #include #include #include /* off_t, for the aksl_fseeko/aksl_ftello pair. POSIX, like aksl_realpath. */ #include #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