summaryrefslogtreecommitdiff
path: root/src/core/config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/config.c')
-rw-r--r--src/core/config.c862
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;
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com