diff --git a/doc/Doxyfile b/doc/Doxyfile index 993ce30011f..d8c073993e2 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -2039,7 +2039,7 @@ PREDEFINED = "ELEKTRA_UNUSED=" # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = "ELEKTRA_NOTIFICATION_TYPE_DECLARATION" # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have diff --git a/doc/decisions/deferred_plugin_calls.md b/doc/decisions/deferred_plugin_calls.md index 0d79775031e..b9c6e0e4406 100644 --- a/doc/decisions/deferred_plugin_calls.md +++ b/doc/decisions/deferred_plugin_calls.md @@ -15,8 +15,9 @@ required to be able to defer these calls until the plugins are loaded. For example when setting I/O bindings with `elektraIoSetBinding()` the exported function `setIoBinding` is called for all globally mounted plugins. -Since global mounting is implemented using the "list" plugin the exported -functions from the plugins are unavailable. +Since global mounting is implemented using the "list" plugin which uses +lazy-loading for its plugins the exported functions from the plugins are +unavailable. Other examples are the "dini" and "multifile" plugins which use multiple plugins to support different file formats. @@ -31,7 +32,8 @@ These plugins also "hide" functions exported by encapsulated plugins. ## Assumptions -1. The called functions do not return a value (e.g. `set`, `open`, `close`, ...) +1. The called functions do not return a value (e.g. `set`, `open`, `close`, ...). + Callbacks can be used as return channel (see "Implications") ## Considered Alternatives @@ -67,14 +69,14 @@ These plugins also "hide" functions exported by encapsulated plugins. Encapsulating plugins export a function called `deferredCall` with the declaration -`void ElektraDeferredCall (Plugin * plugin, char * name, KeySet * parameters)`. +`void elektraDeferredCall (Plugin * plugin, char * name, KeySet * parameters)`. Encapsulating plugins shall save multiple deferred calls and call the exported functions specified by `name` passing the `parameters` KeySet when a plugin is initialized in the same order as received. Plugins that support deferred calls shall have the following declaration for their functions -`void ElektraDeferredCallable (Plugin * plugin, KeySet * parameters)`. +`void somePluginFunction (Plugin * plugin, KeySet * parameters)`. The calling developer is responsible for ensuring that the called functions have a compatible declaration. Encapsulated plugins that do not export a specified function name are omitted. @@ -89,15 +91,15 @@ breaking callers. The called function receive their parameters via a KeySet. While called functions could return data using the `parameters` KeySet (or a -seperate KeySet) there is no defined moment when the data can be collected. +separate KeySet) there is no defined moment when the data can be collected. Defining such a moment would break the lazy-loading constraint. It is recommended to use callbacks passed as `parameters`. -The callback function declaration is not affected by this decision. +Callback function declarations are not limited by this decision. ## Related decisions -- Elektra's invoke functionality will be extended to also allow use of deferred -calls with new functions: +- Elektra's invoke functionality will be extended to also allow us to use + deferred calls with new functions: - `int elektraInvokeFunctionDeferred (ElektraInvokeHandle * handle, const char * elektraPluginFunctionName, KeySet * ks)` which defers a call if the plugin exports `deferredCall`. @@ -106,7 +108,7 @@ calls with new functions: - Functions supporting deferred calls should allow for multiple calls (i.e. they should be idempotent). - This leaves state at affected plugins and does avoids duplicating state (e.g. + This leaves state at affected plugins and does avoid duplicating state (e.g. "was this function called for this plugin before?") in encapsulating plugins. diff --git a/doc/images/notifications.svg b/doc/images/notifications.svg index 9c6412f86be..9daaf5f5e16 100644 --- a/doc/images/notifications.svg +++ b/doc/images/notifications.svg @@ -10,8 +10,8 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="143.07768mm" - height="92.272842mm" - viewBox="0 0 143.07768 92.272842" + height="82.787338mm" + viewBox="0 0 143.07768 82.787338" version="1.1" id="svg8" inkscape:version="0.92.1 r15371" @@ -128,15 +128,15 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.98994949" - inkscape:cx="352.43636" - inkscape:cy="316.21597" + inkscape:zoom="1.979899" + inkscape:cx="270.38302" + inkscape:cy="145.14869" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="true" inkscape:snap-bbox="false" - inkscape:measure-start="225,624" - inkscape:measure-end="348,747" + inkscape:measure-start="264.383,186.383" + inkscape:measure-end="264.383,132.383" inkscape:snap-global="true" inkscape:snap-text-baseline="true" showguides="false" @@ -153,8 +153,8 @@ type="xygrid" id="grid4508" empspacing="3" - originx="0.10133949" - originy="-201.51114" /> + originx="0.10133948" + originy="-209.44865" /> @@ -172,14 +172,21 @@ inkscape:label="Ebene 1" inkscape:groupmode="layer" id="layer1" - transform="translate(0.1013395,-3.2160262)"> + transform="translate(0.1013395,-4.7640087)"> + + width="34.924999" + height="11.112499" + x="101.59999" + y="8.0750084" + style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.15000001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.27200001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> - + y="54.112507" /> Application - Internal-NotificationPlugin - - Redis - I/O - Library(e.g. glib, libuv) + x="119.06995" + y="15.011325" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93888889px;line-height:0.64999998;font-family:'Latin Modern Roman';-inkscape-font-specification:'Latin Modern Roman, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:0.26458332;" + id="tspan4584">Event Loop I/O-Wrapper Interface - + id="tspan4584-2">I/O Interface NotificationWrapper - - DBus - + id="tspan4676">Library ZeroMQ - I/O-Wrapper Binding + id="tspan4584-2-27">I/O Binding Transport Plugins + + - - - - + y="68.400063" + style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.21698041;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> Emergent BehaviorLibrary + id="tspan5063">Logging Plugins - libelektra + id="tspan4563-6-2">Elektra + d="m 119.79282,27.890472 1.92881,-3.10419 h -1.07156 v -4.856499 h 1.07156 l -1.92881,-3.104181 -1.92881,3.104181 h 1.07156 v 4.856499 h -1.07156 z" + id="path4542-6-5-0-0-7-9-4" + sodipodi:nodetypes="ccccccccccc" /> + + Notification API diff --git a/doc/news/_preparation_next_release.md b/doc/news/_preparation_next_release.md index c38a3f6ae73..3ebf4ec4f5f 100644 --- a/doc/news/_preparation_next_release.md +++ b/doc/news/_preparation_next_release.md @@ -40,7 +40,7 @@ You can also read the news [on our website](https://www.libelektra.org/news/0.8. - Type system preview - Chef Cookbook - Elektra Web 1.6 - +- Notifications ### Type system preview @@ -133,6 +133,37 @@ Try it out now on: http://webdemo.libelektra.org/ Thanks to Daniel Bugl. +### Notifications + +Elektra's notification feature which allows applications to keep persistent +configuration settings in sync with the key database and other applications was +greatly improved with this release: + +- The [notification API](https://doc.libelektra.org/api/current/html/group__kdbnotification.html) + now supports more types and has improved support for callbacks. +- With the addition of the [zeromqsend](https://www.libelektra.org/plugins/zeromqsend) + and [zeromqrecv](https://www.libelektra.org/plugins/zeromqrecv) plugins + together with the [hub-zeromq](https://www.libelektra.org/tools/hub-zeromq) + tool we have an alternative to the D-Bus transport plugins + ([dbus](https://www.libelektra.org/plugins/dbus) and + [dbusrecv](https://www.libelektra.org/plugins/dbusrecv)). +- The new asynchronous I/O binding for [ev](https://www.libelektra.org/bindings/io_ev) + is the third I/O binding - so notifications can be used in applications using + [glib](https://www.libelektra.org/bindings/io_glib), [uv](https://www.libelektra.org/bindings/io_uv) + or [ev](https://www.libelektra.org/bindings/io_ev). + If your application uses a different library please check out the + ["How to create your own I/O binding" section](https://www.libelektra.org/tutorials/notifications#how-to-create-your-own-i-o-binding) in the [notification tutorial](https://www.libelektra.org/tutorials/notifications). +- Notifications can be used to reload KDB after Elektra's configuration (e.g. + mountpoints or globally mounted plugins) has changed. + We added a [how-to to the notification tutorial](https://www.libelektra.org/tutorials/notifications#howto-reload-kdb-when-elektras-configuration-has-changed) + that explains the required steps and the ["notificationReload"](https://www.libelektra.org/examples/notificationreload) example with the complete code. + +More details can be [found](#zeromq-transport-plugins) [in](#misc) [this](#bindings) [news](#notifications). +Check out the updated [notification tutorial](https://www.libelektra.org/tutorials/notifications) +and notification examples ([polling](https://www.libelektra.org/examples/notificationpolling), +[async](https://www.libelektra.org/examples/notificationasync) and +[reload](https://www.libelektra.org/examples/notificationreload). + ## Plugins ### CCode @@ -367,7 +398,8 @@ Thanks to Daniel Bugl. - replaced strdup with elektraStrDup (for C99 compatibility) *(Markus Raab)* - You can now remove the basename of a key via the C++ API by calling `key.delBaseName()`. *(René Schwaiger)* - The function `elektraArrayGetNextKey` now uses `NULL` instead of the empty string as init value for the returned key. *(René Schwaiger)* - +- <> +- <> - <> ### pluginprocess @@ -393,6 +425,17 @@ Thanks to Daniel Bugl. It can be used to integrate the notification feature with applications based on [ev](http://libev.schmorp.de) main loops. *(Thomas Wahringer)* +## Notifications + +- The + [notification API](https://doc.libelektra.org/api/current/html/group__kdbnotification.html) + was extended. + The API now supports more types: `int`, `unsigned int`, + `long`, `unsigned long`, `long long`, `unsinged long long`, `float` and `double`. + It also supports all of Elektra's `kdb_*_t` types defined in `kdbtypes.h`. + Also contexts for callbacks were added and + `elektraNotificationRegisterCallbackSameOrBelow()` allows for notifications + for the registered key or below. *(Thomas Wahringer)* ## Tools @@ -618,7 +661,10 @@ compiled against an older 0.8 version of Elektra will continue to work Following changes were made: - The C++ API was extended with delBaseName() -- <> +- `kdbtypes.h` now comes with support for C99 types +- We added the private headerfiles `kdbnotificationinternal.h`, `kdbioplugin.h`. *(Thomas Wahringer)* +- The I/O binding header files have been moved a new directory called `kdbio`. + For example, instead of including `elektra/kdbio_ev.h` users of the binding now include `elektra/kdbio/ev.h`. *(Thomas Wahringer)* - <> ## Website diff --git a/doc/tutorials/notifications.md b/doc/tutorials/notifications.md index d17fbc98a77..df66c209ec2 100644 --- a/doc/tutorials/notifications.md +++ b/doc/tutorials/notifications.md @@ -2,17 +2,7 @@ ## Preface -**Not all of the features described in this document are implemented yet.** - -Development state: - - - [x] internalnotification plugin (support for int and callback) - - [x] notification wrapper (support for int and callback) - - [X] transport plugin dbus - - [ ] transport plugin zeromq - - [ ] transport plugin redis - - [ ] internalnotification plugin & notfication wrapper (support for Elektra's basic types) - +**The features described in this document are experimental.** This document explains how notifications are implemented in Elektra and how they can be used by application developers. @@ -21,28 +11,32 @@ they can be used by application developers. Elektra's notification feature consists of several components. While sending and receiving notifications is implemented by plugins, -applications use APIs provided by wrappers in order to use different plugins. +applications use the notification API in order to use different plugins. -A -[wrapper for notifications](https://doc.libelektra.org/api/current/html/group__kdbnotification.html) -provides the API for receiving and handling notifications. -A [wrapper for I/O operations](https://doc.libelektra.org/api/current/html/group__kdbio.html) +The +[notification API](https://doc.libelektra.org/api/current/html/group__kdbnotification.html) +implemented by the `elektra-notification` library allows receiving and handling +notifications. +An [I/O abstraction layer](https://doc.libelektra.org/api/current/html/group__kdbio.html) allows asynchronous notification processing by compatible plugins. -The I/O wrapper consists of an *interface* used by transport plugins and -multiple implementations of that interface called *I/O bindings*. -A I/O binding implements the actual I/O management functions for a specific -I/O management library. +The abstraction layer consists of an *interface* used by transport plugins and +different implementations of that interface called *I/O bindings*. +An I/O binding implements the actual I/O management functions for a specific +event loop API. Applications typically use one I/O binding but can also use none or multiple I/O bindings. +For more on I/O bindings see the +[API documenation](https://doc.libelektra.org/api/current/html/group__kdbio.html). -Transport plugins exchange notifications via different protocols like D-Bus, -Redis and ZeroMQ. -For each type of transport there are two types of plugins: One for sending and -one for receiving notifications. +Transport plugins exchange notifications via different protocols like D-Bus or +ZeroMQ. +For each type of transport there are typically two types of plugins: One for +sending and one for receiving notifications. Developers do not interact with those plugins directly. The underlying transports are transparent to them. -An internal-notification plugin implements notification handling functions and +The "internalnotification" plugin implements notification handling functions and feeds back configuration changes from within the application. +It is only used internally by the `elektra-notification` library. ![Overview](../images/notifications.svg) @@ -53,46 +47,40 @@ notifications but not whether notifications are sent or which transport is used. How notifications are sent is specified in the *notification configuration* by the system operator. -## Notification configuration +## Notification Configuration System operators can mount the desired transport plugins and configure them -(e.g. set channel, host, port and credentials) either globally or when mounting -a configuration file. +(e.g. set channel, host, port and credentials) globally. They need to mount both sending and receiving plugins in order to use a transport. ```sh -kdb mount file.ini system/example ini dbussend dbusreceive +kdb global-mount dbus announce=once dbusrecv ``` +Plugins usable as transport plugin are marked with `notification` on the +[plugin page](https://www.libelektra.org/plugins/readme#notification-and-logging). + ## How to integrate an I/O binding and send notifications asynchronously Developers do not need to change their programs in order to start sending notifications. -However without the integration of an I/O binding notifications are sent -synchronously which will block normal program execution. +However without the integration of an I/O binding notifications *may* be sent +synchronously which would block normal program execution. For programs without time constraints (e.g. CLI programs) this may not be important, but for GUIs or network services this will have negative impact. -Since many different I/O management libraries exist (e.g. libuv, glib or libev) -the transport plugins use the I/O interface for their I/O operations. -Each I/O management library needs its own I/O binding. -Developers can also create their own I/O binding for the I/O management library -of their choice. -This is described in the last section. +The "zeromqsend" and "dbus" plugins do not block program execution for sending +as sending is handled asynchronously be the underlying libraries. -Each I/O binding has its own initialization function that creates a new -I/O binding and connects it to the I/O management library. -For this tutorial we will assume that libuv is used. -For details on how to use a specific binding please look at the bindings' -README.md in `src/bindings/io/`. +The following is a basic example of an application using Elektra extended by +the initialization of an I/O binding. ```C #include #include -#include -#include +#include #include @@ -100,7 +88,9 @@ void main (void) { KDB* repo; - // .. open KDB + // Open KDB + Key * key = keyNew ("/sw/myorg/myapp/#0/current", KEY_END); + KDB * kdb = kdbOpen (key); // Create libuv event loop uv_loop_t * loop = uv_default_loop (); @@ -108,33 +98,36 @@ void main (void) // Initialize I/O binding tied to event loop ElektraIoInterface * binding = elektraIoUvNew (loop); - // Use I/O binding for our kdb instance - elektraIoSetBinding (kdb, binding); + // Use I/O binding for our kdb instance + elektraIoSetBinding (kdb, binding); - // Initialize notification wrapper - elektraNotificationOpen (kdb); + // Normal application setup code ... // Start the event loop uv_run (loop, UV_RUN_DEFAULT); // Cleanup - elektraNotificationClose (kdb); - elektraIoBindingCleanup (binding); + kdbClose (kdb, key); + elektraIoBindingCleanup (binding); uv_loop_close (loop); } - -void someFunction (void) -{ - // notifications are sent asynchronously on kdbSet - // over configured transports using our uv event loop - kdbSet (repo, key, parentKey); -} ``` ## How to receive notifications -We extend the example from the previous section where we already created our -I/O binding and initialized the notification-wrapper. +Since many different I/O management libraries exist (e.g. libuv, glib or libev) +the transport plugins use the I/O interface for their I/O operations. +Each I/O management library needs its own I/O binding. +Developers can also create their own I/O binding for the I/O management library +of their choice. +This is described in the last section. + +Each I/O binding has its own initialization function that creates a new +I/O binding and connects it to the I/O management library. +For this tutorial we will assume that libuv 1.x is used. +For details on how to use a specific binding please look at available I/O +bindings on the [bindings page](https://www.libelektra.org/bindings/readme). + In order to handle change notifications a developer can either register a variable or a callback. @@ -142,36 +135,75 @@ variable or a callback. Values of registered variables are automatically updated when the value of the assigned key has changed. -In the following example we will register an integer variable: +In the following example we will register an integer variable. -```C -KDB * repo; +The following examples are shortened for tangibility. The complete code is available in +["notificationAsync" example](https://www.libelektra.org/examples/notificationasync). -// ... initialization of KDB and I/O binding +```C +#include +#include +#include +#include -Key * key = keyNew ("/sw/myorg/myprogram/#0/current/value", KEY_END); -int keyValue; +#include -int result = elektraNotificationRegisterInt (repo, key, &keyValue); -if (!result) +static void printVariable (ElektraIoTimerOperation * timerOp) { - printf ("could not register variable!\n"); - return; + int value = *(int *) elektraIoTimerGetData (timerOp); + printf ("\nMy integer value is %d\n", value); } -// repeatedly print variable -while (1) { - printf ("value is %d\n", keyValue); +void main (void) +{ + KDB* repo; + + // Open KDB + Key * key = keyNew ("/sw/myorg/myapp/#0/current", KEY_END); + KDB * kdb = kdbOpen (key); + + // Create libuv event loop + uv_loop_t * loop = uv_default_loop (); + + // Initialize I/O binding tied to event loop + ElektraIoInterface * binding = elektraIoUvNew (loop); + + // Use I/O binding for our kdb instance + elektraIoSetBinding (kdb, binding); + + // Initialize notification wrapper + elektraNotificationOpen (kdb); + + // Register "value" for updates + Key * registeredKey = keyNew ("/sw/myorg/myapp/#0/current/value", KEY_END); + int value; + elektraNotificationRegisterInt (repo, registeredKey, &value); + + // Create a timer to repeatedly print "value" + ElektraIoTimerOperation * timer = elektraIoNewTimerOperation (2000, 1, printVariable, &value); + elektraIoBindingAddTimer (binding, timer); + + // Get configuration + KeySet * config = ksNew(0, KS_END); + kdbGet (kdb, config, key); + printVariable (timer); // "value" was automatically updated + + // Start the event loop + uv_run (loop, UV_RUN_DEFAULT); - sleep(2); + // Cleanup + elektraNotificationClose (kdb); + kdbClose (kdb, key); + elektraIoBindingRemoveTimer (timer); + elektraIoBindingCleanup (binding); + uv_loop_close (loop); } ``` -After calling `elektraNotificationRegisterInt` the variable `keyValue` will be +After calling `elektraNotificationRegisterInt()` the variable `value` will be automatically updated if the key in the program above is changed by another program (e.g. by using the `kdb` CLI command). -For automatic updates to work transport plugins have to be in place either at -a mountpoint above the configuration or mounted globally. +For automatic updates to work transport plugins have to be mounted globally. ### Callbacks @@ -194,8 +226,10 @@ changed key needs further processing. #define ANSI_COLOR_RED "\x1b[31m" #define ANSI_COLOR_GREEN "\x1b[32m" -void setTerminalColor (Key * color) +void setTerminalColor (Key * color, void * context ELEKTRA_UNUSED) { + // context contains whatever was passed as 4th parameter + // to elektraNotificationRegisterCallback() char * value = keyString (color); if (strcmp (value, "red") == 0) @@ -214,29 +248,277 @@ int main (void) // ... initialization of KDB, I/O binding and notifications - Key * configBase = keyNew ("/sw/myorg/myprogram/#0/current", KEY_END); - Key * color = keyNew ("/sw/myorg/myprogram/#0/current/color", KEY_END); + Key * color = keyNew ("/sw/myorg/myapp/#0/current/color", KEY_END); + + // Re-Initialize on key changes + elektraNotificationRegisterCallback(repo, color, &setTerminalColor, NULL); + + // ... start loop, etc. +} +``` + +### How-To: Reload KDB when Elektra's configuration has changed + +This section shows how the notification feature is used to reload an +application's KDB instance when Elektra's configuration has changed. +This enables applications to apply changes to mount points or globally mounted +plugins without restarting. + +**Step 1: Register for changes to Elektra's configuration** - // Retrieve key from kdb - KeySet * ks = ksNew (10, KS_END); - kdbGet (repo, ks, configBase); - Key * key = ksLookup (ks, color, 0); - if (key) { - // Initialization - setTerminalColor (key); +To achieve reloading on Elektra configuration changes we register for changes +below the key `/elektra` using +`elektraNotificationRegisterCallbackSameOrBelow()`. + +```C +Key * elektraKey = keyNew ("/elektra", KEY_END); +elektraNotificationRegisterCallbackSameOrBelow (kdb, elektraKey, elektraChangedCallback, NULL)) +keyDel (elektraKey); +``` + +**Step 2: Create a function for reloading KDB** + +Since our application needs to repeatedly initialize KDB on +configuration changes we need to create a function which cleans +up and reinitializes KDB. + +```C +void initKdb (ElektraIoTimerOperation * timerOp ELEKTRA_UNUSED) +{ + if (kdb != NULL) + { + // Cleanup notifications and close KDB + elektraNotificationClose (kdb); + kdbClose (kdb, parentKey); } - // Re-Initialize on key changes - int result = elektraNotificationRegisterCallback(repo, color, &setTerminalColor); - if (!result) { - printf ("could not register callback!\n"); - return -1; + kdb = kdbOpen (parentKey); + elektraIoSetBinding (kdb, binding); + elektraNotificationOpen (kdb); + + // Code for registration from snippet before + Key * elektraKey = keyNew ("/elektra", KEY_END); + elektraNotificationRegisterCallbackSameOrBelow (kdb, elektraKey, elektraChangedCallback, NULL); + keyDel (elektraKey); + + // TODO: add application specific registrations + + // Get configuration + kdbGet (kdb, config, parentKey); +} +``` + +**Step 3: Handle configuration changes** + +The last step is to connect the registration for changes to the +(re-)initialization function. +Directly calling the function is discouraged due to tight coupling (see +guidelines in the next section) and also results in an application crash since +the notification API is closed while processing notification callbacks. +Therefore we suggest to add a timer operation with a sufficiently small interval +which is enabled only on configuration changes. +This timer will then call the initialization function. + +First, we create the timer in the main loop setup of the application. + +```C +// (global declaration) +ElektraIoTimerOperation * reload; + +// main loop setup (e.g. main()) +// the timer operation is disabled for now and +// will reload KDB after 100 milliseconds +reload = elektraIoNewTimerOperation (100, 0, initKdb, NULL); +elektraIoBindingAddTimer (binding, reload); +``` + +Now we add the callback function for the registration from step 1: + +```C +void elektraChangedCallback (Key * changedKey, void * context) +{ + // Enable operation to reload KDB as soon as possible + elektraIoTimerSetEnabled (reload, 1); + elektraIoBindingUpdateTimer (reload); +} +``` + +Finally we disable the timer in the initialization function: + +```C +void initKdb (void) +{ + // Stop reload task + elektraIoTimerSetEnabled (reload, 0); + elektraIoBindingUpdateTimer (reload); + + if (kdb != NULL) + { + // Cleanup notifications and close KDB + elektraNotificationClose (kdb); + kdbClose (kdb, parentKey); } - // ... start loop, etc. + // ... } ``` +By correct application of these three steps any application can react to changes +to Elektra's configuration. +The snippets above omit error handling for brevity. The complete code including +error handling is available in the +["notification reload" example](https://www.libelektra.org/examples/notificationreload). +This example also omits globals by passing them as user data using the +`elektraIo*GetData()` functions. + +## Emergent Behavior Guidelines + +When applications react to configuration changes made by other applications this +can lead to *emergent behavior*. +We speak of emergent behavior when the parts of a system are functioning as +designed but an unintended, unexpected or unanticipated behavior at system level +occurs. + +For example, take the following sequence of events: + +1. application `A` changes its configuration +2. application `B` receives a notification about the change from `A` and updates its configuration + + Given these two steps the sequence could be a case of *wanted* emergent behavior: + Maybe application `B` keeps track of the number of global configuration + changes. + Now consider adding the following events to the sequence: + +3. application `A` receives a notification about the change from `B` and changes its configuration +4. *continue at step 2* + +The additional step causes an infinite cycle of configuration updates +which introduces *unwanted* behavior. + +When designing a system it is desirable to use components with predictable and +well-defined behavior. +As a system grows larger and gets more *complex* unpredictable behavior +emerges that was neither intended by the system designer nor by the designer of +the components. +This system behavior is called *emergent behavior* if it cannot be explained +from its components but only from analysis of the whole system. + +Emergent behavior can be beneficial for a system, for example, useful cooperation +in an ant colony but it also has disadvantages. +Systems that bear *unwanted* emergent behavior are difficult to manage and +experience failures in the worst case. +This kind of unwanted emergent behavior is called +[*emergent misbehavior*](http://www.hpl.hp.com/techreports/2006/HPL-2006-2.html). +Examples of emergent misbehavior are traffic jams or the +[Millenium Footbridge](https://researchcourse.pbworks.com/f/structural+engineering.pdf) +[incident in London](https://www.sciencedaily.com/releases/2005/11/051103080801.htm). + +An evaluation of Elektra's notification feature shows that it can exhibit the +following symptoms: + +- **Synchronization** occurs due to the shared notification medium. + Multiple applications receive a notification at the same time and execute + their configuration update logic. + In turn shared resources like hard disk or CPU become overutilized. +- **Oscillation** occurs when applications are reacting to configuration changes + by other applications. +- In the worst case oscillation results in **livelock** when the frequency of + configuration updates becomes so high that applications are only executing + configuration update logic. +- **Phase change** is a sudden change of the system behavior in reaction to a + minor change (e.g. incremental change of a configuration setting). + Phase change is introduced by application logic. + +Building on these findings we will now present guidelines for preventing +emergent misbehavior when using the notification API and callbacks in particular. + +### Guideline 1: Avoid callbacks +> Most of the guidelines are related to callbacks. +> With normal use of the notification API emergent behavior should not occur. + +Callbacks couple an application temporally to configuration changes of other +applications or instances of the same application. +This observation is the basis for [Guidelines 1](#guideline-1-avoid-callbacks), +[2](#guideline-2-wait-before-reacting-to-changes) and [3](#guideline-3-avoid-updates-as-reaction-to-change). +While it is possible with registered variables to check for configuration +changes at regular time intervals and react to changes the coupling is not as +tight as with callbacks. + +### Guideline 2: Wait before reacting to changes +> Waiting after receiving a notification decouples an application from changes and reduces the risk for unwanted ***synchronization***. + +In applications where applying changes has impact on resource usage (e.g. CPU or +disk) applying a time delay as suggested by this Guideline is a +sensible choice. +But this guideline is not only limited to these applications. + +Generally waiting before reacting to changes reduces the risk for unwanted +synchronization by decoupling the application temporally. +Waiting can be implemented using random time delays which further promotes +decoupling since applications react at different points in time to changes. +Waiting can also be implemented using a flag: +Callbacks set the flag and when the control flow is in the main loop again, the +pending updates are applied and the flag is cleared. + +### Guideline 3: Avoid updates as reaction to change +> Avoid changing the configuration as reaction to a change. +> This reduces the risk for unwanted ***oscillation***. + +While this guideline does not forbid updating the key database using `kdbSet()` +in a callback it advises to avoid it. +If we recall the example from before we see how updating as reaction to change +leads to unwanted oscillation. +If necessary, the function `kdbSet()` should be temporally decoupled as +suggested in [Guideline 2](#guideline-2-wait-before-reacting-to-changes). + +This guideline applies especially to callbacks but is also relevant when variables +are polled for changes by the application. + +### Guideline 4: Do not use notifications for synchronization +> Applications should not use notifications for synchronization as this can lead to ***phase change***. + +This guideline limits the use of the notification API to notifications about +configuration changes. +There are better suited techiques for different use cases. +Applications should not keep track of changes and change their behavior on +certain conditions. + +For example, this happens when applications synchronize themselves at startup by +incrementing a counter in the key database. +When a certain limit of application instances is reached the applications +proceed with different behavior. +If this behavior affects other applications phase change has occured. + +### Guideline 5: Apply changes immediately +> Call `kdbSet()` to save updated configuration immediately after a change occured. +> This reduces conflicting changes in the key database. + +When a configuration setting is updated within an application this guideline +suggests to write the change immediately to the key database using `kdbSet()`. +This ensures that other applications have the same view of the key database and +operate on current settings. + +### Guideline 6: Be careful on what to call inside callbacks +> Notification callbacks are called from within Elektra. +> Calling `kdbClose()`, `elektraNotificationClose()` or `elektraSetIoBinding()` in a callback will lead to undefined behavior or an application crash. + +Closing and cleaning up the KDB handle will cause an application crash because +the control flow returns from the callback to now removed code. +While this can be considered an implementation detail it aligns with +[Guideline 2](#guidline-2-wait-before-reacting-to-changes) since reinitialization of KDB +uses more resources than other operations like `kdbGet()` or `kdbSet()`. + +## Logging + +In order to analyze application behavior the [logging plugins](https://www.libelektra.org/plugins/readme#notification-and-logging) +can be used with the `get=on` option when mounting: + +```sh +kdb global-mount syslog get=on +``` + +Now both reading from and writing to Elektra's key database is logged. + ## How to create your own I/O Binding Developers can create their own bindings if the I/O management library of their diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7267a03f916..495a98d4860 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,5 @@ include (LibAddMacros) +include (LibAddBinding) # don't call add_headers in a loop add_headers (HDR_FILES) @@ -20,7 +21,9 @@ file (GLOB TESTS *.c) foreach (file ${TESTS}) get_filename_component (name ${file} NAME_WE) - do_example (${name}) + if (NOT "${name}" MATCHES "^notification") + do_example (${name}) + endif () endforeach (file ${TESTS}) target_link_elektra (cascading elektra-kdb) @@ -31,3 +34,77 @@ target_link_elektra (kdbintro elektra-kdb) target_link_elektra (kdbopen elektra-kdb) target_link_elektra (kdbset elektra-kdb) target_link_elektra (set_key elektra-kdb) + +# Notification examples + +# Cannot build examples without notification library which requires the internalnotification plugin +list (FIND ADDED_PLUGINS + "internalnotification" + FOUND_NAME) +if (FOUND_NAME GREATER -1) + + # Build notification polling example + set (EXAMPLE notificationPolling) + + set (SRC_FILES notificationPolling.c) + set (SOURCES ${SRC_FILES} ${HDR_FILES}) + + add_executable (${EXAMPLE} ${SOURCES}) + add_dependencies (${EXAMPLE} kdberrors_generated) + + target_link_elektra (${EXAMPLE} elektra-kdb elektra-notification) + + # TODO resolve issues.libelektra.org/2007 + check_binding_was_added ("io_uv" IS_INCLUDED) + if (IS_INCLUDED) + + # Build notification async example + set (EXAMPLE notificationAsync) + + set (SRC_FILES notificationAsync.c) + set (SOURCES ${SRC_FILES} ${HDR_FILES}) + if (BUILD_FULL OR BUILD_STATIC) + list (APPEND SOURCES + $) # add sources for elektra-io-uv for static and full builds + endif () + + add_executable (${EXAMPLE} ${SOURCES}) + add_dependencies (${EXAMPLE} kdberrors_generated) + + target_link_elektra (${EXAMPLE} elektra-kdb elektra-notification elektra-io elektra-io-uv) + if (BUILD_FULL OR BUILD_STATIC) + target_link_libraries (${EXAMPLE} ${LIBUV_LDFLAGS}) + endif () + + if (LIBUV_VERSION VERSION_LESS "1.0") + target_compile_definitions (${EXAMPLE} PRIVATE "HAVE_LIBUV0") + else () + target_compile_definitions (${EXAMPLE} PRIVATE "HAVE_LIBUV1") + endif () + + endif () + + check_binding_was_added ("io_glib" IS_INCLUDED) + if (IS_INCLUDED) + + # Build notification reload example + set (EXAMPLE notificationReload) + + set (SRC_FILES notificationReload.c) + set (SOURCES ${SRC_FILES} ${HDR_FILES}) + if (BUILD_FULL OR BUILD_STATIC) + list (APPEND SOURCES + $) # add sources for elektra-io-uv for static and full builds + endif () + + add_executable (${EXAMPLE} ${SOURCES}) + add_dependencies (${EXAMPLE} kdberrors_generated) + + target_link_elektra (${EXAMPLE} elektra-kdb elektra-notification elektra-io elektra-io-glib) + if (BUILD_FULL OR BUILD_STATIC) + target_link_libraries (${EXAMPLE} ${GLIB_LIBRARIES}) + endif () + + endif () + +endif () diff --git a/src/libs/notification/example/example_notification_async.c b/examples/notificationAsync.c similarity index 59% rename from src/libs/notification/example/example_notification_async.c rename to examples/notificationAsync.c index 256d39d5f0e..11583261535 100644 --- a/src/libs/notification/example/example_notification_async.c +++ b/examples/notificationAsync.c @@ -1,7 +1,17 @@ /** * @file * - * @brief Implementation of notification functions as defined in kdbnotification.h + * @brief Example for notification library which repeatedly reads some keys and + * reacts to them + * + * Requires: + * - io_uv binding + * - Transport plugins (e.g. kdb global-mount dbus announce=once dbusrecv) + * + * Relevant keys for this example: + * - /sw/example/notification/#0/current/value: Set to any integer value + * - /sw/example/notification/#0/current/color: Set the text color. Possible + * values are "red", "green" and "blue". * * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) * @@ -10,7 +20,7 @@ #include #include // elektraFree #include // I/O binding functions (elektraIo*) -#include // I/O binding constructor for uv (elektraIoUvNew) +#include // I/O binding constructor for uv (elektraIoUvNew) #include // notification functions #include // uv functions @@ -18,13 +28,22 @@ #include // signal() #include // printf() & co +uv_async_t wakeup; + +#ifdef HAVE_LIBUV0 +static void wakeupCallback (uv_async_t * async ELEKTRA_UNUSED, int unknown ELEKTRA_UNUSED) +{ + // nothing to do; callback required for libuv 0.x +} +#endif + // from https://en.wikipedia.org/wiki/ANSI_escape_code#Colors #define ANSI_COLOR_RESET "\x1b[0m" #define ANSI_COLOR_RED "\x1b[31m" #define ANSI_COLOR_GREEN "\x1b[32m" #define ANSI_COLOR_BLUE "\x1b[34m" -static void setTerminalColor (Key * color) +static void setTerminalColor (Key * color, void * context ELEKTRA_UNUSED) { const char * value = keyString (color); printf ("Callback called. Changing color to %s\n", value); @@ -58,12 +77,14 @@ static void onSIGNAL (int signal) if (signal == SIGINT) { uv_stop (uv_default_loop ()); + // Without this call the loop would be "sleeping" until the next timer interval + uv_async_send (&wakeup); } } -static void printVariable (ElektraIoTimerOperation * timer) +static void printVariable (ElektraIoTimerOperation * timerOp) { - int value = *(int *) elektraIoTimerGetData (timer); + int value = *(int *) elektraIoTimerGetData (timerOp); printf ("\nMy integer value is %d\n", value); } @@ -71,15 +92,14 @@ int main (void) { // Cleanup on SIGINT signal (SIGINT, onSIGNAL); - signal (SIGQUIT, onSIGNAL); KeySet * config = ksNew (20, KS_END); - Key * key = keyNew ("/sw/tests/example_notification/#0/current", KEY_END); + Key * key = keyNew ("/sw/example/notification/#0/current", KEY_END); KDB * kdb = kdbOpen (key); if (kdb == NULL) { - printf ("could not open KDB. aborting\n"); + printf ("could not open KDB, aborting\n"); return -1; } @@ -90,24 +110,24 @@ int main (void) int result = elektraNotificationOpen (kdb); if (!result) { - printf ("could init notification. aborting\n"); + printf ("could not init notification, aborting\n"); return -1; } int value = 0; - Key * intKeyToWatch = keyNew ("/sw/tests/example_notification/#0/current/value", KEY_END); + Key * intKeyToWatch = keyNew ("/sw/example/notification/#0/current/value", KEY_END); result = elektraNotificationRegisterInt (kdb, intKeyToWatch, &value); if (!result) { - printf ("could not register variable. aborting\n"); + printf ("could not register variable, aborting\n"); return -1; } - Key * callbackKeyToWatch = keyNew ("/sw/tests/example_notification/#0/current/color", KEY_END); - result = elektraNotificationRegisterCallback (kdb, callbackKeyToWatch, &setTerminalColor); + Key * callbackKeyToWatch = keyNew ("/sw/example/notification/#0/current/color", KEY_END); + result = elektraNotificationRegisterCallback (kdb, callbackKeyToWatch, &setTerminalColor, NULL); if (!result) { - printf ("could not register callback. aborting!"); + printf ("could not register callback, aborting!"); return -1; } @@ -115,13 +135,24 @@ int main (void) ElektraIoTimerOperation * timer = elektraIoNewTimerOperation (2000, 1, printVariable, &value); elektraIoBindingAddTimer (binding, timer); - kdbGet (kdb, config, key); - printf ("Asynchronous Notification Example Application\n"); + printf ("Please note that notification transport plugins are required see\n" + " https://www.libelektra.org/tutorials/notifications#notification-configuration!\n"); printf ("- Set \"%s\" to red, blue or green to change the text color\n", keyName (callbackKeyToWatch)); printf ("- Set \"%s\" to any integer value\n", keyName (intKeyToWatch)); printf ("Send SIGINT (Ctl+C) to exit.\n\n"); + // Get configuration + kdbGet (kdb, config, key); + printVariable (timer); // "value" was automatically updated + + // This allows us to wake the loop from our signal handler +#ifdef HAVE_LIBUV1 + uv_async_init (loop, &wakeup, NULL); +#else + uv_async_init (loop, &wakeup, wakeupCallback); +#endif + uv_run (loop, UV_RUN_DEFAULT); // Cleanup @@ -132,10 +163,10 @@ int main (void) kdbClose (kdb, key); elektraIoBindingCleanup (binding); - uv_run (loop, UV_RUN_ONCE); // allow cleanup + uv_run (loop, UV_RUN_NOWAIT); #ifdef HAVE_LIBUV1 uv_loop_close (uv_default_loop ()); -#elif HAVE_LIBUV0 +#else uv_loop_delete (uv_default_loop ()); #endif diff --git a/src/libs/notification/example/example_notification.c b/examples/notificationPolling.c similarity index 71% rename from src/libs/notification/example/example_notification.c rename to examples/notificationPolling.c index c00debaa04a..9ae373264fc 100644 --- a/src/libs/notification/example/example_notification.c +++ b/examples/notificationPolling.c @@ -1,13 +1,20 @@ /** * @file * - * @brief Implementation of notification functions as defined in kdbnotification.h + * @brief Example for notification library which repeatedly reads some keys and + * reacts to them; see "notificationAsync.c" for an example without polling + * + * Relevant keys for this example: + * - /sw/example/notification/#0/current/value: Set to any integer value + * - /sw/example/notification/#0/current/color: Set the text color. Possible + * values are "red", "green" and "blue". * * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) * */ #include +#include // ELEKTRA_UNUSED #include #include @@ -23,7 +30,7 @@ static volatile int keepRunning = 0; #define ANSI_COLOR_GREEN "\x1b[32m" #define ANSI_COLOR_BLUE "\x1b[34m" -static void setTerminalColor (Key * color) +static void setTerminalColor (Key * color, void * context ELEKTRA_UNUSED) { const char * value = keyString (color); printf ("Callback called. Changing color to %s\n", value); @@ -82,35 +89,35 @@ int main (void) KeySet * config = ksNew (20, KS_END); - Key * key = keyNew ("/sw/tests/example_notification/#0/current", KEY_END); + Key * key = keyNew ("/sw/example/notification/#0/current", KEY_END); KDB * kdb = kdbOpen (key); if (kdb == NULL) { - printf ("could not open KDB. aborting\n"); + printf ("could not open KDB, aborting\n"); return -1; } int result = elektraNotificationOpen (kdb); if (!result) { - printf ("could init notification. aborting\n"); + printf ("could not init notification, aborting\n"); return -1; } int value = 0; - Key * intKeyToWatch = keyNew ("/sw/tests/example_notification/#0/current/value", KEY_END); + Key * intKeyToWatch = keyNew ("/sw/example/notification/#0/current/value", KEY_END); result = elektraNotificationRegisterInt (kdb, intKeyToWatch, &value); if (!result) { - printf ("could not register variable. aborting\n"); + printf ("could not register variable, aborting\n"); return -1; } - Key * callbackKeyToWatch = keyNew ("/sw/tests/example_notification/#0/current/color", KEY_END); - result = elektraNotificationRegisterCallback (kdb, callbackKeyToWatch, &setTerminalColor); + Key * callbackKeyToWatch = keyNew ("/sw/example/notification/#0/current/color", KEY_END); + result = elektraNotificationRegisterCallback (kdb, callbackKeyToWatch, &setTerminalColor, NULL); if (!result) { - printf ("could not register callback. aborting!"); + printf ("could not register callback, aborting!"); return -1; } @@ -120,7 +127,7 @@ int main (void) while (!keepRunning) { // After this kdbGet the integer variable is updated and the callback was called. - // TODO remove polling or make it optional when "transport plugins" are available + // see "notificationAsync" for an example without polling kdbGet (kdb, config, key); // Print values diff --git a/examples/notificationReload.c b/examples/notificationReload.c new file mode 100644 index 00000000000..5e102b6cac7 --- /dev/null +++ b/examples/notificationReload.c @@ -0,0 +1,213 @@ +/** + * @file + * + * @brief Example for notification library which reloads KDB when Elektra's + * configuration (e.g. mount points or global plugins) has changed. + * This example also shows how to pass user data using elektraIo*GetData(). + * + * Requires: + * - io_glib binding + * - Transport plugins (e.g. kdb global-mount zeromqsend zeromqrecv && kdb run-hub-zeromq) + * + * Relevant keys for this example: + * - /sw/example/notification/#0/current/value: Set to any integer value + * Add additional transport plugins and remove the original pair afterwards or + * mount a file which sets the key above to a different value and unmount it again + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#include +#include // elektraFree +#include // I/O binding functions (elektraIo*) +#include // I/O binding constructor for glib (elektraIoGlibNew) +#include // notification functions + +#include // g_unix_signal_add() +#include // glib functions + +#include // signal() +#include // printf() & co +#include // exit() + +#define TWO_SECONDS 2000 +#define RELOAD_INTERVAL 100 + +/** + * Data container for this example to demo + * usage of the elektraIo*GetData() functions. + * + * Members could also be globals. + */ +typedef struct ExampleUserData +{ + GMainLoop * loop; + KDB * kdb; + Key * parentKey; + KeySet * config; + ElektraIoInterface * binding; + Key * intKeyToWatch; + int valueToPrint; + ElektraIoTimerOperation * timer; + ElektraIoTimerOperation * reload; +} ExampleUserData; + +static void elektraChangedCallback (Key * changedKey ELEKTRA_UNUSED, void * context); + +/** + * Initializes KDB on first call and performs cleanup before initialization on + * subsequent calls. + * + * @param timerOp unused + */ +static void initKdb (ElektraIoTimerOperation * timerOp ELEKTRA_UNUSED) +{ + ExampleUserData * data = (ExampleUserData *) elektraIoTimerGetData (timerOp); + + int didReload = 0; + + // Stop reload task + elektraIoTimerSetEnabled (data->reload, 0); + elektraIoBindingUpdateTimer (data->reload); + + if (data->kdb != NULL) + { + // Cleanup notifications and close KDB + elektraNotificationClose (data->kdb); + kdbClose (data->kdb, data->parentKey); + didReload = 1; + } + + data->kdb = kdbOpen (data->parentKey); + if (data->kdb == NULL) + { + printf ("could not open KDB, aborting\n"); + exit (1); + } + + elektraIoSetBinding (data->kdb, data->binding); + + int result = elektraNotificationOpen (data->kdb); + if (!result) + { + printf ("could not init notification, aborting\n"); + exit (1); + } + + result = elektraNotificationRegisterInt (data->kdb, data->intKeyToWatch, &data->valueToPrint); + if (!result) + { + printf ("could not register variable, aborting\n"); + exit (1); + } + + Key * elektraKey = keyNew ("/elektra", KEY_END); + if (!elektraNotificationRegisterCallbackSameOrBelow (data->kdb, elektraKey, elektraChangedCallback, data)) + { + printf ("could not register for changes to Elektra's configuration, aborting\n"); + exit (1); + } + keyDel (elektraKey); + + // Get configuration + kdbGet (data->kdb, data->config, data->parentKey); + + if (didReload) + { + printf ("KDB reloaded.\n"); + } +} + +static gboolean onSIGNAL (gpointer user_data) +{ + ExampleUserData * data = (ExampleUserData *) user_data; + // Cleanup + elektraIoBindingRemoveTimer (data->timer); + elektraFree (data->timer); + elektraIoBindingRemoveTimer (data->reload); + elektraFree (data->reload); + elektraNotificationClose (data->kdb); + kdbClose (data->kdb, data->parentKey); + elektraIoBindingCleanup (data->binding); + + g_main_loop_quit (data->loop); + return FALSE; +} + +/** + * This function is called whenever Elektra's configuration has changed. + * Since cannot call elektraNotificationClose() here we start a timer operation + * which allows us to reload KDB in the next main loop iteration. + * + * @param changedKey unused + * @param context unused + */ +static void elektraChangedCallback (Key * changedKey ELEKTRA_UNUSED, void * context) +{ + printf ("\nElektra's configuration has changed.\n"); + + // Enable operation to reload KDB as soon as possible + ExampleUserData * data = (ExampleUserData *) context; + elektraIoTimerSetEnabled (data->reload, 1); + elektraIoBindingUpdateTimer (data->reload); +} + +static void printVariable (ElektraIoTimerOperation * timerOp) +{ + // int value = *(int *) elektraIoTimerGetData (timerOp); + ExampleUserData * data = (ExampleUserData *) elektraIoTimerGetData (timerOp); + printf ("\nMy integer value is %d\n", data->valueToPrint); +} + +int main (void) +{ + ExampleUserData * data = elektraCalloc (sizeof (*data)); + if (data == NULL) + { + printf ("elektraCalloc failed"); + return 1; + } + + // Create glib main loop + GMainContext * context = NULL; // use default context + data->loop = g_main_loop_new (context, 0); + data->binding = elektraIoGlibNew (context); + + // Signal Handling + g_unix_signal_add (SIGINT, onSIGNAL, data); + + data->config = ksNew (20, KS_END); + data->parentKey = keyNew ("/sw/example/notification/#0/current", KEY_END); + data->intKeyToWatch = keyNew ("/sw/example/notification/#0/current/value", KEY_END); + + // Setup timer that repeatedly prints the variable + data->timer = elektraIoNewTimerOperation (TWO_SECONDS, 1, printVariable, data); + elektraIoBindingAddTimer (data->binding, data->timer); + + // Setup timer for reloading Elektra's configuration + data->reload = elektraIoNewTimerOperation (RELOAD_INTERVAL, 0, initKdb, data); + elektraIoBindingAddTimer (data->binding, data->reload); + + printf ("Reloading Notification Example Application\n"); + printf ("Please note that notification transport plugins are required see\n" + " https://www.libelektra.org/tutorials/notifications#notification-configuration!\n"); + printf ("- Set \"%s\" to any integer value\n", keyName (data->intKeyToWatch)); + printf ("- Try to add additional transport plugins and remove the original pair afterwards\n"); + printf ("- Mount a file which sets the key above to a different value and unmount it\n"); + printf ("Send SIGINT (Ctl+C) to exit.\n\n"); + + // Initialize KDB + initKdb (data->reload); + printVariable (data->timer); // "value" was automatically updated + + g_main_loop_run (data->loop); + + g_main_loop_unref (data->loop); + + ksDel (data->config); + keyDel (data->intKeyToWatch); + keyDel (data->parentKey); + elektraFree (data); + printf ("cleanup done!\n"); +} diff --git a/scripts/docker/jenkinsnode/Dockerfile b/scripts/docker/jenkinsnode/Dockerfile index f1ab5de6bb9..73edbe885a8 100644 --- a/scripts/docker/jenkinsnode/Dockerfile +++ b/scripts/docker/jenkinsnode/Dockerfile @@ -31,6 +31,7 @@ RUN apt-get -y install \ libgcrypt20-dev \ libbotan1.10-dev \ libev-dev \ + libuv1-dev \ libsystemd-dev \ libuv1-dev \ libzmq3-dev \ diff --git a/src/bindings/io/ev/README.md b/src/bindings/io/ev/README.md index 06d88dda4a1..d3f32f89483 100644 --- a/src/bindings/io/ev/README.md +++ b/src/bindings/io/ev/README.md @@ -37,7 +37,7 @@ Populated I/O interface ```C #include #include -#include +#include #include diff --git a/src/bindings/io/ev/example/exampleio_ev.c b/src/bindings/io/ev/example/exampleio_ev.c index 0cb750b9802..576d3da55f5 100644 --- a/src/bindings/io/ev/example/exampleio_ev.c +++ b/src/bindings/io/ev/example/exampleio_ev.c @@ -26,7 +26,7 @@ #include // assertions (ELEKTRA_NOT_NULL) #include // malloc & free #include // I/O binding functions (elektraIo*) -#include // I/O binding constructor for ev (elektraIoEvNew) +#include // I/O binding constructor for ev (elektraIoEvNew) #include // ev functions diff --git a/src/bindings/io/ev/io_ev.c b/src/bindings/io/ev/io_ev.c index 63bc229e141..188454dccca 100644 --- a/src/bindings/io/ev/io_ev.c +++ b/src/bindings/io/ev/io_ev.c @@ -80,7 +80,7 @@ static EvBindingData * newBindingData (void) EvBindingData * bindingData = elektraCalloc (sizeof (*bindingData)); if (bindingData == NULL) { - ELEKTRA_LOG_WARNING ("elektraMalloc failed"); + ELEKTRA_LOG_WARNING ("elektraCalloc failed"); return NULL; } diff --git a/src/bindings/io/ev/testio_ev.c b/src/bindings/io/ev/testio_ev.c index d42bc8032b6..4a5cd27a794 100644 --- a/src/bindings/io/ev/testio_ev.c +++ b/src/bindings/io/ev/testio_ev.c @@ -16,7 +16,7 @@ #include -#include +#include static ElektraIoInterface * createBinding (void) { diff --git a/src/bindings/io/glib/README.md b/src/bindings/io/glib/README.md index d1d604f9743..ce507c493e0 100644 --- a/src/bindings/io/glib/README.md +++ b/src/bindings/io/glib/README.md @@ -36,10 +36,9 @@ Populated I/O interface ## Example ```C - #include #include -#include +#include #include @@ -65,5 +64,7 @@ void main (void) elektraIoBindingCleanup (binding); g_main_loop_unref (loop); } - ``` + +Please check out the ["notificationReload" example](https://www.libelektra.org/examples/notificationreload) +which uses this I/O binding. diff --git a/src/bindings/io/glib/example/exampleio_glib.c b/src/bindings/io/glib/example/exampleio_glib.c index fc87edadb3e..274fe4617a3 100644 --- a/src/bindings/io/glib/example/exampleio_glib.c +++ b/src/bindings/io/glib/example/exampleio_glib.c @@ -26,7 +26,7 @@ #include // assertions (ELEKTRA_NOT_NULL) #include // malloc & free #include // I/O binding functions (elektraIo*) -#include // I/O binding constructor for glib (elektraIoGlibNew) +#include // I/O binding constructor for glib (elektraIoGlibNew) #include // glib functions diff --git a/src/bindings/io/glib/io_glib.c b/src/bindings/io/glib/io_glib.c index 2acf634ef2e..7e87fd9cba8 100644 --- a/src/bindings/io/glib/io_glib.c +++ b/src/bindings/io/glib/io_glib.c @@ -102,7 +102,7 @@ static GlibBindingData * newBindingData (void) GlibBindingData * bindingData = elektraCalloc (sizeof (*bindingData)); if (bindingData == NULL) { - ELEKTRA_LOG_WARNING ("elektraMalloc failed"); + ELEKTRA_LOG_WARNING ("elektraCalloc failed"); return NULL; } @@ -178,6 +178,17 @@ static int ioGlibBindingFdDispatch (GSource * source, GSourceFunc callback, void return G_SOURCE_CONTINUE; } +/** + * Cleanup after file descriptor has been detached + * @param source glib source + */ +static void ioGlibBindingFdCleanup (GSource * source) +{ + FdSource * fdSource = (FdSource *) source; + elektraFree (fdSource->bindingData->fdFuncs); + elektraFree (fdSource->bindingData); +} + /** * Calls the associated callback. * Called by glib whenever a timer has elapsed. @@ -282,7 +293,7 @@ static int ioGlibBindingAddFd (ElektraIoInterface * binding, ElektraIoFdOperatio funcs->prepare = ioGlibBindingFdPrepare; funcs->check = ioGlibBindingFdCheck; funcs->dispatch = ioGlibBindingFdDispatch; - funcs->finalize = NULL; + funcs->finalize = ioGlibBindingFdCleanup; bindingData->fdFuncs = funcs; FdSource * fdSource = (FdSource *) g_source_new (funcs, sizeof *fdSource); GSource * gSource = &fdSource->gSource; @@ -320,8 +331,6 @@ static int ioGlibBindingRemoveFd (ElektraIoFdOperation * fdOp) } g_source_destroy (gSource); g_source_unref (gSource); - elektraFree (bindingData->fdFuncs); - elektraFree (bindingData); return 1; } diff --git a/src/bindings/io/glib/testio_glib.c b/src/bindings/io/glib/testio_glib.c index c34aae062ee..5a287e28bc5 100644 --- a/src/bindings/io/glib/testio_glib.c +++ b/src/bindings/io/glib/testio_glib.c @@ -16,7 +16,7 @@ #include -#include +#include GMainLoop * loop; GMainContext * context; diff --git a/src/bindings/io/uv/README.md b/src/bindings/io/uv/README.md index df008f2babc..1dfcd6c9fd5 100644 --- a/src/bindings/io/uv/README.md +++ b/src/bindings/io/uv/README.md @@ -35,10 +35,9 @@ Populated I/O interface ## Example ```C - #include #include -#include +#include #include @@ -63,5 +62,7 @@ void main (void) elektraIoBindingCleanup (binding); uv_loop_close (loop); } - ``` + +Please check out the ["notificationAsync" example](https://www.libelektra.org/examples/notificationasync) +which uses this I/O binding. diff --git a/src/bindings/io/uv/example/exampleio_uv.c b/src/bindings/io/uv/example/exampleio_uv.c index f603ab8f094..f30e0b578dc 100644 --- a/src/bindings/io/uv/example/exampleio_uv.c +++ b/src/bindings/io/uv/example/exampleio_uv.c @@ -26,7 +26,7 @@ #include // assertions (ELEKTRA_NOT_NULL) #include // malloc & free #include // I/O binding functions (elektraIo*) -#include // I/O binding constructor for uv (elektraIoUvNew) +#include // I/O binding constructor for uv (elektraIoUvNew) #include // uv functions diff --git a/src/bindings/io/uv/io_uv.c b/src/bindings/io/uv/io_uv.c index eb0f14b1343..24432600390 100644 --- a/src/bindings/io/uv/io_uv.c +++ b/src/bindings/io/uv/io_uv.c @@ -84,7 +84,7 @@ static UvBindingData * newBindingData (void) UvBindingData * bindingData = elektraCalloc (sizeof (*bindingData)); if (bindingData == NULL) { - ELEKTRA_LOG_WARNING ("elektraMalloc failed"); + ELEKTRA_LOG_WARNING ("elektraCalloc failed"); return NULL; } diff --git a/src/bindings/io/uv/testio_uv.c b/src/bindings/io/uv/testio_uv.c index 29fe584920d..55b97dc812f 100644 --- a/src/bindings/io/uv/testio_uv.c +++ b/src/bindings/io/uv/testio_uv.c @@ -16,7 +16,7 @@ #include -#include +#include static ElektraIoInterface * createBinding (void) { diff --git a/src/include/CMakeLists.txt b/src/include/CMakeLists.txt index 972b2519151..49d3e88586b 100644 --- a/src/include/CMakeLists.txt +++ b/src/include/CMakeLists.txt @@ -106,23 +106,10 @@ install (FILES "${CMAKE_CURRENT_BINARY_DIR}/kdbconfig.h" "${CMAKE_CURRENT_BINARY_DIR}/kdbversion.h" DESTINATION include/${TARGET_INCLUDE_FOLDER}) -check_binding_included ("io_uv" IO_UV_INCLUDED SUBDIRECTORY "io/uv" SILENT) -if (IO_UV_INCLUDED) - install (FILES kdbio_uv.h DESTINATION include/${TARGET_INCLUDE_FOLDER}) -endif () - -check_binding_included ("io_glib" IO_GLIB_INCLUDED SUBDIRECTORY "io/glib" SILENT) -if (IO_GLIB_INCLUDED) - install (FILES kdbio_glib.h DESTINATION include/${TARGET_INCLUDE_FOLDER}) -endif () - -check_binding_included ("io_ev" IO_EV_INCLUDED SUBDIRECTORY "io/ev" SILENT) -if (IO_EV_INCLUDED) - install (FILES kdbio_ev.h DESTINATION include/${TARGET_INCLUDE_FOLDER}) -endif () - add_custom_target (elektra_config_headers ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/kdb.h ${CMAKE_CURRENT_BINARY_DIR}/kdbconfig.h ${CMAKE_CURRENT_BINARY_DIR}/kdbversion.h) + +add_subdirectory (kdbio) diff --git a/src/include/kdbinvoke.h b/src/include/kdbinvoke.h index 423a2d59b01..c5cb231e516 100644 --- a/src/include/kdbinvoke.h +++ b/src/include/kdbinvoke.h @@ -44,6 +44,7 @@ int elektraDeferredCallAdd (ElektraDeferredCallList * list, const char * name, K ElektraDeferredCallList * elektraDeferredCallCreateList (void); void elektraDeferredCallDeleteList (ElektraDeferredCallList * list); void elektraDeferredCallsExecute (Plugin * plugin, ElektraDeferredCallList * list); +int elektraDeferredCall (Plugin * handle, const char * elektraPluginFunctionName, KeySet * parameters); #ifdef __cplusplus } diff --git a/src/include/kdbio/CMakeLists.txt b/src/include/kdbio/CMakeLists.txt new file mode 100644 index 00000000000..b71fabcffa7 --- /dev/null +++ b/src/include/kdbio/CMakeLists.txt @@ -0,0 +1,14 @@ +check_binding_included ("io_uv" IO_UV_INCLUDED SUBDIRECTORY "io/uv" SILENT) +if (IO_UV_INCLUDED) + install (FILES uv.h DESTINATION include/${TARGET_INCLUDE_FOLDER}/kdbio) +endif () + +check_binding_included ("io_glib" IO_GLIB_INCLUDED SUBDIRECTORY "io/glib" SILENT) +if (IO_GLIB_INCLUDED) + install (FILES glib.h DESTINATION include/${TARGET_INCLUDE_FOLDER}/kdbio) +endif () + +check_binding_included ("io_ev" IO_EV_INCLUDED SUBDIRECTORY "io/ev" SILENT) +if (IO_EV_INCLUDED) + install (FILES ev.h DESTINATION include/${TARGET_INCLUDE_FOLDER}/kdbio) +endif () diff --git a/src/include/kdbio_adapter_dbus.h b/src/include/kdbio/adapters/dbus.h similarity index 94% rename from src/include/kdbio_adapter_dbus.h rename to src/include/kdbio/adapters/dbus.h index 77f864316c6..726337d8335 100644 --- a/src/include/kdbio_adapter_dbus.h +++ b/src/include/kdbio/adapters/dbus.h @@ -5,8 +5,8 @@ * * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) */ -#ifndef __ELEKTAR_IO_ADAPTER_DBUS_H__ -#define __ELEKTAR_IO_ADAPTER_DBUS_H__ +#ifndef ELEKTRA_IO_ADAPTER_DBUS_H +#define ELEKTRA_IO_ADAPTER_DBUS_H #include #include diff --git a/src/include/kdbio_adapter_zeromq.h b/src/include/kdbio/adapters/zeromq.h similarity index 96% rename from src/include/kdbio_adapter_zeromq.h rename to src/include/kdbio/adapters/zeromq.h index 3a991698a31..49b3c9a8b8e 100644 --- a/src/include/kdbio_adapter_zeromq.h +++ b/src/include/kdbio/adapters/zeromq.h @@ -5,8 +5,8 @@ * * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) */ -#ifndef __ELEKTAR_IO_ADAPTER_ZEROMQ_H__ -#define __ELEKTAR_IO_ADAPTER_ZEROMQ_H__ +#ifndef ELEKTRA_IO_ADAPTER_ZEROMQ_H +#define ELEKTRA_IO_ADAPTER_ZEROMQ_H #include #include diff --git a/src/include/kdbio_ev.h b/src/include/kdbio/ev.h similarity index 95% rename from src/include/kdbio_ev.h rename to src/include/kdbio/ev.h index 20ea1ff2972..447cc7f933d 100644 --- a/src/include/kdbio_ev.h +++ b/src/include/kdbio/ev.h @@ -8,8 +8,8 @@ #ifndef KDB_IOWRAPPER_EV_H_ #define KDB_IOWRAPPER_EV_H_ -#include "kdbio.h" #include +#include /** * Create and initialize a new I/O binding. diff --git a/src/include/kdbio_glib.h b/src/include/kdbio/glib.h similarity index 95% rename from src/include/kdbio_glib.h rename to src/include/kdbio/glib.h index 47086bec1a2..468bf7e56b9 100644 --- a/src/include/kdbio_glib.h +++ b/src/include/kdbio/glib.h @@ -8,8 +8,8 @@ #ifndef KDB_IOWRAPPER_GLIB_H_ #define KDB_IOWRAPPER_GLIB_H_ -#include "kdbio.h" #include +#include /** * Create and initialize a new I/O binding. diff --git a/src/include/kdbio_uv.h b/src/include/kdbio/uv.h similarity index 95% rename from src/include/kdbio_uv.h rename to src/include/kdbio/uv.h index e8e6d05af99..9a1fe4984c7 100644 --- a/src/include/kdbio_uv.h +++ b/src/include/kdbio/uv.h @@ -8,7 +8,7 @@ #ifndef KDB_IOWRAPPER_UV_H_ #define KDB_IOWRAPPER_UV_H_ -#include "kdbio.h" +#include #include /** diff --git a/src/include/kdbioprivate.h b/src/include/kdbioprivate.h index 686443fcec8..ce8ccfcee75 100644 --- a/src/include/kdbioprivate.h +++ b/src/include/kdbioprivate.h @@ -197,7 +197,7 @@ typedef struct _ElektraIoInterface /** * Free memory used by I/O binding. * - * All added file descriptors and timers have to be removed before calling this function. + * All added operations have to be removed before calling this function. * * @param binding I/O binding * @return 0 on success, any other value on error diff --git a/src/include/kdbnotification.h b/src/include/kdbnotification.h index 2fd51f348bb..c73dbe60fda 100644 --- a/src/include/kdbnotification.h +++ b/src/include/kdbnotification.h @@ -13,6 +13,7 @@ #define KDB_NOTIFICATION_H_ #include "kdb.h" +#include "kdbtypes.h" /** * @defgroup kdbnotification Notification @@ -22,6 +23,12 @@ * For an introduction to notifications please see the * Notification Tutorial. * + * Examples: + * + * - [Basic notifications using polling](https://www.libelektra.org/examples/notificationpolling) + * - [Using asynchronous I/O bindings](https://www.libelektra.org/examples/notificationasync) + * - [Reload KDB when Elektra's configuration has changed](https://www.libelektra.org/examples/notificationreload) + * * @par Global Mounting * * elektraNotificationOpen() loads and mounts the @@ -76,6 +83,7 @@ extern "C" { #endif /** + * @ingroup kdbnotification * Initialize the notification system for the given KDB instance. * * Asynchronous receiving of notifications requires an I/O binding. @@ -90,6 +98,7 @@ extern "C" { int elektraNotificationOpen (KDB * kdb); /** + * @ingroup kdbnotification * Stop the notification system for the given KDB instance. * * May only be called after elektraNotificationOpen() was called for given KDB @@ -101,37 +110,97 @@ int elektraNotificationOpen (KDB * kdb); */ int elektraNotificationClose (KDB * kdb); +#define ELEKTRA_NOTIFICATION_REGISTER_NAME(TYPE_NAME) elektraNotificationRegister##TYPE_NAME + +#define ELEKTRA_NOTIFICATION_REGISTER_SIGNATURE(TYPE, TYPE_NAME) \ + /** @copydoc elektraNotificationRegisterInt */ \ + int ELEKTRA_NOTIFICATION_REGISTER_NAME (TYPE_NAME) (KDB * kdb, Key * key, TYPE * variable) + +#define ELEKTRA_NOTIFICATION_TYPE_DECLARATION(TYPE, TYPE_NAME) ELEKTRA_NOTIFICATION_REGISTER_SIGNATURE (TYPE, TYPE_NAME); + /** - * Subscribe for automatic updates to a given integer variable when the given - * key value is changed. + * @ingroup kdbnotification + * @brief Subscribe for automatic updates to a given variable when the given key value is changed. + * + * On kdbGet iff the key is present and its content is valid, the registered variable is updated. * * @param handle plugin handle * @param key key to watch for changes - * @param variable integer variable + * @param variable variable * * @retval 1 on success * @retval 0 on failure + * @{ */ -int elektraNotificationRegisterInt (KDB * kdb, Key * key, int * variable); +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (int, Int) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (unsigned int, UnsignedInt) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (long, Long) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (unsigned long, UnsignedLong) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (long long, LongLong) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (unsigned long long, UnsignedLongLong) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (float, Float) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (double, Double) + +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_boolean_t, KdbBoolean) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_char_t, KdbChar) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_octet_t, KdbOctet) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_short_t, KdbShort) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_unsigned_short_t, KdbUnsignedShort) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_long_t, KdbLong) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_unsigned_long_t, KdbUnsignedLong) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_long_long_t, KdbLongLong) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_unsigned_long_long_t, KdbUnsignedLongLong) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_float_t, KdbFloat) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_double_t, KdbDouble) +ELEKTRA_NOTIFICATION_TYPE_DECLARATION (kdb_long_double_t, KdbLongDouble) +/** @} */ + /** + * @ingroup kdbnotification + * Callback function called when string to number conversion failed. + * + * @param key key with invalid value + * @param context user supplied callback context + */ +typedef void (*ElektraNotificationConversionErrorCallback) (Key * key, void * context); + +/** + * @ingroup kdbnotification * Callback function for key changes. * - * @param key changed key + * @param key changed key + * @param context user supplied callback context */ -typedef void (*ElektraNotificationChangeCallback) (Key * key); +typedef void (*ElektraNotificationChangeCallback) (Key * key, void * context); /** + * @ingroup kdbnotification * Subscribe for updates via callback when a given key value is changed. * * @param handle plugin handle * @param key key to watch for changes * @param callback callback function + * @param context user supplied context passed to callback function + * + * @retval 1 on success + * @retval 0 on failure + */ +int elektraNotificationRegisterCallback (KDB * kdb, Key * key, ElektraNotificationChangeCallback callback, void * context); + +/** + * @ingroup kdbnotification + * Subscribe for updates via callback when a given key or a key below changed. + * + * @param handle plugin handle + * @param key key to watch for changes + * @param callback callback function + * @param context user supplied context passed to callback function * * @retval 1 on success * @retval 0 on failure */ -int elektraNotificationRegisterCallback (KDB * kdb, Key * key, ElektraNotificationChangeCallback callback); +int elektraNotificationRegisterCallbackSameOrBelow (KDB * kdb, Key * key, ElektraNotificationChangeCallback callback, void * context); #ifdef __cplusplus diff --git a/src/include/kdbnotificationinternal.h b/src/include/kdbnotificationinternal.h index a14ac654bdb..0ecce0c75b8 100644 --- a/src/include/kdbnotificationinternal.h +++ b/src/include/kdbnotificationinternal.h @@ -3,7 +3,7 @@ * @ingroup kdbnotification * * @brief Elektra-Notification structures and declarations for developing - * notification and transport plugins. + * notification and transport plugins. Only available in Elektra's source. * * Only used by elektra-notification library, notification plugins (e.g. * internalnotification) and transport plugins. @@ -23,30 +23,74 @@ namespace ckdb extern "C" { #endif +#define ELEKTRA_NOTIFICATION_REGISTERFUNC_TYPEDEF(FUNC_TYPE_NAME, TYPE) \ + typedef int (*FUNC_TYPE_NAME) (Plugin * handle, Key * key, TYPE * variable); + +#define ELEKTRA_NOTIFICATION_TYPE_DEFINITION(TYPE, TYPE_NAME) \ + ELEKTRA_NOTIFICATION_REGISTER_SIGNATURE (TYPE, TYPE_NAME) \ + { \ + if (!kdb || !key || !variable) \ + { \ + ELEKTRA_LOG_WARNING ("null pointer passed"); \ + return 0; \ + } \ + /* get notification plugins */ \ + Plugin * notificationPlugin = getNotificationPlugin (kdb); \ + if (!notificationPlugin) \ + { \ + return 0; \ + } \ + /* get register function from plugin */ \ + size_t func = elektraPluginGetFunction (notificationPlugin, "register" #TYPE_NAME); \ + if (!func) \ + { \ + return 0; \ + } \ + ELEKTRA_NOTIFICATION_REGISTERFUNC_TYPEDEF (RegisterFuncType, TYPE) \ + RegisterFuncType registerFunc = (RegisterFuncType) func; \ + return registerFunc (notificationPlugin, key, variable); \ + } + /** - * Subscribe for automatic updates to a given integer variable when the given - * key value is changed. + * Subscribe for updates via callback when a given key value is changed. + * Exported as "registerCallback" by notification plugins. * * @param handle plugin handle * @param key key to watch for changes - * @param variable integer variable + * @param callback callback function + * @param context user supplied context passed to callback function * * @retval 1 on success * @retval 0 on failure */ -typedef int (*ElektraNotificationPluginRegisterInt) (Plugin * handle, Key * key, int * variable); +typedef int (*ElektraNotificationPluginRegisterCallback) (Plugin * handle, Key * key, ElektraNotificationChangeCallback callback, + void * context); /** - * Subscribe for updates via callback when a given key value is changed. + * Subscribe for updates via callback when a given key or a key below is changed. + * Exported as "registerCallbackSameOrBelow" by notification plugins. * * @param handle plugin handle * @param key key to watch for changes * @param callback callback function + * @param context user supplied context passed to callback function * * @retval 1 on success * @retval 0 on failure */ -typedef int (*ElektraNotificationPluginRegisterCallback) (Plugin * handle, Key * key, ElektraNotificationChangeCallback callback); +typedef int (*ElektraNotificationPluginRegisterCallbackSameOrBelow) (Plugin * handle, Key * key, ElektraNotificationChangeCallback callback, + void * context); + +/** + * Allow setting a callback that is called when a value conversion failed. + * Exported as "setConversionErrorCallback" notification plugins. + * + * @param kdb kdb handle + * @param callback callback + * @param context context + */ +typedef void (*ElektraNotificationSetConversionErrorCallback) (Plugin * handle, ElektraNotificationConversionErrorCallback callback, + void * context); /** * Context for notification callbacks. @@ -69,7 +113,7 @@ typedef void (*ElektraNotificationCallback) (Key * key, ElektraNotificationCallb * Exported as "openNotification" by transport plugins. * * @param handle plugin handle - * @param parameters contains the keys "/callback" (ElektraNotificationCallback * ) and "/context" (ElektraNotificationCallbackContext *). + * @param parameters contains the keys "/callback" (ElektraNotificationCallback) and "/context" (ElektraNotificationCallbackContext *). */ typedef void (*ElektraNotificationOpenNotification) (Plugin * handle, KeySet * parameters); @@ -83,6 +127,14 @@ typedef void (*ElektraNotificationOpenNotification) (Plugin * handle, KeySet * p */ typedef void (*ElektraNotificationCloseNotification) (Plugin * handle, KeySet * parameters); +/** + * Used by notification plugins to get values from the key database. + * + * @param kdb kdb handle + * @param changedKey which key was updated + */ +typedef void (*ElektraNotificationKdbUpdate) (KDB * kdb, Key * changedKey); + /** * Private struct with information about for ElektraNotificationCallback. * @internal @@ -91,7 +143,9 @@ typedef void (*ElektraNotificationCloseNotification) (Plugin * handle, KeySet * */ struct _ElektraNotificationCallbackContext { - KDB * kdb; /*!< The pointer the KDB handle.*/ + KDB * kdb; /*!< The pointer to kdb handle.*/ + + ElektraNotificationKdbUpdate kdbUpdate; /*!< The pointer to the update function.*/ Plugin * notificationPlugin; /*!< Notification plugin handle.*/ }; diff --git a/src/include/kdbtypes.h b/src/include/kdbtypes.h index ad107626595..d14607f44d2 100644 --- a/src/include/kdbtypes.h +++ b/src/include/kdbtypes.h @@ -48,8 +48,32 @@ using octet_t = uint8_t; // default: 0 // for C (and C++) -typedef unsigned char kdb_boolean_t; typedef unsigned char kdb_char_t; + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +// for C99+ +#include +#include +#include + +typedef uint8_t kdb_octet_t; +typedef bool kdb_boolean_t; +typedef int16_t kdb_short_t; +typedef int32_t kdb_long_t; +typedef int64_t kdb_long_long_t; +typedef uint16_t kdb_unsigned_short_t; +typedef uint32_t kdb_unsigned_long_t; +typedef uint64_t kdb_unsigned_long_long_t; + +#define ELEKTRA_LONG_F "%" PRIi32 +#define ELEKTRA_UNSIGNED_LONG_F "%" PRIu32 +#define ELEKTRA_LONG_LONG_F "%" PRIi64 +#define ELEKTRA_LONG_LONG_S strtoll +#define ELEKTRA_UNSIGNED_LONG_LONG_F "%" PRIu64 +#define ELEKTRA_UNSIGNED_LONG_LONG_S strtoull + +#else // for C89 +typedef unsigned char kdb_boolean_t; typedef unsigned char kdb_octet_t; typedef signed short kdb_short_t; typedef unsigned short kdb_unsigned_short_t; @@ -86,6 +110,8 @@ typedef long long kdb_long_long_t; typedef unsigned long long kdb_unsigned_long_long_t; #endif +#endif // for C89 + typedef float kdb_float_t; typedef double kdb_double_t; diff --git a/src/include/macros/type_create_to_value.h b/src/include/macros/type_create_to_value.h new file mode 100644 index 00000000000..7ef10075ced --- /dev/null +++ b/src/include/macros/type_create_to_value.h @@ -0,0 +1,126 @@ +/** + * @copyright BSD License (see doc/COPYING or http://www.libelektra.org) + * + * @brief Create key to type conversion function. + * + * This supermacro creates the following functions: + * - int NAME_MACRO (TYPE_NAME) (Key * key, TYPE * variable) + * + * @param TYPE valid C type (e.g. int or kdb_short_t) + * @param TYPE_NAME name suffix for the functions (e.g. Int or UnsignedLong) + * @param VALUE_TYPE optional, defaults to TYPE. Ideally a larger type assigned to variable `value` for + * checking the range before the variable is updated + * @param TO_VALUE expression for converting `string` (variable containing the key value) to VALUE_TYPE + * @param CHECK_CONVERSION optional, defaults to true. A boolean expression. Allows to check the range after + * conversion. Use ELEKTRA_TYPE_CHECK_CONVERSION to check if a conversion using + * strto*()-functions was successful and ELEKTRA_TYPE_CHECK_CONVERSION_RANGE (RANGE) + * to check additionally for a specified range. + * @param PRE_CHECK_CONVERSION_BLOCK optional, defaults to empty. Allows to add additional code for pre-conversion checks + * (e.g. ELEKTRA_TYPE_NEGATIVE_PRE_CHECK_BLOCK) + * @param PRE_CHECK_CONVERSION optional, defaults to true. A boolean expression to check the contents of `string` before conversion + * (e.g. ELEKTRA_TYPE_NEGATIVE_PRE_CHECK). + * @param DISABLE_UNDEF_PARAMETERS define to disable undefining of parameters after the macro. Use if parameters + * are used within another supermacro. + */ +#ifndef TYPE +#error "You have to #define TYPE, TYPE_NAME, TO_VALUE and NAME_MACRO before including the type_create_to_value supermacro" +#endif +#ifndef VALUE_TYPE +// use type as default if not set +#define VALUE_TYPE TYPE +#endif +#ifndef TYPE_NAME +#error "You have to #define TYPE, TYPE_NAME, TO_VALUE and NAME_MACRO before including the type_create_to_value supermacro" +#endif +#ifndef NAME_MACRO +#error "You have to #define TYPE, TYPE_NAME, TO_VALUE and NAME_MACRO before including the type_create_to_value supermacro" +#endif +#ifndef TO_VALUE +#error "You have to #define TYPE, TYPE_NAME, TO_VALUE and NAME_MACRO before including the type_create_to_value supermacro" +#endif +#ifndef CHECK_CONVERSION +#define CHECK_CONVERSION 1 +#endif +#ifndef PRE_CHECK_CONVERSION +#define PRE_CHECK_CONVERSION 1 +#endif +#ifndef PRE_CHECK_BLOCK +#define PRE_CHECK_BLOCK +#endif + +// These macros get defined at first inclusion +#ifndef ELEKTRA_TYPE_CONVERSION_MACROS +#define ELEKTRA_TYPE_CONVERSION_MACROS +#define ELEKTRA_TYPE_CHECK_CONVERSION (*end == 0 && errno == 0) +#define ELEKTRA_TYPE_CHECK_CONVERSION_RANGE(CHECK_RANGE) (ELEKTRA_TYPE_CHECK_CONVERSION && CHECK_RANGE) +#define ELEKTRA_TYPE_NEGATIVE_PRE_CHECK_BLOCK \ + const char * test = string; \ + while (isspace (test[0]) || test[0] == 0) \ + { \ + test++; \ + } +#define ELEKTRA_TYPE_NEGATIVE_PRE_CHECK (test[0] != '-') +#endif + +#include // errno + +#define TYPE_CONVERSION_SIGNATURE(TYPE, TYPE_NAME, NAME_MACRO) int NAME_MACRO (TYPE_NAME) (Key * key, TYPE * variable) + +/** + * Convert string to TYPE. + * + * The variable is only changed if no conversion error occured + * + * Example: + * int variable = 1234; + * if (!NAME_MACRO (TYPE_NAME) (key, &variable)) + * { + * // conversion failed + * // variable == 1234 + * } + * // variable was changed + * + * @param key key + * @param variable pointer to variable + * @retval 1 on success + * @retval 0 on conversion error + */ +TYPE_CONVERSION_SIGNATURE (TYPE, TYPE_NAME, NAME_MACRO) +{ + char * end ELEKTRA_UNUSED; + const char * string = keyValue (key); + errno = 0; + PRE_CHECK_BLOCK + if (!PRE_CHECK_CONVERSION) + { + ELEKTRA_LOG_WARNING ("pre-check for type conversion failed! string=%s", keyString (key)); + return 0; + } + // convert string to target type + VALUE_TYPE value = TO_VALUE; + if (CHECK_CONVERSION) + { + // only update if conversion was successful + *(variable) = value; + return 1; + } + else + { + ELEKTRA_LOG_WARNING ("type conversion failed! string=%s, stopped=%c errno=%d", keyString (key), *end, errno); + return 0; + } +} + +#undef TYPE_CONVERSION_SIGNATURE + +#ifndef DISABLE_UNDEF_PARAMETERS +#undef TYPE +#undef VALUE_TYPE +#undef TYPE_NAME +#undef NAME_MACRO +#undef TO_VALUE +#undef CHECK_CONVERSION +#undef PRE_CHECK_BLOCK +#undef PRE_CHECK_CONVERSION +#endif +#undef DISABLE_UNDEF_PARAMETERS diff --git a/src/libs/README.md b/src/libs/README.md index fc809be8986..976cdf66aea 100644 --- a/src/libs/README.md +++ b/src/libs/README.md @@ -31,13 +31,13 @@ Applications and plugins can choose to not link against it if they want to stay libelektra-pluginprocess.so **[libpluginprocess](pluginprocess/)** contains functions aiding in executing plugins in a separate -process and communicating with those child processes. This child process is forked from Elektra's +process and communicating with those child processes. This child process is forked from Elektra's main process each time such plugin is used and gets closed again afterwards. It uses a simple -communication protocol based on a KeySet that gets serialized through a pipe via the dump plugin to +communication protocol based on a KeySet that gets serialized through a pipe via the dump plugin to orchestrate the processes. -This is useful for plugins which cause memory leaks to be isolated in an own process. Furthermore -this is useful for runtimes or libraries that cannot be reinitialized in the same process after they +This is useful for plugins which cause memory leaks to be isolated in an own process. Furthermore +this is useful for runtimes or libraries that cannot be reinitialized in the same process after they have been used. ### Libproposal @@ -82,3 +82,22 @@ data structures. libelektra-invoke.so **[libinvoke](invoke/)** provides a simple API allowing us to call functions exported by plugins. + +### IO + + libelektra-io.so + +**[io](io/)** provides the +[common API](https://doc.libelektra.org/api/current/html/group__kdbio.html) for +using asynchronous I/O bindings. + +### Notification + + libelektra-notification.so + +**[notification](notification/)** provides the [notification API](https://doc.libelektra.org/api/current/html/group__kdbnotification.html). +Usage examples: + +- [Basic notifications using polling](https://www.libelektra.org/examples/notificationpolling) +- [Using asynchronous I/O bindings](https://www.libelektra.org/examples/notificationasync) +- [Reload KDB when Elektra's configuration has changed](https://www.libelektra.org/examples/notificationreload) diff --git a/src/libs/invoke/invoke.c b/src/libs/invoke/invoke.c index 9e139aaee9f..2b78f3ef85d 100644 --- a/src/libs/invoke/invoke.c +++ b/src/libs/invoke/invoke.c @@ -5,6 +5,7 @@ * * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) */ +#include #include #include #include // for elektraPluginOpen/Close @@ -337,7 +338,7 @@ void elektraInvokeClose (ElektraInvokeHandle * handle, Key * errorKey) } /** - * Invokes a deferable function on a plugin handle. + * Invokes a deferable function on an invoke handle. * If the function is exported by the plugin it is directly invoked, * if the plugin supports deferring calls, the call is deferred. * @@ -345,33 +346,17 @@ void elektraInvokeClose (ElektraInvokeHandle * handle, Key * errorKey) * * @param handle invoke handle * @param elektraPluginFunctionName function name - * @param ks parameter key set + * @param parameters parameter key set * @retval 0 on success - * @retval 1 when function not exported and deferring unsupported by plugin + * @retval -1 when the call failed (direct call and deferring not available) */ int elektraInvokeCallDeferable (ElektraInvokeHandle * handle, const char * elektraPluginFunctionName, KeySet * parameters) { - ElektraDeferredCallable direct = *(ElektraDeferredCallable *) elektraInvokeGetFunction (handle, elektraPluginFunctionName); - if (direct) - { - direct (handle->plugin, parameters); - } - else + if (!handle) { - ElektraDeferredCall deferredCall = *(ElektraDeferredCall *) elektraInvokeGetFunction (handle, "deferredCall"); - if (deferredCall) - { - deferredCall (handle->plugin, elektraPluginFunctionName, parameters); - } - else - { - // no direct call and deferring possible - return 1; - } + return -1; } - - // success - return 0; + return elektraDeferredCall (handle->plugin, elektraPluginFunctionName, parameters); } /** @@ -384,9 +369,61 @@ int elektraInvokeCallDeferable (ElektraInvokeHandle * handle, const char * elekt */ void elektraInvokeExecuteDeferredCalls (ElektraInvokeHandle * handle, ElektraDeferredCallList * list) { + if (!handle) + { + return; + } elektraDeferredCallsExecute (handle->plugin, list); } +/** + * Call a deferable function on a plugin handle. + * If the function is exported by the plugin it is directly invoked, + * if the plugin supports deferring calls, the call is deferred. + * If both is possible (function is exported and deferred calls are supported), + * the function is directly called and the call is deferred (i.e. for nested plugins). + * + * @param handle invoke handle + * @param elektraPluginFunctionName function name + * @param parameters parameter key set. Can bee freed afterwards. + * @retval 0 on success + * @retval -1 when the call failed (direct call and deferring not available) + */ +int elektraDeferredCall (Plugin * handle, const char * elektraPluginFunctionName, KeySet * parameters) +{ + ELEKTRA_NOT_NULL (handle); + ELEKTRA_NOT_NULL (elektraPluginFunctionName); + + int result; + size_t direct = elektraPluginGetFunction (handle, elektraPluginFunctionName); + if (direct) + { + ElektraDeferredCallable directFn = (ElektraDeferredCallable) direct; + directFn (handle, parameters); + result = 0; // success + } + else + { + // no direct call possible + result = -1; + } + + size_t deferredCall = elektraPluginGetFunction (handle, "deferredCall"); + if (deferredCall) + { + ElektraDeferredCall deferredCallFn = (ElektraDeferredCall) deferredCall; + deferredCallFn (handle, elektraPluginFunctionName, parameters); + result = 0; // success + } + else + { + // deferred calls not possible + result = -1; + } + + return result; +} + /** * Add a new deferred call to the deferred call list. * @@ -400,6 +437,8 @@ void elektraInvokeExecuteDeferredCalls (ElektraInvokeHandle * handle, ElektraDef */ int elektraDeferredCallAdd (ElektraDeferredCallList * list, const char * name, KeySet * parameters) { + ELEKTRA_NOT_NULL (list); + ELEKTRA_NOT_NULL (name); _ElektraDeferredCall * item = elektraMalloc (sizeof *item); if (item == NULL) { @@ -453,6 +492,7 @@ ElektraDeferredCallList * elektraDeferredCallCreateList (void) */ void elektraDeferredCallDeleteList (ElektraDeferredCallList * list) { + ELEKTRA_NOT_NULL (list); _ElektraDeferredCall * item = list->head; while (item != NULL) { @@ -478,6 +518,8 @@ void elektraDeferredCallDeleteList (ElektraDeferredCallList * list) */ void elektraDeferredCallsExecute (Plugin * plugin, ElektraDeferredCallList * list) { + ELEKTRA_NOT_NULL (plugin); + ELEKTRA_NOT_NULL (list); _ElektraDeferredCall * item = list->head; while (item != NULL) { diff --git a/src/libs/io/CMakeLists.txt b/src/libs/io/CMakeLists.txt index e21e67182b7..110d9a77a50 100644 --- a/src/libs/io/CMakeLists.txt +++ b/src/libs/io/CMakeLists.txt @@ -2,7 +2,7 @@ set (SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/io.c") set (LIBRARY_NAME elektra-io) -add_lib (io SOURCES ${SOURCES} LINK_ELEKTRA elektra-kdb) +add_lib (io SOURCES ${SOURCES} LINK_ELEKTRA elektra-kdb elektra-invoke) configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/${LIBRARY_NAME}.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}.pc" @ONLY) diff --git a/src/libs/io/adapter/dbus/dbus.c b/src/libs/io/adapter/dbus/dbus.c index 2427ca54a5c..9034afdf586 100644 --- a/src/libs/io/adapter/dbus/dbus.c +++ b/src/libs/io/adapter/dbus/dbus.c @@ -6,7 +6,7 @@ * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) */ #include -#include +#include #include #include diff --git a/src/libs/io/adapter/zeromq/zeromq.c b/src/libs/io/adapter/zeromq/zeromq.c index fdcfc1006b4..00efa3124bb 100644 --- a/src/libs/io/adapter/zeromq/zeromq.c +++ b/src/libs/io/adapter/zeromq/zeromq.c @@ -7,7 +7,7 @@ */ #include #include -#include +#include #include #include diff --git a/src/libs/io/io.c b/src/libs/io/io.c index 99bb3ca0a41..8ec853534da 100644 --- a/src/libs/io/io.c +++ b/src/libs/io/io.c @@ -8,6 +8,7 @@ */ #include +#include #include #include #include @@ -34,22 +35,7 @@ void elektraIoSetBinding (KDB * kdb, ElektraIoInterface * ioBinding) continue; } - size_t func = elektraPluginGetFunction (plugin, "setIoBinding"); - if (func) - { - ElektraIoPluginSetBinding setIoBinding = (ElektraIoPluginSetBinding) func; - setIoBinding (plugin, parameters); - } - else - { - func = elektraPluginGetFunction (plugin, "deferredCall"); - if (func) - { - typedef void (*DeferFunctionCall) (Plugin * handle, char * name, KeySet * parameters); - DeferFunctionCall defer = (DeferFunctionCall) func; - defer (plugin, "setIoBinding", parameters); - } - } + elektraDeferredCall (plugin, "setIoBinding", parameters); } } diff --git a/src/libs/notification/CMakeLists.txt b/src/libs/notification/CMakeLists.txt index e7707232a76..b0e43bed8e8 100644 --- a/src/libs/notification/CMakeLists.txt +++ b/src/libs/notification/CMakeLists.txt @@ -10,14 +10,12 @@ else () set (LIBRARY_NAME elektra-notification) - add_lib (notification SOURCES ${SOURCES} LINK_ELEKTRA elektra-kdb elektra-ease) + add_lib (notification SOURCES ${SOURCES} LINK_ELEKTRA elektra-kdb elektra-ease elektra-invoke) configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/${LIBRARY_NAME}.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}.pc" @ONLY) install (FILES "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}.pc" DESTINATION lib${LIB_SUFFIX}/${TARGET_PKGCONFIG_FOLDER}) - add_subdirectory (example) - if (ENABLE_TESTING) add_subdirectory (tests) endif (ENABLE_TESTING) diff --git a/src/libs/notification/example/CMakeLists.txt b/src/libs/notification/example/CMakeLists.txt deleted file mode 100644 index 8f9baee5b68..00000000000 --- a/src/libs/notification/example/CMakeLists.txt +++ /dev/null @@ -1,49 +0,0 @@ -include (LibAddMacros) -include (LibAddBinding) - -# Build example_notification -set (EXAMPLE example_notification) - -set (SRC_FILES example_notification.c) -add_headers (ELEKTRA_HEADERS) -set (SOURCES ${SRC_FILES} ${ELEKTRA_HEADERS}) - -add_executable (${EXAMPLE} ${SOURCES}) -add_dependencies (${EXAMPLE} kdberrors_generated) - -# target_include_directories (${EXAMPLE} SYSTEM PUBLIC ${LIBUV_INCLUDE_DIRS}) - -target_link_elektra (${EXAMPLE} elektra-kdb elektra-notification) - -# - The asynchronous example requires io_uv -# - io_uv is disabled for ASAN builds, see https://github.com/ElektraInitiative/libelektra/issues/2007 -check_binding_included ("io_uv" IS_INCLUDED SUBDIRECTORY "io/uv" SILENT) -find_package (PkgConfig) -pkg_check_modules (LIBUV QUIET libuv) -if (IS_INCLUDED AND LIBUV_FOUND AND NOT ENABLE_ASAN) - - set (EXAMPLE example_notification_async) # Build example_notification_async - - set (SRC_FILES example_notification_async.c) - add_headers (ELEKTRA_HEADERS) - set (SOURCES ${SRC_FILES} ${ELEKTRA_HEADERS}) - if (BUILD_FULL OR BUILD_STATIC) - - list (APPEND SOURCES - $) # add sources for elektra-io-uv for static and full builds - endif () - - add_executable (${EXAMPLE} ${SOURCES}) - add_dependencies (${EXAMPLE} kdberrors_generated) - - target_link_elektra (${EXAMPLE} elektra-kdb elektra-notification elektra-io elektra-io-uv) - if (BUILD_FULL OR BUILD_STATIC) - target_link_libraries (${EXAMPLE} ${LIBUV_LDFLAGS}) - endif () - - if (LIBUV_VERSION VERSION_LESS "1.0") - target_compile_definitions (${EXAMPLE} PRIVATE "HAVE_LIBUV0") - else () - target_compile_definitions (${EXAMPLE} PRIVATE "HAVE_LIBUV1") - endif () -endif () diff --git a/src/libs/notification/example/README.md b/src/libs/notification/example/README.md deleted file mode 100644 index d654e7581ae..00000000000 --- a/src/libs/notification/example/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Example Applications for Notifications - -This folder contains two example notifications: - -- example_notification: Repeatedly calls kdbGet, does not require transport plugins -- example_notification_async: Uses asynchronous I/O. Requires transport plugins - -Both applications use the same keys: - -- /sw/tests/example_notification/#0/current/value: Set to any integer value -- /sw/tests/example_notification/#0/current/color: Set the text color. Valid - values are "red", "green" and "blue". - -## "example_notification" - -Is always built with the notification library. - -## "example_notification_async" - -Requires: - -- Binding: `io_uv` - -Usage: - -Make sure that the required transport plugins are mounted (e.g. for D-Bus): - -> kdb global-mount dbus dbusrecv diff --git a/src/libs/notification/notification.c b/src/libs/notification/notification.c index 8357b1c43f1..b914f32fc44 100644 --- a/src/libs/notification/notification.c +++ b/src/libs/notification/notification.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -662,22 +663,7 @@ static void pluginsOpenNotification (KDB * kdb, ElektraNotificationCallback call } - size_t func = elektraPluginGetFunction (plugin, "openNotification"); - if (func) - { - ElektraNotificationOpenNotification openNotification = (ElektraNotificationOpenNotification) func; - openNotification (plugin, parameters); - } - else - { - func = elektraPluginGetFunction (plugin, "deferredCall"); - if (func) - { - typedef void (*DeferFunctionCall) (Plugin * handle, char * name, KeySet * parameters); - DeferFunctionCall defer = (DeferFunctionCall) func; - defer (plugin, "openNotification", parameters); - } - } + elektraDeferredCall (plugin, "openNotification", parameters); } } @@ -699,26 +685,21 @@ static void pluginsCloseNotification (KDB * kdb) continue; } - size_t func = elektraPluginGetFunction (plugin, "closeNotification"); - if (func) - { - ElektraNotificationCloseNotification closeNotification = (ElektraNotificationCloseNotification) func; - closeNotification (plugin, NULL); - } - else - { - func = elektraPluginGetFunction (plugin, "deferredCall"); - if (func) - { - typedef void (*DeferFunctionCall) (Plugin * handle, char * name, KeySet * parameters); - DeferFunctionCall defer = (DeferFunctionCall) func; - defer (plugin, "closeNotification", NULL); - } - } + elektraDeferredCall (plugin, "closeNotification", NULL); } } } +/** + * @see kdbnotificationinternal.h ::ElektraNotificationKdbUpdate + */ +static void elektraNotificationKdbUpdate (KDB * kdb, Key * changedKey) +{ + KeySet * ks = ksNew (0, KS_END); + kdbGet (kdb, ks, changedKey); + ksDel (ks); +} + int elektraNotificationOpen (KDB * kdb) { // Make sure kdb is not null @@ -760,6 +741,7 @@ int elektraNotificationOpen (KDB * kdb) return 0; } context->kdb = kdb; + context->kdbUpdate = &elektraNotificationKdbUpdate; context->notificationPlugin = notificationPlugin; // Get notification callback from notification plugin @@ -850,9 +832,29 @@ static Plugin * getNotificationPlugin (KDB * kdb) } } -int elektraNotificationRegisterInt (KDB * kdb, Key * key, int * variable) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (int, Int) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (unsigned int, UnsignedInt) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (long, Long) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (unsigned long, UnsignedLong) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (float, Float) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (double, Double) + +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_boolean_t, KdbBoolean) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_char_t, KdbChar) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_octet_t, KdbOctet) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_short_t, KdbShort) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_unsigned_short_t, KdbUnsignedShort) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_long_t, KdbLong) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_unsigned_long_t, KdbUnsignedLong) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_long_long_t, KdbLongLong) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_unsigned_long_long_t, KdbUnsignedLongLong) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_float_t, KdbFloat) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_double_t, KdbDouble) +ELEKTRA_NOTIFICATION_TYPE_DEFINITION (kdb_long_double_t, KdbLongDouble) + +int elektraNotificationRegisterCallback (KDB * kdb, Key * key, ElektraNotificationChangeCallback callback, void * context) { - if (!kdb || !key || !variable) + if (!kdb || !key || !callback) { ELEKTRA_LOG_WARNING ("null pointer passed"); return 0; @@ -866,18 +868,18 @@ int elektraNotificationRegisterInt (KDB * kdb, Key * key, int * variable) } // Get register function from plugin - size_t func = elektraPluginGetFunction (notificationPlugin, "registerInt"); + size_t func = elektraPluginGetFunction (notificationPlugin, "registerCallback"); if (!func) { return 0; } // Call register function - ElektraNotificationPluginRegisterInt registerFunc = (ElektraNotificationPluginRegisterInt) func; - return registerFunc (notificationPlugin, key, variable); + ElektraNotificationPluginRegisterCallback registerFunc = (ElektraNotificationPluginRegisterCallback) func; + return registerFunc (notificationPlugin, key, callback, context); } -int elektraNotificationRegisterCallback (KDB * kdb, Key * key, ElektraNotificationChangeCallback callback) +int elektraNotificationRegisterCallbackSameOrBelow (KDB * kdb, Key * key, ElektraNotificationChangeCallback callback, void * context) { if (!kdb || !key || !callback) { @@ -893,13 +895,41 @@ int elektraNotificationRegisterCallback (KDB * kdb, Key * key, ElektraNotificati } // Get register function from plugin - size_t func = elektraPluginGetFunction (notificationPlugin, "registerCallback"); + size_t func = elektraPluginGetFunction (notificationPlugin, "registerCallbackSameOrBelow"); if (!func) { return 0; } // Call register function - ElektraNotificationPluginRegisterCallback registerFunc = (ElektraNotificationPluginRegisterCallback) func; - return registerFunc (notificationPlugin, key, callback); + ElektraNotificationPluginRegisterCallbackSameOrBelow registerFunc = (ElektraNotificationPluginRegisterCallbackSameOrBelow) func; + return registerFunc (notificationPlugin, key, callback, context); +} + +int elektraNotificationSetConversionErrorCallback (KDB * kdb, ElektraNotificationConversionErrorCallback callback, void * context) +{ + if (!kdb || !callback) + { + ELEKTRA_LOG_WARNING ("null pointer passed"); + return 0; + } + + // Find notification plugin + Plugin * notificationPlugin = getNotificationPlugin (kdb); + if (!notificationPlugin) + { + return 0; + } + + // Get register function from plugin + size_t func = elektraPluginGetFunction (notificationPlugin, "setConversionErrorCallback"); + if (!func) + { + return 0; + } + + // Call register function + ElektraNotificationSetConversionErrorCallback setCallbackFunc = (ElektraNotificationSetConversionErrorCallback) func; + setCallbackFunc (notificationPlugin, callback, context); + return 1; } diff --git a/src/libs/notification/tests/testlib_notification.c b/src/libs/notification/tests/testlib_notification.c index 5847a6b2312..968a8a23d52 100644 --- a/src/libs/notification/tests/testlib_notification.c +++ b/src/libs/notification/tests/testlib_notification.c @@ -20,7 +20,6 @@ static void test_openclose (void) { printf ("test open & close\n"); - // TODO test with ASAN and with & without cascading key Key * key = keyNew ("system/sw/tests/testlib_notification", KEY_END); KDB * kdb = kdbOpen (key); exit_if_fail (kdb, "opening kdb failed"); @@ -71,7 +70,7 @@ static void test_registerInt (void) keyDel (valueKey); } -static void testCallback (Key * key ELEKTRA_UNUSED) +static void testCallback (Key * key ELEKTRA_UNUSED, void * context ELEKTRA_UNUSED) { callback_called = 1; } @@ -86,11 +85,11 @@ static void test_registerCallback (void) KDB * kdb = kdbOpen (key); - succeed_if (elektraNotificationRegisterCallback (kdb, valueKey, testCallback) == 0, "register should fail before open"); + succeed_if (elektraNotificationRegisterCallback (kdb, valueKey, testCallback, NULL) == 0, "register should fail before open"); elektraNotificationOpen (kdb); - succeed_if (elektraNotificationRegisterCallback (kdb, valueKey, testCallback), "register failed"); + succeed_if (elektraNotificationRegisterCallback (kdb, valueKey, testCallback, NULL), "register failed"); // call kdbGet; value gets automatically updated KeySet * config = ksNew (0, KS_END); diff --git a/src/plugins/boolean/boolean.c b/src/plugins/boolean/boolean.c index 040e19521e5..68bff80d1de 100644 --- a/src/plugins/boolean/boolean.c +++ b/src/plugins/boolean/boolean.c @@ -29,8 +29,8 @@ typedef enum { typedef struct { - char * true; - char * false; + char * trueValue; + char * falseValue; InvalidAction invalid; char ** trueValues; char ** falseValues; @@ -115,14 +115,14 @@ static void normalize (Key * key, Key * parentKey, BoolData * data) if (isTrue (value, trueStrings)) { keySetMeta (key, "origvalue", keyString (key)); - ELEKTRA_LOG_DEBUG ("Convert “%s” to “%s”", value, data->true); - keySetString (key, data->true); + ELEKTRA_LOG_DEBUG ("Convert “%s” to “%s”", value, data->trueValue); + keySetString (key, data->trueValue); } else if (isFalse (value, falseStrings)) { keySetMeta (key, "origvalue", keyString (key)); - ELEKTRA_LOG_DEBUG ("Convert “%s” to “%s”", value, data->false); - keySetString (key, data->false); + ELEKTRA_LOG_DEBUG ("Convert “%s” to “%s”", value, data->falseValue); + keySetString (key, data->falseValue); } else { @@ -135,20 +135,20 @@ static void normalize (Key * key, Key * parentKey, BoolData * data) { ELEKTRA_ADD_WARNINGF (ELEKTRA_WARNING_INVALID_BOOL, parentKey, "Key %s with value %s is not a valid boolean. Defaulting to %s.", keyName (key), - keyString (key), data->true); + keyString (key), data->trueValue); } keySetMeta (key, "origvalue", keyString (key)); - keySetString (key, data->true); + keySetString (key, data->trueValue); break; case FALSE: if (data->invalid & WARNING) { ELEKTRA_ADD_WARNINGF (ELEKTRA_WARNING_INVALID_BOOL, parentKey, "Key %s with value %s is not a valid boolean. Defaulting to %s.", keyName (key), - keyString (key), data->false); + keyString (key), data->falseValue); } keySetMeta (key, "origvalue", keyString (key)); - keySetString (key, data->false); + keySetString (key, data->falseValue); break; case WARNING: break; @@ -228,8 +228,8 @@ static void parseConfig (KeySet * config, BoolData * data) else data->invalid |= WARNING; } - data->true = (char *) trueValue; - data->false = (char *) falseValue; + data->trueValue = (char *) trueValue; + data->falseValue = (char *) falseValue; Key * validTrueKey = ksLookupByName (config, "/true", 0); Key * validFalseKey = ksLookupByName (config, "/false", 0); if (validTrueKey) @@ -323,8 +323,8 @@ int elektraBooleanSet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned ELEKTRA parseConfig (config, data); elektraPluginSetData (handle, data); } - const char * trueValue = data->true; - const char * falseValue = data->false; + const char * trueValue = data->trueValue; + const char * falseValue = data->falseValue; ksRewind (returned); Key * key; @@ -360,4 +360,3 @@ Plugin * ELEKTRA_PLUGIN_EXPORT (boolean) ELEKTRA_PLUGIN_SET, &elektraBooleanSet, ELEKTRA_PLUGIN_END); } - diff --git a/src/plugins/dbus/dbus.h b/src/plugins/dbus/dbus.h index 30f1a8e1695..f7ee0f3a743 100644 --- a/src/plugins/dbus/dbus.h +++ b/src/plugins/dbus/dbus.h @@ -18,7 +18,7 @@ #include // elektraIoDbus*() -#include +#include /** * @internal diff --git a/src/plugins/dbusrecv/dbusrecv.h b/src/plugins/dbusrecv/dbusrecv.h index b6eafc2d8c7..fe8ebd4d374 100644 --- a/src/plugins/dbusrecv/dbusrecv.h +++ b/src/plugins/dbusrecv/dbusrecv.h @@ -19,7 +19,7 @@ #include // elektraIoDbus*() -#include +#include /** * @internal diff --git a/src/plugins/dbusrecv/testmod_dbusrecv.c b/src/plugins/dbusrecv/testmod_dbusrecv.c index 8b433f33ce5..f819f055fdf 100644 --- a/src/plugins/dbusrecv/testmod_dbusrecv.c +++ b/src/plugins/dbusrecv/testmod_dbusrecv.c @@ -10,7 +10,7 @@ #include // printf() & co -#include +#include #include #include diff --git a/src/plugins/internalnotification/README.md b/src/plugins/internalnotification/README.md index 7b4c1924e2f..02ec3a26124 100644 --- a/src/plugins/internalnotification/README.md +++ b/src/plugins/internalnotification/README.md @@ -15,51 +15,14 @@ Allows applications to automatically update registered variables when the value of a specified key has changed. Application developers should use the -[Notification API](https://doc.libelektra.org/api/current/html/group__kdbnotification.html) +[notification API](https://doc.libelektra.org/api/current/html/group__kdbnotification.html) instead of the functions exported by this plugin. The API is easier to use and decouples applications from this plugin. -## Exported Methods +## Exported Functions -This plugin exports the following functions. The functions addresses are -exported below `system/elektra/modules/internalnotification/exports/`. - -All functions have a similar signature: - -```C -int registerX (Plugin * handle, Key * key, ...); -``` - -If the given `key` is contained in a KeySet on a kdbGet or kdbSet operation a -action according to the function's description is executed. -Cascading keys as `key` names are also supported. - -*Parameters* - -- *handle* The internal plugin `handle` is exported as - `system/elektra/modules/internalnotification/exports/handle`. -- *key* Key to watch for changes. - -*Returns* - -1 on success, 0 otherwise - -Please note that the plugin API may change as this plugin is experimental. - -### int registerInt (Plugin * handle, Key * key, int * variable) - -The key's value is converted to integer and the registered variable is updated -with the new value. - -*Additional Parameters* - -- *variable* Pointer to the variable - -### int registerCallback (Plugin * handle, Key * key, ElektraNotificationChangeCallback callback) - -When the key changes the callback is called with the new key. - -*Additional Parameters* - -- *callback* Callback function with the signature - `void (*ElektraNotificationChangeCallback) (Key * key)`. +This plugin exports various functions starting with "register*" below +`system/elektra/modules/internalnotification/exports/`. +These functions should not be used directly. +Instead the [notification API](https://doc.libelektra.org/api/current/html/group__kdbnotification.html) +should be used. diff --git a/src/plugins/internalnotification/internalnotification.c b/src/plugins/internalnotification/internalnotification.c index e9a4349431d..524f314ea4e 100644 --- a/src/plugins/internalnotification/internalnotification.c +++ b/src/plugins/internalnotification/internalnotification.c @@ -9,19 +9,15 @@ #include "internalnotification.h" -#include -#include - #include #include #include #include #include -typedef enum { - TYPE_INT = 1 << 0, - TYPE_CALLBACK = 1 << 1, -} KeyRegistrationType; +#include // isspace() +#include // errno +#include // strto* functions /** * Structure for registered key variable pairs @@ -30,13 +26,11 @@ typedef enum { struct _KeyRegistration { char * name; - KeyRegistrationType type; char * lastValue; - union - { - int * variable; - ElektraNotificationChangeCallback callback; - } ref; + int sameOrBelow; + int freeContext; + ElektraNotificationChangeCallback callback; + void * context; struct _KeyRegistration * next; }; typedef struct _KeyRegistration KeyRegistration; @@ -49,25 +43,88 @@ struct _PluginState { KeyRegistration * head; KeyRegistration * last; + ElektraNotificationConversionErrorCallback conversionErrorCallback; + void * conversionErrorCallbackContext; }; typedef struct _PluginState PluginState; +/** + * @see kdbnotificationinternal.h ::ElektraNotificationSetConversionErrorCallback + */ +static void elektraInternalnotificationSetConversionErrorCallback (Plugin * handle, ElektraNotificationConversionErrorCallback callback, + void * context) +{ + ELEKTRA_NOT_NULL (handle); + ELEKTRA_NOT_NULL (callback); + PluginState * data = elektraPluginGetData (handle); + ELEKTRA_NOT_NULL (data); + + data->conversionErrorCallback = callback; + data->conversionErrorCallbackContext = context; +} + /** * @internal - * Convert key name into cascading key name for comparison. + * Check if two keys have the same name. + * If one of the keys is cascading only the cascading names are compared. * - * Key name is not modified, nothing to free + * @param key key + * @param check check + * @retval 1 if keys have the same name + * @retval 0 otherwise + */ +static int checkKeyIsSame (Key * key, Key * check) +{ + int result = 0; + if (keyGetNamespace (check) == KEY_NS_CASCADING || keyGetNamespace (key) == KEY_NS_CASCADING) + { + const char * cascadingCheck = strrchr (keyName (check), '/'); + const char * cascadingKey = strrchr (keyName (key), '/'); + if (cascadingCheck != NULL && cascadingKey != NULL) + { + result = elektraStrCmp (cascadingKey, cascadingCheck) == 0; + } + else + { + if (cascadingCheck == NULL) + { + ELEKTRA_LOG_WARNING ("invalid key given: '%s' is not a valid key", cascadingCheck); + } + if (cascadingKey == NULL) + { + ELEKTRA_LOG_WARNING ("invalid key given: '%s' is not a valid key", cascadingKey); + } + } + } + else + { + result = elektraStrCmp (keyName (check), keyName (key)) == 0; + } + return result; +} + +/** + * @internal + * Check if a key has the same name or is below a given key. * - * @param keyName key name - * @return pointer to cascading name + * @param key key + * @param check check + * @retval 1 if key has the same name or is below + * @retval 0 otherwise */ -static const char * toCascadingName (const char * keyName) +static int checkKeyIsBelowOrSame (Key * key, Key * check) { - while (keyName[0] != '/') + int result = 0; + if (keyIsBelow (key, check)) + { + result = 1; + } + else { - keyName++; + result = checkKeyIsSame (key, check); } - return keyName; + + return result; } /** @@ -80,12 +137,11 @@ static const char * toCascadingName (const char * keyName) * @param key changed key * @param context callback context */ -static void elektraInternalnotificationDoUpdate (Key * changedKey, ElektraNotificationCallbackContext * context) +void elektraInternalnotificationDoUpdate (Key * changedKey, ElektraNotificationCallbackContext * context) { ELEKTRA_NOT_NULL (changedKey); ELEKTRA_NOT_NULL (context); - KDB * kdb = context->kdb; Plugin * plugin = context->notificationPlugin; PluginState * pluginState = elektraPluginGetData (plugin); @@ -95,28 +151,24 @@ static void elektraInternalnotificationDoUpdate (Key * changedKey, ElektraNotifi KeyRegistration * keyRegistration = pluginState->head; while (keyRegistration != NULL) { - Key * registereKey = keyNew (keyRegistration->name, KEY_END); + Key * registeredKey = keyNew (keyRegistration->name, KEY_END); - if (keyIsBelow (changedKey, registereKey)) - { - kdbChanged |= 1; - } - else if (keyGetNamespace (registereKey) == KEY_NS_CASCADING || keyGetNamespace (changedKey) == KEY_NS_CASCADING) + // check if registered key is same or below changed/commit key + kdbChanged |= checkKeyIsBelowOrSame (changedKey, registeredKey); + + if (keyRegistration->sameOrBelow) { - const char * cascadingRegisterdKey = toCascadingName (keyRegistration->name); - const char * cascadingChangedKey = toCascadingName (keyName (changedKey)); - kdbChanged |= elektraStrCmp (cascadingChangedKey, cascadingRegisterdKey) == 0; + // check if registered key is also above changed/commit key + kdbChanged |= checkKeyIsBelowOrSame (registeredKey, changedKey); } keyRegistration = keyRegistration->next; - keyDel (registereKey); + keyDel (registeredKey); } if (kdbChanged) { - KeySet * ks = ksNew (0, KS_END); - kdbGet (kdb, ks, changedKey); - ksDel (ks); + context->kdbUpdate (context->kdb, changedKey); } keyDel (changedKey); } @@ -125,18 +177,30 @@ static void elektraInternalnotificationDoUpdate (Key * changedKey, ElektraNotifi * Creates a new KeyRegistration structure and appends it at the end of the registration list * @internal * - * @param pluginState internal plugin data structure + * @param pluginState internal plugin data structure + * @param key key + * @param callback callback for changes + * @param context context for callback + * @param freeContext context needs to be freed on close * * @return pointer to created KeyRegistration structure or NULL if memory allocation failed */ -static KeyRegistration * elektraInternalnotificationAddNewRegistration (PluginState * pluginState) +static KeyRegistration * elektraInternalnotificationAddNewRegistration (PluginState * pluginState, Key * key, + ElektraNotificationChangeCallback callback, void * context, + int freeContext) { - KeyRegistration * item = elektraCalloc (sizeof *item); + KeyRegistration * item = elektraMalloc (sizeof *item); if (item == NULL) { return NULL; } item->next = NULL; + item->lastValue = NULL; + item->name = elektraStrDup (keyName (key)); + item->callback = callback; + item->context = context; + item->sameOrBelow = 0; + item->freeContext = freeContext; if (pluginState->head == NULL) { @@ -153,6 +217,29 @@ static KeyRegistration * elektraInternalnotificationAddNewRegistration (PluginSt return item; } +/** + * @internal + * Check if a key set contains a key that is same or below a given key. + * + * @param key key + * @param ks key set + * @retval 1 if the key set contains the key + * @retval 0 otherwise + */ +static int keySetContainsSameOrBelow (Key * check, KeySet * ks) +{ + Key * current; + ksRewind (ks); + while ((current = ksNext (ks)) != NULL) + { + if (checkKeyIsBelowOrSame (check, current)) + { + return 1; + } + } + return 0; +} + /** * Updates all KeyRegistrations according to data from the given KeySet * @internal @@ -170,167 +257,242 @@ void elektraInternalnotificationUpdateRegisteredKeys (Plugin * plugin, KeySet * KeyRegistration * registeredKey = pluginState->head; while (registeredKey != NULL) { - Key * key = ksLookupByName (keySet, registeredKey->name, 0); - if (key == NULL) - { - registeredKey = registeredKey->next; - continue; - } - - // Detect changes for string keys int changed = 0; - if (!keyIsString (key)) + Key * key; + if (registeredKey->sameOrBelow) { - // always notify for binary keys - changed = 1; + Key * checkKey = keyNew (registeredKey->name, KEY_END); + if (keySetContainsSameOrBelow (checkKey, keySet)) + { + changed = 1; + key = checkKey; + } + else + { + keyDel (checkKey); + } } else { - const char * currentValue = keyString (key); - changed = registeredKey->lastValue == NULL || strcmp (currentValue, registeredKey->lastValue) != 0; - - if (changed) + key = ksLookupByName (keySet, registeredKey->name, 0); + if (key != NULL) { - // Save last value - char * buffer = elektraStrDup (currentValue); - if (buffer) + // Detect changes for string keys + if (!keyIsString (key)) + { + // always notify for binary keys + changed = 1; + } + else { - if (registeredKey->lastValue != NULL) + const char * currentValue = keyString (key); + changed = registeredKey->lastValue == NULL || strcmp (currentValue, registeredKey->lastValue) != 0; + + if (changed) { - // Free previous value - elektraFree (registeredKey->lastValue); + // Save last value + char * buffer = elektraStrDup (currentValue); + if (buffer) + { + if (registeredKey->lastValue != NULL) + { + // Free previous value + elektraFree (registeredKey->lastValue); + } + registeredKey->lastValue = buffer; + } } - registeredKey->lastValue = buffer; } } } if (changed) { - // Perform actions depending on type - switch (registeredKey->type) + ELEKTRA_LOG_DEBUG ("found changed registeredKey=%s with string value \"%s\". using context or variable=%p", + registeredKey->name, keyString (key), registeredKey->context); + + // Invoke callback + ElektraNotificationChangeCallback callback = *(ElektraNotificationChangeCallback) registeredKey->callback; + callback (key, registeredKey->context); + if (registeredKey->sameOrBelow) { - case TYPE_INT: - ELEKTRA_LOG_DEBUG ("found registeredKey=%s; updating variable=%p with string value \"%s\"", - registeredKey->name, (void *) registeredKey->ref.variable, keyString (key)); - - // Convert string value to long - char * end; - errno = 0; - long int value = strtol (keyString (key), &end, 10); - // Update variable if conversion was successful and did not exceed integer range - if (*end == 0 && errno == 0 && value <= INT_MAX && value >= INT_MIN) - { - *(registeredKey->ref.variable) = value; - } - else - { - ELEKTRA_LOG_WARNING ("conversion failed! keyString=\"%s\" *end=%c, errno=%d, value=%ld", - keyString (key), *end, errno, value); - } - break; - case TYPE_CALLBACK: - ELEKTRA_LOG_DEBUG ("found registeredKey=%s; invoking callback", registeredKey->name); - ElektraNotificationChangeCallback callback = - *(ElektraNotificationChangeCallback) registeredKey->ref.callback; - callback (key); - break; + keyDel (key); } } + // proceed with next registered key registeredKey = registeredKey->next; } } +// Generate register and conversion functions +// for built-in C types +#define TYPE int +#define VALUE_TYPE long int +#define TYPE_NAME Int +#define TO_VALUE (strtol (string, &end, 10)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION_RANGE (value <= INT_MAX && value >= INT_MIN) +#include "macros/add_type.h" + +#define TYPE unsigned int +#define VALUE_TYPE unsigned long int +#define TYPE_NAME UnsignedInt +#define TO_VALUE (strtoul (string, &end, 10)) +#define PRE_CHECK_BLOCK ELEKTRA_TYPE_NEGATIVE_PRE_CHECK_BLOCK +#define PRE_CHECK_CONVERSION ELEKTRA_TYPE_NEGATIVE_PRE_CHECK +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION_RANGE (value <= UINT_MAX) +#include "macros/add_type.h" + +#define TYPE long +#define TYPE_NAME Long +#define TO_VALUE (strtol (string, &end, 10)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE unsigned long +#define TYPE_NAME UnsignedLong +#define TO_VALUE (strtoul (string, &end, 10)) +#define PRE_CHECK_BLOCK ELEKTRA_TYPE_NEGATIVE_PRE_CHECK_BLOCK +#define PRE_CHECK_CONVERSION ELEKTRA_TYPE_NEGATIVE_PRE_CHECK +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE long long +#define TYPE_NAME LongLong +#define TO_VALUE (strtoll (string, &end, 10)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE unsigned long long +#define TYPE_NAME UnsignedLongLong +#define TO_VALUE (strtoull (string, &end, 10)) +#define PRE_CHECK_BLOCK ELEKTRA_TYPE_NEGATIVE_PRE_CHECK_BLOCK +#define PRE_CHECK_CONVERSION ELEKTRA_TYPE_NEGATIVE_PRE_CHECK +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE float +#define TYPE_NAME Float +#define TO_VALUE (strtof (string, &end)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE double +#define TYPE_NAME Double +#define TO_VALUE (strtod (string, &end)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +// for kdb_*_t Types +#define TYPE kdb_boolean_t +#define TYPE_NAME KdbBoolean +#define TO_VALUE (!strcmp (string, "1")) +#include "macros/add_type.h" + +#define TYPE kdb_char_t +#define TYPE_NAME KdbChar +#define TO_VALUE (string[0]) +#include "macros/add_type.h" + +#define TYPE kdb_octet_t +#define VALUE_TYPE unsigned int +#define TYPE_NAME KdbOctet +#define TO_VALUE (strtoul (string, &end, 10)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION_RANGE (value <= 255) +#include "macros/add_type.h" + +#define TYPE kdb_short_t +#define VALUE_TYPE int +#define TYPE_NAME KdbShort +#define TO_VALUE (strtol (string, &end, 10)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION_RANGE (value <= SHRT_MAX && value >= SHRT_MIN) +#include "macros/add_type.h" + +#define TYPE kdb_unsigned_short_t +#define VALUE_TYPE unsigned int +#define TYPE_NAME KdbUnsignedShort +#define TO_VALUE (strtoul (string, &end, 10)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION_RANGE (value <= USHRT_MAX) +#include "macros/add_type.h" + +#define TYPE kdb_long_t +#define TYPE_NAME KdbLong +#define TO_VALUE (strtol (string, &end, 10)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE kdb_unsigned_long_t +#define TYPE_NAME KdbUnsignedLong +#define TO_VALUE (strtoul (string, &end, 10)) +#define PRE_CHECK_BLOCK ELEKTRA_TYPE_NEGATIVE_PRE_CHECK_BLOCK +#define PRE_CHECK_CONVERSION ELEKTRA_TYPE_NEGATIVE_PRE_CHECK +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE kdb_long_long_t +#define TYPE_NAME KdbLongLong +#define TO_VALUE (ELEKTRA_LONG_LONG_S (string, &end, 10)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE kdb_unsigned_long_long_t +#define TYPE_NAME KdbUnsignedLongLong +#define TO_VALUE (ELEKTRA_UNSIGNED_LONG_LONG_S (string, &end, 10)) +#define PRE_CHECK_BLOCK ELEKTRA_TYPE_NEGATIVE_PRE_CHECK_BLOCK +#define PRE_CHECK_CONVERSION ELEKTRA_TYPE_NEGATIVE_PRE_CHECK +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE kdb_float_t +#define TYPE_NAME KdbFloat +#define TO_VALUE (strtof (string, &end)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE kdb_double_t +#define TYPE_NAME KdbDouble +#define TO_VALUE (strtod (string, &end)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" + +#define TYPE kdb_long_double_t +#define TYPE_NAME KdbLongDouble +#define TO_VALUE (strtold (string, &end)) +#define CHECK_CONVERSION ELEKTRA_TYPE_CHECK_CONVERSION +#include "macros/add_type.h" /** - * Subscribe for automatic updates to a given integer variable when the given - * key value has changed. - * - * Implementation of ElektraNotificationPluginRegisterInt() - * @see kdbnotificationinternal.h - * - * @param handle plugin handle - * @param variable integer variable - * @param key key to watch for changes - * - * @retval 1 on success - * @retval 0 on failure + * @see kdbnotificationinternal.h ::ElektraNotificationPluginRegisterCallback */ -int elektraInternalnotificationRegisterInt (Plugin * handle, Key * key, int * variable) +int elektraInternalnotificationRegisterCallback (Plugin * handle, Key * key, ElektraNotificationChangeCallback callback, void * context) { PluginState * pluginState = elektraPluginGetData (handle); ELEKTRA_ASSERT (pluginState != NULL, "plugin state was not initialized properly"); - KeyRegistration * registeredKey = elektraInternalnotificationAddNewRegistration (pluginState); + KeyRegistration * registeredKey = elektraInternalnotificationAddNewRegistration (pluginState, key, callback, context, 0); if (registeredKey == NULL) { return 0; } - // Copy key name - size_t nameBufferSize = keyGetNameSize (key); - char * nameBuffer = elektraMalloc (nameBufferSize); - if (nameBuffer == NULL) - { - return 0; - } - ssize_t result = keyGetName (key, nameBuffer, nameBufferSize); - if (result == 1 || result == -1) - { - return 0; - } - - // Save key registration - registeredKey->name = nameBuffer; - registeredKey->type = TYPE_INT; - registeredKey->ref.variable = variable; - return 1; } /** - * Subscribe for updates via callback when a given key value is changed. - * key value has changed. - * - * Implementation of ElektraNotificationPluginRegisterCallback() - * @see kdbnotificationinternal.h - * - * @param handle plugin handle - * @param key key to watch for changes - * @param callback callback function - * - * @retval 1 on success - * @retval 0 on failure + * @see kdbnotificationinternal.h ::ElektraNotificationPluginRegisterCallbackSameOrBelow */ -int elektraInternalnotificationRegisterCallback (Plugin * handle, Key * key, ElektraNotificationChangeCallback callback) +int elektraInternalnotificationRegisterCallbackSameOrBelow (Plugin * handle, Key * key, ElektraNotificationChangeCallback callback, + void * context) { PluginState * pluginState = elektraPluginGetData (handle); ELEKTRA_ASSERT (pluginState != NULL, "plugin state was not initialized properly"); - KeyRegistration * registeredKey = elektraInternalnotificationAddNewRegistration (pluginState); + KeyRegistration * registeredKey = elektraInternalnotificationAddNewRegistration (pluginState, key, callback, context, 0); if (registeredKey == NULL) { return 0; } - - // Copy key name - size_t nameBufferSize = keyGetNameSize (key); - char * nameBuffer = elektraMalloc (nameBufferSize); - if (nameBuffer == NULL) - { - return 0; - } - ssize_t result = keyGetName (key, nameBuffer, nameBufferSize); - if (result == 1 || result == -1) - { - return 0; - } - - // Save key registration - registeredKey->name = nameBuffer; - registeredKey->type = TYPE_CALLBACK; - registeredKey->ref.callback = callback; + registeredKey->sameOrBelow = 1; return 1; } @@ -368,10 +530,25 @@ int elektraInternalnotificationGet (Plugin * handle, KeySet * returned, Key * pa elektraInternalnotificationDoUpdate, KEY_END), // Export register* functions - keyNew ("system/elektra/modules/internalnotification/exports/registerInt", KEY_FUNC, - elektraInternalnotificationRegisterInt, KEY_END), + INTERNALNOTIFICATION_EXPORT_FUNCTION (Int), INTERNALNOTIFICATION_EXPORT_FUNCTION (UnsignedInt), + INTERNALNOTIFICATION_EXPORT_FUNCTION (Long), INTERNALNOTIFICATION_EXPORT_FUNCTION (UnsignedLong), + INTERNALNOTIFICATION_EXPORT_FUNCTION (LongLong), INTERNALNOTIFICATION_EXPORT_FUNCTION (UnsignedLongLong), + INTERNALNOTIFICATION_EXPORT_FUNCTION (Float), INTERNALNOTIFICATION_EXPORT_FUNCTION (Double), + + // Export register* functions for kdb_*_t types + INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbBoolean), INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbChar), + INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbOctet), INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbShort), + INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbUnsignedShort), INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbLong), + INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbUnsignedLong), INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbLongLong), + INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbUnsignedLongLong), INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbFloat), + INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbDouble), INTERNALNOTIFICATION_EXPORT_FUNCTION (KdbLongDouble), + keyNew ("system/elektra/modules/internalnotification/exports/registerCallback", KEY_FUNC, elektraInternalnotificationRegisterCallback, KEY_END), + keyNew ("system/elektra/modules/internalnotification/exports/registerCallbackSameOrBelow", KEY_FUNC, + elektraInternalnotificationRegisterCallbackSameOrBelow, KEY_END), + keyNew ("system/elektra/modules/internalnotification/exports/setConversionErrorCallback", KEY_FUNC, + elektraInternalnotificationSetConversionErrorCallback, KEY_END), #include ELEKTRA_README (internalnotification) @@ -430,6 +607,8 @@ int elektraInternalnotificationOpen (Plugin * handle, Key * parentKey ELEKTRA_UN // Initialize list pointers for registered keys pluginState->head = NULL; pluginState->last = NULL; + pluginState->conversionErrorCallback = NULL; + pluginState->conversionErrorCallbackContext = NULL; } return 1; @@ -461,6 +640,10 @@ int elektraInternalnotificationClose (Plugin * handle, Key * parentKey ELEKTRA_U { elektraFree (current->lastValue); } + if (current->freeContext) + { + elektraFree (current->context); + } elektraFree (current); current = next; diff --git a/src/plugins/internalnotification/internalnotification.h b/src/plugins/internalnotification/internalnotification.h index 1af7469e837..bb4da6cb099 100644 --- a/src/plugins/internalnotification/internalnotification.h +++ b/src/plugins/internalnotification/internalnotification.h @@ -11,9 +11,9 @@ #define ELEKTRA_PLUGIN_INTERNALNOTIFICATION_H #include +#include #include - int elektraInternalnotificationGet (Plugin * handle, KeySet * ks, Key * parentKey); int elektraInternalnotificationSet (Plugin * handle, KeySet * ks, Key * parentKey); int elektraInternalnotificationClose (Plugin * handle, Key * errorKey); @@ -23,5 +23,25 @@ Plugin * ELEKTRA_PLUGIN_EXPORT (internalnotification); // Not exported by plugin; used for testing void elektraInternalnotificationUpdateRegisteredKeys (Plugin * plugin, KeySet * keySet); +void elektraInternalnotificationDoUpdate (Key * changedKey, ElektraNotificationCallbackContext * context); + +#define INTERNALNOTIFICATION_REGISTER_NAME(TYPE_NAME) elektraInternalnotificationRegister##TYPE_NAME + +#define INTERNALNOTIFICATION_EXPORT_FUNCTION(TYPE_NAME) \ + keyNew ("system/elektra/modules/internalnotification/exports/register" #TYPE_NAME, KEY_FUNC, \ + INTERNALNOTIFICATION_REGISTER_NAME (TYPE_NAME), KEY_END) + +/** + * Structure containing conversion context + * @internal + */ +struct _ElektraInternalnotificationConversionContext +{ + void * variable; + ElektraNotificationConversionErrorCallback errorCallback; + void * errorCallbackContext; +}; +typedef struct _ElektraInternalnotificationConversionContext _ElektraInternalnotificationConversionContext; + #endif diff --git a/src/plugins/internalnotification/macros/add_type.h b/src/plugins/internalnotification/macros/add_type.h new file mode 100644 index 00000000000..5e9f5fa5aad --- /dev/null +++ b/src/plugins/internalnotification/macros/add_type.h @@ -0,0 +1,109 @@ +/** + * @copyright BSD License (see doc/COPYING or http://www.libelektra.org) + * + * @brief Add a type to the internalnotification plugin. + * + * Additional required steps: + * - Export the register function using INTERNALNOTIFICATION_EXPORT_FUNCTION in elektraInternalnotificationGet() + * - Update testmod_internalnotification.c: Generate additional test cases using the create_type_tests supermacro + * - Update kdbnotification.h: add a ELEKTRA_NOTIFICATION_TYPE_DECLARATION + * - Update libs/notification/notification.c: add a ELEKTRA_NOTIFICATION_TYPE_DEFINITION + * + * This supermacro creates the following functions: + * - void elektraInternalnotificationConvertTYPE_NAME (Key * key, void * context) + * - int elektraInternalnotificationRegisterTYPE_NAME (Plugin * handle, Key * key, TYPE * variable) + * + * @param TYPE valid C type (e.g. int or kdb_short_t) + * @param TYPE_NAME name suffix for the functions (e.g. Int or UnsignedLong) + * @param VALUE_TYPE optional, defaults to TYPE. Ideally a larger type assigned to variable `value` for + * checking the range before the variable is updated + * @param TO_VALUE expression for converting `string` (variable containing the key value) to VALUE_TYPE + * @param CHECK_CONVERSION optional, defaults to true. A boolean expression. Allows to check the range after + * conversion. Use INTERNALNOTIFICATION_CHECK_CONVERSION to check if a conversion using + * strto*()-functions was successful and INTERNALNOTIFICATION_CHECK_CONVERSION_RANGE (RANGE) + * to check additionally for a specified range. + */ +#ifndef TYPE +#error "You have to #define TYPE, TYPE_NAME and TO_VALUE before including the addType supermacro" +#endif +#ifndef VALUE_TYPE +// use type as default if not set +#define VALUE_TYPE TYPE +#endif +#ifndef TYPE_NAME +#error "You have to #define TYPE, TYPE_NAME and TO_VALUE before including the addType supermacro" +#endif +#ifndef TO_VALUE +#error "You have to #define TYPE, TYPE_NAME and TO_VALUE before including the addType supermacro" +#endif +#ifndef CHECK_CONVERSION +#define CHECK_CONVERSION 1 +#endif + +#define ELEKTRA_CONCAT(X, Y) ELEKTRA_CONCAT2 (X, Y) +#define ELEKTRA_CONCAT2(X, Y) X##Y + +#define INTERNALNOTIFICATION_CONVERSION_FUNCTION_NAME(TYPE_NAME) ELEKTRA_CONCAT (elektraInternalnotificationConvert, TYPE_NAME) +#define INTERNALNOTIFICATION_CONVERSION_CALLBACK_NAME(TYPE_NAME) ELEKTRA_CONCAT (elektraInternalnotificationConvertCallback, TYPE_NAME) + +#define INTERNALNOTIFICATION_REGISTER_SIGNATURE(TYPE, TYPE_NAME) \ + int INTERNALNOTIFICATION_REGISTER_NAME (TYPE_NAME) (Plugin * handle, Key * key, TYPE * variable) + +#define INTERNALNOTIFICATION_CONVERSION_CALLBACK_SIGNATURE(TYPE_NAME) \ + void INTERNALNOTIFICATION_CONVERSION_CALLBACK_NAME (TYPE_NAME) (Key * key, void * context) + +#define DISABLE_UNDEF_PARAMETERS +#define NAME_MACRO INTERNALNOTIFICATION_CONVERSION_FUNCTION_NAME +#include + +INTERNALNOTIFICATION_CONVERSION_CALLBACK_SIGNATURE (TYPE_NAME) +{ + _ElektraInternalnotificationConversionContext * ctx = (_ElektraInternalnotificationConversionContext *) context; + TYPE * variable = (TYPE *) ctx->variable; + if (!INTERNALNOTIFICATION_CONVERSION_FUNCTION_NAME (TYPE_NAME) (key, variable)) + { + if (ctx->errorCallback) + { + ctx->errorCallback (key, ctx->errorCallbackContext); + } + } +} + +INTERNALNOTIFICATION_REGISTER_SIGNATURE (TYPE, TYPE_NAME) +{ + PluginState * pluginState = elektraPluginGetData (handle); + ELEKTRA_ASSERT (pluginState != NULL, "plugin state was not initialized properly"); + + _ElektraInternalnotificationConversionContext * context = elektraMalloc (sizeof *context); + if (context == NULL) + { + return 0; + } + context->errorCallback = pluginState->conversionErrorCallback; + context->errorCallbackContext = pluginState->conversionErrorCallbackContext; + context->variable = variable; + + KeyRegistration * registeredKey = elektraInternalnotificationAddNewRegistration ( + pluginState, key, INTERNALNOTIFICATION_CONVERSION_CALLBACK_NAME (TYPE_NAME), context, 1); + if (registeredKey == NULL) + { + return 0; + } + return 1; +} + +#undef ELEKTRA_CONCAT +#undef ELEKTRA_CONCAT2 +#undef INTERNALNOTIFICATION_CONVERSION_CALLBACK_NAME +#undef INTERNALNOTIFICATION_CONVERSION_FUNCTION_NAME_SIGNATURE +#undef INTERNALNOTIFICATION_CONVERSION_CALLBACK_SIGNATURE +#undef INTERNALNOTIFICATION_REGISTER_SIGNATURE +#undef NAME_MACRO + +#undef TYPE +#undef VALUE_TYPE +#undef TYPE_NAME +#undef TO_VALUE +#undef CHECK_CONVERSION +#undef PRE_CHECK_BLOCK +#undef PRE_CHECK_CONVERSION diff --git a/src/plugins/internalnotification/macros/create_type_tests.h b/src/plugins/internalnotification/macros/create_type_tests.h new file mode 100644 index 00000000000..aa2fbb37fc2 --- /dev/null +++ b/src/plugins/internalnotification/macros/create_type_tests.h @@ -0,0 +1,103 @@ +/** + * @copyright BSD License (see doc/COPYING or http://www.libelektra.org) + * + * @brief Create test cases for internalnotification type. + * + * This supermacro creates the following functions: + * - int internalnotificationRegisterTYPE_NAME (Plugin * plugin, Key * key, TYPE * variable) + * - static void test_updateTYPE_NAME (void) + * - static void test_noUpdateTYPE_NAME (void) (only if INVALID_VALUE is defined) + * + * @param TYPE valid C type (e.g. int or kdb_short_t) + * @param TYPE_NAME name suffix for the functions (e.g. Int or UnsignedLong) + * @param TEST_VALUE value of type TYPE. Used for the "update" test case + * @param FORMAT_STRING format to convert TEST_VALUE to string (passed to elektraFormat()) + * @param CHECK_VALUE optional, default is (value == TEST_VALUE). Boolean expression to check if `value` equals the test value + * @param INVALID_VALUE optional. Value of type string. Used for the no update test case. If not defined, "no update" test case is + * omitted + * @param CHECK_INVALID optioal, defaults to (value == 0). Check if the variable `value` has not been updated. Value should be 0. + */ +#ifndef TYPE +#error "You have to #define TYPE, TYPE_NAME, FORMAT_STRING, TEST_VALUE and CHECK_VALUE before including the create_type_tests supermacro" +#endif +#ifndef TYPE_NAME +#error "You have to #define TYPE, TYPE_NAME, FORMAT_STRING, TEST_VALUE and CHECK_VALUE before including the create_type_tests supermacro" +#endif +#ifndef FORMAT_STRING +#error "You have to #define TYPE, TYPE_NAME, FORMAT_STRING, TEST_VALUE and CHECK_VALUE before including the create_type_tests supermacro" +#endif +#ifndef TEST_VALUE +#error "You have to #define TYPE, TYPE_NAME, FORMAT_STRING, TEST_VALUE and CHECK_VALUE before including the create_type_tests supermacro" +#endif +#ifndef CHECK_VALUE +#define CHECK_VALUE (value == TEST_VALUE) +#endif +#ifndef CHECK_INVALID +#define CHECK_INVALID (value == 0) +#endif + +#define ELEKTRA_CONCAT(X, Y) ELEKTRA_CONCAT2 (X, Y) +#define ELEKTRA_CONCAT2(X, Y) X##Y + +#define REGISTER_FUNC_NAME(TYPE_NAME) ELEKTRA_CONCAT (internalnotificationRegister, TYPE_NAME) + +#define TEST_CASE_UPDATE_SIGNATURE(TYPE_NAME) static void TEST_CASE_UPDATE_NAME (TYPE_NAME) (void) +#define TEST_CASE_NO_UPDATE_SIGNATURE(TYPE_NAME) static void TEST_CASE_NO_UPDATE_NAME (TYPE_NAME) (void) + +static int REGISTER_FUNC_NAME (TYPE_NAME) (Plugin * plugin, Key * key, TYPE * variable) +{ + size_t address = elektraPluginGetFunction (plugin, ELEKTRA_STRINGIFY (ELEKTRA_CONCAT (register, TYPE_NAME))); + if (!address) yield_error ("function not exported"); + + /* register key with plugin */ + ELEKTRA_NOTIFICATION_REGISTERFUNC_TYPEDEF (RegisterFuncType, TYPE) + return ((RegisterFuncType) address) (plugin, key, variable); +} + +TEST_CASE_UPDATE_SIGNATURE (TYPE_NAME) +{ + printf ("test update\n"); + KeySet * conf = ksNew (0, KS_END); + PLUGIN_OPEN ("internalnotification"); + Key * registeredKey = keyNew ("/test/internalnotification/value", KEY_END); + TYPE value = 0; + succeed_if (REGISTER_FUNC_NAME (TYPE_NAME) (plugin, registeredKey, &value) == 1, "registration was not successful"); + char * valueStr = elektraFormat (FORMAT_STRING, TEST_VALUE); + Key * valueKey = keyNew ("user/test/internalnotification/value", KEY_VALUE, valueStr, KEY_END); + KeySet * ks = ksNew (1, valueKey, KS_END); + elektraInternalnotificationUpdateRegisteredKeys (plugin, ks); + succeed_if (CHECK_VALUE, "registered value was not updated"); + free (valueStr); + keyDel (registeredKey); + ksDel (ks); + PLUGIN_CLOSE (); +} + +#ifdef INVALID_VALUE +TEST_CASE_NO_UPDATE_SIGNATURE (TYPE_NAME) +{ + printf ("test no update with invalid value\n"); + KeySet * conf = ksNew (0, KS_END); + PLUGIN_OPEN ("internalnotification"); + Key * valueKey = keyNew ("user/test/internalnotification/value", KEY_END); + KeySet * ks = ksNew (1, valueKey, KS_END); + TYPE value = 0; + succeed_if (REGISTER_FUNC_NAME (TYPE_NAME) (plugin, valueKey, &value) == 1, "registration was not successful"); + keySetString (valueKey, INVALID_VALUE); + elektraInternalnotificationUpdateRegisteredKeys (plugin, ks); + succeed_if (CHECK_INVALID, "registered value was updated"); + ksDel (ks); + PLUGIN_CLOSE (); +} +#endif + +#undef ELEKTRA_CONCAT +#undef ELEKTRA_CONCAT2 + +#undef TYPE +#undef TYPE_NAME +#undef FORMAT_STRING +#undef TEST_VALUE +#undef CHECK_VALUE +#undef CHECK_INVALID +#undef INVALID_VALUE diff --git a/src/plugins/internalnotification/testmod_internalnotification.c b/src/plugins/internalnotification/testmod_internalnotification.c index a8c4062b4ed..b5f84bffe33 100644 --- a/src/plugins/internalnotification/testmod_internalnotification.c +++ b/src/plugins/internalnotification/testmod_internalnotification.c @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -23,20 +24,64 @@ int callback_called; char * callback_keyValue; char * callback_keyName; +int doUpdate_callback_called; + +#define CALLBACK_CONTEXT_MAGIC_NUMBER ((void *) 1234) + +#define TEST_CASE_UPDATE_NAME(TYPE_NAME) test_update##TYPE_NAME +#define TEST_CASE_NO_UPDATE_NAME(TYPE_NAME) test_noUpdate##TYPE_NAME + +#define RUN_TYPE_TESTS(TYPE_NAME) \ + printf ("\n" #TYPE_NAME "\n----------------\n"); \ + TEST_CASE_UPDATE_NAME (TYPE_NAME) (); \ + TEST_CASE_NO_UPDATE_NAME (TYPE_NAME) (); + +static void test_callback (Key * key, void * context) +{ + succeed_if (context == CALLBACK_CONTEXT_MAGIC_NUMBER, "callback context was not passed"); + callback_called = 1; + callback_keyValue = (char *) keyValue (key); + callback_keyName = (char *) keyName (key); +} + static int internalnotificationRegisterInt (Plugin * plugin, Key * key, int * variable) { size_t address = elektraPluginGetFunction (plugin, "registerInt"); + if (!address) yield_error ("function not exported"); + + // Register key with plugin + ELEKTRA_NOTIFICATION_REGISTERFUNC_TYPEDEF (RegisterFuncType, int) + return ((RegisterFuncType) address) (plugin, key, variable); +} + +static int internalnotificationSetConversionErrorCallback (Plugin * plugin, ElektraNotificationConversionErrorCallback callback, + void * context) +{ + size_t address = elektraPluginGetFunction (plugin, "setConversionErrorCallback"); + if (!address) yield_error ("function not exported"); // Register key with plugin - return ((ElektraNotificationPluginRegisterInt) address) (plugin, key, variable); + ((ElektraNotificationSetConversionErrorCallback) address) (plugin, callback, context); + return 1; } -static int internalnotificationRegisterCallback (Plugin * plugin, Key * key, ElektraNotificationChangeCallback callback) +static int internalnotificationRegisterCallback (Plugin * plugin, Key * key, ElektraNotificationChangeCallback callback, void * context) { size_t address = elektraPluginGetFunction (plugin, "registerCallback"); + if (!address) yield_error ("function not exported"); // Register key with plugin - return ((ElektraNotificationPluginRegisterCallback) address) (plugin, key, callback); + return ((ElektraNotificationPluginRegisterCallback) address) (plugin, key, callback, context); +} + +static int internalnotificationRegisterCallbackSameOrBelow (Plugin * plugin, Key * key, ElektraNotificationChangeCallback callback, + void * context) +{ + size_t address = elektraPluginGetFunction (plugin, "registerCallbackSameOrBelow"); + if (!address) yield_error ("function not exported"); + + // Register key with plugin + return ((ElektraNotificationPluginRegisterCallbackSameOrBelow) address) (plugin, key, callback, context); } static int digits (long long number) @@ -190,6 +235,38 @@ static void test_intNoUpdateWithInvalidValue (void) PLUGIN_CLOSE (); } +static void test_conversionError (void) +{ + printf ("test conversion error callback is called on invalid value\n"); + + KeySet * conf = ksNew (0, KS_END); + PLUGIN_OPEN ("internalnotification"); + + Key * valueKey = keyNew ("user/test/internalnotification/value", KEY_END); + KeySet * ks = ksNew (1, valueKey, KS_END); + + succeed_if (internalnotificationSetConversionErrorCallback (plugin, test_callback, CALLBACK_CONTEXT_MAGIC_NUMBER) == 1, + "call to elektraInternalnotificationSetConversionErrorCallback was not successful"); + + int value = 123; + succeed_if (internalnotificationRegisterInt (plugin, valueKey, &value) == 1, + "call to elektraInternalnotificationRegisterInt was not successful"); + + keySetString (valueKey, "42abcd"); + + callback_called = 0; + callback_keyName = NULL; + callback_keyValue = NULL; + elektraInternalnotificationUpdateRegisteredKeys (plugin, ks); + + succeed_if (value == 123, "registered value was updated"); + succeed_if (callback_called, "conversion error callback was not called"); + succeed_if_same_string (keyName (valueKey), callback_keyName) succeed_if_same_string (keyString (valueKey), callback_keyValue) + + ksDel (ks); + PLUGIN_CLOSE (); +} + static void test_intUpdateWithValueNotYetExceedingIntMax (void) { printf ("test update with value = INT_MAX\n"); @@ -302,13 +379,6 @@ static void test_intNoUpdateWithValueExceedingIntMin (void) PLUGIN_CLOSE (); } -static void test_callback (Key * key) -{ - callback_called = 1; - callback_keyValue = (char *) keyValue (key); - callback_keyName = (char *) keyName (key); -} - static void test_callbackCalledWithKey (void) { printf ("test callback is called with changed key\n"); @@ -320,7 +390,7 @@ static void test_callbackCalledWithKey (void) Key * valueKey = keyNew ("user/test/internalnotification/value", KEY_VALUE, value, KEY_END); KeySet * ks = ksNew (1, valueKey, KS_END); - succeed_if (internalnotificationRegisterCallback (plugin, valueKey, test_callback) == 1, + succeed_if (internalnotificationRegisterCallback (plugin, valueKey, test_callback, CALLBACK_CONTEXT_MAGIC_NUMBER) == 1, "call to elektraInternalnotificationRegisterCallback was not successful"); elektraInternalnotificationUpdateRegisteredKeys (plugin, ks); @@ -344,7 +414,7 @@ static void test_callbackCalledWithChangeDetection (void) Key * valueKey = keyNew ("user/test/internalnotification/value", KEY_VALUE, value, KEY_END); KeySet * ks = ksNew (1, valueKey, KS_END); - succeed_if (internalnotificationRegisterCallback (plugin, valueKey, test_callback) == 1, + succeed_if (internalnotificationRegisterCallback (plugin, valueKey, test_callback, CALLBACK_CONTEXT_MAGIC_NUMBER) == 1, "call to elektraInternalnotificationRegisterCallback was not successful"); elektraInternalnotificationUpdateRegisteredKeys (plugin, ks); @@ -359,6 +429,291 @@ static void test_callbackCalledWithChangeDetection (void) PLUGIN_CLOSE (); } +static void test_doUpdate_callback (KDB * kdb ELEKTRA_UNUSED, Key * changedKey ELEKTRA_UNUSED) +{ + doUpdate_callback_called = 1; +} + +static void test_doUpdateShouldUpdateKey (void) +{ + printf ("test doUpdate should update same key\n"); + + KeySet * conf = ksNew (0, KS_END); + PLUGIN_OPEN ("internalnotification"); + + Key * changedKey = keyNew ("user/test/internalnotification/value", KEY_END); + + succeed_if (internalnotificationRegisterCallback (plugin, changedKey, test_callback, NULL) == 1, + "call to elektraInternalnotificationRegisterCallback was not successful"); + + ElektraNotificationCallbackContext * context = elektraMalloc (sizeof *context); + context->kdbUpdate = NULL; + context->kdbUpdate = test_doUpdate_callback; + context->notificationPlugin = plugin; + + doUpdate_callback_called = 0; + elektraInternalnotificationDoUpdate (changedKey, context); + + succeed_if (doUpdate_callback_called, "did not call callback for registered key"); + + elektraFree (context); + PLUGIN_CLOSE (); +} + +static void test_doUpdateShouldUpdateKeyBelow (void) +{ + printf ("test doUpdate should update key below changed key\n"); + + KeySet * conf = ksNew (0, KS_END); + PLUGIN_OPEN ("internalnotification"); + + Key * changedKey = keyNew ("user/test/internalnotification", KEY_END); + + Key * registeredKey = keyNew ("user/test/internalnotification/value", KEY_END); + succeed_if (internalnotificationRegisterCallback (plugin, registeredKey, test_callback, NULL) == 1, + "call to elektraInternalnotificationRegisterCallback was not successful"); + + ElektraNotificationCallbackContext * context = elektraMalloc (sizeof *context); + context->kdbUpdate = NULL; + context->kdbUpdate = test_doUpdate_callback; + context->notificationPlugin = plugin; + + doUpdate_callback_called = 0; + elektraInternalnotificationDoUpdate (changedKey, context); + + succeed_if (doUpdate_callback_called, "did not call callback for registered key"); + + elektraFree (context); + keyDel (registeredKey); + PLUGIN_CLOSE (); +} + +static void test_doUpdateShouldNotUpdateKeyAbove (void) +{ + printf ("test doUpdate should not update key above changed key\n"); + + KeySet * conf = ksNew (0, KS_END); + PLUGIN_OPEN ("internalnotification"); + + Key * changedKey = keyNew ("user/test/internalnotification/value", KEY_END); + + Key * registeredKey = keyNew ("user/test/internalnotification", KEY_END); + succeed_if (internalnotificationRegisterCallback (plugin, registeredKey, test_callback, NULL) == 1, + "call to elektraInternalnotificationRegisterCallback was not successful"); + + ElektraNotificationCallbackContext * context = elektraMalloc (sizeof *context); + context->kdbUpdate = NULL; + context->kdbUpdate = test_doUpdate_callback; + context->notificationPlugin = plugin; + + doUpdate_callback_called = 0; + elektraInternalnotificationDoUpdate (changedKey, context); + + succeed_if (doUpdate_callback_called == 0, "did call callback for key above"); + + elektraFree (context); + keyDel (registeredKey); + PLUGIN_CLOSE (); +} + +static void test_doUpdateShouldUpdateKeyAbove (void) +{ + printf ("test doUpdate should update key above changed key for sameOrBelow callbacks\n"); + + KeySet * conf = ksNew (0, KS_END); + PLUGIN_OPEN ("internalnotification"); + + Key * changedKey = keyNew ("user/test/internalnotification/value", KEY_END); + + Key * registeredKey = keyNew ("user/test/internalnotification", KEY_END); + succeed_if (internalnotificationRegisterCallbackSameOrBelow (plugin, registeredKey, test_callback, NULL) == 1, + "call to internalnotificationRegisterCallbackSameOrBelow was not successful"); + + ElektraNotificationCallbackContext * context = elektraMalloc (sizeof *context); + context->kdbUpdate = NULL; + context->kdbUpdate = test_doUpdate_callback; + context->notificationPlugin = plugin; + + doUpdate_callback_called = 0; + elektraInternalnotificationDoUpdate (changedKey, context); + + succeed_if (doUpdate_callback_called, "did not call callback for key above"); + + elektraFree (context); + keyDel (registeredKey); + PLUGIN_CLOSE (); +} + +static void test_doUpdateShouldNotUpdateUnregisteredKey (void) +{ + printf ("test doUpdate should not update unregistered key\n"); + + KeySet * conf = ksNew (0, KS_END); + PLUGIN_OPEN ("internalnotification"); + + Key * changedKey = keyNew ("user/test/internalnotification/value", KEY_END); + + // No key registration made + + ElektraNotificationCallbackContext * context = elektraMalloc (sizeof *context); + context->kdbUpdate = NULL; + context->kdbUpdate = test_doUpdate_callback; + context->notificationPlugin = plugin; + + doUpdate_callback_called = 0; + elektraInternalnotificationDoUpdate (changedKey, context); + + succeed_if (doUpdate_callback_called == 0, "did call callback for unregistered key"); + + elektraFree (context); + PLUGIN_CLOSE (); +} + +// Generate test cases for C built-in types +#define TYPE unsigned int +#define TYPE_NAME UnsignedInt +#define FORMAT_STRING "%u" +#define TEST_VALUE UINT_MAX +#define INVALID_VALUE "-1" +#include "macros/create_type_tests.h" + +#define TYPE long +#define TYPE_NAME Long +#define FORMAT_STRING "%ld" +#define TEST_VALUE LONG_MAX +#define INVALID_VALUE "5000abc000" +#include "macros/create_type_tests.h" + +#define TYPE unsigned long +#define TYPE_NAME UnsignedLong +#define FORMAT_STRING "%lu" +#define TEST_VALUE ULONG_MAX +#define INVALID_VALUE "-446744073715" +#include "macros/create_type_tests.h" + +#define TYPE long long +#define TYPE_NAME LongLong +#define FORMAT_STRING "%lld" +#define TEST_VALUE LLONG_MAX +#define INVALID_VALUE "322337abc6854775807" +#include "macros/create_type_tests.h" + +#define TYPE unsigned long long +#define TYPE_NAME UnsignedLongLong +#define FORMAT_STRING "%llu" +#define TEST_VALUE ULLONG_MAX +#define INVALID_VALUE "-3223372036854775807" +#include "macros/create_type_tests.h" + +#define TYPE float +#define TYPE_NAME Float +#define FORMAT_STRING "%f" +#define TEST_VALUE 2.3 +#define CHECK_VALUE (value >= 2.295 && value <= 2.305) +#define INVALID_VALUE "4.a" +#define CHECK_INVALID ((int) value == 0) +#include "macros/create_type_tests.h" + +#define TYPE double +#define TYPE_NAME Double +#define FORMAT_STRING "%1.8f" +#define TEST_VALUE 1.00000001 +#define CHECK_VALUE (value >= 1 + 1e-9 && value <= 1 + 1e-7) +#define INVALID_VALUE "4.a" +#define CHECK_INVALID ((int) value == 0) +#include "macros/create_type_tests.h" + +// for kdb_*_t types +#define TYPE kdb_boolean_t +#define TYPE_NAME KdbBoolean +#define FORMAT_STRING "%d" +#define TEST_VALUE 1 +#define CHECK_VALUE (value) +#include "macros/create_type_tests.h" + +#define TYPE kdb_char_t +#define TYPE_NAME KdbChar +#define FORMAT_STRING "abc%d" +#define TEST_VALUE 1 +#define CHECK_VALUE (value == 'a') +#include "macros/create_type_tests.h" + +#define TYPE kdb_octet_t +#define TYPE_NAME KdbOctet +#define FORMAT_STRING "%d" +#define TEST_VALUE 255 +#define INVALID_VALUE "4a" +#include "macros/create_type_tests.h" + +#define TYPE kdb_short_t +#define TYPE_NAME KdbShort +#define FORMAT_STRING "%d" +#define TEST_VALUE SHRT_MIN +#define INVALID_VALUE "-55ABC" +#include "macros/create_type_tests.h" + +#define TYPE kdb_unsigned_short_t +#define TYPE_NAME KdbUnsignedShort +#define FORMAT_STRING "%d" +#define TEST_VALUE USHRT_MAX +#define INVALID_VALUE "-55" +#include "macros/create_type_tests.h" + +#define TYPE kdb_long_t +#define TYPE_NAME KdbLong +#define FORMAT_STRING "%d" +#define TEST_VALUE INT_MIN +#define INVALID_VALUE "B5C" +#include "macros/create_type_tests.h" + +#define TYPE kdb_unsigned_long_t +#define TYPE_NAME KdbUnsignedLong +#define FORMAT_STRING "%u" +#define TEST_VALUE UINT_MAX +#define INVALID_VALUE "-523255" +#include "macros/create_type_tests.h" + +#define TYPE kdb_long_long_t +#define TYPE_NAME KdbLongLong +#define FORMAT_STRING ELEKTRA_LONG_LONG_F +#define TEST_VALUE (kdb_long_long_t) LONG_MAX +#define INVALID_VALUE "50000asasd" +#include "macros/create_type_tests.h" + +#define TYPE kdb_unsigned_long_long_t +#define TYPE_NAME KdbUnsignedLongLong +#define FORMAT_STRING ELEKTRA_UNSIGNED_LONG_LONG_F +#define TEST_VALUE (kdb_unsigned_long_long_t) ULONG_MAX +#define INVALID_VALUE "-5326523652" +#include "macros/create_type_tests.h" + +#define TYPE kdb_float_t +#define TYPE_NAME KdbFloat +#define FORMAT_STRING "%f" +#define TEST_VALUE 2.3 +#define CHECK_VALUE (value >= 2.295 && value <= 2.305) +#define INVALID_VALUE "4.a" +#define CHECK_INVALID ((int) value == 0) +#include "macros/create_type_tests.h" + +#define TYPE kdb_double_t +#define TYPE_NAME KdbDouble +#define FORMAT_STRING "%1.8f" +#define TEST_VALUE 1.00000001 +#define CHECK_VALUE (value >= 1 + 1e-9 && value <= 1 + 1e-7) +#define INVALID_VALUE "4.a" +#define CHECK_INVALID ((int) value == 0) +#include "macros/create_type_tests.h" + +#define TYPE kdb_long_double_t +#define TYPE_NAME KdbLongDouble +#define FORMAT_STRING "%1.8f" +#define TEST_VALUE 1.00000001 +#define CHECK_VALUE (value >= 1 + 1e-9 && value <= 1 + 1e-7) +#define INVALID_VALUE "4.a" +#define CHECK_INVALID ((int) value == 0) +#include "macros/create_type_tests.h" + int main (int argc, char ** argv) { printf ("INTERNALNOTIFICATION TESTS\n"); @@ -369,6 +724,7 @@ int main (int argc, char ** argv) test_basics (); test_updateOnKdbGet (); test_updateOnKdbSet (); + test_conversionError (); printf ("\nregisterInt\n-----------\n"); test_intUpdateWithCascadingKey (); @@ -382,7 +738,40 @@ int main (int argc, char ** argv) test_callbackCalledWithKey (); test_callbackCalledWithChangeDetection (); - printf ("\ntestmod_internalnotification RESULTS: %d test(s) done. %d error(s).\n", nbTest, nbError); + RUN_TYPE_TESTS (UnsignedInt) + RUN_TYPE_TESTS (Long) + RUN_TYPE_TESTS (UnsignedLong) + RUN_TYPE_TESTS (LongLong) + RUN_TYPE_TESTS (UnsignedLongLong) + + RUN_TYPE_TESTS (Float) + RUN_TYPE_TESTS (Double) + + printf ("\nKdbBoolean\n----------------\n"); + TEST_CASE_UPDATE_NAME (KdbBoolean) (); + + printf ("\nKdbChar\n----------------\n"); + TEST_CASE_UPDATE_NAME (KdbChar) (); + + RUN_TYPE_TESTS (KdbOctet) + RUN_TYPE_TESTS (KdbShort) + RUN_TYPE_TESTS (KdbUnsignedShort) + RUN_TYPE_TESTS (KdbLong) + RUN_TYPE_TESTS (KdbUnsignedLong) + RUN_TYPE_TESTS (KdbLongLong) + RUN_TYPE_TESTS (KdbUnsignedLongLong) + RUN_TYPE_TESTS (KdbFloat) + RUN_TYPE_TESTS (KdbDouble) + RUN_TYPE_TESTS (KdbLongDouble) + + printf ("\nelektraInternalnotificationDoUpdate\n-----------------------------------\n"); + test_doUpdateShouldUpdateKey (); + test_doUpdateShouldUpdateKeyBelow (); + test_doUpdateShouldNotUpdateKeyAbove (); + test_doUpdateShouldNotUpdateUnregisteredKey (); + test_doUpdateShouldUpdateKeyAbove (); + + print_result ("testmod_internalnotification"); return nbError; } diff --git a/src/plugins/list/list.h b/src/plugins/list/list.h index 73f2ec87635..cce67148e4f 100644 --- a/src/plugins/list/list.h +++ b/src/plugins/list/list.h @@ -21,9 +21,7 @@ int elektraListSet (Plugin * handle, KeySet * ks, Key * parentKey); int elektraListError (Plugin * handle, KeySet * ks, Key * parentKey); int elektraListAddPlugin (Plugin * handle, KeySet * pluginConfig); int elektraListEditPlugin (Plugin * handle, KeySet * pluginConfig); -void elektraListSetIoBinding (Plugin * handle, ElektraIoInterface * binding); -void elektraListOpenNotification (Plugin * handle, ElektraNotificationCallback callback, ElektraNotificationCallbackContext * context); -void elektraListCloseNotification (Plugin * handle); + Plugin * ELEKTRA_PLUGIN_EXPORT (list); #endif diff --git a/src/plugins/zeromqrecv/testmod_zeromqrecv.c b/src/plugins/zeromqrecv/testmod_zeromqrecv.c index 23e691f282e..7e2ef92a55c 100644 --- a/src/plugins/zeromqrecv/testmod_zeromqrecv.c +++ b/src/plugins/zeromqrecv/testmod_zeromqrecv.c @@ -12,7 +12,7 @@ #include // time() #include // usleep() -#include // elektraIoUvNew() +#include // elektraIoUvNew() #include // ElektraIoPluginSetBinding #include diff --git a/src/plugins/zeromqrecv/zeromqrecv.h b/src/plugins/zeromqrecv/zeromqrecv.h index 66b7fa4c513..89462e9f306 100644 --- a/src/plugins/zeromqrecv/zeromqrecv.h +++ b/src/plugins/zeromqrecv/zeromqrecv.h @@ -17,7 +17,7 @@ #include -#include // elektraIoAdapterZeroMq*() +#include // elektraIoAdapterZeroMq*() #define ELEKTRA_ZEROMQ_DEFAULT_SUB_ENDPOINT "tcp://localhost:6001" diff --git a/src/plugins/zeromqsend/README.md b/src/plugins/zeromqsend/README.md index ddcdda68c4d..88221820e0e 100644 --- a/src/plugins/zeromqsend/README.md +++ b/src/plugins/zeromqsend/README.md @@ -50,7 +50,7 @@ This plugin supports the following configuration options when mounting: [`ipc`](http://api.zeromq.org/4-2:zmq-ipc) and [`tcp`](http://api.zeromq.org/4-2:zmq-tcp) ZeroMQ transports are recommended. The default value is "tcp://localhost:6000". -- **connectTimeout**: Timeout for establishing connections in seconds. The default value is "2". +- **connectTimeout**: Timeout for establishing connections in miliseconds. The default value is "1000". - **subscribeTimeout**: Timeout for waiting for subscribers in miliseconds. The default value is "200". # Notification Format diff --git a/src/plugins/zeromqsend/publish.c b/src/plugins/zeromqsend/publish.c index f35a8afff9a..91f14e70b7d 100644 --- a/src/plugins/zeromqsend/publish.c +++ b/src/plugins/zeromqsend/publish.c @@ -67,6 +67,22 @@ static int getMonitorEvent (void * monitor) return event; } +static struct timespec ts_diff (struct timespec now, struct timespec start) +{ + struct timespec diff; + if ((now.tv_nsec - start.tv_nsec) < 0) + { + diff.tv_sec = now.tv_sec - start.tv_sec - 1; + diff.tv_nsec = 1000000000 + now.tv_nsec - start.tv_nsec; + } + else + { + diff.tv_sec = now.tv_sec - start.tv_sec; + diff.tv_nsec = now.tv_nsec - start.tv_nsec; + } + return diff; +} + /** * Wait for connection message from ZeroMQ monitor socket. * @@ -77,8 +93,23 @@ static int getMonitorEvent (void * monitor) */ static int waitForConnection (void * monitorSocket, long connectTimeout) { - time_t start = time (NULL); struct timespec wait; + struct timespec start; + struct timespec now; + struct timespec diff; + time_t startFallback = -1; + long timeoutSec = (connectTimeout / (1000)); + long timeoutNsec = (connectTimeout % (1000)) * (1000 * 1000); + if (clock_gettime (CLOCK_MONOTONIC, &start) == -1) + { + ELEKTRA_LOG_WARNING ("Using slower fallback for timeout detection"); + startFallback = time (NULL); + // minimum timeout is 1 second when using the fallback + if (timeoutSec == 0) + { + timeoutSec = 1; + } + } // wait for connection established event int connected = 0; @@ -91,7 +122,18 @@ static int waitForConnection (void * monitorSocket, long connectTimeout) int event = getMonitorEvent (monitorSocket); - if (time (NULL) - start > connectTimeout) + int timeout = 0; + if (startFallback == -1) + { + clock_gettime (CLOCK_MONOTONIC, &now); + diff = ts_diff (now, start); + timeout = diff.tv_sec >= timeoutSec && diff.tv_nsec >= timeoutNsec; + } + else + { + timeout = time (NULL) - startFallback >= timeoutSec; + } + if (timeout) { ELEKTRA_LOG_WARNING ("connection timed out. could not publish notification"); zmq_close (monitorSocket); @@ -134,9 +176,10 @@ static int waitForSubscription (void * socket, long subscribeTimeout) struct timespec start; struct timespec now; struct timespec wait; + struct timespec diff; time_t startFallback = -1; long timeoutSec = (subscribeTimeout / (1000)); - long timeoutNsec = (subscribeTimeout % (1000)) * (1000 * 1000 * 1000); + long timeoutNsec = (subscribeTimeout % (1000)) * (1000 * 1000); if (clock_gettime (CLOCK_MONOTONIC, &start) == -1) { ELEKTRA_LOG_WARNING ("Using slower fallback for timeout detection"); @@ -171,7 +214,8 @@ static int waitForSubscription (void * socket, long subscribeTimeout) if (startFallback == -1) { clock_gettime (CLOCK_MONOTONIC, &now); - timeout = now.tv_sec - start.tv_sec >= timeoutSec && now.tv_nsec - start.tv_nsec >= timeoutNsec; + diff = ts_diff (now, start); + timeout = diff.tv_sec >= timeoutSec && diff.tv_nsec >= timeoutNsec; } else { diff --git a/src/plugins/zeromqsend/testmod_zeromqsend.c b/src/plugins/zeromqsend/testmod_zeromqsend.c index 39f5fbc18b2..090cc800be2 100644 --- a/src/plugins/zeromqsend/testmod_zeromqsend.c +++ b/src/plugins/zeromqsend/testmod_zeromqsend.c @@ -42,7 +42,7 @@ void * context; #define TEST_ENDPOINT "tcp://127.0.0.1:6002" /** extended timeouts for tests */ -#define TESTCONFIG_CONNECT_TIMEOUT "5" +#define TESTCONFIG_CONNECT_TIMEOUT "5000" #define TESTCONFIG_SUBSCRIBE_TIMEOUT "5000" /** diff --git a/src/plugins/zeromqsend/zeromqsend.h b/src/plugins/zeromqsend/zeromqsend.h index 94b8a31cef1..fa161baa8a7 100644 --- a/src/plugins/zeromqsend/zeromqsend.h +++ b/src/plugins/zeromqsend/zeromqsend.h @@ -21,7 +21,7 @@ #define ELEKTRA_ZEROMQ_DEFAULT_PUB_ENDPOINT "tcp://localhost:6000" /** default connection timeout for plugin */ -#define ELEKTRA_ZEROMQ_DEFAULT_CONNECT_TIMEOUT 2 +#define ELEKTRA_ZEROMQ_DEFAULT_CONNECT_TIMEOUT 1000 /** default subscription timeout for plugin */ #define ELEKTRA_ZEROMQ_DEFAULT_SUBSCRIBE_TIMEOUT 200 diff --git a/src/tools/hub-zeromq/README.md b/src/tools/hub-zeromq/README.md index 29fc63e8f5d..74922ca3aca 100644 --- a/src/tools/hub-zeromq/README.md +++ b/src/tools/hub-zeromq/README.md @@ -8,8 +8,8 @@ The hub does not feature authentication or encryption. ## Usage -The hub can be controlled using the CLI commands "kdb run-@tool@"" for -starting and "kdb stop-@tool@" for stopping. +The hub can be controlled using the CLI commands "kdb run-hub-zeromq" for +starting and "kdb stop-hub-zeromq" for stopping. ## Configuration diff --git a/src/tools/hub-zeromq/hub-zeromq.c b/src/tools/hub-zeromq/hub-zeromq.c index fec7e1f84e8..b6280b8c7a5 100644 --- a/src/tools/hub-zeromq/hub-zeromq.c +++ b/src/tools/hub-zeromq/hub-zeromq.c @@ -35,7 +35,7 @@ static void onSignal (int signal) int main (void) { - printf ("lightweight zeromq message hub\n"); + printf ("\nlightweight zeromq message hub\n"); // exit on SIGINT signal (SIGINT, onSignal); @@ -98,6 +98,7 @@ int main (void) printf ("listening on %s (XSUB for zeromqsend)\n", xSubEndpoint); printf ("listening on %s (XPUB for zeromqrecv)\n", xPubEndpoint); + printf ("hub is running\n"); ksDel (config); // forward messages between sockets