ascii-chat 0.11.33
Video chat in your terminal
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
25#include <ascii-chat/common.h>
26#include <ascii-chat/log/log.h>
27
28/* ============================================================================
29 * Thread-Local Storage
30 * ============================================================================
31 */
32
34 .file = NULL,
35 .line = 0,
36 .function = NULL,
37 .context_message = NULL,
38 .timestamp = 0,
39 .system_errno = 0,
40 .backtrace = {.count = 0, .symbols = NULL},
41 .has_system_error = false};
42
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 struct {
57} g_error_stats = {.stats = {0}, .lifecycle = LIFECYCLE_INIT};
58
59/* ============================================================================
60 * Thread-Safe Error Storage
61 * ============================================================================
62 */
63
64#define MAX_THREAD_ERRORS 64
65static struct {
68 bool valid;
69} thread_errors[MAX_THREAD_ERRORS] = {0};
70
71/* ============================================================================
72 * Internal Helper Functions
73 * ============================================================================
74 */
75
76static void capture_backtrace(backtrace_t *bt) {
77#ifndef NDEBUG // Capture in Debug and Dev modes
78 if (bt) {
79 backtrace_capture(bt); // Capture without symbolizing (symbolize later if needed)
80 }
81#else
82 (void)bt;
83#endif
84}
85
86static bool skip_backtrace_frame(const char *frame) {
87 return (strstr(frame, "BaseThreadInitThunk") != NULL || strstr(frame, "RtlUserThreadStart") != NULL ||
88 strstr(frame, "__scrt_common_main_seh") != NULL || strstr(frame, "capture_backtrace") != NULL ||
89 strstr(frame, "safe_backtrace") != NULL || strstr(frame, "platform_backtrace") != NULL ||
90 strstr(frame, "asciichat_set_errno") != NULL || strstr(frame, "asciichat_set_errno_with_message") != NULL ||
91 strstr(frame, "SET_ERRNO") != NULL || strstr(frame, "asciichat_fatal_with_context") != NULL ||
92 strstr(frame, "asciichat_print_error_context") != NULL);
93}
94
95void log_labeled(const char *label, log_color_t color, const char *message, ...) {
96 va_list args;
97 va_start(args, message);
98 char *formatted_message = format_message(message, args);
99 va_end(args);
100
101 safe_fprintf(stderr, "%s: %s\n", colored_string(color, label), formatted_message);
102
103 log_file("%s: %s", label, formatted_message);
104
105 SAFE_FREE(formatted_message);
106}
107
108/* ============================================================================
109 * Core Error Setting Functions
110 * ============================================================================
111 */
112
113void asciichat_set_errno(asciichat_error_t code, const char *file, int line, const char *function,
114 const char *context_message) {
115 // Suppress error context allocation during cleanup to prevent leaks
116 if (g_suppress_error_context) {
117 return;
118 }
119
120 // Clear any existing context message
124 }
125
126 // Set the error context
133
134 // Set the simple error code variable
135 asciichat_errno = code;
136
137 // Copy context message if provided
138 if (context_message == NULL) {
139 log_error("context_message is NULL");
140 const char *fallback = "No context message (this is invalid - set a context message)";
141 size_t len = strlen(fallback) + 1;
145 } else {
146 log_error("SAFE_MALLOC failed for fallback context_message");
147 }
148 } else {
149 size_t len = strlen(context_message) + 1;
153 } else {
154 log_error("SAFE_MALLOC failed for context_message");
155 }
156 }
157
158 // Capture stack trace in debug builds
160 capture_backtrace(&asciichat_errno_context.backtrace);
161
162 // Record in statistics
164}
165
166void asciichat_set_errno_with_message(asciichat_error_t code, const char *file, int line, const char *function,
167 const char *format, ...) {
168 va_list args;
169 va_start(args, format);
170
171 char *context_message = format_message(format, args);
172 asciichat_set_errno(code, file, line, function, context_message);
173
174 if (context_message) {
175 SAFE_FREE(context_message);
176 }
177
178 va_end(args);
179}
180
181void asciichat_set_errno_with_system_error(asciichat_error_t code, const char *file, int line, const char *function,
182 int sys_errno) {
183 asciichat_set_errno(code, file, line, function, NULL);
186}
187
189 const char *function, int sys_errno, const char *format, ...) {
190 va_list args;
191 va_start(args, format);
192
193 char *context_message = format_message(format, args);
194 asciichat_set_errno(code, file, line, function, context_message);
197
198 if (context_message) {
199 SAFE_FREE(context_message);
200 }
201
202 va_end(args);
203}
204
205void asciichat_set_errno_with_wsa_error(asciichat_error_t code, const char *file, int line, const char *function,
206 int wsa_error) {
207 asciichat_set_errno(code, file, line, function, NULL);
210}
211
215
216/* ============================================================================
217 * Error Checking and Clearing Functions
218 * ============================================================================
219 */
220
223 return false;
224 }
225
226 if (context) {
227 *context = asciichat_errno_context;
228 }
229
230 return true;
231}
232
251
255
256/* ============================================================================
257 * Enhanced FATAL Functions
258 * ============================================================================
259 */
260
261void asciichat_fatal_with_context(asciichat_error_t code, const char *file, int line, const char *function,
262 const char *format, ...) {
263 (void)file;
264 (void)line;
265 (void)function;
266
267 // Print library error context if available
269 if (HAS_ERRNO(&err_ctx)) {
270 log_labeled("\nasciichat_errno: libary code error context", LOG_COLOR_ERROR, "");
272 } else {
273 log_plain("WARNING: No error context found (asciichat_errno_context.code=%d)", asciichat_errno_context.code);
274 }
275
276 safe_fprintf(stderr, "\n");
277 log_labeled("FATAL ERROR", LOG_COLOR_FATAL, "exit code %d (%s)", (int)code, asciichat_error_string(code));
278#ifndef NDEBUG
279 const char *relative_file = extract_project_relative_path(file);
280 log_plain(" Location: %s:%d in %s()", relative_file, line, function);
281#endif
282
283 if (format) {
284 va_list args;
285 va_start(args, format);
286 char *formatted_message = format_message(format, args);
287 log_plain(" Error message: %s", formatted_message);
288 SAFE_FREE(formatted_message);
289 va_end(args);
290 }
291
292#ifndef NDEBUG
293 // Always print platform backtrace in debug/dev builds
294 backtrace_t bt = {0};
296 if (bt.count > 0) {
297 backtrace_print("\nFATAL BACKTRACE", &bt, 0, 0, skip_backtrace_frame);
298 backtrace_t_free(&bt);
299 }
300#endif
301
302#ifdef EMSCRIPTEN_BUILD
303 // In WASM builds, do not call exit() — it permanently kills the module runtime.
304 // Instead, set the error and return so callers can handle it gracefully.
305 SET_ERRNO(code, "FATAL (non-exit in WASM): code=%d", (int)code);
306#else
307 exit(code);
308#endif
309}
310
311/* ============================================================================
312 * Error Context Printing Functions
313 * ============================================================================
314 */
315
317 if (!context || context->code == ASCIICHAT_OK) {
318 return;
319 }
320
321 if (context->file && context->line && context->function) {
322 log_plain(" Location: %s:%d in %s()", extract_project_relative_path(context->file), context->line,
323 context->function);
324 } else {
325 log_plain(" Location: unknown (set by system code)");
326 }
327
328 if (context->context_message) {
329 safe_fprintf(stderr, " %s %s\n", colored_string(LOG_COLOR_WARN, "Context:"), context->context_message);
330 log_file(" Context: %s", context->context_message);
331 }
332
333 if (context->has_system_error) {
334 log_plain(" System error: %s (code: %d, meaning: %s)", SAFE_STRERROR(context->system_errno), context->system_errno,
335 SAFE_STRERROR(context->system_errno));
336 }
337
338 // Print timestamp
339 if (context->timestamp > 0) {
340 time_t sec = (time_t)(context->timestamp / NS_PER_MS_INT);
341 long usec = (long)(context->timestamp % NS_PER_MS_INT);
342 struct tm tm_info;
343 if (platform_localtime(&sec, &tm_info) == ASCIICHAT_OK) {
344 char time_str[64];
345 (void)strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &tm_info);
346 log_plain(" Timestamp: %s.%06ld", time_str, usec);
347 }
348 }
349
350 // Print stack trace from library error
351 if (context->backtrace.count > 0) {
352 // Symbolize backtrace only when actually printing it (not at capture time)
354 if (context->backtrace.symbols) {
355 backtrace_print("\nBacktrace from library error", &context->backtrace, 0, 0, skip_backtrace_frame);
356 }
357 }
358}
359
360/* ============================================================================
361 * Error Statistics Functions
362 * ============================================================================
363 */
364
366 if (lifecycle_init(&g_error_stats.lifecycle, "error_stats")) {
367 memset(&g_error_stats.stats, 0, sizeof(g_error_stats.stats));
368 }
369}
370
372 if (!lifecycle_is_initialized(&g_error_stats.lifecycle)) {
373 if (lifecycle_init(&g_error_stats.lifecycle, "error_stats")) {
374 mutex_init(&g_error_stats.mutex, "error_stats");
375 memset(&g_error_stats.stats, 0, sizeof(g_error_stats.stats));
376 }
377 }
378
379 mutex_lock(&g_error_stats.mutex);
380
381 if (code >= 0 && code < 256) {
382 g_error_stats.stats.error_counts[code]++;
383 }
384 g_error_stats.stats.total_errors++;
385 g_error_stats.stats.last_error_time = time_ns_to_us(time_get_realtime_ns());
386 g_error_stats.stats.last_error_code = code;
387
388 mutex_unlock(&g_error_stats.mutex);
389}
390
392 if (!lifecycle_is_initialized(&g_error_stats.lifecycle)) {
393 log_plain("No errors recorded.\n");
394 return;
395 }
396
397 mutex_lock(&g_error_stats.mutex);
398
399 if (g_error_stats.stats.total_errors == 0) {
400 mutex_unlock(&g_error_stats.mutex);
401 log_plain("No errors recorded.\n");
402 return;
403 }
404
405 // Copy stats to local variable to minimize lock hold time
406 asciichat_error_stats_t local_stats = g_error_stats.stats;
407 mutex_unlock(&g_error_stats.mutex);
408
409 log_plain("\n=== ascii-chat Error Statistics ===\n");
410 log_plain("Total errors: %llu\n", (unsigned long long)local_stats.total_errors);
411
412 if (local_stats.last_error_time > 0) {
413 time_t sec = (time_t)(local_stats.last_error_time / NS_PER_MS_INT);
414 struct tm tm_info;
415 if (platform_localtime(&sec, &tm_info) == ASCIICHAT_OK) {
416 char time_str[64];
417 strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &tm_info);
418 log_plain("Last error: %s (code %d)\n", time_str, (int)local_stats.last_error_code);
419 }
420 }
421
422 log_plain("\nError breakdown:\n");
423 for (int i = 0; i < 256; i++) {
424 if (local_stats.error_counts[i] > 0) {
425 log_plain(" %3d (%s): %llu\n", i, asciichat_error_string((asciichat_error_t)i),
426 (unsigned long long)local_stats.error_counts[i]);
427 }
428 }
429 log_plain("\n");
430}
431
433 if (lifecycle_is_initialized(&g_error_stats.lifecycle)) {
434 mutex_lock(&g_error_stats.mutex);
435 memset(&g_error_stats.stats, 0, sizeof(g_error_stats.stats));
436 mutex_unlock(&g_error_stats.mutex);
437 }
438}
439
441 if (!lifecycle_is_initialized(&g_error_stats.lifecycle)) {
442 lifecycle_init(&g_error_stats.lifecycle, "error_stats");
443 }
444
445 mutex_lock(&g_error_stats.mutex);
446 asciichat_error_stats_t result = g_error_stats.stats;
447 mutex_unlock(&g_error_stats.mutex);
448
449 return result;
450}
451
452/* ============================================================================
453 * Thread-Safe Error Propagation Functions
454 * ============================================================================
455 */
456
458 for (int i = 0; i < MAX_THREAD_ERRORS; i++) {
459 if (thread_errors[i].valid && thread_errors[i].thread_id == thread_id) {
460 return thread_errors[i].error_code;
461 }
462 }
463 return ASCIICHAT_OK;
464}
465
467 // Find existing entry or empty slot
468 int slot = -1;
469 for (int i = 0; i < MAX_THREAD_ERRORS; i++) {
470 if (thread_errors[i].valid && thread_errors[i].thread_id == thread_id) {
471 slot = i;
472 break;
473 }
474 if (!thread_errors[i].valid && slot == -1) {
475 slot = i;
476 }
477 }
478
479 if (slot >= 0) {
480 thread_errors[slot].thread_id = thread_id;
481 thread_errors[slot].error_code = code;
482 thread_errors[slot].valid = true;
483 }
484}
485
487 for (int i = 0; i < MAX_THREAD_ERRORS; i++) {
488 if (thread_errors[i].valid && thread_errors[i].thread_id == thread_id) {
489 thread_errors[i].valid = false;
490 break;
491 }
492 }
493}
494
495/* ============================================================================
496 * Cleanup
497 * ============================================================================
498 */
499
500void asciichat_errno_suppress(bool suppress) {
501 g_suppress_error_context = suppress;
502}
503
506
509 }
510
511 // Reset the context to a clean state
514
515 // Suppress any further error context allocation to prevent cleanup-phase leaks
516 // This prevents other atexit() functions from allocating new contexts after we've cleaned up
517 g_suppress_error_context = true;
518}
asciichat_error_stats_t stats
asciichat_error_t error_code
#define MAX_THREAD_ERRORS
bool valid
lifecycle_t lifecycle
int thread_id
mutex_t mutex
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
⚙️ Common definitions, error codes, macros, and types shared throughout the application
void backtrace_t_free(backtrace_t *bt)
Free backtrace symbols.
void backtrace_capture_and_symbolize(backtrace_t *bt)
Capture and symbolize in one call.
void backtrace_capture(backtrace_t *bt)
Capture current stack into bt.
void backtrace_print(const char *label, const backtrace_t *bt, int skip_frames, int max_frames, backtrace_frame_filter_t filter)
Print backtrace with dual output: colored stderr + plain log file.
void backtrace_symbolize(backtrace_t *bt)
Symbolize bt->ptrs into bt->symbols.
📍 Backtrace capture, symbolization, and formatting
Cross-platform error handling utilities.
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_STRERROR(errnum)
Definition common.h:465
void asciichat_set_errno(asciichat_error_t code, const char *file, int line, const char *function, const char *context_message)
Set error code with basic context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
bool asciichat_has_wsa_error(void)
Check if current error has WSA error code.
void asciichat_clear_thread_error(int thread_id)
Clear error code for a specific thread.
void asciichat_fatal_with_context(asciichat_error_t code, const char *file, int line, const char *function, const char *format,...)
Exit with error code and context (used by FATAL macro)
void asciichat_errno_suppress(bool suppress)
Suppress error logging and reporting.
void asciichat_set_errno_with_system_error(asciichat_error_t code, const char *file, int line, const char *function, int sys_errno)
Set error code with system error (errno)
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
__thread asciichat_error_t asciichat_errno
Thread-local current error code.
void asciichat_error_stats_print(void)
Print error statistics to stderr.
bool asciichat_has_errno(asciichat_error_context_t *context)
Check if error occurred and get full context.
void asciichat_clear_errno(void)
Clear the current error state.
asciichat_error_t asciichat_get_thread_error(int thread_id)
Get error code for a specific thread.
asciichat_error_t asciichat_get_errno(void)
Get current error code.
void asciichat_set_errno_with_wsa_error(asciichat_error_t code, const char *file, int line, const char *function, int wsa_error)
Set error code with Windows socket error (WSA error)
asciichat_error_stats_t asciichat_error_stats_get(void)
Get current error statistics.
void asciichat_error_stats_init(void)
Initialize error statistics system.
void asciichat_set_errno_with_message(asciichat_error_t code, const char *file, int line, const char *function, const char *format,...)
Set error code with formatted message.
void asciichat_errno_destroy(void)
Cleanup error system resources.
void asciichat_error_stats_reset(void)
Reset all error statistics to zero.
__thread asciichat_error_context_t asciichat_errno_context
Thread-local error context storage.
void asciichat_set_thread_error(int thread_id, asciichat_error_t code)
Set error code for a specific thread.
void asciichat_print_error_context(const asciichat_error_context_t *context)
Print full error context to stderr.
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,...)
Set error code with system error and formatted message.
void asciichat_error_stats_record(asciichat_error_t code)
Record an error in statistics.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
char * format_message(const char *format, va_list args)
Format a message using va_list.
Definition log/log.c:248
log_color_t
Color enum for logging - indexes into color arrays.
Definition log/log.h:130
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
void log_labeled(const char *label, log_color_t color, const char *message,...)
Print a labeled message with color.
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
Definition log/log.h:646
#define log_plain(...)
Plain logging - writes to both log file and stderr without timestamps or log levels.
Definition log/log.h:618
@ LOG_COLOR_FATAL
Definition log/log.h:136
@ LOG_COLOR_ERROR
Definition log/log.h:135
@ LOG_COLOR_WARN
Definition log/log.h:134
uint64_t time_get_realtime_ns(void)
Get current wall-clock (real) time in nanoseconds.
Definition util/time.c:119
#define NS_PER_MS_INT
Definition time.h:156
int safe_fprintf(FILE *stream, const char *format,...)
Safe formatted output to file stream.
Definition system.c:172
void platform_clear_error_state(void)
Definition util.c:69
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Platform-safe localtime wrapper.
Definition util.c:50
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
const char * extract_project_relative_path(const char *file)
Extract relative path from an absolute path.
Definition path.c:456
Platform initialization and static synchronization helpers.
bool lifecycle_is_initialized(const lifecycle_t *lc)
Definition lifecycle.c:155
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26
Lock-free module lifecycle state machine using pure stdatomic.
#define LIFECYCLE_INIT
Static initializer for module-global lifecycle variables (no sync primitive)
Definition lifecycle.h:68
📝 Logging API with multiple log levels and terminal output control
action_args_t args
📂 Path Manipulation Utilities
int frame
Definition splash.c:99
Error context structure.
int wsa_error
Windows socket error code (if applicable, 0 otherwise)
int system_errno
System errno value (if applicable, 0 otherwise)
const char * function
Function name where error occurred (NULL in release builds)
char * context_message
Optional custom message (dynamically allocated, owned by system)
uint64_t timestamp
Timestamp when error occurred (microseconds since epoch)
bool has_system_error
True if system_errno is valid.
const char * file
Source file where error occurred (NULL in release builds)
int line
Line number where error occurred (0 in release builds)
bool has_wsa_error
True if wsa_error is valid.
backtrace_t backtrace
Stack trace (debug builds only)
asciichat_error_t code
Error code (asciichat_error_t enum value)
Error statistics structure.
uint64_t last_error_time
Timestamp of last error (microseconds since epoch)
uint64_t error_counts[256]
Count for each error code (256 possible error codes)
asciichat_error_t last_error_code
Error code of last error recorded.
uint64_t total_errors
Total number of errors recorded (sum of all error_counts)
int count
Number of frames captured.
char ** symbols
Symbolized strings (NULL until backtrace_symbolize called)
Mutex type (POSIX: pthread_mutex_t with debug tracking)
Cross-platform system functions interface for ascii-chat.
⏱️ High-precision timing utilities using sokol_time.h and uthash
🔤 String Manipulation and Shell Escaping Utilities