Enumerations | |
enum | backend_t { KDB_BE_OPEN = 1, KDB_BE_CLOSE = 1<<1, KDB_BE_GET = 1<<2, KDB_BE_SET = 1<<3, KDB_BE_VERSION = 1<<4, KDB_BE_DESCRIPTION = 1<<5, KDB_BE_AUTHOR = 1<<6, KDB_BE_LICENCE = 1<<7, KDB_BE_END = 0 } |
Functions | |
KDB * | kdbBackendExport (const char *backendName,...) |
int | kdbOpen_backend (KDB *handle) |
int | kdbClose_backend (KDB *handle) |
ssize_t | kdbGet_backend (KDB *handle, KeySet *returned, const Key *parentKey) |
ssize_t | kdbSet_backend (KDB *handle, KeySet *returned, const Key *parentKey) |
KDBEXPORT (backend) |
Since version 0.7.0 Elektra can have multiple storage backends, called just backends henceforth, at once for different purposes.
The other KDB methods are higher level. They use the above methods to do their job, and generally don't have to be reimplemented for a different backend, but there might be a solution to do so for higher performance in future. kdbh* methods are for access to the internals of KDB, which will be passed to all functions.
#include <kdbbackend.h>
Don't include kdb.h, it will be automatically included and some macros will avoid redefining structs where you have more insight from a backend than you would normally have. Additionally you get the declaration of all functions described here, except the one you have to implement.
A backends is guaranteed to be loaded whenever calling kdbGet() or kdbSet() requires the backend, but may already be loaded at kdbOpen(). It might be loaded explizit by kdbMount() at any time after kdbOpen(). Backends get a chance to initialize by calling kdbOpen_backend() whenever they are loaded.
Using kdbUnmount() a backend may closed during runtime. All backends will be closed when kdbClose() is called. Backends might be unloaded after some time of inactivity or other reasons. After loading backends get a chance to cleanup by calling kdbClose_backend().
That means it is not guaranteed that the backend live the whole time nor it will be loaded only one time. A tactic to handle this well is to build stateless backends referring to kdbGet_backend() and kdbSet_backend(). That means that there is no more information present than in the storage itself. Be aware that you must not have any global variables in your backend. Read more about that in kdbOpen_backend(). But to be stateless you also have to consider not to store any other than caching information into kdbhGetBackendData(). I repeat: it must be possible to restore everything dynamically stored without exception.
A backend is defined by a single name, for example BACKENDNAME
, that causes libelektra.so look for its library as libelektra-BACKENDNAME.so
.
// // This is my implementation for an Elektra backend storage. // // To compile it: // $ cc -fpic `pkg-config --cflags elektra` -o myback.o -c myback.c // $ cc -shared -fpic `pkg-config --libs elektra` -o libelektra-myback.so myback.o // // To use it: // $ preload mount myback system/myback myback /tmp/nofile // $ kdb ls system/myback // $ kdb set system/myback/key "value" // $ kdb get system/myback/key // #include <kdbbackend.h> #define BACKENDNAME "backend" int kdbOpen_backend(KDB *handle) {...} int kdbClose_backend(KDB *handle) {...} int kdbGet_backend(KDB handle, KeySet *returned, Key *key) {...} int kdbSet_backend(KDB handle, KeySet *returned, Key *key) {...} KDBEXPORT(backend) { return kdbBackendExport(BACKENDNAME, KDB_BE_OPEN, &kdbOpen_backend, KDB_BE_CLOSE, &kdbClose_backend, KDB_BE_GET, &kdbGet_backend, KDB_BE_SET, &kdbSet_backend, KDB_BE_END); }
Don't copy above example out, use src/backends/template, it does compile as-is and does some initialization and cleanup already.
Elektra source code tree includes several backend implementations https://svn.libelektra.org/svn/elektra/trunk/src/backends/ that can also be used as a reference.
It is allowed to provide additional information, even if you declared you don't have it. If you declare that you are capable of doing something, you must provide it without exceptions.
The very related method keySetDir() sets the executable bits of mode. Even if your backend does not support mode, it might support directories, meaning that keys have the mode 0664 or 0775 for directories. Declaring kdbcGetnoDir() means that the backend is flat, no key will be true for keyIsDir() and so can't have any subkeys.
Using keySetRaw() does not set the type, be sure to use keySetType() afterwards. This can be KEY_TYPE_STRING and KEY_TYPE_BINARY or any other type in type_t, leading to same results as explained above, but also any other number in the range of type_t. Declare kdbcGetnoTypes() when your backend does not support arbitrary types.
enum backend_t |
Switches to denote the backend methods. Used in calls to kdbBackendExport().
KDB_BE_OPEN | Next arg is backend for kdbOpen() |
KDB_BE_CLOSE | Next arg is backend for kdbClose() |
KDB_BE_GET | Next arg is backend for kdbGet() |
KDB_BE_SET | Next arg is backend for kdbSet() |
KDB_BE_VERSION | Next arg is char * for Version |
KDB_BE_DESCRIPTION | Next arg is char * for Description |
KDB_BE_AUTHOR | Next arg is char * for Author |
KDB_BE_LICENCE | Next arg is char * for Licence |
KDB_BE_END | End of arguments |
KDB* kdbBackendExport | ( | const char * | backendName, | |
... | ||||
) |
This function must be called by a backend's kdbBackendFactory() to define the backend's methods that will be exported.
See KDBEXPORT() how to use it for backends.
The order and number of arguments are flexible (as in keyNew() and ksNew()) to let libelektra.so evolve without breaking its ABI compatibility with backends. So for each method a backend must export, there is a flag defined by backend_t. Each flag tells kdbBackendExport() which method comes next. A backend can have no implementation for a few methods that have default inefficient high-level implementations and to use these defaults, simply don't pass anything to kdbBackendExport() about them.
backendName | a simple name for this backend |
int kdbClose_backend | ( | KDB * | handle | ) |
Finalize the backend. Called prior to unloading the backend dynamic module. Should ensure that no functions or static/global variables from the module will ever be accessed again.
Make sure to free all memory that your backend requested at runtime.
Specifically make sure to capDel() all capabilites and free your backendData in kdbhGetBackendData().
After this call, libelektra.so will unload the backend library, so this is the point to shutdown any affairs with the storage.
handle | contains internal information of opened key database |
KDBEXPORT | ( | backend | ) |
All KDB methods implemented by the backend can have random names, except kdbBackendFactory(). This is the single symbol that will be looked up when loading the backend, and the first method of the backend implementation that will be called.
Its purpose is to publish the exported methods for libelektra.so. The implementation inside the provided skeleton is usually enough: simply call kdbBackendExport() with all methods that must be exported.
The first paramter is the name of the backend. Then every backend must have: KDB_BE_OPEN
, KDB_BE_CLOSE
, KDB_BE_GET
and KDB_BE_SET
You might also give following information by char *: KDB_BE_VERSION
, KDB_BE_AUTHOR
, KDB_BE_LICENCE
and KDB_BE_DESCRIPTION
You must use static "char arrays" in a read only segment. Don't allocate storage, it won't be freed.
With capability you can get that information on runtime from any backend with kdbGetCapability().
The last parameter must be KDB_BE_END
.
kdbOpenBackend()
ssize_t kdbGet_backend | ( | KDB * | handle, | |
KeySet * | returned, | |||
const Key * | parentKey | |||
) |
Retrieve information from a permanent storage to construct a keyset.
The keyset returned
needs to be filled with information so that the application using elektra can access it. See the live cycle of a comment to understand:
kdbGet_backend(KDB *handle, KeySet *returned, Key *parentKey) { // the task of kdbGet_backend is to retrieve the comment out of the permanent storage Key *key = keyDup (parentKey); // generate a new key to hold the information char *comment; loadfromdisc (comment); keySetComment (key, comment, size); // set the information ksAppendKey(returned, key); } // Now return to kdbGet int kdbGet(KDB *handle, KeySet *keyset, Key *parentKey, options) { kdbGet_backend (handle, keyset, 0); // postprocess the keyset and return it } // Now return to usercode, waiting for the comment void usercode (Key *key) { kdbGet (handle, keyset, parentKey, 0); key = ksCurrent (keyset, key); // lookup the key from the keyset keyGetComment (key); // now the usercode retrieves the comment }
returned
need to be fetched from permanent storage and stored in the key. So this specification needs to give an exhaustive list of information present in a key.all
keys with the flag KEY_FLAG_SYNC set.may
has keys with the flag KEY_FLAG_STAT set.The caller kdbGet() will make sure that afterwards you were called, whenever the user requested it with the options, that:
returned
has the parentKey
and all keys direct below (keyIsDirectBelow()) with all information from the storage. Make sure to return all keys, all directories and also all hidden keys. If some of them are not wished, the caller kdbGet() will drop these keys, see above.ssize_t kdbGet_backend(KDB *handle, KeySet *update, const Key *parentKey) { Key * current; KeySet *returned = ksNew(ksGetSize(update)*2, KS_END); find_key (parentKey); current = keyDup (parentKey); if (keyNeedStat(parentKey)) { current = stat_key(current); } else { current = fetch_key(current); } clear_bit (KEY_FLAG_SYNC, current->flags); ksAppendKey(returned, current); while ((current = next_key()) != 0) { // search if key was passed in update by caller Key * tmp = ksLookup (update, current, KDB_O_WITHOWNER|KDB_O_POP); if (tmp) current = tmp; // key was passed, so use it if (keyNeedStat(parentKey) || keyNeedStat(current)) { current = stat_key (current); set_bit (KEY_FLAG_STAT, current->flags); } else { current = fetch_key(current); } clear_bit (KEY_FLAG_SYNC, current->flags); ksAppendKey(returned, current); // TODO: delete lookup key } if (error_happened()) { errno = restore_errno(); return -1; } ksClear (update); // the rest of update keys is not in storage anymore ksAppend(update, returned); // append the keys ksDel (returned); return nr_keys(); }
parentKey
and add it and then search all keys below and add them too, of course with all requested information (which is only depended on keyNeedStat()).parentKey
. If the backend supports a less time-consuming method to just get names and metadata, implement it, otherwise declare kdbcGetnoStat().
The implementation works as follows: When the parentKey
has keyNeedStat() set, all keys need to be stated instead of getting them. So the keys you ksAppendKey() don't have a value nor a comment and make sure that KEY_FLAG_SYNC is not set, but keyNeedStat() must be set for all keys which are only stated.
The keys in returned
may already have keyNeedStat() set. These keys must keep the status keyNeedStat() and you don't need to get the value and comment. See the example above for code.
returned
KeySet. There are following possibilities:parentKey
) and you know that the key has changed. You need to fully retrieve the key and remove the KEY_FLAG_SYNC flag.parentKey
has keyNeedStat(). You just need to stat the key. Make sure that KEY_FLAG_SYNC is not set, but KEY_FLAG_STAT needs to be set. Append the key to returned
.parentKey
keyNeedStat() is false. You need to fully retrieve the key out of storage, clear KEY_FLAG_STAT and KEY_FLAG_SYNC and ksAppendKey() it to the returned
keyset.
The only valid call for your backend is then that parentKey
equals the mountpoint
. For all other parentKey
you must, add nothing and just return 0.
if (strcmp (keyName(kdbhGetMountpoint(handle)), keyName(parentKey))) return 0;
If the parentKey
is your mountpoint you will of course fetch all keys, and not only the keys direct below the parentKey
. So returned
is valid iff:
If any calls you use change errno, make sure to restore the old errno.
handle | contains internal information of opened key database | |
returned | contains a keyset where the function need to append the keys got from the storage. There might be also some keys inside it, see conditions. You may use them to support efficient updating of keys, see Updating. | |
parentKey | contains the information below which key the keys should be gotten. |
-1 on failure, the current key in returned shows the position. In normal execution cases a positive value will be returned. But in some cases you are not able to get keys and have to return -1. If you declare kdbcGetnoError() you are done, but otherwise you have to set the cause of the error. (Will be added in 0.7.1)
int kdbOpen_backend | ( | KDB * | handle | ) |
Initialize the backend. This is the first method kdbOpenBackend() calls after dynamically loading the backend library.
This method is responsible of:
If your backend does not support all aspects described in kdbGet_backend() and kdbSet_backend() you need capabilities to export this information. Per default you declare to be fully compliant to the specification given here, to change it get a pointer to KDBCap structure by using kdbhGetCapability().
You may also read the configuration you can get with kdbhGetConfig() and transform it into other structures used by your backend.
But be aware that you don't have any global variables. If you do your backend will not be threadsafe. You can use kdbhSetBackendData() and kdbhGetBackendData() to store and get any information related to your backend.
The correct substitute for global variables will be:
struct _GlobalData{ int global; }; typedef struct _GlobalData GlobalData; int kdbOpen_backend(KDB *handle) { PasswdData *data; data=malloc(sizeof(PasswdData)); data.global = 20; kdbhSetBackendData(handle,data); }
Make sure to free everything in kdbClose_backend().
handle | contains internal information of opened key database |
ssize_t kdbSet_backend | ( | KDB * | handle, | |
KeySet * | returned, | |||
const Key * | parentKey | |||
) |
Store a keyset permanently.
This function does everything related to set and remove keys in a backend. There is only one function for that purpose to make implementation and locking much easier.
The keyset returned
was filled in with information from the application using elektra and the task of this function is to store it in a permanent way so that a subsequent call of kdbGet_backend() can rebuild the keyset as it was before. See the live cycle of a comment to understand:
void usercode (Key *key) { keySetComment (key, "mycomment"); // the usercode stores a comment for the key ksAppendKey(keyset, key); // append the key to the keyset kdbSet (handle, keyset, 0, 0); } // so now kdbSet is called int kdbSet(KDB *handle, KeySet *keyset, Key *parentKey, options) { // find appropriate backend kdbSet_backend (handle, keyset, 0); // the keyset with the key will be passed to this function } // so now kdbSet_backend(), which is the function described here, is called kdbSet_backend(KDB *handle, KeySet *keyset, Key *parentKey) { // the task of kdbSet_backend is now to store the comment Key *key = ksCurrent (keyset); // get out the key where the user set the comment before char *comment = allocate(size); keyGetComment (key, comment, size); savetodisc (comment); }
returned
need to be stored permanetly. So this specification needs to give an exhaustive list of information present in a key.
returned
holds all keys which must be saved permanently for this keyset. The keyset is sorted and rewinded. All keys having children must be true for keyIsDir().
The parentKey
is the key which is the ancestor for all other keys in the keyset. The first key of the keyset returned
has the same keyname. The parentKey is below the mountpoint, see kdbhGetMountpoint().
The caller kdbSet will fulfill following parts:
returned
need to be stored permanently.returned
is stored permanently.Lock your permanent storage in an exclusive way, no access of a concurrent kdbSet_backend() or kdbGet_backend() is possible and these methods block until the function has finished. Otherwise declare kdbcGetnoLock().
handle | contains internal information of opened key database | |
returned | contains a keyset with relevant keys | |
parentKey | contains the information where to set the keys |
Return 0 on success with no changed key in database
Return -1 on failure.