#ifndef _AKERR_H_ #define _AKERR_H_ #if (defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1) || (!defined(AKERR_USE_STDLIB)) #include #include #include #include #include #endif /* * Threading. * * scripts/generrno.sh stamps this value in at build time from the AKERR_THREADS * build option, the same way it stamps AKERR_LAST_ERRNO_VALUE. It is generated * rather than defined by the consumer on purpose: whether the library * serializes its global state and whether __akerr_last_ignored is a * thread-local are the same decision, and a consumer that disagreed with the * library about it would link against a differently shaped symbol. * * 1 The error pool and the status registry are mutex protected, and the * per-thread state below is thread local. Every entry point may be called * from any thread. See "Thread safety" in README.md for what that does and * does not cover. * 0 The library was built -DAKERR_THREADS=none for a single-threaded * process: no locking, no thread-local storage, and calling it from more * than one thread is undefined. * * Consumers can test it: #if AKERR_THREAD_SAFE. */ #define AKERR_THREAD_SAFE AKERR_THREAD_SAFE_SED #if AKERR_THREAD_SAFE == 1 #if defined(__GNUC__) || defined(__clang__) #define AKERR_THREAD_LOCAL __thread #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define AKERR_THREAD_LOCAL _Thread_local #elif defined(_MSC_VER) #define AKERR_THREAD_LOCAL __declspec(thread) #else #error "libakerror was built thread safe, but this compiler has no thread-local storage specifier that akerror.h knows about. Rebuild libakerror with -DAKERR_THREADS=none, or add the spelling here." #endif #else #define AKERR_THREAD_LOCAL #endif // FIXME: This is huge now. It used to be 1000 bytes, then I wanted to report errors // related to filesystem paths, which made it grow beyond PATH_MAX, then I started // reporting messages including 2 file paths (PATH_MAX * 2), so now to make the compiler warnings // shut up, it's enormous (PATH_MAX*3). #define AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH 12384 #define AKERR_MAX_ERROR_NAME_LENGTH 64 #define AKERR_MAX_ERROR_FNAME_LENGTH PATH_MAX #define AKERR_MAX_ERROR_FUNCTION_LENGTH 128 #define AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH (AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH + AKERR_MAX_ERROR_NAME_LENGTH + AKERR_MAX_ERROR_FNAME_LENGTH + AKERR_MAX_ERROR_FUNCTION_LENGTH + 16) #define AKERR_LAST_ERRNO_VALUE AKERR_LAST_ERRNO_VALUE_SED #define AKERR_NULLPOINTER (AKERR_LAST_ERRNO_VALUE + 1) /** A pointer had a NULL value where such was not permissible */ #define AKERR_OUTOFBOUNDS (AKERR_LAST_ERRNO_VALUE + 2) /** Attempt to access a datastructure outside of bounds */ #define AKERR_API (AKERR_LAST_ERRNO_VALUE + 3) /** An otherwise unspecified API contract has been violated */ #define AKERR_ATTRIBUTE (AKERR_LAST_ERRNO_VALUE + 4) /** Relates to accessing of attributes on objects */ #define AKERR_TYPE (AKERR_LAST_ERRNO_VALUE + 5) /** An object had the incorrect type */ #define AKERR_KEY (AKERR_LAST_ERRNO_VALUE + 6) /** A key was either invalid for or not present in a map */ #define AKERR_INDEX (AKERR_LAST_ERRNO_VALUE + 8) /** An error occurred when attempting to index an indexable datastructure (other than out of bounds) */ #define AKERR_FORMAT (AKERR_LAST_ERRNO_VALUE + 9) /** An error occurred in the formatting of an object (usually a string) */ #define AKERR_IO (AKERR_LAST_ERRNO_VALUE + 10) /** An unspecified IO error occurred. */ #define AKERR_VALUE (AKERR_LAST_ERRNO_VALUE + 11) /** A provided value was invalid */ #define AKERR_RELATIONSHIP (AKERR_LAST_ERRNO_VALUE + 12) /** An error occurred in establishing, maintaining or severing a relationship between two objects */ #define AKERR_EOF (AKERR_LAST_ERRNO_VALUE + 13) /** The end of a stream or file has been encountered */ #define AKERR_CIRCULAR_REFERENCE (AKERR_LAST_ERRNO_VALUE + 14) /** Indicates that a circular reference has been found in a linked list */ #define AKERR_ITERATOR_BREAK (AKERR_LAST_ERRNO_VALUE + 15) /** Used to prematurely end an iteration cycle (such as when searching a graph and the desired node has been found) */ #define AKERR_NOT_IMPLEMENTED (AKERR_LAST_ERRNO_VALUE + 16) /** A method was called that is defined but not currently implemented */ #define AKERR_BADEXC (AKERR_LAST_ERRNO_VALUE + 17) /** The libakerr library was given an akerr_ErrorContext to parse that did not come from AKERR_ARRAY_ERROR (likely an uninitialized pointer) */ /* * Registry failures. These are ordinary status codes, not a private return * enumeration: akerr_reserve_status_range() and akerr_register_status_name() * return akerr_ErrorContext * like everything else in this library, so a refused * reservation can be CATCH-ed, HANDLE-d, PASS-ed, or left unhandled to produce a * stack trace and stop the program. Success returns NULL. */ #define AKERR_STATUS_RANGE_OVERLAP (AKERR_LAST_ERRNO_VALUE + 18) /** Some part of the range is already owned by someone else */ #define AKERR_STATUS_RANGE_FULL (AKERR_LAST_ERRNO_VALUE + 19) /** No reservation slots remain (see AKERR_MAX_RESERVED_STATUS_RANGES) */ #define AKERR_STATUS_RANGE_INVALID (AKERR_LAST_ERRNO_VALUE + 20) /** Bad count, bad owner string, or the range overflows int */ #define AKERR_STATUS_NAME_UNRESERVED (AKERR_LAST_ERRNO_VALUE + 21) /** No owner has reserved a range containing this status */ #define AKERR_STATUS_NAME_FOREIGN (AKERR_LAST_ERRNO_VALUE + 22) /** The status lies in a range reserved by a different owner */ #define AKERR_STATUS_NAME_FULL (AKERR_LAST_ERRNO_VALUE + 23) /** The name registry is full (raise AKERR_STATUS_NAME_SLOTS) */ #define AKERR_STATUS_NAME_INVALID (AKERR_LAST_ERRNO_VALUE + 24) /** NULL/empty/over-long owner, or a NULL name */ /* The last status the library defines for itself. Everything from * AKERR_LAST_ERRNO_VALUE + 1 through here must have a registered name. */ #define AKERR_LAST_LIBRARY_STATUS AKERR_STATUS_NAME_INVALID /* * Status values 0 through 255 are reserved by libakerror at akerr_init() time: * the host's errno values plus the AKERR_* codes above. Consumers allocate from * 256 upwards. Reserving any part of this band fails with * AKERR_STATUS_RANGE_OVERLAP naming AKERR_LIBRARY_OWNER. */ #define AKERR_LIBRARY_OWNER "libakerror" #define AKERR_RESERVED_STATUS_COUNT 256 #define AKERR_FIRST_CONSUMER_STATUS AKERR_RESERVED_STATUS_COUNT /* * The library reserves status values 0 through 255 for itself (see akerr_init), * which must contain every AKERR_* code above. AKERR_LAST_ERRNO_VALUE is * derived from the host's errno list at build time, so on a platform with an * unusually large errno space these codes could escape the band and collide * with consumer codes allocated at 256. Fail the build instead. */ typedef char akerr_assert_codes_within_reserved_band[(AKERR_LAST_LIBRARY_STATUS < 256) ? 1 : -1]; /* * A process exit status is one byte wide. exit() takes an int, but the kernel * keeps only the low 8 bits of it and throws the rest away, so 0 through 255 * are the only statuses that can also be exit codes -- and every consumer status * begins at AKERR_FIRST_CONSUMER_STATUS (256). No wider variant exists to reach * for: _exit(), _Exit(), quick_exit() and the raw exit_group syscall all * truncate identically, and even waitid()'s int-wide si_status reports the * truncated value, because the truncation happened before the parent looked. * * akerr_exit() therefore substitutes AKERR_EXIT_STATUS_UNREPRESENTABLE for any * status it cannot deliver intact, rather than passing the low byte -- status * 256 would exit 0 and report success. 125 is the conventional "the tool itself * failed" code (126, 127 and 128+n belong to the shell). It is inside the * library's reserved band, so it is also some host's errno: the exit code says * only that the process died of an error, and the stack trace carries the real * status. */ #define AKERR_EXIT_STATUS_MAX 255 #define AKERR_EXIT_STATUS_UNREPRESENTABLE 125 #define AKERR_MAX_ARRAY_ERROR 128 typedef struct { char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH]; int arrayid; int status; bool handled; int refcount; char fname[AKERR_MAX_ERROR_FNAME_LENGTH]; char function[AKERR_MAX_ERROR_FNAME_LENGTH]; int lineno; bool reported; char stacktracebuf[AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH]; char *stacktracebufptr; } akerr_ErrorContext; #define AKERR_NOIGNORE __attribute__((warn_unused_result)) /* akerr_exit() does not come back, and the compiler should know it: a handler * whose last statement is a call to it is complete, not falling off the end. */ #define AKERR_NORETURN __attribute__((noreturn)) typedef void (*akerr_ErrorUnhandledErrorHandler)(akerr_ErrorContext *errctx); typedef void (*akerr_ErrorLogFunction)(const char *f, ...); extern akerr_ErrorContext AKERR_ARRAY_ERROR[AKERR_MAX_ARRAY_ERROR]; /* * Set these before starting threads. They are read on every error and written * by nothing but your own code, so changing one while other threads are raising * errors is a data race the library cannot mediate. */ extern akerr_ErrorUnhandledErrorHandler akerr_handler_unhandled_error; extern akerr_ErrorLogFunction akerr_log_method; /* * The error IGNORE() last swallowed, per thread: an ignored error is a fact * about the thread that ignored it, and one shared slot would have two threads * overwriting each other's. Thread local only when AKERR_THREAD_SAFE is 1. */ extern AKERR_THREAD_LOCAL akerr_ErrorContext *__akerr_last_ignored; akerr_ErrorContext AKERR_NOIGNORE *akerr_release_error(akerr_ErrorContext *ptr); /* * Check a context out of the pool. The returned context already carries one * reference: finding a free slot and claiming it is a single operation under * the pool lock, because two threads scanning at once would otherwise be handed * the same slot. Release it with akerr_release_error() (or let RELEASE_ERROR, * SUCCEED_RETURN or FINISH do it for you). Returns NULL when every slot is * checked out. */ akerr_ErrorContext AKERR_NOIGNORE *akerr_next_error(); /* * Look up (name == NULL) or register (name != NULL) the display name for a * status. Registration succeeds only if some owner has reserved a range * containing `status`; prefer akerr_register_status_name(), which also checks * that the range belongs to you and raises an error saying why a registration * was refused. This entry point cannot return an error context, so a refusal * here is reported through akerr_log_method and reads back as "Unknown Error". * Never returns NULL -- an unregistered status reads back as "Unknown Error". */ char *akerr_name_for_status(int status, char *name); /* * Register a display name for a status you own. `owner` must match the owner * string passed to akerr_reserve_status_range() for the range containing * `status`. Returns NULL on success, or an error context whose status is one of * the AKERR_STATUS_NAME_* codes above -- CATCH it, HANDLE it, or let it * propagate. */ akerr_ErrorContext AKERR_NOIGNORE *akerr_register_status_name(const char *owner, int status, const char *name); /* * Claim `count` status values starting at `first_status` for `owner`. Repeating * an identical reservation for the same owner is a no-op; any other collision is * refused. Returns NULL on success, or an error context whose status is one of * the AKERR_STATUS_RANGE_* codes above. Treat any error as an initialization * failure: either handle it or let it propagate out of your init function. */ akerr_ErrorContext AKERR_NOIGNORE *akerr_reserve_status_range(int first_status, int count, const char *owner); void akerr_init(); /* * Terminate the process, reporting `status`. Use this instead of exit() * anywhere you are leaving on account of an akerr status -- an unhandled-error * handler of your own, a CLI's top-level HANDLE block, an init routine that * cannot continue -- so that every exit out of the library's status space maps * the same way. * * Exits with `status` when 0 <= status <= AKERR_EXIT_STATUS_MAX, and with * AKERR_EXIT_STATUS_UNREPRESENTABLE otherwise (see above). Status 0 exits 0: * zero is this library's success status, and passing it here says the program * finished, not that it failed with a code that got lost. */ void AKERR_NORETURN akerr_exit(int status); /* * The default akerr_handler_unhandled_error: logs nothing further -- the stack * trace has already been printed by the time it runs -- and hands `ptr->status` * to akerr_exit(), or exits 1 when `ptr` is NULL. Replace it if you need a * different mapping, and call akerr_exit() from your replacement. */ void akerr_default_handler_unhandled_error(akerr_ErrorContext *ptr); void akerr_default_logger(const char *f, ...); int akerr_valid_error_address(akerr_ErrorContext *ptr); /* defined in src/errno.c which is built dynamically at build time from system errno definitions */ void akerr_init_errno(void); /* * Internal. Names a status in the library's own reserved band on behalf of * akerr_init() and the generated errno table, which have no caller to raise * into: a failure here is logged and terminates the program. Not part of the * consumer API -- use akerr_register_status_name(), which raises instead. */ void __akerr_name_library_status(int status, const char *name); /* * Internal. Bounded string copy into a fixed buffer, always NUL-terminated. * Raises AKERR_NULLPOINTER for a NULL destination or source and AKERR_VALUE for * a capacity that leaves no room for a terminator. Exported so the library's * own tests can drive those guards; not part of the consumer API. */ akerr_ErrorContext AKERR_NOIGNORE *__akerr_copy_string(char *destination, int capacity, const char *source); #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); \ #define LOG_ERROR(__err_context) \ LOG_ERROR_WITH_MESSAGE(__err_context, ""); #define RELEASE_ERROR(__err_context) \ if ( __err_context != NULL ) { \ __err_context = akerr_release_error(__err_context); \ } #define PREPARE_ERROR(__err_context) \ akerr_init(); \ akerr_ErrorContext __attribute__ ((unused)) *__err_context = NULL; /* * akerr_next_error() hands back a context that already holds one reference -- * it has to, or a second thread could be given the same slot between the scan * and the increment. There is nothing to increment here. */ #define ENSURE_ERROR_READY(__err_context) \ if ( __err_context == NULL ) { \ __err_context = akerr_next_error(); \ if ( __err_context == NULL ) { \ akerr_log_method("%s:%s:%d: Unable to pull an error context from the array!", __FILE__, (char *)__func__, __LINE__); \ exit(1); \ } \ } /* * Append a formatted line to the error's stack-trace buffer, bounded by the * space that remains so a deep propagation chain cannot write past the end of * stacktracebuf. snprintf reports the length it *would* have written, which on * truncation exceeds what it actually wrote, so the cursor advance is clamped * to the remaining space. */ #define AKERR_STACKTRACE_APPEND(__err_context, ...) \ do { \ char *__akerr_stb = (char *)__err_context->stacktracebuf; \ size_t __akerr_used = (size_t)(__err_context->stacktracebufptr - __akerr_stb); \ if ( __akerr_used < AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH ) { \ size_t __akerr_rem = AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH - __akerr_used; \ int __akerr_n = snprintf(__err_context->stacktracebufptr, __akerr_rem, __VA_ARGS__); \ if ( __akerr_n < 0 ) { \ __akerr_n = 0; \ } \ __err_context->stacktracebufptr += ((size_t)__akerr_n < __akerr_rem) \ ? (size_t)__akerr_n : (__akerr_rem - 1); \ } \ } while ( 0 ) /* * Failure and success methods for functions that return akerr_ErrorContext * */ #define FAIL_ZERO_RETURN(__err_context, __x, __err, __message, ...) \ if ( __x == 0 ) { \ FAIL(__err_context, __err, __message, ##__VA_ARGS__); \ return __err_context; \ } #define FAIL_NONZERO_RETURN(__err_context, __x, __err, __message, ...) \ if ( __x != 0 ) { \ FAIL(__err_context, __err, __message, ##__VA_ARGS__); \ return __err_context; \ } #define FAIL_RETURN(__err_context, __err, __message, ...) \ FAIL(__err_context, __err, __message, ##__VA_ARGS__); \ return __err_context; #define SUCCEED_RETURN(__err_context) \ RELEASE_ERROR(__err_context); \ return NULL; /* * Failure and success methods for use inside of ATTEMPT() blocks */ #define FAIL_ZERO_BREAK(__err_context, __x, __err, __message, ...) \ if ( __x == 0 ) { \ FAIL(__err_context, __err, __message, ##__VA_ARGS__); \ break; \ } #define FAIL_NONZERO_BREAK(__err_context, __x, __err, __message, ...) \ if ( __x != 0 ) { \ FAIL(__err_context, __err, __message, ##__VA_ARGS__); \ break; \ } #define FAIL_BREAK(__err_context, __err_, __message, ...) \ FAIL(__err_context, __err_, __message, ##__VA_ARGS__); \ break; #define SUCCEED_BREAK(__err_context) \ SUCCEED(__err_context); \ break; /* * General failure and success methods */ #define FAIL(__err_context, __err, __message, ...) \ ENSURE_ERROR_READY(__err_context); \ __err_context->status = __err; \ snprintf((char *)__err_context->fname, AKERR_MAX_ERROR_FNAME_LENGTH, "%s", __FILE__); \ snprintf((char *)__err_context->function, AKERR_MAX_ERROR_FUNCTION_LENGTH, "%s", __func__); \ __err_context->lineno = __LINE__; \ snprintf((char *)__err_context->message, AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH, __message, ## __VA_ARGS__); \ AKERR_STACKTRACE_APPEND(__err_context, "%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) \ ENSURE_ERROR_READY(__err_context); \ __err_context->status = 0; /* * Defines for the ATTEMPT/CATCH/CLEANUP/PROCESS/HANDLE/FINISH process */ #define ATTEMPT \ switch ( 0 ) { \ case 0: \ #define VALID(__err_context, __stmt) \ __stmt; \ if ( akerr_valid_error_address(__err_context) == 0 ) { \ __err_context = NULL; \ FAIL(__err_context, AKERR_BADEXC, "Received (akerr_ErrorContext *) from an invalid memory region. (Did the method finish without calling SUCCEED_RETURN?)"); \ } #define DETECT(__err_context, __stmt) \ VALID(__err_context, __stmt); \ if ( __err_context != NULL ) { \ if ( __err_context->status != 0 ) { \ AKERR_STACKTRACE_APPEND(__err_context, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \ break; \ } \ } #define CATCH(__err_context, __stmt) \ DETECT(__err_context, __err_context = __stmt); #define PASS(__err_context, __stmt) \ switch ( 0 ) { \ case 0: \ DETECT(__err_context, __err_context = __stmt); \ } \ FINISH_LOGIC(__err_context, true); #define IGNORE(__stmt) \ __akerr_last_ignored = __stmt; \ if ( __akerr_last_ignored != NULL ) { \ LOG_ERROR_WITH_MESSAGE(__akerr_last_ignored, "** IGNORED ERROR **"); \ } #define CLEANUP \ }; #define PROCESS(__err_context) \ if ( __err_context != NULL ) { \ switch ( __err_context->status ) { \ case 0: \ __err_context->handled = true; #define HANDLE(__err_context, __err_status) \ break; \ case __err_status: \ __err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \ __err_context->handled = true; #define HANDLE_GROUP(__err_context, __err_status) \ case __err_status: \ __err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \ __err_context->handled = true; #define HANDLE_DEFAULT(__err_context) \ break; \ default: \ __err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \ __err_context->handled = true; #define FINISH_LOGIC(__err_context, __pass_up) \ if ( __err_context != NULL ) { \ if ( __err_context->handled == false && __pass_up == true ) { \ return __err_context; \ } \ } \ #define FINISH(__err_context, __pass_up) \ }; \ }; \ FINISH_LOGIC(__err_context, __pass_up) \ RELEASE_ERROR(__err_context); #define FINISH_NORETURN(__err_context) \ }; \ }; \ if ( __err_context != NULL ) { \ if ( __err_context->handled == false ) { \ LOG_ERROR_WITH_MESSAGE(__err_context, "Unhandled Error"); \ akerr_handler_unhandled_error(__err_context); \ } \ } \ RELEASE_ERROR(__err_context); #endif // _AKERR_H_