/** * @file config.c * @brief Simple key=value configuration file parser implementation. */ #include "config.h" #include #include #include #include /* Enough for typical config files; doubles on overflow. */ #define CONFIG_INITIAL_CAPACITY 16 /* Sufficient for file paths and URIs. */ #define CONFIG_LINE_MAX 1024 /* ======================================================================== * Static Helpers * ======================================================================== */ /** * @brief Trims leading and trailing whitespace in place. * * Modifies the string by writing a NUL terminator after the last * non-whitespace character and returns a pointer past any leading * whitespace. */ static char * _s_trim (char *s) { while (*s == ' ' || *s == '\t') s++; size_t len = strlen (s); while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t')) len--; s[len] = '\0'; return s; } /** * @brief Appends a key-value pair to the config's dynamic array. * * Grows the array by doubling capacity when full. * * @return 0 on success, -1 on allocation failure. */ static int _s_config_append (config *cfg, const char *key, const char *value) { if (cfg->count == cfg->capacity) { size_t new_cap = cfg->capacity * 2; config_entry *tmp = realloc (cfg->entries, new_cap * sizeof (config_entry)); if (!tmp) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Config: out of memory"); return -1; } cfg->entries = tmp; cfg->capacity = new_cap; } cfg->entries[cfg->count].key = strdup (key); cfg->entries[cfg->count].value = strdup (value); if (!cfg->entries[cfg->count].key || !cfg->entries[cfg->count].value) { free (cfg->entries[cfg->count].key); free (cfg->entries[cfg->count].value); UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Config: out of memory"); return -1; } cfg->count++; return 0; } /* ======================================================================== * Public API * ======================================================================== */ int config_load (const char *path, config *cfg) { memset (cfg, 0, sizeof (config)); cfg->entries = malloc (CONFIG_INITIAL_CAPACITY * sizeof (config_entry)); if (!cfg->entries) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Config: out of memory"); return -1; } cfg->capacity = CONFIG_INITIAL_CAPACITY; FILE *fp = fopen (path, "r"); if (!fp) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Config: cannot open '%s'", path); free (cfg->entries); memset (cfg, 0, sizeof (config)); return -1; } char line[CONFIG_LINE_MAX]; int line_num = 0; while (fgets (line, sizeof (line), fp)) { line_num++; /* Strip trailing newline / carriage return. */ size_t len = strlen (line); while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; /* Skip blank lines and comments. */ char *trimmed = _s_trim (line); if (*trimmed == '\0' || *trimmed == '#') continue; /* Find the '=' separator. */ char *eq = strchr (trimmed, '='); if (!eq) { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Config: syntax error at %s:%d " "(missing '=')", path, line_num); fclose (fp); config_free (cfg); return -1; } *eq = '\0'; char *key = _s_trim (trimmed); char *value = _s_trim (eq + 1); if (*key == '\0') { UA_LOG_ERROR (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Config: empty key at %s:%d", path, line_num); fclose (fp); config_free (cfg); return -1; } if (_s_config_append (cfg, key, value) != 0) { fclose (fp); config_free (cfg); return -1; } } fclose (fp); return 0; } const char * config_get (const config *cfg, const char *key) { for (size_t i = 0; i < cfg->count; i++) { if (strcmp (cfg->entries[i].key, key) == 0) return cfg->entries[i].value; } return NULL; } const char * config_require (const config *cfg, const char *key, const char *program) { const char *val = config_get (cfg, key); if (!val) UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "%s: missing required config key '%s'", program, key); return val; } int config_require_int (const config *cfg, const char *key, const char *program) { const char *val = config_require (cfg, key, program); if (!val) return -1; char *endptr; long num = strtol (val, &endptr, 10); if (*endptr != '\0') { UA_LOG_FATAL (UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "%s: config key '%s' is not a valid integer: '%s'", program, key, val); return -1; } return (int)num; } void config_free (config *cfg) { for (size_t i = 0; i < cfg->count; i++) { free (cfg->entries[i].key); free (cfg->entries[i].value); } free (cfg->entries); memset (cfg, 0, sizeof (config)); }