=== Overview ===
[xuni.git] / src / error.c
blobdd1928c233f194498f8cd32a1a3e43866168ed8a
1 /*! \file error.c
3 Error handling code. Allows multiple file names and streams to be
4 registered; errors are output to each of them. Errors of various types can
5 be reported with log_message(), which supports printf()-style format
6 strings.
8 If a file is specified as a stream, it is not closed automatically.
9 (Useful for using "stderr" and such.) Files specified as file names are
10 opened just before an error message needs to be written to them. (If a
11 file cannot be opened, this is only announced on stderr. There is a limit
12 to how far this sort of thing should go.) If no files could be opened, or
13 no files were registered, the error is printed to stderr.
15 See get_error_type() for the types of errors allowed and their brief
16 descriptions.
18 This code does not use xuni's reference counter (in memory.c); rather,
19 the reference counter uses these error handling functions.
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <time.h>
28 #include "SDL.h"
30 #include "error.h"
31 #include "graphics.h"
32 #include "memory.h"
33 #include "version.h"
35 /* This code is quite similar to memory.c in many respects. */
37 struct error_stream_t {
38 const char *filename;
39 FILE *fp;
40 int opened;
43 struct error_stream_array_t {
44 struct error_stream_t *stream;
45 size_t alloc, n;
48 static struct error_stream_array_t stream = {0, 0, 0};
50 static void xuni_error_grow_stream_alloc(void);
52 /*! Initializes xuni's error handling subsystem.
54 void xuni_error_initialize(void) {
58 static void xuni_error_grow_stream_alloc(void) {
59 struct error_stream_t *p;
61 /* since few error streams are usually registered, add only one */
62 stream.alloc ++;
63 /*if(!stream.alloc) stream.alloc = 1;
64 else stream.alloc *= 2;*/
66 /* Avoid using xuni's reference counter, because it uses this error-
67 handling code to report errors.
69 p = realloc(stream.stream, stream.alloc * sizeof(*stream.stream));
70 if(!p) {
71 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
72 "Error allocating %lu struct error_stream_t's",
73 (unsigned long)stream.alloc);
74 return;
76 stream.stream = p;
79 /*! Registers a file name or stream to print error messages to. Either
80 \a filename or \a fp must be non-NULL to specify the file as a filename or
81 a stream, respectively.
83 If \a filename is not NULL, the file specified by this string will be
84 opened and have error messages written to it only when errors are
85 encountered. (In other words, the file is kept closed whenever possible.)
87 If \a fp is not NULL, errors will be written to this open file stream.
88 This stream will not be closed, allowing stderr or user-managed files to
89 be used.
91 If both arguments, or neither, are NULL, the behaviour of this function is
92 undefined.
94 \param filename The name of the file to register.
95 \param fp An opened file stream to register. Works for standard streams
96 like stderr.
98 void xuni_error_add_stream(const char *filename, FILE *fp) {
99 if(stream.n == stream.alloc) xuni_error_grow_stream_alloc();
101 if(filename) {
102 stream.stream[stream.n].filename
103 = xuni_memory_duplicate_string(filename);
105 else stream.stream[stream.n].filename = 0;
107 stream.stream[stream.n].fp = fp;
108 stream.stream[stream.n].opened = !fp;
110 stream.n ++;
113 /*! Frees resources allocated for xuni's error handling subsystem. This means
114 the list of opened files that were added. Any opened files are closed,
115 too, but currently no files remain open long enough for this function to
116 have to deal with them.
118 void xuni_error_quit(void) {
119 size_t x;
121 for(x = 0; x < stream.n; x ++) {
122 xuni_memory_free((void *)stream.stream[x].filename);
124 if(stream.stream[x].fp && stream.stream[x].opened) {
125 fclose(stream.stream[x].fp);
129 free(stream.stream);
132 static const char *get_ctime(void);
133 static const char *get_error_type(enum error_type_t type);
134 static void log_message_stream(FILE *stream, enum error_type_t type,
135 enum error_flag_t flag, const char *file, int line, const char *date,
136 const char *format, va_list arg);
138 static const char *get_ctime(void) {
139 time_t tt = time(0);
141 return ctime(&tt);
144 static const char *get_error_type(enum error_type_t type) {
145 static const char *type_name[] = {
146 "Log message", /* ERROR_TYPE_LOG */
147 "Warning", /* ERROR_TYPE_WARNING */
148 "Fatal error", /* ERROR_TYPE_FATAL */
149 "Settings error", /* ERROR_TYPE_SETTING */
150 "Resource error", /* ERROR_TYPE_RESOURCE */
151 "Memory error", /* ERROR_TYPE_MEMORY */
152 "Data structure error", /* ERROR_TYPE_DATASTRUCT */
153 "Lookup table error" /* ERROR_TYPE_LOOKUP_TABLE */
156 if(type >= sizeof(type_name)/sizeof(*type_name)) type = 0;
158 return type_name[type];
161 static void log_message_stream(FILE *stream, enum error_type_t type,
162 enum error_flag_t flag, const char *file, int line, const char *date,
163 const char *format, va_list arg) {
165 if(!(flag & ERROR_FLAG_CONTINUED)) {
166 /* note: date is terminated by a newline */
167 fprintf(stream, "xuni: %s from %s line %i, at %s ",
168 get_error_type(type), file, line, date);
170 else fputs(" [continued] ", stream);
172 vfprintf(stream, format, arg);
174 if(flag & ERROR_FLAG_SDL) {
175 fputs("\n SDL: ", stream);
176 fputs(SDL_GetError(), stream);
179 putc('\n', stream);
182 /*! Records an error, which can be one of many different types. The message,
183 which consists mainly of \a format with other information thrown in, is
184 output to all registered error streams.
186 \param type The type of error. Affects the string describing what type of
187 error the error message is.
188 \param flag More specific information about the error. Affects which other
189 error reporting functions should be called. For example,
190 \c ERROR_FLAG_SDL causes the output of SDL_GetError() to be recorded.
191 \param file The file in which the error occurred. Should be \c __FILE__.
192 \param line The line at which the error occurred. Should be \c __LINE__.
193 \param format The printf()-compatible string describing the error. The
194 variable arguments afterwards can be used to match arguments to format
195 specifiers, denoted with percent signs (%) in this format string.
197 void log_message(enum error_type_t type, enum error_flag_t flag,
198 const char *file, int line, const char *format, ...) {
200 const char *date = get_ctime();
201 va_list arg;
202 size_t x, wrote = 0;
204 for(x = 0; x < stream.n; x ++) {
205 if(!stream.stream[x].fp) {
206 stream.stream[x].fp = fopen(stream.stream[x].filename, "a");
207 if(!stream.stream[x].fp) {
208 fprintf(stderr, "Can't open log file \"%s\"\n",
209 stream.stream[x].filename);
210 continue;
214 va_start(arg, format);
215 log_message_stream(stream.stream[x].fp, type, flag, file, line, date,
216 format, arg);
217 va_end(arg);
219 if(stream.stream[x].fp && stream.stream[x].filename) {
220 fclose(stream.stream[x].fp);
221 stream.stream[x].fp = 0;
224 wrote ++;
227 if(!wrote) {
228 va_start(arg, format);
229 log_message_stream(stderr, type, flag, file, line, date, format, arg);
230 va_end(arg);