diff options
| author | info@mode42.com <info@mode42.com> | 2026-08-08 03:54:55 +0000 |
|---|---|---|
| committer | info@mode42.com <info@mode42.com> | 2026-08-08 03:54:55 +0000 |
| commit | 20cb29c2f8c5c87bc590896854a20b1473ceb358 (patch) | |
| tree | 2857f41513a56ad41af97b57362639298aa7f033 /src/core/config.c | |
#2
Diffstat (limited to 'src/core/config.c')
| -rw-r--r-- | src/core/config.c | 862 |
1 files changed, 862 insertions, 0 deletions
diff --git a/src/core/config.c b/src/core/config.c new file mode 100644 index 0000000..9b84d97 --- /dev/null +++ b/src/core/config.c @@ -0,0 +1,862 @@ +#include "hybbx/config.h" +#include "hybbx/limits.h" +#include "hybbx/util.h" + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define HYBBX_CONFIG_INITIAL_CAP 32 + +static char *hybbx_strdup(const char *s) +{ + size_t len; + char *copy; + + if (s == NULL) { + return NULL; + } + + len = strlen(s) + 1; + copy = malloc(len); + if (copy != NULL) { + memcpy(copy, s, len); + } + return copy; +} + +static char *trim_inplace(char *s) +{ + char *end; + + if (s == NULL) { + return NULL; + } + + while (*s != '\0' && isspace((unsigned char)*s)) { + s++; + } + + if (*s == '\0') { + return s; + } + + end = s + strlen(s) - 1; + while (end > s && isspace((unsigned char)*end)) { + *end = '\0'; + end--; + } + + return s; +} + +static void strip_inline_comment_inplace(char *value) +{ + size_t i; + int in_single = 0; + int in_double = 0; + + if (value == NULL) { + return; + } + + for (i = 0; value[i] != '\0'; i++) { + char c = value[i]; + + if (!in_single && !in_double && (c == ';' || c == '#')) { + value[i] = '\0'; + break; + } + + if (!in_double && c == '\'') { + in_single = !in_single; + } else if (!in_single && c == '"') { + in_double = !in_double; + } + } + + { + char *end = value + strlen(value); + + while (end > value && isspace((unsigned char)*(end - 1))) { + *--end = '\0'; + } + } +} + +static int is_comment_line(const char *line) +{ + const char *p = line; + + while (*p != '\0' && isspace((unsigned char)*p)) { + p++; + } + + return *p == ';' || *p == '#'; +} + +static hybbx_result_t append_entry(hybbx_config_t *config, + const char *section, + const char *key, + const char *value) +{ + hybbx_config_entry_t *entry; + hybbx_config_entry_t *grown; + + if (section == NULL || key == NULL || value == NULL) { + return HYBBX_ERR_INVALID; + } + + if (strlen(section) >= HYBBX_CONFIG_SECTION_MAX || + strlen(key) >= HYBBX_CONFIG_KEY_MAX || + strlen(value) >= HYBBX_CONFIG_VALUE_MAX) { + return HYBBX_ERR_INVALID; + } + + if (config->count == 0) { + config->entries = malloc(HYBBX_CONFIG_INITIAL_CAP * sizeof(*config->entries)); + if (config->entries == NULL) { + return HYBBX_ERR_NOMEM; + } + } else if ((config->count % HYBBX_CONFIG_INITIAL_CAP) == 0) { + grown = realloc(config->entries, + (config->count + HYBBX_CONFIG_INITIAL_CAP) * + sizeof(*config->entries)); + if (grown == NULL) { + return HYBBX_ERR_NOMEM; + } + config->entries = grown; + } + + entry = &config->entries[config->count]; + entry->section = hybbx_strdup(section); + entry->key = hybbx_strdup(key); + entry->value = hybbx_strdup(value); + + if (entry->section == NULL || entry->key == NULL || entry->value == NULL) { + free(entry->section); + free(entry->key); + free(entry->value); + return HYBBX_ERR_NOMEM; + } + + config->count++; + return HYBBX_OK; +} + +hybbx_result_t hybbx_config_load(hybbx_config_t *config, const char *path) +{ + FILE *fp; + char line[HYBBX_CONFIG_LINE_MAX]; + char current_section[HYBBX_CONFIG_SECTION_MAX] = ""; + char *eq; + char *key; + char *value; + + if (config == NULL || path == NULL) { + return HYBBX_ERR_INVALID; + } + + memset(config, 0, sizeof(*config)); + + fp = fopen(path, "r"); + if (fp == NULL) { + return HYBBX_ERR_IO; + } + + while (fgets(line, sizeof(line), fp) != NULL) { + char *content; + size_t len; + + len = strlen(line); + if (len > 0 && line[len - 1] != '\n' && + len >= sizeof(line) - 1) { + fclose(fp); + hybbx_config_free(config); + return HYBBX_ERR_INVALID; + } + + content = trim_inplace(line); + + if (content[0] == '\0' || is_comment_line(content)) { + continue; + } + + if (content[0] == '[') { + char *closing = strchr(content, ']'); + + if (closing == NULL) { + fclose(fp); + hybbx_config_free(config); + return HYBBX_ERR_INVALID; + } + + *closing = '\0'; + strncpy(current_section, content + 1, sizeof(current_section) - 1); + current_section[sizeof(current_section) - 1] = '\0'; + trim_inplace(current_section); + continue; + } + + if (current_section[0] == '\0') { + fclose(fp); + hybbx_config_free(config); + return HYBBX_ERR_INVALID; + } + + eq = strchr(content, '='); + if (eq == NULL) { + fclose(fp); + hybbx_config_free(config); + return HYBBX_ERR_INVALID; + } + + *eq = '\0'; + key = trim_inplace(content); + value = trim_inplace(eq + 1); + strip_inline_comment_inplace(value); + + len = strlen(value); + if (len >= 2 && + ((value[0] == '"' && value[len - 1] == '"') || + (value[0] == '\'' && value[len - 1] == '\''))) { + value[len - 1] = '\0'; + value++; + } + + if (append_entry(config, current_section, key, value) != HYBBX_OK) { + fclose(fp); + hybbx_config_free(config); + return HYBBX_ERR_NOMEM; + } + } + + fclose(fp); + return HYBBX_OK; +} + +void hybbx_config_free(hybbx_config_t *config) +{ + size_t i; + + if (config == NULL) { + return; + } + + for (i = 0; i < config->count; i++) { + free(config->entries[i].section); + free(config->entries[i].key); + free(config->entries[i].value); + } + + free(config->entries); + config->entries = NULL; + config->count = 0; +} + +const char *hybbx_config_get(const hybbx_config_t *config, + const char *section, + const char *key, + const char *default_value) +{ + size_t i; + + if (config == NULL || section == NULL || key == NULL) { + return default_value; + } + + for (i = 0; i < config->count; i++) { + if (strcmp(config->entries[i].section, section) == 0 && + strcmp(config->entries[i].key, key) == 0) { + return config->entries[i].value; + } + } + + return default_value; +} + +int hybbx_config_get_bool(const hybbx_config_t *config, + const char *section, + const char *key, + int default_value) +{ + const char *value = hybbx_config_get(config, section, key, NULL); + + if (value == NULL) { + return default_value; + } + + return hybbx_parse_bool(value, default_value); +} + +unsigned hybbx_config_get_uint(const hybbx_config_t *config, + const char *section, + const char *key, + unsigned default_value, + unsigned min_value, + unsigned max_value) +{ + const char *value; + char *end; + unsigned long parsed; + + value = hybbx_config_get(config, section, key, NULL); + if (value == NULL || value[0] == '\0') { + return default_value; + } + + parsed = strtoul(value, &end, 10); + if (end == value || *end != '\0') { + return default_value; + } + + if (parsed < min_value) { + return min_value; + } + if (parsed > max_value) { + return max_value; + } + + return (unsigned)parsed; +} + +static int config_section_has_keys(const hybbx_config_t *config, + const char *section) +{ + size_t i; + + if (config == NULL || section == NULL) { + return 0; + } + + for (i = 0; i < config->count; i++) { + const hybbx_config_entry_t *entry = &config->entries[i]; + + if (strcmp(entry->section, section) != 0) { + continue; + } + if (strcmp(entry->key, "enabled") == 0) { + continue; + } + return 1; + } + + return 0; +} + +static int config_suffix_is_digits(const char *suffix) +{ + if (suffix == NULL || suffix[0] == '\0') { + return 0; + } + + while (*suffix != '\0') { + if (!isdigit((unsigned char)*suffix)) { + return 0; + } + suffix++; + } + + return 1; +} + +int hybbx_config_resolve_transport_section(const hybbx_config_t *config, + const char *plugin_name, + char *out_section, + size_t out_size) +{ + char prefix[128]; + size_t prefix_len; + size_t i; + + if (config == NULL || plugin_name == NULL || out_section == NULL || + out_size == 0) { + return 0; + } + + snprintf(prefix, sizeof(prefix), "transport.%s", plugin_name); + if (config_section_has_keys(config, prefix)) { + hybbx_strlcpy(out_section, prefix, out_size); + return 1; + } + + prefix_len = strlen(prefix); + for (i = 0; i < config->count; i++) { + const char *sec = config->entries[i].section; + const char *suffix; + + if (sec == NULL || strncmp(sec, prefix, prefix_len) != 0) { + continue; + } + + suffix = sec + prefix_len; + if (!config_suffix_is_digits(suffix)) { + continue; + } + if (!config_section_has_keys(config, sec)) { + continue; + } + + hybbx_strlcpy(out_section, sec, out_size); + return 1; + } + + hybbx_strlcpy(out_section, prefix, out_size); + return 0; +} + +char *hybbx_config_format_section(const hybbx_config_t *config, + const char *section) +{ + size_t i; + size_t cap = 64; + size_t len = 0; + char *out; + int first = 1; + + if (config == NULL || section == NULL) { + return NULL; + } + + out = malloc(cap); + if (out == NULL) { + return NULL; + } + out[0] = '\0'; + + for (i = 0; i < config->count; i++) { + const hybbx_config_entry_t *entry = &config->entries[i]; + size_t need; + + if (strcmp(entry->section, section) != 0) { + continue; + } + + if (strcmp(entry->key, "enabled") == 0) { + continue; + } + + need = strlen(entry->key) + strlen(entry->value) + 2; + if (!first) { + need++; + } + + if (len + need + 1 > cap) { + char *grown; + + while (len + need + 1 > cap) { + cap *= 2; + } + grown = realloc(out, cap); + if (grown == NULL) { + free(out); + return NULL; + } + out = grown; + } + + if (!first) { + out[len++] = ';'; + out[len] = '\0'; + } + + len += (size_t)snprintf(out + len, cap - len, "%s=%s", + entry->key, entry->value); + first = 0; + } + + return out; +} + +typedef struct format_transport_ctx { + const hybbx_config_t *config; + const char *prefix; + size_t prefix_len; + char **sections; + size_t count; + size_t cap; +} format_transport_ctx_t; + +static int format_transport_section_seen(format_transport_ctx_t *ctx, + const char *section) +{ + size_t i; + + for (i = 0; i < ctx->count; i++) { + if (strcmp(ctx->sections[i], section) == 0) { + return 1; + } + } + return 0; +} + +static hybbx_result_t format_transport_section_add(format_transport_ctx_t *ctx, + const char *section) +{ + char *copy; + + if (format_transport_section_seen(ctx, section)) { + return HYBBX_OK; + } + + if (ctx->count >= ctx->cap) { + size_t new_cap = ctx->cap == 0 ? 4 : ctx->cap * 2; + char **grown = realloc(ctx->sections, new_cap * sizeof(*grown)); + + if (grown == NULL) { + return HYBBX_ERR_NOMEM; + } + ctx->sections = grown; + ctx->cap = new_cap; + } + + copy = hybbx_strdup(section); + if (copy == NULL) { + return HYBBX_ERR_NOMEM; + } + + ctx->sections[ctx->count++] = copy; + return HYBBX_OK; +} + +char *hybbx_config_format_transport_sections(const hybbx_config_t *config, + const char *plugin_name) +{ + format_transport_ctx_t ctx; + char prefix[128]; + size_t i; + size_t len = 0; + size_t cap = 64; + char *out; + hybbx_result_t rc; + + if (config == NULL || plugin_name == NULL) { + return NULL; + } + + memset(&ctx, 0, sizeof(ctx)); + ctx.config = config; + snprintf(prefix, sizeof(prefix), "transport.%s", plugin_name); + ctx.prefix = prefix; + ctx.prefix_len = strlen(prefix); + + if (config_section_has_keys(config, prefix)) { + rc = format_transport_section_add(&ctx, prefix); + if (rc != HYBBX_OK) { + goto fail; + } + } + + for (i = 0; i < config->count; i++) { + const char *sec = config->entries[i].section; + const char *suffix; + + if (sec == NULL || strncmp(sec, prefix, ctx.prefix_len) != 0) { + continue; + } + + suffix = sec + ctx.prefix_len; + if (!config_suffix_is_digits(suffix)) { + continue; + } + if (!config_section_has_keys(config, sec)) { + continue; + } + + rc = format_transport_section_add(&ctx, sec); + if (rc != HYBBX_OK) { + goto fail; + } + } + + if (ctx.count == 0) { + for (i = 0; i < ctx.count; i++) { + free(ctx.sections[i]); + } + free(ctx.sections); + return hybbx_config_format_section(config, prefix); + } + + out = malloc(cap); + if (out == NULL) { + rc = HYBBX_ERR_NOMEM; + goto fail; + } + out[0] = '\0'; + + for (i = 0; i < ctx.count; i++) { + char *section_cfg = hybbx_config_format_section(config, ctx.sections[i]); + size_t part_len; + + if (section_cfg == NULL) { + free(out); + rc = HYBBX_ERR_NOMEM; + goto fail; + } + + part_len = strlen(section_cfg); + if (len > 0) { + if (len + 1 + part_len + 1 > cap) { + while (len + 1 + part_len + 1 > cap) { + cap *= 2; + } + { + char *grown = realloc(out, cap); + + if (grown == NULL) { + free(section_cfg); + free(out); + rc = HYBBX_ERR_NOMEM; + goto fail; + } + out = grown; + } + } + out[len++] = HYBBX_PACKET_RADIO_INSTANCE_SEP; + out[len] = '\0'; + } + + if (len + part_len + 1 > cap) { + while (len + part_len + 1 > cap) { + cap *= 2; + } + { + char *grown = realloc(out, cap); + + if (grown == NULL) { + free(section_cfg); + free(out); + rc = HYBBX_ERR_NOMEM; + goto fail; + } + out = grown; + } + } + + memcpy(out + len, section_cfg, part_len + 1); + len += part_len; + free(section_cfg); + } + + for (i = 0; i < ctx.count; i++) { + free(ctx.sections[i]); + } + free(ctx.sections); + return out; + +fail: + for (i = 0; i < ctx.count; i++) { + free(ctx.sections[i]); + } + free(ctx.sections); + return NULL; +} + +char *hybbx_config_prepend_packet_radio_max25(const hybbx_config_t *config, + const char *body) +{ + char prefix[HYBBX_CONFIG_VALUE_MAX * 2]; + char *out; + size_t prefix_len; + size_t body_len; + int check; + + if (config == NULL) { + return body != NULL ? hybbx_strdup(body) : NULL; + } + + if (body == NULL || body[0] == '\0') { + return hybbx_strdup(body != NULL ? body : ""); + } + + check = hybbx_config_get_bool(config, "max25", "check", 1); + snprintf(prefix, sizeof(prefix), + "max25_check=%s;max25_host=%s;max25_port=%u;max25_timeout_ms=%u", + check ? "yes" : "no", + hybbx_config_get(config, "max25", "host", "127.0.0.1"), + hybbx_config_get_uint(config, "max25", "port", + HYBBX_MAX25_DEFAULT_PORT, 1u, 65535u), + hybbx_config_get_uint(config, "max25", "timeout_ms", + HYBBX_MAX25_PROBE_TIMEOUT_MS, + 100u, 60000u)); + + if (body == NULL || body[0] == '\0') { + return hybbx_strdup(prefix); + } + + prefix_len = strlen(prefix); + body_len = strlen(body); + out = malloc(prefix_len + 1u + body_len + 1u); + if (out == NULL) { + return NULL; + } + + memcpy(out, prefix, prefix_len); + out[prefix_len] = HYBBX_PACKET_RADIO_INSTANCE_SEP; + memcpy(out + prefix_len + 1u, body, body_len + 1u); + return out; +} + +char *hybbx_config_format_packet_radio_start(const hybbx_config_t *config) +{ + char *body; + + body = hybbx_config_format_transport_sections(config, "packet_radio"); + if (body == NULL) { + return NULL; + } + + { + char *out = hybbx_config_prepend_packet_radio_max25(config, body); + + free(body); + return out; + } +} + +char *hybbx_config_format_baycom_start(const hybbx_config_t *config) +{ + char *body; + + body = hybbx_config_format_transport_sections(config, "baycom"); + if (body == NULL) { + return NULL; + } + + { + char *out = hybbx_config_prepend_packet_radio_max25(config, body); + + free(body); + return out; + } +} + +void hybbx_config_foreach(const hybbx_config_t *config, + hybbx_config_iter_fn fn, void *ctx) +{ + size_t i; + + if (config == NULL || fn == NULL) { + return; + } + + for (i = 0; i < config->count; i++) { + const hybbx_config_entry_t *entry = &config->entries[i]; + + fn(entry->section, entry->key, entry->value, ctx); + } +} + +void hybbx_config_foreach_section(const hybbx_config_t *config, + hybbx_config_section_iter_fn fn, void *ctx) +{ + size_t i; + + if (config == NULL || fn == NULL) { + return; + } + + for (i = 0; i < config->count; i++) { + const char *section = config->entries[i].section; + size_t j; + int seen = 0; + + for (j = 0; j < i; j++) { + if (strcmp(config->entries[j].section, section) == 0) { + seen = 1; + break; + } + } + + if (!seen) { + fn(section, ctx); + } + } +} + +hybbx_result_t hybbx_config_set(hybbx_config_t *config, + const char *section, + const char *key, + const char *value) +{ + size_t i; + + if (config == NULL || section == NULL || key == NULL || value == NULL) { + return HYBBX_ERR_INVALID; + } + + for (i = 0; i < config->count; i++) { + if (strcmp(config->entries[i].section, section) == 0 && + strcmp(config->entries[i].key, key) == 0) { + char *copy = hybbx_strdup(value); + + if (copy == NULL) { + return HYBBX_ERR_NOMEM; + } + free(config->entries[i].value); + config->entries[i].value = copy; + return HYBBX_OK; + } + } + + return append_entry(config, section, key, value); +} + +void hybbx_config_remove_section(hybbx_config_t *config, const char *section) +{ + size_t i; + + if (config == NULL || section == NULL) { + return; + } + + i = 0; + while (i < config->count) { + if (strcmp(config->entries[i].section, section) == 0) { + free(config->entries[i].section); + free(config->entries[i].key); + free(config->entries[i].value); + if (i + 1 < config->count) { + memmove(&config->entries[i], &config->entries[i + 1], + (config->count - i - 1) * sizeof(config->entries[i])); + } + config->count--; + } else { + i++; + } + } +} + +hybbx_result_t hybbx_config_save(const hybbx_config_t *config, + const char *path) +{ + FILE *fp; + size_t i; + const char *current = NULL; + + if (config == NULL || path == NULL) { + return HYBBX_ERR_INVALID; + } + + fp = fopen(path, "w"); + if (fp == NULL) { + return HYBBX_ERR_IO; + } + + for (i = 0; i < config->count; i++) { + const hybbx_config_entry_t *entry = &config->entries[i]; + + if (current == NULL || strcmp(current, entry->section) != 0) { + if (current != NULL) { + fputc('\n', fp); + } + fprintf(fp, "[%s]\n", entry->section); + current = entry->section; + } + fprintf(fp, "%s = %s\n", entry->key, entry->value); + } + + fclose(fp); + return HYBBX_OK; +} |
