$darkmode
Elektra 0.11.0
Macros | Functions
KDB

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

Macros

#define KDB_VERSION   "x.y.z"
 The version information in x.y.z format as string. More...
 
#define KDB_VERSION_MAJOR   x
 The version information of the major version as number. More...
 
#define KDB_VERSION_MINOR   y
 The version information of the minor version as number. More...
 
#define KDB_VERSION_PATCH   z
 The version information of the patch version as number. 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...
 
KDB * kdbOpen (const KeySet *contract, 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 from the Key database in an atomic and universal way. More...
 
int kdbSet (KDB *handle, KeySet *ks, Key *parentKey)
 Set Keys to the Key database in an atomic and universal way. 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:

stateDiagram-v2 direction LR op1: opened op2: opened cl1: closed cl2: closed r: ready state fork_state <> [*] --> fork_state fork_state --> op1: kdbOpen() fork_state --> op2: kdbOpen() op1 --> r: kdbGet() r --> op1: kdbSet() [conflict] r --> r: kdbSet() [successful] r --> r: kdbGet() r --> cl1: kdbClose() op2 --> cl2: kdbClose() state join_state <> cl1 --> join_state cl2 --> join_state join_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>
#include <stddef.h>
int main (void)
{
KeySet * myConfig = ksNew (0, KS_END);
Key * parentKey = keyNew ("/sw/MyApp", KEY_END);
KDB * handle = kdbOpen (NULL, 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
}
int kdbSet(KDB *handle, KeySet *ks, Key *parentKey)
Set Keys to the Key database in an atomic and universal way.
Definition: kdb.c:2343
int kdbGet(KDB *handle, KeySet *ks, Key *parentKey)
Retrieve Keys from the Key database in an atomic and universal way.
Definition: kdb.c:1734
KDB * kdbOpen(const KeySet *contract, Key *errorKey)
Opens the session with the Key database.
Definition: kdb.c:967
int kdbClose(KDB *handle, Key *errorKey)
Closes the session with the Key database.
Definition: kdb.c:1105
int keyDel(Key *key)
A destructor for Key objects.
Definition: key.c:459
Key * keyNew(const char *name,...)
A practical way to fully create a Key object in one step.
Definition: key.c:144
@ KEY_END
Definition: kdbenum.c:95
int ksDel(KeySet *ks)
A destructor for KeySet objects.
Definition: keyset.c:521
KeySet * ksNew(size_t alloc,...)
Allocate, initialize and return a new KeySet object.
Definition: keyset.c:282
#define KS_END
End of a list of keys.
Definition: kdbenum.c:156
int main(int argc, char **argv)
[kdbio testsuite main]
Definition: testio_doc.c:48

To output warnings, you can use following code:

Key * cutpoint = keyNew ("meta:/warnings", KEY_END);
KeySet * warnings = ksCut (keyMeta (warningKey), cutpoint);
if (!warningKey || ksGetSize (warnings) == 0)
{
ksDel (warnings);
keyDel (cutpoint);
return 1;
}
printf ("There are %zu warnings\n", ksGetSize (warnings));
elektraCursor i = 1;
while (i < ksGetSize (warnings))
{
++i;
Key * cur = ksAtCursor (warnings, i);
while (!keyIsDirectlyBelow (cutpoint, cur))
{
printf ("%s: %s\n", keyName (cur) + keyGetNameSize (cutpoint), keyString (cur));
++i;
cur = ksAtCursor (warnings, i);
}
printf ("\n");
}
ksDel (warnings);
keyDel (cutpoint);
KeySet * keyMeta(Key *key)
Returns the KeySet holding the given Key's metadata.
Definition: keymeta.c:547
const char * keyName(const Key *key)
Returns a pointer to the abbreviated real internal key name.
Definition: elektra/keyname.c:429
ssize_t keyGetNameSize(const Key *key)
Bytes needed to store the Key's name (excluding owner).
Definition: elektra/keyname.c:457
KeySet * ksCut(KeySet *ks, const Key *cutpoint)
Cuts out all Keys from KeySet ks that are below or at cutpoint.
Definition: keyset.c:1608
ssize_t ksGetSize(const KeySet *ks)
Return the number of Keys that ks contains.
Definition: keyset.c:791
Key * ksAtCursor(const KeySet *ks, elektraCursor pos)
Return Key at given position pos.
Definition: keyset.c:1978
int keyIsDirectlyBelow(const Key *key, const Key *check)
Check whether the Key check is directly below the Key key.
Definition: keytest.c:245
const char * keyString(const Key *key)
Get a pointer to the c-string representing the value.
Definition: keyvalue.c:208

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")));
const Key * keyGetMeta(const Key *key, const char *metaName)
Returns the Key for a metadata entry with name metaName.
Definition: keymeta.c:379

Macro Definition Documentation

◆ KDB_VERSION

#define KDB_VERSION   "x.y.z"

The version information in x.y.z format as string.

To get the version at run-time, you can get the key system:/elektra/version/constants/KDB_VERSION

See also
VERSION.md.
KDB_VERSION_MAJOR
KDB_VERSION_MINOR
KDB_VERSION_PATCH

◆ KDB_VERSION_MAJOR

#define KDB_VERSION_MAJOR   x

The version information of the major version as number.

To get the version at run-time, you can get the key system:/elektra/version/constants/KDB_VERSION_MAJOR

See also
VERSION.md.
KDB_VERSION

◆ KDB_VERSION_MINOR

#define KDB_VERSION_MINOR   y

The version information of the minor version as number.

To get the version at run-time, you can get the key system:/elektra/version/constants/KDB_VERSION_MINOR

See also
VERSION.md.
KDB_VERSION

◆ KDB_VERSION_PATCH

#define KDB_VERSION_PATCH   z

The version information of the patch version as number.

To get the version at run-time, you can get the key system:/elektra/version/constants/KDB_VERSION_PATCH

See also
VERSION.md.
KDB_VERSION

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 are finished working 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
Since
1.0.0
See also
kdbOpen() for opening a session with a Key database

◆ kdbGet()

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

Retrieve Keys from the Key database in an atomic and universal way.

Precondition
The handle must be a valid KDB handle as returned from kdbOpen().
The KeySet returned must be a valid KeySet, i.e., constructed with ksNew().
The KeySet returned must contain keys only from the spec:/, dir:/, user:/, system:/, default:/ or proc:/ namespaces.
The Key parentKey must be a valid Key, i.e., constructed with keyNew().
The Key parentKey must not have read-only name, value or metadata.
The Key parentKey must use the spec:/, dir:/, user:/, system:/, default:/, proc:/ or cascading namespace.

If you pass NULL or a key with read-only metadata as parentKey, kdbGet() will fail immediately without doing anything. If you pass another invalid parentKey, or NULL as ks or handle, kdbGet() will set an error on parentKey and then return immediately.

Note
If you pass a non-NULL parentKey with writable metadata, kdbGet() will always remove any existing errors and warnings from parentKey.
Warning
If you later call kdbSet() with the same handle you must make sure to pass all keys from ks, which you do not want to remove.
Loadable Namespaces

Not all namespace can be loaded.

  • spec:/, dir:/, user:/ and system:/ can be loaded via kdbGet().
  • proc:/ keys can be loaded via kdbGet(), but are not persisted or cached.
  • default:/ keys can be inserted by kdbGet() but they will always stem from a specification in spec:/ keys.
  • If ks contains a key with any other namespace, an error will be returned.
Parent Key

The parentKey defines which parts of ks will be loaded. Everything that is at or below parentKey wil be loaded together with any key that shares a backend with such a key. Backends are always loaded as an atomic unit.

Note
If parentKey is in the cascading namespace, keys of all loadable namespaces (see above) will be loaded. This is generally the recommended approach.

Upon sucessfully returning kdbGet() also sets the value of parentKey to the storage identifier used by the backend that contains (or would contain) parentKey. For file-based backends this is the absolute path of the underlying file. Other backends may use different identifiers, but it always uniquely identifies the underlying storage unit.

Note
If parentKey is in the cascading, default:/ or `proc:/ namespace, the value of parentKey will be set to an empty string. This is done, because those namespaces are not persistable (see kdbSet()) and therfore have no storage identifier.
KeySet Modifications

Below or at parentKey, the KeySet ks will mostly contain keys loaded from backends. The only exception are proc:/ and spec:/ keys that were already present, before kdbGet() was called and do not overlap with an existing backend (for those namespaces). This can be used to provide a hard-coded fallback specifications and/or process-specific data.

Keys not below (or at) parentKey that were present when kdbGet() was called, may still be removed. For example, this could be because they overlap with a backend that also has keys below parentKey (backends are atomic units).

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 (NULL, 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/#0"));
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.
}
Key * ksLookupByName(KeySet *ks, const char *name, elektraLookupFlags options)
Convenience method to look for a Key contained in ks with name name.
Definition: keyset.c:2778

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
parentKeyKeys below parentKey will be retrieved from handle. It is also used to add warnings and set error information.
ksthe (pre-initialized) KeySet returned with all keys found will not be changed on error or if no update is required
Return values
2if only proc:/ backends were executed. This means no data was loaded from storage. There might be warnings attached to the parentKey! Depending on your use case, you might need to treat them as errors!
1if the Keys were retrieved successfully. There might be warnings attached to the parentKey! Depending on your use case, you might need to treat them as errors!
0if there was no update at all - no changes are made to the KeySet then. There might be warnings attached to the parentKey! Depending on your use case, you might need to treat them as erorrs!
-1on failure - no changes are made to the KeySet then
Since
1.0.0
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
kdbClose() to finish affairs with the Key database.

◆ kdbOpen()

KDB* kdbOpen ( const KeySet *  contract,
Key *  errorKey 
)

Opens the session with the Key database.

Precondition
errorKey must be a valid key, e.g. created with keyNew()

You must always call this method before retrieving or committing any keys to the database. At the end of a program, after using the Key database (KDB), you must not forget to call kdbClose() to free resources.

The method will bootstrap itself in 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. Then the global plugins and global keyset data from the contract is processed. Finally, the libraries for backends will be loaded and with it the KDB data structure will be initialized.

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_END);
KDB * h = kdbOpen (NULL, parent);
// fetch keys and work with them
kdbClose (h, parent);
}
void thread2 (void)
{
Key * parent = keyNew ("/app/part2", KEY_END);
KDB * h = kdbOpen (NULL, 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
contractthe contract that should be ensured before opening the KDB all data is copied and the KeySet can safely be used for e.g. kdbGet() later
errorKeythe key which holds errors and warnings which were issued
Returns
handle to the newly created KDB on success
Return values
NULLon failure
Since
1.0.0
See also
kdbClose() to close the session of a Key database opened by kdbOpen()

◆ kdbSet()

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

Set Keys to the Key database in an atomic and universal way.

Precondition
kdbGet() must be called before kdbSet():
The KeySet ks must be a valid KeySet, i.e., constructed with ksNew().
The KeySet ks must only contain only keys in the spec:/, dir:/, user:/, system:/, default:/ or proc:/ namespaces.
The Key parentKey must be a valid Key, e.g. constructed with keyNew().
The Key parentKey must not have read-only name, value or metadata.
The Key parentKey must use the spec:/, dir:/, user:/, system:/, default:/, proc:/ or cascading namespace.

If you pass NULL or a key with read-only metadata as parentKey, kdbSet() will fail immediately without doing anything. If you pass another invalid parentKey, or NULL as ks or handle, kdbSet() will set an error on parentKey and then return immediately.

Note
If you pass a non-NULL parentKey with writable metadata, kdbSet() will always remove any existing errors and warnings from parentKey.
Persistable Namespaces

Not all namespace can be persisted.

  • spec:/, dir:/, user:/ and system:/ will be persisted by kdbSet().
  • default:/ and proc:/ keys are ignored by kdbSet().
  • If ks contains a key with any other namespace, an error will be returned.

In general it is recommended to use a parentKey in the cascading namespace to cover all namespaces at once.

Parent Key

The parentKey defines which parts of ks will be stored. Everything that is at or below parentKey will be persisted together with any key that shares a backend with such a key. Backends are always stored as an atomic unit.

Note
If parentKey is in the cascading namespace, keys of all persistable namespaces (see above) will be stored. This is generally the recommended approach.
KeySet modifications

The contents of ks will mostly not be modified by kdbSet(). The only modifications made are those caused by applying the specification in spec:/ to dir:/, user:/ and system:/.

Errors
If parentKey == NULL or parentKey has read-only metadata, kdbSet() will immediately return the error code -1. In all other error cases the following happens:
  • Error information will be written into the metadata of the parent key, if possible.
  • 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)
  • repeating 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
Only backends that
  • contain at least one changed key according to elektraDiffCalculate(),
  • contain fewer keys than at the end of kdbGet() will be called. There won't be an unnecessary write for unchanged keys.

If none of the backends needs an update, kdbSet() returns 0 and does nothing.

KeySet * myConfig = ksNew (0, KS_END);
Key * parentKey = keyNew ("system:/sw/MyApp", KEY_END);
KDB * handle = kdbOpen (NULL, 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 = elektraMergeGetConflicts (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);
@ KEY_VALUE
Definition: kdbenum.c:88
ssize_t ksAppendKey(KeySet *ks, Key *toAppend)
Appends a Key to the end of ks.
Definition: keyset.c:968
KeySet * ksDup(const KeySet *source)
Return a duplicate of a KeySet.
Definition: keyset.c:371
@ MERGE_STRATEGY_ABORT
Definition: kdbmerge.h:26
KeySet * elektraMerge(KeySet *our, Key *ourRoot, KeySet *their, Key *theirRoot, KeySet *base, Key *baseRoot, Key *resultKey, enum MergeStrategy strategy, Key *informationKey)
This function can incorporate changes from two modified versions (our and their) into a common preced...
Definition: kdbmerge.c:1192
int elektraMergeGetConflicts(Key *informationKey)
This function returns the number of conflicts that is store in the key.
Definition: kdbmerge.c:357

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
parentKeyKeys below parentKey will be set to handle. It is also used to add warnings and set error information.
Return values
1on success
0if nothing had to be done, no changes in KDB
-1on failure, no changes in KDB, an error will be set on parentKey if possible (see "Errors" above)
Since
1.0.0
See also
kdbOpen() for getting handle
kdbClose() that must be called afterwards
ksCurrent() contains the error Key

◆ 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.