src/lib/error.h
author Tero Marttila <terom@fixme.fi>
Wed, 27 May 2009 23:57:48 +0300
branchnew-lib-errors
changeset 217 7728d6ec3abf
parent 216 a10ba529ae39
child 218 5229a5d098b2
permissions -rw-r--r--
nexus.c compiles
#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>

/**
 * 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_CUSTOM(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 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);  \
        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, const char *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 = %#x", #val, (int) (val));

/**
 * General-purpose errors that may be useful and don't belong in any more specific namespace.
 */
enum general_error_code {
    ERR_GENERAL_NONE,
    ERR_MEM,                ///< memory allocation error
    ERR_NOT_IMPLEMENTED,    ///< function not implmented: <func>
    ERR_MISC,               ///< miscellaneous error: <error>
    ERR_CMD_OPT,            ///< invalid command line option: <error> - XXX: replace with something getopt
    ERR_UNKNOWN,            ///< unknown error
};

const struct error_list general_errors;

#endif /* LIBQMSK_ERROR_H */