Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for spec plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #include "spec.h"
11 :
12 : #include <kdbassert.h>
13 : #include <kdbease.h>
14 : #include <kdberrors.h>
15 : #include <kdbglobbing.h>
16 : #include <kdbhelper.h>
17 : #include <kdblogger.h>
18 : #include <kdbmeta.h>
19 : #include <kdbtypes.h>
20 :
21 : #include <fnmatch.h>
22 : #include <stdio.h>
23 : #include <stdlib.h>
24 : #include <string.h>
25 :
26 : typedef enum
27 : {
28 : IGNORE,
29 : ERROR,
30 : WARNING,
31 : INFO
32 : } OnConflict;
33 :
34 :
35 : // clang-format off
36 : #define NO_CONFLICTS "00000" // on char per conflict type below
37 : #define CONFLICT_ARRAYMEMBER 0
38 : #define CONFLICT_INVALID 1
39 : #define CONFLICT_COLLISION 2
40 : #define CONFLICT_OUTOFRANGE 3
41 : #define CONFLICT_WILDCARDMEMBER 4
42 : // missing keys are handled directly
43 : #define SIZE_CONFLICTS sizeof(NO_CONFLICTS)
44 : // clang-format on
45 :
46 : #define CONFIG_BASE_NAME_GET "/conflict/get"
47 : #define CONFIG_BASE_NAME_SET "/conflict/set"
48 :
49 : typedef struct
50 : {
51 : OnConflict member;
52 : OnConflict invalid;
53 : OnConflict count;
54 : OnConflict conflict;
55 : OnConflict range;
56 : OnConflict missing;
57 : } ConflictHandling;
58 :
59 : static void copyMeta (Key * dest, Key * src);
60 :
61 4315 : static bool specMatches (Key * specKey, Key * otherKey)
62 : {
63 : // ignore namespaces for globbing
64 4315 : Key * globKey = keyNew (strchr (keyName (otherKey), '/'), KEY_END);
65 4315 : bool matches = elektraKeyGlob (globKey, strchr (keyName (specKey), '/')) == 0;
66 4315 : keyDel (globKey);
67 4315 : return matches;
68 : }
69 :
70 :
71 : static inline void safeFree (void * ptr)
72 : {
73 164 : if (ptr != NULL)
74 : {
75 164 : elektraFree (ptr);
76 : }
77 : }
78 :
79 : /* region Config parsing */
80 : /* ========================= */
81 :
82 7975 : static OnConflict parseOnConflictKey (const Key * key)
83 : {
84 7975 : const char * string = keyString (key);
85 7975 : if (strcmp (string, "ERROR") == 0)
86 : {
87 : return ERROR;
88 : }
89 7873 : else if (strcmp (string, "WARNING") == 0)
90 : {
91 : return WARNING;
92 : }
93 7873 : else if (strcmp (string, "INFO") == 0)
94 : {
95 : return INFO;
96 : }
97 : else
98 : {
99 7873 : return IGNORE;
100 : }
101 : }
102 :
103 7975 : static void parseConfig (KeySet * config, ConflictHandling * ch, const char baseName[14])
104 : {
105 : char nameBuffer[32];
106 7975 : strcpy (nameBuffer, baseName);
107 7975 : char * nameBufferEnd = nameBuffer + strlen (nameBuffer);
108 :
109 7975 : Key * key = ksLookupByName (config, nameBuffer, 0);
110 7975 : OnConflict base = parseOnConflictKey (key);
111 :
112 7975 : strcpy (nameBufferEnd, "/member");
113 7975 : key = ksLookupByName (config, nameBuffer, 0);
114 7975 : ch->member = key == NULL ? base : parseOnConflictKey (key);
115 :
116 7975 : strcpy (nameBufferEnd, "/invalid");
117 7975 : key = ksLookupByName (config, nameBuffer, 0);
118 7975 : ch->invalid = key == NULL ? base : parseOnConflictKey (key);
119 :
120 7975 : strcpy (nameBufferEnd, "/collision");
121 7975 : key = ksLookupByName (config, nameBuffer, 0);
122 7975 : ch->conflict = key == NULL ? base : parseOnConflictKey (key);
123 :
124 7975 : strcpy (nameBufferEnd, "/range");
125 7975 : key = ksLookupByName (config, nameBuffer, 0);
126 7975 : ch->range = key == NULL ? base : parseOnConflictKey (key);
127 :
128 7975 : strcpy (nameBufferEnd, "/missing");
129 7975 : key = ksLookupByName (config, nameBuffer, 0);
130 7975 : ch->missing = key == NULL ? base : parseOnConflictKey (key);
131 7975 : }
132 :
133 5326 : static void parseLocalConfig (Key * specKey, ConflictHandling * ch, bool isKdbGet)
134 : {
135 : char nameBuffer[32];
136 5326 : strcpy (nameBuffer, isKdbGet ? CONFIG_BASE_NAME_GET : CONFIG_BASE_NAME_SET);
137 5326 : char * nameBufferEnd = nameBuffer + strlen (nameBuffer);
138 :
139 5326 : strcpy (nameBufferEnd, "/member");
140 5326 : const Key * key = keyGetMeta (specKey, nameBuffer);
141 5326 : ch->member = key == NULL ? ch->member : parseOnConflictKey (key);
142 :
143 5326 : strcpy (nameBufferEnd, "/invalid");
144 5326 : key = keyGetMeta (specKey, nameBuffer);
145 5326 : ch->invalid = key == NULL ? ch->invalid : parseOnConflictKey (key);
146 :
147 5326 : strcpy (nameBufferEnd, "/collision");
148 5326 : key = keyGetMeta (specKey, nameBuffer);
149 5326 : ch->conflict = key == NULL ? ch->conflict : parseOnConflictKey (key);
150 :
151 5326 : strcpy (nameBufferEnd, "/range");
152 5326 : key = keyGetMeta (specKey, nameBuffer);
153 5326 : ch->range = key == NULL ? ch->range : parseOnConflictKey (key);
154 :
155 5326 : strcpy (nameBufferEnd, "/missing");
156 5326 : key = keyGetMeta (specKey, nameBuffer);
157 5326 : ch->missing = key == NULL ? ch->missing : parseOnConflictKey (key);
158 5326 : }
159 :
160 : // endregion Config parsing
161 :
162 : /* region Conflict handling */
163 : /* ========================= */
164 :
165 20 : static void addConflict (Key * key, int conflict)
166 : {
167 20 : char conflicts[SIZE_CONFLICTS] = NO_CONFLICTS;
168 :
169 20 : const Key * meta = keyGetMeta (key, "conflict");
170 20 : if (keyGetValueSize (meta) == SIZE_CONFLICTS)
171 : {
172 2 : keyGetString (meta, conflicts, SIZE_CONFLICTS);
173 : }
174 :
175 20 : conflicts[conflict] = '1';
176 20 : keySetMeta (key, "conflict", conflicts);
177 20 : }
178 :
179 : /**
180 : * Handle a single conflict.
181 : *
182 : * @param parentKey The parent key (to store errors and warnings).
183 : * @param msg The conflict message
184 : * @param onConflict What to do with the conflict
185 : */
186 180 : static void handleConflict (Key * parentKey, const char * msg, OnConflict onConflict)
187 : {
188 : ELEKTRA_LOG_DEBUG ("spec conflict: %s", msg);
189 :
190 180 : switch (onConflict)
191 : {
192 : case ERROR:
193 178 : keySetMeta (parentKey, "internal/spec/error", "1");
194 178 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (parentKey, "%s", msg);
195 178 : break;
196 : case WARNING:
197 0 : ELEKTRA_ADD_VALIDATION_SEMANTIC_WARNINGF (parentKey, "%s", msg);
198 0 : break;
199 : case INFO:
200 0 : elektraMetaArrayAdd (parentKey, "logs/spec/info", msg);
201 0 : break;
202 : case IGNORE:
203 : default:
204 : break;
205 : }
206 180 : }
207 :
208 : /**
209 : * Handles all conflicts for the given key.
210 : *
211 : * @param key The key whose conflicts we handle.
212 : * @param parentKey The parent key (for storing errors/warnings)
213 : * @param specKey The spec Key causing the conflict (for additional information, e.g. max array size)
214 : * @param ch How conflicts should be handled
215 : *
216 : * @retval 0 if no conflicts where found, or all found conflicts are ignored
217 : * @retval -1 otherwise
218 : */
219 10548 : static int handleConflicts (Key * key, Key * parentKey, Key * specKey, const ConflictHandling * ch)
220 : {
221 10548 : const Key * metaKey = keyGetMeta (key, "conflict");
222 :
223 10548 : if (!metaKey)
224 : {
225 : return 0;
226 : }
227 :
228 164 : char conflicts[SIZE_CONFLICTS] = NO_CONFLICTS;
229 164 : if (keyGetValueSize (metaKey) == SIZE_CONFLICTS)
230 : {
231 164 : keyGetString (metaKey, conflicts, SIZE_CONFLICTS);
232 : }
233 :
234 164 : int ret = 0;
235 164 : if (conflicts[CONFLICT_INVALID] == '1' && ch->invalid != IGNORE)
236 : {
237 0 : const Key * moreMsg = keyGetMeta (key, "conflict/invalid");
238 : char * msg;
239 0 : if (moreMsg != NULL)
240 : {
241 0 : msg = elektraFormat ("Invalid key %s: %s", keyName (key), keyString (moreMsg));
242 : }
243 : else
244 : {
245 0 : msg = elektraFormat ("Invalid key %s", keyName (key));
246 : }
247 :
248 0 : handleConflict (parentKey, msg, ch->invalid);
249 0 : elektraFree (msg);
250 0 : ret = -1;
251 : }
252 :
253 164 : if (conflicts[CONFLICT_ARRAYMEMBER] == '1' && ch->member != IGNORE)
254 : {
255 156 : char * problemKeys = elektraMetaArrayToString (key, keyName (keyGetMeta (key, "conflict/arraymember")), ", ");
256 156 : char * msg =
257 156 : elektraFormat ("Array key %s has invalid children (only array elements allowed): %s", keyName (key), problemKeys);
258 156 : handleConflict (parentKey, msg, ch->member);
259 156 : elektraFree (msg);
260 : safeFree (problemKeys);
261 : ret = -1;
262 : }
263 :
264 164 : if (conflicts[CONFLICT_WILDCARDMEMBER] == '1' && ch->member != IGNORE)
265 : {
266 0 : char * problemKeys = elektraMetaArrayToString (key, keyName (keyGetMeta (key, "conflict/wildcardmember")), ", ");
267 0 : char * msg =
268 0 : elektraFormat ("Widlcard key %s has invalid children (no array elements allowed): %s", keyName (key), problemKeys);
269 0 : handleConflict (parentKey, msg, ch->member);
270 0 : elektraFree (msg);
271 : safeFree (problemKeys);
272 : ret = -1;
273 : }
274 :
275 164 : if (conflicts[CONFLICT_COLLISION] == '1' && ch->conflict != IGNORE)
276 : {
277 2 : char * problemKeys = elektraMetaArrayToString (key, keyName (keyGetMeta (key, "conflict/collision")), ", ");
278 2 : char * msg = elektraFormat ("%s has conflicting metakeys: %s", keyName (key), problemKeys);
279 2 : handleConflict (parentKey, msg, ch->conflict);
280 2 : elektraFree (msg);
281 : safeFree (problemKeys);
282 : ret = -1;
283 : }
284 :
285 164 : if (conflicts[CONFLICT_OUTOFRANGE] == '1' && ch->range != IGNORE)
286 : {
287 6 : const Key * min = keyGetMeta (specKey, "array/min");
288 6 : const Key * max = keyGetMeta (specKey, "array/max");
289 :
290 6 : char * msg = elektraFormat ("%s has invalid number of members: %s. Expected: %s - %s", keyName (key),
291 : keyString (keyGetMeta (key, "conflict/outofrange")), min == NULL ? "" : keyString (min),
292 : max == NULL ? "" : keyString (max));
293 6 : handleConflict (parentKey, msg, ch->range);
294 6 : elektraFree (msg);
295 6 : ret = -1;
296 : }
297 :
298 : return ret;
299 : }
300 :
301 : /**
302 : * Handles all errors (conflicts) for the given key, including those stored in immediate parent.
303 : *
304 : * @param key The key whose conflicts we handle.
305 : * @param parentKey The parent key (for storing errors/warnings)
306 : * @param ks The full KeySet
307 : * @param specKey The spec Key causing the conflict (for additional information, e.g. max array size)
308 : * @param ch How conflicts should be handled
309 : * @param isKdbGet is this called from kdbGet?
310 : *
311 : * @retval 0 if no conflicts where found
312 : * @retval -1 otherwise
313 : */
314 5326 : static int handleErrors (Key * key, Key * parentKey, KeySet * ks, Key * specKey, const ConflictHandling * ch, bool isKdbGet)
315 : {
316 : ConflictHandling localCh;
317 5326 : memcpy (&localCh, ch, sizeof (ConflictHandling));
318 :
319 5326 : parseLocalConfig (specKey, &localCh, isKdbGet);
320 :
321 5326 : Key * parentLookup = keyDup (key);
322 5326 : keySetBaseName (parentLookup, NULL);
323 :
324 5326 : cursor_t cursor = ksGetCursor (ks);
325 5326 : Key * parent = ksLookup (ks, parentLookup, KDB_O_NONE);
326 5326 : ksSetCursor (ks, cursor);
327 :
328 5326 : keyDel (parentLookup);
329 :
330 5326 : int ret = handleConflicts (parent, parentKey, specKey, &localCh) || handleConflicts (key, parentKey, specKey, &localCh);
331 :
332 5326 : return ret;
333 : }
334 :
335 2227 : static int processAllConflicts (Key * specKey, KeySet * ks, Key * parentKey, const ConflictHandling * ch, bool isKdbGet)
336 : {
337 : Key * cur;
338 2227 : int ret = 0;
339 2227 : ksRewind (ks);
340 9780 : while ((cur = ksNext (ks)) != NULL)
341 : {
342 5326 : if (handleErrors (cur, parentKey, ks, specKey, ch, isKdbGet) != 0)
343 : {
344 164 : ret = -1;
345 : }
346 : }
347 2227 : return ret;
348 : }
349 :
350 : // endregion Conflict handling
351 :
352 : /* region Array handling */
353 : /* ========================= */
354 :
355 : /**
356 : * Checks whether the given key is an array spec,
357 : * i.e. it has a keyname part that is "#".
358 : *
359 : * @param key a spec key
360 : *
361 : * @retval #true if @p key is an array spec
362 : * @retval #false otherwise
363 : */
364 4268 : static bool isArraySpec (const Key * key)
365 : {
366 4268 : size_t usize = keyGetUnescapedNameSize (key);
367 4268 : const char * cur = keyUnescapedName (key);
368 4268 : const char * end = cur + usize;
369 :
370 31316 : while (cur < end)
371 : {
372 23148 : size_t len = strlen (cur);
373 :
374 23148 : if (len == 1 && cur[0] == '#')
375 : {
376 : return true;
377 : }
378 :
379 22780 : cur += len + 1;
380 : }
381 :
382 : return false;
383 : }
384 :
385 90 : static bool validateArraySize (Key * arrayParent, Key * spec)
386 : {
387 90 : const Key * arrayActualKey = keyGetMeta (arrayParent, "array");
388 90 : const char * arrayActual = arrayActualKey == NULL ? "" : keyString (arrayActualKey);
389 :
390 90 : const Key * arrayMinKey = keyGetMeta (spec, "array/min");
391 90 : const char * arrayMin = arrayMinKey == NULL ? NULL : keyString (arrayMinKey);
392 :
393 90 : const Key * arrayMaxKey = keyGetMeta (spec, "array/max");
394 90 : const char * arrayMax = arrayMaxKey == NULL ? NULL : keyString (arrayMaxKey);
395 :
396 90 : return (arrayMin == NULL || strcmp (arrayMin, arrayActual) <= 0) && (arrayMax == NULL || 0 <= strcmp (arrayActual, arrayMax));
397 : }
398 :
399 50 : static void validateEmptyArray (KeySet * ks, Key * arraySpecParent, Key * parentKey, OnConflict onConflict)
400 : {
401 50 : Key * parentLookup = keyNew (strchr (keyName (arraySpecParent), '/'), KEY_END);
402 :
403 : // either existed already, or was added by processSpecKey because of KeySet order
404 50 : Key * arrayParent = ksLookup (ks, parentLookup, 0);
405 50 : if (keyGetMeta (arrayParent, "internal/spec/array/validated") != NULL)
406 : {
407 12 : keyDel (parentLookup);
408 12 : return;
409 : }
410 :
411 38 : bool immediate = arrayParent == NULL;
412 38 : if (immediate)
413 : {
414 26 : arrayParent = keyNew (keyName (parentLookup), KEY_END);
415 : }
416 :
417 : // TODO: [improvement] ksExtract?, like ksCut, but doesn't remove -> no need for ksDup
418 38 : KeySet * ksCopy = ksDup (ks);
419 38 : KeySet * subKeys = ksCut (ksCopy, parentLookup);
420 38 : ksDel (ksCopy);
421 :
422 38 : ssize_t parentLen = keyGetUnescapedNameSize (parentLookup);
423 :
424 38 : bool haveConflict = false;
425 : Key * cur;
426 38 : ksRewind (subKeys);
427 152 : while ((cur = ksNext (subKeys)) != NULL)
428 : {
429 76 : if (keyIsBelow (parentLookup, cur) == 0)
430 : {
431 12 : continue;
432 : }
433 :
434 64 : const char * checkStr = keyUnescapedName (cur);
435 64 : ssize_t len = strlen (checkStr);
436 :
437 64 : if (keyGetUnescapedNameSize (cur) - len == parentLen)
438 : {
439 12 : continue;
440 : }
441 :
442 52 : checkStr += len;
443 52 : checkStr += parentLen;
444 :
445 52 : if (elektraArrayValidateBaseNameString (checkStr) < 0)
446 : {
447 6 : haveConflict = true;
448 6 : addConflict (arrayParent, CONFLICT_ARRAYMEMBER);
449 6 : elektraMetaArrayAdd (arrayParent, "conflict/arraymember", keyName (cur));
450 : }
451 : }
452 :
453 38 : if (immediate)
454 : {
455 26 : if (haveConflict)
456 : {
457 6 : char * problemKeys =
458 6 : elektraMetaArrayToString (arrayParent, keyName (keyGetMeta (arrayParent, "conflict/arraymember")), ", ");
459 6 : char * msg = elektraFormat ("Array key %s has invalid children (only array elements allowed): %s",
460 : keyName (arrayParent), problemKeys);
461 6 : handleConflict (parentKey, msg, onConflict);
462 6 : elektraFree (msg);
463 : safeFree (problemKeys);
464 : }
465 26 : keyDel (arrayParent);
466 : }
467 :
468 38 : ksDel (subKeys);
469 38 : keyDel (parentLookup);
470 :
471 38 : if (!immediate)
472 : {
473 12 : keySetMeta (arrayParent, "internal/spec/array/validated", "");
474 : }
475 : }
476 :
477 198 : static void validateArrayMembers (KeySet * ks, Key * arraySpec)
478 : {
479 198 : Key * parentLookup = keyNew (strchr (keyName (arraySpec), '/'), KEY_END);
480 198 : keySetBaseName (parentLookup, NULL);
481 :
482 : // either existed already, or was added by processSpecKey because of KeySet order
483 198 : Key * arrayParent = ksLookup (ks, parentLookup, 0);
484 198 : if (keyGetMeta (arrayParent, "internal/spec/array/validated") != NULL)
485 : {
486 18 : keyDel (parentLookup);
487 18 : return;
488 : }
489 :
490 : // TODO: [improvement] ksExtract?, like ksCut, but doesn't remove -> no need for ksDup
491 180 : KeySet * ksCopy = ksDup (ks);
492 180 : KeySet * subKeys = ksCut (ksCopy, parentLookup);
493 180 : ksDel (ksCopy);
494 :
495 180 : ssize_t parentLen = keyGetUnescapedNameSize (parentLookup);
496 :
497 : Key * cur;
498 180 : ksRewind (subKeys);
499 590 : while ((cur = ksNext (subKeys)) != NULL)
500 : {
501 230 : if (keyIsBelow (parentLookup, cur) == 0)
502 : {
503 40 : continue;
504 : }
505 :
506 190 : const char * checkStr = keyUnescapedName (cur);
507 190 : ssize_t len = strlen (checkStr);
508 :
509 190 : if (keyGetUnescapedNameSize (cur) - len == parentLen)
510 : {
511 70 : continue;
512 : }
513 :
514 120 : checkStr += len;
515 120 : checkStr += parentLen;
516 :
517 120 : if (elektraArrayValidateBaseNameString (checkStr) < 0)
518 : {
519 8 : addConflict (arrayParent, CONFLICT_ARRAYMEMBER);
520 8 : elektraMetaArrayAdd (arrayParent, "conflict/arraymember", keyName (cur));
521 : }
522 : }
523 :
524 180 : ksDel (subKeys);
525 180 : keyDel (parentLookup);
526 :
527 180 : keySetMeta (arrayParent, "internal/spec/array/validated", "");
528 : }
529 :
530 : // instantiates all array spec parts in an array spec key (e.g. abc/#/a/d/#/e)
531 170 : static KeySet * instantiateArraySpec (KeySet * ks, Key * arraySpec, Key * parentKey, OnConflict onConflict)
532 : {
533 170 : cursor_t cursor = ksGetCursor (ks);
534 170 : size_t usize = keyGetUnescapedNameSize (arraySpec);
535 170 : const char * cur = keyUnescapedName (arraySpec);
536 170 : const char * end = cur + usize;
537 :
538 170 : cur += strlen (cur) + 1; // skip "spec"
539 :
540 170 : KeySet * newKeys = ksNew (1, keyNew ("spec", KEY_END), KS_END);
541 170 : KeySet * parents = ksNew (0, KS_END);
542 170 : Key * specCur = keyNew ("spec", KEY_END);
543 :
544 1416 : while (cur < end)
545 : {
546 1076 : size_t len = strlen (cur);
547 :
548 1076 : KeySet * curNew = ksNew (0, KS_END);
549 1076 : if (len == 1 && cur[0] == '#')
550 : {
551 :
552 : Key * k;
553 186 : ksRewind (newKeys);
554 562 : while ((k = ksNext (newKeys)) != NULL)
555 : {
556 190 : Key * lookup = ksLookupByName (ks, strchr (keyName (k), '/'), 0);
557 190 : const Key * arrayMeta = lookup == NULL ? NULL : keyGetMeta (lookup, "array");
558 190 : Key * specLookup = ksLookup (ks, specCur, 0);
559 190 : if (arrayMeta != NULL)
560 : {
561 90 : if (!validateArraySize (lookup, specLookup))
562 : {
563 4 : addConflict (lookup, CONFLICT_OUTOFRANGE);
564 4 : keySetMeta (lookup, "conflict/outofrange", keyString (arrayMeta));
565 58 : continue;
566 : }
567 : }
568 : else
569 : {
570 100 : lookup = specLookup;
571 100 : arrayMeta = lookup == NULL ? NULL : keyGetMeta (lookup, "array");
572 : }
573 :
574 186 : const char * arraySize = arrayMeta == NULL ? "" : keyString (arrayMeta);
575 :
576 186 : if (strlen (arraySize) == 0)
577 : {
578 : // empty array
579 50 : validateEmptyArray (ks, k, parentKey, onConflict);
580 50 : continue;
581 : }
582 :
583 136 : if (elektraArrayValidateBaseNameString (arraySize) <= 0)
584 : {
585 0 : addConflict (lookup, CONFLICT_INVALID);
586 0 : keySetMeta (lookup, "conflict/invalid", "invalid array metadata");
587 0 : continue;
588 : }
589 :
590 : char elem[ELEKTRA_MAX_ARRAY_SIZE];
591 136 : kdb_long_long_t i = 0;
592 136 : elektraWriteArrayNumber (elem, i);
593 :
594 460 : while (strcmp (elem, arraySize) <= 0)
595 : {
596 188 : Key * new = keyDup (k);
597 188 : keyAddBaseName (new, elem);
598 188 : ksAppendKey (curNew, new);
599 :
600 188 : ++i;
601 188 : elektraWriteArrayNumber (elem, i);
602 : }
603 :
604 136 : Key * parent = keyNew (keyName (k), KEY_END);
605 136 : keyAddBaseName (parent, "#");
606 136 : ksAppendKey (parents, parent);
607 : }
608 : }
609 : else
610 : {
611 : Key * k;
612 890 : ksRewind (newKeys);
613 2666 : while ((k = ksNext (newKeys)) != NULL)
614 : {
615 886 : Key * new = keyDup (k);
616 886 : keyAddBaseName (new, cur);
617 886 : ksAppendKey (curNew, new);
618 : }
619 : }
620 1076 : ksDel (newKeys);
621 1076 : newKeys = curNew;
622 :
623 1076 : keyAddBaseName (specCur, cur);
624 1076 : cur += len + 1;
625 : }
626 :
627 170 : keyDel (specCur);
628 :
629 170 : ksAppend (newKeys, parents);
630 170 : ksDel (parents);
631 :
632 : Key * k;
633 170 : ksRewind (newKeys);
634 644 : while ((k = ksNext (newKeys)) != NULL)
635 : {
636 304 : keySetMeta (k, "internal/spec/array", "");
637 304 : copyMeta (k, arraySpec);
638 : }
639 :
640 170 : ksSetCursor (ks, cursor);
641 170 : return newKeys;
642 : }
643 :
644 : // endregion Array handling
645 :
646 : /* region Wildcard (_) handling */
647 : /* ========================================= */
648 :
649 : /**
650 : * Checks whether the given key is a wildcard spec,
651 : * i.e. it has a keyname part that is "_".
652 : *
653 : * @param key a spec key
654 : *
655 : * @retval #true if @p key is a wildcard spec
656 : * @retval #false otherwise
657 : */
658 2227 : static bool isWildcardSpec (const Key * key)
659 : {
660 2227 : size_t usize = keyGetUnescapedNameSize (key);
661 2227 : const char * cur = keyUnescapedName (key);
662 2227 : const char * end = cur + usize;
663 :
664 16610 : while (cur < end)
665 : {
666 12218 : size_t len = strlen (cur);
667 :
668 12218 : if (len == 1 && cur[0] == '_')
669 : {
670 : return true;
671 : }
672 :
673 12156 : cur += len + 1;
674 : }
675 :
676 : return false;
677 : }
678 :
679 : /**
680 : * Handles wildcard spec keys. A conflict will be added,
681 : * if the number of keys directly below
682 : * @param ks
683 : * @param key
684 : * @param specKey
685 : */
686 15 : static void validateWildcardSubs (KeySet * ks, Key * key)
687 : {
688 15 : Key * parent = keyDup (key);
689 15 : keySetBaseName (parent, NULL);
690 :
691 : // TODO: [improvement] ksExtract?, like ksCut, but doesn't remove -> no need for ksDup
692 15 : KeySet * ksCopy = ksDup (ks);
693 15 : KeySet * subKeys = ksCut (ksCopy, parent);
694 15 : ksDel (ksCopy);
695 :
696 : Key * cur;
697 57 : while ((cur = ksNext (subKeys)) != NULL)
698 : {
699 27 : if (keyIsDirectBelow (parent, cur))
700 : {
701 23 : if (elektraArrayValidateBaseNameString (keyBaseName (cur)) > 0)
702 : {
703 0 : addConflict (parent, CONFLICT_WILDCARDMEMBER);
704 0 : elektraMetaArrayAdd (parent, "conflict/wildcardmember", keyName (cur));
705 : }
706 : }
707 : }
708 15 : ksDel (subKeys);
709 15 : keyDel (parent);
710 15 : }
711 :
712 : // endregion Wildcard (_) handling
713 :
714 : /**
715 : * Copies all metadata (except for internal/ and conflict/) from @p dest to @p src
716 : */
717 937 : static void copyMeta (Key * dest, Key * src)
718 : {
719 937 : keyRewindMeta (src);
720 : const Key * meta;
721 4004 : while ((meta = keyNextMeta (src)) != NULL)
722 : {
723 2130 : const char * name = keyName (meta);
724 2130 : if (strncmp (name, "internal/", 9) != 0 && strncmp (name, "conflict/", 9) != 0)
725 : {
726 1852 : const Key * oldMeta = keyGetMeta (dest, name);
727 1852 : if (oldMeta != NULL)
728 : {
729 : // don't overwrite metadata
730 : // array metadata is not a conflict
731 510 : if (strcmp (name, "array") != 0 && strcmp (keyString (oldMeta), keyString (meta)) != 0)
732 : {
733 2 : char * conflictName = elektraFormat ("conflict/%s", name);
734 2 : keySetMeta (dest, conflictName, keyString (oldMeta));
735 2 : elektraFree (conflictName);
736 2 : addConflict (dest, CONFLICT_COLLISION);
737 2 : elektraMetaArrayAdd (dest, "conflict/collision", name);
738 : }
739 : }
740 : else
741 : {
742 1342 : keyCopyMeta (dest, src, name);
743 : }
744 : }
745 : }
746 937 : }
747 :
748 : /**
749 : * Process exactly one key of the specification.
750 : *
751 : * @param specKey The spec Key to process.
752 : * @param parentKey The parent key (for errors)
753 : * @param ks The full KeySet
754 : * @param ch How should conflicts be handled?
755 : * @param isKdbGet is this the kdbGet call?
756 : *
757 : * @retval 0 on success
758 : * @retval -1 otherwise
759 : */
760 2227 : static int processSpecKey (Key * specKey, Key * parentKey, KeySet * ks, const ConflictHandling * ch, bool isKdbGet)
761 : {
762 2227 : bool require = keyGetMeta (specKey, "require") != NULL;
763 2227 : bool wildcardSpec = isWildcardSpec (specKey);
764 :
765 2227 : if (isArraySpec (specKey))
766 : {
767 198 : validateArrayMembers (ks, specKey);
768 : // only process possible conflicts (e.g. from empty arrays)
769 : // then skip uninstantiated array specs
770 198 : return processAllConflicts (specKey, ks, parentKey, ch, isKdbGet);
771 : }
772 :
773 2029 : int found = 0;
774 : Key * cur;
775 :
776 2029 : ksRewind (ks);
777 2029 : ksNext (ks); // set cursor to first
778 :
779 : // externalize cursor to avoid having to reset after ksLookups
780 2029 : cursor_t cursor = ksGetCursor (ks);
781 6344 : for (; (cur = ksAtCursor (ks, cursor)) != NULL; ++cursor)
782 : {
783 4315 : if (!specMatches (specKey, cur))
784 : {
785 4002 : continue;
786 : }
787 :
788 313 : found = 1;
789 :
790 313 : if (wildcardSpec)
791 : {
792 15 : validateWildcardSubs (ks, cur);
793 : }
794 :
795 313 : copyMeta (cur, specKey);
796 : }
797 :
798 :
799 2029 : int ret = 0;
800 2029 : if (!found)
801 : {
802 1722 : if (require)
803 : {
804 10 : char * msg = elektraFormat ("Required key %s is missing.", strchr (keyName (specKey), '/'));
805 10 : handleConflict (parentKey, msg, ch->missing);
806 10 : elektraFree (msg);
807 10 : if (ch->missing != IGNORE)
808 : {
809 8 : ret = -1;
810 : }
811 : }
812 :
813 1722 : if (isKdbGet)
814 : {
815 567 : if (keyGetMeta (specKey, "assign/condition") != NULL)
816 : {
817 2 : Key * newKey = keyNew (strchr (keyName (specKey), '/'), KEY_CASCADING_NAME, KEY_END);
818 2 : copyMeta (newKey, specKey);
819 2 : ksAppendKey (ks, newKey);
820 : }
821 565 : else if (keyGetMeta (specKey, "default") != NULL)
822 : {
823 280 : Key * newKey = keyNew (strchr (keyName (specKey), '/'), KEY_CASCADING_NAME, KEY_VALUE,
824 : keyString (keyGetMeta (specKey, "default")), KEY_END);
825 280 : copyMeta (newKey, specKey);
826 280 : ksAppendKey (ks, newKey);
827 : }
828 : }
829 :
830 1722 : if (keyGetMeta (specKey, "array") != NULL)
831 : {
832 38 : Key * newKey = keyNew (strchr (keyName (specKey), '/'), KEY_CASCADING_NAME, KEY_END);
833 38 : copyMeta (newKey, specKey);
834 38 : if (!isKdbGet)
835 : {
836 0 : keySetMeta (newKey, "internal/spec/remove", "");
837 : }
838 38 : ksAppendKey (ks, newKey);
839 : }
840 : }
841 :
842 2029 : if (processAllConflicts (specKey, ks, parentKey, ch, isKdbGet) != 0)
843 : {
844 40 : ret = -1;
845 : }
846 :
847 : return ret;
848 : }
849 :
850 5361 : int elektraSpecGet (Plugin * handle, KeySet * returned, Key * parentKey)
851 : {
852 5361 : if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/spec"))
853 : {
854 98 : KeySet * contract =
855 98 : ksNew (30, keyNew ("system/elektra/modules/spec", KEY_VALUE, "spec plugin waits for your orders", KEY_END),
856 : keyNew ("system/elektra/modules/spec/exports", KEY_END),
857 : keyNew ("system/elektra/modules/spec/exports/get", KEY_FUNC, elektraSpecGet, KEY_END),
858 : keyNew ("system/elektra/modules/spec/exports/set", KEY_FUNC, elektraSpecSet, KEY_END),
859 : #include ELEKTRA_README
860 : keyNew ("system/elektra/modules/spec/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
861 98 : ksAppend (returned, contract);
862 98 : ksDel (contract);
863 :
864 98 : return ELEKTRA_PLUGIN_STATUS_SUCCESS; // success
865 : }
866 :
867 : // parse configuration
868 : ConflictHandling ch;
869 :
870 5263 : KeySet * config = elektraPluginGetConfig (handle);
871 5263 : parseConfig (config, &ch, CONFIG_BASE_NAME_GET);
872 :
873 : // build spec
874 5263 : KeySet * specKS = ksNew (0, KS_END);
875 :
876 : Key * cur;
877 5263 : ksRewind (returned);
878 49844 : while ((cur = ksNext (returned)) != NULL)
879 : {
880 39318 : if (keyGetNamespace (cur) == KEY_NS_SPEC)
881 : {
882 623 : if (isArraySpec (cur))
883 : {
884 114 : KeySet * specs = instantiateArraySpec (returned, cur, parentKey, ch.member);
885 114 : ksAppend (specKS, specs);
886 114 : ksDel (specs);
887 : }
888 :
889 623 : ksAppendKey (specKS, cur);
890 : }
891 : }
892 :
893 5263 : int ret = ELEKTRA_PLUGIN_STATUS_SUCCESS;
894 5263 : if (keyGetMeta (parentKey, "internal/spec/error") != NULL)
895 : {
896 4 : ret = ELEKTRA_PLUGIN_STATUS_ERROR;
897 : }
898 :
899 : // remove spec namespace from returned
900 5263 : Key * specParent = keyNew ("spec", KEY_END);
901 5263 : ksDel (ksCut (returned, specParent));
902 5263 : keyDel (specParent);
903 :
904 : // extract other namespaces
905 5263 : KeySet * ks = ksCut (returned, parentKey);
906 :
907 : // do actual work
908 : Key * specKey;
909 5263 : ksRewind (specKS);
910 11299 : while ((specKey = ksNext (specKS)) != NULL)
911 : {
912 773 : if (processSpecKey (specKey, parentKey, ks, &ch, true) != 0)
913 : {
914 68 : ret = ELEKTRA_PLUGIN_STATUS_ERROR;
915 : }
916 : }
917 :
918 : // reconstruct KeySet
919 5263 : ksAppend (returned, specKS);
920 5263 : ksAppend (returned, ks);
921 :
922 : // cleanup
923 5263 : ksDel (ks);
924 5263 : ksDel (specKS);
925 :
926 5263 : keySetMeta (parentKey, "internal/spec/error", NULL);
927 :
928 5263 : return ret;
929 : }
930 :
931 2712 : int elektraSpecSet (Plugin * handle, KeySet * returned, Key * parentKey)
932 : {
933 : // parse configuration
934 : ConflictHandling ch;
935 :
936 2712 : KeySet * config = elektraPluginGetConfig (handle);
937 2712 : parseConfig (config, &ch, CONFIG_BASE_NAME_SET);
938 :
939 : // build spec
940 2712 : KeySet * specKS = ksNew (0, KS_END);
941 :
942 : Key * cur;
943 2712 : ksRewind (returned);
944 172563 : while ((cur = ksNext (returned)) != NULL)
945 : {
946 167139 : if (keyGetNamespace (cur) == KEY_NS_SPEC)
947 : {
948 1418 : if (isArraySpec (cur))
949 : {
950 56 : KeySet * specs = instantiateArraySpec (returned, cur, parentKey, ch.member);
951 56 : ksAppend (specKS, specs);
952 56 : ksDel (specs);
953 : }
954 :
955 1418 : ksAppendKey (specKS, cur);
956 : }
957 : }
958 :
959 2712 : int ret = ELEKTRA_PLUGIN_STATUS_SUCCESS;
960 2712 : if (keyGetMeta (parentKey, "internal/spec/error") != NULL)
961 : {
962 0 : ret = ELEKTRA_PLUGIN_STATUS_ERROR;
963 : }
964 :
965 : // remove spec namespace from returned
966 2712 : Key * specParent = keyNew ("spec", KEY_END);
967 2712 : ksDel (ksCut (returned, specParent));
968 2712 : keyDel (specParent);
969 :
970 : // extract other namespaces
971 2712 : KeySet * ks = ksCut (returned, parentKey);
972 :
973 : // do actual work
974 : Key * specKey;
975 2712 : ksRewind (specKS);
976 6878 : while ((specKey = ksNext (specKS)) != NULL)
977 : {
978 1454 : if (processSpecKey (specKey, parentKey, ks, &ch, false) != 0)
979 : {
980 0 : ret = ELEKTRA_PLUGIN_STATUS_ERROR;
981 : }
982 :
983 1454 : keySetMeta (specKey, "internal/spec/array/validated", NULL);
984 :
985 1454 : if (keyGetMeta (specKey, "internal/spec/array") == NULL && keyGetMeta (specKey, "internal/spec/remove") == NULL)
986 : {
987 1406 : ksAppendKey (returned, specKey);
988 : }
989 : }
990 :
991 : // reconstruct KeySet
992 2712 : ksRewind (ks);
993 163149 : while ((cur = ksNext (ks)) != NULL)
994 : {
995 157725 : if (keyGetNamespace (cur) == KEY_NS_SPEC)
996 : {
997 0 : continue;
998 : }
999 :
1000 157725 : keySetMeta (cur, "internal/spec/array/validated", NULL);
1001 :
1002 157725 : if (keyGetMeta (cur, "internal/spec/array") == NULL && keyGetMeta (cur, "internal/spec/remove") == NULL)
1003 : {
1004 157725 : ksAppendKey (returned, cur);
1005 : }
1006 : }
1007 :
1008 : // cleanup
1009 2712 : ksDel (ks);
1010 2712 : ksDel (specKS);
1011 :
1012 2712 : keySetMeta (parentKey, "internal/spec/error", NULL);
1013 :
1014 2712 : return ret;
1015 : }
1016 :
1017 5552 : Plugin * ELEKTRA_PLUGIN_EXPORT
1018 : {
1019 : // clang-format off
1020 5552 : return elektraPluginExport ("spec",
1021 : ELEKTRA_PLUGIN_GET, &elektraSpecGet,
1022 : ELEKTRA_PLUGIN_SET, &elektraSpecSet,
1023 : ELEKTRA_PLUGIN_END);
1024 : }
1025 :
|