From 34386b635e60e7120a27349d712024f10443913c Mon Sep 17 00:00:00 2001 From: mikael-lovqvists-claude-agent Date: Thu, 26 Mar 2026 22:42:10 +0000 Subject: [PATCH] Rewrite config storage: typed union instead of raw strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Config_Entry now holds a union {s, u16, u32, flags} typed at parse time. Getters read directly from the union — no string conversion at access time. config_dump reconstructs flag display as 'token | token' from the bitmask. Separators in flag values: comma, pipe, and whitespace all accepted. Co-Authored-By: Claude Sonnet 4.6 --- src/modules/config/config.c | 179 ++++++++++++++++++++---------------- 1 file changed, 100 insertions(+), 79 deletions(-) diff --git a/src/modules/config/config.c b/src/modules/config/config.c index 00cc76c..16eaa2b 100644 --- a/src/modules/config/config.c +++ b/src/modules/config/config.c @@ -6,14 +6,20 @@ #include "config.h" -#define MAX_ENTRIES 256 -#define MAX_LINE 512 -#define MAX_STR 256 +#define MAX_ENTRIES 256 +#define MAX_LINE 512 +#define MAX_STR 256 struct Config_Entry { - char section[MAX_STR]; - char key[MAX_STR]; - char value[MAX_STR]; + char section[MAX_STR]; + char key[MAX_STR]; + Config_Type type; + union { + char s[MAX_STR]; + uint16_t u16; + uint32_t u32; + uint32_t flags; + } val; }; struct Config { @@ -25,36 +31,68 @@ struct Config { /* -- string helpers -------------------------------------------------------- */ static void strip(char *s) { - /* strip leading whitespace */ char *start = s; while (*start && isspace((unsigned char)*start)) { start++; } - - /* strip trailing whitespace */ char *end = start + strlen(start); while (end > start && isspace((unsigned char)*(end - 1))) { end--; } *end = '\0'; - if (start != s) { memmove(s, start, (size_t)(end - start) + 1); } } -/* strip inline comment (# or ;) that appears outside a value */ static void strip_comment(char *s) { for (char *p = s; *p; p++) { - if (*p == '#' || *p == ';') { - *p = '\0'; - break; - } + if (*p == '#' || *p == ';') { *p = '\0'; break; } } } -/* replace commas with spaces so flag lists parse uniformly */ -static void commas_to_spaces(char *s) { +/* normalise flag separators: comma, pipe, and whitespace all become space */ +static void normalise_separators(char *s) { for (char *p = s; *p; p++) { - if (*p == ',') { *p = ' '; } + if (*p == ',' || *p == '|') { *p = ' '; } } } -/* -- schema lookup --------------------------------------------------------- */ +/* -- parse typed value ----------------------------------------------------- */ + +static uint32_t parse_flags(const char *raw, const struct Config_Flag_Def *defs) { + char buf[MAX_STR]; + strncpy(buf, raw, MAX_STR - 1); + buf[MAX_STR - 1] = '\0'; + normalise_separators(buf); + + uint32_t bits = 0; + char *tok = strtok(buf, " \t"); + while (tok) { + for (const struct Config_Flag_Def *fd = defs; fd && fd->token; fd++) { + if (strcmp(tok, fd->token) == 0) { bits |= fd->value; break; } + } + tok = strtok(NULL, " \t"); + } + return bits; +} + +static void set_entry_value(struct Config_Entry *e, + const struct Config_Def *def, const char *raw) +{ + e->type = def->type; + switch (def->type) { + case CONFIG_STRING: + strncpy(e->val.s, raw, MAX_STR - 1); + e->val.s[MAX_STR - 1] = '\0'; + break; + case CONFIG_UINT16: + e->val.u16 = (uint16_t)strtoul(raw, NULL, 10); + break; + case CONFIG_UINT32: + e->val.u32 = (uint32_t)strtoul(raw, NULL, 10); + break; + case CONFIG_FLAGS: + e->val.flags = parse_flags(raw, def->flags); + break; + } +} + +/* -- entry table ----------------------------------------------------------- */ static const struct Config_Def *find_def(const struct Config_Def *schema, const char *section, const char *key) @@ -67,8 +105,6 @@ static const struct Config_Def *find_def(const struct Config_Def *schema, return NULL; } -/* -- entry lookup ---------------------------------------------------------- */ - static struct Config_Entry *find_entry(struct Config *cfg, const char *section, const char *key) { @@ -82,16 +118,13 @@ static struct Config_Entry *find_entry(struct Config *cfg, } static struct Config_Entry *add_entry(struct Config *cfg, - const char *section, const char *key, const char *value) + const char *section, const char *key) { if (cfg->count >= MAX_ENTRIES) { return NULL; } struct Config_Entry *e = &cfg->entries[cfg->count++]; + memset(e, 0, sizeof(*e)); strncpy(e->section, section, MAX_STR - 1); strncpy(e->key, key, MAX_STR - 1); - strncpy(e->value, value, MAX_STR - 1); - e->section[MAX_STR - 1] = '\0'; - e->key [MAX_STR - 1] = '\0'; - e->value [MAX_STR - 1] = '\0'; return e; } @@ -99,14 +132,14 @@ static struct Config_Entry *add_entry(struct Config *cfg, static void fill_defaults(struct Config *cfg) { for (const struct Config_Def *d = cfg->schema; d->section; d++) { - if (!find_entry(cfg, d->section, d->key)) { - add_entry(cfg, d->section, d->key, - d->default_val ? d->default_val : ""); - } + if (find_entry(cfg, d->section, d->key)) { continue; } + struct Config_Entry *e = add_entry(cfg, d->section, d->key); + if (!e) { continue; } + set_entry_value(e, d, d->default_val ? d->default_val : ""); } } -/* -- parse ----------------------------------------------------------------- */ +/* -- file parser ----------------------------------------------------------- */ static struct App_Error parse_file(struct Config *cfg, const char *path) { FILE *f = fopen(path, "r"); @@ -118,11 +151,9 @@ static struct App_Error parse_file(struct Config *cfg, const char *path) { while (fgets(line, sizeof(line), f)) { strip_comment(line); strip(line); - if (line[0] == '\0') { continue; } if (line[0] == '[') { - /* section header */ char *end = strchr(line, ']'); if (!end) { continue; } *end = '\0'; @@ -134,31 +165,20 @@ static struct App_Error parse_file(struct Config *cfg, const char *path) { char *eq = strchr(line, '='); if (!eq || section[0] == '\0') { continue; } - *eq = '\0'; char *key = line; char *val = eq + 1; strip(key); strip(val); - if (key[0] == '\0') { continue; } - /* only store keys that appear in the schema */ const struct Config_Def *def = find_def(cfg->schema, section, key); if (!def) { continue; } - /* normalise flag values: commas → spaces */ - char normalised[MAX_STR]; - strncpy(normalised, val, MAX_STR - 1); - normalised[MAX_STR - 1] = '\0'; - if (def->type == CONFIG_FLAGS) { commas_to_spaces(normalised); } - struct Config_Entry *e = find_entry(cfg, section, key); - if (e) { - strncpy(e->value, normalised, MAX_STR - 1); - } else { - add_entry(cfg, section, key, normalised); - } + if (!e) { e = add_entry(cfg, section, key); } + if (!e) { continue; } + set_entry_value(e, def, val); } fclose(f); @@ -175,10 +195,7 @@ struct App_Error config_load(struct Config **out, const char *path, cfg->schema = schema; struct App_Error err = parse_file(cfg, path); - if (!APP_IS_OK(err)) { - free(cfg); - return err; - } + if (!APP_IS_OK(err)) { free(cfg); return err; } fill_defaults(cfg); *out = cfg; @@ -204,51 +221,28 @@ const char *config_get_str(struct Config *cfg, const char *section, const char *key) { struct Config_Entry *e = find_entry(cfg, section, key); - return e ? e->value : ""; + return (e && e->type == CONFIG_STRING) ? e->val.s : ""; } uint16_t config_get_u16(struct Config *cfg, const char *section, const char *key) { struct Config_Entry *e = find_entry(cfg, section, key); - if (!e || e->value[0] == '\0') { return 0; } - return (uint16_t)strtoul(e->value, NULL, 10); + return (e && e->type == CONFIG_UINT16) ? e->val.u16 : 0; } uint32_t config_get_u32(struct Config *cfg, const char *section, const char *key) { struct Config_Entry *e = find_entry(cfg, section, key); - if (!e || e->value[0] == '\0') { return 0; } - return (uint32_t)strtoul(e->value, NULL, 10); + return (e && e->type == CONFIG_UINT32) ? e->val.u32 : 0; } uint32_t config_get_flags(struct Config *cfg, const char *section, const char *key) { struct Config_Entry *e = find_entry(cfg, section, key); - if (!e || e->value[0] == '\0') { return 0; } - - const struct Config_Def *def = find_def(cfg->schema, section, key); - if (!def || !def->flags) { return 0; } - - uint32_t result = 0; - char buf[MAX_STR]; - strncpy(buf, e->value, MAX_STR - 1); - buf[MAX_STR - 1] = '\0'; - - char *token = strtok(buf, " \t"); - while (token) { - for (const struct Config_Flag_Def *fd = def->flags; fd->token; fd++) { - if (strcmp(token, fd->token) == 0) { - result |= fd->value; - break; - } - } - token = strtok(NULL, " \t"); - } - - return result; + return (e && e->type == CONFIG_FLAGS) ? e->val.flags : 0; } void config_dump(struct Config *cfg) { @@ -259,6 +253,33 @@ void config_dump(struct Config *cfg) { printf("[%s]\n", e->section); cur_section = e->section; } - printf(" %-24s = %s\n", e->key, e->value); + + switch (e->type) { + case CONFIG_STRING: + printf(" %-24s = %s\n", e->key, e->val.s); + break; + case CONFIG_UINT16: + printf(" %-24s = %u\n", e->key, (unsigned)e->val.u16); + break; + case CONFIG_UINT32: + printf(" %-24s = %u\n", e->key, e->val.u32); + break; + case CONFIG_FLAGS: { + const struct Config_Def *def = find_def(cfg->schema, e->section, e->key); + char display[MAX_STR] = ""; + if (def && def->flags) { + for (const struct Config_Flag_Def *fd = def->flags; fd->token; fd++) { + if (e->val.flags & fd->value) { + if (display[0] != '\0') { + strncat(display, " | ", sizeof(display) - strlen(display) - 1); + } + strncat(display, fd->token, sizeof(display) - strlen(display) - 1); + } + } + } + printf(" %-24s = %s\n", e->key, display[0] ? display : "(none)"); + break; + } + } } }