LCOV - code coverage report
Current view: top level - src/plugins/reference - reference.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 175 203 86.2 %
Date: 2019-09-12 12:28:41 Functions: 9 10 90.0 %

          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             : }

Generated by: LCOV version 1.13