ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
Platform Abstractions

๐Ÿ”Œ Cross-platform abstractions for threading, sockets, and system calls, and hardware access More...

Files

file  abstraction.c
 ๐Ÿ—๏ธ Common platform abstraction stubs (OS-specific code in posix/ and windows/ subdirectories)
 
file  abstraction.h
 ๐Ÿ”Œ Cross-platform abstraction layer umbrella header for ascii-chat
 
file  api.h
 DLL export/import macros for cross-platform symbol visibility.
 
file  cond.h
 Cross-platform condition variable interface for ascii-chat.
 
file  file.h
 Cross-platform file I/O interface for ascii-chat.
 
file  fs.h
 Cross-platform file system operations.
 
file  init.h
 Platform initialization and static synchronization helpers.
 
file  internal.h
 Private implementation helpers for platform abstraction layer.
 
file  memory.h
 Cross-platform memory management utilities.
 
file  mmap.h
 Cross-platform memory-mapped file interface.
 
file  mutex.h
 Cross-platform mutex interface for ascii-chat.
 
file  pipe.h
 Cross-platform pipe/agent socket interface for ascii-chat.
 
file  process.h
 Cross-platform process execution utilities.
 
file  question.h
 Cross-platform interactive prompting utilities.
 
file  rwlock.h
 Cross-platform read-write lock interface for ascii-chat.
 
file  socket.c
 ๐ŸŒ Common socket utility functions (cross-platform implementations)
 
file  socket.h
 Cross-platform socket interface for ascii-chat.
 
file  string.h
 Platform-independent safe string functions.
 
file  symbols.c
 ๐Ÿ” Symbol resolution cache: llvm-symbolizer/addr2line wrapper with hashtable-backed caching
 
file  symbols.h
 Symbol Resolution Cache for Backtrace Addresses.
 
file  system.c
 ๐Ÿ”ง Shared cross-platform system utilities (included by posix/system.c and windows/system.c)
 
file  system.h
 Cross-platform system functions interface for ascii-chat.
 
file  terminal.h
 ๐Ÿ–ฅ๏ธ Cross-platform terminal interface for ascii-chat
 
file  thread.c
 ๐Ÿงต Thread utilities and helpers
 
file  thread.h
 ๐Ÿงต Cross-platform thread interface for ascii-chat
 
file  util.h
 Public platform utility API for string, memory, and file operations.
 
file  windows_compat.h
 Wrapper for windows.h with C23 alignment compatibility.
 
file  windows_errno.h
 Windows errno compatibility definitions.
 

Data Structures

struct  platform_stat_t
 File type information from stat() More...
 
struct  static_mutex_t
 Static mutex structure for global mutexes requiring static initialization. More...
 
struct  static_rwlock_t
 Static reader-writer lock structure for global rwlocks requiring static initialization. More...
 
struct  static_cond_t
 Static condition variable structure for global condition variables requiring static initialization. More...
 
struct  platform_mmap
 Memory-mapped file handle. More...
 
struct  prompt_opts_t
 Options for text prompts. More...
 
struct  symbol_entry_t
 Symbol cache entry structure for address-to-symbol mapping. More...
 
struct  bin_cache_entry_t
 Binary PATH cache entry structure for binary detection caching. More...
 
struct  terminal_size_t
 Terminal size structure. More...
 
struct  terminal_capabilities_t
 Complete terminal capabilities structure. More...
 
struct  tty_info_t
 TTY detection and management structure. More...
 

Macros

#define ASCIICHAT_API   __attribute__((visibility("default")))
 Export symbols on Unix platforms (Linux, macOS)
 
#define PLATFORM_O_RDONLY   O_RDONLY
 Open file for reading only.
 
#define PLATFORM_O_WRONLY   O_WRONLY
 Open file for writing only.
 
#define PLATFORM_O_RDWR   O_RDWR
 Open file for reading and writing.
 
#define PLATFORM_O_CREAT   O_CREAT
 Create file if it doesn't exist.
 
#define PLATFORM_O_EXCL   O_EXCL
 Fail if file already exists (with O_CREAT)
 
#define PLATFORM_O_TRUNC   O_TRUNC
 Truncate file to zero length if it exists.
 
#define PLATFORM_O_APPEND   O_APPEND
 Append to end of file.
 
#define PLATFORM_O_BINARY   0
 Open file in binary mode (Windows)
 
#define STATIC_MUTEX_INIT   {PTHREAD_MUTEX_INITIALIZER, 1}
 
#define STATIC_RWLOCK_INIT   {PTHREAD_RWLOCK_INITIALIZER, 1}
 
#define STATIC_COND_INIT   {PTHREAD_COND_INITIALIZER, 1}
 
#define INVALID_PIPE_VALUE   (-1)
 Invalid pipe value (POSIX: -1)
 
#define PROMPT_OPTS_DEFAULT   (prompt_opts_t){.echo = true, .same_line = false, .mask_char = 0}
 Default prompt options (echo enabled, answer on next line)
 
#define PROMPT_OPTS_PASSWORD   (prompt_opts_t){.echo = false, .same_line = true, .mask_char = '*'}
 Prompt options for password input (no echo, asterisk masking, same line)
 
#define PROMPT_OPTS_INLINE   (prompt_opts_t){.echo = true, .same_line = true, .mask_char = 0}
 Prompt options for inline text input (echo enabled, same line)
 
#define INVALID_SOCKET_VALUE   (-1)
 Invalid socket value (POSIX: -1)
 
#define SOCKET_ERROR_WOULDBLOCK   EWOULDBLOCK
 
#define SOCKET_ERROR_INPROGRESS   EINPROGRESS
 
#define SOCKET_ERROR_AGAIN   EAGAIN
 
#define SAFE_IGNORE_PRINTF_RESULT(expr)   ((void)(expr))
 
#define PATH_DELIM   '/'
 Platform-specific path separator character.
 
#define PATH_SEPARATOR_STR   "/"
 
#define PATH_ENV_SEPARATOR   ":"
 Platform-specific PATH environment variable separator.
 
#define FILE_PERM_PRIVATE   0600
 File permission: Private (owner read/write only)
 
#define DIR_PERM_PRIVATE   0700
 Directory permission: Private (owner read/write/execute only)
 
#define FILE_PERM_PUBLIC_READ   0644
 File permission: Public read, owner write.
 
#define FILE_PERM_MASK   0777
 Permission mask for all permissions.
 
#define PLATFORM_MAX_PATH_LENGTH   4096
 Maximum path length supported by the operating system.
 
#define PLATFORM_ACCESS_EXISTS   0
 Access modes for platform_access()
 
#define PLATFORM_ACCESS_WRITE   2
 Check if file/directory is writable.
 
#define PLATFORM_ACCESS_READ   4
 Check if file/directory is readable.
 
#define PLATFORM_O_RDONLY   O_RDONLY
 Open file for reading only.
 
#define PLATFORM_O_WRONLY   O_WRONLY
 Open file for writing only.
 
#define PLATFORM_O_RDWR   O_RDWR
 Open file for reading and writing.
 
#define PLATFORM_O_CREAT   O_CREAT
 Create file if it doesn't exist.
 
#define PLATFORM_O_EXCL   O_EXCL
 Fail if file already exists (with O_CREAT)
 
#define PLATFORM_O_TRUNC   O_TRUNC
 Truncate file to zero length if it exists.
 
#define PLATFORM_O_APPEND   O_APPEND
 Append to end of file.
 
#define PLATFORM_O_BINARY   0
 Open file in binary mode (Windows)
 
#define EINVAL   22
 
#define ERANGE   34
 
#define ETIMEDOUT   110
 
#define EINTR   4
 
#define EBADF   9
 
#define EAGAIN   11
 
#define EWOULDBLOCK   11
 
#define EPIPE   32
 
#define ECONNREFUSED   111
 
#define ENETUNREACH   101
 
#define EHOSTUNREACH   113
 
#define ECONNRESET   104
 
#define ENOTSOCK   88
 

Typedefs

typedef pthread_cond_t cond_t
 Condition variable type (POSIX: pthread_cond_t)
 
typedef struct platform_mmap platform_mmap_t
 Memory-mapped file handle.
 
typedef pthread_mutex_t mutex_t
 Mutex type (POSIX: pthread_mutex_t)
 
typedef int pipe_t
 Pipe handle type (POSIX: int file descriptor)
 
typedef pthread_rwlock_t rwlock_t
 Read-write lock type (POSIX: pthread_rwlock_t)
 
typedef int socket_t
 Socket handle type (POSIX: int)
 
typedef void(* signal_handler_t) (int)
 Signal handler function type.
 
typedef bool(* console_ctrl_handler_t) (console_ctrl_event_t event)
 Console control handler callback type.
 
typedef bool(* backtrace_frame_filter_t) (const char *frame)
 Callback type for filtering backtrace frames.
 
typedef pthread_t asciichat_thread_t
 Thread handle type (POSIX: pthread_t)
 
typedef pthread_t thread_id_t
 Thread ID type (POSIX: pthread_t)
 
typedef pthread_key_t tls_key_t
 Thread-local storage key type (POSIX: pthread_key_t)
 

Enumerations

enum  console_ctrl_event_t {
  CONSOLE_CTRL_C = 0 , CONSOLE_CTRL_BREAK = 1 , CONSOLE_CLOSE = 2 , CONSOLE_LOGOFF = 3 ,
  CONSOLE_SHUTDOWN = 4
}
 Console control event types (cross-platform Ctrl+C handling) More...
 
enum  terminal_color_mode_t {
  TERM_COLOR_AUTO = -1 , TERM_COLOR_NONE = 0 , TERM_COLOR_16 = 1 , TERM_COLOR_256 = 2 ,
  TERM_COLOR_TRUECOLOR = 3
}
 Terminal color support levels. More...
 
enum  terminal_capability_flags_t {
  TERM_CAP_COLOR_16 = 0x0001 , TERM_CAP_COLOR_256 = 0x0002 , TERM_CAP_COLOR_TRUE = 0x0004 , TERM_CAP_UTF8 = 0x0008 ,
  TERM_CAP_BACKGROUND = 0x0010
}
 Terminal capability flags (bitmask) More...
 
enum  render_mode_t { RENDER_MODE_FOREGROUND = 0 , RENDER_MODE_BACKGROUND = 1 , RENDER_MODE_HALF_BLOCK = 2 }
 Render mode preferences. More...
 

Functions

int cond_init (cond_t *cond)
 Initialize a condition variable.
 
int cond_destroy (cond_t *cond)
 Destroy a condition variable.
 
int cond_wait (cond_t *cond, mutex_t *mutex)
 Wait on a condition variable (blocking)
 
int cond_timedwait (cond_t *cond, mutex_t *mutex, int timeout_ms)
 Wait on a condition variable with timeout.
 
int cond_signal (cond_t *cond)
 Signal a condition variable (wake one waiting thread)
 
int cond_broadcast (cond_t *cond)
 Broadcast to a condition variable (wake all waiting threads)
 
asciichat_error_t platform_mkdir (const char *path, int mode)
 Create a directory.
 
asciichat_error_t platform_stat (const char *path, platform_stat_t *stat_out)
 Get file statistics.
 
int platform_is_regular_file (const char *path)
 Check if a path is a regular file.
 
int platform_is_directory (const char *path)
 Check if a path is a directory.
 
asciichat_error_t platform_init (void)
 Initialize platform-specific subsystems.
 
void platform_cleanup (void)
 Cleanup platform-specific subsystems.
 
size_t platform_malloc_size (const void *ptr)
 Get the size of an allocated memory block.
 
void platform_mmap_init (platform_mmap_t *mapping)
 Initialize a platform_mmap_t structure.
 
asciichat_error_t platform_mmap_open (const char *path, size_t size, platform_mmap_t *out)
 Memory-map a file for read/write access.
 
void platform_mmap_close (platform_mmap_t *mapping)
 Unmap and close a memory-mapped file.
 
void platform_mmap_sync (platform_mmap_t *mapping, bool async)
 Flush memory-mapped changes to disk.
 
bool platform_mmap_is_valid (const platform_mmap_t *mapping)
 Check if a mapping is currently valid.
 
