This module provides the C level error handling functionality for
Imager.
-A few functions return or pass in a char **, this is a NULL terminated
-list of pointers to error messages (which are NUL terminated strings,
-just as it normal in C :). Even though these aren't passed as char
-const * const * pointers, don't modify the strings or the pointers.
+A few functions return or pass in an i_errmsg *, this is list of error
+structures, terminated by an entry with a NULL msg value, each of
+which contains a msg and an error code. Even though these aren't
+passed as i_errmsg const * pointers, don't modify the strings
+or the pointers.
The interface as currently defined isn't thread safe, unfortunately.
=cut
*/
-#include "image.h"
+#include "imageri.h"
#include <stdio.h>
#include <stdlib.h>
-/* we never actually use the last item - it's the NULL terminator */
-#define ERRSTK 20
-char *error_stack[ERRSTK];
-int error_sp = ERRSTK - 1;
-/* we track the amount of space used each string, so we don't reallocate
- space unless we need to.
- This also means that a memory tracking library may see the memory
- allocated for this as a leak. */
-int error_space[ERRSTK];
-
-static i_error_cb error_cb;
-static i_failed_cb failed_cb;
-static int failures_fatal;
-static char *argv0;
-
/*
-=item i_set_argv0(char const *program)
-
-Sets the name of the program to be displayed in fatal error messages.
+=item im_errors(ctx)
+=synopsis i_errmsg *errors = im_errors(aIMCTX);
+=synopsis i_errmsg *errors = i_errors();
-The simplest way to use this is just:
+Returns a pointer to the first element of an array of error messages,
+terminated by a NULL pointer. The highest level message is first.
- i_set_argv0(argv[0]);
+Also callable as C<i_errors()>.
-when your program starts.
+=cut
*/
-void i_set_argv0(char const *name) {
- if (!name)
- return;
- char *dupl = mymalloc(strlen(name)+1);
- strcpy(dupl, name);
- if (argv0)
- myfree(argv0);
- argv0 = dupl;
+i_errmsg *im_errors(im_context_t ctx) {
+ return ctx->error_stack + ctx->error_sp;
}
/*
-=item i_set_failure_fatal(int failure_fatal)
+=back
-If failure_fatal is non-zero then any future failures will result in
-Imager exiting your program with a message describing the failure.
+=head1 INTERNAL FUNCTIONS
-Returns the previous setting.
+These functions are called by Imager to report errors through the
+above interface.
-=cut
-*/
-int i_set_failures_fatal(int fatal) {
- int old = failures_fatal;
- failures_fatal = fatal;
+It may be desirable to have functions to mark the stack and reset to
+the mark.
- return old;
-}
+=over
-/*
-=item i_set_error_cb(i_error_cb)
+=item im_clear_error(ctx)
+X<im_clear_error API>X<i_clear_error API>
+=synopsis im_clear_error(aIMCTX);
+=synopsis i_clear_error();
+=category Error handling
-Sets a callback function that is called each time an error is pushed
-onto the error stack.
+Clears the error stack.
-Returns the previous callback.
+Called by any Imager function before doing any other processing.
-i_set_failed_cb() is probably more useful.
+Also callable as C<i_clear_error()>.
=cut
*/
-i_error_cb i_set_error_cb(i_error_cb cb) {
- i_error_cb old = error_cb;
- error_cb = cb;
- return old;
+void
+im_clear_error(im_context_t ctx) {
+#ifdef IMAGER_DEBUG_MALLOC
+ int i;
+
+ for (i = 0; i < IM_ERROR_COUNT; ++i) {
+ if (ctx->error_space[i]) {
+ myfree(ctx->error_stack[i].msg);
+ ctx->error_stack[i].msg = NULL;
+ ctx->error_space[i] = 0;
+ }
+ }
+#endif
+ ctx->error_sp = IM_ERROR_COUNT-1;
}
/*
-=item i_set_failed_cb(i_failed_cb cb)
+=item im_push_error(ctx, code, message)
+X<im_push_error API>X<i_push_error API>
+=synopsis i_push_error(0, "Yep, it's broken");
+=synopsis i_push_error(errno, "Error writing");
+=synopsis im_push_error(aIMCTX, 0, "Something is wrong");
+=category Error handling
-Sets a callback function that is called each time an Imager function
-fails.
+Called by an Imager function to push an error message onto the stack.
-Returns the previous callback.
+No message is pushed if the stack is full (since this means someone
+forgot to call i_clear_error(), or that a function that doesn't do
+error handling is calling function that does.).
=cut
*/
-i_failed_cb i_set_failed_cb(i_failed_cb cb) {
- i_failed_cb old = failed_cb;
- failed_cb = cb;
+void
+im_push_error(im_context_t ctx, int code, char const *msg) {
+ size_t size = strlen(msg)+1;
+
+ if (ctx->error_sp <= 0)
+ /* bad, bad programmer */
+ return;
- return old;
+ --ctx->error_sp;
+ if (ctx->error_alloc[ctx->error_sp] < size) {
+ if (ctx->error_stack[ctx->error_sp].msg)
+ myfree(ctx->error_stack[ctx->error_sp].msg);
+ /* memory allocated on the following line is only ever released when
+ we need a bigger string */
+ /* size is size (len+1) of an existing string, overflow would mean
+ the system is broken anyway */
+ ctx->error_stack[ctx->error_sp].msg = mymalloc(size); /* checked 17jul05 tonyc */
+ ctx->error_alloc[ctx->error_sp] = size;
+ }
+ strcpy(ctx->error_stack[ctx->error_sp].msg, msg);
+ ctx->error_stack[ctx->error_sp].code = code;
}
/*
-=item i_errors()
+=item im_push_errorvf(ctx, code, format, args)
+X<im_push_error_vf API>X<i_push_errorvf API>
+=synopsis va_args args;
+=synopsis va_start(args, lastarg);
+=synopsis im_push_errorvf(ctx, code, format, args);
+=category Error handling
-Returns a pointer to the first element of an array of error messages,
-terminated by a NULL pointer. The highest level message is first.
+Intended for use by higher level functions, takes a varargs pointer
+and a format to produce the finally pushed error message.
+
+Does not support perl specific format codes.
+
+Also callable as C<i_push_errorvf(code, format, args)>
=cut
*/
-char **i_errors() {
- return error_stack + error_sp;
+void
+im_push_errorvf(im_context_t ctx, int code, char const *fmt, va_list ap) {
+ char buf[1024];
+#if defined(IMAGER_VSNPRINTF)
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+#elif defined(_MSC_VER)
+ _vsnprintf(buf, sizeof(buf), fmt, ap);
+#else
+ /* is there a way to detect vsnprintf()?
+ for this and other functions we need some mechanism to handle
+ detection (like perl's Configure, or autoconf)
+ */
+ vsprintf(buf, fmt, ap);
+#endif
+ im_push_error(ctx, code, buf);
}
/*
-=back
-
-=head1 INTERNAL FUNCTIONS
-
-These functions are called by Imager to report errors through the
-above interface.
-
-It may be desirable to have functions to mark the stack and reset to
-the mark.
-
-=over
+=item i_push_errorf(int code, char const *fmt, ...)
+=synopsis i_push_errorf(errno, "Cannot open file %s: %d", filename, errno);
+=category Error handling
-=item i_clear_error()
+A version of i_push_error() that does printf() like formatting.
-Called by any imager function before doing any other processing.
+Does not support perl specific format codes.
-=cut */
-void i_clear_error() {
- error_sp = ERRSTK-1;
+=cut
+*/
+void
+i_push_errorf(int code, char const *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ i_push_errorvf(code, fmt, ap);
+ va_end(ap);
}
/*
-=item i_push_error(char const *msg)
+=item im_push_errorf(ctx, code, char const *fmt, ...)
+=synopsis im_push_errorf(aIMCTX, errno, "Cannot open file %s: %d", filename, errno);
+=category Error handling
-Called by an imager function to push an error message onto the stack.
+A version of im_push_error() that does printf() like formatting.
-No message is pushed if the stack is full (since this means someone
-forgot to call i_clear_error(), or that a function that doesn't do
-error handling is calling function that does.).
+Does not support perl specific format codes.
=cut
*/
-void i_push_error(char const *msg) {
- int size = strlen(msg)+1;
-
- if (error_sp <= 0)
- /* bad, bad programmer */
- return;
-
- --error_sp;
- if (error_space[error_sp] < size) {
- if (error_stack[error_sp])
- myfree(error_stack[error_sp]);
- /* memory allocated on the following line is only ever release when
- we need a bigger string */
- error_stack[error_sp] = mymalloc(size);
- error_space[error_sp] = size;
- }
- strcpy(error_stack[error_sp], msg);
-
- if (error_cb)
- error_cb(msg);
+void
+im_push_errorf(im_context_t ctx, int code, char const *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ im_push_errorvf(ctx, code, fmt, ap);
+ va_end(ap);
}
+#ifdef IMAGER_I_FAILED
+#error "This isn't used and is untested"
+
/*
=item i_failed(char const *msg)
Called by Imager code to indicate that a top-level has failed.
-msg can be NULL.
+msg can be NULL, in which case no error is pushed.
Calls the current failed callback, if any.
=cut
*/
-int i_failed(char const *msg) {
+int i_failed(int code, char const *msg) {
if (msg)
- i_push_error(msg);
+ i_push_error(code, msg);
if (failed_cb)
failed_cb(error_stack + error_sp);
if (failures_fatal) {
+ int sp;
+ size_t total; /* total length of error messages */
+ char *full; /* full message for logging */
if (argv0)
fprintf(stderr, "%s: ", argv0);
fputs("error:\n", stderr);
- while (error_stack[error_sp]) {
- fprintf(stderr, " %s\n", error_stack[error_sp]);
- ++error_sp;
+ sp = error_sp;
+ while (error_stack[sp].msg) {
+ fprintf(stderr, " %s\n", error_stack[sp].msg);
+ ++sp;
}
- exit(EXIT_FAILURE);
+ /* we want to log the error too, build an error message to hand to
+ i_fatal() */
+ total = 1; /* remember the NUL */
+ for (sp = error_sp; error_stack[sp].msg; ++sp) {
+ size_t new_total += strlen(error_stack[sp].msg) + 2;
+ if (new_total < total) {
+ /* overflow, somehow */
+ break;
+ }
+ }
+ full = mymalloc(total);
+ if (!full) {
+ /* just quit, at least it's on stderr */
+ exit(EXIT_FAILURE);
+ }
+ *full = 0;
+ for (sp = error_sp; error_stack[sp].msg; ++sp) {
+ strcat(full, error_stack[sp].msg);
+ strcat(full, ": ");
+ }
+ /* lose the extra ": " */
+ full[strlen(full)-2] = '\0';
+ i_fatal(EXIT_FAILURE, "%s", full);
}
return 0;
}
+#endif
+
+/*
+=item im_assert_fail(file, line, message)
+
+Called when an im_assert() assertion fails.
+
+=cut
+*/
+
+void
+im_assert_fail(char const *file, int line, char const *message) {
+ fprintf(stderr, "Assertion failed line %d file %s: %s\n",
+ line, file, message);
+ abort();
+}
+
/*
=back