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