ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
main.c
Go to the documentation of this file.
1
23#include <stdio.h>
24#include <unistd.h>
25#include <stdlib.h>
26#include <string.h>
27#include <stdbool.h>
28#include <signal.h>
29
30// Mode-specific entry points
31#include "server/main.h"
32#include "client/main.h"
33#include "mirror/main.h"
35#include "discovery/main.h"
37
38// Global exit API (exported in main.h)
39#include "main.h"
40
41// Application callbacks for library integration
43
44// Keyboard help UI
46
47// Utilities
51
52// Common headers for version info and initialization
53#include <ascii-chat/atomic.h>
54#include <ascii-chat/common.h>
55#include <ascii-chat/version.h>
66#include <ascii-chat/log/log.h>
67#include <ascii-chat/log/json.h>
68#include <ascii-chat/log/grep.h>
77
78#ifndef NDEBUG
85#endif
86
87#ifndef _WIN32
88#include <signal.h>
89#endif
90
91/* ============================================================================
92 * Constants and Configuration
93 * ============================================================================ */
94
95#define APP_NAME "ascii-chat"
96#define VERSION ASCII_CHAT_VERSION_FULL
97
98/* ============================================================================
99 * Default Log File Path Determination
100 * ============================================================================ */
101
108static const char *get_mode_log_filename(asciichat_mode_t mode) {
109 switch (mode) {
110 case MODE_SERVER:
111 return "server.log";
112 case MODE_CLIENT:
113 return "client.log";
114 case MODE_MIRROR:
115 return "mirror.log";
117 return "acds.log";
118 case MODE_DISCOVERY:
119 return "discovery.log";
120 case MODE_INVALID:
121 default:
122 return "ascii-chat.log";
123 }
124}
125
136static void generate_default_log_path(asciichat_mode_t mode, char *buf, size_t buf_size) {
137 if (!buf || buf_size == 0) {
138 return;
139 }
140
141 // Get mode-specific log filename
142 const char *log_filename = get_mode_log_filename(mode);
143
144#ifndef NDEBUG
145 // Debug mode: use current working directory
146 SAFE_STRNCPY(buf, log_filename, buf_size - 1);
147#else
148 // Non-debug mode: use system temp directory
149#ifdef _WIN32
150 const char *temp_dir = SAFE_GETENV("TEMP");
151 if (!temp_dir) {
152 temp_dir = SAFE_GETENV("TMP");
153 }
154 if (!temp_dir) {
155 temp_dir = SAFE_GETENV("USERPROFILE");
156 }
157 if (!temp_dir) {
158 temp_dir = ".";
159 }
160 safe_snprintf(buf, buf_size, "%s\\ascii-chat\\%s", temp_dir, log_filename);
161#else
162 safe_snprintf(buf, buf_size, "/tmp/ascii-chat/%s", log_filename);
163#endif
164 // Create directory if it doesn't exist (non-debug mode only)
165 char dir_path[PATH_MAX];
166 SAFE_STRNCPY(dir_path, buf, sizeof(dir_path) - 1);
167 char *last_slash = strrchr(dir_path, '/');
168#ifdef _WIN32
169 char *last_backslash = strrchr(dir_path, '\\');
170 if (last_backslash && (!last_slash || last_backslash > last_slash)) {
171 last_slash = last_backslash;
172 }
173#endif
174 if (last_slash) {
175 *last_slash = '\0';
176 platform_mkdir_recursive(dir_path, 0700);
177 }
178#endif
179}
180
181/* ============================================================================
182 * Global Application Exit State (Centralized Signal Handling)
183 * ============================================================================ */
184
187
188// Register with descriptive name for debug output
189#ifndef NDEBUG
190#define REGISTER_GLOBAL_ATOMICS() \
191 do { \
192 static bool _global_atomics_registered = false; \
193 if (!_global_atomics_registered) { \
194 NAMED_REGISTER_ATOMIC(&g_should_exit, "application_exit_flag", NULL); \
195 _global_atomics_registered = true; \
196 } \
197 } while (0)
198#else
199#define REGISTER_GLOBAL_ATOMICS() \
200 do { \
201 } while (0)
202#endif
203
205static void (*g_interrupt_callback)(void) = NULL;
206
207/* ============================================================================
208 * Global Exit API Implementation (from main.h)
209 * ============================================================================ */
210
211bool should_exit(void) {
213}
214
215void signal_exit(void) {
216 // Note: This function may be called from a signal handler context where
217 // other threads may hold mutex locks. We avoid log_console() here to prevent
218 // deadlock (render loop holds terminal lock, signal handler can't acquire it).
219 // The shutdown will be logged by normal thread context later.
221 void (*cb)(void) = g_interrupt_callback;
222 if (cb) {
223 cb();
224 }
225}
226
227void set_interrupt_callback(void (*cb)(void)) {
228 g_interrupt_callback = cb;
229}
230
231/* ============================================================================
232 * Signal Handlers
233 * ============================================================================ */
234
235#ifndef _WIN32
240static void handle_sigterm(int sig) {
241 // Write to stderr about which signal was received for debugging
242 const char *sig_name = (sig == SIGTERM) ? "SIGTERM"
243 : (sig == SIGINT) ? "SIGINT"
244 : (sig == SIGHUP) ? "SIGHUP"
245 : "UNKNOWN";
246 char log_out[64];
247 safe_snprintf(log_out, sizeof(log_out), "Signal '%s' received", sig_name);
248
249 // If Ctrl+C (SIGINT) and help screen is active, close it instead of exiting
250 if (sig == SIGINT && keyboard_help_is_active_global()) {
251 log_console(LOG_INFO, "Ctrl+C received - closing help screen");
253 return; // Help is now closed, don't exit
254 }
255
256 if (sig == SIGTERM || sig == SIGHUP || sig == SIGINT) {
257 safe_snprintf(log_out, sizeof(log_out), "%s - shutting down", log_out);
258 }
259 // log_console() is async-signal-safe - uses atomic ops and platform_write_all()
260 log_console(LOG_INFO, log_out);
261
262#ifndef NDEBUG
263 // Trigger debug sync state printing on shutdown (async-signal-safe)
265#endif
266
267 // Call signal_exit() to set flag AND interrupt blocking socket operations
268 signal_exit();
269}
270#endif
271
277static bool console_ctrl_handler(console_ctrl_event_t event) {
278 if (event != CONSOLE_CTRL_C && event != CONSOLE_CTRL_BREAK && event != CONSOLE_CLOSE) {
279 return false;
280 }
281
282 // Check if keyboard help screen is active (defined in session/display.c)
283 // Uses atomic load only - safe from signal context
285 // Help screen is active - close it instead of exiting
286 log_console(LOG_INFO, "Ctrl+C received - closing help screen");
288 return true; // Handled - don't exit
289 }
290
291 log_console(LOG_INFO, "Ctrl+C received - shutting down");
292
293#ifndef NDEBUG
294 // Trigger debug sync state printing on shutdown (async-signal-safe)
296#endif
297
298 // Double Ctrl+C forces immediate exit
299 static atomic_t ctrl_c_count = {0};
300 static bool ctrl_c_count_registered = false;
301 if (!ctrl_c_count_registered) {
302 NAMED_REGISTER_ATOMIC(&ctrl_c_count, "ctrl_c_interrupt_count", NULL);
303 ctrl_c_count_registered = true;
304 }
305 if (atomic_fetch_add_int(&ctrl_c_count, 1) + 1 > 1) {
307 }
308
309 signal_exit();
310 return true;
311}
312
313/* ============================================================================
314 * Debug Signal Handlers (all modes)
315 * ============================================================================ */
316
317#ifndef NDEBUG
318#ifndef _WIN32
323static void common_handle_sigusr1(int sig) {
324 (void)sig;
326}
327
332static void common_handle_sigusr2(int sig) {
333 (void)sig;
334 // Log to stderr directly since we're in signal context
335 // (avoid logging system which uses mutexes)
336 write(STDERR_FILENO, "[SIGUSR2 received]\n", 19);
338}
339#endif
340#endif
341
347 platform_set_console_ctrl_handler(console_ctrl_handler);
348
349#ifndef _WIN32
350 platform_signal_handler_t handlers[] = {
351 {SIGINT, handle_sigterm},
352 {SIGTERM, handle_sigterm},
353 {SIGPIPE, SIG_IGN},
354 };
356 log_debug("Signal handlers registered");
357#endif
358}
359
360/* ============================================================================
361 * Mode Registration Table
362 * ============================================================================ */
363
364typedef int (*mode_entry_point_t)(void);
365
366typedef struct {
367 const char *name;
368 const char *description;
371
372static const mode_descriptor_t g_mode_table[] = {
373 {
374 .name = "server",
375 .description = "Run as multi-client video chat server",
376 .entry_point = server_main,
377 },
378 {
379 .name = "client",
380 .description = "Run as video chat client (connect to server)",
381 .entry_point = client_main,
382 },
383 {
384 .name = "mirror",
385 .description = "View local webcam as ASCII art (no server)",
386 .entry_point = mirror_main,
387 },
388 {
389 .name = "discovery-service",
390 .description = "Secure P2P session signalling",
391 .entry_point = acds_main,
392 },
393 {.name = NULL, .description = NULL, .entry_point = NULL},
394};
395
396/* ============================================================================
397 * Help and Usage Functions
398 * ============================================================================ */
399
400static void print_usage(asciichat_mode_t mode) {
401 // Use the new usage() function from common.c which handles mode-specific help properly
402 usage(stdout, mode);
403}
404
405// Discovery mode is implicit (no keyword) so it has a separate descriptor
406static const mode_descriptor_t g_discovery_mode = {
407 .name = "discovery",
408 .description = "P2P session with automatic host negotiation",
409 .entry_point = discovery_main,
410};
411
428static const mode_descriptor_t *g_mode_lookup[] = {
429 [MODE_SERVER] = &g_mode_table[0], // server_main
430 [MODE_CLIENT] = &g_mode_table[1], // client_main
431 [MODE_MIRROR] = &g_mode_table[2], // mirror_main
432 [MODE_DISCOVERY_SERVICE] = &g_mode_table[3], // acds_main
433 [MODE_DISCOVERY] = &g_discovery_mode, // discovery_main (implicit mode)
434 [MODE_INVALID] = NULL, // Invalid: no descriptor
435};
436
446static const mode_descriptor_t *find_mode(asciichat_mode_t mode) {
447 // Bounds check: ensure mode is within valid range (0 to MODE_INVALID)
448 if (mode < MODE_SERVER || mode > MODE_INVALID) {
449 return NULL;
450 }
451 return g_mode_lookup[mode];
452}
453
454/* ============================================================================
455 * Helper Functions for Post-Options Processing
456 * ============================================================================ */
457
464static void on_exit_show_cursor(void) {
465 (void)terminal_cursor_show();
466}
467
468/* ============================================================================
469 * Main Entry Point
470 * ============================================================================ */
471
472int main(int argc, char *argv[]) {
473 // Set global argc/argv for early argv inspection (e.g., in terminal.c)
474 g_argc = argc;
475 g_argv = argv;
476
477 // Validate basic argument structure
478 if (argc < 1 || argv == NULL || argv[0] == NULL) {
479 return 1;
480 }
481
482 // Show cursor early in case a previous session crashed with it hidden
483 (void)terminal_cursor_show();
484
485 // Initialize the named registry for debugging (allows --debug-state to show registered synchronization primitives)
486#ifndef NDEBUG
487 named_init();
488 // Initialize atomic operations debug tracking
490 // Register global atomics with descriptive names for debug sync state monitoring
492 // Static atomics are now auto-registered via ATOMIC_INIT_AUTO macro at definition time
493 // Register all packet types from the packet_type_t enum
495#endif
496
497 // Register the main thread IMMEDIATELY after named_init() to ensure it's available for all subsequent allocations
498#ifndef NDEBUG
499#ifdef _WIN32
500 NAMED_REGISTER_THREAD(GetCurrentThread(), "main", NULL);
501#else
503#endif
504 // Also save main thread ID for memory reporting (must be very early)
506#endif
507
508 // VERY FIRST: Scan for --color BEFORE ANY logging initialization
509 // This sets global flags that persist through cleanup, enabling --color to force colors
510 for (int i = 1; i < argc; i++) {
511 if (strcmp(argv[i], "--color") == 0 || strcmp(argv[i], "--color=true") == 0) {
512 g_color_flag_passed = true;
513 g_color_flag_value = true;
514 break;
515 } else if (strncmp(argv[i], "--color=", 8) == 0) {
516 g_color_flag_passed = true;
517 g_color_flag_value = strcmp(argv[i], "--color=false") != 0;
518 break;
519 }
520 }
521
522 // SECOND: Scan for --grep BEFORE ANY logging initialization
523 // This ensures ALL logs (including from shared_init) can be filtered
524 // Supports multiple --grep patterns (ORed together)
525 for (int i = 1; i < argc - 1; i++) {
526 if (strcmp(argv[i], "--grep") == 0) {
527 const char *pattern = argv[i + 1];
528 asciichat_error_t filter_result = grep_init(pattern);
529 if (filter_result != ASCIICHAT_OK) {
530 log_error("Invalid --grep pattern or invalid flags: \"%s\" - use /pattern/flags format (e.g., \"/query/ig\" or "
531 "\"/literal/F\")",
532 pattern);
533 return 1;
534 }
535 i++; // Skip the pattern argument
536 }
537 }
538
539 // Detect terminal capabilities early so colored help output works
540 // Logging will be initialized by asciichat_shared_init() before options_init()
543 caps = apply_color_mode_override(caps);
544
545 // Warn if Release build was built from dirty working tree
546#if ASCII_CHAT_GIT_IS_DIRTY
547 if (strcmp(ASCII_CHAT_BUILD_TYPE, "Release") == 0) {
548 // Dirty git warning will be logged after logging initialization
549 }
550#endif
551
552 // Load color scheme early (from config files and CLI) before logging initialization
553 // This allows logging to use the correct colors from the start
555
556 // Parse --color-scheme from argv early to set logging colors for help output
557 const char *colorscheme_name = "pastel"; // default
558 for (int i = 1; i < argc - 1; i++) {
559 if (strcmp(argv[i], "--color-scheme") == 0) {
560 colorscheme_name = argv[i + 1];
561 break;
562 }
563 }
564
565 // Load and apply colorscheme to logging BEFORE options_init() so help gets colors
566 color_scheme_t scheme;
567 if (colorscheme_load_builtin(colorscheme_name, &scheme) == ASCIICHAT_OK) {
568 log_set_color_scheme(&scheme);
569 } else {
570 // Fall back to default pastel if scheme not found
571 if (colorscheme_load_builtin("pastel", &scheme) == ASCIICHAT_OK) {
572 log_set_color_scheme(&scheme);
573 }
574 }
575
576 // Initialize logging colors so they're ready for help output
578
579 // EARLY PARSE: Find the mode position (first positional argument)
580 // Binary-level options must appear BEFORE the mode
581 int mode_position = -1;
582 for (int i = 1; i < argc; i++) {
583 if (argv[i][0] != '-') {
584 // Found a positional argument - this is the mode or session string
585 mode_position = i;
586 break;
587 }
588
589 // Skip option and its argument if needed
590 // Check if this is an option that takes a required argument
591 const char *arg = argv[i];
592 const char *opt_name = arg;
593 if (arg[0] == '-') {
594 opt_name = arg + (arg[1] == '-' ? 2 : 1);
595 }
596
597 // Handle --option=value format
598 if (strchr(opt_name, '=')) {
599 continue; // Value is part of this arg, no need to skip next
600 }
601
602 // Options that take required arguments
603 if (strcmp(arg, "--log-file") == 0 || strcmp(arg, "-L") == 0 || strcmp(arg, "--log-level") == 0 ||
604 strcmp(arg, "--config") == 0 || strcmp(arg, "--color-scheme") == 0 || strcmp(arg, "--log-template") == 0) {
605 if (i + 1 < argc) {
606 i++; // Skip the argument value
607 }
608 }
609 }
610
611 // EARLY PARSE: Determine mode from argv to know if this is client-like mode
612 // The first positional argument (after options) is the mode: client, server, mirror, discovery, acds
613 bool is_client_like_mode = false;
614 if (mode_position > 0) {
615 const char *first_arg = argv[mode_position];
616 if (strcmp(first_arg, "client") == 0 || strcmp(first_arg, "mirror") == 0 || strcmp(first_arg, "discovery") == 0) {
617 is_client_like_mode = true;
618 }
619 }
620
621 // EARLY PARSE: Extract log file from argv (--log-file or -L)
622 // Must appear BEFORE the mode
623 // Generate default log path based on build type and mode
624 char default_log_path[PATH_MAX];
625 asciichat_mode_t detected_mode = MODE_INVALID;
626 if (mode_position > 0) {
627 const char *mode_str = argv[mode_position];
628 // Convert mode string to enum
629 if (strcmp(mode_str, "server") == 0) {
630 detected_mode = MODE_SERVER;
631 } else if (strcmp(mode_str, "client") == 0) {
632 detected_mode = MODE_CLIENT;
633 } else if (strcmp(mode_str, "mirror") == 0) {
634 detected_mode = MODE_MIRROR;
635 } else if (strcmp(mode_str, "acds") == 0 || strcmp(mode_str, "discovery-service") == 0) {
636 detected_mode = MODE_DISCOVERY_SERVICE;
637 } else if (strcmp(mode_str, "discovery") == 0) {
638 detected_mode = MODE_DISCOVERY;
639 }
640 }
641 generate_default_log_path(detected_mode, default_log_path, sizeof(default_log_path));
642
643 const char *log_file = default_log_path;
644 int max_search = (mode_position > 0) ? mode_position : argc;
645 for (int i = 1; i < max_search - 1; i++) {
646 if ((strcmp(argv[i], "--log-file") == 0 || strcmp(argv[i], "-L") == 0)) {
647 log_file = argv[i + 1];
648 break;
649 }
650 }
651
652 // EARLY PARSE: Check for --json (JSON logging format)
653 // If JSON format is enabled, we'll use the JSON filename during early init
654 // --json MUST appear BEFORE the mode
655 bool early_json_format = false;
656 if (mode_position > 0) {
657 for (int i = 1; i < mode_position; i++) {
658 if (strcmp(argv[i], "--json") == 0) {
659 early_json_format = true;
660 break;
661 }
662 }
663 } else {
664 // If no mode found, search entire argv
665 for (int i = 1; i < argc; i++) {
666 if (strcmp(argv[i], "--json") == 0) {
667 early_json_format = true;
668 break;
669 }
670 }
671 }
672
673 // EARLY PARSE: Extract log template from argv (--log-template)
674 // Note: This is for format templates, different from --log-format which is the output format
675 // Binary-level options must appear BEFORE the mode
676 const char *early_log_template = NULL;
677 bool early_log_template_console_only = false;
678 for (int i = 1; i < max_search - 1; i++) {
679 if (strcmp(argv[i], "--log-template") == 0) {
680 early_log_template = argv[i + 1];
681 break;
682 }
683 }
684 for (int i = 1; i < max_search; i++) {
685 if (strcmp(argv[i], "--log-format-console-only") == 0) {
686 early_log_template_console_only = true;
687 break;
688 }
689 }
690
691 // Documentation actions should not be preceded by startup diagnostics.
692 bool early_quiet_output = false;
693 for (int i = 1; i < argc; i++) {
694 if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--version") == 0 ||
695 strcmp(argv[i], "-V") == 0) {
696 early_quiet_output = true;
697 break;
698 }
699 }
700
701 // Initialize shared subsystems BEFORE options_init()
702 // This ensures options parsing can use properly configured logging with colors
703 // If JSON format is requested, don't write text logs to file
704 // All logs will be JSON formatted later once options_init() runs
705 const char *early_log_file = early_json_format ? NULL : log_file;
706 asciichat_error_t init_result = asciichat_shared_init(early_log_file, is_client_like_mode, early_quiet_output);
707 if (init_result != ASCIICHAT_OK) {
708 return init_result;
709 }
710
711 // Route logs to stderr if stdout is piped (MUST happen early, before options_init logs)
712 // This keeps stdout clean for data output (e.g., --snapshot mode piped to file)
715 }
716
717 // Keep documentation output free of diagnostics while parsing the options.
718 if (early_quiet_output) {
720 }
721
722 // Register cleanup of shared subsystems to run on normal exit
723 // Library code doesn't call atexit() - the application is responsible
724 // IMPORTANT: atexit handlers run in LIFO order - last registered runs first
725 // So register order (top to bottom) = execution order (bottom to top):
726 // 1. PCRE2 cleanup (runs last) - frees compiled regexes after all code stops using them
727 // 2. Path cleanup (runs 3rd) - now a no-op but kept for API compatibility
728 // 3. Shared destroy (runs 2nd) - frees all shared resources
729 // 4. Cursor show (runs 1st) - shows cursor before exit
730 (void)atexit(asciichat_pcre2_cleanup_all);
731 (void)atexit(path_cleanup_thread_locals);
732 (void)atexit(asciichat_shared_destroy);
733 (void)atexit(on_exit_show_cursor);
734
735 // SECRET: Check for --backtrace (debug builds only) BEFORE options_init()
736 // Prints a backtrace and exits immediately - useful for debugging hangs
737#ifndef NDEBUG
738 for (int i = 1; i < argc; i++) {
739 if (strcmp(argv[i], "--backtrace") == 0) {
740 log_info("=== Backtrace at startup ===");
742 log_info("=== End Backtrace ===");
744 return 0;
745 }
746 }
747#endif
748
749 // NOW parse all options - can use logging with colors!
750 asciichat_error_t options_result = options_init(argc, argv);
751 if (options_result != ASCIICHAT_OK) {
753 if (HAS_ERRNO(&error_ctx)) {
754 log_error("Error: %s", error_ctx.context_message);
755 } else {
756 log_error("Error: Failed to initialize options (error code: %d)", options_result);
757 }
758
759 // Clean up options state before exiting
762
763 exit(options_result);
764 }
765
766 // Get parsed options
767 const options_t *opts = options_get();
768 if (!opts) {
769 log_error("Error: Options not initialized");
770 return 1;
771 }
772
773 // Apply the parsed level after the early logger has initialized.
775 // Determine output format early to decide on filename and logging strategy
776 bool use_json_logging = GET_OPTION(json);
777
778 // Determine the log filename
779 // Check if user explicitly passed --log-file (not just the mode-specific default from options_init)
780 bool user_specified_log_file = false;
781 for (int i = 1; i < argc - 1; i++) {
782 if (strcmp(argv[i], "--log-file") == 0 || strcmp(argv[i], "-L") == 0) {
783 user_specified_log_file = true;
784 break;
785 }
786 }
787
788 const char *final_log_file = opts->log_file;
789 char json_filename_buf[256];
790
791 if (use_json_logging) {
792 // When JSON logging is enabled, determine JSON output filename
793 if (user_specified_log_file) {
794 // User explicitly specified a log file - use it exactly for JSON output
795 SAFE_STRNCPY(json_filename_buf, final_log_file, sizeof(json_filename_buf) - 1);
796 } else {
797 // Using default: replace .log with .json in the mode-specific default
798 // e.g., server.log -> server.json, mirror.log -> mirror.json, etc.
799 size_t len = strlen(final_log_file);
800 if (len > 4 && strcmp(&final_log_file[len - 4], ".log") == 0) {
801 // File ends with .log - replace with .json
802 SAFE_STRNCPY(json_filename_buf, final_log_file, sizeof(json_filename_buf) - 1);
803 // Replace .log with .json
804 asciichat_error_t strcpy_result =
805 SAFE_STRCPY(&json_filename_buf[len - 4], sizeof(json_filename_buf) - (len - 4), ".json");
806 if (strcpy_result != ASCIICHAT_OK) {
807 log_error("Failed to write .json extension: %s", asciichat_error_string(strcpy_result));
808 }
809 } else {
810 // File doesn't end with .log - just append .json
811 snprintf(json_filename_buf, sizeof(json_filename_buf), "%s.json", final_log_file);
812 }
813 }
814 final_log_file = json_filename_buf;
815 // For JSON mode: Initialize logging without file (text will be disabled)
816 // We'll set up JSON output separately below
817 // Note: log_init() was already called in asciichat_shared_init()
818 } else {
819 // Text logging mode: use the file from options (which is mode-specific default or user-specified)
820 // Note: log_init() was already called in asciichat_shared_init()
821 }
822
823 // Apply custom log template if specified (use early parsed value if available, otherwise use options)
824 const char *final_format = early_log_template ? early_log_template : GET_OPTION(log_template);
825 bool final_format_console_only =
826 early_log_template ? early_log_template_console_only : GET_OPTION(log_format_console_only);
827 if (final_format && final_format[0] != '\0') {
828 asciichat_error_t fmt_result = log_set_format(final_format, final_format_console_only);
829 if (fmt_result != ASCIICHAT_OK) {
830 log_error("Failed to apply custom log format");
831 }
832 }
833
834 // Configure JSON output if requested
835 if (use_json_logging) {
836 // Open the JSON file for output
837 int json_fd = platform_open("main_log_file", final_log_file, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
838 if (json_fd >= 0) {
839 log_set_json_output(json_fd);
840 } else {
841 // Failed to open JSON file - report error and exit
842 SET_ERRNO(ERROR_CONFIG, "Failed to open JSON output file: %s", final_log_file);
843 return ERROR_CONFIG;
844 }
845 }
846
847 // Initialize colors now that logging is fully initialized
848 // This must happen after log_init() since log_init_colors() checks if g_log.initialized
850
851 // Apply quiet mode - disables terminal output
852 // Status screen mode only disables terminal output if terminal is interactive
853 // In non-interactive mode (piped output), logs go to stdout/stderr normally
854 if (GET_OPTION(quiet) ||
855 (opts->detected_mode == MODE_SERVER && GET_OPTION(status_screen) && terminal_is_interactive())) {
857 }
858
859 // Initialize palette based on command line options
860 const char *custom_chars = opts && opts->palette_custom_set ? opts->palette_custom : NULL;
861 if (apply_palette_config(GET_OPTION(palette_type), custom_chars) != 0) {
862 FATAL(ERROR_CONFIG, "Failed to apply palette configuration");
863 }
864
865 // Set quiet mode for memory debugging
866#if defined(DEBUG_MEMORY) && !defined(USE_MIMALLOC_DEBUG) && !defined(NDEBUG)
868#endif
869
870 // Truncate log if it's already too large
872 fflush(stderr);
873
874 // Handle --help and --version (these are detected and flagged by options_init)
875 // Terminal capabilities already initialized before options_init() at startup
876 if (opts->help) {
878 print_usage(opts->detected_mode);
879 fflush(NULL);
880 _Exit(0);
881 }
882
883 if (opts->version) {
886 // action_show_version() calls _Exit(), so we don't reach here
887 }
888
889#ifndef NDEBUG
890 // Handle --debug-state (debug builds only)
891 // Sleep for specified time AFTER mode initialization so locks are created
892 if (IS_OPTION_EXPLICIT(debug_sync_state_time, opts) && opts->debug_sync_state_time > 0.0) {
893 log_info("Will print sync state after %f seconds", opts->debug_sync_state_time);
894 }
895#endif
896
897 // For server mode with status screen: disable terminal output only if interactive
898 // In non-interactive mode (piped output), logs go to stdout/stderr normally
899 // The status screen (when shown) will capture and display logs in its buffer instead
902 }
903
904 log_dev("Logging initialized to %s", final_log_file);
905
906 // Note: We do NOT auto-disable colors when stdout appears to be piped, because:
907 // 1. Tools like ripgrep can display ANSI colors when piped
908 // 2. Sandboxed/containerized environments may report false positives for isatty()
909 // 3. Users can explicitly disable colors with --color=false if needed
910 // Color behavior is now fully controlled by --color and --color-mode options.
911
912#ifndef NDEBUG
913 // Initialize lock debugging system after logging is fully set up
914 log_debug("Initializing lock debug system...");
915 int debug_sync_result = debug_sync_init();
916 if (debug_sync_result != 0) {
917 LOG_ERRNO_IF_SET("Debug sync system initialization failed");
918 FATAL(ERROR_PLATFORM_INIT, "Debug sync system initialization failed");
919 }
920 log_debug("Debug sync system initialized successfully");
921
922 // Initialize memory debug system
923 // TEMPORARILY DISABLED: these calls hang in non-interactive mode
924 // log_debug("Initializing memory debug system...");
925 // int debug_memory_result = debug_memory_thread_init();
926 // if (debug_memory_result != 0) {
927 // LOG_ERRNO_IF_SET("Memory debug system initialization failed");
928 // FATAL(ERROR_PLATFORM_INIT, "Memory debug system initialization failed");
929 // }
930 // log_debug("Memory debug system initialized successfully");
931
932 // Start memory debug (now just prints report directly)
933 // if (debug_memory_thread_start() != 0) {
934 // LOG_ERRNO_IF_SET("Memory debug startup failed");
935 // FATAL(ERROR_THREAD, "Memory debug startup failed");
936 // }
937
938#ifndef _WIN32
939 // Unblock SIGUSR1 and SIGUSR2 at process level to ensure delivery
940 sigset_t set;
941 sigemptyset(&set);
942 sigaddset(&set, SIGUSR1);
943 sigaddset(&set, SIGUSR2);
944 sigprocmask(SIG_UNBLOCK, &set, NULL);
945
946 // Register SIGUSR1 to trigger synchronization debugging output in all modes
947 signal_handler_t old_usr1 = platform_signal(SIGUSR1, common_handle_sigusr1);
948 if (old_usr1 == SIG_ERR) {
949 log_warn("Failed to register SIGUSR1 handler");
950 } else {
951 log_debug("SIGUSR1 handler registered successfully");
952 }
953
954 // Register SIGUSR2 to trigger memory report in all modes
955 signal_handler_t old_usr2 = platform_signal(SIGUSR2, common_handle_sigusr2);
956 if (old_usr2 == SIG_ERR) {
957 log_warn("Failed to register SIGUSR2 handler");
958 } else {
959 log_debug("SIGUSR2 handler registered successfully");
960 }
961#endif
962#endif
963
964 if (opts->fps > 0) {
965 if (opts->fps < 1 || opts->fps > 144) {
966 log_warn("FPS value %d out of range (1-144), using default", opts->fps);
967 } else {
968 log_debug("FPS set from command line: %d", opts->fps);
969 }
970 }
971
972 // Automatic update check in background thread (once per week maximum)
973 if (!GET_OPTION(no_check_update)) {
975 }
976
977 // Set up global signal handlers BEFORE mode dispatch
978 // All modes use the same centralized exit mechanism
980
981 // Register application callbacks so lib code can check exit flags
982 // This connects the render loop's should_exit check to signal_exit()
983 static const app_callbacks_t app_callbacks = {
985 .signal_exit = signal_exit,
986 .platform_get_monotonic_time_us = platform_get_monotonic_time_us,
987 .platform_sleep_ms = platform_sleep_ms,
988 };
989 app_callbacks_register(&app_callbacks);
990
991 // Register shutdown callback so splash thread and other code can check for exit
993
994#ifndef NDEBUG
995 // Start debug threads now, after initialization but before mode entry
996 // This avoids lock contention during critical initialization phase
997 // Note: debug_sync_start_thread() and debug_memory_thread_start() now use
998 // direct pthread calls instead of mutex_init/cond_init to avoid named registry
999 // deadlock during thread startup
1000 if (debug_sync_start_thread() != 0) {
1001 LOG_ERRNO_IF_SET("Debug sync thread startup failed");
1002 FATAL(ERROR_THREAD, "Debug sync thread startup failed");
1003 }
1004 log_debug("Debug sync thread started");
1005
1006#endif
1007
1008 // Find and dispatch to mode entry point
1009 const mode_descriptor_t *mode = find_mode(opts->detected_mode);
1010 if (!mode) {
1011 log_error("Error: Mode not found for detected_mode=%d", opts->detected_mode);
1012 return 1;
1013 }
1014
1015 // Call the mode-specific entry point
1016 // Mode entry points use options_get() to access parsed options
1017 int exit_code = mode->entry_point();
1018
1019 // Named registry cleanup is handled by asciichat_shared_destroy()
1020 // (called via atexit handler), which runs AFTER the memory report
1021
1022 if (exit_code == ERROR_USAGE) {
1023 exit(ERROR_USAGE);
1024 }
1025
1026 return exit_code;
1027}
Action option callbacks for ascii-chat.
void app_callbacks_register(const app_callbacks_t *callbacks)
Application-level callbacks for library code.
โš ๏ธโ€ผ๏ธ Comprehensive thread-local error context system for ascii-chat
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
int atomic_fetch_add_int(atomic_t *a, int delta)
Atomically add to an int and return the previous value.
Definition atomic.c:209
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
โš›๏ธ Atomic operations abstraction layer with debug tracking
#define ATOMIC_INIT_AUTO(...)
Initialize an atomic_t with automatic named registration.
Definition atomic.h:510
Options builder API for flexible command-line option configuration.
int client_main(void)
Client mode entry point for unified binary.
ascii-chat Client Mode Entry Point Header
asciichat_error_t options_colorscheme_init_early(int argc, const char *const argv[])
Initialize color scheme early (before logging)
asciichat_error_t colorscheme_load_builtin(const char *name, color_scheme_t *scheme)
Load a built-in color scheme.
โš™๏ธ Common definitions, error codes, macros, and types shared throughout the application
void debug_atomic_init(void)
Lifecycle initialization for debug atomic tracking.
Debug tracking for atomic operations.
๐Ÿ“ Backtrace capture, symbolization, and formatting
void debug_memory_trigger_report(void)
void debug_memory_set_quiet_mode(bool quiet)
๐Ÿ” Memory debugging helpers for tracking allocations in debug builds
Named object registry for debugging โ€” log identifiable resource names.
int acds_main(void)
ACDS (discovery-service mode) entry point.
int discovery_main(void)
Run discovery mode main loop.
ascii-chat Discovery Mode Entry Point Header
#define IS_OPTION_EXPLICIT(name, opts)
Definition explicit.h:57
atomic_t g_should_exit
Global application exit flag (shared across all modes)
Definition globals.c:34
asciichat_error_t grep_init(const char *pattern)
Initialize log filtering with PCRE2 regex pattern.
Definition grep.c:498
bool g_color_flag_value
Value of โ€“color flag (true if โ€“color was in argv) Set by options_init() before RCU is initialized,...
Definition common.c:58
char ** g_argv
Global argv for early argument inspection (e.g., โ€“color flag detection) Set by main() for access from...
Definition common.c:54
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
int g_argc
Global argc for early argument inspection (e.g., โ€“color flag detection) Set by main() for access from...
Definition common.c:53
bool g_color_flag_passed
Was โ€“color explicitly passed in command-line arguments? Set by options_init() before RCU is initializ...
Definition common.c:57
#define SAFE_GETENV(name)
Definition common.h:434
asciichat_error_t asciichat_shared_init(const char *log_file, bool is_client, bool quiet_startup)
Initialize common subsystems shared by client and server.
Definition common.c:115
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:471
#define FATAL(code,...)
Exit with error code and custom message, with stack trace in debug builds.
Definition common.h:158
void asciichat_shared_destroy(void)
Clean up all shared library subsystems.
Definition common.c:195
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_REGISTER_THREAD(thread, name, parent_ptr)
Register a thread handle with name and automatic format specifier.
void named_registry_register_packet_types(void)
Register all packet types from packet_type_t enum.
asciichat_error_t named_init(void)
Initialize the named object registry.
Definition debug/named.c:89
void debug_sync_set_main_thread_id(void)
Initialize debug synchronization system.
Definition sync.c:639
void debug_sync_trigger_print(void)
Trigger sync state print immediately (synchronous)
Definition sync.c:710
int debug_sync_start_thread(void)
Start background debug thread for scheduled operations.
Definition sync.c:657
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
#define HAS_ERRNO(var)
Check if an error occurred and get full context.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_PLATFORM_INIT
Definition error_codes.h:60
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_CONFIG
Definition error_codes.h:57
@ ERROR_USAGE
Definition error_codes.h:53
@ ERROR_THREAD
void log_truncate_if_large(void)
Manually truncate large log files.
Definition log/log.c:807
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
void log_init_colors(void)
Initialize logging color system with current terminal capabilities.
Definition log/log.c:1671
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
void log_set_force_stderr(bool enabled)
Force all terminal log output to stderr.
Definition log/log.c:721
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
void log_set_level(log_level_t level)
Set the minimum log level.
Definition log/log.c:691
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
asciichat_error_t log_set_format(const char *format_str, bool console_only)
Set a custom log format string.
Definition log/log.c:744
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
Definition log/log.h:646
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log/log.c:700
void log_set_color_scheme(const color_scheme_t *scheme)
Set the color scheme for logging output.
Definition log/log.c:1728
void log_redetect_terminal_capabilities(void)
Re-detect terminal capabilities after logging is initialized.
Definition log/log.c:1602
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
asciichat_mode_t
Mode type for options parsing.
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
asciichat_error_t options_init(int argc, char **argv)
Initialize options by parsing command-line arguments with unified mode detection.
void usage(FILE *desc, asciichat_mode_t mode)
Print usage information for a specific mode.
@ MODE_CLIENT
Client mode - network client options.
@ MODE_DISCOVERY_SERVICE
Discovery server mode - session management and WebRTC signaling.
@ MODE_INVALID
Invalid mode.
@ MODE_SERVER
Server mode - network server options.
@ MODE_MIRROR
Mirror mode - local webcam viewing (no network)
@ MODE_DISCOVERY
Discovery mode - participant that can dynamically become host.
int apply_palette_config(palette_type_t type, const char *custom_chars)
Apply palette configuration (set global palette)
Definition palette.c:250
signal_handler_t platform_signal(int sig, signal_handler_t handler)
Set a signal handler.
Definition misc.c:115
asciichat_error_t platform_register_signal_handlers(const platform_signal_handler_t *handlers, int count)
Register multiple signal handlers at once.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
void backtrace_print_simple(int skip_frames)
Print backtrace of the current call stack.
asciichat_error_t platform_mkdir_recursive(const char *path, int mode)
Create directories recursively (mkdir -p equivalent)
void platform_force_exit(int exit_code)
Forcefully terminate the process immediately without cleanup.
Definition misc.c:121
int platform_open(const char *name, const char *pathname, int flags,...)
Safe file open (open replacement)
uint64_t platform_get_monotonic_time_us(void)
Get monotonic time in microseconds.
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.
#define FILE_PERM_PRIVATE
File permission: Private (owner read/write only)
Definition filesystem.h:187
asciichat_error_t terminal_cursor_show(void)
Show terminal cursor.
bool keyboard_help_is_active_global(void)
Check if keyboard help is active (global accessor for signal handlers)
void update_banner_start_check(void)
Start the background update check thread.
void keyboard_help_toggle_global(void)
Toggle keyboard help (global accessor for signal handlers)
bool(* shutdown_check_fn)(void)
Shutdown check callback function type.
Definition shutdown.h:32
void shutdown_register_callback(shutdown_check_fn callback)
Register application's shutdown check function.
Definition common.c:69
void path_cleanup_thread_locals(void)
Cleanup thread-local path resources.
Definition path.c:85
๐Ÿ“ JSON structured logging output
โš™๏ธ Unified options parsing system for ascii-chat with builder pattern and lock-free access
๐Ÿงต Cross-platform thread interface for ascii-chat
๐Ÿ†˜ Interactive keyboard help overlay for session keyboard shortcuts
#define asciichat_thread_self()
void * asciichat_thread_t
void log_set_json_output(int fd)
Set the JSON output file descriptor.
Definition log/log.c:729
๐Ÿ“ Logging API with multiple log levels and terminal output control
#define log_console(level, message)
Definition log/log.h:1003
void signal_exit(void)
Definition main.c:215
int main(int argc, char *argv[])
Definition main.c:472
#define REGISTER_GLOBAL_ATOMICS()
Definition main.c:190
void setup_signal_handlers(void)
Definition main.c:346
int(* mode_entry_point_t)(void)
Definition main.c:364
bool should_exit(void)
Definition main.c:211
void set_interrupt_callback(void(*cb)(void))
Definition main.c:227
int mirror_main(void)
Run mirror mode main loop.
ascii-chat Mirror Mode Interface
terminal_capabilities_t apply_color_mode_override(terminal_capabilities_t caps)
Apply command-line overrides to detected capabilities.
Definition misc.c:29
void(* signal_handler_t)(int)
Definition misc.c:113
Common utilities and helpers for option parsing across all modes.
void action_show_version(void)
Show version information and exit.
๐Ÿ“‚ Path Manipulation Utilities
void asciichat_pcre2_cleanup_all(void)
Free all PCRE2 singletons in the global registry.
Definition pcre2.c:214
๐Ÿ” Centralized PCRE2 singleton pattern for efficient regex compilation
Cross-platform backtrace and stack trace capture.
bool terminal_is_interactive(void)
Check if the session is fully interactive.
bool terminal_should_force_stderr(void)
Determine if logs should be forced to stderr.
terminal_capabilities_t detect_terminal_capabilities(void)
Detect terminal capabilities.
void options_state_destroy(void)
Shutdown RCU options system.
Definition rcu.c:461
void options_cleanup_schema(void)
Clean up schema resources.
Definition rcu.c:492
int server_main(void)
Server mode entry point for unified binary.
ascii-chat Server Mode Entry Point Header
Cross-platform crash signal and exception handling.
Intro and status screen display for ascii-chat with animated rainbow effects.
ClangTool/LibTooling compatibility shim for stdbool.h.
bool(* should_exit)(void)
Error context structure.
char * context_message
Optional custom message (dynamically allocated, owned by system)
Atomic value wrapper for integral/boolean types.
Definition atomic.h:76
Color scheme definition.
Definition colorscheme.h:62
Metadata for a mode.
Definition main.c:366
const char * name
Definition main.c:367
const char * description
Definition main.c:368
mode_entry_point_t entry_point
Definition main.c:369
Consolidated options structure.
double debug_sync_state_time
Time parameter for โ€“sync-state option (debug builds only)
asciichat_mode_t detected_mode
Mode detected from command-line arguments.
log_level_t log_level
Log level threshold.
char palette_custom[256]
Custom palette characters.
bool status_screen
Show status screen (default: true = show, use โ€“no-status-screen to hide)
bool palette_custom_set
True if custom palette was set.
bool version
Show version information.
int fps
Target framerate (1-144, default: 60)
char log_file[256]
Log file path.
Signal handler descriptor for bulk registration.
Definition system.h:295
Complete terminal capabilities structure.
Definition terminal.h:709
int debug_sync_init(void)
Definition sync.c:644
๐Ÿ”’ Synchronization primitive debugging (mutexes, rwlocks, condition variables)
Cross-platform system functions interface for ascii-chat.
๐Ÿ–ฅ๏ธ Cross-platform terminal interface for ascii-chat
bool platform_set_console_ctrl_handler(console_ctrl_handler_t handler)
Register a console control handler (for Ctrl+C, etc.)
console_ctrl_event_t
Console control event types (cross-platform Ctrl+C handling)
Definition terminal.h:1211
@ CONSOLE_CTRL_BREAK
Definition terminal.h:1213
@ CONSOLE_CTRL_C
Definition terminal.h:1212
@ CONSOLE_CLOSE
Definition terminal.h:1214
โฑ๏ธ High-precision timing utilities using sokol_time.h and uthash
#define LOG_INFO
Definition types.h:40
Update available prompt screen shown after splash.
๐Ÿ”„ Update checker for GitHub releases with DNS connectivity test and cache
๐Ÿ”ค UTF-8 Encoding and Decoding Utilities
๐Ÿ”ค String Manipulation and Shell Escaping Utilities
#define ASCII_CHAT_BUILD_TYPE
Definition version.h:14