LCOV - code coverage report
Current view: top level - src/plugins/specload - specload.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 228 300 76.0 %
Date: 2019-09-12 12:28:41 Functions: 12 14 85.7 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief Source for specload plugin
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  *
       8             :  */
       9             : 
      10             : #include "specload.h"
      11             : 
      12             : #include <kdberrors.h>
      13             : #include <kdbhelper.h>
      14             : 
      15             : #include <kdbease.h>
      16             : #include <kdbinvoke.h>
      17             : #include <kdbmodule.h>
      18             : #include <kdbproposal.h>
      19             : #include <stdio.h>
      20             : #include <stdlib.h>
      21             : #include <unistd.h>
      22             : #include <utime.h>
      23             : 
      24             : // keep #ifdef in sync with kdb export
      25             : #ifdef _WIN32
      26             : #define STDIN_FILENAME ("CON")
      27             : #define STDOUT_FILENAME ("CON")
      28             : #else
      29             : #define STDIN_FILENAME ("/dev/stdin")
      30             : #define STDOUT_FILENAME ("/dev/stdout")
      31             : #endif
      32             : 
      33             : struct change
      34             : {
      35             :         const char * meta;
      36             :         bool add;
      37             :         bool edit;
      38             :         bool remove;
      39             : };
      40             : 
      41             : // TODO: allow more changes
      42             : static struct change allowedChanges[] = { { "description", true, true, true },
      43             :                                           { "opt/help", true, true, true },
      44             :                                           { "default", true, true, false },
      45             :                                           { "type", true, false, false },
      46             :                                           { NULL, false, false, false } };
      47             : 
      48             : static bool readConfig (KeySet * conf, char ** directFilePtr, char ** appPtr, char *** argvPtr, Key * errorKey);
      49             : static bool loadSpec (KeySet * returned, const char * directFile, const char * app, char * argv[], Key * parentKey,
      50             :                       ElektraInvokeHandle * quickDump);
      51             : static int isChangeAllowed (Key * oldKey, Key * newKey);
      52             : static KeySet * calculateMetaDiff (Key * oldKey, Key * newKey);
      53             : 
      54          57 : static inline void freeArgv (char ** argv)
      55             : {
      56          57 :         if (argv != NULL)
      57             :         {
      58             :                 size_t index = 0;
      59          60 :                 while (argv[index] != NULL)
      60             :                 {
      61          40 :                         elektraFree (argv[index]);
      62          40 :                         ++index;
      63             :                 }
      64          20 :                 elektraFree (argv);
      65             :         }
      66          57 : }
      67             : 
      68           0 : static int copyError (Key * dest, Key * src)
      69             : {
      70           0 :         keyRewindMeta (src);
      71           0 :         const Key * metaKey = keyGetMeta (src, "error");
      72           0 :         if (!metaKey) return 0;
      73           0 :         keySetMeta (dest, keyName (metaKey), keyString (metaKey));
      74           0 :         while ((metaKey = keyNextMeta (src)) != NULL)
      75             :         {
      76           0 :                 if (strncmp (keyName (metaKey), "error/", 6) != 0) break;
      77           0 :                 keySetMeta (dest, keyName (metaKey), keyString (metaKey));
      78             :         }
      79             :         return 1;
      80             : }
      81             : 
      82         108 : int elektraSpecloadOpen (Plugin * handle, Key * errorKey)
      83             : {
      84         108 :         Specload * specload = elektraMalloc (sizeof (Specload));
      85             : 
      86         108 :         KeySet * conf = elektraPluginGetConfig (handle);
      87         108 :         if (ksLookupByName (conf, "system/module", 0) != NULL || ksLookupByName (conf, "system/sendspec", 0) != NULL)
      88             :         {
      89          70 :                 elektraFree (specload);
      90          70 :                 return ELEKTRA_PLUGIN_STATUS_SUCCESS;
      91             :         }
      92             : 
      93          38 :         if (!readConfig (conf, &specload->directFile, &specload->app, &specload->argv, errorKey))
      94             :         {
      95           2 :                 elektraFree (specload);
      96           2 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
      97             :         }
      98             : 
      99          36 :         specload->quickDumpConfig = ksNew (0, KS_END);
     100          36 :         specload->quickDump = elektraInvokeOpen ("quickdump", specload->quickDumpConfig, errorKey);
     101             : 
     102          36 :         if (!specload->quickDump)
     103             :         {
     104           0 :                 elektraFree (specload);
     105           0 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     106             :         }
     107             : 
     108          36 :         elektraPluginSetData (handle, specload);
     109             : 
     110          36 :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     111             : }
     112             : 
     113         108 : int elektraSpecloadClose (Plugin * handle, Key * errorKey)
     114             : {
     115         108 :         Specload * specload = elektraPluginGetData (handle);
     116             : 
     117         108 :         if (specload != NULL)
     118             :         {
     119          36 :                 elektraInvokeClose (specload->quickDump, errorKey);
     120             : 
     121          36 :                 ksDel (specload->quickDumpConfig);
     122             : 
     123          36 :                 if (specload->directFile != NULL)
     124             :                 {
     125          26 :                         elektraFree (specload->directFile);
     126             :                 }
     127             : 
     128          36 :                 if (specload->app != NULL)
     129             :                 {
     130          10 :                         elektraFree (specload->app);
     131             :                 }
     132             : 
     133          36 :                 freeArgv (specload->argv);
     134             : 
     135          36 :                 elektraFree (specload);
     136          36 :                 elektraPluginSetData (handle, NULL);
     137             :         }
     138             : 
     139         108 :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     140             : }
     141             : 
     142             : /**
     143             :  * Sends the given specification (@p spec) over stdout, to be received by the process using specload.
     144             :  *
     145             :  * Note: To use this function with elektraInvoke2Args, call elektraInvokeOpen with a config containing
     146             :  * the key 'system/sendspec'. This postpones the check for an existent app until elektraSpecloadGet is called.
     147             :  *
     148             :  * @param handle    A specload plugin handle.
     149             :  * @param spec      The specification to send.
     150             :  * @param parentKey The parent key under which the target specload instance was mounted. Value unused.
     151             :  *
     152             :  * @retval #ELEKTRA_PLUGIN_STATUS_SUCCESS on success
     153             :  * @retval #ELEKTRA_PLUGIN_STATUS_ERROR on error
     154             :  */
     155          46 : int elektraSpecloadSendSpec (Plugin * handle ELEKTRA_UNUSED, KeySet * spec, Key * parentKey)
     156             : {
     157          46 :         Key * errorKey = keyNew (0, KEY_END);
     158             : 
     159          46 :         KeySet * quickDumpConf = ksNew (0, KS_END);
     160             : 
     161          46 :         if (keyGetMeta (parentKey, "system/elektra/quickdump/noparent") != NULL)
     162             :         {
     163           2 :                 ksAppendKey (quickDumpConf, keyNew ("system/noparent", KEY_END));
     164             :         }
     165             : 
     166          46 :         ElektraInvokeHandle * quickDump = elektraInvokeOpen ("quickdump", quickDumpConf, errorKey);
     167             : 
     168          46 :         Key * quickDumpParent = keyNew (keyName (parentKey), KEY_VALUE, STDOUT_FILENAME, KEY_END);
     169             : 
     170          46 :         int result = elektraInvoke2Args (quickDump, "set", spec, quickDumpParent);
     171             : 
     172          46 :         elektraInvokeClose (quickDump, errorKey);
     173          46 :         keyDel (errorKey);
     174          46 :         keyDel (quickDumpParent);
     175          46 :         ksDel (quickDumpConf);
     176             : 
     177          46 :         return result == ELEKTRA_PLUGIN_STATUS_SUCCESS ? ELEKTRA_PLUGIN_STATUS_SUCCESS : ELEKTRA_PLUGIN_STATUS_ERROR;
     178             : }
     179             : 
     180          84 : int elektraSpecloadGet (Plugin * handle, KeySet * returned, Key * parentKey)
     181             : {
     182          84 :         if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/specload"))
     183             :         {
     184          74 :                 KeySet * contract =
     185          74 :                         ksNew (30, keyNew ("system/elektra/modules/specload", KEY_VALUE, "specload plugin waits for your orders", KEY_END),
     186             :                                keyNew ("system/elektra/modules/specload/exports", KEY_END),
     187             :                                keyNew ("system/elektra/modules/specload/exports/open", KEY_FUNC, elektraSpecloadOpen, KEY_END),
     188             :                                keyNew ("system/elektra/modules/specload/exports/close", KEY_FUNC, elektraSpecloadClose, KEY_END),
     189             :                                keyNew ("system/elektra/modules/specload/exports/get", KEY_FUNC, elektraSpecloadGet, KEY_END),
     190             :                                keyNew ("system/elektra/modules/specload/exports/set", KEY_FUNC, elektraSpecloadSet, KEY_END),
     191             :                                keyNew ("system/elektra/modules/specload/exports/checkconf", KEY_FUNC, elektraSpecloadCheckConfig, KEY_END),
     192             :                                keyNew ("system/elektra/modules/specload/exports/sendspec", KEY_FUNC, elektraSpecloadSendSpec, KEY_END),
     193             : #include ELEKTRA_README
     194             :                                keyNew ("system/elektra/modules/specload/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END);
     195          74 :                 ksAppend (returned, contract);
     196          74 :                 ksDel (contract);
     197             : 
     198          74 :                 return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     199             :         }
     200             : 
     201          10 :         if (keyGetNamespace (parentKey) != KEY_NS_SPEC)
     202             :         {
     203           0 :                 ELEKTRA_SET_INTERFACE_ERROR (parentKey, "This plugin can only be used for the spec namespace");
     204           0 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     205             :         }
     206             : 
     207          10 :         Specload * specload = elektraPluginGetData (handle);
     208             : 
     209          10 :         KeySet * spec = ksNew (0, KS_END);
     210             : 
     211          10 :         if (!loadSpec (spec, specload->directFile, specload->app, specload->argv, parentKey, specload->quickDump))
     212             :         {
     213           0 :                 ksDel (spec);
     214           0 :                 ELEKTRA_SET_INSTALLATION_ERROR (
     215             :                         parentKey, "Couldn't load the base specification. Make sure the app is available and the arguments are correct");
     216           0 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     217             :         }
     218             : 
     219          10 :         const char * overlayFile = keyString (parentKey);
     220          10 :         if (overlayFile[0] != '/')
     221             :         {
     222           2 :                 char * path = elektraFormat ("%s/%s", KDB_DB_SPEC, overlayFile);
     223           2 :                 keySetString (parentKey, path);
     224           2 :                 elektraFree (path);
     225             :         }
     226             : 
     227          10 :         if (access (keyString (parentKey), F_OK) != -1)
     228             :         {
     229           4 :                 if (elektraInvoke2Args (specload->quickDump, "get", spec, parentKey) == ELEKTRA_PLUGIN_STATUS_ERROR)
     230             :                 {
     231           0 :                         ksDel (spec);
     232           0 :                         ELEKTRA_SET_INSTALLATION_ERROR (parentKey, "Couldn't load the overlay specification");
     233           0 :                         return ELEKTRA_PLUGIN_STATUS_ERROR;
     234             :                 }
     235             :         }
     236             : 
     237          10 :         ksAppend (returned, spec);
     238          10 :         ksDel (spec);
     239             : 
     240          10 :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     241             : }
     242             : 
     243          56 : int elektraSpecloadSet (Plugin * handle, KeySet * returned, Key * parentKey)
     244             : {
     245          56 :         if (keyGetNamespace (parentKey) != KEY_NS_SPEC)
     246             :         {
     247           0 :                 ELEKTRA_SET_INTERFACE_ERROR (parentKey, "This plugin can only be used for the spec namespace");
     248           0 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     249             :         }
     250             : 
     251          56 :         Specload * specload = elektraPluginGetData (handle);
     252             : 
     253          56 :         KeySet * spec = ksNew (0, KS_END);
     254          56 :         if (!loadSpec (spec, specload->directFile, specload->app, specload->argv, parentKey, specload->quickDump))
     255             :         {
     256           0 :                 ksDel (spec);
     257           0 :                 ELEKTRA_SET_INSTALLATION_ERROR (
     258             :                         parentKey, "Couldn't load the base specification. Make sure the app is available and the arguments are correct");
     259           0 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     260             :         }
     261             : 
     262          56 :         const char * overlayFile = keyString (parentKey);
     263          56 :         if (overlayFile[0] != '/')
     264             :         {
     265           0 :                 char * path = elektraFormat ("%s/%s", KDB_DB_SPEC, overlayFile);
     266           0 :                 keySetString (parentKey, path);
     267           0 :                 elektraFree (path);
     268             :         }
     269             : 
     270          56 :         KeySet * oldData = ksNew (ksGetSize (returned), KS_END);
     271          56 :         if (access (keyString (parentKey), F_OK) != -1)
     272             :         {
     273          52 :                 if (elektraInvoke2Args (specload->quickDump, "get", oldData, parentKey) == ELEKTRA_PLUGIN_STATUS_ERROR)
     274             :                 {
     275           0 :                         ksDel (oldData);
     276           0 :                         ELEKTRA_SET_INSTALLATION_ERROR (parentKey, "Couldn't load the overlay specification");
     277           0 :                         return ELEKTRA_PLUGIN_STATUS_ERROR;
     278             :                 }
     279             :         }
     280             : 
     281          56 :         KeySet * overrides = ksNew (0, KS_END);
     282             : 
     283          56 :         cursor_t cursor = ksGetCursor (returned);
     284          56 :         ksRewind (returned);
     285             :         Key * new;
     286             :         Key * old;
     287         156 :         while ((new = ksNext (returned)) != NULL)
     288             :         {
     289          56 :                 old = ksLookup (oldData, new, KDB_O_POP);
     290          56 :                 if (old == NULL)
     291             :                 {
     292          28 :                         old = ksLookup (spec, new, 0);
     293             :                 }
     294             : 
     295          56 :                 int changeAllowed = isChangeAllowed (old, new);
     296          56 :                 keyDel (old);
     297             : 
     298          56 :                 if (changeAllowed < 0)
     299             :                 {
     300          12 :                         ELEKTRA_SET_RESOURCE_ERROR (parentKey, "This kind of change is not allowed");
     301          12 :                         ksSetCursor (returned, cursor);
     302          12 :                         ksDel (overrides);
     303          12 :                         ksDel (oldData);
     304          12 :                         ksDel (spec);
     305          12 :                         return ELEKTRA_PLUGIN_STATUS_ERROR;
     306             :                 }
     307             : 
     308          44 :                 if (changeAllowed > 0)
     309             :                 {
     310          36 :                         ksAppendKey (overrides, new);
     311             :                 }
     312             :         }
     313          44 :         ksDel (spec);
     314             : 
     315             :         // check if remaining old keys can be removed
     316          88 :         while ((old = ksNext (oldData)) != NULL)
     317             :         {
     318           0 :                 if (isChangeAllowed (old, NULL) > 0)
     319             :                 {
     320           0 :                         ksSetCursor (returned, cursor);
     321           0 :                         ksDel (overrides);
     322           0 :                         ksDel (oldData);
     323           0 :                         return ELEKTRA_PLUGIN_STATUS_ERROR;
     324             :                 }
     325             :         }
     326          44 :         ksDel (oldData);
     327             : 
     328          44 :         ksSetCursor (returned, cursor);
     329             : 
     330          44 :         int result = elektraInvoke2Args (specload->quickDump, "set", overrides, parentKey);
     331          44 :         ksDel (overrides);
     332          44 :         return result;
     333             : }
     334             : 
     335          21 : int elektraSpecloadCheckConfig (Key * errorKey, KeySet * conf)
     336             : {
     337             :         char * directFile;
     338             :         char * app;
     339             :         char ** argv;
     340             : 
     341          21 :         if (!readConfig (conf, &directFile, &app, &argv, errorKey))
     342             :         {
     343             :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     344             :         }
     345             : 
     346          21 :         bool directFileMode = directFile != NULL;
     347             : 
     348          21 :         KeySet * quickDumpConfig = ksNew (0, KS_END);
     349          21 :         ElektraInvokeHandle * quickDump = elektraInvokeOpen ("quickdump", quickDumpConfig, errorKey);
     350             : 
     351          21 :         KeySet * spec = ksNew (0, KS_END);
     352             : 
     353          21 :         bool result = loadSpec (spec, directFile, app, argv, errorKey, quickDump);
     354             : 
     355          21 :         elektraInvokeClose (quickDump, errorKey);
     356          21 :         ksDel (quickDumpConfig);
     357          21 :         elektraFree (directFile);
     358          21 :         elektraFree (app);
     359          21 :         freeArgv (argv);
     360          21 :         ksDel (spec);
     361             : 
     362          21 :         if (!result)
     363             :         {
     364           0 :                 if (directFileMode)
     365             :                 {
     366           0 :                         ELEKTRA_SET_INSTALLATION_ERROR (
     367             :                                 errorKey, "Couldn't load the specification. Make sure the specified file is a valid quickdump file");
     368             :                 }
     369             :                 else
     370             :                 {
     371           0 :                         ELEKTRA_SET_INSTALLATION_ERROR (
     372             :                                 errorKey, "Couldn't load the specification. Make sure the app is available and the arguments are correct");
     373             :                 }
     374             :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     375             :         }
     376             : 
     377             :         return ELEKTRA_PLUGIN_STATUS_NO_UPDATE;
     378             : }
     379             : 
     380          59 : bool readConfig (KeySet * conf, char ** directFilePtr, char ** appPtr, char *** argvPtr, Key * errorKey)
     381             : {
     382          59 :         Key * fileKey = ksLookupByName (conf, "/file", 0);
     383             : 
     384          59 :         if (fileKey != NULL)
     385             :         {
     386          37 :                 const char * directFile = keyString (fileKey);
     387             : 
     388          37 :                 if (directFile[0] != '/')
     389             :                 {
     390           0 :                         ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (errorKey, "The value of the file config key '%s' is not an absolute path",
     391             :                                                                  directFile);
     392           0 :                         return false;
     393             :                 }
     394             : 
     395          37 :                 if (access (directFile, R_OK) != 0)
     396             :                 {
     397           0 :                         ELEKTRA_SET_RESOURCE_ERRORF (errorKey, "File '%s' doesn't exist or cannot be read", directFile);
     398           0 :                         return false;
     399             :                 }
     400             : 
     401          37 :                 *directFilePtr = elektraStrDup (directFile);
     402          37 :                 *appPtr = NULL;
     403          37 :                 *argvPtr = NULL;
     404             : 
     405          37 :                 return true;
     406             :         }
     407             : 
     408          22 :         Key * appKey = ksLookupByName (conf, "/app", 0);
     409             : 
     410          22 :         if (appKey == NULL)
     411             :         {
     412           2 :                 ELEKTRA_SET_INSTALLATION_ERROR (errorKey, "You need to set an application using the app config key");
     413           2 :                 return false;
     414             :         }
     415             : 
     416          20 :         const char * app = keyString (appKey);
     417             : 
     418          20 :         if (app[0] != '/')
     419             :         {
     420           0 :                 ELEKTRA_SET_VALIDATION_SYNTACTIC_ERRORF (errorKey, "The value of the app config key '%s' is not an absolute path", app);
     421           0 :                 return false;
     422             :         }
     423             : 
     424          20 :         if (access (app, X_OK) != 0)
     425             :         {
     426           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (errorKey, "App '%s' doesn't exist or is not executable", app);
     427           0 :                 return false;
     428             :         }
     429             : 
     430             :         KeySet * args;
     431          20 :         if (ksLookupByName (conf, "/app/args", 0) == NULL)
     432             :         {
     433          20 :                 args = ksNew (1, keyNew ("user/app/args/#0", KEY_VALUE, "--elektra-spec", KEY_END), KS_END);
     434             :         }
     435             :         else
     436             :         {
     437           0 :                 Key * parentKey = keyNew ("/app/args", KEY_END);
     438           0 :                 args = elektraArrayGet (parentKey, conf);
     439           0 :                 keyDel (parentKey);
     440             :         }
     441             : 
     442          20 :         ssize_t size = ksGetSize (args);
     443          20 :         char ** argv = elektraMalloc ((size + 2) * sizeof (char *));
     444          20 :         argv[0] = elektraStrDup (app);
     445             : 
     446          20 :         size_t index = 1;
     447          20 :         ksRewind (args);
     448             :         Key * cur;
     449          60 :         while ((cur = ksNext (args)) != NULL)
     450             :         {
     451          20 :                 argv[index] = elektraStrDup (keyString (cur));
     452          20 :                 ++index;
     453             :         }
     454          20 :         argv[index] = NULL;
     455          20 :         ksDel (args);
     456             : 
     457          20 :         *directFilePtr = NULL;
     458          20 :         *appPtr = elektraStrDup (app);
     459          20 :         *argvPtr = argv;
     460             : 
     461          20 :         return true;
     462             : }
     463             : 
     464          87 : bool loadSpec (KeySet * returned, const char * directFile, const char * app, char * argv[], Key * parentKey,
     465             :                ElektraInvokeHandle * quickDump)
     466             : {
     467          87 :         if (directFile != NULL)
     468             :         {
     469          45 :                 Key * quickDumpParent = keyNew (keyName (parentKey), KEY_VALUE, directFile, KEY_END);
     470          45 :                 int result = elektraInvoke2Args (quickDump, "get", returned, quickDumpParent);
     471             : 
     472          45 :                 if (result != ELEKTRA_PLUGIN_STATUS_SUCCESS)
     473             :                 {
     474           0 :                         copyError (parentKey, quickDumpParent);
     475             :                 }
     476          45 :                 keyDel (quickDumpParent);
     477             : 
     478          45 :                 return result == ELEKTRA_PLUGIN_STATUS_SUCCESS;
     479             :         }
     480             : 
     481             :         pid_t pid;
     482             :         int fd[2];
     483             : 
     484          42 :         if (pipe (fd) != 0)
     485             :         {
     486           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not execute app. Reason: %s", strerror (errno));
     487           0 :                 return false;
     488             :         }
     489             : 
     490          42 :         pid = fork ();
     491             : 
     492          84 :         if (pid == -1)
     493             :         {
     494           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not execute app. Reason: %s", strerror (errno));
     495           0 :                 return false;
     496             :         }
     497             : 
     498          84 :         if (pid == 0)
     499             :         {
     500             :                 // child
     501          42 :                 if (dup2 (fd[1], STDOUT_FILENO) == -1)
     502             :                 {
     503           0 :                         exit (EXIT_FAILURE);
     504             :                 }
     505             : 
     506          42 :                 close (fd[0]);
     507          42 :                 close (fd[1]);
     508             : 
     509          42 :                 execv (app, argv);
     510             : 
     511          42 :                 exit (EXIT_FAILURE);
     512             :         }
     513             : 
     514             :         // parent
     515          42 :         close (fd[1]);
     516             : 
     517          42 :         int stdin_copy = dup (STDIN_FILENO);
     518             : 
     519          42 :         if (dup2 (fd[0], STDIN_FILENO) == -1)
     520             :         {
     521           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not execute app. Reason: %s", strerror (errno));
     522           0 :                 return false;
     523             :         }
     524             : 
     525          42 :         close (fd[0]);
     526             : 
     527          42 :         Key * quickDumpParent = keyNew (keyName (parentKey), KEY_VALUE, STDIN_FILENAME, KEY_END);
     528             : 
     529          42 :         int result = elektraInvoke2Args (quickDump, "get", returned, quickDumpParent);
     530             : 
     531          42 :         if (result != ELEKTRA_PLUGIN_STATUS_SUCCESS)
     532             :         {
     533           0 :                 copyError (parentKey, quickDumpParent);
     534             :         }
     535          42 :         keyDel (quickDumpParent);
     536             : 
     537          42 :         if (dup2 (stdin_copy, STDIN_FILENO) == -1)
     538             :         {
     539           0 :                 ELEKTRA_SET_RESOURCE_ERRORF (parentKey, "Could not execute app. Reason: %s", strerror (errno));
     540           0 :                 return false;
     541             :         }
     542          42 :         close (stdin_copy);
     543             : 
     544          42 :         return result == ELEKTRA_PLUGIN_STATUS_SUCCESS;
     545             : }
     546             : 
     547             : /**
     548             :  * Checks whether the @p oldKey can be changed into @p newKey safely.
     549             :  *
     550             :  * Both @p oldKey and @p newKey may be NULL, to represent adding and
     551             :  * removing Keys respectively.
     552             :  *
     553             :  * @param oldKey old Key, or NULL for added Key
     554             :  * @param newKey new Key, or NULL for removed Key
     555             :  *
     556             :  * @retval 0  no change detected
     557             :  * @retval 1  change allowed
     558             :  * @retval -1 change forbidden
     559             :  * @retval -2 error, e.g. different keynames
     560             :  */
     561          56 : int isChangeAllowed (Key * oldKey, Key * newKey)
     562             : {
     563          56 :         if (oldKey == newKey)
     564             :         {
     565             :                 // same key (pointer)
     566             :                 return 0;
     567             :         }
     568             : 
     569          56 :         keyswitch_t changes = keyCompare (oldKey, newKey);
     570          56 :         if (changes == 0)
     571             :         {
     572             :                 // equal keys
     573             :                 return 0;
     574             :         }
     575             : 
     576          48 :         if (changes != KEY_NULL && changes != KEY_META)
     577             :         {
     578             :                 // only metadata changes allowed
     579             :                 return -1;
     580             :         }
     581             : 
     582          40 :         if ((changes & KEY_NAME) != 0)
     583             :         {
     584             :                 // different key names
     585             :                 return -2;
     586             :         }
     587             : 
     588          40 :         if (oldKey == NULL)
     589             :         {
     590          20 :                 if (keyIsBinary (newKey) ? keyValue (newKey) != NULL : strlen (keyString (newKey)) > 0)
     591             :                 {
     592             :                         // adding values not allowed
     593             :                         return -1;
     594             :                 }
     595             : 
     596          16 :                 oldKey = keyNew (keyName (newKey), KEY_END);
     597             :         }
     598             :         else
     599             :         {
     600          20 :                 oldKey = keyDup (oldKey);
     601             :         }
     602             : 
     603          36 :         if (newKey == NULL)
     604             :         {
     605           0 :                 if (keyIsBinary (oldKey) ? keyValue (oldKey) != NULL : strlen (keyString (oldKey)) > 0)
     606             :                 {
     607             :                         // removing values not allowed
     608             :                         return -1;
     609             :                 }
     610             : 
     611           0 :                 newKey = keyNew (keyName (oldKey), KEY_END);
     612             :         }
     613             :         else
     614             :         {
     615          36 :                 newKey = keyDup (newKey);
     616             :         }
     617             : 
     618          36 :         KeySet * metaDiff = calculateMetaDiff (oldKey, newKey);
     619             : 
     620          36 :         keyDel (oldKey);
     621          36 :         keyDel (newKey);
     622             : 
     623         180 :         for (int i = 0; allowedChanges[i].meta != NULL; ++i)
     624             :         {
     625         144 :                 struct change cur = allowedChanges[i];
     626         144 :                 Key * diff = ksLookupByName (metaDiff, cur.meta, KDB_O_POP);
     627             : 
     628         144 :                 if (diff == NULL)
     629             :                 {
     630          96 :                         continue;
     631             :                 }
     632             : 
     633          48 :                 if (strcmp (keyString (diff), "add") == 0 && !cur.add)
     634             :                 {
     635           0 :                         keyDel (diff);
     636           0 :                         ksDel (metaDiff);
     637             :                         // add not allowed
     638           0 :                         return -1;
     639             :                 }
     640             : 
     641          48 :                 if (strcmp (keyString (diff), "edit") == 0 && !cur.edit)
     642             :                 {
     643           0 :                         keyDel (diff);
     644           0 :                         ksDel (metaDiff);
     645             :                         // edit not allowed
     646           0 :                         return -1;
     647             :                 }
     648             : 
     649          48 :                 if (strcmp (keyString (diff), "remove") == 0 && !cur.remove)
     650             :                 {
     651           0 :                         keyDel (diff);
     652           0 :                         ksDel (metaDiff);
     653             :                         // remove not allowed
     654           0 :                         return -1;
     655             :                 }
     656             : 
     657          48 :                 keyDel (diff);
     658             :         }
     659             : 
     660          36 :         size_t size = ksGetSize (metaDiff);
     661             : 
     662          36 :         ksDel (metaDiff);
     663             : 
     664          36 :         return size == 0 ? 1 : -1;
     665             : }
     666             : 
     667             : /**
     668             :  * Calculate a diff for the metadata of two keys.
     669             :  *
     670             :  * For each meta key that is different between @p oldKey and @p newKey,
     671             :  * a key will be created in the resulting KeySet. The name of this key
     672             :  * is the name of the metakey. The value of the key is determined as follows:
     673             :  * <ul>
     674             :  *   <li>If @p oldKey has a meta key not present in @p newKey: value = "remove"</li>
     675             :  *   <li>If @p newKey has a meta key not present in @p oldKey: value = "add"</li>
     676             :  *   <li>If metakey is present in both @p oldKey and @p newKey, but its value changed: value = "edit"</li>
     677             :  * </ul>
     678             :  * Additionally the old and new values are stored in the metakeys `old` and `new` respectively.
     679             :  *
     680             :  * @param oldKey the old key
     681             :  * @param newKey the new key
     682             :  * @return a KeySet (has to be `ksDel`ed) containing the diff
     683             :  */
     684          36 : KeySet * calculateMetaDiff (Key * oldKey, Key * newKey)
     685             : {
     686          36 :         KeySet * result = ksNew (0, KS_END);
     687             : 
     688          36 :         keyRewindMeta (oldKey);
     689          36 :         keyRewindMeta (newKey);
     690             : 
     691          36 :         const Key * oldMeta = keyNextMeta (oldKey);
     692          36 :         const Key * newMeta = keyNextMeta (newKey);
     693             : 
     694         120 :         while (oldMeta != NULL && newMeta != NULL)
     695             :         {
     696          48 :                 const char * oldName = keyName (oldMeta);
     697          48 :                 const char * newName = keyName (newMeta);
     698             : 
     699          48 :                 int cmp = elektraStrCmp (oldName, newName);
     700          48 :                 if (cmp < 0)
     701             :                 {
     702             :                         // oldKey has to "catch up"
     703           4 :                         ksAppendKey (result,
     704             :                                      keyNew (oldName, KEY_META_NAME, KEY_VALUE, "remove", KEY_META, "old", keyString (oldMeta), KEY_END));
     705           4 :                         oldMeta = keyNextMeta (oldKey);
     706             :                 }
     707          44 :                 else if (cmp > 0)
     708             :                 {
     709             :                         // newKey has to "catch up"
     710          12 :                         ksAppendKey (result,
     711             :                                      keyNew (newName, KEY_META_NAME, KEY_VALUE, "add", KEY_META, "new", keyString (newMeta), KEY_END));
     712          12 :                         newMeta = keyNextMeta (newKey);
     713             :                 }
     714             :                 else
     715             :                 {
     716             :                         // same name
     717          32 :                         ksAppendKey (result, keyNew (oldName, KEY_META_NAME, KEY_VALUE, "edit", KEY_META, "old", keyString (oldMeta),
     718             :                                                      KEY_META, "new", keyString (newMeta), KEY_END));
     719          32 :                         oldMeta = keyNextMeta (oldKey);
     720          32 :                         newMeta = keyNextMeta (newKey);
     721             :                 }
     722             :         }
     723             : 
     724             :         // remaining metadata in oldKey was removed
     725          36 :         while ((oldMeta = keyNextMeta (oldKey)) != NULL)
     726             :         {
     727           0 :                 ksAppendKey (result,
     728             :                              keyNew (keyName (oldMeta), KEY_META_NAME, KEY_VALUE, "remove", KEY_META, "old", keyString (oldMeta), KEY_END));
     729             :         }
     730             : 
     731             :         // remaining metadata in newKey was added
     732          36 :         while ((newMeta = keyNextMeta (newKey)) != NULL)
     733             :         {
     734           0 :                 ksAppendKey (result,
     735             :                              keyNew (keyName (newMeta), KEY_META_NAME, KEY_VALUE, "add", KEY_META, "new", keyString (newMeta), KEY_END));
     736             :         }
     737             : 
     738          36 :         return result;
     739             : }
     740             : 
     741         108 : Plugin * ELEKTRA_PLUGIN_EXPORT
     742             : {
     743             :         // clang-format off
     744         108 :         return elektraPluginExport ("specload",
     745             :                                     ELEKTRA_PLUGIN_OPEN,        &elektraSpecloadOpen,
     746             :                                     ELEKTRA_PLUGIN_CLOSE,       &elektraSpecloadClose,
     747             :                                     ELEKTRA_PLUGIN_GET, &elektraSpecloadGet,
     748             :                                     ELEKTRA_PLUGIN_SET, &elektraSpecloadSet,
     749             :                                     ELEKTRA_PLUGIN_END);
     750             :         // clang-format on
     751             : }

Generated by: LCOV version 1.13