int debug_mutex_lock (mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
 
int debug_mutex_trylock (mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
 
int debug_mutex_unlock (mutex_t *mutex, const char *file_name, int line_number, const char *function_name)
 
bool lock_debug_is_initialized (void)
 
int mutex_init (mutex_t *mutex)
 Initialize a mutex.
 
int mutex_destroy (mutex_t *mutex)
 Destroy a mutex.
 
int mutex_lock_impl (mutex_t *mutex)
 Lock a mutex (implementation function)
 
int mutex_trylock_impl (mutex_t *mutex)
 Try to lock a mutex without blocking (implementation function)
 
int mutex_unlock_impl (mutex_t *mutex)
 Unlock a mutex (implementation function)
 
pipe_t platform_pipe_connect (const char *path)
 Connect to an agent via named pipe (Windows) or Unix socket (POSIX)
 
int platform_pipe_close (pipe_t pipe)
 Close a pipe connection.
 
ssize_t platform_pipe_read (pipe_t pipe, void *buf, size_t len)
 Read data from a pipe.
 
ssize_t platform_pipe_write (pipe_t pipe, const void *buf, size_t len)
 Write data to a pipe.
 
bool platform_pipe_is_valid (pipe_t pipe)
 Check if a pipe handle is valid.
 
asciichat_error_t platform_popen (const char *command, const char *mode, FILE **out_stream)
 Execute a command and return a file stream for reading/writing.
 
asciichat_error_t platform_pclose (FILE **stream_ptr)
 Close a process stream opened with platform_popen()
 
int platform_prompt_question (const char *prompt, char *buffer, size_t max_len, prompt_opts_t opts)
 Prompt the user for text input.
 
bool platform_prompt_yes_no (const char *prompt, bool default_yes)
 Prompt the user for a yes/no answer.
 
bool platform_is_interactive (void)
 Check if interactive prompting is available.
 
int debug_rwlock_rdlock (rwlock_t *rwlock, const char *file_name, int line_number, const char *function_name)
 
int debug_rwlock_wrlock (rwlock_t *rwlock, const char *file_name, int line_number, const char *function_name)
 
int debug_rwlock_rdunlock (rwlock_t *rwlock, const char *file_name, int line_number, const char *function_name)
 
int debug_rwlock_wrunlock (rwlock_t *rwlock, const char *file_name, int line_number, const char *function_name)
 
int rwlock_init (rwlock_t *lock)
 Initialize a read-write lock.
 
int rwlock_destroy (rwlock_t *lock)
 Destroy a read-write lock.
 
int rwlock_init_impl (rwlock_t *lock)
 Initialize a read-write lock (implementation function)
 
int rwlock_destroy_impl (rwlock_t *lock)
 Destroy a read-write lock (implementation function)
 
int rwlock_rdlock_impl (rwlock_t *lock)
 Acquire a read lock (implementation function)
 
int rwlock_wrlock_impl (rwlock_t *lock)
 Acquire a write lock (implementation function)
 
int rwlock_rdunlock_impl (rwlock_t *lock)
 Release a read lock (implementation function)
 
int rwlock_wrunlock_impl (rwlock_t *lock)
 Release a write lock (implementation function)
 
void socket_optimize_for_streaming (socket_t sock)
 Optimize socket for high-throughput video streaming.
 
asciichat_error_t socket_init (void)
 Initialize socket subsystem (required on Windows)
 
void socket_cleanup (void)
 Cleanup socket subsystem.
 
socket_t socket_create (int domain, int type, int protocol)
 Create a new socket.
 
int socket_close (socket_t sock)
 Close a socket.
 
int socket_bind (socket_t sock, const struct sockaddr *addr, socklen_t addrlen)
 Bind a socket to an address.
 
int socket_listen (socket_t sock, int backlog)
 Listen for incoming connections.
 
socket_t socket_accept (socket_t sock, struct sockaddr *addr, socklen_t *addrlen)
 Accept an incoming connection.
 
int socket_connect (socket_t sock, const struct sockaddr *addr, socklen_t addrlen)
 Connect to a remote address.
 
ssize_t socket_send (socket_t sock, const void *buf, size_t len, int flags)
 Send data on a socket.
 
ssize_t socket_recv (socket_t sock, void *buf, size_t len, int flags)
 Receive data from a socket.
 
ssize_t socket_sendto (socket_t sock, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen)
 Send data to a specific address (UDP)
 
ssize_t socket_recvfrom (socket_t sock, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
 Receive data from a specific address (UDP)
 
int socket_setsockopt (socket_t sock, int level, int optname, const void *optval, socklen_t optlen)
 Set socket option.
 
int socket_getsockopt (socket_t sock, int level, int optname, void *optval, socklen_t *optlen)
 Get socket option.
 
int socket_shutdown (socket_t sock, int how)
 Shutdown socket I/O.
 
int socket_getpeername (socket_t sock, struct sockaddr *addr, socklen_t *addrlen)
 Get peer address.
 
int socket_getsockname (socket_t sock, struct sockaddr *addr, socklen_t *addrlen)
 Get socket local address.
 
int socket_set_blocking (socket_t sock)
 Set socket to blocking mode.
 
int socket_set_nonblocking (socket_t sock, bool nonblocking)
 Set socket to non-blocking mode.
 
int socket_set_reuseaddr (socket_t sock, bool reuse)
 Set SO_REUSEADDR socket option.
 
int socket_set_nodelay (socket_t sock, bool nodelay)
 Set TCP_NODELAY socket option (disable Nagle's algorithm)
 
int socket_set_keepalive (socket_t sock, bool keepalive)
 Set SO_KEEPALIVE socket option.
 
int socket_set_keepalive_params (socket_t sock, bool enable, int idle, int interval, int count)
 Set TCP keepalive parameters.
 
int socket_set_linger (socket_t sock, bool enable, int timeout)
 Set SO_LINGER socket option.
 
int socket_set_timeout (socket_t sock, uint32_t timeout_ms)
 Set socket receive and send timeouts.
 
int socket_set_buffer_sizes (socket_t sock, int recv_size, int send_size)
 Set socket buffer sizes.
 
int socket_get_peer_address (socket_t sock, struct sockaddr *addr, socklen_t *addrlen)
 Get peer address (convenience function)
 
int socket_get_error (socket_t sock)
 Get socket-specific error code.
 
int socket_get_last_error (void)
 Get last socket error code.
 
const char * socket_get_error_string (void)
 Get last socket error as string.
 
int socket_poll (struct pollfd *fds, nfds_t nfds, int timeout)
 Poll sockets for events.
 
int socket_select (socket_t max_fd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
 Select sockets for I/O readiness.
 
void socket_fd_zero (fd_set *set)
 Clear an fd_set.
 
void socket_fd_set (socket_t sock, fd_set *set)
 Add a socket to an fd_set.
 
int socket_fd_isset (socket_t sock, fd_set *set)
 Check if a socket is in an fd_set.
 
int socket_get_fd (socket_t sock)
 Get the underlying file descriptor (POSIX compatibility)
 
bool socket_is_valid (socket_t sock)
 Check if a socket handle is valid.
 
asciichat_error_t symbol_cache_init (void)
 Initialize the symbol cache.
 
void symbol_cache_cleanup (void)
 Clean up the symbol cache and free all resources.
 
const char * symbol_cache_lookup (void *addr)
 Look up a symbol for a given address.
 
bool symbol_cache_insert (void *addr, const char *symbol)
 Insert a symbol into the cache.
 
void symbol_cache_get_stats (uint64_t *hits_out, uint64_t *misses_out, size_t *entries_out)
 Get cache statistics.
 
void symbol_cache_print_stats (void)
 Print cache statistics to logging system.
 
char ** symbol_cache_resolve_batch (void *const *buffer, int size)
 Resolve multiple addresses using addr2line and cache results.
 
void symbol_cache_free_symbols (char **symbols)
 Free symbol array returned by symbol_cache_resolve_batch.
 
void platform_sleep_ms (unsigned int ms)
 Sleep for a specified number of milliseconds.
 
uint64_t platform_get_monotonic_time_us (void)
 Get monotonic time in microseconds.
 
asciichat_error_t platform_localtime (const time_t *timer, struct tm *result)
 Platform-safe localtime wrapper.
 
asciichat_error_t platform_gtime (const time_t *timer, struct tm *result)
 Platform-safe gmtime wrapper.
 
int platform_get_pid (void)
 Get the current process ID.
 
const char * platform_get_username (void)
 Get the current username.
 
signal_handler_t platform_signal (int sig, signal_handler_t handler)
 Set a signal handler.
 
bool platform_set_console_ctrl_handler (console_ctrl_handler_t handler)
 Register a console control handler (for Ctrl+C, etc.)
 
const char * platform_getenv (const char *name)
 Get an environment variable value.
 
int platform_setenv (const char *name, const char *value)
 Set an environment variable.
 
int platform_isatty (int fd)
 Check if a file descriptor is a terminal.
 
const char * platform_ttyname (int fd)
 Get the name of the terminal associated with a file descriptor.
 
int platform_fsync (int fd)
 Synchronize a file descriptor to disk.
 
int platform_backtrace (void **buffer, int size)
 Get a backtrace of the current call stack.
 
char ** platform_backtrace_symbols (void *const *buffer, int size)
 Convert backtrace addresses to symbol names.
 
void platform_backtrace_symbols_free (char **strings)
 Free symbol array returned by platform_backtrace_symbols()
 
void platform_install_crash_handler (void)
 Install crash handlers for the application.
 
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 consistent formatting.
 
int platform_format_backtrace_symbols (char *buffer, size_t buffer_size, const char *label, char **symbols, int count, int skip_frames, int max_frames, backtrace_frame_filter_t filter)
 Format pre-resolved backtrace symbols to a buffer.
 
void platform_print_backtrace (int skip_frames)
 Print a backtrace of the current call stack.
 
asciichat_error_t platform_memcpy (void *dest, size_t dest_size, const void *src, size_t count)
 Platform-safe memcpy wrapper.
 
asciichat_error_t platform_memset (void *dest, size_t dest_size, int ch, size_t count)
 Platform-safe memset wrapper.
 
asciichat_error_t platform_memmove (void *dest, size_t dest_size, const void *src, size_t count)
 Platform-safe memmove wrapper.
 
asciichat_error_t platform_strcpy (char *dest, size_t dest_size, const char *src)
 Platform-safe strcpy wrapper.
 
asciichat_error_t platform_resolve_hostname_to_ipv4 (const char *hostname, char *ipv4_out, size_t ipv4_out_size)
 Resolve hostname to IPv4 address.
 
asciichat_error_t platform_load_system_ca_certs (char **pem_data_out, size_t *pem_size_out)
 Load system CA certificates for TLS/HTTPS.
 
bool platform_is_binary_in_path (const char *bin_name)
 Check if a binary is available in the system PATH.
 
void platform_cleanup_binary_path_cache (void)
 Cleanup the binary PATH cache.
 
bool platform_get_executable_path (char *exe_path, size_t path_size)
 Get the path to the current executable.
 
bool platform_get_temp_dir (char *temp_dir, size_t path_size)
 Get the system temporary directory path.
 
bool platform_get_cwd (char *cwd, size_t path_size)
 Get the current working directory of the process.
 
int platform_access (const char *path, int mode)
 Check file/directory access permissions.
 
asciichat_error_t terminal_get_size (terminal_size_t *size)
 Get terminal size.
 
asciichat_error_t terminal_set_raw_mode (bool enable)
 Set terminal to raw mode.
 
asciichat_error_t terminal_set_echo (bool enable)
 Set terminal echo mode.
 
bool terminal_supports_color (void)
 Check if terminal supports color.
 
bool terminal_supports_unicode (void)
 Check if terminal supports unicode.
 
bool terminal_supports_utf8 (void)
 Check if terminal supports UTF-8.
 
asciichat_error_t terminal_clear_screen (void)
 Clear the terminal screen.
 
asciichat_error_t terminal_move_cursor (int row, int col)
 Move cursor to specified position.
 
void terminal_enable_ansi (void)
 Enable ANSI escape sequences.
 
asciichat_error_t terminal_set_buffering (bool line_buffered)
 Set terminal buffering mode.
 
asciichat_error_t terminal_flush (int fd)
 Flush terminal output.
 
asciichat_error_t terminal_get_cursor_position (int *row, int *col)
 Get current cursor position.
 
asciichat_error_t terminal_save_cursor (void)
 Save cursor position.
 
asciichat_error_t terminal_restore_cursor (void)
 Restore saved cursor position.
 
asciichat_error_t terminal_set_title (const char *title)
 Set terminal window title.
 
asciichat_error_t terminal_ring_bell (void)
 Ring terminal bell.
 
asciichat_error_t terminal_hide_cursor (int fd, bool hide)
 Hide or show cursor.
 
asciichat_error_t terminal_set_scroll_region (int top, int bottom)
 Set scroll region.
 
asciichat_error_t terminal_reset (int fd)
 Reset terminal to default state.
 
asciichat_error_t terminal_cursor_home (int fd)
 Move cursor to home position (top-left)
 
asciichat_error_t terminal_clear_scrollback (int fd)
 Clear terminal scrollback buffer.
 
terminal_capabilities_t detect_terminal_capabilities (void)
 Detect terminal capabilities.
 
tty_info_t get_current_tty (void)
 Get current TTY information.
 
bool is_valid_tty_path (const char *path)
 Check if a TTY path is valid.
 
asciichat_error_t get_terminal_size (unsigned short int *width, unsigned short int *height)
 Get terminal size with multiple fallback methods.
 
const char * terminal_color_level_name (terminal_color_mode_t level)
 Get name of color level.
 
const char * terminal_capabilities_summary (const terminal_capabilities_t *caps)
 Get summary string of terminal capabilities.
 
void print_terminal_capabilities (const terminal_capabilities_t *caps)
 Print terminal capabilities to stdout.
 
void test_terminal_output_modes (void)
 Test terminal output modes.
 
terminal_capabilities_t apply_color_mode_override (terminal_capabilities_t caps)
 Apply command-line overrides to detected capabilities.
 
int asciichat_thread_create (asciichat_thread_t *thread, void *(*func)(void *), void *arg)
 Create a new thread.
 
int asciichat_thread_join (asciichat_thread_t *thread, void **retval)
 Wait for a thread to complete (blocking)
 
int asciichat_thread_join_timeout (asciichat_thread_t *thread, void **retval, uint32_t timeout_ms)
 Wait for a thread to complete with timeout.
 
void asciichat_thread_exit (void *retval)
 Exit the current thread.
 
thread_id_t asciichat_thread_self (void)
 Get the current thread's ID.
 
int asciichat_thread_equal (thread_id_t t1, thread_id_t t2)
 Compare two thread IDs for equality.
 
uint64_t asciichat_thread_current_id (void)
 Get the current thread's unique numeric ID.
 
bool asciichat_thread_is_initialized (asciichat_thread_t *thread)
 Check if a thread handle has been initialized.
 
void asciichat_thread_init (asciichat_thread_t *thread)
 Initialize a thread handle to an uninitialized state.
 
asciichat_error_t asciichat_thread_set_realtime_priority (void)
 Set the current thread to real-time priority.
 
asciichat_error_t thread_create_or_fail (asciichat_thread_t *thread, void *(*func)(void *), void *arg, const char *thread_name, uint32_t client_id)
 Create a thread with standardized error handling and logging.
 
int ascii_tls_key_create (tls_key_t *key, void(*destructor)(void *))
 Create a thread-local storage key.
 
int ascii_tls_key_delete (tls_key_t key)
 Delete a thread-local storage key.
 
void * ascii_tls_get (tls_key_t key)
 Get thread-local value for a key.
 
int ascii_tls_set (tls_key_t key, void *value)
 Set thread-local value for a key.
 
int platform_snprintf (char *str, size_t size, const char *format,...)
 Safe string formatting (snprintf replacement)
 
int platform_vsnprintf (char *str, size_t size, const char *format, va_list ap)
 Safe variable-argument string formatting.
 
char * platform_strdup (const char *s)
 Duplicate string (strdup replacement)
 
char * platform_strndup (const char *s, size_t n)
 Duplicate string with length limit (strndup replacement)
 
int platform_strcasecmp (const char *s1, const char *s2)
 Case-insensitive string comparison.
 
int platform_strncasecmp (const char *s1, const char *s2, size_t n)
 Case-insensitive string comparison with length limit.
 
char * platform_strtok_r (char *str, const char *delim, char **saveptr)
 Thread-safe string tokenization (strtok_r replacement)
 
size_t platform_strlcpy (char *dst, const char *src, size_t size)
 Safe string copy with size tracking (strlcpy)
 
size_t platform_strlcat (char *dst, const char *src, size_t size)
 Safe string concatenation with size tracking (strlcat)
 
int platform_strncpy (char *dst, size_t dst_size, const char *src, size_t count)
 Safe string copy with explicit size bounds (strncpy replacement)
 
void * platform_aligned_alloc (size_t alignment, size_t size)
 Allocate aligned memory.
 
void platform_aligned_free (void *ptr)
 Free aligned memory.
 
void platform_memory_barrier (void)
 Perform memory barrier/fence operation.
 
const char * platform_strerror (int errnum)
 Get thread-safe error string.
 
int platform_get_last_error (void)
 Get last platform error code.
 
void platform_set_last_error (int error)
 Set platform error code.
 
int platform_open (const char *pathname, int flags,...)
 Safe file open (open replacement)
 
FILE * platform_fopen (const char *filename, const char *mode)
 Safe file open stream (fopen replacement)
 
FILE * platform_fdopen (int fd, const char *mode)
 Convert file descriptor to stream (fdopen replacement)
 
ssize_t platform_read (int fd, void *buf, size_t count)
 Safe file read (read replacement)
 
int platform_close (int fd)
 Safe file close (close replacement)
 
int platform_unlink (const char *pathname)
 Delete/unlink file.
 
int platform_chmod (const char *pathname, int mode)
 Change file permissions/mode.
 

Variables

int errno
 

Cross-Platform Utility Functions

void platform_sleep_usec (unsigned int usec)
 High-precision sleep function with microsecond precision.
 
ssize_t platform_write (int fd, const void *buf, size_t count)
 Platform-safe write function.
 

Safe String Formatting Functions

int safe_snprintf (char *buffer, size_t buffer_size, const char *format,...)
 Safe version of snprintf that ensures null termination.
 
int safe_fprintf (FILE *stream, const char *format,...)
 Safe version of fprintf.
 
char * platform_strcat (char *dest, size_t dest_size, const char *src)
 Safe version of strcat with buffer size protection.
 
int safe_sscanf (const char *str, const char *format,...)
 Safe version of sscanf with validation.
 

Platform Detection Macros

#define PLATFORM_WINDOWS   0
 Platform detection: 1 on Windows, 0 on POSIX.
 
#define PLATFORM_POSIX   1
 Platform detection: 1 on POSIX, 0 on Windows.
 

Compiler Attribute Macros

#define PACKED_STRUCT_BEGIN
 Begin a packed structure (POSIX: no-op, uses attribute)
 
#define PACKED_STRUCT_END
 End a packed structure (POSIX: no-op, uses attribute)
 
#define PACKED_ATTR   __attribute__((packed))
 Packed structure attribute (POSIX: attribute((packed)))
 
#define ALIGNED_ATTR(x)   __attribute__((aligned(x)))
 Memory alignment attribute (POSIX: attribute((aligned)))
 

Thread-Local Storage and Alignment Macros

#define THREAD_LOCAL   __thread
 Thread-local storage keyword (POSIX: __thread)
 
#define ALIGNED_32   __attribute__((aligned(32)))
 32-byte alignment macro (POSIX: attribute((aligned(32))))
 
#define ALIGNED_16   __attribute__((aligned(16)))
 16-byte alignment macro (POSIX: attribute((aligned(16))))
 

Utility Macros

#define UNUSED(x)   ((void)(x))
 Suppress unused parameter warnings.
 

Mutex Locking Macros

#define mutex_lock(mutex)    (lock_debug_is_initialized() ? debug_mutex_lock(mutex, __FILE__, __LINE__, __func__) : mutex_lock_impl(mutex))
 Lock a mutex (with debug tracking in debug builds)
 
#define mutex_trylock(mutex)    (lock_debug_is_initialized() ? debug_mutex_trylock(mutex, __FILE__, __LINE__, __func__) : mutex_trylock_impl(mutex))
 Try to lock a mutex without blocking (with debug tracking in debug builds)
 
#define mutex_unlock(mutex)    (lock_debug_is_initialized() ? debug_mutex_unlock(mutex, __FILE__, __LINE__, __func__) : mutex_unlock_impl(mutex))
 Unlock a mutex (with debug tracking in debug builds)
 

Read-Write Lock Macros

#define rwlock_rdlock(lock)    (lock_debug_is_initialized() ? debug_rwlock_rdlock(lock, __FILE__, __LINE__, __func__) : rwlock_rdlock_impl(lock))
 Acquire a read lock (with debug tracking in debug builds)
 
#define rwlock_wrlock(lock)    (lock_debug_is_initialized() ? debug_rwlock_wrlock(lock, __FILE__, __LINE__, __func__) : rwlock_wrlock_impl(lock))
 Acquire a write lock (with debug tracking in debug builds)
 
#define rwlock_rdunlock(lock)    (lock_debug_is_initialized() ? debug_rwlock_rdunlock(lock, __FILE__, __LINE__, __func__) : rwlock_rdunlock_impl(lock))
 Release a read lock (with debug tracking in debug builds)
 
#define rwlock_wrunlock(lock)    (lock_debug_is_initialized() ? debug_rwlock_wrunlock(lock, __FILE__, __LINE__, __func__) : rwlock_wrunlock_impl(lock))
 Release a write lock (with debug tracking in debug builds)
 

Detailed Description

๐Ÿ”Œ Cross-platform abstractions for threading, sockets, and system calls, and hardware access

This header provides the main entry point for the platform abstraction layer, enabling ascii-chat to run seamlessly on Windows, Linux, and macOS with a unified API. It serves as the umbrella header that includes all platform abstraction components.

Purpose:

The abstraction layer eliminates platform-specific code (#ifdef _WIN32 blocks) from application code by providing a unified API. All platform differences are hidden behind this abstraction layer, making the application code completely platform-independent.

Usage:

// Include the main platform abstraction header
// Use unified API - works identically on all platforms
socket_t sock = socket_create(AF_INET, SOCK_STREAM, 0);
asciichat_thread_create(&thread, worker_func, arg);
๐Ÿ”Œ Cross-platform abstraction layer umbrella header for ascii-chat
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
socket_t socket_create(int domain, int type, int protocol)
Create a new socket.
pthread_t asciichat_thread_t
Thread handle type (POSIX: pthread_t)
int asciichat_thread_create(asciichat_thread_t *thread, void *(*func)(void *), void *arg)
Create a new thread.

Organization:

The abstraction layer is organized into modular components, each with its own header file. This header includes them all:

Platform Detection:

This header automatically detects the target platform using _WIN32 macro:

Compatibility Layer:

This header also provides platform compatibility macros and definitions:

Utility Macros:

Thread-Local Storage:

Provides unified thread-local storage support:

Initialization:

Before using platform functions, call platform_init() (required on Windows for Winsock initialization). See platform/system.h for details.

Example:
int main() {
// Initialize platform (required on Windows)
return 1;
}
// Use platform functions - no #ifdefs needed!
socket_t sock = socket_create(AF_INET, SOCK_STREAM, 0);
asciichat_thread_create(&thread, worker, NULL);
// Cleanup
return 0;
}
int main(int argc, char **argv)
Definition acds/main.c:55
@ ASCIICHAT_OK
Definition error_codes.h:48
void platform_cleanup(void)
Cleanup platform-specific subsystems.
asciichat_error_t platform_init(void)
Initialize platform-specific subsystems.
Note
This header includes all platform abstraction components. For fine-grained control, you can include individual component headers directly (e.g., platform/thread.h, platform/socket.h).
All platform-specific code (#ifdef _WIN32) is contained in this header and implementation files. Application code never needs platform conditionals.
See also
Platform Abstraction Layer for comprehensive documentation
platform/thread.h for threading primitives
platform/mutex.h for mutex operations
platform/socket.h for socket operations
platform/terminal.h for terminal I/O
platform/system.h for system functions
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header defines the ASCIICHAT_API macro used to control symbol visibility in shared libraries (DLLs on Windows, .so/.dylib on Unix).

Windows DLL Behavior:

Unix Behavior:

Usage:

// In header file (extern declaration):
extern ASCIICHAT_API int global_variable;
extern ASCIICHAT_API void my_function(void);
// In source file (definition):
ASCIICHAT_API int global_variable = 42;
ASCIICHAT_API void my_function(void) { ... }
#define ASCIICHAT_API
Export symbols on Unix platforms (Linux, macOS)
Definition api.h:82

CRITICAL: This header must have ZERO dependencies

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
2025

This header provides a unified condition variable interface that abstracts platform-specific implementations (Windows Condition Variables vs POSIX pthread condition variables).

The interface provides:

Note
On Windows, uses CONDITION_VARIABLE. On POSIX systems, uses pthread_cond_t.
Condition variables must be used with a mutex (mutex_t) for proper synchronization.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides unified file operations with consistent behavior across Windows and POSIX platforms.

The interface provides:

Note
File operations are declared in internal.h for internal use.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

Provides platform-independent file system functions including directory creation, file statistics, and type checking.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
December 2025

This header provides platform initialization functions and static initialization helpers for synchronization primitives that need to work before main().

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header contains PRIVATE implementation helpers and macros used ONLY by the platform abstraction layer implementation files.

IMPORTANT: This header must ONLY be included within lib/platform/ files. All external code should use platform/util.h for public platform utilities.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

Provides platform-independent memory functions including querying allocated block sizes for memory debugging and leak tracking.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
December 2025

This header provides a unified interface for memory-mapped files across platforms. Memory-mapped files allow treating file contents as memory, enabling efficient shared state and crash-safe logging.

The interface provides:

Platform implementations:

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
December 2025

This header provides a unified mutex interface that abstracts platform-specific implementations (Windows Critical Sections vs POSIX pthread mutexes).

The interface provides:

Note
On Windows, uses CRITICAL_SECTION for lightweight synchronization. On POSIX systems, uses pthread_mutex_t.
In debug builds, mutex_lock() and mutex_unlock() macros use lock debugging if enabled. In release builds, they call the implementation directly for zero overhead.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides a unified interface for agent communication (SSH agent, GPG agent) that abstracts platform-specific implementations:

The interface provides:

Note
On Windows, uses named pipes (CreateFileA, ReadFile, WriteFile). On POSIX systems, uses Unix domain sockets (socket, connect, read, write).
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
October 2025

Provides platform-independent process execution functions for running external programs (like ssh-keygen, gpg) and capturing their output.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
December 2025

Provides interactive prompting functionality across Windows, Linux, and macOS. Supports text input with optional echo, yes/no questions with defaults, and configurable answer placement (same line or next line).

All prompt functions handle terminal locking to prevent log interleaving, check for TTY availability, and support non-interactive mode detection.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
December 2025

This header provides a unified read-write lock interface that abstracts platform-specific implementations (Windows SRW Locks vs POSIX pthread read-write locks).

The interface provides:

Note
On Windows, uses SRWLOCK for lightweight synchronization. On POSIX systems, uses pthread_rwlock_t.
In debug builds, rwlock_rdlock(), rwlock_wrlock(), rwlock_rdunlock(), and rwlock_wrunlock() macros use lock debugging if enabled. In release builds, they call the implementation directly for zero overhead.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides a unified socket interface that abstracts platform-specific implementations (Windows Winsock2 vs POSIX sockets).

The interface provides:

Note
On Windows, uses Winsock2 API (SOCKET type). On POSIX systems, uses standard BSD sockets (int type).
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides safe string formatting and manipulation functions that satisfy clang-tidy cert-err33-c requirements. All functions ensure null termination and prevent buffer overflow, making them safe replacements for standard C library functions.

CORE FEATURES:

SAFETY GUARANTEES:

Note
All functions in this header are designed to be drop-in replacements for standard C library functions with additional safety guarantees.
Functions satisfy clang-tidy cert-err33-c requirements for safe string operations.
Return values should always be checked to ensure operations succeeded.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides a high-performance symbol resolution cache system for converting backtrace addresses to human-readable symbol names. The system caches resolved symbols to avoid expensive addr2line subprocess spawns on every backtrace operation.

CORE FEATURES:

PERFORMANCE BENEFITS:

Symbol resolution without caching requires:

With caching:

ARCHITECTURE:

The symbol cache uses a hashtable to store resolved symbols:

BATCH RESOLUTION:

For efficient backtrace processing:

STATISTICS TRACKING:

The system tracks:

These statistics enable performance monitoring and cache efficiency analysis.

THREAD SAFETY:

Note
The symbol cache significantly improves backtrace performance by avoiding repeated addr2line subprocess spawns for the same addresses.
Cached symbol strings are owned by the cache and should not be freed by callers. The cache manages memory automatically.
addr2line must be available in PATH for batch resolution to work.
Debug symbols must be available in the executable for resolution.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
October 2025

This header provides unified system functions including process management, environment variables, TTY operations, and signal handling.

The interface provides:

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides unified terminal I/O operations including ANSI escape sequences, cursor control, and terminal configuration.

The interface provides:

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides a unified thread interface that abstracts platform-specific implementations (Windows threads vs POSIX pthreads).

The interface provides:

Note
On Windows, uses HANDLE for thread representation. On POSIX systems, uses pthread_t.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides the public API for platform-specific utility functions that are needed by the main codebase. These utilities provide cross-platform implementations of common operations with consistent behavior across Windows, Linux, and macOS.

DESIGN PHILOSOPHY:

This header is the ONLY way to access platform implementation details from outside the platform/ directory. All platform-internal implementations remain private to the platform/ directory via platform/internal.h.

CORE FEATURES:

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
December 2025

This header provides a single point to include windows.h with proper alignment. The pragma pack ensures Windows SDK types get 8-byte alignment as expected, then immediately restores default packing so application structs are unaffected.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

This header provides POSIX-style errno constant definitions for Windows compatibility. Windows headers do not define these constants by default, so this header fills the gap to enable cross-platform code.

CORE FEATURES:

WINDOWS COMPATIBILITY:

Windows uses WSA errors for socket operations, but many functions also use standard errno. This header ensures standard errno constants are available on Windows for consistent error handling.

DEFINED CONSTANTS:

This header defines the following errno constants:

Note
This header must be included before any Windows headers that might define or use these constants.
All constants are protected with #ifndef guards to prevent redefinition errors.
On POSIX systems, these constants are typically defined in <errno.h>, but this header can still be included safely.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

Platform Abstraction Layer README

Overview

Welcome! This guide will help you understand the platform abstraction layerโ€”the unsung hero that makes ascii-chat work seamlessly across Windows, Linux, and macOS.

Here's the thing about cross-platform development: every operating system has its own way of doing things. Windows uses different APIs than Linux. macOS has its quirks. Writing code that works everywhere usually means lots of #ifdef _WIN32 scattered throughout your codebase, making it a mess to read and maintain.

But what if you could write your code once and have it just work everywhere? That's exactly what the platform abstraction layer does. It provides a unified APIโ€”one set of functions that work identically on all platforms. Under the hood, it translates your calls to the appropriate platform-specific APIs. You write clean, readable code, and the platform layer handles all the messy details.

Think of it like an adapter plug for international travel. You don't need to carry different devices for each countryโ€”you just bring one adapter that works everywhere. Same idea here!

What does the platform layer abstract?

  • Threading: Create threads, join them, get thread IDsโ€”same API everywhere
  • Synchronization: Mutexes, read-write locks, condition variables
  • Networking: Sockets that work the same on Windows and POSIX
  • Terminal I/O: Control the terminal, detect capabilities, raw mode
  • System functions: Environment variables, sleep, signals, crash handling
  • File I/O: Safe, portable file operations with proper error handling
  • String operations: Safe string handling across platforms

The Big Achievement: Zero platform-specific code (#ifdef blocks) in application code!

Supported Platforms: Windows 10+, Linux (POSIX), macOS (POSIX)

Design Philosophy

The platform abstraction layer follows a comprehensive philosophy that guides every design decision. Understanding these principles will help you use the platform layer effectively and understand why it works the way it does.

Complete Isolation

Principle: Platform-specific code is 100% isolated in implementation files.

This is the most important principleโ€”application code (src/) has ZERO #ifdef blocks for platform detection. All platform differences live in lib/platform/posix/ or lib/platform/windows/.

Why this matters:

// โŒ BAD - Platform checks scattered everywhere
#ifdef _WIN32
HANDLE thread;
CreateThread(...);
#else
pthread_t thread;
pthread_create(...);
#endif
// โœ… GOOD - Single API, works everywhere
asciichat_thread_create(&thread, thread_func, arg);

Benefits of complete isolation:

  • Clean separation: Platform bugs are fixed in one location
  • Better readability: Application code focuses on business logic
  • Easier testing: Can mock platform layer for unit tests
  • Maintainability: Changes to platform code don't affect application

Zero Overhead in Release Builds

Principle: Abstraction should add zero runtime cost in production.

The platform layer achieves this through:

  • Direct function calls (no function pointers or virtual dispatch)
  • Inline wrappers for simple operations
  • Compile-time selection of platform code

In release builds:

// This abstraction call:
mutex_lock(&my_mutex);
// Compiles directly to:
// Windows: EnterCriticalSection(&my_mutex.cs);
// POSIX: pthread_mutex_lock(&my_mutex.mutex);
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140

No runtime overhead, no performance penalty! The abstraction disappears at compile time.

Optional Debug Support

Principle: Enable extensive debugging without affecting production performance.

Debug builds can enable lock tracking, memory debugging, and verbose logging:

// Enable debug features at compile time
#define DEBUG_THREADS // Track thread creation/destruction
#define DEBUG_LOCKS // Track lock acquisition/release
#define DEBUG_MEMORY // Track allocations
// No runtime cost in release builds!

This gives you powerful debugging when you need it, zero cost when you don't.

POSIX-First API Design

Principle: Use POSIX semantics as the baseline, adapt Windows to match.

Why POSIX first?

  • POSIX is the standard for Unix-like systems (Linux, macOS, BSD)
  • Well-documented, stable APIs that have been around for decades
  • Most developers already know POSIX threading and socket APIs

The abstraction:

  1. Defines API based on POSIX semantics
  2. POSIX implementation is thin wrapper (often just delegation)
  3. Windows implementation adapts Windows APIs to POSIX semantics

Example:

// POSIX version (simple delegation)
int mutex_lock(mutex_t *mutex) {
return pthread_mutex_lock(&mutex->mutex);
}
// Windows version (adaptation)
int mutex_lock(mutex_t *mutex) {
EnterCriticalSection(&mutex->cs);
return 0; // Windows doesn't return error codes like POSIX
}
pthread_mutex_t mutex_t
Mutex type (POSIX: pthread_mutex_t)
Definition mutex.h:38

Strong Type Safety

Principle: Use distinct types to prevent platform-specific mistakes.

Platform types are completely opaque to application code:

// Platform-specific types (application never sees these)
typedef struct {
#ifdef _WIN32
HANDLE handle;
#else
pthread_t thread;
#endif
// Application just uses asciichat_thread_t
asciichat_thread_create(&my_thread, worker, NULL);

This prevents common mistakes like:

  • Comparing Windows HANDLE to -1 (wrong, should be NULL)
  • Comparing POSIX socket to NULL (wrong, should be -1)
  • Using platform-specific functions on wrong types

Static Initialization

Principle: Support global synchronization primitives with zero runtime init cost.

You can declare global mutexes, locks, and condition variables without explicit initialization functions:

// Global mutex - works on all platforms, no init() call needed
void critical_function() {
static_mutex_lock(&g_mutex);
// Critical section
static_mutex_unlock(&g_mutex);
}
#define STATIC_MUTEX_INIT
Definition init.h:107
Static mutex structure for global mutexes requiring static initialization.
Definition init.h:40

How it works:

  • POSIX: Uses compile-time initializers (PTHREAD_MUTEX_INITIALIZER)
  • Windows: Uses lazy initialization with atomic compare-exchange

Both approaches are thread-safe and require zero explicit initialization!

Consistent Semantics

Principle: The platform layer normalizes differences that don't matter to the application.

What gets normalized:

  • Type unification: Socket handles are socket_t everywhere
  • Error code normalization: Error codes use errno convention everywhere
  • Function signature normalization: Thread functions use void* (*)(void*)
  • Flag normalization: File operations use POSIX-style flags

Application code can assume consistent semantics without worrying about platform differences in function signatures, error handling, or data types.

Minimal Abstraction Surface

Principle: Expose only what's needed.

The platform layer doesn't try to abstract everything. Instead, it provides a focused API that covers only what ascii-chat needs:

  • Threading (thread creation, joining, detaching)
  • Synchronization (mutexes, rwlocks, condition variables)
  • Networking (sockets with auto-optimization)
  • Terminal I/O (raw mode, cursor control, size detection)
  • System utilities (environment, strings, error handling)
  • Debugging support (backtraces, symbol resolution)

Complex platform features that ascii-chat doesn't use (e.g., Windows COM, macOS CoreFoundation, Linux-specific ioctls) are left out entirely. This keeps the abstraction layer small, maintainable, and focused.

Benefits of minimalism:

  • Faster compilation: Fewer headers, smaller API surface
  • Easier maintenance: Less code to maintain, fewer edge cases
  • Better focus: API designed for ascii-chat's specific needs

Architecture

The platform abstraction layer uses a three-layer architecture that keeps everything clean and organized.

Three-Layer Design

Layer 1: Header Definition (lib/platform/*.h)

  • Defines the public API that application code uses
  • Declares abstract types (asciichat_thread_t, mutex_t, socket_t, etc.)
  • Documents all functions with Doxygen comments
  • Completely platform-agnostic

Layer 2: POSIX Implementation (lib/platform/posix/*.c)

  • Implements the API using POSIX standards
  • Thin wrappers around pthread, BSD sockets, termios
  • Works on Linux, macOS, BSD, and other Unix-like systems

Layer 3: Windows Implementation (lib/platform/windows/*.c)

  • Implements the same API using Windows APIs
  • Adapts Windows semantics to match POSIX behavior
  • Uses Windows threads, Critical Sections, Winsock2, Console API

The build system automatically selects Layer 2 or Layer 3 based on the target platform.

Directory Structure

lib/platform/
โ”œโ”€โ”€ README.md # Platform abstraction documentation
โ”œโ”€โ”€ abstraction.h # Main header - includes everything
โ”œโ”€โ”€ abstraction.c # Common implementation (minimal)
โ”œโ”€โ”€ init.h # Static initialization helpers
โ”œโ”€โ”€ util.h # Public utilities (ssize_t, aligned memory, errors, files)
โ”œโ”€โ”€ internal.h # Private helpers (ONLY for lib/platform/* files)
โ”‚
โ”œโ”€โ”€ thread.h # Thread API definition
โ”œโ”€โ”€ mutex.h # Mutex API definition
โ”œโ”€โ”€ rwlock.h # Read-write lock API definition
โ”œโ”€โ”€ cond.h # Condition variable API definition
โ”œโ”€โ”€ socket.h # Socket API definition
โ”œโ”€โ”€ terminal.h # Terminal I/O API definition
โ”œโ”€โ”€ system.h # System functions API definition
โ”œโ”€โ”€ string.h # String operations API definition
โ”œโ”€โ”€ file.h # File I/O API definition
โ”œโ”€โ”€ password.h # Password input API definition
โ”‚
โ”œโ”€โ”€ posix/ # POSIX implementations (Linux/macOS)
โ”‚ โ”œโ”€โ”€ thread.c
โ”‚ โ”œโ”€โ”€ mutex.c
โ”‚ โ”œโ”€โ”€ rwlock.c
โ”‚ โ”œโ”€โ”€ cond.c
โ”‚ โ”œโ”€โ”€ socket.c
โ”‚ โ”œโ”€โ”€ terminal.c
โ”‚ โ”œโ”€โ”€ system.c
โ”‚ โ””โ”€โ”€ symbols.c # Backtrace using execinfo.h
โ”‚
โ””โ”€โ”€ windows/ # Windows implementations
โ”œโ”€โ”€ thread.c # Most complex (2500+ lines!)
โ”œโ”€โ”€ mutex.c
โ”œโ”€โ”€ rwlock.c
โ”œโ”€โ”€ cond.c
โ”œโ”€โ”€ socket.c
โ”œโ”€โ”€ terminal.c
โ”œโ”€โ”€ system.c
โ”œโ”€โ”€ symbols.c # Backtrace using StackWalk64
โ”œโ”€โ”€ getopt.c # POSIX getopt for Windows
โ”œโ”€โ”€ windows_compat.h # Windows.h wrapper
โ””โ”€โ”€ windows_errno.h # POSIX errno on Windows

Header Organization

Most application code only needs to include one header:

#include "platform/abstraction.h" // Includes everything you need

For specific components, you can include individual headers:

#include "platform/thread.h" // Just threading
#include "platform/socket.h" // Just sockets
#include "platform/terminal.h" // Just terminal I/O
๐Ÿงต Cross-platform thread interface for ascii-chat
Cross-platform socket interface for ascii-chat.
๐Ÿ–ฅ๏ธ Cross-platform terminal interface for ascii-chat

The abstraction.h header automatically includes all component headers, so you get the complete platform abstraction API in one include.

Note
Important: platform/internal.h is a private header that MUST only be included within the lib/platform/ directory. External code should never include it directly. All public utilities are available through platform/util.h, platform/abstraction.h, or specific component headers like platform/socket.h.

Core Components

The platform abstraction is organized into focused components, each handling a specific aspect of cross-platform development.

Threading (thread.h)

Thread creation, joining, and management with timeout support.

Basic thread operations:

void* worker_thread(void* arg) {
int* value = (int*)arg;
printf("Worker processing: %d\n", *value);
return NULL;
}
int main() {
// Create thread
int data = 42;
int result = asciichat_thread_create(&thread, worker_thread, &data);
if (result != 0) {
log_error("Thread creation failed");
return 1;
}
// Wait for thread to finish
asciichat_thread_join(&thread, NULL);
return 0;
}
#define log_error(...)
Log an ERROR message.
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
Wait for a thread to complete (blocking)

Thread operations with timeout:

// Wait up to 5 seconds for thread to complete
int result = asciichat_thread_join_timeout(&thread, NULL, 5000);
if (result == ETIMEDOUT) {
log_warn("Thread did not finish in time");
// Thread is still running!
} else if (result == 0) {
log_info("Thread completed successfully");
}
#define log_warn(...)
Log a WARN message.
#define log_info(...)
Log an INFO message.
#define ETIMEDOUT
int asciichat_thread_join_timeout(asciichat_thread_t *thread, void **retval, uint32_t timeout_ms)
Wait for a thread to complete with timeout.

Thread identity and comparison:

// Get current thread ID
// Compare thread IDs
if (asciichat_thread_equal(my_id, other_id)) {
log_debug("Same thread");
}
// Get numeric thread ID for logging
log_debug("Thread ID: %" PRIu64, tid);
unsigned long long uint64_t
Definition common.h:59
uint64_t asciichat_thread_current_id(void)
#define log_debug(...)
Log a DEBUG message.
thread_id_t asciichat_thread_self(void)
Get the current thread's ID.
int asciichat_thread_equal(thread_id_t t1, thread_id_t t2)
Compare two thread IDs for equality.
pthread_t thread_id_t
Thread ID type (POSIX: pthread_t)

Important platform differences:

  • Windows: Timeout join fully supported
  • macOS: Timeout join falls back to blocking (no pthread_timedjoin_np)
  • Linux: Full timeout join support via pthread_timedjoin_np

Mutexes (mutex.h)

Mutual exclusion locks for protecting shared data.

Basic mutex usage:

typedef struct {
mutex_t lock;
int counter;
} shared_data_t;
void increment_counter(shared_data_t* data) {
mutex_lock(&data->lock);
data->counter++;
mutex_unlock(&data->lock);
}
int main() {
shared_data_t data;
mutex_init(&data.lock);
data.counter = 0;
// Use the data with multiple threads...
mutex_destroy(&data.lock);
return 0;
}
int mutex_init(mutex_t *mutex)
Initialize a mutex.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.

Try-lock (non-blocking):

int result = mutex_trylock(&data->lock);
if (result == 0) {
// Lock acquired!
data->counter++;
mutex_unlock(&data->lock);
} else {
// Lock was busy, couldn't acquire it
log_debug("Lock busy, skipping operation");
}
#define mutex_trylock(mutex)
Try to lock a mutex without blocking (with debug tracking in debug builds)
Definition mutex.h:157

Static initialization for global mutexes:

// Global mutex - no explicit init needed!
void update_config(const char* key, const char* value) {
static_mutex_lock(&g_config_lock);
// Update configuration
static_mutex_unlock(&g_config_lock);
}

Platform implementations:

  • POSIX: pthread_mutex_t with error checking enabled
  • Windows: CRITICAL_SECTION with 4000 spin count for performance

Read-Write Locks (rwlock.h)

Read-write locks allow multiple concurrent readers but only one writer.

Why use read-write locks?

  • Performance: Multiple threads can read simultaneously
  • Correctness: Writers get exclusive access for modifications
  • Scalability: Ideal for read-heavy workloads

Basic usage:

typedef struct {
rwlock_t lock;
char* data;
} shared_resource_t;
// Multiple readers can access simultaneously
const char* read_data(shared_resource_t* resource) {
rwlock_rdlock(&resource->lock);
const char* result = resource->data; // Read operation
rwlock_rdunlock(&resource->lock);
return result;
}
// Writers get exclusive access
void write_data(shared_resource_t* resource, const char* new_data) {
rwlock_wrlock(&resource->lock);
free(resource->data);
resource->data = strdup(new_data); // Write operation
rwlock_wrunlock(&resource->lock);
}
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:249
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:194
pthread_rwlock_t rwlock_t
Read-write lock type (POSIX: pthread_rwlock_t)
Definition rwlock.h:40
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:213
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:231

Real-world example from ascii-chat:

// Global client list protected by read-write lock
static client_t** g_clients = NULL;
static size_t g_client_count = 0;
// Broadcasting - many threads read concurrently
void broadcast_frame(const frame_t* frame) {
static_rwlock_rdlock(&g_client_manager_rwlock);
for (size_t i = 0; i < g_client_count; i++) {
send_frame_to_client(g_clients[i], frame);
}
static_rwlock_unlock(&g_client_manager_rwlock);
}
// Adding client - requires exclusive write access
void add_client(client_t* client) {
static_rwlock_wrlock(&g_client_manager_rwlock);
g_clients = realloc(g_clients, (g_client_count + 1) * sizeof(client_t*));
g_clients[g_client_count++] = client;
static_rwlock_unlock(&g_client_manager_rwlock);
}
#define STATIC_RWLOCK_INIT
Definition init.h:108
rwlock_t g_client_manager_rwlock
Reader-writer lock protecting the global client manager.
int add_client(server_context_t *server_ctx, socket_t socket, const char *client_ip, int port)
Frame structure that stores both data and actual size.
Definition ringbuffer.h:382
Static reader-writer lock structure for global rwlocks requiring static initialization.
Definition init.h:64

Platform implementations:

  • POSIX: pthread_rwlock_t with standard semantics
  • Windows: SRWLOCK (Slim Reader-Writer Lock) - only 8 bytes!

Important note on Windows:

  • Windows SRWLocks don't distinguish between read/write unlock
  • Both rwlock_rdunlock() and rwlock_wrunlock() call ReleaseSRWLock*()
  • Use the explicit unlock functions for code clarity anyway

Condition Variables (cond.h)

Condition variables enable threads to wait for specific conditions to become true.

Basic pattern:

typedef struct {
mutex_t lock;
cond_t cond;
bool ready;
} sync_t;
// Thread 1: Wait for condition
void wait_for_ready(sync_t* sync) {
mutex_lock(&sync->lock);
while (!sync->ready) {
cond_wait(&sync->cond, &sync->lock); // Atomically unlocks and waits
}
// Condition is now true, lock is held
mutex_unlock(&sync->lock);
}
// Thread 2: Signal condition
void set_ready(sync_t* sync) {
mutex_lock(&sync->lock);
sync->ready = true;
cond_signal(&sync->cond); // Wake up one waiter
mutex_unlock(&sync->lock);
}
int cond_signal(cond_t *cond)
Signal a condition variable (wake one waiting thread)
pthread_cond_t cond_t
Condition variable type (POSIX: pthread_cond_t)
Definition cond.h:38
int cond_wait(cond_t *cond, mutex_t *mutex)
Wait on a condition variable (blocking)

Timeout waiting:

mutex_lock(&sync->lock);
while (!sync->ready) {
int result = cond_timedwait(&sync->cond, &sync->lock, 5000); // 5 seconds
if (result == ETIMEDOUT) {
log_warn("Timeout waiting for condition");
break;
}
}
mutex_unlock(&sync->lock);
int cond_timedwait(cond_t *cond, mutex_t *mutex, int timeout_ms)
Wait on a condition variable with timeout.

Broadcasting to multiple waiters:

// Wake up ALL waiting threads
mutex_lock(&sync->lock);
sync->ready = true;
cond_broadcast(&sync->cond); // Wake everyone
mutex_unlock(&sync->lock);
int cond_broadcast(cond_t *cond)
Broadcast to a condition variable (wake all waiting threads)

Static initialization:

// Global condition variable
static bool g_shutdown = false;
void wait_for_shutdown() {
static_mutex_lock(&g_shutdown_lock);
while (!g_shutdown) {
static_cond_wait(&g_shutdown_cond, &g_shutdown_lock);
}
static_mutex_unlock(&g_shutdown_lock);
}
void trigger_shutdown() {
static_mutex_lock(&g_shutdown_lock);
g_shutdown = true;
static_cond_broadcast(&g_shutdown_cond);
static_mutex_unlock(&g_shutdown_lock);
}
#define STATIC_COND_INIT
Definition init.h:109
static_cond_t g_shutdown_cond
Global shutdown condition variable for waking blocked threads.
Static condition variable structure for global condition variables requiring static initialization.
Definition init.h:88

Sockets (socket.h)

Network socket operations with automatic optimization and error handling.

Basic server:

int main() {
platform_init(); // Initializes Winsock on Windows
// Create socket
socket_t server_sock = socket_create(AF_INET, SOCK_STREAM, 0);
if (!socket_is_valid(server_sock)) {
log_error("Socket creation failed: %s", socket_get_error_string());
return 1;
}
// Set socket options
socket_set_reuseaddr(server_sock, true);
socket_set_nodelay(server_sock, true);
// Bind and listen
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(27224);
if (socket_bind(server_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
log_error("Bind failed: %s", socket_get_error_string());
socket_close(server_sock);
return 1;
}
socket_listen(server_sock, 10);
// Accept connections
socket_t client_sock = socket_accept(server_sock, NULL, NULL);
if (socket_is_valid(client_sock)) {
// Handle client...
socket_close(client_sock);
}
socket_close(server_sock);
platform_cleanup(); // Cleanup Winsock on Windows
return 0;
}
int socket_set_nodelay(socket_t sock, bool nodelay)
Set TCP_NODELAY socket option (disable Nagle's algorithm)
bool socket_is_valid(socket_t sock)
Check if a socket handle is valid.
int socket_close(socket_t sock)
Close a socket.
int socket_bind(socket_t sock, const struct sockaddr *addr, socklen_t addrlen)
Bind a socket to an address.
socket_t socket_accept(socket_t sock, struct sockaddr *addr, socklen_t *addrlen)
Accept an incoming connection.
const char * socket_get_error_string(void)
Get last socket error as string.
int socket_set_reuseaddr(socket_t sock, bool reuse)
Set SO_REUSEADDR socket option.
int socket_listen(socket_t sock, int backlog)
Listen for incoming connections.

Automatic socket optimization:

When you accept a connection, the platform layer automatically optimizes it:

socket_t client_sock = socket_accept(server_sock, NULL, NULL);
// Automatically applied:
// - TCP_NODELAY enabled (low latency)
// - 2MB send/recv buffers (falls back to 512KB, then 128KB)
// - 5s send timeout, 10s recv timeout
// - SO_KEEPALIVE enabled

You can override these settings if needed:

socket_set_nodelay(client_sock, false); // Re-enable Nagle
socket_setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, ...); // Custom timeout
int socket_setsockopt(socket_t sock, int level, int optname, const void *optval, socklen_t optlen)
Set socket option.

Non-blocking I/O:

// Make socket non-blocking
socket_set_nonblocking(client_sock, true);
// Now send/recv return immediately with EWOULDBLOCK if not ready
ssize_t sent = socket_send(client_sock, data, size, 0);
if (sent < 0) {
// Would block, try again later
}
}
int socket_set_nonblocking(socket_t sock, bool nonblocking)
Set socket to non-blocking mode.
#define EWOULDBLOCK
ssize_t socket_send(socket_t sock, const void *buf, size_t len, int flags)
Send data on a socket.
int socket_get_last_error(void)
Get last socket error code.

Polling multiple sockets:

struct pollfd fds[2];
fds[0].fd = socket_get_fd(server_sock);
fds[0].events = POLLIN;
fds[1].fd = socket_get_fd(client_sock);
fds[1].events = POLLIN | POLLOUT;
int ready = socket_poll(fds, 2, 1000); // 1 second timeout
if (ready > 0) {
if (fds[0].revents & POLLIN) {
// Server socket ready for accept
}
if (fds[1].revents & POLLIN) {
// Client socket ready for read
}
}
int socket_get_fd(socket_t sock)
Get the underlying file descriptor (POSIX compatibility)
int socket_poll(struct pollfd *fds, nfds_t nfds, int timeout)
Poll sockets for events.

Platform differences:

  • Windows: Requires socket_init() to initialize Winsock (done by platform_init())
  • POSIX: No initialization needed
  • Error codes: Automatically normalized to POSIX errno values

Terminal I/O (terminal.h)

Terminal control, cursor manipulation, and capability detection.

Basic terminal setup:

int main() {
// Get terminal size
if (terminal_get_size(&size) == 0) {
printf("Terminal: %dx%d\n", size.cols, size.rows);
}
// Check capabilities
printf("Terminal supports color!\n");
}
printf("Terminal supports Unicode!\n");
}
return 0;
}
bool terminal_supports_unicode(void)
Check if terminal supports unicode.
asciichat_error_t terminal_get_size(terminal_size_t *size)
Get terminal size.
bool terminal_supports_color(void)
Check if terminal supports color.
Terminal size structure.
Definition terminal.h:55
int cols
Number of columns (width) in terminal.
Definition terminal.h:57
int rows
Number of rows (height) in terminal.
Definition terminal.h:56

Raw mode for interactive applications:

// Enable raw mode (no line buffering, no echo)
// Now read individual keypresses
char c;
read(STDIN_FILENO, &c, 1);
printf("You pressed: %c\n", c);
// Restore normal mode
asciichat_error_t terminal_set_raw_mode(bool enable)
Set terminal to raw mode.
asciichat_error_t terminal_set_echo(bool enable)
Set terminal echo mode.

Cursor control:

// Hide cursor
// Move cursor to specific position (1-based)
terminal_move_cursor(10, 20); // Row 10, Column 20
// Save/restore cursor position
printf("Hello from top-left!");
terminal_restore_cursor(); // Back to original position
// Show cursor again
asciichat_error_t terminal_save_cursor(void)
Save cursor position.
asciichat_error_t terminal_move_cursor(int row, int col)
Move cursor to specified position.
asciichat_error_t terminal_restore_cursor(void)
Restore saved cursor position.
asciichat_error_t terminal_hide_cursor(int fd, bool hide)
Hide or show cursor.

Screen manipulation:

// Clear entire screen
// Set terminal title
terminal_set_title("ascii-chat v1.0");
// Ring the terminal bell
// Set scroll region (for double-buffering)
terminal_set_scroll_region(5, 20); // Rows 5-20 can scroll
// Reset terminal to default state
asciichat_error_t terminal_reset(int fd)
Reset terminal to default state.
asciichat_error_t terminal_set_title(const char *title)
Set terminal window title.
asciichat_error_t terminal_clear_screen(void)
Clear the terminal screen.
asciichat_error_t terminal_set_scroll_region(int top, int bottom)
Set scroll region.
asciichat_error_t terminal_ring_bell(void)
Ring terminal bell.

Platform-specific features:

  • Windows: Automatically enables ANSI escape sequences on Windows 10+
  • POSIX: Full termios support for all terminal control

System Functions (system.h)

System-level operations: sleep, signals, crash handlers, process info.

Sleep operations:

// Sleep for 1 second
// Short sleep for timing loops
platform_sleep_ms(16); // ~60 FPS
void platform_sleep_ms(unsigned int ms)
Sleep for a specified number of milliseconds.

Platform sleep precision:

  • Linux/macOS: Microsecond precision with nanosleep()
  • Windows: ~15ms minimum resolution (system timer granularity)

Process information:

// Get current process ID
int pid = platform_get_pid();
log_info("Process ID: %d", pid);
// Get current username
const char* user = platform_get_username();
log_info("Running as: %s", user);
int platform_get_pid(void)
Get the current process ID.
const char * platform_get_username(void)
Get the current username.

Environment variables:

// Get environment variable
const char* home = platform_getenv("HOME");
if (home) {
printf("Home directory: %s\n", home);
}
// Set environment variable
platform_setenv("MY_VAR", "my_value");
int platform_setenv(const char *name, const char *value)
Set an environment variable.
const char * platform_getenv(const char *name)
Get an environment variable value.

TTY detection:

// Check if stdout is a terminal
if (platform_isatty(STDOUT_FILENO)) {
printf("Running in a terminal\n");
const char* tty = platform_ttyname(STDOUT_FILENO);
printf("TTY device: %s\n", tty);
} else {
printf("Output is redirected\n");
}
int platform_isatty(int fd)
Check if a file descriptor is a terminal.
const char * platform_ttyname(int fd)
Get the name of the terminal associated with a file descriptor.

Signal handling:

// Signal handler function
void handle_sigint(int sig) {
log_info("Caught SIGINT, shutting down...");
g_shutdown = true;
}
// Register signal handler (thread-safe on all platforms)
platform_signal(SIGINT, handle_sigint);
signal_handler_t platform_signal(int sig, signal_handler_t handler)
Set a signal handler.

Crash handlers (automatic backtrace on crash):

// Crash handlers are automatically installed by platform_init()!
int main() {
platform_init(); // Installs crash handlers
// If the program crashes, you get a backtrace:
// *** CRASH DETECTED ***
// Signal: SIGSEGV (Segmentation fault)
//
// === BACKTRACE ===
// 0: main
// 1: some_function
// 2: another_function
// ================
return 0;
}

Manual backtrace (for debugging):

// Print backtrace right now
platform_print_backtrace(0); // 0 = don't skip any frames
// Get backtrace for custom processing
void* buffer[64];
int count = platform_backtrace(buffer, 64);
char** symbols = platform_backtrace_symbols(buffer, count);
for (int i = 0; i < count; i++) {
printf("%s\n", symbols[i]);
}
int platform_backtrace(void **buffer, int size)
Get a backtrace of the current call stack.
void platform_backtrace_symbols_free(char **strings)
Free symbol array returned by platform_backtrace_symbols()
void platform_print_backtrace(int skip_frames)
Print a backtrace of the current call stack.
char ** platform_backtrace_symbols(void *const *buffer, int size)
Convert backtrace addresses to symbol names.

Platform-specific signal support:

  • POSIX: Full signal support (SIGINT, SIGTERM, SIGWINCH, etc.)
  • Windows: Limited support (SIGINT, SIGTERM work but SIGWINCH is a no-op)

String Operations (string.h)

Safe string handling that works consistently across platforms.

Safe formatting:

// Safe snprintf (handles Windows _snprintf quirks)
char buffer[64];
platform_snprintf(buffer, sizeof(buffer), "Value: %d", 42);
// Always null-terminates, even on Windows
int platform_snprintf(char *str, size_t size, const char *format,...)
Safe string formatting (snprintf replacement)

Safe string copy:

// BSD strlcpy semantics on all platforms
char dest[32];
size_t copied = platform_strlcpy(dest, "Hello, world!", sizeof(dest));
// dest is guaranteed to be null-terminated
// copied is the length of the source string
size_t platform_strlcpy(char *dst, const char *src, size_t size)
Safe string copy with size tracking (strlcpy)

Safe string concatenation:

char buffer[64] = "Hello";
platform_strlcat(buffer, ", world!", sizeof(buffer));
// buffer is guaranteed to be null-terminated
size_t platform_strlcat(char *dst, const char *src, size_t size)
Safe string concatenation with size tracking (strlcat)

Case-insensitive comparison:

if (platform_strcasecmp("hello", "HELLO") == 0) {
printf("Strings are equal (case-insensitive)\n");
}
if (platform_strncasecmp("hello", "HELLO", 3) == 0) {
printf("First 3 characters match\n");
}
int platform_strncasecmp(const char *s1, const char *s2, size_t n)
Case-insensitive string comparison with length limit.
int platform_strcasecmp(const char *s1, const char *s2)
Case-insensitive string comparison.

String duplication:

// Duplicate entire string
char* copy = platform_strdup("Hello, world!");
printf("%s\n", copy);
free(copy);
// Duplicate first N characters
char* partial = platform_strndup("Hello, world!", 5);
printf("%s\n", partial); // "Hello"
free(partial);
char * platform_strndup(const char *s, size_t n)
Duplicate string with length limit (strndup replacement)
char * platform_strdup(const char *s)
Duplicate string (strdup replacement)

Thread-safe tokenization:

char input[] = "one,two,three";
char* saveptr;
char* token = platform_strtok_r(input, ",", &saveptr);
while (token) {
printf("Token: %s\n", token);
token = platform_strtok_r(NULL, ",", &saveptr);
}
char * platform_strtok_r(char *str, const char *delim, char **saveptr)
Thread-safe string tokenization (strtok_r replacement)

File I/O (file.h)

Platform-safe file operations.

Opening files:

// Open with platform-specific handling
int fd = platform_open("config.txt", O_RDWR | O_CREAT, 0600);
if (fd < 0) {
log_error("Failed to open file: %s", strerror(errno));
return -1;
}
int platform_open(const char *pathname, int flags,...)
Safe file open (open replacement)
int errno

Reading and writing:

char buffer[1024];
ssize_t bytes_read = platform_read(fd, buffer, sizeof(buffer));
if (bytes_read < 0) {
log_error("Read failed: %s", strerror(errno));
}
const char* data = "Hello, file!";
ssize_t bytes_written = platform_write(fd, data, strlen(data));
if (bytes_written < 0) {
log_error("Write failed: %s", strerror(errno));
}
ssize_t platform_read(int fd, void *buf, size_t count)
Safe file read (read replacement)
ssize_t platform_write(int fd, const void *buf, size_t count)
Platform-safe write function.
_Atomic uint64_t bytes_written
Definition mmap.c:40

Closing files:

if (platform_close(fd) < 0) {
log_error("Close failed: %s", strerror(errno));
}
int platform_close(int fd)
Safe file close (close replacement)

Force sync to disk:

// Ensure data is written to disk
if (platform_fsync(fd) < 0) {
log_error("Fsync failed: %s", strerror(errno));
}
int platform_fsync(int fd)
Synchronize a file descriptor to disk.

Static Initialization

One of the most powerful features of the platform abstraction layer is static initialization. You can declare global synchronization primitives without any explicit initialization code!

Why Static Initialization?

Traditional approach (error-prone):

// โŒ BAD - Requires explicit initialization
static pthread_mutex_t g_mutex;
static bool g_mutex_initialized = false;
void init() {
if (!g_mutex_initialized) {
pthread_mutex_init(&g_mutex, NULL);
g_mutex_initialized = true;
}
}
void use_mutex() {
if (!g_mutex_initialized) {
// Race condition! What if another thread is in init()?
init();
}
pthread_mutex_lock(&g_mutex);
// ...
}

Platform abstraction approach (correct):

// โœ… GOOD - No initialization needed!
void use_mutex() {
static_mutex_lock(&g_mutex); // Just works!
// ...
static_mutex_unlock(&g_mutex);
}

How It Works

The magic happens at different times on different platforms:

POSIX (Linux/macOS):

// Compile-time initialization
#define STATIC_MUTEX_INIT { .mutex = PTHREAD_MUTEX_INITIALIZER, .initialized = true }
// The mutex is fully initialized at compile time!

Windows:

// Runtime lazy initialization (but thread-safe!)
#define STATIC_MUTEX_INIT { .cs = {0}, .initialized = false }
int static_mutex_lock(static_mutex_t* mutex) {
if (!mutex->initialized) {
// Atomic compare-exchange ensures only one thread initializes
if (InterlockedCompareExchange(&mutex->initialized, 1, 0) == 0) {
InitializeCriticalSection(&mutex->cs);
mutex->initialized = 1;
} else {
// Another thread is initializing, wait for it
while (!mutex->initialized) {
Sleep(0); // Yield to other threads
}
}
}
EnterCriticalSection(&mutex->cs);
return 0;
}
volatile int initialized
Thread-safe initialization flag (POSIX: int for atomic operations)
Definition init.h:48

Both approaches are:

  • Thread-safe: No race conditions
  • Zero-overhead: No runtime cost on POSIX, minimal cost on Windows
  • Transparent: Same API on all platforms

Static Initialization Usage

Global mutex:

#include "platform/init.h"
static config_t* g_config = NULL;
void set_config(const char* key, const char* value) {
static_mutex_lock(&g_config_mutex);
// Update config
static_mutex_unlock(&g_config_mutex);
}
Platform initialization and static synchronization helpers.

Global read-write lock:

static cache_t* g_cache = NULL;
const char* cache_get(const char* key) {
static_rwlock_rdlock(&g_cache_lock);
const char* value = cache_lookup(g_cache, key);
static_rwlock_unlock(&g_cache_lock);
return value;
}
void cache_set(const char* key, const char* value) {
static_rwlock_wrlock(&g_cache_lock);
cache_insert(g_cache, key, value);
static_rwlock_unlock(&g_cache_lock);
}

Global condition variable:

static bool g_work_available = false;
void worker_thread() {
while (true) {
static_mutex_lock(&g_work_mutex);
while (!g_work_available) {
static_cond_wait(&g_work_cond, &g_work_mutex);
}
// Do work
g_work_available = false;
static_mutex_unlock(&g_work_mutex);
}
}
void submit_work() {
static_mutex_lock(&g_work_mutex);
g_work_available = true;
static_cond_signal(&g_work_cond);
static_mutex_unlock(&g_work_mutex);
}

Crash Handling

The platform abstraction layer automatically installs crash handlers that capture backtraces when your program crashes. This is invaluable for debugging!

Automatic Installation

Crash handlers are automatically installed when you call platform_init():

int main() {
platform_init(); // Installs crash handlers
// Your program runs...
return 0;
}

No additional setup needed - it just works!

Supported Crash Types

POSIX (Linux/macOS):

  • SIGSEGV - Segmentation fault (null pointer, buffer overflow)
  • SIGABRT - Abort signal (assertion failures, abort() calls)
  • SIGFPE - Floating point exception (divide by zero)
  • SIGILL - Illegal instruction
  • SIGBUS - Bus error (alignment issues)

Windows:

  • EXCEPTION_ACCESS_VIOLATION - Access violation (like SIGSEGV)
  • EXCEPTION_ARRAY_BOUNDS_EXCEEDED - Array bounds exceeded
  • EXCEPTION_DATATYPE_MISALIGNMENT - Data type misalignment
  • EXCEPTION_FLT_DIVIDE_BY_ZERO - Floating point divide by zero
  • EXCEPTION_FLT_INVALID_OPERATION - Floating point invalid operation
  • EXCEPTION_ILLEGAL_INSTRUCTION - Illegal instruction
  • EXCEPTION_INT_DIVIDE_BY_ZERO - Integer divide by zero
  • EXCEPTION_STACK_OVERFLOW - Stack overflow
  • C runtime signals: SIGABRT, SIGFPE, SIGILL

Output Format

When a crash occurs, you get a detailed report:

*** CRASH DETECTED ***
Signal: SIGSEGV (Segmentation fault)
=== BACKTRACE ===
0: handle_client (lib/network.c:456)
1: client_thread (src/server.c:234)
2: thread_wrapper (lib/platform/posix/thread.c:89)
3: start_thread
================

The backtrace shows:

  • Function names (when debug symbols are available)
  • Source file and line number (with debug symbols)
  • Call stack from crash point to program entry

Symbol Resolution

Symbol resolution (function names in backtraces) works best with debug symbols:

Debug builds (recommended for development):

cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build

Release builds (limited symbol info):

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

Platform-specific symbol support:

  • Linux: Uses backtrace_symbols() from execinfo.h
  • macOS: Uses backtrace_symbols() from execinfo.h
  • Windows: Uses StackWalk64() and SymFromAddr() with dbghelp.dll

Thread Safety

Crash handlers work across all threads in your application:

  • Windows: SetUnhandledExceptionFilter() is process-wide
  • POSIX: sigaction() with SA_SIGINFO works on all threads
  • Backtrace: Captures the call stack of the crashing thread

Example with multiple threads:

void* worker_thread(void* arg) {
int* ptr = NULL;
*ptr = 42; // CRASH! Null pointer dereference
return NULL;
}
int main() {
platform_init(); // Installs crash handlers
asciichat_thread_create(&thread, worker_thread, NULL);
asciichat_thread_join(&thread, NULL);
return 0;
}
// Output:
// *** CRASH DETECTED ***
// Signal: SIGSEGV (Segmentation fault)
//
// === BACKTRACE ===
// 0: worker_thread
// 1: thread_wrapper
// 2: start_thread
// ================

The crash handler correctly identifies the crashing thread and its stack!

Windows-Specific Details

Windows has some unique characteristics that the platform layer handles for you.

Winsock Initialization

Windows requires explicit initialization of the Winsock library before using sockets:

// Handled automatically by platform_init()
int main() {
platform_init(); // Calls WSAStartup() on Windows
// Now you can use sockets!
socket_t sock = socket_create(AF_INET, SOCK_STREAM, 0);
platform_cleanup(); // Calls WSACleanup() on Windows
return 0;
}

You never need to call WSAStartup() or WSACleanup() directly!

ANSI Escape Sequences

Modern Windows (Windows 10+) supports ANSI escape sequences, but they need to be explicitly enabled:

// Handled automatically by platform_init()
int main() {
platform_init(); // Calls terminal_enable_ansi() on Windows
// Now you can use ANSI colors!
printf("\033[31mRed text\033[0m\n");
printf("\033[32mGreen text\033[0m\n");
return 0;
}

The terminal functions automatically use Console API calls on older Windows versions.

Windows Lock Implementation

Windows uses different synchronization primitives than POSIX:

Mutexes (CRITICAL_SECTION):

// Implemented using CRITICAL_SECTION with spin count
typedef struct {
CRITICAL_SECTION cs;
int mutex_init(mutex_t* mutex) {
InitializeCriticalSectionAndSpinCount(&mutex->cs, 4000);
mutex->initialized = true;
return 0;
}
bool initialized
Definition mmap.c:36

Spin count of 4000 means the thread will spin 4000 times before sleeping, which improves performance for short-held locks.

Read-Write Locks (SRWLOCK):

// Slim Reader-Writer Lock - only 8 bytes!
typedef struct {
SRWLOCK lock;
int rwlock_rdlock(rwlock_t* lock) {
AcquireSRWLockShared(&lock->lock);
return 0;
}
int rwlock_wrlock(rwlock_t* lock) {
AcquireSRWLockExclusive(&lock->lock);
return 0;
}

SRW Locks are lightweight (8 bytes vs 40+ bytes for CRITICAL_SECTION) and very fast.

Condition Variables:

typedef struct {
CONDITION_VARIABLE cv;
int cond_wait(cond_t* cond, mutex_t* mutex) {
SleepConditionVariableCS(&cond->cv, &mutex->cs, INFINITE);
return 0;
}

Windows Thread Implementation

The Windows thread implementation is the most complex part of the platform layer (2500+ lines!) because it includes sophisticated crash handling:

Thread Creation Flow:

  1. Allocate thread wrapper structure
  2. Initialize symbol handler (first thread only)
  3. Create Windows thread with wrapper function
  4. Wrapper catches exceptions and generates backtraces
  5. Call user's thread function
  6. Clean up on exit

Exception Handling in Threads:

DWORD WINAPI thread_wrapper(LPVOID arg) {
thread_wrapper_arg_t* wrapper_arg = (thread_wrapper_arg_t*)arg;
__try {
// Initialize symbol handler
ensure_symbol_handler_initialized();
__try {
// Call user's thread function
void* result = wrapper_arg->start_routine(wrapper_arg->arg);
return (DWORD)(uintptr_t)result;
}
__except(exception_filter(GetExceptionInformation())) {
// Inner exception handler
}
}
__finally {
// Cleanup
}
}

This nested exception handling ensures crashes are caught and reported even in worker threads!

Windows Signal Limitations

Windows has limited signal support compared to POSIX:

Working signals:

  • SIGINT - Ctrl+C (works via SetConsoleCtrlHandler)
  • SIGTERM - Termination request (limited support)
  • SIGABRT - Abort signal (via C runtime)
  • SIGFPE - Floating point exception (via C runtime)
  • SIGILL - Illegal instruction (via C runtime)

Non-working signals:

  • SIGWINCH - Terminal resize (defined but does nothing)
  • Most other POSIX signals

The platform layer provides these as no-ops so your code compiles:

// This compiles on Windows but does nothing
platform_signal(SIGWINCH, resize_handler);

Use Windows Console API functions like GetConsoleScreenBufferInfo() to detect terminal size changes on Windows.

Windows Backtrace Implementation

Windows backtraces use the DbgHelp library with multiple fallback strategies:

Primary method (StackWalk64):

STACKFRAME64 frame = {0};
frame.AddrPC.Offset = context.Rip; // Instruction pointer
frame.AddrStack.Offset = context.Rsp; // Stack pointer
frame.AddrFrame.Offset = context.Rbp; // Frame pointer
while (StackWalk64(IMAGE_FILE_MACHINE_AMD64, process, thread,
&frame, &context, NULL, SymFunctionTableAccess64,
SymGetModuleBase64, NULL)) {
// Resolve symbol for frame.AddrPC.Offset
}

Fallback methods:

  1. Manual stack walking using frame pointers
  2. Symbol resolution using addr2line.exe
  3. Raw addresses if symbol resolution fails

This multi-strategy approach ensures you get the best possible backtrace even when debug symbols are missing or DbgHelp has issues.

POSIX-Specific Details

POSIX implementation is generally simpler because the platform abstraction API is based on POSIX semantics.

Thin Delegation

Most POSIX functions are thin wrappers:

// Thread creation - simple delegation
int asciichat_thread_create(asciichat_thread_t* thread, void* (*func)(void*), void* arg) {
return pthread_create(&thread->thread, NULL, func, arg);
}
// Mutex locking - simple delegation
int mutex_lock(mutex_t* mutex) {
return pthread_mutex_lock(&mutex->mutex);
}

This keeps the abstraction lightweight and efficient on POSIX platforms.

Full Signal Support

POSIX platforms have complete signal support:

// All signals work as expected
platform_signal(SIGINT, sigint_handler); // Ctrl+C
platform_signal(SIGTERM, sigterm_handler); // Termination
platform_signal(SIGWINCH, resize_handler); // Terminal resize
platform_signal(SIGUSR1, user_handler); // User-defined signal

The platform layer uses sigaction() for thread-safe signal handling:

struct sigaction new_action, old_action;
new_action.sa_handler = handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = SA_RESTART; // Restart interrupted syscalls
sigaction(sig, &new_action, &old_action);
return old_action.sa_handler;
}
void(* signal_handler_t)(int)
Signal handler function type.
Definition system.h:44

POSIX Backtrace

POSIX platforms use the standard execinfo.h backtrace API:

int platform_backtrace(void** buffer, int size) {
return backtrace(buffer, size);
}
char** platform_backtrace_symbols(void* const* buffer, int size) {
return backtrace_symbols(buffer, size);
}

Symbol resolution works automatically if:

  • Debug symbols are included in the binary
  • Binary is compiled with frame pointers (-fno-omit-frame-pointer)

macOS-Specific Notes

macOS is mostly POSIX-compliant but has a few quirks:

Thread timeout join: macOS doesn't have pthread_timedjoin_np(), so timeouts fall back to blocking:

int asciichat_thread_join_timeout(asciichat_thread_t* thread, void** retval, int timeout_ms) {
#ifdef __APPLE__
// No timeout support, fall back to blocking join
return pthread_join(thread->thread, retval);
#else
// Linux has pthread_timedjoin_np
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
return pthread_timedjoin_np(thread->thread, retval, &ts);
#endif
}

Type name conflicts: macOS system headers define thread_t, so we use asciichat_thread_t to avoid conflicts:

// โŒ BAD - Conflicts with mach/mach_types.h
typedef struct { pthread_t thread; } thread_t;
// โœ… GOOD - Unique name avoids conflicts
typedef struct { pthread_t thread; } asciichat_thread_t;

Complete Examples

Here are some complete, real-world examples of using the platform abstraction layer.

Multi-Threaded Server

A simple echo server that handles multiple clients using threads:

#include <stdio.h>
#include <string.h>
typedef struct {
socket_t client_sock;
int client_id;
void* handle_client(void* arg) {
char buffer[1024];
printf("Client %d connected\n", info->client_id);
while (1) {
ssize_t received = socket_recv(info->client_sock, buffer, sizeof(buffer) - 1, 0);
if (received <= 0) break;
buffer[received] = '\0';
printf("Client %d: %s", info->client_id, buffer);
// Echo back to client
socket_send(info->client_sock, buffer, received, 0);
}
printf("Client %d disconnected\n", info->client_id);
socket_close(info->client_sock);
free(info);
return NULL;
}
int main() {
// Create listening socket
socket_t server_sock = socket_create(AF_INET, SOCK_STREAM, 0);
socket_set_reuseaddr(server_sock, true);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(8080);
socket_bind(server_sock, (struct sockaddr*)&addr, sizeof(addr));
socket_listen(server_sock, 10);
printf("Server listening on port 8080\n");
// Accept clients and spawn threads
int client_id = 0;
while (1) {
socket_t client_sock = socket_accept(server_sock, NULL, NULL);
if (!socket_is_valid(client_sock)) continue;
client_info_t* info = malloc(sizeof(client_info_t));
info->client_sock = client_sock;
info->client_id = ++client_id;
if (asciichat_thread_create(&thread, handle_client, info) != 0) {
fprintf(stderr, "Failed to create thread\n");
socket_close(client_sock);
free(info);
}
}
socket_close(server_sock);
return 0;
}
ssize_t socket_recv(socket_t sock, void *buf, size_t len, int flags)
Receive data from a socket.
struct client_info client_info_t
Per-client state structure for server-side client management.
Per-client state structure for server-side client management.
atomic_uint client_id

Producer-Consumer Pattern

Classic producer-consumer with condition variables:

#include <stdio.h>
#include <stdlib.h>
#define QUEUE_SIZE 10
typedef struct {
int items[QUEUE_SIZE];
int count;
int head;
int tail;
mutex_t lock;
cond_t not_empty;
cond_t not_full;
} queue_t;
void queue_init(queue_t* q) {
q->count = 0;
q->head = 0;
q->tail = 0;
mutex_init(&q->lock);
cond_init(&q->not_empty);
cond_init(&q->not_full);
}
void queue_push(queue_t* q, int item) {
mutex_lock(&q->lock);
while (q->count == QUEUE_SIZE) {
cond_wait(&q->not_full, &q->lock);
}
q->items[q->tail] = item;
q->tail = (q->tail + 1) % QUEUE_SIZE;
q->count++;
cond_signal(&q->not_empty);
mutex_unlock(&q->lock);
}
int queue_pop(queue_t* q) {
mutex_lock(&q->lock);
while (q->count == 0) {
cond_wait(&q->not_empty, &q->lock);
}
int item = q->items[q->head];
q->head = (q->head + 1) % QUEUE_SIZE;
q->count--;
cond_signal(&q->not_full);
mutex_unlock(&q->lock);
return item;
}
void* producer(void* arg) {
queue_t* q = (queue_t*)arg;
for (int i = 0; i < 100; i++) {
queue_push(q, i);
printf("Produced: %d\n", i);
}
return NULL;
}
void* consumer(void* arg) {
queue_t* q = (queue_t*)arg;
for (int i = 0; i < 100; i++) {
int item = queue_pop(q);
printf("Consumed: %d\n", item);
}
return NULL;
}
int main() {
queue_t queue;
queue_init(&queue);
asciichat_thread_t prod_thread, cons_thread;
asciichat_thread_create(&prod_thread, producer, &queue);
asciichat_thread_create(&cons_thread, consumer, &queue);
asciichat_thread_join(&prod_thread, NULL);
asciichat_thread_join(&cons_thread, NULL);
mutex_destroy(&queue.lock);
cond_destroy(&queue.not_empty);
cond_destroy(&queue.not_full);
return 0;
}
int cond_init(cond_t *cond)
Initialize a condition variable.
int cond_destroy(cond_t *cond)
Destroy a condition variable.

Interactive Terminal Application

Simple interactive menu using terminal I/O:

#include <stdio.h>
#include <unistd.h>
void display_menu() {
printf("โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—\n");
printf("โ•‘ Main Menu โ•‘\n");
printf("โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ\n");
printf("โ•‘ 1. Option One โ•‘\n");
printf("โ•‘ 2. Option Two โ•‘\n");
printf("โ•‘ 3. Option Three โ•‘\n");
printf("โ•‘ Q. Quit โ•‘\n");
printf("โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n");
printf("\nChoice: ");
fflush(stdout);
}
int main() {
// Check if we're in a terminal
if (!platform_isatty(STDIN_FILENO)) {
fprintf(stderr, "This program must be run in a terminal\n");
return 1;
}
// Set up terminal
// Main loop
while (1) {
display_menu();
char choice;
read(STDIN_FILENO, &choice, 1);
if (choice == 'q' || choice == 'Q') {
break;
}
switch (choice) {
case '1':
printf("You selected option 1!\n");
printf("Press any key to continue...");
read(STDIN_FILENO, &choice, 1);
break;
case '2':
printf("You selected option 2!\n");
printf("Press any key to continue...");
read(STDIN_FILENO, &choice, 1);
break;
case '3':
printf("You selected option 3!\n");
printf("Press any key to continue...");
read(STDIN_FILENO, &choice, 1);
break;
}
}
// Restore terminal
return 0;
}

Best Practices

Here are the golden rules for using the platform abstraction layer effectively.

Always Init and Cleanup

Rule: Always call platform_init() at program start and platform_cleanup() at exit.

// โœ… GOOD
int main() {
// Your program logic
return 0;
}
// โŒ BAD - Missing init/cleanup
int main() {
socket_t sock = socket_create(...); // May fail on Windows!
return 0;
}

Why this matters:

  • Windows requires Winsock initialization before using sockets
  • Crash handlers are installed by platform_init()
  • ANSI terminal support is enabled by platform_init()
  • Proper cleanup prevents resource leaks

Never Use Platform Ifdefs

Rule: Never use #ifdef _WIN32 or #ifdef __linux__ in application code.

// โŒ BAD - Platform-specific code in application
#ifdef _WIN32
HANDLE thread;
CreateThread(...);
#else
pthread_t thread;
pthread_create(...);
#endif
// โœ… GOOD - Use platform abstraction
asciichat_thread_create(&thread, func, arg);

If you find yourself writing platform-specific code:

  1. Check if the platform layer already provides what you need
  2. If not, add it to the platform layer (don't scatter ifdefs!)
  3. Keep all platform differences isolated in lib/platform/

Use Platform Types

Rule: Use platform abstraction types, not native types.

// โŒ BAD - Platform-specific types
pthread_t thread; // Only works on POSIX!
CRITICAL_SECTION cs; // Only works on Windows!
int sockfd; // Wrong on Windows (should be SOCKET)!
// โœ… GOOD - Platform abstraction types
asciichat_thread_t thread; // Works everywhere
mutex_t mutex; // Works everywhere
socket_t sock; // Works everywhere

Platform types are opaque - you don't need to know (or care) what they contain.

Check Return Values

Rule: Always check return values from platform functions.

// โŒ BAD - No error checking
asciichat_thread_create(&thread, func, arg);
socket_t sock = socket_create(AF_INET, SOCK_STREAM, 0);
socket_bind(sock, &addr, sizeof(addr));
// โœ… GOOD - Proper error checking
if (asciichat_thread_create(&thread, func, arg) != 0) {
log_error("Thread creation failed");
return ERROR_THREAD_CREATE;
}
socket_t sock = socket_create(AF_INET, SOCK_STREAM, 0);
if (!socket_is_valid(sock)) {
log_error("Socket creation failed: %s", socket_get_error_string());
return ERROR_SOCKET_CREATE;
}
if (socket_bind(sock, &addr, sizeof(addr)) < 0) {
log_error("Bind failed: %s", socket_get_error_string());
socket_close(sock);
return ERROR_SOCKET_BIND;
}

Platform functions return:

  • 0 on success, non-zero on error (for most functions)
  • INVALID_SOCKET_VALUE for invalid sockets (use socket_is_valid())
  • NULL for failed allocations

Prefer Static Init

Rule: Use static initialization for global synchronization primitives.

// โŒ BAD - Explicit initialization required
static mutex_t g_mutex;
static bool g_mutex_initialized = false;
void init() {
if (!g_mutex_initialized) {
mutex_init(&g_mutex);
g_mutex_initialized = true;
}
}
void use_mutex() {
if (!g_mutex_initialized) init();
mutex_lock(&g_mutex);
// ...
}
// โœ… GOOD - Static initialization, no init needed
void use_mutex() {
static_mutex_lock(&g_mutex); // Just works!
// ...
static_mutex_unlock(&g_mutex);
}

Static initialization is:

  • Thread-safe: No race conditions
  • Automatic: No explicit init calls needed
  • Clean: Less boilerplate code

Check Thread Creation Before Join

Rule: Only join threads that were successfully created.

// โŒ BAD - Joining uninitialized thread
asciichat_thread_create(&thread, func, arg); // What if this fails?
asciichat_thread_join(&thread, NULL); // Undefined behavior!
// โœ… GOOD - Check creation before join
bool thread_created = false;
if (asciichat_thread_create(&thread, func, arg) == 0) {
thread_created = true;
} else {
log_error("Thread creation failed");
}
if (thread_created) {
asciichat_thread_join(&thread, NULL);
}

Or use a helper function:

// Check if thread was initialized
asciichat_thread_join(&thread, NULL);
}
bool asciichat_thread_is_initialized(asciichat_thread_t *thread)
Check if a thread handle has been initialized.

Socket Validation

Rule: Use socket_is_valid() to check socket validity, not comparisons.

// โŒ BAD - Platform-specific checks
if (sock < 0) { ... } // Wrong on Windows!
if (sock == NULL) { ... } // Wrong on POSIX!
if (sock >= 0) { ... } // Wrong on Windows!
// โœ… GOOD - Platform-independent check
if (!socket_is_valid(sock)) {
log_error("Invalid socket");
}
if (socket_is_valid(sock)) {
// Socket is valid, use it
}

Why this matters:

  • POSIX: Invalid sockets are -1, valid sockets are >= 0
  • Windows: Invalid sockets are INVALID_SOCKET (typically ~0), valid sockets can be any value

Migration Guide

Converting existing code to use the platform abstraction layer is straightforward.

Migrating Threads

From POSIX pthreads:

// Before (POSIX-only)
#include <pthread.h>
pthread_t thread;
pthread_create(&thread, NULL, worker, arg);
pthread_join(thread, NULL);
// After (cross-platform)
asciichat_thread_create(&thread, worker, arg);
asciichat_thread_join(&thread, NULL);

From Windows threads:

// Before (Windows-only)
#include <windows.h>
HANDLE thread = CreateThread(NULL, 0, worker, arg, 0, NULL);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
// After (cross-platform)
asciichat_thread_create(&thread, worker, arg);
asciichat_thread_join(&thread, NULL);

Migrating Mutexes

From POSIX mutexes:

// Before
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
// After
mutex_t mutex;
mutex_init(&mutex);
mutex_lock(&mutex);
mutex_unlock(&mutex);
mutex_destroy(&mutex);

From Windows critical sections:

// Before
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
EnterCriticalSection(&cs);
LeaveCriticalSection(&cs);
DeleteCriticalSection(&cs);
// After
mutex_t mutex;
mutex_init(&mutex);
mutex_lock(&mutex);
mutex_unlock(&mutex);
mutex_destroy(&mutex);

Migrating Sockets

From POSIX sockets:

// Before
#include <sys/socket.h>
#include <netinet/in.h>
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sockfd, &addr, sizeof(addr));
listen(sockfd, 10);
int client = accept(sockfd, NULL, NULL);
close(sockfd);
// After
socket_t sockfd = socket_create(AF_INET, SOCK_STREAM, 0);
socket_bind(sockfd, &addr, sizeof(addr));
socket_listen(sockfd, 10);
socket_t client = socket_accept(sockfd, NULL, NULL);
socket_close(sockfd);

From Windows Winsock:

// Before
#include <winsock2.h>
WSADATA wsa;
WSAStartup(MAKEWORD(2,2), &wsa);
SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sockfd, &addr, sizeof(addr));
listen(sockfd, 10);
SOCKET client = accept(sockfd, NULL, NULL);
closesocket(sockfd);
WSACleanup();
// After
platform_init(); // Handles WSAStartup
socket_t sockfd = socket_create(AF_INET, SOCK_STREAM, 0);
socket_bind(sockfd, &addr, sizeof(addr));
socket_listen(sockfd, 10);
socket_t client = socket_accept(sockfd, NULL, NULL);
socket_close(sockfd);
platform_cleanup(); // Handles WSACleanup

Migrating Terminal I/O

From termios (POSIX):

// Before
#include <termios.h>
struct termios old_tio, new_tio;
tcgetattr(STDIN_FILENO, &old_tio);
new_tio = old_tio;
new_tio.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
// ...
tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
// After
// ...

Migrating Signal Handling

From POSIX signals:

// Before
#include <signal.h>
void handler(int sig) { /* ... */ }
signal(SIGINT, handler);
// After
void handler(int sig) { /* ... */ }
platform_signal(SIGINT, handler); // Thread-safe on all platforms!

Troubleshooting

Common issues and how to solve them.

Link Errors

Problem: Undefined reference to platform functions.

Solution: Make sure platform files are included in your build:

# CMakeLists.txt
if(WIN32)
set(PLATFORM_SOURCES
lib/platform/windows/thread.c
lib/platform/windows/mutex.c
# ... other Windows files
)
else()
set(PLATFORM_SOURCES
lib/platform/posix/thread.c
lib/platform/posix/mutex.c
# ... other POSIX files
)
endif()
add_executable(my_app main.c ${PLATFORM_SOURCES})

Socket Creation Fails

Problem: socket_create() returns invalid socket on Windows.

Solution: Call platform_init() before using sockets:

int main() {
platform_init(); // Must be called first on Windows!
socket_t sock = socket_create(AF_INET, SOCK_STREAM, 0);
// Now it works!
return 0;
}

Thread Join Crashes

Problem: Crash when calling asciichat_thread_join().

Solution: Check if thread was successfully created:

if (asciichat_thread_create(&thread, func, arg) == 0) {
// Only join if creation succeeded
asciichat_thread_join(&thread, NULL);
}

No Function Names in Backtrace

Problem: Backtrace shows addresses but no function names.

Solution: Build with debug symbols:

# Enable debug symbols
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build

On Linux, also install debug info:

# Debian/Ubuntu
sudo apt-get install libc6-dbg

Timeout Functions Don't Work on Windows

Problem: Timeout functions seem to block forever on Windows.

Solution: This is expected - some timeout functions fall back to blocking on certain platforms. Use a separate watchdog thread if you need guaranteed timeouts:

// Watchdog pattern for guaranteed timeout
typedef struct {
bool completed;
mutex_t lock;
} watchdog_t;
void* watchdog_func(void* arg) {
watchdog_t* wd = (watchdog_t*)arg;
platform_sleep_ms(5000); // 5 second timeout
mutex_lock(&wd->lock);
if (!wd->completed) {
log_warn("Thread did not complete in time!");
// Handle timeout
}
mutex_unlock(&wd->lock);
return NULL;
}

Performance Considerations

The platform abstraction layer is designed for minimal overhead, but there are still some performance characteristics to be aware of.

Zero Overhead in Release

Release builds have essentially zero abstraction overhead:

  • Direct function calls (no virtual dispatch)
  • Inlined wrappers for simple operations
  • Compile-time platform selection

Example assembly comparison (mutex lock on Linux):

; Direct pthread call
call pthread_mutex_lock
; Platform abstraction call (same!)
call mutex_lock
; mutex_lock implementation:
jmp pthread_mutex_lock ; Direct jump, no overhead

Debug Mode Overhead

Debug builds can enable tracking that has some overhead:

  • Lock tracking: ~5-10% overhead
  • Thread tracking: ~2-5% overhead
  • Memory tracking: ~10-20% overhead

This is acceptable in debug builds because:

  • You get valuable debugging information
  • Debug builds aren't used in production
  • The overhead helps find bugs early

Socket Auto-Optimization

The platform layer automatically optimizes accepted sockets:

  • TCP_NODELAY: Disables Nagle algorithm for low latency
  • Large buffers: 2MB send/recv buffers (falls back to 512KB, 128KB)
  • Timeouts: 5s send, 10s recv timeouts prevent hanging
  • Keepalive: Detects broken connections

These optimizations are specifically tuned for ascii-chat's video streaming use case. If you need different settings, you can override them:

socket_t client = socket_accept(server, NULL, NULL);
// Override auto-optimization
socket_set_nodelay(client, false); // Re-enable Nagle
socket_setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, ...); // Custom timeout

Sleep Precision

Platform sleep precision varies:

  • Linux: Microsecond precision with nanosleep()
  • macOS: Microsecond precision with nanosleep()
  • Windows: ~15ms minimum resolution (system timer granularity)

For Windows, use multimedia timers if you need better precision:

#ifdef _WIN32
timeBeginPeriod(1); // Request 1ms timer resolution
platform_sleep_ms(10); // Now actually sleeps ~10ms
timeEndPeriod(1); // Restore default resolution
#else
platform_sleep_ms(10); // Already precise
#endif

Contributing

Want to add new platform abstractions or improve existing ones?

Adding New Platform APIs

When adding a new platform abstraction:

  1. Define the interface in a new header (e.g., lib/platform/foo.h):
    #ifndef PLATFORM_FOO_H
    #define PLATFORM_FOO_H
    #include "platform/util.h"
    typedef struct foo_s foo_t;
    int foo_init(foo_t* foo);
    #endif
    Public platform utility API for string, memory, and file operations.
  2. Implement for POSIX (lib/platform/posix/foo.c):
    #include "platform/foo.h"
    #include <posix_foo.h> // POSIX-specific header
    struct foo_s {
    posix_foo_t native_foo;
    };
    int foo_init(foo_t* foo) {
    return posix_foo_init(&foo->native_foo);
    }
  3. Implement for Windows (lib/platform/windows/foo.c):
    #include "platform/foo.h"
    #include <windows.h>
    struct foo_s {
    HANDLE native_foo;
    };
    int foo_init(foo_t* foo) {
    foo->native_foo = CreateFoo(...);
    return (foo->native_foo != NULL) ? 0 : -1;
    }
  4. Add to abstraction.h:
    #include "platform/foo.h"
  5. Document thoroughly with Doxygen comments
  6. Write tests for all platforms

Writing Platform Tests

Platform abstraction tests should:

  • Test on all platforms (Windows, Linux, macOS)
  • Test edge cases and error conditions
  • Verify thread safety where applicable
  • Use Criterion test framework

Example test structure:

#include <criterion/criterion.h>
#include "platform/foo.h"
Test(foo, initialization) {
foo_t foo;
cr_assert_eq(foo_init(&foo), 0, "foo_init should succeed");
foo_destroy(&foo);
}
Test(foo, thread_safety) {
// Test concurrent access from multiple threads
}

Additional Resources

Want to learn more?

Platform Abstraction Documentation:

  • Platform README - Original documentation
  • abstraction.h - Main header file

Individual Component Documentation:

External Resources:

Related Topics:

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025

Macro Definition Documentation

◆ ALIGNED_16

#define ALIGNED_16   __attribute__((aligned(16)))

#include <abstraction.h>

16-byte alignment macro (POSIX: attribute((aligned(16))))

Definition at line 391 of file abstraction.h.

◆ ALIGNED_32

#define ALIGNED_32   __attribute__((aligned(32)))

#include <abstraction.h>

32-byte alignment macro (POSIX: attribute((aligned(32))))

Definition at line 386 of file abstraction.h.

◆ ALIGNED_ATTR

#define ALIGNED_ATTR (   x)    __attribute__((aligned(x)))

#include <abstraction.h>

Memory alignment attribute (POSIX: attribute((aligned)))

Parameters
xAlignment in bytes (must be power of 2)

Definition at line 263 of file abstraction.h.

◆ ASCIICHAT_API

#define ASCIICHAT_API   __attribute__((visibility("default")))

#include <api.h>

Export symbols on Unix platforms (Linux, macOS)

Uses GCC/Clang visibility attributes to explicitly export symbols. This is needed when building with -fvisibility=hidden for better performance and smaller symbol tables.

Definition at line 82 of file api.h.

◆ DIR_PERM_PRIVATE

#define DIR_PERM_PRIVATE   0700

#include <system.h>

Directory permission: Private (owner read/write/execute only)

Octal mode 0700: rwx---— Used for private directories like ~/.ascii-chat

Note
On Windows, this is a no-op (Windows uses ACLs instead of POSIX permissions)

Definition at line 649 of file system.h.

◆ EAGAIN

#define EAGAIN   11

#include <windows_errno.h>

Definition at line 81 of file windows_errno.h.

◆ EBADF

#define EBADF   9

#include <windows_errno.h>

Definition at line 77 of file windows_errno.h.

◆ ECONNREFUSED

#define ECONNREFUSED   111

#include <windows_errno.h>

Definition at line 93 of file windows_errno.h.

◆ ECONNRESET

#define ECONNRESET   104

#include <windows_errno.h>

Definition at line 105 of file windows_errno.h.

◆ EHOSTUNREACH

#define EHOSTUNREACH   113

#include <windows_errno.h>

Definition at line 101 of file windows_errno.h.

◆ EINTR

#define EINTR   4

#include <windows_errno.h>

Definition at line 73 of file windows_errno.h.

◆ EINVAL

#define EINVAL   22

#include <windows_errno.h>

Definition at line 61 of file windows_errno.h.

◆ ENETUNREACH

#define ENETUNREACH   101

#include <windows_errno.h>

Definition at line 97 of file windows_errno.h.

◆ ENOTSOCK

#define ENOTSOCK   88

#include <windows_errno.h>

Definition at line 109 of file windows_errno.h.

◆ EPIPE

#define EPIPE   32

#include <windows_errno.h>

Definition at line 89 of file windows_errno.h.

◆ ERANGE

#define ERANGE   34

#include <windows_errno.h>

Definition at line 65 of file windows_errno.h.

◆ ETIMEDOUT

#define ETIMEDOUT   110

#include <windows_errno.h>

Definition at line 69 of file windows_errno.h.

◆ EWOULDBLOCK

#define EWOULDBLOCK   11

#include <windows_errno.h>

Definition at line 85 of file windows_errno.h.

◆ FILE_PERM_MASK

#define FILE_PERM_MASK   0777

#include <system.h>

Permission mask for all permissions.

Octal mode 0777: rwxrwxrwx Used for masking permission bits (e.g., st_mode & 0777)

Definition at line 669 of file system.h.

◆ FILE_PERM_PRIVATE

#define FILE_PERM_PRIVATE   0600

#include <system.h>

File permission: Private (owner read/write only)

Octal mode 0600: rw----— Used for sensitive files like private keys, log files, and configuration files.

Note
On Windows, this is a no-op (Windows uses ACLs instead of POSIX permissions)

Definition at line 637 of file system.h.

◆ FILE_PERM_PUBLIC_READ

#define FILE_PERM_PUBLIC_READ   0644

#include <system.h>

File permission: Public read, owner write.

Octal mode 0644: rw-r–r– Used for files that should be readable by others but only writable by owner.

Definition at line 659 of file system.h.

◆ INVALID_PIPE_VALUE

#define INVALID_PIPE_VALUE   (-1)

#include <pipe.h>

Invalid pipe value (POSIX: -1)

Definition at line 42 of file pipe.h.

◆ INVALID_SOCKET_VALUE

#define INVALID_SOCKET_VALUE   (-1)

#include <socket.h>

Invalid socket value (POSIX: -1)

Definition at line 52 of file socket.h.

◆ mutex_lock

#define mutex_lock (   mutex)     (lock_debug_is_initialized() ? debug_mutex_lock(mutex, __FILE__, __LINE__, __func__) : mutex_lock_impl(mutex))

#include <mutex.h>

Lock a mutex (with debug tracking in debug builds)

Parameters
mutexPointer to mutex to lock

Locks the mutex, blocking if necessary until the lock is acquired.

Note
In debug builds, this macro includes lock debugging if initialized. In release builds, calls the implementation directly for zero overhead.

Definition at line 140 of file mutex.h.

141 : mutex_lock_impl(mutex))
int mutex_lock_impl(mutex_t *mutex)
Lock a mutex (implementation function)

◆ mutex_trylock

#define mutex_trylock (   mutex)     (lock_debug_is_initialized() ? debug_mutex_trylock(mutex, __FILE__, __LINE__, __func__) : mutex_trylock_impl(mutex))

#include <mutex.h>

Try to lock a mutex without blocking (with debug tracking in debug builds)

Parameters
mutexPointer to mutex to try to lock
Returns
0 if lock was acquired, non-zero if mutex was already locked
Note
In debug builds, this macro includes lock debugging if initialized. In release builds, calls the implementation directly for zero overhead.

Definition at line 157 of file mutex.h.

158 : mutex_trylock_impl(mutex))
int mutex_trylock_impl(mutex_t *mutex)
Try to lock a mutex without blocking (implementation function)

◆ mutex_unlock

#define mutex_unlock (   mutex)     (lock_debug_is_initialized() ? debug_mutex_unlock(mutex, __FILE__, __LINE__, __func__) : mutex_unlock_impl(mutex))

#include <mutex.h>

Unlock a mutex (with debug tracking in debug builds)

Parameters
mutexPointer to mutex to unlock

Unlocks the mutex. The mutex must be locked by the current thread.

Note
In debug builds, this macro includes lock debugging if initialized. In release builds, calls the implementation directly for zero overhead.

Definition at line 175 of file mutex.h.

176 : mutex_unlock_impl(mutex))
int mutex_unlock_impl(mutex_t *mutex)
Unlock a mutex (implementation function)

◆ PACKED_ATTR

#define PACKED_ATTR   __attribute__((packed))

#include <abstraction.h>

Packed structure attribute (POSIX: attribute((packed)))

On POSIX, use this attribute directly on structure definitions.

Example:
typedef struct {
uint8_t field1;
uint16_t field2;
} PACKED_ATTR packed_struct_t;
unsigned short uint16_t
Definition common.h:57
unsigned char uint8_t
Definition common.h:56
#define PACKED_ATTR
Packed structure attribute (POSIX: attribute((packed)))

Definition at line 256 of file abstraction.h.

◆ PACKED_STRUCT_BEGIN

#define PACKED_STRUCT_BEGIN

#include <abstraction.h>

Begin a packed structure (POSIX: no-op, uses attribute)

Definition at line 238 of file abstraction.h.

◆ PACKED_STRUCT_END

#define PACKED_STRUCT_END

#include <abstraction.h>

End a packed structure (POSIX: no-op, uses attribute)

Definition at line 240 of file abstraction.h.

◆ PATH_DELIM

#define PATH_DELIM   '/'

#include <system.h>

Platform-specific path separator character.

  • Windows: '\' (backslash)
  • Unix/POSIX: '/' (forward slash)

Use this constant instead of hardcoding separators or using #ifdef _WIN32.

Note
For string literals, use PATH_SEPARATOR_STR instead.

Definition at line 605 of file system.h.

◆ PATH_ENV_SEPARATOR

#define PATH_ENV_SEPARATOR   ":"

#include <system.h>

Platform-specific PATH environment variable separator.

  • Windows: ";" (semicolon)
  • Unix/POSIX: ":" (colon)

Definition at line 620 of file system.h.

◆ PATH_SEPARATOR_STR

#define PATH_SEPARATOR_STR   "/"

#include <system.h>

Definition at line 606 of file system.h.

◆ PLATFORM_ACCESS_EXISTS

#define PLATFORM_ACCESS_EXISTS   0

#include <system.h>

Access modes for platform_access()

Check if file/directory exists

Definition at line 786 of file system.h.

◆ PLATFORM_ACCESS_READ

#define PLATFORM_ACCESS_READ   4

#include <system.h>

Check if file/directory is readable.

Definition at line 788 of file system.h.

◆ PLATFORM_ACCESS_WRITE

#define PLATFORM_ACCESS_WRITE   2

#include <system.h>

Check if file/directory is writable.

Definition at line 787 of file system.h.

◆ PLATFORM_MAX_PATH_LENGTH

#define PLATFORM_MAX_PATH_LENGTH   4096

#include <system.h>

Maximum path length supported by the operating system.

Platform-specific values:

  • Windows: 32767 characters (extended-length path with \?\ prefix)
  • Linux: 4096 bytes (PATH_MAX from limits.h)
  • macOS: 1024 bytes (PATH_MAX from sys/syslimits.h)
Note
Windows legacy MAX_PATH (260) is too restrictive for modern use. We use the extended-length limit instead.

Definition at line 703 of file system.h.

◆ PLATFORM_O_APPEND [1/2]

#define PLATFORM_O_APPEND   O_APPEND

#include <file.h>

Append to end of file.

Definition at line 50 of file file.h.

◆ PLATFORM_O_APPEND [2/2]

#define PLATFORM_O_APPEND   O_APPEND

#include <util.h>

Append to end of file.

Definition at line 433 of file platform/util.h.

◆ PLATFORM_O_BINARY [1/2]

#define PLATFORM_O_BINARY   0

#include <file.h>

Open file in binary mode (Windows)

Definition at line 51 of file file.h.

◆ PLATFORM_O_BINARY [2/2]

#define PLATFORM_O_BINARY   0

#include <util.h>

Open file in binary mode (Windows)

Definition at line 434 of file platform/util.h.

◆ PLATFORM_O_CREAT [1/2]

#define PLATFORM_O_CREAT   O_CREAT

#include <file.h>

Create file if it doesn't exist.

Definition at line 47 of file file.h.

◆ PLATFORM_O_CREAT [2/2]

#define PLATFORM_O_CREAT   O_CREAT

#include <util.h>

Create file if it doesn't exist.

Definition at line 430 of file platform/util.h.

◆ PLATFORM_O_EXCL [1/2]

#define PLATFORM_O_EXCL   O_EXCL

#include <file.h>

Fail if file already exists (with O_CREAT)

Definition at line 48 of file file.h.

◆ PLATFORM_O_EXCL [2/2]

#define PLATFORM_O_EXCL   O_EXCL

#include <util.h>

Fail if file already exists (with O_CREAT)

Definition at line 431 of file platform/util.h.

◆ PLATFORM_O_RDONLY [1/2]

#define PLATFORM_O_RDONLY   O_RDONLY

#include <file.h>

Open file for reading only.

Definition at line 44 of file file.h.

◆ PLATFORM_O_RDONLY [2/2]

#define PLATFORM_O_RDONLY   O_RDONLY

#include <util.h>

Open file for reading only.

Definition at line 427 of file platform/util.h.

◆ PLATFORM_O_RDWR [1/2]

#define PLATFORM_O_RDWR   O_RDWR

#include <file.h>

Open file for reading and writing.

Definition at line 46 of file file.h.

◆ PLATFORM_O_RDWR [2/2]

#define PLATFORM_O_RDWR   O_RDWR

#include <util.h>

Open file for reading and writing.

Definition at line 429 of file platform/util.h.

◆ PLATFORM_O_TRUNC [1/2]

#define PLATFORM_O_TRUNC   O_TRUNC

#include <file.h>

Truncate file to zero length if it exists.

Definition at line 49 of file file.h.

◆ PLATFORM_O_TRUNC [2/2]

#define PLATFORM_O_TRUNC   O_TRUNC

#include <util.h>

Truncate file to zero length if it exists.

Definition at line 432 of file platform/util.h.

◆ PLATFORM_O_WRONLY [1/2]

#define PLATFORM_O_WRONLY   O_WRONLY

#include <file.h>

Open file for writing only.

Definition at line 45 of file file.h.

◆ PLATFORM_O_WRONLY [2/2]

#define PLATFORM_O_WRONLY   O_WRONLY

#include <util.h>

Open file for writing only.

Definition at line 428 of file platform/util.h.

◆ PLATFORM_POSIX

#define PLATFORM_POSIX   1

#include <abstraction.h>

Platform detection: 1 on POSIX, 0 on Windows.

Definition at line 166 of file abstraction.h.

◆ PLATFORM_WINDOWS

#define PLATFORM_WINDOWS   0

#include <abstraction.h>

Platform detection: 1 on Windows, 0 on POSIX.

Definition at line 164 of file abstraction.h.

◆ PROMPT_OPTS_DEFAULT

#define PROMPT_OPTS_DEFAULT   (prompt_opts_t){.echo = true, .same_line = false, .mask_char = 0}

#include <question.h>

Default prompt options (echo enabled, answer on next line)

Definition at line 40 of file question.h.

◆ PROMPT_OPTS_INLINE

#define PROMPT_OPTS_INLINE   (prompt_opts_t){.echo = true, .same_line = true, .mask_char = 0}

#include <question.h>

Prompt options for inline text input (echo enabled, same line)

Definition at line 50 of file question.h.

◆ PROMPT_OPTS_PASSWORD

#define PROMPT_OPTS_PASSWORD   (prompt_opts_t){.echo = false, .same_line = true, .mask_char = '*'}

#include <question.h>

Prompt options for password input (no echo, asterisk masking, same line)

Definition at line 45 of file question.h.

◆ rwlock_rdlock

#define rwlock_rdlock (   lock)     (lock_debug_is_initialized() ? debug_rwlock_rdlock(lock, __FILE__, __LINE__, __func__) : rwlock_rdlock_impl(lock))

#include <rwlock.h>

Acquire a read lock (with debug tracking in debug builds)

Parameters
lockPointer to read-write lock

Acquires a shared read lock. Multiple threads can hold read locks simultaneously. Blocks if a write lock is held.

Note
In debug builds, this macro includes lock debugging if initialized. In release builds, calls the implementation directly for zero overhead.

Definition at line 194 of file rwlock.h.

195 : rwlock_rdlock_impl(lock))
int rwlock_rdlock_impl(rwlock_t *lock)
Acquire a read lock (implementation function)

◆ rwlock_rdunlock

#define rwlock_rdunlock (   lock)     (lock_debug_is_initialized() ? debug_rwlock_rdunlock(lock, __FILE__, __LINE__, __func__) : rwlock_rdunlock_impl(lock))

#include <rwlock.h>

Release a read lock (with debug tracking in debug builds)

Parameters
lockPointer to read-write lock

Releases a shared read lock held by the calling thread.

Note
In debug builds, this macro includes lock debugging if initialized. In release builds, calls the implementation directly for zero overhead.

Definition at line 231 of file rwlock.h.

232 : rwlock_rdunlock_impl(lock))
int rwlock_rdunlock_impl(rwlock_t *lock)
Release a read lock (implementation function)

◆ rwlock_wrlock

#define rwlock_wrlock (   lock)     (lock_debug_is_initialized() ? debug_rwlock_wrlock(lock, __FILE__, __LINE__, __func__) : rwlock_wrlock_impl(lock))

#include <rwlock.h>

Acquire a write lock (with debug tracking in debug builds)

Parameters
lockPointer to read-write lock

Acquires an exclusive write lock. Only one thread can hold a write lock, and it excludes all read locks. Blocks if any locks are held.

Note
In debug builds, this macro includes lock debugging if initialized. In release builds, calls the implementation directly for zero overhead.

Definition at line 213 of file rwlock.h.

214 : rwlock_wrlock_impl(lock))
int rwlock_wrlock_impl(rwlock_t *lock)
Acquire a write lock (implementation function)

◆ rwlock_wrunlock

#define rwlock_wrunlock (   lock)     (lock_debug_is_initialized() ? debug_rwlock_wrunlock(lock, __FILE__, __LINE__, __func__) : rwlock_wrunlock_impl(lock))

#include <rwlock.h>

Release a write lock (with debug tracking in debug builds)

Parameters
lockPointer to read-write lock

Releases an exclusive write lock held by the calling thread.

Note
In debug builds, this macro includes lock debugging if initialized. In release builds, calls the implementation directly for zero overhead.

Definition at line 249 of file rwlock.h.

250 : rwlock_wrunlock_impl(lock))
int rwlock_wrunlock_impl(rwlock_t *lock)
Release a write lock (implementation function)

