3
#define NETDATA_RRD_INTERNALS
4
#include "rrd.h"
5
6
-char *translate_label_source(LABEL_SOURCE l) {
7
- switch (l) {
8
- case LABEL_SOURCE_AUTO:
9
- return "AUTO";
10
- case LABEL_SOURCE_NETDATA_CONF:
11
- return "NETDATA.CONF";
12
- case LABEL_SOURCE_DOCKER :
13
- return "DOCKER";
14
- case LABEL_SOURCE_ENVIRONMENT :
15
- return "ENVIRONMENT";
16
- case LABEL_SOURCE_KUBERNETES :
17
- return "KUBERNETES";
18
- default:
19
- return "Invalid label source";
20
- }
21
-}
22
-
23
-int is_valid_label_value(char *value) {
24
- while(*value) {
25
- if(*value == '"' || *value == '\'' || *value == '*' || *value == '!') {
26
- return 0;
6
+// ----------------------------------------------------------------------------
7
+// labels sanitization
8
+
9
+/*
10
+ * All labels follow these rules:
11
+ *
12
+ * Character Symbol Values Names
13
+ * UTF-8 characters UTF-8 yes -> _
14
+ * Lower case letter [a-z] yes yes
15
+ * Upper case letter [A-Z] yes -> [a-z]
16
+ * Digit [0-9] yes yes
17
+ * Underscore _ yes yes
18
+ * Minus - yes yes
19
+ * Plus + yes -> _
20
+ * Colon : yes -> _
21
+ * Semicolon ; -> : -> _
22
+ * Equal = -> : -> _
23
+ * Period . yes yes
24
+ * Comma , -> . -> .
25
+ * Slash / yes -> _
26
+ * Backslash \ -> / -> _
27
+ * At @ yes -> _
28
+ * Space -> _ yes
29
+ * Opening parenthesis ( -> _ yes
30
+ * Closing parenthesis ) -> _ yes
31
+ * anything else -> _ -> _
32
+*
33
+ * The above rules should allow users to set in tags (indicative):
34
+ *
35
+ * 1. hostnames and domain names as-is
36
+ * 2. email addresses as-is
37
+ * 3. floating point numbers, converted to always use a dot as the decimal point
38
+ *
39
+ * Leading and trailing spaces and control characters are removed from both label
40
+ * names and values.
41
+ *
42
+ * Multiple spaces inside the label name or the value are removed (only 1 is retained).
43
+ * In names spaces are also converted to underscores.
44
+ *
45
+ * Names that are only underscores are rejected (they do not enter the dictionary).
46
+ *
47
+ * The above rules do not require any conversion to be included in JSON strings.
48
+ *
49
+ * Label names and values are truncated to LABELS_MAX_LENGTH (200) characters.
50
+ *
51
+ * When parsing, label key and value are separated by the first colon (:) found.
52
+ * So label:value1:value2 is parsed as key = "label", value = "value1:value2"
53
+ *
54
+ * This means a label key cannot contain a colon (:) - it is converted to
55
+ * underscore if it does.
56
+ *
57
+ */
58
+
59
+#define RRDLABELS_MAX_NAME_LENGTH 200
60
+#define RRDLABELS_MAX_VALUE_LENGTH 800 // 800 in bytes, up to 200 UTF-8 characters
61
+
62
+static unsigned char label_spaces_char_map[256];
63
+static unsigned char label_names_char_map[256];
64
+static unsigned char label_values_char_map[256] = {
65
+ [0] = '\0', //
66
+ [1] = '_', //
67
+ [2] = '_', //
68
+ [3] = '_', //
69
+ [4] = '_', //
70
+ [5] = '_', //
71
+ [6] = '_', //
72
+ [7] = '_', //
73
+ [8] = '_', //
74
+ [9] = '_', //
75
+ [10] = '_', //
76
+ [11] = '_', //
77
+ [12] = '_', //
78
+ [13] = '_', //
79
+ [14] = '_', //
80
+ [15] = '_', //
81
+ [16] = '_', //
82
+ [17] = '_', //
83
+ [18] = '_', //
84
+ [19] = '_', //
85
+ [20] = '_', //
86
+ [21] = '_', //
87
+ [22] = '_', //
88
+ [23] = '_', //
89
+ [24] = '_', //
90
+ [25] = '_', //
91
+ [26] = '_', //
92
+ [27] = '_', //
93
+ [28] = '_', //
94
+ [29] = '_', //
95
+ [30] = '_', //
96
+ [31] = '_', //
97
+ [32] = ' ', // SPACE keep
98
+ [33] = '_', // !
99
+ [34] = '_', // "
100
+ [35] = '_', // #
101
+ [36] = '_', // $
102
+ [37] = '_', // %
103
+ [38] = '_', // &
104
+ [39] = '_', // '
105
+ [40] = '(', // ( keep
106
+ [41] = ')', // ) keep
107
+ [42] = '_', // *
108
+ [43] = '+', // + keep
109
+ [44] = '.', // , convert , to .
110
+ [45] = '-', // - keep
111
+ [46] = '.', // . keep
112
+ [47] = '/', // / keep
113
+ [48] = '0', // 0 keep
114
+ [49] = '1', // 1 keep
115
+ [50] = '2', // 2 keep
116
+ [51] = '3', // 3 keep
117
+ [52] = '4', // 4 keep
118
+ [53] = '5', // 5 keep
119
+ [54] = '6', // 6 keep
120
+ [55] = '7', // 7 keep
121
+ [56] = '8', // 8 keep
122
+ [57] = '9', // 9 keep
123
+ [58] = ':', // : keep
124
+ [59] = ':', // ; convert ; to :
125
+ [60] = '_', // <
126
+ [61] = ':', // = convert = to :
127
+ [62] = '_', // >
128
+ [63] = '_', // ?
129
+ [64] = '@', // @
130
+ [65] = 'A', // A keep
131
+ [66] = 'B', // B keep
132
+ [67] = 'C', // C keep
133
+ [68] = 'D', // D keep
134
+ [69] = 'E', // E keep
135
+ [70] = 'F', // F keep
136
+ [71] = 'G', // G keep
137
+ [72] = 'H', // H keep
138
+ [73] = 'I', // I keep
139
+ [74] = 'J', // J keep
140
+ [75] = 'K', // K keep
141
+ [76] = 'L', // L keep
142
+ [77] = 'M', // M keep
143
+ [78] = 'N', // N keep
144
+ [79] = 'O', // O keep
145
+ [80] = 'P', // P keep
146
+ [81] = 'Q', // Q keep
147
+ [82] = 'R', // R keep
148
+ [83] = 'S', // S keep
149
+ [84] = 'T', // T keep
150
+ [85] = 'U', // U keep
151
+ [86] = 'V', // V keep
152
+ [87] = 'W', // W keep
153
+ [88] = 'X', // X keep
154
+ [89] = 'Y', // Y keep
155
+ [90] = 'Z', // Z keep
156
+ [91] = '_', // [
157
+ [92] = '/', // backslash convert \ to /
158
+ [93] = '_', // ]
159
+ [94] = '_', // ^
160
+ [95] = '_', // _ keep
161
+ [96] = '_', // `
162
+ [97] = 'a', // a keep
163
+ [98] = 'b', // b keep
164
+ [99] = 'c', // c keep
165
+ [100] = 'd', // d keep
166
+ [101] = 'e', // e keep
167
+ [102] = 'f', // f keep
168
+ [103] = 'g', // g keep
169
+ [104] = 'h', // h keep
170
+ [105] = 'i', // i keep
171
+ [106] = 'j', // j keep
172
+ [107] = 'k', // k keep
173
+ [108] = 'l', // l keep
174
+ [109] = 'm', // m keep
175
+ [110] = 'n', // n keep
176
+ [111] = 'o', // o keep
177
+ [112] = 'p', // p keep
178
+ [113] = 'q', // q keep
179
+ [114] = 'r', // r keep
180
+ [115] = 's', // s keep
181
+ [116] = 't', // t keep
182
+ [117] = 'u', // u keep
183
+ [118] = 'v', // v keep
184
+ [119] = 'w', // w keep
185
+ [120] = 'x', // x keep
186
+ [121] = 'y', // y keep
187
+ [122] = 'z', // z keep
188
+ [123] = '_', // {
189
+ [124] = '_', // |
190
+ [125] = '_', // }
191
+ [126] = '_', // ~
192
+ [127] = '_', //
193
+ [128] = '_', //
194
+ [129] = '_', //
195
+ [130] = '_', //
196
+ [131] = '_', //
197
+ [132] = '_', //
198
+ [133] = '_', //
199
+ [134] = '_', //
200
+ [135] = '_', //
201
+ [136] = '_', //
202
+ [137] = '_', //
203
+ [138] = '_', //
204
+ [139] = '_', //
205
+ [140] = '_', //
206
+ [141] = '_', //
207
+ [142] = '_', //
208
+ [143] = '_', //
209
+ [144] = '_', //
210
+ [145] = '_', //
211
+ [146] = '_', //
212
+ [147] = '_', //
213
+ [148] = '_', //
214
+ [149] = '_', //
215
+ [150] = '_', //
216
+ [151] = '_', //
217
+ [152] = '_', //
218
+ [153] = '_', //
219
+ [154] = '_', //
220
+ [155] = '_', //
221
+ [156] = '_', //
222
+ [157] = '_', //
223
+ [158] = '_', //
224
+ [159] = '_', //
225
+ [160] = '_', //
226
+ [161] = '_', //
227
+ [162] = '_', //
228
+ [163] = '_', //
229
+ [164] = '_', //
230
+ [165] = '_', //
231
+ [166] = '_', //
232
+ [167] = '_', //
233
+ [168] = '_', //
234
+ [169] = '_', //
235
+ [170] = '_', //
236
+ [171] = '_', //
237
+ [172] = '_', //
238
+ [173] = '_', //
239
+ [174] = '_', //
240
+ [175] = '_', //
241
+ [176] = '_', //
242
+ [177] = '_', //
243
+ [178] = '_', //
244
+ [179] = '_', //
245
+ [180] = '_', //
246
+ [181] = '_', //
247
+ [182] = '_', //
248
+ [183] = '_', //
249
+ [184] = '_', //
250
+ [185] = '_', //
251
+ [186] = '_', //
252
+ [187] = '_', //
253
+ [188] = '_', //
254
+ [189] = '_', //
255
+ [190] = '_', //
256
+ [191] = '_', //
257
+ [192] = '_', //
258
+ [193] = '_', //
259
+ [194] = '_', //
260
+ [195] = '_', //
261
+ [196] = '_', //
262
+ [197] = '_', //
263
+ [198] = '_', //
264
+ [199] = '_', //
265
+ [200] = '_', //
266
+ [201] = '_', //
267
+ [202] = '_', //
268
+ [203] = '_', //
269
+ [204] = '_', //
270
+ [205] = '_', //
271
+ [206] = '_', //
272
+ [207] = '_', //
273
+ [208] = '_', //
274
+ [209] = '_', //
275
+ [210] = '_', //
276
+ [211] = '_', //
277
+ [212] = '_', //
278
+ [213] = '_', //
279
+ [214] = '_', //
280
+ [215] = '_', //
281
+ [216] = '_', //
282
+ [217] = '_', //
283
+ [218] = '_', //
284
+ [219] = '_', //
285
+ [220] = '_', //
286
+ [221] = '_', //
287
+ [222] = '_', //
288
+ [223] = '_', //
289
+ [224] = '_', //
290
+ [225] = '_', //
291
+ [226] = '_', //
292
+ [227] = '_', //
293
+ [228] = '_', //
294
+ [229] = '_', //
295
+ [230] = '_', //
296
+ [231] = '_', //
297
+ [232] = '_', //
298
+ [233] = '_', //
299
+ [234] = '_', //
300
+ [235] = '_', //
301
+ [236] = '_', //
302
+ [237] = '_', //
303
+ [238] = '_', //
304
+ [239] = '_', //
305
+ [240] = '_', //
306
+ [241] = '_', //
307
+ [242] = '_', //
308
+ [243] = '_', //
309
+ [244] = '_', //
310
+ [245] = '_', //
311
+ [246] = '_', //
312
+ [247] = '_', //
313
+ [248] = '_', //
314
+ [249] = '_', //
315
+ [250] = '_', //
316
+ [251] = '_', //
317
+ [252] = '_', //
318
+ [253] = '_', //
319
+ [254] = '_', //
320
+ [255] = '_' //
321
+};
322
+
323
+__attribute__((constructor)) void initialize_labels_keys_char_map(void) {
324
+ // copy the values char map to the names char map
325
+ size_t i;
326
+ for(i = 0; i < 256 ;i++)
327
+ label_names_char_map[i] = label_values_char_map[i];
328
+
329
+ // apply overrides to the label names map
330
+ label_names_char_map['A'] = 'a';
331
+ label_names_char_map['B'] = 'b';
332
+ label_names_char_map['C'] = 'c';
333
+ label_names_char_map['D'] = 'd';
334
+ label_names_char_map['E'] = 'e';
335
+ label_names_char_map['F'] = 'f';
336
+ label_names_char_map['G'] = 'g';
337
+ label_names_char_map['H'] = 'h';
338
+ label_names_char_map['I'] = 'i';
339
+ label_names_char_map['J'] = 'j';
340
+ label_names_char_map['K'] = 'k';
341
+ label_names_char_map['L'] = 'l';
342
+ label_names_char_map['M'] = 'm';
343
+ label_names_char_map['N'] = 'n';
344
+ label_names_char_map['O'] = 'o';
345
+ label_names_char_map['P'] = 'p';
346
+ label_names_char_map['Q'] = 'q';
347
+ label_names_char_map['R'] = 'r';
348
+ label_names_char_map['S'] = 's';
349
+ label_names_char_map['T'] = 't';
350
+ label_names_char_map['U'] = 'u';
351
+ label_names_char_map['V'] = 'v';
352
+ label_names_char_map['W'] = 'w';
353
+ label_names_char_map['X'] = 'x';
354
+ label_names_char_map['Y'] = 'y';
355
+ label_names_char_map['Z'] = 'z';
356
+ label_names_char_map['='] = '_';
357
+ label_names_char_map[':'] = '_';
358
+ label_names_char_map['+'] = '_';
359
+ label_names_char_map[';'] = '_';
360
+ label_names_char_map['@'] = '_';
361
+ label_names_char_map['/'] = '_';
362
+ label_names_char_map['('] = '_';
363
+ label_names_char_map[')'] = '_';
364
+ label_names_char_map[' '] = '_';
365
+ label_names_char_map['\\'] = '_';
366
+
367
+ // create the spaces map
368
+ for(i = 0; i < 256 ;i++)
369
+ label_spaces_char_map[i] = (isspace(i) || iscntrl(i) || !isprint(i))?1:0;
370
+
371
+}
372
+
373
+static size_t rrdlabels_sanitize(unsigned char *dst, const unsigned char *src, size_t dst_size, unsigned char *char_map, bool utf) {
374
+ if(unlikely(!dst_size)) return 0;
375
+ if(unlikely(!src || !*src)) {
376
+ *dst = '\0';
377
+ return 0;
378
+ }
379
+
380
+ unsigned char *d = dst;
381
+
382
+ // make room for the final string termination
383
+ unsigned char *end = &d[dst_size - 1];
384
+
385
+ // copy while converting, but keep only one white space
386
+ // we start wil last_is_space = 1 to skip leading spaces
387
+ int last_is_space = 1;
388
+ size_t mblen = 0;
389
+ while(*src && d < end) {
390
+ unsigned char c = *src;
391
+
392
+ if(IS_UTF8_STARTBYTE(c) && IS_UTF8_BYTE(src[1]) && d + 2 < end) {
393
+ // UTF-8 multi-byte encoded character
394
+
395
+ // find how big this character is (2-4 bytes)
396
+ size_t utf_character_size = 2;
397
+ while(utf_character_size <= 4 && src[utf_character_size] && IS_UTF8_BYTE(src[utf_character_size]) && !IS_UTF8_STARTBYTE(src[utf_character_size]))
398
+ utf_character_size++;
399
+
400
+ if(utf) {
401
+ while(utf_character_size) {
402
+ utf_character_size--;
403
+ *d++ = *src++;
404
+ }
405
+ }
406
+ else {
407
+ // UTF-8 characters are not allowed.
408
+ // Assume it is an underscore
409
+ // and skip all except the first byte
410
+ *d++ = '_';
411
+ src += (utf_character_size - 1);
412
+ }
413
+
414
+ last_is_space = 0;
415
+ mblen++;
416
+ continue;
417
+ }
418
+
419
+ if(label_spaces_char_map[c]) {
420
+ // a space character
421
+
422
+ if(!last_is_space) {
423
+ // add one space
424
+ *d++ = char_map[c];
425
+ mblen++;
426
+ }
427
+
428
+ last_is_space++;
429
+ }
430
+ else {
431
+ *d++ = char_map[c];
432
+ last_is_space = 0;
433
+ mblen++;
434
}
435
29
- value++;
436
+ src++;
437
}
438
32
- return 1;
439
+ // remove the last trailing space
440
+ if(last_is_space && d > dst) {
441
+ d--;
442
+ mblen--;
443
+ }
444
+
445
+ // put a termination at the end of what we copied
446
+ *d = '\0';
447
+
448
+ // check if dst is all underscores and empty it if it is
449
+ d = dst;
450
+ while(*d == '_') d++;
451
+ if(!*d) {
452
+ *dst = '\0';
453
+ mblen = 0;
454
+ }
455
+
456
+ return mblen;
457
}
458
35
-int is_valid_label_key(char *key) {
36
- //Prometheus exporter
37
- if(!strcmp(key, "chart") || !strcmp(key, "family") || !strcmp(key, "dimension"))
38
- return 0;
459
+static inline size_t rrdlabels_sanitize_name(char *dst, const char *src, size_t dst_size) {
460
+ return rrdlabels_sanitize((unsigned char *)dst, (const unsigned char *)src, dst_size, label_names_char_map, 0);
461
+}
462
40
- //Netdata and Prometheus internal
41
- if (*key == '_')
42
- return 0;
463
+static inline size_t rrdlabels_sanitize_value(char *dst, const char *src, size_t dst_size) {
464
+ return rrdlabels_sanitize((unsigned char *)dst, (const unsigned char *)src, dst_size, label_values_char_map, 1);
465
+}
466
+
467
+// ----------------------------------------------------------------------------
468
+// rrdlabels_create()
469
+
470
+typedef struct rrdlabel {
471
+ const char *value;
472
+ RRDLABEL_SRC label_source;
473
+} RRDLABEL;
474
44
- while(*key) {
45
- if(!(isdigit(*key) || isalpha(*key) || *key == '.' || *key == '_' || *key == '-'))
46
- return 0;
475
+static void rrdlabel_insert_callback(const char *name, void *value, void *data) {
476
+ (void)name;
477
+ DICTIONARY *dict = (DICTIONARY *)data; (void)dict;
478
+ RRDLABEL *lb = (RRDLABEL *)value;
479
48
- key++;
480
+ // allocate our own memory for the value
481
+ lb->value = strdupz(lb->value);
482
+ lb->label_source |= RRDLABEL_FLAG_NEW;
483
+}
484
+
485
+static void rrdlabel_delete_callback(const char *name, void *value, void *data) {
486
+ (void)name;
487
+ DICTIONARY *dict = (DICTIONARY *)data; (void)dict;
488
+ RRDLABEL *lb = (RRDLABEL *)value;
489
+
490
+ freez((void *)lb->value);
491
+ lb->value = NULL;
492
+}
493
+
494
+static void rrdlabel_conflict_callback(const char *name, void *oldvalue, void *newvalue, void *data) {
495
+ (void)name;
496
+ DICTIONARY *dict = (DICTIONARY *)data; (void)dict;
497
+ RRDLABEL *lbold = (RRDLABEL *)oldvalue;
498
+ RRDLABEL *lbnew = (RRDLABEL *)newvalue;
499
+
500
+ if(strcmp(lbold->value, lbnew->value) == 0) {
501
+ // they are the same
502
+ lbold->label_source |= lbnew->label_source;
503
+ lbold->label_source |= RRDLABEL_FLAG_OLD;
504
+ }
505
+ else {
506
+ // they are different
507
+ freez((void *)lbold->value);
508
+ lbold->value = strdupz(lbnew->value);
509
+ lbold->label_source = lbnew->label_source;
510
+ lbold->label_source |= RRDLABEL_FLAG_NEW;
511
}
512
+}
513
51
- return 1;
514
+DICTIONARY *rrdlabels_create(void) {
515
+ DICTIONARY *dict = dictionary_create(DICTIONARY_FLAG_DONT_OVERWRITE_VALUE);
516
+ dictionary_register_insert_callback(dict, rrdlabel_insert_callback, dict);
517
+ dictionary_register_delete_callback(dict, rrdlabel_delete_callback, dict);
518
+ dictionary_register_conflict_callback(dict, rrdlabel_conflict_callback, dict);
519
+ return dict;
520
}
521
54
-void strip_last_symbol(
55
- char *str,
56
- char symbol,
57
- SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters)
58
-{
59
- char *end = str;
522
61
- while (*end && *end != symbol) {
62
- if (unlikely(skip_escaped_characters && *end == '\\')) {
63
- end++;
64
- if (unlikely(!*end))
65
- break;
66
- }
67
- end++;
523
+// ----------------------------------------------------------------------------
524
+// rrdlabels_destroy()
525
+
526
+void rrdlabels_destroy(DICTIONARY *labels_dict) {
527
+ dictionary_destroy(labels_dict);
528
+}
529
+
530
+
531
+// ----------------------------------------------------------------------------
532
+// rrdlabels_add()
533
+
534
+static void labels_add_already_sanitized(DICTIONARY *dict, const char *key, const char *value, RRDLABEL_SRC ls) {
535
+ if(ls & RRDLABEL_FLAG_NEW) ls &= ~RRDLABEL_FLAG_NEW;
536
+ if(ls & RRDLABEL_FLAG_OLD) ls &= ~RRDLABEL_FLAG_OLD;
537
+
538
+ RRDLABEL tmp = {
539
+ .label_source = ls,
540
+ .value = value
541
+ };
542
+ dictionary_set(dict, key, &tmp, sizeof(RRDLABEL));
543
+}
544
+
545
+
546
+void rrdlabels_add(DICTIONARY *dict, const char *name, const char *value, RRDLABEL_SRC ls) {
547
+ if(!dict) {
548
+ error("%s(): called with NULL dictionary.", __FUNCTION__ );
549
+ return;
550
}
69
- if (likely(*end == symbol))
70
- *end = '\0';
551
+
552
+ char n[RRDLABELS_MAX_NAME_LENGTH + 1], v[RRDLABELS_MAX_VALUE_LENGTH + 1];
553
+ rrdlabels_sanitize_name(n, name, RRDLABELS_MAX_NAME_LENGTH);
554
+ rrdlabels_sanitize_value(v, value, RRDLABELS_MAX_VALUE_LENGTH);
555
+
556
+ if(!*n) {
557
+ error("%s: cannot add name '%s' (value '%s') which is sanitized as empty string", __FUNCTION__, name, value);
558
+ return;
559
+ }
560
+
561
+ labels_add_already_sanitized(dict, n, v, ls);
562
}
563
73
-char *strip_double_quotes(char *str, SKIP_ESCAPED_CHARACTERS_OPTION skip_escaped_characters)
74
-{
75
- if (*str == '"') {
76
- str++;
77
- strip_last_symbol(str, '"', skip_escaped_characters);
564
+static const char *get_quoted_string_up_to(char *dst, size_t dst_size, const char *string, char upto1, char upto2) {
565
+ size_t len = 0;
566
+ char *d = dst, quote = 0;
567
+ while(*string && len++ < dst_size) {
568
+ if(unlikely(!quote && (*string == '\'' || *string == '"'))) {
569
+ quote = *string++;
570
+ continue;
571
+ }
572
+
573
+ if(unlikely(quote && *string == quote)) {
574
+ quote = 0;
575
+ string++;
576
+ continue;
577
+ }
578
+
579
+ if(unlikely(quote && *string == '\\' && string[1])) {
580
+ string++;
581
+ *d++ = *string++;
582
+ continue;
583
+ }
584
+
585
+ if(unlikely(!quote && (*string == upto1 || *string == upto2))) break;
586
+
587
+ *d++ = *string++;
588
}
589
+ *d = '\0';
590
80
- return str;
591
+ if(*string) string++;
592
+
593
+ return string;
594
}
595
83
-struct label *create_label(char *key, char *value, LABEL_SOURCE label_source)
84
-{
85
- size_t key_len = strlen(key), value_len = strlen(value);
86
- size_t n = sizeof(struct label) + key_len + 1 + value_len + 1;
87
- struct label *result = callocz(1,n);
88
- if (result != NULL) {
89
- char *c = (char *)result;
90
- c += sizeof(struct label);
91
- strcpy(c, key);
92
- result->key = c;
93
- c += key_len + 1;
94
- strcpy(c, value);
95
- result->value = c;
96
- result->label_source = label_source;
97
- result->key_hash = simple_hash(result->key);
596
+void rrdlabels_add_pair(DICTIONARY *dict, const char *string, RRDLABEL_SRC ls) {
597
+ if(!dict) {
598
+ error("%s(): called with NULL dictionary.", __FUNCTION__ );
599
+ return;
600
}
99
- return result;
601
+
602
+ char name[RRDLABELS_MAX_NAME_LENGTH + 1];
603
+ string = get_quoted_string_up_to(name, RRDLABELS_MAX_NAME_LENGTH, string, '=', ':');
604
+
605
+ char value[RRDLABELS_MAX_VALUE_LENGTH + 1];
606
+ get_quoted_string_up_to(value, RRDLABELS_MAX_VALUE_LENGTH, string, '\0', '\0');
607
+
608
+ rrdlabels_add(dict, name, value, ls);
609
}
610
102
-void free_label_list(struct label *labels)
103
-{
104
- while (labels != NULL)
105
- {
106
- struct label *current = labels;
107
- labels = labels->next;
108
- freez(current);
611
+// ----------------------------------------------------------------------------
612
+// rrdlabels_get_to_buffer_or_null()
613
+
614
+void rrdlabels_get_value_to_buffer_or_null(DICTIONARY *labels, BUFFER *wb, const char *key, const char *quote, const char *null) {
615
+ // Get a read lock on the dictionary
616
+ // to copy the value into the buffer
617
+ void *v;
618
+ dfe_start_read(labels, v) {
619
+ RRDLABEL *lb = dictionary_get_having_read_lock(labels, key);
620
+
621
+ if(lb && lb->value)
622
+ buffer_sprintf(wb, "%s%s%s", quote, lb->value, quote);
623
+ else
624
+ buffer_strcat(wb, null);
625
+
626
+ break;
627
}
628
+ dfe_done(v);
629
}
630
112
-void replace_label_list(struct label_index *labels, struct label *new_labels)
113
-{
114
- netdata_rwlock_wrlock(&labels->labels_rwlock);
115
- struct label *old_labels = labels->head;
116
- labels->head = new_labels;
117
- netdata_rwlock_unlock(&labels->labels_rwlock);
631
119
- free_label_list(old_labels);
632
+// ----------------------------------------------------------------------------
633
+// rrdlabels_unmark_all()
634
+// remove labels RRDLABEL_FLAG_OLD and RRDLABEL_FLAG_NEW from all dictionary items
635
+
636
+static int remove_flags_old_new(const char *name, void *value, void *data) {
637
+ (void)name;
638
+ (void)data;
639
+
640
+ RRDLABEL *lb = (RRDLABEL *)value;
641
+
642
+ if(lb->label_source & RRDLABEL_FLAG_OLD) lb->label_source &= ~RRDLABEL_FLAG_OLD;
643
+ if(lb->label_source & RRDLABEL_FLAG_NEW) lb->label_source &= ~RRDLABEL_FLAG_NEW;
644
+
645
+ return 1;
646
}
647
122
-struct label *add_label_to_list(struct label *l, char *key, char *value, LABEL_SOURCE label_source)
123
-{
124
- struct label *lab = create_label(key, value, label_source);
125
- lab->next = l;
126
- return lab;
648
+void rrdlabels_unmark_all(DICTIONARY *labels) {
649
+ dictionary_walkthrough_read(labels, remove_flags_old_new, NULL);
650
}
651
129
-void update_label_list(struct label **labels, struct label *new_labels)
130
-{
131
- free_label_list(*labels);
132
- *labels = NULL;
652
134
- while (new_labels != NULL)
135
- {
136
- *labels = add_label_to_list(*labels, new_labels->key, new_labels->value, new_labels->label_source);
137
- new_labels = new_labels->next;
653
+// ----------------------------------------------------------------------------
654
+// rrdlabels_remove_all_unmarked()
655
+// remove dictionary items that are neither old, nor new
656
+
657
+static int remove_not_old_not_new_callback(const char *name, void *value, void *data) {
658
+ DICTIONARY *dict = (DICTIONARY *)data;
659
+ RRDLABEL *lb = (RRDLABEL *)value;
660
+
661
+ if(!(lb->label_source & RRDLABEL_FLAG_OLD) && !(lb->label_source & RRDLABEL_FLAG_NEW)) {
662
+ dictionary_del_having_write_lock(dict, name);
663
+ return 1;
664
}
665
+
666
+ return 0;
667
}
668
141
-struct label *label_list_lookup_key(struct label *head, char *key, uint32_t key_hash)
142
-{
143
- while (head != NULL)
144
- {
145
- if (head->key_hash == key_hash && !strcmp(head->key, key))
146
- return head;
147
- head = head->next;
148
- }
149
- return NULL;
669
+void rrdlabels_remove_all_unmarked(DICTIONARY *labels) {
670
+ dictionary_walkthrough_write(labels, remove_not_old_not_new_callback, labels);
671
}
672
152
-int label_list_contains_key(struct label *head, char *key, uint32_t key_hash)
153
-{
154
- return (label_list_lookup_key(head, key, key_hash) != NULL);
673
+
674
+// ----------------------------------------------------------------------------
675
+// rrdlabels_walkthrough_read()
676
+
677
+struct labels_walkthrough {
678
+ int (*callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data);
679
+ void *data;
680
+};
681
+
682
+static int labels_walkthrough_callback(const char *name, void *value, void *data) {
683
+ struct labels_walkthrough *d = (struct labels_walkthrough *)data;
684
+ RRDLABEL *lb = (RRDLABEL *)value;
685
+
686
+ RRDLABEL_SRC ls = lb->label_source;
687
+ if(ls & RRDLABEL_FLAG_NEW) ls &= ~RRDLABEL_FLAG_NEW;
688
+ if(ls & RRDLABEL_FLAG_OLD) ls &= ~RRDLABEL_FLAG_OLD;
689
+
690
+ return d->callback(name, lb->value, ls, d->data);
691
+}
692
+
693
+int rrdlabels_walkthrough_read(DICTIONARY *labels, int (*callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data), void *data) {
694
+ struct labels_walkthrough d = {
695
+ .callback = callback,
696
+ .data = data
697
+ };
698
+ return dictionary_walkthrough_read(labels, labels_walkthrough_callback, &d);
699
+}
700
+
701
+int rrdlabels_sorted_walkthrough_read(DICTIONARY *labels, int (*callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data), void *data) {
702
+ struct labels_walkthrough d = {
703
+ .callback = callback,
704
+ .data = data
705
+ };
706
+ return dictionary_sorted_walkthrough_read(labels, labels_walkthrough_callback, &d);
707
}
708
157
-int label_list_contains(struct label *head, struct label *check)
158
-{
159
- return label_list_contains_key(head, check->key, check->key_hash);
709
+
710
+// ----------------------------------------------------------------------------
711
+// rrdlabels_migrate_to_these()
712
+// migrate an existing label list to a new list, INPLACE
713
+
714
+static int copy_label_to_dictionary_callback(const char *name, void *value, void *data) {
715
+ DICTIONARY *dst = (DICTIONARY *)data;
716
+ RRDLABEL *lb = (RRDLABEL *)value;
717
+ labels_add_already_sanitized(dst, name, lb->value, lb->label_source);
718
+ return 1;
719
}
720
162
-struct label *label_list_lookup_keylist(struct label *head, char *key)
163
-{
164
- SIMPLE_PATTERN *pattern = NULL;
721
+void rrdlabels_migrate_to_these(DICTIONARY *dst, DICTIONARY *src) {
722
+ if(!dst || !src) return;
723
+
724
+ // remove the RRDLABEL_FLAG_OLD and RRDLABEL_FLAG_NEW from all items
725
+ rrdlabels_unmark_all(dst);
726
+
727
+ // Mark the existing ones as RRDLABEL_FLAG_OLD,
728
+ // or the newly added ones as RRDLABEL_FLAG_NEW
729
+ dictionary_walkthrough_read(src, copy_label_to_dictionary_callback, dst);
730
+
731
+ // remove the unmarked dst
732
+ rrdlabels_remove_all_unmarked(dst);
733
+}
734
+
735
+void rrdlabels_copy(DICTIONARY *dst, DICTIONARY *src) {
736
+ if(!dst || !src) return;
737
+
738
+ dictionary_walkthrough_read(src, copy_label_to_dictionary_callback, dst);
739
+}
740
+
741
+
742
+// ----------------------------------------------------------------------------
743
+// rrdlabels_match_simple_pattern()
744
+// returns true when there are keys in the dictionary matching a simple pattern
745
+
746
+struct simple_pattern_match_name_value {
747
+ SIMPLE_PATTERN *pattern;
748
+ char equal;
749
+};
750
+
751
+static int simple_pattern_match_name_only_callback(const char *name, void *value, void *data) {
752
+ struct simple_pattern_match_name_value *t = (struct simple_pattern_match_name_value *)data;
753
+ (void)value;
754
+
755
+ // we return -1 to stop the walkthrough on first match
756
+ if(simple_pattern_matches(t->pattern, name)) return -1;
757
+
758
+ return 0;
759
+}
760
+
761
+static int simple_pattern_match_name_and_value_callback(const char *name, void *value, void *data) {
762
+ struct simple_pattern_match_name_value *t = (struct simple_pattern_match_name_value *)data;
763
+ RRDLABEL *lb = (RRDLABEL *)value;
764
+
765
+ // we return -1 to stop the walkthrough on first match
766
+ if(simple_pattern_matches(t->pattern, name)) return -1;
767
+
768
+ size_t len = RRDLABELS_MAX_NAME_LENGTH + RRDLABELS_MAX_VALUE_LENGTH + 2; // +1 for =, +1 for \0
769
+ char tmp[len], *dst = &tmp[0];
770
+ const char *v = lb->value;
771
166
- pattern = simple_pattern_create(key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT);
772
+ // copy the name
773
+ while(*name) *dst++ = *name++;
774
168
- while (head != NULL)
169
- {
170
- if (simple_pattern_matches(pattern, head->key))
775
+ // add the equal
776
+ *dst++ = t->equal;
777
+
778
+ // add the value
779
+ while(*v) *dst++ = *v++;
780
+
781
+ // terminate it
782
+ *dst = '\0';
783
+
784
+ if(simple_pattern_matches(t->pattern, tmp)) return -1;
785
+
786
+ return 0;
787
+}
788
+
789
+bool rrdlabels_match_simple_pattern_parsed(DICTIONARY *labels, SIMPLE_PATTERN *pattern, char equal) {
790
+ if (!labels) return false;
791
+
792
+ struct simple_pattern_match_name_value t = {
793
+ .pattern = pattern,
794
+ .equal = equal
795
+ };
796
+
797
+ int ret = dictionary_walkthrough_read(labels, equal?simple_pattern_match_name_and_value_callback:simple_pattern_match_name_only_callback, &t);
798
+
799
+ return (ret == -1)?true:false;
800
+}
801
+
802
+bool rrdlabels_match_simple_pattern(DICTIONARY *labels, const char *simple_pattern_txt) {
803
+ if (!labels) return false;
804
+
805
+ SIMPLE_PATTERN *pattern = simple_pattern_create(simple_pattern_txt, " ,|\t\r\n\f\v", SIMPLE_PATTERN_EXACT);
806
+ char equal = '\0';
807
+
808
+ const char *s;
809
+ for(s = simple_pattern_txt; *s ; s++) {
810
+ if (*s == '=' || *s == ':') {
811
+ equal = *s;
812
break;
172
- head = head->next;
813
+ }
814
}
815
+
816
+ bool ret = rrdlabels_match_simple_pattern_parsed(labels, pattern, equal);
817
+
818
simple_pattern_free(pattern);
175
- return head;
819
+
820
+ return ret;
821
}
822
178
-int label_list_contains_keylist(struct label *head, char *keylist)
179
-{
180
- return (label_list_lookup_keylist(head, keylist) != NULL);
823
+
824
+// ----------------------------------------------------------------------------
825
+// Log all labels
826
+
827
+static int rrdlabels_log_label_to_buffer_callback(const char *name, void *value, void *data) {
828
+ BUFFER *wb = (BUFFER *)data;
829
+ RRDLABEL *lb = (RRDLABEL *)value;
830
+
831
+ buffer_sprintf(wb, "Label: %s: \"%s\" (", name, lb->value);
832
+
833
+ size_t sources = 0;
834
+ if(lb->label_source & RRDLABEL_SRC_AUTO)
835
+ buffer_sprintf(wb, "%sauto", sources++?",":"");
836
+
837
+ if(lb->label_source & RRDLABEL_SRC_CONFIG)
838
+ buffer_sprintf(wb, "%snetdata.conf", sources++?",":"");
839
+
840
+ if(lb->label_source & RRDLABEL_SRC_K8S)
841
+ buffer_sprintf(wb, "%sk8s", sources++?",":"");
842
+
843
+ if(lb->label_source & RRDLABEL_SRC_ACLK)
844
+ buffer_sprintf(wb, "%saclk", sources++?",":"");
845
+
846
+ if(!sources)
847
+ buffer_strcat(wb, "unknown");
848
+
849
+ buffer_strcat(wb, ")\n");
850
+
851
+ return 1;
852
}
853
854
+void rrdlabels_log_to_buffer(DICTIONARY *labels, BUFFER *wb) {
855
+ dictionary_sorted_walkthrough_read(labels, rrdlabels_log_label_to_buffer_callback, wb);
856
+}
857
184
-/* Create a list with entries from both lists.
185
- If any entry in the low priority list is masked by an entry in the high priority list then delete it.
186
-*/
187
-struct label *merge_label_lists(struct label *lo_pri, struct label *hi_pri)
188
-{
189
- struct label *result = hi_pri;
190
- while (lo_pri != NULL)
191
- {
192
- struct label *current = lo_pri;
193
- lo_pri = lo_pri->next;
194
- if (!label_list_contains(result, current)) {
195
- current->next = result;
196
- result = current;
197
- }
198
- else
199
- freez(current);
858
+
859
+// ----------------------------------------------------------------------------
860
+// rrdlabels_to_buffer()
861
+
862
+struct labels_to_buffer {
863
+ BUFFER *wb;
864
+ bool (*filter_callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data);
865
+ void *filter_data;
866
+ void (*name_sanitizer)(char *dst, const char *src, size_t dst_size);
867
+ void (*value_sanitizer)(char *dst, const char *src, size_t dst_size);
868
+ const char *before_each;
869
+ const char *quote;
870
+ const char *equal;
871
+ const char *between_them;
872
+ size_t count;
873
+};
874
+
875
+static int label_to_buffer_callback(const char *name, void *value, void *data) {
876
+ struct labels_to_buffer *t = (struct labels_to_buffer *)data;
877
+ RRDLABEL *lb = (RRDLABEL *)value;
878
+
879
+ size_t n_size = (t->name_sanitizer ) ? ( RRDLABELS_MAX_NAME_LENGTH * 2 ) : 1;
880
+ size_t v_size = (t->value_sanitizer) ? ( RRDLABELS_MAX_VALUE_LENGTH * 2 ) : 1;
881
+
882
+ char n[n_size];
883
+ char v[v_size];
884
+
885
+ const char *nn = name, *vv = lb->value;
886
+
887
+ if(t->name_sanitizer) {
888
+ t->name_sanitizer(n, name, n_size);
889
+ nn = n;
890
+ }
891
+
892
+ if(t->value_sanitizer) {
893
+ t->value_sanitizer(v, lb->value, v_size);
894
+ vv = v;
895
+ }
896
+
897
+ if(!t->filter_callback || (t->filter_callback && t->filter_callback(name, lb->value, lb->label_source, t->filter_data))) {
898
+ buffer_sprintf(t->wb, "%s%s%s%s%s%s%s%s%s", t->count++?t->between_them:"", t->before_each, t->quote, nn, t->quote, t->equal, t->quote, vv, t->quote);
899
+ return 1;
900
+ }
901
+
902
+ return 0;
903
+}
904
+
905
+void rrdlabels_to_buffer(DICTIONARY *labels, BUFFER *wb, const char *before_each, const char *equal, const char *quote, const char *between_them, bool (*filter_callback)(const char *name, const char *value, RRDLABEL_SRC ls, void *data), void *filter_data, void (*name_sanitizer)(char *dst, const char *src, size_t dst_size), void (*value_sanitizer)(char *dst, const char *src, size_t dst_size)) {
906
+ struct labels_to_buffer tmp = {
907
+ .wb = wb,
908
+ .filter_callback = filter_callback,
909
+ .filter_data = filter_data,
910
+ .name_sanitizer = name_sanitizer,
911
+ .value_sanitizer = value_sanitizer,
912
+ .before_each = before_each,
913
+ .equal = equal,
914
+ .quote = quote,
915
+ .between_them = between_them,
916
+ .count = 0
917
+ };
918
+ dictionary_sorted_walkthrough_read(labels, label_to_buffer_callback, (void *)&tmp);
919
+}
920
+
921
+static int chart_label_store_to_sql_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) {
922
+ RRDSET *st = (RRDSET *)data;
923
+ sql_store_chart_label(st->chart_uuid, (int)ls, (char *)name, (char *)value);
924
+ return 1;
925
+}
926
+
927
+void rrdset_update_rrdlabels(RRDSET *st, DICTIONARY *new_rrdlabels) {
928
+ if(!st->state->chart_labels)
929
+ st->state->chart_labels = rrdlabels_create();
930
+
931
+ if (new_rrdlabels)
932
+ rrdlabels_migrate_to_these(st->state->chart_labels, new_rrdlabels);
933
+
934
+ // TODO - we should also cleanup sqlite from old new_rrdlabels that have been removed
935
+ rrdlabels_walkthrough_read(st->state->chart_labels, chart_label_store_to_sql_callback, st);
936
+}
937
+
938
+// ----------------------------------------------------------------------------
939
+// rrdlabels unit test
940
+
941
+struct rrdlabels_unittest_add_a_pair {
942
+ const char *pair;
943
+ const char *expected_name;
944
+ const char *expected_value;
945
+ const char *name;
946
+ const char *value;
947
+ RRDLABEL_SRC ls;
948
+ int errors;
949
+};
950
+
951
+int rrdlabels_unittest_add_a_pair_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) {
952
+ struct rrdlabels_unittest_add_a_pair *t = (struct rrdlabels_unittest_add_a_pair *)data;
953
+
954
+ t->name = name;
955
+ t->value = value;
956
+ t->ls = ls;
957
+
958
+ if(strcmp(name, t->expected_name) != 0) {
959
+ fprintf(stderr, "name is wrong, found \"%s\", expected \"%s\"", name, t->expected_name);
960
+ t->errors++;
961
+ }
962
+
963
+ if(value == NULL && t->expected_value == NULL) {
964
+ ;
965
+ }
966
+ else if(value == NULL || t->expected_value == NULL) {
967
+ fprintf(stderr, "value is wrong, found \"%s\", expected \"%s\"", value?value:"(null)", t->expected_value?t->expected_value:"(null)");
968
+ t->errors++;
969
+ }
970
+ else if(strcmp(value, t->expected_value) != 0) {
971
+ fprintf(stderr, "values don't match, found \"%s\", expected \"%s\"", value?value:"(null)", t->expected_value?t->expected_value:"(null)");
972
+ t->errors++;
973
+ }
974
+
975
+ return 1;
976
+}
977
+
978
+int rrdlabels_unittest_add_a_pair(const char *pair, const char *name, const char *value) {
979
+ DICTIONARY *labels = rrdlabels_create();
980
+ int errors;
981
+
982
+ fprintf(stderr, "rrdlabels_add_pair(labels, %s) ... ", pair);
983
+
984
+ rrdlabels_add_pair(labels, pair, RRDLABEL_SRC_CONFIG);
985
+
986
+ struct rrdlabels_unittest_add_a_pair tmp = {
987
+ .pair = pair,
988
+ .expected_name = name,
989
+ .expected_value = value,
990
+ .errors = 0
991
+ };
992
+ int ret = rrdlabels_walkthrough_read(labels, rrdlabels_unittest_add_a_pair_callback, &tmp);
993
+ errors = tmp.errors;
994
+ if(ret != 1) {
995
+ fprintf(stderr, "failed to get \"%s\" label", name);
996
+ errors++;
997
}
201
- return result;
998
+
999
+ if(!errors)
1000
+ fprintf(stderr, " OK, name='%s' and value='%s'\n", tmp.name, tmp.value?tmp.value:"(null)");
1001
+ else
1002
+ fprintf(stderr, " FAILED\n");
1003
+
1004
+ rrdlabels_destroy(labels);
1005
+ return errors;
1006
}
1007
1008
+int rrdlabels_unittest_add_pairs() {
1009
+ fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1010
+
1011
+ int errors = 0;
1012
+
1013
+ // basic test
1014
+ errors += rrdlabels_unittest_add_a_pair("tag=value", "tag", "value");
1015
+ errors += rrdlabels_unittest_add_a_pair("tag:value", "tag", "value");
1016
+
1017
+ // test newlines
1018
+ errors += rrdlabels_unittest_add_a_pair(" tag = \t value \r\n", "tag", "value");
1019
+
1020
+ // test : in values
1021
+ errors += rrdlabels_unittest_add_a_pair("tag=:value", "tag", ":value");
1022
+ errors += rrdlabels_unittest_add_a_pair("tag::value", "tag", ":value");
1023
+ errors += rrdlabels_unittest_add_a_pair(" tag = :value ", "tag", ":value");
1024
+ errors += rrdlabels_unittest_add_a_pair(" tag : :value ", "tag", ":value");
1025
+ errors += rrdlabels_unittest_add_a_pair("tag:5", "tag", "5");
1026
+ errors += rrdlabels_unittest_add_a_pair("tag:55", "tag", "55");
1027
+ errors += rrdlabels_unittest_add_a_pair("tag:aa", "tag", "aa");
1028
+ errors += rrdlabels_unittest_add_a_pair("tag:a", "tag", "a");
1029
+
1030
+ // test empty values
1031
+ errors += rrdlabels_unittest_add_a_pair("tag", "tag", "");
1032
+ errors += rrdlabels_unittest_add_a_pair("tag:", "tag", "");
1033
+ errors += rrdlabels_unittest_add_a_pair("tag:\"\"", "tag", "");
1034
+ errors += rrdlabels_unittest_add_a_pair("tag:''", "tag", "");
1035
+ errors += rrdlabels_unittest_add_a_pair("tag:\r\n", "tag", "");
1036
+ errors += rrdlabels_unittest_add_a_pair("tag\r\n", "tag", "");
1037
+
1038
+ // test UTF-8 in values
1039
+ errors += rrdlabels_unittest_add_a_pair("tag: country:Ελλάδα", "tag", "country:Ελλάδα");
1040
+ errors += rrdlabels_unittest_add_a_pair("\"tag\": \"country:Ελλάδα\"", "tag", "country:Ελλάδα");
1041
+ errors += rrdlabels_unittest_add_a_pair("\"tag\": country:\"Ελλάδα\"", "tag", "country:Ελλάδα");
1042
+ errors += rrdlabels_unittest_add_a_pair("\"tag=1\": country:\"Gre\\\"ece\"", "tag_1", "country:Gre_ece");
1043
+ errors += rrdlabels_unittest_add_a_pair("\"tag=1\" = country:\"Gre\\\"ece\"", "tag_1", "country:Gre_ece");
1044
+
1045
+ errors += rrdlabels_unittest_add_a_pair("\t'LABE=L'\t=\t\"World\" peace", "labe_l", "World_peace");
1046
+ errors += rrdlabels_unittest_add_a_pair("\t'LA\\'B:EL'\t=\tcountry:\"World\":\"Europe\":\"Greece\"", "la_b_el", "country:World:Europe:Greece");
1047
+
1048
+ errors += rrdlabels_unittest_add_a_pair("NAME=\"VALUE\"", "name", "VALUE");
1049
+ errors += rrdlabels_unittest_add_a_pair("\"NAME\" : \"VALUE\"", "name", "VALUE");
1050
+ errors += rrdlabels_unittest_add_a_pair("NAME: \"VALUE\"", "name", "VALUE");
1051
+
1052
+ return errors;
1053
+}
1054
+
1055
+int rrdlabels_unittest_check_simple_pattern(DICTIONARY *labels, const char *pattern, bool expected) {
1056
+ fprintf(stderr, "rrdlabels_match_simple_pattern(labels, \"%s\") ... ", pattern);
1057
+
1058
+ bool ret = rrdlabels_match_simple_pattern(labels, pattern);
1059
+ fprintf(stderr, "%s, got %s expected %s\n", (ret == expected)?"OK":"FAILED", ret?"true":"false", expected?"true":"false");
1060
+
1061
+ return (ret == expected)?0:1;
1062
+}
1063
+
1064
+int rrdlabels_unittest_simple_pattern() {
1065
+ fprintf(stderr, "\n%s() tests\n", __FUNCTION__);
1066
+
1067
+ int errors = 0;
1068
+
1069
+ DICTIONARY *labels = rrdlabels_create();
1070
+ rrdlabels_add(labels, "tag1", "value1", RRDLABEL_SRC_CONFIG);
1071
+ rrdlabels_add(labels, "tag2", "value2", RRDLABEL_SRC_CONFIG);
1072
+ rrdlabels_add(labels, "tag3", "value3", RRDLABEL_SRC_CONFIG);
1073
+
1074
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "*", true);
1075
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "tag", false);
1076
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "tag*", true);
1077
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "*1", true);
1078
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "value*", false);
1079
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "*=value*", true);
1080
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "*:value*", true);
1081
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "*2", true);
1082
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "*2 *3", true);
1083
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "!tag3 *2", true);
1084
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "tag1 tag2", true);
1085
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "tag1tag2", false);
1086
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "invalid1 invalid2 tag3", true);
1087
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "!tag1 tag4", false);
1088
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "tag1=value1", true);
1089
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "tag1=value2", false);
1090
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "tag*=value*", true);
1091
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "!tag*=value*", false);
1092
+ errors += rrdlabels_unittest_check_simple_pattern(labels, "!tag2=something2 tag2=*2", true);
1093
+
1094
+ rrdlabels_destroy(labels);
1095
+
1096
+ return errors;
1097
+}
1098
+
1099
+int rrdlabels_unittest_sanitize_value(const char *src, const char *expected) {
1100
+ char buf[RRDLABELS_MAX_VALUE_LENGTH + 1];
1101
+ size_t mblen = rrdlabels_sanitize_value(buf, src, RRDLABELS_MAX_VALUE_LENGTH);
1102
+
1103
+ int err = 0;
1104
+ if(strcmp(buf, expected) != 0) err = 1;
1105
+
1106
+ fprintf(stderr, "%s(%s): %s, expected '%s', got '%s', mblen = %zu, bytes = %zu\n", __FUNCTION__, src, (err==1)?"FAILED":"OK", expected, buf, mblen, strlen(buf));
1107
+ return err;
1108
+}
1109
+
1110
+int rrdlabels_unittest_sanitization() {
1111
+ int errors = 0;
1112
+
1113
+ errors += rrdlabels_unittest_sanitize_value("", "");
1114
+ errors += rrdlabels_unittest_sanitize_value("1", "1");
1115
+ errors += rrdlabels_unittest_sanitize_value(" hello world ", "hello_world");
1116
+
1117
+ // 2-byte UTF-8
1118
+ errors += rrdlabels_unittest_sanitize_value(" Ελλάδα ", "Ελλάδα");
1119
+ errors += rrdlabels_unittest_sanitize_value("aŰbŲcŴ", "aŰbŲcŴ");
1120
+ errors += rrdlabels_unittest_sanitize_value("Ű b Ų c Ŵ", "Ű_b_Ų_c_Ŵ");
1121
+
1122
+ // 3-byte UTF-8
1123
+ errors += rrdlabels_unittest_sanitize_value("‱", "‱");
1124
+ errors += rrdlabels_unittest_sanitize_value("a‱b", "a‱b");
1125
+ errors += rrdlabels_unittest_sanitize_value("a ‱ b", "a_‱_b");
1126
+
1127
+ // 4-byte UTF-8
1128
+ errors += rrdlabels_unittest_sanitize_value("𩸽", "𩸽");
1129
+ errors += rrdlabels_unittest_sanitize_value("a𩸽b", "a𩸽b");
1130
+ errors += rrdlabels_unittest_sanitize_value("a 𩸽 b", "a_𩸽_b");
1131
+
1132
+ // mixed multi-byte
1133
+ errors += rrdlabels_unittest_sanitize_value("Ű‱𩸽‱Ű", "Ű‱𩸽‱Ű");
1134
+
1135
+ return errors;
1136
+}
1137
+
1138
+int rrdlabels_unittest(void) {
1139
+ int errors = 0;
1140
+
1141
+ errors += rrdlabels_unittest_sanitization();
1142
+ errors += rrdlabels_unittest_add_pairs();
1143
+ errors += rrdlabels_unittest_simple_pattern();
1144
+
1145
+ fprintf(stderr, "%d errors found\n", errors);
1146
+ return errors;
1147
+}