@cryptotaxi247 / netdata-1 / commits / f54defa27

Remove VLA (variable-length arrays) (libnetdata) (#22218)

thiagoftsm committed Apr 24, 2026 at 11:11 UTC f54defa27b005f2f1b71e9af699e58640d06cd94
31 files changed +516 -270
src/daemon/main.c
+2
@@ -221,6 +221,7 @@ int progress_unittest(void);
221 int dyncfg_unittest(void);
222 int eval_unittest(void);
223 int duration_unittest(void);
224 +int statistical_unittest(void);
225 int health_config_unittest(void);
226 int utf8_sanitizer_unittest(void);
227 int yaml_unittest(void);
@@ -444,6 +445,7 @@ int netdata_main(int argc, char **argv) {
445 if (dyncfg_unittest()) return 1;
446 if (eval_unittest()) return 1;
447 if (duration_unittest()) return 1;
448 + if (statistical_unittest()) return 1;
449 if (utf8_sanitizer_unittest()) return 1;
450 if (health_config_unittest()) return 1;
451 if (yaml_unittest()) return 1;
src/libnetdata/aral/aral.c
+6 -2
@@ -1697,7 +1697,7 @@ static void aral_test_thread(void *ptr) {
1697
1698 // fprintf(stderr, "all %zu, to free %zu, step %zu\n", all, to_free, step);
1699
1700 - size_t free_list[to_free];
1700 + size_t *free_list = mallocz(to_free * sizeof(*free_list));
1701 for (size_t i = 0; i < to_free; i++) {
1702 size_t pos = step * i;
1703 aral_freez(ar, pointers[pos]);
@@ -1709,6 +1709,8 @@ static void aral_test_thread(void *ptr) {
1709 size_t pos = free_list[i];
1710 pointers[pos] = unittest_aral_malloc(ar, marked);
1711 }
1712 +
1713 + freez(free_list);
1714 }
1715
1716 for (size_t i = 0; i < elements; i++) {
@@ -1745,7 +1747,7 @@ int aral_stress_test(size_t threads, size_t elements, size_t seconds) {
1747 };
1748
1749 usec_t started_ut = now_monotonic_usec();
1748 - ND_THREAD *thread_ptrs[threads];
1750 + ND_THREAD **thread_ptrs = callocz(threads, sizeof(*thread_ptrs));
1751
1752 for(size_t i = 0; i < threads ; i++) {
1753 char tag[ND_THREAD_TAG_MAX + 1];
@@ -1778,6 +1780,8 @@ int aral_stress_test(size_t threads, size_t elements, size_t seconds) {
1780 nd_thread_join(thread_ptrs[i]);
1781 }
1782
1783 + freez(thread_ptrs);
1784 +
1785 usec_t ended_ut = now_monotonic_usec();
1786
1787 if (auc.ar->aral_lock.pages_free && auc.ar->aral_lock.pages_free->page_lock.used_elements) {
src/libnetdata/dictionary/dictionary-unittest.c
+6 -6
@@ -665,15 +665,15 @@ static void unittest_dict_thread(void *arg) {
665
666 static int dictionary_unittest_threads() {
667 time_t seconds_to_run = 5;
668 - int threads_to_create = 2;
668 + enum { DICTIONARY_UNITTEST_THREADS = 2 };
669
670 - struct thread_unittest tu[threads_to_create];
671 - memset(tu, 0, sizeof(struct thread_unittest) * threads_to_create);
670 + struct thread_unittest tu[DICTIONARY_UNITTEST_THREADS];
671 + memset(tu, 0, sizeof(struct thread_unittest) * DICTIONARY_UNITTEST_THREADS);
672
673 fprintf(
674 stderr,
675 "\nChecking dictionary concurrency with %d threads for %lld seconds...\n",
676 - threads_to_create,
676 + DICTIONARY_UNITTEST_THREADS,
677 (long long)seconds_to_run);
678
679 // threads testing of dictionary
@@ -682,7 +682,7 @@ static int dictionary_unittest_threads() {
682 tu[0].dups = 1;
683 tu[0].dict = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE, &stats, 0);
684
685 - for (int i = 0; i < threads_to_create; i++) {
685 + for (int i = 0; i < DICTIONARY_UNITTEST_THREADS; i++) {
686 if(i)
687 tu[i] = tu[0];
688
@@ -693,7 +693,7 @@ static int dictionary_unittest_threads() {
693
694 sleep_usec(seconds_to_run * USEC_PER_SEC);
695
696 - for (int i = 0; i < threads_to_create; i++) {
696 + for (int i = 0; i < DICTIONARY_UNITTEST_THREADS; i++) {
697 __atomic_store_n(&tu[i].join, 1, __ATOMIC_RELAXED);
698
699 nd_thread_join(tu[i].thread);
src/libnetdata/eval/eval-utils.c
+8 -6
@@ -342,12 +342,14 @@ void expression_hardcode_variable(EVAL_EXPRESSION *expression, STRING *variable,
342
343 size_t source_len = string_strlen(expression->source);
344
345 - char find1[string_strlen(variable) + 1 + 1];
346 - snprintfz(find1, sizeof(find1), "$%s", string2str(variable));
345 + size_t find1_size = string_strlen(variable) + 1 + 1;
346 + CLEAN_CHAR_P *find1 = mallocz(find1_size);
347 + snprintfz(find1, find1_size, "$%s", string2str(variable));
348 size_t find1_len = strlen(find1);
349
349 - char find2[string_strlen(variable) + 1 + 3];
350 - snprintfz(find2, sizeof(find2), "${%s}", string2str(variable));
350 + size_t find2_size = string_strlen(variable) + 1 + 3;
351 + CLEAN_CHAR_P *find2 = mallocz(find2_size);
352 + snprintfz(find2, find2_size, "${%s}", string2str(variable));
353 size_t find2_len = strlen(find2);
354
355 // Calculate the maximum possible buffer size needed
@@ -355,8 +357,8 @@ void expression_hardcode_variable(EVAL_EXPRESSION *expression, STRING *variable,
357 size_t min_var_len = MIN(find1_len, find2_len);
358 size_t max_buf_size = source_len + 1 + (matches * (replace_len > min_var_len ? replace_len - min_var_len : 0));
359
358 - char buf1[max_buf_size];
359 - char buf2[max_buf_size];
360 + CLEAN_CHAR_P *buf1 = mallocz(max_buf_size);
361 + CLEAN_CHAR_P *buf2 = mallocz(max_buf_size);
362
363 char *dst[2] = {buf1, buf2};
364
src/libnetdata/facets/facets.c
+17 -11
@@ -726,13 +726,12 @@ static void facet_key_set_name(FACET_KEY *k, const char *name, size_t name_lengt
726
727 // an actual value, not a filter
728
729 - char buf[name_length + 1];
730 - memcpy(buf, name, name_length);
731 - buf[name_length] = '\0';
729 + char *name_copy = callocz(name_length + 1, sizeof(char));
730 + memcpy(name_copy, name, name_length);
731
733 - internal_fatal(strchr(buf, '='), "found = in key");
732 + internal_fatal(strchr(name_copy, '='), "found = in key");
733
735 - k->name = strdupz(buf);
734 + k->name = name_copy;
735 facet_key_late_init(k->facets, k);
736 }
737
@@ -2418,13 +2417,15 @@ void facets_sort_and_reorder_keys(FACETS *facets) {
2417 if(!entries)
2418 return;
2419
2421 - FACET_KEY *keys[entries];
2422 - memcpy(keys, facets->keys_with_values.array, sizeof(FACET_KEY *) * entries);
2420 + FACET_KEY **keys = mallocz(entries * sizeof(*keys));
2421 + memcpy(keys, facets->keys_with_values.array, entries * sizeof(*keys));
2422
2423 qsort(keys, entries, sizeof(FACET_KEY *), facets_keys_reorder_compar);
2424
2425 for(size_t i = 0; i < entries ;i++)
2426 keys[i]->order = i + 1;
2427 +
2428 + freez(keys);
2429 }
2430
2431 static int facets_key_values_reorder_by_name_compar(const void *a, const void *b) {
@@ -2479,7 +2480,8 @@ static int facets_key_values_reorder_by_name_numeric_compar(const void *a, const
2480 static uint32_t facets_sort_and_reorder_values_internal(FACET_KEY *k) {
2481 bool all_values_numeric = true;
2482 size_t entries = k->values.used;
2482 - FACET_VALUE *values[entries], *v;
2483 + FACET_VALUE **values = mallocz(entries * sizeof(*values));
2484 + FACET_VALUE *v;
2485 uint32_t used = 0;
2486 foreach_value_in_key(k, v) {
2487 if((k->facets->options & FACETS_OPTION_DONT_SEND_EMPTY_VALUE_FACETS) && v->empty)
@@ -2499,8 +2501,10 @@ static uint32_t facets_sort_and_reorder_values_internal(FACET_KEY *k) {
2501 }
2502 foreach_value_in_key_done(v);
2503
2502 - if(!used)
2504 + if(!used) {
2505 + freez(values);
2506 return 0;
2507 + }
2508
2509 if(k->facets->options & FACETS_OPTION_SORT_FACETS_ALPHABETICALLY) {
2510 if(all_values_numeric)
@@ -2514,6 +2518,7 @@ static uint32_t facets_sort_and_reorder_values_internal(FACET_KEY *k) {
2518 for(size_t i = 0; i < used; i++)
2519 values[i]->order = i + 1;
2520
2521 + freez(values);
2522 return used;
2523 }
2524
@@ -2530,10 +2535,10 @@ static uint32_t facets_sort_and_reorder_values(FACET_KEY *k) {
2535 uint32_t ret = 0;
2536
2537 size_t entries = k->values.used;
2533 - struct {
2538 + struct facet_value_restore {
2539 const char *name;
2540 uint32_t name_len;
2536 - } values[entries];
2541 + } *values = mallocz(entries * sizeof(*values));
2542 FACET_VALUE *v;
2543 uint32_t used = 0;
2544
@@ -2566,6 +2571,7 @@ static uint32_t facets_sort_and_reorder_values(FACET_KEY *k) {
2571 foreach_value_in_key_done(v);
2572
2573 buffer_free(tb);
2574 + freez(values);
2575 return ret;
2576 }
2577
src/libnetdata/facets/logs_query_status.h
+1 -2
@@ -493,8 +493,7 @@ static inline bool lqs_request_parse_GET(LOGS_QUERY_STATUS *lqs, BUFFER *wb, cha
493
494 buffer_json_member_add_object(wb, "_request");
495
496 - char func_copy[strlen(function) + 1];
497 - memcpy(func_copy, function, sizeof(func_copy));
496 + CLEAN_CHAR_P *func_copy = strdupz(function);
497
498 char *words[LQS_MAX_PARAMS] = { NULL };
499 size_t num_words = quoted_strings_splitter_whitespace(func_copy, words, LQS_MAX_PARAMS);
src/libnetdata/inicfg/dyncfg.c
+1 -2
@@ -273,8 +273,7 @@ int dyncfg_node_find_and_call(DICTIONARY *dyncfg_nodes, const char *transaction,
273 if(!function || !*function)
274 return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "command received is empty");
275
276 - char buf[strlen(function) + 1];
277 - memcpy(buf, function, sizeof(buf));
276 + CLEAN_CHAR_P *buf = strdupz(function);
277
278 char *words[MAX_FUNCTION_PARAMETERS]; // an array of pointers for the words in this line
279 size_t num_words = quoted_strings_splitter_whitespace(buf, words, MAX_FUNCTION_PARAMETERS);
src/libnetdata/json/json-c-parser-inline.c
+44 -6
@@ -2,6 +2,44 @@
2
3 #include "../libnetdata.h"
4
5 +// "JSON parser failed: " prefix (20 chars) + json-c error string (< 60 chars) + NUL
6 +#define JSON_PARSER_ERROR_MSG_MAX 256
7 +#define JSON_PARSER_ERROR_PREFIX "JSON parser failed: "
8 +#define JSON_PARSER_ERROR_TRUNCATION_SUFFIX "..."
9 +#define JSON_PARSER_UNKNOWN_ERROR "unknown error"
10 +
11 +static void json_parser_format_error(char *dst, size_t dst_size, const char *error_msg, size_t error_len) {
12 + if(unlikely(!dst_size))
13 + return;
14 +
15 + if(!error_msg || !*error_msg) {
16 + error_msg = JSON_PARSER_UNKNOWN_ERROR;
17 + error_len = sizeof(JSON_PARSER_UNKNOWN_ERROR) - 1;
18 + }
19 +
20 + const size_t prefix_len = sizeof(JSON_PARSER_ERROR_PREFIX) - 1;
21 + size_t available = dst_size - 1;
22 + if(unlikely(available <= prefix_len)) {
23 + dst[0] = '\0';
24 + return;
25 + }
26 +
27 + available -= prefix_len;
28 +
29 + bool truncated = error_len > available;
30 + if(truncated) {
31 + size_t suffix_len = sizeof(JSON_PARSER_ERROR_TRUNCATION_SUFFIX) - 1;
32 + if(available > suffix_len)
33 + available -= suffix_len;
34 + }
35 +
36 + snprintfz(dst, dst_size, "%s%.*s%s",
37 + JSON_PARSER_ERROR_PREFIX,
38 + (int)available,
39 + error_msg,
40 + truncated ? JSON_PARSER_ERROR_TRUNCATION_SUFFIX : "");
41 +}
42 +
43 int rrd_call_function_error(BUFFER *wb, const char *msg, int code) {
44 buffer_reset(wb);
45 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
@@ -29,8 +67,8 @@ struct json_object *json_parse_function_payload_or_error(BUFFER *output, BUFFER
67 struct json_object *jobj = json_tokener_parse_ex(tokener, buffer_tostring(payload), (int)buffer_strlen(payload));
68 if (json_tokener_get_error(tokener) != json_tokener_success) {
69 const char *error_msg = json_tokener_error_desc(json_tokener_get_error(tokener));
32 - char tmp[strlen(error_msg) + 100];
33 - snprintf(tmp, sizeof(tmp), "JSON parser failed: %s", error_msg);
70 + char tmp[JSON_PARSER_ERROR_MSG_MAX];
71 + json_parser_format_error(tmp, sizeof(tmp), error_msg, error_msg ? strnlen(error_msg, JSON_PARSER_ERROR_MSG_MAX) : 0);
72 json_tokener_free(tokener);
73 *code = rrd_call_function_error(output, tmp, HTTP_RESP_INTERNAL_SERVER_ERROR);
74 return NULL;
@@ -39,8 +77,8 @@ struct json_object *json_parse_function_payload_or_error(BUFFER *output, BUFFER
77
78 CLEAN_BUFFER *error = buffer_create(0, NULL);
79 if(!cb(jobj, cb_data, error)) {
42 - char tmp[buffer_strlen(error) + 100];
43 - snprintfz(tmp, sizeof(tmp), "JSON parser failed: %s", buffer_tostring(error));
80 + char tmp[JSON_PARSER_ERROR_MSG_MAX];
81 + json_parser_format_error(tmp, sizeof(tmp), buffer_tostring(error), buffer_strlen(error));
82 *code = rrd_call_function_error(output, tmp, HTTP_RESP_BAD_REQUEST);
83 json_object_put(jobj);
84 return NULL;
@@ -66,8 +104,8 @@ int json_parse_payload_or_error(BUFFER *payload, BUFFER *error, json_parse_funct
104 struct json_object *jobj = json_tokener_parse_ex(tokener, buffer_tostring(payload), (int)buffer_strlen(payload));
105 if (json_tokener_get_error(tokener) != json_tokener_success) {
106 const char *error_msg = json_tokener_error_desc(json_tokener_get_error(tokener));
69 - char tmp[strlen(error_msg) + 100];
70 - snprintf(tmp, sizeof(tmp), "JSON parser failed: %s", error_msg);
107 + char tmp[JSON_PARSER_ERROR_MSG_MAX];
108 + json_parser_format_error(tmp, sizeof(tmp), error_msg, error_msg ? strnlen(error_msg, JSON_PARSER_ERROR_MSG_MAX) : 0);
109 json_tokener_free(tokener);
110 buffer_strcat(error, tmp);
111 return HTTP_RESP_BAD_REQUEST;
src/libnetdata/json/json-c-parser-unittest.c
+105
@@ -189,6 +189,11 @@ static bool wrap_parse_array_item_object(json_object *jobj_in, size_t *count,
189 // Test helpers
190 // ============================================================================
191
192 +static bool json_function_payload_fail_with_error(json_object *jobj __maybe_unused, void *data, BUFFER *error) {
193 + buffer_strcat(error, (const char *)data);
194 + return false;
195 +}
196 +
197 #define T(cond, msg) do { \
198 if (!(cond)) { fprintf(stderr, " FAILED: %s\n", msg); failed++; } \
199 } while(0)
@@ -1913,6 +1918,104 @@ static int test_parse_array_item_object(void) {
1918 return failed;
1919 }
1920
1921 +// ----------------------------------------------------------------------------
1922 +// json_parse_function_payload_or_error() error path:
1923 +// callback error should be capped and explicitly truncated
1924 +// ----------------------------------------------------------------------------
1925 +static int test_parse_function_payload_error_cap(void) {
1926 + int failed = 0;
1927 + BUFFER *payload = buffer_create(0, NULL);
1928 + BUFFER *output = buffer_create(0, NULL);
1929 + char long_error[1024];
1930 + int code = 0;
1931 +
1932 + memset(long_error, 'A', sizeof(long_error) - 1);
1933 + long_error[sizeof(long_error) - 1] = '\0';
1934 +
1935 + buffer_strcat(payload, "{}");
1936 +
1937 + struct json_object *result = json_parse_function_payload_or_error(output, payload, &code,
1938 + json_function_payload_fail_with_error,
1939 + long_error);
1940 +
1941 + T(result == NULL, "function_payload_error_cap: callback failure should return NULL");
1942 + T(code == HTTP_RESP_BAD_REQUEST, "function_payload_error_cap: callback failure should return bad request");
1943 +
1944 + json_object *response = json_tokener_parse(buffer_tostring(output));
1945 + T(response != NULL, "function_payload_error_cap: output should be valid JSON");
1946 +
1947 + const char *error_msg = NULL;
1948 + if(response) {
1949 + json_object *error_obj = NULL;
1950 + T(json_object_object_get_ex(response, "errorMessage", &error_obj),
1951 + "function_payload_error_cap: response should include errorMessage");
1952 +
1953 + if(error_obj)
1954 + error_msg = json_object_get_string(error_obj);
1955 + }
1956 +
1957 + T(error_msg != NULL, "function_payload_error_cap: errorMessage should be readable");
1958 + T(error_msg && strncmp(error_msg, "JSON parser failed: ", strlen("JSON parser failed: ")) == 0,
1959 + "function_payload_error_cap: errorMessage should include parser prefix");
1960 + T(error_msg && strstr(error_msg, "...") != NULL,
1961 + "function_payload_error_cap: errorMessage should explicitly indicate truncation");
1962 + T(error_msg && strlen(error_msg) < sizeof(long_error) - 1,
1963 + "function_payload_error_cap: errorMessage should be shorter than the original callback error");
1964 + T(buffer_strlen(output) < sizeof(long_error),
1965 + "function_payload_error_cap: output JSON should stay smaller than the original callback error");
1966 +
1967 + if(response)
1968 + json_object_put(response);
1969 + buffer_free(output);
1970 + buffer_free(payload);
1971 +
1972 + return failed;
1973 +}
1974 +
1975 +// ----------------------------------------------------------------------------
1976 +// json_parse_function_payload_or_error() error path:
1977 +// empty callback error should fall back to "unknown error"
1978 +// ----------------------------------------------------------------------------
1979 +static int test_parse_function_payload_empty_error_fallback(void) {
1980 + int failed = 0;
1981 + BUFFER *payload = buffer_create(0, NULL);
1982 + BUFFER *output = buffer_create(0, NULL);
1983 + int code = 0;
1984 +
1985 + buffer_strcat(payload, "{}");
1986 +
1987 + struct json_object *result = json_parse_function_payload_or_error(output, payload, &code,
1988 + json_function_payload_fail_with_error,
1989 + "");
1990 +
1991 + T(result == NULL, "function_payload_empty_error_fallback: callback failure should return NULL");
1992 + T(code == HTTP_RESP_BAD_REQUEST, "function_payload_empty_error_fallback: callback failure should return bad request");
1993 +
1994 + json_object *response = json_tokener_parse(buffer_tostring(output));
1995 + T(response != NULL, "function_payload_empty_error_fallback: output should be valid JSON");
1996 +
1997 + const char *error_msg = NULL;
1998 + if(response) {
1999 + json_object *error_obj = NULL;
2000 + T(json_object_object_get_ex(response, "errorMessage", &error_obj),
2001 + "function_payload_empty_error_fallback: response should include errorMessage");
2002 +
2003 + if(error_obj)
2004 + error_msg = json_object_get_string(error_obj);
2005 + }
2006 +
2007 + T(error_msg != NULL, "function_payload_empty_error_fallback: errorMessage should be readable");
2008 + T(error_msg && strcmp(error_msg, "JSON parser failed: unknown error") == 0,
2009 + "function_payload_empty_error_fallback: empty callback error should use the unknown error fallback");
2010 +
2011 + if(response)
2012 + json_object_put(response);
2013 + buffer_free(output);
2014 + buffer_free(payload);
2015 +
2016 + return failed;
2017 +}
2018 +
2019 // ============================================================================
2020 // Entry point
2021 // ============================================================================
@@ -1942,6 +2045,8 @@ int json_c_parser_unittest(void) {
2045 { "SUBOBJECT", test_parse_subobject },
2046 { "ARRAY", test_parse_array },
2047 { "ARRAY_ITEM_OBJECT", test_parse_array_item_object },
2048 + { "FUNCTION_PAYLOAD_ERROR_CAP", test_parse_function_payload_error_cap },
2049 + { "FUNCTION_PAYLOAD_EMPTY_ERROR_FALLBACK", test_parse_function_payload_empty_error_fallback },
2050 { NULL, NULL }
2051 };
2052
src/libnetdata/libnetdata.c
+19 -2
@@ -144,12 +144,25 @@ char *find_and_replace(const char *src, const char *find, const char *replace, c
144 return value;
145 }
146
147 +static inline bool run_command_validate_max_line_length(const char *command, int max_line_length) {
148 + if (likely(max_line_length > 0))
149 + return true;
150 +
151 + netdata_log_error("Invalid max_line_length %d for command '%s'.",
152 + max_line_length, command ? command : "(null)");
153 + return false;
154 +}
155 +
156 BUFFER *run_command_and_get_output_to_buffer(const char *command, int max_line_length) {
157 + if (unlikely(!run_command_validate_max_line_length(command, max_line_length)))
158 + return NULL;
159 +
160 BUFFER *wb = buffer_create(0, NULL);
161
162 POPEN_INSTANCE *pi = spawn_popen_run(command);
163 if(pi) {
152 - char buffer[max_line_length + 1];
164 + size_t buffer_size = (size_t)max_line_length + 1;
165 + CLEAN_CHAR_P *buffer = mallocz(buffer_size);
166 while (fgets(buffer, max_line_length, spawn_popen_stdout(pi))) {
167 buffer[max_line_length] = '\0';
168 buffer_strcat(wb, buffer);
@@ -166,9 +179,13 @@ BUFFER *run_command_and_get_output_to_buffer(const char *command, int max_line_l
179 }
180
181 bool run_command_and_copy_output_to_stdout(const char *command, int max_line_length) {
182 + if (unlikely(!run_command_validate_max_line_length(command, max_line_length)))
183 + return false;
184 +
185 POPEN_INSTANCE *pi = spawn_popen_run(command);
186 if(pi) {
171 - char buffer[max_line_length + 1];
187 + size_t buffer_size = (size_t)max_line_length + 1;
188 + CLEAN_CHAR_P *buffer = mallocz(buffer_size);
189
190 while (fgets(buffer, max_line_length, spawn_popen_stdout(pi)))
191 fprintf(stdout, "%s", buffer);
src/libnetdata/local-sockets/local-sockets.h
+16 -6
@@ -23,6 +23,9 @@
23
24 #define UID_UNSET (uid_t)(UINT32_MAX)
25
26 +// max cmdline bytes read from /proc/<pid>/cmdline — reader-side guard must match
27 +#define LOCAL_SOCKETS_CMDLINE_MAX 8192
28 +
29 // --------------------------------------------------------------------------------------------------------------------
30 // hashtable for keeping the namespaces
31 // key and value is the namespace inode
@@ -583,7 +586,7 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch
586 struct dirent *proc_entry;
587 char filename[FILENAME_MAX + 1];
588 char comm[TASK_COMM_LEN];
586 - char cmdline[8192];
589 + char cmdline[LOCAL_SOCKETS_CMDLINE_MAX];
590 const char *cmdline_trimmed;
591 uint64_t net_ns_inode;
592
@@ -1579,8 +1582,14 @@ static inline bool local_sockets_get_namespace_sockets_with_pid(LS_STATE *ls, st
1582 if(read(spawn_server_instance_read_fd(si), &len, sizeof(len)) != sizeof(len))
1583 local_sockets_log(ls, "failed to read cmdline length from pipe");
1584
1585 + if(len > LOCAL_SOCKETS_CMDLINE_MAX) {
1586 + // broken pipe protocol: writer caps at LOCAL_SOCKETS_CMDLINE_MAX bytes
1587 + local_sockets_log(ls, "cmdline length %zu from child exceeds limit (%d), aborting namespace socket collection", len, LOCAL_SOCKETS_CMDLINE_MAX);
1588 + break;
1589 + }
1590 +
1591 if(len) {
1583 - char cmdline[len + 1];
1592 + CLEAN_CHAR_P *cmdline = mallocz(len + 1);
1593 if(read(spawn_server_instance_read_fd(si), cmdline, len) != (ssize_t)len)
1594 local_sockets_log(ls, "failed to read cmdline from pipe");
1595 else {
@@ -1663,10 +1672,8 @@ static inline void local_sockets_namespaces(LS_STATE *ls) {
1672 if(threads > 100) threads = 100;
1673
1674 size_t last_thread = 0;
1666 - ND_THREAD *workers[threads];
1667 - struct local_sockets_namespace_worker workers_data[threads];
1668 - memset(workers, 0, sizeof(workers));
1669 - memset(workers_data, 0, sizeof(workers_data));
1675 + ND_THREAD **workers = callocz(threads, sizeof(*workers));
1676 + struct local_sockets_namespace_worker *workers_data = callocz(threads, sizeof(*workers_data));
1677
1678 spinlock_lock(&ls->spinlock);
1679
@@ -1711,6 +1718,9 @@ static inline void local_sockets_namespaces(LS_STATE *ls) {
1718 if(workers[i])
1719 nd_thread_join(workers[i]);
1720 }
1721 +
1722 + freez(workers_data);
1723 + freez(workers);
1724 }
1725
1726 #endif // LOCAL_SOCKETS_USE_SETNS
src/libnetdata/locks/waitq.c
+11 -9
@@ -204,8 +204,10 @@ static int unittest_stress(void) {
204 fprintf(stderr, "\nStress testing waiting queue...\n");
205
206 WAITQ wq = WAITQ_INITIALIZER;
207 - const size_t num_priorities = 4;
208 - const size_t total_threads = num_priorities * THREADS_PER_PRIORITY;
207 + enum {
208 + WAITQ_STRESS_NUM_PRIORITIES = 4,
209 + WAITQ_STRESS_TOTAL_THREADS = WAITQ_STRESS_NUM_PRIORITIES * THREADS_PER_PRIORITY
210 + };
211
212 // Test both with and without sleep
213 for(int test = 0; test < 2; test++) {
@@ -216,12 +218,12 @@ static int unittest_stress(void) {
218 TEST_DURATION_SEC, with_sleep ? "with" : "without");
219
220 // Prepare thread stats and args
219 - THREAD_STATS stats[total_threads];
220 - struct thread_args thread_args[total_threads];
221 - ND_THREAD *threads[total_threads];
221 + THREAD_STATS stats[WAITQ_STRESS_TOTAL_THREADS];
222 + struct thread_args thread_args[WAITQ_STRESS_TOTAL_THREADS];
223 + ND_THREAD *threads[WAITQ_STRESS_TOTAL_THREADS];
224
225 fprintf(stderr, "Starting %zu threads for %ds test %s sleep...\n",
224 - total_threads,
226 + (size_t)WAITQ_STRESS_TOTAL_THREADS,
227 TEST_DURATION_SEC,
228 with_sleep ? "with" : "without");
229
@@ -264,12 +266,12 @@ static int unittest_stress(void) {
266 __atomic_store_n(&stop_flag, true, __ATOMIC_RELEASE);
267
268 // Wait for threads and collect stats
267 - fprintf(stderr, "Waiting for %zu threads to finish...\n", total_threads);
268 - for(size_t i = 0; i < total_threads; i++)
269 + fprintf(stderr, "Waiting for %zu threads to finish...\n", (size_t)WAITQ_STRESS_TOTAL_THREADS);
270 + for(size_t i = 0; i < WAITQ_STRESS_TOTAL_THREADS; i++)
271 nd_thread_join(threads[i]);
272
273 // Print stats
272 - print_thread_stats(stats, total_threads, TEST_DURATION_SEC * USEC_PER_SEC);
274 + print_thread_stats(stats, WAITQ_STRESS_TOTAL_THREADS, TEST_DURATION_SEC * USEC_PER_SEC);
275 }
276
277 waitq_destroy(&wq);
src/libnetdata/log/nd_log-to-systemd-journal.c
+3 -9
@@ -93,11 +93,9 @@ bool nd_logger_journal_libsystemd(struct log_field *fields __maybe_unused, size_
93 //
94 // UPDATE ALL OF THEM FOR NEW FEATURES OR FIXES
95
96 - struct iovec iov[fields_max];
96 + struct iovec iov[THREAD_FIELDS_MAX];
97 int iov_count = 0;
98
99 - memset(iov, 0, sizeof(iov));
100 -
99 CLEAN_BUFFER *tmp = NULL;
100
101 for (size_t i = 0; i < fields_max; i++) {
@@ -179,12 +177,8 @@ bool nd_logger_journal_libsystemd(struct log_field *fields __maybe_unused, size_
177 }
178 }
179
182 - // Clean up allocated memory
183 - for (int i = 0; i < iov_count; i++) {
184 - if (iov[i].iov_base != NULL) {
185 - free(iov[i].iov_base);
186 - }
187 - }
180 + for (int i = 0; i < iov_count; i++)
181 + free(iov[i].iov_base);
182
183 return r == 0;
184 #else
src/libnetdata/os/close_range.c
+3 -2
@@ -89,8 +89,8 @@ void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num, int f
89 }
90
91 // copy the fds array to ensure we will not alter them
92 - int fds_copy[fds_num];
93 - memcpy(fds_copy, fds, sizeof(fds_copy));
92 + int *fds_copy = mallocz(fds_num * sizeof(*fds_copy));
93 + memcpy(fds_copy, fds, fds_num * sizeof(*fds_copy));
94
95 qsort(fds_copy, fds_num, sizeof(int), compare_ints);
96
@@ -110,4 +110,5 @@ void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num, int f
110 }
111
112 os_close_range(start, CLOSE_RANGE_FD_MAX, flags);
113 + freez(fds_copy);
114 }
src/libnetdata/os/system-maps/cached-sid-username.c
+7 -6
@@ -103,19 +103,20 @@ static SID_VALUE *lookup_or_convert_user_id_to_name_lookup(PSID sid) {
103
104 size_t tmp_size = sizeof(SID_VALUE) + size;
105 size_t tmp_key_size = sizeof(SID_KEY) + size;
106 - uint8_t buf[tmp_size];
107 - SID_VALUE *tmp = (SID_VALUE *)&buf;
106 + SID_VALUE *tmp = mallocz(tmp_size);
107 memcpy(&tmp->key.sid, sid, size);
108 tmp->key.len = size;
109
110 spinlock_lock(&sid_globals.spinlock);
111 SID_VALUE *found = simple_hashtable_get_SID(&sid_globals.hashtable, &tmp->key, tmp_key_size);
112 spinlock_unlock(&sid_globals.spinlock);
114 - if(found) return found;
113 + if(found) {
114 + freez(tmp);
115 + return found;
116 + }
117
118 // allocate the SID_VALUE
117 - found = mallocz(tmp_size);
118 - memcpy(found, buf, tmp_size);
119 + found = tmp;
120
121 lookup_user_in_system(found);
122
@@ -197,4 +198,4 @@ STRING *cached_sid_fullname_or_sid_str(PSID sid) {
198 return NULL;
199 }
200
200 -#endif
\ No newline at end of file
201 +#endif
src/libnetdata/query_progress/progress.c
+4 -4
@@ -616,12 +616,12 @@ int progress_function_result(BUFFER *wb, const char *hostname) {
616 // ----------------------------------------------------------------------------
617
618 int progress_unittest(void) {
619 - size_t permanent = 100;
620 - nd_uuid_t valid[permanent];
619 + enum { PROGRESS_UNITTEST_PERMANENT = 100 };
620 + nd_uuid_t valid[PROGRESS_UNITTEST_PERMANENT];
621
622 usec_t started = now_monotonic_usec();
623
624 - for(size_t i = 0; i < permanent ;i++) {
624 + for(size_t i = 0; i < PROGRESS_UNITTEST_PERMANENT ;i++) {
625 uuid_generate_random(valid[i]);
626 query_progress_start_or_update(&valid[i], 0, HTTP_REQUEST_MODE_GET, HTTP_ACL_ACLK, "permanent", NULL, "test");
627 }
@@ -633,7 +633,7 @@ int progress_unittest(void) {
633 query_progress_finished(&t, 0, 200, 1234, 123, 12);
634
635 QUERY_PROGRESS *qp;
636 - for(size_t i = 0; i < permanent ;i++) {
636 + for(size_t i = 0; i < PROGRESS_UNITTEST_PERMANENT ;i++) {
637 qp = query_progress_find_in_hashtable_unsafe(&valid[i]);
638 assert(qp);
639 (void)qp;
src/libnetdata/sanitizers/chart_id_and_name.c
+12 -3
@@ -2,6 +2,11 @@
2
3 #include "../libnetdata.h"
4
5 +// defined again in health/rrdvar.h — keep both in sync
6 +#ifndef RRDVAR_MAX_LENGTH
7 +#define RRDVAR_MAX_LENGTH 1024
8 +#endif
9 +
10 // --------------------------------------------------------------------------------------------------------------------
11 // RRD string sanitization (for units, title, family, context, plugin, module)
12 //
@@ -198,8 +203,12 @@ char *rrdset_strncpyz_name(char *dst, const char *src, size_t dst_size_minus_1)
203
204 bool rrdvar_fix_name(char *variable) {
205 size_t len = strlen(variable);
201 - char buf[len + 1];
202 - memcpy(buf, variable, sizeof(buf));
206 +
207 + char buf[RRDVAR_MAX_LENGTH + 1];
208 + if(unlikely(len >= sizeof(buf)))
209 + len = sizeof(buf) - 1;
210 +
211 + memcpy(buf, variable, len + 1);
212 sanitize_chart_name(variable, variable, len + 1);
204 - return memcmp(buf, variable, sizeof(buf)) != 0;
213 + return memcmp(buf, variable, len + 1) != 0;
214 }
src/libnetdata/signals/signal-code.c
+1 -2
@@ -237,8 +237,7 @@ void SIGNAL_CODE_2str_h(SIGNAL_CODE code, char *buf, size_t size) {
237 SIGNAL_CODE SIGNAL_CODE_2id_h(const char *str) {
238 if(!str || !*str) return 0;
239
240 - char buf[strlen(str) + 1];
241 - memcpy(buf, str, strlen(str) + 1);
240 + CLEAN_CHAR_P *buf = strdupz(str);
241
242 char *si_code_str = strchr(buf, '/');
243 if(si_code_str) {
src/libnetdata/socket/connect-to.c
+3 -4
@@ -351,8 +351,7 @@ int connect_to_this(const char *definition, int default_port, struct timeval *ti
351 return -ND_SOCK_ERR_NO_HOST_IN_DEFINITION;
352 }
353
354 - char buffer[strlen(definition) + 1];
355 - strcpy(buffer, definition);
354 + CLEAN_CHAR_P *buffer = strdupz(definition);
355
356 char default_service[10 + 1];
357 snprintfz(default_service, 10, "%d", default_port);
@@ -403,8 +402,8 @@ void foreach_entry_in_connection_string(const char *destination, bool (*callback
402 // is there anything?
403 if(!*s || s == e) break;
404
406 - char buf[e - s + 1];
407 - strncpyz(buf, s, e - s);
405 + CLEAN_CHAR_P *buf = mallocz((size_t)(e - s) + 1);
406 + strncpyz(buf, s, (size_t)(e - s));
407
408 if(callback(buf, data)) break;
409
src/libnetdata/socket/listen-sockets.c
+3 -4
@@ -338,8 +338,7 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition,
338 struct addrinfo hints;
339 struct addrinfo *result = NULL, *rp = NULL;
340
341 - char buffer[strlen(definition) + 1];
342 - strcpy(buffer, definition);
341 + CLEAN_CHAR_P *buffer = strdupz(definition);
342
343 char buffer2[10 + 1];
344 snprintfz(buffer2, 10, "%d", default_port);
@@ -549,8 +548,8 @@ int listen_sockets_setup(LISTEN_SOCKETS *sockets) {
548 // is there anything?
549 if(!*s || s == e) break;
550
552 - char buf[e - s + 1];
553 - strncpyz(buf, s, e - s);
551 + CLEAN_CHAR_P *buf = mallocz((size_t)(e - s) + 1);
552 + strncpyz(buf, s, (size_t)(e - s));
553 bind_to_this(sockets, buf, sockets->default_port, sockets->backlog);
554
555 s = e;
src/libnetdata/socket/nd-poll.c
+3 -1
@@ -182,7 +182,9 @@ static int compare_last_served(const void *a, const void *b) {
182 static void sort_events(nd_poll_t *ndpl) {
183 if(ndpl->used <= 1) return;
184
185 - sortable_event_t sortable_array[ndpl->used];
185 + internal_fatal(ndpl->used > MAX_EVENTS_PER_CALL, "ndpl->used exceeds MAX_EVENTS_PER_CALL");
186 +
187 + sortable_event_t sortable_array[MAX_EVENTS_PER_CALL];
188 for (size_t i = 0; i < ndpl->used; ++i) {
189 struct fd_info *fdi = POINTERS_GET(&ndpl->pointers, ndpl->ev[i].data.fd);
190 sortable_array[i] = (sortable_event_t){
src/libnetdata/socket/nd-sock.c
+1 -2
@@ -76,8 +76,7 @@ bool nd_sock_connect_to_this(ND_SOCK *s, const char *definition, int default_por
76
77 // Extract hostname for SNI before establishing connection
78 if(ssl) {
79 - char buffer[strlen(definition) + 1];
80 - strcpy(buffer, definition);
79 + CLEAN_CHAR_P *buffer = strdupz(definition);
80
81 char *host = buffer;
82
src/libnetdata/spawn_server/log-forwarder.c
+34 -3
@@ -26,6 +26,16 @@ typedef struct LOG_FORWARDER {
26
27 static void log_forwarder_thread_func(void *arg);
28
29 +static inline size_t log_forwarder_max_pfds(void) {
30 + size_t max_nfds = SIZE_MAX / sizeof(struct pollfd);
31 + size_t max_poll_nfds = (size_t)(nfds_t)-1;
32 +
33 + if(max_poll_nfds < max_nfds)
34 + max_nfds = max_poll_nfds;
35 +
36 + return max_nfds;
37 +}
38 +
39 // --------------------------------------------------------------------------------------------------------------------
40 // helper functions
41
@@ -256,6 +266,9 @@ static inline size_t log_forwarder_remove_deleted_unsafe(LOG_FORWARDER *lf) {
266
267 static void log_forwarder_thread_func(void *arg) {
268 LOG_FORWARDER *lf = (LOG_FORWARDER *)arg;
269 + struct pollfd *pfds = NULL;
270 + size_t pfds_capacity = 0;
271 + const size_t max_pfds = log_forwarder_max_pfds();
272
273 while (1) {
274 spinlock_lock(&lf->spinlock);
@@ -272,15 +285,31 @@ static void log_forwarder_thread_func(void *arg) {
285 }
286
287 // Count the number of fds
275 - size_t nfds = 1 + log_forwarder_remove_deleted_unsafe(lf);
288 + size_t entries = log_forwarder_remove_deleted_unsafe(lf);
289 + internal_fatal(entries > max_pfds - 1,
290 + "Log forwarder: too many file descriptors to poll (%zu > %zu)",
291 + entries + 1, max_pfds);
292 + size_t nfds = 1 + entries;
293 +
294 + // Reuse the pollfd array across iterations to avoid heap churn in the worker loop.
295 + if (unlikely(nfds > pfds_capacity)) {
296 + size_t new_capacity = pfds_capacity ? pfds_capacity : 1;
297 + while (new_capacity < nfds) {
298 + internal_fatal(new_capacity > max_pfds / 2,
299 + "Log forwarder: pollfd capacity overflow while growing to %zu fds",
300 + nfds);
301 + new_capacity *= 2;
302 + }
303
277 - struct pollfd pfds[nfds];
304 + pfds = reallocz(pfds, new_capacity * sizeof(*pfds));
305 + pfds_capacity = new_capacity;
306 + }
307
308 // First, the notification pipe
309 pfds[0].fd = lf->pipe_fds[PIPE_READ];
310 pfds[0].events = POLLIN;
311
283 - int idx = 1;
312 + size_t idx = 1;
313 for(LOG_FORWARDER_ENTRY *entry = lf->entries; entry ; entry = entry->next, idx++) {
314 pfds[idx].fd = entry->fd;
315 pfds[idx].events = POLLIN;
@@ -375,6 +404,8 @@ static void log_forwarder_thread_func(void *arg) {
404 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Log forwarder: poll() error");
405 }
406
407 + freez(pfds);
408 +
409 spinlock_lock(&lf->spinlock);
410 mark_all_entries_for_deletion_unsafe(lf);
411 log_forwarder_remove_deleted_unsafe(lf);
src/libnetdata/spawn_server/spawn-tester.c
+83 -125
@@ -45,45 +45,24 @@ void child_check_fds(void) {
45 }
46
47 // --------------------------------------------------------------------------------------------------------------------
48 -// kill to stop
48
50 -int plugin_kill_to_stop() {
51 - child_check_fds();
52 - child_check_environment();
49 +static void test_int_fds_echo_loop(SPAWN_INSTANCE *si, const char *msg, size_t iterations) {
50 + if(!msg || !*msg) return;
51
54 - char buffer[1024];
55 - while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
56 - fprintf(stderr, "+");
57 - printf("%s", buffer);
58 - fflush(stdout);
59 - }
52 + const size_t max_msg_len = (size_t)(SSIZE_MAX / 2);
53 + size_t ulen = strnlen(msg, max_msg_len + 1);
54 + if(unlikely(ulen > max_msg_len))
55 + return;
56
61 - return 0;
62 -}
63 -
64 -void test_int_fds_plugin_kill_to_stop(SPAWN_SERVER *server, const char *argv0) {
65 - const char *params[] = {
66 - argv0,
67 - "plugin-kill-to-stop",
68 - NULL,
69 - };
57 + ssize_t len = (ssize_t)ulen;
58 + size_t buffer_size = ulen * 2;
59 + CLEAN_CHAR_P *buffer = mallocz(buffer_size);
60
71 - SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
72 - if(!si) {
73 - nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
74 - exit(1);
75 - }
76 -
77 - const char *msg = "Hello World!\n";
78 - ssize_t len = strlen(msg);
79 - char buffer[len * 2];
80 -
81 - for(size_t j = 0; j < 30 ;j++) {
61 + for(size_t j = 0; j < iterations; j++) {
62 fprintf(stderr, "-");
83 - memset(buffer, 0, sizeof(buffer));
63 + memset(buffer, 0, buffer_size);
64
65 ssize_t rc = write(spawn_server_instance_write_fd(si), msg, len);
86 -
66 if (rc != len) {
67 nd_log(NDLS_COLLECTORS, NDLP_ERR,
68 "Cannot write to plugin. Expected to write %zd bytes, wrote %zd bytes",
@@ -91,7 +70,7 @@ void test_int_fds_plugin_kill_to_stop(SPAWN_SERVER *server, const char *argv0) {
70 exit(1);
71 }
72
94 - rc = read(spawn_server_instance_read_fd(si), buffer, sizeof(buffer));
73 + rc = read(spawn_server_instance_read_fd(si), buffer, buffer_size);
74 if (rc != len) {
75 nd_log(NDLS_COLLECTORS, NDLP_ERR,
76 "Cannot read from plugin. Expected to read %zd bytes, read %zd bytes",
@@ -107,35 +86,23 @@ void test_int_fds_plugin_kill_to_stop(SPAWN_SERVER *server, const char *argv0) {
86 }
87 }
88 fprintf(stderr, "\n");
110 -
111 - int code = spawn_server_exec_kill(server, si, 0);
112 -
113 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
114 - "child exited with code %d",
115 - code);
116 -
117 - if(code != 15 && code != 0) {
118 - nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0 or 15, but exited with code %d", code);
119 - warnings++;
120 - }
89 }
90
123 -void test_popen_plugin_kill_to_stop(const char *argv0) {
124 - char cmd[FILENAME_MAX + 100];
125 - snprintfz(cmd, sizeof(cmd), "exec %s plugin-kill-to-stop", argv0);
126 - POPEN_INSTANCE *pi = spawn_popen_run(cmd);
127 - if(!pi) {
128 - nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
129 - exit(1);
130 - }
91 +static void test_popen_echo_loop(POPEN_INSTANCE *pi, const char *msg, size_t iterations) {
92 + if(!msg || !*msg) return;
93
132 - const char *msg = "Hello World!\n";
133 - size_t len = strlen(msg);
134 - char buffer[len * 2];
94 + const size_t max_msg_len =
95 + ((size_t)(INT_MAX / 2) < (size_t)(SSIZE_MAX / 2)) ? (size_t)(INT_MAX / 2) : (size_t)(SSIZE_MAX / 2);
96 + size_t len = strnlen(msg, max_msg_len + 1);
97 + if(unlikely(len > max_msg_len))
98 + return;
99
136 - for(size_t j = 0; j < 30 ;j++) {
100 + size_t buffer_size = len * 2;
101 + CLEAN_CHAR_P *buffer = mallocz(buffer_size);
102 +
103 + for(size_t j = 0; j < iterations; j++) {
104 fprintf(stderr, "-");
138 - memset(buffer, 0, sizeof(buffer));
105 + memset(buffer, 0, buffer_size);
106
107 size_t rc = fwrite(msg, 1, len, spawn_popen_stdin(pi));
108 if (rc != len) {
@@ -146,7 +113,7 @@ void test_popen_plugin_kill_to_stop(const char *argv0) {
113 }
114 fflush(spawn_popen_stdin(pi));
115
149 - char *s = fgets(buffer, sizeof(buffer), spawn_popen_stdout(pi));
116 + char *s = fgets(buffer, (int)buffer_size, spawn_popen_stdout(pi));
117 if (!s || strlen(s) != len) {
118 nd_log(NDLS_COLLECTORS, NDLP_ERR,
119 "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
@@ -161,6 +128,62 @@ void test_popen_plugin_kill_to_stop(const char *argv0) {
128 }
129 }
130 fprintf(stderr, "\n");
131 +}
132 +
133 +// --------------------------------------------------------------------------------------------------------------------
134 +// kill to stop
135 +
136 +int plugin_kill_to_stop() {
137 + child_check_fds();
138 + child_check_environment();
139 +
140 + char buffer[1024];
141 + while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
142 + fprintf(stderr, "+");
143 + printf("%s", buffer);
144 + fflush(stdout);
145 + }
146 +
147 + return 0;
148 +}
149 +
150 +void test_int_fds_plugin_kill_to_stop(SPAWN_SERVER *server, const char *argv0) {
151 + const char *params[] = {
152 + argv0,
153 + "plugin-kill-to-stop",
154 + NULL,
155 + };
156 +
157 + SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
158 + if(!si) {
159 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
160 + exit(1);
161 + }
162 +
163 + test_int_fds_echo_loop(si, "Hello World!\n", 30);
164 +
165 + int code = spawn_server_exec_kill(server, si, 0);
166 +
167 + nd_log(NDLS_COLLECTORS, NDLP_ERR,
168 + "child exited with code %d",
169 + code);
170 +
171 + if(code != 15 && code != 0) {
172 + nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0 or 15, but exited with code %d", code);
173 + warnings++;
174 + }
175 +}
176 +
177 +void test_popen_plugin_kill_to_stop(const char *argv0) {
178 + char cmd[FILENAME_MAX + 100];
179 + snprintfz(cmd, sizeof(cmd), "exec %s plugin-kill-to-stop", argv0);
180 + POPEN_INSTANCE *pi = spawn_popen_run(cmd);
181 + if(!pi) {
182 + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
183 + exit(1);
184 + }
185 +
186 + test_popen_echo_loop(pi, "Hello World!\n", 30);
187
188 int code = spawn_popen_kill(pi, 0);
189
@@ -205,39 +228,7 @@ void test_int_fds_plugin_close_to_stop(SPAWN_SERVER *server, const char *argv0)
228 exit(1);
229 }
230
208 - const char *msg = "Hello World!\n";
209 - ssize_t len = strlen(msg);
210 - char buffer[len * 2];
211 -
212 - for(size_t j = 0; j < 30 ;j++) {
213 - fprintf(stderr, "-");
214 - memset(buffer, 0, sizeof(buffer));
215 -
216 - ssize_t rc = write(spawn_server_instance_write_fd(si), msg, len);
217 - if (rc != len) {
218 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
219 - "Cannot write to plugin. Expected to write %zd bytes, wrote %zd bytes",
220 - len, rc);
221 - exit(1);
222 - }
223 -
224 - rc = read(spawn_server_instance_read_fd(si), buffer, sizeof(buffer));
225 - if (rc != len) {
226 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
227 - "Cannot read from plugin. Expected to read %zd bytes, read %zd bytes",
228 - len, rc);
229 - exit(1);
230 - }
231 - if (memcmp(msg, buffer, len) != 0) {
232 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
233 - "Read corrupted data. Expected '%s', Read '%s'",
234 - msg, buffer);
235 - exit(1);
236 - }
237 -
238 - break;
239 - }
240 - fprintf(stderr, "\n");
231 + test_int_fds_echo_loop(si, "Hello World!\n", 1);
232
233 int code = spawn_server_exec_wait(server, si);
234
@@ -260,40 +251,7 @@ void test_popen_plugin_close_to_stop(const char *argv0) {
251 exit(1);
252 }
253
263 - const char *msg = "Hello World!\n";
264 - size_t len = strlen(msg);
265 - char buffer[len * 2];
266 -
267 - for(size_t j = 0; j < 30 ;j++) {
268 - fprintf(stderr, "-");
269 - memset(buffer, 0, sizeof(buffer));
270 -
271 - size_t rc = fwrite(msg, 1, len, spawn_popen_stdin(pi));
272 - if (rc != len) {
273 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
274 - "Cannot write to plugin. Expected to write %zu bytes, wrote %zu bytes",
275 - len, rc);
276 - exit(1);
277 - }
278 - fflush(spawn_popen_stdin(pi));
279 -
280 - char *s = fgets(buffer, sizeof(buffer), spawn_popen_stdout(pi));
281 - if (!s || strlen(s) != len) {
282 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
283 - "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
284 - len, (size_t)(s ? strlen(s) : 0));
285 - exit(1);
286 - }
287 - if (memcmp(msg, buffer, len) != 0) {
288 - nd_log(NDLS_COLLECTORS, NDLP_ERR,
289 - "Read corrupted data. Expected '%s', Read '%s'",
290 - msg, buffer);
291 - exit(1);
292 - }
293 -
294 - break;
295 - }
296 - fprintf(stderr, "\n");
254 + test_popen_echo_loop(pi, "Hello World!\n", 1);
255
256 int code = spawn_popen_wait(pi);
257
src/libnetdata/spawn_server/spawn_popen.c
+5 -2
@@ -84,7 +84,7 @@ POPEN_INSTANCE *spawn_popen_run_variadic(const char *cmd, ...) {
84 va_end(args_copy);
85
86 // Allocate memory for argv array (+2 for cmd and NULL terminator)
87 - const char *argv[argc + 2];
87 + const char **argv = callocz(argc + 2, sizeof(*argv));
88
89 // Populate the argv array
90 argv[0] = cmd;
@@ -97,7 +97,10 @@ POPEN_INSTANCE *spawn_popen_run_variadic(const char *cmd, ...) {
97 // End processing variadic arguments
98 va_end(args);
99
100 - return spawn_popen_run_argv(argv);
100 + POPEN_INSTANCE *pi = spawn_popen_run_argv(argv);
101 + freez(argv);
102 +
103 + return pi;
104 }
105
106 POPEN_INSTANCE *spawn_popen_run(const char *cmd) {
src/libnetdata/spawn_server/spawn_server_windows.c
+6 -2
@@ -61,8 +61,9 @@ static BUFFER *argv_to_windows(const char **argv) {
61 BUFFER *wb = buffer_create(0, NULL);
62
63 // argv[0] is the path
64 - char b[strlen(argv[0]) * 2 + FILENAME_MAX];
65 - cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, sizeof(b));
64 + size_t b_size = strlen(argv[0]) * 2 + FILENAME_MAX;
65 + CLEAN_CHAR_P *b = mallocz(b_size);
66 + cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, b_size);
67
68 for(size_t i = 0; argv[i] ;i++) {
69 const char *s = (i == 0) ? b : argv[i];
@@ -149,6 +150,9 @@ SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd __maybe_un
150 if (type != SPAWN_INSTANCE_TYPE_EXEC)
151 return NULL;
152
153 + if(!argv || !argv[0] || !*argv[0])
154 + return NULL;
155 +
156 int pipe_stdin[2] = { -1, -1 }, pipe_stdout[2] = { -1, -1 }, pipe_stderr[2] = { -1, -1 };
157
158 errno_clear();
src/libnetdata/statistical/statistical.c
+61 -9
@@ -49,37 +49,89 @@ inline NETDATA_DOUBLE average(const NETDATA_DOUBLE *series, size_t entries) {
49
50 // --------------------------------------------------------------------------------------------------------------------
51
52 +// periods up to this size use a stack buffer to avoid heap overhead
53 +#define MOVING_AVERAGE_STACK_PERIOD 32
54 +
55 NETDATA_DOUBLE moving_average(const NETDATA_DOUBLE *series, size_t entries, size_t period) {
53 - if(unlikely(period <= 0))
56 + // Keep the zero-period fast path while making the rolling window size
57 + // unambiguously non-zero for the arithmetic below.
58 + const size_t window = period ? period : 1;
59 +
60 + if(unlikely(period == 0))
61 return 0.0;
62
63 size_t i, count;
64 NETDATA_DOUBLE sum = 0, avg = 0;
58 - NETDATA_DOUBLE p[period];
65
60 - for(count = 0; count < period ; count++)
61 - p[count] = 0.0;
66 + NETDATA_DOUBLE stack_buf[MOVING_AVERAGE_STACK_PERIOD];
67 + NETDATA_DOUBLE *heap_p = NULL;
68 + NETDATA_DOUBLE *p;
69 +
70 + if(window <= MOVING_AVERAGE_STACK_PERIOD) {
71 + memset(stack_buf, 0, window * sizeof(*stack_buf));
72 + p = stack_buf;
73 + } else {
74 + heap_p = callocz(window, sizeof(*heap_p));
75 + p = heap_p;
76 + }
77
78 for(i = 0, count = 0; i < entries; i++) {
79 NETDATA_DOUBLE value = series[i];
80 if(unlikely(!netdata_double_isnumber(value))) continue;
81 + size_t slot = count % window;
82
67 - if(unlikely(count < period)) {
83 + if(unlikely(count < window)) {
84 sum += value;
69 - avg = (count == period - 1) ? sum / (NETDATA_DOUBLE)period : 0;
85 + avg = (count == window - 1) ? sum / (NETDATA_DOUBLE)window : 0;
86 }
87 else {
72 - sum = sum - p[count % period] + value;
73 - avg = sum / (NETDATA_DOUBLE)period;
88 + sum = sum - p[slot] + value;
89 + avg = sum / (NETDATA_DOUBLE)window;
90 }
91
76 - p[count % period] = value;
92 + p[slot] = value;
93 count++;
94 }
95
96 + freez(heap_p);
97 return avg;
98 }
99
100 +static int statistical_unittest_assert_close(const char *name, NETDATA_DOUBLE expected, NETDATA_DOUBLE actual) {
101 + if(ABS(expected - actual) <= 0.000001)
102 + return 0;
103 +
104 + fprintf(stderr, "statistical_unittest: %s failed, expected " NETDATA_DOUBLE_FORMAT ", got " NETDATA_DOUBLE_FORMAT "\n",
105 + name, expected, actual);
106 + return 1;
107 +}
108 +
109 +int statistical_unittest(void) {
110 + int errors = 0;
111 +
112 + NETDATA_DOUBLE series[] = { 1, 2, 3, 4, 5 };
113 + NETDATA_DOUBLE heap_series[MOVING_AVERAGE_STACK_PERIOD + 8];
114 +
115 + for(size_t i = 0; i < sizeof(heap_series) / sizeof(heap_series[0]); i++)
116 + heap_series[i] = (NETDATA_DOUBLE)(i + 1);
117 +
118 + errors += statistical_unittest_assert_close("moving_average(period=0)", 0.0,
119 + moving_average(series, sizeof(series) / sizeof(series[0]), 0));
120 + errors += statistical_unittest_assert_close("moving_average(stack path)", 4.0,
121 + moving_average(series, sizeof(series) / sizeof(series[0]), 3));
122 + errors += statistical_unittest_assert_close("moving_average(heap path)",
123 + ((NETDATA_DOUBLE)(sizeof(heap_series) / sizeof(heap_series[0])) + 1.0) / 2.0,
124 + moving_average(heap_series, sizeof(heap_series) / sizeof(heap_series[0]),
125 + sizeof(heap_series) / sizeof(heap_series[0])));
126 +
127 + if(errors)
128 + fprintf(stderr, "statistical_unittest: %d errors found\n", errors);
129 + else
130 + fprintf(stderr, "statistical_unittest: all tests passed\n");
131 +
132 + return errors ? 1 : 0;
133 +}
134 +
135 // --------------------------------------------------------------------------------------------------------------------
136
137 static int qsort_compare(const void *a, const void *b) {
src/libnetdata/statistical/statistical.h
+3
@@ -32,4 +32,7 @@ NETDATA_DOUBLE percentile_on_sorted_series(const NETDATA_DOUBLE *series, size_t
32 NETDATA_DOUBLE *copy_series(const NETDATA_DOUBLE *series, size_t entries);
33 void sort_series(NETDATA_DOUBLE *series, size_t entries);
34
35 +// unit tests
36 +int statistical_unittest(void);
37 +
38 #endif //NETDATA_STATISTICAL_H
src/libnetdata/string/string.c
+39 -30
@@ -134,11 +134,9 @@ STRING *string_dup(STRING *string) {
134 }
135
136 // Search the index and return an ACQUIRED string entry, or NULL
137 -static STRING *string_index_search(const char *str, size_t length) {
137 +static STRING *string_index_search(const char *str, size_t length, uint8_t partition) {
138 STRING *string;
139
140 - uint8_t partition = string_partition_str(str);
141 -
140 // Find the string in the index
141 // With a read-lock so that multiple readers can use the index concurrently.
142
@@ -177,11 +175,9 @@ static STRING *string_index_search(const char *str, size_t length) {
175 // The returned entry is ACQUIRED, and it can either be:
176 // 1. a new item inserted, or
177 // 2. an item found in the index that is not currently deleted
180 -static STRING *string_index_insert(const char *str, size_t length) {
178 +static STRING *string_index_insert(const char *str, size_t length, uint8_t partition) {
179 STRING *string;
180
183 - uint8_t partition = string_partition_str(str);
184 -
181 rw_spinlock_write_lock(&string_base[partition].spinlock);
182
183 int64_t judy_mem = 0;
@@ -198,8 +194,7 @@ static STRING *string_index_insert(const char *str, size_t length) {
194
195 if (unlikely(Rc == PJERR)) {
196 fatal(
201 - "STRING: Cannot insert entry with name '%s' to JudyHS, JU_ERRNO_* == %u, ID == %d",
202 - str,
197 + "STRING: Cannot insert entry to JudyHS, JU_ERRNO_* == %u, ID == %d",
198 JU_ERRNO(&J_Error),
199 JU_ERRID(&J_Error));
200 }
@@ -210,7 +205,8 @@ static STRING *string_index_insert(const char *str, size_t length) {
205 // a new item added to the index
206 long mem_size = (long)sizeof(STRING) + (long)length;
207 string = mallocz(mem_size);
213 - strcpy((char *)string->str, str);
208 + memcpy((char *)string->str, str, length - 1);
209 + ((char *)string->str)[length - 1] = '\0';
210 string->length = length;
211 string->refcount = 1;
212
@@ -304,21 +300,22 @@ static void string_index_delete(STRING *string) {
300
301 ALWAYS_INLINE
302 STRING *string_strdupz(const char *str) {
307 - if(unlikely(!str || !*str)) return NULL;
303 + size_t length = 0;
304 + if(likely(str))
305 + length = strlen(str);
306
309 -#ifdef NETDATA_INTERNAL_CHECKS
310 - uint8_t partition = string_partition_str(str);
311 -#endif
307 + if(unlikely(!length)) return NULL;
308
313 - size_t length = strlen(str) + 1;
314 - STRING *string = string_index_search(str, length);
309 + length++;
310 + uint8_t partition = string_partition_str(str);
311 + STRING *string = string_index_search(str, length, partition);
312
313 while(!string) {
314 // The search above did not find anything,
315 // We loop here, because during insert we may find an entry that is being deleted by another thread.
316 // So, we have to let it go and retry to insert it again.
317
321 - string = string_index_insert(str, length);
318 + string = string_index_insert(str, length, partition);
319 }
320
321 // statistics
@@ -336,17 +333,11 @@ ALWAYS_INLINE
333 STRING *string_strndupz(const char *str, size_t len) {
334 if(unlikely(!str || !*str || !len)) return NULL;
335
339 -#ifdef NETDATA_INTERNAL_CHECKS
336 uint8_t partition = string_partition_str(str);
341 -#endif
337
343 - char buf[len + 1];
344 - memcpy(buf, str, len);
345 - buf[len] = '\0';
346 -
347 - STRING *string = string_index_search(buf, len + 1);
338 + STRING *string = string_index_search(str, len + 1, partition);
339 while(!string)
349 - string = string_index_insert(buf, len + 1);
340 + string = string_index_insert(str, len + 1, partition);
341
342 string_stats_atomic_increment(partition, active_references);
343
@@ -450,7 +441,9 @@ STRING *string_2way_merge(STRING *a, STRING *b) {
441 size_t alen = string_strlen(a);
442 size_t blen = string_strlen(b);
443 size_t length = alen + blen + string_strlen(string_2way_merge_X) + 1;
453 - char buf1[length + 1], buf2[length + 1], *dst1;
444 + CLEAN_CHAR_P *buf1 = mallocz(length + 1);
445 + CLEAN_CHAR_P *buf2 = mallocz(length + 1);
446 + char *dst1;
447 const char *s1, *s2;
448
449 s1 = string2str(a);
@@ -788,6 +781,22 @@ int string_unittest(size_t entries) {
781 else
782 fprintf(stderr, "OK: string is properly handling different strings\n");
783
784 + STRING *s_null = string_strdupz(NULL);
785 + if(s_null != NULL) {
786 + errors++;
787 + fprintf(stderr, "ERROR: NULL string input should return NULL\n");
788 + }
789 + else
790 + fprintf(stderr, "OK: NULL string input returns NULL\n");
791 +
792 + STRING *s_empty = string_strdupz("");
793 + if(s_empty != NULL) {
794 + errors++;
795 + fprintf(stderr, "ERROR: empty string input should return NULL\n");
796 + }
797 + else
798 + fprintf(stderr, "OK: empty string input returns NULL\n");
799 +
800 usec_t start_ut, end_ut;
801 STRING **strings = mallocz(entries * sizeof(STRING *));
802
@@ -902,16 +911,16 @@ int string_unittest(size_t entries) {
911 string_statistics(&oinserts, &odeletes, &osearches, &oentries, &oreferences, &omemory, &omemory_index, &oduplications, &oreleases);
912
913 time_t seconds_to_run = 5;
905 - int threads_to_create = 2;
914 + enum { STRING_UNITTEST_THREADS = 2 };
915 fprintf(
916 stderr,
917 "Checking string concurrency with %d threads for %lld seconds...\n",
909 - threads_to_create,
918 + STRING_UNITTEST_THREADS,
919 (long long)seconds_to_run);
920 // check string concurrency
912 - ND_THREAD *threads[threads_to_create];
921 + ND_THREAD *threads[STRING_UNITTEST_THREADS];
922 tu.join = 0;
914 - for (int i = 0; i < threads_to_create; i++) {
923 + for (int i = 0; i < STRING_UNITTEST_THREADS; i++) {
924 char buf[100 + 1];
925 snprintf(buf, 100, "string%d", i);
926 threads[i] = nd_thread_create(buf, NETDATA_THREAD_OPTION_DONT_LOG, string_thread, &tu);
@@ -919,7 +928,7 @@ int string_unittest(size_t entries) {
928 sleep_usec(seconds_to_run * USEC_PER_SEC);
929
930 __atomic_store_n(&tu.join, 1, __ATOMIC_RELAXED);
922 - for (int i = 0; i < threads_to_create; i++)
931 + for (int i = 0; i < STRING_UNITTEST_THREADS; i++)
932 nd_thread_join(threads[i]);
933
934 size_t inserts, deletes, searches, sentries, references, memory, memory_index, duplications, releases;
src/libnetdata/url/url.c
+1 -1
@@ -272,7 +272,7 @@ url_is_request_complete_and_extract_payload(const char *begin, const char *end,
272 while (*space && !isspace((uint8_t)*space) && *space != ';') space++;
273 size_t ct_len = space - ct;
274
275 - char ct_copy[ct_len + 1];
275 + CLEAN_CHAR_P *ct_copy = mallocz(ct_len + 1);
276 memcpy(ct_copy, ct, ct_len);
277 ct_copy[ct_len] = '\0';
278
src/libnetdata/uuid/uuidmap.c
+8 -9
@@ -362,20 +362,19 @@ static void concurrent_test_thread(void *arg) {
362 }
363
364 static int uuidmap_concurrent_unittest(void) {
365 - const int num_threads = 4;
366 - const int num_seconds = 5;
367 - fprintf(stderr, "\nTesting concurrent UUID Map access with %d threads for %d seconds...\n", num_threads, num_seconds);
365 + enum { UUIDMAP_UNITTEST_THREADS = 4, UUIDMAP_UNITTEST_SECONDS = 5 };
366 + fprintf(stderr, "\nTesting concurrent UUID Map access with %d threads for %d seconds...\n", UUIDMAP_UNITTEST_THREADS, UUIDMAP_UNITTEST_SECONDS);
367 int errors = 0;
368
370 - THREAD_STATS stats[num_threads];
369 + THREAD_STATS stats[UUIDMAP_UNITTEST_THREADS];
370 memset(stats, 0, sizeof(stats));
371
373 - ND_THREAD *threads[num_threads];
372 + ND_THREAD *threads[UUIDMAP_UNITTEST_THREADS];
373
374 // Start threads
375 __atomic_store_n(&stop_flag, false, __ATOMIC_RELAXED);
376
378 - for(int i = 0; i < num_threads; i++) {
377 + for(int i = 0; i < UUIDMAP_UNITTEST_THREADS; i++) {
378 char thread_name[32];
379 snprintf(thread_name, sizeof(thread_name), "UUID-TEST-%d", i);
380 threads[i] = nd_thread_create(
@@ -386,18 +385,18 @@ static int uuidmap_concurrent_unittest(void) {
385 }
386
387 // Let it run for 5 seconds
389 - sleep_usec(num_seconds * USEC_PER_SEC);
388 + sleep_usec(UUIDMAP_UNITTEST_SECONDS * USEC_PER_SEC);
389
390 // Stop threads
391 __atomic_store_n(&stop_flag, true, __ATOMIC_RELEASE);
392
393 // Wait for threads
395 - for(int i = 0; i < num_threads; i++)
394 + for(int i = 0; i < UUIDMAP_UNITTEST_THREADS; i++)
395 nd_thread_join(threads[i]);
396
397 // Print statistics
398 size_t total_cycles = 0;
400 - for(int i = 0; i < num_threads; i++) {
399 + for(int i = 0; i < UUIDMAP_UNITTEST_THREADS; i++) {
400 fprintf(stderr, "Thread %d stats:\n"
401 " Cycles completed : %zu\n"
402 " Creates : %zu\n"