#include "ini-config.h" const char* INI_ConfigGetCommandlineOption(uint8_t argc, const char* argv[], const char* val) { size_t n = strlen(val); int c = argc; while (--c > 0) { if (!strncmp(argv[c], val, n)) { if (!*(argv[c] + n) && c < argc - 1) { /* coverity treats unchecked argv as "tainted" */ if (!argv[c + 1] || strlen(argv[c + 1]) > 1024) return NULL; return argv[c + 1]; } if (argv[c][n] == '=') return &argv[c][n + 1]; return argv[c] + n; } } return NULL; } #define INI_CONFIG_REGISTER_INT_TYPE(ID, T) \ typedef bool INI_ConfigValidate##ID(T); \ \ typedef T INI_ConfigDeserialize##ID(const char*); \ \ typedef void INI_ConfigSerialize##ID(T, const char[128]); \ \ void INI_ConfigEnsureValid##ID(INI_ConfigItem* item, T raw_value, T default_value) { \ T* dest = item->dest; \ if (item->validator) { \ INI_ConfigValidate##ID* validate = item->validator; \ if (validate(raw_value)) { \ *dest = raw_value; \ return; \ } \ *dest = default_value; \ return; \ } \ *dest = raw_value; \ } \ \ void INI_ConfigLoad##ID(INI_ConfigItem* item, const char* config_path) { \ static T raw_value; \ static T default_value; \ default_value = *((T*) item->default_value); \ if (item->transformer.deserialize && item->transformer.serialize) { \ INI_ConfigDeserialize##ID* deserialize = item->transformer.deserialize; \ INI_ConfigSerialize##ID* serialize = item->transformer.serialize; \ const char serialized_default_value[128]; \ if (default_value) { \ serialize(default_value, serialized_default_value); \ } \ char buffer[128]; \ ini_gets(item->section, item->key, serialized_default_value, buffer, 128, config_path); \ raw_value = deserialize(buffer); \ } else { \ raw_value = ini_getl(item->section, item->key, default_value, config_path); \ } \ INI_ConfigEnsureValid##ID(item, raw_value, default_value); \ } \ \ INI_ConfigSaveItemResult INI_ConfigSave##ID(INI_ConfigItem* item, const char* config_path) { \ T dest = *((T*) item->dest); \ if (item->validator) { \ INI_ConfigValidate##ID* validate = item->validator; \ if (!validate(dest)) { \ dest = *((const T*) item->default_value); \ } \ } \ \ if (item->transformer.deserialize && item->transformer.serialize) { \ INI_ConfigSerialize##ID* serialize = item->transformer.serialize; \ const char serialized_value[128]; \ serialize(dest, serialized_value); \ if (!ini_puts(item->section, item->key, serialized_value, config_path)) { \ return -1; \ } \ return 0; \ } \ \ if (!ini_putl(item->section, item->key, dest, config_path)) { \ return -1; \ } \ \ return 0; \ } \ \ void INI_ConfigOverride##ID(INI_ConfigItem* item, uint8_t argc, const char* argv[]) { \ if (!item->cmdline_option) { \ return; \ } \ const char* cmdline_buffer; \ char* rest_of_string; \ static T dest; \ static T config_value; \ config_value = *((T*) item->dest); \ if ((cmdline_buffer = INI_ConfigGetCommandlineOption(argc, argv, item->cmdline_option))) { \ dest = strtol(cmdline_buffer, &rest_of_string, 10); \ if (strcmp(cmdline_buffer, rest_of_string) != 0) { \ INI_ConfigEnsureValid##ID(item, dest, config_value); \ return; \ } \ } \ } INI_CONFIG_REGISTER_INT_TYPE(U8, uint8_t); INI_CONFIG_REGISTER_INT_TYPE(U16, uint16_t); INI_CONFIG_REGISTER_INT_TYPE(U32, uint32_t); INI_CONFIG_REGISTER_INT_TYPE(I8, int8_t); INI_CONFIG_REGISTER_INT_TYPE(I16, int16_t); INI_CONFIG_REGISTER_INT_TYPE(I32, int32_t); typedef bool INI_ConfigLoadParamsStringValidator(const char*); void INI_ConfigEnsureValidString(INI_ConfigItem* item, const char* buffer) { if (item->validator) { INI_ConfigLoadParamsStringValidator* validator = item->validator; if (validator(buffer)) { memcpy(item->dest, buffer, item->type.size); return; } memcpy(item->dest, item->default_value, item->type.size); return; } memcpy(item->dest, buffer, item->type.size); } void INI_ConfigLoadString(INI_ConfigItem* item, const char* config_path) { char buffer[item->type.size]; ini_gets(item->section, item->key, item->default_value, buffer, (int32_t) item->type.size, config_path); INI_ConfigEnsureValidString(item, buffer); } INI_ConfigSaveItemResult INI_ConfigSaveString(INI_ConfigItem* item, const char* config_path) { const char* dest = (const char*) item->dest; if (item->validator) { INI_ConfigLoadParamsStringValidator* validator = item->validator; if (!validator(dest)) { dest = (const char*) item->default_value; } } if (!ini_puts(item->section, item->key, dest, config_path)) { return INI_CONFIG_SAVE_ITEM_ERROR; } return INI_CONFIG_SAVE_ITEM_OK; } void INI_ConfigOverrideString(INI_ConfigItem* item, uint8_t argc, const char* argv[]) { if (!item->cmdline_option) { return; } const char* cmdline_buffer; if ((cmdline_buffer = INI_ConfigGetCommandlineOption(argc, argv, item->cmdline_option))) { INI_ConfigEnsureValidString(item, cmdline_buffer); } } void INI_ConfigLoad(INI_ConfigItem item[], const char* config_path) { uint8_t i; for (i = 0; item[i].type.size > 0; i += 1) { if (!item[i].type.load) { continue; } item[i].type.load(&item[i], config_path); } } INI_ConfigSaveResult INI_ConfigSave(INI_ConfigItem item[], const char* config_path) { uint8_t i; int32_t problems = 0; for (i = 0; item[i].type.size > 0; i += 1) { int32_t result = item[i].type.save ? item[i].type.save(&item[i], config_path) : 0; if (result < 0) { problems |= (1 << (int32_t) i); } } return -problems; } void INI_ConfigOverride(INI_ConfigItem item[], uint8_t argc, const char* argv[]) { uint8_t i; for (i = 0; item[i].type.size > 0; i += 1) { if (!item[i].type.override) { continue; } item[i].type.override(&item[i], argc, argv); // TODO specify command-line arg external from config item? } } INI_ConfigInitializeResult INI_ConfigInitialize(INI_ConfigItem item[], const char* config_path, uint8_t argc, const char* argv[]) { INI_ConfigLoad(item, config_path); INI_ConfigSaveResult save_result = INI_ConfigSave(item, config_path); if (save_result < 0) { //INI_LogError("config", "Sync failed! Result: %u", save_result); return INI_CONFIG_INITIALIZE_RESULT_ERROR; } INI_ConfigOverride(item, argc, argv); if (save_result > 0) { //INI_LogWarn(false, "config", "Sync encountered issues. Result: %u", save_result); return INI_CONFIG_INITIALIZE_RESULT_WARNING; } //INI_LogInfo(INI_LOG_CATEGORY_GLOBAL, "config", "Sync successful."); return INI_CONFIG_INITIALIZE_RESULT_OK; }