ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
rcu.c
Go to the documentation of this file.
1
6#include <ascii-chat/options/rcu.h>
7#include <ascii-chat/options/schema.h>
8#include <ascii-chat/asciichat_errno.h>
9#include <ascii-chat/common.h>
10#include <ascii-chat/log/logging.h>
11#include <ascii-chat/platform/abstraction.h>
12#include <ascii-chat/platform/init.h>
13#include <ascii-chat/platform/process.h>
14
15#include <stdatomic.h>
16#include <stdlib.h>
17#include <string.h>
18#ifndef _WIN32
19#include <pthread.h>
20#endif
21
22// ============================================================================
23// RCU State: Global Atomic Pointer
24// ============================================================================
25
34static _Atomic(options_t *) g_options = NULL;
35
47static mutex_t g_options_write_mutex;
48
52static bool g_options_initialized = false;
53// Mutex to protect initialization check (TOCTOU race prevention)
54static static_mutex_t g_options_init_mutex = STATIC_MUTEX_INIT;
55// Track the PID to detect fork() - after fork, child needs to reinitialize
56static pid_t g_init_pid = -1;
57
67static const options_t g_default_options = (options_t){
68 // Binary-Level Options (must match options_t_new() defaults)
69 .help = OPT_HELP_DEFAULT,
70 .version = OPT_VERSION_DEFAULT,
71
72 // Logging
73 .log_level = LOG_INFO,
74 .quiet = false,
75 .json = false,
76 .verbose_level = OPT_VERBOSE_LEVEL_DEFAULT,
77
78 // Terminal Dimensions
79 .width = OPT_WIDTH_DEFAULT,
80 .height = OPT_HEIGHT_DEFAULT,
81 .auto_width = OPT_AUTO_WIDTH_DEFAULT,
82 .auto_height = OPT_AUTO_HEIGHT_DEFAULT,
83
84 // Display
85 .color_mode = COLOR_MODE_AUTO,
86 .palette_type = PALETTE_STANDARD,
87 .render_mode = RENDER_MODE_FOREGROUND,
88 .fps = OPT_FPS_DEFAULT,
89 .flip_x = OPT_FLIP_X_DEFAULT,
90 .flip_y = OPT_FLIP_Y_DEFAULT,
91
92 // Performance
93 .compression_level = OPT_COMPRESSION_LEVEL_DEFAULT,
94
95 // Webcam
96 .test_pattern = false,
97 .webcam_index = OPT_WEBCAM_INDEX_DEFAULT,
98
99 // Network
100 .max_clients = OPT_MAX_CLIENTS_DEFAULT,
101 .discovery_port = OPT_ACDS_PORT_INT_DEFAULT,
102 .port = OPT_PORT_INT_DEFAULT,
103 .websocket_port = OPT_WEBSOCKET_PORT_SERVER_DEFAULT,
104
105 // Audio
106 .audio_enabled = OPT_AUDIO_ENABLED_DEFAULT,
107 .microphone_index = OPT_MICROPHONE_INDEX_DEFAULT,
108 .speakers_index = OPT_SPEAKERS_INDEX_DEFAULT,
109 .microphone_sensitivity = OPT_MICROPHONE_SENSITIVITY_DEFAULT,
110 .speakers_volume = OPT_SPEAKERS_VOLUME_DEFAULT,
111
112 // All other fields are zero-initialized (empty strings, NULL, 0, false, etc.)
113};
114
115// ============================================================================
116// Memory Reclamation Strategy
117// ============================================================================
118
132#define MAX_DEFERRED_FREES 64
133static options_t *g_deferred_frees[MAX_DEFERRED_FREES];
134static size_t g_deferred_free_count = 0;
135static mutex_t g_deferred_free_mutex;
136
142static void deferred_free_add(options_t *old_opts) {
143 if (!old_opts) {
144 return;
145 }
146
147 mutex_lock(&g_deferred_free_mutex);
148
149 if (g_deferred_free_count < MAX_DEFERRED_FREES) {
150 g_deferred_frees[g_deferred_free_count++] = old_opts;
151 log_debug("Added options struct %p to deferred free list (count=%zu)", (void *)old_opts, g_deferred_free_count);
152 } else {
153 // List full - free immediately (risky but unlikely with infrequent updates)
154 log_warn("Deferred free list full (%d entries), freeing options struct %p immediately", MAX_DEFERRED_FREES,
155 (void *)old_opts);
156 SAFE_FREE(old_opts);
157 }
158
159 mutex_unlock(&g_deferred_free_mutex);
160}
161
167static void deferred_free_all(void) {
168 mutex_lock(&g_deferred_free_mutex);
169
170 log_debug("Freeing %zu deferred options structs", g_deferred_free_count);
171
172 for (size_t i = 0; i < g_deferred_free_count; i++) {
173 SAFE_FREE(g_deferred_frees[i]);
174 }
175
176 g_deferred_free_count = 0;
177 mutex_unlock(&g_deferred_free_mutex);
178}
179
180// ============================================================================
181// Fork Handler Registration
182// ============================================================================
183
184#ifndef _WIN32
191static void options_rcu_atfork_child(void) {
192 // Reset the static mutex initialized flag to force reinitialization
193 g_options_init_mutex.initialized = 0;
194 // Reset the RCU state
195 g_options_initialized = false;
196 // Reset the atomic options pointer to avoid dangling references
197 // The parent's allocated memory is not accessible/valid in the child
198 atomic_store(&g_options, NULL);
199 // Don't reset g_init_pid - we'll detect the fork in options_state_init
200}
201
208__attribute__((constructor)) static void register_fork_handlers_constructor(void) {
209 // Register the child handler that will be called after fork() in the child process
210 pthread_atfork(NULL, NULL, options_rcu_atfork_child);
211}
212#endif
213
214// ============================================================================
215// Public API Implementation
216// ============================================================================
217
218asciichat_error_t options_state_init(void) {
219 // Detect fork: after fork(), child process must reinitialize mutexes BEFORE locking
220 // The inherited mutex is in an inconsistent state and can cause deadlocks/signals
221 // We must reset it before calling static_mutex_lock
222 pid_t current_pid = platform_get_pid();
223 if ((g_options_initialized || g_init_pid != -1) && g_init_pid != current_pid) {
224// We're in a forked child - reset the static mutex state before using it
225// On POSIX, set initialized=0 so static_mutex_lock will reinitialize it
226// On Windows, the InterlockedCompareExchange logic handles this automatically
227#ifndef _WIN32
228 g_options_init_mutex.initialized = 0; // Force reinitialization on next lock
229#else
230 g_options_init_mutex.initialized = 0; // Same approach for Windows
231#endif
232 log_debug("Detected fork in options_state_init (parent PID %d, child PID %d) - resetting mutex", g_init_pid,
233 current_pid);
234 g_options_initialized = false;
235 g_init_pid = current_pid; // Set to current PID so we know we're initialized in this process
236 }
237
238 static_mutex_lock(&g_options_init_mutex);
239
240 // Check if already initialized in this process
241 if (g_options_initialized && g_init_pid == current_pid) {
242 // Already initialized in this process
243 static_mutex_unlock(&g_options_init_mutex);
244 log_warn("Options state already initialized");
245 return ASCIICHAT_OK;
246 }
247
248 // Initialize write mutex
249 if (mutex_init(&g_options_write_mutex) != 0) {
250 static_mutex_unlock(&g_options_init_mutex);
251 return SET_ERRNO(ERROR_THREAD, "Failed to initialize options write mutex");
252 }
253
254 // Initialize deferred free mutex
255 if (mutex_init(&g_deferred_free_mutex) != 0) {
256 mutex_destroy(&g_options_write_mutex);
257 static_mutex_unlock(&g_options_init_mutex);
258 return SET_ERRNO(ERROR_THREAD, "Failed to initialize deferred free mutex");
259 }
260
261 // Allocate initial options struct (will be populated later by options_state_populate_from_globals)
262 options_t *initial_opts = SAFE_MALLOC(sizeof(options_t), options_t *);
263 if (!initial_opts) {
264 mutex_destroy(&g_options_write_mutex);
265 mutex_destroy(&g_deferred_free_mutex);
266 static_mutex_unlock(&g_options_init_mutex);
267 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate initial options struct");
268 }
269
270 // Initialize all defaults using options_t_new()
271 *initial_opts = options_t_new();
272
273 // Publish initial struct (release semantics - make all fields visible to readers)
274 atomic_store_explicit(&g_options, initial_opts, memory_order_release);
275
276 g_options_initialized = true;
277 g_init_pid = current_pid; // Record PID to detect fork later
278 static_mutex_unlock(&g_options_init_mutex);
279 log_dev("Options state initialized with RCU pattern (PID %d)", current_pid);
280
281 return ASCIICHAT_OK;
282}
283
284asciichat_error_t options_state_set(const options_t *opts) {
285 if (!opts) {
286 return SET_ERRNO(ERROR_INVALID_PARAM, "opts is NULL");
287 }
288
289 // Check initialization flag under lock to prevent TOCTOU race
290 static_mutex_lock(&g_options_init_mutex);
291 if (!g_options_initialized) {
292 static_mutex_unlock(&g_options_init_mutex);
293 return SET_ERRNO(ERROR_INVALID_STATE, "Options state not initialized (call options_state_init first)");
294 }
295 static_mutex_unlock(&g_options_init_mutex);
296
297 // Get current struct (should be the initial zero-initialized one during startup)
298 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
299
300 // Copy provided options into current struct
301 // Note: No need for RCU update here since this is called during initialization
302 // before any reader threads exist
303 memcpy(current, opts, sizeof(options_t));
304
305 log_debug("Options state set from parsed struct");
306 return ASCIICHAT_OK;
307}
308
310 // Check and clear initialization flag under lock
311 static_mutex_lock(&g_options_init_mutex);
312 if (!g_options_initialized) {
313 static_mutex_unlock(&g_options_init_mutex);
314 return;
315 }
316 g_options_initialized = false; // Clear flag first to prevent new operations
317 static_mutex_unlock(&g_options_init_mutex);
318
319 // Get current options pointer
320 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
321
322 // Clear the atomic pointer (prevent further reads)
323 atomic_store_explicit(&g_options, NULL, memory_order_release);
324
325 // Free current struct
326 SAFE_FREE(current);
327
328 // Free all deferred structs
329 deferred_free_all();
330
331 // Cleanup schema (frees all dynamically allocated config strings)
333
334 // Destroy dynamically created mutexes
335 // NOTE: Do NOT destroy g_options_init_mutex - it's statically initialized
336 // and needs to persist across shutdown/init cycles (especially in tests)
337 mutex_destroy(&g_options_write_mutex);
338 mutex_destroy(&g_deferred_free_mutex);
339
340 log_debug("Options state shutdown complete");
341}
342
346
347const options_t *options_get(void) {
348 // Lock-free read with acquire semantics
349 // Guarantees we see all writes made before the pointer was published
350 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
351
352 // If options not yet published, return safe static default instead of crashing
353 // This allows atexit handlers to safely call GET_OPTION() during cleanup
354 if (!current) {
355 // Return static default - safe for atexit handlers to read
356 return (const options_t *)&g_default_options;
357 }
358
359 return current;
360}
361
362static asciichat_error_t options_update(void (*updater)(options_t *, void *), void *context) {
363 if (!updater) {
364 return SET_ERRNO(ERROR_INVALID_PARAM, "updater function is NULL");
365 }
366
367 if (!g_options_initialized) {
368 return SET_ERRNO(ERROR_INVALID_STATE, "Options state not initialized");
369 }
370
371 // Serialize writers with mutex
372 mutex_lock(&g_options_write_mutex);
373
374 // 1. Load current options (acquire semantics)
375 options_t *old_opts = atomic_load_explicit(&g_options, memory_order_acquire);
376
377 // 2. Allocate new options struct
378 options_t *new_opts = SAFE_MALLOC(sizeof(options_t), options_t *);
379 if (!new_opts) {
380 mutex_unlock(&g_options_write_mutex);
381 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate new options struct");
382 }
383
384 // 3. Copy current values to new struct
385 memcpy(new_opts, old_opts, sizeof(options_t));
386
387 // 4. Call user updater to modify the new struct
388 updater(new_opts, context);
389
390 // 5. Atomically swap global pointer (release semantics)
391 // This makes the new struct visible to all readers
392 atomic_store_explicit(&g_options, new_opts, memory_order_release);
393
394 // 6. Add old struct to deferred free list
395 deferred_free_add(old_opts);
396
397 mutex_unlock(&g_options_write_mutex);
398
399 log_debug("Options updated via RCU (old=%p, new=%p)", (void *)old_opts, (void *)new_opts);
400 return ASCIICHAT_OK;
401}
402
403// ============================================================================
404// Generic Option Setters
405// ============================================================================
406
410typedef struct {
411 const char *field_name;
412 int value;
414
415static void int_field_updater(options_t *opts, void *context) {
416 int_field_ctx_t *ctx = (int_field_ctx_t *)context;
417 if (strcmp(ctx->field_name, "width") == 0)
418 opts->width = ctx->value;
419 else if (strcmp(ctx->field_name, "height") == 0)
420 opts->height = ctx->value;
421 else if (strcmp(ctx->field_name, "max_clients") == 0)
422 opts->max_clients = ctx->value;
423 else if (strcmp(ctx->field_name, "compression_level") == 0)
424 opts->compression_level = ctx->value;
425 else if (strcmp(ctx->field_name, "reconnect_attempts") == 0)
426 opts->reconnect_attempts = ctx->value;
427 else if (strcmp(ctx->field_name, "microphone_index") == 0)
428 opts->microphone_index = ctx->value;
429 else if (strcmp(ctx->field_name, "speakers_index") == 0)
430 opts->speakers_index = ctx->value;
431 else if (strcmp(ctx->field_name, "discovery_port") == 0)
432 opts->discovery_port = ctx->value;
433 else if (strcmp(ctx->field_name, "port") == 0)
434 opts->port = ctx->value;
435 else if (strcmp(ctx->field_name, "fps") == 0)
436 opts->fps = ctx->value;
437 else if (strcmp(ctx->field_name, "color_mode") == 0)
438 opts->color_mode = (terminal_color_mode_t)ctx->value;
439 else if (strcmp(ctx->field_name, "color_filter") == 0)
440 opts->color_filter = (color_filter_t)ctx->value;
441 else if (strcmp(ctx->field_name, "render_mode") == 0)
442 opts->render_mode = (render_mode_t)ctx->value;
443 else if (strcmp(ctx->field_name, "log_level") == 0)
444 opts->log_level = (log_level_t)ctx->value;
445 else if (strcmp(ctx->field_name, "palette_type") == 0)
446 opts->palette_type = (palette_type_t)ctx->value;
447}
448
449asciichat_error_t options_set_int(const char *field_name, int value) {
450 if (!field_name) {
451 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
452 return ERROR_INVALID_PARAM;
453 }
454
455 if (strcmp(field_name, "width") != 0 && strcmp(field_name, "height") != 0 && strcmp(field_name, "max_clients") != 0 &&
456 strcmp(field_name, "compression_level") != 0 && strcmp(field_name, "reconnect_attempts") != 0 &&
457 strcmp(field_name, "microphone_index") != 0 && strcmp(field_name, "speakers_index") != 0 &&
458 strcmp(field_name, "discovery_port") != 0 && strcmp(field_name, "port") != 0 && strcmp(field_name, "fps") != 0 &&
459 strcmp(field_name, "color_mode") != 0 && strcmp(field_name, "color_filter") != 0 &&
460 strcmp(field_name, "render_mode") != 0 && strcmp(field_name, "log_level") != 0 &&
461 strcmp(field_name, "palette_type") != 0) {
462 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown integer field: %s", field_name);
463 return ERROR_INVALID_PARAM;
464 }
465
466 int_field_ctx_t ctx = {.field_name = field_name, .value = value};
467 return options_update(int_field_updater, &ctx);
468}
469
473typedef struct {
474 const char *field_name;
475 bool value;
477
478static void bool_field_updater(options_t *opts, void *context) {
479 bool_field_ctx_t *ctx = (bool_field_ctx_t *)context;
480 if (strcmp(ctx->field_name, "no_compress") == 0)
481 opts->no_compress = ctx->value;
482 else if (strcmp(ctx->field_name, "encode_audio") == 0)
483 opts->encode_audio = ctx->value;
484 else if (strcmp(ctx->field_name, "flip_x") == 0)
485 opts->flip_x = ctx->value;
486 else if (strcmp(ctx->field_name, "flip_y") == 0)
487 opts->flip_y = ctx->value;
488 else if (strcmp(ctx->field_name, "test_pattern") == 0)
489 opts->test_pattern = ctx->value;
490 else if (strcmp(ctx->field_name, "no_audio_mixer") == 0)
491 opts->no_audio_mixer = ctx->value;
492 else if (strcmp(ctx->field_name, "show_capabilities") == 0)
493 opts->show_capabilities = ctx->value;
494 else if (strcmp(ctx->field_name, "force_utf8") == 0)
495 opts->force_utf8 = ctx->value;
496 else if (strcmp(ctx->field_name, "audio_enabled") == 0)
497 opts->audio_enabled = ctx->value;
498 else if (strcmp(ctx->field_name, "audio_analysis_enabled") == 0)
499 opts->audio_analysis_enabled = ctx->value;
500 else if (strcmp(ctx->field_name, "audio_no_playback") == 0)
501 opts->audio_no_playback = ctx->value;
502 else if (strcmp(ctx->field_name, "stretch") == 0)
503 opts->stretch = ctx->value;
504 else if (strcmp(ctx->field_name, "snapshot_mode") == 0)
505 opts->snapshot_mode = ctx->value;
506 else if (strcmp(ctx->field_name, "strip_ansi") == 0)
507 opts->strip_ansi = ctx->value;
508 else if (strcmp(ctx->field_name, "quiet") == 0)
509 opts->quiet = ctx->value;
510 else if (strcmp(ctx->field_name, "encrypt_enabled") == 0)
511 opts->encrypt_enabled = ctx->value;
512 else if (strcmp(ctx->field_name, "no_encrypt") == 0)
513 opts->no_encrypt = ctx->value;
514 else if (strcmp(ctx->field_name, "discovery") == 0)
515 opts->discovery = ctx->value;
516 else if (strcmp(ctx->field_name, "discovery_expose_ip") == 0)
517 opts->discovery_expose_ip = ctx->value;
518 else if (strcmp(ctx->field_name, "discovery_insecure") == 0)
519 opts->discovery_insecure = ctx->value;
520 else if (strcmp(ctx->field_name, "webrtc") == 0)
521 opts->webrtc = ctx->value;
522 else if (strcmp(ctx->field_name, "lan_discovery") == 0)
523 opts->lan_discovery = ctx->value;
524 else if (strcmp(ctx->field_name, "no_mdns_advertise") == 0)
525 opts->no_mdns_advertise = ctx->value;
526 else if (strcmp(ctx->field_name, "prefer_webrtc") == 0)
527 opts->prefer_webrtc = ctx->value;
528 else if (strcmp(ctx->field_name, "no_webrtc") == 0)
529 opts->no_webrtc = ctx->value;
530 else if (strcmp(ctx->field_name, "webrtc_skip_stun") == 0)
531 opts->webrtc_skip_stun = ctx->value;
532 else if (strcmp(ctx->field_name, "webrtc_disable_turn") == 0)
533 opts->webrtc_disable_turn = ctx->value;
534 else if (strcmp(ctx->field_name, "enable_upnp") == 0)
535 opts->enable_upnp = ctx->value;
536 else if (strcmp(ctx->field_name, "require_server_identity") == 0)
537 opts->require_server_identity = ctx->value;
538 else if (strcmp(ctx->field_name, "require_client_identity") == 0)
539 opts->require_client_identity = ctx->value;
540 else if (strcmp(ctx->field_name, "require_server_verify") == 0)
541 opts->require_server_verify = ctx->value;
542 else if (strcmp(ctx->field_name, "require_client_verify") == 0)
543 opts->require_client_verify = ctx->value;
544 else if (strcmp(ctx->field_name, "palette_custom_set") == 0)
545 opts->palette_custom_set = ctx->value;
546 else if (strcmp(ctx->field_name, "media_loop") == 0)
547 opts->media_loop = ctx->value;
548 else if (strcmp(ctx->field_name, "media_from_stdin") == 0)
549 opts->media_from_stdin = ctx->value;
550 else if (strcmp(ctx->field_name, "auto_width") == 0)
551 opts->auto_width = ctx->value;
552 else if (strcmp(ctx->field_name, "auto_height") == 0)
553 opts->auto_height = ctx->value;
554 else if (strcmp(ctx->field_name, "splash") == 0)
555 opts->splash = ctx->value;
556 else if (strcmp(ctx->field_name, "status_screen") == 0)
557 opts->status_screen = ctx->value;
558 else if (strcmp(ctx->field_name, "matrix_rain") == 0)
559 opts->matrix_rain = ctx->value;
560}
561
562asciichat_error_t options_set_bool(const char *field_name, bool value) {
563 if (!field_name) {
564 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
565 return ERROR_INVALID_PARAM;
566 }
567
568 // Validate field exists
569 if (strcmp(field_name, "no_compress") != 0 && strcmp(field_name, "encode_audio") != 0 &&
570 strcmp(field_name, "flip_x") != 0 && strcmp(field_name, "flip_y") != 0 &&
571 strcmp(field_name, "test_pattern") != 0 && strcmp(field_name, "no_audio_mixer") != 0 &&
572 strcmp(field_name, "show_capabilities") != 0 && strcmp(field_name, "force_utf8") != 0 &&
573 strcmp(field_name, "audio_enabled") != 0 && strcmp(field_name, "audio_analysis_enabled") != 0 &&
574 strcmp(field_name, "audio_no_playback") != 0 && strcmp(field_name, "stretch") != 0 &&
575 strcmp(field_name, "snapshot_mode") != 0 && strcmp(field_name, "strip_ansi") != 0 &&
576 strcmp(field_name, "quiet") != 0 && strcmp(field_name, "encrypt_enabled") != 0 &&
577 strcmp(field_name, "no_encrypt") != 0 && strcmp(field_name, "discovery") != 0 &&
578 strcmp(field_name, "discovery_expose_ip") != 0 && strcmp(field_name, "discovery_insecure") != 0 &&
579 strcmp(field_name, "webrtc") != 0 && strcmp(field_name, "lan_discovery") != 0 &&
580 strcmp(field_name, "no_mdns_advertise") != 0 && strcmp(field_name, "prefer_webrtc") != 0 &&
581 strcmp(field_name, "no_webrtc") != 0 && strcmp(field_name, "webrtc_skip_stun") != 0 &&
582 strcmp(field_name, "webrtc_disable_turn") != 0 && strcmp(field_name, "enable_upnp") != 0 &&
583 strcmp(field_name, "require_server_identity") != 0 && strcmp(field_name, "require_client_identity") != 0 &&
584 strcmp(field_name, "require_server_verify") != 0 && strcmp(field_name, "require_client_verify") != 0 &&
585 strcmp(field_name, "palette_custom_set") != 0 && strcmp(field_name, "media_loop") != 0 &&
586 strcmp(field_name, "media_from_stdin") != 0 && strcmp(field_name, "auto_width") != 0 &&
587 strcmp(field_name, "auto_height") != 0 && strcmp(field_name, "splash") != 0 &&
588 strcmp(field_name, "status_screen") != 0 && strcmp(field_name, "matrix_rain") != 0) {
589 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown boolean field: %s", field_name);
590 return ERROR_INVALID_PARAM;
591 }
592
593 bool_field_ctx_t ctx = {.field_name = field_name, .value = value};
594 return options_update(bool_field_updater, &ctx);
595}
596
600typedef struct {
601 const char *field_name;
602 const char *value;
604
605static void string_field_updater(options_t *opts, void *context) {
606 string_field_ctx_t *ctx = (string_field_ctx_t *)context;
607 if (strcmp(ctx->field_name, "address") == 0)
608 SAFE_STRNCPY(opts->address, ctx->value, sizeof(opts->address));
609 else if (strcmp(ctx->field_name, "address6") == 0)
610 SAFE_STRNCPY(opts->address6, ctx->value, sizeof(opts->address6));
611 else if (strcmp(ctx->field_name, "encrypt_key") == 0)
612 SAFE_STRNCPY(opts->encrypt_key, ctx->value, sizeof(opts->encrypt_key));
613 else if (strcmp(ctx->field_name, "password") == 0)
614 SAFE_STRNCPY(opts->password, ctx->value, sizeof(opts->password));
615 else if (strcmp(ctx->field_name, "encrypt_keyfile") == 0)
616 SAFE_STRNCPY(opts->encrypt_keyfile, ctx->value, sizeof(opts->encrypt_keyfile));
617 else if (strcmp(ctx->field_name, "server_key") == 0)
618 SAFE_STRNCPY(opts->server_key, ctx->value, sizeof(opts->server_key));
619 else if (strcmp(ctx->field_name, "client_keys") == 0)
620 SAFE_STRNCPY(opts->client_keys, ctx->value, sizeof(opts->client_keys));
621 else if (strcmp(ctx->field_name, "discovery_server") == 0)
622 SAFE_STRNCPY(opts->discovery_server, ctx->value, sizeof(opts->discovery_server));
623 else if (strcmp(ctx->field_name, "discovery_service_key") == 0)
624 SAFE_STRNCPY(opts->discovery_service_key, ctx->value, sizeof(opts->discovery_service_key));
625 else if (strcmp(ctx->field_name, "discovery_database_path") == 0)
626 SAFE_STRNCPY(opts->discovery_database_path, ctx->value, sizeof(opts->discovery_database_path));
627 else if (strcmp(ctx->field_name, "log_file") == 0)
628 SAFE_STRNCPY(opts->log_file, ctx->value, sizeof(opts->log_file));
629 else if (strcmp(ctx->field_name, "media_file") == 0)
630 SAFE_STRNCPY(opts->media_file, ctx->value, sizeof(opts->media_file));
631 else if (strcmp(ctx->field_name, "palette_custom") == 0)
632 SAFE_STRNCPY(opts->palette_custom, ctx->value, sizeof(opts->palette_custom));
633 else if (strcmp(ctx->field_name, "stun_servers") == 0)
634 SAFE_STRNCPY(opts->stun_servers, ctx->value, sizeof(opts->stun_servers));
635 else if (strcmp(ctx->field_name, "turn_servers") == 0)
636 SAFE_STRNCPY(opts->turn_servers, ctx->value, sizeof(opts->turn_servers));
637 else if (strcmp(ctx->field_name, "turn_username") == 0)
638 SAFE_STRNCPY(opts->turn_username, ctx->value, sizeof(opts->turn_username));
639 else if (strcmp(ctx->field_name, "turn_credential") == 0)
640 SAFE_STRNCPY(opts->turn_credential, ctx->value, sizeof(opts->turn_credential));
641 else if (strcmp(ctx->field_name, "turn_secret") == 0)
642 SAFE_STRNCPY(opts->turn_secret, ctx->value, sizeof(opts->turn_secret));
643 else if (strcmp(ctx->field_name, "session_string") == 0)
644 SAFE_STRNCPY(opts->session_string, ctx->value, sizeof(opts->session_string));
645}
646
647asciichat_error_t options_set_string(const char *field_name, const char *value) {
648 if (!field_name) {
649 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
650 return ERROR_INVALID_PARAM;
651 }
652
653 if (!value) {
654 SET_ERRNO(ERROR_INVALID_PARAM, "value is NULL");
655 return ERROR_INVALID_PARAM;
656 }
657
658 // Validate field exists
659 if (strcmp(field_name, "address") != 0 && strcmp(field_name, "address6") != 0 &&
660 strcmp(field_name, "encrypt_key") != 0 && strcmp(field_name, "password") != 0 &&
661 strcmp(field_name, "encrypt_keyfile") != 0 && strcmp(field_name, "server_key") != 0 &&
662 strcmp(field_name, "client_keys") != 0 && strcmp(field_name, "discovery_server") != 0 &&
663 strcmp(field_name, "discovery_service_key") != 0 && strcmp(field_name, "discovery_database_path") != 0 &&
664 strcmp(field_name, "log_file") != 0 && strcmp(field_name, "media_file") != 0 &&
665 strcmp(field_name, "palette_custom") != 0 && strcmp(field_name, "stun_servers") != 0 &&
666 strcmp(field_name, "turn_servers") != 0 && strcmp(field_name, "turn_username") != 0 &&
667 strcmp(field_name, "turn_credential") != 0 && strcmp(field_name, "turn_secret") != 0 &&
668 strcmp(field_name, "session_string") != 0) {
669 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown string field: %s", field_name);
670 return ERROR_INVALID_PARAM;
671 }
672
673 string_field_ctx_t ctx = {.field_name = field_name, .value = value};
674 return options_update(string_field_updater, &ctx);
675}
676
680typedef struct {
681 const char *field_name;
682 double value;
684
685static void double_field_updater(options_t *opts, void *context) {
686 double_field_ctx_t *ctx = (double_field_ctx_t *)context;
687 if (strcmp(ctx->field_name, "snapshot_delay") == 0)
688 opts->snapshot_delay = ctx->value;
689 else if (strcmp(ctx->field_name, "microphone_sensitivity") == 0)
690 opts->microphone_sensitivity = ctx->value;
691 else if (strcmp(ctx->field_name, "speakers_volume") == 0)
692 opts->speakers_volume = ctx->value;
693}
694
695asciichat_error_t options_set_double(const char *field_name, double value) {
696 if (!field_name) {
697 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
698 return ERROR_INVALID_PARAM;
699 }
700
701 // Normalize option names to internal field names
702 const char *internal_name = field_name;
703 if (strcmp(field_name, "microphone-volume") == 0 || strcmp(field_name, "ivolume") == 0) {
704 internal_name = "microphone_sensitivity";
705 } else if (strcmp(field_name, "speakers-volume") == 0 || strcmp(field_name, "volume") == 0) {
706 internal_name = "speakers_volume";
707 }
708
709 if (strcmp(internal_name, "snapshot_delay") != 0 && strcmp(internal_name, "microphone_sensitivity") != 0 &&
710 strcmp(internal_name, "speakers_volume") != 0) {
711 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown double field: %s", field_name);
712 return ERROR_INVALID_PARAM;
713 }
714
715 double_field_ctx_t ctx = {.field_name = internal_name, .value = value};
716 return options_update(double_field_updater, &ctx);
717}
options_t options_t_new(void)
__attribute__((constructor))
Constructor to initialize fork handlers at startup.
Definition rcu.c:208
asciichat_error_t options_set_int(const char *field_name, int value)
Definition rcu.c:449
void options_state_destroy(void)
Definition rcu.c:309
asciichat_error_t options_state_init(void)
Definition rcu.c:218
asciichat_error_t options_set_double(const char *field_name, double value)
Definition rcu.c:695
void options_cleanup_schema(void)
Definition rcu.c:343
const options_t * options_get(void)
Definition rcu.c:347
asciichat_error_t options_set_string(const char *field_name, const char *value)
Definition rcu.c:647
asciichat_error_t options_set_bool(const char *field_name, bool value)
Definition rcu.c:562
#define MAX_DEFERRED_FREES
Deferred free list for old options structs.
Definition rcu.c:132
asciichat_error_t options_state_set(const options_t *opts)
Definition rcu.c:284
void config_schema_destroy(void)
Definition schema.c:446
Context for boolean field updates in RCU updater callback.
Definition rcu.c:473
const char * field_name
Name of the field to update.
Definition rcu.c:474
bool value
New value to set.
Definition rcu.c:475
Context for double field updates in RCU updater callback.
Definition rcu.c:680
double value
New value to set.
Definition rcu.c:682
const char * field_name
Name of the field to update.
Definition rcu.c:681
Context for integer field updates in RCU updater callback.
Definition rcu.c:410
int value
New value to set.
Definition rcu.c:412
const char * field_name
Name of the field to update.
Definition rcu.c:411
Context for string field updates in RCU updater callback.
Definition rcu.c:600
const char * value
New value to set.
Definition rcu.c:602
const char * field_name
Name of the field to update.
Definition rcu.c:601
int mutex_init(mutex_t *mutex)
Definition threading.c:16
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21
pid_t platform_get_pid(void)
Definition wasm/system.c:35