All checks were successful
The freestanding build (-DAKERR_USE_STDLIB=OFF) did not compile at all: bool, PATH_MAX and NULL were used unconditionally in the public header but only included under the stdlib branch, and the CMake option was pasted straight into a preprocessor definition, so a non-numeric cache spelling (-DAKERR_USE_STDLIB=ON) silently evaluated to 0. - Normalize AKERR_USE_STDLIB to a plain 1/0 in CMake before stamping it, and use a consistent '#if AKERR_USE_STDLIB' everywhere it is tested. - Include <stdbool.h>/<stddef.h> unconditionally (freestanding-safe); keep <stdlib.h>/<string.h>/<stdio.h> behind AKERR_USE_STDLIB; move <limits.h> out of the public header into src/error.c, its only user. - Define the freestanding runtime contract: AKERR_RUNTIME_HEADER must name a header providing exit, memset, snprintf, strcmp, strlen and strncpy when AKERR_USE_STDLIB is OFF, or the header #errors naming them. Add cmake/akerr_default_runtime.h, a libc-backed convenience default so this repo's own OFF build and tests work out of the box. - Route ENSURE_ERROR_READY's pool-exhaustion path through akerr_exit() instead of a direct exit(1). Deliberate behavior change: that exit code moves from 1 to AKERR_EXIT_STATUS_UNREPRESENTABLE (125), the same sentinel every other unrepresentable status already uses. Documented in docs/building.md and UPGRADING.md. Not an ABI break. - Retire PATH_MAX: AKERR_MAX_ERROR_FNAME_LENGTH is now stamped by scripts/generrno.sh from a new AKERR_MAX_ERROR_FNAME_LENGTH cache variable, defaulting to 4096 (PATH_MAX on Linux/glibc) so sizeof(akerr_ErrorContext) and the soname are unchanged. Rewrite the now-stale PATH_MAX justification in src/lock.h's feature-test-macro comment. - Fail the configure with a FATAL_ERROR, not a warning, when AKERR_USE_STDLIB=OFF and AKERR_THREADS would resolve to pthread, naming -DAKERR_THREADS=none as the fix. - Keep the generated errno table (errno.c) out of the OFF build; skip the 'errno --list' shellout in scripts/generrno.sh under OFF and stamp AKERR_LAST_ERRNO_VALUE from a new fallback cache variable (default 133, Linux's EHWPOISON) instead. - Update docs/building.md: drop the known-defect paragraph, fix sprintf -> snprintf, add size_t, drop PATH_MAX, and document the new options and the exit-code change. - Guard tests/err_errno.c's registered-name assertion behind AKERR_USE_STDLIB: akerr_init_errno() is not called when it is OFF. - Add a CI job that configures/builds/tests AKERR_USE_STDLIB=OFF with AKERR_THREADS=none, plus a compile-only -nostdinc -ffreestanding check of tests/freestanding_fixture.c against the generated header. Bump the project version to 2.0.2 (no ABI break: soname and struct layout are unchanged). Verified locally: OFF+none configures and builds clean with all tests passing; ON and the plain default build both build and pass their full test suites (37/37); OFF with the default/auto thread backend fails configure with a message naming -DAKERR_THREADS=none; sizeof(akerr_ErrorContext) is unchanged (37296 bytes, fname/function still 4096 each) versus the pre-change tree. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
124 lines
4.1 KiB
C
124 lines
4.1 KiB
C
#ifndef _AKERR_LOCK_H_
|
|
#define _AKERR_LOCK_H_
|
|
|
|
/*
|
|
* Serialization for the library's process-global state: the error pool
|
|
* (AKERR_ARRAY_ERROR) and the status registry. Private to the library -- none
|
|
* of this appears in the installed header, so the backend is not part of the
|
|
* ABI and can be changed without touching a consumer.
|
|
*
|
|
* The backend is chosen at configure time by the AKERR_THREADS build option and
|
|
* never by autodetection here. A build that quietly decided it did not need
|
|
* locking is exactly the failure this has to prevent: it would produce a
|
|
* library that reports itself thread safe and is not.
|
|
*
|
|
* AKERR_THREADS_PTHREAD POSIX threads.
|
|
* AKERR_THREADS_NONE No locking at all, for a build that has declared
|
|
* itself single threaded (-DAKERR_THREADS=none).
|
|
*
|
|
* One lock covers both tables, and it is recursive. Both are deliberate:
|
|
*
|
|
* - Raising an error re-enters the library. FAIL() calls
|
|
* akerr_name_for_status() to render the status into the stack trace and
|
|
* ENSURE_ERROR_READY() to check a context out of the pool, so a refusal
|
|
* raised from inside a locked registry operation takes the lock again on
|
|
* the same thread. A non-recursive mutex deadlocks there.
|
|
* - With a single lock there is no lock ordering to get wrong, and no way for
|
|
* a future caller to acquire the pool and the registry in the opposite
|
|
* order from this file.
|
|
*
|
|
* The cost is that error *construction* is serialized across threads. Errors
|
|
* are the exceptional path; correctness is worth more there than throughput.
|
|
*/
|
|
|
|
/*
|
|
* PTHREAD_MUTEX_RECURSIVE is XSI, so glibc hides it under a strict -std=c99
|
|
* without _XOPEN_SOURCE. No feature-test macro is defined here: this file is
|
|
* only ever compiled with AKERR_THREADS_PTHREAD, which CMake now refuses to
|
|
* pair with AKERR_USE_STDLIB=OFF (see the AKERR_THREADS/AKERR_USE_STDLIB
|
|
* check in CMakeLists.txt), so whatever default feature-test macros the host
|
|
* libc uses when building the rest of the (hosted) library apply here too.
|
|
* Build with -D_XOPEN_SOURCE=700 if you need strict C99.
|
|
*/
|
|
#if defined(AKERR_THREADS_PTHREAD) && AKERR_THREADS_PTHREAD == 1
|
|
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
|
|
typedef pthread_mutex_t akerr_Mutex;
|
|
typedef pthread_once_t akerr_Once;
|
|
#define AKERR_ONCE_INIT PTHREAD_ONCE_INIT
|
|
|
|
/*
|
|
* Terminal on failure. There is no error context to raise into: the pool one
|
|
* would come from is the thing this lock protects, and every path that could
|
|
* report the failure needs the lock to do it. A process whose error library
|
|
* silently stopped locking is worse than one that stops here.
|
|
*/
|
|
static void akerr_mutex_init(akerr_Mutex *mutex)
|
|
{
|
|
pthread_mutexattr_t attr;
|
|
|
|
if ( pthread_mutexattr_init(&attr) != 0 ||
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0 ||
|
|
pthread_mutex_init(mutex, &attr) != 0 ) {
|
|
abort();
|
|
}
|
|
pthread_mutexattr_destroy(&attr);
|
|
}
|
|
|
|
static void akerr_mutex_lock(akerr_Mutex *mutex)
|
|
{
|
|
pthread_mutex_lock(mutex);
|
|
}
|
|
|
|
static void akerr_mutex_unlock(akerr_Mutex *mutex)
|
|
{
|
|
pthread_mutex_unlock(mutex);
|
|
}
|
|
|
|
static void akerr_once(akerr_Once *once, void (*routine)(void))
|
|
{
|
|
pthread_once(once, routine);
|
|
}
|
|
|
|
#elif defined(AKERR_THREADS_NONE) && AKERR_THREADS_NONE == 1
|
|
|
|
typedef char akerr_Mutex;
|
|
typedef int akerr_Once;
|
|
#define AKERR_ONCE_INIT 0
|
|
|
|
static void akerr_mutex_init(akerr_Mutex *mutex)
|
|
{
|
|
(void)mutex;
|
|
}
|
|
|
|
static void akerr_mutex_lock(akerr_Mutex *mutex)
|
|
{
|
|
(void)mutex;
|
|
}
|
|
|
|
static void akerr_mutex_unlock(akerr_Mutex *mutex)
|
|
{
|
|
(void)mutex;
|
|
}
|
|
|
|
/*
|
|
* The flag is raised before the routine runs, so a routine that calls back into
|
|
* akerr_init() sees initialization already in progress and does not recurse --
|
|
* the same short-circuit the pthread backend gets from akerr_initializing.
|
|
*/
|
|
static void akerr_once(akerr_Once *once, void (*routine)(void))
|
|
{
|
|
if ( *once == 0 ) {
|
|
*once = 1;
|
|
routine();
|
|
}
|
|
}
|
|
|
|
#else
|
|
#error "No threading backend selected. Build libakerror through its CMake, which defines AKERR_THREADS_PTHREAD or AKERR_THREADS_NONE from the AKERR_THREADS option."
|
|
#endif
|
|
|
|
#endif // _AKERR_LOCK_H_
|