Elektra  0.9.2
Functions
KDB

General methods to access the Key database. More...

Functions

KeySet * ksRenameKeys (KeySet *config, const char *name)
 Takes the first key and cuts off this common part for all other keys, instead name will be prepended. More...
 
int elektraOpenBootstrap (KDB *handle, KeySet *keys, Key *errorKey)
 Bootstrap, first phase with fallback.
 
KDB * kdbOpen (Key *errorKey)
 Opens the session with the Key database. More...
 
int kdbClose (KDB *handle, Key *errorKey)
 Closes the session with the Key database. More...
 
int kdbGet (KDB *handle, KeySet *ks, Key *parentKey)
 Retrieve keys in an atomic and universal way. More...
 
int kdbSet (KDB *handle, KeySet *ks, Key *parentKey)
 Set keys in an atomic and universal way. More...
 
int kdbEnsure (KDB *handle, KeySet *contract, Key *parentKey)
 This function can be used to ensure the given KDB handle meets certain clauses, specified in contract. More...
 

Detailed Description

General methods to access the Key database.

To use them:

#include <kdb.h>

The kdb*() methods are used to access the storage, to get and set KeySets.

Parameters common for all these functions are:

Note
The parentKey is an obligation for you, but only an hint for KDB. KDB does not remember anything about the configuration. You need to pass the same configuration back to kdbSet(), otherwise parts of the configuration get lost. Only keys below the parentKey are subject for change, the rest must be left untouched.

KDB uses different backend implementations that know the details about how to access the storage. One backend consists of multiple plugins. See writing a new plugin for information about how to write a plugin. Backends are state-less regarding the configuration (because of that you must pass back the whole configuration for every backend), but have a state for:

State

As we see in the figure, kdbOpen() can be called arbitrarily often in any number of threads.

For every handle you got from kdbOpen(), for every parentKey with a different name, only the shown state transitions are valid. From a freshly opened KDB, only kdbGet() and kdbClose() are allowed, because otherwise conflicts (error C02000) would not be detected.

Once kdbGet() was called (for a specific handle+parentKey), any number of kdbGet() and kdbSet() can be used with this handle respective parentKey, unless kdbSet() had a conflict (error C02000) with another application. Every affair with KDB needs to be finished with kdbClose().

The name of the parentKey in kdbOpen() and kdbClose() does not matter.

In the usual case we just have one parentKey and one handle. In these cases we just have to remember to use kdbGet() before kdbSet():

#include <kdb.h>
int main (void)
{
KeySet * myConfig = ksNew (0, KS_END);
Key * parentKey = keyNew ("/sw/MyApp", KEY_CASCADING_NAME, KEY_END);
KDB * handle = kdbOpen (parentKey);
kdbGet (handle, myConfig, parentKey); // kdbGet() must be first
// now any number of any kdbGet()/kdbSet() calls are allowed, e.g.:
kdbSet (handle, myConfig, parentKey);
ksDel (myConfig); // delete the in-memory configuration
kdbClose (handle, parentKey); // no more affairs with the key database.
keyDel (parentKey); // working with key/ks does not need kdb
}

To output warnings, you can use following code:

const Key * metaWarnings = keyGetMeta (warningKey, "warnings");
if (!metaWarnings) return 1; /* There are no current warnings */
int nrWarnings = atoi (keyString (metaWarnings));
printf ("There are %d warnings\n", nrWarnings + 1);
for (int i = 0; i <= nrWarnings; ++i)
{
char buffer[] = "warnings/#00\0description";
buffer[10] = i / 10 % 10 + '0';
buffer[11] = i % 10 + '0';
printf ("buffer is: %s\n", buffer);
strncat (buffer, "/number", sizeof (buffer) - strlen (buffer) - 1);
printf ("number: %s\n", keyString (keyGetMeta (warningKey, buffer)));
buffer[12] = '\0';
strncat (buffer, "/description", sizeof (buffer) - strlen (buffer) - 1);
printf ("description: %s\n", keyString (keyGetMeta (warningKey, buffer)));
buffer[12] = '\0';
strncat (buffer, "/module", sizeof (buffer) - strlen (buffer) - 1);
keyGetMeta (warningKey, buffer);
printf ("module: %s\n", keyString (keyGetMeta (warningKey, buffer)));
buffer[12] = '\0';
strncat (buffer, "/file", sizeof (buffer) - strlen (buffer) - 1);
keyGetMeta (warningKey, buffer);
printf ("file: %s\n", keyString (keyGetMeta (warningKey, buffer)));
buffer[12] = '\0';
strncat (buffer, "/line", sizeof (buffer) - strlen (buffer) - 1);
keyGetMeta (warningKey, buffer);
printf ("line: %s\n", keyString (keyGetMeta (warningKey, buffer)));
buffer[12] = '\0';
strncat (buffer, "/reason", sizeof (buffer) - strlen (buffer) - 1);
keyGetMeta (warningKey, buffer);
printf ("reason: %s\n", keyString (keyGetMeta (warningKey, buffer)));
buffer[12] = '\0';
strncat (buffer, "/mountpoint", sizeof (buffer) - strlen (buffer) - 1);
keyGetMeta (warningKey, buffer);
printf ("reason: %s\n", keyString (keyGetMeta (warningKey, buffer)));
buffer[12] = '\0';
strncat (buffer, "/configfile", sizeof (buffer) - strlen (buffer) - 1);
keyGetMeta (warningKey, buffer);
printf ("reason: %s\n", keyString (keyGetMeta (warningKey, buffer)));
}

