blob: 00142ec610dca6600fc12a7e6711566600d1635e [file] [log] [blame]
/* libnih
*
* error.c - error handling
*
* Copyright © 2006 Scott James Remnant <scott@netsplit.com>.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/list.h>
#include <nih/logging.h>
#include <nih/string.h>
#include "error.h"
/* Prototypes for static functions */
static void nih_error_clear (void);
/**
* NihErrorCtx:
* @entry: list header,
* @error: current error.
*
* This structure is used to provide barriers that errors cannot cross,
* for example when performing an operation after an error has occurred.
**/
typedef struct nih_error_ctx {
NihList entry;
NihError *error;
} NihErrorCtx;
/**
* context_stack:
*
* Stack of error contexts.
**/
static __thread NihList *context_stack = NULL;
/**
* CURRENT_CONTEXT:
*
* Macro to obtain the current context.
**/
#define CURRENT_CONTEXT ((NihErrorCtx *)context_stack->prev)
/**
* DEFAULT_CONTEXT:
*
* Macro to obtain the default context.
**/
#define DEFAULT_CONTEXT ((NihErrorCtx *)context_stack->next)
/**
* nih_error_init:
*
* Initialise the context stack.
**/
static inline void
nih_error_init (void)
{
if (! context_stack) {
NIH_MUST (context_stack = nih_list_new ());
nih_error_push_context ();
}
}
/**
* nih_error_raise:
* @number: numeric identifier,
* @message: human-readable message.
*
* Raises an error with the given details in the current error context,
* if an unhandled error already exists then an error message is emmitted
* through the logging system; you should try to avoid this.
*
* @message should be a static string, as it will not be freed when the
* error object is.
**/
void
nih_error_raise (int number,
const char *message)
{
NihError *error;
nih_assert (number > 0);
nih_assert (message != NULL);
nih_error_init ();
NIH_MUST (error = nih_new (CURRENT_CONTEXT, NihError));
error->number = number;
error->message = message;
nih_error_raise_again (error);
}
/**
* nih_error_raise_printf:
* @number: numeric identifier,
* @format: format string for human-readable message.
*
* Raises an error with the given details in the current error context,
* if an unhandled error already exists then an error message is emmitted
* through the logging system; you should try to avoid this.
*
* The human-readable message for the error is parsed according to @format,
* and allocated as a child of the error object so that it is freed.
**/
void
nih_error_raise_printf (int number,
const char *format,
...)
{
NihError *error;
va_list args;
nih_assert (number > 0);
nih_assert (format != NULL);
nih_error_init ();
NIH_MUST (error = nih_new (CURRENT_CONTEXT, NihError));
error->number = number;
va_start (args, format);
NIH_MUST (error->message = nih_vsprintf (error, format, args));
va_end (args);
nih_error_raise_again (error);
}
/**
* nih_error_raise_system:
*
* Raises an error with details taken from the current value of %errno,
* if an unhandled error already exists then an error message is emmitted
* through the logging system; you should try to avoid this.
**/
void
nih_error_raise_system (void)
{
NihError *error;
int saved_errno;
nih_assert (errno > 0);
saved_errno = errno;
nih_error_init ();
NIH_MUST (error = nih_new (CURRENT_CONTEXT, NihError));
error->number = saved_errno;
NIH_MUST (error->message = nih_strdup (error, strerror (saved_errno)));
nih_error_raise_again (error);
errno = saved_errno;
}
/**
* nih_error_raise_again:
* @error: existing object to raise.
*
* Raises the existing error object in the current error context,
* if an unhandled error already exists then an error message is emmitted
* through the logging system; you should try to avoid this.
*
* This is normally used to raise a taken error that has not been handled,
* or to raise a custom error object.
**/
void
nih_error_raise_again (NihError *error)
{
nih_assert (error != NULL);
nih_assert (error->number > 0);
nih_assert (error->message != NULL);
nih_error_init ();
if (CURRENT_CONTEXT->error)
nih_error_clear ();
CURRENT_CONTEXT->error = error;
}
/**
* nih_error_clear:
*
* Clear the error from the current context, emitting a log message so that
* it isn't lost. In an ideal world, this would be an assertion, except
* it would assert in the wrong place (a new error, not at the unhandled one).
**/
static void
nih_error_clear (void)
{
nih_assert (context_stack != NULL);
nih_assert (CURRENT_CONTEXT->error != NULL);
nih_error ("%s: %s", _("Unhandled Error"),
CURRENT_CONTEXT->error->message);
nih_free (CURRENT_CONTEXT->error);
CURRENT_CONTEXT->error = NULL;
}
/**
* nih_error_get:
*
* Returns the last unhandled error from the current context, clearing it
* so that further errors may be raised.
*
* The object must be freed with #nih_free once you are finished with it,
* if you want to raise it again, use #nih_error_raise_again.
*
* Returns: error object from current context.
**/
NihError *
nih_error_get (void)
{
NihError *error;
nih_assert (context_stack != NULL);
nih_assert (CURRENT_CONTEXT->error != NULL);
error = CURRENT_CONTEXT->error;
CURRENT_CONTEXT->error = NULL;
return error;
}
/**
* nih_error_push_context:
*
* Creates a new context in which errors can occur without disturbing any
* previous unhandled error, useful for touring a particular piece of
* processing that handles its own errors and may be triggered as a result
* of another error.
**/
void
nih_error_push_context (void)
{
NihErrorCtx *new_context;
nih_error_init ();
NIH_MUST (new_context = nih_new (context_stack, NihErrorCtx));
nih_list_init (&new_context->entry);
new_context->error = NULL;
nih_list_add (context_stack, &new_context->entry);
}
/**
* nih_error_pop_context:
*
* Ends the last context created with #nih_error_push_context, deliberate
* care should be taken so that these are always properly nested (through
* the correct use of scope, for example) and contexts are not left unpopped.
**/
void
nih_error_pop_context (void)
{
NihErrorCtx *context;
nih_assert (context_stack != NULL);
nih_assert (CURRENT_CONTEXT != DEFAULT_CONTEXT);
context = CURRENT_CONTEXT;
if (context->error)
nih_error_clear ();
nih_list_remove (&context->entry);
nih_free (context);
}