ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
system.c
Go to the documentation of this file.
1
7// NOTE: This file is #included by windows/system.c and posix/system.c
8// All necessary headers are already included by the parent files
9
10#include <stdatomic.h>
11#include <errno.h>
12#include <stdarg.h>
13#include <stdio.h>
14#include <ascii-chat/common.h>
15#include <ascii-chat/uthash/uthash.h> // UBSan-safe uthash wrapper
16#include <ascii-chat/log/logging.h>
17#include <ascii-chat/log/format.h>
18#include <ascii-chat/util/string.h>
19#include <time.h>
20
21// Platform-specific binary suffix
22#ifdef _WIN32
23#define BIN_SUFFIX ".exe"
24#else
25#define BIN_SUFFIX ""
26#endif
27// PATH_DELIM and PATH_ENV_SEPARATOR are now defined in system.h
28
29// ============================================================================
30// Maximum Path Length
31// ============================================================================
32
44#ifdef _WIN32
45// Windows extended-length path maximum
46// Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
47#define PLATFORM_MAX_PATH_LENGTH 32767
48#elif defined(__linux__)
49// Linux PATH_MAX (typically 4096)
50#ifndef PATH_MAX
51#define PLATFORM_MAX_PATH_LENGTH 4096
52#else
53#define PLATFORM_MAX_PATH_LENGTH PATH_MAX
54#endif
55#elif defined(__APPLE__)
56// macOS PATH_MAX (typically 1024)
57#ifndef PATH_MAX
58#define PLATFORM_MAX_PATH_LENGTH 1024
59#else
60#define PLATFORM_MAX_PATH_LENGTH PATH_MAX
61#endif
62#else
63// Fallback for unknown platforms
64#define PLATFORM_MAX_PATH_LENGTH 4096
65#endif
66
67// ============================================================================
68// Binary PATH Detection Cache
69// ============================================================================
70
118typedef struct {
120 char *bin_name;
124 UT_hash_handle hh;
126
127static bin_cache_entry_t *g_bin_path_cache = NULL; // uthash head pointer
128static rwlock_t g_cache_rwlock;
129static atomic_bool g_cache_initialized = false;
130
134static bool is_executable_file(const char *path) {
135#ifdef _WIN32
136 // On Windows, check if file exists and is readable
137 // Windows doesn't have execute permission bits like Unix
138 DWORD attrs = GetFileAttributesA(path);
139 if (attrs == INVALID_FILE_ATTRIBUTES) {
140 return false;
141 }
142 // Must be a regular file (not a directory)
143 if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
144 return false;
145 }
146 // Try to open for reading to verify access
147 HANDLE h = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
148 if (h == INVALID_HANDLE_VALUE) {
149 return false;
150 }
151 CloseHandle(h);
152 return true;
153#else
154 // On Unix, use access() to check if file exists and is executable
155 return (access(path, X_OK) == 0);
156#endif
157}
158
166#ifdef _WIN32
167static bool convert_unix_path_to_windows(const char *unix_path, char *win_path, size_t win_path_size) {
168 if (!unix_path || !win_path || win_path_size < 4) {
169 return false;
170 }
171
172 // Check for Unix-style path like /c/foo or /d/bar (Git Bash format)
173 if (unix_path[0] == '/' && unix_path[1] != '\0' && unix_path[2] == '/') {
174 // Convert /x/... to X:\...
175 char drive_letter = (char)toupper((unsigned char)unix_path[1]);
176 safe_snprintf(win_path, win_path_size, "%c:%s", drive_letter, unix_path + 2);
177 // Convert remaining forward slashes to backslashes
178 for (char *p = win_path; *p; p++) {
179 if (*p == '/')
180 *p = '\\';
181 }
182 return true;
183 }
184
185 // Already Windows-style or relative path
186 SAFE_STRNCPY(win_path, unix_path, win_path_size);
187 return false;
188}
189#endif
190
194static bool check_binary_in_path_uncached(const char *bin_name) {
195 char bin_with_suffix[512];
196 char full_path[PLATFORM_MAX_PATH_LENGTH];
197
198 // On Windows, add .exe suffix if not present
199#ifdef _WIN32
200 if (strstr(bin_name, ".exe") == NULL) {
201 safe_snprintf(bin_with_suffix, sizeof(bin_with_suffix), "%s%s", bin_name, BIN_SUFFIX);
202 } else {
203 SAFE_STRNCPY(bin_with_suffix, bin_name, sizeof(bin_with_suffix));
204 }
205#else
206 SAFE_STRNCPY(bin_with_suffix, bin_name, sizeof(bin_with_suffix));
207#endif
208
209 // Get PATH environment variable
210 const char *path_env = SAFE_GETENV("PATH");
211 if (!path_env) {
212 return false;
213 }
214
215 // Make a copy we can modify
216 size_t path_len = strlen(path_env);
217 char *path_copy = SAFE_MALLOC(path_len + 1, char *);
218 if (!path_copy) {
219 return false;
220 }
221 SAFE_STRNCPY(path_copy, path_env, path_len + 1);
222
223 // Search each directory in PATH
224 // On Windows, try both ';' (native) and ':' (Git Bash) as separators
225 bool found = false;
226 char *saveptr = NULL;
227
228#ifdef _WIN32
229 // Detect separator: Git Bash uses ':' with Unix-style paths (/c/foo)
230 // Native Windows uses ';' with Windows paths (C:\foo)
231 const char *separator = (strchr(path_copy, ';') != NULL) ? ";" : ":";
232#else
233 const char *separator = PATH_ENV_SEPARATOR;
234#endif
235
236 char *dir = platform_strtok_r(path_copy, separator, &saveptr);
237
238 while (dir != NULL) {
239 // Skip empty directory entries
240 if (dir[0] == '\0') {
241 dir = platform_strtok_r(NULL, separator, &saveptr);
242 continue;
243 }
244
245#ifdef _WIN32
246 // Convert Unix-style paths from Git Bash to Windows paths
247 char win_dir[PLATFORM_MAX_PATH_LENGTH];
248 convert_unix_path_to_windows(dir, win_dir, sizeof(win_dir));
249
250 // Build full path to binary (use backslash for Windows)
251 safe_snprintf(full_path, sizeof(full_path), "%s\\%s", win_dir, bin_with_suffix);
252#else
253 // Build full path to binary
254 safe_snprintf(full_path, sizeof(full_path), "%s%c%s", dir, PATH_DELIM, bin_with_suffix);
255#endif
256
257 // Check if file exists and is executable
258 if (is_executable_file(full_path)) {
259 found = true;
260 break;
261 }
262
263 dir = platform_strtok_r(NULL, separator, &saveptr);
264 }
265
266 SAFE_FREE(path_copy);
267 return found;
268}
269
273static void init_cache_once(void) {
274 bool expected = false;
275 if (!atomic_compare_exchange_strong(&g_cache_initialized, &expected, true)) {
276 return; // Already initialized
277 }
278
279 // Initialize uthash head pointer (NULL = empty)
280 g_bin_path_cache = NULL;
281
282 // Initialize rwlock for thread-safe access
283 if (rwlock_init(&g_cache_rwlock) != 0) {
284 log_error("Failed to initialize binary PATH cache rwlock");
285 atomic_store(&g_cache_initialized, false);
286 }
287}
288
292static void free_cache_entry(bin_cache_entry_t *entry) {
293 if (entry) {
294 if (entry->bin_name) {
295 SAFE_FREE(entry->bin_name);
296 }
297 SAFE_FREE(entry);
298 }
299}
300
305 if (!atomic_load(&g_cache_initialized)) {
306 return;
307 }
308
309 if (g_bin_path_cache) {
310 rwlock_wrlock(&g_cache_rwlock);
311
312 // Free all cached entries using uthash iteration
313 bin_cache_entry_t *entry, *tmp;
314 HASH_ITER(hh, g_bin_path_cache, entry, tmp) {
315 HASH_DELETE(hh, g_bin_path_cache, entry);
316 free_cache_entry(entry);
317 }
318
319 rwlock_wrunlock(&g_cache_rwlock);
320 rwlock_destroy(&g_cache_rwlock);
321 g_bin_path_cache = NULL;
322 }
323
324 atomic_store(&g_cache_initialized, false);
325}
326
327// ============================================================================
328// Public API
329// ============================================================================
330
331bool platform_is_binary_in_path(const char *bin_name) {
332 if (!bin_name || bin_name[0] == '\0') {
333 return false;
334 }
335
336 // Initialize cache if needed
337 init_cache_once();
338 if (!atomic_load(&g_cache_initialized)) {
339 // Cache initialization failed, check directly (this should never happen)
340 SET_ERRNO(ERROR_INVALID_STATE, "Binary PATH cache not initialized, checking directly (this should never happen)");
341 return check_binary_in_path_uncached(bin_name);
342 }
343
344 // Check cache first
345 rwlock_rdlock(&g_cache_rwlock);
346 bin_cache_entry_t *entry = NULL;
347 HASH_FIND_STR(g_bin_path_cache, bin_name, entry);
348 rwlock_rdunlock(&g_cache_rwlock);
349
350 if (entry) {
351 // Cache hit
352 log_dev("Binary '%s' %s in PATH (%s)", bin_name, colored_string(LOG_COLOR_INFO, "found"),
353 colored_string(LOG_COLOR_WARN, "cached"));
354 return entry->in_path;
355 }
356
357 // Cache miss - check PATH and cache result
358 bool found = check_binary_in_path_uncached(bin_name);
359
360 // Create new cache entry
361 entry = SAFE_MALLOC(sizeof(bin_cache_entry_t), bin_cache_entry_t *);
362 if (!entry) {
363 SET_ERRNO(ERROR_MEMORY, "Failed to allocate cache entry");
364 return found; // Return result without caching
365 }
366
367 entry->bin_name = platform_strdup(bin_name);
368 if (!entry->bin_name) {
369 SET_ERRNO(ERROR_MEMORY, "Failed to duplicate binary name");
370 SAFE_FREE(entry);
371 return found;
372 }
373
374 entry->in_path = found;
375
376 // Add to cache using uthash
377 rwlock_wrlock(&g_cache_rwlock);
378 HASH_ADD_KEYPTR(hh, g_bin_path_cache, entry->bin_name, strlen(entry->bin_name), entry);
379 rwlock_wrunlock(&g_cache_rwlock);
380
381 log_dev("Binary '%s' %s in PATH", bin_name,
382 colored_string(found ? LOG_COLOR_INFO : LOG_COLOR_ERROR, found ? "found" : "NOT found"));
383
384 return found;
385}
386
393bool platform_get_executable_path(char *exe_path, size_t path_size) {
394 if (!exe_path || path_size == 0) {
395 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: exe_path=%p, path_size=%zu", (void *)exe_path, path_size);
396 return false;
397 }
398
399#ifdef _WIN32
400 DWORD len = GetModuleFileNameA(NULL, exe_path, (DWORD)path_size);
401 if (len == 0) {
402 SET_ERRNO_SYS(ERROR_INVALID_STATE, "GetModuleFileNameA failed: error code %lu", GetLastError());
403 return false;
404 }
405 if (len >= path_size) {
406 SET_ERRNO(ERROR_BUFFER_OVERFLOW,
407 "Executable path exceeds buffer size (path length >= %zu bytes, buffer size = %zu bytes)", (size_t)len,
408 path_size);
409 return false;
410 }
411 return true;
412
413#elif defined(__linux__)
414 ssize_t len = readlink("/proc/self/exe", exe_path, path_size - 1);
415 if (len < 0) {
416 SET_ERRNO_SYS(ERROR_INVALID_STATE, "readlink(\"/proc/self/exe\") failed: %s", SAFE_STRERROR(errno));
417 return false;
418 }
419 if ((size_t)len >= path_size - 1) {
420 SET_ERRNO(ERROR_BUFFER_OVERFLOW,
421 "Executable path exceeds buffer size (path length >= %zu bytes, buffer size = %zu bytes)", (size_t)len,
422 path_size);
423 return false;
424 }
425 exe_path[len] = '\0';
426 return true;
427
428#elif defined(__APPLE__)
429 uint32_t bufsize = (uint32_t)path_size;
430 int result = _NSGetExecutablePath(exe_path, &bufsize);
431 if (result != 0) {
432 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "_NSGetExecutablePath failed: path requires %u bytes, buffer size = %zu bytes",
433 bufsize, path_size);
434 return false;
435 }
436 return true;
437
438#else
439 SET_ERRNO(ERROR_GENERAL, "Unsupported platform - cannot get executable path");
440 return false;
441#endif
442}
443
456int safe_snprintf(char *buffer, size_t buffer_size, const char *format, ...) {
457 if (!buffer || !format || buffer_size == 0) {
458 return -1;
459 }
460
461 va_list args;
462 va_start(args, format);
463
464 /* Delegate to platform_snprintf for actual formatting */
465 int ret = platform_vsnprintf(buffer, buffer_size, format, args);
466
467 va_end(args);
468 return ret;
469}
470
480int safe_fprintf(FILE *stream, const char *format, ...) {
481 if (!stream || !format) {
482 return -1;
483 }
484
485 va_list args;
486 va_start(args, format);
487 int ret = vfprintf(stream, format, args);
488 va_end(args);
489
490 return ret;
491}
492
507int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap) {
508 if (!format) {
509 return -1;
510 }
511
512 // Allow NULL buffer with size 0 for size calculation (standard vsnprintf behavior)
513 if (!buffer && buffer_size > 0) {
514 return -1; // Non-NULL buffer required when buffer_size > 0
515 }
516
517 /* Delegate to platform_vsnprintf for actual formatting */
518 return platform_vsnprintf(buffer, buffer_size, format, ap);
519}
520
535void platform_print_backtrace_symbols(const char *label, char **symbols, int count, int skip_frames, int max_frames,
536 backtrace_frame_filter_t filter) {
537 if (!symbols || count <= 0) {
538 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: symbols=%p, count=%d", symbols, count);
539 return;
540 }
541
542 // Calculate frame limits
543 int start = skip_frames;
544 int end = count;
545 if (max_frames > 0 && (start + max_frames) < end) {
546 end = start + max_frames;
547 }
548
549 // Build backtrace in two versions: colored for terminal, plain for log file
550 char colored_buffer[16384] = {0};
551 char plain_buffer[16384] = {0};
552 int colored_offset = 0;
553 int plain_offset = 0;
554
555 // Format log header using logging system's template
556 char log_header_buf[512] = {0};
557 time_t now = time(NULL);
558 struct tm *tm_info = localtime(&now);
559 char timestamp[32];
560 strftime(timestamp, sizeof(timestamp), "%H:%M:%S", tm_info);
561
562 thread_id_t tid = asciichat_thread_self();
563 uint64_t tid_val = (uintptr_t)tid;
564
565 // Get current time in nanoseconds for template formatting
566 uint64_t time_ns = platform_get_monotonic_time_us() * 1000ULL;
567
568 // Try to format header using the logging system's template with color
569 log_template_t *format = log_get_template();
570 if (format) {
571 // Color the label with WARN color for terminal output
572 const char *colored_label_ptr = colored_string(LOG_COLOR_WARN, label);
573 char colored_label_buf[256];
574 strncpy(colored_label_buf, colored_label_ptr, sizeof(colored_label_buf) - 1);
575 colored_label_buf[sizeof(colored_label_buf) - 1] = '\0';
576
577 int len = log_template_apply(format, log_header_buf, sizeof(log_header_buf), LOG_WARN, timestamp, __FILE__,
578 __LINE__, __func__, tid_val, colored_label_buf, true, time_ns);
579 if (len > 0) {
580 // Successfully formatted with logging template
581 colored_offset += safe_snprintf(colored_buffer + colored_offset, sizeof(colored_buffer) - (size_t)colored_offset,
582 "%s\n", log_header_buf);
583 } else {
584 // Fallback: manual formatting if template fails
585 safe_snprintf(log_header_buf, sizeof(log_header_buf), "[%s] [WARN] [tid:%llu] %s: %s", timestamp, tid_val,
586 __func__, label);
587 const char *colored_header_ptr = colored_string(LOG_COLOR_WARN, log_header_buf);
588 char colored_header_buf[512];
589 strncpy(colored_header_buf, colored_header_ptr, sizeof(colored_header_buf) - 1);
590 colored_header_buf[sizeof(colored_header_buf) - 1] = '\0';
591 colored_offset += safe_snprintf(colored_buffer + colored_offset, sizeof(colored_buffer) - (size_t)colored_offset,
592 "%s\n", colored_header_buf);
593 }
594 } else {
595 // Fallback: manual formatting if no template available
596 safe_snprintf(log_header_buf, sizeof(log_header_buf), "[%s] [WARN] [tid:%llu] %s: %s", timestamp, tid_val, __func__,
597 label);
598 const char *colored_header_ptr = colored_string(LOG_COLOR_WARN, log_header_buf);
599 char colored_header_buf[512];
600 strncpy(colored_header_buf, colored_header_ptr, sizeof(colored_header_buf) - 1);
601 colored_header_buf[sizeof(colored_header_buf) - 1] = '\0';
602 colored_offset += safe_snprintf(colored_buffer + colored_offset, sizeof(colored_buffer) - (size_t)colored_offset,
603 "%s\n", colored_header_buf);
604 }
605
606 // Add plain label header for log file
607 plain_offset +=
608 safe_snprintf(plain_buffer + plain_offset, sizeof(plain_buffer) - (size_t)plain_offset, "%s\n", label);
609
610 // Build backtrace frames with colored output for terminal, plain for log
611 int frame_num = 0;
612 for (int i = start; i < end && colored_offset < (int)sizeof(colored_buffer) - 512; i++) {
613 const char *symbol = symbols[i] ? symbols[i] : "???";
614
615 // Skip frame if filter says to
616 if (filter && filter(symbol)) {
617 continue;
618 }
619
620 // Build frame number string
621 char frame_num_str[16];
622 safe_snprintf(frame_num_str, sizeof(frame_num_str), "%d", frame_num);
623
624 // Get colored frame number - copy to temp buffer to avoid rotating buffer issues
625 const char *colored_num_ptr = colored_string(LOG_COLOR_GREY, frame_num_str);
626 char colored_num_buf[256];
627 strncpy(colored_num_buf, colored_num_ptr, sizeof(colored_num_buf) - 1);
628 colored_num_buf[sizeof(colored_num_buf) - 1] = '\0';
629
630 // Parse symbol to extract parts for selective coloring
631 // Format: "[binary_name] function_name() (file:line)"
632 char colored_symbol[2048] = {0};
633 const char *s = symbol;
634 int colored_sym_offset = 0;
635
636 // Color binary name between brackets
637 if (*s == '[') {
638 colored_sym_offset +=
639 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, "[");
640 s++;
641 // Find closing bracket
642 const char *bracket_end = strchr(s, ']');
643 if (bracket_end) {
644 int bin_len = bracket_end - s;
645 char bin_name[512];
646 strncpy(bin_name, s, bin_len);
647 bin_name[bin_len] = '\0';
648 const char *colored_bin_ptr = colored_string(LOG_COLOR_DEBUG, bin_name);
649 char colored_bin_buf[512];
650 strncpy(colored_bin_buf, colored_bin_ptr, sizeof(colored_bin_buf) - 1);
651 colored_bin_buf[sizeof(colored_bin_buf) - 1] = '\0';
652 colored_sym_offset += safe_snprintf(colored_symbol + colored_sym_offset,
653 sizeof(colored_symbol) - colored_sym_offset, "%s", colored_bin_buf);
654 colored_sym_offset +=
655 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, "] ");
656 s = bracket_end + 1;
657 }
658 }
659
660 // Skip leading spaces after bracket
661 while (*s && *s == ' ')
662 s++;
663
664 // Parse: could be "function() (file:line)" or "file:line (unresolved)"
665 const char *paren_start = strchr(s, '(');
666
667 // Detect format: if there's a colon before the first paren, it's "file:line (description)"
668 const char *colon_pos = strchr(s, ':');
669 if (colon_pos && paren_start && colon_pos < paren_start) {
670 // Format: "file:line (unresolved function)" - rearrange to "(unresolved) (file:line)"
671 // Extract file:line part (trim trailing spaces)
672 int file_len = paren_start - s;
673 while (file_len > 0 && s[file_len - 1] == ' ')
674 file_len--;
675 char file_part[512];
676 strncpy(file_part, s, file_len);
677 file_part[file_len] = '\0';
678
679 // Extract description content (without parens)
680 const char *paren_end = strchr(paren_start, ')');
681 int desc_content_len = paren_end - paren_start - 1; // -1 to skip opening paren
682 char desc_content[512];
683 strncpy(desc_content, paren_start + 1, desc_content_len); // +1 to skip opening paren
684 desc_content[desc_content_len] = '\0';
685
686 const char *colored_desc_ptr = colored_string(LOG_COLOR_ERROR, desc_content);
687 char colored_desc_buf[512];
688 strncpy(colored_desc_buf, colored_desc_ptr, sizeof(colored_desc_buf) - 1);
689 colored_desc_buf[sizeof(colored_desc_buf) - 1] = '\0';
690 colored_sym_offset += safe_snprintf(colored_symbol + colored_sym_offset,
691 sizeof(colored_symbol) - colored_sym_offset, "(%s)", colored_desc_buf);
692
693 // Now color file:line in parens
694 // Skip leading spaces in file_part
695 const char *file_start = file_part;
696 while (*file_start && *file_start == ' ')
697 file_start++;
698
699 char *file_colon = strchr(file_start, ':');
700 if (file_colon) {
701 int filename_len = file_colon - file_start;
702 char filename[512];
703 strncpy(filename, file_start, filename_len);
704 filename[filename_len] = '\0';
705
706 const char *colored_file_ptr = colored_string(LOG_COLOR_DEBUG, filename);
707 char colored_file_buf[512];
708 strncpy(colored_file_buf, colored_file_ptr, sizeof(colored_file_buf) - 1);
709 colored_file_buf[sizeof(colored_file_buf) - 1] = '\0';
710
711 const char *line_num = file_colon + 1;
712 const char *colored_line_ptr = colored_string(LOG_COLOR_GREY, line_num);
713 char colored_line_buf[512];
714 strncpy(colored_line_buf, colored_line_ptr, sizeof(colored_line_buf) - 1);
715 colored_line_buf[sizeof(colored_line_buf) - 1] = '\0';
716
717 colored_sym_offset +=
718 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, " (%s:%s)",
719 colored_file_buf, colored_line_buf);
720 }
721 } else if (paren_start) {
722 // Format: "function() (file:line)"
723 int func_len = paren_start - s;
724 char func_name[512];
725 strncpy(func_name, s, func_len);
726 func_name[func_len] = '\0';
727
728 const char *colored_func_ptr = colored_string(LOG_COLOR_DEV, func_name);
729 char colored_func_buf[512];
730 strncpy(colored_func_buf, colored_func_ptr, sizeof(colored_func_buf) - 1);
731 colored_func_buf[sizeof(colored_func_buf) - 1] = '\0';
732 colored_sym_offset += safe_snprintf(colored_symbol + colored_sym_offset,
733 sizeof(colored_symbol) - colored_sym_offset, "%s()", colored_func_buf);
734
735 // Find file:line in second set of parens
736 s = paren_start + 1;
737 while (*s && *s != '(')
738 s++;
739
740 if (*s == '(') {
741 const char *file_paren_end = strchr(s, ')');
742 if (file_paren_end) {
743 colored_sym_offset +=
744 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, " (");
745 int file_len = file_paren_end - s - 1;
746 char file_part[512];
747 strncpy(file_part, s + 1, file_len);
748 file_part[file_len] = '\0';
749
750 // Skip leading spaces in file_part
751 const char *file_start = file_part;
752 while (*file_start && *file_start == ' ')
753 file_start++;
754
755 char *colon_pos = strchr(file_start, ':');
756 if (colon_pos) {
757 int filename_len = colon_pos - file_start;
758 char filename[512];
759 strncpy(filename, file_start, filename_len);
760 filename[filename_len] = '\0';
761
762 const char *colored_file_ptr = colored_string(LOG_COLOR_DEBUG, filename);
763 char colored_file_buf[512];
764 strncpy(colored_file_buf, colored_file_ptr, sizeof(colored_file_buf) - 1);
765 colored_file_buf[sizeof(colored_file_buf) - 1] = '\0';
766
767 const char *line_num = colon_pos + 1;
768 const char *colored_line_ptr = colored_string(LOG_COLOR_GREY, line_num);
769 char colored_line_buf[512];
770 strncpy(colored_line_buf, colored_line_ptr, sizeof(colored_line_buf) - 1);
771 colored_line_buf[sizeof(colored_line_buf) - 1] = '\0';
772
773 colored_sym_offset +=
774 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, "%s:%s",
775 colored_file_buf, colored_line_buf);
776 }
777 colored_sym_offset +=
778 safe_snprintf(colored_symbol + colored_sym_offset, sizeof(colored_symbol) - colored_sym_offset, ")");
779 }
780 }
781 } else {
782 // No parens, likely a hex address - color with FATAL
783 const char *colored_addr_ptr = colored_string(LOG_COLOR_FATAL, s);
784 char colored_addr_buf[512];
785 strncpy(colored_addr_buf, colored_addr_ptr, sizeof(colored_addr_buf) - 1);
786 colored_addr_buf[sizeof(colored_addr_buf) - 1] = '\0';
787 colored_sym_offset += safe_snprintf(colored_symbol + colored_sym_offset,
788 sizeof(colored_symbol) - colored_sym_offset, "%s", colored_addr_buf);
789 }
790
791 // Format colored buffer: " [colored_num] colored_symbol\n"
792 colored_offset += safe_snprintf(colored_buffer + colored_offset, sizeof(colored_buffer) - (size_t)colored_offset,
793 " [%s] %s\n", colored_num_buf, colored_symbol);
794
795 // Format plain buffer: " [num] symbol\n"
796 plain_offset += safe_snprintf(plain_buffer + plain_offset, sizeof(plain_buffer) - (size_t)plain_offset,
797 " [%d] %s\n", frame_num, symbol);
798 frame_num++;
799 }
800
801 // TODO: Investigate why log_warn() can't be used here. Currently we bypass the logging
802 // system to preserve ANSI color codes, but this means we lose the normal log formatting
803 // (timestamps, log level, etc). Ideally log_warn() should accept a flag to preserve codes.
804 fprintf(stderr, "%s", colored_buffer);
805
806 // Write plain version to log file only (skip stderr since we already printed colored version)
807 log_file_msg("%s", plain_buffer);
808}
int buffer_size
Size of circular buffer.
Definition grep.c:84
int log_template_apply(const log_template_t *format, char *buf, size_t buf_size, log_level_t level, const char *timestamp, const char *file, int line, const char *func, uint64_t tid, const char *message, bool use_colors, uint64_t time_nanoseconds)
Definition log/format.c:455
void log_file_msg(const char *fmt,...)
void * log_get_template(void)
Get the current log format template.
action_args_t args
char * platform_strdup(const char *s)
int platform_vsnprintf(char *str, size_t size, const char *format, va_list ap)
uint64_t platform_get_monotonic_time_us(void)
Binary PATH cache entry structure for binary detection caching.
Definition system.c:118
bool in_path
Whether binary was found in PATH (true = found, false = not found)
Definition system.c:122
char * bin_name
Binary name string (allocated, owned by cache) - also used as uthash key.
Definition system.c:120
UT_hash_handle hh
uthash handle
Definition system.c:124
int safe_fprintf(FILE *stream, const char *format,...)
Safe formatted output to file stream.
Definition system.c:480
bool platform_get_executable_path(char *exe_path, size_t path_size)
Get the path to the current executable.
Definition system.c:393
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
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
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Safe formatted string printing with va_list.
Definition system.c:507
bool platform_is_binary_in_path(const char *bin_name)
Definition system.c:331
#define PLATFORM_MAX_PATH_LENGTH
Definition system.c:64
#define BIN_SUFFIX
Definition system.c:25
void platform_cleanup_binary_path_cache(void)
Cleanup the binary PATH cache.
Definition system.c:304
asciichat_thread_t asciichat_thread_self(void)
Definition threading.c:54
int rwlock_init(rwlock_t *rwlock)
Definition threading.c:63
const char * colored_string(log_color_t color, const char *text)