◆ SAFE_IGNORE_PRINTF_RESULT

#define SAFE_IGNORE_PRINTF_RESULT (   expr)    ((void)(expr))

#include <system.h>

Check return value of snprintf/fprintf and cast to void if needed

This macro satisfies clang-tidy cert-err33-c by explicitly handling the return value of printf family functions.

Definition at line 427 of file system.h.

◆ SOCKET_ERROR_AGAIN

#define SOCKET_ERROR_AGAIN   EAGAIN

#include <socket.h>

Definition at line 495 of file socket.h.

◆ SOCKET_ERROR_INPROGRESS

#define SOCKET_ERROR_INPROGRESS   EINPROGRESS

#include <socket.h>

Definition at line 494 of file socket.h.

◆ SOCKET_ERROR_WOULDBLOCK

#define SOCKET_ERROR_WOULDBLOCK   EWOULDBLOCK

#include <socket.h>

Definition at line 493 of file socket.h.

◆ STATIC_COND_INIT

#define STATIC_COND_INIT   {PTHREAD_COND_INITIALIZER, 1}

#include <init.h>

Definition at line 109 of file init.h.

◆ STATIC_MUTEX_INIT

#define STATIC_MUTEX_INIT   {PTHREAD_MUTEX_INITIALIZER, 1}

