master
c 461 lines 19.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "inicfg_internals.h"
4
5 ENUM_STR_MAP_DEFINE(CONFIG_VALUE_TYPES) = {
6 { .id = CONFIG_VALUE_TYPE_UNKNOWN, .name ="unknown", },
7 { .id = CONFIG_VALUE_TYPE_TEXT, .name ="text", },
8 { .id = CONFIG_VALUE_TYPE_HOSTNAME, .name ="hostname", },
9 { .id = CONFIG_VALUE_TYPE_USERNAME, .name ="username", },
10 { .id = CONFIG_VALUE_TYPE_FILENAME, .name ="filename", },
11 { .id = CONFIG_VALUE_TYPE_PATH, .name ="path", },
12 { .id = CONFIG_VALUE_TYPE_SIMPLE_PATTERN, .name ="simple pattern", },
13 { .id = CONFIG_VALUE_TYPE_URL, .name ="URL", },
14 { .id = CONFIG_VALUE_TYPE_ENUM, .name ="one of keywords", },
15 { .id = CONFIG_VALUE_TYPE_BITMAP, .name ="any of keywords", },
16 { .id = CONFIG_VALUE_TYPE_INTEGER, .name ="number (integer)", },
17 { .id = CONFIG_VALUE_TYPE_DOUBLE, .name ="number (double)", },
18 { .id = CONFIG_VALUE_TYPE_BOOLEAN, .name ="yes or no", },
19 { .id = CONFIG_VALUE_TYPE_BOOLEAN_ONDEMAND, .name ="yes, no, or auto", },
20 { .id = CONFIG_VALUE_TYPE_DURATION_IN_SECS, .name ="duration (seconds)", },
21 { .id = CONFIG_VALUE_TYPE_DURATION_IN_MS, .name ="duration (ms)", },
22 { .id = CONFIG_VALUE_TYPE_DURATION_IN_DAYS_TO_SECONDS, .name ="duration (days)", },
23 { .id = CONFIG_VALUE_TYPE_SIZE_IN_BYTES, .name ="size (bytes)", },
24 { .id = CONFIG_VALUE_TYPE_SIZE_IN_MB, .name ="size (MiB)", },
25 };
26
27 ENUM_STR_DEFINE_FUNCTIONS(CONFIG_VALUE_TYPES, CONFIG_VALUE_TYPE_UNKNOWN, "unknown");
28
29 #if defined(OS_WINDOWS)
30 static bool inicfg_windows_is_path_list_env_var(const struct config_section *sect, const struct config_option *opt) {
31 return !string_strcmp(sect->name, CONFIG_SECTION_ENV_VARS) &&
32 (!string_strcmp(opt->name, "PATH") || !string_strcmp(opt->name, "PYTHONPATH"));
33 }
34
35 static bool inicfg_windows_is_quoted_path_list_dir_var(const struct config_section *sect, const struct config_option *opt) {
36 return !string_strcmp(sect->name, CONFIG_SECTION_DIRECTORIES) &&
37 !string_strcmp(opt->name, "plugins");
38 }
39
40 static bool inicfg_windows_is_log_path_setting(const struct config_section *sect, const struct config_option *opt) {
41 return ((!string_strcmp(sect->name, CONFIG_SECTION_LOGS) &&
42 (!string_strcmp(opt->name, "debug") ||
43 !string_strcmp(opt->name, "daemon") ||
44 !string_strcmp(opt->name, "collector") ||
45 !string_strcmp(opt->name, "access") ||
46 !string_strcmp(opt->name, "health"))) ||
47 (!string_strcmp(sect->name, CONFIG_SECTION_CLOUD) &&
48 !string_strcmp(opt->name, "conversation log file")));
49 }
50
51 static const char *inicfg_windows_path_list_for_display(const char *value, char *dst, size_t dst_size) {
52 if (!value || !*value || !dst || dst_size == 0)
53 return value ? value : "";
54
55 // Internal storage always uses ':' as separator (normalized on read).
56 // A ';' here means the value was never normalized, so treat it as already Windows-format.
57 if (strchr(value, ';')) {
58 snprintfz(dst, dst_size, "%s", value);
59 return dst;
60 }
61
62 dst[0] = '\0';
63 size_t len = 0;
64 size_t value_len = strnlen(value, CONFIG_MAX_VALUE);
65 const char *value_end = value + value_len;
66 bool first = true;
67
68 const char *segment_start = value;
69 while (segment_start <= value_end) {
70 if (segment_start == value_end)
71 break;
72
73 const char *separator = memchr(segment_start, ':', (size_t)(value_end - segment_start));
74 size_t segment_len = separator
75 ? (size_t)(separator - segment_start)
76 : value_len - (size_t)(segment_start - value);
77
78 char segment[CONFIG_MAX_VALUE + 1];
79 if (segment_len > CONFIG_MAX_VALUE)
80 segment_len = CONFIG_MAX_VALUE;
81
82 memcpy(segment, segment_start, segment_len);
83 segment[segment_len] = '\0';
84
85 CLEAN_CHAR_P *translated = os_translate_msys_to_windows_path(segment);
86 if (!first)
87 len = strcatz(dst, len, ";", dst_size);
88 len = strcatz(dst, len, translated, dst_size);
89 first = false;
90
91 if (!separator)
92 break;
93
94 segment_start = separator + 1;
95 }
96
97 return dst;
98 }
99
100 static const char *inicfg_windows_quoted_path_list_for_display(const char *value, char *dst, size_t dst_size) {
101 if (!value || !*value || !dst || dst_size == 0)
102 return value ? value : "";
103
104 CLEAN_CHAR_P *copy = strdupz(value);
105 // 256 slots matches the limit in reformat_quoted_path_list; entries beyond this are silently ignored.
106 char *words[256] = { 0 };
107 size_t num_words = quoted_strings_splitter_config(copy, words, _countof(words));
108 if (!num_words) {
109 snprintfz(dst, dst_size, "%s", value);
110 return dst;
111 }
112
113 dst[0] = '\0';
114 size_t len = 0;
115 for(size_t i = 0; i < num_words; i++) {
116 CLEAN_CHAR_P *translated = os_translate_msys_to_windows_path(words[i]);
117 if (i)
118 len = strcatz(dst, len, " ", dst_size);
119 len = strcatz(dst, len, "\"", dst_size);
120 len = strcatz(dst, len, translated, dst_size);
121 len = strcatz(dst, len, "\"", dst_size);
122 }
123
124 return dst;
125 }
126
127 static const char *inicfg_windows_value_for_display(
128 const struct config_section *sect,
129 const struct config_option *opt,
130 const char *value,
131 char *dst,
132 size_t dst_size)
133 {
134 if (!value || !*value)
135 return value ? value : "";
136
137 if (inicfg_windows_is_path_list_env_var(sect, opt))
138 return inicfg_windows_path_list_for_display(value, dst, dst_size);
139
140 if (inicfg_windows_is_quoted_path_list_dir_var(sect, opt))
141 return inicfg_windows_quoted_path_list_for_display(value, dst, dst_size);
142
143 if (inicfg_windows_is_log_path_setting(sect, opt))
144 return inicfg_log_path_setting_for_display(value, dst, dst_size);
145
146 // Translate all PATH/FILENAME-typed options, plus all remaining DIRECTORIES keys.
147 // The list-type keys handled above are ENV_VARS.PATH/PYTHONPATH and DIRECTORIES.plugins.
148 // If a new list-type key is added to either section, add a dedicated check before this fallback.
149 if ((opt->type != CONFIG_VALUE_TYPE_PATH && opt->type != CONFIG_VALUE_TYPE_FILENAME) &&
150 string_strcmp(sect->name, CONFIG_SECTION_DIRECTORIES) != 0)
151 return value;
152
153 return os_translate_path(dst, value, dst_size);
154 }
155 #endif
156
157
158 // ----------------------------------------------------------------------------
159 // config load/save
160
161 int inicfg_load(struct config *root, char *filename, int overwrite_used, const char *section_name) {
162 int line = 0;
163 struct config_section *sect = NULL;
164 int is_exporter_config = 0;
165 int _connectors = 0; // number of exporting connector sections we have
166 char working_instance[CONFIG_MAX_NAME + 1];
167 char working_connector[CONFIG_MAX_NAME + 1];
168 struct config_section *working_connector_section = NULL;
169 int global_exporting_section = 0;
170
171 char buffer[CONFIG_FILE_LINE_MAX + 1], *s;
172
173 if(!filename) filename = CONFIG_DIR "/" CONFIG_FILENAME;
174
175 netdata_log_debug(D_CONFIG, "CONFIG: opening config file '%s'", filename);
176
177 FILE *fp = fopen(filename, "r");
178 if(!fp) {
179 if(errno != ENOENT)
180 netdata_log_info("CONFIG: cannot open file '%s'. Using internal defaults.", filename);
181
182 return 0;
183 }
184
185 CLEAN_STRING *section_string = string_strdupz(section_name);
186 is_exporter_config = (strstr(filename, EXPORTING_CONF) != NULL);
187
188 while(fgets(buffer, CONFIG_FILE_LINE_MAX, fp) != NULL) {
189 buffer[CONFIG_FILE_LINE_MAX] = '\0';
190 line++;
191
192 s = trim(buffer);
193 if(!s || !*s || *s == '#') {
194 netdata_log_debug(D_CONFIG, "CONFIG: ignoring line %d of file '%s', it is empty.", line, filename);
195 continue;
196 }
197
198 int len = (int) strlen(s);
199 if(*s == '[' && s[len - 1] == ']') {
200 // new section
201 s[len - 1] = '\0';
202 s++;
203
204 if (is_exporter_config) {
205 global_exporting_section = !(strcmp(s, CONFIG_SECTION_EXPORTING)) || !(strcmp(s, CONFIG_SECTION_PROMETHEUS));
206
207 if (unlikely(!global_exporting_section)) {
208 int rc;
209 rc = is_valid_connector(s, 0);
210 if (likely(rc)) {
211 strncpyz(working_connector, s, CONFIG_MAX_NAME);
212 s = s + rc + 1;
213 if (unlikely(!(*s))) {
214 _connectors++;
215 sprintf(buffer, "instance_%d", _connectors);
216 s = buffer;
217 }
218 strncpyz(working_instance, s, CONFIG_MAX_NAME);
219 working_connector_section = NULL;
220 if (unlikely(inicfg_section_find(root, working_instance))) {
221 netdata_log_error("Instance (%s) already exists", working_instance);
222 sect = NULL;
223 continue;
224 }
225 }
226 else {
227 sect = NULL;
228 netdata_log_error("Section (%s) does not specify a valid connector", s);
229 continue;
230 }
231 }
232 }
233
234 sect = inicfg_section_find(root, s);
235 if(!sect)
236 sect = inicfg_section_create(root, s);
237
238 if(sect && section_string && overwrite_used && section_string == sect->name) {
239 SECTION_LOCK(sect);
240
241 while(sect->values)
242 inicfg_option_remove_and_delete(sect, sect->values, true);
243
244 SECTION_UNLOCK(sect);
245 }
246
247 continue;
248 }
249
250 if(!sect) {
251 // line outside a section
252 netdata_log_error("CONFIG: ignoring line %d ('%s') of file '%s', it is outside all sections.", line, s, filename);
253 continue;
254 }
255
256 if(section_string && overwrite_used && section_string != sect->name)
257 continue;
258
259 char *name = s;
260 char *value = strchr(s, '=');
261 if(!value) {
262 netdata_log_error("CONFIG: ignoring line %d ('%s') of file '%s', there is no = in it.", line, s, filename);
263 continue;
264 }
265 *value = '\0';
266 value++;
267
268 name = trim(name);
269 value = trim(value);
270
271 if(!name || *name == '#') {
272 netdata_log_error("CONFIG: ignoring line %d of file '%s', name is empty.", line, filename);
273 continue;
274 }
275
276 if(!value) value = "";
277
278 struct config_option *opt = inicfg_option_find(sect, name);
279
280 if (!opt) {
281 opt = inicfg_option_create(sect, name, value);
282 if (likely(is_exporter_config) && unlikely(!global_exporting_section)) {
283 if (unlikely(!working_connector_section)) {
284 working_connector_section = inicfg_section_find(root, working_connector);
285 if (!working_connector_section)
286 working_connector_section = inicfg_section_create(root, working_connector);
287 if (likely(working_connector_section)) {
288 add_connector_instance(working_connector_section, sect);
289 }
290 }
291 }
292 }
293 else {
294 if (((opt->flags & CONFIG_VALUE_USED) && overwrite_used) || !(opt->flags & CONFIG_VALUE_USED)) {
295 string_freez(opt->value);
296 opt->value = string_strdupz(value);
297 }
298 }
299 opt->flags |= CONFIG_VALUE_LOADED;
300 }
301
302 fclose(fp);
303
304 return 1;
305 }
306
307 void inicfg_generate(struct config *root, BUFFER *wb, int only_changed, bool netdata_conf)
308 {
309 int i, pri;
310 struct config_section *sect;
311 struct config_option *opt;
312
313 {
314 int found_host_labels = 0;
315 for (sect = root->sections; sect; sect = sect->next)
316 if(!string_strcmp(sect->name, CONFIG_SECTION_HOST_LABEL))
317 found_host_labels = 1;
318
319 if(netdata_conf && !found_host_labels) {
320 inicfg_section_create(root, CONFIG_SECTION_HOST_LABEL);
321 inicfg_get_raw_value(root, CONFIG_SECTION_HOST_LABEL, "name", "value", CONFIG_VALUE_TYPE_TEXT, NULL);
322 }
323 }
324
325 if(netdata_conf) {
326 buffer_strcat(wb,
327 "# netdata configuration\n"
328 "#\n"
329 "# You can download the latest version of this file, using:\n"
330 "#\n"
331 "# wget -O /etc/netdata/netdata.conf http://localhost:19999/netdata.conf\n"
332 "# or\n"
333 "# curl -o /etc/netdata/netdata.conf http://localhost:19999/netdata.conf\n"
334 "#\n"
335 "# You can uncomment and change any of the options below.\n"
336 "# The value shown in the commented settings, is the default value.\n"
337 "#\n"
338 "\n# global netdata configuration\n");
339 }
340
341 for(i = 0; i <= 17 ;i++) {
342 APPCONFIG_LOCK(root);
343 for(sect = root->sections; sect; sect = sect->next) {
344 if(!string_strcmp(sect->name, CONFIG_SECTION_GLOBAL)) pri = 0;
345 else if(!string_strcmp(sect->name, CONFIG_SECTION_DB)) pri = 1;
346 else if(!string_strcmp(sect->name, CONFIG_SECTION_DIRECTORIES)) pri = 2;
347 else if(!string_strcmp(sect->name, CONFIG_SECTION_LOGS)) pri = 3;
348 else if(!string_strcmp(sect->name, CONFIG_SECTION_ENV_VARS)) pri = 4;
349 else if(!string_strcmp(sect->name, CONFIG_SECTION_HOST_LABEL)) pri = 5;
350 else if(!string_strcmp(sect->name, CONFIG_SECTION_SQLITE)) pri = 6;
351 else if(!string_strcmp(sect->name, CONFIG_SECTION_CLOUD)) pri = 7;
352 else if(!string_strcmp(sect->name, CONFIG_SECTION_ML)) pri = 8;
353 else if(!string_strcmp(sect->name, CONFIG_SECTION_HEALTH)) pri = 9;
354 else if(!string_strcmp(sect->name, CONFIG_SECTION_WEB)) pri = 10;
355 else if(!string_strcmp(sect->name, CONFIG_SECTION_WEBRTC)) pri = 11;
356 // by default, new sections will get pri = 12 (set at the end, below)
357 else if(!string_strcmp(sect->name, CONFIG_SECTION_REGISTRY)) pri = 13;
358 else if(!string_strcmp(sect->name, CONFIG_SECTION_PULSE)) pri = 14;
359 else if(!string_strcmp(sect->name, CONFIG_SECTION_PLUGINS)) pri = 15;
360 else if(!string_strcmp(sect->name, CONFIG_SECTION_STATSD)) pri = 16;
361 else if(!string_strncmp(sect->name, "plugin:", 7)) pri = 17; // << change the loop too if you change this
362 else pri = 12; // this is used for any new (currently unknown) sections
363
364 if(i == pri) {
365 int loaded = 0;
366 int used = 0;
367 int changed = 0;
368 int count = 0;
369
370 SECTION_LOCK(sect);
371 for(opt = sect->values; opt; opt = opt->next) {
372 used += (opt->flags & CONFIG_VALUE_USED)?1:0;
373 loaded += (opt->flags & CONFIG_VALUE_LOADED)?1:0;
374 changed += (opt->flags & CONFIG_VALUE_CHANGED)?1:0;
375 count++;
376 }
377 SECTION_UNLOCK(sect);
378
379 if(!count) continue;
380 if(only_changed && !changed && !loaded) continue;
381
382 if(!used)
383 buffer_sprintf(wb, "\n# section '%s' is not used.", string2str(sect->name));
384
385 buffer_sprintf(wb, "\n[%s]\n", string2str(sect->name));
386
387 size_t options_added = 0;
388 bool last_had_comments = false;
389 SECTION_LOCK(sect);
390 for(opt = sect->values; opt; opt = opt->next) {
391 bool unused = used && !(opt->flags & CONFIG_VALUE_USED);
392 bool migrated = used && (opt->flags & CONFIG_VALUE_MIGRATED);
393 bool reformatted = used && (opt->flags & CONFIG_VALUE_REFORMATTED);
394 bool show_default = used && (opt->flags & (CONFIG_VALUE_LOADED|CONFIG_VALUE_CHANGED) && opt->value_default);
395
396 if((unused || migrated || reformatted || show_default)) {
397 if(options_added)
398 buffer_strcat(wb, "\n");
399
400 buffer_sprintf(wb, "\t#| >>> [%s].%s <<<\n",
401 string2str(sect->name), string2str(opt->name));
402
403 last_had_comments = true;
404 }
405 else if(last_had_comments) {
406 buffer_strcat(wb, "\n");
407 last_had_comments = false;
408 }
409
410 if(unused)
411 buffer_sprintf(wb, "\t#| found in the config file, but is not used\n");
412
413 if(migrated && reformatted)
414 buffer_sprintf(wb, "\t#| migrated from: [%s].%s = %s\n",
415 string2str(opt->migrated.section), string2str(opt->migrated.name),
416 string2str(opt->value_original));
417 else {
418 if (migrated)
419 buffer_sprintf(wb, "\t#| migrated from: [%s].%s\n",
420 string2str(opt->migrated.section), string2str(opt->migrated.name));
421
422 if (reformatted)
423 buffer_sprintf(wb, "\t#| reformatted from: %s\n",
424 string2str(opt->value_original));
425 }
426
427 const char *current_value = string2str(opt->value);
428 const char *default_value = opt->value_default ? string2str(opt->value_default) : "";
429 #if defined(OS_WINDOWS)
430 char current_value_windows[CONFIG_MAX_VALUE + 1];
431 char default_value_windows[CONFIG_MAX_VALUE + 1];
432
433 current_value = inicfg_windows_value_for_display(
434 sect, opt, current_value, current_value_windows, sizeof(current_value_windows));
435 if(opt->value_default)
436 default_value = inicfg_windows_value_for_display(
437 sect, opt, default_value, default_value_windows, sizeof(default_value_windows));
438 #endif
439
440 if(show_default)
441 buffer_sprintf(wb, "\t#| datatype: %s, default value: %s\n",
442 CONFIG_VALUE_TYPES_2str(opt->type),
443 default_value);
444
445 buffer_sprintf(wb, "\t%s%s = %s\n",
446 (
447 !(opt->flags & CONFIG_VALUE_LOADED) &&
448 !(opt->flags & CONFIG_VALUE_CHANGED) &&
449 (opt->flags & CONFIG_VALUE_USED)
450 ) ? "# " : "",
451 string2str(opt->name),
452 current_value);
453
454 options_added++;
455 }
456 SECTION_UNLOCK(sect);
457 }
458 }
459 APPCONFIG_UNLOCK(root);
460 }
461 }