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>
34static _Atomic(options_t *) g_options = NULL;
47static mutex_t g_options_write_mutex;
52static bool g_options_initialized =
false;
54static static_mutex_t g_options_init_mutex = STATIC_MUTEX_INIT;
56static pid_t g_init_pid = -1;
67static const options_t g_default_options = (options_t){
69 .help = OPT_HELP_DEFAULT,
70 .version = OPT_VERSION_DEFAULT,
73 .log_level = LOG_INFO,
76 .verbose_level = OPT_VERBOSE_LEVEL_DEFAULT,
79 .width = OPT_WIDTH_DEFAULT,
80 .height = OPT_HEIGHT_DEFAULT,
81 .auto_width = OPT_AUTO_WIDTH_DEFAULT,
82 .auto_height = OPT_AUTO_HEIGHT_DEFAULT,
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,
93 .compression_level = OPT_COMPRESSION_LEVEL_DEFAULT,
96 .test_pattern =
false,
97 .webcam_index = OPT_WEBCAM_INDEX_DEFAULT,
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,
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,
132#define MAX_DEFERRED_FREES 64
134static size_t g_deferred_free_count = 0;
135static mutex_t g_deferred_free_mutex;
142static void deferred_free_add(options_t *old_opts) {
147 mutex_lock(&g_deferred_free_mutex);
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);
154 log_warn(
"Deferred free list full (%d entries), freeing options struct %p immediately",
MAX_DEFERRED_FREES,
159 mutex_unlock(&g_deferred_free_mutex);
167static void deferred_free_all(
void) {
168 mutex_lock(&g_deferred_free_mutex);
170 log_debug(
"Freeing %zu deferred options structs", g_deferred_free_count);
172 for (
size_t i = 0; i < g_deferred_free_count; i++) {
173 SAFE_FREE(g_deferred_frees[i]);
176 g_deferred_free_count = 0;
177 mutex_unlock(&g_deferred_free_mutex);
191static void options_rcu_atfork_child(
void) {
193 g_options_init_mutex.initialized = 0;
195 g_options_initialized =
false;
198 atomic_store(&g_options, NULL);
208__attribute__((constructor))
static void register_fork_handlers_constructor(
void) {
210 pthread_atfork(NULL, NULL, options_rcu_atfork_child);
223 if ((g_options_initialized || g_init_pid != -1) && g_init_pid != current_pid) {
228 g_options_init_mutex.initialized = 0;
230 g_options_init_mutex.initialized = 0;
232 log_debug(
"Detected fork in options_state_init (parent PID %d, child PID %d) - resetting mutex", g_init_pid,
234 g_options_initialized =
false;
235 g_init_pid = current_pid;
238 static_mutex_lock(&g_options_init_mutex);
241 if (g_options_initialized && g_init_pid == current_pid) {
243 static_mutex_unlock(&g_options_init_mutex);
244 log_warn(
"Options state already initialized");
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");
255 if (
mutex_init(&g_deferred_free_mutex) != 0) {
257 static_mutex_unlock(&g_options_init_mutex);
258 return SET_ERRNO(ERROR_THREAD,
"Failed to initialize deferred free mutex");
262 options_t *initial_opts = SAFE_MALLOC(
sizeof(options_t), options_t *);
266 static_mutex_unlock(&g_options_init_mutex);
267 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate initial options struct");
274 atomic_store_explicit(&g_options, initial_opts, memory_order_release);
276 g_options_initialized =
true;
277 g_init_pid = current_pid;
278 static_mutex_unlock(&g_options_init_mutex);
279 log_dev(
"Options state initialized with RCU pattern (PID %d)", current_pid);
286 return SET_ERRNO(ERROR_INVALID_PARAM,
"opts is NULL");
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)");
295 static_mutex_unlock(&g_options_init_mutex);
298 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
303 memcpy(current, opts,
sizeof(options_t));
305 log_debug(
"Options state set from parsed struct");
311 static_mutex_lock(&g_options_init_mutex);
312 if (!g_options_initialized) {
313 static_mutex_unlock(&g_options_init_mutex);
316 g_options_initialized =
false;
317 static_mutex_unlock(&g_options_init_mutex);
320 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
323 atomic_store_explicit(&g_options, NULL, memory_order_release);
340 log_debug(
"Options state shutdown complete");
350 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
356 return (
const options_t *)&g_default_options;
362static asciichat_error_t options_update(
void (*updater)(options_t *,
void *),
void *context) {
364 return SET_ERRNO(ERROR_INVALID_PARAM,
"updater function is NULL");
367 if (!g_options_initialized) {
368 return SET_ERRNO(ERROR_INVALID_STATE,
"Options state not initialized");
372 mutex_lock(&g_options_write_mutex);
375 options_t *old_opts = atomic_load_explicit(&g_options, memory_order_acquire);
378 options_t *new_opts = SAFE_MALLOC(
sizeof(options_t), options_t *);
380 mutex_unlock(&g_options_write_mutex);
381 return SET_ERRNO(ERROR_MEMORY,
"Failed to allocate new options struct");
385 memcpy(new_opts, old_opts,
sizeof(options_t));
388 updater(new_opts, context);
392 atomic_store_explicit(&g_options, new_opts, memory_order_release);
395 deferred_free_add(old_opts);
397 mutex_unlock(&g_options_write_mutex);
399 log_debug(
"Options updated via RCU (old=%p, new=%p)", (
void *)old_opts, (
void *)new_opts);
415static void int_field_updater(options_t *opts,
void *context) {
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;
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;
451 SET_ERRNO(ERROR_INVALID_PARAM,
"field_name is NULL");
452 return ERROR_INVALID_PARAM;
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;
467 return options_update(int_field_updater, &ctx);
478static void bool_field_updater(options_t *opts,
void *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;
564 SET_ERRNO(ERROR_INVALID_PARAM,
"field_name is NULL");
565 return ERROR_INVALID_PARAM;
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;
594 return options_update(bool_field_updater, &ctx);
605static void string_field_updater(options_t *opts,
void *context) {
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));
649 SET_ERRNO(ERROR_INVALID_PARAM,
"field_name is NULL");
650 return ERROR_INVALID_PARAM;
654 SET_ERRNO(ERROR_INVALID_PARAM,
"value is NULL");
655 return ERROR_INVALID_PARAM;
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;
674 return options_update(string_field_updater, &ctx);
685static void double_field_updater(options_t *opts,
void *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;
697 SET_ERRNO(ERROR_INVALID_PARAM,
"field_name is NULL");
698 return ERROR_INVALID_PARAM;
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";
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;
716 return options_update(double_field_updater, &ctx);
options_t options_t_new(void)
__attribute__((constructor))
Constructor to initialize fork handlers at startup.
asciichat_error_t options_set_int(const char *field_name, int value)
void options_state_destroy(void)
asciichat_error_t options_state_init(void)
asciichat_error_t options_set_double(const char *field_name, double value)
void options_cleanup_schema(void)
const options_t * options_get(void)
asciichat_error_t options_set_string(const char *field_name, const char *value)
asciichat_error_t options_set_bool(const char *field_name, bool value)
#define MAX_DEFERRED_FREES
Deferred free list for old options structs.
asciichat_error_t options_state_set(const options_t *opts)
void config_schema_destroy(void)
Context for boolean field updates in RCU updater callback.
const char * field_name
Name of the field to update.
bool value
New value to set.
Context for double field updates in RCU updater callback.
double value
New value to set.
const char * field_name
Name of the field to update.
Context for integer field updates in RCU updater callback.
int value
New value to set.
const char * field_name
Name of the field to update.
Context for string field updates in RCU updater callback.
const char * value
New value to set.
const char * field_name
Name of the field to update.
int mutex_init(mutex_t *mutex)
int mutex_destroy(mutex_t *mutex)
pid_t platform_get_pid(void)