LCOV - code coverage report
Current view: top level - src/libs/pluginprocess - pluginprocess.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 140 207 67.6 %
Date: 2019-09-12 12:28:41 Functions: 12 13 92.3 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief Source for the pluginprocess library
       5             :  *
       6             :  * Executes plugins in a separate process via fork and uses a simple
       7             :  * communication protocol based on the dump plugin via named pipes.
       8             :  *
       9             :  * The communication protocol works as follows, where Child and Parent stand
      10             :  * for the child and the parent process:
      11             :  * 1)  Four pipes are created to handle the communication in a reliable way
      12             :        - parentCommandPipe is used for a keyset containing metainformation about
      13             :        the communication protocol, going from Parent to Child
      14             :        - parentPayloadPipe is used for transferring the actual payload that gets passed
      15             :        to a plugin, going from Parent to Child
      16             :        - childCommandPipe is used for a keyset containing metainformation about
      17             :        the communication protocol, going from Child to Parent
      18             :        - childPayloadPipe is used for transferring the actual payload that gets passed
      19             :        to a plugin, going from Child to Parent
      20             :  * 2)  The parent constructs a keyset called commandKeySet, containing:
      21             :  *     - /pluginprocess/parent
      22             :  *       a copy of the parent key passed to the plugin interface
      23             :  *     - /pluginprocess/parent/name
      24             :  *       the name of the parent key passed to the plugin interface
      25             :  *     - /pluginprocess/command
      26             :  *       the command that should be executed by the child process
      27             :  *     - /pluginprocess/payload/size
      28             :  *       the size of the payload keyset, -1 if there is no payload
      29             :  *     - /pluginprocess/version
      30             :  *       a number indicating the version of this communication
      31             :  *       protocol
      32             :  * 3)  The parent sends over the commandKeySet over the parentCommandPipe
      33             :  * 4)  For operations requiring a keyset, the parent sends the
      34             :  *     keyset (originalKeySet) that is passed to this plugin over the parentPayloadPipe
      35             :  * 5)  Child receives the commandKeySet
      36             :  * 6)  Child receives the keyset if one exists
      37             :  * 7)  Child executes the plugin on the keyset with the originalKey
      38             :  * 8)  Child adds the result value of the plugin operation to the commandKeySet
      39             :        into the key /pluginprocess/result
      40             :  * 9)  Child sends the commandKeySet over the childCommandPipe
      41             :  * 10) For operations requiring a keyset, Child sends the keyset
      42             :  *    over the childPayloadPipe
      43             :  * 11) Parent receives the commandKeySet and interpretes /pluginprocess/result
      44             :  * 12) For operations requiring a keyset, Parent receives the keyset
      45             :  *     and copies it back to originalKeySet set
      46             :  * 13) Parent returns the result value from the child process
      47             :  *
      48             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
      49             :  */
      50             : 
      51             : #include "kdbpluginprocess.h"
      52             : #include <kdberrors.h>
      53             : #include <kdbinvoke.h>
      54             : #include <kdblogger.h>
      55             : #include <kdbprivate.h> // To access the plugin function pointers
      56             : 
      57             : #include <errno.h>
      58             : #include <limits.h>
      59             : #include <signal.h>
      60             : #include <stdio.h>
      61             : #include <stdlib.h>
      62             : #include <sys/stat.h>
      63             : #include <sys/types.h>
      64             : #include <unistd.h>
      65             : 
      66             : struct _ElektraPluginProcess
      67             : {
      68             :         int parentCommandPipe[2];
      69             :         int parentPayloadPipe[2];
      70             :         int childCommandPipe[2];
      71             :         int childPayloadPipe[2];
      72             : 
      73             :         Key * parentCommandPipeKey;
      74             :         Key * parentPayloadPipeKey;
      75             :         Key * childCommandPipeKey;
      76             :         Key * childPayloadPipeKey;
      77             : 
      78             :         int pid;
      79             :         int counter;
      80             :         ElektraInvokeHandle * dump;
      81             :         void * pluginData;
      82             : };
      83             : 
      84         199 : static void cleanupPluginData (ElektraPluginProcess * pp, Key * errorKey, int cleanAllPipes)
      85             : {
      86         199 :         if (pp->dump) elektraInvokeClose (pp->dump, errorKey);
      87             : 
      88         199 :         if (pp->parentCommandPipeKey) keyDel (pp->parentCommandPipeKey);
      89         199 :         if (pp->parentPayloadPipeKey) keyDel (pp->parentPayloadPipeKey);
      90         199 :         if (pp->childCommandPipeKey) keyDel (pp->childCommandPipeKey);
      91         199 :         if (pp->childPayloadPipeKey) keyDel (pp->childPayloadPipeKey);
      92             : 
      93             :         // twisted way to clean either both ends if something failed upon or before forking
      94             :         // and the used ends otherwise
      95         398 :         for (int pipeIdx = !elektraPluginProcessIsParent (pp); pipeIdx <= cleanAllPipes; ++pipeIdx)
      96             :         {
      97         199 :                 if (pp->parentCommandPipe[!pipeIdx]) close (pp->parentCommandPipe[!pipeIdx]);
      98         199 :                 if (pp->parentPayloadPipe[!pipeIdx]) close (pp->parentPayloadPipe[!pipeIdx]);
      99         199 :                 if (pp->childCommandPipe[pipeIdx]) close (pp->childCommandPipe[pipeIdx]);
     100         199 :                 if (pp->childPayloadPipe[pipeIdx]) close (pp->childPayloadPipe[pipeIdx]);
     101             :         }
     102             : 
     103         199 :         elektraFree (pp);
     104         199 : }
     105             : 
     106        1774 : static char * longToStr (long i)
     107             : {
     108             :         long size;
     109             :         char * str;
     110        1774 :         size = snprintf (NULL, 0, "%ld", i);
     111        1774 :         str = elektraMalloc (size + 1);
     112        1774 :         size = snprintf (str, size + 1, "%ld", i);
     113        1774 :         return str;
     114             : }
     115             : 
     116             : /** Start the child process' command loop
     117             :  *
     118             :  * This will make the child process wait for plugin commands
     119             :  * and execute them, returning the result to the parent. This
     120             :  * is typically called in a plugin's open function.
     121             :  *
     122             :  * @param handle the plugin's handle
     123             :  * @param pp the data structure containing the plugin's process information
     124             :  * @see elektraPluginProcessInit how to use this function in a plugin
     125             :  * @ingroup processplugin
     126             :  **/
     127           0 : void elektraPluginProcessStart (Plugin * handle, ElektraPluginProcess * pp)
     128             : {
     129           0 :         int counter = 0;
     130             : 
     131             :         do
     132             :         {
     133           0 :                 KeySet * commandKeySet = ksNew (6, KS_END);
     134           0 :                 KeySet * keySet = NULL;
     135             :                 ELEKTRA_LOG_DEBUG ("Child: Wait for commands on pipe %s", keyString (pp->parentCommandPipeKey));
     136           0 :                 elektraInvoke2Args (pp->dump, "get", commandKeySet, pp->parentCommandPipeKey);
     137             : 
     138           0 :                 if (ksGetSize (commandKeySet) == 0)
     139             :                 {
     140             :                         ELEKTRA_LOG_DEBUG ("Child: Failed to read from parentCommandPipe, exiting");
     141           0 :                         ksDel (commandKeySet);
     142           0 :                         break;
     143             :                 }
     144             : 
     145           0 :                 Key * payloadSizeKey = ksLookupByName (commandKeySet, "/pluginprocess/payload/size", KDB_O_NONE);
     146             :                 char * endPtr;
     147             :                 // We'll always write some int value into it, so this should be fine
     148           0 :                 int prevErrno = errno;
     149           0 :                 errno = 0;
     150           0 :                 long payloadSize = strtol (keyString (payloadSizeKey), &endPtr, 10);
     151             :                 // in case the payload size fails to be transferred, that it shouldn't, we can only assume no payload
     152           0 :                 if (*endPtr == '\0' && errno != ERANGE && payloadSize >= 0)
     153             :                 {
     154           0 :                         keySet = ksNew (payloadSize, KS_END);
     155           0 :                         elektraInvoke2Args (pp->dump, "get", keySet, pp->parentPayloadPipeKey);
     156             :                         ELEKTRA_LOG_DEBUG ("Child: We received a KeySet with %zd keys in it", ksGetSize (keySet));
     157             :                 }
     158           0 :                 errno = prevErrno;
     159             : 
     160           0 :                 Key * commandKey = ksLookupByName (commandKeySet, "/pluginprocess/command", KDB_O_NONE);
     161           0 :                 Key * parentNameKey = ksLookupByName (commandKeySet, "/pluginprocess/parent/name", KDB_O_NONE);
     162           0 :                 Key * parentKey = ksLookupByName (commandKeySet, "/pluginprocess/parent", KDB_O_POP);
     163           0 :                 Key * key = keyDup (parentKey);
     164           0 :                 keySetName (key, keyString (parentNameKey));
     165           0 :                 int result = ELEKTRA_PLUGIN_STATUS_ERROR;
     166             : 
     167             :                 // We'll always write some int value into it, so this should be fine
     168           0 :                 prevErrno = errno;
     169           0 :                 errno = 0;
     170           0 :                 long command = strtol (keyString (commandKey), &endPtr, 10);
     171           0 :                 if (*endPtr == '\0' && errno != ERANGE)
     172             :                 {
     173             :                         ELEKTRA_LOG ("Child: We want to execute the command with the value %ld now", command);
     174             :                         // Its hard to figure out the enum size in a portable way but for this comparison it should be ok
     175           0 :                         switch (command)
     176             :                         {
     177             :                         case ELEKTRA_PLUGINPROCESS_OPEN:
     178           0 :                                 counter++;
     179           0 :                                 result = handle->kdbOpen (handle, key);
     180           0 :                                 break;
     181             :                         case ELEKTRA_PLUGINPROCESS_CLOSE:
     182           0 :                                 counter--;
     183           0 :                                 result = handle->kdbClose (handle, key);
     184           0 :                                 break;
     185             :                         case ELEKTRA_PLUGINPROCESS_GET:
     186           0 :                                 result = handle->kdbGet (handle, keySet, key);
     187           0 :                                 break;
     188             :                         case ELEKTRA_PLUGINPROCESS_SET:
     189           0 :                                 result = handle->kdbSet (handle, keySet, key);
     190           0 :                                 break;
     191             :                         case ELEKTRA_PLUGINPROCESS_ERROR:
     192           0 :                                 result = handle->kdbError (handle, keySet, key);
     193           0 :                                 break;
     194             :                         default:
     195             :                                 result = ELEKTRA_PLUGIN_STATUS_ERROR;
     196             :                         }
     197             :                         ELEKTRA_LOG_DEBUG ("Child: Command executed with return value %d", result);
     198             :                 }
     199             :                 else
     200             :                 {
     201             :                         ELEKTRA_LOG_DEBUG ("Child: Unrecognized command %s", keyString (commandKey));
     202           0 :                         ELEKTRA_SET_PLUGIN_MISBEHAVIOR_ERRORF (key, "Received invalid command code or no KeySet from child process: %s",
     203             :                                                                keyString (commandKey));
     204             :                 }
     205           0 :                 errno = prevErrno;
     206           0 :                 char * resultStr = longToStr (result);
     207           0 :                 ksAppendKey (commandKeySet, keyNew ("/pluginprocess/result", KEY_VALUE, resultStr, KEY_END));
     208           0 :                 elektraFree (resultStr);
     209           0 :                 keySetName (key, "/pluginprocess/parent");
     210           0 :                 ksAppendKey (commandKeySet, key);
     211           0 :                 keyDel (parentKey);
     212             : 
     213             :                 ELEKTRA_LOG_DEBUG ("Child: Writing the results back to the parent");
     214           0 :                 elektraInvoke2Args (pp->dump, "set", commandKeySet, pp->childCommandPipeKey);
     215           0 :                 if (keySet != NULL)
     216             :                 {
     217           0 :                         char * resultPayloadSize = longToStr (ksGetSize (keySet));
     218           0 :                         keySetString (payloadSizeKey, resultPayloadSize);
     219           0 :                         elektraFree (resultPayloadSize);
     220           0 :                         elektraInvoke2Args (pp->dump, "set", keySet, pp->childPayloadPipeKey);
     221           0 :                         ksDel (keySet);
     222             :                 }
     223           0 :                 ksDel (commandKeySet);
     224             :                 ELEKTRA_LOG ("Child: Command handled, startup counter is at %d", counter);
     225           0 :         } while (counter);
     226             : 
     227             :         // Final Cleanup
     228             :         ELEKTRA_LOG_DEBUG ("Child: All done, exiting the child process now");
     229           0 :         cleanupPluginData (pp, 0, 1);
     230             :         // All done, exit the child process so it won't do any actual effects in elektra
     231           0 :         _Exit (EXIT_SUCCESS);
     232             : }
     233             : 
     234             : /** Call a plugin's function in a child process
     235             :  *
     236             :  * This will wrap all the required information to execute the given
     237             :  * command in a keyset and send it over to the child process. Then
     238             :  * it waits for the child process's answer and copies the result
     239             :  * back into the original plugin keyset and plugin key.
     240             :  *
     241             :  * Typically called like
     242             :  * @code
     243             : int elektraPluginSet (Plugin * handle, KeySet * returned, Key * parentKey)
     244             : {
     245             :         ElektraPluginProcess * pp = elektraPluginGetData (handle);
     246             :         if (elektraPluginProcessIsParent (pp)) return elektraPluginProcessSend (pp, ELEKTRA_PLUGINPROCESS_SET, returned, parentKey);
     247             : 
     248             :         // actual plugin functionality to be executed in a child process
     249             :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     250             : }
     251             :  * @endcode
     252             :  *
     253             :  * @param pp the data structure containing the plugin's process information
     254             :  * @param command the plugin command that should be executed, e.g. ELEKTRA_PLUGINPROCESS_GET
     255             :  * @param originalKeySet the original key set that the parent process receives
     256             :  * @param key the original key the parent process receives
     257             :  * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the child process communication failed
     258             :  * @retval the called plugin's return value otherwise
     259             :  * @see elektraPluginProcessIsParent for checking if we are in the parent or child process
     260             :  * @ingroup processplugin
     261             :  **/
     262         495 : int elektraPluginProcessSend (const ElektraPluginProcess * pp, pluginprocess_t command, KeySet * originalKeySet, Key * key)
     263             : {
     264             :         // Ensure we have a keyset when trying to call GET SET and ERROR
     265         495 :         if ((command == ELEKTRA_PLUGINPROCESS_GET || command == ELEKTRA_PLUGINPROCESS_SET || command == ELEKTRA_PLUGINPROCESS_ERROR) &&
     266             :             originalKeySet == NULL)
     267             :         {
     268           6 :                 ELEKTRA_SET_INTERFACE_ERROR (
     269             :                         key, "Variable originalKeySet has to exist when calling GET SET and ERROR via pluginprocess but it is NULL");
     270           6 :                 return ELEKTRA_PLUGIN_STATUS_ERROR;
     271             :         }
     272             : 
     273             :         // Construct the command set that controls the pluginprocess communication
     274         489 :         KeySet * commandKeySet = ksNew (6, KS_END);
     275         489 :         ksAppendKey (commandKeySet, keyNew ("/pluginprocess/parent/name", KEY_VALUE, keyName (key), KEY_END));
     276         489 :         Key * parentKey = keyDup (key);
     277         489 :         keySetName (parentKey, "/pluginprocess/parent");
     278         489 :         ksAppendKey (commandKeySet, parentKey);
     279         489 :         char * commandStr = longToStr (command);
     280         489 :         ksAppendKey (commandKeySet, keyNew ("/pluginprocess/command", KEY_VALUE, commandStr, KEY_END));
     281         489 :         elektraFree (commandStr);
     282         489 :         ksAppendKey (commandKeySet, keyNew ("/pluginprocess/version", KEY_VALUE, "1", KEY_END));
     283             : 
     284             :         // Some plugin functions don't use keysets, in that case don't send any actual payload, signal via flag
     285         489 :         KeySet * keySet = originalKeySet != NULL ? ksDup (originalKeySet) : NULL;
     286         489 :         char * payloadSizeStr = longToStr (ksGetSize (originalKeySet));
     287         489 :         ksAppendKey (commandKeySet,
     288             :                      keyNew ("/pluginprocess/payload/size", KEY_VALUE, originalKeySet == NULL ? "-1" : payloadSizeStr, KEY_END));
     289         489 :         elektraFree (payloadSizeStr);
     290             : 
     291             :         // Serialize, currently statically use dump as our default format, this already writes everything out to the pipe
     292             :         ELEKTRA_LOG ("Parent: Sending data to issue command %u it through pipe %s", command, keyString (pp->parentCommandPipeKey));
     293         489 :         elektraInvoke2Args (pp->dump, "set", commandKeySet, pp->parentCommandPipeKey);
     294         489 :         if (keySet != NULL)
     295             :         {
     296             :                 ELEKTRA_LOG ("Parent: Sending the payload keyset with %zd keys through the pipe %s", ksGetSize (keySet),
     297             :                              keyString (pp->parentPayloadPipeKey));
     298          87 :                 elektraInvoke2Args (pp->dump, "set", keySet, pp->parentPayloadPipeKey);
     299             :         }
     300             : 
     301             :         // Deserialize
     302             :         ELEKTRA_LOG_DEBUG ("Parent: Waiting for the result now on pipe %s", keyString (pp->childCommandPipeKey));
     303         489 :         elektraInvoke2Args (pp->dump, "get", commandKeySet, pp->childCommandPipeKey);
     304             : 
     305         489 :         if (keySet != NULL)
     306             :         {
     307             :                 // clear the keyset before to avoid memleaks caused by dump
     308             :                 char * endPtr;
     309          87 :                 int prevErrno = errno;
     310          87 :                 errno = 0;
     311          87 :                 long payloadSize =
     312          87 :                         strtol (keyString (ksLookupByName (commandKeySet, "/pluginprocess/payload/size", KDB_O_NONE)), &endPtr, 10);
     313             :                 // in case the payload size fails to be transferred, that it shouldn't, we simply assume the previous size
     314          87 :                 if (*endPtr != '\0' || errno == ERANGE || payloadSize < 0) payloadSize = ksGetSize (keySet);
     315          87 :                 errno = prevErrno;
     316          87 :                 ksDel (keySet);
     317          87 :                 keySet = ksNew (payloadSize, KS_END);
     318          87 :                 elektraInvoke2Args (pp->dump, "get", keySet, pp->childPayloadPipeKey);
     319             :                 ELEKTRA_LOG ("Parent: We received %zd keys in return", ksGetSize (keySet));
     320             :         }
     321             : 
     322             :         // Bring everything back in order by removing our process-related keys
     323         489 :         Key * parentDeserializedKey = ksLookupByName (commandKeySet, "/pluginprocess/parent", KDB_O_NONE);
     324         489 :         Key * resultKey = ksLookupByName (commandKeySet, "/pluginprocess/result", KDB_O_NONE);
     325             : 
     326             :         // Parse the result value
     327             :         char * endPtr;
     328         489 :         int prevErrno = errno;
     329         489 :         errno = 0;
     330         489 :         long lresult = strtol (keyString (resultKey), &endPtr, 10);
     331         489 :         if (*endPtr != '\0' || errno == ERANGE || lresult > INT_MAX || lresult < INT_MIN)
     332             :         {
     333           4 :                 ELEKTRA_SET_PLUGIN_MISBEHAVIOR_ERRORF (key, "Received invalid return code or no KeySet from child process: %s",
     334             :                                                        keyString (resultKey));
     335           4 :                 lresult = ELEKTRA_PLUGIN_STATUS_ERROR;
     336             :         }
     337             :         else // Copy everything back into the actual keysets
     338             :         {
     339         485 :                 Key * parentKeyInOriginalKeySet = keySet != NULL ? ksLookup (originalKeySet, key, KDB_O_NONE) : NULL;
     340             :                 // maybe there are just 2 keys with the same name, can happen in theory, so compare memory
     341         485 :                 int parentKeyExistsInOriginalKeySet = parentKeyInOriginalKeySet == key;
     342             :                 // if the child added the parent key to the keyset pop it from the keyset
     343             :                 // then reinsert key after we copied the data and delete this serialized copy
     344         485 :                 Key * parentKeyInKeySet = keySet != NULL ? ksLookup (keySet, key, KDB_O_POP) : NULL;
     345         485 :                 int childAddedParentKey = parentKeyInKeySet != NULL;
     346             : 
     347             :                 // Unfortunately we can't use keyCopy here as ksAppendKey locks it so it will fail
     348             :                 // This is the case if the parent key is also contained in the originalKeySet / has been appended
     349             :                 // As an invariant we assume plugins don't change the parent key's name during a plugin call
     350             :                 // This would interfere with keyset memberships
     351         485 :                 keySetString (key, keyString (parentDeserializedKey));
     352             : 
     353             :                 // Clear metadata before, we allow children to modify it
     354         485 :                 keyRewindMeta (key);
     355             :                 const Key * currentMeta;
     356        1334 :                 while ((currentMeta = keyNextMeta (key)) != NULL)
     357             :                 {
     358         364 :                         keySetMeta (key, keyName (currentMeta), 0);
     359             :                 }
     360         485 :                 keyCopyAllMeta (key, parentDeserializedKey);
     361         485 :                 if (childAddedParentKey) keyCopyAllMeta (key, parentKeyInKeySet);
     362             : 
     363         485 :                 if (keySet != NULL)
     364             :                 {
     365             :                         // in case originalKeySet contains key this would make it stuck
     366             :                         // thus remove it here and re-add it afterwards
     367          87 :                         if (parentKeyExistsInOriginalKeySet) ksLookup (originalKeySet, parentKeyInOriginalKeySet, KDB_O_POP);
     368          87 :                         ksCopy (originalKeySet, keySet);
     369          87 :                         if (parentKeyExistsInOriginalKeySet || childAddedParentKey) ksAppendKey (originalKeySet, key);
     370          87 :                         if (childAddedParentKey) keyDel (parentKeyInKeySet);
     371             :                 }
     372             :         }
     373         489 :         errno = prevErrno;
     374             : 
     375             :         // Command finished, cleanup the remaining memory now
     376         489 :         ksDel (commandKeySet);
     377         489 :         if (keySet != NULL) ksDel (keySet);
     378             : 
     379         489 :         return lresult; // Safe, we had a bound check before, and plugins should return values in the int range
     380             : }
     381             : 
     382             : /** Check if a given plugin process is the parent or the child process
     383             :  *
     384             :  * @param pp the data structure containing the plugin's process information
     385             :  * @retval 0 if it's the child process
     386             :  * @retval the child process' pid otherwise
     387             :  * @ingroup processplugin
     388             :  **/
     389        1092 : int elektraPluginProcessIsParent (const ElektraPluginProcess * pp)
     390             : {
     391        1092 :         return pp->pid != 0;
     392             : }
     393             : 
     394        1592 : static char * concat (const char * str1, const char * str2)
     395             : {
     396        1592 :         const int str1Len = strlen (str1);
     397        1592 :         const int str2Len = strlen (str2);
     398        1592 :         char * concat = elektraMalloc (str1Len + str2Len + 1);
     399        1592 :         strcpy (concat, str1);
     400        1592 :         strncpy (concat + str1Len, str2, str2Len + 1);
     401        1592 :         return concat;
     402             : }
     403             : 
     404         796 : static int makePipe (ElektraPluginProcess * pp, Key * errorKey, const char * pipeName, int pipeRef[2])
     405             : {
     406             :         int ret;
     407         796 :         if ((ret = pipe (pipeRef)))
     408             :         {
     409           0 :                 cleanupPluginData (pp, errorKey, 1);
     410           0 :                 ELEKTRA_SET_PLUGIN_MISBEHAVIOR_ERRORF (errorKey, "Failed to initialize %s, pipe () returned %d", pipeName, ret);
     411           0 :                 return 0;
     412             :         }
     413             :         return 1;
     414             : }
     415             : 
     416         796 : static Key * makePipeKey (const char * pipeName, const int pipeFd)
     417             : {
     418             :         // create the key for this pipe for the use with dump
     419             :         Key * key;
     420             : 
     421         796 :         char * pipeFdStr = longToStr (pipeFd);
     422         796 :         char * pipeKeyValue = concat ("/dev/fd/", pipeFdStr);
     423         796 :         elektraFree (pipeFdStr);
     424         796 :         char * pipeKeyName = concat ("/pluginprocess/pipe/", pipeName);
     425         796 :         key = keyNew (pipeKeyName, KEY_VALUE, pipeKeyValue, KEY_END);
     426         796 :         elektraFree (pipeKeyName);
     427         796 :         elektraFree (pipeKeyValue);
     428             : 
     429         796 :         return key;
     430             : }
     431             : 
     432             : /** Initialize a plugin to be executed in its own process
     433             :  *
     434             :  * This will prepare all the required resources and then fork the current
     435             :  * process. After the initialization the child process will typically
     436             :  * call the command loop while the parent starts to send commands to it.
     437             :  * Also the resulting process information has to be stored for the plugin.
     438             :  * In order to allow users to handle custom plugin data this will not
     439             :  * automatically call elektraPluginSetData.
     440             :  *
     441             :  * Typically called in a plugin's open function like (assuming no custom plugin data):
     442             :  * @code
     443             : int elektraPluginOpen (Plugin * handle, Key * errorKey)
     444             : {
     445             :         ElektraPluginProcess * pp = elektraPluginGetData (handle);
     446             :         if (pp == NULL)
     447             :         {
     448             :                 if ((pp = elektraPluginProcessInit (errorKey)) == NULL) return ELEKTRA_PLUGIN_STATUS_ERROR;
     449             :                 elektraPluginSetData (handle, pp);
     450             :                 if (!elektraPluginProcessIsParent (pp)) elektraPluginProcessStart (handle, pp);
     451             :         }
     452             :         if (elektraPluginProcessIsParent (pp)) return elektraPluginProcessOpen (pp, errorKey);
     453             : 
     454             :         // actual plugin functionality to be executed in a child process
     455             :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     456             : }
     457             :  * @endcode
     458             :  *
     459             :  * @param handle the plugin's handle
     460             :  * @param errorKey a key where error messages will be set
     461             :  * @retval NULL if the initialization failed
     462             :  * @retval a pointer to the information
     463             :  * @ingroup processplugin
     464             :  **/
     465         199 : ElektraPluginProcess * elektraPluginProcessInit (Key * errorKey)
     466             : {
     467             :         // First time initialization
     468             :         ElektraPluginProcess * pp;
     469         199 :         pp = elektraMalloc (sizeof (ElektraPluginProcess));
     470         199 :         pp->counter = 0;
     471         199 :         pp->pluginData = NULL;
     472         199 :         pp->parentCommandPipeKey = NULL;
     473         199 :         pp->parentPayloadPipeKey = NULL;
     474         199 :         pp->childCommandPipeKey = NULL;
     475         199 :         pp->childPayloadPipeKey = NULL;
     476             : 
     477         199 :         pp->dump = elektraInvokeOpen ("dump", 0, errorKey);
     478         199 :         if (!pp->dump)
     479             :         {
     480           0 :                 cleanupPluginData (pp, errorKey, 0);
     481           0 :                 ELEKTRA_SET_INSTALLATION_ERROR (errorKey, "Failed to initialize the dump plugin");
     482           0 :                 return NULL;
     483             :         }
     484             : 
     485             :         // As generally recommended, ignore SIGPIPE because we will notice that the
     486             :         // commandKeySet has been transferred incorrectly anyway to detect broken pipes
     487         199 :         signal (SIGPIPE, SIG_IGN);
     488             : 
     489             :         // Prepare the pipes
     490         398 :         if (!makePipe (pp, errorKey, "parentCommandPipe", pp->parentCommandPipe) ||
     491         398 :             !makePipe (pp, errorKey, "parentPayloadPipe", pp->parentPayloadPipe) ||
     492         398 :             !makePipe (pp, errorKey, "childCommandPipe", pp->childCommandPipe) ||
     493         199 :             !makePipe (pp, errorKey, "childPayloadPipe", pp->childPayloadPipe))
     494             :                 return NULL;
     495             : 
     496         199 :         pp->pid = fork ();
     497             : 
     498         199 :         if (pp->pid < 0)
     499             :         {
     500           0 :                 cleanupPluginData (pp, errorKey, 1);
     501           0 :                 ELEKTRA_SET_PLUGIN_MISBEHAVIOR_ERRORF (errorKey, "Failed to fork the plugin process, fork () returned %d", pp->pid);
     502           0 :                 return NULL;
     503             :         }
     504             : 
     505         199 :         int pipeIdx = elektraPluginProcessIsParent (pp);
     506         199 :         close (pp->parentCommandPipe[!pipeIdx]);
     507         199 :         close (pp->parentPayloadPipe[!pipeIdx]);
     508         199 :         close (pp->childCommandPipe[pipeIdx]);
     509         199 :         close (pp->childPayloadPipe[pipeIdx]);
     510             : 
     511             :         ELEKTRA_LOG_DEBUG ("parentCommandPipe[%d] has file descriptor %d", pipeIdx, pp->parentCommandPipe[pipeIdx]);
     512             :         ELEKTRA_LOG_DEBUG ("parentPayloadPipe[%d] has file descriptor %d", pipeIdx, pp->parentPayloadPipe[pipeIdx]);
     513             :         ELEKTRA_LOG_DEBUG ("childCommandPipe[%d] has file descriptor %d", !pipeIdx, pp->childCommandPipe[!pipeIdx]);
     514             :         ELEKTRA_LOG_DEBUG ("childPayloadPipe[%d] has file descriptor %d", !pipeIdx, pp->childPayloadPipe[!pipeIdx]);
     515             : 
     516             :         // Prepare the keys for the pipes to use with dump
     517         199 :         pp->parentCommandPipeKey = makePipeKey ("parentCommandPipe", pp->parentCommandPipe[pipeIdx]);
     518         199 :         pp->parentPayloadPipeKey = makePipeKey ("parentPayloadPipe", pp->parentPayloadPipe[pipeIdx]);
     519         199 :         pp->childCommandPipeKey = makePipeKey ("childCommandPipe", pp->childCommandPipe[!pipeIdx]);
     520         199 :         pp->childPayloadPipeKey = makePipeKey ("childPayloadPipe", pp->childPayloadPipe[!pipeIdx]);
     521             : 
     522             :         ELEKTRA_LOG_DEBUG ("parentCommandPipeKey is %s on %d", keyString (pp->parentCommandPipeKey), pp->pid);
     523             :         ELEKTRA_LOG_DEBUG ("parentPayloadPipeKey is %s on %d", keyString (pp->parentPayloadPipeKey), pp->pid);
     524             :         ELEKTRA_LOG_DEBUG ("childCommandPipeKey is %s on %d", keyString (pp->childCommandPipeKey), pp->pid);
     525             :         ELEKTRA_LOG_DEBUG ("childPayloadPipeKey is %s on %d", keyString (pp->childPayloadPipeKey), pp->pid);
     526             : 
     527             :         ELEKTRA_LOG_DEBUG ("The pluginprocess is set with the pid %d", pp->pid);
     528         199 :         return pp;
     529             : }
     530             : 
     531             : /** Call a plugin's open function in a child process
     532             :  *
     533             :  * This will increase the internal counter how often open/close has been called,
     534             :  * so the opened pipes and forks will not be closed too early.
     535             :  *
     536             :  * @param pp the data structure containing the plugin's process information
     537             :  * @param errorKey a key where error messages will be set
     538             :  * @retval the return value of the plugin's open function
     539             :  * @see elektraPluginProcessInit for an example how and where this function is typically used
     540             :  * @ingroup processplugin
     541             :  **/
     542         201 : int elektraPluginProcessOpen (ElektraPluginProcess * pp, Key * errorKey)
     543             : {
     544         201 :         pp->counter = pp->counter + 1;
     545         201 :         return elektraPluginProcessSend (pp, ELEKTRA_PLUGINPROCESS_OPEN, NULL, errorKey);
     546             : }
     547             : 
     548             : /** Cleanup a plugin process
     549             :  *
     550             :  * This will decrease the internal counter how often open/close has been called,
     551             :  * closing opened pipes when the counter reaches 0. This will not delete the
     552             :  * plugin data associated with the handle as it may contain other data out of
     553             :  * the scope of this library, so this has to be done manually like
     554             :  * @code
     555             : int elektraPluginClose (Plugin * handle, Key * errorKey)
     556             : {
     557             :         ElektraPluginProcess * pp = elektraPluginGetData (handle);
     558             :         if (pp && elektraPluginProcessIsParent (pp)) {
     559             :                 ElektraPluginProcessCloseResult result = elektraPluginProcessClose (pp, errorKey);
     560             :                 if (result.cleanedUp) elektraPluginSetData (handle, NULL);
     561             :                 return result.result;
     562             :         }
     563             : 
     564             :         // actual plugin functionality to be executed in a child process
     565             :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     566             : }
     567             :  * @endcode
     568             :  *
     569             :  * Note that pp might be null here if the initialization failed!
     570             :  *
     571             :  * @param pp the data structure containing the plugin's process information
     572             :  * @retval 1 if the data structure got cleaned up
     573             :  * @retval 0 if the data structure is still used
     574             :  * @ingroup processplugin
     575             :  **/
     576         203 : ElektraPluginProcessCloseResult elektraPluginProcessClose (ElektraPluginProcess * pp, Key * errorKey)
     577             : {
     578         203 :         int result = ELEKTRA_PLUGIN_STATUS_SUCCESS;
     579         203 :         if (pp->counter > 0)
     580             :         {
     581         201 :                 pp->counter = pp->counter - 1;
     582         201 :                 result = elektraPluginProcessSend (pp, ELEKTRA_PLUGINPROCESS_CLOSE, NULL, errorKey);
     583             :         }
     584         203 :         int done = pp->counter <= 0;
     585         203 :         if (done) cleanupPluginData (pp, errorKey, 0);
     586         203 :         ElektraPluginProcessCloseResult closeResult = { result, done };
     587         203 :         return closeResult;
     588             : }
     589             : 
     590             : /** Store a pointer to any plugin related data that is being executed inside an own process.
     591             :  *
     592             :  * This is required in case additional arbitrary plugin data should be stored. Pluginprocess
     593             :  * has to be stored using elektraPluginSetData. Plugin data for the child process
     594             :  * has to be stored using this function like
     595             :  * @code
     596             : int elektraPluginOpen (Plugin * handle, Key * errorKey)
     597             : {
     598             :         ElektraPluginProcess * pp = elektraPluginGetData (handle);
     599             :         if (pp == NULL)
     600             :         {
     601             :                 if ((pp = elektraPluginProcessInit (errorKey)) == NULL) return ELEKTRA_PLUGIN_STATUS_ERROR;
     602             :                 ArbitraryPluginData * data = // initialize your plugin data
     603             :                 elektraPluginProcessSetData (pp, data);
     604             :                 elektraPluginSetData (handle, pp);
     605             :                 if (!elektraPluginProcessIsParent (pp)) elektraPluginProcessStart (handle, pp);
     606             :         }
     607             :         if (elektraPluginProcessIsParent (pp)) return elektraPluginProcessOpen (pp, errorKey);
     608             : 
     609             :         // actual plugin functionality to be executed in a child process
     610             :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     611             : }
     612             :  * @endcode
     613             :  *
     614             :  * Furthermore ensure to cleanup the data after the plugin is done like
     615             :  * @code
     616             : int elektraPluginClose (Plugin * handle, Key * errorKey)
     617             : {
     618             :         ElektraPluginProcess * pp = elektraPluginGetData (handle);
     619             :         if (elektraPluginProcessIsParent (pp)) {
     620             :                 ArbitraryPluginData * data = elektraPluginProcessGetData (pp);
     621             :                 ElektraPluginProcessCloseResult result = elektraPluginProcessClose (pp, errorKey);
     622             :                 if (result.cleanedUp)
     623             :                 {
     624             :                         elektraPluginSetData (handle, NULL);
     625             :                         // cleanup data here, this was the last call to the plugin
     626             :                 }
     627             :                 return result.result;
     628             :         }
     629             : 
     630             :         // actual plugin functionality to be executed in a child process
     631             :         return ELEKTRA_PLUGIN_STATUS_SUCCESS;
     632             : }
     633             :  * @endcode
     634             :  *
     635             :  * This way you can use elektraPluginProcessGetData (handle) in your child process
     636             :  * to get the data you want your plugin to work with.
     637             :  *
     638             :  * @param pp the data structure containing the plugin's process information
     639             :  * @param data the pointer to the data
     640             :  */
     641          73 : void elektraPluginProcessSetData (ElektraPluginProcess * pp, void * data)
     642             : {
     643          73 :         if (pp) pp->pluginData = data;
     644          73 : }
     645             : 
     646             : /** Get a pointer to any plugin related data stored before.
     647             :  *
     648             :  * If elektraPluginProcessSetData was not called earlier, NULL will be returned.
     649             :  *
     650             :  * @param pp the data structure containing the plugin's process information
     651             :  * @retval a pointer to the data
     652             :  */
     653         119 : void * elektraPluginProcessGetData (const ElektraPluginProcess * pp)
     654             : {
     655         119 :         if (pp) return pp->pluginData;
     656             :         return NULL;
     657             : }

Generated by: LCOV version 1.13