#include <init.h>

Definition at line 107 of file init.h.

◆ STATIC_RWLOCK_INIT

#define STATIC_RWLOCK_INIT   {PTHREAD_RWLOCK_INITIALIZER, 1}

#include <init.h>

Definition at line 108 of file init.h.

◆ THREAD_LOCAL

#define THREAD_LOCAL   __thread

#include <abstraction.h>

Thread-local storage keyword (POSIX: __thread)

Definition at line 381 of file abstraction.h.

◆ UNUSED

#define UNUSED (   x)    ((void)(x))

#include <abstraction.h>

Suppress unused parameter warnings.

Parameters
xParameter name to mark as unused

Use this macro to suppress compiler warnings about unused function parameters. Especially useful for callback functions where not all parameters are used.

Example:
void callback(void *data, int flags) {
UNUSED(flags); // Suppress warning about unused 'flags' parameter
process_data(data);
}
#define UNUSED(x)
Suppress unused parameter warnings.
Note
This macro casts the parameter to void, which effectively tells the compiler that the parameter is intentionally unused.

Definition at line 733 of file abstraction.h.

Typedef Documentation

◆ asciichat_thread_t

typedef pthread_t asciichat_thread_t

#include <thread.h>

Thread handle type (POSIX: pthread_t)

Definition at line 42 of file platform/thread.h.

◆ backtrace_frame_filter_t

typedef bool(* backtrace_frame_filter_t) (const char *frame)

#include <system.h>

Callback type for filtering backtrace frames.

Parameters
frameThe frame string to check
Returns
true if the frame should be skipped, false to include it

Definition at line 335 of file system.h.

◆ cond_t

typedef pthread_cond_t cond_t

#include <cond.h>

Condition variable type (POSIX: pthread_cond_t)

Definition at line 38 of file cond.h.

◆ console_ctrl_handler_t

typedef bool(* console_ctrl_handler_t) (console_ctrl_event_t event)

#include <system.h>

Console control handler callback type.

Parameters
eventThe control event that occurred
Returns
true if the event was handled, false to pass to next handler
Note
On Windows, this is called from a separate thread, not from signal context
On Unix, this is called from signal context (limited safe operations)

Definition at line 199 of file system.h.

◆ mutex_t

typedef pthread_mutex_t mutex_t

#include <mutex.h>

Mutex type (POSIX: pthread_mutex_t)

Definition at line 38 of file mutex.h.

◆ pipe_t

typedef int pipe_t

#include <pipe.h>

Pipe handle type (POSIX: int file descriptor)

Definition at line 40 of file pipe.h.

◆ platform_mmap_t

#include <mmap.h>

Memory-mapped file handle.

Contains platform-specific handles and mapping information. Do not access members directly; use the platform_mmap_* functions.

◆ rwlock_t

typedef pthread_rwlock_t rwlock_t

#include <rwlock.h>

Read-write lock type (POSIX: pthread_rwlock_t)

