/** * Checks whether specload mode was invoked and if so, sends the specification over stdout * in the format expected by specload. * * You MUST not output anything to stdout before invoking this function. Ideally invoking this * is the first thing you do in your main()-function. * * This function will ONLY RETURN, if specload mode was NOT invoked. Otherwise it will call `exit()`. * * @param argc pass the value of argc from main * @param argv pass the value of argv from main */ void specloadCheck (int argc, const char ** argv) { if (argc != 2 || strcmp (argv[1], "--elektra-spec") != 0) { return; } KeySet * spec = ksNew (6, keyNew("", KEY_META, "mountpoint", "tests_gen_elektra_context.ini", KEY_END), keyNew ("/mydouble", KEY_VALUE, "0.0", KEY_META, "default", "0.0", KEY_META, "type", "double", KEY_END), keyNew ("/myfloatarray/#", KEY_VALUE, "0.0", KEY_META, "default", "0.0", KEY_META, "type", "float", KEY_END), keyNew ("/myint", KEY_VALUE, "0", KEY_META, "default", "0", KEY_META, "type", "long", KEY_END), keyNew ("/mystring", KEY_META, "default", "", KEY_META, "type", "string", KEY_END), keyNew ("/print", KEY_VALUE, "0", KEY_META, "default", "0", KEY_META, "type", "boolean", KEY_END), KS_END); ; Key * parentKey = keyNew ("spec/tests/script/gen/elektra/simple", KEY_END); KeySet * specloadConf = ksNew (1, keyNew ("system/sendspec", KEY_END), KS_END); ElektraInvokeHandle * specload = elektraInvokeOpen ("specload", specloadConf, parentKey); int result = elektraInvoke2Args (specload, "sendspec", spec, parentKey); elektraInvokeClose (specload, parentKey); keyDel (parentKey); ksDel (specloadConf); ksDel (spec); exit (result == ELEKTRA_PLUGIN_STATUS_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE); }
/** Call a plugin's function in a child process * * This will wrap all the required information to execute the given * command in a keyset and send it over to the child process. Then * it waits for the child process's answer and copies the result * back into the original plugin keyset and plugin key. * * Typically called like * @code int elektraPluginSet (Plugin * handle, KeySet * returned, Key * parentKey) { ElektraPluginProcess * pp = elektraPluginGetData (handle); if (elektraPluginProcessIsParent (pp)) return elektraPluginProcessSend (pp, ELEKTRA_PLUGINPROCESS_SET, returned, parentKey); // actual plugin functionality to be executed in a child process return ELEKTRA_PLUGIN_STATUS_SUCCESS; } * @endcode * * @param pp the data structure containing the plugin's process information * @param command the plugin command that should be executed, e.g. ELEKTRA_PLUGINPROCESS_GET * @param originalKeySet the original key set that the parent process receives * @param key the original key the parent process receives * @retval ELEKTRA_PLUGIN_STATUS_ERROR if the child process communication failed * @retval the called plugin's return value otherwise * @see elektraPluginProcessIsParent for checking if we are in the parent or child process * @ingroup processplugin **/ int elektraPluginProcessSend (const ElektraPluginProcess * pp, pluginprocess_t command, KeySet * originalKeySet, Key * key) { // Ensure we have a keyset when trying to call GET SET and ERROR if ((command == ELEKTRA_PLUGINPROCESS_GET || command == ELEKTRA_PLUGINPROCESS_SET || command == ELEKTRA_PLUGINPROCESS_ERROR) && originalKeySet == NULL) { ELEKTRA_SET_ERROR (191, key, "originalKeySet has to exist when calling GET SET and ERROR via pluginprocess; but it is NULL"); return ELEKTRA_PLUGIN_STATUS_ERROR; } // Construct the command set that controls the pluginprocess communication KeySet * commandKeySet = ksNew (6, KS_END); ksAppendKey (commandKeySet, keyNew ("/pluginprocess/parent/name", KEY_VALUE, keyName (key), KEY_END)); Key * parentKey = keyDup (key); keySetName (parentKey, "/pluginprocess/parent"); ksAppendKey (commandKeySet, parentKey); char * commandStr = longToStr (command); ksAppendKey (commandKeySet, keyNew ("/pluginprocess/command", KEY_VALUE, commandStr, KEY_END)); elektraFree (commandStr); ksAppendKey (commandKeySet, keyNew ("/pluginprocess/version", KEY_VALUE, "1", KEY_END)); // Some plugin functions don't use keysets, in that case don't send any actual payload, signal via flag KeySet * keySet = originalKeySet != NULL ? ksDup (originalKeySet) : NULL; char * payloadSizeStr = longToStr (ksGetSize (originalKeySet)); ksAppendKey (commandKeySet, keyNew ("/pluginprocess/payload/size", KEY_VALUE, originalKeySet == NULL ? "-1" : payloadSizeStr, KEY_END)); elektraFree (payloadSizeStr); // Serialize, currently statically use dump as our default format, this already writes everything out to the pipe ELEKTRA_LOG ("Parent: Sending data to issue command %u it through pipe %s", command, keyString (pp->parentCommandPipeKey)); elektraInvoke2Args (pp->dump, "set", commandKeySet, pp->parentCommandPipeKey); if (keySet != NULL) { ELEKTRA_LOG ("Parent: Sending the payload keyset with %zd keys through the pipe %s", ksGetSize (keySet), keyString (pp->parentPayloadPipeKey)); elektraInvoke2Args (pp->dump, "set", keySet, pp->parentPayloadPipeKey); } // Deserialize ELEKTRA_LOG_DEBUG ("Parent: Waiting for the result now on pipe %s", keyString (pp->childCommandPipeKey)); elektraInvoke2Args (pp->dump, "get", commandKeySet, pp->childCommandPipeKey); if (keySet != NULL) { // clear the keyset before to avoid memleaks caused by dump char * endPtr; int prevErrno = errno; errno = 0; long payloadSize = strtol (keyString (ksLookupByName (commandKeySet, "/pluginprocess/payload/size", KDB_O_NONE)), &endPtr, 10); // in case the payload size fails to be transferred, that it shouldn't, we simply assume the previous size if (*endPtr != '\0' || errno == ERANGE || payloadSize < 0) payloadSize = ksGetSize (keySet); errno = prevErrno; ksDel (keySet); keySet = ksNew (payloadSize, KS_END); elektraInvoke2Args (pp->dump, "get", keySet, pp->childPayloadPipeKey); ELEKTRA_LOG ("Parent: We received %zd keys in return", ksGetSize (keySet)); } // Bring everything back in order by removing our process-related keys Key * parentDeserializedKey = ksLookupByName (commandKeySet, "/pluginprocess/parent", KDB_O_NONE); Key * resultKey = ksLookupByName (commandKeySet, "/pluginprocess/result", KDB_O_NONE); // Parse the result value char * endPtr; int prevErrno = errno; errno = 0; long lresult = strtol (keyString (resultKey), &endPtr, 10); if (*endPtr != '\0' || errno == ERANGE || lresult > INT_MAX || lresult < INT_MIN) { ELEKTRA_SET_ERRORF (191, key, "Received invalid return code or no KeySet: %s", keyString (resultKey)); lresult = ELEKTRA_PLUGIN_STATUS_ERROR; } else // Copy everything back into the actual keysets { Key * parentKeyInOriginalKeySet = keySet != NULL ? ksLookup (originalKeySet, key, KDB_O_NONE) : NULL; // maybe there are just 2 keys with the same name, can happen in theory, so compare memory int parentKeyExistsInOriginalKeySet = parentKeyInOriginalKeySet == key; // if the child added the parent key to the keyset pop it from the keyset // then reinsert key after we copied the data and delete this serialized copy Key * parentKeyInKeySet = keySet != NULL ? ksLookup (keySet, key, KDB_O_POP) : NULL; int childAddedParentKey = parentKeyInKeySet != NULL; // Unfortunately we can't use keyCopy here as ksAppendKey locks it so it will fail // This is the case if the parent key is also contained in the originalKeySet / has been appended // As an invariant we assume plugins don't change the parent key's name during a plugin call // This would interfere with keyset memberships keySetString (key, keyString (parentDeserializedKey)); // Clear metadata before, we allow children to modify it keyRewindMeta (key); const Key * currentMeta; while ((currentMeta = keyNextMeta (key)) != NULL) { keySetMeta (key, keyName (currentMeta), 0); } keyCopyAllMeta (key, parentDeserializedKey); if (childAddedParentKey) keyCopyAllMeta (key, parentKeyInKeySet); if (keySet != NULL) { // in case originalKeySet contains key this would make it stuck // thus remove it here and re-add it afterwards if (parentKeyExistsInOriginalKeySet) ksLookup (originalKeySet, parentKeyInOriginalKeySet, KDB_O_POP); ksCopy (originalKeySet, keySet); if (parentKeyExistsInOriginalKeySet || childAddedParentKey) ksAppendKey (originalKeySet, key); if (childAddedParentKey) keyDel (parentKeyInKeySet); } } errno = prevErrno; // Command finished, cleanup the remaining memory now ksDel (commandKeySet); if (keySet != NULL) ksDel (keySet); return lresult; // Safe, we had a bound check before, and plugins should return values in the int range }
/** Start the child process' command loop * * This will make the child process wait for plugin commands * and execute them, returning the result to the parent. This * is typically called in a plugin's open function. * * @param handle the plugin's handle * @param pp the data structure containing the plugin's process information * @see elektraPluginProcessInit how to use this function in a plugin * @ingroup processplugin **/ void elektraPluginProcessStart (Plugin * handle, ElektraPluginProcess * pp) { int counter = 0; do { KeySet * commandKeySet = ksNew (6, KS_END); KeySet * keySet = NULL; ELEKTRA_LOG_DEBUG ("Child: Wait for commands on pipe %s", keyString (pp->parentCommandPipeKey)); elektraInvoke2Args (pp->dump, "get", commandKeySet, pp->parentCommandPipeKey); if (ksGetSize (commandKeySet) == 0) { ELEKTRA_LOG_DEBUG ("Child: Failed to read from parentCommandPipe, exiting"); ksDel (commandKeySet); break; } Key * payloadSizeKey = ksLookupByName (commandKeySet, "/pluginprocess/payload/size", KDB_O_NONE); char * endPtr; // We'll always write some int value into it, so this should be fine int prevErrno = errno; errno = 0; long payloadSize = strtol (keyString (payloadSizeKey), &endPtr, 10); // in case the payload size fails to be transferred, that it shouldn't, we can only assume no payload if (*endPtr == '\0' && errno != ERANGE && payloadSize >= 0) { keySet = ksNew (payloadSize, KS_END); elektraInvoke2Args (pp->dump, "get", keySet, pp->parentPayloadPipeKey); ELEKTRA_LOG_DEBUG ("Child: We received a KeySet with %zd keys in it", ksGetSize (keySet)); } errno = prevErrno; Key * commandKey = ksLookupByName (commandKeySet, "/pluginprocess/command", KDB_O_NONE); Key * parentNameKey = ksLookupByName (commandKeySet, "/pluginprocess/parent/name", KDB_O_NONE); Key * parentKey = ksLookupByName (commandKeySet, "/pluginprocess/parent", KDB_O_POP); Key * key = keyDup (parentKey); keySetName (key, keyString (parentNameKey)); int result = ELEKTRA_PLUGIN_STATUS_ERROR; // We'll always write some int value into it, so this should be fine prevErrno = errno; errno = 0; long command = strtol (keyString (commandKey), &endPtr, 10); if (*endPtr == '\0' && errno != ERANGE) { ELEKTRA_LOG ("Child: We want to execute the command with the value %ld now", command); // Its hard to figure out the enum size in a portable way but for this comparison it should be ok switch (command) { case ELEKTRA_PLUGINPROCESS_OPEN: counter++; result = handle->kdbOpen (handle, key); break; case ELEKTRA_PLUGINPROCESS_CLOSE: counter--; result = handle->kdbClose (handle, key); break; case ELEKTRA_PLUGINPROCESS_GET: result = handle->kdbGet (handle, keySet, key); break; case ELEKTRA_PLUGINPROCESS_SET: result = handle->kdbSet (handle, keySet, key); break; case ELEKTRA_PLUGINPROCESS_ERROR: result = handle->kdbError (handle, keySet, key); break; default: result = ELEKTRA_PLUGIN_STATUS_ERROR; } ELEKTRA_LOG_DEBUG ("Child: Command executed with return value %d", result); } else { ELEKTRA_LOG_DEBUG ("Child: Unrecognized command %s", keyString (commandKey)); ELEKTRA_SET_ERRORF (191, key, "Received invalid command code or no KeySet: %s", keyString (commandKey)); } errno = prevErrno; char * resultStr = longToStr (result); ksAppendKey (commandKeySet, keyNew ("/pluginprocess/result", KEY_VALUE, resultStr, KEY_END)); elektraFree (resultStr); keySetName (key, "/pluginprocess/parent"); ksAppendKey (commandKeySet, key); keyDel (parentKey); ELEKTRA_LOG_DEBUG ("Child: Writing the results back to the parent"); elektraInvoke2Args (pp->dump, "set", commandKeySet, pp->childCommandPipeKey); if (keySet != NULL) { char * resultPayloadSize = longToStr (ksGetSize (keySet)); keySetString (payloadSizeKey, resultPayloadSize); elektraFree (resultPayloadSize); elektraInvoke2Args (pp->dump, "set", keySet, pp->childPayloadPipeKey); ksDel (keySet); } ksDel (commandKeySet); ELEKTRA_LOG ("Child: Command handled, startup counter is at %d", counter); } while (counter); // Final Cleanup ELEKTRA_LOG_DEBUG ("Child: All done, exiting the child process now"); cleanupPluginData (pp, 0, 1); // All done, exit the child process so it won't do any actual effects in elektra _Exit (EXIT_SUCCESS); }
* @param spec The specification to send. * @param parentKey The parent key under which the target specload instance was mounted. Value unused. * * @retval #ELEKTRA_PLUGIN_STATUS_SUCCESS on success * @retval #ELEKTRA_PLUGIN_STATUS_ERROR on error */ int elektraSpecloadSendSpec (Plugin * handle ELEKTRA_UNUSED, KeySet * spec, Key * parentKey) { Key * errorKey = keyNew (0, KEY_END); KeySet * quickDumpConf = ksNew (0, KS_END); ElektraInvokeHandle * quickDump = elektraInvokeOpen ("quickdump", quickDumpConf, errorKey); Key * quickDumpParent = keyNew (keyName (parentKey), KEY_VALUE, STDOUT_FILENAME, KEY_END); int result = elektraInvoke2Args (quickDump, "set", spec, quickDumpParent); elektraInvokeClose (quickDump, errorKey); keyDel (errorKey); keyDel (quickDumpParent); ksDel (quickDumpConf); return result == ELEKTRA_PLUGIN_STATUS_SUCCESS ? ELEKTRA_PLUGIN_STATUS_SUCCESS : ELEKTRA_PLUGIN_STATUS_ERROR; } int elektraSpecloadGet (Plugin * handle, KeySet * returned, Key * parentKey) { if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/specload")) { KeySet * contract = ksNew (30, keyNew ("system/elektra/modules/specload", KEY_VALUE, "specload plugin waits for your orders", KEY_END),