To output the error, you can use following code:

const Key * metaError = keyGetMeta (errorKey, "error");
if (!metaError) return 1; /* There is no current error */
printf ("number: %s\n", keyString (keyGetMeta (errorKey, "error/number")));
printf ("description: : %s\n", keyString (keyGetMeta (errorKey, "error/description")));
printf ("module: : %s\n", keyString (keyGetMeta (errorKey, "error/module")));
printf ("at: %s:%s\n", keyString (keyGetMeta (errorKey, "error/file")), keyString (keyGetMeta (errorKey, "error/line")));
printf ("reason: : %s\n", keyString (keyGetMeta (errorKey, "error/reason")));
printf ("mountpoint: : %s\n", keyString (keyGetMeta (errorKey, "error/mountpoint")));
printf ("configfile: : %s\n", keyString (keyGetMeta (errorKey, "error/configfile")));

Function Documentation

◆ kdbClose()

int kdbClose ( KDB *  handle,
Key *  errorKey 
)

Closes the session with the Key database.

Precondition
The handle must be a valid handle as returned from kdbOpen()
errorKey must be a valid key, e.g. created with keyNew()

This is the counterpart of kdbOpen().

You must call this method when you finished your affairs with the key database. You can manipulate Key and KeySet objects also after kdbClose(), but you must not use any kdb*() call afterwards.

The handle parameter will be finalized and all resources associated to it will be freed. After a kdbClose(), the handle cannot be used anymore.

Parameters
handlecontains internal information of opened key database
errorKeythe key which holds error/warning information
Return values
0on success
-1on NULL pointer

◆ kdbEnsure()

int kdbEnsure ( KDB *  handle,
KeySet *  contract,
Key *  parentKey 
)

This function can be used to ensure the given KDB handle meets certain clauses, specified in contract.

Currently the following clauses are supported:

  • system/elektra/ensure/plugins/<mountpoint>/<pluginname> defines the state of the plugin <pluginname> for the mountpoint <mountpoint>:
    • The value unmounted ensures the plugin is not mounted, at this mountpoint.
    • The value mounted ensures the plugin is mounted, at this mountpoint. If the plugin is not mounted, we will try to mount it.
    • The value remount always mounts the plugin, at this mountpoint. If it was already mounted, it will me unmounted and mounted again. This can be used to ensure the plugin is mounted with a certain configuration.
  • Keys below system/elektra/ensure/plugins/<mountpoint>/<pluginname>/config are extracted and used as the plugins config KeySet during mounting. system/elektra/ensure/plugins/<mountpoint>/<pluginname> will be replaced by user in the keynames. If no keys are given, an empty KeySet is used.

There are a few special values for <mountpoint>:

  • global is used to indicate the plugin should (un)mounted as a global plugin. Currently this only supports (un)mounting plugins from/to the subposition maxonce.
  • parent is used to indicate the keyname of parentKey shall be used as the mountpoint.

If <mountpoint> is none of those values, it has to be valid keyname with the slashes escaped. That means it has to start with /, user, system, dir or spec.

If <mountpoint> is NOT global, currently only unmounted is supported (not mounted and remounted).

NOTE: This function only works properly, if the list plugin is mounted in all global positions. If this is not the case, 1 will be returned, because this is seen as an implicit clause in the contract. Additionally any contract that specifies clauses for the list plugin is rejected as malformed.

Parameters
handlecontains internal information of opened key database
contractKeySet containing the contract described above. This will always be ksDel()ed. Even in error cases.
parentKeyThe parentKey used if the parent special value is used, otherwise only used for error reporting.
Return values
0on success
1if clauses of the contract are unmet
-1on NULL pointers, or malformed contract