Definition at line 40 of file rwlock.h.

◆ signal_handler_t

typedef void(* signal_handler_t) (int)

#include <system.h>

Signal handler function type.

Parameters
sigSignal number

Definition at line 44 of file system.h.

◆ socket_t

typedef int socket_t

#include <socket.h>

Socket handle type (POSIX: int)

Definition at line 50 of file socket.h.

◆ thread_id_t

typedef pthread_t thread_id_t

#include <thread.h>

Thread ID type (POSIX: pthread_t)

Definition at line 44 of file platform/thread.h.

◆ tls_key_t

typedef pthread_key_t tls_key_t

#include <thread.h>

Thread-local storage key type (POSIX: pthread_key_t)

Definition at line 46 of file platform/thread.h.

Enumeration Type Documentation

◆ console_ctrl_event_t

#include <system.h>

Console control event types (cross-platform Ctrl+C handling)

Enumerator
CONSOLE_CTRL_C 

Ctrl+C pressed (SIGINT equivalent)

CONSOLE_CTRL_BREAK 

Ctrl+Break pressed (Windows only, maps to SIGINT on Unix)

CONSOLE_CLOSE 

Console window closed

CONSOLE_LOGOFF 

User logoff event (Windows only)

CONSOLE_SHUTDOWN 

System shutdown event (Windows only)

Definition at line 181 of file system.h.

