General methods to access the Key database.
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...
|
|
General methods to access the Key database.
To use them:
The kdb*() methods are used to access the storage, to get and set KeySets.
Parameters common for all these functions are:
- handle, as returned by kdbOpen(), need to be passed to every call
- parentKey is used for every call to add warnings and set an error. For kdbGet() / kdbSet() it is used to give an hint which keys should be retrieved/stored.
- 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:
- a two phase-commit
- a conflict detection (error 30) and
- optimizations that avoid redoing already done operations.
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 30) 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 30) 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 ()
{
Key * parentKey =
keyNew (
"/sw/MyApp", KEY_CASCADING_NAME,
KEY_END);
KDB * handle =
kdbOpen (parentKey);
kdbGet (handle, myConfig, parentKey);
kdbSet (handle, myConfig, parentKey);
}
To output warnings, you can use following code:
const Key * metaWarnings =
keyGetMeta (warningKey,
"warnings");
if (!metaWarnings) return 1;
int nrWarnings = atoi (
keyString (metaWarnings));
char buffer[] = "warnings/#00\0description";
printf ("There are %d warnings\n", nrWarnings + 1);
for (int i = 0; i <= nrWarnings; ++i)
{
buffer[10] = i / 10 % 10 + '0';
buffer[11] = i % 10 + '0';
printf ("buffer is: %s\n", buffer);
strncat (buffer, "/number", sizeof (buffer) - 1);
buffer[12] = '\0';
strncat (buffer, "/description", sizeof (buffer) - 1);
buffer[12] = '\0';
strncat (buffer, "/ingroup", sizeof (buffer) - 1);
buffer[12] = '\0';
strncat (buffer, "/module", sizeof (buffer) - 1);
buffer[12] = '\0';
strncat (buffer, "/file", sizeof (buffer) - 1);
buffer[12] = '\0';
strncat (buffer, "/line", sizeof (buffer) - 1);
buffer[12] = '\0';
strncat (buffer, "/reason", sizeof (buffer) - 1);
buffer[12] = '\0';
strncat (buffer, "/mountpoint", sizeof (buffer) - 1);
buffer[12] = '\0';
strncat (buffer, "/configfile", sizeof (buffer) - 1);
}
To output the error, you can use following code:
const Key * metaError =
keyGetMeta (errorKey,
"error");
if (!metaError) return 1;
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
-
handle | contains internal information of opened key database |
errorKey | the key which holds error/warning information |
- Return values
-
0 | on success |
-1 | on NULL pointer |
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, which violates the preconditions, 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().
It will fully retrieve, at least, all keys under the parentKey
folder, with all subfolders and their children.
- Note
- kdbGet() might retrieve more keys then 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 ()
{
kdbGet (handle, myConfig, key);
Key * result =
ksLookupByName (myConfig,
"/sw/MyApp/Tests/TestKey1", 0);
const char * key_name =
keyName (result);
printf ("key: %s value: %s comment: %s\n", key_name, key_value, key_comment);
}
When a backend fails kdbGet() will return -1 with all error and warning information in the parentKey
. The parameter returned
will not be changed.
- Updates:
- 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 unchanged in the keyset, even if they were manipulated.
It is your responsibility to save the original keyset if you need it afterwards.
If you want to get the same keyset again, you need to open a second handle to the key database using kdbOpen().
- Parameters
-
handle | contains internal information of opened key database |
parentKey | is used to add warnings and set an error information. Additionally, its name is an hint which keys should be retrieved (it is possible that more are retrieved).
- cascading keys (starting with /) will retrieve the same path in all namespaces
- / will retrieve all keys
|
ks | the (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
-
1 | if the keys were retrieved successfully |
0 | if there was no update - no changes are made to the keyset then |
-1 | on failure - no changes are made to the keyset then |
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
datastructure 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 ()
{
Key * parent =
keyNew (
"/app/part1", KEY_CASCADING_NAME,
KEY_END);
}
void thread2 ()
{
Key * parent =
keyNew (
"/app/part2", KEY_CASCADING_NAME,
KEY_END);
}
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
-
errorKey | the key which holds errors and warnings which were issued |
- See also
- kdbGet(), kdbClose() to end all affairs to the Key database.
- Return values
-
handle | on success |
NULL | on failure |
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
With parentKey
you can give an hint which part of the given keyset is of interest for you. Then you promise, you only modified or removed keys below this key.
- Errors
- If some error occurs, kdbSet() will stop. In this situation the KeySet internal cursor will be set on the key that generated the error. None of the keys are actually committed in this situation (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 operator 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. So only changed keys are updated. If no key of a backend needs to be synced any affairs to backends are omitted and 0 is returned.
KDB * handle =
kdbOpen (parentKey);
kdbGet (handle, myConfig, parentKey);
KeySet * base =
ksDup (myConfig);
KeySet * ours =
ksDup (myConfig);
KeySet * theirs;
int ret =
kdbSet (handle, myConfig, parentKey);
while (ret == -1)
{
int userInput = showElektraErrorDialog (parentKey, problemKey);
switch (userInput)
{
case INPUT_USE_OURS:
kdbGet (handle, myConfig, parentKey);
myConfig = ours;
break;
case INPUT_DO_MERGE:
kdbGet (handle, theirs, parentKey);
KeySet * res = doElektraMerge (ours, theirs, base);
myConfig = res;
break;
case INPUT_USE_THEIRS:
kdbGet (handle, myConfig, parentKey);
break;
}
ret =
kdbSet (handle, myConfig, 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
-
handle | contains internal information of opened key database |
ks | a KeySet which should contain changed keys, otherwise nothing is done |
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
- meta-names will be rejected (error 104)
- empty/invalid (error 105)
|
- Return values
-
1 | on success |
0 | if nothing had to be done, no changes in KDB |
-1 | on 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