ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
asciichat_errno.c
Go to the documentation of this file.
1
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <stdarg.h>
11#include <time.h>
12#ifndef _WIN32
13#include <errno.h>
14#endif
15
16#include <ascii-chat/asciichat_errno.h>
17#include <ascii-chat/util/path.h>
18#include <ascii-chat/util/time.h>
19#include <ascii-chat/util/string.h>
20#include <ascii-chat/platform/system.h>
21#include <ascii-chat/platform/errno.h>
22#include <ascii-chat/platform/init.h>
23#include <ascii-chat/common.h>
24#include <ascii-chat/log/logging.h>
25
26/* ============================================================================
27 * Thread-Local Storage
28 * ============================================================================
29 */
30
31__thread asciichat_error_context_t asciichat_errno_context = {.code = ASCIICHAT_OK,
32 .file = NULL,
33 .line = 0,
34 .function = NULL,
35 .context_message = NULL,
36 .timestamp = 0,
37 .system_errno = 0,
38 .backtrace = {0},
39 .backtrace_symbols = NULL,
40 .stack_depth = 0,
41 .has_system_error = false};
42
43__thread asciichat_error_t asciichat_errno = ASCIICHAT_OK;
44
45// Suppression flag to prevent error context allocation during cleanup
46static bool g_suppress_error_context = false;
47
48/* ============================================================================
49 * Error Statistics
50 * ============================================================================
51 */
52
53static asciichat_error_stats_t error_stats = {0};
54static bool stats_initialized = false;
55static static_mutex_t g_error_stats_mutex = STATIC_MUTEX_INIT;
56
57/* ============================================================================
58 * Thread-Safe Error Storage
59 * ============================================================================
60 */
61
62#define MAX_THREAD_ERRORS 64
63static struct {
65 asciichat_error_t error_code;
66 bool valid;
67} thread_errors[MAX_THREAD_ERRORS] = {0};
68
69/* ============================================================================
70 * Internal Helper Functions
71 * ============================================================================
72 */
73
74static void capture_backtrace(void **backtrace, char ***backtrace_symbols, int *stack_depth) {
75#ifndef NDEBUG // Capture in Debug and Dev modes
76 *stack_depth = platform_backtrace(backtrace, 32);
77 *backtrace_symbols = platform_backtrace_symbols(backtrace, *stack_depth);
78#else
79 (void)backtrace;
80 (void)backtrace_symbols;
81 *stack_depth = 0;
82#endif
83}
84
85static bool skip_backtrace_frame(const char *frame) {
86 return (strstr(frame, "BaseThreadInitThunk") != NULL || strstr(frame, "RtlUserThreadStart") != NULL ||
87 strstr(frame, "__scrt_common_main_seh") != NULL || strstr(frame, "capture_backtrace") != NULL ||
88 strstr(frame, "safe_backtrace") != NULL || strstr(frame, "platform_backtrace") != NULL ||
89 strstr(frame, "asciichat_set_errno") != NULL || strstr(frame, "asciichat_set_errno_with_message") != NULL ||
90 strstr(frame, "SET_ERRNO") != NULL || strstr(frame, "asciichat_fatal_with_context") != NULL ||
91 strstr(frame, "asciichat_print_error_context") != NULL);
92}
93
94void log_labeled(const char *label, log_color_t color, const char *message, ...) {
95 va_list args;
96 va_start(args, message);
97 char *formatted_message = format_message(message, args);
98 va_end(args);
99
100 safe_fprintf(stderr, "%s: %s\n", colored_string(color, label), formatted_message);
101
102 log_file("%s: %s", label, formatted_message);
103
104 SAFE_FREE(formatted_message);
105}
106
107/* ============================================================================
108 * Core Error Setting Functions
109 * ============================================================================
110 */
111
112void asciichat_set_errno(asciichat_error_t code, const char *file, int line, const char *function,
113 const char *context_message) {
114 // Suppress error context allocation during cleanup to prevent leaks
115 if (g_suppress_error_context) {
116 return;
117 }
118
119 // Clear any existing context message
120 if (asciichat_errno_context.context_message) {
121 SAFE_FREE(asciichat_errno_context.context_message);
122 asciichat_errno_context.context_message = NULL;
123 }
124
125 // Set the error context
126 asciichat_errno_context.code = code;
127 asciichat_errno_context.file = file;
128 asciichat_errno_context.line = line;
129 asciichat_errno_context.function = function;
130 asciichat_errno_context.timestamp = time_ns_to_us(time_get_realtime_ns());
131 asciichat_errno_context.has_system_error = false;
132
133 // Set the simple error code variable
134 asciichat_errno = code;
135
136 // Copy context message if provided
137 if (context_message == NULL) {
138 log_error("context_message is NULL");
139 const char *fallback = "No context message (this is invalid - set a context message)";
140 size_t len = strlen(fallback) + 1;
141 asciichat_errno_context.context_message = SAFE_MALLOC(len, char *);
142 if (asciichat_errno_context.context_message) {
143 SAFE_STRNCPY(asciichat_errno_context.context_message, fallback, len);
144 } else {
145 log_error("SAFE_MALLOC failed for fallback context_message");
146 }
147 } else {
148 size_t len = strlen(context_message) + 1;
149 asciichat_errno_context.context_message = SAFE_MALLOC(len, char *);
150 if (asciichat_errno_context.context_message) {
151 SAFE_STRNCPY(asciichat_errno_context.context_message, context_message, len);
152 } else {
153 log_error("SAFE_MALLOC failed for context_message");
154 }
155 }
156
157 // Capture stack trace in debug builds
158 if (asciichat_errno_context.backtrace_symbols != NULL) {
160 asciichat_errno_context.backtrace_symbols = NULL;
161 }
162 capture_backtrace(asciichat_errno_context.backtrace, &asciichat_errno_context.backtrace_symbols,
163 &asciichat_errno_context.stack_depth);
164
165 // Record in statistics
167}
168
169void asciichat_set_errno_with_message(asciichat_error_t code, const char *file, int line, const char *function,
170 const char *format, ...) {
171 va_list args;
172 va_start(args, format);
173
174 char *context_message = format_message(format, args);
175 asciichat_set_errno(code, file, line, function, context_message);
176
177 if (context_message) {
178 SAFE_FREE(context_message);
179 }
180
181 va_end(args);
182}
183
184void asciichat_set_errno_with_system_error(asciichat_error_t code, const char *file, int line, const char *function,
185 int sys_errno) {
186 asciichat_set_errno(code, file, line, function, NULL);
187 asciichat_errno_context.system_errno = sys_errno;
188 asciichat_errno_context.has_system_error = true;
189}
190
191void asciichat_set_errno_with_system_error_and_message(asciichat_error_t code, const char *file, int line,
192 const char *function, int sys_errno, const char *format, ...) {
193 va_list args;
194 va_start(args, format);
195
196 char *context_message = format_message(format, args);
197 asciichat_set_errno(code, file, line, function, context_message);
198 asciichat_errno_context.system_errno = sys_errno;
199 asciichat_errno_context.has_system_error = true;
200
201 if (context_message) {
202 SAFE_FREE(context_message);
203 }
204
205 va_end(args);
206}
207
208void asciichat_set_errno_with_wsa_error(asciichat_error_t code, const char *file, int line, const char *function,
209 int wsa_error) {
210 asciichat_set_errno(code, file, line, function, NULL);
211 asciichat_errno_context.wsa_error = wsa_error;
212 asciichat_errno_context.has_wsa_error = true;
213}
214
216 return asciichat_errno_context.has_wsa_error;
217}
218
219/* ============================================================================
220 * Error Checking and Clearing Functions
221 * ============================================================================
222 */
223
224bool asciichat_has_errno(asciichat_error_context_t *context) {
225 if (asciichat_errno_context.code == ASCIICHAT_OK) {
226 return false;
227 }
228
229 if (context) {
230 *context = asciichat_errno_context;
231 }
232
233 return true;
234}
235
237 if (asciichat_errno_context.context_message) {
238 SAFE_FREE(asciichat_errno_context.context_message);
239 asciichat_errno_context.context_message = NULL;
240 }
241
242 if (asciichat_errno_context.backtrace_symbols != NULL) {
244 asciichat_errno_context.backtrace_symbols = NULL;
245 }
246
248 asciichat_errno_context.code = ASCIICHAT_OK;
249 asciichat_errno_context.system_errno = 0;
250 asciichat_errno_context.has_system_error = false;
251 asciichat_errno_context.has_wsa_error = false;
252 asciichat_errno_context.wsa_error = 0;
253
254 /* Clear platform-specific error state */
256}
257
258asciichat_error_t asciichat_get_errno(void) {
259 return asciichat_errno_context.code;
260}
261
262/* ============================================================================
263 * Enhanced FATAL Functions
264 * ============================================================================
265 */
266
267void asciichat_fatal_with_context(asciichat_error_t code, const char *file, int line, const char *function,
268 const char *format, ...) {
269 (void)file;
270 (void)line;
271 (void)function;
272
273 // Print library error context if available
274 asciichat_error_context_t err_ctx;
275 if (HAS_ERRNO(&err_ctx)) {
276 log_labeled("\nasciichat_errno: libary code error context", LOG_COLOR_ERROR, "");
278 } else {
279 log_plain("WARNING: No error context found (asciichat_errno_context.code=%d)", asciichat_errno_context.code);
280 }
281
282 safe_fprintf(stderr, "\n");
283 log_labeled("FATAL ERROR", LOG_COLOR_FATAL, "exit code %d (%s)", (int)code, asciichat_error_string(code));
284#ifndef NDEBUG
285 const char *relative_file = extract_project_relative_path(file);
286 log_plain(" Location: %s:%d in %s()", relative_file, line, function);
287#endif
288
289 if (format) {
290 va_list args;
291 va_start(args, format);
292 char *formatted_message = format_message(format, args);
293 log_plain(" Error message: %s", formatted_message);
294 SAFE_FREE(formatted_message);
295 va_end(args);
296 }
297
298#ifndef NDEBUG
299 // Always print platform backtrace in debug/dev builds
300 void *buffer[32];
301 int size = platform_backtrace(buffer, 32);
302 if (size > 0) {
303 char **symbols = platform_backtrace_symbols(buffer, size);
304 if (symbols) {
305 platform_print_backtrace_symbols("\nFATAL BACKTRACE", symbols, size, 0, 0, skip_backtrace_frame);
307 }
308 }
309#endif
310
311 exit(code);
312}
313
314/* ============================================================================
315 * Error Context Printing Functions
316 * ============================================================================
317 */
318
319void asciichat_print_error_context(const asciichat_error_context_t *context) {
320 if (!context || context->code == ASCIICHAT_OK) {
321 return;
322 }
323
324 if (context->file && context->line && context->function) {
325 log_plain(" Location: %s:%d in %s()", extract_project_relative_path(context->file), context->line,
326 context->function);
327 } else {
328 log_plain(" Location: unknown (set by system code)");
329 }
330
331 if (context->context_message) {
332 safe_fprintf(stderr, " %s %s\n", colored_string(LOG_COLOR_WARN, "Context:"), context->context_message);
333 log_file(" Context: %s", context->context_message);
334 }
335
336 if (context->has_system_error) {
337 log_plain(" System error: %s (code: %d, meaning: %s)", SAFE_STRERROR(context->system_errno), context->system_errno,
338 SAFE_STRERROR(context->system_errno));
339 }
340
341 // Print timestamp
342 if (context->timestamp > 0) {
343 time_t sec = (time_t)(context->timestamp / NS_PER_MS_INT);
344 long usec = (long)(context->timestamp % NS_PER_MS_INT);
345 struct tm tm_info;
346 if (platform_localtime(&sec, &tm_info) == ASCIICHAT_OK) {
347 char time_str[64];
348 (void)strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &tm_info);
349 log_plain(" Timestamp: %s.%06ld", time_str, usec);
350 }
351 }
352
353 // Print stack trace from library error
354 if (context->stack_depth > 0 && context->backtrace_symbols) {
355 platform_print_backtrace_symbols("\nBacktrace from library error", context->backtrace_symbols, context->stack_depth,
356 0, 0, skip_backtrace_frame);
357 }
358}
359
360/* ============================================================================
361 * Error Statistics Functions
362 * ============================================================================
363 */
364
366 static_mutex_lock(&g_error_stats_mutex);
367
368 if (!stats_initialized) {
369 memset(&error_stats, 0, sizeof(error_stats));
370 stats_initialized = true;
371 }
372
373 static_mutex_unlock(&g_error_stats_mutex);
374}
375
376void asciichat_error_stats_record(asciichat_error_t code) {
377 static_mutex_lock(&g_error_stats_mutex);
378
379 if (!stats_initialized) {
380 memset(&error_stats, 0, sizeof(error_stats));
381 stats_initialized = true;
382 }
383
384 if (code >= 0 && code < 256) {
385 error_stats.error_counts[code]++;
386 }
387 error_stats.total_errors++;
388 error_stats.last_error_time = time_ns_to_us(time_get_realtime_ns());
389 error_stats.last_error_code = code;
390
391 static_mutex_unlock(&g_error_stats_mutex);
392}
393
395 static_mutex_lock(&g_error_stats_mutex);
396
397 if (!stats_initialized || error_stats.total_errors == 0) {
398 static_mutex_unlock(&g_error_stats_mutex);
399 log_plain("No errors recorded.\n");
400 return;
401 }
402
403 // Copy stats to local variable to minimize lock hold time
404 asciichat_error_stats_t local_stats = error_stats;
405 static_mutex_unlock(&g_error_stats_mutex);
406
407 log_plain("\n=== ascii-chat Error Statistics ===\n");
408 log_plain("Total errors: %llu\n", (unsigned long long)local_stats.total_errors);
409
410 if (local_stats.last_error_time > 0) {
411 time_t sec = (time_t)(local_stats.last_error_time / NS_PER_MS_INT);
412 struct tm tm_info;
413 if (platform_localtime(&sec, &tm_info) == ASCIICHAT_OK) {
414 char time_str[64];
415 strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &tm_info);
416 log_plain("Last error: %s (code %d)\n", time_str, (int)local_stats.last_error_code);
417 }
418 }
419
420 log_plain("\nError breakdown:\n");
421 for (int i = 0; i < 256; i++) {
422 if (local_stats.error_counts[i] > 0) {
423 log_plain(" %3d (%s): %llu\n", i, asciichat_error_string((asciichat_error_t)i),
424 (unsigned long long)local_stats.error_counts[i]);
425 }
426 }
427 log_plain("\n");
428}
429
431 static_mutex_lock(&g_error_stats_mutex);
432 memset(&error_stats, 0, sizeof(error_stats));
433 static_mutex_unlock(&g_error_stats_mutex);
434}
435
436asciichat_error_stats_t asciichat_error_stats_get(void) {
437 static_mutex_lock(&g_error_stats_mutex);
438
439 if (!stats_initialized) {
440 memset(&error_stats, 0, sizeof(error_stats));
441 stats_initialized = true;
442 }
443
444 asciichat_error_stats_t result = error_stats;
445 static_mutex_unlock(&g_error_stats_mutex);
446
447 return result;
448}
449
450/* ============================================================================
451 * Thread-Safe Error Propagation Functions
452 * ============================================================================
453 */
454
455asciichat_error_t asciichat_get_thread_error(int thread_id) {
456 for (int i = 0; i < MAX_THREAD_ERRORS; i++) {
457 if (thread_errors[i].valid && thread_errors[i].thread_id == thread_id) {
458 return thread_errors[i].error_code;
459 }
460 }
461 return ASCIICHAT_OK;
462}
463
464void asciichat_set_thread_error(int thread_id, asciichat_error_t code) {
465 // Find existing entry or empty slot
466 int slot = -1;
467 for (int i = 0; i < MAX_THREAD_ERRORS; i++) {
468 if (thread_errors[i].valid && thread_errors[i].thread_id == thread_id) {
469 slot = i;
470 break;
471 }
472 if (!thread_errors[i].valid && slot == -1) {
473 slot = i;
474 }
475 }
476
477 if (slot >= 0) {
478 thread_errors[slot].thread_id = thread_id;
479 thread_errors[slot].error_code = code;
480 thread_errors[slot].valid = true;
481 }
482}
483
485 for (int i = 0; i < MAX_THREAD_ERRORS; i++) {
486 if (thread_errors[i].valid && thread_errors[i].thread_id == thread_id) {
487 thread_errors[i].valid = false;
488 break;
489 }
490 }
491}
492
493/* ============================================================================
494 * Cleanup
495 * ============================================================================
496 */
497
498void asciichat_errno_suppress(bool suppress) {
499 g_suppress_error_context = suppress;
500}
501
503 if (asciichat_errno_context.backtrace_symbols != NULL) {
505 asciichat_errno_context.backtrace_symbols = NULL;
506 }
507
508 if (asciichat_errno_context.context_message != NULL) {
509 SAFE_FREE(asciichat_errno_context.context_message);
510 }
511
512 // Reset the context to a clean state
514 asciichat_errno_context.code = ASCIICHAT_OK;
515
516 // Suppress any further error context allocation to prevent cleanup-phase leaks
517 // This prevents other atexit() functions from allocating new contexts after we've cleaned up
518 g_suppress_error_context = true;
519}
asciichat_error_t error_code
void asciichat_set_errno(asciichat_error_t code, const char *file, int line, const char *function, const char *context_message)
bool asciichat_has_wsa_error(void)
#define MAX_THREAD_ERRORS
void asciichat_clear_thread_error(int thread_id)
bool valid
void asciichat_fatal_with_context(asciichat_error_t code, const char *file, int line, const char *function, const char *format,...)
void asciichat_errno_suppress(bool suppress)
void asciichat_set_errno_with_system_error(asciichat_error_t code, const char *file, int line, const char *function, int sys_errno)
__thread asciichat_error_t asciichat_errno
void asciichat_error_stats_print(void)
bool asciichat_has_errno(asciichat_error_context_t *context)
void asciichat_clear_errno(void)
asciichat_error_t asciichat_get_thread_error(int thread_id)
asciichat_error_t asciichat_get_errno(void)
void asciichat_set_errno_with_wsa_error(asciichat_error_t code, const char *file, int line, const char *function, int wsa_error)
asciichat_error_stats_t asciichat_error_stats_get(void)
void asciichat_error_stats_init(void)
void log_labeled(const char *label, log_color_t color, const char *message,...)
void asciichat_set_errno_with_message(asciichat_error_t code, const char *file, int line, const char *function, const char *format,...)
void asciichat_errno_destroy(void)
void asciichat_error_stats_reset(void)
int thread_id
__thread asciichat_error_context_t asciichat_errno_context
void asciichat_set_thread_error(int thread_id, asciichat_error_t code)
void asciichat_print_error_context(const asciichat_error_context_t *context)
void asciichat_set_errno_with_system_error_and_message(asciichat_error_t code, const char *file, int line, const char *function, int sys_errno, const char *format,...)
void asciichat_error_stats_record(asciichat_error_t code)
char * format_message(const char *format, va_list args)
action_args_t args
const char * extract_project_relative_path(const char *file)
Definition path.c:410
int safe_fprintf(FILE *stream, const char *format,...)
Safe formatted output to file stream.
Definition system.c:480
void platform_print_backtrace_symbols(const char *label, char **symbols, int count, int skip_frames, int max_frames, backtrace_frame_filter_t filter)
Print pre-resolved backtrace symbols with colored terminal output and plain log file output.
Definition system.c:535
const char * colored_string(log_color_t color, const char *text)
uint64_t time_get_realtime_ns(void)
Definition util/time.c:59
int platform_backtrace(void **buffer, int size)
Definition util.c:14
void platform_clear_error_state(void)
Definition util.c:67
void platform_backtrace_symbols_destroy(char **symbols)
Definition util.c:26
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Definition util.c:48
char ** platform_backtrace_symbols(void *const *buffer, int size)
Definition util.c:20