summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/config
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-09 08:36:11 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-11-09 08:37:11 +0000
commit910c794ec6d0a364b4aabccf22b715cb45780e83 (patch)
tree561a9ef6b6a4668102674e1a52b3e7563c57ac61 /src/libnetdata/config
parentReleasing debian version 1.47.5-1. (diff)
downloadnetdata-910c794ec6d0a364b4aabccf22b715cb45780e83.tar.xz
netdata-910c794ec6d0a364b4aabccf22b715cb45780e83.zip
Merging upstream version 2.0.0 (Closes: #923993, #1042533, #1045145).
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libnetdata/config')
-rw-r--r--src/libnetdata/config/README.md9
-rw-r--r--src/libnetdata/config/appconfig.c973
-rw-r--r--src/libnetdata/config/appconfig.h128
-rw-r--r--src/libnetdata/config/appconfig_api_boolean.c68
-rw-r--r--src/libnetdata/config/appconfig_api_boolean.h24
-rw-r--r--src/libnetdata/config/appconfig_api_durations.c134
-rw-r--r--src/libnetdata/config/appconfig_api_durations.h21
-rw-r--r--src/libnetdata/config/appconfig_api_numbers.c43
-rw-r--r--src/libnetdata/config/appconfig_api_numbers.h16
-rw-r--r--src/libnetdata/config/appconfig_api_sizes.c86
-rw-r--r--src/libnetdata/config/appconfig_api_sizes.h16
-rw-r--r--src/libnetdata/config/appconfig_api_text.c17
-rw-r--r--src/libnetdata/config/appconfig_api_text.h12
-rw-r--r--src/libnetdata/config/appconfig_cleanup.c60
-rw-r--r--src/libnetdata/config/appconfig_conf_file.c320
-rw-r--r--src/libnetdata/config/appconfig_exporters.c95
-rw-r--r--src/libnetdata/config/appconfig_internals.h119
-rw-r--r--src/libnetdata/config/appconfig_migrate.c95
-rw-r--r--src/libnetdata/config/appconfig_options.c183
-rw-r--r--src/libnetdata/config/appconfig_sections.c82
-rw-r--r--src/libnetdata/config/appconfig_traversal.c21
-rw-r--r--src/libnetdata/config/dyncfg.c8
22 files changed, 1513 insertions, 1017 deletions
diff --git a/src/libnetdata/config/README.md b/src/libnetdata/config/README.md
index 665a7196..fb1473c8 100644
--- a/src/libnetdata/config/README.md
+++ b/src/libnetdata/config/README.md
@@ -1,12 +1,3 @@
-<!--
-title: "Netdata ini config files"
-custom_edit_url: https://github.com/netdata/netdata/edit/master/src/libnetdata/config/README.md
-sidebar_label: "Netdata ini config files"
-learn_status: "Published"
-learn_topic_type: "Tasks"
-learn_rel_path: "Developers/libnetdata"
--->
-
# Netdata ini config files
Configuration files `netdata.conf` and `stream.conf` are Netdata ini files.
diff --git a/src/libnetdata/config/appconfig.c b/src/libnetdata/config/appconfig.c
index 81946b59..f26417ac 100644
--- a/src/libnetdata/config/appconfig.c
+++ b/src/libnetdata/config/appconfig.c
@@ -1,961 +1,82 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "../libnetdata.h"
+#include "appconfig_internals.h"
-/*
- * @Input:
- * Connector / instance to add to an internal structure
- * @Return
- * The current head of the linked list of connector_instance
- *
- */
-
-_CONNECTOR_INSTANCE *add_connector_instance(struct section *connector, struct section *instance)
-{
- static struct _connector_instance *global_connector_instance = NULL;
- struct _connector_instance *local_ci, *local_ci_tmp;
-
- if (unlikely(!connector)) {
- if (unlikely(!instance))
- return global_connector_instance;
-
- local_ci = global_connector_instance;
- while (local_ci) {
- local_ci_tmp = local_ci->next;
- freez(local_ci);
- local_ci = local_ci_tmp;
- }
- global_connector_instance = NULL;
- return NULL;
- }
-
- local_ci = callocz(1, sizeof(struct _connector_instance));
- local_ci->instance = instance;
- local_ci->connector = connector;
- strncpyz(local_ci->instance_name, instance->name, CONFIG_MAX_NAME);
- strncpyz(local_ci->connector_name, connector->name, CONFIG_MAX_NAME);
- local_ci->next = global_connector_instance;
- global_connector_instance = local_ci;
-
- return global_connector_instance;
-}
-
-int is_valid_connector(char *type, int check_reserved)
-{
- int rc = 1;
-
- if (unlikely(!type))
- return 0;
-
- if (!check_reserved) {
- if (unlikely(is_valid_connector(type,1))) {
- return 0;
- }
- //if (unlikely(*type == ':')
- // return 0;
- char *separator = strrchr(type, ':');
- if (likely(separator)) {
- *separator = '\0';
- rc = separator - type;
- } else
- return 0;
- }
-// else {
-// if (unlikely(is_valid_connector(type,1))) {
-// netdata_log_error("Section %s invalid -- reserved name", type);
-// return 0;
-// }
-// }
-
- if (!strcmp(type, "graphite") || !strcmp(type, "graphite:plaintext")) {
- return rc;
- } else if (!strcmp(type, "graphite:http") || !strcmp(type, "graphite:https")) {
- return rc;
- } else if (!strcmp(type, "json") || !strcmp(type, "json:plaintext")) {
- return rc;
- } else if (!strcmp(type, "json:http") || !strcmp(type, "json:https")) {
- return rc;
- } else if (!strcmp(type, "opentsdb") || !strcmp(type, "opentsdb:telnet")) {
- return rc;
- } else if (!strcmp(type, "opentsdb:http") || !strcmp(type, "opentsdb:https")) {
- return rc;
- } else if (!strcmp(type, "prometheus_remote_write")) {
- return rc;
- } else if (!strcmp(type, "prometheus_remote_write:http") || !strcmp(type, "prometheus_remote_write:https")) {
- return rc;
- } else if (!strcmp(type, "kinesis") || !strcmp(type, "kinesis:plaintext")) {
- return rc;
- } else if (!strcmp(type, "pubsub") || !strcmp(type, "pubsub:plaintext")) {
- return rc;
- } else if (!strcmp(type, "mongodb") || !strcmp(type, "mongodb:plaintext")) {
- return rc;
- }
-
- return 0;
-}
-
-// ----------------------------------------------------------------------------
-// locking
-
-inline void appconfig_wrlock(struct config *root) {
- netdata_mutex_lock(&root->mutex);
-}
-
-inline void appconfig_unlock(struct config *root) {
- netdata_mutex_unlock(&root->mutex);
-}
-
-inline void config_section_wrlock(struct section *co) {
- netdata_mutex_lock(&co->mutex);
-}
-
-inline void config_section_unlock(struct section *co) {
- netdata_mutex_unlock(&co->mutex);
-}
-
-
-// ----------------------------------------------------------------------------
-// config name-value index
-
-static int appconfig_option_compare(void *a, void *b) {
- if(((struct config_option *)a)->hash < ((struct config_option *)b)->hash) return -1;
- else if(((struct config_option *)a)->hash > ((struct config_option *)b)->hash) return 1;
- else return strcmp(((struct config_option *)a)->name, ((struct config_option *)b)->name);
-}
-
-#define appconfig_option_index_add(co, cv) (struct config_option *)avl_insert_lock(&((co)->values_index), (avl_t *)(cv))
-#define appconfig_option_index_del(co, cv) (struct config_option *)avl_remove_lock(&((co)->values_index), (avl_t *)(cv))
-
-static struct config_option *appconfig_option_index_find(struct section *co, const char *name, uint32_t hash) {
- struct config_option tmp;
- tmp.hash = (hash)?hash:simple_hash(name);
- tmp.name = (char *)name;
-
- return (struct config_option *)avl_search_lock(&(co->values_index), (avl_t *) &tmp);
-}
-
-
-// ----------------------------------------------------------------------------
-// config sections index
-
-int appconfig_section_compare(void *a, void *b) {
- if(((struct section *)a)->hash < ((struct section *)b)->hash) return -1;
- else if(((struct section *)a)->hash > ((struct section *)b)->hash) return 1;
- else return strcmp(((struct section *)a)->name, ((struct section *)b)->name);
-}
-
-#define appconfig_index_add(root, cfg) (struct section *)avl_insert_lock(&(root)->index, (avl_t *)(cfg))
-#define appconfig_index_del(root, cfg) (struct section *)avl_remove_lock(&(root)->index, (avl_t *)(cfg))
-
-static struct section *appconfig_index_find(struct config *root, const char *name, uint32_t hash) {
- struct section tmp;
- tmp.hash = (hash)?hash:simple_hash(name);
- tmp.name = (char *)name;
-
- return (struct section *)avl_search_lock(&root->index, (avl_t *) &tmp);
-}
-
-
-// ----------------------------------------------------------------------------
-// config section methods
-
-static inline struct section *appconfig_section_find(struct config *root, const char *section) {
- return appconfig_index_find(root, section, 0);
-}
-
-static inline struct section *appconfig_section_create(struct config *root, const char *section) {
- netdata_log_debug(D_CONFIG, "Creating section '%s'.", section);
-
- struct section *co = callocz(1, sizeof(struct section));
- co->name = strdupz(section);
- co->hash = simple_hash(co->name);
- netdata_mutex_init(&co->mutex);
-
- avl_init_lock(&co->values_index, appconfig_option_compare);
-
- if(unlikely(appconfig_index_add(root, co) != co))
- netdata_log_error("INTERNAL ERROR: indexing of section '%s', already exists.", co->name);
+int appconfig_exists(struct config *root, const char *section, const char *name) {
+ struct config_section *sect = appconfig_section_find(root, section);
+ if(!sect) return 0;
- appconfig_wrlock(root);
- struct section *co2 = root->last_section;
- if(co2) {
- co2->next = co;
- } else {
- root->first_section = co;
- }
- root->last_section = co;
- appconfig_unlock(root);
+ struct config_option *opt = appconfig_option_find(sect, name);
+ if(!opt) return 0;
- return co;
+ return 1;
}
-void appconfig_section_destroy_non_loaded(struct config *root, const char *section)
-{
- struct section *co;
- struct config_option *cv, *cv_next;
-
- netdata_log_debug(D_CONFIG, "Destroying section '%s'.", section);
-
- co = appconfig_section_find(root, section);
- if(!co) {
- netdata_log_error("Could not destroy section '%s'. Not found.", section);
+void appconfig_set_default_raw_value(struct config *root, const char *section, const char *name, const char *value) {
+ struct config_section *sect = appconfig_section_find(root, section);
+ if(!sect) {
+ appconfig_set_raw_value(root, section, name, value, CONFIG_VALUE_TYPE_UNKNOWN);
return;
}
- config_section_wrlock(co);
- for(cv = co->values; cv ; cv = cv->next) {
- if (cv->flags & CONFIG_VALUE_LOADED) {
- /* Do not destroy values that were loaded from the configuration files. */
- config_section_unlock(co);
- return;
- }
- }
- for(cv = co->values ; cv ; cv = cv_next) {
- cv_next = cv->next;
- if(unlikely(!appconfig_option_index_del(co, cv)))
- netdata_log_error("Cannot remove config option '%s' from section '%s'.", cv->name, co->name);
- freez(cv->value);
- freez(cv->name);
- freez(cv);
- }
- co->values = NULL;
- config_section_unlock(co);
-
- if (unlikely(!appconfig_index_del(root, co))) {
- netdata_log_error("Cannot remove section '%s' from config.", section);
+ struct config_option *opt = appconfig_option_find(sect, name);
+ if(!opt) {
+ appconfig_set_raw_value(root, section, name, value, CONFIG_VALUE_TYPE_UNKNOWN);
return;
}
-
- appconfig_wrlock(root);
-
- if (root->first_section == co) {
- root->first_section = co->next;
-
- if (root->last_section == co)
- root->last_section = root->first_section;
- } else {
- struct section *co_cur = root->first_section, *co_prev = NULL;
- while(co_cur && co_cur != co) {
- co_prev = co_cur;
- co_cur = co_cur->next;
- }
-
- if (co_cur) {
- co_prev->next = co_cur->next;
+ opt->flags |= CONFIG_VALUE_USED;
- if (root->last_section == co_cur)
- root->last_section = co_prev;
- }
- }
-
- appconfig_unlock(root);
-
- avl_destroy_lock(&co->values_index);
- freez(co->name);
- pthread_mutex_destroy(&co->mutex);
- freez(co);
-}
-
-void appconfig_section_option_destroy_non_loaded(struct config *root, const char *section, const char *name)
-{
- netdata_log_debug(D_CONFIG, "Destroying section option '%s -> %s'.", section, name);
-
- struct section *co;
- co = appconfig_section_find(root, section);
- if (!co) {
- netdata_log_error("Could not destroy section option '%s -> %s'. The section not found.", section, name);
- return;
- }
-
- config_section_wrlock(co);
-
- struct config_option *cv;
-
- cv = appconfig_option_index_find(co, name, simple_hash(name));
-
- if (cv && cv->flags & CONFIG_VALUE_LOADED) {
- config_section_unlock(co);
- return;
- }
-
- if (unlikely(!(cv && appconfig_option_index_del(co, cv)))) {
- config_section_unlock(co);
- netdata_log_error("Could not destroy section option '%s -> %s'. The option not found.", section, name);
+ if(opt->flags & CONFIG_VALUE_LOADED)
return;
- }
-
- if (co->values == cv) {
- co->values = co->values->next;
- } else {
- struct config_option *cv_cur = co->values, *cv_prev = NULL;
- while (cv_cur && cv_cur != cv) {
- cv_prev = cv_cur;
- cv_cur = cv_cur->next;
- }
- if (cv_cur) {
- cv_prev->next = cv_cur->next;
- }
- }
-
- freez(cv->value);
- freez(cv->name);
- freez(cv);
-
- config_section_unlock(co);
- return;
-}
-
-// ----------------------------------------------------------------------------
-// config name-value methods
-
-static inline struct config_option *appconfig_value_create(struct section *co, const char *name, const char *value) {
- netdata_log_debug(D_CONFIG, "Creating config entry for name '%s', value '%s', in section '%s'.", name, value, co->name);
-
- struct config_option *cv = callocz(1, sizeof(struct config_option));
- cv->name = strdupz(name);
- cv->hash = simple_hash(cv->name);
- cv->value = strdupz(value);
-
- struct config_option *found = appconfig_option_index_add(co, cv);
- if(found != cv) {
- netdata_log_error("indexing of config '%s' in section '%s': already exists - using the existing one.", cv->name, co->name);
- freez(cv->value);
- freez(cv->name);
- freez(cv);
- return found;
- }
-
- config_section_wrlock(co);
- struct config_option *cv2 = co->values;
- if(cv2) {
- while (cv2->next) cv2 = cv2->next;
- cv2->next = cv;
- }
- else co->values = cv;
- config_section_unlock(co);
-
- return cv;
-}
-
-int appconfig_exists(struct config *root, const char *section, const char *name) {
- struct config_option *cv;
-
- netdata_log_debug(D_CONFIG, "request to get config in section '%s', name '%s'", section, name);
-
- struct section *co = appconfig_section_find(root, section);
- if(!co) return 0;
-
- cv = appconfig_option_index_find(co, name, 0);
- if(!cv) return 0;
-
- return 1;
-}
-
-int appconfig_move(struct config *root, const char *section_old, const char *name_old, const char *section_new, const char *name_new) {
- struct config_option *cv_old, *cv_new;
- int ret = -1;
-
- netdata_log_debug(D_CONFIG, "request to rename config in section '%s', old name '%s', to section '%s', new name '%s'", section_old, name_old, section_new, name_new);
-
- struct section *co_old = appconfig_section_find(root, section_old);
- if(!co_old) return ret;
-
- struct section *co_new = appconfig_section_find(root, section_new);
- if(!co_new) co_new = appconfig_section_create(root, section_new);
-
- config_section_wrlock(co_old);
- if(co_old != co_new)
- config_section_wrlock(co_new);
-
- cv_old = appconfig_option_index_find(co_old, name_old, 0);
- if(!cv_old) goto cleanup;
-
- cv_new = appconfig_option_index_find(co_new, name_new, 0);
- if(cv_new) goto cleanup;
-
- if(unlikely(appconfig_option_index_del(co_old, cv_old) != cv_old))
- netdata_log_error("INTERNAL ERROR: deletion of config '%s' from section '%s', deleted the wrong config entry.", cv_old->name, co_old->name);
-
- if(co_old->values == cv_old) {
- co_old->values = cv_old->next;
- }
- else {
- struct config_option *t;
- for(t = co_old->values; t && t->next != cv_old ;t = t->next) ;
- if(!t || t->next != cv_old)
- netdata_log_error("INTERNAL ERROR: cannot find variable '%s' in section '%s' of the config - but it should be there.", cv_old->name, co_old->name);
- else
- t->next = cv_old->next;
- }
-
- freez(cv_old->name);
- cv_old->name = strdupz(name_new);
- cv_old->hash = simple_hash(cv_old->name);
-
- cv_new = cv_old;
- cv_new->next = co_new->values;
- co_new->values = cv_new;
-
- if(unlikely(appconfig_option_index_add(co_new, cv_old) != cv_old))
- netdata_log_error("INTERNAL ERROR: re-indexing of config '%s' in section '%s', already exists.", cv_old->name, co_new->name);
-
- ret = 0;
-
-cleanup:
- if(co_old != co_new)
- config_section_unlock(co_new);
- config_section_unlock(co_old);
- return ret;
-}
-
-char *appconfig_get_by_section(struct section *co, const char *name, const char *default_value)
-{
- struct config_option *cv;
- // Only calls internal to this file check for a NULL result and they do not supply a NULL arg.
- // External caller should treat NULL as an error case.
- cv = appconfig_option_index_find(co, name, 0);
- if (!cv) {
- if (!default_value) return NULL;
- cv = appconfig_value_create(co, name, default_value);
- if (!cv) return NULL;
- }
- cv->flags |= CONFIG_VALUE_USED;
+ if(string_strcmp(opt->value, value) != 0) {
+ opt->flags |= CONFIG_VALUE_CHANGED;
- if((cv->flags & CONFIG_VALUE_LOADED) || (cv->flags & CONFIG_VALUE_CHANGED)) {
- // this is a loaded value from the config file
- // if it is different than the default, mark it
- if(!(cv->flags & CONFIG_VALUE_CHECKED)) {
- if(default_value && strcmp(cv->value, default_value) != 0) cv->flags |= CONFIG_VALUE_CHANGED;
- cv->flags |= CONFIG_VALUE_CHECKED;
- }
+ string_freez(opt->value);
+ opt->value = string_strdupz(value);
}
-
- return(cv->value);
}
+bool stream_conf_needs_dbengine(struct config *root) {
+ struct config_section *sect;
+ bool ret = false;
-char *appconfig_get(struct config *root, const char *section, const char *name, const char *default_value)
-{
- if (default_value == NULL)
- netdata_log_debug(D_CONFIG, "request to get config in section '%s', name '%s' or fail", section, name);
- else
- netdata_log_debug(D_CONFIG, "request to get config in section '%s', name '%s', default_value '%s'", section, name, default_value);
-
- struct section *co = appconfig_section_find(root, section);
- if (!co && !default_value)
- return NULL;
- if(!co) co = appconfig_section_create(root, section);
-
- return appconfig_get_by_section(co, name, default_value);
-}
+ APPCONFIG_LOCK(root);
+ for(sect = root->sections; sect; sect = sect->next) {
+ if(string_strcmp(sect->name, "stream") == 0)
+ continue; // the first section is not relevant
-long long appconfig_get_number(struct config *root, const char *section, const char *name, long long value)
-{
- char buffer[100], *s;
- sprintf(buffer, "%lld", value);
-
- s = appconfig_get(root, section, name, buffer);
- if(!s) return value;
-
- return strtoll(s, NULL, 0);
-}
-
-NETDATA_DOUBLE appconfig_get_float(struct config *root, const char *section, const char *name, NETDATA_DOUBLE value)
-{
- char buffer[100], *s;
- sprintf(buffer, "%0.5" NETDATA_DOUBLE_MODIFIER, value);
-
- s = appconfig_get(root, section, name, buffer);
- if(!s) return value;
-
- return str2ndd(s, NULL);
-}
-
-inline int appconfig_test_boolean_value(char *s) {
- if(!strcasecmp(s, "yes") || !strcasecmp(s, "true") || !strcasecmp(s, "on")
- || !strcasecmp(s, "auto") || !strcasecmp(s, "on demand"))
- return 1;
-
- return 0;
-}
-
-int appconfig_get_boolean_by_section(struct section *co, const char *name, int value) {
- char *s;
-
- s = appconfig_get_by_section(co, name, (!value)?"no":"yes");
- if(!s) return value;
-
- return appconfig_test_boolean_value(s);
-}
-
-int appconfig_get_boolean(struct config *root, const char *section, const char *name, int value)
-{
- char *s;
- if(value) s = "yes";
- else s = "no";
-
- s = appconfig_get(root, section, name, s);
- if(!s) return value;
-
- return appconfig_test_boolean_value(s);
-}
-
-int appconfig_get_boolean_ondemand(struct config *root, const char *section, const char *name, int value)
-{
- char *s;
-
- if(value == CONFIG_BOOLEAN_AUTO)
- s = "auto";
-
- else if(value == CONFIG_BOOLEAN_NO)
- s = "no";
-
- else
- s = "yes";
-
- s = appconfig_get(root, section, name, s);
- if(!s) return value;
-
- if(!strcmp(s, "yes") || !strcmp(s, "true") || !strcmp(s, "on"))
- return CONFIG_BOOLEAN_YES;
- else if(!strcmp(s, "no") || !strcmp(s, "false") || !strcmp(s, "off"))
- return CONFIG_BOOLEAN_NO;
- else if(!strcmp(s, "auto") || !strcmp(s, "on demand"))
- return CONFIG_BOOLEAN_AUTO;
-
- return value;
-}
-
-const char *appconfig_set_default(struct config *root, const char *section, const char *name, const char *value)
-{
- struct config_option *cv;
-
- netdata_log_debug(D_CONFIG, "request to set default config in section '%s', name '%s', value '%s'", section, name, value);
-
- struct section *co = appconfig_section_find(root, section);
- if(!co) return appconfig_set(root, section, name, value);
-
- cv = appconfig_option_index_find(co, name, 0);
- if(!cv) return appconfig_set(root, section, name, value);
-
- cv->flags |= CONFIG_VALUE_USED;
-
- if(cv->flags & CONFIG_VALUE_LOADED)
- return cv->value;
-
- if(strcmp(cv->value, value) != 0) {
- cv->flags |= CONFIG_VALUE_CHANGED;
-
- freez(cv->value);
- cv->value = strdupz(value);
- }
-
- return cv->value;
-}
-
-const char *appconfig_set(struct config *root, const char *section, const char *name, const char *value)
-{
- struct config_option *cv;
-
- netdata_log_debug(D_CONFIG, "request to set config in section '%s', name '%s', value '%s'", section, name, value);
-
- struct section *co = appconfig_section_find(root, section);
- if(!co) co = appconfig_section_create(root, section);
-
- cv = appconfig_option_index_find(co, name, 0);
- if(!cv) cv = appconfig_value_create(co, name, value);
- cv->flags |= CONFIG_VALUE_USED;
-
- if(strcmp(cv->value, value) != 0) {
- cv->flags |= CONFIG_VALUE_CHANGED;
-
- freez(cv->value);
- cv->value = strdupz(value);
- }
-
- return value;
-}
-
-long long appconfig_set_number(struct config *root, const char *section, const char *name, long long value)
-{
- char buffer[100];
- sprintf(buffer, "%lld", value);
-
- appconfig_set(root, section, name, buffer);
-
- return value;
-}
-
-NETDATA_DOUBLE appconfig_set_float(struct config *root, const char *section, const char *name, NETDATA_DOUBLE value)
-{
- char buffer[100];
- sprintf(buffer, "%0.5" NETDATA_DOUBLE_MODIFIER, value);
-
- appconfig_set(root, section, name, buffer);
-
- return value;
-}
-
-int appconfig_set_boolean(struct config *root, const char *section, const char *name, int value)
-{
- char *s;
- if(value) s = "yes";
- else s = "no";
-
- appconfig_set(root, section, name, s);
-
- return value;
-}
-
-int appconfig_get_duration(struct config *root, const char *section, const char *name, const char *value)
-{
- int result = 0;
- const char *s;
-
- s = appconfig_get(root, section, name, value);
- if(!s) goto fallback;
-
- if(!config_parse_duration(s, &result)) {
- netdata_log_error("config option '[%s].%s = %s' is configured with an valid duration", section, name, s);
- goto fallback;
- }
-
- return result;
-
- fallback:
- if(!config_parse_duration(value, &result))
- netdata_log_error("INTERNAL ERROR: default duration supplied for option '[%s].%s = %s' is not a valid duration", section, name, value);
-
- return result;
-}
-
-// ----------------------------------------------------------------------------
-// config load/save
-
-int appconfig_load(struct config *root, char *filename, int overwrite_used, const char *section_name)
-{
- int line = 0;
- struct section *co = NULL;
- int is_exporter_config = 0;
- int _connectors = 0; // number of exporting connector sections we have
- char working_instance[CONFIG_MAX_NAME + 1];
- char working_connector[CONFIG_MAX_NAME + 1];
- struct section *working_connector_section = NULL;
- int global_exporting_section = 0;
-
- char buffer[CONFIG_FILE_LINE_MAX + 1], *s;
-
- if(!filename) filename = CONFIG_DIR "/" CONFIG_FILENAME;
-
- netdata_log_debug(D_CONFIG, "CONFIG: opening config file '%s'", filename);
-
- FILE *fp = fopen(filename, "r");
- if(!fp) {
- // netdata_log_info("CONFIG: cannot open file '%s'. Using internal defaults.", filename);
- return 0;
- }
-
- uint32_t section_hash = 0;
- if(section_name) {
- section_hash = simple_hash(section_name);
- }
- is_exporter_config = (strstr(filename, EXPORTING_CONF) != NULL);
-
- while(fgets(buffer, CONFIG_FILE_LINE_MAX, fp) != NULL) {
- buffer[CONFIG_FILE_LINE_MAX] = '\0';
- line++;
-
- s = trim(buffer);
- if(!s || *s == '#') {
- netdata_log_debug(D_CONFIG, "CONFIG: ignoring line %d of file '%s', it is empty.", line, filename);
- continue;
- }
-
- int len = (int) strlen(s);
- if(*s == '[' && s[len - 1] == ']') {
- // new section
- s[len - 1] = '\0';
- s++;
-
- if (is_exporter_config) {
- global_exporting_section =
- !(strcmp(s, CONFIG_SECTION_EXPORTING)) || !(strcmp(s, CONFIG_SECTION_PROMETHEUS));
- if (unlikely(!global_exporting_section)) {
- int rc;
- rc = is_valid_connector(s, 0);
- if (likely(rc)) {
- strncpyz(working_connector, s, CONFIG_MAX_NAME);
- s = s + rc + 1;
- if (unlikely(!(*s))) {
- _connectors++;
- sprintf(buffer, "instance_%d", _connectors);
- s = buffer;
- }
- strncpyz(working_instance, s, CONFIG_MAX_NAME);
- working_connector_section = NULL;
- if (unlikely(appconfig_section_find(root, working_instance))) {
- netdata_log_error("Instance (%s) already exists", working_instance);
- co = NULL;
- continue;
- }
- } else {
- co = NULL;
- netdata_log_error("Section (%s) does not specify a valid connector", s);
- continue;
- }
- }
- }
-
- co = appconfig_section_find(root, s);
- if(!co) co = appconfig_section_create(root, s);
-
- if(co && section_name && overwrite_used && section_hash == co->hash && !strcmp(section_name, co->name)) {
- config_section_wrlock(co);
- struct config_option *cv2 = co->values;
- while (cv2) {
- struct config_option *save = cv2->next;
- struct config_option *found = appconfig_option_index_del(co, cv2);
- if(found != cv2)
- netdata_log_error("INTERNAL ERROR: Cannot remove '%s' from section '%s', it was not inserted before.",
- cv2->name, co->name);
-
- freez(cv2->name);
- freez(cv2->value);
- freez(cv2);
- cv2 = save;
- }
- co->values = NULL;
- config_section_unlock(co);
- }
-
- continue;
- }
-
- if(!co) {
- // line outside a section
- netdata_log_error("CONFIG: ignoring line %d ('%s') of file '%s', it is outside all sections.", line, s, filename);
- continue;
- }
-
- if(section_name && overwrite_used && section_hash != co->hash && strcmp(section_name, co->name)) {
- continue;
- }
-
- char *name = s;
- char *value = strchr(s, '=');
- if(!value) {
- netdata_log_error("CONFIG: ignoring line %d ('%s') of file '%s', there is no = in it.", line, s, filename);
- continue;
- }
- *value = '\0';
- value++;
-
- name = trim(name);
- value = trim(value);
-
- if(!name || *name == '#') {
- netdata_log_error("CONFIG: ignoring line %d of file '%s', name is empty.", line, filename);
+ struct config_option *opt = appconfig_get_raw_value_of_option_in_section(sect, "enabled", NULL, CONFIG_VALUE_TYPE_UNKNOWN, NULL);
+ if(!opt || !appconfig_test_boolean_value(string2str(opt->value)))
continue;
- }
-
- if(!value) value = "";
-
- struct config_option *cv = appconfig_option_index_find(co, name, 0);
-
- if (!cv) {
- cv = appconfig_value_create(co, name, value);
- if (likely(is_exporter_config) && unlikely(!global_exporting_section)) {
- if (unlikely(!working_connector_section)) {
- working_connector_section = appconfig_section_find(root, working_connector);
- if (!working_connector_section)
- working_connector_section = appconfig_section_create(root, working_connector);
- if (likely(working_connector_section)) {
- add_connector_instance(working_connector_section, co);
- }
- }
- }
- } else {
- if (((cv->flags & CONFIG_VALUE_USED) && overwrite_used) || !(cv->flags & CONFIG_VALUE_USED)) {
- netdata_log_debug(
- D_CONFIG, "CONFIG: line %d of file '%s', overwriting '%s/%s'.", line, filename, co->name, cv->name);
- freez(cv->value);
- cv->value = strdupz(value);
- } else
- netdata_log_debug(
- D_CONFIG,
- "CONFIG: ignoring line %d of file '%s', '%s/%s' is already present and used.",
- line,
- filename,
- co->name,
- cv->name);
- }
- cv->flags |= CONFIG_VALUE_LOADED;
- }
- fclose(fp);
-
- return 1;
-}
-
-void appconfig_generate(struct config *root, BUFFER *wb, int only_changed)
-{
- int i, pri;
- struct section *co;
- struct config_option *cv;
-
- {
- int found_host_labels = 0;
- for (co = root->first_section; co; co = co->next)
- if(!strcmp(co->name, CONFIG_SECTION_HOST_LABEL))
- found_host_labels = 1;
-
- if(!found_host_labels) {
- appconfig_section_create(root, CONFIG_SECTION_HOST_LABEL);
- appconfig_get(root, CONFIG_SECTION_HOST_LABEL, "name", "value");
+ opt = appconfig_get_raw_value_of_option_in_section(sect, "db", NULL, CONFIG_VALUE_TYPE_UNKNOWN, NULL);
+ if(opt && string_strcmp(opt->value, "dbengine") == 0) {
+ ret = true;
+ break;
}
}
+ APPCONFIG_UNLOCK(root);
- buffer_strcat(wb,
- "# netdata configuration\n"
- "#\n"
- "# You can download the latest version of this file, using:\n"
- "#\n"
- "# wget -O /etc/netdata/netdata.conf http://localhost:19999/netdata.conf\n"
- "# or\n"
- "# curl -o /etc/netdata/netdata.conf http://localhost:19999/netdata.conf\n"
- "#\n"
- "# You can uncomment and change any of the options below.\n"
- "# The value shown in the commented settings, is the default value.\n"
- "#\n"
- "\n# global netdata configuration\n");
-
- for(i = 0; i <= 17 ;i++) {
- appconfig_wrlock(root);
- for(co = root->first_section; co ; co = co->next) {
- if(!strcmp(co->name, CONFIG_SECTION_GLOBAL)) pri = 0;
- else if(!strcmp(co->name, CONFIG_SECTION_DB)) pri = 1;
- else if(!strcmp(co->name, CONFIG_SECTION_DIRECTORIES)) pri = 2;
- else if(!strcmp(co->name, CONFIG_SECTION_LOGS)) pri = 3;
- else if(!strcmp(co->name, CONFIG_SECTION_ENV_VARS)) pri = 4;
- else if(!strcmp(co->name, CONFIG_SECTION_HOST_LABEL)) pri = 5;
- else if(!strcmp(co->name, CONFIG_SECTION_SQLITE)) pri = 6;
- else if(!strcmp(co->name, CONFIG_SECTION_CLOUD)) pri = 7;
- else if(!strcmp(co->name, CONFIG_SECTION_ML)) pri = 8;
- else if(!strcmp(co->name, CONFIG_SECTION_HEALTH)) pri = 9;
- else if(!strcmp(co->name, CONFIG_SECTION_WEB)) pri = 10;
- else if(!strcmp(co->name, CONFIG_SECTION_WEBRTC)) pri = 11;
- // by default, new sections will get pri = 12 (set at the end, below)
- else if(!strcmp(co->name, CONFIG_SECTION_REGISTRY)) pri = 13;
- else if(!strcmp(co->name, CONFIG_SECTION_GLOBAL_STATISTICS)) pri = 14;
- else if(!strcmp(co->name, CONFIG_SECTION_PLUGINS)) pri = 15;
- else if(!strcmp(co->name, CONFIG_SECTION_STATSD)) pri = 16;
- else if(!strncmp(co->name, "plugin:", 7)) pri = 17; // << change the loop too if you change this
- else pri = 12; // this is used for any new (currently unknown) sections
-
- if(i == pri) {
- int loaded = 0;
- int used = 0;
- int changed = 0;
- int count = 0;
-
- config_section_wrlock(co);
- for(cv = co->values; cv ; cv = cv->next) {
- used += (cv->flags & CONFIG_VALUE_USED)?1:0;
- loaded += (cv->flags & CONFIG_VALUE_LOADED)?1:0;
- changed += (cv->flags & CONFIG_VALUE_CHANGED)?1:0;
- count++;
- }
- config_section_unlock(co);
-
- if(!count) continue;
- if(only_changed && !changed && !loaded) continue;
-
- if(!used) {
- buffer_sprintf(wb, "\n# section '%s' is not used.", co->name);
- }
-
- buffer_sprintf(wb, "\n[%s]\n", co->name);
-
- config_section_wrlock(co);
- for(cv = co->values; cv ; cv = cv->next) {
-
- if(used && !(cv->flags & CONFIG_VALUE_USED)) {
- buffer_sprintf(wb, "\n\t# option '%s' is not used.\n", cv->name);
- }
- buffer_sprintf(wb, "\t%s%s = %s\n", ((!(cv->flags & CONFIG_VALUE_LOADED)) && (!(cv->flags & CONFIG_VALUE_CHANGED)) && (cv->flags & CONFIG_VALUE_USED))?"# ":"", cv->name, cv->value);
- }
- config_section_unlock(co);
- }
- }
- appconfig_unlock(root);
- }
+ return ret;
}
-/**
- * Parse Duration
- *
- * Parse the string setting the result
- *
- * @param string the timestamp string
- * @param result the output variable
- *
- * @return It returns 1 on success and 0 otherwise
- */
-int config_parse_duration(const char* string, int* result) {
- while(*string && isspace((uint8_t)*string)) string++;
-
- if(unlikely(!*string)) goto fallback;
-
- if(*string == 'n' && !strcmp(string, "never")) {
- // this is a valid option
- *result = 0;
- return 1;
- }
+bool stream_conf_has_uuid_section(struct config *root) {
+ struct config_section *sect = NULL;
+ bool is_parent = false;
- // make sure it is a number
- if(!(isdigit((uint8_t)*string) || *string == '+' || *string == '-')) goto fallback;
+ APPCONFIG_LOCK(root);
+ for (sect = root->sections; sect; sect = sect->next) {
+ nd_uuid_t uuid;
- char *e = NULL;
- NETDATA_DOUBLE n = str2ndd(string, &e);
- if(e && *e) {
- switch (*e) {
- case 'Y':
- *result = (int) (n * 31536000);
- break;
- case 'M':
- *result = (int) (n * 2592000);
- break;
- case 'w':
- *result = (int) (n * 604800);
- break;
- case 'd':
- *result = (int) (n * 86400);
- break;
- case 'h':
- *result = (int) (n * 3600);
- break;
- case 'm':
- *result = (int) (n * 60);
- break;
- case 's':
- default:
- *result = (int) (n);
- break;
+ if (uuid_parse(string2str(sect->name), uuid) != -1 &&
+ appconfig_get_boolean_by_section(sect, "enabled", 0)) {
+ is_parent = true;
+ break;
}
}
- else
- *result = (int)(n);
-
- return 1;
-
- fallback:
- *result = 0;
- return 0;
-}
+ APPCONFIG_UNLOCK(root);
-struct section *appconfig_get_section(struct config *root, const char *name)
-{
- return appconfig_section_find(root, name);
+ return is_parent;
}
diff --git a/src/libnetdata/config/appconfig.h b/src/libnetdata/config/appconfig.h
index 214a15ed..f1551b38 100644
--- a/src/libnetdata/config/appconfig.h
+++ b/src/libnetdata/config/appconfig.h
@@ -103,7 +103,6 @@
#define CONFIG_SECTION_GLOBAL_STATISTICS "global statistics"
#define CONFIG_SECTION_DB "db"
-
// these are used to limit the configuration names and values lengths
// they are not enforced by config.c functions (they will strdup() all strings, no matter of their length)
#define CONFIG_MAX_NAME 1024
@@ -113,94 +112,43 @@
// Config definitions
#define CONFIG_FILE_LINE_MAX ((CONFIG_MAX_NAME + CONFIG_MAX_VALUE + 1024) * 2)
-#define CONFIG_VALUE_LOADED 0x01 // has been loaded from the config
-#define CONFIG_VALUE_USED 0x02 // has been accessed from the program
-#define CONFIG_VALUE_CHANGED 0x04 // has been changed from the loaded value or the internal default value
-#define CONFIG_VALUE_CHECKED 0x08 // has been checked if the value is different from the default
-
-struct config_option {
- avl_t avl_node; // the index entry of this entry - this has to be first!
-
- uint8_t flags;
- uint32_t hash; // a simple hash to speed up searching
- // we first compare hashes, and only if the hashes are equal we do string comparisons
-
- char *name;
- char *value;
-
- struct config_option *next; // config->mutex protects just this
-};
-
-struct section {
- avl_t avl_node; // the index entry of this section - this has to be first!
-
- uint32_t hash; // a simple hash to speed up searching
- // we first compare hashes, and only if the hashes are equal we do string comparisons
-
- char *name;
-
- struct section *next; // global config_mutex protects just this
-
- struct config_option *values;
- avl_tree_lock values_index;
-
- netdata_mutex_t mutex; // this locks only the writers, to ensure atomic updates
- // readers are protected using the rwlock in avl_tree_lock
-};
+struct config_section;
struct config {
- struct section *first_section;
- struct section *last_section; // optimize inserting at the end
- netdata_mutex_t mutex;
+ struct config_section *sections;
+ SPINLOCK spinlock;
avl_tree_lock index;
};
-#define CONFIG_BOOLEAN_INVALID 100 // an invalid value to check for validity (used as default initialization when needed)
+#define APPCONFIG_INITIALIZER (struct config) { \
+ .sections = NULL, \
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER, \
+ .index = { \
+ .avl_tree = { \
+ .root = NULL, \
+ .compar = appconfig_section_compare, \
+ }, \
+ .rwlock = AVL_LOCK_INITIALIZER, \
+ }, \
+ }
-#define CONFIG_BOOLEAN_NO 0 // disabled
-#define CONFIG_BOOLEAN_YES 1 // enabled
+int appconfig_load(struct config *root, char *filename, int overwrite_used, const char *section_name);
-#ifndef CONFIG_BOOLEAN_AUTO
-#define CONFIG_BOOLEAN_AUTO 2 // enabled if it has useful info when enabled
-#endif
+typedef bool (*appconfig_foreach_value_cb_t)(void *data, const char *name, const char *value);
+size_t appconfig_foreach_value_in_section(struct config *root, const char *section, appconfig_foreach_value_cb_t cb, void *data);
-int appconfig_load(struct config *root, char *filename, int overwrite_used, const char *section_name);
-void config_section_wrlock(struct section *co);
-void config_section_unlock(struct section *co);
-
-char *appconfig_get_by_section(struct section *co, const char *name, const char *default_value);
-char *appconfig_get(struct config *root, const char *section, const char *name, const char *default_value);
-long long appconfig_get_number(struct config *root, const char *section, const char *name, long long value);
-NETDATA_DOUBLE appconfig_get_float(struct config *root, const char *section, const char *name, NETDATA_DOUBLE value);
-int appconfig_get_boolean_by_section(struct section *co, const char *name, int value);
-int appconfig_get_boolean(struct config *root, const char *section, const char *name, int value);
-int appconfig_get_boolean_ondemand(struct config *root, const char *section, const char *name, int value);
-int appconfig_get_duration(struct config *root, const char *section, const char *name, const char *value);
-
-const char *appconfig_set(struct config *root, const char *section, const char *name, const char *value);
-const char *appconfig_set_default(struct config *root, const char *section, const char *name, const char *value);
-long long appconfig_set_number(struct config *root, const char *section, const char *name, long long value);
-NETDATA_DOUBLE appconfig_set_float(struct config *root, const char *section, const char *name, NETDATA_DOUBLE value);
-int appconfig_set_boolean(struct config *root, const char *section, const char *name, int value);
+// sets a raw value, only if it is not loaded from the config
+void appconfig_set_default_raw_value(struct config *root, const char *section, const char *name, const char *value);
int appconfig_exists(struct config *root, const char *section, const char *name);
int appconfig_move(struct config *root, const char *section_old, const char *name_old, const char *section_new, const char *name_new);
+int appconfig_move_everywhere(struct config *root, const char *name_old, const char *name_new);
-void appconfig_generate(struct config *root, BUFFER *wb, int only_changed);
+void appconfig_generate(struct config *root, BUFFER *wb, int only_changed, bool netdata_conf);
int appconfig_section_compare(void *a, void *b);
-void appconfig_section_destroy_non_loaded(struct config *root, const char *section);
-void appconfig_section_option_destroy_non_loaded(struct config *root, const char *section, const char *name);
-
-int config_parse_duration(const char* string, int* result);
-
-struct section *appconfig_get_section(struct config *root, const char *name);
-
-void appconfig_wrlock(struct config *root);
-void appconfig_unlock(struct config *root);
-
-int appconfig_test_boolean_value(char *s);
+bool appconfig_test_boolean_value(const char *s);
struct connector_instance {
char instance_name[CONFIG_MAX_NAME + 1];
@@ -208,13 +156,37 @@ struct connector_instance {
};
typedef struct _connector_instance {
- struct section *connector; // actual connector
- struct section *instance; // This instance
+ struct config_section *connector; // actual connector
+ struct config_section *instance; // This instance
char instance_name[CONFIG_MAX_NAME + 1];
char connector_name[CONFIG_MAX_NAME + 1];
struct _connector_instance *next; // Next instance
} _CONNECTOR_INSTANCE;
-_CONNECTOR_INSTANCE *add_connector_instance(struct section *connector, struct section *instance);
+_CONNECTOR_INSTANCE *add_connector_instance(struct config_section *connector, struct config_section *instance);
+
+// ----------------------------------------------------------------------------
+// shortcuts for the default netdata configuration
+
+#define config_load(filename, overwrite_used, section) appconfig_load(&netdata_config, filename, overwrite_used, section)
+
+#define config_set_default_raw_value(section, name, value) appconfig_set_default_raw_value(&netdata_config, section, name, value)
+
+#define config_exists(section, name) appconfig_exists(&netdata_config, section, name)
+#define config_move(section_old, name_old, section_new, name_new) appconfig_move(&netdata_config, section_old, name_old, section_new, name_new)
+
+#define netdata_conf_generate(buffer, only_changed) appconfig_generate(&netdata_config, buffer, only_changed, true)
+
+#define config_section_destroy(section) appconfig_section_destroy_non_loaded(&netdata_config, section)
+#define config_section_option_destroy(section, name) appconfig_section_option_destroy_non_loaded(&netdata_config, section, name)
+
+bool stream_conf_needs_dbengine(struct config *root);
+bool stream_conf_has_uuid_section(struct config *root);
+
+#include "appconfig_api_text.h"
+#include "appconfig_api_numbers.h"
+#include "appconfig_api_boolean.h"
+#include "appconfig_api_sizes.h"
+#include "appconfig_api_durations.h"
-#endif /* NETDATA_CONFIG_H */ \ No newline at end of file
+#endif // NETDATA_CONFIG_H
diff --git a/src/libnetdata/config/appconfig_api_boolean.c b/src/libnetdata/config/appconfig_api_boolean.c
new file mode 100644
index 00000000..abe51573
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_boolean.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+#include "appconfig_api_boolean.h"
+
+bool appconfig_test_boolean_value(const char *s) {
+ if(!strcasecmp(s, "yes") || !strcasecmp(s, "true") || !strcasecmp(s, "on")
+ || !strcasecmp(s, "auto") || !strcasecmp(s, "on demand"))
+ return true;
+
+ return false;
+}
+
+int appconfig_get_boolean_by_section(struct config_section *sect, const char *name, int value) {
+ struct config_option *opt = appconfig_get_raw_value_of_option_in_section(
+ sect, name, (!value) ? "no" : "yes", CONFIG_VALUE_TYPE_BOOLEAN, NULL);
+ if(!opt) return value;
+
+ return appconfig_test_boolean_value(string2str(opt->value));
+}
+
+int appconfig_get_boolean(struct config *root, const char *section, const char *name, int value) {
+ const char *s;
+ if(value) s = "yes";
+ else s = "no";
+
+ struct config_option *opt = appconfig_get_raw_value(root, section, name, s, CONFIG_VALUE_TYPE_BOOLEAN, NULL);
+ if(!opt) return value;
+ s = string2str(opt->value);
+
+ return appconfig_test_boolean_value(s);
+}
+
+int appconfig_get_boolean_ondemand(struct config *root, const char *section, const char *name, int value) {
+ const char *s;
+
+ if(value == CONFIG_BOOLEAN_AUTO)
+ s = "auto";
+
+ else if(value == CONFIG_BOOLEAN_NO)
+ s = "no";
+
+ else
+ s = "yes";
+
+ struct config_option *opt = appconfig_get_raw_value(root, section, name, s, CONFIG_VALUE_TYPE_BOOLEAN_ONDEMAND, NULL);
+ if(!opt) return value;
+
+ s = string2str(opt->value);
+ if(!strcmp(s, "yes") || !strcmp(s, "true") || !strcmp(s, "on"))
+ return CONFIG_BOOLEAN_YES;
+ else if(!strcmp(s, "no") || !strcmp(s, "false") || !strcmp(s, "off"))
+ return CONFIG_BOOLEAN_NO;
+ else if(!strcmp(s, "auto") || !strcmp(s, "on demand"))
+ return CONFIG_BOOLEAN_AUTO;
+
+ return value;
+}
+
+int appconfig_set_boolean(struct config *root, const char *section, const char *name, int value) {
+ const char *s;
+ if(value) s = "yes";
+ else s = "no";
+
+ appconfig_set_raw_value(root, section, name, s, CONFIG_VALUE_TYPE_BOOLEAN);
+
+ return value;
+}
diff --git a/src/libnetdata/config/appconfig_api_boolean.h b/src/libnetdata/config/appconfig_api_boolean.h
new file mode 100644
index 00000000..2b05fce6
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_boolean.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_APPCONFIG_API_BOOLEAN_H
+#define NETDATA_APPCONFIG_API_BOOLEAN_H
+
+#define CONFIG_BOOLEAN_INVALID 100 // an invalid value to check for validity (used as default initialization when needed)
+
+#define CONFIG_BOOLEAN_NO 0 // disabled
+#define CONFIG_BOOLEAN_YES 1 // enabled
+
+#ifndef CONFIG_BOOLEAN_AUTO
+#define CONFIG_BOOLEAN_AUTO 2 // enabled if it has useful info when enabled
+#endif
+
+int appconfig_get_boolean(struct config *root, const char *section, const char *name, int value);
+#define config_get_boolean(section, name, value) appconfig_get_boolean(&netdata_config, section, name, value)
+
+int appconfig_get_boolean_ondemand(struct config *root, const char *section, const char *name, int value);
+#define config_get_boolean_ondemand(section, name, value) appconfig_get_boolean_ondemand(&netdata_config, section, name, value)
+
+int appconfig_set_boolean(struct config *root, const char *section, const char *name, int value);
+#define config_set_boolean(section, name, value) appconfig_set_boolean(&netdata_config, section, name, value)
+
+#endif //NETDATA_APPCONFIG_API_BOOLEAN_H
diff --git a/src/libnetdata/config/appconfig_api_durations.c b/src/libnetdata/config/appconfig_api_durations.c
new file mode 100644
index 00000000..88c462ac
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_durations.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+#include "appconfig_api_durations.h"
+
+
+static STRING *reformat_duration_seconds(STRING *value) {
+ int result = 0;
+ if(!duration_parse_seconds(string2str(value), &result))
+ return value;
+
+ char buf[128];
+ if(duration_snprintf_time_t(buf, sizeof(buf), result) > 0 && string_strcmp(value, buf) != 0) {
+ string_freez(value);
+ return string_strdupz(buf);
+ }
+
+ return value;
+}
+
+time_t appconfig_get_duration_seconds(struct config *root, const char *section, const char *name, time_t default_value) {
+ char default_str[128];
+ duration_snprintf_time_t(default_str, sizeof(default_str), default_value);
+
+ struct config_option *opt = appconfig_get_raw_value(
+ root, section, name, default_str, CONFIG_VALUE_TYPE_DURATION_IN_SECS, reformat_duration_seconds);
+ if(!opt)
+ return default_value;
+
+ const char *s = string2str(opt->value);
+
+ int result = 0;
+ if(!duration_parse_seconds(s, &result)) {
+ appconfig_set_raw_value(root, section, name, default_str, CONFIG_VALUE_TYPE_DURATION_IN_SECS);
+ netdata_log_error("config option '[%s].%s = %s' is configured with an invalid duration", section, name, s);
+ return default_value;
+ }
+
+ return ABS(result);
+}
+
+time_t appconfig_set_duration_seconds(struct config *root, const char *section, const char *name, time_t value) {
+ char str[128];
+ duration_snprintf_time_t(str, sizeof(str), value);
+
+ appconfig_set_raw_value(root, section, name, str, CONFIG_VALUE_TYPE_DURATION_IN_SECS);
+ return value;
+}
+
+static STRING *reformat_duration_ms(STRING *value) {
+ int64_t result = 0;
+ if(!duration_parse_msec_t(string2str(value), &result))
+ return value;
+
+ char buf[128];
+ if(duration_snprintf_msec_t(buf, sizeof(buf), result) > 0 && string_strcmp(value, buf) != 0) {
+ string_freez(value);
+ return string_strdupz(buf);
+ }
+
+ return value;
+}
+
+msec_t appconfig_get_duration_ms(struct config *root, const char *section, const char *name, msec_t default_value) {
+ char default_str[128];
+ duration_snprintf_msec_t(default_str, sizeof(default_str), default_value);
+
+ struct config_option *opt = appconfig_get_raw_value(
+ root, section, name, default_str, CONFIG_VALUE_TYPE_DURATION_IN_MS, reformat_duration_ms);
+ if(!opt)
+ return default_value;
+
+ const char *s = string2str(opt->value);
+
+ smsec_t result = 0;
+ if(!duration_parse_msec_t(s, &result)) {
+ appconfig_set_raw_value(root, section, name, default_str, CONFIG_VALUE_TYPE_DURATION_IN_MS);
+ netdata_log_error("config option '[%s].%s = %s' is configured with an invalid duration", section, name, s);
+ return default_value;
+ }
+
+ return ABS(result);
+}
+
+msec_t appconfig_set_duration_ms(struct config *root, const char *section, const char *name, msec_t value) {
+ char str[128];
+ duration_snprintf_msec_t(str, sizeof(str), (smsec_t)value);
+
+ appconfig_set_raw_value(root, section, name, str, CONFIG_VALUE_TYPE_DURATION_IN_MS);
+ return value;
+}
+
+static STRING *reformat_duration_days(STRING *value) {
+ int64_t result = 0;
+ if(!duration_parse_days(string2str(value), &result))
+ return value;
+
+ char buf[128];
+ if(duration_snprintf_days(buf, sizeof(buf), result) > 0 && string_strcmp(value, buf) != 0) {
+ string_freez(value);
+ return string_strdupz(buf);
+ }
+
+ return value;
+}
+
+unsigned appconfig_get_duration_days(struct config *root, const char *section, const char *name, unsigned default_value) {
+ char default_str[128];
+ duration_snprintf_days(default_str, sizeof(default_str), (int)default_value);
+
+ struct config_option *opt = appconfig_get_raw_value(
+ root, section, name, default_str, CONFIG_VALUE_TYPE_DURATION_IN_DAYS, reformat_duration_days);
+ if(!opt)
+ return default_value;
+
+ const char *s = string2str(opt->value);
+
+ int64_t result = 0;
+ if(!duration_parse_days(s, &result)) {
+ appconfig_set_raw_value(root, section, name, default_str, CONFIG_VALUE_TYPE_DURATION_IN_DAYS);
+ netdata_log_error("config option '[%s].%s = %s' is configured with an invalid duration", section, name, s);
+ return default_value;
+ }
+
+ return (unsigned)ABS(result);
+}
+
+unsigned appconfig_set_duration_days(struct config *root, const char *section, const char *name, unsigned value) {
+ char str[128];
+ duration_snprintf_days(str, sizeof(str), value);
+ appconfig_set_raw_value(root, section, name, str, CONFIG_VALUE_TYPE_DURATION_IN_DAYS);
+ return value;
+}
+
diff --git a/src/libnetdata/config/appconfig_api_durations.h b/src/libnetdata/config/appconfig_api_durations.h
new file mode 100644
index 00000000..26d6c6ba
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_durations.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_APPCONFIG_API_DURATIONS_H
+#define NETDATA_APPCONFIG_API_DURATIONS_H
+
+msec_t appconfig_get_duration_ms(struct config *root, const char *section, const char *name, msec_t default_value);
+msec_t appconfig_set_duration_ms(struct config *root, const char *section, const char *name, msec_t value);
+#define config_get_duration_ms(section, name, value) appconfig_get_duration_ms(&netdata_config, section, name, value)
+#define config_set_duration_ms(section, name, value) appconfig_set_duration_ms(&netdata_config, section, name, value)
+
+time_t appconfig_get_duration_seconds(struct config *root, const char *section, const char *name, time_t default_value);
+time_t appconfig_set_duration_seconds(struct config *root, const char *section, const char *name, time_t value);
+#define config_get_duration_seconds(section, name, value) appconfig_get_duration_seconds(&netdata_config, section, name, value)
+#define config_set_duration_seconds(section, name, value) appconfig_set_duration_seconds(&netdata_config, section, name, value)
+
+unsigned appconfig_get_duration_days(struct config *root, const char *section, const char *name, unsigned default_value);
+unsigned appconfig_set_duration_days(struct config *root, const char *section, const char *name, unsigned value);
+#define config_get_duration_days(section, name, value) appconfig_get_duration_days(&netdata_config, section, name, value)
+#define config_set_duration_days(section, name, value) appconfig_set_duration_days(&netdata_config, section, name, value)
+
+#endif //NETDATA_APPCONFIG_API_DURATIONS_H
diff --git a/src/libnetdata/config/appconfig_api_numbers.c b/src/libnetdata/config/appconfig_api_numbers.c
new file mode 100644
index 00000000..cc3776c1
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_numbers.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+#include "appconfig_api_numbers.h"
+
+long long appconfig_get_number(struct config *root, const char *section, const char *name, long long value) {
+ char buffer[100];
+ sprintf(buffer, "%lld", value);
+
+ struct config_option *opt = appconfig_get_raw_value(root, section, name, buffer, CONFIG_VALUE_TYPE_INTEGER, NULL);
+ if(!opt) return value;
+
+ const char *s = string2str(opt->value);
+ return strtoll(s, NULL, 0);
+}
+
+NETDATA_DOUBLE appconfig_get_double(struct config *root, const char *section, const char *name, NETDATA_DOUBLE value) {
+ char buffer[100];
+ sprintf(buffer, "%0.5" NETDATA_DOUBLE_MODIFIER, value);
+
+ struct config_option *opt = appconfig_get_raw_value(root, section, name, buffer, CONFIG_VALUE_TYPE_DOUBLE, NULL);
+ if(!opt) return value;
+
+ const char *s = string2str(opt->value);
+ return str2ndd(s, NULL);
+}
+
+long long appconfig_set_number(struct config *root, const char *section, const char *name, long long value) {
+ char buffer[100];
+ sprintf(buffer, "%lld", value);
+
+ appconfig_set_raw_value(root, section, name, buffer, CONFIG_VALUE_TYPE_INTEGER);
+ return value;
+}
+
+NETDATA_DOUBLE appconfig_set_double(struct config *root, const char *section, const char *name, NETDATA_DOUBLE value) {
+ char buffer[100];
+ sprintf(buffer, "%0.5" NETDATA_DOUBLE_MODIFIER, value);
+
+ appconfig_set_raw_value(root, section, name, buffer, CONFIG_VALUE_TYPE_DOUBLE);
+ return value;
+}
+
diff --git a/src/libnetdata/config/appconfig_api_numbers.h b/src/libnetdata/config/appconfig_api_numbers.h
new file mode 100644
index 00000000..58d382e3
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_numbers.h
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_APPCONFIG_API_NUMBERS_H
+#define NETDATA_APPCONFIG_API_NUMBERS_H
+
+long long appconfig_get_number(struct config *root, const char *section, const char *name, long long value);
+long long appconfig_set_number(struct config *root, const char *section, const char *name, long long value);
+#define config_get_number(section, name, value) appconfig_get_number(&netdata_config, section, name, value)
+#define config_set_number(section, name, value) appconfig_set_number(&netdata_config, section, name, value)
+
+NETDATA_DOUBLE appconfig_get_double(struct config *root, const char *section, const char *name, NETDATA_DOUBLE value);
+NETDATA_DOUBLE appconfig_set_double(struct config *root, const char *section, const char *name, NETDATA_DOUBLE value);
+#define config_get_double(section, name, value) appconfig_get_double(&netdata_config, section, name, value)
+#define config_set_double(section, name, value) appconfig_set_float(&netdata_config, section, name, value)
+
+#endif //NETDATA_APPCONFIG_API_NUMBERS_H
diff --git a/src/libnetdata/config/appconfig_api_sizes.c b/src/libnetdata/config/appconfig_api_sizes.c
new file mode 100644
index 00000000..67b1dce9
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_sizes.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+#include "appconfig_api_sizes.h"
+
+static STRING *reformat_size_bytes(STRING *value) {
+ uint64_t result = 0;
+ if(!size_parse_bytes(string2str(value), &result))
+ return value;
+
+ char buf[128];
+ if(size_snprintf_bytes(buf, sizeof(buf), result) > 0 && string_strcmp(value, buf) != 0) {
+ string_freez(value);
+ return string_strdupz(buf);
+ }
+
+ return value;
+}
+
+uint64_t appconfig_get_size_bytes(struct config *root, const char *section, const char *name, uint64_t default_value) {
+ char default_str[128];
+ size_snprintf_bytes(default_str, sizeof(default_str), (int)default_value);
+
+ struct config_option *opt =
+ appconfig_get_raw_value(root, section, name, default_str, CONFIG_VALUE_TYPE_SIZE_IN_BYTES, reformat_size_bytes);
+ if(!opt)
+ return default_value;
+
+ const char *s = string2str(opt->value);
+ uint64_t result = 0;
+ if(!size_parse_bytes(s, &result)) {
+ appconfig_set_raw_value(root, section, name, default_str, CONFIG_VALUE_TYPE_SIZE_IN_BYTES);
+ netdata_log_error("config option '[%s].%s = %s' is configured with an invalid size", section, name, s);
+ return default_value;
+ }
+
+ return result;
+}
+
+uint64_t appconfig_set_size_bytes(struct config *root, const char *section, const char *name, uint64_t value) {
+ char str[128];
+ size_snprintf_bytes(str, sizeof(str), value);
+ appconfig_set_raw_value(root, section, name, str, CONFIG_VALUE_TYPE_SIZE_IN_BYTES);
+ return value;
+}
+
+static STRING *reformat_size_mb(STRING *value) {
+ uint64_t result = 0;
+ if(!size_parse_mb(string2str(value), &result))
+ return value;
+
+ char buf[128];
+ if(size_snprintf_mb(buf, sizeof(buf), result) > 0 && string_strcmp(value, buf) != 0) {
+ string_freez(value);
+ return string_strdupz(buf);
+ }
+
+ return value;
+}
+
+uint64_t appconfig_get_size_mb(struct config *root, const char *section, const char *name, uint64_t default_value) {
+ char default_str[128];
+ size_snprintf_mb(default_str, sizeof(default_str), (int)default_value);
+
+ struct config_option *opt =
+ appconfig_get_raw_value(root, section, name, default_str, CONFIG_VALUE_TYPE_SIZE_IN_MB, reformat_size_mb);
+ if(!opt)
+ return default_value;
+
+ const char *s = string2str(opt->value);
+ uint64_t result = 0;
+ if(!size_parse_mb(s, &result)) {
+ appconfig_set_raw_value(root, section, name, default_str, CONFIG_VALUE_TYPE_SIZE_IN_MB);
+ netdata_log_error("config option '[%s].%s = %s' is configured with an invalid size", section, name, s);
+ return default_value;
+ }
+
+ return (unsigned)result;
+}
+
+uint64_t appconfig_set_size_mb(struct config *root, const char *section, const char *name, uint64_t value) {
+ char str[128];
+ size_snprintf_mb(str, sizeof(str), value);
+ appconfig_set_raw_value(root, section, name, str, CONFIG_VALUE_TYPE_SIZE_IN_MB);
+ return value;
+}
diff --git a/src/libnetdata/config/appconfig_api_sizes.h b/src/libnetdata/config/appconfig_api_sizes.h
new file mode 100644
index 00000000..98ef209f
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_sizes.h
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_APPCONFIG_API_SIZES_H
+#define NETDATA_APPCONFIG_API_SIZES_H
+
+uint64_t appconfig_get_size_bytes(struct config *root, const char *section, const char *name, uint64_t default_value);
+uint64_t appconfig_set_size_bytes(struct config *root, const char *section, const char *name, uint64_t value);
+#define config_get_size_bytes(section, name, value) appconfig_get_size_bytes(&netdata_config, section, name, value)
+#define config_set_size_bytes(section, name, value) appconfig_set_size_bytes(&netdata_config, section, name, value)
+
+uint64_t appconfig_get_size_mb(struct config *root, const char *section, const char *name, uint64_t default_value);
+uint64_t appconfig_set_size_mb(struct config *root, const char *section, const char *name, uint64_t value);
+#define config_get_size_mb(section, name, value) appconfig_get_size_mb(&netdata_config, section, name, value)
+#define config_set_size_mb(section, name, value) appconfig_set_size_mb(&netdata_config, section, name, value)
+
+#endif //NETDATA_APPCONFIG_API_SIZES_H
diff --git a/src/libnetdata/config/appconfig_api_text.c b/src/libnetdata/config/appconfig_api_text.c
new file mode 100644
index 00000000..b314972f
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_text.c
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+#include "appconfig_api_text.h"
+
+const char *appconfig_get(struct config *root, const char *section, const char *name, const char *default_value) {
+ struct config_option *opt = appconfig_get_raw_value(root, section, name, default_value, CONFIG_VALUE_TYPE_TEXT, NULL);
+ if(!opt)
+ return default_value;
+
+ return string2str(opt->value);
+}
+
+const char *appconfig_set(struct config *root, const char *section, const char *name, const char *value) {
+ struct config_option *opt = appconfig_set_raw_value(root, section, name, value, CONFIG_VALUE_TYPE_TEXT);
+ return string2str(opt->value);
+}
diff --git a/src/libnetdata/config/appconfig_api_text.h b/src/libnetdata/config/appconfig_api_text.h
new file mode 100644
index 00000000..7e1e85f7
--- /dev/null
+++ b/src/libnetdata/config/appconfig_api_text.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_APPCONFIG_API_TEXT_H
+#define NETDATA_APPCONFIG_API_TEXT_H
+
+const char *appconfig_get(struct config *root, const char *section, const char *name, const char *default_value);
+const char *appconfig_set(struct config *root, const char *section, const char *name, const char *value);
+#define config_get(section, name, default_value) appconfig_get(&netdata_config, section, name, default_value)
+#define config_set(section, name, default_value) appconfig_set(&netdata_config, section, name, default_value)
+
+
+#endif //NETDATA_APPCONFIG_API_TEXT_H
diff --git a/src/libnetdata/config/appconfig_cleanup.c b/src/libnetdata/config/appconfig_cleanup.c
new file mode 100644
index 00000000..22f4ac3e
--- /dev/null
+++ b/src/libnetdata/config/appconfig_cleanup.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+
+void appconfig_section_destroy_non_loaded(struct config *root, const char *section)
+{
+ struct config_section *sect;
+ struct config_option *opt;
+
+ netdata_log_debug(D_CONFIG, "Destroying section '%s'.", section);
+
+ sect = appconfig_section_find(root, section);
+ if(!sect) {
+ netdata_log_error("Could not destroy section '%s'. Not found.", section);
+ return;
+ }
+
+ SECTION_LOCK(sect);
+
+ // find if there is any loaded option
+ for(opt = sect->values; opt; opt = opt->next) {
+ if (opt->flags & CONFIG_VALUE_LOADED) {
+ // do not destroy values that were loaded from the configuration files.
+ SECTION_UNLOCK(sect);
+ return;
+ }
+ }
+
+ // no option is loaded, free them all
+ appconfig_section_remove_and_delete(root, sect, false, true);
+}
+
+void appconfig_section_option_destroy_non_loaded(struct config *root, const char *section, const char *name) {
+ struct config_section *sect;
+ sect = appconfig_section_find(root, section);
+ if (!sect) {
+ netdata_log_error("Could not destroy section option '%s -> %s'. The section not found.", section, name);
+ return;
+ }
+
+ SECTION_LOCK(sect);
+
+ struct config_option *opt = appconfig_option_find(sect, name);
+ if (opt && opt->flags & CONFIG_VALUE_LOADED) {
+ SECTION_UNLOCK(sect);
+ return;
+ }
+
+ if (unlikely(!(opt && appconfig_option_del(sect, opt)))) {
+ SECTION_UNLOCK(sect);
+ netdata_log_error("Could not destroy section option '%s -> %s'. The option not found.", section, name);
+ return;
+ }
+
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(sect->values, opt, prev, next);
+
+ appconfig_option_free(opt);
+ SECTION_UNLOCK(sect);
+}
+
diff --git a/src/libnetdata/config/appconfig_conf_file.c b/src/libnetdata/config/appconfig_conf_file.c
new file mode 100644
index 00000000..4ac8b376
--- /dev/null
+++ b/src/libnetdata/config/appconfig_conf_file.c
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+
+ENUM_STR_MAP_DEFINE(CONFIG_VALUE_TYPES) = {
+ { .id = CONFIG_VALUE_TYPE_UNKNOWN, .name ="unknown", },
+ { .id = CONFIG_VALUE_TYPE_TEXT, .name ="text", },
+ { .id = CONFIG_VALUE_TYPE_HOSTNAME, .name ="hostname", },
+ { .id = CONFIG_VALUE_TYPE_USERNAME, .name ="username", },
+ { .id = CONFIG_VALUE_TYPE_FILENAME, .name ="filename", },
+ { .id = CONFIG_VALUE_TYPE_PATH, .name ="path", },
+ { .id = CONFIG_VALUE_TYPE_SIMPLE_PATTERN, .name ="simple pattern", },
+ { .id = CONFIG_VALUE_TYPE_URL, .name ="URL", },
+ { .id = CONFIG_VALUE_TYPE_ENUM, .name ="one of keywords", },
+ { .id = CONFIG_VALUE_TYPE_BITMAP, .name ="any of keywords", },
+ { .id = CONFIG_VALUE_TYPE_INTEGER, .name ="number (integer)", },
+ { .id = CONFIG_VALUE_TYPE_DOUBLE, .name ="number (double)", },
+ { .id = CONFIG_VALUE_TYPE_BOOLEAN, .name ="yes or no", },
+ { .id = CONFIG_VALUE_TYPE_BOOLEAN_ONDEMAND, .name ="yes, no, or auto", },
+ { .id = CONFIG_VALUE_TYPE_DURATION_IN_SECS, .name ="duration (seconds)", },
+ { .id = CONFIG_VALUE_TYPE_DURATION_IN_MS, .name ="duration (ms)", },
+ { .id = CONFIG_VALUE_TYPE_DURATION_IN_DAYS, .name ="duration (days)", },
+ { .id = CONFIG_VALUE_TYPE_SIZE_IN_BYTES, .name ="size (bytes)", },
+ { .id = CONFIG_VALUE_TYPE_SIZE_IN_MB, .name ="size (MiB)", },
+};
+
+ENUM_STR_DEFINE_FUNCTIONS(CONFIG_VALUE_TYPES, CONFIG_VALUE_TYPE_UNKNOWN, "unknown");
+
+
+// ----------------------------------------------------------------------------
+// config load/save
+
+int appconfig_load(struct config *root, char *filename, int overwrite_used, const char *section_name) {
+ int line = 0;
+ struct config_section *sect = NULL;
+ int is_exporter_config = 0;
+ int _connectors = 0; // number of exporting connector sections we have
+ char working_instance[CONFIG_MAX_NAME + 1];
+ char working_connector[CONFIG_MAX_NAME + 1];
+ struct config_section *working_connector_section = NULL;
+ int global_exporting_section = 0;
+
+ char buffer[CONFIG_FILE_LINE_MAX + 1], *s;
+
+ if(!filename) filename = CONFIG_DIR "/" CONFIG_FILENAME;
+
+ netdata_log_debug(D_CONFIG, "CONFIG: opening config file '%s'", filename);
+
+ FILE *fp = fopen(filename, "r");
+ if(!fp) {
+ if(errno != ENOENT)
+ netdata_log_info("CONFIG: cannot open file '%s'. Using internal defaults.", filename);
+
+ return 0;
+ }
+
+ CLEAN_STRING *section_string = string_strdupz(section_name);
+ is_exporter_config = (strstr(filename, EXPORTING_CONF) != NULL);
+
+ while(fgets(buffer, CONFIG_FILE_LINE_MAX, fp) != NULL) {
+ buffer[CONFIG_FILE_LINE_MAX] = '\0';
+ line++;
+
+ s = trim(buffer);
+ if(!s || *s == '#') {
+ netdata_log_debug(D_CONFIG, "CONFIG: ignoring line %d of file '%s', it is empty.", line, filename);
+ continue;
+ }
+
+ int len = (int) strlen(s);
+ if(*s == '[' && s[len - 1] == ']') {
+ // new section
+ s[len - 1] = '\0';
+ s++;
+
+ if (is_exporter_config) {
+ global_exporting_section = !(strcmp(s, CONFIG_SECTION_EXPORTING)) || !(strcmp(s, CONFIG_SECTION_PROMETHEUS));
+
+ if (unlikely(!global_exporting_section)) {
+ int rc;
+ rc = is_valid_connector(s, 0);
+ if (likely(rc)) {
+ strncpyz(working_connector, s, CONFIG_MAX_NAME);
+ s = s + rc + 1;
+ if (unlikely(!(*s))) {
+ _connectors++;
+ sprintf(buffer, "instance_%d", _connectors);
+ s = buffer;
+ }
+ strncpyz(working_instance, s, CONFIG_MAX_NAME);
+ working_connector_section = NULL;
+ if (unlikely(appconfig_section_find(root, working_instance))) {
+ netdata_log_error("Instance (%s) already exists", working_instance);
+ sect = NULL;
+ continue;
+ }
+ }
+ else {
+ sect = NULL;
+ netdata_log_error("Section (%s) does not specify a valid connector", s);
+ continue;
+ }
+ }
+ }
+
+ sect = appconfig_section_find(root, s);
+ if(!sect)
+ sect = appconfig_section_create(root, s);
+
+ if(sect && section_string && overwrite_used && section_string == sect->name) {
+ SECTION_LOCK(sect);
+
+ while(sect->values)
+ appconfig_option_remove_and_delete(sect, sect->values, true);
+
+ SECTION_UNLOCK(sect);
+ }
+
+ continue;
+ }
+
+ if(!sect) {
+ // line outside a section
+ netdata_log_error("CONFIG: ignoring line %d ('%s') of file '%s', it is outside all sections.", line, s, filename);
+ continue;
+ }
+
+ if(section_string && overwrite_used && section_string != sect->name)
+ continue;
+
+ char *name = s;
+ char *value = strchr(s, '=');
+ if(!value) {
+ netdata_log_error("CONFIG: ignoring line %d ('%s') of file '%s', there is no = in it.", line, s, filename);
+ continue;
+ }
+ *value = '\0';
+ value++;
+
+ name = trim(name);
+ value = trim(value);
+
+ if(!name || *name == '#') {
+ netdata_log_error("CONFIG: ignoring line %d of file '%s', name is empty.", line, filename);
+ continue;
+ }
+
+ if(!value) value = "";
+
+ struct config_option *opt = appconfig_option_find(sect, name);
+
+ if (!opt) {
+ opt = appconfig_option_create(sect, name, value);
+ if (likely(is_exporter_config) && unlikely(!global_exporting_section)) {
+ if (unlikely(!working_connector_section)) {
+ working_connector_section = appconfig_section_find(root, working_connector);
+ if (!working_connector_section)
+ working_connector_section = appconfig_section_create(root, working_connector);
+ if (likely(working_connector_section)) {
+ add_connector_instance(working_connector_section, sect);
+ }
+ }
+ }
+ }
+ else {
+ if (((opt->flags & CONFIG_VALUE_USED) && overwrite_used) || !(opt->flags & CONFIG_VALUE_USED)) {
+ string_freez(opt->value);
+ opt->value = string_strdupz(value);
+ }
+ }
+ opt->flags |= CONFIG_VALUE_LOADED;
+ }
+
+ fclose(fp);
+
+ return 1;
+}
+
+void appconfig_generate(struct config *root, BUFFER *wb, int only_changed, bool netdata_conf)
+{
+ int i, pri;
+ struct config_section *sect;
+ struct config_option *opt;
+
+ {
+ int found_host_labels = 0;
+ for (sect = root->sections; sect; sect = sect->next)
+ if(!string_strcmp(sect->name, CONFIG_SECTION_HOST_LABEL))
+ found_host_labels = 1;
+
+ if(netdata_conf && !found_host_labels) {
+ appconfig_section_create(root, CONFIG_SECTION_HOST_LABEL);
+ appconfig_get_raw_value(root, CONFIG_SECTION_HOST_LABEL, "name", "value", CONFIG_VALUE_TYPE_TEXT, NULL);
+ }
+ }
+
+ if(netdata_conf) {
+ buffer_strcat(wb,
+ "# netdata configuration\n"
+ "#\n"
+ "# You can download the latest version of this file, using:\n"
+ "#\n"
+ "# wget -O /etc/netdata/netdata.conf http://localhost:19999/netdata.conf\n"
+ "# or\n"
+ "# curl -o /etc/netdata/netdata.conf http://localhost:19999/netdata.conf\n"
+ "#\n"
+ "# You can uncomment and change any of the options below.\n"
+ "# The value shown in the commented settings, is the default value.\n"
+ "#\n"
+ "\n# global netdata configuration\n");
+ }
+
+ for(i = 0; i <= 17 ;i++) {
+ APPCONFIG_LOCK(root);
+ for(sect = root->sections; sect; sect = sect->next) {
+ if(!string_strcmp(sect->name, CONFIG_SECTION_GLOBAL)) pri = 0;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_DB)) pri = 1;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_DIRECTORIES)) pri = 2;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_LOGS)) pri = 3;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_ENV_VARS)) pri = 4;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_HOST_LABEL)) pri = 5;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_SQLITE)) pri = 6;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_CLOUD)) pri = 7;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_ML)) pri = 8;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_HEALTH)) pri = 9;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_WEB)) pri = 10;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_WEBRTC)) pri = 11;
+ // by default, new sections will get pri = 12 (set at the end, below)
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_REGISTRY)) pri = 13;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_GLOBAL_STATISTICS)) pri = 14;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_PLUGINS)) pri = 15;
+ else if(!string_strcmp(sect->name, CONFIG_SECTION_STATSD)) pri = 16;
+ else if(!string_strncmp(sect->name, "plugin:", 7)) pri = 17; // << change the loop too if you change this
+ else pri = 12; // this is used for any new (currently unknown) sections
+
+ if(i == pri) {
+ int loaded = 0;
+ int used = 0;
+ int changed = 0;
+ int count = 0;
+
+ SECTION_LOCK(sect);
+ for(opt = sect->values; opt; opt = opt->next) {
+ used += (opt->flags & CONFIG_VALUE_USED)?1:0;
+ loaded += (opt->flags & CONFIG_VALUE_LOADED)?1:0;
+ changed += (opt->flags & CONFIG_VALUE_CHANGED)?1:0;
+ count++;
+ }
+ SECTION_UNLOCK(sect);
+
+ if(!count) continue;
+ if(only_changed && !changed && !loaded) continue;
+
+ if(!used)
+ buffer_sprintf(wb, "\n# section '%s' is not used.", string2str(sect->name));
+
+ buffer_sprintf(wb, "\n[%s]\n", string2str(sect->name));
+
+ size_t options_added = 0;
+ bool last_had_comments = false;
+ SECTION_LOCK(sect);
+ for(opt = sect->values; opt; opt = opt->next) {
+ bool unused = used && !(opt->flags & CONFIG_VALUE_USED);
+ bool migrated = used && (opt->flags & CONFIG_VALUE_MIGRATED);
+ bool reformatted = used && (opt->flags & CONFIG_VALUE_REFORMATTED);
+ bool show_default = used && (opt->flags & (CONFIG_VALUE_LOADED|CONFIG_VALUE_CHANGED) && opt->value_default);
+
+ if((unused || migrated || reformatted || show_default)) {
+ if(options_added)
+ buffer_strcat(wb, "\n");
+
+ buffer_sprintf(wb, "\t#| >>> [%s].%s <<<\n",
+ string2str(sect->name), string2str(opt->name));
+
+ last_had_comments = true;
+ }
+ else if(last_had_comments) {
+ buffer_strcat(wb, "\n");
+ last_had_comments = false;
+ }
+
+ if(unused)
+ buffer_sprintf(wb, "\t#| found in the config file, but is not used\n");
+
+ if(migrated && reformatted)
+ buffer_sprintf(wb, "\t#| migrated from: [%s].%s = %s\n",
+ string2str(opt->migrated.section), string2str(opt->migrated.name),
+ string2str(opt->value_original));
+ else {
+ if (migrated)
+ buffer_sprintf(wb, "\t#| migrated from: [%s].%s\n",
+ string2str(opt->migrated.section), string2str(opt->migrated.name));
+
+ if (reformatted)
+ buffer_sprintf(wb, "\t#| reformatted from: %s\n",
+ string2str(opt->value_original));
+ }
+
+ if(show_default)
+ buffer_sprintf(wb, "\t#| datatype: %s, default value: %s\n",
+ CONFIG_VALUE_TYPES_2str(opt->type),
+ string2str(opt->value_default));
+
+ buffer_sprintf(wb, "\t%s%s = %s\n",
+ (
+ !(opt->flags & CONFIG_VALUE_LOADED) &&
+ !(opt->flags & CONFIG_VALUE_CHANGED) &&
+ (opt->flags & CONFIG_VALUE_USED)
+ ) ? "# " : "",
+ string2str(opt->name),
+ string2str(opt->value));
+
+ options_added++;
+ }
+ SECTION_UNLOCK(sect);
+ }
+ }
+ APPCONFIG_UNLOCK(root);
+ }
+}
diff --git a/src/libnetdata/config/appconfig_exporters.c b/src/libnetdata/config/appconfig_exporters.c
new file mode 100644
index 00000000..1fafb298
--- /dev/null
+++ b/src/libnetdata/config/appconfig_exporters.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+
+/*
+ * @Input:
+ * Connector / instance to add to an internal structure
+ * @Return
+ * The current head of the linked list of connector_instance
+ *
+ */
+
+_CONNECTOR_INSTANCE *add_connector_instance(struct config_section *connector, struct config_section *instance)
+{
+ static struct _connector_instance *global_connector_instance = NULL;
+ struct _connector_instance *local_ci, *local_ci_tmp;
+
+ if (unlikely(!connector)) {
+ if (unlikely(!instance))
+ return global_connector_instance;
+
+ local_ci = global_connector_instance;
+ while (local_ci) {
+ local_ci_tmp = local_ci->next;
+ freez(local_ci);
+ local_ci = local_ci_tmp;
+ }
+ global_connector_instance = NULL;
+ return NULL;
+ }
+
+ local_ci = callocz(1, sizeof(struct _connector_instance));
+ local_ci->instance = instance;
+ local_ci->connector = connector;
+ strncpyz(local_ci->instance_name, string2str(instance->name), CONFIG_MAX_NAME);
+ strncpyz(local_ci->connector_name, string2str(connector->name), CONFIG_MAX_NAME);
+ local_ci->next = global_connector_instance;
+ global_connector_instance = local_ci;
+
+ return global_connector_instance;
+}
+
+int is_valid_connector(char *type, int check_reserved) {
+ int rc = 1;
+
+ if (unlikely(!type))
+ return 0;
+
+ if (!check_reserved) {
+ if (unlikely(is_valid_connector(type,1))) {
+ return 0;
+ }
+ //if (unlikely(*type == ':')
+ // return 0;
+ char *separator = strrchr(type, ':');
+ if (likely(separator)) {
+ *separator = '\0';
+ rc = (int)(separator - type);
+ } else
+ return 0;
+ }
+ // else {
+ // if (unlikely(is_valid_connector(type,1))) {
+ // netdata_log_error("Section %s invalid -- reserved name", type);
+ // return 0;
+ // }
+ // }
+
+ if (!strcmp(type, "graphite") || !strcmp(type, "graphite:plaintext")) {
+ return rc;
+ } else if (!strcmp(type, "graphite:http") || !strcmp(type, "graphite:https")) {
+ return rc;
+ } else if (!strcmp(type, "json") || !strcmp(type, "json:plaintext")) {
+ return rc;
+ } else if (!strcmp(type, "json:http") || !strcmp(type, "json:https")) {
+ return rc;
+ } else if (!strcmp(type, "opentsdb") || !strcmp(type, "opentsdb:telnet")) {
+ return rc;
+ } else if (!strcmp(type, "opentsdb:http") || !strcmp(type, "opentsdb:https")) {
+ return rc;
+ } else if (!strcmp(type, "prometheus_remote_write")) {
+ return rc;
+ } else if (!strcmp(type, "prometheus_remote_write:http") || !strcmp(type, "prometheus_remote_write:https")) {
+ return rc;
+ } else if (!strcmp(type, "kinesis") || !strcmp(type, "kinesis:plaintext")) {
+ return rc;
+ } else if (!strcmp(type, "pubsub") || !strcmp(type, "pubsub:plaintext")) {
+ return rc;
+ } else if (!strcmp(type, "mongodb") || !strcmp(type, "mongodb:plaintext")) {
+ return rc;
+ }
+
+ return 0;
+}
+
diff --git a/src/libnetdata/config/appconfig_internals.h b/src/libnetdata/config/appconfig_internals.h
new file mode 100644
index 00000000..492e8ce5
--- /dev/null
+++ b/src/libnetdata/config/appconfig_internals.h
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_APPCONFIG_INTERNALS_H
+#define NETDATA_APPCONFIG_INTERNALS_H
+
+#include "appconfig.h"
+
+typedef enum __attribute__((packed)) {
+ CONFIG_VALUE_TYPE_UNKNOWN = 0,
+ CONFIG_VALUE_TYPE_TEXT,
+ CONFIG_VALUE_TYPE_HOSTNAME,
+ CONFIG_VALUE_TYPE_USERNAME,
+ CONFIG_VALUE_TYPE_FILENAME,
+ CONFIG_VALUE_TYPE_PATH,
+ CONFIG_VALUE_TYPE_SIMPLE_PATTERN,
+ CONFIG_VALUE_TYPE_URL,
+ CONFIG_VALUE_TYPE_ENUM,
+ CONFIG_VALUE_TYPE_BITMAP,
+ CONFIG_VALUE_TYPE_INTEGER,
+ CONFIG_VALUE_TYPE_DOUBLE,
+ CONFIG_VALUE_TYPE_BOOLEAN,
+ CONFIG_VALUE_TYPE_BOOLEAN_ONDEMAND,
+ CONFIG_VALUE_TYPE_DURATION_IN_SECS,
+ CONFIG_VALUE_TYPE_DURATION_IN_MS,
+ CONFIG_VALUE_TYPE_DURATION_IN_DAYS,
+ CONFIG_VALUE_TYPE_SIZE_IN_BYTES,
+ CONFIG_VALUE_TYPE_SIZE_IN_MB,
+} CONFIG_VALUE_TYPES;
+
+typedef enum __attribute__((packed)) {
+ CONFIG_VALUE_LOADED = (1 << 0), // has been loaded from the config
+ CONFIG_VALUE_USED = (1 << 1), // has been accessed from the program
+ CONFIG_VALUE_CHANGED = (1 << 2), // has been changed from the loaded value or the internal default value
+ CONFIG_VALUE_CHECKED = (1 << 3), // has been checked if the value is different from the default
+ CONFIG_VALUE_MIGRATED = (1 << 4), // has been migrated from an old config
+ CONFIG_VALUE_REFORMATTED = (1 << 5), // has been reformatted with the official formatting
+} CONFIG_VALUE_FLAGS;
+
+struct config_option {
+ avl_t avl_node; // the index entry of this entry - this has to be first!
+
+ CONFIG_VALUE_TYPES type;
+ CONFIG_VALUE_FLAGS flags;
+
+ STRING *name;
+ STRING *value;
+
+ STRING *value_original; // the original value of this option (the first value it got, independently on how it got it)
+ STRING *value_default; // the internal default value of this option (the first value it got, from appconfig_get_XXX())
+
+ // when we move options around, this is where we keep the original
+ // section and name (of the first migration)
+ struct {
+ STRING *section;
+ STRING *name;
+ } migrated;
+
+ struct config_option *prev, *next; // config->mutex protects just this
+};
+
+struct config_section {
+ avl_t avl_node; // the index entry of this section - this has to be first!
+
+ STRING *name;
+
+ struct config_option *values;
+ avl_tree_lock values_index;
+
+ SPINLOCK spinlock;
+ struct config_section *prev, *next; // global config_mutex protects just this
+};
+
+// ----------------------------------------------------------------------------
+// locking
+
+#define APPCONFIG_LOCK(root) spinlock_lock(&((root)->spinlock))
+#define APPCONFIG_UNLOCK(root) spinlock_unlock(&((root)->spinlock))
+#define SECTION_LOCK(sect) spinlock_lock(&((sect)->spinlock))
+#define SECTION_UNLOCK(sect) spinlock_unlock(&((sect)->spinlock));
+
+// config sections
+void appconfig_section_free(struct config_section *sect);
+void appconfig_section_remove_and_delete(struct config *root, struct config_section *sect, bool have_root_lock, bool have_sect_lock);
+#define appconfig_section_add(root, cfg) (struct config_section *)avl_insert_lock(&(root)->index, (avl_t *)(cfg))
+#define appconfig_section_del(root, cfg) (struct config_section *)avl_remove_lock(&(root)->index, (avl_t *)(cfg))
+struct config_section *appconfig_section_find(struct config *root, const char *name);
+struct config_section *appconfig_section_create(struct config *root, const char *section);
+
+// config options
+void appconfig_option_cleanup(struct config_option *opt);
+void appconfig_option_free(struct config_option *opt);
+void appconfig_option_remove_and_delete(struct config_section *sect, struct config_option *opt, bool have_sect_lock);
+void appconfig_option_remove_and_delete_all(struct config_section *sect, bool have_sect_lock);
+int appconfig_option_compare(void *a, void *b);
+#define appconfig_option_add(co, cv) (struct config_option *)avl_insert_lock(&((co)->values_index), (avl_t *)(cv))
+#define appconfig_option_del(co, cv) (struct config_option *)avl_remove_lock(&((co)->values_index), (avl_t *)(cv))
+struct config_option *appconfig_option_find(struct config_section *sect, const char *name);
+struct config_option *appconfig_option_create(struct config_section *sect, const char *name, const char *value);
+
+// lookup
+int appconfig_get_boolean_by_section(struct config_section *sect, const char *name, int value);
+
+typedef STRING *(*reformat_t)(STRING *value);
+struct config_option *appconfig_get_raw_value_of_option_in_section(struct config_section *sect, const char *option, const char *default_value, CONFIG_VALUE_TYPES type, reformat_t cb);
+struct config_option *appconfig_get_raw_value(struct config *root, const char *section, const char *option, const char *default_value, CONFIG_VALUE_TYPES type, reformat_t cb);
+
+void appconfig_set_raw_value_of_option(struct config_option *opt, const char *value, CONFIG_VALUE_TYPES type);
+struct config_option *appconfig_set_raw_value_of_option_in_section(struct config_section *sect, const char *option, const char *value, CONFIG_VALUE_TYPES type);
+struct config_option *appconfig_set_raw_value(struct config *root, const char *section, const char *option, const char *value, CONFIG_VALUE_TYPES type);
+
+// cleanup
+void appconfig_section_destroy_non_loaded(struct config *root, const char *section);
+void appconfig_section_option_destroy_non_loaded(struct config *root, const char *section, const char *name);
+
+// exporters
+_CONNECTOR_INSTANCE *add_connector_instance(struct config_section *connector, struct config_section *instance);
+int is_valid_connector(char *type, int check_reserved);
+
+#endif //NETDATA_APPCONFIG_INTERNALS_H
diff --git a/src/libnetdata/config/appconfig_migrate.c b/src/libnetdata/config/appconfig_migrate.c
new file mode 100644
index 00000000..0c21ec06
--- /dev/null
+++ b/src/libnetdata/config/appconfig_migrate.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+
+int appconfig_move(struct config *root, const char *section_old, const char *name_old, const char *section_new, const char *name_new) {
+ struct config_option *opt_old, *opt_new;
+ int ret = -1;
+
+ netdata_log_debug(D_CONFIG, "request to rename config in section '%s', old name '%s', to section '%s', new name '%s'", section_old, name_old, section_new, name_new);
+
+ struct config_section *sect_old = appconfig_section_find(root, section_old);
+ if(!sect_old) return ret;
+
+ struct config_section *sect_new = appconfig_section_find(root, section_new);
+ if(!sect_new) sect_new = appconfig_section_create(root, section_new);
+
+ SECTION_LOCK(sect_old);
+ if(sect_old != sect_new)
+ SECTION_LOCK(sect_new);
+
+ opt_old = appconfig_option_find(sect_old, name_old);
+ if(!opt_old) goto cleanup;
+
+ opt_new = appconfig_option_find(sect_new, name_new);
+ if(opt_new) goto cleanup;
+
+ if(unlikely(appconfig_option_del(sect_old, opt_old) != opt_old))
+ netdata_log_error("INTERNAL ERROR: deletion of config '%s' from section '%s', deleted the wrong config entry.",
+ string2str(opt_old->name), string2str(sect_old->name));
+
+ // remember the old position of the item
+ struct config_option *opt_old_next = (sect_old == sect_new) ? opt_old->next : NULL;
+
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(sect_old->values, opt_old, prev, next);
+
+ nd_log(NDLS_DAEMON, NDLP_WARNING,
+ "CONFIG: option '[%s].%s' has been migrated to '[%s].%s'.",
+ section_old, name_old,
+ section_new, name_new);
+
+ if(!opt_old->migrated.name) {
+ string_freez(opt_old->migrated.section);
+ opt_old->migrated.section = string_dup(sect_old->name);
+ opt_old->migrated.name = opt_old->name;
+ }
+ else
+ string_freez(opt_old->name);
+
+ opt_old->name = string_strdupz(name_new);
+ opt_old->flags |= CONFIG_VALUE_MIGRATED;
+
+ opt_new = opt_old;
+
+ // put in the list, but try to keep the order
+ if(opt_old_next && sect_old == sect_new)
+ DOUBLE_LINKED_LIST_INSERT_ITEM_BEFORE_UNSAFE(sect_new->values, opt_old_next, opt_new, prev, next);
+ else {
+ // we don't have the old next item (probably a different section?)
+ // find the last MIGRATED one
+ struct config_option *t = sect_new->values ? sect_new->values->prev : NULL;
+ for (; t && t != sect_new->values ; t = t->prev) {
+ if (t->flags & CONFIG_VALUE_MIGRATED)
+ break;
+ }
+ if (t == sect_new->values)
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(sect_new->values, opt_new, prev, next);
+ else
+ DOUBLE_LINKED_LIST_INSERT_ITEM_AFTER_UNSAFE(sect_new->values, t, opt_new, prev, next);
+ }
+
+ if(unlikely(appconfig_option_add(sect_new, opt_old) != opt_old))
+ netdata_log_error("INTERNAL ERROR: re-indexing of config '%s' in section '%s', already exists.",
+ string2str(opt_old->name), string2str(sect_new->name));
+
+ ret = 0;
+
+cleanup:
+ if(sect_old != sect_new)
+ SECTION_UNLOCK(sect_new);
+ SECTION_UNLOCK(sect_old);
+ return ret;
+}
+
+int appconfig_move_everywhere(struct config *root, const char *name_old, const char *name_new) {
+ int ret = -1;
+ APPCONFIG_LOCK(root);
+ struct config_section *sect;
+ for(sect = root->sections; sect; sect = sect->next) {
+ if(appconfig_move(root, string2str(sect->name), name_old, string2str(sect->name), name_new) == 0)
+ ret = 0;
+ }
+ APPCONFIG_UNLOCK(root);
+ return ret;
+}
+
diff --git a/src/libnetdata/config/appconfig_options.c b/src/libnetdata/config/appconfig_options.c
new file mode 100644
index 00000000..f619d08a
--- /dev/null
+++ b/src/libnetdata/config/appconfig_options.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+
+// ----------------------------------------------------------------------------
+// config options index
+
+int appconfig_option_compare(void *a, void *b) {
+ if(((struct config_option *)a)->name < ((struct config_option *)b)->name) return -1;
+ else if(((struct config_option *)a)->name > ((struct config_option *)b)->name) return 1;
+ else return string_cmp(((struct config_option *)a)->name, ((struct config_option *)b)->name);
+}
+
+struct config_option *appconfig_option_find(struct config_section *sect, const char *name) {
+ struct config_option opt_tmp = {
+ .name = string_strdupz(name),
+ };
+
+ struct config_option *rc = (struct config_option *)avl_search_lock(&(sect->values_index), (avl_t *) &opt_tmp);
+
+ appconfig_option_cleanup(&opt_tmp);
+ return rc;
+}
+
+// ----------------------------------------------------------------------------
+// config options methods
+
+void appconfig_option_cleanup(struct config_option *opt) {
+ string_freez(opt->value);
+ string_freez(opt->name);
+ string_freez(opt->migrated.section);
+ string_freez(opt->migrated.name);
+ string_freez(opt->value_original);
+ string_freez(opt->value_default);
+
+ opt->value = NULL;
+ opt->name = NULL;
+ opt->migrated.section = NULL;
+ opt->migrated.name = NULL;
+ opt->value_original = NULL;
+ opt->value_default = NULL;
+}
+
+void appconfig_option_free(struct config_option *opt) {
+ appconfig_option_cleanup(opt);
+ freez(opt);
+}
+
+struct config_option *appconfig_option_create(struct config_section *sect, const char *name, const char *value) {
+ struct config_option *opt = callocz(1, sizeof(struct config_option));
+ opt->name = string_strdupz(name);
+ opt->value = string_strdupz(value);
+ opt->value_original = string_dup(opt->value);
+
+ struct config_option *opt_found = appconfig_option_add(sect, opt);
+ if(opt_found != opt) {
+ nd_log(NDLS_DAEMON, NDLP_INFO,
+ "CONFIG: config '%s' in section '%s': already exists - using the existing one.",
+ string2str(opt->name), string2str(sect->name));
+ appconfig_option_free(opt);
+ return opt_found;
+ }
+
+ SECTION_LOCK(sect);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(sect->values, opt, prev, next);
+ SECTION_UNLOCK(sect);
+
+ return opt;
+}
+
+void appconfig_option_remove_and_delete(struct config_section *sect, struct config_option *opt, bool have_sect_lock) {
+ struct config_option *opt_found = appconfig_option_del(sect, opt);
+ if(opt_found != opt) {
+ nd_log(NDLS_DAEMON, NDLP_ERR,
+ "INTERNAL ERROR: Cannot remove '%s' from section '%s', it was not inserted before.",
+ string2str(opt->name), string2str(sect->name));
+ return;
+ }
+
+ if(!have_sect_lock)
+ SECTION_LOCK(sect);
+
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(sect->values, opt, prev, next);
+
+ if(!have_sect_lock)
+ SECTION_UNLOCK(sect);
+
+ appconfig_option_free(opt);
+}
+
+void appconfig_option_remove_and_delete_all(struct config_section *sect, bool have_sect_lock) {
+ if(!have_sect_lock)
+ SECTION_LOCK(sect);
+
+ while(sect->values)
+ appconfig_option_remove_and_delete(sect, sect->values, true);
+
+ if(!have_sect_lock)
+ SECTION_UNLOCK(sect);
+}
+
+void appconfig_get_raw_value_of_option(struct config_option *opt, const char *default_value, CONFIG_VALUE_TYPES type, reformat_t cb) {
+ opt->flags |= CONFIG_VALUE_USED;
+
+ if(type != CONFIG_VALUE_TYPE_UNKNOWN)
+ opt->type = type;
+
+ if((opt->flags & CONFIG_VALUE_LOADED) || (opt->flags & CONFIG_VALUE_CHANGED)) {
+ // this is a loaded value from the config file
+ // if it is different from the default, mark it
+ if(!(opt->flags & CONFIG_VALUE_CHECKED)) {
+ if(!(opt->flags & CONFIG_VALUE_REFORMATTED) && cb) {
+ STRING *value_old = opt->value;
+ opt->value = cb(opt->value);
+ if(opt->value != value_old)
+ opt->flags |= CONFIG_VALUE_REFORMATTED;
+ }
+
+ if(default_value && string_strcmp(opt->value, default_value) != 0)
+ opt->flags |= CONFIG_VALUE_CHANGED;
+
+ opt->flags |= CONFIG_VALUE_CHECKED;
+ }
+ }
+
+ if(!opt->value_default)
+ opt->value_default = string_strdupz(default_value);
+}
+
+struct config_option *appconfig_get_raw_value_of_option_in_section(struct config_section *sect, const char *option, const char *default_value, CONFIG_VALUE_TYPES type, reformat_t cb) {
+ // Only calls internal to this file check for a NULL result, and they do not supply a NULL arg.
+ // External caller should treat NULL as an error case.
+ struct config_option *opt = appconfig_option_find(sect, option);
+ if (!opt) {
+ if (!default_value) return NULL;
+ opt = appconfig_option_create(sect, option, default_value);
+ if (!opt) return NULL;
+ }
+
+ appconfig_get_raw_value_of_option(opt, default_value, type, cb);
+ return opt;
+}
+
+struct config_option *appconfig_get_raw_value(struct config *root, const char *section, const char *option, const char *default_value, CONFIG_VALUE_TYPES type, reformat_t cb) {
+ struct config_section *sect = appconfig_section_find(root, section);
+ if(!sect) {
+ if(!default_value) return NULL;
+ sect = appconfig_section_create(root, section);
+ }
+
+ return appconfig_get_raw_value_of_option_in_section(sect, option, default_value, type, cb);
+}
+
+void appconfig_set_raw_value_of_option(struct config_option *opt, const char *value, CONFIG_VALUE_TYPES type) {
+ opt->flags |= CONFIG_VALUE_USED;
+
+ if(opt->type == CONFIG_VALUE_TYPE_UNKNOWN)
+ opt->type = type;
+
+ if(string_strcmp(opt->value, value) != 0) {
+ opt->flags |= CONFIG_VALUE_CHANGED;
+
+ string_freez(opt->value);
+ opt->value = string_strdupz(value);
+ }
+}
+
+struct config_option *appconfig_set_raw_value_of_option_in_section(struct config_section *sect, const char *option, const char *value, CONFIG_VALUE_TYPES type) {
+ struct config_option *opt = appconfig_option_find(sect, option);
+ if(!opt)
+ opt = appconfig_option_create(sect, option, value);
+
+ appconfig_set_raw_value_of_option(opt, value, type);
+ return opt;
+}
+
+struct config_option *appconfig_set_raw_value(struct config *root, const char *section, const char *option, const char *value, CONFIG_VALUE_TYPES type) {
+ struct config_section *sect = appconfig_section_find(root, section);
+ if(!sect)
+ sect = appconfig_section_create(root, section);
+
+ return appconfig_set_raw_value_of_option_in_section(sect, option, value, type);
+}
diff --git a/src/libnetdata/config/appconfig_sections.c b/src/libnetdata/config/appconfig_sections.c
new file mode 100644
index 00000000..2180803a
--- /dev/null
+++ b/src/libnetdata/config/appconfig_sections.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+
+// ----------------------------------------------------------------------------
+// config sections index
+
+int appconfig_section_compare(void *a, void *b) {
+ if(((struct config_section *)a)->name < ((struct config_section *)b)->name) return -1;
+ else if(((struct config_section *)a)->name > ((struct config_section *)b)->name) return 1;
+ else return string_cmp(((struct config_section *)a)->name, ((struct config_section *)b)->name);
+}
+
+struct config_section *appconfig_section_find(struct config *root, const char *name) {
+ struct config_section sect_tmp = {
+ .name = string_strdupz(name),
+ };
+
+ struct config_section *rc = (struct config_section *)avl_search_lock(&root->index, (avl_t *) &sect_tmp);
+ string_freez(sect_tmp.name);
+ return rc;
+}
+
+// ----------------------------------------------------------------------------
+// config section methods
+
+void appconfig_section_free(struct config_section *sect) {
+ avl_destroy_lock(&sect->values_index);
+ string_freez(sect->name);
+ freez(sect);
+}
+
+void appconfig_section_remove_and_delete(struct config *root, struct config_section *sect, bool have_root_lock, bool have_sect_lock) {
+ struct config_section *sect_found = appconfig_section_del(root, sect);
+ if(sect_found != sect) {
+ nd_log(NDLS_DAEMON, NDLP_ERR,
+ "INTERNAL ERROR: Cannot remove section '%s', it was not inserted before.",
+ string2str(sect->name));
+ return;
+ }
+
+ appconfig_option_remove_and_delete_all(sect, have_sect_lock);
+
+ if(!have_root_lock)
+ APPCONFIG_LOCK(root);
+
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(root->sections, sect, prev, next);
+
+ if(!have_root_lock)
+ APPCONFIG_UNLOCK(root);
+
+ // if the caller has the section lock, we will unlock it, to cleanup
+ if(have_sect_lock)
+ SECTION_UNLOCK(sect);
+
+ appconfig_section_free(sect);
+}
+
+struct config_section *appconfig_section_create(struct config *root, const char *section) {
+ struct config_section *sect = callocz(1, sizeof(struct config_section));
+ sect->name = string_strdupz(section);
+ spinlock_init(&sect->spinlock);
+
+ avl_init_lock(&sect->values_index, appconfig_option_compare);
+
+ struct config_section *sect_found = appconfig_section_add(root, sect);
+ if(sect_found != sect) {
+ nd_log(NDLS_DAEMON, NDLP_ERR,
+ "CONFIG: section '%s', already exists, using existing.",
+ string2str(sect->name));
+ appconfig_section_free(sect);
+ return sect_found;
+ }
+
+ APPCONFIG_LOCK(root);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(root->sections, sect, prev, next);
+ APPCONFIG_UNLOCK(root);
+
+ return sect;
+}
+
+
diff --git a/src/libnetdata/config/appconfig_traversal.c b/src/libnetdata/config/appconfig_traversal.c
new file mode 100644
index 00000000..f26def2c
--- /dev/null
+++ b/src/libnetdata/config/appconfig_traversal.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "appconfig_internals.h"
+
+size_t appconfig_foreach_value_in_section(struct config *root, const char *section, appconfig_foreach_value_cb_t cb, void *data) {
+ size_t used = 0;
+ struct config_section *co = appconfig_section_find(root, section);
+ if(co) {
+ SECTION_LOCK(co);
+ struct config_option *cv;
+ for(cv = co->values; cv ; cv = cv->next) {
+ if(cb(data, string2str(cv->name), string2str(cv->value))) {
+ cv->flags |= CONFIG_VALUE_USED;
+ used++;
+ }
+ }
+ SECTION_UNLOCK(co);
+ }
+
+ return used;
+}
diff --git a/src/libnetdata/config/dyncfg.c b/src/libnetdata/config/dyncfg.c
index 244864c6..81b050f8 100644
--- a/src/libnetdata/config/dyncfg.c
+++ b/src/libnetdata/config/dyncfg.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "../../libnetdata/libnetdata.h"
+#include "../libnetdata.h"
// ----------------------------------------------------------------------------
@@ -211,7 +211,7 @@ bool dyncfg_is_valid_id(const char *id) {
return true;
}
-static inline bool is_forbidden_char(char c) {
+static inline bool is_forbidden_filename_char(char c) {
if(isspace((uint8_t)c) || !isprint((uint8_t)c))
return true;
@@ -239,7 +239,7 @@ char *dyncfg_escape_id_for_filename(const char *id) {
char *dest = escaped;
while (*src) {
- if (is_forbidden_char(*src)) {
+ if (is_forbidden_filename_char(*src)) {
sprintf(dest, "%%%02X", (unsigned char)*src);
dest += 3;
} else {
@@ -277,7 +277,7 @@ int dyncfg_node_find_and_call(DICTIONARY *dyncfg_nodes, const char *transaction,
memcpy(buf, function, sizeof(buf));
char *words[MAX_FUNCTION_PARAMETERS]; // an array of pointers for the words in this line
- size_t num_words = quoted_strings_splitter_pluginsd(buf, words, MAX_FUNCTION_PARAMETERS);
+ size_t num_words = quoted_strings_splitter_whitespace(buf, words, MAX_FUNCTION_PARAMETERS);
const char *id = get_word(words, num_words, 1);
const char *action = get_word(words, num_words, 2);