2 * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
4 * Contact: Mateusz Malicki <m.malicki2@samsung.com>
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License
21 * @author Mateusz Malicki (m.malicki2@samsung.com)
22 * @brief Declaration of CommandLineInterface class
25 #include "command-line-interface.hpp"
38 #include <boost/algorithm/string/predicate.hpp>
39 #include <boost/filesystem.hpp>
41 #include <readline/readline.h>
42 #include <readline/history.h>
44 using namespace vasum::cli;
46 namespace fs = boost::filesystem;
50 static int interactiveMode = 0;
51 std::vector<CommandLineInterface> commands = {
55 "Create and add zone",
56 MODE_COMMAND_LINE | MODE_INTERACTIVE,
57 {{"zone_id", "zone name", ""},
58 {"[zone_tname]", "optional zone template name", ""}}
64 MODE_COMMAND_LINE | MODE_INTERACTIVE,
65 {{"zone_id", "zone name", "{ZONE}"}}
71 MODE_COMMAND_LINE | MODE_INTERACTIVE,
72 {{"zone_id", "zone name", "{ZONE}"}}
77 "Attach to zone text console",
78 MODE_COMMAND_LINE | MODE_INTERACTIVE,
79 {{"zone_id", "zone name", "{ZONE}"}}
85 MODE_COMMAND_LINE | MODE_INTERACTIVE,
86 {{"zone_id", "zone name", "{ZONE}"}}
91 "Suspend (lock) zone",
92 MODE_COMMAND_LINE | MODE_INTERACTIVE,
93 {{"zone_id", "zone name", "{ZONE}"}}
98 "Resume (unlock) zone",
99 MODE_COMMAND_LINE | MODE_INTERACTIVE,
100 {{"zone_id", "zone name", "{ZONE}"}}
105 "Set active (foreground) zone",
106 MODE_COMMAND_LINE | MODE_INTERACTIVE,
107 {{"zone_id", "zone name", "{ZONE}"}}
112 "Get active (foreground) zone",
113 MODE_COMMAND_LINE | MODE_INTERACTIVE,
119 "Get available zone ids",
120 MODE_COMMAND_LINE | MODE_INTERACTIVE,
126 "List status for one or all zones (id, state, terminal, root path)",
127 MODE_COMMAND_LINE | MODE_INTERACTIVE,
128 {{"[zone_id]", "zone name", "{ZONE}"}}
133 "Clean up zones root directory",
134 MODE_COMMAND_LINE | MODE_INTERACTIVE,
140 "Grants access to the given device",
141 MODE_COMMAND_LINE | MODE_INTERACTIVE,
142 {{"zone_id", "zone name", "{ZONE}"},
143 {"device", "device name", ""}}
148 "Revokes access to the given device",
149 MODE_COMMAND_LINE | MODE_INTERACTIVE,
150 {{"zone_id", "zone name", "{ZONE}"},
151 {"device", "device name", ""}}
156 "Create network virtualization for the zone",
157 MODE_COMMAND_LINE | MODE_INTERACTIVE,
159 {"zone_id", "zone name", "{ZONE}"},
160 {"netdevtype", "interface type (veth, macvlan, phys)\n"
161 " veth - create new zone iface and bridge to host\n"
162 "macvlan - create new zone slave iface briged to master with specified mode\n"
163 " phys - move existing iface from host to zone (no way to move it back)",
166 {"zone_netdev", "interface name (eth0)", "eth0|eth1"},
167 {"host_netdev", "bridge name (virbr0)", "virbr0|virbr1"},
168 {"mode", "macvlan mode (private, vepa, bridge, passthru)\n"
169 " private - bridge but no comunicate with otheri vlan\n"
170 " vepa - ethernet switch\n"
171 " bridge - light weight to other vlan\n"
172 "passthru - only one vlan device",
173 "private|vepa|bridge|passthru"}}
178 "Destroy netdev in zone",
179 MODE_COMMAND_LINE | MODE_INTERACTIVE,
180 {{"zone_id", "zone name", "{ZONE}"},
181 {"netdev", "interface name (eth0)", "{NETDEV}"}}
186 "List network devices in the zone",
187 MODE_COMMAND_LINE | MODE_INTERACTIVE,
188 {{"zone_id", "zone name", "{ZONE}"},
189 {"[netdev]", "interface name (eth0)", "{NETDEV}"}}
194 "Setup a network device in the zone up",
195 MODE_COMMAND_LINE | MODE_INTERACTIVE,
196 {{"zone_id", "zone name", "{ZONE}"},
197 {"netdev", "interface name (eth0)", "{NETDEV}"}}
202 "Setup a network device in the zone down",
203 MODE_COMMAND_LINE | MODE_INTERACTIVE,
204 {{"zone_id", "zone name", "{ZONE}"},
205 {"netdev", "interface name (eth0)", "{NETDEV}"}}
210 "Add ip/mask address to network interface",
211 MODE_COMMAND_LINE | MODE_INTERACTIVE,
212 {{"zone_id", "zone name", "{ZONE}"},
213 {"netdev", "interface name (eth0)", "{NETDEV}"},
214 {"ip", "address IPv4 or IPv6", ""},
215 {"prefix", "mask length in bits", "24"}}
220 "Del ip/mask address from network interface",
221 MODE_COMMAND_LINE | MODE_INTERACTIVE,
222 {{"zone_id", "zone name", "{ZONE}"},
223 {"netdev", "interface name (eth0)", "{NETDEV}"},
224 {"ip", "address IPv4 or IPv6", ""},
225 {"prefix", "mask length in bits", "24"}}
230 "Exclusively lock the command queue",
243 std::map<std::string,const CommandLineInterface> commandMap;
245 // wrappers for CommandLineInterface
247 void printUsage(std::ostream& out, const std::string& name, unsigned int mode)
249 const std::vector<std::string> addLineBefore = {"device-grant", "net-create", "qlock"};
255 out << "Usage: " << n << "[-h|help|-f <filename>|[<command> [-h|help|<args>]]]\n\n";
256 if (mode == MODE_COMMAND_LINE) {
257 out << "Description:\n"
258 << "\tCommand line tool to manage vasum containers.\n"
259 << "\tCalled without parameters enters interactive mode.\n"
261 << "\t-h,help print this help\n"
262 << "\t-f <filename> read and execute commands from file\n\n";
264 out << "command can be one of the following:\n";
266 for (const auto& command : commands) {
267 if (command.isAvailable(mode)) {
268 if (std::find(addLineBefore.begin(), addLineBefore.end(), command.getName()) != addLineBefore.end()) {
271 out << " " << std::setw(20) << std::left << command.getName();
272 const std::string& d = command.getDescription();
273 std::stringstream ss(d);
275 std::getline(ss, item);
276 out << item << std::endl;
280 out << "\nType '" << n << "command help' to read about a specific one.\n";
286 CommandLineInterface::connect();
287 } catch (const std::runtime_error& ex) {
288 std::cerr << ex.what() << std::endl;
298 CommandLineInterface::disconnect();
299 } catch (const std::runtime_error& ex) {
300 std::cerr << ex.what() << std::endl;
307 int executeCommand(const Args& argv, int mode)
309 auto pair = commandMap.find(argv[0]);
310 if (pair == commandMap.end()) {
314 const CommandLineInterface& command = pair->second;
315 if (!command.isAvailable(mode)) {
316 std::cerr << "Command not available in this mode" << std::endl;
320 if (argv.size() > 1 && (argv[1] == "-h" || argv[1] == "help")) {
321 command.printUsage(std::cout);
326 command.execute(argv);
327 } catch (const std::runtime_error& ex) {
328 std::cerr << ex.what() << std::endl;
335 // readline completion support
336 const std::vector<std::string> buildComplList(const Args& argv);
337 char *completion_generator(const char* text, int state)
339 static std::vector<std::string> list;
340 static unsigned index = 0;
345 char *ln = rl_line_buffer;
346 std::istringstream iss(ln);
347 Args argv{std::istream_iterator<std::string>{iss},
348 std::istream_iterator<std::string>{}};
350 size_t len = strlen(text);
351 if (len == 0 && argv.size() > 0) {
355 const std::vector<std::string>& l = buildComplList(argv);
356 for (const auto &i : l) {
357 if (strncmp(text, i.c_str(), len) == 0) {
362 if (index < list.size()) {
363 return ::strdup(list[index++].c_str());
369 char** completion(const char* text, int /*start*/, int /*end*/)
371 ::rl_attempted_completion_over = 1; //disable default completion
372 return ::rl_completion_matches(text, &completion_generator);
375 static bool readline_from(const std::string& prompt, std::istream& stream, std::string& ln)
377 if (interactiveMode) {
378 char *cmd = ::readline(prompt.c_str());
385 std::getline(stream, ln);
386 if (!stream.good()) {
391 if (interactiveMode && !ln.empty()) {
392 ::add_history(ln.c_str());
398 static int processStream(std::istream& stream)
400 if (connect() != EXIT_SUCCESS) {
404 int rc = EXIT_FAILURE;
406 while (readline_from("vsm> ", stream, ln)) {
407 if (ln.empty() || ln[0] == '#') { //skip empty line or comment
411 std::istringstream iss(ln);
412 Args argv{std::istream_iterator<std::string>{iss},
413 std::istream_iterator<std::string>{}};
415 if (commandMap.count(argv[0]) == 0) {
416 printUsage(std::cout, "", MODE_INTERACTIVE);
420 rc = executeCommand(argv, MODE_INTERACTIVE);
421 if (rc == EXIT_FAILURE && !interactiveMode) {
429 static int processFile(const std::string& fn)
431 std::ifstream stream(fn);
432 if (!stream.good()) {
433 //TODO: Turn on exceptions on stream (and in the entire file)
434 std::cerr << "Can't open file " << fn << std::endl;
438 return processStream(stream);
441 void printList(const std::vector<std::string>& list)
443 for (const auto& i : list) {
444 std::cout << i << std::endl;
448 const std::vector<std::string> buildComplList(const Args& argv)
450 if (argv.size() < 2) {
451 std::vector<std::string> list;
452 for (const auto& command : commands) {
453 if (command.isAvailable(MODE_COMMAND_LINE)) {
454 list.push_back(command.getName());
459 std::string cmd = argv[0];
460 if (commandMap.find(cmd) != commandMap.end()) {
461 return commandMap[cmd].buildCompletionList(argv);
463 return std::vector<std::string>();
467 int bashComplMode(int argc, const char *argv[])
469 int rc = EXIT_FAILURE;
471 Args args(argv, argv + argc);
472 printList(buildComplList(args));
474 } catch (const std::runtime_error& ex) {
480 int cliMode(const int argc, const char** argv)
482 if (std::string(argv[1]) == "-h" || std::string(argv[1]) == "help") {
483 printUsage(std::cout, argv[0], MODE_COMMAND_LINE);
487 if (commandMap.find(argv[1]) == commandMap.end()) {
488 printUsage(std::cout, argv[0], MODE_COMMAND_LINE);
492 // pass all the arguments excluding argv[0] - the executable name
493 Args commandArgs(argv + 1, argv + argc);
494 int rc = executeCommand(commandArgs, MODE_COMMAND_LINE);
499 fs::path getHomePath() {
500 const char *h = ::getenv("HOME");
501 return fs::path(h ? h : "");
508 int main(const int argc, const char *argv[])
510 for (const auto& command : commands) {
511 commandMap.insert(std::pair<std::string,const CommandLineInterface>(command.getName(),command));
514 int rc = EXIT_FAILURE;
518 if (std::string(argv[1]) == "--bash-completion") {
519 rc = bashComplMode(argc - 2, argv + 2);
520 } else if (std::string(argv[1]) == "-f") {
522 std::cerr << "Filename expected" << std::endl;
526 rc = processFile(std::string(argv[2]));
529 rc = cliMode(argc, argv);
533 fs::path historyfile(".vsm_history");
535 if (isatty(0) == 1) {
536 fs::path home = getHomePath();
538 historyfile = home / historyfile;
540 ::read_history(historyfile.c_str());
543 ::rl_attempted_completion_function = completion;
546 rc = processStream(std::cin);
548 if (interactiveMode) {
549 ::write_history(historyfile.c_str());
550 std::cout << std::endl; // finish prompt line