/** * @brief Retrieve keys in an atomic and universal way. * * @pre The @p handle must be passed as returned from kdbOpen(). * * @pre The @p returned KeySet must be a valid KeySet, e.g. constructed * with ksNew(). * * @pre The @p parentKey Key must be a valid Key, e.g. constructed with * keyNew(). * * If you pass NULL on any parameter kdbGet() will fail immediately without doing anything. * * The @p returned KeySet may already contain some keys, e.g. from previous * kdbGet() calls. The new retrieved keys will be appended using * ksAppendKey(). * * If not done earlier kdbGet() will fully retrieve all keys under the @p parentKey * folder recursively (See Optimization below when it will not be done). * * @note kdbGet() might retrieve more keys than requested (that are not * below parentKey). These keys must be passed to calls of kdbSet(), * otherwise they will be lost. This stems from the fact that the * user has the only copy of the whole configuration and backends * only write configuration that was passed to them. * For example, if you kdbGet() "system/mountpoint/interest" * you will not only get all keys below system/mountpoint/interest, * but also all keys below system/mountpoint (if system/mountpoint * is a mountpoint as the name suggests, but * system/mountpoint/interest is not a mountpoint). * Make sure to not touch or remove keys outside the keys of interest, * because others may need them! * * @par Example: * This example demonstrates the typical usecase within an application * (without error handling). * * @include kdbget.c * * When a backend fails kdbGet() will return -1 with all * error and warning information in the @p parentKey. * The parameter @p returned will not be changed. * * @par Optimization: * In the first run of kdbGet all requested (or more) keys are retrieved. On subsequent * calls only the keys are retrieved where something was changed * inside the key database. The other keys stay in the * KeySet returned as passed. * * It is your responsibility to save the original keyset if you * need it afterwards. * * If you want to be sure to get a fresh keyset again, you need to open a * second handle to the key database using kdbOpen(). * * @param handle contains internal information of @link kdbOpen() opened @endlink key database * @param parentKey is used to add warnings and set an error * information. Additionally, its name is a hint which keys * should be retrieved (it is possible that more are retrieved, see Note above). * - cascading keys (starting with /) will retrieve the same path in all namespaces * - / will retrieve all keys * @param ks the (pre-initialized) KeySet returned with all keys found * will not be changed on error or if no update is required * @see ksLookup(), ksLookupByName() for powerful * lookups after the KeySet was retrieved * @see kdbOpen() which needs to be called before * @see kdbSet() to save the configuration afterwards and kdbClose() to * finish affairs with the key database. * @retval 1 if the keys were retrieved successfully * @retval 0 if there was no update - no changes are made to the keyset then * @retval -1 on failure - no changes are made to the keyset then * @ingroup kdb */ int kdbGet (KDB * handle, KeySet * ks, Key * parentKey) { elektraNamespace ns = keyGetNamespace (parentKey); if (ns == KEY_NS_NONE) { return -1; } Key * oldError = keyNew (keyName (parentKey), KEY_END); copyError (oldError, parentKey); if (ns == KEY_NS_META) { clearError (parentKey); keyDel (oldError); ELEKTRA_SET_ERRORF (104, parentKey, "metakey with name \"%s\" passed to kdbGet", keyName (parentKey)); return -1; } if (ns == KEY_NS_EMPTY) { ELEKTRA_ADD_WARNING (105, parentKey, "invalid key name passed to kdbGet"); } int errnosave = errno; Key * initialParent = keyDup (parentKey); ELEKTRA_LOG ("now in new kdbGet (%s)", keyName (parentKey)); Split * split = splitNew (); if (!handle || !ks) { clearError (parentKey); ELEKTRA_SET_ERROR (37, parentKey, "handle or ks null pointer"); goto error; } elektraGlobalGet (handle, ks, parentKey, PREGETSTORAGE, INIT); elektraGlobalGet (handle, ks, parentKey, PREGETSTORAGE, MAXONCE); elektraGlobalGet (handle, ks, parentKey, PREGETSTORAGE, DEINIT); if (splitBuildup (split, handle, parentKey) == -1) { clearError (parentKey); ELEKTRA_SET_ERROR (38, parentKey, "error in splitBuildup"); goto error; } // Check if a update is needed at all switch (elektraGetCheckUpdateNeeded (split, parentKey)) { case 0: // We don't need an update so let's do nothing keySetName (parentKey, keyName (initialParent)); elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, INIT); elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, MAXONCE); elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, DEINIT); splitUpdateFileName (split, handle, parentKey); keyDel (initialParent); splitDel (split); errno = errnosave; keyDel (oldError); return 0; case -1: goto error; // otherwise fall trough } // Appoint keys (some in the bypass) if (splitAppoint (split, handle, ks) == -1) { clearError (parentKey); ELEKTRA_SET_ERROR (38, parentKey, "error in splitAppoint"); goto error; } if (handle->globalPlugins[POSTGETSTORAGE][FOREACH] || handle->globalPlugins[POSTGETCLEANUP][FOREACH]) { clearError (parentKey); if (elektraGetDoUpdateWithGlobalHooks (NULL, split, NULL, parentKey, initialParent, FIRST) == -1) { goto error; } else { copyError (parentKey, oldError); } keySetName (parentKey, keyName (initialParent)); if (splitGet (split, parentKey, handle) == -1) { ELEKTRA_ADD_WARNING (108, parentKey, keyName (ksCurrent (ks))); // continue, because sizes are already updated } ksClear (ks); splitMerge (split, ks); clearError (parentKey); if (elektraGetDoUpdateWithGlobalHooks (handle, split, ks, parentKey, initialParent, LAST) == -1) { goto error; } else { copyError (parentKey, oldError); } } else { /* Now do the real updating, but not for bypassed keys in split->size-1 */ clearError (parentKey); if (elektraGetDoUpdate (split, parentKey) == -1) { goto error; } else { copyError (parentKey, oldError); } /* Now post-process the updated keysets */ if (splitGet (split, parentKey, handle) == -1) { ELEKTRA_ADD_WARNING (108, parentKey, keyName (ksCurrent (ks))); // continue, because sizes are already updated } /* We are finished, now just merge everything to returned */ ksClear (ks); splitMerge (split, ks); } elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, INIT); elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, MAXONCE); elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, DEINIT); ksRewind (ks); keySetName (parentKey, keyName (initialParent)); splitUpdateFileName (split, handle, parentKey); keyDel (initialParent); keyDel (oldError); splitDel (split); errno = errnosave; return 1; error: keySetName (parentKey, keyName (initialParent)); elektraGlobalError (handle, ks, parentKey, POSTGETSTORAGE, INIT); elektraGlobalError (handle, ks, parentKey, POSTGETSTORAGE, MAXONCE); elektraGlobalError (handle, ks, parentKey, POSTGETSTORAGE, DEINIT); keySetName (parentKey, keyName (initialParent)); if (handle) splitUpdateFileName (split, handle, parentKey); keyDel (initialParent); keyDel (oldError); splitDel (split); errno = errnosave; return -1; }
/* * Copy a file with path srcName to a file with name newName. * If the destination already exists, the operation fails. * Links are followed. * Special files (fifos, char devices, block devices, etc) will be * read as long as there is data available and the destination will be * a regular file with that data. * The new file will have the same permissions as the old. * If an error occurs during copying, an attempt will be made to * remove the copy. */ int copyFile (uio_DirHandle *srcDir, const char *srcName, uio_DirHandle *dstDir, const char *newName) { uio_Handle *src, *dst; struct stat sb; #define BUFSIZE 65536 uint8 *buf, *bufPtr; ssize_t numInBuf, numWritten; src = uio_open (srcDir, srcName, O_RDONLY #ifdef WIN32 | O_BINARY #endif , 0); if (src == NULL) return -1; if (uio_fstat (src, &sb) == -1) return copyError (src, NULL, NULL, NULL, NULL); dst = uio_open (dstDir, newName, O_WRONLY | O_CREAT | O_EXCL #ifdef WIN32 | O_BINARY #endif , sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); if (dst == NULL) return copyError (src, NULL, NULL, NULL, NULL); buf = HMalloc(BUFSIZE); // This was originally a statically allocated buffer, // but as this function might be run from a thread with // a small Stack, this is better. while (1) { numInBuf = uio_read (src, buf, BUFSIZE); if (numInBuf == -1) { if (errno == EINTR) continue; return copyError (src, dst, dstDir, newName, buf); } if (numInBuf == 0) break; bufPtr = buf; do { numWritten = uio_write (dst, bufPtr, numInBuf); if (numWritten == -1) { if (errno == EINTR) continue; return copyError (src, dst, dstDir, newName, buf); } numInBuf -= numWritten; bufPtr += numWritten; } while (numInBuf > 0); } HFree (buf); uio_close (src); uio_close (dst); errno = 0; return 0; }
/** @brief Set keys in an atomic and universal way. * * @pre kdbGet() must be called before kdbSet(): * - initially (after kdbOpen()) * - after conflict errors in kdbSet(). * * @pre The @p returned KeySet must be a valid KeySet, e.g. constructed * with ksNew(). * * @pre The @p parentKey Key must be a valid Key, e.g. constructed with * keyNew(). * * If you pass NULL on any parameter kdbSet() will fail immediately without doing anything. * * With @p parentKey you can give an hint which part of the given keyset * is of interest for you. Then you promise to only modify or * remove keys below this key. All others would be passed back * as they were retrieved by kdbGet(). * * @par Errors * If some error occurs: * - kdbSet() will leave the KeySet's * internal cursor on the key that generated the error. * - Error information will be written into the metadata of * the parent key. * - None of the keys are actually committed in this situation, i.e. no * configuration file will be modified. * * In case of errors you should present the error message to the user and let the user decide what * to do. Possible solutions are: * - remove the problematic key and use kdbSet() again (for validation or type errors) * - change the value of the problematic key and use kdbSet() again (for validation errors) * - do a kdbGet() (for conflicts, i.e. error 30) and then * - set the same keyset again (in favour of what was set by this user) * - drop the old keyset (in favour of what was set from another application) * - merge the original, your own and the other keyset * - export the configuration into a file (for unresolvable errors) * - repeat the same kdbSet might be of limited use if the user does * not explicitly request it, because temporary * errors are rare and its unlikely that they fix themselves * (e.g. disc full, permission problems) * * @par Optimization * Each key is checked with keyNeedSync() before being actually committed. * If no key of a backend needs to be synced * any affairs to backends are omitted and 0 is returned. * * @snippet kdbset.c set * * showElektraErrorDialog() and doElektraMerge() need to be implemented * by the user of Elektra. For doElektraMerge a 3-way merge algorithm exists in * libelektra-tools. * * @param handle contains internal information of @link kdbOpen() opened @endlink key database * @param ks a KeySet which should contain changed keys, otherwise nothing is done * @param parentKey is used to add warnings and set an error * information. Additionally, its name is an hint which keys * should be committed (it is possible that more are changed). * - cascading keys (starting with /) will set the path in all namespaces * - / will commit all keys * - metanames will be rejected (error 104) * - empty/invalid (error 105) * @retval 1 on success * @retval 0 if nothing had to be done, no changes in KDB * @retval -1 on failure, no changes in KDB * @see keyNeedSync() * @see ksCurrent() contains the error key * @see kdbOpen() and kdbGet() that must be called first * @see kdbClose() that must be called afterwards * @ingroup kdb */ int kdbSet (KDB * handle, KeySet * ks, Key * parentKey) { elektraNamespace ns = keyGetNamespace (parentKey); if (ns == KEY_NS_NONE) { return -1; } Key * oldError = keyNew (keyName (parentKey), KEY_END); copyError (oldError, parentKey); if (ns == KEY_NS_META) { clearError (parentKey); // clear previous error to set new one ELEKTRA_SET_ERRORF (104, parentKey, "metakey with name \"%s\" passed to kdbSet", keyName (parentKey)); keyDel (oldError); return -1; } if (ns == KEY_NS_EMPTY) { ELEKTRA_ADD_WARNING (105, parentKey, "invalid key name passed to kdbSet"); } if (!handle || !ks) { clearError (parentKey); // clear previous error to set new one ELEKTRA_SET_ERROR (37, parentKey, "handle or ks null pointer"); keyDel (oldError); return -1; } int errnosave = errno; Key * initialParent = keyDup (parentKey); ELEKTRA_LOG ("now in new kdbSet (%s) %p %zd", keyName (parentKey), (void *)handle, ksGetSize (ks)); elektraGlobalSet (handle, ks, parentKey, PRESETSTORAGE, INIT); elektraGlobalSet (handle, ks, parentKey, PRESETSTORAGE, MAXONCE); elektraGlobalSet (handle, ks, parentKey, PRESETSTORAGE, DEINIT); ELEKTRA_LOG ("after presetstorage maxonce(%s) %p %zd", keyName (parentKey), (void *)handle, ksGetSize (ks)); Split * split = splitNew (); Key * errorKey = 0; if (splitBuildup (split, handle, parentKey) == -1) { clearError (parentKey); // clear previous error to set new one ELEKTRA_SET_ERROR (38, parentKey, "error in splitBuildup"); goto error; } // 1.) Search for syncbits int syncstate = splitDivide (split, handle, ks); if (syncstate == -1) { clearError (parentKey); // clear previous error to set new one ELEKTRA_SET_ERROR (8, parentKey, keyName (ksCurrent (ks))); goto error; } ELEKTRA_ASSERT (syncstate == 0 || syncstate == 1, "syncstate not 0 or 1, but %d", syncstate); // 2.) Search for changed sizes syncstate |= splitSync (split); ELEKTRA_ASSERT (syncstate <= 1, "syncstate not equal or below 1, but %d", syncstate); if (syncstate != 1) { /* No update is needed */ keySetName (parentKey, keyName (initialParent)); if (syncstate < 0) clearError (parentKey); // clear previous error to set new one if (syncstate == -1) { ELEKTRA_SET_ERROR (107, parentKey, "Assert failed: invalid namespace"); } else if (syncstate < -1) { ELEKTRA_SET_ERROR (107, parentKey, keyName (split->parents[-syncstate - 2])); } keyDel (initialParent); splitDel (split); errno = errnosave; keyDel (oldError); return syncstate == 0 ? 0 : -1; } ELEKTRA_ASSERT (syncstate == 1, "syncstate not 1, but %d", syncstate); splitPrepare (split); clearError (parentKey); // clear previous error to set new one if (elektraSetPrepare (split, parentKey, &errorKey, handle->globalPlugins) == -1) { goto error; } else { // no error, restore old error copyError (parentKey, oldError); } keySetName (parentKey, keyName (initialParent)); elektraGlobalSet (handle, ks, parentKey, PRECOMMIT, INIT); elektraGlobalSet (handle, ks, parentKey, PRECOMMIT, MAXONCE); elektraGlobalSet (handle, ks, parentKey, PRECOMMIT, DEINIT); elektraSetCommit (split, parentKey); elektraGlobalSet (handle, ks, parentKey, COMMIT, INIT); elektraGlobalSet (handle, ks, parentKey, COMMIT, MAXONCE); elektraGlobalSet (handle, ks, parentKey, COMMIT, DEINIT); splitUpdateSize (split); keySetName (parentKey, keyName (initialParent)); elektraGlobalSet (handle, ks, parentKey, POSTCOMMIT, INIT); elektraGlobalSet (handle, ks, parentKey, POSTCOMMIT, MAXONCE); elektraGlobalSet (handle, ks, parentKey, POSTCOMMIT, DEINIT); for (size_t i = 0; i < ks->size; ++i) { // remove all flags from all keys clear_bit (ks->array[i]->flags, KEY_FLAG_SYNC); } keySetName (parentKey, keyName (initialParent)); keyDel (initialParent); splitDel (split); keyDel (oldError); errno = errnosave; return 1; error: keySetName (parentKey, keyName (initialParent)); elektraGlobalError (handle, ks, parentKey, PREROLLBACK, INIT); elektraGlobalError (handle, ks, parentKey, PREROLLBACK, MAXONCE); elektraGlobalError (handle, ks, parentKey, PREROLLBACK, DEINIT); elektraSetRollback (split, parentKey); if (errorKey) { Key * found = ksLookup (ks, errorKey, 0); if (!found) { ELEKTRA_ADD_WARNING (82, parentKey, keyName (errorKey)); } } keySetName (parentKey, keyName (initialParent)); elektraGlobalError (handle, ks, parentKey, POSTROLLBACK, INIT); elektraGlobalError (handle, ks, parentKey, POSTROLLBACK, MAXONCE); elektraGlobalError (handle, ks, parentKey, POSTROLLBACK, DEINIT); keySetName (parentKey, keyName (initialParent)); keyDel (initialParent); splitDel (split); errno = errnosave; keyDel (oldError); return -1; }