/** * @internal * Called whenever the socket becomes readable. * ZeroMq since sends multipart messages atomically (all or nothing) * both message parts are instantly available. * * @param socket ZeroMq socket * @param context context passed to elektraIoAdapterZeroMqAttach() */ static void zeroMqRecvSocketReadable (void * socket, void * context) { ElektraZeroMqRecvPluginData * data = context; char * changeType; char * changedKeyName; zmq_msg_t message; zmq_msg_init (&message); int result = zmq_msg_recv (&message, socket, ZMQ_DONTWAIT); if (result == -1) { ELEKTRA_LOG_WARNING ("receiving change type failed: %s; aborting", zmq_strerror (zmq_errno ())); zmq_msg_close (&message); return; } if (!zmq_msg_more (&message)) { ELEKTRA_LOG_WARNING ("message has only one part; aborting"); zmq_msg_close (&message); return; } int length = zmq_msg_size (&message); changeType = elektraStrNDup (zmq_msg_data (&message), length + 1); changeType[length] = '\0'; ELEKTRA_LOG_DEBUG ("received change type %s", changeType); result = zmq_msg_recv (&message, socket, ZMQ_DONTWAIT); if (result == -1) { ELEKTRA_LOG_WARNING ("receiving key name failed: %s; aborting", zmq_strerror (zmq_errno ())); elektraFree (changeType); zmq_msg_close (&message); return; } length = zmq_msg_size (&message); changedKeyName = elektraStrNDup (zmq_msg_data (&message), length + 1); changedKeyName[length] = '\0'; ELEKTRA_LOG_DEBUG ("received key name %s", changedKeyName); // notify about changes Key * changedKey = keyNew (changedKeyName, KEY_END); data->notificationCallback (changedKey, data->notificationContext); zmq_msg_close (&message); elektraFree (changeType); elektraFree (changedKeyName); }
/** * @internal * Unmount global plugin at run-time. * * Removes a plugin at all placements. * Undos `mountGlobalPlugin()`. * * @param kdb KDB handle * @param plugin Plugin handle * @retval 0 on errors * @retval 1 on success */ static int unmountGlobalPlugin (KDB * kdb, Plugin * plugin) { ELEKTRA_NOT_NULL (kdb); ELEKTRA_NOT_NULL (plugin); char * placementList = getPluginPlacementList (plugin); // Parse plament list (contains placements from README.md seperated by // whitespace) char * placement = strtok (placementList, " "); while (placement != NULL) { // Convert placement name to internal index int placementIndex = placementToPosition (placement); if (placementIndex == -1) { elektraFree (placementList); return 0; } if (kdb->globalPlugins[placementIndex][MAXONCE] == plugin) { // Remove from direct placement as global plugin kdb->globalPlugins[placementIndex][MAXONCE] = NULL; } else { Plugin * pluginAtPlacement = kdb->globalPlugins[placementIndex][MAXONCE]; // Add plugin to list plugin if (strcmp (pluginAtPlacement->name, "list") == 0) { ELEKTRA_LOG_DEBUG ( "required position %s/maxonce taken by list plugin, " "removing plugin", placement); int result = listRemovePlugin (pluginAtPlacement, plugin); if (!result) { ELEKTRA_LOG_WARNING ("could not remove plugin from list plugin at position %s/maxonce", placement); elektraFree (placementList); return 0; } } else { ELEKTRA_LOG_WARNING ( "required position %s/maxonce taken by plugin %s, " "should be either list or plugin!", placement, pluginAtPlacement->name); } } // Process next placement in list placement = strtok (NULL, " "); } elektraFree (placementList); return 1; }
/** * @see ElektraNotificationOpenNotification (kdbnotificationinternal.h) */ void elektraZeroMqRecvOpenNotification (Plugin * handle, KeySet * parameters) { ELEKTRA_NOT_NULL (handle); ElektraZeroMqRecvPluginData * pluginData = elektraPluginGetData (handle); ELEKTRA_NOT_NULL (pluginData); ElektraNotificationCallback callback; Key * callbackKey = ksLookupByName (parameters, "/callback", 0); ELEKTRA_NOT_NULL (callbackKey); callback = *(ElektraNotificationCallback *) keyValue (callbackKey); ElektraNotificationCallbackContext * context; Key * contextKey = ksLookupByName (parameters, "/context", 0); if (contextKey != NULL) { context = *(ElektraNotificationCallbackContext **) keyValue (contextKey); } else { context = NULL; } pluginData->notificationCallback = callback; pluginData->notificationContext = context; // init dbus connections if (pluginData->ioBinding) { elektraZeroMqRecvSetup (pluginData); } else { ELEKTRA_LOG_DEBUG ("no I/O binding present. plugin in noop mode"); } }
/** * @internal * Global mount given plugin at run-time. * * Reads placements from the plugin directly inserts the plugin. * Also supports adding itself to the list plugin at run-time if present * at requested global placement. * * @param kdb KDB handle * @param plugin Plugin handle * @retval 0 on errors * @retval 1 on success */ static int mountGlobalPlugin (KDB * kdb, Plugin * plugin) { ELEKTRA_NOT_NULL (kdb); ELEKTRA_NOT_NULL (plugin); char * placementList = getPluginPlacementList (plugin); // Parse plament list (contains placements from README.md seperated by // whitespace) char * placement = strtok (placementList, " "); while (placement != NULL) { // Convert placement name to internal index int placementIndex = placementToPosition (placement); if (placementIndex == -1) { elektraFree (placementList); return 0; } if (kdb->globalPlugins[placementIndex][MAXONCE] == NULL) { // Insert directly as global plugin kdb->globalPlugins[placementIndex][MAXONCE] = plugin; } else { Plugin * pluginAtPlacement = kdb->globalPlugins[placementIndex][MAXONCE]; // Add plugin to list plugin if (strcmp (pluginAtPlacement->name, "list") == 0) { ELEKTRA_LOG_DEBUG ("required position %s/maxonce taken by list plugin, adding plugin", placement); int result = listAddPlugin (pluginAtPlacement, plugin, placement); if (!result) { ELEKTRA_LOG_WARNING ("could not add plugin to list plugin at position %s/maxonce", placement); elektraFree (placementList); return 0; } } else { // cannot manually add list module here. configuration is broken: // the list module needs to be mounted in every position to keep track // of the current position ELEKTRA_LOG_WARNING ("required position %s/maxonce taken by plugin %s, aborting!", placement, pluginAtPlacement->name); elektraFree (placementList); return 0; } } // Process next placement in list placement = strtok (NULL, " "); } elektraFree (placementList); return 1; }
/** * @brief Wrapper for open(). * * @param parentKey containing the filename * @param flags file access mode * @param mode file mode bits when file is created * * @return file descriptor */ static int openFile (Key * parentKey, int flag, mode_t mode) { int fd; ELEKTRA_LOG_DEBUG ("opening file %s", keyString (parentKey)); if ((fd = open (keyString (parentKey), flag, mode)) == -1) { ELEKTRA_LOG_WARNING ("error opening file %s", keyString (parentKey)); } return fd; }
/** * @brief This function walks a tree calling methods of the given listener. * * @param listener This argument specifies the listener which this function * uses to convert the tree to a key set. * @param root This variable stores the root of the tree this function * visits. */ void walk (Listener & listener, node const & node) { ELEKTRA_LOG_DEBUG ("Parse tree: %s", toString (node).c_str ()); // If the document contains only one a single value we call `exitValue` // for that function. We need to handle that special case to not add // value multiple times for maps (once for `c_l_block_map_implicit_value` // and `c_l_block_seq_entry`) and once for the child of // `c_l_block_map_implicit_value`). if (node.is_root () && !node.children.empty () && ends_with (node.children.back ()->name (), "node")) { listener.exitValue (node.children.back ()->content ()); return; } executeListenerMethods (listener, node); }
/** Initialize a plugin to be executed in its own process * * This will prepare all the required resources and then fork the current * process. After the initialization the child process will typically * call the command loop while the parent starts to send commands to it. * Also the resulting process information has to be stored for the plugin. * In order to allow users to handle custom plugin data this will not * automatically call elektraPluginSetData. * * Typically called in a plugin's open function like (assuming no custom plugin data): * @code int elektraPluginOpen (Plugin * handle, Key * errorKey) { ElektraPluginProcess * pp = elektraPluginGetData (handle); if (pp == NULL) { if ((pp = elektraPluginProcessInit (errorKey)) == NULL) return ELEKTRA_PLUGIN_STATUS_ERROR; elektraPluginSetData (handle, pp); if (!elektraPluginProcessIsParent (pp)) elektraPluginProcessStart (handle, pp); } if (elektraPluginProcessIsParent (pp)) return elektraPluginProcessOpen (pp, errorKey); // actual plugin functionality to be executed in a child process return ELEKTRA_PLUGIN_STATUS_SUCCESS; } * @endcode * * @param handle the plugin's handle * @param errorKey a key where error messages will be set * @retval NULL if the initialization failed * @retval a pointer to the information * @ingroup processplugin **/ ElektraPluginProcess * elektraPluginProcessInit (Key * errorKey) { // First time initialization ElektraPluginProcess * pp; pp = elektraMalloc (sizeof (ElektraPluginProcess)); pp->counter = 0; pp->pluginData = NULL; pp->parentCommandPipeKey = NULL; pp->parentPayloadPipeKey = NULL; pp->childCommandPipeKey = NULL; pp->childPayloadPipeKey = NULL; pp->dump = elektraInvokeOpen ("dump", 0, errorKey); if (!pp->dump) { cleanupPluginData (pp, errorKey, 0); ELEKTRA_SET_ERROR (190, errorKey, "Failed to initialize the dump plugin"); return NULL; } // As generally recommended, ignore SIGPIPE because we will notice that the // commandKeySet has been transferred incorrectly anyway to detect broken pipes signal (SIGPIPE, SIG_IGN); // Prepare the pipes if (!makePipe (pp, errorKey, "parentCommandPipe", pp->parentCommandPipe) || !makePipe (pp, errorKey, "parentPayloadPipe", pp->parentPayloadPipe) || !makePipe (pp, errorKey, "childCommandPipe", pp->childCommandPipe) || !makePipe (pp, errorKey, "childPayloadPipe", pp->childPayloadPipe)) return NULL; pp->pid = fork (); if (pp->pid < 0) { cleanupPluginData (pp, errorKey, 1); ELEKTRA_SET_ERRORF (190, errorKey, "Failed to fork the plugin process, fork () returned %d", pp->pid); return NULL; } int pipeIdx = elektraPluginProcessIsParent (pp); close (pp->parentCommandPipe[!pipeIdx]); close (pp->parentPayloadPipe[!pipeIdx]); close (pp->childCommandPipe[pipeIdx]); close (pp->childPayloadPipe[pipeIdx]); ELEKTRA_LOG_DEBUG ("parentCommandPipe[%d] has file descriptor %d", pipeIdx, pp->parentCommandPipe[pipeIdx]); ELEKTRA_LOG_DEBUG ("parentPayloadPipe[%d] has file descriptor %d", pipeIdx, pp->parentPayloadPipe[pipeIdx]); ELEKTRA_LOG_DEBUG ("childCommandPipe[%d] has file descriptor %d", !pipeIdx, pp->childCommandPipe[!pipeIdx]); ELEKTRA_LOG_DEBUG ("childPayloadPipe[%d] has file descriptor %d", !pipeIdx, pp->childPayloadPipe[!pipeIdx]); // Prepare the keys for the pipes to use with dump pp->parentCommandPipeKey = makePipeKey ("parentCommandPipe", pp->parentCommandPipe[pipeIdx]); pp->parentPayloadPipeKey = makePipeKey ("parentPayloadPipe", pp->parentPayloadPipe[pipeIdx]); pp->childCommandPipeKey = makePipeKey ("childCommandPipe", pp->childCommandPipe[!pipeIdx]); pp->childPayloadPipeKey = makePipeKey ("childPayloadPipe", pp->childPayloadPipe[!pipeIdx]); ELEKTRA_LOG_DEBUG ("parentCommandPipeKey is %s on %d", keyString (pp->parentCommandPipeKey), pp->pid); ELEKTRA_LOG_DEBUG ("parentPayloadPipeKey is %s on %d", keyString (pp->parentPayloadPipeKey), pp->pid); ELEKTRA_LOG_DEBUG ("childCommandPipeKey is %s on %d", keyString (pp->childCommandPipeKey), pp->pid); ELEKTRA_LOG_DEBUG ("childPayloadPipeKey is %s on %d", keyString (pp->childPayloadPipeKey), pp->pid); ELEKTRA_LOG_DEBUG ("The pluginprocess is set with the pid %d", pp->pid); return pp; }
/** 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); }
/** * Updates all KeyRegistrations according to data from the given KeySet * @internal * * @param plugin internal plugin handle * @param keySet key set retrieved from hooks * e.g. elektraInternalnotificationGet or elektraInternalnotificationSet) * */ void elektraInternalnotificationUpdateRegisteredKeys (Plugin * plugin, KeySet * keySet) { PluginState * pluginState = elektraPluginGetData (plugin); ELEKTRA_ASSERT (pluginState != NULL, "plugin state was not initialized properly"); KeyRegistration * registeredKey = pluginState->head; while (registeredKey != NULL) { int changed = 0; Key * key; if (registeredKey->sameOrBelow) { Key * checkKey = keyNew (registeredKey->name, KEY_END); if (keySetContainsSameOrBelow (checkKey, keySet)) { changed = 1; key = checkKey; } else { keyDel (checkKey); } } else { key = ksLookupByName (keySet, registeredKey->name, 0); if (key != NULL) { // Detect changes for string keys if (!keyIsString (key)) { // always notify for binary keys changed = 1; } else { const char * currentValue = keyString (key); changed = registeredKey->lastValue == NULL || strcmp (currentValue, registeredKey->lastValue) != 0; if (changed) { // Save last value char * buffer = elektraStrDup (currentValue); if (buffer) { if (registeredKey->lastValue != NULL) { // Free previous value elektraFree (registeredKey->lastValue); } registeredKey->lastValue = buffer; } } } } } if (changed) { ELEKTRA_LOG_DEBUG ("found changed registeredKey=%s with string value \"%s\". using context or variable=%p", registeredKey->name, keyString (key), registeredKey->context); // Invoke callback ElektraNotificationChangeCallback callback = *(ElektraNotificationChangeCallback) registeredKey->callback; callback (key, registeredKey->context); if (registeredKey->sameOrBelow) { keyDel (key); } } // proceed with next registered key registeredKey = registeredKey->next; } }
/* See documentation in header file. */ int ini_parse_file (FILE * file, const struct IniConfig * config, void * user) { /* Uses a fair bit of stack (use heap instead if you need to) */ char * line; char section[MAX_SECTION] = ""; char prev_name[MAX_NAME] = ""; char * start; char * end; char * name; char * value; char delim = config->delim; int lineno = 0; int error = 0; line = (char *)malloc (INI_MAX_LINE); ELEKTRA_LOG_DEBUG ("Allocated memory for line"); if (!line) { return -2; } /* Scan through file line by line */ while (fgets (line, INI_MAX_LINE, file) != NULL) { lineno++; ELEKTRA_LOG_DEBUG ("Read line %d with content “%s”", lineno, line); start = line; #if INI_ALLOW_BOM if (lineno == 1 && (unsigned char)start[0] == 0xEF && (unsigned char)start[1] == 0xBB && (unsigned char)start[2] == 0xBF) { start += 3; config->bomHandler (user, 1); } else { config->bomHandler (user, 0); } #endif if (*start == '\n') { if (!config->commentHandler (user, "") && !error) error = lineno; continue; } start = lskip (line); if (*start == '\0') { if (!config->commentHandler (user, "") && !error) error = lineno; continue; } if (isContinuation (line, config) && config->supportMultiline && *prev_name) { start = line + strlen (config->continuationString); if (*start == '"') ++start; end = line + (strlen (line) - 1); while ((*end != '"') && (!isprint (*end)) && (end > start)) { if (*end == '\n') *end = '\0'; --end; } if (*end == '"') *end = '\0'; if (!config->keyHandler (user, section, prev_name, start, 1) && !error) error = lineno; } else if (isSection (line)) { ELEKTRA_LOG_DEBUG ("Line contains a section"); end = line + (strlen (line) - 1); while (end > start) { if (*end == ']') break; --end; } ++start; if (*end == ']') { *end = '\0'; strncpy0 (section, start, sizeof (section)); *prev_name = '\0'; ELEKTRA_LOG_DEBUG ("Found section “%s”", section); size_t numberBackslashes = 0; for (char * endSection = section + strlen (section) - 1; endSection >= section && *endSection == '\\'; endSection--) { numberBackslashes++; } if (numberBackslashes % 2 != 0) { ELEKTRA_LOG_WARNING ("Found uneven number of backlashes at end of section"); error = lineno; break; } if (!config->sectionHandler (user, section) && !error) error = lineno; } else { end = line + (strlen (line) - 1); if (*end == '\n') { strncpy0 (section, start, sizeof (section)); while (fgets (line, INI_MAX_LINE, file)) { end = line + (strlen (line) - 1); while ((end > line) && *end != ']') --end; if (*end == ']') { *end = '\0'; strncpy0 (section + strlen (section), line, sizeof (section) - strlen (section)); *prev_name = '\0'; if (!config->sectionHandler (user, section) && !error) error = lineno; break; } else { strncpy0 (section + strlen (section), line, sizeof (section) - strlen (section)); } } } else { error = lineno; } } } else if (isComment (line)) { start = line; end = line + (strlen (line) - 1); if (*end == '\n') *end = '\0'; if (!config->commentHandler (user, start) && !error) error = lineno; } else { ELEKTRA_LOG_DEBUG ("Line contains a key"); char * ptr = start; unsigned int assign = 0; ELEKTRA_LOG_DEBUG ("Search for delimiter “%c”", delim); while (*ptr) { if (*ptr == delim) { ++assign; } ++ptr; } if (assign == 1) { ELEKTRA_LOG_DEBUG ("Found exactly one delimiter"); name = start; end = strchr (start, delim); if (*name == '"') { ELEKTRA_LOG_DEBUG ("Name starts with double quote character"); ++name; if (*(end - 2) == '"') { *(end - 2) = '\0'; } else if (*(end - 1) == '"') { *(end - 1) = '\0'; } else { ELEKTRA_LOG_DEBUG ("Did not find closing double quote characters in current line"); strncpy0 (prev_name, name, sizeof (prev_name)); while (fgets (line, INI_MAX_LINE, file)) { ELEKTRA_LOG_DEBUG ("Read continuation line with content “%s”", line); end = line + (strlen (line) - 1); while (end > line && *end != '"') --end; if (*end == '"') { ELEKTRA_LOG_DEBUG ("Found closing double quote character"); *(end++) = '\0'; strncpy0 (prev_name + strlen (prev_name), line, sizeof (prev_name) - strlen (prev_name)); break; } else { ELEKTRA_LOG_DEBUG ("Found name continuation"); strncpy (prev_name + strlen (prev_name), line, sizeof (prev_name) - strlen (prev_name)); } ELEKTRA_LOG_DEBUG ("New extended name is “%s”", prev_name); } name = prev_name; ELEKTRA_LOG_DEBUG ("Name of key is “%s”", name); } } if (*end != delim) { ELEKTRA_LOG_DEBUG ("Search for delimiter in “%s”", end); ptr = lskip (end + 1); end = strchr (ptr, delim); if (end && *end == delim) { *end = '\0'; ELEKTRA_LOG_DEBUG ("Found delimiter – New name is “%s”", end); } else { ELEKTRA_LOG_WARNING ("Unable to find delimiter"); error = lineno; break; } } else { *end = '\0'; } if (name != prev_name && end > line) { rstrip (end - 1); } value = lskip (end + 1); end = find_char_or_comment (value, '\0'); if (*end == ';') *end = '\0'; rstrip (value); if (*value == '"') { *(value++) = '\0'; while ((*end != '"') && !isprint (*end) && end > value) --end; if (*end == '"') *end = '\0'; } if (prev_name != name) strncpy0 (prev_name, name, sizeof (prev_name)); if (!config->keyHandler (user, section, name, value, 0) && !error) error = lineno; } else if (assign == 0) { ELEKTRA_LOG_DEBUG ("Found no delimiter"); if (*start == '"') { ELEKTRA_LOG_DEBUG ("Found initial double quote character"); ++start; end = line + (strlen (line) - 1); while (end > start && *end != '"') --end; if (*end == '"' && end != start) { *end = '\0'; if (!config->keyHandler (user, section, start, NULL, 0) && !error) error = lineno; } else { ELEKTRA_LOG_DEBUG ("Did not find closing double quote character"); strncpy0 (prev_name, start, sizeof (prev_name)); while (fgets (line, INI_MAX_LINE, file)) { end = line + (strlen (line) - 1); ELEKTRA_LOG_DEBUG ("Read continuation line with content “%s”", line); while (end > line && *end != '"') --end; if (*end == '"') { ELEKTRA_LOG_DEBUG ("Found closing double quote character"); *end = '\0'; strncpy0 (prev_name + strlen (prev_name), line, sizeof (prev_name) - strlen (prev_name)); break; } else { ELEKTRA_LOG_DEBUG ("Found name continuation"); strncpy (prev_name + strlen (prev_name), line, sizeof (prev_name) - strlen (prev_name)); } ELEKTRA_LOG_DEBUG ("New extended name is “%s”", prev_name); } name = prev_name; ptr = end + 1; end = strchr (ptr, '='); if (!end) end = strchr (ptr, ':'); if (!end) { if (!config->keyHandler (user, section, name, NULL, 0) && !error) error = lineno; } else { *end = '\0'; value = lskip (end + 1); if (*value == '"') end = find_char_or_comment (value, '\0'); if (*end == ';') *end = '\0'; rstrip (value); if (*value == '"' || *(value + 1) == '"') { if (*value == '"') *(value++) = '\0'; else if (*(value + 1) == '"') { *(value + 1) = '\0'; value += 2; } while ((*end != '"') && !isprint (*end) && end > value) --end; if (*end == '"') *end = '\0'; } if (prev_name != name) strncpy0 (prev_name, name, sizeof (prev_name)); if (!config->keyHandler (user, section, name, value, 0) && !error) error = lineno; } } } else { name = rstrip (start); strncpy0 (prev_name, name, sizeof (prev_name)); if (!config->keyHandler (user, section, name, NULL, 0) && !error) error = lineno; } } else { ELEKTRA_LOG_DEBUG ("Found multiple delimiters"); ptr = start + 1; while (*ptr) { if (*ptr == delim) { if (*(ptr + 1) == '"' || *(ptr + 2) == '"' || *(ptr - 1) == '"' || *(ptr - 2) == '"') break; } ++ptr; } if (*ptr) { ELEKTRA_LOG_DEBUG ("Found double quote character"); char tmpDel[4] = { ' ', delim, ' ', '\0' }; end = strstr (ptr, tmpDel); name = NULL; if (end) { // keyname == "=" or " = " where '=' is the delimiter if (*(ptr + 1) == '"') { *(ptr + 1) = '\0'; } else if (*(ptr + 2) == '"') { *(ptr + 2) = '\0'; } if (*(ptr - 1) == '"') *(ptr - 1) = '\0'; else if (*(ptr - 2) == '"') *(ptr - 2) = '\0'; name = ptr; } else if (*ptr == delim) { *ptr = '\0'; rstrip (start); if (*start == '"') ++start; if (*(ptr - 1) == '"') *(ptr - 1) = '\0'; else if (*(ptr - 2) == '"') *(ptr - 2) = '\0'; name = start; } else { if (!end) end = strrstr (start + 1, tmpDel); *end = '\0'; ptr = end + 2; rstrip (start); name = start; } value = ptr + 1; end = find_char_or_comment (value, '\0'); if (*end == ';') *end = '\0'; rstrip (value); if (*value == '"' || *(value + 1) == '"') { if (*value == '"') *(value++) = '\0'; else if (*(value + 1) == '"') { *(value + 1) = '\0'; value += 2; } while ((*end != '"') && !isprint (*end) && end > value) --end; if (*end == '"') *end = '\0'; } } else { ELEKTRA_LOG_DEBUG ("Found no double quote character"); rstrip (start); name = start; end = strchr (start, delim); if (!end) { ELEKTRA_LOG_DEBUG ("Found no delimiter"); value = NULL; } else { ELEKTRA_LOG_DEBUG ("Found delimiter"); if (*end == delim) *end = '\0'; rstrip (end - 1); value = lskip (end + 1); rstrip (value); if (*value == '"') { *(value++) = '\0'; while ((*end != '"') && !isprint (*end) && end > value) --end; if (*end == '"') *end = '\0'; } } } strncpy0 (prev_name, name, sizeof (prev_name)); if (!config->keyHandler (user, section, name, value, 0) && !error) error = lineno; } } #if INI_STOP_ON_FIRST_ERROR if (error) break; #endif } free (line); return error; }
int elektraSimpleiniGet (Plugin * handle, KeySet * returned, Key * parentKey) { /* get all keys */ if (!strcmp (keyName (parentKey), "system/elektra/modules/simpleini")) { KeySet * moduleConfig = ksNew ( 30, keyNew ("system/elektra/modules/simpleini", KEY_VALUE, "simpleini plugin waits for your orders", KEY_END), keyNew ("system/elektra/modules/simpleini/exports", KEY_END), keyNew ("system/elektra/modules/simpleini/exports/get", KEY_FUNC, elektraSimpleiniGet, KEY_END), keyNew ("system/elektra/modules/simpleini/exports/set", KEY_FUNC, elektraSimpleiniSet, KEY_END), #include "readme_simpleini.c" keyNew ("system/elektra/modules/simpleini/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), keyNew ("system/elektra/modules/simpleini/config/needs", KEY_VALUE, "the needed configuration to work in a backend", KEY_END), keyNew ("system/elektra/modules/simpleini/config/needs/chars", KEY_VALUE, "Characters needed", KEY_END), // space in value now works: // TODO: characters present in format should be escaped /* keyNew ("system/elektra/modules/simpleini/config/needs/chars/20", KEY_VALUE, "61", KEY_END), // space -> a keyNew ("system/elektra/modules/simpleini/config/needs/chars/23", KEY_VALUE, "62", KEY_END), // # -> b keyNew ("system/elektra/modules/simpleini/config/needs/chars/25", KEY_VALUE, "63", KEY_END), // % -> c (escape character) keyNew ("system/elektra/modules/simpleini/config/needs/chars/3B", KEY_VALUE, "64", KEY_END), // ; -> d keyNew ("system/elektra/modules/simpleini/config/needs/chars/3D", KEY_VALUE, "65", KEY_END), // = -> e keyNew ("system/elektra/modules/simpleini/config/needs/chars/5C", KEY_VALUE, "66", KEY_END), // \\ -> f */ keyNew ("system/elektra/modules/simpleini/config/needs/chars/0A", KEY_VALUE, "67", KEY_END), // enter (NL) -> g keyNew ("system/elektra/modules/simpleini/config/needs/chars/0D", KEY_VALUE, "68", KEY_END), // CR -> h keyNew ("system/elektra/modules/simpleini/config/needs/escape", KEY_VALUE, "25", KEY_END), KS_END); ksAppend (returned, moduleConfig); ksDel (moduleConfig); return 1; } char * key = 0; char * value = 0; int errnosave = errno; FILE * fp = fopen (keyString (parentKey), "r"); if (!fp) { ELEKTRA_SET_ERROR_GET (parentKey); errno = errnosave; return -1; } char * format = getFormat (handle, "%ms", "%m[^\n]"); ELEKTRA_LOG ("Read from '%s' with format '%s'", keyString (parentKey), format); int n = 0; size_t size = 0; ssize_t ksize = 0; #pragma GCC diagnostic ignored "-Wformat" // icc warning #269: invalid format string conversion while ((n = fscanf (fp, format, &key, &value)) >= 0) { ELEKTRA_LOG_DEBUG ("Read %d parts: '%s' with value '%s'", n, key, value); if (n == 0) { // discard line getline (&key, &size, fp); ELEKTRA_LOG_DEBUG ("Discard '%s'", key); elektraFree (key); key = 0; continue; } Key * read = keyNew (keyName (parentKey), KEY_END); if (keyAddName (read, key) == -1) { ELEKTRA_ADD_WARNING (ELEKTRA_WARNING_INVALID_KEY, parentKey, key); keyDel (read); continue; } if (n == 2) { keySetString (read, value); elektraFree (value); value = 0; } if (ksAppendKey (returned, read) != ksize + 1) { ELEKTRA_SET_ERROR (ELEKTRA_ERROR_NOEOF, parentKey, "duplicated key"); return -1; } ++ksize; elektraFree (key); key = 0; } if (feof (fp) == 0) { elektraFree (format); fclose (fp); ELEKTRA_SET_ERROR (ELEKTRA_ERROR_NOEOF, parentKey, "not at the end of file"); return -1; } elektraFree (format); fclose (fp); return 1; /* success */ }
/** * @brief Opens the session with the Key database. * * @pre errorKey must be a valid key, e.g. created with keyNew() * * The method will bootstrap itself the following way. * The first step is to open the default backend. With it * system/elektra/mountpoints will be loaded and all needed * libraries and mountpoints will be determined. * These libraries for backends will be loaded and with it the * @p KDB data structure will be initialized. * * You must always call this method before retrieving or committing any * keys to the database. In the end of the program, * after using the key database, you must not forget to kdbClose(). * * The pointer to the @p KDB structure returned will be initialized * like described above, and it must be passed along on any kdb*() * method your application calls. * * Get a @p KDB handle for every thread using elektra. Don't share the * handle across threads, and also not the pointer accessing it: * * @snippet kdbopen.c open * * You don't need kdbOpen() if you only want to * manipulate plain in-memory Key or KeySet objects. * * @pre errorKey must be a valid key, e.g. created with keyNew() * * @param errorKey the key which holds errors and warnings which were issued * @see kdbGet(), kdbClose() to end all affairs to the key database. * @retval handle on success * @retval NULL on failure * @ingroup kdb */ KDB * kdbOpen (Key * errorKey) { if (!errorKey) { ELEKTRA_LOG ("no error key passed"); return 0; } ELEKTRA_LOG ("called with %s", keyName (errorKey)); int errnosave = errno; KDB * handle = elektraCalloc (sizeof (struct _KDB)); Key * initialParent = keyDup (errorKey); handle->modules = ksNew (0, KS_END); if (elektraModulesInit (handle->modules, errorKey) == -1) { ksDel (handle->modules); elektraFree (handle); ELEKTRA_SET_ERROR (94, errorKey, "elektraModulesInit returned with -1"); keySetName (errorKey, keyName (initialParent)); keySetString (errorKey, keyString (initialParent)); keyDel (initialParent); errno = errnosave; return 0; } KeySet * keys = ksNew (0, KS_END); int inFallback = 0; switch (elektraOpenBootstrap (handle, keys, errorKey)) { case -1: ksDel (handle->modules); elektraFree (handle); ELEKTRA_SET_ERROR (40, errorKey, "could not open default backend"); keySetName (errorKey, keyName (initialParent)); keySetString (errorKey, keyString (initialParent)); keyDel (initialParent); errno = errnosave; return 0; case 0: ELEKTRA_ADD_WARNING (17, errorKey, "Initial kdbGet() failed, you should either fix " KDB_DB_INIT " or the fallback " KDB_DB_FILE); break; case 2: ELEKTRA_LOG ("entered fallback code for bootstrapping"); inFallback = 1; break; } keySetString (errorKey, "kdbOpen(): mountGlobals"); if (mountGlobals (handle, ksDup (keys), handle->modules, errorKey) == -1) { // mountGlobals also sets a warning containing the name of the plugin that failed to load ELEKTRA_ADD_WARNING (139, errorKey, "Mounting global plugins failed"); } keySetName (errorKey, keyName (initialParent)); keySetString (errorKey, "kdbOpen(): backendClose"); backendClose (handle->defaultBackend, errorKey); splitDel (handle->split); handle->defaultBackend = 0; handle->trie = 0; #ifdef HAVE_LOGGER if (inFallback) ELEKTRA_LOG_WARNING ("fallback for bootstrapping: you might want to run `kdb upgrade-bootstrap`"); Key * key; ksRewind (keys); for (key = ksNext (keys); key; key = ksNext (keys)) { ELEKTRA_LOG_DEBUG ("config for createTrie name: %s value: %s", keyName (key), keyString (key)); } #endif handle->split = splitNew (); keySetString (errorKey, "kdbOpen(): mountOpen"); // Open the trie, keys will be deleted within mountOpen if (mountOpen (handle, keys, handle->modules, errorKey) == -1) { ELEKTRA_ADD_WARNING (93, errorKey, "Initial loading of trie did not work"); } keySetString (errorKey, "kdbOpen(): mountDefault"); if (mountDefault (handle, handle->modules, inFallback, errorKey) == -1) { ELEKTRA_SET_ERROR (40, errorKey, "could not reopen and mount default backend"); keySetString (errorKey, "kdbOpen(): close"); kdbClose (handle, errorKey); keySetName (errorKey, keyName (initialParent)); keySetString (errorKey, keyString (initialParent)); keyDel (initialParent); errno = errnosave; return 0; } keySetString (errorKey, "kdbOpen(): mountVersion"); mountVersion (handle, errorKey); keySetString (errorKey, "kdbOpen(): mountModules"); if (mountModules (handle, handle->modules, errorKey) == -1) { ELEKTRA_ADD_WARNING (92, errorKey, "Mounting modules did not work"); } keySetName (errorKey, keyName (initialParent)); keySetString (errorKey, keyString (initialParent)); keyDel (initialParent); errno = errnosave; return handle; }