181 {
182 CONSOLE_CTRL_C = 0,
184 CONSOLE_CLOSE = 2,
185 CONSOLE_LOGOFF = 3,
console_ctrl_event_t
Console control event types (cross-platform Ctrl+C handling)
Definition system.h:181
@ CONSOLE_CTRL_BREAK
Definition system.h:183
@ CONSOLE_SHUTDOWN
Definition system.h:186
@ CONSOLE_LOGOFF
Definition system.h:185
@ CONSOLE_CTRL_C
Definition system.h:182
@ CONSOLE_CLOSE
Definition system.h:184

◆ render_mode_t

#include <terminal.h>

Render mode preferences.

Enumeration of rendering modes for ASCII art output. Different modes provide different visual effects and require different terminal capabilities.

Enumerator
RENDER_MODE_FOREGROUND 

Foreground colors only (text color)

RENDER_MODE_BACKGROUND 

Background colors (block colors)

RENDER_MODE_HALF_BLOCK 

Unicode half-block characters (mixed foreground/background)

Definition at line 467 of file terminal.h.

467 {
render_mode_t
Render mode preferences.
Definition terminal.h:467
@ RENDER_MODE_FOREGROUND
Foreground colors only (text color)
Definition terminal.h:469
@ RENDER_MODE_BACKGROUND
Background colors (block colors)
Definition terminal.h:471
@ RENDER_MODE_HALF_BLOCK
Unicode half-block characters (mixed foreground/background)
Definition terminal.h:473

◆ terminal_capability_flags_t

#include <terminal.h>

Terminal capability flags (bitmask)

Bitmask enumeration for terminal capabilities. Multiple flags can be combined to indicate support for various features. Used in terminal capability detection and rendering optimization.

Enumerator
TERM_CAP_COLOR_16 

16-color support (TERM_CAP_COLOR_16)

TERM_CAP_COLOR_256 

256-color support (TERM_CAP_COLOR_256)

TERM_CAP_COLOR_TRUE 

Truecolor support (TERM_CAP_COLOR_TRUE)

TERM_CAP_UTF8 

UTF-8 encoding support (TERM_CAP_UTF8)

TERM_CAP_BACKGROUND 

Background color support (TERM_CAP_BACKGROUND)

Definition at line 446 of file terminal.h.

446 {
448 TERM_CAP_COLOR_16 = 0x0001,
450 TERM_CAP_COLOR_256 = 0x0002,
452 TERM_CAP_COLOR_TRUE = 0x0004,
454 TERM_CAP_UTF8 = 0x0008,
456 TERM_CAP_BACKGROUND = 0x0010
terminal_capability_flags_t
Terminal capability flags (bitmask)
Definition terminal.h:446
@ TERM_CAP_COLOR_256
256-color support (TERM_CAP_COLOR_256)
Definition terminal.h:450
@ TERM_CAP_COLOR_16
16-color support (TERM_CAP_COLOR_16)
Definition terminal.h:448
@ TERM_CAP_UTF8
UTF-8 encoding support (TERM_CAP_UTF8)
Definition terminal.h:454
@ TERM_CAP_BACKGROUND
Background color support (TERM_CAP_BACKGROUND)
Definition terminal.h:456
@ TERM_CAP_COLOR_TRUE
Truecolor support (TERM_CAP_COLOR_TRUE)
Definition terminal.h:452

◆ terminal_color_mode_t

#include <terminal.h>

Terminal color support levels.

Enumeration of terminal color capability levels from no color support to full 24-bit truecolor support. Used for capability detection and rendering mode selection.

Enumerator
TERM_COLOR_AUTO 

Auto-detect color support from terminal capabilities.

TERM_COLOR_NONE 

No color support (monochrome terminal)

TERM_COLOR_16 

16-color support (standard ANSI colors)

TERM_COLOR_256 

256-color support (extended ANSI palette)

TERM_COLOR_TRUECOLOR 

24-bit truecolor support (RGB colors)

Definition at line 424 of file terminal.h.

424 {
426 TERM_COLOR_AUTO = -1,
428 TERM_COLOR_NONE = 0,
430 TERM_COLOR_16 = 1,
432 TERM_COLOR_256 = 2,
terminal_color_mode_t
Terminal color support levels.
Definition terminal.h:424
@ TERM_COLOR_NONE
No color support (monochrome terminal)
Definition terminal.h:428
@ TERM_COLOR_16
16-color support (standard ANSI colors)
Definition terminal.h:430
@ TERM_COLOR_256
256-color support (extended ANSI palette)
Definition terminal.h:432
@ TERM_COLOR_AUTO
Auto-detect color support from terminal capabilities.
Definition terminal.h:426
@ TERM_COLOR_TRUECOLOR
24-bit truecolor support (RGB colors)
Definition terminal.h:434

Function Documentation

◆ apply_color_mode_override()

terminal_capabilities_t apply_color_mode_override ( terminal_capabilities_t  caps)

#include <terminal.h>

Apply command-line overrides to detected capabilities.

Parameters
capsTerminal capabilities structure to modify
Returns
Modified terminal capabilities structure

Applies any command-line option overrides to the detected terminal capabilities. Overrides may include:

  • Force color mode (–color, –no-color, –256, –truecolor)
  • Force UTF-8 mode (–utf8)
  • Render mode selection (–bg, –fg, –half-block)
  • Palette selection (–palette)
Note
Returns a modified copy of the input structure.
Overrides take precedence over detected capabilities.

Referenced by client_main(), main(), mirror_main(), tcp_client_send_terminal_capabilities(), and threaded_send_terminal_size_with_auto_detect().

◆ ascii_tls_get()

void * ascii_tls_get ( tls_key_t  key)

#include <thread.h>

Get thread-local value for a key.

Parameters
keyTLS key
Returns
Thread-local value, or NULL if not set

Returns the thread-local value associated with the specified key for the calling thread.

Referenced by asciichat_instr_runtime_get().

◆ ascii_tls_key_create()

int ascii_tls_key_create ( tls_key_t key,
void(*)(void *)  destructor 
)

#include <thread.h>

Create a thread-local storage key.

Parameters
keyPointer to TLS key (output parameter)
destructorOptional destructor function called when thread exits (may be NULL)
Returns
0 on success, non-zero on error

Creates a new TLS key that can be used to store thread-specific data. If a destructor is provided, it will be called with the stored value when a thread terminates (if the value is non-NULL).

◆ ascii_tls_key_delete()

int ascii_tls_key_delete ( tls_key_t  key)

#include <thread.h>

Delete a thread-local storage key.

Parameters
keyTLS key to delete
Returns
0 on success, non-zero on error

Deletes the specified TLS key. Does NOT call destructors for existing thread-local values. The caller is responsible for cleanup before deletion.

Referenced by asciichat_instr_runtime_global_shutdown().

◆ ascii_tls_set()

int ascii_tls_set ( tls_key_t  key,
void *  value 
)

#include <thread.h>

Set thread-local value for a key.

Parameters
keyTLS key
valueValue to store
Returns
0 on success, non-zero on error

Associates the specified value with the key for the calling thread.

Referenced by asciichat_instr_runtime_get().

◆ asciichat_thread_create()

int asciichat_thread_create ( asciichat_thread_t thread,
void *(*)(void *)  func,
void *  arg 
)

#include <thread.h>

Create a new thread.

Parameters
threadPointer to thread handle (output parameter)
funcThread function to execute
argArgument to pass to thread function
Returns
0 on success, non-zero on error

Creates a new thread that executes the given function with the provided argument. The thread handle is stored in the thread parameter.

Referenced by discover_session_parallel(), server_main(), tcp_server_run(), thread_create_or_fail(), and thread_pool_spawn().

◆ asciichat_thread_current_id()

uint64_t asciichat_thread_current_id ( void  )

#include <thread.h>

Get the current thread's unique numeric ID.

Returns
Unique thread identifier as a 64-bit unsigned integer

Returns a unique numeric identifier for the current thread. This is more portable than thread_id_t for comparisons.

◆ asciichat_thread_equal()

int asciichat_thread_equal ( thread_id_t  t1,
thread_id_t  t2 
)

#include <thread.h>

Compare two thread IDs for equality.

Parameters
t1First thread ID
t2Second thread ID
Returns
Non-zero if thread IDs are equal, 0 otherwise

◆ asciichat_thread_exit()

void asciichat_thread_exit ( void *  retval)

#include <thread.h>

Exit the current thread.

Parameters
retvalReturn value to pass to thread joiner (or NULL)

Terminates the calling thread and optionally passes a return value to any thread waiting to join.

◆ asciichat_thread_init()

void asciichat_thread_init ( asciichat_thread_t thread)

#include <thread.h>

Initialize a thread handle to an uninitialized state.

Parameters
threadPointer to thread handle

Sets the thread handle to an uninitialized state. Useful for static initialization or resetting a thread handle.

Referenced by discover_session_parallel(), and stop_client_render_threads().

◆ asciichat_thread_is_initialized()

bool asciichat_thread_is_initialized ( asciichat_thread_t thread)

#include <thread.h>

Check if a thread handle has been initialized.

Parameters
threadPointer to thread handle
Returns
true if thread is initialized, false otherwise

Referenced by discover_session_parallel(), stop_client_render_threads(), and stop_client_threads().

◆ asciichat_thread_join()

int asciichat_thread_join ( asciichat_thread_t thread,
void **  retval 
)

#include <thread.h>

Wait for a thread to complete (blocking)

Parameters
threadThread handle to wait for
retvalPointer to store thread return value (or NULL to ignore)
Returns
0 on success, non-zero on error

Blocks the calling thread until the specified thread terminates.

Referenced by discover_session_parallel(), server_main(), stop_client_render_threads(), stop_client_threads(), and thread_pool_stop_all().

◆ asciichat_thread_join_timeout()

int asciichat_thread_join_timeout ( asciichat_thread_t thread,
void **  retval,
uint32_t  timeout_ms 
)

#include <thread.h>

Wait for a thread to complete with timeout.

Parameters
threadThread handle to wait for
retvalPointer to store thread return value (or NULL to ignore)
timeout_msTimeout in milliseconds
Returns
0 on success, non-zero on timeout or error

Waits for the specified thread to terminate, with a maximum wait time. Returns non-zero if the timeout expires before the thread completes.

Referenced by audio_start_thread().

◆ asciichat_thread_self()

thread_id_t asciichat_thread_self ( void  )

#include <thread.h>

Get the current thread's ID.

Returns
Thread ID of the calling thread

Returns a platform-specific thread identifier for the calling thread.

Referenced by log_lock_terminal(), and log_plain_msg().

◆ asciichat_thread_set_realtime_priority()

asciichat_error_t asciichat_thread_set_realtime_priority ( void  )

#include <thread.h>

Set the current thread to real-time priority.

Returns
ASCIICHAT_OK on success, error code on failure

Attempts to set the current thread to real-time priority for time-critical operations like audio processing.

Platform-specific implementations:

  • Linux: Uses pthread_setschedparam() with SCHED_FIFO at priority 80
  • macOS: Uses thread_policy_set() with THREAD_TIME_CONSTRAINT_POLICY
  • Windows: Uses SetThreadPriority() with THREAD_PRIORITY_TIME_CRITICAL
Note
On Linux, requires CAP_SYS_NICE capability or rtprio resource limit
On Windows, does not require special privileges
On macOS, requires mach_thread_self() to work

Referenced by audio_set_realtime_priority().

◆ cond_broadcast()

int cond_broadcast ( cond_t cond)

#include <cond.h>

Broadcast to a condition variable (wake all waiting threads)

Parameters
condPointer to condition variable to broadcast
Returns
0 on success, non-zero on error

Wakes up all threads that are waiting on the condition variable. If no threads are waiting, the broadcast has no effect.

◆ cond_destroy()

int cond_destroy ( cond_t cond)

#include <cond.h>

Destroy a condition variable.

Parameters
condPointer to condition variable to destroy
Returns
0 on success, non-zero on error

Destroys the condition variable and frees any associated resources. No threads should be waiting on the condition variable when this is called.

Referenced by acip_webrtc_transport_create(), discover_session_parallel(), and tcp_client_destroy().

◆ cond_init()

int cond_init ( cond_t cond)

#include <cond.h>

Initialize a condition variable.

Parameters
condPointer to condition variable to initialize
Returns
0 on success, non-zero on error

Initializes the condition variable for use. Must be called before any other condition variable operations.

Referenced by acip_webrtc_transport_create(), discover_session_parallel(), and tcp_client_create().

◆ cond_signal()

int cond_signal ( cond_t cond)

#include <cond.h>

Signal a condition variable (wake one waiting thread)

Parameters
condPointer to condition variable to signal
Returns
0 on success, non-zero on error

Wakes up one thread that is waiting on the condition variable. If no threads are waiting, the signal is lost.

Referenced by __attribute__().

◆ cond_timedwait()

int cond_timedwait ( cond_t cond,
mutex_t mutex,
int  timeout_ms 
)

#include <cond.h>

Wait on a condition variable with timeout.

Parameters
condPointer to condition variable to wait on
mutexPointer to mutex that must be locked by the calling thread
timeout_msTimeout in milliseconds
Returns
0 if condition was signaled, non-zero on timeout or error

Atomically unlocks the mutex and waits on the condition variable with a timeout. Returns non-zero if the timeout expires before the condition is signaled.

Warning
The mutex must be locked before calling this function.

Referenced by discover_session_parallel().

◆ cond_wait()

int cond_wait ( cond_t cond,
mutex_t mutex 
)

#include <cond.h>

Wait on a condition variable (blocking)

Parameters
condPointer to condition variable to wait on
mutexPointer to mutex that must be locked by the calling thread
Returns
0 on success, non-zero on error

Atomically unlocks the mutex and waits on the condition variable. The mutex must be locked by the calling thread before calling this function. Upon return, the mutex will be locked again.

Warning
The mutex must be locked before calling this function.

◆ debug_mutex_lock()

int debug_mutex_lock ( mutex_t mutex,
const char *  file_name,
int  line_number,
const char *  function_name 
)

#include <mutex.h>

◆ debug_mutex_trylock()

int debug_mutex_trylock ( mutex_t mutex,
const char *  file_name,
int  line_number,
const char *  function_name 
)

#include <mutex.h>

◆ debug_mutex_unlock()

int debug_mutex_unlock ( mutex_t mutex,
const char *  file_name,
int  line_number,
const char *  function_name 
)

#include <mutex.h>

◆ debug_rwlock_rdlock()

int debug_rwlock_rdlock ( rwlock_t rwlock,
const char *  file_name,
int  line_number,
const char *  function_name 
)

#include <rwlock.h>

◆ debug_rwlock_rdunlock()

int debug_rwlock_rdunlock ( rwlock_t rwlock,
const char *  file_name,
int  line_number,
const char *  function_name 
)

#include <rwlock.h>

◆ debug_rwlock_wrlock()

int debug_rwlock_wrlock ( rwlock_t rwlock,
const char *  file_name,
int  line_number,
const char *  function_name 
)

#include <rwlock.h>

◆ debug_rwlock_wrunlock()

int debug_rwlock_wrunlock ( rwlock_t rwlock,
const char *  file_name,
int  line_number,
const char *  function_name 
)

#include <rwlock.h>

◆ detect_terminal_capabilities()

terminal_capabilities_t detect_terminal_capabilities ( void  )

#include <terminal.h>

Detect terminal capabilities.

Returns
Terminal capabilities structure with all detected features

Comprehensively detects terminal capabilities including:

  • Color support level (none, 16, 256, truecolor)
  • UTF-8 encoding support
  • Terminal type and environment variables
  • Render mode preferences
  • Detection reliability

Detection uses multiple methods:

  • Environment variable analysis ($TERM, $COLORTERM, $LC_ALL, $LANG)
  • Terminal type database lookups
  • Runtime capability queries (where available)
Note
Returns a structure with all detected capabilities.
Detection reliability is indicated by detection_reliable field.
Use apply_color_mode_override() to apply command-line overrides.

Referenced by action_show_capabilities(), client_main(), log_redetect_terminal_capabilities(), main(), mirror_main(), tcp_client_send_terminal_capabilities(), and threaded_send_terminal_size_with_auto_detect().

◆ get_current_tty()

tty_info_t get_current_tty ( void  )

#include <terminal.h>

Get current TTY information.

Returns
TTY information structure with file descriptor and path

Retrieves information about the current TTY (terminal). Returns file descriptor for TTY access, device path, and ownership information. Useful for advanced terminal operations that require direct TTY access.

Note
File descriptor may need to be closed if owns_fd is true.
TTY path is platform-specific (Unix: /dev/tty, Windows: CON, etc.).

◆ get_terminal_size()

asciichat_error_t get_terminal_size ( unsigned short int *  width,
unsigned short int *  height 
)

#include <terminal.h>

Get terminal size with multiple fallback methods.

Parameters
widthPointer to store width in columns (must not be NULL)
heightPointer to store height in rows (must not be NULL)
Returns
ASCIICHAT_OK on success, error code on failure

Detects terminal size using multiple fallback methods for reliability:

  1. Terminal size query (ioctl TIOCGWINSZ on Unix, Console API on Windows)
  2. Environment variable fallback ($COLUMNS, $LINES)
  3. Default size fallback (80x24) if all methods fail
Note
On failure, output parameters are not modified.
Terminal size may change if terminal is resized.
This function is more reliable than terminal_get_size() due to fallbacks.

Referenced by update_dimensions_for_full_height(), and update_dimensions_to_terminal_size().

◆ is_valid_tty_path()

bool is_valid_tty_path ( const char *  path)

#include <terminal.h>

Check if a TTY path is valid.

Parameters
pathPath to check (must not be NULL)
Returns
true if path is valid TTY device, false otherwise

Validates that a path points to a valid TTY (terminal) device. Checks device file existence and type on Unix systems.

Note
Returns false for non-TTY devices or invalid paths.
Useful for validating TTY paths before use.

◆ lock_debug_is_initialized()

bool lock_debug_is_initialized ( void  )

#include <mutex.h>

Referenced by stats_logger_thread().

◆ mutex_destroy()

◆ mutex_init()

◆ mutex_lock_impl()

int mutex_lock_impl ( mutex_t mutex)

#include <mutex.h>

Lock a mutex (implementation function)

Parameters
mutexPointer to mutex to lock
Returns
0 on success, non-zero on error
Note
This is the implementation function. Use mutex_lock() macro instead, which includes debug tracking in debug builds.

◆ mutex_trylock_impl()

int mutex_trylock_impl ( mutex_t mutex)

#include <mutex.h>

Try to lock a mutex without blocking (implementation function)

Parameters
mutexPointer to mutex to try to lock
Returns
0 if lock was acquired, non-zero if mutex was already locked

Attempts to acquire the mutex lock without blocking. Returns immediately whether the lock was acquired or not.

Note
This is the implementation function. Use mutex_trylock() macro instead, which includes debug tracking in debug builds.

◆ mutex_unlock_impl()

int mutex_unlock_impl ( mutex_t mutex)

#include <mutex.h>

Unlock a mutex (implementation function)

Parameters
mutexPointer to mutex to unlock
Returns
0 on success, non-zero on error
Note
This is the implementation function. Use mutex_unlock() macro instead, which includes debug tracking in debug builds.

◆ platform_access()

int platform_access ( const char *  path,
int  mode 
)

#include <system.h>

Check file/directory access permissions.

Platform-safe wrapper for access() / _access(). Tests whether the calling process has the requested access to the specified path.

Platform-specific implementations:

  • POSIX: Uses access() with F_OK, R_OK, W_OK, X_OK modes
  • Windows: Uses _access() with 0, 2, 4, 6 modes
Parameters
pathFile or directory path to check
modeAccess mode to test (PLATFORM_ACCESS_EXISTS, PLATFORM_ACCESS_WRITE, PLATFORM_ACCESS_READ)
Returns
0 on success (access permitted), -1 on failure (access denied or path doesn't exist)
Note
Thread-safe on all platforms
Does not follow symbolic links on POSIX (uses access() not faccessat())
Returns -1 if path is NULL
Example:
// Directory is writable
}
int platform_access(const char *path, int mode)
Check file/directory access permissions.
#define PLATFORM_ACCESS_WRITE
Check if file/directory is writable.
Definition system.h:787

Referenced by get_log_dir().

◆ platform_aligned_alloc()

void * platform_aligned_alloc ( size_t  alignment,
size_t  size 
)

#include <util.h>

Allocate aligned memory.

Parameters
alignmentRequired alignment (must be power of 2)
sizeNumber of bytes to allocate
Returns
Pointer to aligned memory, or NULL on error

Allocates memory with specified alignment. Should be freed with platform_aligned_free().

◆ platform_aligned_free()

void platform_aligned_free ( void *  ptr)

#include <util.h>

Free aligned memory.

Parameters
ptrPointer returned from platform_aligned_alloc()

Frees memory previously allocated with platform_aligned_alloc().

◆ platform_backtrace()

int platform_backtrace ( void **  buffer,
int  size 
)

#include <system.h>

Get a backtrace of the current call stack.

Parameters
bufferArray of pointers to store return addresses
sizeMaximum number of frames to capture
Returns
Number of frames captured

Captures the current call stack into the provided buffer. Returns the number of frames actually captured.

Referenced by asciichat_fatal_with_context().

◆ platform_backtrace_symbols()

char ** platform_backtrace_symbols ( void *const *  buffer,
int  size 
)

#include <system.h>

Convert backtrace addresses to symbol names.

Parameters
bufferArray of return addresses from platform_backtrace()
sizeNumber of frames in buffer
Returns
Array of symbol name strings, or NULL on error

Converts the return addresses from platform_backtrace() into human-readable symbol names (function names, file names, line numbers).

Note
The returned array must be freed with platform_backtrace_symbols_free().

Referenced by asciichat_fatal_with_context().

◆ platform_backtrace_symbols_free()

void platform_backtrace_symbols_free ( char **  strings)

#include <system.h>

Free symbol array returned by platform_backtrace_symbols()

Parameters
stringsArray of symbol strings to free

Frees the memory allocated by platform_backtrace_symbols().

Referenced by asciichat_clear_errno(), asciichat_errno_cleanup(), asciichat_fatal_with_context(), and asciichat_set_errno().

◆ platform_chmod()

int platform_chmod ( const char *  pathname,
int  mode 
)

#include <util.h>

Change file permissions/mode.

Parameters
pathnameFile path
modeNew file mode (permissions)
Returns
0 on success, -1 on error

Changes file permissions/mode. Cross-platform chmod replacement.

Referenced by add_known_host().

◆ platform_cleanup()

void platform_cleanup ( void  )

#include <init.h>

Cleanup platform-specific subsystems.

Performs cleanup for platform-specific subsystems. Should be called during program shutdown.

Referenced by asciichat_shared_init().

◆ platform_cleanup_binary_path_cache()

void platform_cleanup_binary_path_cache ( void  )

#include <system.h>

Cleanup the binary PATH cache.

Frees all cached binary PATH lookup results and destroys the cache. Should be called during program cleanup (e.g., in platform_cleanup()).

Note
Thread-safe: Uses internal locking
Safe to call even if cache was never initialized

Definition at line 261 of file system.c.

261 {
262 if (!atomic_load(&g_cache_initialized)) {
263 return;
264 }
265
266 if (g_bin_path_cache) {
267 rwlock_wrlock(&g_cache_rwlock);
268
269 // Free all cached entries using uthash iteration
270 bin_cache_entry_t *entry, *tmp;
271 HASH_ITER(hh, g_bin_path_cache, entry, tmp) {
272 HASH_DELETE(hh, g_bin_path_cache, entry);
273 free_cache_entry(entry);
274 }
275
276 rwlock_wrunlock(&g_cache_rwlock);
277 rwlock_destroy(&g_cache_rwlock);
278 g_bin_path_cache = NULL;
279 }
280
281 atomic_store(&g_cache_initialized, false);
282}
int rwlock_destroy(rwlock_t *lock)
Destroy a read-write lock.
Binary PATH cache entry structure for binary detection caching.
Definition system.c:126

References rwlock_destroy(), rwlock_wrlock, and rwlock_wrunlock.

Referenced by server_main().

◆ platform_close()

int platform_close ( int  fd)

#include <util.h>

Safe file close (close replacement)

Parameters
fdFile descriptor to close
Returns
0 on success, -1 on error

Cross-platform file closing.

Referenced by asciichat_instr_runtime_destroy(), check_known_host(), check_known_host_no_identity(), display_cleanup(), log_destroy(), log_init(), and remove_known_host().

◆ platform_fdopen()

FILE * platform_fdopen ( int  fd,
const char *  mode 
)

#include <util.h>

Convert file descriptor to stream (fdopen replacement)

Parameters
fdFile descriptor
modeOpen mode string for the stream
Returns
FILE pointer, or NULL on error

Associates a FILE stream with an existing file descriptor.

Referenced by check_known_host(), check_known_host_no_identity(), and remove_known_host().

◆ platform_fopen()

FILE * platform_fopen ( const char *  filename,
const char *  mode 
)

#include <util.h>

Safe file open stream (fopen replacement)

Parameters
filenameFile path to open
modeOpen mode string (e.g., "r", "w", "rb")
Returns
FILE pointer, or NULL on error

Cross-platform file stream opening.

Referenced by add_known_host(), config_create_default(), parse_keys_from_file(), parse_public_key(), parse_ssh_private_key(), and validate_ssh_key_file().

◆ platform_format_backtrace_symbols()

int platform_format_backtrace_symbols ( char *  buffer,
size_t  buffer_size,
const char *  label,
char **  symbols,
int  count,
int  skip_frames,
int  max_frames,
backtrace_frame_filter_t  filter 
)

#include <system.h>

Format pre-resolved backtrace symbols to a buffer.

Same format as platform_print_backtrace_symbols() but writes to a buffer.

Parameters
bufferOutput buffer
buffer_sizeSize of output buffer
labelHeader label (e.g., "Call stack")
symbolsArray of pre-resolved symbol strings
countNumber of symbols in the array
skip_framesNumber of frames to skip from the start
max_framesMaximum frames to print (0 = unlimited)
filterOptional filter callback to skip specific frames (NULL = no filtering)
Returns
Number of bytes written (excluding null terminator)

◆ platform_fsync()

int platform_fsync ( int  fd)

#include <system.h>

Synchronize a file descriptor to disk.

Parameters
fdFile descriptor to sync
Returns
0 on success, non-zero on error

Forces all buffered data for the file descriptor to be written to disk.

◆ platform_get_cwd()

bool platform_get_cwd ( char *  cwd,
size_t  path_size 
)

#include <system.h>

Get the current working directory of the process.

Normalizes the result using platform-specific semantics and does not append a trailing directory separator.

Parameters
cwdBuffer to store the current working directory
path_sizeSize of the buffer in bytes
Returns
true on success, false on failure (buffer too small or API error)

Referenced by get_log_dir(), and path_validate_user_path().

◆ platform_get_executable_path()

bool platform_get_executable_path ( char *  exe_path,
size_t  path_size 
)

#include <system.h>

Get the path to the current executable.

Retrieves the full path to the currently running executable using platform-specific methods.

Platform-specific implementations:

  • Windows: GetModuleFileNameA()
  • Linux: readlink("/proc/self/exe")
  • macOS: _NSGetExecutablePath()
Parameters
exe_pathBuffer to store the executable path
path_sizeSize of the buffer
Returns
true on success, false on failure
Note
Thread-safe
Buffer should be PLATFORM_MAX_PATH_LENGTH bytes to support all paths
Example:
char exe_path[PLATFORM_MAX_PATH_LENGTH];
if (platform_get_executable_path(exe_path, sizeof(exe_path))) {
// Use exe_path
}
#define PLATFORM_MAX_PATH_LENGTH
Definition common.h:91
bool platform_get_executable_path(char *exe_path, size_t path_size)
Get the path to the current executable.
Definition system.c:351
Parameters
exe_pathBuffer to store the executable path
path_sizeSize of the buffer
Returns
true on success, false on failure

Definition at line 351 of file system.c.

351 {
352 if (!exe_path || path_size == 0) {
353 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: exe_path=%p, path_size=%zu", (void *)exe_path, path_size);
354 return false;
355 }
356
357#ifdef _WIN32
358 DWORD len = GetModuleFileNameA(NULL, exe_path, (DWORD)path_size);
359 if (len == 0) {
360 SET_ERRNO_SYS(ERROR_INVALID_STATE, "GetModuleFileNameA failed: error code %lu", GetLastError());
361 return false;
362 }
363 if (len >= path_size) {
365 "Executable path exceeds buffer size (path length >= %zu bytes, buffer size = %zu bytes)", (size_t)len,
366 path_size);
367 return false;
368 }
369 return true;
370
371#elif defined(__linux__)
372 ssize_t len = readlink("/proc/self/exe", exe_path, path_size - 1);
373 if (len < 0) {
374 SET_ERRNO_SYS(ERROR_INVALID_STATE, "readlink(\"/proc/self/exe\") failed: %s", SAFE_STRERROR(errno));
375 return false;
376 }
377 if ((size_t)len >= path_size - 1) {
379 "Executable path exceeds buffer size (path length >= %zu bytes, buffer size = %zu bytes)", (size_t)len,
380 path_size);
381 return false;
382 }
383 exe_path[len] = '\0';
384 return true;
385
386#elif defined(__APPLE__)
387 uint32_t bufsize = (uint32_t)path_size;
388 int result = _NSGetExecutablePath(exe_path, &bufsize);
389 if (result != 0) {
390 SET_ERRNO(ERROR_BUFFER_OVERFLOW, "_NSGetExecutablePath failed: path requires %u bytes, buffer size = %zu bytes",
391 bufsize, path_size);
392 return false;
393 }
394 return true;
395
396#else
397 SET_ERRNO(ERROR_GENERAL, "Unsupported platform - cannot get executable path");
398 return false;
399#endif
400}
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRERROR(errnum)
Definition common.h:385
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_INVALID_STATE
@ ERROR_GENERAL
Definition error_codes.h:49
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
Definition error_codes.h:98

References errno, ERROR_BUFFER_OVERFLOW, ERROR_GENERAL, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, SAFE_STRERROR, SET_ERRNO, and SET_ERRNO_SYS.

◆ platform_get_last_error()

int platform_get_last_error ( void  )

#include <util.h>

Get last platform error code.

Returns
Error code (errno on POSIX, GetLastError on Windows)

Retrieves the last error that occurred. Equivalent to errno on POSIX.

◆ platform_get_monotonic_time_us()

uint64_t platform_get_monotonic_time_us ( void  )

#include <system.h>

Get monotonic time in microseconds.

Returns
Current monotonic time in microseconds

Returns a monotonically increasing time value in microseconds. Useful for measuring elapsed time without being affected by system clock changes. Thread-safe and lock-free.

Platform-specific implementations:

  • Unix/POSIX: Uses CLOCK_MONOTONIC via clock_gettime()
  • Windows: Uses QueryPerformanceCounter()
Note
The absolute value is arbitrary; only differences are meaningful.
Wraps after approximately 584,942 years (uint64_t).

◆ platform_get_pid()

int platform_get_pid ( void  )

#include <system.h>

Get the current process ID.

Returns
Process ID of the calling process

Referenced by asciichat_instr_runtime_get().

◆ platform_get_temp_dir()

bool platform_get_temp_dir ( char *  temp_dir,
size_t  path_size 
)

#include <system.h>

Get the system temporary directory path.

Retrieves the path to the system's temporary directory using platform-specific methods. Verifies the directory exists and is writable.

Platform-specific implementations:

  • Windows: TEMP% or TMP% environment variable, fallback to C:\Temp
  • Linux/macOS: /tmp
Parameters
temp_dirBuffer to store the temporary directory path
path_sizeSize of the buffer
Returns
true on success (directory exists and is writable), false on failure
Note
Thread-safe
Returned path does not include trailing directory separator
Buffer should be at least 256 bytes to support typical paths
Returns false if the directory doesn't exist or lacks write permission
Example:
char temp_dir[256];
if (platform_get_temp_dir(temp_dir, sizeof(temp_dir))) {
// temp_dir is valid and writable
char log_path[512];
snprintf(log_path, sizeof(log_path), "%s/myapp.log", temp_dir);
}
bool platform_get_temp_dir(char *temp_dir, size_t path_size)
Get the system temporary directory path.

Referenced by get_log_dir(), and path_validate_user_path().

◆ platform_get_username()

const char * platform_get_username ( void  )

#include <system.h>

Get the current username.

Returns
Pointer to username string (may be static, do not free)

Returns the username of the current user.

Note
The returned string may be a static buffer. Do not modify or free it.

Referenced by server_connection_establish().

◆ platform_getenv()

const char * platform_getenv ( const char *  name)

#include <system.h>

Get an environment variable value.

Parameters
nameEnvironment variable name
Returns
Pointer to value string (or NULL if not set), do not free

Returns the value of the specified environment variable.

Note
The returned string may be a static buffer. Do not modify or free it.

Referenced by client_audio_pipeline_process_duplex(), client_crypto_handshake(), crypto_handshake_client_key_exchange(), ed25519_verify_signature(), expand_path(), get_config_dir(), parse_ssh_private_key(), path_validate_user_path(), and prompt_unknown_host().

◆ platform_gtime()

asciichat_error_t platform_gtime ( const time_t *  timer,
struct tm *  result 
)

#include <system.h>

Platform-safe gmtime wrapper.

Uses gmtime_s on Windows and gmtime_r on POSIX. Thread-safe on all platforms.

Parameters
timerPointer to time_t value
resultPointer to struct tm to receive result
Returns
ASCIICHAT_OK on success, error code on error

Referenced by asciichat_instr_log_line().

◆ platform_init()

asciichat_error_t platform_init ( void  )

#include <init.h>

Initialize platform-specific subsystems.

Returns
ASCIICHAT_OK on success, error code on failure

Initializes platform-specific subsystems such as Winsock on Windows. Must be called before using any platform-specific functions.

Referenced by asciichat_shared_init().

◆ platform_install_crash_handler()

void platform_install_crash_handler ( void  )

#include <system.h>

Install crash handlers for the application.

Installs signal handlers for common crash signals (SIGSEGV, SIGABRT, etc.) that will print a backtrace before terminating the process.

Note
This should be called early in program initialization.

◆ platform_is_binary_in_path()

bool platform_is_binary_in_path ( const char *  bin_name)

#include <system.h>

Check if a binary is available in the system PATH.

This function checks if the specified binary can be found in the PATH by searching each directory in the PATH environment variable. Results are cached to avoid repeated filesystem checks.

On Windows: Automatically appends .exe if needed, checks with GetFileAttributesA On Unix: Uses access() with X_OK to verify executable permission

Parameters
bin_nameBase name of the binary (e.g., "ssh-keygen", "llvm-symbolizer") On Windows, .exe extension is added automatically if not present
Returns
true if binary is in PATH and executable, false otherwise
Note
Thread-safe: Uses internal locking for cache access
First call for a binary checks filesystem, subsequent calls use cache
No external dependencies (doesn't spawn where/command -v)
Example:
if (platform_is_binary_in_path("ssh-keygen")) {
// Use ssh-keygen
}
bool platform_is_binary_in_path(const char *bin_name)
Check if a binary is available in the system PATH.
Definition system.c:288

Definition at line 288 of file system.c.

288 {
289 if (!bin_name || bin_name[0] == '\0') {
290 return false;
291 }
292
293 // Initialize cache if needed
294 init_cache_once();
295 if (!atomic_load(&g_cache_initialized)) {
296 // Cache initialization failed, check directly (this should never happen)
297 SET_ERRNO(ERROR_INVALID_STATE, "Binary PATH cache not initialized, checking directly (this should never happen)");
298 return check_binary_in_path_uncached(bin_name);
299 }
300
301 // Check cache first
302 rwlock_rdlock(&g_cache_rwlock);
303 bin_cache_entry_t *entry = NULL;
304 HASH_FIND_STR(g_bin_path_cache, bin_name, entry);
305 rwlock_rdunlock(&g_cache_rwlock);
306
307 const char **colors = log_get_color_array();
308
309 if (entry) {
310 // Cache hit
311 log_debug("Binary '%s' %sfound%s in PATH (%scached%s)", bin_name, colors[LOG_COLOR_INFO], colors[LOG_COLOR_RESET],
312 colors[LOG_COLOR_WARN], colors[LOG_COLOR_RESET]);
313 return entry->in_path;
314 }
315
316 // Cache miss - check PATH and cache result
317 bool found = check_binary_in_path_uncached(bin_name);
318
319 // Create new cache entry
321 if (!entry) {
322 SET_ERRNO(ERROR_MEMORY, "Failed to allocate cache entry");
323 return found; // Return result without caching
324 }
325
326 entry->bin_name = platform_strdup(bin_name);
327 if (!entry->bin_name) {
328 SET_ERRNO(ERROR_MEMORY, "Failed to duplicate binary name");
329 SAFE_FREE(entry);
330 return found;
331 }
332
333 entry->in_path = found;
334
335 // Add to cache using uthash
336 rwlock_wrlock(&g_cache_rwlock);
337 HASH_ADD_KEYPTR(hh, g_bin_path_cache, entry->bin_name, strlen(entry->bin_name), entry);
338 rwlock_wrunlock(&g_cache_rwlock);
339
340 log_debug("Binary '%s' %s%s%s in PATH", bin_name, colors[found ? LOG_COLOR_INFO : LOG_COLOR_ERROR],
341 found ? "found" : "NOT found", colors[LOG_COLOR_RESET]);
342 return found;
343}
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
@ ERROR_MEMORY
Definition error_codes.h:53
const char ** log_get_color_array(void)
Get the appropriate color array based on terminal capabilities.
@ LOG_COLOR_RESET
@ LOG_COLOR_ERROR
@ LOG_COLOR_INFO
@ LOG_COLOR_WARN
bool in_path
Whether binary was found in PATH (true = found, false = not found)
Definition system.c:130
char * bin_name
Binary name string (allocated, owned by cache) - also used as uthash key.
Definition system.c:128

References bin_cache_entry_t::bin_name, ERROR_INVALID_STATE, ERROR_MEMORY, bin_cache_entry_t::in_path, LOG_COLOR_ERROR, LOG_COLOR_INFO, LOG_COLOR_RESET, LOG_COLOR_WARN, log_debug, log_get_color_array(), platform_strdup(), rwlock_rdlock, rwlock_rdunlock, rwlock_wrlock, rwlock_wrunlock, SAFE_FREE, SAFE_MALLOC, and SET_ERRNO.

◆ platform_is_directory()

int platform_is_directory ( const char *  path)

#include <fs.h>

Check if a path is a directory.

Convenience function that checks if a path points to a directory. Does not follow symbolic links.

Parameters
pathPath to check
Returns
Non-zero (true) if path is a directory, 0 (false) otherwise
Note
Does not follow symbolic links.
Returns false for regular files, sockets, pipes, etc.
Returns false if the path doesn't exist.

◆ platform_is_interactive()

bool platform_is_interactive ( void  )

#include <question.h>

Check if interactive prompting is available.

Returns
true if stdin is a TTY and interactive prompting is possible

Use this to check before calling prompt functions in contexts where non-interactive operation is acceptable (e.g., scripted usage).

Referenced by client_crypto_handshake(), prompt_password(), and prompt_password_simple().

◆ platform_is_regular_file()

int platform_is_regular_file ( const char *  path)

#include <fs.h>

Check if a path is a regular file.

Convenience function that checks if a path points to a regular file. Does not follow symbolic links.

Parameters
pathFile path to check
Returns
Non-zero (true) if path is a regular file, 0 (false) otherwise
Note
Does not follow symbolic links.
Returns false for directories, sockets, pipes, etc.
Returns false if the file doesn't exist.

◆ platform_isatty()

int platform_isatty ( int  fd)

#include <system.h>

Check if a file descriptor is a terminal.

Parameters
fdFile descriptor to check
Returns
Non-zero if fd is a terminal, 0 otherwise

Referenced by display_init(), main(), prompt_unknown_host(), and prompt_unknown_host_no_identity().

◆ platform_load_system_ca_certs()

asciichat_error_t platform_load_system_ca_certs ( char **  pem_data_out,
size_t *  pem_size_out 
)

#include <system.h>

Load system CA certificates for TLS/HTTPS.

Loads the operating system's trusted root CA certificates in PEM format. This allows TLS connections to trust the same CAs that the OS trusts.

Platform-specific paths:

  • Linux (Debian/Ubuntu): /etc/ssl/certs/ca-certificates.crt
  • Linux (RHEL/CentOS): /etc/pki/tls/certs/ca-bundle.crt
  • macOS: /etc/ssl/cert.pem or Security framework
  • Windows: Uses CryptoAPI certificate store
Parameters
pem_data_outPointer to receive allocated PEM data (caller must free)
pem_size_outPointer to receive size of PEM data
Returns
ASCIICHAT_OK on success, error code on failure
Note
The caller must free the allocated PEM data with SAFE_FREE() or ALLOC_FREE().
Example:
char* pem_data;
size_t pem_size;
if (platform_load_system_ca_certs(&pem_data, &pem_size) == ASCIICHAT_OK) {
// Use pem_data for TLS verification
SAFE_FREE(pem_data);
}
asciichat_error_t platform_load_system_ca_certs(char **pem_data_out, size_t *pem_size_out)
Load system CA certificates for TLS/HTTPS.

Referenced by https_get().

◆ platform_localtime()

asciichat_error_t platform_localtime ( const time_t *  timer,
struct tm *  result 
)

#include <system.h>

Platform-safe localtime wrapper.

Uses localtime_s on Windows and localtime_r on POSIX. Thread-safe on all platforms.

Parameters
timerPointer to time_t value
resultPointer to struct tm to receive result
Returns
ASCIICHAT_OK on success, error code on error

Referenced by asciichat_error_stats_print(), asciichat_print_error_context(), and get_current_time_formatted().

◆ platform_malloc_size()

size_t platform_malloc_size ( const void *  ptr)

#include <memory.h>

Get the size of an allocated memory block.

Returns the size in bytes of the memory block pointed to by ptr. The block must have been allocated with malloc(), calloc(), or realloc().

Platform-specific implementations:

  • Windows: _msize()
  • macOS: malloc_size()
  • Linux: malloc_usable_size()
Parameters
ptrPointer to allocated memory block
Returns
Size of the block in bytes, or 0 if ptr is NULL
Note
This function requires the block to be allocated with the standard memory allocation functions. Behavior is undefined for invalid pointers.
The returned size may be larger than the requested allocation due to allocator padding and alignment requirements.

◆ platform_memcpy()

asciichat_error_t platform_memcpy ( void *  dest,
size_t  dest_size,
const void *  src,
size_t  count 
)

#include <system.h>

Platform-safe memcpy wrapper.

Uses memcpy_s on Windows when available (C11) and memcpy with bounds checking on POSIX. Provides consistent interface across platforms.

Parameters
destDestination buffer
dest_sizeSize of destination buffer
srcSource buffer
countNumber of bytes to copy
Returns
ASCIICHAT_OK on success, error code on error

◆ platform_memmove()

asciichat_error_t platform_memmove ( void *  dest,
size_t  dest_size,
const void *  src,
size_t  count 
)

#include <system.h>

Platform-safe memmove wrapper.

Uses memmove_s on Windows when available (C11) and memmove with bounds checking on POSIX. Handles overlapping memory regions safely.

Parameters
destDestination buffer
dest_sizeSize of destination buffer
srcSource buffer
countNumber of bytes to move
Returns
ASCIICHAT_OK on success, error code on error

◆ platform_memory_barrier()

void platform_memory_barrier ( void  )

#include <util.h>

Perform memory barrier/fence operation.

Ensures all memory operations before this call are visible to other threads before operations after this call. Platform-specific implementation using atomic operations or memory barriers.

◆ platform_memset()

asciichat_error_t platform_memset ( void *  dest,
size_t  dest_size,
int  ch,
size_t  count 
)

#include <system.h>

Platform-safe memset wrapper.

Uses memset_s on Windows when available (C11) and memset with bounds checking on POSIX. Provides consistent interface across platforms.

Parameters
destDestination buffer
dest_sizeSize of destination buffer
chValue to set (cast to unsigned char)
countNumber of bytes to set
Returns
ASCIICHAT_OK on success, error code on error

◆ platform_mkdir()

asciichat_error_t platform_mkdir ( const char *  path,
int  mode 
)

#include <fs.h>

Create a directory.

Creates a directory with the specified permissions. If the directory already exists, this is not an error.

Platform-specific implementations:

  • POSIX: Uses mkdir() with mode parameter
  • Windows: Uses CreateDirectoryA(), mode is ignored
Parameters
pathDirectory path to create
modeFile permissions (0700 for owner rwx only, ignored on Windows)
Returns
ASCIICHAT_OK on success (or if directory exists), error code on failure
Note
Permissions only apply to parent directories that need to be created.
On Windows, the mode parameter is ignored (uses ACLs).
Returns ASCIICHAT_OK even if the directory already exists.
Example:
if (platform_mkdir("~/.ascii-chat", 0700) == ASCIICHAT_OK) {
// Directory created or already exists
}
asciichat_error_t platform_mkdir(const char *path, int mode)
Create a directory.

Referenced by get_log_dir().

◆ platform_mmap_close()

void platform_mmap_close ( platform_mmap_t mapping)

#include <mmap.h>

Unmap and close a memory-mapped file.

Unmaps the memory region and closes the underlying file handle. Safe to call on an already-closed or uninitialized mapping.

Parameters
mappingMapping handle to close
Note
Does not explicitly sync before closing; kernel will flush dirty pages eventually. Call platform_mmap_sync() first if immediate persistence is required.

Referenced by log_mmap_destroy().

◆ platform_mmap_init()

void platform_mmap_init ( platform_mmap_t mapping)

#include <mmap.h>

Initialize a platform_mmap_t structure.

Sets all fields to safe initial values. Call before first use.

Parameters
mappingPointer to mapping structure to initialize

Referenced by log_mmap_init().

◆ platform_mmap_is_valid()

bool platform_mmap_is_valid ( const platform_mmap_t mapping)

#include <mmap.h>

Check if a mapping is currently valid.

Parameters
mappingMapping handle to check
Returns
true if the mapping is open and usable, false otherwise

◆ platform_mmap_open()

asciichat_error_t platform_mmap_open ( const char *  path,
size_t  size,
platform_mmap_t out 
)

#include <mmap.h>

Memory-map a file for read/write access.

Opens or creates a file and maps it into memory. The file is created if it doesn't exist, and resized to the specified size.

The mapping uses shared mode (MAP_SHARED on POSIX, FILE_MAP_ALL_ACCESS on Windows) so changes are visible to other processes and persist to the file.

Parameters
pathFile path to map (created if doesn't exist)
sizeDesired mapping size in bytes
[out]outOutput mapping handle (must be initialized with platform_mmap_init)
Returns
ASCIICHAT_OK on success, error code on failure
Note
On success, out->addr contains the mapped memory address
Call platform_mmap_close() to unmap and close

Example:

if (platform_mmap_open("/tmp/log.mmap", 1024 * 1024, &mapping) == ASCIICHAT_OK) {
// Use mapping.addr as normal memory
memset(mapping.addr, 0, mapping.size);
}
void platform_mmap_close(platform_mmap_t *mapping)
Unmap and close a memory-mapped file.
asciichat_error_t platform_mmap_open(const char *path, size_t size, platform_mmap_t *out)
Memory-map a file for read/write access.
void platform_mmap_init(platform_mmap_t *mapping)
Initialize a platform_mmap_t structure.
Memory-mapped file handle.

Referenced by log_mmap_init().

◆ platform_mmap_sync()

void platform_mmap_sync ( platform_mmap_t mapping,
bool  async 
)

#include <mmap.h>

Flush memory-mapped changes to disk.

Requests the kernel to flush any modified pages to the underlying file. This is typically not needed as the kernel flushes automatically, but can be used to ensure data persistence at specific points.

Parameters
mappingMapping handle to sync
asyncIf true, return immediately (async flush). If false, block until flush completes (sync flush).
Note
On crash, unflushed data may be lost. For crash-critical data, call platform_mmap_sync(mapping, false) after important writes.

Referenced by log_mmap_destroy(), log_mmap_rotate(), log_mmap_sync(), and log_mmap_write().

◆ platform_open()

int platform_open ( const char *  pathname,
int  flags,
  ... 
)

#include <util.h>

Safe file open (open replacement)

Parameters
pathnameFile path to open
flagsOpen flags (O_RDONLY, O_WRONLY, O_RDWR, etc.)
...Variable arguments (mode for O_CREAT)
Returns
File descriptor, or -1 on error

Cross-platform file opening with consistent behavior. Use PLATFORM_O_* flags for portability.

Referenced by acds_identity_save(), audio_init(), check_known_host(), check_known_host_no_identity(), gpg_sign_with_key(), log_init(), remove_known_host(), and test_logging_disable().

◆ platform_pclose()

asciichat_error_t platform_pclose ( FILE **  stream_ptr)

#include <process.h>

Close a process stream opened with platform_popen()

Closes the stream and waits for the process to terminate. Returns the process exit status.

Platform-specific implementations:

  • POSIX: Uses pclose() and waits for process
  • Windows: Uses _pclose() and waits for process
Parameters
stream_ptrPointer to FILE* stream to close
Returns
ASCIICHAT_OK on success, error code on failure
Note
stream_ptr must be a pointer to a FILE* stream obtained from platform_popen().
The FILE* pointer is set to NULL after closing.
Always sets errno context on failure for debugging.

◆ platform_pipe_close()

int platform_pipe_close ( pipe_t  pipe)

#include <pipe.h>

Close a pipe connection.

Parameters
pipePipe handle to close
Returns
0 on success, non-zero on error

Closes the pipe connection using the appropriate platform-specific function:

  • Windows: CloseHandle()
  • POSIX: close()

Referenced by ssh_agent_add_key(), ssh_agent_has_key(), ssh_agent_is_available(), and ssh_agent_sign().

◆ platform_pipe_connect()

pipe_t platform_pipe_connect ( const char *  path)

#include <pipe.h>

Connect to an agent via named pipe (Windows) or Unix socket (POSIX)

Parameters
pathPath to agent (named pipe path on Windows, socket path on POSIX)
Returns
Pipe handle on success, INVALID_PIPE_VALUE on error

Connects to an agent using the appropriate platform-specific mechanism:

  • Windows: Opens named pipe via CreateFileA
  • POSIX: Connects to Unix domain socket via socket() + connect()
Note
The path format differs by platform:
  • Windows: Named pipe path (e.g., "\\\\.\\pipe\\openssh-ssh-agent")
  • POSIX: Unix socket path (e.g., "/tmp/ssh-XXXXXX/agent.XXXXXX")

◆ platform_pipe_is_valid()

bool platform_pipe_is_valid ( pipe_t  pipe)

#include <pipe.h>

Check if a pipe handle is valid.

Parameters
pipePipe handle to check
Returns
true if pipe is valid, false otherwise

◆ platform_pipe_read()

ssize_t platform_pipe_read ( pipe_t  pipe,
void *  buf,
size_t  len 
)

#include <pipe.h>

Read data from a pipe.

Parameters
pipePipe handle to read from
bufBuffer to store read data
lenMaximum number of bytes to read
Returns
Number of bytes read on success, -1 on error, 0 on connection closed

Reads data from the pipe using the appropriate platform-specific function:

  • Windows: ReadFile()
  • POSIX: read()
Note
This function may read fewer bytes than requested (short read). Caller should handle partial reads if needed.

Referenced by gpg_get_public_key(), ssh_agent_add_key(), ssh_agent_has_key(), and ssh_agent_sign().

◆ platform_pipe_write()

ssize_t platform_pipe_write ( pipe_t  pipe,
const void *  buf,
size_t  len 
)

#include <pipe.h>

Write data to a pipe.

Parameters
pipePipe handle to write to
bufData buffer to write
lenNumber of bytes to write
Returns
Number of bytes written on success, -1 on error

Writes data to the pipe using the appropriate platform-specific function:

  • Windows: WriteFile()
  • POSIX: write()
Note
This function may write fewer bytes than requested (short write). Caller should handle partial writes if needed.

Referenced by gpg_get_public_key(), ssh_agent_add_key(), ssh_agent_has_key(), and ssh_agent_sign().

◆ platform_popen()

asciichat_error_t platform_popen ( const char *  command,
const char *  mode,
FILE **  out_stream 
)

#include <process.h>

Execute a command and return a file stream for reading/writing.

Opens a process for communication, similar to POSIX popen(). Creates a unidirectional pipe to read from or write to the process.

Platform-specific implementations:

  • POSIX: Uses popen()
  • Windows: Uses _popen()
Parameters
commandCommand line to execute (e.g., "ssh-keygen -l -f file.pub")
modeFile stream mode: "r" for reading, "w" for writing
out_streamPointer to receive the FILE* stream
Returns
ASCIICHAT_OK on success, error code on failure
Note
The returned stream must be closed with platform_pclose().
On Windows, the mode parameters are the same as POSIX popen().
Example:
FILE *stream;
if (platform_popen("ssh-keygen -l -f key.pub", "r", &stream) == ASCIICHAT_OK) {
char line[256];
fgets(line, sizeof(line), stream);
platform_pclose(&stream);
}
asciichat_error_t platform_popen(const char *command, const char *mode, FILE **out_stream)
Execute a command and return a file stream for reading/writing.
asciichat_error_t platform_pclose(FILE **stream_ptr)
Close a process stream opened with platform_popen()

◆ platform_print_backtrace()

void platform_print_backtrace ( int  skip_frames)

#include <system.h>

Print a backtrace of the current call stack.

Parameters
skip_framesNumber of frames to skip from the top

Captures a backtrace and prints it using platform_print_backtrace_symbols(). Useful for debugging crashes or errors.

◆ platform_print_backtrace_symbols()

void platform_print_backtrace_symbols ( const char *  label,
char **  symbols,
int  count,
int  skip_frames,
int  max_frames,
backtrace_frame_filter_t  filter 
)

#include <system.h>

Print pre-resolved backtrace symbols with consistent formatting.

Uses colored format for all backtraces: [0] crypto_handshake_server_complete() (lib/crypto/handshake.c:1471) [1] server_crypto_handshake() (src/server/crypto.c:511)

Parameters
labelHeader label (e.g., "Backtrace", "Call stack")
symbolsArray of pre-resolved symbol strings
countNumber of symbols in the array
skip_framesNumber of frames to skip from the start
max_framesMaximum frames to print (0 = unlimited)
filterOptional filter callback to skip specific frames (NULL = no filtering)

Referenced by asciichat_fatal_with_context(), and asciichat_print_error_context().

◆ platform_prompt_question()

int platform_prompt_question ( const char *  prompt,
char *  buffer,
size_t  max_len,
prompt_opts_t  opts 
)

#include <question.h>

Prompt the user for text input.

Parameters
promptThe prompt message to display
bufferBuffer to store the entered text
max_lenMaximum length of the buffer (including null terminator)
optsPrompt options (use PROMPT_OPTS_DEFAULT, PROMPT_OPTS_PASSWORD, etc.)
Returns
0 on success, -1 on failure or user cancellation (Ctrl+C)

Displays a prompt and reads user input. The prompt format depends on opts:

  • same_line=true: "prompt: " (user types on same line)
  • same_line=false: "prompt:\n> " (user types on next line after "> ")

When echo=false, input is hidden and optionally masked with mask_char.

Note
Acquires terminal lock during prompting to prevent log interleaving.
Returns -1 if stdin is not a TTY (non-interactive mode).

Referenced by prompt_password(), and prompt_password_simple().

◆ platform_prompt_yes_no()

bool platform_prompt_yes_no ( const char *  prompt,
bool  default_yes 
)

#include <question.h>

Prompt the user for a yes/no answer.

Parameters
promptThe question to ask (without the yes/no suffix)
default_yesIf true, default is Yes (Y/n); if false, default is No (y/N)
Returns
true if user answered yes, false if user answered no or on error

Displays a yes/no prompt with the default shown in uppercase:

  • default_yes=true: "prompt (Y/n)? "
  • default_yes=false: "prompt (y/N)? "

Accepts: "yes", "y", "Y" for yes; "no", "n", "N" for no. Empty input (just Enter) returns the default value.

Note
Acquires terminal lock during prompting to prevent log interleaving.
Returns false if stdin is not a TTY (non-interactive mode).

Referenced by client_crypto_handshake(), config_create_default(), prompt_unknown_host(), prompt_unknown_host_no_identity(), and server_main().

◆ platform_read()

ssize_t platform_read ( int  fd,
void *  buf,
size_t  count 
)

#include <util.h>

Safe file read (read replacement)

Parameters
fdFile descriptor
bufBuffer to read into
countNumber of bytes to read
Returns
Number of bytes read, or -1 on error

Cross-platform file reading.

◆ platform_resolve_hostname_to_ipv4()

asciichat_error_t platform_resolve_hostname_to_ipv4 ( const char *  hostname,
char *  ipv4_out,
size_t  ipv4_out_size 
)

#include <system.h>

Resolve hostname to IPv4 address.

Performs DNS resolution to convert a hostname to an IPv4 address string. Handles platform-specific networking initialization and cleanup.

Parameters
hostnameHostname to resolve (e.g., "example.com")
ipv4_outBuffer to store the resolved IPv4 address (e.g., "192.168.1.1")
ipv4_out_sizeSize of the output buffer
Returns
ASCIICHAT_OK on success, error code on failure

Referenced by validate_opt_ip_address().

◆ platform_set_console_ctrl_handler()

bool platform_set_console_ctrl_handler ( console_ctrl_handler_t  handler)

#include <system.h>

Register a console control handler (for Ctrl+C, etc.)

Parameters
handlerHandler function to register, or NULL to unregister
Returns
true on success, false on failure

This provides cross-platform handling for console control events like Ctrl+C.

  • On Windows: Uses SetConsoleCtrlHandler() for proper signal handling
  • On Unix: Uses sigaction() for SIGINT/SIGTERM handling

Unlike platform_signal() which uses CRT signal() on Windows (known issues), this function uses the native Windows API for reliable Ctrl+C handling.

Note
Only one handler is supported at a time. Registering a new handler replaces the previous one.

Referenced by client_main(), and mirror_main().

◆ platform_set_last_error()

void platform_set_last_error ( int  error)

#include <util.h>

Set platform error code.

Parameters
errorError code to set

Sets the current error code. Equivalent to errno on POSIX.

◆ platform_setenv()

int platform_setenv ( const char *  name,
const char *  value 
)

#include <system.h>

Set an environment variable.

Parameters
nameEnvironment variable name
valueEnvironment variable value (or NULL to unset)
Returns
0 on success, non-zero on error

Sets or unsets an environment variable.

Referenced by __attribute__().

◆ platform_signal()

signal_handler_t platform_signal ( int  sig,
signal_handler_t  handler 
)

#include <system.h>

Set a signal handler.

Parameters
sigSignal number (e.g., SIGINT, SIGTERM)
handlerSignal handler function (or SIG_DFL, SIG_IGN)
Returns
Previous signal handler, or SIG_ERR on error

Registers a signal handler for the specified signal.

Referenced by client_main(), mirror_main(), and server_main().

◆ platform_sleep_ms()

void platform_sleep_ms ( unsigned int  ms)

#include <system.h>

Sleep for a specified number of milliseconds.

Parameters
msNumber of milliseconds to sleep

Sleeps the current thread for the specified duration.

Referenced by __attribute__(), acds_server_shutdown(), disconnect_client_for_bad_data(), and discovery_tui_select().

◆ platform_sleep_usec()

void platform_sleep_usec ( unsigned int  usec)

#include <abstraction.h>

High-precision sleep function with microsecond precision.

Parameters
usecNumber of microseconds to sleep

Sleeps the current thread for the specified number of microseconds with high precision. Supports early wakeup via shutdown signaling.

Note
On Windows, Sleep() has ~15ms minimum resolution. This function uses more precise timing mechanisms for microsecond-level accuracy.
On POSIX, uses usleep() or nanosleep() for microsecond precision.
Example:
platform_sleep_usec(1000); // Sleep for 1000 microseconds (1 millisecond)
void platform_sleep_usec(unsigned int usec)
High-precision sleep function with microsecond precision.

Referenced by __attribute__(), adaptive_sleep_do(), audio_cleanup(), audio_stop_thread(), capture_stop_thread(), client_audio_render_thread(), client_main(), client_send_thread_func(), client_video_render_thread(), keepalive_stop_thread(), mirror_main(), protocol_stop_connection(), server_connection_establish(), stats_logger_thread(), and tcp_client_connect().

◆ platform_snprintf()

int platform_snprintf ( char *  str,
size_t  size,
const char *  format,
  ... 
)

#include <util.h>

Safe string formatting (snprintf replacement)

Parameters
strDestination buffer
sizeSize of destination buffer
formatPrintf-style format string
...Variable arguments for format string
Returns
Number of characters written, or negative on error

Cross-platform wrapper around snprintf that behaves consistently on Windows and POSIX systems.

◆ platform_stat()

asciichat_error_t platform_stat ( const char *  path,
platform_stat_t stat_out 
)

#include <fs.h>

Get file statistics.

Retrieves metadata about a file without following symbolic links.

Platform-specific implementations:

  • POSIX: Uses lstat()
  • Windows: Uses GetFileAttributesExA()
Parameters
pathFile path to stat
stat_outPointer to platform_stat_t to receive results
Returns
ASCIICHAT_OK on success, error code on failure
Note
Does not follow symbolic links (uses lstat on POSIX).
Sets errno context on failure.
Example:
platform_stat_t stat_info;
if (platform_stat("key_file", &stat_info) == ASCIICHAT_OK) {
if (stat_info.is_regular_file) {
// Process the file
}
}
asciichat_error_t platform_stat(const char *path, platform_stat_t *stat_out)
Get file statistics.
File type information from stat()
Definition fs.h:31
int is_regular_file
Non-zero if file is a regular file.
Definition fs.h:34

◆ platform_strcasecmp()

int platform_strcasecmp ( const char *  s1,
const char *  s2 
)

#include <util.h>

Case-insensitive string comparison.

Parameters
s1First string
s2Second string
Returns
0 if equal, <0 if s1<s2, >0 if s1>s2 (case-insensitive)

Compares two strings case-insensitively.

Referenced by options_init(), validate_opt_log_level(), and validate_opt_reconnect().

◆ platform_strcat()

char * platform_strcat ( char *  dest,
size_t  dest_size,
const char *  src 
)

#include <string.h>

Safe version of strcat with buffer size protection.

Parameters
destDestination buffer (must not be NULL)
dest_sizeSize of destination buffer (must be > 0)
srcSource string to append (must not be NULL)
Returns
Destination buffer on success, NULL on error

Safely appends a source string to a destination buffer with buffer size protection. Prevents buffer overflow by validating available space before concatenation.

SAFETY FEATURES:

  • Buffer size validation prevents overflow
  • Guaranteed null termination of result
  • Returns NULL on error (buffer too small, invalid parameters)
  • Validates available space before appending
Note
Function validates that destination has sufficient space for source.
If concatenation would exceed buffer size, returns NULL.
Result is always null-terminated on success.
Warning
Always check return value. NULL indicates operation failed (typically buffer too small).
Example
char buf[64] = "Hello";
if (platform_strcat(buf, sizeof(buf), " world") == NULL) {
// Operation failed (buffer too small)
}
char * platform_strcat(char *dest, size_t dest_size, const char *src)
Safe version of strcat with buffer size protection.

◆ platform_strcpy()

asciichat_error_t platform_strcpy ( char *  dest,
size_t  dest_size,
const char *  src 
)

#include <system.h>

Platform-safe strcpy wrapper.

Uses strcpy_s on Windows when available (C11) and strncpy with bounds checking on POSIX. Always null-terminates the destination string.

Parameters
destDestination buffer
dest_sizeSize of destination buffer
srcSource string
Returns
ASCIICHAT_OK on success, error code on error

◆ platform_strdup()

char * platform_strdup ( const char *  s)

#include <util.h>

Duplicate string (strdup replacement)

Parameters
sString to duplicate
Returns
Dynamically allocated copy of string, or NULL on error

Cross-platform string duplication. Allocated memory should be freed with free().

Referenced by config_create_default(), config_load_and_apply(), expand_path(), parse_ssh_private_key(), platform_is_binary_in_path(), remove_known_host(), symbol_cache_insert(), and symbol_cache_resolve_batch().

◆ platform_strerror()

const char * platform_strerror ( int  errnum)

#include <util.h>

Get thread-safe error string.

Parameters
errnumError number (errno or GetLastError on Windows)
Returns
Pointer to error description string

Returns string description of error code. Thread-safe.

◆ platform_strlcat()

size_t platform_strlcat ( char *  dst,
const char *  src,
size_t  size 
)

#include <util.h>

Safe string concatenation with size tracking (strlcat)

Parameters
dstDestination buffer (must be null-terminated)
srcSource string
sizeSize of destination buffer
Returns
Length of concatenated string (before truncation)

Safely appends source to destination, truncating if necessary. Always null-terminates destination (if size > 0).

◆ platform_strlcpy()

size_t platform_strlcpy ( char *  dst,
const char *  src,
size_t  size 
)

#include <util.h>

Safe string copy with size tracking (strlcpy)

Parameters
dstDestination buffer
srcSource string
sizeSize of destination buffer
Returns
Length of source string (before truncation)

Safely copies string to destination, truncating if necessary. Always null-terminates destination (if size > 0).

◆ platform_strncasecmp()

int platform_strncasecmp ( const char *  s1,
const char *  s2,
size_t  n 
)

#include <util.h>

Case-insensitive string comparison with length limit.

Parameters
s1First string
s2Second string
nMaximum number of characters to compare
Returns
0 if equal, <0 if s1<s2, >0 if s1>s2 (case-insensitive)

Compares up to n characters of two strings case-insensitively.

◆ platform_strncpy()

int platform_strncpy ( char *  dst,
size_t  dst_size,
const char *  src,
size_t  count 
)

#include <util.h>

Safe string copy with explicit size bounds (strncpy replacement)

Parameters
dstDestination buffer
dst_sizeSize of destination buffer
srcSource string
countMaximum number of characters to copy
Returns
0 on success, -1 on overflow/error

Safely copies string with explicit destination size and character count limits. Always null-terminates destination on success.

Referenced by parse_private_key(), and parse_public_key().

◆ platform_strndup()

char * platform_strndup ( const char *  s,
size_t  n 
)

#include <util.h>

Duplicate string with length limit (strndup replacement)

Parameters
sString to duplicate
nMaximum number of characters to copy
Returns
Dynamically allocated copy of string (max n chars), or NULL on error

Duplicates up to n characters from string. Allocated memory should be freed with free().

◆ platform_strtok_r()

char * platform_strtok_r ( char *  str,
const char *  delim,
char **  saveptr 
)

#include <util.h>

Thread-safe string tokenization (strtok_r replacement)

Parameters
strString to tokenize (or NULL to continue tokenizing)
delimDelimiter characters
saveptrPointer to save tokenization state
Returns
Pointer to next token, or NULL when no more tokens

Thread-safe version of strtok. State is maintained in saveptr between calls.

◆ platform_ttyname()

const char * platform_ttyname ( int  fd)

#include <system.h>

Get the name of the terminal associated with a file descriptor.

Parameters
fdFile descriptor
Returns
Pointer to terminal name string (or NULL), may be static, do not free

Returns the name of the terminal device associated with the file descriptor.

Note
The returned string may be a static buffer. Do not modify or free it.

◆ platform_unlink()

int platform_unlink ( const char *  pathname)

#include <util.h>

Delete/unlink file.

Parameters
pathnameFile path to delete
Returns
0 on success, -1 on error

Deletes a file. Cross-platform unlink replacement.

◆ platform_vsnprintf()

int platform_vsnprintf ( char *  str,
size_t  size,
const char *  format,
va_list  ap 
)

#include <util.h>

Safe variable-argument string formatting.

Parameters
strDestination buffer
sizeSize of destination buffer
formatPrintf-style format string
apVariable argument list
Returns
Number of characters written, or negative on error

Variable-argument version of platform_snprintf.

◆ platform_write()

ssize_t platform_write ( int  fd,
const void *  buf,
size_t  count 
)

#include <abstraction.h>

Platform-safe write function.

Safe file write (write replacement)

Parameters
fdFile descriptor to write to
bufBuffer containing data to write
countNumber of bytes to write
Returns
Number of bytes written on success, -1 on error

Cross-platform write function that handles Windows-specific quirks (e.g., CRLF line endings) and provides consistent behavior across platforms.

Note
On Windows, automatically handles line ending conversion if needed.
On POSIX, equivalent to standard write().
Parameters
fdFile descriptor
bufBuffer to write from
countNumber of bytes to write
Returns
Number of bytes written, or -1 on error

Cross-platform file writing.

◆ print_terminal_capabilities()

void print_terminal_capabilities ( const terminal_capabilities_t caps)

#include <terminal.h>

Print terminal capabilities to stdout.

Parameters
capsTerminal capabilities structure (must not be NULL)

Prints a detailed report of terminal capabilities to stdout including:

  • Color support level and count
  • UTF-8 encoding support
  • Render mode preferences
  • Terminal type and environment variables
  • Detection reliability
Note
Useful for debugging terminal capability detection.
Output is formatted for human readability.

Referenced by client_main(), and main().

◆ rwlock_destroy()

int rwlock_destroy ( rwlock_t lock)

#include <rwlock.h>

Destroy a read-write lock.

Parameters
lockPointer to read-write lock to destroy
Returns
0 on success, non-zero on error

Destroys the read-write lock and frees any associated resources. The lock must not be held by any thread when this is called.

Referenced by mixer_create(), mixer_destroy(), platform_cleanup_binary_path_cache(), server_main(), symbol_cache_cleanup(), and timer_system_cleanup().

◆ rwlock_destroy_impl()

int rwlock_destroy_impl ( rwlock_t lock)

#include <rwlock.h>

Destroy a read-write lock (implementation function)

Parameters
lockPointer to read-write lock to destroy
Returns
0 on success, non-zero on error
Note
This is the implementation function. Use rwlock_destroy() instead.

◆ rwlock_init()

int rwlock_init ( rwlock_t lock)

#include <rwlock.h>

Initialize a read-write lock.

Parameters
lockPointer to read-write lock to initialize
Returns
0 on success, non-zero on error

Initializes the read-write lock for use. Must be called before any other lock operations.

Referenced by mixer_create(), server_main(), symbol_cache_init(), and timer_system_init().

◆ rwlock_init_impl()

int rwlock_init_impl ( rwlock_t lock)

#include <rwlock.h>

Initialize a read-write lock (implementation function)

Parameters
lockPointer to read-write lock to initialize
Returns
0 on success, non-zero on error
Note
This is the implementation function. Use rwlock_init() instead.

◆ rwlock_rdlock_impl()

int rwlock_rdlock_impl ( rwlock_t lock)

#include <rwlock.h>

Acquire a read lock (implementation function)

Parameters
lockPointer to read-write lock
Returns
0 on success, non-zero on error

Acquires a shared read lock. Multiple threads can hold read locks simultaneously. Blocks if a write lock is held.

Note
This is the implementation function. Use rwlock_rdlock() macro instead, which includes debug tracking in debug builds.

◆ rwlock_rdunlock_impl()

int rwlock_rdunlock_impl ( rwlock_t lock)

#include <rwlock.h>

Release a read lock (implementation function)

Parameters
lockPointer to read-write lock
Returns
0 on success, non-zero on error

Releases a shared read lock held by the calling thread.

Note
This is the implementation function. Use rwlock_rdunlock() macro instead, which includes debug tracking in debug builds.

◆ rwlock_wrlock_impl()

int rwlock_wrlock_impl ( rwlock_t lock)

#include <rwlock.h>

Acquire a write lock (implementation function)

Parameters
lockPointer to read-write lock
Returns
0 on success, non-zero on error

Acquires an exclusive write lock. Only one thread can hold a write lock, and it excludes all read locks. Blocks if any locks are held.

Note
This is the implementation function. Use rwlock_wrlock() macro instead, which includes debug tracking in debug builds.

◆ rwlock_wrunlock_impl()

int rwlock_wrunlock_impl ( rwlock_t lock)

#include <rwlock.h>

Release a write lock (implementation function)

Parameters
lockPointer to read-write lock
Returns
0 on success, non-zero on error

Releases an exclusive write lock held by the calling thread.

Note
This is the implementation function. Use rwlock_wrunlock() macro instead, which includes debug tracking in debug builds.

◆ safe_fprintf()

int safe_fprintf ( FILE *  stream,
const char *  format,
  ... 
)

#include <string.h>

Safe version of fprintf.

Platform-safe fprintf wrapper.

Parameters
streamFile stream to write to (must not be NULL)
formatFormat string (must not be NULL)
...Variable arguments for format string
Returns
Number of characters written, or negative value on error

Formats and writes to a file stream with error checking. Validates that formatting operations succeed and returns meaningful error codes.

SAFETY FEATURES:

  • Stream validation before writing
  • Error detection and reporting
  • Return value indicates success or failure
Note
Return value indicates number of characters written on success.
Negative return value indicates an error occurred.
Function validates stream and format string before operations.
Warning
Always check return value. Negative values indicate errors.

Uses fprintf_s on Windows and fprintf on POSIX.

Parameters
streamFile stream
formatFormat string
...Variable arguments
Returns
Number of characters written or negative on error

Referenced by add_known_host(), asciichat_fatal_with_context(), asciichat_print_error_context(), log_init(), log_labeled(), log_msg(), log_plain_msg(), and webcam_print_init_error_help().

◆ safe_snprintf()

int safe_snprintf ( char *  buffer,
size_t  buffer_size,
const char *  format,
  ... 
)

#include <string.h>

Safe version of snprintf that ensures null termination.

Platform-safe snprintf wrapper.

Parameters
bufferBuffer to write to (must not be NULL)
buffer_sizeSize of the buffer (must be > 0)
formatFormat string (must not be NULL)
...Variable arguments for format string
Returns
Number of characters written (not including null terminator), or negative value on error

Formats a string into a buffer with guaranteed null termination. Always ensures the buffer is null-terminated even if truncation occurs, unlike the standard snprintf() which may not null-terminate on some platforms.

SAFETY FEATURES:

  • Guaranteed null termination (buffer always ends with '\0')
  • Buffer size validation before formatting
  • Truncation detection via return value
  • No buffer overflow (operation is bounded by buffer_size)
Note
Return value indicates number of characters that would have been written if buffer was large enough (not including null terminator).
If return value >= buffer_size, output was truncated.
Buffer is always null-terminated, even if truncation occurred.
Warning
Always check return value. Values >= buffer_size indicate truncation.
Example
char buf[64];
int len = safe_snprintf(buf, sizeof(buf), "Hello %s", "world");
if (len >= sizeof(buf)) {
// Output was truncated
}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe version of snprintf that ensures null termination.

Uses snprintf_s on Windows and snprintf with additional safety on POSIX. Always null-terminates the output buffer.

Parameters
bufferOutput buffer
buffer_sizeSize of output buffer
formatFormat string
...Variable arguments
Returns
Number of characters written (excluding null terminator) or negative on error

Referenced by build_github_gpg_url(), build_github_ssh_url(), build_gitlab_gpg_url(), build_gitlab_ssh_url(), check_gpg_key_expiry(), check_known_host(), check_known_host_no_identity(), config_create_default(), config_load_and_apply(), crypto_handshake_client_key_exchange(), crypto_handshake_server_auth_challenge(), crypto_handshake_server_start(), display_mitm_warning(), expand_path(), format_bytes_pretty(), format_gpg_key_display(), format_public_key(), get_config_dir(), get_known_hosts_path(), get_log_dir(), gpg_agent_sign(), gpg_get_public_key(), gpg_sign_detached_ed25519(), gpg_sign_with_key(), gpg_verify_signature_with_binary(), https_get(), options_init(), parse_gpg_key(), parse_private_key(), parse_ssh_private_key(), path_validate_user_path(), prompt_unknown_host(), prompt_unknown_host_no_identity(), remove_known_host(), stats_logger_thread(), and test_get_binary_path().

◆ safe_sscanf()

int safe_sscanf ( const char *  str,
const char *  format,
  ... 
)

#include <string.h>

Safe version of sscanf with validation.

Parameters
strString to parse (must not be NULL)
formatFormat string (must not be NULL)
...Variable arguments for parsed values (must not be NULL)
Returns
Number of successfully parsed items, or negative value on error

Safely parses a string using a format specification with input validation. Returns the number of successfully parsed items and validates input parameters.

SAFETY FEATURES:

  • Input string and format validation
  • Return value indicates number of successfully parsed items
  • Error detection and reporting
Note
Return value indicates number of successfully parsed format specifiers.
Negative return value indicates an error occurred.
Function validates string and format before parsing.
Warning
Always check return value. Negative values or fewer parsed items than expected indicate errors or incomplete parsing.
Example
int x, y;
int parsed = safe_sscanf("10 20", "%d %d", &x, &y);
if (parsed == 2) {
// Both values parsed successfully
}
int safe_sscanf(const char *str, const char *format,...)
Safe version of sscanf with validation.

◆ socket_accept()

socket_t socket_accept ( socket_t  sock,
struct sockaddr *  addr,
socklen_t *  addrlen 
)

#include <socket.h>

Accept an incoming connection.

Parameters
sockListening socket
addrPointer to store peer address (or NULL)
addrlenPointer to address length (input/output)
Returns
New socket handle on success, INVALID_SOCKET_VALUE on error

Referenced by accept_with_timeout().

◆ socket_bind()

int socket_bind ( socket_t  sock,
const struct sockaddr *  addr,
socklen_t  addrlen 
)

#include <socket.h>

Bind a socket to an address.

Parameters
sockSocket to bind
addrAddress to bind to
addrlenLength of address structure
Returns
0 on success, non-zero on error

◆ socket_cleanup()

void socket_cleanup ( void  )

#include <socket.h>

Cleanup socket subsystem.

Cleans up the socket subsystem. On Windows, this cleans up Winsock. Should be called during program shutdown.

Referenced by server_main().

◆ socket_close()

int socket_close ( socket_t  sock)

#include <socket.h>

Close a socket.

Parameters
sockSocket to close
Returns
0 on success, non-zero on error

Referenced by acds_client_connect(), acds_client_disconnect(), acds_client_handler(), disconnect_client_for_bad_data(), https_get(), server_main(), tcp_server_reject_client(), tcp_server_run(), and tcp_server_shutdown().

◆ socket_connect()

int socket_connect ( socket_t  sock,
const struct sockaddr *  addr,
socklen_t  addrlen 
)

#include <socket.h>

Connect to a remote address.

Parameters
sockSocket to connect
addrRemote address to connect to
addrlenLength of address structure
Returns
0 on success, non-zero on error

◆ socket_create()

socket_t socket_create ( int  domain,
int  type,
int  protocol 
)

#include <socket.h>

Create a new socket.

Parameters
domainSocket domain (e.g., AF_INET, AF_INET6, AF_UNIX)
typeSocket type (e.g., SOCK_STREAM, SOCK_DGRAM)
protocolProtocol (typically 0 for automatic)
Returns
Socket handle on success, INVALID_SOCKET_VALUE on error

Referenced by server_connection_establish(), and tcp_client_connect().

◆ socket_fd_isset()

int socket_fd_isset ( socket_t  sock,
fd_set *  set 
)

#include <socket.h>

Check if a socket is in an fd_set.

Parameters
sockSocket to check
setfd_set to check in
Returns
Non-zero if socket is in set, 0 otherwise

Referenced by accept_with_timeout(), connect_with_timeout(), recv_with_timeout(), send_with_timeout(), and tcp_server_run().

◆ socket_fd_set()

void socket_fd_set ( socket_t  sock,
fd_set *  set 
)

#include <socket.h>

Add a socket to an fd_set.

Parameters
sockSocket to add
setfd_set to add to

Referenced by accept_with_timeout(), connect_with_timeout(), recv_with_timeout(), send_with_timeout(), and tcp_server_run().

◆ socket_fd_zero()

void socket_fd_zero ( fd_set *  set)

#include <socket.h>

Clear an fd_set.

Parameters
setfd_set to clear

Referenced by accept_with_timeout(), connect_with_timeout(), recv_with_timeout(), send_with_timeout(), and tcp_server_run().

◆ socket_get_error()

int socket_get_error ( socket_t  sock)

#include <socket.h>

Get socket-specific error code.

Parameters
sockSocket to query
Returns
Error code (platform-specific)

Referenced by server_connection_establish().

◆ socket_get_error_string()

const char * socket_get_error_string ( void  )

#include <socket.h>

Get last socket error as string.

Returns
Pointer to error string (may be static, do not free)
Note
The returned string may be a static buffer. Do not modify or free it.

Referenced by acds_client_connect(), network_error_string(), and tcp_server_run().

◆ socket_get_fd()

int socket_get_fd ( socket_t  sock)

#include <socket.h>

Get the underlying file descriptor (POSIX compatibility)

Parameters
sockSocket handle
Returns
File descriptor value (on POSIX, same as socket handle)

◆ socket_get_last_error()

int socket_get_last_error ( void  )

#include <socket.h>

Get last socket error code.

Returns
Error code (platform-specific)

Referenced by tcp_server_run().

◆ socket_get_peer_address()

int socket_get_peer_address ( socket_t  sock,
struct sockaddr *  addr,
socklen_t *  addrlen 
)

#include <socket.h>

Get peer address (convenience function)

Parameters
sockConnected socket
addrPointer to store peer address
addrlenPointer to address length (input/output)
Returns
0 on success, non-zero on error
Note
This is a convenience wrapper around socket_getpeername().

◆ socket_getpeername()

int socket_getpeername ( socket_t  sock,
struct sockaddr *  addr,
socklen_t *  addrlen 
)

#include <socket.h>

Get peer address.

Parameters
sockConnected socket
addrPointer to store peer address
addrlenPointer to address length (input/output)
Returns
0 on success, non-zero on error

◆ socket_getsockname()

int socket_getsockname ( socket_t  sock,
struct sockaddr *  addr,
socklen_t *  addrlen 
)

#include <socket.h>

Get socket local address.

Parameters
sockSocket to query
addrPointer to store local address
addrlenPointer to address length (input/output)
Returns
0 on success, non-zero on error

◆ socket_getsockopt()

int socket_getsockopt ( socket_t  sock,
int  level,
int  optname,
void *  optval,
socklen_t *  optlen 
)

#include <socket.h>

Get socket option.

Parameters
sockSocket to query
levelOption level (e.g., SOL_SOCKET, IPPROTO_TCP)
optnameOption name (e.g., SO_REUSEADDR, TCP_NODELAY)
optvalPointer to store option value
optlenPointer to option length (input/output)
Returns
0 on success, non-zero on error

Referenced by connect_with_timeout().

◆ socket_init()

asciichat_error_t socket_init ( void  )

#include <socket.h>

Initialize socket subsystem (required on Windows)

Returns
ASCIICHAT_OK on success, error code on failure

Initializes the socket subsystem. On Windows, this initializes Winsock. Must be called before any socket operations.

◆ socket_is_valid()

bool socket_is_valid ( socket_t  sock)

#include <socket.h>

Check if a socket handle is valid.

Parameters
sockSocket handle to check
Returns
true if socket is valid, false otherwise

Referenced by packet_send(), tcp_client_close(), tcp_client_connect(), tcp_client_destroy(), and tcp_client_shutdown().

◆ socket_listen()

int socket_listen ( socket_t  sock,
int  backlog 
)

#include <socket.h>

Listen for incoming connections.

Parameters
sockSocket to listen on (must be bound)
backlogMaximum length of the queue of pending connections
Returns
0 on success, non-zero on error

◆ socket_optimize_for_streaming()

void socket_optimize_for_streaming ( socket_t  sock)

#include <socket.c>

Optimize socket for high-throughput video streaming.

Consolidates socket configuration for real-time video streaming:

  • Disables Nagle's algorithm (TCP_NODELAY)
  • Sets large send/receive buffers with automatic fallbacks
  • Enables TCP keepalive
  • Sets send/receive timeouts

This common implementation applies to both POSIX and Windows platforms.

Parameters
sockSocket to configure
sockSocket to optimize

Applies multiple socket optimizations for video streaming:

  • Disables Nagle's algorithm (TCP_NODELAY)
  • Sets large send/receive buffers (2MB with fallbacks to 512KB and 128KB)
  • Enables keepalive
  • Sets timeouts to prevent blocking indefinitely

This function consolidates socket configuration that is needed for real-time video streaming. It gracefully handles buffer size negotiation by falling back to smaller sizes if the OS doesn't support large buffers.

Note
Warnings are logged if individual options fail, but the function continues to apply the remaining options.

Definition at line 36 of file socket.c.

36 {
37 // 1. Disable Nagle's algorithm - CRITICAL for real-time video
38 // TCP_NODELAY ensures data is sent immediately without waiting for ACKs
39 int nodelay = 1;
40 if (socket_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)) != 0) {
41 log_warn("Failed to disable Nagle's algorithm (TCP_NODELAY) on socket");
42 }
43
44 // 2. Increase send buffer for video streaming (2MB with fallbacks)
45 // Large buffers reduce packet drops during bursty frame transmission
46 int send_buffer = 2 * 1024 * 1024; // 2MB
47 if (socket_setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &send_buffer, sizeof(send_buffer)) != 0) {
48 send_buffer = 512 * 1024; // 512KB fallback
49 if (socket_setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &send_buffer, sizeof(send_buffer)) != 0) {
50 send_buffer = 128 * 1024; // 128KB fallback
51 socket_setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &send_buffer, sizeof(send_buffer));
52 }
53 }
54
55 // 3. Increase receive buffer (2MB with fallbacks)
56 // Allows buffering of multiple incoming frames before processing
57 int recv_buffer = 2 * 1024 * 1024; // 2MB
58 if (socket_setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recv_buffer, sizeof(recv_buffer)) != 0) {
59 recv_buffer = 512 * 1024; // 512KB fallback
60 if (socket_setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recv_buffer, sizeof(recv_buffer)) != 0) {
61 recv_buffer = 128 * 1024; // 128KB fallback
62 socket_setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recv_buffer, sizeof(recv_buffer));
63 }
64 }
65
66 // 4. Enable keepalive to detect dead connections
67 int keepalive = 1;
68 socket_setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));
69}

