diff --git a/doc/tutorials/cascading.md b/doc/tutorials/cascading.md index 5b23106b61c..6024e601d5c 100644 --- a/doc/tutorials/cascading.md +++ b/doc/tutorials/cascading.md @@ -91,41 +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 - -If a value is to be written to a cascading key, i.e., a key starting with '/', only cascading keys that resolve to an existing key will be used. +## Cascading writes are not possible For example, ```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/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 06c3924e05c..6bfd01a9342 100644 --- a/src/tools/kdb/factory.hpp +++ b/src/tools/kdb/factory.hpp @@ -57,7 +57,6 @@ #include #include #include -#include #include #include #include @@ -92,7 +91,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 ("ls", std::make_shared> ())); m_factory.insert (std::make_pair ("cache", std::make_shared> ())); diff --git a/src/tools/kdb/main.c b/src/tools/kdb/main.c index ee305e31466..1e3a7982ca9 100644 --- a/src/tools/kdb/main.c +++ b/src/tools/kdb/main.c @@ -68,6 +68,7 @@ command subcommands[] = { { "get", addGetSpec, execGet }, { "record-start", addRecordStartSpec, execRecordStart }, { "record-undo", addRecordUndoSpec, execRecordUndo }, + { "set", addSetSpec, execSet }, }; cppCommand cppSubcommands[] = { @@ -85,7 +86,6 @@ cppCommand cppSubcommands[] = { { "mv", addMvSpec, execCppMv }, { "namespace", addNamespaceSpec, execCppNamespace }, { "rm", addRmSpec, execCppRm }, - { "set", addSetSpec, execCppSet }, { "sget", addSgetSpec, execCppSget }, { "cache", addCacheSpec, execCppCache }, { "complete", addCompleteSpec, execCppComplete }, diff --git a/src/tools/kdb/set.c b/src/tools/kdb/set.c index b7bc2a03abf..f2cdccd643c 100644 --- a/src/tools/kdb/set.c +++ b/src/tools/kdb/set.c @@ -6,10 +6,14 @@ * @copyright BSD License (see LICENSE.md or https://www.libelektra.org) */ +#include #include +#include +#include +#include #include - -#include +#include +#include #define COMMAND_NAME "set" @@ -31,7 +35,82 @@ void addSetSpec (KeySet * spec) ADD_BASIC_OPTIONS (spec, COMMAND_SPEC_KEY (COMMAND_NAME)) } -int execCppSet (int argc, char ** argv) +int execSet (KeySet * options, Key * errorKey) { - return cpp_main (argc, argv); + 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); + } + + Key * parentKey = getKeyFromOptions (GET_OPTION (options, "name"), errorKey, verbose); + if (parentKey == NULL) + { + RETURN (2) + } + + const char * value = GET_OPTION (options, "value"); + + if (keyGetNamespace (parentKey) == KEY_NS_NONE || keyGetNamespace (parentKey) == KEY_NS_CASCADING) + { + ELEKTRA_SET_VALIDATION_SYNTACTIC_ERROR (errorKey, "key does not specify a namespace"); + 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 (keyName (parentKey), 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", keyName (parentKey), + GET_ERR (parentKey)); + } + keyCopyAllMeta (errorKey, parentKey); + + keyDel (key); + +cleanup: + if (!noNewLine) + { + printf ("\n"); + } + 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 index 586a936836f..5c7c5d4b927 100644 --- a/src/tools/kdb/set.h +++ b/src/tools/kdb/set.h @@ -25,9 +25,9 @@ void addSetSpec (KeySet * spec); * @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 + * @retval >0 errors occurred, keyGetMeta (errorKey, "error/reason") for info * */ -int execCppSet (int argc, char ** argv); +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 diff --git a/tests/shell/shell_recorder/CMakeLists.txt b/tests/shell/shell_recorder/CMakeLists.txt index dda0503ddaa..99579f4d1cb 100644 --- a/tests/shell/shell_recorder/CMakeLists.txt +++ b/tests/shell/shell_recorder/CMakeLists.txt @@ -32,6 +32,7 @@ if (ENABLE_KDB_TESTING) "${CMAKE_CURRENT_BINARY_DIR}/tutorial_wrapper/markdown_shell_recorder.sh" @ONLY) add_shell_recorder_test (cli/cli_get.esr) + add_shell_recorder_test (cli/cli_set.esr) add_shell_recorder_test (db_changes.esr) add_shell_recorder_test (hosts.esr REQUIRED_PLUGINS hosts) # DISABLED add_shell_recorder_test (listtest.esr REQUIRED_PLUGINS dump list) diff --git a/tests/shell/shell_recorder/cli/cli_set.esr b/tests/shell/shell_recorder/cli/cli_set.esr new file mode 100644 index 00000000000..39ccef1de9a --- /dev/null +++ b/tests/shell/shell_recorder/cli/cli_set.esr @@ -0,0 +1,73 @@ +Mountpoint: user:/tests/cli/set + +RET: 0 +STDOUT: Create a new key user:/tests/cli/set/foo with string "bar" +< kdb set $Mountpoint/foo bar + +RET: 0 +STDOUT: Create a new key user:/tests/cli/set/foo/two with string "bar2" +< kdb set $Mountpoint/foo/two bar2 + +RET: 0 +STDOUT: Create a new key user:/tests/cli/set/foo/three with string "bar3" +< kdb set $Mountpoint/foo/three bar3 + +RET: 0 +STDOUT-REGEX: got 3 keys⏎searching user:/tests/cli/set/foo, found: user:/tests/cli/set/foo, options: KDB_O_CALLBACK⏎The resulting keyname is user:/tests/cli/set/foo⏎The resulting value size is 4⏎bar +< kdb get -v $Mountpoint/foo + +STDOUT: bar3 +< kdb get -a $Mountpoint/foo/three + +RET: 0 +STDOUT-REGEX: got [0-9]{2,3} keys⏎searching user:/tests/cli/set/foo/three, found: user:/tests/cli/set/foo/three, options: KDB_O_CALLBACK⏎The resulting keyname is user:/tests/cli/set/foo/three⏎The resulting value size is 5⏎bar3 +< kdb get -av $Mountpoint/foo/three + +RET: 0 +STDOUT-REGEX: got 3 keys⏎searching spec:/tests/cli/set/foo, found: , options: KDB_O_CALLBACK⏎ searching proc:/tests/cli/set/foo, found: , options: KDB_O_CALLBACK⏎ searching dir:/tests/cli/set/foo, found: , options: KDB_O_CALLBACK⏎ searching user:/tests/cli/set/foo, found: user:/tests/cli/set/foo, options: KDB_O_CALLBACK⏎The resulting keyname is user:/tests/cli/set/foo⏎The resulting value size is 4⏎bar +< kdb get -v /tests/cli/set/foo + +RET: 0 +STDOUT: bar3 +< kdb get -a /tests/cli/set/foo/three + +RET: 0 +STDOUT-REGEX: got [0-9]{2,3} keys⏎searching spec:/tests/cli/set/foo/three, found: , options: KDB_O_CALLBACK⏎ searching proc:/tests/cli/set/foo/three, found: , options: KDB_O_CALLBACK⏎ searching dir:/tests/cli/set/foo/three, found: , options: KDB_O_CALLBACK⏎ searching user:/tests/cli/set/foo/three, found: user:/tests/cli/set/foo/three, options: KDB_O_CALLBACK⏎The resulting keyname is user:/tests/cli/set/foo/three⏎The resulting value size is 5⏎bar3 +< kdb get -av /tests/cli/set/foo/three + +RET: 0 +STDOUT: bar3 +< kdb get $Mountpoint/foo/three + +RET: 11 +STDERR: Did not find key 'user:/tests/cli/set/foo/four' +< kdb get $Mountpoint/foo/four + +RET: 11 +STDERR: Did not find key 'user:/tests/cli/set/foo/four' +< kdb get -a $Mountpoint/foo/four + +RET: 11 +STDERR: Did not find key 'user:/tests/cli/set/foo/four' +< kdb get -av $Mountpoint/foo/four + +RET: 11 +STDERR: Did not find key 'user:/tests/cli/set/foo/four' +< kdb get -av $Mountpoint/foo/four + +RET: 0 +< kdb set user:/sw/elektra/kdb/#0/current/bookmarks/gettest user:/tests/cli/set + +RET: 0 +STDOUT: bar3 +< kdb get +gettest/foo/three + +RET: 2 +STDERR: .*'gettest/foo/three' is not valid key name. +< kdb get gettest/foo/three + +RET: 0 +< kdb rm user:/sw/elektra/kdb/#0/current/bookmarks/gettest + +RET: 0 +< kdb rm -r $Mountpoint/