◆ kdbGet()

int kdbGet ( KDB *  handle,
KeySet *  ks,
Key *  parentKey 
)

Retrieve keys in an atomic and universal way.

Precondition
The handle must be passed as returned from kdbOpen().
The returned KeySet must be a valid KeySet, e.g. constructed with ksNew().
The 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 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 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!
Example:
This example demonstrates the typical usecase within an application (without error handling).
#include <kdb.h>
#include <stdio.h>
int main (void)
{
KeySet * myConfig = ksNew (0, KS_END);
// for error handling see kdbget_error.c
// clang-format off
Key * key = keyNew ("/sw/tests/myapp/#0/current/", KEY_END);
KDB * handle = kdbOpen (key);
kdbGet (handle, myConfig, key);
Key * result = ksLookupByName (myConfig, "/sw/tests/myapp/#0/current/testkey1", 0);
// clang-format on
keyDel (key);
const char * key_name = keyName (result);
const char * key_value = keyString (result);
const char * key_comment = keyString (keyGetMeta (result, "comment"));
printf ("key: %s value: %s comment: %s\n", key_name, key_value, key_comment);
ksDel (myConfig); // delete the in-memory configuration
// maybe you want kdbSet() myConfig here
kdbClose (handle, 0); // no more affairs with the key database.
}

When a backend fails kdbGet() will return -1 with all error and warning information in the parentKey. The parameter returned will not be changed.

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().

Parameters
handlecontains internal information of opened key database
parentKeyis 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
ksthe (pre-initialized) KeySet returned with all keys found will not be changed on error or if no update is required
See also
ksLookup(), ksLookupByName() for powerful lookups after the KeySet was retrieved
kdbOpen() which needs to be called before
kdbSet() to save the configuration afterwards and kdbClose() to finish affairs with the Key database.
Return values
1if the keys were retrieved successfully
0if there was no update - no changes are made to the keyset then
-1on failure - no changes are made to the keyset then

◆ kdbOpen()

KDB* kdbOpen ( Key *  errorKey)

Opens the session with the Key database.

Precondition
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 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 KDB structure returned will be initialized like described above, and it must be passed along on any kdb*() method your application calls.

Get a KDB handle for every thread using elektra. Don't share the handle across threads, and also not the pointer accessing it:

void thread1 (void)
{
Key * parent = keyNew ("/app/part1", KEY_CASCADING_NAME, KEY_END);
KDB * h = kdbOpen (parent);
// fetch keys and work with them
kdbClose (h, parent);
}
void thread2 (void)
{
Key * parent = keyNew ("/app/part2", KEY_CASCADING_NAME, KEY_END);
KDB * h = kdbOpen (parent);
// fetch keys and work with them
kdbClose (h, parent);
}

You don't need kdbOpen() if you only want to manipulate plain in-memory Key or KeySet objects.

Precondition
errorKey must be a valid key, e.g. created with keyNew()
Parameters
errorKeythe key which holds errors and warnings which were issued
See also
kdbGet(), kdbClose() to end all affairs to the Key database.
Return values
handleon success
NULLon failure

◆ kdbSet()

int kdbSet ( KDB *  handle,
KeySet *  ks,
Key *  parentKey 
)

Set keys in an atomic and universal way.

Precondition
kdbGet() must be called before kdbSet():
The returned KeySet must be a valid KeySet, e.g. constructed with ksNew().
The 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 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().

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 C02000) 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)
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.
KeySet * myConfig = ksNew (0, KS_END);
Key * parentKey = keyNew ("system/sw/MyApp", KEY_END);
KDB * handle = kdbOpen (parentKey);
kdbGet (handle, myConfig, parentKey); // kdbGet needs to be called first!
KeySet * base = ksDup (myConfig); // save a copy of original keyset
// change the keys within myConfig
ksAppendKey (myConfig, keyNew ("system/sw/MyApp/Test", KEY_VALUE, "5", KEY_END));
KeySet * ours = ksDup (myConfig); // save a copy of our keyset
KeySet * theirs; // needed for 3-way merging
int ret = kdbSet (handle, myConfig, parentKey);
while (ret == -1) // as long as we have an error
{
int strategy = showElektraErrorDialog (parentKey);
theirs = ksDup (ours);
kdbGet (handle, theirs, parentKey); // refresh key database
KeySet * result = elektraMerge(
ksCut(ours, parentKey), parentKey,
ksCut(theirs, parentKey), parentKey,
ksCut(base, parentKey), parentKey,
parentKey, strategy, parentKey);
int numberOfConflicts = getConflicts (parentKey);
ksDel (theirs);
if (result != NULL) {
ret = kdbSet (handle, result, parentKey);
} else {
// an error happened while merging
if (numberOfConflicts > 0 && strategy == MERGE_STRATEGY_ABORT)
{
// Error due to merge conflicts
ret = -1;
}
else
{
// Internal errors, out of memory etc.
ret = -1;
}
}
}
ksDel (ours);
ksDel (base);
ksDel (myConfig); // delete the in-memory configuration
kdbClose (handle, parentKey); // no more affairs with the key database.
keyDel (parentKey);