References log_warn, and socket_setsockopt().

◆ socket_poll()

int socket_poll ( struct pollfd *  fds,
nfds_t  nfds,
int  timeout 
)

#include <socket.h>

Poll sockets for events.

Parameters
fdsArray of pollfd structures
nfdsNumber of file descriptors to poll
timeoutTimeout in milliseconds (-1 for infinite)
Returns
Number of sockets with events, -1 on error

◆ socket_recv()

ssize_t socket_recv ( socket_t  sock,
void *  buf,
size_t  len,
int  flags 
)

#include <socket.h>

Receive data from a socket.

Parameters
sockSocket to receive from
bufBuffer to store received data
lenMaximum number of bytes to receive
flagsSocket flags (typically 0)
Returns
Number of bytes received on success, -1 on error, 0 on connection closed

◆ socket_recvfrom()

ssize_t socket_recvfrom ( socket_t  sock,
void *  buf,
size_t  len,
int  flags,
struct sockaddr *  src_addr,
socklen_t *  addrlen 
)

#include <socket.h>

Receive data from a specific address (UDP)

Parameters
sockSocket to receive from
bufBuffer to store received data
lenMaximum number of bytes to receive
flagsSocket flags (typically 0)
src_addrPointer to store source address (or NULL)
addrlenPointer to address length (input/output)
Returns
Number of bytes received on success, -1 on error

◆ socket_select()

int socket_select ( socket_t  max_fd,
fd_set *  readfds,
fd_set *  writefds,
fd_set *  exceptfds,
struct timeval *  timeout 
)

#include <socket.h>

Select sockets for I/O readiness.

Parameters
max_fdHighest file descriptor number (plus 1)
readfdsSet of sockets to check for read readiness (or NULL)
writefdsSet of sockets to check for write readiness (or NULL)
exceptfdsSet of sockets to check for exceptions (or NULL)
timeoutTimeout value (or NULL for infinite)
Returns
Number of ready sockets, -1 on error

Referenced by accept_with_timeout(), connect_with_timeout(), recv_with_timeout(), send_with_timeout(), and tcp_server_run().

◆ socket_send()

ssize_t socket_send ( socket_t  sock,
const void *  buf,
size_t  len,
int  flags 
)

#include <socket.h>

Send data on a socket.

Parameters
sockSocket to send on
bufData buffer to send
lenNumber of bytes to send
flagsSocket flags (typically 0)
Returns
Number of bytes sent on success, -1 on error

◆ socket_sendto()

ssize_t socket_sendto ( socket_t  sock,
const void *  buf,
size_t  len,
int  flags,
const struct sockaddr *  dest_addr,
socklen_t  addrlen 
)

#include <socket.h>

Send data to a specific address (UDP)

Parameters
sockSocket to send on
bufData buffer to send
lenNumber of bytes to send
flagsSocket flags (typically 0)
dest_addrDestination address
addrlenLength of destination address
Returns
Number of bytes sent on success, -1 on error

◆ socket_set_blocking()

int socket_set_blocking ( socket_t  sock)

#include <socket.h>

Set socket to blocking mode.

Parameters
sockSocket to configure
Returns
0 on success, non-zero on error

Referenced by connect_with_timeout().

◆ socket_set_buffer_sizes()

int socket_set_buffer_sizes ( socket_t  sock,
int  recv_size,
int  send_size 
)

#include <socket.h>

Set socket buffer sizes.

Parameters
sockSocket to configure
recv_sizeReceive buffer size in bytes
send_sizeSend buffer size in bytes
Returns
0 on success, non-zero on error

◆ socket_set_keepalive()

int socket_set_keepalive ( socket_t  sock,
bool  keepalive 
)

#include <socket.h>

Set SO_KEEPALIVE socket option.

Parameters
sockSocket to configure
keepalivetrue to enable keepalive, false to disable
Returns
0 on success, non-zero on error

Referenced by server_connection_establish(), and tcp_client_connect().

◆ socket_set_keepalive_params()

int socket_set_keepalive_params ( socket_t  sock,
bool  enable,
int  idle,
int  interval,
int  count 
)

#include <socket.h>

Set TCP keepalive parameters.

Parameters
sockSocket to configure
enableEnable/disable keepalive
idleIdle time before sending first keepalive probe (seconds)
intervalInterval between keepalive probes (seconds)
countNumber of keepalive probes before connection failure
Returns
0 on success, non-zero on error

Referenced by set_socket_keepalive().

◆ socket_set_linger()

int socket_set_linger ( socket_t  sock,
bool  enable,
int  timeout 
)

#include <socket.h>

Set SO_LINGER socket option.

Parameters
sockSocket to configure
enableEnable/disable lingering
timeoutLinger timeout in seconds
Returns
0 on success, non-zero on error

◆ socket_set_nodelay()

int socket_set_nodelay ( socket_t  sock,
bool  nodelay 
)

#include <socket.h>

Set TCP_NODELAY socket option (disable Nagle's algorithm)

Parameters
sockSocket to configure
nodelaytrue to disable Nagle's algorithm, false to enable
Returns
0 on success, non-zero on error

◆ socket_set_nonblocking()

int socket_set_nonblocking ( socket_t  sock,
bool  nonblocking 
)

#include <socket.h>

Set socket to non-blocking mode.

Parameters
sockSocket to configure
nonblockingtrue for non-blocking, false for blocking
Returns
0 on success, non-zero on error

Referenced by set_socket_nonblocking().

◆ socket_set_reuseaddr()

int socket_set_reuseaddr ( socket_t  sock,
bool  reuse 
)

#include <socket.h>

Set SO_REUSEADDR socket option.

Parameters
sockSocket to configure
reusetrue to enable reuse, false to disable
Returns
0 on success, non-zero on error

◆ socket_set_timeout()

int socket_set_timeout ( socket_t  sock,
uint32_t  timeout_ms 
)

#include <socket.h>

Set socket receive and send timeouts.

Parameters
sockSocket to configure
timeout_msTimeout in milliseconds
Returns
0 on success, non-zero on error

Sets both SO_RCVTIMEO (receive timeout) and SO_SNDTIMEO (send timeout) to prevent indefinite blocking on socket operations.

Platform-specific implementations:

  • Windows: Uses DWORD timeout in milliseconds
  • POSIX: Uses struct timeval with seconds and microseconds
Parameters
sockSocket to configure
timeout_msTimeout in milliseconds
Returns
0 on success, non-zero on error

Cross-platform implementation that sets both SO_RCVTIMEO and SO_SNDTIMEO. Platform-specific socket_setsockopt() handles the differences between Windows (DWORD milliseconds) and POSIX (struct timeval).

Definition at line 81 of file socket.c.

81 {
82#ifdef _WIN32
83 // Windows: SO_RCVTIMEO and SO_SNDTIMEO use DWORD (milliseconds)
84 DWORD timeout_val = (DWORD)timeout_ms;
85 if (socket_setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout_val, sizeof(timeout_val)) != 0) {
86 return -1;
87 }
88 if (socket_setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout_val, sizeof(timeout_val)) != 0) {
89 return -1;
90 }
91#else
92 // POSIX: SO_RCVTIMEO and SO_SNDTIMEO use struct timeval (seconds + microseconds)
93 struct timeval tv;
94 tv.tv_sec = timeout_ms / 1000;
95 tv.tv_usec = (timeout_ms % 1000) * 1000;
96 if (socket_setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
97 return -1;
98 }
99 if (socket_setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) != 0) {
100 return -1;
101 }
102#endif
103 return 0;
104}

References socket_setsockopt().

Referenced by acds_client_connect().

◆ socket_setsockopt()

int socket_setsockopt ( socket_t  sock,
int  level,
int  optname,
const void *  optval,
socklen_t  optlen 
)

#include <socket.h>

Set socket option.

Parameters
sockSocket to configure
levelOption level (e.g., SOL_SOCKET, IPPROTO_TCP)
optnameOption name (e.g., SO_REUSEADDR, TCP_NODELAY)
optvalPointer to option value
optlenLength of option value
Returns
0 on success, non-zero on error

Referenced by set_socket_timeout(), socket_configure_buffers(), socket_optimize_for_streaming(), and socket_set_timeout().

◆ socket_shutdown()

int socket_shutdown ( socket_t  sock,
int  how 
)

#include <socket.h>

Shutdown socket I/O.

Parameters
sockSocket to shutdown
howShutdown mode (SHUT_RD, SHUT_WR, SHUT_RDWR)
Returns
0 on success, non-zero on error

Referenced by disconnect_client_for_bad_data(), server_connection_shutdown(), and tcp_client_shutdown().

◆ symbol_cache_cleanup()

void symbol_cache_cleanup ( void  )

#include <symbols.h>

Clean up the symbol cache and free all resources.

Destroys the symbol cache and frees all cached symbols and internal structures. Should be called at application shutdown after all backtraces are complete.

Note
Safe to call multiple times (no-op after first call).
All cached symbol strings are freed automatically.

Definition at line 419 of file symbols.c.

