ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
config.c File Reference

📋 TOML configuration file parser with schema validation and CLI override support More...

Go to the source code of this file.

Data Structures

union  option_parsed_value_t
 Union holding all possible parsed option values. More...
 
struct  option_type_handler_t
 Type handler - encapsulates all 4 operations for one option type. More...
 
struct  config_builder_t
 Helper structure for building config content in a buffer. More...
 

Macros

Internal Macros
#define CONFIG_WARN(fmt, ...)
 Print configuration warning using the logging system.
 
#define CONFIG_DEBUG(fmt, ...)
 Print configuration debug message.
 

Functions

asciichat_error_t config_load_and_apply (asciichat_mode_t detected_mode, const char *config_path, bool strict, options_t *opts)
 Main function to load configuration from file and apply to global options.
 
asciichat_error_t config_create_default (const char *config_path)
 
asciichat_error_t config_load_system_and_user (asciichat_mode_t detected_mode, bool strict, options_t *opts)
 
TOML Value Extraction Helpers

Helper functions to safely extract typed values from TOML datum structures.

Schema-Based Configuration Parser

Detailed Description

📋 TOML configuration file parser with schema validation and CLI override support

Definition in file config.c.

Macro Definition Documentation

◆ CONFIG_DEBUG

#define CONFIG_DEBUG (   fmt,
  ... 
)
Value:
do { \
/* Debug messages are only shown in debug builds after logging is initialized */ \
/* Use log_debug which safely checks initialization itself */ \
log_debug(fmt, ##__VA_ARGS__); \
} while (0)

Print configuration debug message.

Debug messages use the logging system if it's initialized, otherwise they are silently dropped.

Definition at line 57 of file config.c.

58 { \
59 /* Debug messages are only shown in debug builds after logging is initialized */ \
60 /* Use log_debug which safely checks initialization itself */ \
61 log_debug(fmt, ##__VA_ARGS__); \
62 } while (0)

◆ CONFIG_WARN

#define CONFIG_WARN (   fmt,
  ... 
)
Value:
do { \
log_warn("Config file: " fmt, ##__VA_ARGS__); \
} while (0)

Print configuration warning using the logging system.

Uses log_warn so that config warnings respect the –quiet flag and are routed through the logging system for proper filtering.

Definition at line 46 of file config.c.

47 { \
48 log_warn("Config file: " fmt, ##__VA_ARGS__); \
49 } while (0)

Function Documentation

◆ config_create_default()

asciichat_error_t config_create_default ( const char *  config_path)

Definition at line 1250 of file config.c.

1250 {
1251 char *config_path_expanded = NULL;
1252
1253 defer(SAFE_FREE(config_path_expanded));
1254
1255 // Create fresh options with all OPT_*_DEFAULT values
1256 options_t defaults = options_t_new();
1257
1258 // Allocate buffer for building config content (256KB should be plenty)
1259 const size_t BUFFER_CAPACITY = 256 * 1024;
1260 config_builder_t builder = {0};
1261 builder.buffer = SAFE_MALLOC(BUFFER_CAPACITY, char *);
1262 if (!builder.buffer) {
1263 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate config buffer");
1264 }
1265 defer(SAFE_FREE(builder.buffer));
1266 builder.capacity = BUFFER_CAPACITY;
1267
1268 // Build version comment in buffer
1269 if (!config_builder_append(&builder, "# ascii-chat configuration file\n")) {
1270 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1271 }
1272 if (!config_builder_append(&builder, "# Generated by ascii-chat v%d.%d.%d-%s\n", ASCII_CHAT_VERSION_MAJOR,
1273 ASCII_CHAT_VERSION_MINOR, ASCII_CHAT_VERSION_PATCH, ASCII_CHAT_GIT_VERSION)) {
1274 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1275 }
1276 if (!config_builder_append(&builder, "#\n")) {
1277 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1278 }
1279 if (!config_builder_append(&builder, "# All options below are commented out because some configuration options\n")) {
1280 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1281 }
1282 if (!config_builder_append(&builder, "# conflict with each other (e.g., --file vs --url, --loop vs --url).\n")) {
1283 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1284 }
1285 if (!config_builder_append(&builder, "# Uncomment only the options you need and avoid conflicting combinations.\n")) {
1286 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1287 }
1288 if (!config_builder_append(&builder, "#\n")) {
1289 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1290 }
1291 if (!config_builder_append(&builder,
1292 "# If you upgrade ascii-chat and this version comment changes, you may need to\n")) {
1293 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1294 }
1295 if (!config_builder_append(&builder, "# delete and regenerate this file with: ascii-chat --config-create\n")) {
1296 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1297 }
1298 if (!config_builder_append(&builder, "#\n\n")) {
1299 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1300 }
1301
1302 // Get all options from schema
1303 size_t metadata_count = 0;
1304 const config_option_metadata_t *metadata = config_schema_get_all(&metadata_count);
1305
1306 // Build list of unique categories in order of first appearance
1307 const char *categories[16] = {0}; // Max expected categories
1308 size_t category_count = 0;
1309
1310 for (size_t i = 0; i < metadata_count && category_count < 16; i++) {
1311 const char *category = metadata[i].category;
1312 if (!category) {
1313 continue;
1314 }
1315
1316 // Check if category already in list
1317 bool found = false;
1318 for (size_t j = 0; j < category_count; j++) {
1319 if (categories[j] && strcmp(categories[j], category) == 0) {
1320 found = true;
1321 break;
1322 }
1323 }
1324
1325 if (!found) {
1326 categories[category_count++] = category;
1327 }
1328 }
1329
1330 // Build each section dynamically from schema
1331 for (size_t cat_idx = 0; cat_idx < category_count; cat_idx++) {
1332 const char *category = categories[cat_idx];
1333 if (!category) {
1334 continue;
1335 }
1336
1337 // Get all options for this category
1338 size_t cat_option_count = 0;
1339 const config_option_metadata_t **cat_options = config_schema_get_by_category(category, &cat_option_count);
1340
1341 if (!cat_options || cat_option_count == 0) {
1342 continue;
1343 }
1344
1345 // Add section header
1346 if (!config_builder_append(&builder, "[%s]\n", category)) {
1347 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1348 }
1349
1350 // Track which options we've written (to avoid duplicates)
1351 bool written_flags[64] = {0}; // Max options per category
1352
1353 // Add each option in this category
1354 for (size_t opt_idx = 0; opt_idx < cat_option_count && opt_idx < 64; opt_idx++) {
1355 const config_option_metadata_t *meta = cat_options[opt_idx];
1356 if (!meta || !meta->toml_key) {
1357 continue;
1358 }
1359
1360 // Skip if already written (duplicate)
1361 if (written_flags[opt_idx]) {
1362 continue;
1363 }
1364
1365 // Skip if this is a duplicate of another option (check by TOML key, not field_offset)
1366 // Note: Multiple options can map to the same field (e.g., server_log_file, client_log_file)
1367 bool is_duplicate = false;
1368 for (size_t j = 0; j < opt_idx; j++) {
1369 if (cat_options[j] && cat_options[j]->toml_key && meta->toml_key &&
1370 strcmp(cat_options[j]->toml_key, meta->toml_key) == 0) {
1371 is_duplicate = true;
1372 break;
1373 }
1374 }
1375 if (is_duplicate) {
1376 continue;
1377 }
1378
1379 // Get field pointer from default options (or mode-specific default if available)
1380 const char *field_ptr = ((const char *)&defaults) + meta->field_offset;
1381
1382 // Buffer to hold mode-specific default value if needed
1383 char mode_default_buffer[OPTIONS_BUFF_SIZE] = {0};
1384 int mode_default_int = 0;
1385
1386 // If this option has a mode_default_getter, use it to get the correct default
1387 if (meta->mode_default_getter) {
1388 asciichat_mode_t mode = extract_mode_from_bitmask(meta->mode_bitmask);
1389 if (mode != MODE_INVALID) {
1390 const void *default_value = meta->mode_default_getter(mode);
1391 if (default_value) {
1392 // Copy the default value to our buffer based on type
1393 if (meta->type == OPTION_TYPE_STRING || meta->type == OPTION_TYPE_CALLBACK) {
1394 const char *str_value = (const char *)default_value;
1395 SAFE_STRNCPY(mode_default_buffer, str_value, sizeof(mode_default_buffer));
1396 field_ptr = mode_default_buffer;
1397 } else if (meta->type == OPTION_TYPE_INT) {
1398 mode_default_int = *(const int *)default_value;
1399 field_ptr = (const char *)&mode_default_int;
1400 }
1401 }
1402 }
1403 }
1404
1405 // Add description comment if available
1406 if (meta->description && strlen(meta->description) > 0) {
1407 if (!config_builder_append(&builder, "# %s\n", meta->description)) {
1408 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1409 }
1410 }
1411
1412 // Format and add the option value using handler (commented out to avoid conflicts)
1413 if (g_type_handlers[meta->type].format_output) {
1414 char formatted_value[BUFFER_SIZE_MEDIUM] = {0};
1415 g_type_handlers[meta->type].format_output(field_ptr, meta->field_size, meta, formatted_value,
1416 sizeof(formatted_value));
1417 const char *output_key = meta->toml_key;
1418 size_t category_len = strlen(category);
1419 if (strncmp(meta->toml_key, category, category_len) == 0 && meta->toml_key[category_len] == '.') {
1420 output_key = meta->toml_key + category_len + 1; // Strip "<category>."
1421 }
1422
1423 if (config_key_should_be_commented(meta->toml_key)) {
1424 if (!config_builder_append(&builder, "# %s = %s\n", output_key, formatted_value)) {
1425 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1426 }
1427 } else {
1428 if (!config_builder_append(&builder, "%s = %s\n", output_key, formatted_value)) {
1429 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1430 }
1431 }
1432 }
1433
1434 // Add blank line after each option
1435 if (!config_builder_append(&builder, "\n")) {
1436 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1437 }
1438
1439 written_flags[opt_idx] = true;
1440 }
1441
1442 // Add blank line between sections (but not after the last section)
1443 if (cat_idx < category_count - 1) {
1444 if (!config_builder_append(&builder, "\n")) {
1445 return SET_ERRNO(ERROR_CONFIG, "Config too large to fit in buffer");
1446 }
1447 }
1448 }
1449
1450 // Now write the buffer to either stdout or a file
1451 if (config_path && strlen(config_path) > 0) {
1452 // User provided a filepath - write to that file with overwrite prompt
1453
1454 // Expand and validate the path
1455 config_path_expanded = expand_path(config_path);
1456 if (!config_path_expanded) {
1457 config_path_expanded = platform_strdup(config_path);
1458 }
1459
1460 if (!config_path_expanded) {
1461 return SET_ERRNO(ERROR_CONFIG, "Failed to resolve config file path");
1462 }
1463
1464 char *validated_config_path = NULL;
1465 asciichat_error_t validate_result =
1466 path_validate_user_path(config_path_expanded, PATH_ROLE_CONFIG_FILE, &validated_config_path);
1467 if (validate_result != ASCIICHAT_OK) {
1468 SAFE_FREE(validated_config_path);
1469 SAFE_FREE(config_path_expanded);
1470 return validate_result;
1471 }
1472 // Free the old path before reassigning (defer will free the new one)
1473 if (config_path_expanded != validated_config_path) {
1474 SAFE_FREE(config_path_expanded);
1475 }
1476 config_path_expanded = validated_config_path;
1477
1478 // Check if file already exists
1479 struct stat st;
1480 if (stat(config_path_expanded, &st) == 0) {
1481 // File exists - ask user if they want to overwrite
1482 log_plain("Config file already exists: %s", config_path_expanded);
1483
1484 bool overwrite = platform_prompt_yes_no("Overwrite", false); // Default to No
1485 if (!overwrite) {
1486 log_plain("Config file creation cancelled.");
1487 return SET_ERRNO(ERROR_CONFIG, "User cancelled overwrite");
1488 }
1489
1490 log_plain("Overwriting existing config file...");
1491 }
1492
1493 // Create directory if needed
1494 char *dir_path = platform_strdup(config_path_expanded);
1495 if (!dir_path) {
1496 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for directory path");
1497 }
1498 defer(SAFE_FREE(dir_path));
1499
1500 // Find the last path separator
1501 char *last_sep = strrchr(dir_path, PATH_DELIM);
1502
1503 if (last_sep) {
1504 *last_sep = '\0';
1505 // Create directory recursively
1506 asciichat_error_t mkdir_result = platform_mkdir_recursive(dir_path, DIR_PERM_PRIVATE);
1507 if (mkdir_result != ASCIICHAT_OK) {
1508 return mkdir_result;
1509 }
1510 }
1511
1512 // Open file for writing
1513 FILE *output_file = platform_fopen(config_path_expanded, "w");
1514 if (!output_file) {
1515 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to open config file for writing: %s", config_path_expanded);
1516 }
1517 defer(SAFE_FCLOSE(output_file));
1518
1519 // Write buffer to file
1520 size_t written = fwrite(builder.buffer, 1, builder.size, output_file);
1521 if (written != builder.size) {
1522 return SET_ERRNO_SYS(ERROR_CONFIG, "Failed to write config to file: %s", config_path_expanded);
1523 }
1524 } else {
1525 // No filepath provided - write buffer to stdout with automatic retry on transient errors
1526 (void)platform_write_all(STDOUT_FILENO, builder.buffer, builder.size);
1527 // Flush C stdio buffer and terminal to ensure piped output is written immediately
1528 (void)fflush(stdout);
1529 (void)terminal_flush(STDOUT_FILENO);
1530 }
1531
1532 return ASCIICHAT_OK;
1533}
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all data to file descriptor with automatic retry on transient errors.
Definition abstraction.c:39
options_t options_t_new(void)
asciichat_error_t path_validate_user_path(const char *input, path_role_t role, char **normalized_out)
Definition path.c:974
char * expand_path(const char *path)
Definition path.c:471
char * platform_strdup(const char *s)
asciichat_error_t terminal_flush(int fd)
const config_option_metadata_t ** config_schema_get_by_category(const char *category, size_t *count)
Definition schema.c:393
const config_option_metadata_t * config_schema_get_all(size_t *count)
Definition schema.c:426
Helper structure for building config content in a buffer.
Definition config.c:1180
size_t capacity
Definition config.c:1183
void(* format_output)(const char *field_ptr, size_t field_size, const config_option_metadata_t *meta, char *buf, size_t bufsize)
Format for TOML output.
Definition config.c:154
bool platform_prompt_yes_no(const char *question, bool default_yes)
Definition util.c:81
FILE * platform_fopen(const char *filename, const char *mode)
asciichat_error_t platform_mkdir_recursive(const char *path, int mode)

References config_builder_t::buffer, config_builder_t::capacity, config_schema_get_all(), config_schema_get_by_category(), expand_path(), option_type_handler_t::format_output, options_t_new(), path_validate_user_path(), platform_fopen(), platform_mkdir_recursive(), platform_prompt_yes_no(), platform_strdup(), platform_write_all(), config_builder_t::size, and terminal_flush().

Referenced by action_create_config(), and options_init().

◆ config_load_and_apply()

asciichat_error_t config_load_and_apply ( asciichat_mode_t  detected_mode,
const char *  config_path,
bool  strict,
options_t *  opts 
)

Main function to load configuration from file and apply to global options.

Parameters
is_clienttrue if loading client configuration, false for server configuration
config_pathOptional path to config file (NULL uses default location)
strictIf true, errors are fatal; if false, errors are non-fatal warnings
Returns
ASCIICHAT_OK on success, error code on failure (if strict) or non-fatal (if !strict)

This is the main entry point for configuration loading. It:

  1. Expands the config file path (default location or custom path)
  2. Checks if the file exists and is a regular file
  3. Parses the TOML file using tomlc17
  4. Applies configuration from each section (network, client, palette, crypto, logging)
  5. Frees resources and returns

Configuration file errors are non-fatal if strict is false:

  • Missing file: Returns ASCIICHAT_OK (config file is optional)
  • Not a regular file: Warns and returns ASCIICHAT_OK
  • Parse errors: Warns and returns ASCIICHAT_OK
  • Invalid values: Individual values are skipped with warnings

If strict is true, any error causes immediate return with error code.

Note
This function should be called before options_init() parses command-line arguments to ensure CLI arguments can override config file values.
Configuration warnings are printed to stderr because logging may not be initialized yet when this function is called.

Definition at line 1044 of file config.c.

1045 {
1046 // detected_mode is used in config_apply_schema for bitmask validation
1047 char *config_path_expanded = NULL;
1048 defer(SAFE_FREE(config_path_expanded));
1049
1050 if (config_path) {
1051 // Use custom path provided
1052 config_path_expanded = expand_path(config_path);
1053 if (!config_path_expanded) {
1054 // If expansion fails, try using as-is (might already be absolute)
1055 config_path_expanded = platform_strdup(config_path);
1056 }
1057 } else {
1058 // Use default location with XDG support
1059 char *config_dir = get_config_dir();
1060 defer(SAFE_FREE(config_dir));
1061 if (config_dir) {
1062 size_t len = strlen(config_dir) + strlen("config.toml") + 1;
1063 config_path_expanded = SAFE_MALLOC(len, char *);
1064 if (config_path_expanded) {
1065 safe_snprintf(config_path_expanded, len, "%sconfig.toml", config_dir);
1066 }
1067 }
1068
1069 // Fallback to ~/.ascii-chat/config.toml
1070 if (!config_path_expanded) {
1071 config_path_expanded = expand_path("~/.ascii-chat/config.toml");
1072 }
1073 }
1074
1075 if (!config_path_expanded) {
1076 if (strict) {
1077 return SET_ERRNO(ERROR_CONFIG, "Failed to resolve config file path");
1078 }
1079 return ASCIICHAT_OK;
1080 }
1081
1082 char *validated_config_path = NULL;
1083 asciichat_error_t validate_result =
1084 path_validate_user_path(config_path_expanded, PATH_ROLE_CONFIG_FILE, &validated_config_path);
1085 if (validate_result != ASCIICHAT_OK) {
1086 SAFE_FREE(validated_config_path);
1087 SAFE_FREE(config_path_expanded);
1088 return validate_result;
1089 }
1090 // Free the old path before reassigning (defer will free the new one)
1091 if (config_path_expanded != validated_config_path) {
1092 SAFE_FREE(config_path_expanded);
1093 }
1094 config_path_expanded = validated_config_path;
1095
1096 // Determine display path for error messages (before any early returns)
1097 const char *display_path = config_path ? config_path : config_path_expanded;
1098
1099 // Log that we're attempting to load config (before logging is initialized, use stderr)
1100 // Only print if terminal output is enabled (suppress with --quiet)
1101 if (config_path && log_get_terminal_output()) {
1102 log_debug("Loading configuration from: %s", display_path);
1103 }
1104
1105 // Check if config file exists
1106 struct stat st;
1107 if (stat(config_path_expanded, &st) != 0) {
1108 if (strict) {
1109 return SET_ERRNO(ERROR_CONFIG, "Config file does not exist: '%s'", display_path);
1110 }
1111 // File doesn't exist, that's OK - not required (non-strict mode)
1112 return ASCIICHAT_OK;
1113 }
1114
1115 // Verify it's a regular file
1116 if (!S_ISREG(st.st_mode)) {
1117 if (strict) {
1118 return SET_ERRNO(ERROR_CONFIG, "Config file exists but is not a regular file: '%s'", display_path);
1119 }
1120 CONFIG_WARN("Config file exists but is not a regular file: '%s' (skipping)", display_path);
1121 return ASCIICHAT_OK;
1122 }
1123
1124 // Parse TOML file
1125 toml_result_t result = toml_parse_file_ex(config_path_expanded);
1126 // Ensure TOML resources are freed at ALL function exit points (defer handles cleanup)
1127 defer(toml_free(result));
1128
1129 if (!result.ok) {
1130 // result.errmsg is an array, so check its first character
1131 const char *errmsg = (strlen(result.errmsg) > 0) ? result.errmsg : "Unknown parse error";
1132
1133 if (strict) {
1134 // For strict mode, return detailed error message directly
1135 // Note: SET_ERRNO stores the message in context, but asciichat_error_string() only returns generic codes
1136 // So we need to format the error message ourselves here
1137 char error_buffer[BUFFER_SIZE_MEDIUM];
1138 safe_snprintf(error_buffer, sizeof(error_buffer), "Failed to parse config file '%s': %s", display_path, errmsg);
1139 toml_free(result); // Explicit cleanup before return (defer transformation not applied)
1140 return SET_ERRNO(ERROR_CONFIG, "%s", error_buffer);
1141 }
1142 CONFIG_WARN("Failed to parse config file '%s': %s (skipping)", display_path, errmsg);
1143 toml_free(result); // Explicit cleanup before return (defer transformation not applied)
1144 return ASCIICHAT_OK; // Non-fatal error
1145 }
1146
1147 // Apply configuration using schema-driven parser with bitmask validation
1148 asciichat_error_t schema_result = config_apply_schema(result.toptab, detected_mode, opts, strict);
1149
1150 if (schema_result != ASCIICHAT_OK && strict) {
1151 toml_free(result); // Explicit cleanup before return (defer transformation not applied)
1152 return schema_result;
1153 }
1154 // In non-strict mode, continue even if some options failed validation
1155
1156 CONFIG_DEBUG("Loaded configuration from %s", display_path);
1157
1158 // Log successful config load (use stderr since logging may not be initialized yet)
1159 // Only print if terminal output is enabled (suppress with --quiet)
1161 log_debug("Loaded configuration from: %s", display_path);
1162 }
1163
1164 // Update RCU system with modified options (for test compatibility)
1165 // In real usage, options_state_set is called later after CLI parsing
1166 asciichat_error_t rcu_result = options_state_set(opts);
1167 if (rcu_result != ASCIICHAT_OK) {
1168 // Non-fatal - RCU might not be initialized yet in some test scenarios
1169 // But log as warning so tests can see if this is the issue
1170 CONFIG_WARN("Failed to update RCU options state: %d (values may not be persisted)", rcu_result);
1171 }
1172
1173 toml_free(result); // Explicit cleanup before return (defer transformation not applied)
1174 return ASCIICHAT_OK;
1175}
#define CONFIG_DEBUG(fmt,...)
Print configuration debug message.
Definition config.c:57
#define CONFIG_WARN(fmt,...)
Print configuration warning using the logging system.
Definition config.c:46
bool log_get_terminal_output(void)
char * get_config_dir(void)
Definition path.c:493
asciichat_error_t options_state_set(const options_t *opts)
Definition rcu.c:284
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456

References CONFIG_DEBUG, CONFIG_WARN, expand_path(), get_config_dir(), log_get_terminal_output(), options_state_set(), path_validate_user_path(), platform_strdup(), and safe_snprintf().

Referenced by config_load_system_and_user().

◆ config_load_system_and_user()

asciichat_error_t config_load_system_and_user ( asciichat_mode_t  detected_mode,
bool  strict,
options_t *  opts 
)

Definition at line 1535 of file config.c.

1535 {
1536 // Use platform abstraction to find all config.toml files across standard locations
1537 config_file_list_t config_files = {0};
1538 asciichat_error_t search_result = platform_find_config_file("config.toml", &config_files);
1539 defer(config_file_list_destroy(&config_files));
1540
1541 if (search_result != ASCIICHAT_OK) {
1542 CONFIG_DEBUG("Failed to search for config files: %d", search_result);
1543 config_file_list_destroy(&config_files);
1544 return search_result;
1545 }
1546
1547 // Cascade load: Load all found configs in reverse order (lowest priority first)
1548 // This allows higher-priority configs to override lower-priority values.
1549 // Example: System configs load first, then user configs override them.
1550
1551 asciichat_error_t result = ASCIICHAT_OK;
1552 for (size_t i = config_files.count; i > 0; i--) {
1553 const config_file_result_t *file = &config_files.files[i - 1];
1554
1555 // Determine strictness based on whether this is a system or user config
1556 // System configs are non-strict (values can be missing, errors are non-fatal)
1557 // User config is strict or non-strict based on parameter
1558 bool is_user_config = !file->is_system_config;
1559 bool file_strict = is_user_config ? strict : false;
1560
1561 CONFIG_DEBUG("Loading config from %s (system=%s, strict=%s)", file->path, file->is_system_config ? "yes" : "no",
1562 file_strict ? "true" : "false");
1563
1564 asciichat_error_t load_result = config_load_and_apply(detected_mode, file->path, file_strict, opts);
1565
1566 if (load_result != ASCIICHAT_OK) {
1567 if (file_strict) {
1568 // Strict mode: errors are fatal
1569 CONFIG_DEBUG("Strict config loading failed for %s", file->path);
1570 result = load_result;
1571 } else {
1572 // Non-strict mode: errors are non-fatal, just log and continue
1573 CONFIG_DEBUG("Non-strict config loading warning for %s: %d (continuing)", file->path, load_result);
1574 CLEAR_ERRNO(); // Clear error context for next file
1575 }
1576 }
1577 }
1578
1579 config_file_list_destroy(&config_files);
1580 return result;
1581}
asciichat_error_t config_load_and_apply(asciichat_mode_t detected_mode, const char *config_path, bool strict, options_t *opts)
Main function to load configuration from file and apply to global options.
Definition config.c:1044
asciichat_error_t platform_find_config_file(const char *filename, config_file_list_t *list_out)
void config_file_list_destroy(config_file_list_t *list)

References CONFIG_DEBUG, config_file_list_destroy(), config_load_and_apply(), and platform_find_config_file().

Referenced by options_init().