terom@216: #ifndef LIBQMSK_ERROR_H terom@216: #define LIBQMSK_ERROR_H terom@216: terom@216: /** terom@216: * @file terom@216: * terom@216: * Support for error handling, using explicit function return codes and error info contexts. terom@216: * terom@216: * This is designed to support multiple "namespaces" of errors, which are entirely independent of eachother. These terom@216: * namespaces can then reference one another, to build a 'tree' of useable error codes. Hence, each 'level' of this terom@216: * tree has it's own set of error codes, and then error info contexts contain a stack of these error codes, which can terom@216: * then be used to trace the error down. terom@216: */ terom@217: #include terom@216: terom@216: /** terom@216: * The type used to represent a scalar error code, there are only unique per level. terom@216: * terom@216: * Note that this type is signed to avoid nastyness with negating error codes, but all valid ERR_* codes are *always* terom@216: * greater than zero in value. terom@216: */ terom@216: typedef signed short err_t; terom@216: terom@216: /** terom@216: * Standardized err_t code to mean no error, evaluates as logical false. terom@216: */ terom@216: static const err_t SUCCESS = 0; terom@216: terom@216: /** terom@216: * Extra information about an error, used to reference error codes in external libraries etc. terom@216: */ terom@216: union error_extra { terom@216: /** (signed) integer code */ terom@216: int int_; terom@216: terom@216: /** Constant string */ terom@216: const char *str; terom@216: }; terom@216: terom@216: /** terom@216: * Errors can contain 'extra' information to reference things like error codes from external libraries; when these are terom@216: * used, a 'type' must be specified to handle these. terom@216: */ terom@216: struct error_extra_type { terom@216: /** Short name */ terom@216: const char *name; terom@216: terom@216: /** Conversion func, must return a static pointer */ terom@216: const char * (*msg_func) (const struct error_extra_type *type, const union error_extra *extra); terom@216: }; terom@216: terom@216: /** terom@216: * error_extra_type for libc errno values terom@216: */ terom@216: const struct error_extra_type error_extra_errno; terom@216: terom@216: /** terom@216: * error_extra_type for string error values terom@216: */ terom@216: const struct error_extra_type error_extra_string; terom@216: terom@216: /** terom@216: * Description of an error code terom@216: */ terom@216: struct error_type { terom@216: /** The actual code */ terom@216: err_t code; terom@216: terom@216: /** The short name of the error */ terom@216: const char *name; terom@216: terom@216: /** The type of any contained extra info */ terom@216: const struct error_extra_type *extra_type; terom@216: terom@216: /** Any linked error_code table for looking up sub-errors */ terom@216: const struct error_list *sublist; terom@216: }; terom@216: terom@216: /** terom@216: * Helper macros to define error_type's terom@216: */ terom@216: #define ERROR_TYPE(code, name) \ terom@216: { (code), (name), NULL, NULL } terom@216: terom@216: #define ERROR_TYPE_ERRNO(code, name) \ terom@216: { (code), (name), &error_extra_errno, NULL } terom@216: terom@216: #define ERROR_TYPE_STRING(code, name) \ terom@216: { (code), (name), &error_extra_string, NULL } terom@216: terom@216: #define ERROR_TYPE_CUSTOM(code, name, type) \ terom@216: { (code), (name), (type), NULL } terom@216: terom@216: #define ERROR_TYPE_SUB(code, name, sub) \ terom@216: { (code), (name), NULL, (sub) } terom@216: terom@216: #define ERROR_TYPE_END \ terom@216: { 0, NULL, NULL, NULL } terom@216: terom@216: /** terom@216: * List of error types terom@216: */ terom@216: struct error_list { terom@216: /** Name of sublib */ terom@216: const char *name; terom@216: terom@216: /** The list */ terom@216: struct error_type list[]; terom@216: }; terom@216: terom@216: /** terom@216: * Helper macro to define an error_list terom@216: */ terom@216: #define ERROR_LIST(name, ...) \ terom@216: { (name), __VA_ARGS__, ERROR_TYPE_END } terom@216: terom@216: /** terom@216: * Maximum number of nesting levels supported for errors terom@216: */ terom@216: #define ERROR_DEPTH_MAX 8 terom@216: terom@216: /** terom@216: * Information about an actual error terom@216: */ terom@216: typedef struct error_state { terom@216: /** The stack of error codes */ terom@216: struct error_item { terom@216: /** This level's error code */ terom@216: err_t code; terom@216: terom@216: /** Optional table for interpreting error_code, can be used instead of error_code::next */ terom@216: const struct error_list *list; terom@216: terom@216: } stack[ERROR_DEPTH_MAX]; terom@216: terom@216: /** Current top of stack for accumulating errors, NULL means empty stack */ terom@217: struct error_item *cur; terom@216: terom@216: /** Type info for external error info */ terom@216: const struct error_extra_type *extra_type; terom@216: terom@216: /** Value of external error info */ terom@216: union error_extra extra_value; terom@216: terom@216: } error_t; terom@216: terom@216: /** terom@216: * Look up the error_type for the given table and scalar code. terom@216: */ terom@216: const struct error_type* error_lookup_code (const struct error_list *list, err_t code); terom@216: terom@216: /** terom@216: * Look up the error_type for for the given root table and error_info. terom@216: */ terom@216: const struct error_type* error_lookup (const error_t *err); terom@216: terom@216: /** terom@217: * Return the error name for the given code. terom@216: */ terom@217: const char* error_name (const struct error_list *list, err_t code); terom@216: terom@216: /** terom@216: * Maximum length of messages returned by error_msg terom@216: */ terom@216: #define ERROR_MSG_MAX 1024 terom@216: terom@216: /** terom@216: * Return a pointer to a statically allocated string describing the given full error terom@216: */ terom@216: const char* error_msg (const error_t *err); terom@216: terom@216: /** terom@216: * Compare the given errors for equivalency terom@216: */ terom@216: bool error_cmp_eq (const error_t *a, const error_t *b); terom@216: terom@216: terom@216: /** terom@217: * Reset the given error state to an empty state. terom@217: * terom@217: * This is just the same as memset() with zero. terom@216: */ terom@217: void error_reset (error_t *err); terom@216: terom@216: /** terom@216: * Evaluates to the current top of the error stack, terom@216: */ terom@217: static struct error_item* error_top (error_t *err) terom@216: { terom@216: return err->cur ? err->cur : err->stack; terom@216: } terom@216: terom@216: /** terom@216: * Evaluate to the most recent error code pushed, or SUCCESS if none terom@216: */ terom@216: static inline err_t error_code (error_t *err) terom@216: { terom@216: return err->cur ? err->cur->code : SUCCESS; terom@216: } terom@216: terom@216: /** terom@216: * Reset the error state, push the given initial err. terom@216: */ terom@216: void error_set (error_t *err, const struct error_list *list, err_t code); terom@216: terom@216: /** terom@216: * Reset, push err, and set extra. terom@216: */ terom@216: void error_set_extra (error_t *err, const struct error_list *list, err_t code, const struct error_extra_type *type, union error_extra extra); terom@216: terom@216: /** terom@216: * Push an additional level of error info onto the error state's stack. If it doesn't fit, XXX: it is ignored. terom@216: */ terom@216: void error_push (error_t *err, const struct error_list *list, err_t code); terom@216: terom@216: /** terom@216: * Copy value of error terom@216: */ terom@216: void error_copy (error_t *dst, const error_t *src); terom@216: terom@216: /** Evalutes to the lvalue representing the error code at the top of the stack */ terom@216: #define ERROR_CODE(err_state) \ terom@216: (error_top(err_state)->code) terom@216: terom@216: /** Push a new error code onto the given state */ terom@216: #define PUSH_ERROR(err_state, err_list, err_code) ({ \ terom@216: error_push(err_state, err_list, err_code); \ terom@216: err_code; }) terom@216: terom@216: /** Set the error to the given value */ terom@216: #define SET_ERROR(err_state, err_list, err_code) ({ \ terom@216: error_set(err_state, err_list, err_code); \ terom@216: err_code; }) terom@216: terom@216: /* Helper macro to use error_set_extra */ terom@216: #define _ERROR_SET_EXTRA(err_state, err_list, err_code, err_extra_type, _err_extra_field_, err_extra_value) \ terom@216: error_set_extra(err_state, err_list, err_code, err_extra_type, ((union error_extra) { ._err_extra_field_ = (err_extra_value)})) terom@216: terom@216: /** Set the error with extra info as integer */ terom@216: #define SET_ERROR_EXTRA(err_state, err_list, err_code, err_extra_type, err_extra_int) ({ \ terom@216: _ERROR_SET_EXTRA(err_state, err_list, err_code, err_extra_type, int_, err_extra); \ terom@216: err_code; }) terom@216: terom@216: /** Set the error with extra info as the libc errno */ terom@216: #define SET_ERROR_ERRNO(err_state, err_list, err_code) ({ \ terom@216: _ERROR_SET_EXTRA(err_state, err_list, err_code, &error_extra_errno, int_, errno); \ terom@216: err_code; }) terom@216: terom@216: /** Set the error with extra info as the given static string */ terom@216: #define SET_ERROR_STR(err_state, err_list, err_code, err_str) ({ \ terom@216: _ERROR_SET_EXTRA(err_state, err_list, err_code, &error_extra_string, str, err_str); \ terom@216: err_code; }) terom@216: terom@216: /** Set the error with the contents of the given error */ terom@216: #define SET_ERROR_COPY(err_state_dst, err_state_src) ({ \ terom@216: error_code(err_state_dst); }) terom@216: terom@216: /** Same as above, but also do a 'goto error' jump */ terom@216: #define JUMP_PUSH_ERROR(...) do { PUSH_ERROR(__VA_ARGS__); goto error; } while (0) terom@216: #define JUMP_SET_ERROR(...) do { SET_ERROR(__VA_ARGS__); goto error; } while (0) terom@216: #define JUMP_SET_ERROR_EXTRA(...) do { SET_ERROR_EXTRA(__VA_ARGS__); goto error; } while (0) terom@216: #define JUMP_SET_ERROR_ERRNO(...) do { SET_ERROR_ERRNO(__VA_ARGS__); goto error; } while (0) terom@216: #define JUMP_SET_ERROR_STR(...) do { SET_ERROR_STR(__VA_ARGS__); goto error; } while (0) terom@216: #define JUMP_SET_ERROR_INFO(...) do { SET_ERROR_INFO(__VA_ARGS__); goto error; } while (0) terom@216: terom@216: terom@216: /** terom@216: * Abort execution of process with error message terom@216: */ terom@216: void _error_abort (const char *file, const char *line, const char *func, const char *fmt, ...); terom@216: #define error_abort(...) _error_abort(__FILE__, __LINE__, __func__, __VA_ARGS__); terom@216: terom@216: /** terom@216: * Used to mark unexpcted conditions for switch-default. The given val must be an integer, as passed to switch terom@216: */ terom@216: #define NOT_REACHED(val) error_abort("%s = %#x", #val, (int) (val)); terom@216: terom@217: /** terom@217: * General-purpose errors that may be useful and don't belong in any more specific namespace. terom@217: */ terom@217: enum general_error_code { terom@217: ERR_GENERAL_NONE, terom@217: ERR_MEM, ///< memory allocation error terom@217: ERR_NOT_IMPLEMENTED, ///< function not implmented: terom@217: ERR_MISC, ///< miscellaneous error: terom@217: ERR_CMD_OPT, ///< invalid command line option: - XXX: replace with something getopt terom@217: ERR_UNKNOWN, ///< unknown error terom@217: }; terom@217: terom@217: const struct error_list general_errors; terom@217: terom@216: #endif /* LIBQMSK_ERROR_H */