diff options
Diffstat (limited to 'src/libnetdata/config/appconfig_conf_file.c')
-rw-r--r-- | src/libnetdata/config/appconfig_conf_file.c | 320 |
1 files changed, 320 insertions, 0 deletions
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); + } +} |