1250 {
1251 char *config_path_expanded = NULL;
1252
1253 defer(SAFE_FREE(config_path_expanded));
1254
1255
1257
1258
1259 const size_t BUFFER_CAPACITY = 256 * 1024;
1261 builder.
buffer = SAFE_MALLOC(BUFFER_CAPACITY,
char *);
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
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
1303 size_t metadata_count = 0;
1305
1306
1307 const char *categories[16] = {0};
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
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
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
1338 size_t cat_option_count = 0;
1340
1341 if (!cat_options || cat_option_count == 0) {
1342 continue;
1343 }
1344
1345
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
1351 bool written_flags[64] = {0};
1352
1353
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
1361 if (written_flags[opt_idx]) {
1362 continue;
1363 }
1364
1365
1366
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
1380 const char *field_ptr = ((const char *)&defaults) + meta->field_offset;
1381
1382
1383 char mode_default_buffer[OPTIONS_BUFF_SIZE] = {0};
1384 int mode_default_int = 0;
1385
1386
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
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
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
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;
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
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
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
1451 if (config_path && strlen(config_path) > 0) {
1452
1453
1454
1456 if (!config_path_expanded) {
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 =
1467 if (validate_result != ASCIICHAT_OK) {
1468 SAFE_FREE(validated_config_path);
1469 SAFE_FREE(config_path_expanded);
1470 return validate_result;
1471 }
1472
1473 if (config_path_expanded != validated_config_path) {
1474 SAFE_FREE(config_path_expanded);
1475 }
1476 config_path_expanded = validated_config_path;
1477
1478
1479 struct stat st;
1480 if (stat(config_path_expanded, &st) == 0) {
1481
1482 log_plain("Config file already exists: %s", config_path_expanded);
1483
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
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
1501 char *last_sep = strrchr(dir_path, PATH_DELIM);
1502
1503 if (last_sep) {
1504 *last_sep = '\0';
1505
1507 if (mkdir_result != ASCIICHAT_OK) {
1508 return mkdir_result;
1509 }
1510 }
1511
1512
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
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
1527
1528 (void)fflush(stdout);
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.
options_t options_t_new(void)
asciichat_error_t path_validate_user_path(const char *input, path_role_t role, char **normalized_out)
char * expand_path(const char *path)
const config_option_metadata_t ** config_schema_get_by_category(const char *category, size_t *count)
const config_option_metadata_t * config_schema_get_all(size_t *count)
Helper structure for building config content in a buffer.
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.
bool platform_prompt_yes_no(const char *question, bool default_yes)
FILE * platform_fopen(const char *filename, const char *mode)
asciichat_error_t platform_mkdir_recursive(const char *path, int mode)