Hope you have been enjoying my tutorial on writing plug-ins so far. In Part 1 we covered the basic overview of a plug-in. Part 2 covered a plug-in’s contract and the best way to write one. Now, for Part 3 we are going to cover the meat of a plug-in, the actual coding. As you should know from reading Part 1, there are five main functions used for plug-ins, elektraPluginOpen, elektraPluginGet, elektraPluginSet, ELEKTRA_PLUGIN_EXPORT(Plugin), where Plugin should be replaced with the name of your plug-in. We are going to start this tutorial by focusing on the elektraPluginGet because it usually is the most critical function.
As we discussed before,
elektraPluginGet is the function responsible for turning information from a file into a usable KeySet. This function usually differs pretty greatly between each plug-in. This function should be of type int, it returns 0 on success or another number on an error. The function will take in a Key, usually called parentKey which contains a string containing the path to the file that is mounted. For instance, if you run the command
kdb mount /etc/linetest system/linetest line then keyString(parentKey) should be equal to “/etc/linetest”. At this point, you generally want to open the file so you can begin saving it into keys. Here is the trickier part to explain. Basically, at this point you will want to iterate through the file and create keys and store string values inside of them according to what your plug-in is supposed to do. I will give a few examples of different plug-ins to better explain.
My line plug-in was written to read files into a KeySet line by line using the newline character as a delimiter and naming the keys by their line number such as (#1, #2, .. #_22) for a file with 22 lines. So once I open the file given by parentKey, every time a I read a line I create a new key, let’s call it new_key using dupKey(parentKey). Then I set new_keys’s name to lineNN (where NN is the line number) using
keyAddBaseName and store the string value of the line into the key using
keySetString. Once the key is initialized, I append it to the KeySet that was passed into the elektraPluginGet function, let’s call it returned for now, using
ksAppendKey(return, new_key). Now the KeySet will contain new_key with the name lineNN properly saved where it should be according to the
kdb mount command (in this case, system/linetest/lineNN), and a string value equal to the contents of that line in the file. MY plug-in repeats these steps as long as it hasn’t reached end of file, thus saving the whole file into a KeySet line by line.
The simpleini plug-in works similarly, but it parses for ini files instead of just line-by-line. At their most simple level, ini files are in the format of
name=value with each pair taking one line. So for this plug-in, it makes a lot of sense to name each Key in the KeySet by the string to the left of the “=” sign and store the value into each key as a string. For instance, the name of the key would be “name” and
keyGetString(name) would return “value”.
As you may have noticed, simpleini and line plug-ins work very similarly. However, they just parse the files differently. The simpleini plug-in parses the file in a way that is more natural to ini file (setting the key’s name to the left side of the equals sign and the value to the right side of the equals sign.) The
elektraPluginGet function is the heart of a storage plug-in, its what allows Elektra to store configurations in it’s database. This function isn’t just run when a file is first mounted, but whenever a file gets updated, this function is run to update the Elektra Key Database to match.
We also gave a brief overview of
elektraPluginSet function. This function is basically the opposite of
elektraPluginGet reads information from a file into the Elektra Key Database,
elektraPluginSet writes information from the database back into the mounted file.
First have a look at the signature of
elektraLineSet(Plugin *handle ELEKTRA_UNUSED, KeySet *toWrite, Key *parentKey)
Lets start with the most important parameters, the KeySet and the parentKey. The KeySet supplied is the KeySet that is going to be persisted in the file. In our case it would contain the Keys representing the lines. The parentKey is the topmost Key of the KeySet at serves several purposes. First, it contains the filename of the destination file as its value. Second, errors and warnings can be emitted via the parentKey. We will discuss error handling in more detail later. The Plugin handle can be used to persist state information in a threadsafe way with
elektraPluginSetData. As our plugin is not stateful and therefore does not use the handle, it is marked as unused in order to supress compiler warnings.
Basically the implementation of
elektraLineSet can be described with the following pseudocode:
open the file
ELEKTRA_SET_ERROR(74, parentKey, keyString(parentKey));
for each key
write the key value together with a newline
close the file
The fullblown code can be found at https://github.com/ElektraInitiative/libelektra/blob/master/src/plugins/line/line.c
As you can see, all
elektraLineSet does is open a file, take each Key from the KeySet (remember they are named #1, #2 … #_22) in order, and write each key as it’s own line in the file. Since we don’t care about the name of the Key in this case (other than for order), we just write the value of
keyString for each Key as a new line in the file. That’s it. Now, each time the mounted KeySet is modified,
elektraPluginSet will be called and the mounted file will be updated.
We haven’t discussed
ELEKTRA_SET_ERROR yet. Because Elektra is a library, printing errors to stderr wouldn’t be a good idea. Instead, errors and warnings can be appended to a key in the form of metadata. This is what
ELEKTRA_SET_ERROR does. Because the parentKey always exists even if a critical error occurres, we append the error to the parentKey. The first parameter is an id specifying the general error that occurred. A listing of existing errors together with a short description and a categorization can be found at https://github.com/ElektraInitiative/libelektra/blob/master/src/liberror/specification. The third parameter can be used to provide additional information about the error. In our case we simply supply the filename of the file that caused the error. The kdb tools will interprete this error and print it in a pretty way. Notice that this can be used in any plugin function where the parentKey is available.
elektraPluginClose functions are not commonly used for storage plug-ins, but they can be useful and are worth reviewing.
elektraPluginOpen function runs before
elektraPluginGet and is useful to do initialization if necessary for the plug-in. On the other hand
elektraPluginClose is run after other functions of the plug-in and can be useful for freeing up resources.
The last function, one that is always needed in a plug-in, is
ELEKTRA_PLUGIN_EXPORT. This functions is responsible for letting Elektra know that the plug-in exists and which methods it implements. The code from my line function is a good example and pretty self-explanatory:
There you have it! This is the last part of my tutorial on writing a storage plug-in for Elektra. Hopefully you now have a good understanding of how Elektra plug-ins work and you’ll be able to add some great functionality into Elektra through development of new plug-ins. I hope you enjoyed this tutorial and if you have any questions just leave a comment!
Ian S. Donnelly