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