showElektraErrorDialog() and doElektraMerge() need to be implemented by the user of Elektra. For doElektraMerge a 3-way merge algorithm exists in libelektra-tools.

Parameters
handlecontains internal information of opened key database
ksa KeySet which should contain changed keys, otherwise nothing is done
parentKeyis 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 C01320)
  • empty/invalid (error C01320)
Return values
1on success
0if nothing had to be done, no changes in KDB
-1on failure, no changes in KDB
See also
keyNeedSync()
ksCurrent() contains the error Key
kdbOpen() and kdbGet() that must be called first
kdbClose() that must be called afterwards

◆ ksRenameKeys()

KeySet* ksRenameKeys ( KeySet *  config,
const char *  name 
)

Takes the first key and cuts off this common part for all other keys, instead name will be prepended.

Returns
a new allocated keyset with keys in user namespace.

The first key is removed in the resulting keyset.

kdbGet
int kdbGet(KDB *handle, KeySet *ks, Key *parentKey)
Retrieve keys in an atomic and universal way.
Definition: kdb.c:1024
kdbOpen
KDB * kdbOpen(Key *errorKey)
Opens the session with the Key database.
Definition: kdb.c:290
ksLookupByName
Key * ksLookupByName(KeySet *ks, const char *name, option_t options)
Convenience method to look for a Key contained in ks that matches name.
Definition: keyset.c:2387
ksNew
KeySet * ksNew(size_t alloc,...)
Allocate, initialize and return a new KeySet object.
Definition: keyset.c:225
main
int main(int argc, char **argv)
[kdbio testsuite main]
Definition: testio_doc.c:48
elektraMerge
KeySet * elektraMerge(KeySet *our, Key *ourRoot, KeySet *their, Key *theirRoot, KeySet *base, Key *baseRoot, Key *resultKey, int strategy, Key *informationKey)
This function can incorporate changes from two modified versions (our and their) into a common preced...
Definition: kdbmerge.c:956
keyString
const char * keyString(const Key *key)
Get the c-string representing the value.
Definition: keyvalue.c:193
keyGetMeta
const Key * keyGetMeta(const Key *key, const char *metaName)
Returns the value of a meta-information given by name.
Definition: keymeta.c:414
KEY_VALUE
@ KEY_VALUE
Definition: kdbenum.c:23
kdbSet
int kdbSet(KDB *handle, KeySet *ks, Key *parentKey)
Set keys in an atomic and universal way.
Definition: kdb.c:1566
KS_END
#define KS_END
End of a list of keys.
Definition: kdbenum.c:88
ksAppendKey
ssize_t ksAppendKey(KeySet *ks, Key *toAppend)
Appends a Key to the end of ks.
Definition: keyset.c:832
keyDel
int keyDel(Key *key)
A destructor for Key objects.
Definition: key.c:568
KEY_END
@ KEY_END
Definition: kdbenum.c:41
KEY_CASCADING_NAME
@ KEY_CASCADING_NAME
Definition: kdbenum.c:39
keyNew
Key * keyNew(const char *name,...)
A practical way to fully create a Key object in one step.
Definition: key.c:167
ksDel
int ksDel(KeySet *ks)
A destructor for KeySet objects.
Definition: keyset.c:437
keyName
const char * keyName(const Key *key)
Returns a pointer to the abbreviated real internal key name.
Definition: elektra/keyname.c:213
ksDup
KeySet * ksDup(const KeySet *source)
Return a duplicate of a keyset.
Definition: keyset.c:306
ksCut
KeySet * ksCut(KeySet *ks, const Key *cutpoint)
Cuts out a keyset at the cutpoint.
Definition: keyset.c:1133
getConflicts
int getConflicts(Key *informationKey)
This function returns the number of conflicts that is store in the key.
Definition: kdbmerge.c:190
kdbClose
int kdbClose(KDB *handle, Key *errorKey)
Closes the session with the Key database.
Definition: kdb.c:439