From dff76da590ee1bf8da6ae6266e8c2b796f36c639 Mon Sep 17 00:00:00 2001 From: Lukasz Pawelczyk Date: Mon, 22 Jun 2015 14:03:15 +0200 Subject: [PATCH] Interactive mode and other improvements to vasum-cli tool [Feature] These features have been implemented/updated: - interactive mode - persistent connection during interactive mode - better help - simple mode for cli auto-completion The interactive mode allows to make full use of queue lock/unlock commands. They can also be easily tested with it. [Cause] n/a [Solution] n/a [Verification] Run vasum-cli and executed several commands. Tested auto completion. Tested lock/unlock queue. Change-Id: If623559c9250bf480cc600ba89c2d5fe02137a18 --- cli/CMakeLists.txt | 17 ++ cli/command-line-interface.cpp | 345 +++++++++++++++++++-------------- cli/command-line-interface.hpp | 133 +++++++++---- cli/main.cpp | 323 ++++++++++++++++++++++++++---- cli/support/vasum-cli-completion.sh.in | 2 +- packaging/vasum.spec | 1 + 6 files changed, 605 insertions(+), 216 deletions(-) diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index f8ad183..36750f0 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -25,6 +25,23 @@ FILE(GLOB cli_SRCS *.cpp *.hpp) SET(CLI_CODENAME "${PROJECT_NAME}-cli") ADD_EXECUTABLE(${CLI_CODENAME} ${cli_SRCS}) +## Readline detection ########################################################## + +FIND_PATH(READLINE_INCLUDE_DIR readline/readline.h + HINTS ${READLINE_ROOT_DIR} PATH_SUFFIXES include) +FIND_LIBRARY(READLINE_LIBRARY readline + HINTS ${READLINE_ROOT_DIR} PATH_SUFFIXES lib) +FIND_LIBRARY(NCURSES_LIBRARY ncurses) # readline depends on libncurses +MARK_AS_ADVANCED(READLINE_INCLUDE_DIR READLINE_LIBRARY NCURSES_LIBRARY) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG + READLINE_LIBRARY NCURSES_LIBRARY READLINE_INCLUDE_DIR) + +SET(READLINE_INCLUDE_DIRS ${READLINE_INCLUDE_DIR}) +SET(READLINE_LIBRARIES ${READLINE_LIBRARY} ${NCURSES_LIBRARY}) +INCLUDE_DIRECTORIES(${READLINE_INCLUDE_DIRS}) +TARGET_LINK_LIBRARIES(${CLI_CODENAME} ${READLINE_LIBRARIES}) ## Link libraries ############################################################## PKG_CHECK_MODULES(LIB_DEPS REQUIRED vasum) diff --git a/cli/command-line-interface.cpp b/cli/command-line-interface.cpp index a8b7d1b..2b56594 100644 --- a/cli/command-line-interface.cpp +++ b/cli/command-line-interface.cpp @@ -45,43 +45,9 @@ using namespace std; namespace vasum { namespace cli { -namespace { - -/** - * Invoke specific function on VsmClient - * - * @param fun Function to be called. It must not throw any exception. - */ -void one_shot(const function& fun) -{ - string msg; - VsmStatus status; - VsmClient client; - - client = vsm_client_create(); - if (NULL == client) { - msg = "Can't create client"; - goto finish; - } - - status = vsm_connect(client); - if (VSMCLIENT_SUCCESS != status) { - msg = vsm_get_status_message(client); - goto finish; - } - - status = fun(client); - if (VSMCLIENT_SUCCESS != status) { - msg = vsm_get_status_message(client); - goto finish; - } +VsmClient CommandLineInterface::client = NULL; -finish: - vsm_client_free(client); - if (! msg.empty()) { - throw runtime_error(msg); - } -} +namespace { template string stringAsInStream(const T& value) @@ -181,11 +147,83 @@ enum macvlan_mode macvlanFromString(const std::string& mode) { } // namespace +void CommandLineInterface::connect() +{ + string msg; + VsmStatus status; + + CommandLineInterface::client = vsm_client_create(); + if (NULL == CommandLineInterface::client) { + msg = "Can't create client"; + goto finish; + } + + status = vsm_connect(client); + if (VSMCLIENT_SUCCESS != status) { + msg = vsm_get_status_message(CommandLineInterface::client); + } + +finish: + if (!msg.empty()) { + vsm_client_free(CommandLineInterface::client); + CommandLineInterface::client = NULL; + throw runtime_error(msg); + } +} + +void CommandLineInterface::disconnect() +{ + string msg; + VsmStatus status; + + status = vsm_disconnect(CommandLineInterface::client); + if (VSMCLIENT_SUCCESS != status) { + msg = vsm_get_status_message(CommandLineInterface::client); + } + + vsm_client_free(CommandLineInterface::client); + CommandLineInterface::client = NULL; + + if (!msg.empty()) { + throw runtime_error(msg); + } +} + +void CommandLineInterface::executeCallback(const function& fun) +{ + string msg; + VsmStatus status; + + status = fun(CommandLineInterface::client); + if (VSMCLIENT_SUCCESS != status) { + msg = vsm_get_status_message(CommandLineInterface::client); + } + + if (!msg.empty()) { + throw runtime_error(msg); + } +} +const std::string& CommandLineInterface::getName() const +{ + return mName; +} + +const std::string& CommandLineInterface::getDescription() const +{ + return mDescription; +} + void CommandLineInterface::printUsage(std::ostream& out) const { - out << mUsage << "\n\n" + out << mName; + for (const auto& args : mArgsSpec) { + out << " " << args.first; + } + + out << "\n\n" << "\tDescription\n" - << "\t\t" << mUsageInfo << "\n"; + << "\t\t" << mDescription << "\n"; + if (!mArgsSpec.empty()) { out << "\n\tOptions\n"; for (const auto& args : mArgsSpec) { @@ -195,90 +233,109 @@ void CommandLineInterface::printUsage(std::ostream& out) const out << "\n"; } -void CommandLineInterface::execute(int pos, int argc, const char** argv) +bool CommandLineInterface::isAvailable(unsigned int mode) const { - mExecutorCallback(pos, argc, argv); + return (mAvailability & mode) == mode; } +void CommandLineInterface::execute(const Args& argv) +{ + mExecutorCallback(argv); +} + + +void lock_queue(const Args& /*argv*/) +{ + CommandLineInterface::executeCallback(vsm_lock_queue); +} -void set_active_zone(int pos, int argc, const char** argv) +void unlock_queue(const Args& /*argv*/) +{ + CommandLineInterface::executeCallback(vsm_unlock_queue); +} + +void set_active_zone(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 1) { + if (argv.size() <= 1) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_set_active_zone, _1, argv[pos + 1])); + CommandLineInterface::executeCallback(bind(vsm_set_active_zone, _1, argv[1].c_str())); } -void create_zone(int pos, int argc, const char** argv) +void create_zone(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 1) { + if (argv.size() <= 1) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_create_zone, _1, argv[pos + 1], argv[pos + 2] ? argv[pos + 2] : nullptr)); + if (argv.size() >= 3 && !argv[2].empty()) { + CommandLineInterface::executeCallback(bind(vsm_create_zone, _1, argv[1].c_str(), argv[2].c_str())); + } else { + CommandLineInterface::executeCallback(bind(vsm_create_zone, _1, argv[1].c_str(), nullptr)); + } } -void destroy_zone(int pos, int argc, const char** argv) +void destroy_zone(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 1) { + if (argv.size() <= 1) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_destroy_zone, _1, argv[pos + 1], 1)); + CommandLineInterface::executeCallback(bind(vsm_destroy_zone, _1, argv[1].c_str(), 1)); } -void shutdown_zone(int pos, int argc, const char** argv) +void shutdown_zone(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 1) { + if (argv.size() <= 1) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_shutdown_zone, _1, argv[pos + 1])); + CommandLineInterface::executeCallback(bind(vsm_shutdown_zone, _1, argv[1].c_str())); } -void start_zone(int pos, int argc, const char** argv) +void start_zone(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 1) { + if (argv.size() <= 1) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_start_zone, _1, argv[pos + 1])); + CommandLineInterface::executeCallback(bind(vsm_start_zone, _1, argv[1].c_str())); } -void lock_zone(int pos, int argc, const char** argv) +void lock_zone(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 1) { + if (argv.size() <= 1) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_lock_zone, _1, argv[pos + 1])); + CommandLineInterface::executeCallback(bind(vsm_lock_zone, _1, argv[1].c_str())); } -void unlock_zone(int pos, int argc, const char** argv) +void unlock_zone(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 1) { + if (argv.size() <= 1) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_unlock_zone, _1, argv[pos + 1])); + CommandLineInterface::executeCallback(bind(vsm_unlock_zone, _1, argv[1].c_str())); } -void get_zones_status(int /* pos */, int /* argc */, const char** /* argv */) +void get_zones_status(const Args& /* argv */) { using namespace std::placeholders; @@ -286,12 +343,12 @@ void get_zones_status(int /* pos */, int /* argc */, const char** /* argv */) VsmString activeId; Table table; - one_shot(bind(vsm_get_zone_ids, _1, &ids)); - one_shot(bind(vsm_get_active_zone_id, _1, &activeId)); + CommandLineInterface::executeCallback(bind(vsm_get_zone_ids, _1, &ids)); + CommandLineInterface::executeCallback(bind(vsm_get_active_zone_id, _1, &activeId)); table.push_back({"Active", "Id", "State", "Terminal", "Root"}); for (VsmString* id = ids; *id; ++id) { VsmZone zone; - one_shot(bind(vsm_lookup_zone_by_id, _1, *id, &zone)); + CommandLineInterface::executeCallback(bind(vsm_lookup_zone_by_id, _1, *id, &zone)); assert(string(zone->id) == string(*id)); table.push_back({string(zone->id) == string(activeId) ? "*" : "", zone->id, @@ -305,12 +362,12 @@ void get_zones_status(int /* pos */, int /* argc */, const char** /* argv */) cout << table << endl; } -void get_zone_ids(int /* pos */, int /* argc */, const char** /* argv */) +void get_zone_ids(const Args& /*argv*/) { using namespace std::placeholders; VsmArrayString ids; - one_shot(bind(vsm_get_zone_ids, _1, &ids)); + CommandLineInterface::executeCallback(bind(vsm_get_zone_ids, _1, &ids)); string delim; for (VsmString* id = ids; *id; ++id) { cout << delim << *id; @@ -320,136 +377,136 @@ void get_zone_ids(int /* pos */, int /* argc */, const char** /* argv */) vsm_array_string_free(ids); } -void get_active_zone_id(int /* pos */, int /* argc */, const char** /* argv */) +void get_active_zone_id(const Args& /*argv*/) { using namespace std::placeholders; VsmString id; - one_shot(bind(vsm_get_active_zone_id, _1, &id)); + CommandLineInterface::executeCallback(bind(vsm_get_active_zone_id, _1, &id)); cout << id << endl; vsm_string_free(id); } -void lookup_zone_by_id(int pos, int argc, const char** argv) +void lookup_zone_by_id(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 1) { + if (argv.size() <= 1) { throw runtime_error("Not enough parameters"); } VsmZone zone; - one_shot(bind(vsm_lookup_zone_by_id, _1, argv[pos + 1], &zone)); + CommandLineInterface::executeCallback(bind(vsm_lookup_zone_by_id, _1, argv[1].c_str(), &zone)); cout << zone << endl; vsm_zone_free(zone); } -void grant_device(int pos, int argc, const char** argv) +void grant_device(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 2) { + if (argv.size() <= 2) { throw runtime_error("Not enough parameters"); } uint32_t flags = O_RDWR; - one_shot(bind(vsm_grant_device, _1, argv[pos + 1], argv[pos + 2], flags)); + CommandLineInterface::executeCallback(bind(vsm_grant_device, _1, argv[1].c_str(), argv[2].c_str(), flags)); } -void revoke_device(int pos, int argc, const char** argv) +void revoke_device(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 2) { + if (argv.size() <= 2) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_revoke_device, _1, argv[pos + 1], argv[pos + 2])); + CommandLineInterface::executeCallback(bind(vsm_revoke_device, _1, argv[1].c_str(), argv[2].c_str())); } -void create_netdev_veth(int pos, int argc, const char** argv) +void create_netdev_veth(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 3) { + if (argv.size() <= 3) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_create_netdev_veth, + CommandLineInterface::executeCallback(bind(vsm_create_netdev_veth, _1, - argv[pos + 1], - argv[pos + 2], - argv[pos + 3])); + argv[1].c_str(), + argv[2].c_str(), + argv[3].c_str())); } -void create_netdev_macvlan(int pos, int argc, const char** argv) +void create_netdev_macvlan(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 4) { + if (argv.size() <= 4) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_create_netdev_macvlan, + CommandLineInterface::executeCallback(bind(vsm_create_netdev_macvlan, _1, - argv[pos + 1], - argv[pos + 2], - argv[pos + 3], - macvlanFromString(argv[pos + 4]))); + argv[1].c_str(), + argv[2].c_str(), + argv[3].c_str(), + macvlanFromString(argv[4].c_str()))); } -void create_netdev_phys(int pos, int argc, const char** argv) +void create_netdev_phys(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 2) { + if (argv.size() <= 2) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_create_netdev_phys, + CommandLineInterface::executeCallback(bind(vsm_create_netdev_phys, _1, - argv[pos + 1], - argv[pos + 2])); + argv[1].c_str(), + argv[2].c_str())); } -void lookup_netdev_by_name(int pos, int argc, const char** argv) +void lookup_netdev_by_name(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 2) { + if (argv.size() <= 2) { throw runtime_error("Not enough parameters"); } VsmNetdev vsmNetdev = NULL; - one_shot(bind(vsm_lookup_netdev_by_name, + CommandLineInterface::executeCallback(bind(vsm_lookup_netdev_by_name, _1, - argv[pos + 1], - argv[pos + 2], + argv[1].c_str(), + argv[2].c_str(), &vsmNetdev)); cout << vsmNetdev << endl; vsm_netdev_free(vsmNetdev); } -void destroy_netdev(int pos, int argc, const char** argv) +void destroy_netdev(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 2) { + if (argv.size() <= 2) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_destroy_netdev, + CommandLineInterface::executeCallback(bind(vsm_destroy_netdev, _1, - argv[pos + 1], - argv[pos + 2])); + argv[1].c_str(), + argv[2].c_str())); } -void zone_get_netdevs(int pos, int argc, const char** argv) +void zone_get_netdevs(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 1) { + if (argv.size() <= 1) { throw runtime_error("Not enough parameters"); } VsmArrayString ids; - one_shot(bind(vsm_zone_get_netdevs, + CommandLineInterface::executeCallback(bind(vsm_zone_get_netdevs, _1, - argv[pos + 1], + argv[1].c_str(), &ids)); string delim; for (VsmString* id = ids; *id; ++id) { @@ -463,18 +520,18 @@ void zone_get_netdevs(int pos, int argc, const char** argv) vsm_array_string_free(ids); } -void netdev_get_ipv4_addr(int pos, int argc, const char** argv) +void netdev_get_ipv4_addr(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 2) { + if (argv.size() <= 2) { throw runtime_error("Not enough parameters"); } in_addr addr; - one_shot(bind(vsm_netdev_get_ipv4_addr, + CommandLineInterface::executeCallback(bind(vsm_netdev_get_ipv4_addr, _1, - argv[pos + 1], - argv[pos + 2], + argv[1].c_str(), + argv[2].c_str(), &addr)); char buf[INET_ADDRSTRLEN]; if (inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN) == NULL) { @@ -483,18 +540,18 @@ void netdev_get_ipv4_addr(int pos, int argc, const char** argv) cout << buf << endl; } -void netdev_get_ipv6_addr(int pos, int argc, const char** argv) +void netdev_get_ipv6_addr(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 2) { + if (argv.size() <= 2) { throw runtime_error("Not enough parameters"); } in6_addr addr; - one_shot(bind(vsm_netdev_get_ipv6_addr, + CommandLineInterface::executeCallback(bind(vsm_netdev_get_ipv6_addr, _1, - argv[pos + 1], - argv[pos + 2], + argv[1].c_str(), + argv[2].c_str(), &addr)); char buf[INET6_ADDRSTRLEN]; if (inet_ntop(AF_INET6, &addr, buf, INET6_ADDRSTRLEN) == NULL) { @@ -503,68 +560,68 @@ void netdev_get_ipv6_addr(int pos, int argc, const char** argv) cout << buf << endl; } -void netdev_set_ipv4_addr(int pos, int argc, const char** argv) +void netdev_set_ipv4_addr(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 4) { + if (argv.size() <= 4) { throw runtime_error("Not enough parameters"); } in_addr addr; - if (inet_pton(AF_INET, argv[pos + 3], &addr) != 1) { + if (inet_pton(AF_INET, argv[3].c_str(), &addr) != 1) { throw runtime_error("Wrong address format"); }; - one_shot(bind(vsm_netdev_set_ipv4_addr, + CommandLineInterface::executeCallback(bind(vsm_netdev_set_ipv4_addr, _1, - argv[pos + 1], - argv[pos + 2], + argv[1].c_str(), + argv[2].c_str(), &addr, - stoi(argv[pos + 4]))); + stoi(argv[4].c_str()))); } -void netdev_set_ipv6_addr(int pos, int argc, const char** argv) +void netdev_set_ipv6_addr(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 4) { + if (argv.size() <= 4) { throw runtime_error("Not enough parameters"); } in6_addr addr; - if (inet_pton(AF_INET6, argv[pos + 3], &addr) != 1) { + if (inet_pton(AF_INET6, argv[3].c_str(), &addr) != 1) { throw runtime_error("Wrong address format"); }; - one_shot(bind(vsm_netdev_set_ipv6_addr, + CommandLineInterface::executeCallback(bind(vsm_netdev_set_ipv6_addr, _1, - argv[pos + 1], - argv[pos + 2], + argv[1].c_str(), + argv[2].c_str(), &addr, - stoi(argv[pos + 4]))); + stoi(argv[4].c_str()))); } -void netdev_up(int pos, int argc, const char** argv) +void netdev_up(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 2) { + if (argv.size() <= 2) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_netdev_up, + CommandLineInterface::executeCallback(bind(vsm_netdev_up, _1, - argv[pos + 1], - argv[pos + 2])); + argv[1].c_str(), + argv[2].c_str())); } -void netdev_down(int pos, int argc, const char** argv) +void netdev_down(const Args& argv) { using namespace std::placeholders; - if (argc <= pos + 2) { + if (argv.size() <= 2) { throw runtime_error("Not enough parameters"); } - one_shot(bind(vsm_netdev_down, + CommandLineInterface::executeCallback(bind(vsm_netdev_down, _1, - argv[pos + 1], - argv[pos + 2])); + argv[1].c_str(), + argv[2].c_str())); } } // namespace cli diff --git a/cli/command-line-interface.hpp b/cli/command-line-interface.hpp index 6e27c74..29282d9 100644 --- a/cli/command-line-interface.hpp +++ b/cli/command-line-interface.hpp @@ -24,6 +24,8 @@ #ifndef CLI_COMMAND_LINE_INTERFACE_HPP #define CLI_COMMAND_LINE_INTERFACE_HPP +#include + #include #include #include @@ -33,6 +35,11 @@ namespace vasum { namespace cli { +#define MODE_COMMAND_LINE (1 << 0) +#define MODE_INTERACTIVE (1 << 1) + +typedef std::vector Args; + /** * Class that implements command pattern. */ @@ -42,7 +49,7 @@ public: /** * @see CommandLineInterface::execute */ - typedef std::function ExecutorCallback; + typedef std::function ExecutorCallback; /** * @see CommandLineInterface::CommandLineInterface @@ -52,7 +59,8 @@ public: /** * Dummy constructor (for stl usage) */ - CommandLineInterface() {} + CommandLineInterface() + : mAvailability(0) {} /** * Construct command @@ -63,15 +71,48 @@ public: * @param argsSpec Description of arguments */ CommandLineInterface(const ExecutorCallback& executorCallback, - const std::string& usage, - const std::string& usageInfo, + const std::string& name, + const std::string& description, + const unsigned int& availability, const ArgsSpec& argsSpec) : mExecutorCallback(executorCallback), - mUsage(usage), - mUsageInfo(usageInfo), + mName(name), + mDescription(description), + mAvailability(availability), mArgsSpec(argsSpec) {} /** + * Set the class (static) in a connected state. + * This persistent connection to the vasum client is required + * for calls like lock/unlock queue to work. + */ + static void connect(); + + /** + * Disconnect the class from a vasum client. + */ + static void disconnect(); + + /** + * Execute a callback passing the connected VsmClient. + * + * @param fun A callback to execute + */ + static void executeCallback(const std::function& fun); + + /** + * Get the command name + */ + const std::string& getName() const; + + /** + * Get the command description + * + * @param out Output stream + */ + const std::string& getDescription() const; + + /** * Print usage to stream * * @param out Output stream @@ -79,6 +120,13 @@ public: void printUsage(std::ostream& out) const; /** + * Check if the command is available in specific mode + * + * @param mode A mode to check the command's availability in + */ + bool isAvailable(unsigned int mode) const; + + /** * Do the work * * It calls the callback passed in constructor @@ -87,13 +135,16 @@ public: * @param argc Number of elements in argv * @param argv Command line arguments */ - void execute(int pos, int argc, const char** argv); + void execute(const Args& argv); private: + static VsmClient client; + const ExecutorCallback mExecutorCallback; - const std::string mUsage; - const std::string mUsageInfo; + const std::string mName; + const std::string mDescription; + const unsigned int mAvailability; const ArgsSpec mArgsSpec; }; @@ -102,174 +153,188 @@ private: * * @see vsm_set_active_zone */ -void set_active_zone(int pos, int argc, const char** argv); +void lock_queue(const Args& argv); + +/** + * Parses command line arguments and call vsm_set_active_zone + * + * @see vsm_set_active_zone + */ +void unlock_queue(const Args& argv); + +/** + * Parses command line arguments and call vsm_set_active_zone + * + * @see vsm_set_active_zone + */ +void set_active_zone(const Args& argv); /** * Parses command line arguments and call vsm_create_zone * * @see vsm_create_zone */ -void create_zone(int pos, int argc, const char** argv); +void create_zone(const Args& argv); /** * Parses command line arguments and call vsm_destroy_zone * * @see vsm_destroy_zone */ -void destroy_zone(int pos, int argc, const char** argv); +void destroy_zone(const Args& argv); /** * Parses command line arguments and call vsm_shutdown_zone * * @see vsm_shutdown_zone */ -void shutdown_zone(int pos, int argc, const char** argv); +void shutdown_zone(const Args& argv); /** * Parses command line arguments and call vsm_start_zone * * @see vsm_start_zone */ -void start_zone(int pos, int argc, const char** argv); +void start_zone(const Args& argv); /** * Parses command line arguments and call vsm_lock_zone * * @see vsm_lock_zone */ -void lock_zone(int pos, int argc, const char** argv); +void lock_zone(const Args& argv); /** * Parses command line arguments and call vsm_unlock_zone * * @see vsm_unlock_zone */ -void unlock_zone(int pos, int argc, const char** argv); +void unlock_zone(const Args& argv); /** * Parses command line arguments and prints list of zone with * some useful informations (id, state, terminal, root path) */ -void get_zones_status(int pos, int argc, const char** argv); +void get_zones_status(const Args& argv); /** * Parses command line arguments and call vsm_get_zone_ids * * @see vsm_get_zone_ids */ -void get_zone_ids(int pos, int argc, const char** argv); +void get_zone_ids(const Args& argv); /** * Parses command line arguments and call vsm_get_active_zone_id * * @see vsm_get_active_zone_id */ -void get_active_zone_id(int pos, int argc, const char** argv); +void get_active_zone_id(const Args& argv); /** * Parses command line arguments and call vsm_lookup_zone_by_id * * @see vsm_lookup_zone_by_id */ -void lookup_zone_by_id(int pos, int argc, const char** argv); +void lookup_zone_by_id(const Args& argv); /** * Parses command line arguments and call vsm_grant_device * * @see vsm_grant_device */ -void grant_device(int pos, int argc, const char** argv); +void grant_device(const Args& argv); /** * Parses command line arguments and call vsm_revoke_device * * @see vsm_revoke_device */ -void revoke_device(int pos, int argc, const char** argv); +void revoke_device(const Args& argv); /** * Parses command line arguments and call vsm_create_netdev_veth * * @see vsm_create_netdev_veth */ -void create_netdev_veth(int pos, int argc, const char** argv); +void create_netdev_veth(const Args& argv); /** * Parses command line arguments and call vsm_create_netdev_macvlan * * @see vsm_create_netdev_macvlan */ -void create_netdev_macvlan(int pos, int argc, const char** argv); +void create_netdev_macvlan(const Args& argv); /** * Parses command line arguments and call vsm_create_netdev_phys * * @see vsm_create_netdev_phys */ -void create_netdev_phys(int pos, int argc, const char** argv); +void create_netdev_phys(const Args& argv); /** * Parses command line arguments and call vsm_lookup_netdev_by_name * * @see vsm_lookup_netdev_by_name */ -void lookup_netdev_by_name(int pos, int argc, const char** argv); +void lookup_netdev_by_name(const Args& argv); /** * Parses command line arguments and call vsm_destroy_netdev * * @see vsm_destroy_netdev */ -void destroy_netdev(int pos, int argc, const char** argv); +void destroy_netdev(const Args& argv); /** * Parses command line arguments and prints result of vsm_zone_get_netdevs * * @see vsm_zone_get_netdevs */ -void zone_get_netdevs(int pos, int argc, const char** argv); +void zone_get_netdevs(const Args& argv); /** * Parses command line arguments and prints result of vsm_netdev_get_ipv4_addr * * @see vsm_netdev_get_ipv4_addr */ -void netdev_get_ipv4_addr(int pos, int argc, const char** argv); +void netdev_get_ipv4_addr(const Args& argv); /** * Parses command line arguments and and prints result of vsm_netdev_get_ipv6_addr * * @see vsm_netdev_get_ipv6_addr */ -void netdev_get_ipv6_addr(int pos, int argc, const char** argv); +void netdev_get_ipv6_addr(const Args& argv); /** * Parses command line arguments and call vsm_netdev_set_ipv4_addr * * @see vsm_netdev_set_ipv4_addr */ -void netdev_set_ipv4_addr(int pos, int argc, const char** argv); +void netdev_set_ipv4_addr(const Args& argv); /** * Parses command line arguments and call vsm_netdev_set_ipv6_addr * * @see vsm_netdev_set_ipv6_addr */ -void netdev_set_ipv6_addr(int pos, int argc, const char** argv); +void netdev_set_ipv6_addr(const Args& argv); /** * Parses command line arguments and call vsm_netdev_up * * @see vsm_netdev_up */ -void netdev_up(int pos, int argc, const char** argv); +void netdev_up(const Args& argv); /** * Parses command line arguments and call vsm_netdev_down * * @see vsm_netdev_down */ -void netdev_down(int pos, int argc, const char** argv); +void netdev_down(const Args& argv); } // namespace cli } // namespace vasum diff --git a/cli/main.cpp b/cli/main.cpp index af36c74..dcb6891 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -30,6 +30,11 @@ #include #include #include +#include +#include +#include +#include +#include using namespace vasum::cli; @@ -37,18 +42,38 @@ namespace { std::map commands = { { + "lock_queue", { + lock_queue, + "lock_queue", + "Exclusively lock the command queue", + MODE_INTERACTIVE, + {} + } + }, + { + "unlock_queue", { + unlock_queue, + "unlock_queue", + "Unlock the queue", + MODE_INTERACTIVE, + {} + } + }, + { "set_active_zone", { set_active_zone, - "set_active_zone zone_id", + "set_active_zone", "Set active (foreground) zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}} } }, { "create_zone", { create_zone, - "create_zone zone_id [zone_tname]", + "create_zone", "Create and add zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"[zone_tname]", "optional zone template name"}} } @@ -56,40 +81,45 @@ std::map commands = { { "destroy_zone", { destroy_zone, - "destroy_zone zone_id", + "destroy_zone", "Destroy zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}} } }, { "shutdown_zone", { shutdown_zone, - "shutdown_zone zone_id", + "shutdown_zone", "Shutdown zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}} } }, { "start_zone", { start_zone, - "start_zone zone_id", + "start_zone", "Start zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}} } }, { "lock_zone", { lock_zone, - "lock_zone zone_id", + "lock_zone", "Lock zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}} } }, { "unlock_zone", { unlock_zone, - "unlock_zone zone_id", + "unlock_zone", "Unlock zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}} } }, @@ -98,6 +128,7 @@ std::map commands = { get_zones_status, "get_zones_status", "Get list of zone with some useful informations (id, state, terminal, root path)", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {} } }, @@ -106,6 +137,7 @@ std::map commands = { get_zone_ids, "get_zone_ids", "Get all zone ids", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {} } }, @@ -114,22 +146,25 @@ std::map commands = { get_active_zone_id, "get_active_zone_id", "Get active (foreground) zone ids", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {} } }, { "lookup_zone_by_id", { lookup_zone_by_id, - "lookup_zone_by_id zone_id", + "lookup_zone_by_id", "Prints informations about zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}} } }, { "grant_device", { grant_device, - "grant_device zone_id device_name", + "grant_device", "Grants access to the given device", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"device_name", " device name"}} } @@ -137,8 +172,9 @@ std::map commands = { { "revoke_device", { revoke_device, - "revoke_device zone_id device_name", + "revoke_device", "Revokes access to the given device", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"device_name", " device name"}} } @@ -146,8 +182,9 @@ std::map commands = { { "create_netdev_veth", { create_netdev_veth, - "create_netdev_veth zone_id zone_netdev_id host_netdev_id", + "create_netdev_veth", "Create netdev in zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"zone_netdev_id", "network device id"}, {"host_netdev_id", "host bridge id"}} @@ -156,8 +193,9 @@ std::map commands = { { "create_netdev_macvlan", { create_netdev_macvlan, - "create_netdev_macvlan zone_id zone_netdev_id host_netdev_id mode", + "create_netdev_macvlan", "Create netdev in zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"zone_netdev_id", "network device id"}, {"host_netdev_id", "host bridge id"}, @@ -167,8 +205,9 @@ std::map commands = { { "create_netdev_phys", { create_netdev_phys, - "create_netdev_phys zone_id netdev_id", + "create_netdev_phys", "Create/move netdev to zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device name"}} } @@ -176,8 +215,9 @@ std::map commands = { { "lookup_netdev_by_name", { lookup_netdev_by_name, - "lookup_netdev_by_name zone_id netdev_id", + "lookup_netdev_by_name", "Get netdev flags", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device name"}} } @@ -185,8 +225,9 @@ std::map commands = { { "destroy_netdev", { destroy_netdev, - "destroy_netdev zone_id devId", + "destroy_netdev", "Destroy netdev in zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device name"}} } @@ -194,16 +235,18 @@ std::map commands = { { "zone_get_netdevs", { zone_get_netdevs, - "zone_get_netdevs zone_id", + "zone_get_netdevs", "List network devices in the zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}} } }, { "netdev_get_ipv4_addr", { netdev_get_ipv4_addr, - "netdev_get_ipv4_addr zone_id netdev_id", + "netdev_get_ipv4_addr", "Get ipv4 address", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device name"}} } @@ -211,8 +254,9 @@ std::map commands = { { "netdev_get_ipv6_addr", { netdev_get_ipv6_addr, - "netdev_get_ipv6_addr zone_id netdev_id", + "netdev_get_ipv6_addr", "Get ipv6 address", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device name"}} } @@ -220,8 +264,9 @@ std::map commands = { { "netdev_set_ipv4_addr", { netdev_set_ipv4_addr, - "netdev_set_ipv4_addr zone_id netdev_id address prefix_len", + "netdev_set_ipv4_addr", "Set ipv4 address", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device name"}, {"address", "ipv4 address"}, @@ -231,8 +276,9 @@ std::map commands = { { "netdev_set_ipv6_addr", { netdev_set_ipv6_addr, - "netdev_set_ipv6_addr zone_id netdev_id address prefix_len", + "netdev_set_ipv6_addr", "Set ipv6 address", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device name"}, {"address", "ipv6 address"}, @@ -242,8 +288,9 @@ std::map commands = { { "netdev_up", { netdev_up, - "netdev_up zone_id netdev_id", + "netdev_up", "Turn up a network device in the zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device id"}} } @@ -251,8 +298,9 @@ std::map commands = { { "netdev_down", { netdev_down, - "netdev_down zone_id netdev_id", + "netdev_down", "Turn down a network device in the zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device id"}} } @@ -260,50 +308,251 @@ std::map commands = { { "zone_get_netdevs", { zone_get_netdevs, - "zone_get_netdevs zone_id", + "zone_get_netdevs", "Turn down a network device in the zone", + MODE_COMMAND_LINE | MODE_INTERACTIVE, {{"zone_id", "id zone name"}, {"netdev_id", "network device id"}} } } }; -void printUsage(std::ostream& out, const std::string& name) +// wrappers for CommandLineInterface + +void printUsage(std::ostream& out, const std::string& name, unsigned int mode) { - out << "Usage: " << name << " [command [-h|args]]\n\n" - << "command can be one of the following:\n"; + std::string n; + if (!name.empty()) { + n = name + " "; + } + + out << "Usage: " << n << "[] [-h|]\n\n"; + if (mode == MODE_COMMAND_LINE) { + out << "Called without parameters enters interactive mode.\n\n"; + } + out << "command can be one of the following:\n"; for (const auto& command : commands) { - command.second.printUsage(out); + if (command.second.isAvailable(mode)) { + out << " " << std::setw(30) << std::left << command.second.getName() + << command.second.getDescription() << "\n"; + } } + + out << "\nSee " << n << "command -h to read about a specific one.\n"; } -} // namespace +int connect() +{ + try { + CommandLineInterface::connect(); + } catch (const std::runtime_error& ex) { + std::cerr << ex.what() << std::endl; + return EXIT_FAILURE; + } -int main(const int argc, const char** argv) + return EXIT_SUCCESS; +} + +int disconnect() { - if (argc < 2) { - printUsage(std::cout, argv[0]); + try { + CommandLineInterface::disconnect(); + } catch (const std::runtime_error& ex) { + std::cerr << ex.what() << std::endl; return EXIT_FAILURE; } - if (commands.count(argv[1]) == 0) { - printUsage(std::cout, argv[0]); + + return EXIT_SUCCESS; +} + +int executeCommand(const Args& argv, int mode) +{ + auto pair = commands.find(argv[0]); + if (pair == commands.end()) { + return EXIT_FAILURE; + } + + CommandLineInterface& command = pair->second; + if (!command.isAvailable(mode)) { return EXIT_FAILURE; } - CommandLineInterface& command = commands[argv[1]]; - auto it = std::find(argv, argv+argc, std::string("-h")); - if (it != argv + argc) { + auto it = std::find(argv.begin(), argv.end(), std::string("-h")); + if (it != argv.end()) { command.printUsage(std::cout); return EXIT_SUCCESS; } try { - command.execute(1, argc, argv); + command.execute(argv); } catch (const std::runtime_error& ex) { std::cerr << ex.what() << std::endl; return EXIT_FAILURE; } + + return EXIT_SUCCESS; +} + +// readline completion support + +char* object_generator(const char* text, int state) +{ + static std::vector objs; + static size_t len = 0, index = 0; + + if (state == 0) { + objs.clear(); + len = ::strlen(text); + index = 0; + + using namespace std::placeholders; + VsmArrayString ids = NULL; + try { + CommandLineInterface::executeCallback(bind(vsm_get_zone_ids, _1, &ids)); + } catch (const std::runtime_error& ex) { + // Quietly ignore, nothing we can do anyway + } + + if (ids != NULL) { + for (VsmString* id = ids; *id; ++id) { + if (::strncmp(text, *id, len) == 0) { + objs.push_back(*id); + } + } + + vsm_array_string_free(ids); + } + } + + if (index < objs.size()) { + return ::strdup(objs[index++].c_str()); + } + + return NULL; +} + +char* cmd_generator(const char* text, int state) +{ + static std::vector cmds; + static size_t len = 0, index = 0; + + if (state == 0) { + cmds.clear(); + len = ::strlen(text); + index = 0; + + for (const auto& command : commands) { + if (command.second.isAvailable(MODE_INTERACTIVE)) { + const std::string& cmd = command.second.getName(); + if (::strncmp(text, cmd.c_str(), len) == 0) { + cmds.push_back(cmd); + } + } + } + } + + if (index < cmds.size()) { + return ::strdup(cmds[index++].c_str()); + } + + return NULL; +} + +char** completion(const char* text, int start, int /*end*/) +{ + char **matches = NULL; + + if (start == 0) { + matches = ::rl_completion_matches(text, &cmd_generator); + } else { + matches = ::rl_completion_matches(text, &object_generator); + } + + return matches; +} + +// handlers for the modes + +int interactiveMode() +{ + if (connect() != EXIT_SUCCESS) + return EXIT_FAILURE; + + ::rl_attempted_completion_function = completion; + + for (;;) { + char *line = ::readline(">>> "); + + if (line == NULL) { + break; + } + if (line[0] == '\0') { + free(line); + continue; + } + + std::istringstream iss(line); + Args argv{std::istream_iterator{iss}, + std::istream_iterator{}}; + + if (commands.count(argv[0]) == 0) { + printUsage(std::cout, "", MODE_INTERACTIVE); + free(line); + continue; + } + + executeCommand(argv, MODE_INTERACTIVE); + ::add_history(line); + free(line); + } + + disconnect(); return EXIT_SUCCESS; } +int bashComplMode() +{ + for (const auto& command : commands) { + if (command.second.isAvailable(MODE_COMMAND_LINE)) { + std::cout << command.second.getName() << "\n"; + } + } + + return EXIT_SUCCESS; +} + +int cliMode(const int argc, const char** argv) +{ + if (commands.count(argv[1]) == 0) { + printUsage(std::cout, argv[0], MODE_COMMAND_LINE); + return EXIT_FAILURE; + } + + if (connect() != EXIT_SUCCESS) { + return EXIT_FAILURE; + } + + // pass all the arguments excluding argv[0] - the executable name + Args commandArgs(argv+1, argv+argc); + int rc = executeCommand(commandArgs, MODE_COMMAND_LINE); + + disconnect(); + return rc; +} + +} // namespace + + +int main(const int argc, const char** argv) +{ + if (argc == 1) { + return interactiveMode(); + } + + if (std::string(argv[1]) == "--bash-completion") { + return bashComplMode(); + } + + return cliMode(argc, argv); +} diff --git a/cli/support/vasum-cli-completion.sh.in b/cli/support/vasum-cli-completion.sh.in index 18dda54..e04b56f 100755 --- a/cli/support/vasum-cli-completion.sh.in +++ b/cli/support/vasum-cli-completion.sh.in @@ -6,7 +6,7 @@ __@PROJECT_NAME@_cli() { COMPREPLY=() if [ "$COMP_CWORD" == "1" ]; then - COMPREPLY=($(compgen -W "$(@CLI_CODENAME@ | grep --color=never -e '^\S' | tail -n +3 | cut -f1 -d' ')" -- $cur)) + COMPREPLY=($(compgen -W "$(@CLI_CODENAME@ --bash-completion)" -- $cur)) elif [ "$COMP_CWORD" == "2" ]; then COMPREPLY=($(compgen -W "-h" -- $cur)) fi diff --git a/packaging/vasum.spec b/packaging/vasum.spec index 13112b5..acb7ada 100644 --- a/packaging/vasum.spec +++ b/packaging/vasum.spec @@ -23,6 +23,7 @@ BuildRequires: cmake BuildRequires: boost-devel BuildRequires: pkgconfig(glib-2.0) BuildRequires: lxc-devel +BuildRequires: readline-devel Requires: lxc %if %{platform_type} == "TIZEN" Requires: iproute2 -- 2.7.4