diff --git a/cmake/Modules/LibAddLib.cmake b/cmake/Modules/LibAddLib.cmake index aa5c0b44157..459f6cd1ae8 100644 --- a/cmake/Modules/LibAddLib.cmake +++ b/cmake/Modules/LibAddLib.cmake @@ -12,7 +12,7 @@ function (add_lib name) if (BUILD_SHARED) add_library (elektra-${name} SHARED ${ARG_SOURCES}) - add_dependencies (elektra-${name} kdberrors_generated elektra_error_codes_generated) + add_dependencies (elektra-${name} kdberrors_generated elektra_error_codes_generated ${ARG_LINK_ELEKTRA}) target_link_libraries (elektra-${name} elektra-core ${ARG_LINK_ELEKTRA}) endif (BUILD_SHARED) diff --git a/doc/news/_preparation_next_release.md b/doc/news/_preparation_next_release.md index fbc34fe119d..efef50446b9 100644 --- a/doc/news/_preparation_next_release.md +++ b/doc/news/_preparation_next_release.md @@ -51,7 +51,24 @@ the `type` plugin will ignore the key. We now also support converting enum value To switch from `boolean` to the new `type`, you don't have to do anything, if you used the default config. If you used a custom configuration please take a look at the [README](https://www.libelektra.org/plugins/type). -### <> +### kdbEnsure + +`kdbEnsure` is a new function in `elektra-kdb`. It can be used to ensure that a KDB instance meets certain clauses specified in a +contract. In principle this a very powerful tool that may be used for a lot of things. For now it only supports a few clauses concerning +plugins: + +- You can specify that a plugin should be mounted globally. This can for example be used to enable the new [gopts](#gopts) plugin. +- Conversely you can also define that a plugin should not be mounted globally, e.g. to disable the `spec` plugin, which is enabled by default. +- Additionally you may want to enforce that a global plugin uses a certain configuration. For this case you can specify that the plugin + should be remounted, i.e. unmounted and immediately mounted again. +- Because of the different architecture involved, for now only unmounting of non-global plugins is supported. + +All changes made by `kdbEnsure` are purely temporary. They will only apply to the KDB handle passed to the function. + +IMPORTANT: `kdbEnsure` only works, if the `list` plugin is mounted in all appropriate global positions. + +Note: `kdbEnsure` right now ignores the `infos/recommends` and `infos/needs` metadata of plugins, so you have to explicitly take care of +dependencies. _(Klemens Böswirth)_ ### <> @@ -155,6 +172,15 @@ The following section lists news about the [modules](https://www.libelektra.org/ - We fixed an incorrect format specifier in a call to the `syslog` function. _(René Schwaiger)_ +### gOpts + +- The [gopts](https://www.libelektra.org/plugins/gopts) plugin simply retrieves the values of `argc`, `argv` and `envp` needed for + [`elektraGetOpts`](https://www.libelektra.org/tutorials/command-line-options) and then makes the call. It is intended to be used as a + global plugin, so that command-line options are automatically parsed when `kdbGet` is called. _(Klemens Böswirth)_ +- The plugin works under WIN32 (via `GetCommandLineW` and `GetEnvironmentString`), MAC_OSX (`_NSGetArgc`, `_NSGetArgv`) and any system that + either has a `sysctl(3)` function that accepts `KERN_PROC_ARGS` (e.g. FreeBSD) or when `procfs` is mounted and either `/proc/self` or + `/proc/curproc` refers to the current process. If you need support for any other systems, feel free to add an implementation. + ## Libraries The text below summarizes updates to the [C (and C++)-based libraries](https://www.libelektra.org/libraries/readme) of Elektra. @@ -171,7 +197,7 @@ compiled against an older 0.8 version of Elektra will continue to work ### Core -- <> +- `kdbGet` now calls global postgetstorage plugins with the parent key passed to `kdbGet`, instead of a random mountpoint. _(Klemens Böswirth)_ - <> - <> diff --git a/doc/tutorials/validation.md b/doc/tutorials/validation.md index 8f8e515ad93..d75b91a0b2c 100644 --- a/doc/tutorials/validation.md +++ b/doc/tutorials/validation.md @@ -318,6 +318,7 @@ Before we look further let us undo the modifications to the key database. ```sh kdb rm -r spec/tests/tutorial kdb rm -r system/tests/tutorial +kdb rm -rf user/tests/tutorial kdb umount spec/tests/tutorial kdb umount /tests/tutorial kdb rm -rf spec diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d1352d8818f..87a01df3a41 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -34,8 +34,9 @@ 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) -target_link_elektra (opts elektra-opts) +target_link_elektra (opts elektra-ease elektra-opts) target_link_elektra (optsSnippets elektra-opts) +target_link_elektra (gopts elektra-ease elektra-opts elektra-kdb) # Notification examples diff --git a/examples/gopts.c b/examples/gopts.c new file mode 100644 index 00000000000..1157c60531c --- /dev/null +++ b/examples/gopts.c @@ -0,0 +1,257 @@ +/** + * @file + * + * @brief + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + */ + +#include +#include +#include + +#include +#include +#include + +extern char ** environ; + +#define BASE_KEY "/sw/org/erm/#0/current" +#define SPEC_BASE_KEY "spec" BASE_KEY + +// ----------------- +// Helper methods +// ----------------- + +/* + * The methods below are only used, so that this example is self-contained. + * If you actually develop an application, you may use the `specload` plugin, + * but in any case the specification should be mounted into the KDB using `kdb mount`. + * + * DO NOT set/unset the specification inside of your application. + */ + +static KeySet * createSpec (void) +{ + return ksNew ( + 10, + keyNew (SPEC_BASE_KEY "/emptydirs", KEY_META, "description", "remove empty directories", KEY_META, "opt", "d", KEY_META, + "opt/arg", "none", KEY_META, "opt/long", "dir", KEY_END), + keyNew (SPEC_BASE_KEY "/force", KEY_META, "description", "ignore nonexistent files and arguments, never prompt", KEY_META, + "opt", "f", KEY_META, "opt/arg", "none", KEY_META, "opt/long", "force", KEY_END), + keyNew (SPEC_BASE_KEY "/interactive", KEY_META, "description", + "prompt according to WHEN: never, once (-I), or always (-i), without WHEN, prompt always", KEY_META, "opt", "#1", + KEY_META, "opt/#0", "i", KEY_META, "opt/#0/arg", "optional", KEY_META, "opt/#0/flagvalue", "always", KEY_META, + "opt/#0/long", "interactive", KEY_META, "opt/#1", "I", KEY_META, "opt/#1/arg", "none", KEY_META, "opt/#1/flagvalue", + "once", KEY_META, "opt/arg/name", "WHEN", KEY_END), + keyNew (SPEC_BASE_KEY "/nopreserve", KEY_META, "description", "do not treat '/' specially", KEY_META, "opt/arg", "none", + KEY_META, "opt/long", "no-preserve-root", KEY_END), + keyNew (SPEC_BASE_KEY "/preserve", KEY_META, "description", + "do not remove '/' (default), with 'all', reject any command line argument on a separate device from its parent", + KEY_META, "opt/arg", "optional", KEY_META, "opt/arg/name", "all", KEY_META, "opt/flagvalue", "root", KEY_META, + "opt/long", "preserve-root", KEY_END), + keyNew (SPEC_BASE_KEY "/recursive", KEY_META, "description", "remove directories and their contents recursively", KEY_META, + "opt", "#1", KEY_META, "opt/#0", "r", KEY_META, "opt/#0/arg", "none", KEY_META, "opt/#0/long", "recursive", + KEY_META, "opt/#1", "R", KEY_META, "opt/#1/arg", "none", KEY_END), + keyNew (SPEC_BASE_KEY "/showversion", KEY_META, "description", "output version information and exit", KEY_META, "opt/arg", + "none", KEY_META, "opt/long", "version", KEY_END), + keyNew (SPEC_BASE_KEY "/singlefs", KEY_META, "description", + "when removing a hierarchy recursively, skip any directory that is on a file system different from that of the " + "corresponding line argument", + KEY_META, "opt/arg", "none", KEY_META, "opt/long", "one-file-system", KEY_END), + keyNew (SPEC_BASE_KEY "/verbose", KEY_META, "description", "explain what is being done", KEY_META, "opt", "v", KEY_META, + "opt/arg", "none", KEY_META, "opt/long", "verbose", KEY_META, "env", "VERBOSE", KEY_END), + keyNew (SPEC_BASE_KEY "/files/#", KEY_META, "description", "the files that shall be deleted", KEY_META, "args", "remaining", + KEY_META, "env", "FILES", KEY_END), + KS_END); +} + +static int setupSpec (void) +{ + Key * parentKey = keyNew (SPEC_BASE_KEY, KEY_END); + KDB * kdb = kdbOpen (parentKey); + KeySet * ks = ksNew (0, KS_END); + kdbGet (kdb, ks, parentKey); + + KeySet * existing = ksCut (ks, parentKey); + if (ksGetSize (existing) > 0) + { + kdbClose (kdb, parentKey); + ksDel (ks); + ksDel (existing); + return 0; + } + ksDel (existing); + + KeySet * spec = createSpec (); + ksAppend (ks, spec); + ksDel (spec); + kdbSet (kdb, ks, parentKey); + kdbClose (kdb, parentKey); + ksDel (ks); + + return 1; +} + +static void removeSpec (void) +{ + Key * parentKey = keyNew (SPEC_BASE_KEY, KEY_END); + KDB * kdb = kdbOpen (parentKey); + KeySet * ks = ksNew (0, KS_END); + kdbGet (kdb, ks, parentKey); + KeySet * spec = ksCut (ks, parentKey); + ksDel (spec); + kdbSet (kdb, ks, parentKey); + kdbClose (kdb, parentKey); + ksDel (ks); +} + +// ----------------- +// Main example +// ----------------- + +int main (void) +{ + if (!setupSpec ()) + { + fprintf (stderr, "ERROR: Couldn't setup spec, keys exist!\n"); + return EXIT_FAILURE; + } + + Key * parentKey = keyNew (BASE_KEY, KEY_END); + KDB * kdb = kdbOpen (parentKey); + + KeySet * contract = ksNew (1, keyNew ("system/elektra/ensure/plugins/global/gopts", KEY_VALUE, "mounted", KEY_END), KS_END); + int rc = kdbEnsure (kdb, contract, parentKey); + if (rc == 1) + { + fprintf (stderr, "ERROR: Contract could not be ensured!\n%s\n", keyString (keyGetMeta (parentKey, "error/reason"))); + kdbClose (kdb, parentKey); + keyDel (parentKey); + removeSpec (); + return EXIT_FAILURE; + } + else if (rc == -1) + { + fprintf (stderr, "ERROR: Contract malformed/NULL pointer!\n%s\n", keyString (keyGetMeta (parentKey, "error/reason"))); + kdbClose (kdb, parentKey); + keyDel (parentKey); + removeSpec (); + return EXIT_FAILURE; + } + + KeySet * ks = ksNew (0, KS_END); + rc = kdbGet (kdb, ks, parentKey); + + if (rc == -1) + { + fprintf (stderr, "ERROR: kdbGet failed! %s\n", keyString (keyGetMeta (parentKey, "error/reason"))); + kdbClose (kdb, parentKey); + keyDel (parentKey); + ksDel (ks); + removeSpec (); + return EXIT_FAILURE; + } + + Key * helpKey = ksLookupByName (ks, "proc/elektra/gopts/help", 0); + if (helpKey != NULL && elektraStrCmp (keyString (helpKey), "1") == 0) + { + char * help = elektraGetOptsHelpMessage (helpKey, NULL, NULL); + printf ("%s\n", help); + elektraFree (help); + kdbClose (kdb, parentKey); + keyDel (parentKey); + ksDel (ks); + removeSpec (); + return EXIT_SUCCESS; + } + + printf ("When called with the same arguments 'rm' \n"); + + Key * lookup = ksLookupByName (ks, BASE_KEY "/emptydirs", 0); + if (lookup != NULL && elektraStrCmp (keyString (lookup), "1") == 0) + { + printf ("will delete empty directories\n"); + } + + lookup = ksLookupByName (ks, BASE_KEY "/force", 0); + if (lookup != NULL && elektraStrCmp (keyString (lookup), "1") == 0) + { + printf ("will use force mode\n"); + } + + lookup = ksLookupByName (ks, BASE_KEY "/interactive", 0); + if (lookup != NULL && elektraStrCmp (keyString (lookup), "never") == 0) + { + printf ("will not use interactive mode\n"); + } + else if (lookup != NULL && elektraStrCmp (keyString (lookup), "once") == 0) + { + printf ("will use interactive mode; ask once\n"); + } + else if (lookup != NULL && elektraStrCmp (keyString (lookup), "always") == 0) + { + printf ("will use interactive mode; always ask\n"); + } + + lookup = ksLookupByName (ks, BASE_KEY "/nopreserve", 0); + if (lookup != NULL && elektraStrCmp (keyString (lookup), "1") == 0) + { + printf ("will not treat '/' specially\n"); + } + + lookup = ksLookupByName (ks, BASE_KEY "/preserve", 0); + if (lookup != NULL && elektraStrCmp (keyString (lookup), "root") == 0) + { + printf ("will never remove '/'"); + } + else if (lookup != NULL && elektraStrCmp (keyString (lookup), "all") == 0) + { + printf ("will reject any argument on separate device from its parent\n"); + } + + lookup = ksLookupByName (ks, BASE_KEY "/recursive", 0); + if (lookup != NULL && elektraStrCmp (keyString (lookup), "1") == 0) + { + printf ("will delete recursively\n"); + } + + lookup = ksLookupByName (ks, BASE_KEY "/showversion", 0); + if (lookup != NULL && elektraStrCmp (keyString (lookup), "1") == 0) + { + printf ("will show version and exit\n"); + } + + lookup = ksLookupByName (ks, BASE_KEY "/singlefs", 0); + if (lookup != NULL && elektraStrCmp (keyString (lookup), "1") == 0) + { + printf ("will skip directories on different file systems\n"); + } + + lookup = ksLookupByName (ks, BASE_KEY "/verbose", 0); + if (lookup != NULL && elektraStrCmp (keyString (lookup), "1") == 0) + { + printf ("will explain what is being done\n"); + } + + printf ("will remove the following files:\n"); + + Key * arrayParent = ksLookupByName (ks, BASE_KEY "/files", 0); + KeySet * files = elektraArrayGet (arrayParent, ks); + + ksRewind (files); + Key * cur = NULL; + while ((cur = ksNext (files)) != NULL) + { + printf (" %s\n", keyString (cur)); + } + printf ("\n"); + + kdbClose (kdb, parentKey); + keyDel (parentKey); + ksDel (ks); + + removeSpec (); + + return EXIT_SUCCESS; +} diff --git a/examples/opts.c b/examples/opts.c index 6b1afba861d..b933872faae 100644 --- a/examples/opts.c +++ b/examples/opts.c @@ -21,7 +21,7 @@ extern char ** environ; static KeySet * createSpec (void) { return ksNew ( - 9, + 10, keyNew (SPEC_BASE_KEY "/emptydirs", KEY_META, "description", "remove empty directories", KEY_META, "opt", "d", KEY_META, "opt/arg", "none", KEY_META, "opt/long", "dir", KEY_END), keyNew (SPEC_BASE_KEY "/force", KEY_META, "description", "ignore nonexistent files and arguments, never prompt", KEY_META, diff --git a/src/bindings/cpp/examples/cpp_example_userexception.cpp b/src/bindings/cpp/examples/cpp_example_userexception.cpp index 3b83a573019..78ce72a9269 100644 --- a/src/bindings/cpp/examples/cpp_example_userexception.cpp +++ b/src/bindings/cpp/examples/cpp_example_userexception.cpp @@ -121,9 +121,29 @@ class KDBException : public Exception return "User Exception: KDB"; } -private: +protected: Key m_key; }; + +class ContractException : public KDBException +{ +public: + explicit ContractException (Key key) : KDBException (key) + { + } + + ~ContractException () noexcept override = default; + + const char * what () const noexcept override + { + if (!m_key) + { + return "Malformed contract"; + } + return KDBException::what (); + } +}; + } // namespace kdb diff --git a/src/bindings/cpp/include/kdb.hpp b/src/bindings/cpp/include/kdb.hpp index bb61752b4fb..8b25ed784f7 100644 --- a/src/bindings/cpp/include/kdb.hpp +++ b/src/bindings/cpp/include/kdb.hpp @@ -56,6 +56,8 @@ class KDB virtual inline int set (KeySet & returned, std::string const & keyname); virtual inline int set (KeySet & returned, Key & parentKey); + inline int ensure (const KeySet & contract, Key & parentKey); + private: ckdb::KDB * handle; ///< holds an kdb handle }; @@ -232,6 +234,33 @@ inline int KDB::set (KeySet & returned, Key & parentKey) return ret; } +/** + * Ensures that the conditions defined in @p contract are met by this KDB. + * + * @see ckdb::kdbEnsure() + * + * @param contract The contract to ensure. + * @param parentKey The parentKey to use. + * + * @throw KDBException if there were problems with the contract or the database + * @throw ContractException if the contract couldn't be ensured + */ +int KDB::ensure (const KeySet & contract, Key & parentKey) +{ + // have to ksDup because contract is consumed and ksDel()ed by kdbEnsure + int ret = ckdb::kdbEnsure (handle, ckdb::ksDup (contract.getKeySet ()), parentKey.getKey ()); + if (ret == -1) + { + throw KDBException (parentKey); + } + if (ret == 1) + { + throw ContractException (parentKey); + } + return ret; +} + + } // end of namespace kdb #endif diff --git a/src/bindings/cpp/include/kdbexcept.hpp b/src/bindings/cpp/include/kdbexcept.hpp index e7fc6a4c9ab..c903e2a377b 100644 --- a/src/bindings/cpp/include/kdbexcept.hpp +++ b/src/bindings/cpp/include/kdbexcept.hpp @@ -60,10 +60,32 @@ class KDBException : public Exception return m_str.c_str (); } -private: +protected: Key m_key; + +private: mutable std::string m_str; }; + +class ContractException : public KDBException +{ +public: + explicit ContractException (Key key) : KDBException (key) + { + } + + ~ContractException () noexcept override = default; + + const char * what () const noexcept override + { + if (!m_key) + { + return "Malformed contract"; + } + return KDBException::what (); + } +}; + } // namespace kdb #endif diff --git a/src/error/specification b/src/error/specification index cd0dfbb031d..c4e2b3827ee 100644 --- a/src/error/specification +++ b/src/error/specification @@ -1339,3 +1339,9 @@ severity:error ingroup:plugin module:specload macro:SPECLOAD + +number:213 +description:the contract was malformed +severity:error +ingroup:kdb +macro:MALFORMED_CONTRACT diff --git a/src/include/kdb.h.in b/src/include/kdb.h.in index a961227df36..2ca487e00f7 100644 --- a/src/include/kdb.h.in +++ b/src/include/kdb.h.in @@ -110,6 +110,7 @@ int kdbGet(KDB *handle, KeySet *returned, int kdbSet(KDB *handle, KeySet *returned, Key *parentKey); +int kdbEnsure (KDB * handle, KeySet * contract, Key * parentKey); /************************************** diff --git a/src/include/kdbconfig.h.in b/src/include/kdbconfig.h.in index 4e31ceb256e..3769391e467 100644 --- a/src/include/kdbconfig.h.in +++ b/src/include/kdbconfig.h.in @@ -202,7 +202,4 @@ - - - #endif diff --git a/src/include/kdbglobal.h b/src/include/kdbglobal.h index 8e6521d1ad6..f6258becebb 100644 --- a/src/include/kdbglobal.h +++ b/src/include/kdbglobal.h @@ -30,6 +30,7 @@ POSITION(GETRESOLVER) \ POSITION(PREGETSTORAGE) \ POSITION(GETSTORAGE) \ + POSITION(PROCGETSTORAGE) \ POSITION(POSTGETSTORAGE) \ POSITION(SETRESOLVER) \ POSITION(POSTGETCLEANUP) \ diff --git a/src/include/kdbprivate.h b/src/include/kdbprivate.h index 32e5949cc7e..b61311ea1a9 100644 --- a/src/include/kdbprivate.h +++ b/src/include/kdbprivate.h @@ -323,9 +323,6 @@ struct _KDB ElektraIoInterface * ioBinding; /*!< binding for asynchronous I/O operations.*/ - Plugin * notificationPlugin; /*!< reference to global plugin for notifications.*/ - ElektraNotificationCallbackContext * notificationCallbackContext; /*!< reference to context for notification callbacks.*/ - KeySet * global; /*!< This keyset can be used by plugins to pass data through the KDB and communicate with other plugins. Plugins shall clean up their parts of the global keyset, which they do not need any more.*/ @@ -519,6 +516,7 @@ int elektraProcessPlugin (Key * cur, int * pluginNumber, char ** pluginName, cha int elektraProcessPlugins (Plugin ** plugins, KeySet * modules, KeySet * referencePlugins, KeySet * config, KeySet * systemConfig, KeySet * global, Key * errorKey); size_t elektraPluginGetFunction (Plugin * plugin, const char * name); +Plugin * elektraPluginFindGlobal (KDB * handle, const char * pluginName); Plugin * elektraPluginMissing (void); Plugin * elektraPluginVersion (void); diff --git a/src/include/kdbproposal.h b/src/include/kdbproposal.h index bbafffcd42a..7bfe77904b5 100644 --- a/src/include/kdbproposal.h +++ b/src/include/kdbproposal.h @@ -89,7 +89,6 @@ int keyRel2 (const Key * k1, const Key * k2, KeyRelType which); Key * keyAsCascading (const Key * key); int keyGetLevelsBelow (const Key * k1, const Key * k2); - #ifdef __cplusplus } } diff --git a/src/libs/elektra/kdb.c b/src/libs/elektra/kdb.c index 1e6e8450269..be8a6d45bae 100644 --- a/src/libs/elektra/kdb.c +++ b/src/libs/elektra/kdb.c @@ -500,6 +500,12 @@ static int elektraGetCheckUpdateNeeded (Split * split, Key * parentKey) return updateNeededOccurred; } +typedef enum +{ + FIRST, + LAST +} UpdatePass; + /** * @internal * @brief Do the real update. @@ -541,12 +547,6 @@ static int elektraGetDoUpdate (Split * split, Key * parentKey) return 0; } -typedef enum -{ - FIRST, - LAST -} UpdatePass; - static KeySet * prepareGlobalKS (KeySet * ks, Key * parentKey) { ksRewind (ks); @@ -577,21 +577,28 @@ static int elektraGetDoUpdateWithGlobalHooks (KDB * handle, Split * split, KeySe UpdatePass run) { const int bypassedSplits = 1; - int pgs_done = 0; - int pgc_done = 0; - elektraGlobalGet (handle, ks, parentKey, GETSTORAGE, INIT); - elektraGlobalGet (handle, ks, parentKey, GETSTORAGE, MAXONCE); + switch (run) + { + case FIRST: + keySetName (parentKey, keyName (initialParent)); + elektraGlobalGet (handle, ks, parentKey, GETSTORAGE, INIT); + elektraGlobalGet (handle, ks, parentKey, GETSTORAGE, MAXONCE); + break; + case LAST: + keySetName (parentKey, keyName (initialParent)); + elektraGlobalGet (handle, ks, parentKey, PROCGETSTORAGE, INIT); + elektraGlobalGet (handle, ks, parentKey, PROCGETSTORAGE, MAXONCE); + elektraGlobalError (handle, ks, parentKey, PROCGETSTORAGE, DEINIT); + break; + default: + break; + } // elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, INIT); for (size_t i = 0; i < split->size - bypassedSplits; i++) { - if (!test_bit (split->syncbits[i], SPLIT_FLAG_SYNC)) - { - // skip it, update is not needed - continue; - } Backend * backend = split->handles[i]; ksRewind (split->keysets[i]); keySetName (parentKey, keyName (split->parents[i])); @@ -611,18 +618,24 @@ static int elektraGetDoUpdateWithGlobalHooks (KDB * handle, Split * split, KeySe { int ret = 0; - if (!pgs_done && (p == (STORAGE_PLUGIN + 1)) && handle->globalPlugins[POSTGETSTORAGE][FOREACH]) + if (p == (STORAGE_PLUGIN + 1) && handle->globalPlugins[PROCGETSTORAGE][FOREACH]) + { + keySetName (parentKey, keyName (initialParent)); + ksRewind (ks); + handle->globalPlugins[PROCGETSTORAGE][FOREACH]->kdbGet (handle->globalPlugins[PROCGETSTORAGE][FOREACH], ks, + parentKey); + keySetName (parentKey, keyName (split->parents[i])); + } + if (p == (STORAGE_PLUGIN + 2) && handle->globalPlugins[POSTGETSTORAGE][FOREACH]) { - pgs_done = 1; keySetName (parentKey, keyName (initialParent)); ksRewind (ks); handle->globalPlugins[POSTGETSTORAGE][FOREACH]->kdbGet (handle->globalPlugins[POSTGETSTORAGE][FOREACH], ks, parentKey); keySetName (parentKey, keyName (split->parents[i])); } - else if (!pgc_done && (p == (NR_OF_PLUGINS - 1)) && handle->globalPlugins[POSTGETCLEANUP][FOREACH]) + else if (p == (NR_OF_PLUGINS - 1) && handle->globalPlugins[POSTGETCLEANUP][FOREACH]) { - pgc_done = 1; keySetName (parentKey, keyName (initialParent)); ksRewind (ks); handle->globalPlugins[POSTGETCLEANUP][FOREACH]->kdbGet (handle->globalPlugins[POSTGETCLEANUP][FOREACH], ks, @@ -634,6 +647,12 @@ static int elektraGetDoUpdateWithGlobalHooks (KDB * handle, Split * split, KeySe { if (p <= STORAGE_PLUGIN) { + if (!test_bit (split->syncbits[i], SPLIT_FLAG_SYNC)) + { + // skip it, update is not needed + continue; + } + ret = backend->getplugins[p]->kdbGet (backend->getplugins[p], split->keysets[i], parentKey); } else @@ -647,6 +666,7 @@ static int elektraGetDoUpdateWithGlobalHooks (KDB * handle, Split * split, KeySe if (ret == -1) { + keySetName (parentKey, keyName (initialParent)); // Ohh, an error occurred, // lets stop the process. elektraGlobalError (handle, ks, parentKey, GETSTORAGE, DEINIT); @@ -655,8 +675,13 @@ static int elektraGetDoUpdateWithGlobalHooks (KDB * handle, Split * split, KeySe } } } - elektraGlobalGet (handle, ks, parentKey, GETSTORAGE, DEINIT); - // elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, DEINIT); + + if (run == FIRST) + { + keySetName (parentKey, keyName (initialParent)); + elektraGlobalGet (handle, ks, parentKey, GETSTORAGE, DEINIT); + // elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, DEINIT); + } return 0; } @@ -813,6 +838,12 @@ int kdbGet (KDB * handle, KeySet * ks, Key * parentKey) switch (elektraGetCheckUpdateNeeded (split, parentKey)) { case 0: // We don't need an update so let's do nothing + + keySetName (parentKey, keyName (initialParent)); + elektraGlobalGet (handle, ks, parentKey, PROCGETSTORAGE, INIT); + elektraGlobalGet (handle, ks, parentKey, PROCGETSTORAGE, MAXONCE); + elektraGlobalGet (handle, ks, parentKey, PROCGETSTORAGE, DEINIT); + keySetName (parentKey, keyName (initialParent)); elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, INIT); elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, MAXONCE); @@ -836,10 +867,12 @@ int kdbGet (KDB * handle, KeySet * ks, Key * parentKey) goto error; } - if (handle->globalPlugins[POSTGETSTORAGE][FOREACH] || handle->globalPlugins[POSTGETCLEANUP][FOREACH]) + if (handle->globalPlugins[POSTGETSTORAGE][FOREACH] || handle->globalPlugins[POSTGETCLEANUP][FOREACH] || + handle->globalPlugins[PROCGETSTORAGE][FOREACH] || handle->globalPlugins[PROCGETSTORAGE][INIT] || + handle->globalPlugins[PROCGETSTORAGE][MAXONCE] || handle->globalPlugins[PROCGETSTORAGE][DEINIT]) { clearError (parentKey); - if (elektraGetDoUpdateWithGlobalHooks (NULL, split, NULL, parentKey, initialParent, FIRST) == -1) + if (elektraGetDoUpdateWithGlobalHooks (handle, split, ks, parentKey, initialParent, FIRST) == -1) { goto error; } @@ -874,6 +907,7 @@ int kdbGet (KDB * handle, KeySet * ks, Key * parentKey) /* Now do the real updating, but not for bypassed keys in split->size-1 */ clearError (parentKey); + // do everything up to position get_storage if (elektraGetDoUpdate (split, parentKey) == -1) { goto error; @@ -882,18 +916,20 @@ int kdbGet (KDB * handle, KeySet * ks, Key * parentKey) { copyError (parentKey, oldError); } + /* Now post-process the updated keysets */ if (splitGet (split, parentKey, handle) == -1) { ELEKTRA_ADD_WARNING (108, parentKey, keyName (ksCurrent (ks))); // continue, because sizes are already updated } - /* We are finished, now just merge everything to returned */ - ksClear (ks); + ksClear (ks); splitMerge (split, ks); } + keySetName (parentKey, keyName (initialParent)); + elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, INIT); elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, MAXONCE); elektraGlobalGet (handle, ks, parentKey, POSTGETSTORAGE, DEINIT); @@ -1324,6 +1360,443 @@ int kdbSet (KDB * handle, KeySet * ks, Key * parentKey) return -1; } +/** + * Checks whether the same instance of the list plugin is mounted in the global (maxonce) positions: + * + * pregetstorage, procgetstorage, postgetstorage, postgetcleanup, + * presetstorage, presetcleanup, precommit, postcommit, + * prerollback and postrollback + * + * @param handle the KDB to check + * + * @retval 1 if list is mounted everywhere + * @retval 0 otherwise + */ +static int ensureListPluginMountedEverywhere (KDB * handle) +{ + GlobalpluginPositions expectedPositions[] = { PREGETSTORAGE, + PROCGETSTORAGE, + POSTGETSTORAGE, + POSTGETCLEANUP, + PRESETSTORAGE, + PRESETCLEANUP, + PRECOMMIT, + POSTCOMMIT, + PREROLLBACK, + POSTROLLBACK, + -1 }; + + Plugin * list = handle->globalPlugins[expectedPositions[0]][MAXONCE]; + if (list == NULL || strcmp (list->name, "list") != 0) + { + ELEKTRA_LOG_WARNING ("list plugin not mounted at position %s/maxonce", GlobalpluginPositionsStr[expectedPositions[0]]); + return 0; + } + + for (int i = 1; expectedPositions[i] > 0; ++i) + { + Plugin * plugin = handle->globalPlugins[expectedPositions[i]][MAXONCE]; + if (plugin != list) + { + // must always be the same instance + ELEKTRA_LOG_WARNING ("list plugin not mounted at position %s/maxonce", + GlobalpluginPositionsStr[expectedPositions[i]]); + return 0; + } + } + + return 1; +} + +/** + * Finds the global placements in which a plugin should be mounted and mounts the plugin there, if it isn't already. + * + * @post The plugin is mounted globally in all placements defined in its infos/placements key. + * + * @param handle the KDB handle to use + * @param pluginName the name of the plugin to mount + * @param pluginConfig the configuration to use, if the plugin has to be mounted + * @param errorKey used for error reporting + * + * @retval 0 on error, warnings will be logged + * @retval 1 on success + */ +static int ensureGlobalPluginMounted (KDB * handle, const char * pluginName, KeySet * pluginConfig, Key * errorKey) +{ + ELEKTRA_NOT_NULL (handle); + ELEKTRA_NOT_NULL (pluginName); + + if (!ensureListPluginMountedEverywhere (handle)) + { + ELEKTRA_LOG_WARNING ("the list plugin MUST be mounted in all global positions, or kdbEnsure will not work"); + return 0; + } + + Plugin * listPlugin = handle->globalPlugins[PREROLLBACK][MAXONCE]; // take any position + typedef int (*mountPluginFun) (Plugin *, const char *, KeySet *, Key *); + mountPluginFun listAddPlugin = (mountPluginFun) elektraPluginGetFunction (listPlugin, "mountplugin"); + + int result = listAddPlugin (listPlugin, pluginName, pluginConfig, errorKey); + if (result == ELEKTRA_PLUGIN_STATUS_ERROR) + { + ELEKTRA_LOG_WARNING ("could not add plugin %s to list plugin", pluginName); + elektraFree (listPlugin); + return 0; + } + + return 1; +} + +/** + * Finds the global placements in which a plugin should be mounted and removes the plugin from these, if it is present. + * + * @post The plugin is not mounted globally in any of the placements defined in its infos/placements key. + * + * @param handle the KDB handle to use + * @param pluginName the name of the plugin to mount + * @param errorKey used for error reporting + * + * @retval 0 on error, warnings will be logged + * @retval 1 on success + */ +static int ensureGlobalPluginUnmounted (KDB * handle, const char * pluginName, Key * errorKey) +{ + ELEKTRA_NOT_NULL (handle); + ELEKTRA_NOT_NULL (pluginName); + + if (!ensureListPluginMountedEverywhere (handle)) + { + ELEKTRA_LOG_WARNING ("the list plugin MUST be mounted in all global positions, or kdbEnsure will not work"); + return 0; + } + + Plugin * listPlugin = handle->globalPlugins[PREROLLBACK][MAXONCE]; // take any position + typedef int (*unmountPluginFun) (Plugin *, const char *, Key *); + unmountPluginFun listRemovePlugin = (unmountPluginFun) elektraPluginGetFunction (listPlugin, "unmountplugin"); + + int result = listRemovePlugin (listPlugin, pluginName, errorKey); + if (result == ELEKTRA_PLUGIN_STATUS_ERROR) + { + ELEKTRA_LOG_WARNING ("could not remove %s from list plugin", pluginName); + return 0; + } + + return 1; +} + +/** + * Finds the placements in which a plugin should be mounted and removes the plugin from these, if it is present. + * The functions only affects the mountpoint given in @p mountpoint. + * + * @post For mountpoint @p mountpoint, the plugin is not mounted in any of the placements defined in its infos/placements key. + * + * @param handle the KDB handle to use + * @param mountpoint the mountpoint to modify + * @param pluginName the name of the plugin to mount + * @param errorKey used for error reporting + * + * @retval 0 on error, warnings will be logged + * @retval 1 on success + */ +static int ensurePluginUnmounted (KDB * handle, const char * mountpoint, const char * pluginName, Key * errorKey) +{ + Key * mountpointKey = keyNew (mountpoint, KEY_END); + Backend * backend = mountGetBackend (handle, mountpointKey); + + int ret = 1; + for (int i = 0; i < NR_OF_PLUGINS; ++i) + { + Plugin * getPlugin = backend->getplugins[i]; + Plugin * setPlugin = backend->setplugins[i]; + Plugin * errorPlugin = backend->errorplugins[i]; + + if (setPlugin != NULL && strcmp (setPlugin->name, pluginName) == 0) + { + if (elektraPluginClose (setPlugin, errorKey) == ELEKTRA_PLUGIN_STATUS_ERROR) + { + ret = 0; + } + backend->setplugins[i] = NULL; + } + + if (getPlugin != NULL && strcmp (getPlugin->name, pluginName) == 0) + { + if (elektraPluginClose (getPlugin, errorKey) == ELEKTRA_PLUGIN_STATUS_ERROR) + { + ret = 0; + } + backend->getplugins[i] = NULL; + } + + if (errorPlugin != NULL && strcmp (errorPlugin->name, pluginName) == 0) + { + if (elektraPluginClose (errorPlugin, errorKey) == ELEKTRA_PLUGIN_STATUS_ERROR) + { + ret = 0; + } + backend->errorplugins[i] = NULL; + } + } + + keyDel (mountpointKey); + return ret; +} + +enum PluginContractState +{ + PLUGIN_STATE_UNMOUNTED, + PLUGIN_STATE_MOUNTED, + PLUGIN_STATE_REMOUNT, +}; + +/** + * Ensures a kdbEnsure() contract clause for a global plugin. + * + * @see kdbEnsure() + * + * @param handle the KDB handle + * @param pluginName the name of the plugin + * @param pluginState the intended clause for the plugin + * @param pluginConfig the config KeySet for the plugin; is always consumed, i.e. you shouldn't ksDel() it after calling this + * @param errorKey used for error reporting + * + * @retval 1 on success + * @retval 0 otherwise + */ +static int ensureGlobalPluginState (KDB * handle, const char * pluginName, enum PluginContractState pluginState, KeySet * pluginConfig, + Key * errorKey) +{ + switch (pluginState) + { + case PLUGIN_STATE_UNMOUNTED: + ksDel (pluginConfig); + return ensureGlobalPluginUnmounted (handle, pluginName, errorKey); + case PLUGIN_STATE_MOUNTED: + return ensureGlobalPluginMounted (handle, pluginName, pluginConfig, errorKey); + case PLUGIN_STATE_REMOUNT: + { + return ensureGlobalPluginUnmounted (handle, pluginName, errorKey) && + ensureGlobalPluginMounted (handle, pluginName, pluginConfig, errorKey); + } + default: + ELEKTRA_ASSERT (0, "missing switch case"); + return 0; + } +} + +/** + * Ensures a kdbEnsure() contract clause for a plugin under a certain mountpoint. + * + * @see kdbEnsure() + * + * @param handle the KDB handle + * @param mountpoint the mountpoint to use + * @param pluginName the name of the plugin + * @param pluginState the intended clause for the plugin + * @param pluginConfig the config KeySet for the plugin; is always consumed, i.e. you shouldn't ksDel() it after calling this + * @param errorKey used for error reporting + * + * @retval 1 on success + * @retval 0 otherwise + */ +static int ensurePluginState (KDB * handle ELEKTRA_UNUSED, const char * mountpoint ELEKTRA_UNUSED, const char * pluginName ELEKTRA_UNUSED, + enum PluginContractState pluginState, KeySet * pluginConfig ELEKTRA_UNUSED, Key * errorKey ELEKTRA_UNUSED) +{ + switch (pluginState) + { + case PLUGIN_STATE_UNMOUNTED: + ksDel (pluginConfig); + return ensurePluginUnmounted (handle, mountpoint, pluginName, errorKey); + case PLUGIN_STATE_MOUNTED: + ELEKTRA_ASSERT (0, "not supported"); + return 0; // TODO: ensurePluginMounted (handle, mountpoint, pluginName, pluginConfig, errorKey); + case PLUGIN_STATE_REMOUNT: + ELEKTRA_ASSERT (0, "not supported"); + return 0; // TODO: ensurePluginUnmounted (handle, mountpoint, pluginName, errorKey) && ensurePluginMounted (handle, + // mountpoint, pluginName, pluginConfig, errorKey); + default: + ELEKTRA_ASSERT (0, "missing switch case"); + return 0; + } +} + +/** + * This function can be used the given KDB @p handle meets certain clauses, + * specified in @p contract. Currently the following clauses are supported: + * + * - `system/elektra/ensure/plugins//` defines the state of the plugin + * `` for the mountpoint ``: + * - The value `unmounted` ensures the plugin is not mounted, at this mountpoint. + * - The value `mounted` ensures the plugin is mounted, at this mountpoint. + * If the plugin is not mounted, we will try to mount it. + * - The value `remount` always mounts the plugin, at this mountpoint. + * If it was already mounted, it will me unmounted and mounted again. + * This can be used to ensure the plugin is mounted with a certain configuration. + * - Keys below `system/elektra/ensure/plugins///config` are extracted and used + * as the plugins config KeySet during mounting. `system/elektra/ensure/plugins//` + * will be repleced by `user` in the keynames. If no keys are given, an empty KeySet is used. + * + * There are a few special values for ``: + * - `global` is used to indicate the plugin should (un)mounted as a global plugin. + * Currently this only supports (un)mounting plugins from/to the subposition `maxonce`. + * - `parent` is used to indicate the keyname of @p parentKey shall be used as the mountpoint. + * + * If `` is none of those values, it has to be valid keyname with the slashes escaped. + * That means it has to start with `/`, `user`, `system`, `dir` or `spec`. + * + * If `` is NOT `global`, currently only `unmounted` is supported (not `mounted` and `remounted`). + * + * NOTE: This function only works properly, if the list plugin is mounted in all global positions. + * If this is not the case, 1 will be returned, because this is seen as an implicit clause in the contract. + * Additionally any contract that specifies clauses for the list plugin is rejected as malformed. + * + * @param handle contains internal information of @link kdbOpen() opened @endlink key database + * @param contract KeySet containing the contract described above. + * This will always be `ksDel()`ed. **Even in error cases.** + * @param parentKey The parentKey used if the `parent` special value is used, + * otherwise only used for error reporting. + * + * @retval 0 on success + * @retval 1 if clauses of the contract are unmet + * @retval -1 on NULL pointers, or malformed contract + */ +int kdbEnsure (KDB * handle, KeySet * contract, Key * parentKey) +{ + if (contract == NULL) + { + return -1; + } + + if (handle == NULL || parentKey == NULL) + { + ksDel (contract); + return -1; + } + + Key * cutpoint = keyNew ("system/elektra/ensure/plugins", KEY_END); + KeySet * pluginsContract = ksCut (contract, cutpoint); + + // delete unused part of contract immediately + ksDel (contract); + + ksRewind (pluginsContract); + Key * clause = NULL; + while ((clause = ksNext (pluginsContract)) != NULL) + { + // only handle 'system/elektra/ensure/plugins//' keys + const char * condUNameBase = keyUnescapedName (clause); + const char * condUName = condUNameBase; + condUName += sizeof ("system\0elektra\0ensure\0plugins"); // skip known common part + + size_t condUSize = keyGetUnescapedNameSize (clause); + if (condUNameBase + condUSize <= condUName) + { + continue; // base key + } + + condUName += strlen (condUName) + 1; // skip mountpoint + if (condUNameBase + condUSize <= condUName) + { + continue; // mountpoint key + } + + condUName += strlen (condUName) + 1; // skip pluginname + if (condUNameBase + condUSize > condUName) + { + continue; // key below 'system/elektra/ensure/plugins//' + } + + const char * mountpoint = keyUnescapedName (clause); + mountpoint += sizeof ("system\0elektra\0ensure\0plugins"); + const char * pluginName = keyBaseName (clause); + const char * pluginStateString = keyString (clause); + + if (strcmp (pluginName, "list") == 0) + { + ELEKTRA_SET_ERROR (ELEKTRA_ERROR_MALFORMED_CONTRACT, parentKey, "Cannot specify clauses for the list plugin!!"); + keyDel (cutpoint); + ksDel (pluginsContract); + return -1; + } + + enum PluginContractState pluginState; + if (strcmp (pluginStateString, "unmounted") == 0) + { + pluginState = PLUGIN_STATE_UNMOUNTED; + } + else if (strcmp (pluginStateString, "mounted") == 0) + { + pluginState = PLUGIN_STATE_MOUNTED; + } + else if (strcmp (pluginStateString, "remount") == 0) + { + pluginState = PLUGIN_STATE_REMOUNT; + } + else + { + ELEKTRA_SET_ERRORF ( + ELEKTRA_ERROR_MALFORMED_CONTRACT, parentKey, + "The key '%s' contained the value '%s', but only 'unmounted', 'mounted' or 'remounted' may be used.", + keyName (clause), pluginStateString); + keyDel (cutpoint); + ksDel (pluginsContract); + return -1; + } + + Key * pluginCutpoint = keyNew (keyName (clause), KEY_END); + keyAddBaseName (pluginCutpoint, "config"); + KeySet * pluginConfig = ksCut (pluginsContract, pluginCutpoint); + ksAppendKey (pluginConfig, pluginCutpoint); + { + KeySet * newPluginConfig = elektraRenameKeys (pluginConfig, "user"); + ksDel (pluginConfig); + pluginConfig = newPluginConfig; + } + + if (strcmp (mountpoint, "global") == 0) + { + + if (!ensureGlobalPluginState (handle, pluginName, pluginState, pluginConfig, parentKey)) + { + keyDel (cutpoint); + ksDel (pluginConfig); + ksDel (pluginsContract); + return 1; + } + } + else + { + if (pluginState != PLUGIN_STATE_UNMOUNTED) + { + ELEKTRA_SET_ERRORF (ELEKTRA_ERROR_MALFORMED_CONTRACT, parentKey, + "The key '%s' contained the value '%s', but only 'unmounted' is supported for " + "non-global clauses at the moment.", + keyName (clause), pluginStateString); + keyDel (cutpoint); + ksDel (pluginConfig); + ksDel (pluginsContract); + return -1; + } + + if (strcmp (mountpoint, "parent") == 0) + { + mountpoint = keyName (parentKey); + } + + if (!ensurePluginState (handle, mountpoint, pluginName, pluginState, pluginConfig, parentKey)) + { + keyDel (cutpoint); + ksDel (pluginsContract); + return 1; + } + } + } + keyDel (cutpoint); + ksDel (pluginsContract); + + return 0; +} + /** * @} */ diff --git a/src/libs/elektra/mount.c b/src/libs/elektra/mount.c index acac733dfae..5057a616f69 100644 --- a/src/libs/elektra/mount.c +++ b/src/libs/elektra/mount.c @@ -320,7 +320,8 @@ KeySet * elektraDefaultGlobalConfig (void) keyNew ("system/elektra/globalplugins/postcommit/user", KEY_VALUE, "list", KEY_END), keyNew ("system/elektra/globalplugins/postcommit/user/placements", KEY_VALUE, "", KEY_END), keyNew ("system/elektra/globalplugins/postcommit/user/placements/error", KEY_VALUE, "prerollback postrollback", KEY_END), - keyNew ("system/elektra/globalplugins/postcommit/user/placements/get", KEY_VALUE, "pregetstorage postgetstorage", KEY_END), + keyNew ("system/elektra/globalplugins/postcommit/user/placements/get", KEY_VALUE, + "pregetstorage procgetstorage postgetstorage", KEY_END), keyNew ("system/elektra/globalplugins/postcommit/user/placements/set", KEY_VALUE, "presetstorage precommit postcommit", KEY_END), #ifndef __MINGW32__ @@ -330,12 +331,15 @@ KeySet * elektraDefaultGlobalConfig (void) keyNew ("system/elektra/globalplugins/postcommit/user/plugins/#0/placements/get", KEY_VALUE, "postgetstorage", KEY_END), keyNew ("system/elektra/globalplugins/postcommit/user/plugins/#0/placements/set", KEY_VALUE, "presetstorage", KEY_END), #endif + keyNew ("system/elektra/globalplugins/postgetcleanup", KEY_VALUE, "list", KEY_END), keyNew ("system/elektra/globalplugins/postgetstorage", KEY_VALUE, "list", KEY_END), keyNew ("system/elektra/globalplugins/postrollback", KEY_VALUE, "list", KEY_END), keyNew ("system/elektra/globalplugins/precommit", KEY_VALUE, "list", KEY_END), keyNew ("system/elektra/globalplugins/pregetstorage", KEY_VALUE, "list", KEY_END), keyNew ("system/elektra/globalplugins/prerollback", KEY_VALUE, "list", KEY_END), - keyNew ("system/elektra/globalplugins/presetstorage", KEY_VALUE, "list", KEY_END), KS_END); + keyNew ("system/elektra/globalplugins/presetcleanup", KEY_VALUE, "list", KEY_END), + keyNew ("system/elektra/globalplugins/presetstorage", KEY_VALUE, "list", KEY_END), + keyNew ("system/elektra/globalplugins/procgetstorage", KEY_VALUE, "list", KEY_END), KS_END); } int mountGlobals (KDB * kdb, KeySet * keys, KeySet * modules, Key * errorKey) diff --git a/src/libs/elektra/plugin.c b/src/libs/elektra/plugin.c index 7d75a7976b9..f4b0a9e05f5 100644 --- a/src/libs/elektra/plugin.c +++ b/src/libs/elektra/plugin.c @@ -441,3 +441,44 @@ Plugin * elektraPluginVersion (void) returned->kdbSet = elektraVersionSet; return returned; } + +/** + * Searches the global plugins for a given plugin name. + * + * NOTE: if the list plugin occupies the prerollback position, + * this queries the list plugin first, and only if we don't find + * anything there, we look directly in the global plugins array + * + * @param handle The KDB handle to search + * @param pluginName The plugin name to look for + * + * @return the plugin handle, if found or NULL otherwise + */ +Plugin * elektraPluginFindGlobal (KDB * handle, const char * pluginName) +{ + Plugin * listPlugin = handle->globalPlugins[PREROLLBACK][MAXONCE]; // take any position + if (listPlugin != NULL && strcmp (listPlugin->name, "list") == 0) + { + typedef Plugin * (*findPluginFun) (Plugin *, const char *); + findPluginFun listFindPlugin = (findPluginFun) elektraPluginGetFunction (listPlugin, "findplugin"); + Plugin * plugin = listFindPlugin (listPlugin, pluginName); + if (plugin != NULL) + { + return plugin; + } + } + + for (GlobalpluginPositions pos = 0; pos < NR_GLOBAL_POSITIONS; ++pos) + { + for (GlobalpluginSubPositions sub = 0; sub < NR_GLOBAL_SUBPOSITIONS; ++sub) + { + Plugin * plugin = handle->globalPlugins[pos][sub]; + if (plugin != NULL && strcmp (plugin->name, pluginName) == 0) + { + return plugin; + } + } + } + + return NULL; +} diff --git a/src/libs/notification/notification.c b/src/libs/notification/notification.c index b914f32fc44..22154994f04 100644 --- a/src/libs/notification/notification.c +++ b/src/libs/notification/notification.c @@ -10,639 +10,17 @@ #include #include -#include +#include #include #include #include #include #include +#include +#include // for elektraGetPluginFunction, elektraPluginFindGlobal, kdb->globalPlugins and plugin->config #include -/** - * @internal - * Converts a placement name to an index in the globalPlugins array of - * the internal KDB structure. - * - * @param placement Placement name - * @return Placement index or -1 on unknown placement name - */ -static int placementToPosition (char * placement) -{ - if (strcmp (placement, "prerollback") == 0) - { - return PREROLLBACK; - } - else if (strcmp (placement, "rollback") == 0) - { - return ROLLBACK; - } - else if (strcmp (placement, "postrollback") == 0) - { - return POSTROLLBACK; - } - else if (strcmp (placement, "getresolver") == 0) - { - return GETRESOLVER; - } - else if (strcmp (placement, "pregetstorage") == 0) - { - return PREGETSTORAGE; - } - else if (strcmp (placement, "getstorage") == 0) - { - return GETSTORAGE; - } - else if (strcmp (placement, "postgetstorage") == 0) - { - return POSTGETSTORAGE; - } - else if (strcmp (placement, "setresolver") == 0) - { - return SETRESOLVER; - } - else if (strcmp (placement, "postgetcleanup") == 0) - { - return POSTGETCLEANUP; - } - else if (strcmp (placement, "presetstorage") == 0) - { - return PRESETSTORAGE; - } - else if (strcmp (placement, "setstorage") == 0) - { - return SETSTORAGE; - } - else if (strcmp (placement, "presetstorage") == 0) - { - return PRESETSTORAGE; - } - else if (strcmp (placement, "setstorage") == 0) - { - return SETSTORAGE; - } - else if (strcmp (placement, "presetcleanup") == 0) - { - return PRESETCLEANUP; - } - else if (strcmp (placement, "precommit") == 0) - { - return PRECOMMIT; - } - else if (strcmp (placement, "commit") == 0) - { - return COMMIT; - } - else if (strcmp (placement, "postcommit") == 0) - { - return POSTCOMMIT; - } - else - { - ELEKTRA_LOG_WARNING ("unknown placement name \"%s\"", placement); - return -1; - } -} - -/** - * @internal - * Convert plament name to list plugin's position type. - * - * The list plugin distinguishes between three position types: get, set & error. - * Plament names are converted to one of these position types. - * - * @param placement Placement name - * @return Placement type for list plugin or NULL on unknown placement - * name - */ -static char * placementToListPositionType (char * placement) -{ - if (strcmp (placement, "prerollback") == 0) - { - return "error"; - } - else if (strcmp (placement, "rollback") == 0) - { - return "error"; - } - else if (strcmp (placement, "postrollback") == 0) - { - return "error"; - } - else if (strcmp (placement, "getresolver") == 0) - { - return "get"; - } - else if (strcmp (placement, "pregetstorage") == 0) - { - return "get"; - } - else if (strcmp (placement, "getstorage") == 0) - { - return "get"; - } - else if (strcmp (placement, "postgetstorage") == 0) - { - return "get"; - } - else if (strcmp (placement, "setresolver") == 0) - { - return "set"; - } - else if (strcmp (placement, "postgetcleanup") == 0) - { - return "get"; - } - else if (strcmp (placement, "presetstorage") == 0) - { - return "set"; - } - else if (strcmp (placement, "setstorage") == 0) - { - return "set"; - } - else if (strcmp (placement, "presetstorage") == 0) - { - return "set"; - } - else if (strcmp (placement, "setstorage") == 0) - { - return "set"; - } - else if (strcmp (placement, "presetcleanup") == 0) - { - return "set"; - } - else if (strcmp (placement, "precommit") == 0) - { - return "set"; - } - else if (strcmp (placement, "commit") == 0) - { - return "set"; - } - else if (strcmp (placement, "postcommit") == 0) - { - return "set"; - } - else - { - ELEKTRA_LOG_WARNING ("unknown placement name \"%s\"", placement); - return NULL; - } -} - -/** - * @internal - * Load plugin by name. - * - * Uses module cache from KDB handle. - * The plugin only needs to be closed after use. - * - * @param kdb KDB handle - * @param name Plugin name - * @param config Plugin configuration - * @return Plugin handle or NULL on error - */ -static Plugin * loadPlugin (KDB * kdb, char * name, KeySet * config) -{ - // Load required plugin - Key * errorKey = keyNew (0); - KeySet * moduleCache = kdb->modules; // use kdb module cache - Plugin * plugin = elektraPluginOpen (name, moduleCache, config, errorKey); - - int hasError = keyGetMeta (errorKey, "error") != NULL; - keyDel (errorKey); - - if (!plugin || hasError) - { - ELEKTRA_LOG_WARNING ("elektraPluginOpen failed!\n"); - return NULL; - } - - return plugin; -} - -/** - * @internal - * Unload plugin given by plugin handle. - * - * @return Plugin handle or NULL on error - */ - -/** - * @internal - * Unload plugin by plugin handle. - * - * @param plugin Plugin handle - * @retval 0 on error - * @retval 1 on success - */ -static int unloadPlugin (Plugin * plugin) -{ - ELEKTRA_NOT_NULL (plugin); - Key * errorKey = keyNew (0); - int result = elektraPluginClose (plugin, errorKey); - - int hasError = keyGetMeta (errorKey, "error") != NULL; - keyDel (errorKey); - - if (!result || hasError) - { - ELEKTRA_LOG_WARNING ("elektraPluginClose failed: result=%d", result); - return 0; - } - else - { - return 1; - } -} - -/** - * @internal - * Read placement list from plugin. - * - * The returned string needs to be freed. - * - * @param plugin Plugin - * @return Space separated list of placement names - */ -static char * getPluginPlacementList (Plugin * plugin) -{ - ELEKTRA_NOT_NULL (plugin); - - // Get placements from plugin - Key * pluginInfo = keyNew ("system/elektra/modules/", KEY_END); - keyAddBaseName (pluginInfo, plugin->name); - KeySet * ksResult = ksNew (0, KS_END); - plugin->kdbGet (plugin, ksResult, pluginInfo); - - Key * placementsKey = keyDup (pluginInfo); - keyAddBaseName (placementsKey, "infos"); - keyAddBaseName (placementsKey, "placements"); - Key * placements = ksLookup (ksResult, placementsKey, 0); - if (placements == NULL) - { - ELEKTRA_LOG_WARNING ("could not read placements from plugin"); - return 0; - } - char * placementList = elektraStrDup (keyString (placements)); - - keyDel (pluginInfo); - keyDel (placementsKey); - ksDel (ksResult); - - return placementList; -} - -/** - * @internal - * Add plugin at placement to list plugin configuration and apply it. - * - * @param list List plugin - * @param plugin Plugin to add - * @param placement Placement name - * @retval 0 on error - * @retval 0 on success - */ -static int listAddPlugin (Plugin * list, Plugin * plugin, char * placement) -{ - ELEKTRA_NOT_NULL (list); - ELEKTRA_NOT_NULL (plugin); - ELEKTRA_NOT_NULL (placement); - - KeySet * newConfig = ksDup (list->config); - - // Find name for next item in plugins array - Key * configBase = keyNew ("user/plugins", KEY_END); - KeySet * array = elektraArrayGet (configBase, newConfig); - Key * pluginItem = elektraArrayGetNextKey (array); - ELEKTRA_NOT_NULL (pluginItem); - keySetString (pluginItem, plugin->name); - keyDel (configBase); - - // Create key with plugin handle - Key * pluginHandle = keyDup (pluginItem); - keyAddName (pluginHandle, "handle"); - keySetBinary (pluginHandle, &plugin, sizeof (plugin)); - - // Create key with plugin placement - char * placementType = placementToListPositionType (placement); - if (placementType == NULL) - { - keyDel (configBase); - keyDel (pluginItem); - keyDel (pluginHandle); - return 0; - } - Key * pluginPlacements = keyDup (pluginItem); - keyAddName (pluginPlacements, "placements/"); - keyAddName (pluginPlacements, placementType); - keySetString (pluginPlacements, placement); - - // Append keys to list plugin configuration - ksAppendKey (newConfig, pluginItem); - ksAppendKey (newConfig, pluginHandle); - ksAppendKey (newConfig, pluginPlacements); - - ksDel (array); - ksDel (list->config); - - // Apply new configuration - list->config = newConfig; - list->kdbOpen (list, NULL); - - return 1; -} - -/** - * @internal - * Create a new key with a different root or common name. - * - * Does not modify `key`. The new key needs to be freed after usage. - * - * Preconditions: The key name starts with `source`. - * - * Example: - * ``` - * Key * source = keyNew("user/plugins/foo/placements/get", KEY_END); - * Key * dest = renameKey ("user/plugins/foo", "user/plugins/bar", source); - * succeed_if_same_string (keyName(dest), "user/plugins/bar/placements/get"); - * ``` - * - * - * @param source Part of the key name to replace - * @param dest Replaces `source` - * @param key key - * @return key with new name - */ -static Key * renameKey (const char * source, const char * dest, Key * key) -{ - const char * name = keyName (key); - char * baseKeyNames = strndup (name + strlen (source), strlen (name)); - - Key * moved = keyDup (key); - keySetName (moved, dest); - keyAddName (moved, baseKeyNames); - - elektraFree (baseKeyNames); - - return moved; -} - -/** - * @internal - * Recursively move all keys in keyset below source to dest. - * - * Modifies the keyset. - * - * Example: - * ``` - * moveKeysRecursive("user/plugins/#0", "user/plugins/#1", config); - * ``` - * - * @param source Root part to replace - * @param dest Destination for keys - * @param keyset keyset - */ -static void moveKeysRecursive (const char * source, const char * dest, KeySet * keyset) -{ - Key * sourceBaseKey = keyNew (source, KEY_END); - KeySet * newKeys = ksNew (0, KS_END); - - // Rename keys in keyset - Key * sourceKey; - ksRewind (keyset); - while ((sourceKey = ksNext (keyset)) != NULL) - { - // Rename all keys below sourceKey - if (!keyIsBelowOrSame (sourceBaseKey, sourceKey)) continue; - Key * destKey = renameKey (source, dest, sourceKey); - ksAppendKey (newKeys, destKey); - } - - // Remove source keys from keyset - KeySet * cut = ksCut (keyset, sourceBaseKey); - ksDel (cut); - - ksAppend (keyset, newKeys); - ksDel (newKeys); - - keyDel (sourceBaseKey); -} - -/** - * @internal - * Remove plugin at all placements from list plugin configuration and apply it. - * - * @param list List plugin - * @param plugin Plugin to remove - * @retval 0 on error - * @retval 1 on success - */ -static int listRemovePlugin (Plugin * list, Plugin * plugin) -{ - ELEKTRA_NOT_NULL (list); - ELEKTRA_NOT_NULL (plugin); - - KeySet * newConfig = ksDup (list->config); - - Key * configBase = keyNew ("user/plugins", KEY_END); - KeySet * array = elektraArrayGet (configBase, newConfig); - - // Find the plugin with our handle - Key * current; - ksRewind (array); - while ((current = ksNext (array)) != NULL) - { - Key * handleLookup = keyDup (current); - keyAddBaseName (handleLookup, "handle"); - Key * handle = ksLookup (newConfig, handleLookup, 0); - keyDel (handleLookup); - - if (handle) - { - Plugin * handleValue = (*(Plugin **) keyValue (handle)); - if (handleValue == plugin) - { - // Remove plugin configuration - KeySet * cut = ksCut (newConfig, current); - ksDel (cut); - } - } - } - ksDel (array); - - // Renumber array items - KeySet * sourceArray = elektraArrayGet (configBase, newConfig); - Key * renumberBase = keyNew ("user/plugins/#", KEY_END); - ksRewind (sourceArray); - while ((current = ksNext (sourceArray)) != NULL) - { - // Create new array item base name e.g. "user/plugins/#0" - elektraArrayIncName (renumberBase); - moveKeysRecursive (keyName (current), keyName (renumberBase), newConfig); - } - - keyDel (configBase); - keyDel (renumberBase); - ksDel (sourceArray); - ksDel (list->config); - - // Apply new configuration - list->config = newConfig; - list->kdbOpen (list, NULL); - - return 1; -} - -/** - * @internal - * Global mount given plugin at run-time. - * - * Reads placements from the plugin directly inserts the plugin. - * Also supports adding itself to the list plugin at run-time if present - * at requested global placement. - * - * @param kdb KDB handle - * @param plugin Plugin handle - * @retval 0 on errors - * @retval 1 on success - */ -static int mountGlobalPlugin (KDB * kdb, Plugin * plugin) -{ - ELEKTRA_NOT_NULL (kdb); - ELEKTRA_NOT_NULL (plugin); - - char * placementList = getPluginPlacementList (plugin); - - // Parse plament list (contains placements from README.md seperated by - // whitespace) - char * placement = strtok (placementList, " "); - while (placement != NULL) - { - // Convert placement name to internal index - int placementIndex = placementToPosition (placement); - if (placementIndex == -1) - { - elektraFree (placementList); - return 0; - } - - if (kdb->globalPlugins[placementIndex][MAXONCE] == NULL) - { - // Insert directly as global plugin - kdb->globalPlugins[placementIndex][MAXONCE] = plugin; - } - else - { - Plugin * pluginAtPlacement = kdb->globalPlugins[placementIndex][MAXONCE]; - // Add plugin to list plugin - if (strcmp (pluginAtPlacement->name, "list") == 0) - { - ELEKTRA_LOG_DEBUG ("required position %s/maxonce taken by list plugin, adding plugin", placement); - int result = listAddPlugin (pluginAtPlacement, plugin, placement); - if (!result) - { - ELEKTRA_LOG_WARNING ("could not add plugin to list plugin at position %s/maxonce", placement); - elektraFree (placementList); - return 0; - } - } - else - { - // cannot manually add list module here. configuration is broken: - // the list module needs to be mounted in every position to keep track - // of the current position - ELEKTRA_LOG_WARNING ("required position %s/maxonce taken by plugin %s, aborting!", placement, - pluginAtPlacement->name); - elektraFree (placementList); - return 0; - } - } - - // Process next placement in list - placement = strtok (NULL, " "); - } - - elektraFree (placementList); - - return 1; -} - -/** - * @internal - * Unmount global plugin at run-time. - * - * Removes a plugin at all placements. - * Undos `mountGlobalPlugin()`. - * - * @param kdb KDB handle - * @param plugin Plugin handle - * @retval 0 on errors - * @retval 1 on success - */ -static int unmountGlobalPlugin (KDB * kdb, Plugin * plugin) -{ - ELEKTRA_NOT_NULL (kdb); - ELEKTRA_NOT_NULL (plugin); - - char * placementList = getPluginPlacementList (plugin); - - // Parse plament list (contains placements from README.md seperated by - // whitespace) - char * placement = strtok (placementList, " "); - while (placement != NULL) - { - // Convert placement name to internal index - int placementIndex = placementToPosition (placement); - if (placementIndex == -1) - { - elektraFree (placementList); - return 0; - } - - if (kdb->globalPlugins[placementIndex][MAXONCE] == plugin) - { - // Remove from direct placement as global plugin - kdb->globalPlugins[placementIndex][MAXONCE] = NULL; - } - else - { - Plugin * pluginAtPlacement = kdb->globalPlugins[placementIndex][MAXONCE]; - // Add plugin to list plugin - if (strcmp (pluginAtPlacement->name, "list") == 0) - { - ELEKTRA_LOG_DEBUG ( - "required position %s/maxonce taken by list plugin, " - "removing plugin", - placement); - int result = listRemovePlugin (pluginAtPlacement, plugin); - if (!result) - { - ELEKTRA_LOG_WARNING ("could not remove plugin from list plugin at position %s/maxonce", placement); - elektraFree (placementList); - return 0; - } - } - else - { - ELEKTRA_LOG_WARNING ( - "required position %s/maxonce taken by plugin %s, " - "should be either list or plugin!", - placement, pluginAtPlacement->name); - } - } - - // Process next placement in list - placement = strtok (NULL, " "); - } - - elektraFree (placementList); - - return 1; -} - static void pluginsOpenNotification (KDB * kdb, ElektraNotificationCallback callback, ElektraNotificationCallbackContext * context) { ELEKTRA_NOT_NULL (kdb); @@ -708,60 +86,66 @@ int elektraNotificationOpen (KDB * kdb) ELEKTRA_LOG_WARNING ("kdb was not set"); return 0; } + + Plugin * notificationPlugin = elektraPluginFindGlobal (kdb, "internalnotification"); // Allow open only once - if (kdb->notificationPlugin) + if (notificationPlugin) { ELEKTRA_LOG_WARNING ("elektraNotificationOpen already called for kdb"); return 0; } - Plugin * notificationPlugin = loadPlugin (kdb, "internalnotification", NULL); - if (!notificationPlugin) + // Create context for notification callback + ElektraNotificationCallbackContext * context = elektraMalloc (sizeof (*context)); + if (context == NULL) { return 0; } + context->kdb = kdb; + context->kdbUpdate = &elektraNotificationKdbUpdate; - int mountResult = mountGlobalPlugin (kdb, notificationPlugin); - if (!mountResult) + Key * parent = keyNew ("", KEY_END); + KeySet * contract = ksNew (2, keyNew ("system/elektra/ensure/plugins/global/internalnotification", KEY_VALUE, "mounted", KEY_END), + keyNew ("system/elektra/ensure/plugins/global/internalnotification/config/context", KEY_BINARY, KEY_SIZE, + sizeof (context), KEY_VALUE, &context, KEY_END), + KS_END); + if (kdbEnsure (kdb, contract, parent) != 0) { - Key * errorKey = keyNew (0); - elektraPluginClose (notificationPlugin, errorKey); - keyDel (errorKey); + keyDel (parent); + ELEKTRA_LOG_WARNING ("kdbEnsure failed"); return 0; } - // Create context for notification callback - ElektraNotificationCallbackContext * context = elektraMalloc (sizeof (*context)); - if (context == NULL) + notificationPlugin = elektraPluginFindGlobal (kdb, "internalnotification"); + if (notificationPlugin == NULL) { - unmountGlobalPlugin (kdb, notificationPlugin); - Key * errorKey = keyNew (0); - elektraPluginClose (notificationPlugin, errorKey); - keyDel (errorKey); + ELEKTRA_LOG_WARNING ("kdbEnsure failed"); return 0; } - context->kdb = kdb; - context->kdbUpdate = &elektraNotificationKdbUpdate; + context->notificationPlugin = notificationPlugin; // Get notification callback from notification plugin size_t func = elektraPluginGetFunction (notificationPlugin, "notificationCallback"); if (!func) { - unmountGlobalPlugin (kdb, notificationPlugin); - Key * errorKey = keyNew (0); - elektraPluginClose (notificationPlugin, errorKey); - keyDel (errorKey); + // remove notification plugin again + contract = ksNew (1, keyNew ("system/elektra/ensure/plugins/global/internalnotification", KEY_VALUE, "unmounted", KEY_END), + KS_END); + if (kdbEnsure (kdb, contract, parent) != 0) + { + ELEKTRA_LOG_WARNING ("kdbEnsure failed"); + } + keyDel (parent); return 0; } ElektraNotificationCallback notificationCallback = (ElektraNotificationCallback) func; + keyDel (parent); + // Open notification for plugins pluginsOpenNotification (kdb, notificationCallback, context); - kdb->notificationPlugin = notificationPlugin; - kdb->notificationCallbackContext = context; - return 1; } @@ -773,37 +157,32 @@ int elektraNotificationClose (KDB * kdb) ELEKTRA_LOG_WARNING ("kdb was not set"); return 0; } + + Plugin * notificationPlugin = elektraPluginFindGlobal (kdb, "internalnotification"); // Make sure open was called - if (!kdb->notificationPlugin) + if (notificationPlugin == NULL) { ELEKTRA_LOG_WARNING ("elektraNotificationOpen not called before elektraPluginClose"); return 0; } - Plugin * notificationPlugin = kdb->notificationPlugin; + Key * contextKey = ksLookupByName (notificationPlugin->config, "user/context", 0); + ElektraNotificationCallbackContext * context = *(ElektraNotificationCallbackContext **) keyValue (contextKey); + elektraFree (context); // Unmount the plugin - int result = unmountGlobalPlugin (kdb, notificationPlugin); - if (!result) - { - return 0; - } - - // Unload the notification plugin - result = unloadPlugin (notificationPlugin); - if (!result) + Key * parent = keyNew ("", KEY_END); + KeySet * contract = + ksNew (1, keyNew ("system/elektra/ensure/plugins/global/internalnotification", KEY_VALUE, "unmounted", KEY_END), KS_END); + if (kdbEnsure (kdb, contract, parent) != 0) { - return 0; + ELEKTRA_LOG_WARNING ("kdbEnsure failed"); } + keyDel (parent); // Close notification for plugins pluginsCloseNotification (kdb); - elektraFree (kdb->notificationCallbackContext); - - kdb->notificationPlugin = NULL; - kdb->notificationCallbackContext = NULL; - return 1; } @@ -818,9 +197,10 @@ static Plugin * getNotificationPlugin (KDB * kdb) { ELEKTRA_NOT_NULL (kdb); - if (kdb->notificationPlugin) + Plugin * notificationPlugin = elektraPluginFindGlobal (kdb, "internalnotification"); + if (notificationPlugin) { - return kdb->notificationPlugin; + return notificationPlugin; } else { diff --git a/src/libs/proposal/proposal.c b/src/libs/proposal/proposal.c index 8af02a50031..a061fd991ba 100644 --- a/src/libs/proposal/proposal.c +++ b/src/libs/proposal/proposal.c @@ -7,8 +7,11 @@ * */ +#include #include +#include +#include #include /** @@ -269,18 +272,38 @@ Key * keyAsCascading (const Key * key) // keyRel2 helper, returns how many levels check is below key, or 0 if check isn't below int keyGetLevelsBelow (const Key * key, const Key * check) { - if (!keyIsBelow (key, check)) return 0; - if (keyGetNamespace (key) != keyGetNamespace (check)) return 0; - Key * toCheck = keyDup (check); - int levels = 0; - while (strcmp (keyName (key), keyName (toCheck))) + const char * keyUName = keyUnescapedName (key); + size_t keyUSize = keyGetUnescapedNameSize (key); + const char * keyUNameEnd = keyUName + keyUSize; + + const char * checkUName = keyUnescapedName (check); + size_t checkUSize = keyGetUnescapedNameSize (check); + const char * checkUNameEnd = checkUName + checkUSize; + + while (strcmp (keyUName, checkUName) == 0) { - keySetBaseName (toCheck, 0); - if (keyName (toCheck)[0] == '\0') keySetName (toCheck, "/"); - ++levels; + keyUName = strchr (keyUName, '\0') + 1; + checkUName = strchr (checkUName, '\0') + 1; + + if (keyUName >= keyUNameEnd) + { + int levels = 0; + while (checkUName < checkUNameEnd) + { + checkUName = strchr (checkUName, '\0') + 1; + ++levels; + } + + return levels; + } + + if (checkUName >= checkUNameEnd) + { + break; + } } - keyDel (toCheck); - return levels; + + return 0; } /** @@ -368,7 +391,6 @@ int keyRel2 (const Key * key, const Key * check, KeyRelType which) return retVal; } - /** * @} */ diff --git a/src/libs/tools/src/plugin.cpp b/src/libs/tools/src/plugin.cpp index b6d8747a242..5b706c25e8a 100644 --- a/src/libs/tools/src/plugin.cpp +++ b/src/libs/tools/src/plugin.cpp @@ -209,10 +209,13 @@ void Plugin::check (vector & warnings) pp.push_back ("getresolver"); pp.push_back ("pregetstorage"); pp.push_back ("getstorage"); + pp.push_back ("procgetstorage"); pp.push_back ("postgetstorage"); pp.push_back ("setresolver"); + pp.push_back ("postgetcleanup"); pp.push_back ("presetstorage"); pp.push_back ("setstorage"); + pp.push_back ("presetcleanup"); pp.push_back ("precommit"); pp.push_back ("commit"); pp.push_back ("postcommit"); diff --git a/src/plugins/README.md b/src/plugins/README.md index 0de4cf32a65..3cea5938f04 100644 --- a/src/plugins/README.md +++ b/src/plugins/README.md @@ -267,3 +267,4 @@ binding during run-time. - [process](process/) proxy plugin that executes other plugins in a separate process - [profile](profile/) links profile keys - [simplespeclang](simplespeclang/) simple configuration specification language +- [gopts](gopts/) global plugin to automatically call `elektraGetOpts` diff --git a/src/plugins/doc/README.md b/src/plugins/doc/README.md index cbf77fd7a25..d28ded49859 100644 --- a/src/plugins/doc/README.md +++ b/src/plugins/doc/README.md @@ -4,7 +4,7 @@ - infos/provides = - infos/needs = - infos/recommends = -- infos/placements = prerollback rollback postrollback getresolver pregetstorage getstorage postgetstorage setresolver presetstorage setstorage precommit commit postcommit +- infos/placements = prerollback rollback postrollback getresolver pregetstorage getstorage procgetstorage postgetstorage setresolver presetstorage setstorage precommit commit postcommit - infos/status = nodep libc experimental discouraged -1000000 - infos/metadata = - infos/description = documentation plugin without functionality diff --git a/src/plugins/gopts/CMakeLists.txt b/src/plugins/gopts/CMakeLists.txt new file mode 100644 index 00000000000..25a84217274 --- /dev/null +++ b/src/plugins/gopts/CMakeLists.txt @@ -0,0 +1,47 @@ +include (LibAddMacros) + +if (WIN32) + set (ELEKTRA_GOPTS_WIN32 ON) + set (gopts_source gopts_win32.h) +elseif (MacOSX) + set (ELEKTRA_GOPTS_OSX ON) + set (gopts_source gopts_osx.h) +else () + try_compile (SYSCTL_TEST ${CMAKE_CURRENT_BINARY_DIR} "${CMAKE_SOURCE_DIR}/src/plugins/gopts/sysctl_test.c") + + if (SYSCTL_TEST) + set (ELEKTRA_GOPTS_SYSCTL ON) + set (gopts_source gopts_sysctl.h) + elseif (EXISTS "/proc/mounts") + set (ELEKTRA_GOPTS_PROCFS ON) + set (gopts_source gopts_procfs.h) + endif () +endif () + +if (gopts_source) + configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/gopts_impl.c.in" "${CMAKE_CURRENT_BINARY_DIR}/gopts_impl.c" @ONLY) + + add_plugin (gopts + SOURCES gopts.h + gopts.c + ${gopts_source} + LINK_ELEKTRA elektra-opts + TEST_README) + + if (ADDTESTING_PHASE) + set (TEST_SOURCES $ ${ARG_OBJECT_SOURCES}) + + add_executable (elektra-gopts-testapp "${CMAKE_CURRENT_SOURCE_DIR}/testapp.c" ${TEST_SOURCES}) + target_link_elektra (elektra-gopts-testapp elektra-core elektra-invoke elektra-opts) + + set (TESTAPP_PATH "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/elektra-gopts-testapp") + configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.c.in" "${CMAKE_CURRENT_BINARY_DIR}/config.c" @ONLY) + + add_plugintest (gopts INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR} TIMEOUT 120) + + if (BUILD_SHARED) + add_dependencies (elektra-gopts-testapp elektra-gopts) + endif (BUILD_SHARED) + add_dependencies (testmod_gopts elektra-gopts-testapp) + endif () +endif () diff --git a/src/plugins/gopts/README.md b/src/plugins/gopts/README.md new file mode 100644 index 00000000000..6ee6d359e43 --- /dev/null +++ b/src/plugins/gopts/README.md @@ -0,0 +1,34 @@ +- infos = Information about the gopts plugin is in keys below +- infos/author = Klemens Böswirth +- infos/licence = BSD +- infos/needs = +- infos/provides = +- infos/recommends = +- infos/placements = procgetstorage +- infos/status = recommended productive maintained nodep libc experimental +- infos/metadata = +- infos/description = Parses command-line options using elektra-opts + +## Introduction + +This plugin is very simple in what it does. It uses system specific methods of accessing `argc` and `argv` outside of `main` and then calls +`elektraGetOpts` to do options processing. + +## Usage + +The preferred way of using this plugin is via `kdbEnsure`: + +```c +KDB * kdb = kdbOpen (parentKey); + +KeySet * contract = ksNew (1, keyNew ("system/elektra/ensure/plugins/global/gopts", KEY_VALUE, "mounted", KEY_END), KS_END); +int rc = kdbEnsure (kdb, contract, parentKey); +if (rc != 0) +{ + // error handling +} + +// gopts now mounted +KeySet * ks = ksNew (0, KS_END); +kdbGet (kdb, ks, parentKey); +``` diff --git a/src/plugins/gopts/config.c.in b/src/plugins/gopts/config.c.in new file mode 100644 index 00000000000..ebb6bc1c6ab --- /dev/null +++ b/src/plugins/gopts/config.c.in @@ -0,0 +1,10 @@ +/** + * @file + * + * @brief Tests for gopts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#define TESTAPP_PATH "@TESTAPP_PATH@" diff --git a/src/plugins/gopts/gopts.c b/src/plugins/gopts/gopts.c new file mode 100644 index 00000000000..ddf75cc984a --- /dev/null +++ b/src/plugins/gopts/gopts.c @@ -0,0 +1,90 @@ +/** + * @file + * + * @brief Source for opts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#include "gopts.h" + +#include +#include +#include +#include +#include + +static int loadArgs (char *** argvp); +static char ** loadEnvp (void); +static void cleanupArgs (int argc, char ** argv); +static void cleanupEnvp (char ** envp); + +#include "gopts_impl.c" + +#if defined(ELEKTRA_GOPTS_PROCFS) +#include "gopts_procfs.h" +#elif defined(ELEKTRA_GOPTS_OSX) +#include "gopts_osx.h" +#elif defined(ELEKTRA_GOPTS_WIN32) +#include "gopts_win32.h" +#elif defined(ELEKTRA_GOPTS_SYSCTL) +#include "gopts_sysctl.h" +#else +#error "No implementation available" +#endif + + +int elektraGOptsGet (Plugin * handle ELEKTRA_UNUSED, KeySet * returned, Key * parentKey) +{ + if (!elektraStrCmp (keyName (parentKey), "system/elektra/modules/gopts")) + { + KeySet * contract = + ksNew (30, keyNew ("system/elektra/modules/gopts", KEY_VALUE, "gopts plugin waits for your orders", KEY_END), + keyNew ("system/elektra/modules/gopts/exports", KEY_END), + keyNew ("system/elektra/modules/gopts/exports/get", KEY_FUNC, elektraGOptsGet, KEY_END), +#include ELEKTRA_README + keyNew ("system/elektra/modules/gopts/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END); + ksAppend (returned, contract); + ksDel (contract); + + return ELEKTRA_PLUGIN_STATUS_SUCCESS; + } + + char ** argv = NULL; + int argc = loadArgs (&argv); + char ** envp = loadEnvp (); + + if (argv == NULL || envp == NULL) + { + return ELEKTRA_PLUGIN_STATUS_ERROR; + } + + int ret = elektraGetOpts (returned, argc, (const char **) argv, (const char **) envp, parentKey); + + cleanupArgs (argc, argv); + cleanupEnvp (envp); + + if (ret == -1) + { + return ELEKTRA_PLUGIN_STATUS_ERROR; + } + else if (ret == 1) + { + Key * helpKey = keyNew ("proc/elektra/gopts/help", KEY_VALUE, "1", KEY_END); + keyCopyAllMeta (helpKey, parentKey); + ksAppendKey (returned, helpKey); + return ELEKTRA_PLUGIN_STATUS_SUCCESS; + } + + return ELEKTRA_PLUGIN_STATUS_SUCCESS; +} + +Plugin * ELEKTRA_PLUGIN_EXPORT +{ + // clang-format off + return elektraPluginExport ("gopts", + ELEKTRA_PLUGIN_GET, &elektraGOptsGet, + ELEKTRA_PLUGIN_END); + // clang-format on +} diff --git a/src/plugins/gopts/gopts.h b/src/plugins/gopts/gopts.h new file mode 100644 index 00000000000..d881f083746 --- /dev/null +++ b/src/plugins/gopts/gopts.h @@ -0,0 +1,19 @@ +/** + * @file + * + * @brief Header for opts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#ifndef ELEKTRA_PLUGIN_OPTS_H +#define ELEKTRA_PLUGIN_OPTS_H + +#include + +int elektraGOptsGet (Plugin * handle, KeySet * ks, Key * parentKey); + +Plugin * ELEKTRA_PLUGIN_EXPORT; + +#endif diff --git a/src/plugins/gopts/gopts_impl.c.in b/src/plugins/gopts/gopts_impl.c.in new file mode 100644 index 00000000000..2e957d39231 --- /dev/null +++ b/src/plugins/gopts/gopts_impl.c.in @@ -0,0 +1,28 @@ +/** + * @file + * + * @brief Tests for gopts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +/* define if the gopts plugin shall use the procfs implementation */ +#ifndef ELEKTRA_GOPTS_PROCFS +#cmakedefine ELEKTRA_GOPTS_PROCFS +#endif + +/* define if the gopts plugin shall use the win32 api implementation */ +#ifndef ELEKTRA_GOPTS_WIN32 +#cmakedefine ELEKTRA_GOPTS_WIN32 +#endif + +/* define if the gopts plugin shall use the osx api implementation */ +#ifndef ELEKTRA_GOPTS_OSX +#cmakedefine ELEKTRA_GOPTS_OSX +#endif + +/* define if the gopts plugin shall use the sysctl implementation */ +#ifndef ELEKTRA_GOPTS_SYSCTL +#cmakedefine ELEKTRA_GOPTS_SYSCTL +#endif diff --git a/src/plugins/gopts/gopts_osx.h b/src/plugins/gopts/gopts_osx.h new file mode 100644 index 00000000000..b39c45ac9b8 --- /dev/null +++ b/src/plugins/gopts/gopts_osx.h @@ -0,0 +1,53 @@ +/** + * @file + * + * @brief Source for opts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#ifndef ELEKTRA_GOPTS_SYSCTL_H +#define ELEKTRA_GOPTS_SYSCTL_H + +#include +#include + +#include + +extern char ** environ; + +static int loadArgs (char *** argvp) +{ + const int * argcp = _NSGetArgc (); + if (!argcp) + { + return 0; + } + + char *** argvp1 = _NSGetArgv (); + if (!argvp1) + { + return 0; + } + *argvp = *argvp1; + + return *argcp; +} + +static char ** loadEnvp (void) +{ + return environ; +} + +static void cleanupArgs (int argc, char ** argv) +{ + elektraFree (argv[0]); + elektraFree (argv); +} + +static void cleanupEnvp (char ** envp ELEKTRA_UNUSED) +{ +} + +#endif // ELEKTRA_GOPTS_SYSCTL_H diff --git a/src/plugins/gopts/gopts_procfs.h b/src/plugins/gopts/gopts_procfs.h new file mode 100644 index 00000000000..c2832539fe7 --- /dev/null +++ b/src/plugins/gopts/gopts_procfs.h @@ -0,0 +1,85 @@ +/** + * @file + * + * @brief Source for opts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#ifndef ELEKTRA_GOPTS_SYSCTL_H +#define ELEKTRA_GOPTS_SYSCTL_H + +#include +#include +#include +#include + +#include + +extern char ** environ; + +static int loadArgs (char *** argvp) +{ + FILE * cmdline; + if (access ("/proc/self/cmdline", F_OK) != -1) + { + cmdline = fopen ("/proc/self/cmdline", "rb"); + } + else if (access ("/proc/curproc/cmdline", F_OK) != -1) + { + cmdline = fopen ("/proc/curproc/cmdline", "rb"); + } + else + { + return 0; + } + + char * arg = NULL; + size_t size = 0; + int argc = 0; + while (getdelim (&arg, &size, 0, cmdline) != -1) + { + ++argc; + } + free (arg); + rewind (cmdline); + + char ** argv = elektraMalloc ((argc + 1) * sizeof (char *)); + + arg = NULL; + int index = 0; + while (getdelim (&arg, &size, 0, cmdline) != -1) + { + argv[index] = elektraStrDup (arg); + ++index; + } + free (arg); + fclose (cmdline); + + argv[argc] = NULL; + *argvp = argv; + + return argc; +} + +static char ** loadEnvp (void) +{ + return environ; +} + +static void cleanupArgs (int argc, char ** argv) +{ + for (int i = 0; i < argc; ++i) + { + elektraFree (argv[i]); + } + + elektraFree (argv); +} + +static void cleanupEnvp (char ** envp ELEKTRA_UNUSED) +{ +} + +#endif // ELEKTRA_GOPTS_SYSCTL_H diff --git a/src/plugins/gopts/gopts_sysctl.h b/src/plugins/gopts/gopts_sysctl.h new file mode 100644 index 00000000000..f3b67f18030 --- /dev/null +++ b/src/plugins/gopts/gopts_sysctl.h @@ -0,0 +1,77 @@ +/** + * @file + * + * @brief Source for opts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#ifndef ELEKTRA_GOPTS_SYSCTL_H +#define ELEKTRA_GOPTS_SYSCTL_H + +#include +#include +#include + +#include + +extern char ** environ; + +static int loadArgs (char *** argvp) +{ + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_ARGS; + mib[3] = -1; + size_t size; + if (sysctl (mib, 4, NULL, &size, NULL, 0) == -1) + { + return 0; + } + char * buf = elektraMalloc (size); + if (sysctl (mib, 4, buf, &size, NULL, 0) == -1) + { + elektraFree (buf); + return 0; + } + + size_t pos = 0; + int argc = 0; + while (pos < size) + { + pos += strlen (buf + pos) + 1; + ++argc; + } + + char ** argv = elektraMalloc (argc * sizeof (char *)); + argv[0] = buf; + pos = strlen (buf) + 1; + for (int i = 1; i < argc; ++i) + { + argv[i] = buf + pos; + pos += strlen (buf + pos) + 1; + } + + *argvp = argv; + + return argc; +} + +static char ** loadEnvp (void) +{ + return environ; +} + +static void cleanupArgs (int argc, char ** argv) +{ + elektraFree (argv[0]); + elektraFree (argv); +} + +static void cleanupEnvp (char ** envp ELEKTRA_UNUSED) +{ +} + +#endif // ELEKTRA_GOPTS_SYSCTL_H diff --git a/src/plugins/gopts/gopts_win32.h b/src/plugins/gopts/gopts_win32.h new file mode 100644 index 00000000000..39883032dfe --- /dev/null +++ b/src/plugins/gopts/gopts_win32.h @@ -0,0 +1,100 @@ +/** + * @file + * + * @brief Source for opts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#ifndef ELEKTRA_GOPTS_SYSCTL_H +#define ELEKTRA_GOPTS_SYSCTL_H + +#include +#include + +#include + +static int loadArgs (char *** argvp) +{ + int argc; + + LPWSTR * args = CommandLineToArgvW (GetCommandLineW (), &argc); + if (args == NULL) + { + return 0; + } + + char ** argv = elektraMalloc ((count + 1) * sizeof (char *)); + + for (int i = 0; i < argc; ++i) + { + // TODO: error handling? + + int size = WideCharToMultiByte (CP_UTF8, 0, args[i], -1, NULL, 0, NULL, NULL); + argv[i] = elektraMalloc (size * sizeof (char)); + + WideCharToMultiByte (CP_UTF8, 0, args[i], -1, argv[i], size, NULL, NULL); + } + + LocalFree (args); + + *argvp = argv; + + return nArgs; +} + +static char ** loadEnvp (void) +{ + LPTCH env = GetEnvironmentStrings (); + if (env == NULL) + { + return NULL; + } + + char * e = env; + size_t size = 0; + int count = 0; + while (e[0] != '\0' && (e = strchr (e, '\0')) != NULL) + { + ++count; + ++e; + } + + char ** envp = elektraMalloc ((count + 1) * sizeof (char *)); + + e = env; + envp[0] = env; + int index = 0; + char * next = NULL; + while (e[0] != '\0' && (next = strchr (e, '\0')) != NULL) + { + envp[index] = e; + ++index; + e = next + 1; + } + envp[index] = e; + envp[index + 1] = NULL; + + return envp; +} + +static void cleanupArgs (int argc, char ** argv) +{ + for (int i = 0; i < argc; ++i) + { + elektraFree (argv[i]); + } + + elektraFree (argv); +} + +static void cleanupEnvp (char ** envp) +{ + if (envp != NULL) + { + FreeEnvironmentStrings (envp[0]); + } +} + +#endif // ELEKTRA_GOPTS_SYSCTL_H diff --git a/src/plugins/gopts/sysctl_test.c b/src/plugins/gopts/sysctl_test.c new file mode 100644 index 00000000000..17435cccf54 --- /dev/null +++ b/src/plugins/gopts/sysctl_test.c @@ -0,0 +1,35 @@ +/** + * @file + * + * @brief Source for opts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#include "gopts_sysctl.h" + +#include + +int main (int argc, char ** argv) +{ + char ** argv2; + int argc2 = loadArgs (&argv2); + + if (argc2 != argc) + { + cleanupArgs (argc2, argv2); + return 1; + } + + for (int i = 0; i < argc; ++i) + { + if (argv2[i] == NULL || strcmp (argv[i], argv2[i]) != 0) + { + cleanupArgs (argc2, argv2); + return 1; + } + } + + return 0; +} diff --git a/src/plugins/gopts/testapp.c b/src/plugins/gopts/testapp.c new file mode 100644 index 00000000000..6d534ce2475 --- /dev/null +++ b/src/plugins/gopts/testapp.c @@ -0,0 +1,108 @@ +/** + * @file + * + * @brief Tests for specload plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#include +#include + +#include +#include +#include + +#include + +#include "testdata.h" + +extern char ** environ; + +static KeySet * getSpec (const char * name, Key ** parentKey) +{ + *parentKey = keyNew ("spec/tests/gopts", KEY_END); + + if (strcmp (name, TEST_EMPTY) == 0) + { + return TEST_KS_EMPTY; + } + + if (strcmp (name, TEST_SINGLEOPT) == 0) + { + return TEST_KS_SINGLEOPT; + } + + if (strcmp (name, TEST_TWOOPT) == 0) + { + return TEST_KS_TWOOPT; + } + + if (strcmp (name, TEST_SINGLEENV) == 0) + { + return TEST_KS_SINGLEENV; + } + + if (strcmp (name, TEST_TWOENV) == 0) + { + return TEST_KS_TWOENV; + } + + if (strcmp (name, TEST_MIXED) == 0) + { + return TEST_KS_MIXED; + } + + yield_error ("unknown spec name"); + printf ("specname: %s\n", name); + exit (EXIT_FAILURE); +} + +int main (int argc, const char ** argv) +{ + const char * specname = argv[1]; + const char * appname = argv[0]; + argv[1] = appname; + + Key * parentKey; + KeySet * ks = getSpec (specname, &parentKey); + + bool libFailed = elektraGetOpts (ks, argc - 1, &argv[1], (const char **) environ, parentKey) != 0; + + KeySet * conf = ksNew (0, KS_END); + + PLUGIN_OPEN ("gopts"); + + Key * parentKey2; + KeySet * ks2 = getSpec (specname, &parentKey2); + + bool pluginFailed = plugin->kdbGet (plugin, ks2, parentKey2) == ELEKTRA_PLUGIN_STATUS_ERROR; + + if (pluginFailed != libFailed) + { + PLUGIN_CLOSE (); + ksDel (ks); + keyDel (parentKey); + ksDel (ks2); + keyDel (parentKey2); + char buf[256]; + strcpy (buf, "elektraGetOpts have different results plugin->get: "); + strncat (buf, argv[0], 128); + yield_error (buf); + + return nbError; + } + + compare_key (parentKey, parentKey2); + compare_keyset (ks, ks2); + + PLUGIN_CLOSE (); + + ksDel (ks); + keyDel (parentKey); + ksDel (ks2); + keyDel (parentKey2); + + return nbError; +} diff --git a/src/plugins/gopts/testdata.h b/src/plugins/gopts/testdata.h new file mode 100644 index 00000000000..2a50b92664c --- /dev/null +++ b/src/plugins/gopts/testdata.h @@ -0,0 +1,41 @@ +/** + * @file + * + * @brief Tests for specload plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#ifndef ELEKTRA_GOPTS_TESTDATA_H +#define ELEKTRA_GOPTS_TESTDATA_H + +#define TEST_EMPTY "empty" +#define TEST_KS_EMPTY ksNew (1, keyNew ("spec/tests/gopts/key", KEY_META, "default", "5", KEY_END), KS_END) + +#define TEST_SINGLEOPT "singleopt" +#define TEST_KS_SINGLEOPT ksNew (1, keyNew ("spec/tests/gopts/key", KEY_META, "opt", "c", KEY_META, "opt/long", "longopt", KEY_END), KS_END) + +#define TEST_TWOOPT "twoopt" +#define TEST_KS_TWOOPT \ + ksNew (2, keyNew ("spec/tests/gopts/key", KEY_META, "opt", "c", KEY_META, "opt/long", "longopt", KEY_END), \ + keyNew ("spec/tests/gopts/key2", KEY_META, "opt", "b", KEY_META, "opt/long", "longopt2", KEY_END), KS_END) + +#define TEST_SINGLEENV "singleenv" +#define TEST_KS_SINGLEENV ksNew (1, keyNew ("spec/tests/gopts/key", KEY_META, "env", "ENV_VAR", KEY_END), KS_END) + +#define TEST_TWOENV "twoenv" +#define TEST_KS_TWOENV \ + ksNew (2, keyNew ("spec/tests/gopts/key", KEY_META, "env", "ENV_VAR", KEY_END), \ + keyNew ("spec/tests/gopts/key2", KEY_META, "env", "OTHER_ENV_VAR", KEY_END), KS_END) + +#define TEST_MIXED "mixed" +#define TEST_KS_MIXED \ + ksNew (2, \ + keyNew ("spec/tests/gopts/key", KEY_META, "opt", "c", KEY_META, "opt/long", "longopt", KEY_META, "env", "ENV_VAR", \ + KEY_END), \ + keyNew ("spec/tests/gopts/key2", KEY_META, "opt", "b", KEY_META, "opt/long", "longopt2", KEY_META, "env", "OTHER_ENV_VAR", \ + KEY_END), \ + KS_END) + +#endif // ELEKTRA_GOPTS_TESTDATA_H diff --git a/src/plugins/gopts/testmod_gopts.c b/src/plugins/gopts/testmod_gopts.c new file mode 100644 index 00000000000..1af97751c8f --- /dev/null +++ b/src/plugins/gopts/testmod_gopts.c @@ -0,0 +1,150 @@ +/** + * @file + * + * @brief Tests for opts plugin + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#include +#include +#include + +#include + +#include + +#include + +#include +#include + +#include "testdata.h" + +// version 6 and 7 of clang-format don't agree whether it is supposed to be *[] or * [] so disable it here +// TODO: re-enable clang-format once version 7 is used on build server +// clang-format off +#define ARGS(NAME, ...) ((const char *[]){ TESTAPP_PATH, NAME, __VA_ARGS__, NULL }) +#define ENVP(LD_LIB_PATH, ...) ((const char *[]){ LD_LIB_PATH, __VA_ARGS__, NULL }) + +#define NO_ARGS(NAME) ((const char *[]){ TESTAPP_PATH, NAME, NULL }) +#define NO_ENVP(LD_LIB_PATH) ((const char *[]){ LD_LIB_PATH, NULL }) +// clang-format on + +static void run_test (const char ** argv, const char ** envp) +{ + printf ("test %s\n", argv[1]); + pid_t pid; + + + pid = fork (); + + if (pid == -1) + { + yield_error ("Could not execute testapp"); + return; + } + + if (pid == 0) + { + /* child */ + execve (TESTAPP_PATH, (char * const *) argv, (char * const *) envp); + + exit (EXIT_FAILURE); + } + + /* parent */ + int status; + do + { + pid_t w = waitpid (pid, &status, 0); + if (w == -1) + { + perror ("waitpid"); + yield_error ("waitpid"); + } + } while (!WIFEXITED (status) && !WIFSIGNALED (status)); + + if (WIFSIGNALED (status)) + { + printf ("child process was killed by signal: %s", strsignal (WTERMSIG (status))); + exit (1); + } + + if (WIFEXITED (status) && WEXITSTATUS (status) != 0) + { + nbError += WEXITSTATUS (status); + yield_error ("child process test failed"); + } +} + + +int main (int argc, char ** argv) +{ + printf ("GOPTS TESTS\n"); + printf ("==================\n\n"); + + init (argc, argv); + + char * ldLibPath = elektraFormat ("LD_LIBRARY_PATH=%s", getenv ("LD_LIBRARY_PATH")); + + run_test (NO_ARGS (TEST_EMPTY), NO_ENVP (ldLibPath)); + + run_test (NO_ARGS (TEST_SINGLEOPT), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_SINGLEOPT, "-capple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_SINGLEOPT, "-capple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_SINGLEOPT, "-c", "apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_SINGLEOPT, "-c", "apple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_SINGLEOPT, "--longopt=apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_SINGLEOPT, "--longopt=apple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_SINGLEOPT, "--longopt", "apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_SINGLEOPT, "--longopt", "apple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_SINGLEOPT, "noopt"), NO_ENVP (ldLibPath)); + + run_test (NO_ARGS (TEST_SINGLEENV), NO_ENVP (ldLibPath)); + run_test (NO_ARGS (TEST_SINGLEENV), ENVP (ldLibPath, "ENV_VAR=apple")); + run_test (NO_ARGS (TEST_SINGLEENV), ENVP (ldLibPath, "OTHER_ENV_VAR=apple")); + + run_test (NO_ARGS (TEST_TWOOPT), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-capple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-capple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-c", "apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-c", "apple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "--longopt=apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "--longopt=apple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "--longopt", "apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "--longopt", "apple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "noopt"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-bapple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-bapple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-b", "apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-b", "apple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "--longopt2=apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "--longopt2=apple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "--longopt2", "apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "--longopt2", "apple", "morearg"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-bapple", "-capple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "-bapple", "morearg", "-c", "apple"), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_TWOOPT, "--longopt2", "apple", "--longopt", "apple"), NO_ENVP (ldLibPath)); + + run_test (NO_ARGS (TEST_TWOENV), NO_ENVP (ldLibPath)); + run_test (NO_ARGS (TEST_TWOENV), ENVP (ldLibPath, "ENV_VAR=apple")); + run_test (NO_ARGS (TEST_TWOENV), ENVP (ldLibPath, "OTHER_ENV_VAR=apple")); + run_test (NO_ARGS (TEST_TWOENV), ENVP (ldLibPath, "ENV_VAR=apple", "OTHER_ENV_VAR=apple")); + run_test (NO_ARGS (TEST_TWOENV), ENVP (ldLibPath, "OTHER_OTHER_ENV_VAR=apple")); + run_test (NO_ARGS (TEST_TWOENV), ENVP (ldLibPath, "ENV_VAR=apple", "OTHER_ENV_VAR=apple", "OTHER_OTHER_ENV_VAR=apple")); + + run_test (NO_ARGS (TEST_MIXED), NO_ENVP (ldLibPath)); + run_test (ARGS (TEST_MIXED, "-capple"), ENVP (ldLibPath, "ENV_VAR=apple")); + run_test (ARGS (TEST_MIXED, "-c", "apple"), ENVP (ldLibPath, "OTHER_ENV_VAR=apple")); + run_test (ARGS (TEST_MIXED, "--longopt=apple"), + ENVP (ldLibPath, "ENV_VAR=apple", "OTHER_ENV_VAR=apple", "OTHER_OTHER_ENV_VAR=apple")); + run_test (ARGS (TEST_MIXED, "--longopt", "apple"), ENVP (ldLibPath, "OTHER_ENV_VAR=apple")); + + elektraFree (ldLibPath); + + print_result ("testmod_gopts"); + + return nbError; +} diff --git a/src/plugins/list/CMakeLists.txt b/src/plugins/list/CMakeLists.txt index c42338fda6d..acf1e4b9e39 100644 --- a/src/plugins/list/CMakeLists.txt +++ b/src/plugins/list/CMakeLists.txt @@ -5,4 +5,6 @@ add_plugin (list list.c LINK_ELEKTRA elektra-kdb elektra-invoke + elektra-ease + elektra-proposal ADD_TEST) diff --git a/src/plugins/list/README.md b/src/plugins/list/README.md index 0715f720e4a..2d2d637708f 100644 --- a/src/plugins/list/README.md +++ b/src/plugins/list/README.md @@ -3,7 +3,7 @@ - infos/licence = BSD - infos/needs = - infos/provides = -- infos/placements = presetstorage pregetstorage postgetstorage precommit postcommit prerollback postrollback +- infos/placements = pregetstorage procgetstorage postgetstorage postgetcleanup presetstorage presetcleanup precommit postcommit prerollback postrollback - infos/status = maintained unittest nodep libc configurable global - infos/description = delegates work to a list of plugins @@ -48,6 +48,23 @@ A list of set-placements for the plugin. Same for "get" and "error" Plugin specific config. +## Exported Functions + +The plugin exports a few useful functions: + +```c +int elektraListMountPlugin (Plugin * handle, const char * pluginName, KeySet * pluginConfig, Key * errorKey) +int elektraListUnmountPlugin (Plugin * handle, const char * pluginName, Key * errorKey) +Plugin * elektraListFindPlugin (Plugin * handle, const char * pluginName) +``` + +`elektraListMountPlugin` can be used to add a new plugin to the config. The placement will be queried from the plugin itself (from its +`infos/placements` metadata). If the plugin is added already nothing happens, otherwise `pluginConfig` is used to open the plugin. + +`elektraListUnmountPlugin` is the opposite, it is used to remove a plugin from the config. + +Finally, `elektraListFindPlugin` looks for a plugin in the config, and if found returns its handle. + ## Example ``` diff --git a/src/plugins/list/list.c b/src/plugins/list/list.c index 4aae957e9bc..36c6fec2330 100644 --- a/src/plugins/list/list.c +++ b/src/plugins/list/list.c @@ -14,6 +14,7 @@ #include "list.h" #include +#include #include #include #include @@ -27,11 +28,14 @@ typedef enum { preGetStorage = 0, + procGetStorage, postGetStorage, postGetCleanup, getEnd } GetPlacements; +static const char * getStrings[] = { "pregetstorage", "procgetstorage", "postgetstorage", "postgetcleanup" }; + typedef enum { preSetStorage = 0, @@ -41,6 +45,8 @@ typedef enum setEnd } SetPlacements; +static const char * setStrings[] = { "presetstorage", "presetcleanup", "precommit", "postcommit" }; + typedef enum { preRollback = 0, @@ -48,6 +54,8 @@ typedef enum errEnd } ErrPlacements; +static const char * errStrings[] = { "prerollback", "postrollback" }; + typedef enum { GET, @@ -64,12 +72,12 @@ typedef struct ErrPlacements errPlacements[2]; // prerollback and postrollback SetPlacements setPlacements[4]; // presetstorage, presetcleanup, precommit and postcommit - GetPlacements getPlacements[3]; // pregetstorage, postgetstorage, postgetclenaup + GetPlacements getPlacements[4]; // pregetstorage, procgetstorage, postgetstorage, postgetclenaup // each keyset contains the list of plugin names for a given placement KeySet * setKS[4]; KeySet * errKS[2]; - KeySet * getKS[3]; + KeySet * getKS[4]; KeySet * plugins; KeySet * modules; @@ -112,7 +120,6 @@ static int listParseConfiguration (Placements * placements, KeySet * config) if (sub) { const char * setString = keyString (sub); - const char * setStrings[] = { "presetstorage", "presetcleanup", "precommit", "postcommit" }; SetPlacements setPlacement = preSetStorage; while (setPlacement != setEnd) { @@ -129,7 +136,6 @@ static int listParseConfiguration (Placements * placements, KeySet * config) if (sub) { const char * getString = keyString (sub); - const char * getStrings[] = { "pregetstorage", "postgetstorage", "postgetcleanup" }; GetPlacements getPlacement = preGetStorage; while (getPlacement != getEnd) { @@ -146,7 +152,6 @@ static int listParseConfiguration (Placements * placements, KeySet * config) if (sub) { const char * errString = keyString (sub); - const char * errStrings[] = { "prerollback", "postrollback" }; ErrPlacements errPlacement = preRollback; while (errPlacement != errEnd) { @@ -214,7 +219,6 @@ int elektraListOpen (Plugin * handle, Key * errorKey ELEKTRA_UNUSED) if (key) { const char * setString = keyString (key); - const char * setStrings[] = { "presetstorage", "presetcleanup", "precommit", "postcommit" }; SetPlacements setPlacement = preSetStorage; while (setPlacement != setEnd) { @@ -229,7 +233,6 @@ int elektraListOpen (Plugin * handle, Key * errorKey ELEKTRA_UNUSED) if (key) { const char * getString = keyString (key); - const char * getStrings[] = { "pregetstorage", "postgetstorage", "postgetcleanup" }; GetPlacements getPlacement = preGetStorage; while (getPlacement != getEnd) { @@ -244,7 +247,6 @@ int elektraListOpen (Plugin * handle, Key * errorKey ELEKTRA_UNUSED) if (key) { const char * errString = keyString (key); - const char * errStrings[] = { "prerollback", "postrollback" }; ErrPlacements errPlacement = preRollback; while (errPlacement != errEnd) { @@ -393,6 +395,9 @@ int elektraListGet (Plugin * handle, KeySet * returned, Key * parentKey) keyNew ("system/elektra/modules/list/exports/addPlugin", KEY_FUNC, elektraListAddPlugin, KEY_END), keyNew ("system/elektra/modules/list/exports/editPlugin", KEY_FUNC, elektraListEditPlugin, KEY_END), keyNew ("system/elektra/modules/list/exports/deferredCall", KEY_FUNC, elektraListDeferredCall, KEY_END), + keyNew ("system/elektra/modules/list/exports/mountplugin", KEY_FUNC, elektraListMountPlugin, KEY_END), + keyNew ("system/elektra/modules/list/exports/unmountplugin", KEY_FUNC, elektraListUnmountPlugin, KEY_END), + keyNew ("system/elektra/modules/list/exports/findplugin", KEY_FUNC, elektraListFindPlugin, KEY_END), #include ELEKTRA_README keyNew ("system/elektra/modules/list/infos/version", KEY_VALUE, PLUGINVERSION, KEY_END), KS_END); ksAppend (returned, contract); @@ -483,6 +488,443 @@ int elektraListAddPlugin (Plugin * handle, KeySet * pluginConfig) return rc; } +static char * getPluginPlacementList (Plugin * plugin) +{ + ELEKTRA_NOT_NULL (plugin); + + // Get placements from plugin + Key * pluginInfo = keyNew ("system/elektra/modules/", KEY_END); + keyAddBaseName (pluginInfo, plugin->name); + KeySet * ksResult = ksNew (0, KS_END); + plugin->kdbGet (plugin, ksResult, pluginInfo); + + Key * placementsKey = keyDup (pluginInfo); + keyAddBaseName (placementsKey, "infos"); + keyAddBaseName (placementsKey, "placements"); + Key * placements = ksLookup (ksResult, placementsKey, 0); + if (placements == NULL) + { + ELEKTRA_LOG_WARNING ("could not read placements from plugin"); + return 0; + } + char * placementList = elektraStrDup (keyString (placements)); + + keyDel (pluginInfo); + keyDel (placementsKey); + ksDel (ksResult); + + return placementList; +} + +static char * extractGetPlacements (const char * placementList) +{ + char * result = elektraMalloc (strlen (placementList) + 1); + result[0] = '\0'; + char * resultPos = result; + const char * last = placementList; + const char * placement = strchr (last, ' '); + while (placement != NULL) + { + size_t len = placement - last; + if (strncasecmp (last, GlobalpluginPositionsStr[GETRESOLVER], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[PREGETSTORAGE], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[GETSTORAGE], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[PROCGETSTORAGE], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[POSTGETSTORAGE], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[POSTGETCLEANUP], len) == 0) + { + strncpy (resultPos, last, len); + resultPos[len] = ' '; + resultPos += len + 1; + } + + last = placement + 1; + placement = strchr (last, ' '); + } + + if (strcasecmp (last, GlobalpluginPositionsStr[GETRESOLVER]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[PREGETSTORAGE]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[GETSTORAGE]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[PROCGETSTORAGE]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[POSTGETSTORAGE]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[POSTGETCLEANUP]) == 0) + { + strcpy (resultPos, last); + resultPos += strlen (last); + } + + *resultPos = '\0'; + return result; +} + +static char * extractSetPlacements (const char * placementList) +{ + char * result = elektraMalloc (strlen (placementList) + 1); + result[0] = '\0'; + char * resultPos = result; + const char * last = placementList; + const char * placement = strchr (last, ' '); + while (placement != NULL) + { + size_t len = placement - last; + if (strncasecmp (last, GlobalpluginPositionsStr[SETRESOLVER], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[PRESETSTORAGE], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[SETSTORAGE], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[PRESETCLEANUP], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[PRECOMMIT], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[COMMIT], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[POSTCOMMIT], len) == 0) + { + strncpy (resultPos, last, len); + resultPos[len] = ' '; + resultPos += len + 1; + } + + last = placement + 1; + placement = strchr (last, ' '); + } + + if (strcasecmp (last, GlobalpluginPositionsStr[SETRESOLVER]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[PRESETSTORAGE]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[SETSTORAGE]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[PRESETCLEANUP]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[PRECOMMIT]) == 0 || strcasecmp (last, GlobalpluginPositionsStr[COMMIT]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[POSTCOMMIT]) == 0) + { + strcpy (resultPos, last); + resultPos += strlen (last); + } + + *resultPos = '\0'; + return result; +} + +static char * extractErrorPlacements (const char * placementList) +{ + char * result = elektraMalloc (strlen (placementList) + 1); + result[0] = '\0'; + char * resultPos = result; + const char * last = placementList; + const char * placement = strchr (last, ' '); + while (placement != NULL) + { + size_t len = placement - last; + if (strncasecmp (last, GlobalpluginPositionsStr[PREROLLBACK], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[ROLLBACK], len) == 0 || + strncasecmp (last, GlobalpluginPositionsStr[POSTROLLBACK], len) == 0) + { + strncpy (resultPos, last, len); + resultPos[len] = ' '; + resultPos += len + 1; + } + + last = placement + 1; + placement = strchr (last, ' '); + } + + if (strcasecmp (last, GlobalpluginPositionsStr[PREROLLBACK]) == 0 || strcasecmp (last, GlobalpluginPositionsStr[ROLLBACK]) == 0 || + strcasecmp (last, GlobalpluginPositionsStr[POSTROLLBACK]) == 0) + { + strcpy (resultPos, last); + resultPos += strlen (last); + } + + *resultPos = '\0'; + return result; +} + +static Key * findPluginInConfig (KeySet * config, const char * pluginName) +{ + Key * configBase = keyNew ("user/plugins", KEY_END); + KeySet * array = elektraArrayGet (configBase, config); + + ksRewind (array); + Key * cur = NULL; + while ((cur = ksNext (array)) != NULL) + { + if (strcmp (keyString (cur), pluginName) == 0) + { + // found plugin + Key * result = keyDup (cur); + keyDel (configBase); + ksDel (array); + return result; + } + } + + keyDel (configBase); + ksDel (array); + return NULL; +} + +static void resetPlugins (Plugin * handle, Key * errorKey) +{ + Placements * placements = elektraPluginGetData (handle); + ksClear (placements->getKS[0]); + ksClear (placements->getKS[1]); + ksClear (placements->getKS[2]); + ksClear (placements->setKS[0]); + ksClear (placements->setKS[1]); + ksClear (placements->setKS[2]); + ksClear (placements->setKS[3]); + ksClear (placements->errKS[0]); + ksClear (placements->errKS[1]); + Key * cur; + ksRewind (placements->plugins); + while ((cur = ksNext (placements->plugins)) != NULL) + { + Plugin * slave; + slave = *(Plugin **) keyValue (cur); + elektraPluginClose (slave, errorKey); + } + ksClear (placements->plugins); +} + +/** + * Adds a plugin in all the intended positions (given in its infos/placements key). + * If the plugin is already added, effectively equivalent to calling ksDel() on pluginConfig. + * + * @param handle A handle of the list plugin + * @param pluginName The plugin to add + * @param pluginConfig The config for the plugin, if it has to be mounted; the KeySet is consumed, + * don't call ksDel() on it afterwards. + * @param errorKey Used for error reporting + * + * @retval #ELEKTRA_PLUGIN_STATUS_SUCCESS if the plugin was added + * @retval #ELEKTRA_PLUGIN_STATUS_NO_UPDATE if the plugin was added already + * @retval #ELEKTRA_PLUGIN_STATUS_ERROR on NULL pointers and other errors + */ +int elektraListMountPlugin (Plugin * handle, const char * pluginName, KeySet * pluginConfig, Key * errorKey) +{ + if (handle == NULL || pluginName == NULL || pluginConfig == NULL) + { + return ELEKTRA_PLUGIN_STATUS_ERROR; + } + + Placements * placements = elektraPluginGetData (handle); + KeySet * config = elektraPluginGetConfig (handle); + + // check if plugin already added + Key * pluginKey = findPluginInConfig (config, pluginName); + if (pluginKey != NULL) + { + keyDel (pluginKey); + ksDel (pluginConfig); // consume config + return ELEKTRA_PLUGIN_STATUS_NO_UPDATE; + } + + // Find name for next item in plugins array + Key * configBase = keyNew ("user/plugins", KEY_END); + KeySet * array = elektraArrayGet (configBase, config); + Key * pluginItem = elektraArrayGetNextKey (array); + ELEKTRA_NOT_NULL (pluginItem); + keySetString (pluginItem, pluginName); + keyDel (configBase); + + Plugin * plugin = elektraPluginOpen (pluginName, placements->modules, pluginConfig, errorKey); + + // Store key with plugin handle + Key * searchKey = keyNew ("/", KEY_END); + keyAddBaseName (searchKey, pluginName); + keySetBinary (searchKey, &plugin, sizeof (plugin)); + + // Find plugin placements + char * placementList = getPluginPlacementList (plugin); + + Key * pluginPlacements = keyDup (pluginItem); + keyAddBaseName (pluginPlacements, "placements"); + + // Append keys to list plugin configuration + ksAppendKey (config, pluginItem); + ksAppendKey (config, pluginPlacements); + + // Add get placements + char * getPlacementsString = extractGetPlacements (placementList); + if (getPlacementsString != NULL) + { + Key * getPlacements = keyDup (pluginPlacements); + keyAddBaseName (getPlacements, "get"); + keySetString (getPlacements, getPlacementsString); + ksAppendKey (config, getPlacements); + } + elektraFree (getPlacementsString); + + + // Add set placements + char * setPlacementsString = extractSetPlacements (placementList); + if (setPlacementsString != NULL) + { + Key * setPlacements = keyDup (pluginPlacements); + keyAddBaseName (setPlacements, "set"); + keySetString (setPlacements, setPlacementsString); + ksAppendKey (config, setPlacements); + } + elektraFree (setPlacementsString); + + // Add error placements + char * errorPlacementsString = extractErrorPlacements (placementList); + if (errorPlacementsString != NULL) + { + Key * errorPlacements = keyDup (pluginPlacements); + keyAddBaseName (errorPlacements, "error"); + keySetString (errorPlacements, errorPlacementsString); + ksAppendKey (config, errorPlacements); + } + elektraFree (errorPlacementsString); + elektraFree (placementList); + ksDel (array); + + // reload configuration + resetPlugins (handle, errorKey); + + // store new handle + ksAppendKey (placements->plugins, searchKey); + return elektraListOpen (handle, errorKey); +} + +/** + * Removes a plugin from all the intended positions (given in its infos/placements key). + * If the plugin isn't present, nothing happens. + * + * @param handle A handle of the list plugin + * @param pluginName The plugin to remove + * @param errorKey Used for error reporting + * + * @retval #ELEKTRA_PLUGIN_STATUS_SUCCESS if the plugin was added + * @retval #ELEKTRA_PLUGIN_STATUS_NO_UPDATE if the plugin was added already + * @retval #ELEKTRA_PLUGIN_STATUS_ERROR on NULL pointers and other errors + */ +int elektraListUnmountPlugin (Plugin * handle, const char * pluginName, Key * errorKey) +{ + if (handle == NULL || pluginName == NULL) + { + return ELEKTRA_PLUGIN_STATUS_ERROR; + } + + Placements * placements = elektraPluginGetData (handle); + KeySet * config = elektraPluginGetConfig (handle); + + // Find plugin + Key * pluginItem = findPluginInConfig (config, pluginName); + if (pluginItem == NULL) + { + return ELEKTRA_PLUGIN_STATUS_NO_UPDATE; + } + + // Look for plugin via handle + Key * pluginHandle = keyDup (pluginItem); + keyAddName (pluginHandle, "handle"); + pluginHandle = ksLookup (config, pluginHandle, KDB_O_DEL); + + // unload plugin if loaded via handle + if (pluginHandle != NULL) + { + elektraPluginClose (*((Plugin **) keyValue (pluginHandle)), errorKey); + keyDel (pluginHandle); + } + + // Look for plugin via plugins + Key * searchKey = keyNew ("/", KEY_END); + keyAddBaseName (searchKey, pluginName); + + // pop if found + searchKey = ksLookup (placements->plugins, searchKey, KDB_O_DEL | KDB_O_POP); + + // unload plugin if loaded via plugins + if (searchKey != NULL) + { + elektraPluginClose (*((Plugin **) keyValue (searchKey)), errorKey); + keyDel (searchKey); + } + + // Remove plugin data from config + ksDel (ksCut (config, pluginItem)); + keyDel (pluginItem); + + // reload configuration + resetPlugins (handle, errorKey); + return elektraListOpen (handle, errorKey); +} + +/** + * Find the handle of plugin. + * + * If elektraListGet(), elektraListSet() and elektraListError() + * haven't been called yet, only plugins added via elektraListMountPlugin() + * will be found. Other plugins aren't opened (and therefore don't have a handle) + * before get/set/error is called. + * + * @param handle A handle of the list plugin + * @param pluginName The name of the plugin to look for + * + * @return the handle for the given plugin, or NULL if not found + * NULL is also returned if @p handle or @p pluginName are NULL + */ +Plugin * elektraListFindPlugin (Plugin * handle, const char * pluginName) +{ + if (handle == NULL || pluginName == NULL) + { + return NULL; + } + + Placements * placements = elektraPluginGetData (handle); + KeySet * config = elektraPluginGetConfig (handle); + + Key * searchKey = keyNew ("/", KEY_END); + keyAddBaseName (searchKey, pluginName); + Key * lookup = ksLookup (placements->plugins, searchKey, KDB_O_DEL); + if (lookup) + { + return *(Plugin **) keyValue (lookup); + } + + + Key * current; + for (int i = 0; i < getEnd; ++i) + { + while ((current = ksNext (placements->getKS[i])) != NULL) + { + Key * handleKey = keyDup (current); + keyAddName (handleKey, "handle"); + Key * handleLookup = ksLookup (config, handleKey, KDB_O_DEL); + if (handleLookup) + { + return *(Plugin **) keyValue (handleLookup); + } + } + } + + for (int i = 0; i < setEnd; ++i) + { + while ((current = ksNext (placements->setKS[i])) != NULL) + { + Key * handleKey = keyDup (current); + keyAddName (handleKey, "handle"); + Key * handleLookup = ksLookup (config, handleKey, KDB_O_DEL); + if (handleLookup) + { + return *(Plugin **) keyValue (handleLookup); + } + } + } + + for (int i = 0; i < errEnd; ++i) + { + while ((current = ksNext (placements->errKS[i])) != NULL) + { + Key * handleKey = keyDup (current); + keyAddName (handleKey, "handle"); + Key * handleLookup = ksLookup (config, handleKey, KDB_O_DEL); + if (handleLookup) + { + return *(Plugin **) keyValue (handleLookup); + } + } + } + + return NULL; +} + int elektraListEditPlugin (Plugin * handle, KeySet * pluginConfig) { if (!pluginConfig) diff --git a/src/plugins/list/list.h b/src/plugins/list/list.h index 2069e43c534..d7dfa5e0447 100644 --- a/src/plugins/list/list.h +++ b/src/plugins/list/list.h @@ -21,6 +21,9 @@ 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); +int elektraListMountPlugin (Plugin * handle, const char * pluginName, KeySet * pluginConfig, Key * errorKey); +int elektraListUnmountPlugin (Plugin * handle, const char * pluginName, Key * errorKey); +Plugin * elektraListFindPlugin (Plugin * handle, const char * pluginName); Plugin * ELEKTRA_PLUGIN_EXPORT; diff --git a/src/plugins/mathcheck/README.md b/src/plugins/mathcheck/README.md index 963b5ff1f30..5cb56a2876a 100644 --- a/src/plugins/mathcheck/README.md +++ b/src/plugins/mathcheck/README.md @@ -28,17 +28,17 @@ Keynames are all either relative to to-be-tested key (starting with `./` or `../ Full example: ```sh -# Backup-and-Restore:/tests/mathcheck +# Backup-and-Restore:user/tests/mathcheck -sudo kdb mount mathcheck.dump /tests/mathcheck mathcheck +sudo kdb mount mathcheck.dump user/tests/mathcheck mathcheck -kdb set /tests/mathcheck/a 3.1 -kdb set /tests/mathcheck/b 4.5 -kdb set /tests/mathcheck/k 7.6 -kdb setmeta /tests/mathcheck/k check/math "== + ../a ../b" +kdb set user/tests/mathcheck/a 3.1 +kdb set user/tests/mathcheck/b 4.5 +kdb set user/tests/mathcheck/k 7.6 +kdb setmeta user/tests/mathcheck/k check/math "== + ../a ../b" # should fail -kdb set /tests/mathcheck/k 7.7 +kdb set user/tests/mathcheck/k 7.7 # RET:5 # ERROR:123 # Set string to "7.7" @@ -49,41 +49,41 @@ kdb set /tests/mathcheck/k 7.7 # Module: mathcheck # At: /home/thomas/Dev/Elektra/libelektra/src/plugins/mathcheck/mathcheck.c:399 # Reason: 7.7 != 7.6 -# Mountpoint: /tests/mathcheck +# Mountpoint: user/tests/mathcheck # Configfile: /home/thomas/.config/mathcheck.dump.25680:1478749409.938013.tmp ``` To calculate values on-demand you can use: ```sh -kdb setmeta /tests/mathcheck/k check/math ":= + @/a @/b" -kdb set /tests/mathcheck/a 8.0 -kdb set /tests/mathcheck/b 4.5 +kdb setmeta user/tests/mathcheck/k check/math ":= + @/a @/b" +kdb set user/tests/mathcheck/a 8.0 +kdb set user/tests/mathcheck/b 4.5 -kdb get /tests/mathcheck/k +kdb get user/tests/mathcheck/k #> 12.5 -kdb set /tests/mathcheck/a 5.5 +kdb set user/tests/mathcheck/a 5.5 -kdb get /tests/mathcheck/k +kdb get user/tests/mathcheck/k #> 10 ``` It also works with constants: ```sh -kdb setmeta /tests/mathcheck/k check/math ":= + ../a '5'" -kdb set /tests/mathcheck/a 5.5 +kdb setmeta user/tests/mathcheck/k check/math ":= + ../a '5'" +kdb set user/tests/mathcheck/a 5.5 -kdb get /tests/mathcheck/k +kdb get user/tests/mathcheck/k #> 10.5 -kdb set /tests/mathcheck/a 8.0 +kdb set user/tests/mathcheck/a 8.0 -kdb get /tests/mathcheck/k +kdb get user/tests/mathcheck/k #> 13 #cleanup -kdb rm -r /tests/mathcheck -sudo kdb umount /tests/mathcheck +kdb rm -r user/tests/mathcheck +sudo kdb umount user/tests/mathcheck ``` diff --git a/src/plugins/multifile/README.md b/src/plugins/multifile/README.md index 493eeed624e..19784301585 100644 --- a/src/plugins/multifile/README.md +++ b/src/plugins/multifile/README.md @@ -43,22 +43,22 @@ The multifile-resolver does so by calling resolver and storage plugins for each ## Examples ```sh -rm -rf ~/.config/multitest || $(exit 0) -mkdir -p ~/.config/multitest || $(exit 0) +rm -rf $(dirname $(kdb file user))/multitest || $(exit 0) +mkdir -p $(dirname $(kdb file user))/multitest || $(exit 0) -cat > ~/.config/multitest/lo.ini << EOF \ +cat > $(dirname $(kdb file user))/multitest/lo.ini << EOF \ [lo]\ addr = 127.0.0.1\ Link encap = Loopback\ EOF -cat > ~/.config/multitest/lan.ini << EOF \ +cat > $(dirname $(kdb file user))/multitest/lan.ini << EOF \ [eth0]\ addr = 192.168.1.216\ Link encap = Ethernet\ EOF -cat > ~/.config/multitest/wlan.ini << EOF \ +cat > $(dirname $(kdb file user))/multitest/wlan.ini << EOF \ [wlan0]\ addr = 192.168.1.125\ Link encap = Ethernet\ @@ -82,7 +82,7 @@ kdb set user/tests/multifile/lan.ini/eth0/addr 10.0.0.2 kdb get user/tests/multifile/lan.ini/eth0/addr #> 10.0.0.2 -cat > ~/.config/multitest/test.ini << EOF \ +cat > $(dirname $(kdb file user))/multitest/test.ini << EOF \ [testsection]\ key = val\ EOF @@ -102,7 +102,7 @@ kdb ls user/tests/multifile kdb rm -r user/tests/multifile/test.ini -stat ~/.config/multifile/test.ini +stat $(dirname $(kdb file user))/multifile/test.ini # RET:1 sudo kdb umount user/tests/multifile @@ -111,12 +111,12 @@ sudo kdb umount user/tests/multifile Recursive: ```sh -rm -rf ~/.config/multitest || $(exit 0) -mkdir -p ~/.config/multitest ~/.config/multitest/a/a1/a12 ~/.config/multitest/a/a2/a22 ~/.config/multitest/b/b1|| $(exit 0) +rm -rf $(dirname $(kdb file user))/multitest || $(exit 0) +mkdir -p $(dirname $(kdb file user))/multitest $(dirname $(kdb file user))/multitest/a/a1/a12 $(dirname $(kdb file user))/multitest/a/a2/a22 $(dirname $(kdb file user))/multitest/b/b1|| $(exit 0) -echo "a1key = a1val" > ~/.config/multitest/a/a1/a12/testa1.file -echo "a2key = a2val" > ~/.config/multitest/a/a2/a22/testa2.file -echo "b1key = b1val" > ~/.config/multitest/b/b1/testb1.file +echo "a1key = a1val" > $(dirname $(kdb file user))/multitest/a/a1/a12/testa1.file +echo "a2key = a2val" > $(dirname $(kdb file user))/multitest/a/a2/a22/testa2.file +echo "b1key = b1val" > $(dirname $(kdb file user))/multitest/b/b1/testb1.file sudo kdb mount -R multifile -c storage="ini",pattern="*.file",recursive=,resolver="resolver" multitest user/tests/multifile @@ -125,7 +125,7 @@ kdb ls user/tests/multifile #> user/tests/multifile/a/a2/a22/testa2.file/a2key #> user/tests/multifile/b/b1/testb1.file/b1key -rm -rf ~/.config/multitest +rm -rf $(dirname $(kdb file user))/multitest sudo kdb umount user/tests/multifile ``` diff --git a/src/plugins/template/README.md b/src/plugins/template/README.md index a8f6a3b0b2c..aa09a4a2563 100644 --- a/src/plugins/template/README.md +++ b/src/plugins/template/README.md @@ -4,7 +4,7 @@ - infos/needs = - infos/provides = - infos/recommends = -- infos/placements = prerollback rollback postrollback getresolver pregetstorage getstorage postgetstorage setresolver presetstorage setstorage precommit commit postcommit +- infos/placements = prerollback rollback postrollback getresolver pregetstorage getstorage procgetstorage postgetstorage setresolver presetstorage setstorage precommit commit postcommit - infos/status = recommended productive maintained reviewed conformant compatible coverage specific unittest shelltest tested nodep libc configurable final preview memleak experimental difficult unfinished old nodoc concept orphan obsolete discouraged -1000000 - infos/metadata = - infos/description = one-line description of template diff --git a/src/plugins/timeofday/README.md b/src/plugins/timeofday/README.md index 98649c75056..7926662bfd0 100644 --- a/src/plugins/timeofday/README.md +++ b/src/plugins/timeofday/README.md @@ -3,7 +3,7 @@ - infos/licence = BSD - infos/provides = tracing - infos/needs = -- infos/placements = pregetstorage postgetstorage presetstorage precommit postcommit prerollback postrollback +- infos/placements = pregetstorage procgetstorage postgetstorage presetstorage precommit postcommit prerollback postrollback - infos/status = maintained tested nodep configurable global - infos/description = Prints timestamps during execution of backend diff --git a/src/plugins/tracer/README.md b/src/plugins/tracer/README.md index cd1dfc2d77e..e02bf45eb93 100644 --- a/src/plugins/tracer/README.md +++ b/src/plugins/tracer/README.md @@ -3,7 +3,7 @@ - infos/licence = BSD - infos/provides = tracing - infos/needs = -- infos/placements = pregetstorage postgetstorage presetstorage precommit postcommit prerollback postrollback +- infos/placements = pregetstorage procgetstorage postgetstorage presetstorage precommit postcommit prerollback postrollback - infos/status = maintained tested nodep configurable global - infos/description = Traces the execution path of a backend diff --git a/tests/ctest/test_proposal.c b/tests/ctest/test_proposal.c index 34b7904e4b7..1f160098b47 100644 --- a/tests/ctest/test_proposal.c +++ b/tests/ctest/test_proposal.c @@ -83,14 +83,21 @@ static void test_keyAsCascading (void) static void test_keyGetLevelsBelow (void) { printf ("test keyGetLevelsBelow\n"); - Key * parent = keyNew ("system/parent", KEY_END); - Key * user = keyNew ("user/parent", KEY_END); - Key * oneLvl = keyNew ("system/parent/child", KEY_END); - Key * threeLvl = keyNew ("system/parent/child1/child2/child3", KEY_END); + Key * grandparent = keyNew ("system/grandparent", KEY_END); + Key * parent = keyNew ("system/grandparent/parent", KEY_END); + Key * user = keyNew ("user/grandparent/parent", KEY_END); + Key * oneLvl = keyNew ("system/grandparent/parent/child", KEY_END); + Key * threeLvl = keyNew ("system/grandparent/parent/child1/child2/child3", KEY_END); succeed_if (keyGetLevelsBelow (parent, oneLvl) == 1, "getLevelsBelow returned wrong value"); succeed_if (keyGetLevelsBelow (parent, threeLvl) == 3, "getLevelsBelow returned wrong value"); succeed_if (keyGetLevelsBelow (parent, parent) == 0, "getLevelsBelow returned wrong value"); succeed_if (keyGetLevelsBelow (parent, user) == 0, "getLevelsBelow returned wrong value"); + succeed_if (keyGetLevelsBelow (parent, grandparent) == 0, "getLevelsBelow returned wrong value"); + succeed_if (keyGetLevelsBelow (grandparent, parent) == 1, "getLevelsBelow returned wrong value"); + succeed_if (keyGetLevelsBelow (grandparent, oneLvl) == 2, "getLevelsBelow returned wrong value"); + succeed_if (keyGetLevelsBelow (grandparent, threeLvl) == 4, "getLevelsBelow returned wrong value"); + succeed_if (keyGetLevelsBelow (threeLvl, grandparent) == 0, "getLevelsBelow returned wrong value"); + keyDel (grandparent); keyDel (parent); keyDel (user); keyDel (oneLvl); diff --git a/tests/icheck.suppression b/tests/icheck.suppression index 87466b501d0..c1030c32a19 100644 --- a/tests/icheck.suppression +++ b/tests/icheck.suppression @@ -4,3 +4,5 @@ // the following entry: elektraArrayDecName elektraIsReferenceRedundant elektraResolveReference +elektraPluginFindGlobal +kdbEnsure diff --git a/tests/kdb/CMakeLists.txt b/tests/kdb/CMakeLists.txt index 7388f5986d0..18753216c48 100644 --- a/tests/kdb/CMakeLists.txt +++ b/tests/kdb/CMakeLists.txt @@ -28,6 +28,7 @@ add_kdb_test (conflict REQUIRED_PLUGINS error) add_kdb_test (error REQUIRED_PLUGINS error list spec) add_kdb_test (nested REQUIRED_PLUGINS error) add_kdb_test (simple REQUIRED_PLUGINS error) +add_kdb_test (ensure REQUIRED_PLUGINS tracer list spec) set_source_files_properties (testkdb_highlevel PROPERTIES COMPILE_FLAGS -Wno-sign-promo) add_kdb_test (highlevel LINK_ELEKTRA elektra-highlevel REQUIRED_PLUGINS error) diff --git a/tests/kdb/testkdb_ensure.cpp b/tests/kdb/testkdb_ensure.cpp new file mode 100644 index 00000000000..713149864fb --- /dev/null +++ b/tests/kdb/testkdb_ensure.cpp @@ -0,0 +1,184 @@ +/** + * @file + * + * @brief Tests for the Backend builder class + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + * + */ + +#include +#include + +#include + +class Ensure : public ::testing::Test +{ +protected: + static const char * testRoot; + static const char * configFileRoot; + std::string specRoot; + std::string userRoot; + + testing::Namespaces namespaces; + testing::MountpointPtr mpRoot; + + Ensure () : specRoot (std::string ("spec") + testRoot), userRoot (std::string ("user") + testRoot), namespaces () + { + } + + virtual void SetUp () override + { + mpRoot.reset (new testing::Mountpoint (testRoot, configFileRoot)); + + using namespace kdb; + KDB kdb; + + KeySet ks; + kdb.get (ks, testRoot); + + ks.append (Key (specRoot + "/speckey/#", KEY_META, "mymeta", "1", KEY_END)); + ks.append (Key (userRoot + "/speckey/#0", KEY_VALUE, "", KEY_END)); + ks.append (Key (userRoot + "/errorkey", KEY_META, "trigger/warnings", "3", KEY_END)); + // ks.append (Key (specRoot + "/goptskey", KEY_META, "opt", "1", KEY_END)); + + kdb.set (ks, testRoot); + } + + virtual void TearDown () override + { + mpRoot.reset (); + } + + static bool checkTracerOutput (const std::string & capture); +}; + +const char * Ensure::testRoot = "/tests/kdb/ensure"; +const char * Ensure::configFileRoot = "kdbFileEnsure.dump"; + + +TEST_F (Ensure, GlobalUnmount) +{ + using namespace kdb; + KDB kdb; + + { + KeySet ks; + kdb.get (ks, testRoot); + + EXPECT_EQ (ks.lookup (userRoot + "/speckey/#0", 0).getMeta ("mymeta"), "1") << "spec plugin didn't run"; + } + + { + KeySet contract; + contract.append (Key ("system/elektra/ensure/plugins/global/spec", KEY_VALUE, "unmounted", KEY_END)); + Key root (specRoot, KEY_END); + kdb.ensure (contract, root); + + KeySet ks; + kdb.get (ks, testRoot); + + EXPECT_FALSE (ks.lookup (userRoot + "/speckey/#0", 0).hasMeta ("mymeta")) << "spec plugin shouldn't have run"; + + kdb.ensure (contract, root); + kdb.get (ks, testRoot); + + EXPECT_FALSE (ks.lookup (userRoot + "/speckey/#0", 0).hasMeta ("mymeta")) << "global unmount should be idempotent"; + } +} + +TEST_F (Ensure, Unmount) +{ + using namespace kdb; + KDB kdb; + + { + KeySet ks; + Key root (testRoot, KEY_END); + kdb.get (ks, root); + kdb.set (ks, root); + + EXPECT_EQ (root.getMeta ("warnings/#00/number"), "3") << "error plugin didn't run"; + } + + { + KeySet contract; + contract.append (Key ("system/elektra/ensure/plugins/parent/error", KEY_VALUE, "unmounted", KEY_END)); + Key uroot (userRoot, KEY_END); + kdb.ensure (contract, uroot); + + KeySet ks; + Key root (testRoot, KEY_END); + kdb.get (ks, root); + kdb.set (ks, root); + + EXPECT_FALSE (root.hasMeta ("warnings")) << "error plugin shouldn't have run"; + + root.delMeta ("warnings"); + + kdb.ensure (contract, uroot); + kdb.get (ks, root); + kdb.set (ks, root); + + EXPECT_FALSE (root.hasMeta ("warnings")) << "unmount should be idempotent"; + } +} + +bool Ensure::checkTracerOutput (const std::string & capture) +{ + EXPECT_FALSE (capture.empty ()) << "tracer plugin not run"; + + std::istringstream lines (capture); + std::string line; + size_t i = 0; + bool success = true; + while (std::getline (lines, line)) + { + std::smatch match; + success = success && std::regex_match (line, match, std::regex (R"(^tracer: .*$)")); + EXPECT_TRUE (success) << "A line didn't match the expected tracer output:" << std::endl << "line: " << line << std::endl; + ++i; + } + + return success; +} + +TEST_F (Ensure, GlobalMount) +{ + using namespace kdb; + KDB kdb; + + { + testing::internal::CaptureStdout (); + KeySet ks; + kdb.get (ks, testRoot); + + EXPECT_TRUE (testing::internal::GetCapturedStdout ().empty ()) << "there should be no output on stdout"; + } + + { + KeySet contract; + contract.append (Key ("system/elektra/ensure/plugins/global/tracer", KEY_VALUE, "mounted", KEY_END)); + Key root (specRoot, KEY_END); + kdb.ensure (contract, root); + + testing::internal::CaptureStdout (); + KeySet ks; + kdb.get (ks, testRoot); + + { + SCOPED_TRACE ("first ensure"); + EXPECT_TRUE (checkTracerOutput (testing::internal::GetCapturedStdout ())) << "tracer output didn't match"; + } + + kdb.ensure (contract, root); + + testing::internal::CaptureStdout (); + kdb.get (ks, testRoot); + + { + SCOPED_TRACE ("second ensure"); + EXPECT_TRUE (checkTracerOutput (testing::internal::GetCapturedStdout ())) << "global mount should be idempotent"; + } + } +}