ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
system.c
Go to the documentation of this file.
1
7// NOTE: This file is #included by windows/system.c and posix/system.c
8// All necessary headers are already included by the parent files
9
10#include <stdatomic.h>
11#include "common.h"
12#include "util/fnv1a.h"
13
14// UBSan-safe hash wrapper for uthash (fnv1a uses 64-bit arithmetic, no overflow)
15// Note: uthash expects HASH_FUNCTION(keyptr, keylen, hashv) where hashv is an output parameter
16#undef HASH_FUNCTION
17#define HASH_FUNCTION(keyptr, keylen, hashv) \
18 do { \
19 if (!(keyptr) || (keylen) == 0) { \
20 (hashv) = 1; /* Non-zero constant for safety */ \
21 } else { \
22 (hashv) = fnv1a_hash_bytes((keyptr), (keylen)); \
23 } \
24 } while (0)
25
26#include "util/uthash.h"
27#include "log/logging.h"
28
29// Platform-specific binary suffix
30#ifdef _WIN32
31#define BIN_SUFFIX ".exe"
32#else
33#define BIN_SUFFIX ""
34#endif
35// PATH_DELIM and PATH_ENV_SEPARATOR are now defined in system.h
36
37// ============================================================================
38// Maximum Path Length
39// ============================================================================
40
52#ifdef _WIN32
53// Windows extended-length path maximum
54// Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
55#define PLATFORM_MAX_PATH_LENGTH 32767
56#elif defined(__linux__)
57// Linux PATH_MAX (typically 4096)
58#ifndef PATH_MAX
59#define PLATFORM_MAX_PATH_LENGTH 4096
60#else
61#define PLATFORM_MAX_PATH_LENGTH PATH_MAX
62#endif
63#elif defined(__APPLE__)
64// macOS PATH_MAX (typically 1024)
65#ifndef PATH_MAX
66#define PLATFORM_MAX_PATH_LENGTH 1024
67#else
68#define PLATFORM_MAX_PATH_LENGTH PATH_MAX
69#endif
70#else
71// Fallback for unknown platforms
72#define PLATFORM_MAX_PATH_LENGTH 4096
73#endif
74
75// ============================================================================
76// Binary PATH Detection Cache
77// ============================================================================
78
126typedef struct {
128 char *bin_name;
132 UT_hash_handle hh;
134
135static bin_cache_entry_t *g_bin_path_cache = NULL; // uthash head pointer
136static rwlock_t g_cache_rwlock;
137static atomic_bool g_cache_initialized = false;
138
142static bool is_executable_file(const char *path) {
143#ifdef _WIN32
144 // On Windows, check if file exists and is readable
145 // Windows doesn't have execute permission bits like Unix
146 DWORD attrs = GetFileAttributesA(path);
147 if (attrs == INVALID_FILE_ATTRIBUTES) {
148 return false;
149 }
150 // Must be a regular file (not a directory)
151 if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
152 return false;
153 }
154 // Try to open for reading to verify access
155 HANDLE h = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
156 if (h == INVALID_HANDLE_VALUE) {
157 return false;
158 }
159 CloseHandle(h);
160 return true;
161#else
162 // On Unix, use access() to check if file exists and is executable
163 return (access(path, X_OK) == 0);
164#endif
165}
166
170static bool check_binary_in_path_uncached(const char *bin_name) {
171 char bin_with_suffix[512];
172 char full_path[PLATFORM_MAX_PATH_LENGTH];
173
174 // On Windows, add .exe suffix if not present
175#ifdef _WIN32
176 if (strstr(bin_name, ".exe") == NULL) {
177 safe_snprintf(bin_with_suffix, sizeof(bin_with_suffix), "%s%s", bin_name, BIN_SUFFIX);
178 } else {
179 SAFE_STRNCPY(bin_with_suffix, bin_name, sizeof(bin_with_suffix));
180 }
181#else
182 SAFE_STRNCPY(bin_with_suffix, bin_name, sizeof(bin_with_suffix));
183#endif
184
185 // Get PATH environment variable
186 const char *path_env = SAFE_GETENV("PATH");
187 if (!path_env) {
188 return false;
189 }
190
191 // Make a copy we can modify
192 size_t path_len = strlen(path_env);
193 char *path_copy = SAFE_MALLOC(path_len + 1, char *);
194 if (!path_copy) {
195 return false;
196 }
197 SAFE_STRNCPY(path_copy, path_env, path_len + 1);
198
199 // Search each directory in PATH
200 bool found = false;
201 char *saveptr = NULL;
202 char *dir = platform_strtok_r(path_copy, PATH_ENV_SEPARATOR, &saveptr);
203
204 while (dir != NULL) {
205 // Skip empty directory entries
206 if (dir[0] == '\0') {
207 dir = platform_strtok_r(NULL, PATH_ENV_SEPARATOR, &saveptr);
208 continue;
209 }
210
211 // Build full path to binary
212 safe_snprintf(full_path, sizeof(full_path), "%s%c%s", dir, PATH_DELIM, bin_with_suffix);
213
214 // Check if file exists and is executable
215 if (is_executable_file(full_path)) {
216 found = true;
217 break;
218 }
219
220 dir = platform_strtok_r(NULL, PATH_ENV_SEPARATOR, &saveptr);
221 }
222
223 SAFE_FREE(path_copy);
224 return found;
225}
226
230static void init_cache_once(void) {
231 bool expected = false;
232 if (!atomic_compare_exchange_strong(&g_cache_initialized, &expected, true)) {
233 return; // Already initialized
234 }
235
236 // Initialize uthash head pointer (NULL = empty)
237 g_bin_path_cache = NULL;
238
239 // Initialize rwlock for thread-safe access
240 if (rwlock_init(&g_cache_rwlock) != 0) {
241 log_error("Failed to initialize binary PATH cache rwlock");
242 atomic_store(&g_cache_initialized, false);
243 }
244}
245
249static void free_cache_entry(bin_cache_entry_t *entry) {
250 if (entry) {
251 if (entry->bin_name) {
252 SAFE_FREE(entry->bin_name);
253 }
254 SAFE_FREE(entry);
255 }
256}
257
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}
283
284// ============================================================================
285// Public API
286// ============================================================================
287
288bool platform_is_binary_in_path(const char *bin_name) {
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}
344
351bool platform_get_executable_path(char *exe_path, size_t path_size) {
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}
#️⃣ FNV-1a Hash Function Implementation
unsigned int uint32_t
Definition common.h:58
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SAFE_GETENV(name)
Definition common.h:378
#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_MEMORY
Definition error_codes.h:53
@ ERROR_GENERAL
Definition error_codes.h:49
@ ERROR_INVALID_PARAM
@ ERROR_BUFFER_OVERFLOW
Definition error_codes.h:98
const char ** log_get_color_array(void)
Get the appropriate color array based on terminal capabilities.
#define log_error(...)
Log an ERROR message.
#define log_debug(...)
Log a DEBUG message.
@ LOG_COLOR_RESET
@ LOG_COLOR_ERROR
@ LOG_COLOR_INFO
@ LOG_COLOR_WARN
bool platform_get_executable_path(char *exe_path, size_t path_size)
Get the path to the current executable.
Definition system.c:351
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:249
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe version of snprintf that ensures null termination.
int rwlock_destroy(rwlock_t *lock)
Destroy a read-write lock.
#define PATH_ENV_SEPARATOR
Platform-specific PATH environment variable separator.
Definition system.h:620
#define PATH_DELIM
Platform-specific path separator character.
Definition system.h:605
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:194
bool platform_is_binary_in_path(const char *bin_name)
Check if a binary is available in the system PATH.
Definition system.c:288
char * platform_strdup(const char *s)
Duplicate string (strdup replacement)
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
char * platform_strtok_r(char *str, const char *delim, char **saveptr)
Thread-safe string tokenization (strtok_r replacement)
int errno
void platform_cleanup_binary_path_cache(void)
Cleanup the binary PATH cache.
Definition system.c:261
int rwlock_init(rwlock_t *lock)
Initialize a read-write lock.
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:231
📝 Logging API with multiple log levels and terminal output control
Binary PATH cache entry structure for binary detection caching.
Definition system.c:126
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
UT_hash_handle hh
uthash handle
Definition system.c:132
#define PLATFORM_MAX_PATH_LENGTH
Definition system.c:72
#define BIN_SUFFIX
Definition system.c:33
#️⃣ Wrapper for uthash.h that ensures common.h is included first