LCOV - code coverage report
Current view: top level - src/plugins/spec - spec.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 401 426 94.1 %
Date: 2019-09-12 12:28:41 Functions: 21 22 95.5 %

          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             : 

Generated by: LCOV version 1.13