From 14e903a81cfbd995e1936f5a5ca4cfa7ab437813 Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Mon, 12 Jan 2026 08:33:31 -0500 Subject: [PATCH] Import all system definitions of `errno` including their string representations, then layer our error definitions on top of that --- CMakeLists.txt | 19 +++++++++++++- README.md | 15 ++++++++--- include/{akerror.h => akerror.tmpl.h} | 38 ++++++++++++++++----------- scripts/generrno.sh | 16 +++++++++++ 4 files changed, 68 insertions(+), 20 deletions(-) rename include/{akerror.h => akerror.tmpl.h} (85%) create mode 100644 scripts/generrno.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d1c582..e0d1265 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,10 +7,27 @@ include(CMakePackageConfigHelpers) set(AKERR_USE_STDLIB 1 CACHE BOOL "Use the C standard library") set(akerror_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/akerror") +set(SCRIPT ${CMAKE_SOURCE_DIR}/scripts/generrno.sh) +set(INFILE ${CMAKE_SOURCE_DIR}/include/akerror.tmpl.h) +set(OUTFILES ${CMAKE_SOURCE_DIR}/src/errno.c ${CMAKE_SOURCE_DIR}/include/akerror.h) +add_custom_command( + OUTPUT ${OUTFILES} + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_SOURCE_DIR} + COMMAND /usr/bin/env bash ${SCRIPT} ${CMAKE_SOURCE_DIR} + DEPENDS ${SCRIPT} ${INFILE} + VERBATIM +) + +set_source_files_properties(src/errno.c PROPERTIES GENERATED TRUE) + +add_custom_target(generrno DEPENDS src/errno.c) + find_package(PkgConfig REQUIRED) add_library(akerror STATIC - src/error.c + src/error.c + src/errno.c ) +add_dependencies(akerror generrno) target_compile_definitions(akerror PUBLIC AKERR_USE_STDLIB=${AKERR_USE_STDLIB} diff --git a/README.md b/README.md index 1d11460..5bd4649 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,14 @@ Any function which uses the `PREPARE_ERROR` macro should have a return type of ` The library uses integer values to specify error codes inside of its context. These integer return codes are defined in `akerror.h` in the form of `AKERR_xxxxx` where `xxxxx` is the name of the error code in question. See `akerror.h` for a list of defined errors and their descriptions. -You can define additional error types by defining additional `AKERR_xxxxx` values. Error values up to 127 are reserved by the library, so begin your error values at 128. Define a human-friendly name for the error with the `error_name_for_status` method: +You can define additional error types by defining additional `AKERR_xxxxx` values. Error values up to 255 are reserved by the library (`AKERR_xxxxx` begins where `errno` stops), so please begin your error values at 256. When you add additional error codes, you need to define `-DAKERR_MAX_ERR_VALUE=n` to the compiler, where `n` is the maximum error code you have defined. If you define custom error codes, `AKERR_MAX_ERR_VALUE` must be >= 256 or the compiler will throw an error. + +Define a human-friendly name for the error with the `error_name_for_status` method somewhere in your code's initialization before the error may be used: ```c error_name_for_status(129, "Some Error Code Description") ``` -When you add additional error codes, you need to define `-DAKERR_MAX_ERR_VALUE=n` to the compiler, where `n` is the maximum error code you have defined. # Installation @@ -78,6 +79,14 @@ cmake --build build cmake --install build ``` +## Templating and autogenerated code + +The build process relies upon `scripts/generrno.sh` which performs the following: + +1. Executes `errno --list` and gathers up the output +1. Templates `include/akerror.tmpl.h` into `include/akerror.h` to set the `AKERR_LAST_ERRNO_VALUE` equal to the highest integer defined by `errno` +2. Generates `src/errno.c` which contains a function called by `akerr_init` which initializes all of the status names for the previously defined values of `errno`. + ## Dependencies This library depends upon `stdlib`. If you don't want to link against stdlib, you must modify the library code to include headers and link against a library that provides the following: @@ -172,7 +181,7 @@ ATTEMPT { `PROCESS(errctx) { ... }` is the block within which you will handle any errors that were caught inside of the `ATTEMPT` block. See "Handling Errors" below. -`FINISH(errctx, true)` terminates the attempt operation. The `FINISH` macro takes two arguments: the name of the akerr_ErrorContext, and a boolean regarding whether or not to pass unhandled errors up to the calling function. Unless you are inside of your `main()` method, this should be true. Inside of your `main()` method, call `FINISH_NORETURN(errctx)` instead. +`FINISH(errctx, true)` terminates the attempt operation. The `FINISH` macro takes two arguments: the name of the akerr_ErrorContext, and a boolean regarding whether or not to pass unhandled errors up to the calling function. Unless you are inside of your `main()` method, this should be true. Inside of your `main()` method, call `FINISH_NORExbTURN(errctx)` instead. # Capturing errors diff --git a/include/akerror.h b/include/akerror.tmpl.h similarity index 85% rename from include/akerror.h rename to include/akerror.tmpl.h index 9d4187a..8c69f09 100644 --- a/include/akerror.h +++ b/include/akerror.tmpl.h @@ -14,23 +14,27 @@ #define AKERR_MAX_ERROR_FUNCTION_LENGTH 128 #define AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH 2048 -#define AKERR_NULLPOINTER 1 -#define AKERR_OUTOFBOUNDS 2 -#define AKERR_API 3 -#define AKERR_ATTRIBUTE 4 -#define AKERR_TYPE 5 -#define AKERR_KEY 6 -#define AKERR_HEAP 7 -#define AKERR_INDEX 8 -#define AKERR_FORMAT 9 -#define AKERR_IO 10 -#define AKERR_REGISTRY 11 -#define AKERR_VALUE 12 -#define AKERR_BEHAVIOR 13 -#define AKERR_RELATIONSHIP 14 +#define AKERR_LAST_ERRNO_VALUE AKERR_LAST_ERRNO_VALUE_SED + +#define AKERR_NULLPOINTER (AKERR_LAST_ERRNO_VALUE + 1) +#define AKERR_OUTOFBOUNDS (AKERR_LAST_ERRNO_VALUE + 2) +#define AKERR_API (AKERR_LAST_ERRNO_VALUE + 3) +#define AKERR_ATTRIBUTE (AKERR_LAST_ERRNO_VALUE + 4) +#define AKERR_TYPE (AKERR_LAST_ERRNO_VALUE + 5) +#define AKERR_KEY (AKERR_LAST_ERRNO_VALUE + 6) +#define AKERR_HEAP (AKERR_LAST_ERRNO_VALUE + 7) +#define AKERR_INDEX (AKERR_LAST_ERRNO_VALUE + 8) +#define AKERR_FORMAT (AKERR_LAST_ERRNO_VALUE + 9) +#define AKERR_IO (AKERR_LAST_ERRNO_VALUE + 10) +#define AKERR_REGISTRY (AKERR_LAST_ERRNO_VALUE + 11) +#define AKERR_VALUE (AKERR_LAST_ERRNO_VALUE + 12) +#define AKERR_BEHAVIOR (AKERR_LAST_ERRNO_VALUE + 13) +#define AKERR_RELATIONSHIP (AKERR_LAST_ERRNO_VALUE + 14) #ifndef AKERR_MAX_ERR_VALUE -#define AKERR_MAX_ERR_VALUE 14 +#define AKERR_MAX_ERR_VALUE (AKERR_LAST_ERRNO_VALUE + 14) +#elif AKERR_MAX_ERR_VALUE < 256 +#error user-defined AKERR_MAX_ERR_VALUE must be >= 256 #endif extern char __AKERR_ERROR_NAMES[AKERR_MAX_ERR_VALUE+1][AKERR_MAX_ERROR_NAME_LENGTH]; @@ -69,6 +73,8 @@ char *akerr_name_for_status(int status, char *name); void akerr_init(); void akerr_default_handler_unhandled_error(akerr_ErrorContext *ptr); void akerr_default_logger(const char *f, ...); +/* defined in src/errno.c which is built dynamically at build time from system errno definitions */ +void akerr_init_errno(void); #define LOG_ERROR_WITH_MESSAGE(__err_context, __err_message) \ akerr_log_method("%s%s:%s:%d: %s %d (%s): %s", (char *)&__err_context->stacktracebuf, (char *)__FILE__, (char *)__func__, __LINE__, __err_message, __err_context->status, akerr_name_for_status(__err_context->status, NULL), __err_context->message); \ @@ -154,7 +160,7 @@ void akerr_default_logger(const char *f, ...); snprintf((char *)__err_context->function, AKERR_MAX_ERROR_FUNCTION_LENGTH, __func__); \ __err_context->lineno = __LINE__; \ snprintf((char *)__err_context->message, AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH, __message, ## __VA_ARGS__); \ - __err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d: %d (%s) : %s\n", (char *)__err_context->fname, (char *)__err_context->function, __err_context->lineno, __err_context->status, akerr_name_for_status(__err_context->status, NULL), __err_context->message); + __err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d: %d (%s) : %s\n", (char *)__err_context->fname, (char *)__err_context->function, __err_context->lineno, __err_context->status, akerr_name_for_status(__err_context->status, NULL), (__err_context->message == NULL ? "" : __err_context->message)); #define SUCCEED(__err_context) \ diff --git a/scripts/generrno.sh b/scripts/generrno.sh new file mode 100644 index 0000000..8252228 --- /dev/null +++ b/scripts/generrno.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +srcdir=$1 +rm -f ${srcdir}/src/errno.c +echo "#include " >> ${srcdir}/src/errno.c +echo "#include " >> ${srcdir}/src/errno.c +echo "void akerr_init_errno(void) {" >> ${srcdir}/src/errno.c +maxval=$(errno --list | cut -d ' ' -f 2 | sort -g | tail -n 1) +errno --list | while read LINE; do + define=$(echo "$LINE" | cut -d ' ' -f 1); + value=$(echo "$LINE" | cut -d ' ' -f 2); + desc=$(echo "$LINE" | cut -d ' ' -f 3-); + echo " akerr_name_for_status(${define}, \"${desc}\");" >> ${srcdir}/src/errno.c ; +done; +echo "}" >> ${srcdir}/src/errno.c +sed "s/#define AKERR_LAST_ERRNO_VALUE .*/#define AKERR_LAST_ERRNO_VALUE ${maxval}/" ${srcdir}/include/akerror.tmpl.h > ${srcdir}/include/akerror.h