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"