419 {
420 if (!atomic_load(&g_symbol_cache_initialized)) {
421 return;
422 }
423
424 // Mark as uninitialized FIRST to prevent new inserts during cleanup
425 atomic_store(&g_symbol_cache_initialized, false);
426
427 // Acquire write lock to prevent any concurrent operations
428 rwlock_wrlock(&g_symbol_cache_lock);
429
430 // Count entries before freeing for debugging
431 size_t entry_count = HASH_COUNT(g_symbol_cache);
432
433 // Free all symbol entries using HASH_ITER
434 symbol_entry_t *entry, *tmp;
435 size_t freed_count = 0;
436 HASH_ITER(hh, g_symbol_cache, entry, tmp) {
437 if (entry) {
438 HASH_DEL(g_symbol_cache, entry);
439 if (entry->symbol) {
440 // Use SAFE_FREE() because entry->symbol was allocated with platform_strdup()
441 // which uses SAFE_MALLOC(), so it's tracked by debug memory system
442 SAFE_FREE(entry->symbol);
443 }
444 SAFE_FREE(entry);
445 freed_count++;
446 }
447 }
448
449 // Release lock and destroy rwlock
450 rwlock_wrunlock(&g_symbol_cache_lock);
451 rwlock_destroy(&g_symbol_cache_lock);
452
453 g_symbol_cache = NULL;
454
455 log_debug("Symbol cache cleaned up: %zu entries counted, %zu entries freed (hits=%llu, misses=%llu)", entry_count,
456 freed_count, (unsigned long long)atomic_load(&g_cache_hits),
457 (unsigned long long)atomic_load(&g_cache_misses));
458}
Symbol cache entry structure for address-to-symbol mapping.
Definition symbols.c:105
char * symbol
Resolved symbol string (allocated, owned by cache)
Definition symbols.c:109

References log_debug, rwlock_destroy(), rwlock_wrlock, rwlock_wrunlock, SAFE_FREE, and symbol_entry_t::symbol.

Referenced by server_main().

◆ symbol_cache_free_symbols()

void symbol_cache_free_symbols ( char **  symbols)

#include <symbols.h>

Free symbol array returned by symbol_cache_resolve_batch.

Parameters
symbolsArray of symbol strings (can be NULL)

Frees the array structure returned by symbol_cache_resolve_batch(). This only frees the array itself, not the individual symbol strings (which are owned by the cache).

Note
Safe to call with NULL pointer (no-op).
This function only frees the array structure, not cached symbol strings.

Definition at line 1173 of file symbols.c.

1173 {
1174 if (!symbols) {
1175 return;
1176 }
1177
1178 // The array is NULL-terminated (allocated with size+1, with result[size] = NULL)
1179 // The terminator is a SINGLE NULL at index 'size'
1180 // Failed allocations use the NULL_SENTINEL string "[NULL]" instead of NULL,
1181 // so there are no NULL entries in the middle - only at the terminator
1182 // This makes iteration safe: we can iterate until we find the first NULL (the terminator)
1183
1184 // Iterate through entries, freeing all non-NULL entries until we hit the NULL terminator
1185 for (int i = 0; i < 64; i++) { // Reasonable limit to prevent infinite loop
1186 if (symbols[i] == NULL) {
1187 // Found NULL - this is the terminator, stop here
1188 break;
1189 }
1190
1191 // Found a non-NULL entry - check if it's the sentinel string
1192 // Both regular strings and sentinel strings are allocated (with strdup),
1193 // so we free them all
1194 SAFE_FREE(symbols[i]);
1195 symbols[i] = NULL; // Clear pointer after freeing
1196 }
1197
1198 // Free the array itself
1199 SAFE_FREE(symbols);
1200}

References SAFE_FREE.

◆ symbol_cache_get_stats()

void symbol_cache_get_stats ( uint64_t hits_out,
uint64_t misses_out,
size_t *  entries_out 
)

#include <symbols.h>

Get cache statistics.

Parameters
hits_outPointer to receive hit count (can be NULL)
misses_outPointer to receive miss count (can be NULL)
entries_outPointer to receive entry count (can be NULL)

Retrieves cumulative statistics from the symbol cache. Useful for performance monitoring and cache efficiency analysis.

Note
All counters are cumulative since cache initialization.
Thread-safe: Can be called from any thread.

Definition at line 540 of file symbols.c.

540 {
541 if (hits_out) {
542 *hits_out = atomic_load(&g_cache_hits);
543 }
544 if (misses_out) {
545 *misses_out = atomic_load(&g_cache_misses);
546 }
547 if (entries_out) {
548 rwlock_rdlock(&g_symbol_cache_lock);
549 *entries_out = HASH_COUNT(g_symbol_cache);
550 rwlock_rdunlock(&g_symbol_cache_lock);
551 }
552}

References rwlock_rdlock, and rwlock_rdunlock.

◆ symbol_cache_init()

asciichat_error_t symbol_cache_init ( void  )

#include <symbols.h>

Initialize the symbol cache.

Returns
ASCIICHAT_OK on success, error code on failure

Initializes the global symbol cache system. Creates the hashtable and initializes statistics counters. Must be called before using any other symbol cache functions.

Note
Idempotent: Safe to call multiple times (no-op after first call).
Thread-safe: Can be called from any thread during initialization.

Definition at line 391 of file symbols.c.

391 {
392 bool expected = false;
393 if (!atomic_compare_exchange_strong(&g_symbol_cache_initialized, &expected, true)) {
394 return 0; // Already initialized
395 }
396
397 // Detect which symbolizer is available (once at init)
398 expected = false;
399 if (atomic_compare_exchange_strong(&g_symbolizer_detected, &expected, true)) {
400 g_symbolizer_type = detect_symbolizer();
401 }
402
403 // Initialize rwlock for thread safety (uthash requires external locking)
404 if (rwlock_init(&g_symbol_cache_lock) != 0) {
405 atomic_store(&g_symbol_cache_initialized, false);
406 return SET_ERRNO(ERROR_THREAD, "Failed to initialize symbol cache rwlock");
407 }
408
409 // Initialize uthash head to NULL (required)
410 g_symbol_cache = NULL;
411
412 atomic_store(&g_cache_hits, 0);
413 atomic_store(&g_cache_misses, 0);
414
415 log_debug("Symbol cache initialized");
416 return 0;
417}
@ ERROR_THREAD
Definition error_codes.h:95
int rwlock_init(rwlock_t *lock)
Initialize a read-write lock.

References ERROR_THREAD, log_debug, rwlock_init(), and SET_ERRNO.

◆ symbol_cache_insert()

bool symbol_cache_insert ( void *  addr,
const char *  symbol 
)

#include <symbols.h>

Insert a symbol into the cache.

Parameters
addrAddress to cache (must not be NULL)
symbolSymbol string to cache (must not be NULL, will be copied)
Returns
true on success, false on failure (memory allocation error)

Inserts a symbol into the cache for future lookups. The symbol string is copied and owned by the cache. If the address already exists in the cache, the symbol is updated.

Note
Symbol string is copied, so caller can free the original string.
Thread-safe: Can be called from multiple threads simultaneously.
Statistics are updated (entry count, etc.).
Warning
Memory allocation failures return false. Check return value.

Definition at line 482 of file symbols.c.

482 {
483 if (!atomic_load(&g_symbol_cache_initialized) || !addr || !symbol) {
484 return false;
485 }
486
487 // Acquire write lock to make the entire operation atomic
488 rwlock_wrlock(&g_symbol_cache_lock);
489
490 // Double-check cache is still initialized after acquiring lock
491 // (cleanup might have marked it uninitialized between our check and lock acquisition)
492 if (!atomic_load(&g_symbol_cache_initialized)) {
493 rwlock_wrunlock(&g_symbol_cache_lock);
494 return false;
495 }
496
497 // Check if entry already exists
498 symbol_entry_t *existing = NULL;
499 HASH_FIND_PTR(g_symbol_cache, &addr, existing);
500
501 if (existing) {
502 // Entry exists - update symbol if different
503 if (existing->symbol && strcmp(existing->symbol, symbol) != 0) {
504 // Free old symbol and allocate new one
505 SAFE_FREE(existing->symbol);
506 existing->symbol = platform_strdup(symbol);
507 if (!existing->symbol) {
508 rwlock_wrunlock(&g_symbol_cache_lock);
509 return false;
510 }
511 }
512 rwlock_wrunlock(&g_symbol_cache_lock);
513 return true;
514 }
515
516 // Create new entry
518 if (!entry) {
519 rwlock_wrunlock(&g_symbol_cache_lock);
520 return false;
521 }
522
523 entry->addr = addr;
524 entry->symbol = platform_strdup(symbol);
525 if (!entry->symbol) {
526 SAFE_FREE(entry);
527 rwlock_wrunlock(&g_symbol_cache_lock);
528 return false;
529 }
530
531 // Add to hash table
532 HASH_ADD_PTR(g_symbol_cache, addr, entry);
533
534 // Release lock
535 rwlock_wrunlock(&g_symbol_cache_lock);
536
537 return true;
538}
void * addr
Memory address key (used for hashtable lookup)
Definition symbols.c:107

References symbol_entry_t::addr, platform_strdup(), rwlock_wrlock, rwlock_wrunlock, SAFE_FREE, SAFE_MALLOC, and symbol_entry_t::symbol.

Referenced by symbol_cache_resolve_batch().

◆ symbol_cache_lookup()

const char * symbol_cache_lookup ( void *  addr)

#include <symbols.h>

Look up a symbol for a given address.

Parameters
addrAddress to resolve (must not be NULL)
Returns
Cached symbol string, or NULL if not in cache

Performs a hashtable lookup to find the cached symbol for the given address. Returns NULL if the address is not in the cache (cache miss). Use symbol_cache_resolve_batch() to resolve uncached addresses.

Note
This is a fast O(1) lookup operation (hashtable lookup).
Returned string is owned by the cache and should not be freed.
String remains valid until cache is cleaned up.
Thread-safe: Can be called from multiple threads simultaneously.
For batch resolution, use symbol_cache_resolve_batch() which handles both cache lookup and addr2line resolution automatically.

Definition at line 460 of file symbols.c.

460 {
461 if (!atomic_load(&g_symbol_cache_initialized) || !addr) {
462 return NULL;
463 }
464
465 rwlock_rdlock(&g_symbol_cache_lock);
466
467 symbol_entry_t *entry = NULL;
468 HASH_FIND_PTR(g_symbol_cache, &addr, entry);
469
470 if (entry) {
471 const char *symbol = entry->symbol;
472 atomic_fetch_add(&g_cache_hits, 1);
473 rwlock_rdunlock(&g_symbol_cache_lock);
474 return symbol;
475 }
476
477 atomic_fetch_add(&g_cache_misses, 1);
478 rwlock_rdunlock(&g_symbol_cache_lock);
479 return NULL;
480}

References rwlock_rdlock, rwlock_rdunlock, and symbol_entry_t::symbol.

Referenced by symbol_cache_resolve_batch().

◆ symbol_cache_print_stats()

void symbol_cache_print_stats ( void  )

#include <symbols.h>

Print cache statistics to logging system.

Logs detailed statistics from the symbol cache including hit rate, miss count, and entry count. Useful for periodic performance monitoring.

Note
Requires logging system to be initialized.
Statistics are formatted and logged at INFO level.

Definition at line 554 of file symbols.c.

554 {
555 uint64_t hits = atomic_load(&g_cache_hits);
556 uint64_t misses = atomic_load(&g_cache_misses);
557
558 rwlock_rdlock(&g_symbol_cache_lock);
559 size_t entries = HASH_COUNT(g_symbol_cache);
560 rwlock_rdunlock(&g_symbol_cache_lock);
561
562 uint64_t total = hits + misses;
563 double hit_rate = total > 0 ? (100.0 * (double)hits / (double)total) : 0.0;
564
565 log_info("Symbol Cache Stats: %zu entries, %llu hits, %llu misses (%.1f%% hit rate)", entries,
566 (unsigned long long)hits, (unsigned long long)misses, hit_rate);
567}

References log_info, rwlock_rdlock, and rwlock_rdunlock.

◆ symbol_cache_resolve_batch()

char ** symbol_cache_resolve_batch ( void *const *  buffer,
int  size 
)

#include <symbols.h>

Resolve multiple addresses using addr2line and cache results.

Parameters
bufferArray of addresses to resolve (must not be NULL)
sizeNumber of addresses in buffer (must be > 0)
Returns
Array of symbol strings (caller must free with symbol_cache_free_symbols), or NULL on error

Resolves multiple addresses to symbol names using addr2line. For each address:

  1. Checks cache first (fast O(1) lookup)
  2. If not cached, resolves using addr2line subprocess
  3. Caches resolved symbol for future lookups
  4. Returns symbol string in output array

This function is optimized for batch backtrace processing:

  • Checks cache for all addresses first (fast)
  • Resolves only uncached addresses in single addr2line invocation
  • Caches all resolved symbols automatically
Note
Returned array has size elements (one per input address).
Array elements are NULL if symbol resolution failed for that address.
Array elements point to cached symbol strings (owned by cache).
Call symbol_cache_free_symbols() to free the array (not the strings).
addr2line must be available in PATH for resolution to work.
Debug symbols must be available in the executable.
Warning
Caller must free the returned array using symbol_cache_free_symbols(). Do NOT free individual strings (owned by cache).

Definition at line 1052 of file symbols.c.

1052 {
1053 if (size <= 0 || !buffer) {
1054 log_error("Invalid parameters: buffer=%p, size=%d", (void *)buffer, size);
1055 return NULL;
1056 }
1057
1058 // DO NOT auto-initialize here - causes circular dependency during lock_debug_init()
1059 // The cache must be initialized explicitly by platform_init() before use
1060 if (!atomic_load(&g_symbol_cache_initialized)) {
1061 // Cache not initialized - fall back to uncached resolution
1062 // This happens during early initialization before platform_init() completes
1063 char **result = run_llvm_symbolizer_batch(buffer, size);
1064 if (!result) {
1065 result = run_addr2line_batch(buffer, size);
1066 }
1067 return result;
1068 }
1069
1070 // Allocate result array (size + 1 for NULL terminator)
1071 // CALLOC zeros the memory, so result[size] is already NULL
1072 char **result = SAFE_CALLOC((size_t)(size + 1), sizeof(char *), char **);
1073 if (!result) {
1074 return NULL;
1075 }
1076
1077 // Ensure NULL terminator is explicitly set (CALLOC already did this, but be explicit)
1078 result[size] = NULL;
1079
1080 // First pass: check cache for all addresses
1081 int uncached_count = 0;
1082 void *uncached_addrs[size];
1083 int uncached_indices[size];
1084
1085 for (int i = 0; i < size; i++) {
1086 const char *cached = symbol_cache_lookup(buffer[i]);
1087 if (cached) {
1088 // Cache hit - duplicate the string
1089 result[i] = platform_strdup(cached);
1090 // If allocation failed, use sentinel string instead of NULL
1091 if (!result[i]) {
1092 result[i] = platform_strdup(NULL_SENTINEL);
1093 }
1094 } else {
1095 // Cache miss - track for batch resolution
1096 uncached_addrs[uncached_count] = buffer[i];
1097 uncached_indices[uncached_count] = i;
1098 uncached_count++;
1099 }
1100 }
1101
1102 // Second pass: resolve uncached addresses with selected symbolizer
1103 if (uncached_count > 0) {
1104 char **resolved = NULL;
1105
1106 // Use the detected symbolizer type
1107 switch (g_symbolizer_type) {
1108 case SYMBOLIZER_LLVM:
1109 resolved = run_llvm_symbolizer_batch(uncached_addrs, uncached_count);
1110 break;
1112 resolved = run_addr2line_batch(uncached_addrs, uncached_count);
1113 break;
1114 case SYMBOLIZER_NONE:
1115 default:
1116 // No symbolizer available - will fall through to raw address handling
1117 resolved = NULL;
1118 break;
1119 }
1120
1121 if (resolved) {
1122 for (int i = 0; i < uncached_count; i++) {
1123 int orig_idx = uncached_indices[i];
1124 if (resolved[i]) {
1125 result[orig_idx] = platform_strdup(resolved[i]);
1126 // If strdup failed, use sentinel string instead of NULL
1127 if (!result[orig_idx]) {
1128 log_error("Failed to duplicate string for result[%d]", orig_idx);
1129 result[orig_idx] = platform_strdup(NULL_SENTINEL);
1130 }
1131 // Only insert into cache if strdup succeeded (and it's not the sentinel)
1132 if (result[orig_idx] && strcmp(result[orig_idx], NULL_SENTINEL) != 0) {
1133 if (!symbol_cache_insert(uncached_addrs[i], resolved[i])) {
1134 log_error("Failed to insert symbol into cache for result[%d]", orig_idx);
1135 }
1136 }
1137 SAFE_FREE(resolved[i]);
1138 } else {
1139 // resolved[i] is NULL - use sentinel string
1140 if (!result[orig_idx]) {
1141 result[orig_idx] = platform_strdup(NULL_SENTINEL);
1142 }
1143 }
1144 if (!result[orig_idx]) {
1145 log_error("Failed to allocate memory for result[%d]", orig_idx);
1146 }
1147 }
1148
1149 SAFE_FREE(resolved);
1150
1151 } else {
1152 // addr2line failed - fill uncached entries with raw addresses or sentinel
1153 for (int i = 0; i < uncached_count; i++) {
1154 int orig_idx = uncached_indices[i];
1155 if (!result[orig_idx]) {
1156 result[orig_idx] = SAFE_MALLOC(32, char *);
1157 if (result[orig_idx]) {
1158 SAFE_SNPRINTF(result[orig_idx], 32, "%p", uncached_addrs[i]);
1159 } else {
1160 result[orig_idx] = platform_strdup(NULL_SENTINEL);
1161 }
1162 }
1163 if (!result[orig_idx]) {
1164 log_error("Failed to allocate memory for result[%d]", orig_idx);
1165 }
1166 }
1167 }
1168 }
1169
1170 return result;
1171}
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
#define SAFE_CALLOC(count, size, cast)
Definition common.h:218
bool symbol_cache_insert(void *addr, const char *symbol)
Insert a symbol into the cache.
Definition symbols.c:482
const char * symbol_cache_lookup(void *addr)
Look up a symbol for a given address.
Definition symbols.c:460
#define NULL_SENTINEL
Definition symbols.c:43
@ SYMBOLIZER_NONE
Definition symbols.c:50
@ SYMBOLIZER_LLVM
Definition symbols.c:51
@ SYMBOLIZER_ADDR2LINE
Definition symbols.c:52

References log_error, NULL_SENTINEL, platform_strdup(), SAFE_CALLOC, SAFE_FREE, SAFE_MALLOC, SAFE_SNPRINTF, symbol_cache_insert(), symbol_cache_lookup(), SYMBOLIZER_ADDR2LINE, SYMBOLIZER_LLVM, and SYMBOLIZER_NONE.

◆ terminal_capabilities_summary()

const char * terminal_capabilities_summary ( const terminal_capabilities_t caps)

#include <terminal.h>

Get summary string of terminal capabilities.

Parameters
capsTerminal capabilities structure (must not be NULL)
Returns
Summary string describing capabilities (may be static, do not free)

Generates a human-readable summary string describing the terminal's capabilities including color level, UTF-8 support, and render mode. Useful for logging and debugging terminal configuration.

Note
Returns static string (do not free).
Summary format: "16-color, UTF-8, foreground mode" (example).

◆ terminal_clear_screen()

asciichat_error_t terminal_clear_screen ( void  )

#include <terminal.h>

Clear the terminal screen.

Returns
ASCIICHAT_OK on success, error code on failure

Clears the terminal screen using ANSI escape sequences (Unix) or Windows Console API. Removes all visible characters and resets cursor position to top-left.

Note
Uses ANSI escape sequence ESC[2J on Unix systems.
On Windows, uses Console API ClearScreen() function.
Screen clearing does not affect scrollback buffer.

◆ terminal_clear_scrollback()

asciichat_error_t terminal_clear_scrollback ( int  fd)

#include <terminal.h>

Clear terminal scrollback buffer.

Parameters
fdFile descriptor for terminal (must be valid)
Returns
ASCIICHAT_OK on success, error code on failure

Clears the terminal scrollback buffer (history of previous output). This removes all previous terminal output that can be scrolled back to view. Useful for starting with a clean terminal state.

Note
Scrollback clearing is terminal-dependent.
Some terminals may not support scrollback clearing.
On Windows, clears console screen buffer.

◆ terminal_color_level_name()

const char * terminal_color_level_name ( terminal_color_mode_t  level)

#include <terminal.h>

Get name of color level.

Parameters
levelColor level enum value (terminal_color_mode_t)
Returns
Human-readable color level name (e.g., "16-color", "truecolor")

Converts a terminal color level enum value to a human-readable string name. Useful for logging and debugging terminal capability detection.

Note
Returns static string (do not free).
Returns "unknown" for invalid level values.

Referenced by handle_client_capabilities_packet().

◆ terminal_cursor_home()

asciichat_error_t terminal_cursor_home ( int  fd)

#include <terminal.h>

Move cursor to home position (top-left)

Parameters
fdFile descriptor for terminal (must be valid)
Returns
ASCIICHAT_OK on success, error code on failure

Moves cursor to home position (row 1, column 1 - top-left corner). Equivalent to terminal_move_cursor(1, 1) but more efficient. Uses ANSI escape sequence ESC[H.

Note
Home position is always row 1, column 1.
Useful for starting new frame rendering at top-left.

◆ terminal_enable_ansi()

void terminal_enable_ansi ( void  )

#include <terminal.h>

Enable ANSI escape sequences.

On Windows, enables ANSI escape sequence processing in the console. This allows Windows console to interpret ANSI escape codes (colors, cursor movement, etc.) that are normally only available on Unix terminals.

Note
This function is a no-op on Unix systems (ANSI already supported).
On Windows, requires Windows 10 build 1511 or later.
ANSI support is enabled for the current console session.

◆ terminal_flush()

asciichat_error_t terminal_flush ( int  fd)

#include <terminal.h>

Flush terminal output.

Parameters
fdFile descriptor to flush (must be valid file descriptor)
Returns
ASCIICHAT_OK on success, error code on failure

Forces all buffered output to be written to the terminal immediately. Ensures that all pending terminal output is displayed before continuing.

Note
This function calls fsync/flush operations on the file descriptor.
Useful for ensuring output is visible before blocking operations.

◆ terminal_get_cursor_position()

asciichat_error_t terminal_get_cursor_position ( int *  row,
int *  col 
)

#include <terminal.h>

Get current cursor position.

Parameters
rowPointer to store row position (must not be NULL, 1-based)
colPointer to store column position (must not be NULL, 1-based)
Returns
ASCIICHAT_OK on success, error code on failure

Queries the terminal for the current cursor position. Uses platform-specific methods to detect cursor location. Positions are returned in 1-based coordinates (row 1, column 1 is top-left).

Note
On failure, output parameters are not modified.
Cursor position detection may not be available on all terminals.

◆ terminal_get_size()

asciichat_error_t terminal_get_size ( terminal_size_t size)

#include <terminal.h>

Get terminal size.

Parameters
sizePointer to store terminal size (must not be NULL)
Returns
ASCIICHAT_OK on success, error code on failure

Queries the terminal for its current dimensions (rows and columns). Uses platform-specific methods (ioctl on Unix, Windows Console API).

Note
On failure, size structure is not modified.
Terminal size may change if terminal is resized.

Referenced by sdp_detect_terminal_capabilities().

◆ terminal_hide_cursor()

asciichat_error_t terminal_hide_cursor ( int  fd,
bool  hide 
)

#include <terminal.h>

Hide or show cursor.

Parameters
fdFile descriptor for terminal (must be valid)
hidetrue to hide cursor, false to show cursor
Returns
ASCIICHAT_OK on success, error code on failure

Controls terminal cursor visibility. Hiding the cursor is useful for full-screen ASCII art rendering where cursor flicker is distracting. Uses ANSI escape sequences (ESC[?25l to hide, ESC[?25h to show).

Note
Hidden cursor should be restored before program exit.
Cursor visibility change is immediate.

Referenced by ascii_write_destroy(), and ascii_write_init().

◆ terminal_move_cursor()

asciichat_error_t terminal_move_cursor ( int  row,
int  col 
)

#include <terminal.h>

Move cursor to specified position.

Parameters
rowRow position (1-based, top is row 1)
colColumn position (1-based, left is column 1)
Returns
ASCIICHAT_OK on success, error code on failure

Moves the terminal cursor to the specified row and column position. Uses ANSI escape sequences (Unix) or Windows Console API. Positions are 1-based (top-left is row 1, column 1).

Note
Row 1 is the top row of the terminal.
Column 1 is the leftmost column of the terminal.
Cursor position may be clamped to terminal bounds.

◆ terminal_reset()

asciichat_error_t terminal_reset ( int  fd)

#include <terminal.h>

Reset terminal to default state.

Parameters
fdFile descriptor for terminal (must be valid)
Returns
ASCIICHAT_OK on success, error code on failure

Resets terminal to default state including:

  • Default colors (foreground/background)
  • Default cursor visibility
  • Default attributes (bold, underline, etc.)
  • Cleared scroll regions

Useful for cleanup before program exit or when resetting terminal state.

Note
This function sends ANSI reset sequence (ESC[0m).
Terminal state is reset immediately.

◆ terminal_restore_cursor()

asciichat_error_t terminal_restore_cursor ( void  )

#include <terminal.h>

Restore saved cursor position.

Returns
ASCIICHAT_OK on success, error code on failure

Restores a previously saved cursor position. Uses ANSI escape sequence ESC[u to restore cursor position. Must be preceded by terminal_save_cursor().

Note
Only restores the most recently saved cursor position.
Some terminals may not support cursor position save/restore.

◆ terminal_ring_bell()

asciichat_error_t terminal_ring_bell ( void  )

#include <terminal.h>

Ring terminal bell.

Returns
ASCIICHAT_OK on success, error code on failure

Rings the terminal bell (beep sound). Uses ANSI escape sequence BEL or platform-specific API to trigger audible notification.

Note
Bell sound depends on terminal/system sound settings.
Some terminals may have bell disabled or silent.

◆ terminal_save_cursor()

asciichat_error_t terminal_save_cursor ( void  )

#include <terminal.h>

Save cursor position.

Returns
ASCIICHAT_OK on success, error code on failure

Saves the current cursor position for later restoration. Uses ANSI escape sequence ESC[s to save cursor position. Restore with terminal_restore_cursor().

Note
Saved position is terminal-specific and not stored in application.
Some terminals may not support cursor position save/restore.

◆ terminal_set_buffering()

asciichat_error_t terminal_set_buffering ( bool  line_buffered)

#include <terminal.h>

Set terminal buffering mode.

Parameters
line_bufferedtrue for line buffering, false for unbuffered
Returns
ASCIICHAT_OK on success, error code on failure

Controls terminal output buffering mode:

  • Line buffering: Output is buffered until newline is written
  • Unbuffered: Output is written immediately (real-time)

Unbuffered mode is useful for real-time ASCII art rendering where immediate output is desired. Line buffering is more efficient for line-based output.

Note
Buffering mode affects stdout/stderr behavior.
Unbuffered mode may reduce performance for large outputs.

◆ terminal_set_echo()

asciichat_error_t terminal_set_echo ( bool  enable)

#include <terminal.h>

Set terminal echo mode.

Parameters
enabletrue to enable echo, false to disable
Returns
ASCIICHAT_OK on success, error code on failure

Controls whether terminal input is echoed back to the display. When echo is disabled, input characters are not displayed (useful for password input or silent key capture).

Note
Echo mode works independently of raw mode.
Disabling echo is useful for password prompts.

Referenced by ascii_write_destroy(), and ascii_write_init().

◆ terminal_set_raw_mode()

asciichat_error_t terminal_set_raw_mode ( bool  enable)

#include <terminal.h>

Set terminal to raw mode.

Parameters
enabletrue to enable raw mode, false to disable
Returns
ASCIICHAT_OK on success, error code on failure

Controls terminal raw mode. In raw mode, terminal input is not processed:

  • No line buffering (character-by-character input)
  • No echo (characters not printed)
  • No canonical mode (no line editing)
  • Immediate character availability

Raw mode is useful for real-time input processing (keyboard events, etc.).

Note
Disabling raw mode restores normal terminal behavior.
Raw mode affects only the current terminal session.

◆ terminal_set_scroll_region()

asciichat_error_t terminal_set_scroll_region ( int  top,
int  bottom 
)

#include <terminal.h>

Set scroll region.

Parameters
topTop row of scroll region (1-based, must be > 0)
bottomBottom row of scroll region (1-based, must be >= top)
Returns
ASCIICHAT_OK on success, error code on failure

Defines a scroll region within the terminal. Only the specified row range will scroll when text exceeds the bottom. Uses ANSI escape sequence ESC[top;bottomr. Useful for preserving header/footer regions while allowing content area to scroll.

Note
Scroll region must have top <= bottom.
Setting scroll region to entire terminal clears the restriction.
Some terminals may not support scroll regions.

◆ terminal_set_title()

asciichat_error_t terminal_set_title ( const char *  title)

#include <terminal.h>

Set terminal window title.

Parameters
titleTitle string to set (must not be NULL)
Returns
ASCIICHAT_OK on success, error code on failure

Sets the terminal window title to the specified string. Uses ANSI escape sequence ESC]0;titleBEL or platform-specific API. Title appears in window title bar or terminal tab.

Note
Title is truncated to terminal-specific maximum length.
Some terminals may not support title setting.

◆ terminal_supports_color()

bool terminal_supports_color ( void  )

#include <terminal.h>

Check if terminal supports color.

Returns
true if terminal supports color, false otherwise

Determines whether the terminal supports color output. Checks environment variables ($TERM, $COLORTERM) and terminal type to detect color capabilities.

Note
Returns true if ANY color support is detected (16, 256, or truecolor).
Use detect_terminal_capabilities() for detailed color level detection.

◆ terminal_supports_unicode()

bool terminal_supports_unicode ( void  )

#include <terminal.h>

Check if terminal supports unicode.

Returns
true if terminal supports unicode, false otherwise

Determines whether the terminal supports Unicode character output. Checks locale settings and terminal type for Unicode support.

Note
Unicode support is broader than UTF-8 (includes UTF-16, etc.).
Use terminal_supports_utf8() for UTF-8 specific detection.

◆ terminal_supports_utf8()

bool terminal_supports_utf8 ( void  )

#include <terminal.h>

Check if terminal supports UTF-8.

Returns
true if terminal supports UTF-8, false otherwise

Determines whether the terminal supports UTF-8 encoding. Checks locale settings ($LC_ALL, $LANG) and terminal type for UTF-8 support.

Note
UTF-8 support is required for Unicode palette characters.
Use detect_terminal_capabilities() for comprehensive capability detection.

Referenced by detect_client_utf8_support().

◆ test_terminal_output_modes()

void test_terminal_output_modes ( void  )

#include <terminal.h>

Test terminal output modes.

Tests various terminal output modes to verify capabilities. Outputs test patterns for different color modes (16-color, 256-color, truecolor) and rendering modes to verify terminal behavior.

Note
This function outputs test patterns to stdout.
Useful for verifying terminal capability detection accuracy.

◆ thread_create_or_fail()

asciichat_error_t thread_create_or_fail ( asciichat_thread_t thread,
void *(*)(void *)  func,
void *  arg,
const char *  thread_name,
uint32_t  client_id 
)

#include <thread.h>

Create a thread with standardized error handling and logging.

Parameters
threadThread handle to fill on success
funcThread function to execute
argArgument to pass to thread function
thread_nameHuman-readable name for logging (e.g., "video_render")
client_idClient ID for error context in logs
Returns
ASCIICHAT_OK on success, ERROR_INVALID_PARAM or ERROR_PLATFORM_INIT on failure

Wraps asciichat_thread_create() with unified error handling and logging. On success, logs at debug level. On failure, uses SET_ERRNO() to record error context and returns:

  • ERROR_INVALID_PARAM if parameters are invalid
  • ERROR_PLATFORM_INIT if thread creation fails
Note
Errors are logged via SET_ERRNO(), use HAS_ERRNO() to check context
thread_name and client_id are used in log messages for debugging

Definition at line 14 of file thread.c.

15 {
16 if (!thread || !func || !thread_name) {
17 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for thread creation");
18 }
19
20 int result = asciichat_thread_create(thread, func, arg);
21 if (result != 0) {
22 return SET_ERRNO(ERROR_PLATFORM_INIT, "Failed to create %s thread for client %u (result=%d)", thread_name,
23 client_id, result);
24 }
25
26 log_debug("Created %s thread for client %u successfully", thread_name, client_id);
27 return ASCIICHAT_OK;
28}
@ ERROR_PLATFORM_INIT
Definition error_codes.h:57

References ASCIICHAT_OK, asciichat_thread_create(), ERROR_INVALID_PARAM, ERROR_PLATFORM_INIT, log_debug, and SET_ERRNO.

Variable Documentation

◆ errno