Line data Source code
1 : /**
2 : * @file
3 : *
4 : * @brief Source for reference plugin
5 : *
6 : * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
7 : *
8 : */
9 :
10 : #include "reference.h"
11 : #include "referencegraph.h"
12 :
13 : #include <kdbease.h>
14 : #include <kdberrors.h>
15 : #include <kdbglobbing.h>
16 : #include <kdbhelper.h>
17 : #include <stdbool.h>
18 :
19 44 : int elektraReferenceGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
20 : {
21 44 : if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/reference"))
22 : {
23 42 : KeySet * contract = ksNew (
24 : 30, keyNew ("system/elektra/modules/reference", KEY_VALUE, "reference plugin waits for your orders", KEY_END),
25 : keyNew ("system/elektra/modules/reference/exports", KEY_END),
26 : keyNew ("system/elektra/modules/reference/exports/get", KEY_FUNC, elektraReferenceGet, KEY_END),
27 : keyNew ("system/elektra/modules/reference/exports/set", KEY_FUNC, elektraReferenceSet, KEY_END),
28 : #include ELEKTRA_README
29 : keyNew ("system/elektra/modules/reference/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
30 42 : ksAppend (returned, contract);
31 42 : ksDel (contract);
32 :
33 42 : return ELEKTRA_PLUGIN_STATUS_SUCCESS;
34 : }
35 : // get all keys
36 :
37 : return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
38 : }
39 :
40 84 : static Key * resolveReference (KeySet * allKeys, const char * reference, const Key * baseKey, Key * parentKey)
41 : {
42 84 : if (reference == NULL || strlen (reference) == 0)
43 : {
44 : return NULL;
45 : }
46 :
47 84 : if (elektraIsReferenceRedundant (reference))
48 : {
49 0 : ELEKTRA_ADD_VALIDATION_SEMANTIC_WARNINGF (parentKey, "Reference '%s' uses '/./' or '/../' redundantly", reference);
50 : }
51 :
52 84 : char * fullReference = elektraResolveReference (reference, baseKey, parentKey);
53 84 : Key * result = ksLookupByName (allKeys, fullReference, 0);
54 84 : elektraFree (fullReference);
55 :
56 84 : return result;
57 : }
58 :
59 6 : static char * resolveRestriction (const char * restriction, const Key * baseKey, Key * parentKey)
60 : {
61 6 : if (restriction == NULL || strlen (restriction) == 0)
62 : {
63 : return NULL;
64 : }
65 :
66 4 : if (elektraIsReferenceRedundant (restriction))
67 : {
68 0 : ELEKTRA_ADD_VALIDATION_SEMANTIC_WARNINGF (parentKey, "Restriction '%s' uses '/./' or '/../' redundantly", restriction);
69 : }
70 :
71 4 : return elektraResolveReference (restriction, baseKey, parentKey);
72 : }
73 :
74 :
75 : static bool checkRestriction (const Key * key, const char * restriction)
76 : {
77 6 : return elektraKeyGlob (key, restriction) == 0;
78 : }
79 :
80 32 : static int checkSingleReference (const Key * key, KeySet * allKeys, Key * parentKey)
81 : {
82 32 : if (key == NULL)
83 : {
84 : return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
85 : }
86 :
87 32 : const char * reference = keyString (key);
88 32 : if (reference == NULL || strlen (reference) == 0)
89 : {
90 : return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
91 : }
92 :
93 28 : const Key * restrictKey = keyGetMeta (key, CHECK_REFERENCE_RESTRICT_KEYNAME);
94 : KeySet * restrictions;
95 28 : if (restrictKey != NULL)
96 : {
97 6 : const char * restrictValue = keyString (restrictKey);
98 6 : if (elektraArrayValidateBaseNameString (restrictValue) >= 0)
99 : {
100 0 : restrictions = elektraArrayGet (restrictKey, allKeys);
101 : }
102 : else
103 : {
104 6 : restrictions = ksNew (1, keyNew ("/#0", KEY_VALUE, restrictValue, KEY_END), KS_END);
105 : }
106 : }
107 : else
108 : {
109 22 : restrictions = ksNew (0, KS_END);
110 : }
111 :
112 : KeySet * refArray;
113 28 : if (elektraArrayValidateBaseNameString (reference) >= 0)
114 : {
115 4 : refArray = elektraArrayGet (key, allKeys);
116 : }
117 : else
118 : {
119 24 : refArray = ksNew (1, keyDup (key), KS_END);
120 : }
121 :
122 28 : ksRewind (refArray);
123 : const Key * arrayElement;
124 71 : while ((arrayElement = ksNext (refArray)) != NULL)
125 : {
126 30 : const char * ref = keyString (arrayElement);
127 30 : if (ref == NULL || strlen (ref) == 0)
128 : {
129 0 : continue;
130 : }
131 :
132 30 : const char * elementName = keyName (arrayElement);
133 :
134 30 : Key * refKey = resolveReference (allKeys, ref, arrayElement, parentKey);
135 30 : bool error = false;
136 30 : if (refKey == NULL)
137 : {
138 11 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (
139 : parentKey, "Reference '%s', set in key '%s', does not reference an existing key", ref, elementName);
140 11 : error = true;
141 : }
142 :
143 30 : if (ksGetSize (restrictions) > 0)
144 : {
145 6 : ksRewind (restrictions);
146 : Key * curRestriction;
147 6 : bool anyMatch = false;
148 18 : while ((curRestriction = ksNext (restrictions)) != NULL)
149 : {
150 6 : char * restriction = resolveRestriction (keyString (curRestriction), key, parentKey);
151 6 : if (checkRestriction (refKey, restriction))
152 : {
153 2 : anyMatch = true;
154 : }
155 6 : elektraFree (restriction);
156 : }
157 :
158 6 : if (!anyMatch)
159 : {
160 4 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (
161 : parentKey, "Reference '%s', set in key '%s', does not any of the given restrictions", ref,
162 : elementName);
163 4 : error = true;
164 : }
165 : }
166 :
167 30 : keyDel (refKey);
168 :
169 30 : if (error)
170 : {
171 15 : ksDel (restrictions);
172 15 : ksDel (refArray);
173 15 : return ELEKTRA_PLUGIN_STATUS_ERROR;
174 : }
175 : }
176 13 : ksDel (restrictions);
177 13 : ksDel (refArray);
178 :
179 :
180 13 : return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
181 : }
182 :
183 10 : static bool checkReferenceGraphAcyclic (const RefGraph * referenceGraph, const char * root)
184 : {
185 10 : if (rgEmpty (referenceGraph))
186 : {
187 : return true;
188 : }
189 :
190 10 : if (!rgHasLeaf (referenceGraph))
191 : {
192 : return false;
193 : }
194 :
195 8 : RefGraph * curGraph = rgDup (referenceGraph);
196 :
197 24 : while (rgHasLeaf (curGraph))
198 : {
199 16 : rgRemoveLeaves (curGraph);
200 :
201 16 : if (rgEmpty (curGraph))
202 : {
203 8 : rgDel (curGraph);
204 8 : return true;
205 : }
206 : }
207 :
208 0 : KeySet * nodes = ksNew (0, KS_END);
209 : ELEKTRA_LOG_NOTICE ("start of path with cycle: %s", root);
210 :
211 0 : const char * node = root;
212 0 : while (ksGetSize (nodes) != ksAppendKey (nodes, keyNew (node, KEY_END)))
213 : {
214 : ELEKTRA_LOG_NOTICE ("refers to: %s", node);
215 0 : node = rgGetEdge (curGraph, root, 0);
216 : }
217 :
218 : ELEKTRA_LOG_NOTICE ("already in chain!!");
219 :
220 0 : rgDel (curGraph);
221 0 : return false;
222 : }
223 :
224 786 : static int filterAlternatives (const Key * key, void * argument)
225 : {
226 786 : const Key * metaKey = keyGetMeta (key, CHECK_REFERENCE_KEYNAME);
227 786 : const char * metaValue = metaKey != NULL ? keyString (metaKey) : NULL;
228 :
229 786 : const Key * referenceParent = (const Key *) argument;
230 786 : return metaValue != NULL && keyIsDirectBelow (referenceParent, key) && strcmp (metaValue, CHECK_REFERNCE_VALUE_ALTERNATIVE) == 0;
231 : }
232 :
233 12 : static int checkRecursiveReference (const Key * rootKey, KeySet * allKeys, Key * parentKey)
234 : {
235 12 : if (rootKey == NULL)
236 : {
237 : return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
238 : }
239 :
240 12 : RefGraph * referenceGraph = rgNew ();
241 12 : KeySet * allRefnames = ksNew (0, KS_END);
242 12 : KeySet * refnameRoots = ksNew (0, KS_END);
243 :
244 12 : ksAppendKey (refnameRoots, keyNew (keyName (rootKey), KEY_END));
245 :
246 : Key * curRoot;
247 36 : while ((curRoot = ksPop (refnameRoots)) != NULL)
248 : {
249 14 : KeySet * keysToCheck = ksNew (0, KS_END);
250 14 : const char * refname = keyBaseName (curRoot);
251 :
252 14 : Key * rootParent = keyDup (curRoot);
253 14 : keySetBaseName (rootParent, NULL);
254 14 : ksAppendKey (keysToCheck, rootParent);
255 :
256 : Key * cur;
257 90 : while ((cur = ksPop (keysToCheck)) != NULL)
258 : {
259 64 : const char * curName = keyName (cur);
260 64 : KeySet * alternatives = ksNew (0, KS_END);
261 64 : elektraKsFilter (alternatives, allKeys, filterAlternatives, cur);
262 :
263 : Key * curAlternative;
264 132 : while ((curAlternative = ksPop (alternatives)) != NULL)
265 : {
266 4 : if (ksLookup (allRefnames, curAlternative, 0) == NULL)
267 : {
268 2 : ksAppendKey (refnameRoots, keyNew (keyName (curAlternative), KEY_END));
269 2 : ksAppendKey (allRefnames, keyNew (keyName (curAlternative), KEY_END));
270 : }
271 4 : keyDel (curAlternative);
272 : }
273 64 : ksDel (alternatives);
274 :
275 64 : Key * tmp = keyNew (curName, KEY_END);
276 64 : keyAddBaseName (tmp, refname);
277 64 : Key * baseKey = ksLookup (allKeys, tmp, 0);
278 64 : keyDel (tmp);
279 :
280 64 : const char * reference = keyString (baseKey);
281 64 : if (reference == NULL || strlen (reference) == 0)
282 : {
283 0 : keyDel (cur);
284 0 : keyDel (baseKey);
285 0 : continue;
286 : }
287 :
288 64 : const Key * restrictKey = keyGetMeta (baseKey, CHECK_REFERENCE_RESTRICT_KEYNAME);
289 : KeySet * restrictions;
290 64 : if (restrictKey != NULL)
291 : {
292 0 : const char * restrictValue = keyString (restrictKey);
293 0 : if (elektraArrayValidateBaseNameString (restrictValue) >= 0)
294 : {
295 0 : restrictions = elektraArrayGet (restrictKey, allKeys);
296 : }
297 : else
298 : {
299 0 : restrictions = ksNew (1, keyNew ("/#0", KEY_VALUE, restrictValue, KEY_END), KS_END);
300 : }
301 : }
302 : else
303 : {
304 64 : restrictions = ksNew (0, KS_END);
305 : }
306 :
307 : KeySet * refArray;
308 64 : if (elektraArrayValidateBaseNameString (reference) >= 0)
309 : {
310 10 : refArray = elektraArrayGet (baseKey, allKeys);
311 : }
312 : else
313 : {
314 54 : Key * element = keyDup (baseKey);
315 54 : keyAddBaseName (element, "#0");
316 54 : refArray = ksNew (1, element, KS_END);
317 : }
318 64 : keyDel (baseKey);
319 :
320 64 : if (!rgContains (referenceGraph, curName))
321 : {
322 64 : rgAddNode (referenceGraph, curName);
323 : }
324 :
325 64 : ksRewind (refArray);
326 : const Key * arrayElement;
327 180 : while ((arrayElement = ksNext (refArray)) != NULL)
328 : {
329 54 : const char * ref = keyString (arrayElement);
330 54 : if (ref == NULL || strlen (ref) == 0)
331 : {
332 0 : continue;
333 : }
334 :
335 54 : const char * elementName = keyName (arrayElement);
336 :
337 54 : Key * refKey = resolveReference (allKeys, ref, arrayElement, parentKey);
338 54 : bool error = false;
339 54 : if (refKey == NULL)
340 : {
341 2 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (
342 : parentKey, "Reference '%s', set in key '%s', does not reference an existing key", ref,
343 : elementName);
344 2 : error = true;
345 : }
346 :
347 54 : const char * refKeyName = keyName (refKey);
348 :
349 54 : if (ksGetSize (restrictions) > 0)
350 : {
351 0 : ksRewind (restrictions);
352 : Key * curRestriction;
353 0 : bool anyMatch = false;
354 0 : while ((curRestriction = ksNext (restrictions)) != NULL)
355 : {
356 0 : char * restriction = resolveRestriction (keyString (curRestriction), baseKey, parentKey);
357 0 : if (checkRestriction (refKey, restriction))
358 : {
359 0 : anyMatch = true;
360 : }
361 0 : elektraFree (restriction);
362 : }
363 :
364 0 : if (!anyMatch)
365 : {
366 0 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (
367 : parentKey,
368 : "Reference '%s', set in key '%s', does not any of the given restrictions", ref,
369 : elementName);
370 0 : error = true;
371 : }
372 : }
373 :
374 54 : if (error)
375 : {
376 2 : rgDel (referenceGraph);
377 2 : ksDel (keysToCheck);
378 2 : ksDel (refnameRoots);
379 2 : ksDel (allRefnames);
380 2 : ksDel (refArray);
381 2 : ksDel (restrictions);
382 2 : keyDel (curRoot);
383 2 : keyDel (refKey);
384 2 : keyDel (cur);
385 2 : return ELEKTRA_PLUGIN_STATUS_ERROR;
386 : }
387 :
388 52 : if (!rgContains (referenceGraph, refKeyName))
389 : {
390 50 : ksAppendKey (keysToCheck, keyDup (refKey));
391 50 : rgAddNode (referenceGraph, refKeyName);
392 : }
393 52 : rgAddEdge (referenceGraph, curName, refKeyName);
394 52 : keyDel (refKey);
395 : }
396 62 : ksDel (refArray);
397 62 : ksDel (restrictions);
398 62 : keyDel (cur);
399 : }
400 12 : ksDel (keysToCheck);
401 12 : keyDel (curRoot);
402 : }
403 10 : ksDel (refnameRoots);
404 10 : ksDel (allRefnames);
405 :
406 10 : char * rootName = elektraStrDup (keyName (rootKey));
407 10 : *strrchr (rootName, '/') = '\0';
408 10 : if (!checkReferenceGraphAcyclic (referenceGraph, rootName))
409 : {
410 2 : ELEKTRA_SET_VALIDATION_SEMANTIC_ERROR (parentKey, "The configuration contains a cyclic reference");
411 :
412 2 : elektraFree (rootName);
413 2 : rgDel (referenceGraph);
414 2 : return ELEKTRA_PLUGIN_STATUS_ERROR;
415 : }
416 8 : rgDel (referenceGraph);
417 8 : elektraFree (rootName);
418 :
419 8 : return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
420 : }
421 :
422 47 : int elektraReferenceSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey)
423 : {
424 : Key * cur;
425 47 : ksRewind (returned);
426 :
427 47 : int status = ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
428 301 : while ((cur = ksNext (returned)) != NULL)
429 : {
430 207 : const Key * metaKey = keyGetMeta (cur, CHECK_REFERENCE_KEYNAME);
431 207 : if (metaKey == NULL)
432 : {
433 161 : continue;
434 : }
435 :
436 46 : const char * metaValue = keyString (metaKey);
437 :
438 46 : cursor_t cursor = ksGetCursor (returned);
439 46 : if (strcmp (metaValue, CHECK_REFERNCE_VALUE_SINGLE) == 0)
440 : {
441 32 : status |= checkSingleReference (cur, returned, parentKey);
442 : }
443 :
444 46 : if (strcmp (metaValue, CHECK_REFERNCE_VALUE_RECURSIVE) == 0)
445 : {
446 12 : status |= checkRecursiveReference (cur, returned, parentKey);
447 : }
448 46 : ksSetCursor (returned, cursor);
449 : }
450 :
451 :
452 47 : return status;
453 : }
454 :
455 106 : Plugin * ELEKTRA_PLUGIN_EXPORT
456 : {
457 : // clang-format off
458 106 : return elektraPluginExport ("reference",
459 : ELEKTRA_PLUGIN_GET, &elektraReferenceGet,
460 : ELEKTRA_PLUGIN_SET, &elektraReferenceSet,
461 : ELEKTRA_PLUGIN_END);
462 : }
|