From b32571c3c6d400e5b5f8ccff4c4e49b21dfcba9f Mon Sep 17 00:00:00 2001 From: Hannes Laimer Date: Wed, 1 Mar 2023 06:49:15 +0100 Subject: [PATCH] kdb-cli: rewrite set ... and (partial) fix #3742, fix #4028, fix #1164 --- doc/tutorials/cascading.md | 33 ++-------- src/plugins/email/README.md | 2 +- src/plugins/hosts/README.md | 4 +- src/plugins/kconfig/README.md | 2 +- src/plugins/range/README.md | 1 + src/tools/kdb/factory.hpp | 2 - src/tools/kdb/main.c | 2 + src/tools/kdb/set.c | 119 ++++++++++++++++++++++++++++++++++ src/tools/kdb/set.cpp | 86 ------------------------ src/tools/kdb/set.h | 33 ++++++++++ src/tools/kdb/set.hpp | 49 -------------- 11 files changed, 163 insertions(+), 170 deletions(-) create mode 100644 src/tools/kdb/set.c delete mode 100644 src/tools/kdb/set.cpp create mode 100644 src/tools/kdb/set.h delete mode 100644 src/tools/kdb/set.hpp diff --git a/doc/tutorials/cascading.md b/doc/tutorials/cascading.md index ec7a0587a9b..05a94f3c6b0 100644 --- a/doc/tutorials/cascading.md +++ b/doc/tutorials/cascading.md @@ -91,39 +91,14 @@ The **proc** namespace is not accessible by the command line tool **kdb**, as it The **spec** namespace is used to store metadata about keys and therefore Elektra handles the **spec** namespace differently to other namespaces. The following part of the tutorial is dedicated to the impact of the **spec** namespace on cascading lookups. -## Write Operations and the cascading Namespace +## Cascading writes are not possible When performing writing operations, a namespace always has to be specified. ```sh -kdb set /tests/tutorial/cascading/#0/current/cascading_write_test value -# STDERR: Aborting: A cascading write to a non-existent key is ambiguous. -# RET: 12 - -kdb meta-set /tests/tutorial/cascading/#0/current/cascading_write_test metakey metavalue -# STDERR: Aborting: A cascading write to a non-existent key is ambiguous. -# RET: 12 -``` - -will both fail, as no matching key exists. -Since there are multiple hypothetical key names that would match the cascading name (keys of specific namespaces like user:, system:, ...) if they existed, it is not clear what the user intended and thus an error is produced. - -To make the previous two operations meaningful, a matching key in the user: namespace is created: - -```sh -kdb set user:/tests/tutorial/cascading/#0/current/cascading_write_test value -#> Create a new key user:/tests/tutorial/cascading/#0/current/cascading_write_test with string "value" -``` - -Now, the operations operate on a well-defined key and succeed this time: - -```sh -kdb set /tests/tutorial/cascading/#0/current/cascading_write_test value -#> Set string to "value" -#> Using name user:/tests/tutorial/cascading/#0/current/cascading_write_test - -kdb meta-set /tests/tutorial/cascading/#0/current/cascading_write_test metakey metavalue -#> Using name user:/tests/tutorial/cascading/#0/current/cascading_write_test +kdb set /tests/tutorial/cascading/key1 "hello world" +# RET: 2 +# STDERR: .*key does not specify a namespace ``` ## Override Links diff --git a/src/plugins/email/README.md b/src/plugins/email/README.md index dbf30c88823..a737b990c43 100644 --- a/src/plugins/email/README.md +++ b/src/plugins/email/README.md @@ -27,7 +27,7 @@ The package is called `libelektra5-experimental`. kdb mount config.dump /tests/email dump email # Incorrect address is valid with incomplete configuration -kdb meta-set spec:/tests/email/noaddr check/email +# kdb meta set spec:/tests/email/noaddr check/email kdb set user:/tests/email/noaddr invalid..address@com # RET: 0 diff --git a/src/plugins/hosts/README.md b/src/plugins/hosts/README.md index ddff8b67381..f847a6fb24c 100644 --- a/src/plugins/hosts/README.md +++ b/src/plugins/hosts/README.md @@ -98,10 +98,10 @@ kdb get /tests/hosts/ipv6/localhost #> ::1 # Should both fail with error C03200 and return 5 -kdb set /tests/hosts/ipv4/localhost ::1 +kdb set user:/tests/hosts/ipv4/localhost ::1 # RET:5 # ERROR:C03200 -kdb set /tests/hosts/ipv6/localhost 127.0.0.1 +kdb set user:/tests/hosts/ipv6/localhost 127.0.0.1 # RET:5 # ERROR:C03200 diff --git a/src/plugins/kconfig/README.md b/src/plugins/kconfig/README.md index ae61c02e7cb..bb71e6c6a78 100644 --- a/src/plugins/kconfig/README.md +++ b/src/plugins/kconfig/README.md @@ -75,7 +75,7 @@ kdb get /tests/kconfig/key #> Value # Set the value to Example -kdb set /tests/kconfig/key Example +kdb set user:/tests/kconfig/key Example # Verify that the value has changed in the file too cat `kdb file user:/tests/kconfig` diff --git a/src/plugins/range/README.md b/src/plugins/range/README.md index e114bc85f0d..ca77dbe6c8c 100644 --- a/src/plugins/range/README.md +++ b/src/plugins/range/README.md @@ -83,6 +83,7 @@ kdb set user:/tests/range/value 2 # RET:0 kdb rm -r user:/tests/range +kdb rm -r spec:/tests/range sudo kdb umount /tests/range ``` diff --git a/src/tools/kdb/factory.hpp b/src/tools/kdb/factory.hpp index 7e79efb6944..1790020d54e 100644 --- a/src/tools/kdb/factory.hpp +++ b/src/tools/kdb/factory.hpp @@ -46,7 +46,6 @@ #include #include #include -#include #include #include #include @@ -81,7 +80,6 @@ class Factory Factory () : m_factory () { // TODO: to add a new command, 2.) add a line here -> and you are done - m_factory.insert (std::make_pair ("set", std::make_shared> ())); m_factory.insert (std::make_pair ("rm", std::make_shared> ())); m_factory.insert (std::make_pair ("cache", std::make_shared> ())); m_factory.insert (std::make_pair ("complete", std::make_shared> ())); diff --git a/src/tools/kdb/main.c b/src/tools/kdb/main.c index 9b89e8623ed..4a986ab9021 100644 --- a/src/tools/kdb/main.c +++ b/src/tools/kdb/main.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,7 @@ command subcommands[] = { { "get", addGetSpec, execGet }, { "ls", addLsSpec, execLs }, { "namespace", addNamespaceSpec, execNamespace }, + { "set", addSetSpec, execSet }, }; void printError (Key * errorKey) diff --git a/src/tools/kdb/set.c b/src/tools/kdb/set.c new file mode 100644 index 00000000000..589f7cdb4d2 --- /dev/null +++ b/src/tools/kdb/set.c @@ -0,0 +1,119 @@ +/** + * @file + * + * @brief KDB set subcommand + * + * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define COMMAND_NAME "set" + +#define GET_OPTION_KEY(options, name) GET_OPT_KEY (options, COMMAND_BASE_KEY (COMMAND_NAME) "/" name) +#define GET_OPTION(options, name) GET_OPT (options, COMMAND_BASE_KEY (COMMAND_NAME) "/" name) + +void addSetSpec (KeySet * spec) +{ + ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME), KEY_META, "description", "Set the value of an individual key.", + KEY_META, "command", COMMAND_NAME, KEY_END)); + ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME) "/force", KEY_META, "description", "Force setting the value", KEY_META, + "opt", "f", KEY_META, "opt/long", "force", KEY_META, "opt/arg", "none", KEY_END)); + ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME) "/name", KEY_META, "description", "The name of the key", KEY_META, + "args", "indexed", KEY_META, "args/index", "0", KEY_END)); + + ksAppendKey (spec, keyNew (COMMAND_SPEC_KEY (COMMAND_NAME) "/value", KEY_META, "description", "The value that should be set", + KEY_META, "args", "indexed", KEY_META, "args/index", "1", KEY_END)); + + ADD_BASIC_OPTIONS (spec, COMMAND_SPEC_KEY (COMMAND_NAME)) +} + +int execSet (KeySet * options, Key * errorKey) +{ + int ret = 0; + GET_BASIC_OPTIONS + + bool force = false; + tmp = GET_OPTION_KEY (options, "force"); + if (tmp != NULL) + { + elektraKeyToBoolean (GET_OPTION_KEY (options, "force"), &force); + } + + const char * name = getKeyNameFromOptions (GET_OPTION (options, "name"), errorKey, verbose); + if (name == NULL) + { + RETURN (2) + } + + const char * value = GET_OPTION (options, "value"); + + Key * parentKey = keyNew (name, KEY_END); + + if (keyGetNamespace (parentKey) == KEY_NS_NONE || keyGetNamespace (parentKey) == KEY_NS_CASCADING) + { + ELEKTRA_SET_VALIDATION_SYNTACTIC_ERROR (errorKey, "key does not specify a namespace"); + elektraFree ((void *) name); + keyDel (parentKey); + RETURN (2) + } + + Key * maybeCascadingParent = keyDup (parentKey, KEY_CP_NAME); + if (!force) + { + keySetNamespace (maybeCascadingParent, KEY_NS_CASCADING); + } + KeySet * conf = ksNew (0, KS_END); + KDB * handle = kdbOpen (NULL, errorKey); + + if (kdbGet (handle, conf, maybeCascadingParent) == -1) + { + ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (errorKey, "could not load '%s': %s", keyName (parentKey), + GET_ERR (maybeCascadingParent)); + ret = 5; + goto cleanup; + } + keyCopyAllMeta (errorKey, parentKey); + + Key * key = ksLookup (conf, parentKey, KDB_O_NONE); + if (key == NULL) + { + key = keyNew (name, KEY_END); + ksAppendKey (conf, key); + CLI_PRINT (CLI_LOG_NONE, "Create a new key %s with string \"%s\"", keyName (key), value); + } + else + { + CLI_PRINT (CLI_LOG_NONE, "Set string to \"%s\"", value); + } + keySetString (key, value); // can't fail, since neither value or key can be null + + if (kdbSet (handle, conf, parentKey) == -1) + { + ret = 5; + ELEKTRA_SET_VALIDATION_SEMANTIC_ERRORF (errorKey, "could not set value for '%s': %s", name, GET_ERR (parentKey)); + } + keyCopyAllMeta (errorKey, parentKey); + + keyDel (key); + +cleanup: + if (!noNewLine) + { + printf ("\n"); + } + elektraFree ((void *) name); + keyDel (parentKey); + keyDel (maybeCascadingParent); + ksDel (conf); + kdbClose (handle, errorKey); + + RETURN (ret) +} diff --git a/src/tools/kdb/set.cpp b/src/tools/kdb/set.cpp deleted file mode 100644 index 00067a6d26d..00000000000 --- a/src/tools/kdb/set.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @file - * - * @brief - * - * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) - */ - -#include - -#include -#include -#include - -#include - -using namespace std; -using namespace kdb; - -SetCommand::SetCommand () -{ -} - -int SetCommand::execute (Cmdline const & cl) -{ - int argc = cl.arguments.size (); - if (argc != 2) - { - throw invalid_argument ("2 arguments needed"); - } - - std::string value = cl.arguments[1]; - - KeySet conf; - Key k = cl.createKey (0); - std::string name = k.getName (); - Key parentKey = cl.getParentKey (k); - - // do not resume on any get errors - // otherwise the user might break - // the config - kdb.get (conf, parentKey); - - bool cascadingWrite = name[0] == '/'; - - Key key = conf.lookup (name); - - std::ostringstream toprint; - - if (!key && cascadingWrite) - { - cerr << "Aborting: A cascading write to a non-existent key is ambiguous." << endl; - return 12; - } - if (!key) - { - toprint << "Create a new key " << name; - key = Key (name, KEY_END); - toprint << " with string \"" << value << '"' << endl; - key.setString (value); - - if (!key.isValid ()) - { - cerr << "no valid name supplied" << endl; - return 11; - } - conf.append (key); - } - else - { - toprint << "Set string to \"" << value << '"' << endl; - key.setString (value); - } - kdb.set (conf, parentKey); - printWarnings (cerr, parentKey, cl.verbose, cl.debug); - printError (cerr, parentKey, cl.verbose, cl.debug); - - if (cascadingWrite) toprint << "Using name " << key.getName () << std::endl; - if (!cl.quiet) cout << toprint.str (); - - return 0; -} - -SetCommand::~SetCommand () -{ -} diff --git a/src/tools/kdb/set.h b/src/tools/kdb/set.h new file mode 100644 index 00000000000..92912c2b238 --- /dev/null +++ b/src/tools/kdb/set.h @@ -0,0 +1,33 @@ +/** +* @file +* +* @brief KDB set subcommand header +* +* @copyright BSD License (see LICENSE.md or https://www.libelektra.org) +*/ + +#ifndef ELEKTRA_KDB_SET_H +#define ELEKTRA_KDB_SET_H + +#include + +/** +* Adds options specification of set command to keySet +* +* @param spec the base spec where the commands spec should be added +*/ +void addSetSpec (KeySet * spec); + +/** +* Runs the set command +* +* @param options cli options and arguments as specified in addSetSpec() +* @param errorKey key where errors and warnings should be saved +* +* @retval 0 set command ran without errors +* @retval 1 errors occurred, keySetMeta (errorKey, "error/reason") for info +* +*/ +int execSet (KeySet * options, Key * errorKey); + +#endif // ELEKTRA_KDB_SET_H diff --git a/src/tools/kdb/set.hpp b/src/tools/kdb/set.hpp deleted file mode 100644 index c64a909e59d..00000000000 --- a/src/tools/kdb/set.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file - * - * @brief - * - * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) - */ - -#ifndef SET_HPP -#define SET_HPP - -#include "coloredkdbio.hpp" -#include -#include - -class SetCommand : public Command -{ - kdb::KDB kdb; - -public: - SetCommand (); - ~SetCommand (); - - virtual std::string getShortOptions () override - { - return "qf"; - } - - virtual std::string getSynopsis () override - { - return " "; - } - - virtual std::string getShortHelpText () override - { - return "Set the value of an individual key."; - } - - virtual std::string getLongHelpText () override - { - return "To set an empty value you need to quote like \"\" (depending on shell)\n" - "To set a negative value you need to use '--' to stop option processing.\n" - "(e.g. 'kdb set -- /tests/neg -3')\n"; - } - - virtual int execute (Cmdline const & cmdline) override; -}; - -#endif