// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*- // Copyright (c) 2001-2007 International Computer Science Institute // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software") // to deal in the Software without restriction, subject to the conditions // listed in the XORP LICENSE file. These conditions include: you must // preserve this copyright notice, and you cannot mention the copyright // holders in advertising related to the Software without their permission. // The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This // notice is a summary of the XORP LICENSE file; the license in that file is // legally binding. #ident "$XORP: xorp/rtrmgr/cli.cc,v 1.137 2007/02/16 22:47:20 pavlin Exp $" #include "rtrmgr_module.h" #include "libxorp/xorp.h" #include "libxorp/xlog.h" #include "libxorp/debug.h" #include "libxorp/token.hh" #include "libxorp/utils.hh" #ifdef HOST_OS_WINDOWS #include "libxorp/win_io.h" #endif #include #ifdef HAVE_PWD_H #include #endif #ifdef HAVE_NETDB_H #include #endif #include "cli.hh" #include "command_tree.hh" #include "op_commands.hh" #include "slave_conf_tree.hh" #include "slave_conf_tree_node.hh" #include "template_tree.hh" #include "template_tree_node.hh" #ifdef HOST_OS_WINDOWS inline uid_t getuid() { return 0; } #endif static string get_user_name(uid_t uid) { string result; #ifdef HOST_OS_WINDOWS // TODO: implement it for Windows UNUSED(uid); return (result); #else // ! HOST_OS_WINDOWS struct passwd* pw = getpwuid(uid); if (pw != NULL) result = pw->pw_name; endpwent(); return (result); #endif // ! HOST_OS_WINDOWS } static string get_my_user_name() { return (get_user_name(getuid())); } // // Append the list elements one-by-one to the vector. // XXX: we need this helper function instead of // "v.append(v.end(), l.begin(), l.end())" // because the above statement generates a compilation error on // FreeBSD-4.10 with gcc-2.95.4 (probably because of a bug in the STL // implementation on that system). // static void append_list_to_vector(vector& result_vector, const list& append_list) { list::const_iterator iter; for (iter = append_list.begin(); iter != append_list.end(); ++iter) result_vector.push_back(*iter); } const string RouterCLI::DEFAULT_XORP_PROMPT_OPERATIONAL = "Xorp> "; const string RouterCLI::DEFAULT_XORP_PROMPT_CONFIGURATION = "XORP# "; RouterCLI::RouterCLI(XorpShellBase& xorpsh, CliNode& cli_node, XorpFd cli_client_input_fd, XorpFd cli_client_output_fd, bool verbose) throw (InitError) : _xorpsh(xorpsh), _cli_node(cli_node), _cli_client_ptr(NULL), _verbose(verbose), _operational_mode_prompt(DEFAULT_XORP_PROMPT_OPERATIONAL), _configuration_mode_prompt(DEFAULT_XORP_PROMPT_CONFIGURATION), _mode(CLI_MODE_NONE), _changes_made(false), _op_mode_cmd(NULL) { string error_msg; // // Set the operational and configuration mode prompts to be // "user@hostname> " and "user@hostname# " respectively. // string user_name = get_my_user_name(); string host_name; do { char buf[MAXHOSTNAMELEN]; memset(buf, 0, sizeof(buf)); if (gethostname(buf, sizeof(buf)) < 0) { #ifdef HOST_OS_WINDOWS XLOG_FATAL("gethostname() failed: %s", win_strerror(WSAGetLastError())); #else XLOG_FATAL("gethostname() failed: %s", strerror(errno)); #endif } buf[sizeof(buf) - 1] = '\0'; host_name = buf; } while (false); _operational_mode_prompt = c_format("%s@%s> ", user_name.c_str(), host_name.c_str()); _configuration_mode_prompt = c_format("%s@%s# ", user_name.c_str(), host_name.c_str()); #if 0 // // XXX: if we want to change the promps to be // "user@hostname> " and "USER@HOSTNAME# " // then uncomment this code. // string::size_type iter; for (iter = 0; iter < _operational_mode_prompt.size(); ++iter) { char& c = _operational_mode_prompt[iter]; if (xorp_isalpha(c)) c = xorp_tolower(c); } for (iter = 0; iter < _configuration_mode_prompt.size(); ++iter) { char& c = _configuration_mode_prompt[iter]; if (xorp_isalpha(c)) c = xorp_toupper(c); } #endif // Check for environmental variables that may overwrite the prompts char* value = NULL; value = getenv("XORP_PROMPT_OPERATIONAL"); if (value != NULL) _operational_mode_prompt = value; value = getenv("XORP_PROMPT_CONFIGURATION"); if (value != NULL) _configuration_mode_prompt = value; _cli_client_ptr = _cli_node.add_client(cli_client_input_fd, cli_client_output_fd, false, _operational_mode_prompt, error_msg); if (_cli_client_ptr == NULL) { error_msg = c_format("Cannot add CliClient: %s", error_msg.c_str()); xorp_throw(InitError, error_msg); } // // We do all this here to allow for later extensions for // internationalization. // _help_o["configure"] = "Switch to configuration mode"; _help_o["configure exclusive"] = "Switch to configuration mode, locking out other users"; _help_o["exit"] = "Exit this command session"; _help_o["help"] = "Provide help with commands"; _help_o["quit"] = "Quit this command session"; _help_c["commit"] = "Commit the current set of changes"; _help_c["create"] = "Alias for the \"set\" command (obsoleted)"; _help_c["delete"] = "Delete a configuration element"; _help_c["edit"] = "Edit a sub-element"; _help_c["exit"] = "Exit from this configuration level"; _help_c["exit configuration-mode"] = "Exit from configuration mode"; _help_c["exit discard"] = "Exit from configuration mode, discarding changes"; _help_c["help"] = "Provide help with commands"; _help_c["load"] = "Load configuration from a file"; _help_c["quit"] = "Quit from this level"; _help_c["run"] = "Run an operational-mode command"; _help_c["save"] = "Save configuration to a file"; _help_c["set"] = "Set the value of a parameter or create a new element"; _help_c["show"] = "Show the configuration (default values may be suppressed)"; _help_c["show -all"] = "Show the configuration (all values displayed)"; _help_c["top"] = "Exit to top level of configuration"; _help_c["up"] = "Exit one level of configuration"; _help_long_o["NULL"] = "\ A XORP router has two modes: operational mode and configuration mode.\n\ Operational mode is used to monitor the state of the router, whereas \n\ configuration mode is used to change the configuration of the router.\n\ \n\ You are currently in operational mode.\n\ To switch to configuration mode, use the \"configure\" command.\n\ You have to be in the xorp group to be able to use configuration mode.\n\ \n\ At most places in the user interface, typing \"?\" will give a list of\n\ possible commands or completions for the current command. Hitting \n\ will cause auto-completion to occur if this can be done unambiguously.\n\ Command cannot be abbreviated - always complete then, either by typing or \n\ by using . \n\ \n\ Type \"help help\" for more details on how to use help.\n\ Type \"help \" for details of how to use a particular command.\n\ Type ? to list currently available commands."; _help_long_o["configure"] = "\ A XORP router has two modes: operational mode and configuration mode.\n\ Operational mode is used to monitor the state of the router, whereas \n\ configuration mode is used to change the configuration of the router.\n\ \n\ You are currently in operational mode.\n\ To switch to configuration mode, use the \"configure\" command.\n\ You have to be in the xorp group to be able to use configuration mode.\n\ \n\ Optional parameters to configure: \n\ exclusive\n\ Use \"help configure exclusive\" for more details."; _help_long_o["configure exclusive"] = "\ To enter exclusive configuration mode you use the command:\n\ configure exclusive\n\ This will allow you to make changes to the router configuration and lock\n\ all other users out of configuration mode until you exit.\n\ Use this command sparingly."; _help_long_o["exit"] = "\ The \"exit\" command will log you out of this command session.\n\ Typing ^D (control-D) also has the same effect."; _help_long_o["quit"] = "\ The \"quit\" command will log you out of this command session.\n\ Typing ^D (control-D) also has the same effect."; _help_long_c["NULL"] = "\ A XORP router has two modes: operational mode and configuration mode.\n\ Operational mode is used to monitor the state of the router, whereas \n\ configuration mode is used to change the configuration of the router.\n\ \n\ You are currently in configuration mode.\n\ To return to operational mode, use the \"exit\" command.\n\ \n\ At most places in the user interface, typing \"?\" will give a list of\n\ possible commands or completions for the current command. Hitting \n\ will cause auto-completion to occur if this can be done unambiguously.\n\ Command cannot be abbreviated - always complete then, either by typing or \n\ by using . \n\ \n\ Type \"help help\" for more details on how to use help.\n\ Type \"help \" for details of how to use a particular command.\n\ Type ? to list currently available commands."; _help_long_c["commit"] = "\ When you change the configuration of a XORP router, the changes do not take\n\ place immediately. This allows you to make additional related changes and\n\ then apply all the changes together. To apply changes to the running\n\ configuration, the \"commit\" command is used. Should there be an error,\n\ the configuration should be rolled back to the previous state."; _help_long_c["create"] = "\ The \"create\" command is an alias for the \"set\" command.\n\ It is obsoleted and will be removed in the future.\n\ \n\ See the \"set\" command."; _help_long_c["delete"] ="\ The \"delete\" command is used to delete a part of the configuration tree.\n\ All configuration nodes under the node that is named in the delete command\n\ will also be deleted. For example, if the configuration contained:\n\ \n\ protocols {\n\ bgp {\n\ peer 10.0.0.1 {\n\ as: 65001 \n\ }\n\ }\n\ \n\ then typing \"delete protocols bgp\", followed by \"commit\" would cause bgp\n\ to be removed from the configuration, together with the peer 10.0.0.1, etc.\n\ In this case this would also cause the BGP routing process to be shut down.\n\ \n\ See also the \"edit\" and \"set\" commands."; _help_long_c["edit"] = "\ The \"edit\" command allows you to navigate around the configuration tree, so\n\ that changes can be made with minimal typing, or just a part of the router\n\ configuration examined. For example, if the configuration contained:\n\ \n\ protocols {\n\ bgp {\n\ peer 10.0.0.1 {\n\ as: 65001 \n\ }\n\ }\n\ \n\ Then typing \"edit protocols bgp peer 10.0.0.1\" would move the current \n\ position to the bgp peer 10.0.0.1.\n\ Then if you wanted to set the as number, you could use the command\n\ set as 65002\n\ instead of the much longer\n\ set protocols bgp peer 10.0.0.1 as 65002\n\ which would have been necessary without changing the current position in \n\ the configuration tree.\n\ \n\ See also the \"exit\", \"quit\", \"top\" and \"up\" commands."; _help_long_c["exit"] = "\ The \"exit\" command will cause the current position in the configuration \n\ to exit one level. For example, if the configuration was:\n\ \n\ protocols {\n\ bgp {\n\ peer 10.0.0.1 {\n\ as: 65001 \n\ }\n\ }\n\ \n\ and the current position is \"protocols bgp peer 10.0.0.1\", then typing \n\ \"exit\" would cause the current position to become \"protocols bgp\". In\n\ this case \"peer 10.0.0.1\" is a single level of configuration\n\ corresponding to a BGP peering session.\n\ \n\ If the current position is at the top level of the configuration, then \n\ \"exit\" will return the login session to operational mode.\n\ \n\ See also \"exit discard\" and \"exit configuration-mode\" for other\n\ uses of the exit command."; _help_long_c["exit configuration-mode"] = "\ The \"exit configuration-mode\" command will cause the login session to \n\ return to operational mode. If there are uncommitted changes, the command\n\ will fail.\n\ \n\ See also the \"exit\" and \"exit discard\" commands."; _help_long_c["exit discard"] = "\ The \"exit discard\" command will cause the login session to return\n\ to operational mode, discarding any changes to the current configuration\n\ that have not been committed.\n\ \n\ See also the \"commit\" and \"exit configuration-mode\" commands."; _help_long_c["help"] = "\ The \"help\" command is used to provide help on many aspects of the command\n\ line interface. If a command has options, usually separate help will be \n\ available for those options. For example, \"help exit\" and \"help exit discard\"\n\ give different and complementory information."; _help_long_c["load"] = "\ The \"load\" command loads a complete router configuration from a named file\n\ and causes the new configuration to become the running configuration.\n\ By default, files are loaded from your own home directory unless an absolute\n\ pathname is given. Directories in the path should be separated by \n\ slashes (\"/\") and not backslashes (\"\\\\\").\n\ \n\ For example:\n\ load xorp-config\n\ load /tmp/test-config\n\ \n\ See also: \"save\"."; _help_long_c["quit"] = "\ The \"quit\" command will cause the current position in the configuration \n\ to quit one level. For example, if the configuration was:\n\ \n\ protocols {\n\ bgp {\n\ peer 10.0.0.1 {\n\ as: 65001 \n\ }\n\ }\n\ \n\ and the current position is \"protocols bgp peer 10.0.0.1\", then typing \n\ \"quit\" would cause the current position to become \"protocols bgp\". In\n\ this case \"peer 10.0.0.1\" is a single level of configuration\n\ corresponding to a BGP peering session.\n\ \n\ If the current position is at the top level of the configuration, then \n\ \"quit\" has no effect.\n\ \n\ See also the \"exit\", \"up\" and \"top\" commands."; _help_long_c["run"] = "\ The \"run\" command allows operational-mode commands to be executed without\n\ leaving configuration-mode. This is particularly important if there are\n\ uncommitted changes to the configuration.\n\ \n\ For example, the operational-mode command \"show bgp peers\" can be run from\n\ configuration-mode as \"run show bgp peers\".\n\ \n\ Navigation commands such as the operational-mode \"configure\" command are not\n\ available using the run command."; _help_long_c["save"] = "\ The \"save\" command causes the current running configuration to be saved to\n\ a named file. By default, files are loaded from your own home directory \n\ unless an absolute pathname is given. Directories in the path should be\n\ separated by slashes (\"/\") and not backslashes (\"\\\").\n\ \n\ For example:\n\ save xorp-config\n\ save /tmp/test-config\n\ \n\ See also: \"load\"."; _help_long_c["set"] = "\ The \"set\" command allows you to set or change configuration parameters. \n\ For example, if the configuration contained:\n\ \n\ protocols {\n\ bgp {\n\ peer 10.0.0.1 {\n\ as: 65001 \n\ }\n\ }\n\ \n\ Then typing \"set protocols bgp peer 10.0.0.1 as 65002\" would change \n\ the AS number of the BGP peer from 65001 to 65002. Only parameters \n\ (leaves in the configuration tree) can be set using the \"set\" \n\ command. New parts of the configuration tree such as a new BGP peer \n\ are created by directly typing them in configuration mode.\n\ \n\ The \"set\" command also allows you to create a new branch of the \n\ configuration tree. For example, if the configuration contained:\n\ \n\ protocols {\n\ bgp {\n\ peer 10.0.0.1 {\n\ as: 65001 \n\ }\n\ }\n\ }\n\ \n\ Then typing \"set protocols static\" would create a configuration\n\ branch for the static routes, and will fill it in with the template \n\ default values:\n\ \n\ protocols {\n\ bgp {\n\ peer 10.0.0.1 {\n\ as: 65001 \n\ }\n\ }\n\ > static {\n\ > targetname: \"static_routes\"\n\ > enabled: true\n\ > }\n\ }\n\ \n\ Then you can use the command \"edit\" to navigate around the new branch\n\ of the configuration tree. Use the command \"set\" to change some of the\n\ values of the new configuration branch:\n\ set protocols static enabled false\n\ would disable the new static routes module.\n\ \n\ See also the \"commit\", \"delete\" and \"edit\" commands."; _help_long_c["show"] = "\ The \"show\" command will display all or part of the router configuration.\n\ Without any parameters, the \"show\" command will display all of the router\n\ configuration below the current position in the command tree (See the \n\ \"edit\" command for how to move the current position). The show command \n\ can also take a part of the configuration as parameters; it will then show \n\ only the selected part of the configuration.\n\ Note that all configuration parameters that have default values are not\n\ displayed.\n\ \n\ If the configuration has been modified, any changes not yet committed will \n\ be highlighted. For example, if \"show\" displays:\n\ \n\ protocols {\n\ bgp {\n\ > peer 10.0.0.1 {\n\ > as: 65001 \n\ > }\n\ }\n\ \n\ then this indicates that the peer 10.0.0.1 has been created or changed, \n\ and the change has not yet been applied to the running router configuration.\n\ \n\ See also the \"show -all\" command."; _help_long_c["show -all"] = "\ The \"show -all\" command will display all or part of the router configuration.\n\ It is same as the \"show\" command except that it displays all configuration\n\ parameters including those that have default values.\n\ \n\ See also the \"show\" command."; _help_long_c["top"] = "\ The \"top\" command will cause the current position in the configuration \n\ to return to the top level of the configuration. For example, if the \n\ configuration was:\n\ \n\ protocols {\n\ bgp {\n\ peer 10.0.0.1 {\n\ as: 65001 \n\ }\n\ }\n\ \n\ and the current position is \"protocols bgp peer 10.0.0.1\", then \n\ typing \"top\" would cause the current position to become the top \n\ of the configuration, outside of the \"protocols\" grouping. The same \n\ result could have been obtained by using the \"exit\" command three \n\ times.\n\ \n\ See also the \"exit\", \"quit\" and \"up\" commands."; _help_long_c["up"] = "\ The \"up\" command will cause the current position in the configuration \n\ to move up one level. For example, if the configuration was:\n\ \n\ protocols {\n\ bgp {\n\ peer 10.0.0.1 {\n\ as: 65001 \n\ }\n\ }\n\ \n\ and the current position is \"protocols bgp peer 10.0.0.1\", then typing \n\ \"up\" would cause the current position to become \"protocols bgp\". In\n\ this case \"peer 10.0.0.1\" is a single level of configuration\n\ corresponding to a BGP peering session.\n\ \n\ If the current position is at the top level of the configuration, then \n\ \"up\" has no effect.\n\ \n\ See also the \"exit\", \"quit\" and \"top\" commands."; // _current_config_node = &(config_tree()->root_node()); operational_mode(); } RouterCLI::~RouterCLI() { if (_cli_client_ptr != NULL) { delete _cli_client_ptr; _cli_client_ptr = NULL; } } bool RouterCLI::done() const { if (_cli_client_ptr == NULL) return (true); if (! _cli_client_ptr->done()) return (false); return (true); } string RouterCLI::get_help_o(const string& s) const { map::const_iterator i = _help_o.find(s); if (i == _help_o.end()) return string(""); else return i->second; } string RouterCLI::get_help_c(const string& s) const { map::const_iterator i = _help_c.find(s); if (i == _help_c.end()) return string(""); else return i->second; } void RouterCLI::operational_mode() { string error_msg; if (_mode == CLI_MODE_OPERATIONAL) return; _mode = CLI_MODE_OPERATIONAL; clear_command_set(); reset_path(); set_prompt("", _operational_mode_prompt); if (add_op_mode_commands(NULL, error_msg) != XORP_OK) { XLOG_FATAL("Cannot add operational mode commands: %s", error_msg.c_str()); } } int RouterCLI::add_op_mode_commands(CliCommand* com0, string& error_msg) { CliCommand *com1, *com2, *help_com, *exit_com, *quit_com; #if 0 com0->add_command("clear", "Clear information in the system *", false, error_msg); #endif // If no root node is specified, default to the true root if (com0 == NULL) com0 = _cli_node.cli_command_root(); if (com0 == _cli_node.cli_command_root()) { com1 = com0->add_command("configure", get_help_o("configure"), false, callback(this, &RouterCLI::configure_func), error_msg); if (com1 == NULL) return (XORP_ERROR); com1->set_global_name(token_line2vector("configure")); com1->set_can_pipe(false); com2 = com1->add_command("exclusive", get_help_o("configure exclusive"), false, callback(this, &RouterCLI::configure_func), error_msg); if (com2 == NULL) return (XORP_ERROR); com2->set_global_name(token_line2vector("configure exclusive")); com2->set_can_pipe(false); // Help Command help_com = com0->add_command("help", get_help_o("help"), false, error_msg); if (help_com == NULL) return (XORP_ERROR); help_com->set_global_name(token_line2vector("help")); help_com->set_dynamic_children_callback( callback(this, &RouterCLI::op_mode_help)); help_com->set_dynamic_process_callback( callback(this, &RouterCLI::op_help_func)); help_com->set_can_pipe(true); // Exit Command exit_com = com0->add_command("exit", get_help_o("exit"), false, callback(this, &RouterCLI::logout_func), error_msg); if (exit_com == NULL) return (XORP_ERROR); exit_com->set_can_pipe(false); // Quit Command quit_com = com0->add_command("quit", get_help_o("quit"), false, callback(this, &RouterCLI::logout_func), error_msg); if (quit_com == NULL) return (XORP_ERROR); quit_com->set_can_pipe(false); } map cmds = op_cmd_list()->top_level_commands(); map::const_iterator iter; for (iter = cmds.begin(); iter != cmds.end(); ++iter) { const CliCommandMatch& ccm = iter->second; com1 = com0->add_command(ccm.command_name(), ccm.help_string(), false, error_msg); if (com1 == NULL) return (XORP_ERROR); vector command_vector_name; command_vector_name.push_back(ccm.command_name()); com1->set_global_name(command_vector_name); com1->set_can_pipe(ccm.can_pipe()); com1->set_default_nomore_mode(ccm.default_nomore_mode()); com1->set_type_match_cb(ccm.type_match_cb()); // Set the callback to generate the node's children com1->set_dynamic_children_callback( callback(op_cmd_list(), &OpCommandList::childlist)); // // Set the callback to pass to the node's children when they // are executed. // com1->set_dynamic_process_callback( callback(this, &RouterCLI::op_mode_func)); com1->set_dynamic_interrupt_callback( callback(this, &RouterCLI::op_mode_cmd_interrupt)); if (ccm.is_executable()) { // XXX: set the processing and interrupt callbacks com1->set_cli_process_callback( callback(this, &RouterCLI::op_mode_func)); com1->set_cli_interrupt_callback( callback(this, &RouterCLI::op_mode_cmd_interrupt)); } } return (XORP_OK); } map RouterCLI::op_mode_help(const vector& command_global_name) const { string command_name; string help_string; bool is_executable = false; bool can_pipe = false; map children; string path, trimmed_path; path = token_vector2line(command_global_name); XLOG_ASSERT(path.substr(0, 4) == "help"); if (path.size() == 4) { trimmed_path == ""; } else { XLOG_ASSERT(path.substr(0, 5) == "help "); trimmed_path = path.substr(5, path.size() - 5); } if (trimmed_path == "") { // Add the static commands string commands[] = { "configure", "exit", "quit", "help" }; is_executable = true; can_pipe = false; size_t i; for (i = 0; i < sizeof(commands)/sizeof(commands[0]); i++) { command_name = commands[i]; help_string = get_help_o(command_name); CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); children.insert(make_pair(command_name, ccm)); } map cmds; cmds = op_cmd_list()->top_level_commands(); map::const_iterator iter; for (iter = cmds.begin(); iter != cmds.end(); ++iter) { const CliCommandMatch& ccm_top = iter->second; const string& command_name = ccm_top.command_name(); help_string = c_format("Give help on the \"%s\" command", command_name.c_str()); CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); children.insert(make_pair(command_name, ccm)); } } else if (trimmed_path == "configure") { is_executable = true; can_pipe = false; command_name = "exclusive"; help_string = get_help_o(trimmed_path + " " + command_name); CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); children.insert(make_pair(command_name, ccm)); } else if (trimmed_path == "configure exclusive") { is_executable = true; can_pipe = false; } else if (trimmed_path == "exit") { is_executable = true; can_pipe = false; } else if (trimmed_path == "quit") { is_executable = true; can_pipe = false; } else if (trimmed_path == "help") { is_executable = true; can_pipe = true; } else { children = op_cmd_list()->childlist(token_line2vector(trimmed_path)); } return children; } void RouterCLI::configure_mode() { // _nesting_depth = 0; if (_mode == CLI_MODE_CONFIGURE) return; if (_mode != CLI_MODE_TEXTENTRY) { display_config_mode_users(); } display_alerts(); _mode = CLI_MODE_CONFIGURE; clear_command_set(); set_prompt("", _configuration_mode_prompt); // Add all the menus apply_path_change(); } void RouterCLI::display_config_mode_users() const { cli_client().cli_print("Entering configuration mode.\n"); if (_config_mode_users.empty()) { cli_client().cli_print("There are no other users in configuration mode.\n"); return; } // // There are other users // if (_config_mode_users.size() == 1) cli_client().cli_print("User "); else cli_client().cli_print("Users "); list::const_iterator iter, iter2; for (iter = _config_mode_users.begin(); iter != _config_mode_users.end(); ++iter) { if (iter != _config_mode_users.begin()) { iter2 = iter; ++iter2; if (iter2 == _config_mode_users.end()) cli_client().cli_print(" and "); else cli_client().cli_print(", "); } string user_name = get_user_name(*iter); if (user_name.empty()) user_name = c_format("UID:%u", XORP_UINT_CAST(*iter)); cli_client().cli_print(c_format("%s", user_name.c_str())); } if (_config_mode_users.size() == 1) cli_client().cli_print(" is also in configuration mode.\n"); else cli_client().cli_print(" are also in configuration mode.\n"); } void RouterCLI::display_alerts() { // Display any alert messages that accumulated while we were busy while (!_alerts.empty()) { cli_client().cli_print(_alerts.front()); _alerts.pop_front(); } } bool RouterCLI::is_config_mode() const { // This is a switch statement to get compile time checking switch (_mode) { case CLI_MODE_NONE: case CLI_MODE_OPERATIONAL: return false; case CLI_MODE_CONFIGURE: case CLI_MODE_TEXTENTRY: return true; } XLOG_UNREACHABLE(); } void RouterCLI::text_entry_mode() { string prompt; _mode = CLI_MODE_TEXTENTRY; clear_command_set(); prompt = " > "; for (size_t i = 0; i < _braces.size(); i++) prompt += " "; set_prompt("", prompt); } int RouterCLI::add_static_configure_mode_commands(string& error_msg) { CliCommand *com0, *com1, *com2, *help_com; com0 = _cli_node.cli_command_root(); // Commit command com1 = com0->add_command("commit", get_help_c("commit"), false, callback(this, &RouterCLI::commit_func), error_msg); if (com1 == NULL) return (XORP_ERROR); com1->set_can_pipe(false); // Create command _create_node = com0->add_command("create", get_help_c("create"), false, error_msg); if (_create_node == NULL) return (XORP_ERROR); _create_node->set_can_pipe(false); // Delete command _delete_node = com0->add_command("delete", get_help_c("delete"), false, error_msg); if (_delete_node == NULL) return (XORP_ERROR); _delete_node->set_can_pipe(false); // Edit command _edit_node = com0->add_command("edit", get_help_c("edit"), false, callback(this, &RouterCLI::edit_func), error_msg); if (_edit_node == NULL) return (XORP_ERROR); _edit_node->set_global_name(token_line2vector("edit")); _edit_node->set_can_pipe(false); // Exit command com1 = com0->add_command("exit", get_help_c("exit"), false, callback(this, &RouterCLI::exit_func), error_msg); if (com1 == NULL) return (XORP_ERROR); com1->set_global_name(token_line2vector("exit")); com1->set_can_pipe(false); com2 = com1->add_command("configuration-mode", get_help_c("exit configuration_mode"), false, callback(this, &RouterCLI::exit_func), error_msg); if (com2 == NULL) return (XORP_ERROR); com2->set_global_name(token_line2vector("exit configuration-mode")); com2->set_can_pipe(false); com2 = com1->add_command("discard", "Exit from configuration mode, discarding changes", false, callback(this, &RouterCLI::exit_func), error_msg); if (com2 == NULL) return (XORP_ERROR); com2->set_global_name(token_line2vector("exit discard")); com2->set_can_pipe(false); // Help Command help_com = com0->add_command("help", get_help_c("help"), false, error_msg); if (help_com == NULL) return (XORP_ERROR); help_com->set_global_name(token_line2vector("help")); help_com->set_dynamic_children_callback( callback(this, &RouterCLI::configure_mode_help)); help_com->set_dynamic_process_callback( callback(this, &RouterCLI::conf_help_func)); help_com->set_can_pipe(true); // Load Command com1 = com0->add_command("load", get_help_c("load"), false, callback(this, &RouterCLI::load_func), error_msg); if (com1 == NULL) return (XORP_ERROR); com1->set_global_name(token_line2vector("load")); com1->set_can_pipe(false); // Quit Command com1 = com0->add_command("quit", get_help_c("quit"), false, callback(this, &RouterCLI::exit_func), error_msg); if (com1 == NULL) return (XORP_ERROR); com1->set_global_name(token_line2vector("quit")); com1->set_can_pipe(false); _run_node = com0->add_command("run", get_help_c("run"), false, error_msg); if (_run_node == NULL) return (XORP_ERROR); _run_node->set_can_pipe(false); if (add_op_mode_commands(_run_node, error_msg) != XORP_OK) return (XORP_ERROR); com1 = com0->add_command("save", get_help_c("save"), false, callback(this, &RouterCLI::save_func), error_msg); if (com1 == NULL) return (XORP_ERROR); com1->set_global_name(token_line2vector("save")); com1->set_can_pipe(false); // Set Command _set_node = com0->add_command("set", get_help_c("set"), false, error_msg); if (_set_node == NULL) return (XORP_ERROR); _set_node->set_can_pipe(false); // Show Command _show_node = com0->add_command("show", get_help_c("show"), false, callback(this, &RouterCLI::show_func), error_msg); if (_show_node == NULL) return (XORP_ERROR); _show_node->set_can_pipe(true); com1 = _show_node->add_command("-all", get_help_c("show -all"), false, callback(this, &RouterCLI::show_func), error_msg); if (com1 == NULL) return (XORP_ERROR); com1->set_global_name(token_line2vector("show -all")); com1->set_can_pipe(true); // Top Command com1 = com0->add_command("top", get_help_c("top"), false, callback(this, &RouterCLI::exit_func), error_msg); if (com1 == NULL) return (XORP_ERROR); com1->set_global_name(token_line2vector("top")); com1->set_can_pipe(false); // Up Command com1 = com0->add_command("up", get_help_c("up"), false, callback(this, &RouterCLI::exit_func), error_msg); if (com1 == NULL) return (XORP_ERROR); com1->set_global_name(token_line2vector("up")); com1->set_can_pipe(false); return (XORP_OK); } map RouterCLI::configure_mode_help(const vector& command_global_name) const { string command_name; string help_string; bool is_executable = false; bool can_pipe = false; map children; string path, trimmed_path; path = token_vector2line(command_global_name); XLOG_ASSERT(path.substr(0, 4) == "help"); if (path.size() == 4) { trimmed_path == ""; } else { XLOG_ASSERT(path.substr(0, 5) == "help "); trimmed_path = path.substr(5, path.size() - 5); } if (trimmed_path == "") { // Add the static commands: string commands[] = { "commit", "create", "delete", "edit", "exit", "help", "load", "quit", "run", "save", "set", "show", "top", "up" }; is_executable = true; can_pipe = false; size_t i; for (i = 0; i < sizeof(commands)/sizeof(commands[0]); i++) { command_name = commands[i]; help_string = get_help_c(command_name); CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); children.insert(make_pair(command_name, ccm)); } // // TODO: need to insert the commands that come from the template // tree here. // } else if (trimmed_path == "exit") { string commands[] = { "configuration-mode", "discard" }; is_executable = true; can_pipe = false; size_t i; for (i = 0; i < sizeof(commands)/sizeof(commands[0]); i++) { command_name = commands[i]; help_string = get_help_c(trimmed_path + " " + command_name); CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); children.insert(make_pair(command_name, ccm)); } } else if (trimmed_path == "show") { string commands[] = { "-all" }; is_executable = true; can_pipe = false; size_t i; for (i = 0; i < sizeof(commands)/sizeof(commands[0]); i++) { command_name = commands[i]; help_string = get_help_c(trimmed_path + " " + command_name); CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); children.insert(make_pair(command_name, ccm)); } } else { // Make the help for static commands executable map::const_iterator i; is_executable = false; for (i = _help_c.begin(); i != _help_c.end(); ++i) { if (trimmed_path == i->first) { is_executable = true; can_pipe = true; break; } } } return children; } void RouterCLI::reset_path() { _path.clear(); } void RouterCLI::set_path(string path) { reset_path(); debug_msg("set path: >%s<\n", path.c_str()); string::size_type ix; while (! path.empty()) { ix = path.find(' '); if (ix == string::npos) { _path.push_back(path); break; } _path.push_back(path.substr(0, ix)); path = path.substr(ix + 1, path.size() - ix + 1); } } void RouterCLI::config_mode_prompt() { string prompt; if (pathstr() == "") { prompt = "[edit]"; } else { prompt = "[edit " + pathstr() + "]"; } set_prompt(prompt, _configuration_mode_prompt); } void RouterCLI::config_changed_by_other_user() { if (! is_config_mode()) { // XXX: we care about the changes only if we are in config mode return; } // // Someone just did a commit, so we may need to reset the session's // editing path and mode. // Also, regenerate the command tree to include the configuration changes. // SlaveConfigTreeNode *current_config_node = config_tree()->find_node(_path); if (current_config_node == NULL) { // XXX: the current config node has disappeared hence reset the path reset_path(); if (_mode != CLI_MODE_CONFIGURE) { // If we are in, say text-entry mode, reset back to configure mode configure_mode(); return; } } // Regenerate the command tree apply_path_change(); } void RouterCLI::apply_path_change() { string error_msg; _cli_node.cli_command_root()->delete_all_commands(); if (add_static_configure_mode_commands(error_msg) != XORP_OK) { XLOG_FATAL("Cannot add static configure mode commands: %s", error_msg.c_str()); } // Rebuild the command subtree for the "edit" command _edit_node->delete_all_commands(); // // Note: the edit subtree must be done before the other subtrees, // because it can force a path change in the root node if the "edit" // subtree is not a node that can be edited. // add_edit_subtree(); // Rebuild the command subtree for the "create" command _create_node->delete_all_commands(); add_create_subtree(); // Rebuild the command subtree for the "delete" command _delete_node->delete_all_commands(); add_delete_subtree(); // Rebuild the command subtree for the "set" command _set_node->delete_all_commands(); add_set_subtree(); // Rebuild the command subtree for the "show" command _show_node->delete_all_commands(); add_show_subtree(); vector vector_path; vector_path.push_back("show"); append_list_to_vector(vector_path, _path); _show_node->set_global_name(vector_path); // Set the prompt appropriately config_mode_prompt(); } void RouterCLI::set_prompt(const string& line1, const string& line2) { if (line1 != "") { cli_client().cli_print(line1 + "\n"); } _cli_node.cli_command_root()->set_allow_cd(true, line2); cli_client().set_current_cli_prompt(line2); } void RouterCLI::add_command_subtree(CliCommand& current_cli_node, const CommandTreeNode& current_ctn, const CLI_PROCESS_CALLBACK& cb, vector vector_path, size_t depth, bool can_pipe, bool include_allowed_values) { string error_msg; const list& children = current_ctn.children(); if (depth > 0) { vector_path.push_back(current_ctn.name()); } list::const_iterator cmd_iter; for (cmd_iter = children.begin(); cmd_iter != children.end(); ++cmd_iter) { const CommandTreeNode& ctn = *(*cmd_iter); const string& cmd_name = ctn.name(); vector vector_subpath = vector_path; vector_subpath.push_back(cmd_name); string help = ctn.help(); if (help == "") { help = "-- No help available --"; } CliCommand* com = NULL; if (ctn.has_command()) { com = current_cli_node.add_command(cmd_name, help, false, cb, error_msg); } else { com = current_cli_node.add_command(cmd_name, help, false, error_msg); } if (com == NULL) { XLOG_FATAL("add_command %s failed: %s", cmd_name.c_str(), error_msg.c_str()); } com->set_can_pipe(can_pipe); com->set_global_name(vector_subpath); add_command_subtree(*com, ctn, cb, vector_path, depth + 1, can_pipe, include_allowed_values); } const TemplateTreeNode* ttn = current_ctn.template_tree_node(); if (include_allowed_values && (ttn != NULL)) { // // Add the command-line completion for the allowed values // map::const_iterator values_iter; for (values_iter = ttn->allowed_values().begin(); values_iter != ttn->allowed_values().end(); ++values_iter) { const string& cmd_name = values_iter->first; vector vector_subpath = vector_path; vector_subpath.push_back(cmd_name); string help = values_iter->second; if (help == "") { help = "-- No help available --"; } CliCommand* com = NULL; if (current_ctn.has_command()) { com = current_cli_node.add_command(cmd_name, help, false, cb, error_msg); } else { com = current_cli_node.add_command(cmd_name, help, false, error_msg); } if (com == NULL) { XLOG_FATAL("add_command %s failed: %s", cmd_name.c_str(), error_msg.c_str()); } com->set_can_pipe(can_pipe); com->set_global_name(vector_subpath); com->set_is_command_argument(true); } // // Add the command-line completion for the allowed ranges // map, string>::const_iterator ranges_iter; for (ranges_iter = ttn->allowed_ranges().begin(); ranges_iter != ttn->allowed_ranges().end(); ++ranges_iter) { const pair& range = ranges_iter->first; ostringstream ost; ost << "[" << range.first << ".." << range.second << "]"; string cmd_name = ost.str(); vector vector_subpath = vector_path; vector_subpath.push_back(cmd_name); string help = ranges_iter->second; if (help == "") { help = "-- No help available --"; } CliCommand* com = NULL; if (current_ctn.has_command()) { com = current_cli_node.add_command(cmd_name, help, false, cb, error_msg); } else { com = current_cli_node.add_command(cmd_name, help, false, error_msg); } if (com == NULL) { XLOG_FATAL("add_command %s failed: %s", cmd_name.c_str(), error_msg.c_str()); } com->set_can_pipe(can_pipe); com->set_global_name(vector_subpath); CliCommand::TypeMatchCb cb; cb = callback(ttn, &TemplateTreeNode::type_match); com->set_type_match_cb(cb); com->set_is_command_argument(true); } } } void RouterCLI::clear_command_set() { CliCommand* com0; com0 = _cli_node.cli_command_root(); com0->delete_all_commands(); } string RouterCLI::pathstr() const { string s; list::const_iterator iter; for (iter = _path.begin(); iter != _path.end(); ++iter) { if (s.empty()) s = *iter; else s += " " + *iter; } return s; } string RouterCLI::pathstr2() const { string s; list::const_iterator iter; for (iter = _path.begin(); iter != _path.end(); ++iter) { if (s.empty()) s = ">" + *iter + "<"; else s += " >" + *iter + "<"; } return s; } void RouterCLI::add_edit_subtree() { CommandTree cmd_tree; list cmds; cmds.push_back("%create"); cmds.push_back("%activate"); cmds.push_back("%update"); SlaveConfigTreeNode *current_config_node = config_tree()->find_node(_path); // // If we ended up at a node that can't be the root for an edit // tree, go back up one level now. // const TemplateTreeNode* ttn = current_config_node->template_tree_node(); if (ttn != NULL) { if (ttn->is_tag() && !_path.empty()) { _path.pop_back(); current_config_node = current_config_node->parent(); } } XLOG_ASSERT(current_config_node != NULL); current_config_node->create_command_tree( cmd_tree, cmds, true, /* include_intermediate_nodes */ false, /* include_children_templates */ false, /* include_leaf_value_nodes */ true, /* include_read_only_nodes */ true, /* include_permanent_nodes */ false /* include_user_hidden_nodes */); debug_msg("==========================================================\n"); debug_msg("edit subtree is:\n\n"); debug_msg("%s", cmd_tree.tree_str().c_str()); debug_msg("==========================================================\n"); if (_braces.empty()) { vector vector_path; vector_path.push_back("edit"); append_list_to_vector(vector_path, _path); add_command_subtree(*_edit_node, cmd_tree.root_node(), callback(this, &RouterCLI::edit_func), vector_path, 0, false, /* can_pipe */ false /* include_allowed_values */); } } void RouterCLI::add_create_subtree() { add_text_entry_commands(_create_node); } void RouterCLI::add_delete_subtree() { CommandTree cmd_tree; list cmds; cmds.push_back("%create"); cmds.push_back("%activate"); cmds.push_back("%update"); cmds.push_back("%set"); cmds.push_back("%delete"); SlaveConfigTreeNode *current_config_node = config_tree()->find_node(_path); XLOG_ASSERT(current_config_node != NULL); current_config_node->create_command_tree( cmd_tree, cmds, true, /* include_intermediate_nodes */ false, /* include_children_templates */ true, /* include_leaf_value_nodes */ true, /* include_read_only_nodes */ false, /* include_permanent_nodes */ false /* include_user_hidden_nodes */); debug_msg("==========================================================\n"); debug_msg("delete subtree is:\n\n"); debug_msg("%s", cmd_tree.tree_str().c_str()); debug_msg("==========================================================\n"); vector vector_path; vector_path.push_back("delete"); append_list_to_vector(vector_path, _path); add_command_subtree(*_delete_node, cmd_tree.root_node(), callback(this, &RouterCLI::delete_func), vector_path, 0, false, /* can_pipe */ false /* include_allowed_values */); } void RouterCLI::add_set_subtree() { add_text_entry_commands(_set_node); } void RouterCLI::add_show_subtree() { CommandTree cmd_tree; CliCommand* com1; list cmds; string error_msg; // XXX: There is nothing to add to the "cmds" list SlaveConfigTreeNode *current_config_node = config_tree()->find_node(_path); XLOG_ASSERT(current_config_node != NULL); current_config_node->create_command_tree( cmd_tree, cmds, true, /* include_intermediate_nodes */ false, /* include_children_templates */ true, /* include_leaf_value_nodes */ true, /* include_read_only_nodes */ true, /* include_permanent_nodes */ false /* include_user_hidden_nodes */); debug_msg("==========================================================\n"); debug_msg("show subtree is:\n\n"); debug_msg("%s", cmd_tree.tree_str().c_str()); debug_msg("==========================================================\n"); vector vector_path, vector_path_all; vector_path.push_back("show"); vector_path_all.push_back("show"); vector_path_all.push_back("-all"); append_list_to_vector(vector_path, _path); append_list_to_vector(vector_path_all, _path); add_command_subtree(*_show_node, cmd_tree.root_node(), callback(this, &RouterCLI::show_func), vector_path, 0, true, /* can_pipe */ false /* include_allowed_values */); com1 = _show_node->add_command("-all", get_help_c("show -all"), false, callback(this, &RouterCLI::show_func), error_msg); if (com1 == NULL) { XLOG_FATAL("add_command %s failed: %s", "-all", error_msg.c_str()); } com1->set_global_name(vector_path_all); com1->set_can_pipe(true); add_command_subtree(*com1, cmd_tree.root_node(), callback(this, &RouterCLI::show_func), vector_path_all, 0, true, /* can_pipe */ false /* include_allowed_values */); } void RouterCLI::add_text_entry_commands(CliCommand* com0) { const SlaveConfigTreeNode* current_config_node; const TemplateTreeNode* ttn; string error_msg; if (com0 == NULL) com0 = _cli_node.cli_command_root(); debug_msg("add_text_entry_commands\n"); current_config_node = config_tree()->find_node(_path); ttn = current_config_node->template_tree_node(); if (ttn == NULL) { XLOG_TRACE(_verbose, "ttn == NULL >>%s<<\n", current_config_node->segname().c_str()); ttn = template_tree()->root_node(); } list::const_iterator tti; for (tti = ttn->children().begin(); tti != ttn->children().end(); ++tti) { TemplateTreeNode* ttn_child = *tti; CliCommand* com = NULL; vector vector_subpath; // XXX: ignore deprecated subtrees if (ttn_child->is_deprecated()) continue; // XXX: ignore user-hidden subtrees if (ttn_child->is_user_hidden()) continue; append_list_to_vector(vector_subpath, _path); vector_subpath.push_back(ttn_child->segname()); string help = ttn_child->help(); if (help == "") { help = "-- No help available --"; } if (ttn_child->is_tag()) { com = com0->add_command(ttn_child->segname(), help, false, error_msg); } else { com = com0->add_command(ttn_child->segname(), help, false, callback(this, &RouterCLI::text_entry_func), error_msg); } if (com == NULL) { XLOG_FATAL("AI: add_command %s for template failed: %s", ttn_child->segname().c_str(), error_msg.c_str()); } else { com->set_global_name(vector_subpath); } com->set_dynamic_children_callback( callback(this, &RouterCLI::text_entry_children_func)); com->set_dynamic_process_callback( callback(this, &RouterCLI::text_entry_func)); com->set_can_pipe(false); } } int RouterCLI::configure_func(const string& , const string& , uint32_t , const vector& command_global_name, const vector& /* argv */) { string error_msg; bool exclusive = false; string cmd_name = token_vector2line(command_global_name); if (cmd_name == "configure exclusive") exclusive = true; idle_ui(); if (_xorpsh.enter_config_mode( exclusive, callback(this, &RouterCLI::enter_config_done)) != true) { error_msg = c_format("ERROR: cannot enter configure mode. " "No Finder?\n"); cli_client().cli_print(error_msg); reenable_ui(); return (XORP_ERROR); } return (XORP_OK); } void RouterCLI::enter_config_done(const XrlError& e) { string error_msg; if (e == XrlError::OKAY()) { if (_xorpsh.get_config_users( callback(this, &RouterCLI::got_config_users)) != true) { error_msg = c_format("ERROR: cannot get list of users in " "configure mode. No Finder?\n"); cli_client().cli_print(error_msg); reenable_ui(); return; } return; } if ((e == XrlError::COMMAND_FAILED()) && (e.note() == "AUTH_FAIL")) { if (check_for_rtrmgr_restart() != true) { error_msg = c_format("ERROR: cannot check for rtrmgr restart. " "No Finder?\n"); cli_client().cli_print(error_msg); reenable_ui(); return; } return; } // // Either something really bad happened, or a user that didn't // have permission attempted to enter config mode. // error_msg = c_format("ERROR: %s.\n", e.note().c_str()); cli_client().cli_print(error_msg); reenable_ui(); } void RouterCLI::got_config_users(const XrlError& e, const XrlAtomList* users) { // Clear the old list if (e != XrlError::OKAY()) { if (e == XrlError::COMMAND_FAILED() && e.note() == "AUTH_FAIL") { check_for_rtrmgr_restart(); return; } cli_client().cli_print("ERROR: failed to get config users from rtrmgr.\n"); } else { _config_mode_users.clear(); size_t nusers = users->size(); bool doneme = false; for (size_t i = 0; i < nusers; i++) { XrlAtom a; a = users->get(i); try { uid_t uid = a.uint32(); // Only include me if I'm in the list more than once. if (uid == getuid() && doneme == false) doneme = true; else _config_mode_users.push_back(uid); } catch (const XrlAtom::WrongType& wt) { // This had better not happen XLOG_FATAL("Internal Error"); } } } reset_path(); configure_mode(); reenable_ui(); } void RouterCLI::new_config_user(uid_t user_id) { if ((_mode != CLI_MODE_CONFIGURE) && (_mode != CLI_MODE_TEXTENTRY)) return; _config_mode_users.push_back(user_id); string user_name = get_user_name(user_id); if (user_name.empty()) user_name = c_format("UID:%u", XORP_UINT_CAST(user_id)); string alert = c_format("User %s entered configuration mode\n", user_name.c_str()); notify_user(alert, false /* not urgent */); } void RouterCLI::leave_config_done(const XrlError& e) { if (e != XrlError::OKAY()) { cli_client().cli_print("ERROR: failed to inform rtrmgr of leaving configuration mode.\n"); // Something really bad happened - should we just exit here? if (e == XrlError::COMMAND_FAILED() && e.note() == "AUTH_FAIL") { check_for_rtrmgr_restart(); operational_mode(); return; } } operational_mode(); reenable_ui(); } void RouterCLI::notify_user(const string& alert, bool urgent) { if (_mode == CLI_MODE_TEXTENTRY && !urgent) { _alerts.push_back(alert); } else { cli_client().cli_print(alert); } } int RouterCLI::op_help_func(const string& , const string& , uint32_t , // cli_session_id const vector& command_global_name, const vector& argv) { string path; string cmd_name = token_vector2line(command_global_name); if (! argv.empty()) return (XORP_ERROR); if (cmd_name.size() == 4) { XLOG_ASSERT(cmd_name.substr(0, 4) == "help"); path = ""; } else { XLOG_ASSERT(cmd_name.substr(0, 5) == "help "); path = cmd_name.substr(5, cmd_name.size() - 5); } if (path == "") path = "NULL"; // Try and find the detailed help for standard commands map::const_iterator i; i = _help_long_o.find(path); if (i != _help_long_o.end()) { cli_client().cli_print("\n" + i->second + "\n\n"); return (XORP_OK); } // // There was no long help description available. If there's a // short description, that will have to do. // i = _help_o.find(path); if (i != _help_o.end()) { cli_client().cli_print(i->second + "\n"); return (XORP_OK); } cli_client().cli_print("Sorry, no help available for " + path + "\n"); return (XORP_OK); } int RouterCLI::conf_help_func(const string& , const string& , uint32_t , // cli_session_id const vector& command_global_name, const vector& argv) { string path; string cmd_name = token_vector2line(command_global_name); if (! argv.empty()) return (XORP_ERROR); if (cmd_name.size() == 4) { XLOG_ASSERT(cmd_name.substr(0, 4) == "help"); path = ""; } else { XLOG_ASSERT(cmd_name.substr(0, 5) == "help "); path = cmd_name.substr(5, cmd_name.size() - 5); } if (path == "") path = "NULL"; // Try and find the detailed help for standard commands map::const_iterator i; i = _help_long_c.find(path); if (i != _help_long_c.end()) { cli_client().cli_print("\n" + i->second + "\n\n"); return (XORP_OK); } // // There was no long help description available. If there's a // short description, that will have to do. // i = _help_c.find(path); if (i != _help_c.end()) { cli_client().cli_print(i->second + "\n"); return (XORP_OK); } cli_client().cli_print("Sorry, no help available for " + path + "\n"); return (XORP_OK); } int RouterCLI::logout_func(const string& , const string& , uint32_t , // cli_session_id const vector& command_global_name, const vector& argv) { string error_msg; string cmd_name = token_vector2line(command_global_name); if (! argv.empty()) { error_msg = c_format("ERROR: \"%s\" does not take any additional " "parameters.\n", cmd_name.c_str()); cli_client().cli_print(error_msg); return (XORP_ERROR); } idle_ui(); if (_cli_node.delete_client(_cli_client_ptr, error_msg) != XORP_OK) { XLOG_FATAL("internal error deleting CLI client: %s", error_msg.c_str()); return (XORP_ERROR); } _cli_client_ptr = NULL; return (XORP_OK); } int RouterCLI::exit_func(const string& , const string& , uint32_t , const vector& command_global_name, const vector& argv) { string error_msg; string cmd_name = token_vector2line(command_global_name); if (cmd_name == "exit configuration-mode") { if (! argv.empty()) { error_msg = c_format("ERROR: \"%s\" does not take any additional " "parameters.\n", cmd_name.c_str()); cli_client().cli_print(error_msg); return (XORP_ERROR); } if (_changes_made) { cli_client().cli_print("ERROR: There are uncommitted changes.\n"); cli_client().cli_print("Use \"commit\" to commit the changes, " "or \"exit discard\" to discard them.\n"); return (XORP_ERROR); } idle_ui(); if (_xorpsh.leave_config_mode( callback(this, &RouterCLI::leave_config_done)) != true) { error_msg = c_format("ERROR: failed to inform rtrmgr of leaving " "configuration mode. No Finder?\n"); cli_client().cli_print(error_msg); operational_mode(); reenable_ui(); return (XORP_ERROR); } return (XORP_OK); } if (cmd_name == "exit discard") { if (! argv.empty()) { error_msg = c_format("ERROR: \"%s\" does not take any additional " "parameters.\n", cmd_name.c_str()); cli_client().cli_print(error_msg); return (XORP_ERROR); } config_tree()->discard_changes(); _changes_made = false; idle_ui(); if (_xorpsh.leave_config_mode( callback(this, &RouterCLI::leave_config_done)) != true) { error_msg = c_format("ERROR: failed to inform rtrmgr of leaving " "config mode. No Finder?\n"); cli_client().cli_print(error_msg); operational_mode(); reenable_ui(); return (XORP_ERROR); } return (XORP_OK); } if (cmd_name == "top") { if (! argv.empty()) { error_msg = c_format("ERROR: \"%s\" does not take any additional " "parameters.\n", cmd_name.c_str()); cli_client().cli_print(error_msg); return (XORP_ERROR); } reset_path(); apply_path_change(); return (XORP_OK); } // // Commands up and exit are similar, except up doesn't exit // configuration-mode if it's executed at the top level // if (cmd_name == "up") { if (! argv.empty()) { error_msg = c_format("ERROR: \"%s\" does not take any additional " "parameters.\n", cmd_name.c_str()); cli_client().cli_print(error_msg); return (XORP_ERROR); } if (! _path.empty()) _path.pop_back(); } if ((cmd_name == "exit") || (cmd_name == "quit")) { if (! _path.empty()) { _path.pop_back(); } else { if (_changes_made) { cli_client().cli_print("ERROR: There are uncommitted changes.\n"); cli_client().cli_print("Use \"commit\" to commit the changes, " "or \"exit discard\" to discard them.\n"); return (XORP_ERROR); } idle_ui(); if (_xorpsh.leave_config_mode( callback(this, &RouterCLI::leave_config_done)) != true) { error_msg = c_format("ERROR: failed to inform rtrmgr of leaving " "config mode. No Finder?\n"); cli_client().cli_print(error_msg); operational_mode(); reenable_ui(); return (XORP_ERROR); } return (XORP_OK); } } apply_path_change(); return (XORP_OK); } int RouterCLI::edit_func(const string& , const string& , uint32_t , // cli_session_id const vector& command_global_name, const vector& argv) { string path; string cmd_name = token_vector2line(command_global_name); if (cmd_name != "edit") { XLOG_ASSERT(cmd_name.substr(0, 5) == "edit "); path = cmd_name.substr(5, cmd_name.size() - 5); } if (! argv.empty()) { string arg_string, msg; for (size_t i = 0; i < argv.size(); i++) { if (! arg_string.empty()) arg_string += " "; arg_string += argv[i]; } if (path.empty()) { msg = c_format("ERROR: sub-element \"%s\" does not exist.\n", arg_string.c_str()); } else { msg = c_format("ERROR: element \"%s\" does not contain " "sub-element \"%s\".\n", path.c_str(), arg_string.c_str()); } cli_client().cli_print(msg); return (XORP_ERROR); } if (cmd_name == "edit") { // Just print the current edit level config_mode_prompt(); return (XORP_OK); } set_path(path); apply_path_change(); return (XORP_OK); } int RouterCLI::extract_leaf_node_operator_and_value(const TemplateTreeNode& ttn, const vector& argv, ConfigOperator& node_operator, string& value, string& error_msg) { string operator_str; // // Test if the assign operator is allowed // bool is_assign_operator_allowed = false; list allowed_operators = ttn.allowed_operators(); list::iterator operator_iter; if (allowed_operators.empty()) { is_assign_operator_allowed = true; allowed_operators.push_back(OP_ASSIGN); } else { for (operator_iter = allowed_operators.begin(); operator_iter != allowed_operators.end(); ++operator_iter) { if (*operator_iter == OP_ASSIGN) { is_assign_operator_allowed = true; break; } } } // // The arguments must be a value, or an operator and a value // value = ""; node_operator = OP_NONE; bool is_error = false; switch (argv.size()) { case 0: // Missing value is_error = true; break; case 1: // A single argument, hence this implies the assign operator if (! is_assign_operator_allowed) is_error = true; node_operator = OP_ASSIGN; value = argv[0]; break; case 2: // An operator followed by a value operator_str = argv[0]; value = argv[1]; try { node_operator = lookup_operator(operator_str); } catch (const ParseError& e) { is_error = true; break; } // Test if the operator is allowed if (find(allowed_operators.begin(), allowed_operators.end(), node_operator) == allowed_operators.end()) { is_error = true; } break; default: // Too many arguments is_error = true; break; } if (is_error) { string operators_str; for (operator_iter = allowed_operators.begin(); operator_iter != allowed_operators.end(); ++operator_iter) { if (! operators_str.empty()) operators_str += " "; operators_str += operator_to_str(*operator_iter); } error_msg = c_format("should take one %soperator [%s] followed by " "one argument of type \"%s\"", (is_assign_operator_allowed)? "optional " : "", operators_str.c_str(), ttn.typestr().c_str()); return (XORP_ERROR); } if (ttn.type_match(value, error_msg) == false) { error_msg = c_format("argument \"%s\" is not a valid \"%s\": %s", value.c_str(), ttn.typestr().c_str(), error_msg.c_str()); return (XORP_ERROR); } return (XORP_OK); } int RouterCLI::text_entry_func(const string& , const string& , uint32_t , // cli_session_id const vector& command_global_name, const vector& argv) { string path; list path_segments; string cmd_name = token_vector2line(command_global_name); // // The path contains the part of the command we managed to command-line // complete. The remainder is in argv. // path = cmd_name; XLOG_TRACE(_verbose, "text_entry_func: %s\n", path.c_str()); SlaveConfigTreeNode *ctn = NULL, *first_new_ctn = NULL, *brace_ctn; const TemplateTreeNode* ttn = NULL; bool value_expected = false; string error_msg; // Restore the indent position from last time we were called size_t original_braces_length = _braces.size(); brace_ctn = config_tree()->find_node(_path); if (original_braces_length == 0) { // // We're coming here from configuration mode, rather that // already being in text_entry_mode. // if (brace_ctn == NULL) { _braces.push_front(0); brace_ctn = &(config_tree()->slave_root_node()); } else { _braces.push_front(brace_ctn->depth()); } } path_segments.insert(path_segments.end(), command_global_name.begin(), command_global_name.end()); // // Push argv onto the list of path segments - there's no // substantial difference between the path and argv, except that // path has already seen some sanity checking. // path_segments.insert(path_segments.end(), argv.begin(), argv.end()); // // The path_segments probably contain part of the path that already // exists and part that does not yet exist. Find which is which. // list new_path_segments; while (!path_segments.empty()) { ctn = config_tree()->find_node(path_segments); if (ctn != NULL) { XLOG_TRACE(_verbose, "ctn: %s\n", ctn->segname().c_str()); break; } new_path_segments.push_front(path_segments.back()); path_segments.pop_back(); } if (ctn == NULL) ctn = brace_ctn; if (ctn == NULL) ctn = &(config_tree()->slave_root_node()); XLOG_ASSERT(ctn != NULL); // // At this point, path_segments contains the path of nodes that // already exist, new_path_segments contains the path that does not // yet exist, and ctn points to the last existing node. // // // Test if the node is hidden from the user // if (ctn->is_user_hidden()) { const TemplateTreeNode* ttn = ctn->template_tree_node(); const TemplateTreeNode* user_hidden_ttn; user_hidden_ttn = ttn->find_first_user_hidden_ancestor(); if (user_hidden_ttn != NULL) { error_msg = c_format("ERROR: user-hidden path \"%s\" " "is not valid: %s.\n", makepath(path_segments).c_str(), user_hidden_ttn->user_hidden_reason().c_str()); cli_client().cli_print(error_msg); goto cleanup; } } // // Test if the node was already created. // if (new_path_segments.empty() && (! ctn->deleted())) { error_msg = c_format("ERROR: node \"%s\" already exists.\n", ctn->segname().c_str()); cli_client().cli_print(error_msg); goto cleanup; } // // If the node was deleted previously, and it is not a tag, then // undelete the whole subtree below it. // if (ctn->deleted() && (! ctn->is_tag())) ctn->undelete_subtree(); // // Undelete the node and all its ancestors (just in case it was deleted // previously). // ctn->undelete_node_and_ancestors(); // // Variable "value_expected" keeps track of whether the next // segment should be a value for the node we just created. // if (ctn->is_tag() || ctn->is_leaf_value()) value_expected = true; else value_expected = false; while (! new_path_segments.empty()) { if (! ctn->children().empty()) { // // The previous node already has children. We need to // check that the current path segment isn't one of those // children. If it is, then we don't need to create a new node. // bool exists = false; list ::iterator cti; for (cti = ctn->children().begin(); cti != ctn->children().end(); ++cti) { if ((*cti)->segname() == new_path_segments.front()) { XLOG_TRACE(_verbose, "Found pre-existing node: %s\n", (*cti)->segname().c_str()); ConfigTreeNode *existing_ctn = (*cti); ctn = (SlaveConfigTreeNode*)(existing_ctn); path_segments.push_back(new_path_segments.front()); new_path_segments.pop_front(); exists = true; break; } } if (exists) continue; } if (value_expected) { // We're expecting a value here if (new_path_segments.front() == "{" || new_path_segments.front() == "}" ) { error_msg = c_format("ERROR: a value for \"%s\" " "is required.\n", ctn->segname().c_str()); cli_client().cli_print(error_msg); goto cleanup; } string value = new_path_segments.front(); ttn = ctn->template_tree_node(); XLOG_ASSERT(ttn != NULL); if (ctn->is_tag()) { const TemplateTreeNode *data_ttn = NULL; // // The tag can have multiple children of different types - // take the first that has valid syntax. // // TODO: we really ought to check the allow commands here string cand_types; string errhelp; string allowed_value_error_msg; list::const_iterator tti; for (tti = ttn->children().begin(); tti != ttn->children().end(); ++tti) { // XXX: ignore deprecated subtrees if ((*tti)->is_deprecated()) continue; // XXX: ignore user-hidden subtrees if ((*tti)->is_user_hidden()) continue; if (! (*tti)->check_allowed_value(value, errhelp)) { if (! allowed_value_error_msg.empty()) allowed_value_error_msg += " "; allowed_value_error_msg += errhelp; errhelp = ""; continue; } if ((*tti)->type_match(value, errhelp)) { data_ttn = (*tti); break; } if (! cand_types.empty()) cand_types += " or "; cand_types += c_format("\"%s\"", (*tti)->typestr().c_str()); } if (data_ttn == NULL) { if (ttn->type() != NODE_VOID) { cand_types = c_format("\"%s\"", ttn->typestr().c_str()); } error_msg = c_format("ERROR: argument \"%s\" " "is not valid.", value.c_str()); if (! allowed_value_error_msg.empty()) { error_msg += " " + allowed_value_error_msg; } if (! cand_types.empty()) { error_msg += c_format(" Allowed type(s): %s.", cand_types.c_str()); } error_msg += "\n"; cli_client().cli_print(error_msg); goto cleanup; } path_segments.push_back(value); XLOG_TRACE(_verbose, "creating node %s\n", value.c_str()); ctn = new SlaveConfigTreeNode(value, makepath(path_segments), data_ttn, ctn, ConfigNodeId::ZERO(), getuid(), clientid(), _verbose); if (first_new_ctn == NULL) first_new_ctn = ctn; _changes_made = true; value_expected = false; } else if (ctn->is_leaf_value()) { // // It must be a leaf, and we're expecting a value, // or an operator and a value. // // We cannot modify a read-only node if (ctn->is_read_only() && (! ctn->is_default_value(value))) { string reason = ctn->read_only_reason(); error_msg = c_format("ERROR: node \"%s\" is read-only", ctn->segname().c_str()); if (! reason.empty()) error_msg += c_format(": %s", reason.c_str()); error_msg += ".\n"; cli_client().cli_print(error_msg); goto cleanup; } vector argv; list::iterator list_iter; for (list_iter = new_path_segments.begin(); list_iter != new_path_segments.end(); ++list_iter) { argv.push_back(*list_iter); } if (new_path_segments.size() == 2) { // XXX: pop-up the operator new_path_segments.pop_front(); } ConfigOperator node_operator = OP_NONE; if (extract_leaf_node_operator_and_value(*ttn, argv, node_operator, value, error_msg) != XORP_OK) { error_msg = c_format("ERROR: node \"%s\": %s.\n", ctn->segname().c_str(), error_msg.c_str()); cli_client().cli_print(error_msg); goto cleanup; } XLOG_ASSERT(ttn->type_match(value, error_msg) == true); XLOG_TRACE(_verbose, "Setting node \"%s\": operator %s " "and value %s\n", ctn->segname().c_str(), operator_to_str(node_operator).c_str(), value.c_str()); // Set the value if (ctn->set_value(value, getuid(), error_msg) != true) { error_msg = c_format("ERROR: invalid value \"%s\": %s\n", value.c_str(), error_msg.c_str()); cli_client().cli_print(error_msg); goto cleanup; } // Set the operator if (ctn->set_operator(node_operator, getuid(), error_msg) != true) { error_msg = c_format("ERROR: invalid operator value \"%s\": %s\n", operator_to_str(node_operator).c_str(), error_msg.c_str()); cli_client().cli_print(error_msg); goto cleanup; } _changes_made = true; value_expected = false; } else { XLOG_UNREACHABLE(); } } else { if (new_path_segments.front() == "{") { // // Just keep track of the nesting depth so we can match // braces on the way back out. // _braces.push_back(ctn->depth()); brace_ctn = ctn; new_path_segments.pop_front(); continue; } if (new_path_segments.front() == "}") { // Jump back out to the appropriate nesting depth if (_braces.size() == 1) { // There's always at least one entry in _braces cli_client().cli_print("ERROR: mismatched \"}\".\n"); goto cleanup; } XLOG_TRACE(_verbose, "braces: %u ctn depth: %u ctn: %s\n", XORP_UINT_CAST(_braces.back()), ctn->depth(), ctn->segname().c_str()); while (ctn->depth() > _braces.back()) { //looks like one or more close braces on the same //line as some config - just pop out //appropriately before we can handle this correctly. ctn = ctn->parent(); XLOG_TRACE(_verbose, "jumping out one level\n"); XLOG_TRACE(_verbose, "braces: %u ctn depth: %u ctn: %s\n", XORP_UINT_CAST(_braces.back()), ctn->depth(), ctn->segname().c_str()); } // The last brace we entered should match the current depth XLOG_ASSERT(_braces.back() == ctn->depth()); _braces.pop_back(); // The next brace should be where we're jumping to. uint32_t new_depth = 0; XLOG_ASSERT(!_braces.empty()); new_depth = _braces.back(); XLOG_TRACE(_verbose, "new_depth: %u\n", XORP_UINT_CAST(new_depth)); XLOG_ASSERT(ctn->depth() > new_depth); while (ctn->depth() > new_depth) { ctn = ctn->parent(); } while (path_segments.size() > new_depth) { path_segments.pop_back(); } brace_ctn = ctn; XLOG_TRACE(_verbose, "brace_ctn = %s\n", brace_ctn->segname().c_str()); new_path_segments.pop_front(); continue; } // // We're expecting a tag, a grouping node, or a leaf node. // path_segments.push_back(new_path_segments.front()); ttn = config_tree()->find_template(path_segments); if (ttn == NULL) { error_msg = c_format("ERROR: path \"%s\" is not valid.\n", makepath(path_segments).c_str()); cli_client().cli_print(error_msg); goto cleanup; } // // Test if some part of the configuration path is deprecated // const TemplateTreeNode* deprecated_ttn; deprecated_ttn = ttn->find_first_deprecated_ancestor(); if (deprecated_ttn != NULL) { error_msg = c_format("ERROR: deprecated path \"%s\" " "is not valid: %s.\n", makepath(path_segments).c_str(), deprecated_ttn->deprecated_reason().c_str()); cli_client().cli_print(error_msg); goto cleanup; } // // Test if some part of the configuration path is user-hidden // const TemplateTreeNode* user_hidden_ttn; user_hidden_ttn = ttn->find_first_user_hidden_ancestor(); if (user_hidden_ttn != NULL) { error_msg = c_format("ERROR: user-hidden path \"%s\" " "is not valid: %s.\n", makepath(path_segments).c_str(), user_hidden_ttn->user_hidden_reason().c_str()); cli_client().cli_print(error_msg); goto cleanup; } XLOG_TRACE(_verbose, "creating node %s\n", ttn->segname().c_str()); ctn = new SlaveConfigTreeNode(ttn->segname(), makepath(path_segments), ttn, ctn, ConfigNodeId::ZERO(), getuid(), clientid(), _verbose); if (first_new_ctn == NULL) first_new_ctn = ctn; _changes_made = true; if (ttn->is_tag() || ctn->is_leaf_value()) { XLOG_TRACE(_verbose, "value expected\n"); value_expected = true; } else { XLOG_TRACE(_verbose, "value not expected\n"); value_expected = false; } } new_path_segments.pop_front(); } // // If there's no more input and we still expected a value, the // input was erroneous. // if (value_expected) { error_msg = c_format("ERROR: node \"%s\" requires a value.\n", ctn->segname().c_str()); cli_client().cli_print(error_msg); goto cleanup; } // Preserve state for next time, and set up the command prompts _path = split(brace_ctn->path(), ' '); if (_braces.size() == 1) { // We're finishing text_entry mode _braces.pop_front(); config_tree()->add_default_children(); // Force the commands to be regenerated if any changes were made if (_mode == CLI_MODE_CONFIGURE && _changes_made) _mode = CLI_MODE_TEXTENTRY; configure_mode(); } else { // // We need to remain in text_entry mode because we haven't seen // the closing braces yet. // text_entry_mode(); add_text_entry_commands(NULL); CliCommand* com; string error_msg; com = _cli_node.cli_command_root()->add_command( "}", "Complete this configuration level", false, callback(this, &RouterCLI::text_entry_func), error_msg); if (com == NULL) { XLOG_FATAL("add_command %s failed: %s", "}", error_msg.c_str()); } com->set_global_name(token_line2vector("}")); com->set_can_pipe(false); } return (XORP_OK); cleanup: // // Cleanup back to the state we were in before we started parsing this mess // while (_braces.size() > original_braces_length) { _braces.pop_back(); } if (first_new_ctn != NULL) { XLOG_TRACE(_verbose, "deleting node %s\n", first_new_ctn->segname().c_str()); first_new_ctn->delete_subtree_silently(); } return (XORP_ERROR); } map RouterCLI::text_entry_children_func(const vector& vector_path) const { string command_name; string help_string; bool is_executable = false; bool can_pipe = false; map children; string path; list path_segments; const TemplateTreeNode *ttn = NULL; path = token_vector2line(vector_path); XLOG_TRACE(_verbose, "text_entry_children_func: %s\n", path.c_str()); string newpath = path; while (! newpath.empty()) { string::size_type ix = newpath.find(' '); if (ix == string::npos) { path_segments.push_back(newpath); break; } path_segments.push_back(newpath.substr(0, ix)); newpath = newpath.substr(ix + 1, newpath.size() - ix + 1); } is_executable = true; can_pipe = false; const ConfigTreeNode* ctn = config_tree()->find_const_node(path_segments); if (ctn != NULL) ttn = ctn->template_tree_node(); if (ttn != NULL && ttn->is_tag()) { list::const_iterator iter; for (iter = ctn->const_children().begin(); iter != ctn->const_children().end(); ++iter) { const ConfigTreeNode* ctn_child = *iter; if (ctn_child->template_tree_node() != NULL) help_string = ctn_child->template_tree_node()->help(); if (help_string == "") { help_string = "-- No help available --"; } command_name = ctn_child->segname(); bool is_executable_tmp = is_executable; bool can_pipe_tmp = can_pipe; CliCommandMatch ccm(command_name, help_string, is_executable_tmp, can_pipe_tmp); if (ctn_child->is_leaf_value()) ccm.set_is_argument_expected(true); children.insert(make_pair(command_name, ccm)); } } ttn = template_tree()->find_node(path_segments); if (ttn != NULL && (! ttn->is_deprecated()) && (! ttn->is_user_hidden())) { list::const_iterator tti; for (tti = ttn->children().begin(); tti != ttn->children().end(); ++tti) { const TemplateTreeNode* ttn_child = *tti; // XXX: ignore deprecated subtrees if (ttn_child->is_deprecated()) continue; // XXX: ignore user-hidden subtrees if (ttn_child->is_user_hidden()) continue; help_string = ttn_child->help(); if (help_string == "") { help_string = "-- No help available --"; } if (ttn_child->segname() == "@") { if (ttn_child->allowed_values().empty() && ttn_child->allowed_ranges().empty()) { string encoded_typestr = ttn_child->encoded_typestr(); command_name = encoded_typestr; CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); CliCommand::TypeMatchCb cb; cb = callback(ttn_child, &TemplateTreeNode::type_match); ccm.set_type_match_cb(cb); if (ttn_child->is_leaf_value()) ccm.set_is_argument_expected(true); children.insert(make_pair(command_name, ccm)); } else { add_allowed_values_and_ranges(ttn_child, is_executable, can_pipe, children); } } else { command_name = ttn_child->segname(); CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); if (ttn_child->is_leaf_value() || ttn_child->is_tag()) ccm.set_is_argument_expected(true); children.insert(make_pair(command_name, ccm)); } } if (ttn->is_tag()) { is_executable = false; can_pipe = false; } if (!ttn->is_tag() && !ttn->children().empty()) { help_string = "Enter text on multiple lines"; command_name = "{"; CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); if (ttn->is_leaf_value()) ccm.set_is_argument_expected(true); children.insert(make_pair(command_name, ccm)); } if (ttn->is_leaf_value() && (! ttn->is_tag()) && ttn->allowed_values().empty() && ttn->allowed_ranges().empty()) { // Leaf node: add a child with expected value type help_string = ttn->help(); if (help_string == "") { help_string = "-- No help available --"; } string encoded_typestr = ttn->encoded_typestr(); command_name = encoded_typestr; CliCommandMatch ccm(command_name, help_string, is_executable, can_pipe); CliCommand::TypeMatchCb cb; cb = callback(ttn, &TemplateTreeNode::type_match); ccm.set_type_match_cb(cb); children.insert(make_pair(command_name, ccm)); } // // Add the command-line completion for the allowed values and ranges // if (ttn->segname() != "@") { add_allowed_values_and_ranges(ttn, is_executable, can_pipe, children); } } return children; } void RouterCLI::add_allowed_values_and_ranges( const TemplateTreeNode* ttn, bool is_executable, bool can_pipe, map& children) const { // // Add the command-line completion for the allowed values // map::const_iterator values_iter; for (values_iter = ttn->allowed_values().begin(); values_iter != ttn->allowed_values().end(); ++values_iter) { const string& cmd_name = values_iter->first; string help_string = values_iter->second; if (help_string == "") { help_string = "-- No help available --"; } CliCommandMatch ccm(cmd_name, help_string, is_executable, can_pipe); ccm.set_is_command_argument(true); children.insert(make_pair(cmd_name, ccm)); } // // Add the command-line completion for the allowed ranges // map, string>::const_iterator ranges_iter; for (ranges_iter = ttn->allowed_ranges().begin(); ranges_iter != ttn->allowed_ranges().end(); ++ranges_iter) { const pair& range = ranges_iter->first; ostringstream ost; ost << "[" << range.first << ".." << range.second << "]"; string cmd_name = ost.str(); string help_string = ranges_iter->second; if (help_string == "") { help_string = "-- No help available --"; } CliCommandMatch ccm(cmd_name, help_string, is_executable, can_pipe); CliCommand::TypeMatchCb cb; cb = callback(ttn, &TemplateTreeNode::type_match); ccm.set_type_match_cb(cb); ccm.set_is_command_argument(true); children.insert(make_pair(cmd_name, ccm)); } } int RouterCLI::delete_func(const string& , const string& , uint32_t , const vector& command_global_name, const vector& argv) { string error_msg; string path; list path_segments; string cmd_name = token_vector2line(command_global_name); XLOG_ASSERT(cmd_name.substr(0, 7) == "delete "); path = cmd_name.substr(7, cmd_name.size() - 7); while (! path.empty()) { string::size_type ix = path.find(' '); if (ix == string::npos) { path_segments.push_back(path); break; } path_segments.push_back(path.substr(0, ix)); path = path.substr(ix + 1, path.size() - ix + 1); } if (! argv.empty()) { // // XXX: The arguments to the "delete" command should refer // to a valid node in the configuration tree, and argument // "command_global_name" should include the name of that node. // Hence, "argv" should always be empty. If "argv" is not empty, // then it is an error. // // // First test whether this is not an existing node that cannot // be removed. // vector::const_iterator iter; for (iter = argv.begin(); iter != argv.end(); ++iter) { path_segments.push_back(*iter); } SlaveConfigTreeNode* ctn = config_tree()->find_node(path_segments); if ((ctn != NULL) && ctn->is_permanent()) { error_msg = c_format("ERROR: cannot delete \"%s\" because it is " "a permanent node.\n", makepath(argv).c_str()); } else { error_msg = c_format("ERROR: cannot delete \"%s\" because it " "doesn't exist.\n", makepath(argv).c_str()); } cli_client().cli_print(error_msg); return (XORP_ERROR); } // // Test if this is a valid node that can be deleted // SlaveConfigTreeNode* ctn = config_tree()->find_node(path_segments); if (ctn == NULL) { error_msg = c_format("ERROR: cannot delete \"%s\" because it doesn't " "exist.\n", makepath(path_segments).c_str()); cli_client().cli_print(error_msg); return (XORP_ERROR); } string result = config_tree()->show_subtree(true, path_segments, false, true); cli_client().cli_print("Deleting: \n"); cli_client().cli_print(result + "\n"); result = config_tree()->mark_subtree_for_deletion(path_segments, getuid()); cli_client().cli_print(result + "\n"); // Regenerate the available command tree without the deleted stuff _changes_made = true; apply_path_change(); return (XORP_OK); } int RouterCLI::commit_func(const string& , const string& , uint32_t , const vector& command_global_name, const vector& argv) { string error_msg; string cmd_name = token_vector2line(command_global_name); if (! argv.empty()) { error_msg = c_format("ERROR: \"%s\" does not take any additional " "parameters.\n", cmd_name.c_str()); cli_client().cli_print(error_msg); return (XORP_ERROR); } if (! _changes_made) { error_msg = c_format("No configuration changes to commit.\n"); cli_client().cli_print(error_msg); config_mode_prompt(); return (XORP_ERROR); } // Clear the UI idle_ui(); string response; XLOG_ASSERT(_changes_made); bool success = config_tree()->commit_changes( response, _xorpsh, callback(this, &RouterCLI::commit_done)); if (! success) { // _changes_made = false; cli_client().cli_print(response); cli_client().cli_print("The configuration has not been changed.\n"); cli_client().cli_print("Fix this error, and run \"commit\" again.\n"); // set_prompt("", _configuration_mode_prompt); // apply_path_change(); silent_reenable_ui(); return (XORP_ERROR); } // cli_client().cli_print(response + "\n"); // apply_path_change(); return (XORP_OK); } void RouterCLI::idle_ui() { cli_client().set_is_waiting_for_data(true); } void RouterCLI::silent_reenable_ui() { cli_client().set_is_waiting_for_data(false); } void RouterCLI::reenable_ui() { cli_client().set_is_waiting_for_data(false); cli_client().post_process_command(); } void RouterCLI::commit_done(bool success, string error_msg) { // // If we get a failure back here, report it. Otherwise expect an // XRL from the rtrmgr to inform us what really happened as the // change was applied. // if (! success) { cli_client().cli_print("Commit Failed\n"); cli_client().cli_print(error_msg); } else { _changes_made = false; cli_client().cli_print("OK\n"); cli_client().cli_print(error_msg); } _xorpsh.set_mode(XorpShellBase::MODE_IDLE); apply_path_change(); reenable_ui(); } int RouterCLI::show_func(const string& , const string& , uint32_t , // cli_session_id const vector& command_global_name, const vector& argv) { string error_msg; string cmd_name = token_vector2line(command_global_name); bool is_show_all = false; bool suppress_default_values = true; bool show_top = false; const string show_command_name = "show"; const string show_all_command_name = "show -all"; if (! argv.empty()) { // // XXX: The arguments to the "show" command should refer // to a valid node in the configuration tree, and argument // "command_global_name" should include the name of that node. // Hence, "argv" should always be empty. If "argv" is not empty, // then it is an error. // error_msg = c_format("ERROR: cannot show \"%s\" because it doesn't " "exist.\n", makepath(argv).c_str()); cli_client().cli_print(error_msg); return (XORP_ERROR); } XLOG_ASSERT(cmd_name.substr(0, show_command_name.size()) == show_command_name); // // Test if this is the "show -all" command // if (cmd_name.size() >= show_all_command_name.size()) { if (cmd_name.substr(0, show_all_command_name.size()) == show_all_command_name) is_show_all = true; } if (is_show_all) suppress_default_values = false; string path; if ((cmd_name == show_command_name) || (cmd_name == show_all_command_name)) { // Command "show" or "show -all" with no parameters at the top level path = ""; } else { string::size_type s; if (is_show_all) s = show_all_command_name.size() + 1; else s = show_command_name.size() + 1; path = cmd_name.substr(s, cmd_name.size() - s); } list path_segments; while (! path.empty()) { string::size_type ix = path.find(' '); if (ix == string::npos) { path_segments.push_back(path); break; } path_segments.push_back(path.substr(0, ix)); path = path.substr(ix + 1, path.size() - ix + 1); } // // XXX: if we explicitly specify to show the value of a leaf node, // then always show it without suppressing it. // SlaveConfigTreeNode* ctn = config_tree()->find_node(path_segments); if ((ctn != NULL) && ctn->is_leaf_value()) { suppress_default_values = false; show_top = true; } string result = config_tree()->show_subtree(show_top, path_segments, false, suppress_default_values); cli_client().cli_print(result + "\n"); config_mode_prompt(); return (XORP_OK); } int RouterCLI::op_mode_func(const string& , const string& , // cli_term_name uint32_t , // cli_session_id const vector& command_global_name, const vector& argv) { list path_segments; string cmd_name = token_vector2line(command_global_name); string full_command; full_command = cmd_name; path_segments.insert(path_segments.end(), command_global_name.begin(), command_global_name.end()); for (size_t i = 0; i < argv.size(); i++) { if (argv[i] == "|") break; // The pipe command path_segments.push_back(argv[i]); full_command += " " + argv[i]; } if (op_cmd_list()->command_match(path_segments, true)) { // Clear the UI idle_ui(); // Verify that any previous command has been disposed of. XLOG_ASSERT(NULL == _op_mode_cmd); _op_mode_cmd = op_cmd_list()->execute( _xorpsh.eventloop(), path_segments, callback(this, &RouterCLI::op_mode_cmd_print), callback(this, &RouterCLI::op_mode_cmd_done)); } else { // // Try to figure out where the error is, so we can give useful // feedback to the user. // list::const_iterator iter; list test_parts; string cmd_error; cli_client().cli_print("ERROR: no matching command:\n"); for (iter = path_segments.begin(); iter != path_segments.end(); ++iter) { if (iter != path_segments.begin()) cmd_error += " "; test_parts.push_back(*iter); if (op_cmd_list()->command_match(test_parts, false)) { for (size_t i = 0; i < iter->size(); i++) { cmd_error += " "; } } else { cmd_error += "^"; break; } } cli_client().cli_print( c_format("%s\n%s\n", full_command.c_str(), cmd_error.c_str()) ); return (XORP_ERROR); } return (XORP_OK); } void RouterCLI::op_mode_cmd_print(const string& result) { if (! result.empty()) { cli_client().cli_print(result); cli_client().flush_process_command_output(); } } void RouterCLI::op_mode_cmd_done(bool success, const string& error_msg) { if (! success) { string tmp_error_msg; if (! error_msg.empty()) tmp_error_msg = error_msg; else tmp_error_msg = ""; cli_client().cli_print(c_format("ERROR: %s\n", tmp_error_msg.c_str())); } op_mode_cmd_tidy(); reenable_ui(); } void RouterCLI::op_mode_cmd_interrupt(const string& server_name, const string& cli_term_name, uint32_t cli_session_id, const vector& command_global_name, const vector& command_args) { // // The user has sent an interrupt. If an operational mode command // is running terminate it. // op_mode_cmd_tidy(); UNUSED(server_name); UNUSED(cli_term_name); UNUSED(cli_session_id); UNUSED(command_global_name); UNUSED(command_args); } void RouterCLI::op_mode_cmd_tidy() { if (_op_mode_cmd != NULL) { delete _op_mode_cmd; _op_mode_cmd = NULL; } } int RouterCLI::save_func(const string& , const string& , uint32_t , // cli_session_id const vector& command_global_name, const vector& argv) { string cmd_name = token_vector2line(command_global_name); if (argv.size() != 1) { cli_client().cli_print("Usage: save \n"); return (XORP_ERROR); } XLOG_ASSERT(cmd_name == "save"); XLOG_TRACE(_verbose, "save, filename = %s\n", argv[0].c_str()); if (_xorpsh.save_to_file(argv[0], callback(this, &RouterCLI::save_communicated), callback(this, &RouterCLI::save_done)) != true) { save_done(false, "Cannot send file to rtrmgr. No Finder?\n"); return (XORP_ERROR); } idle_ui(); return (XORP_OK); } // // Method save_communicated() is called when the request for the save has been // communicated to the rtrmgr, or when this communication has failed. // void RouterCLI::save_communicated(const XrlError& e) { if (e != XrlError::OKAY()) { cli_client().cli_print("ERROR: Save failed.\n"); if (e == XrlError::COMMAND_FAILED()) { if (e.note() == "AUTH_FAIL") { check_for_rtrmgr_restart(); return; } else { cli_client().cli_print(c_format("%s\n", e.note().c_str())); } } else { cli_client().cli_print("Failed to communicate save command to rtrmgr.\n"); cli_client().cli_print(c_format("%s\n", e.error_msg())); } _xorpsh.set_mode(XorpShellBase::MODE_IDLE); reenable_ui(); return; } _xorpsh.set_mode(XorpShellBase::MODE_SAVING); // // Don't enable the UI - we'll get called back when the saving has // completed. // } // // Method save_done() is called when the save has completed, // or when something goes wrong during this process. // void RouterCLI::save_done(bool success, string error_msg) { if (! success) { cli_client().cli_print("ERROR: Save failed.\n"); cli_client().cli_print(error_msg); } else { cli_client().cli_print("Save done.\n"); } _xorpsh.set_mode(XorpShellBase::MODE_IDLE); reenable_ui(); } int RouterCLI::load_func(const string& , const string& , uint32_t , const vector& command_global_name, const vector& argv) { string cmd_name = token_vector2line(command_global_name); if (argv.size() != 1) { cli_client().cli_print("Usage: load \n"); return (XORP_ERROR); } XLOG_ASSERT(cmd_name == "load"); XLOG_TRACE(_verbose, "load, filename = %s\n", argv[0].c_str()); if (_xorpsh.load_from_file(argv[0], callback(this, &RouterCLI::load_communicated), callback(this, &RouterCLI::load_done)) != true) { load_done(false, "Cannot tell the rtrmgr to load the configuration. " "No Finder?\n"); return (XORP_ERROR); } idle_ui(); return (XORP_OK); } // // Method load_communicated() is called when the request for the load has been // communicated to the rtrmgr, or when this communication has failed. // void RouterCLI::load_communicated(const XrlError& e) { if (e != XrlError::OKAY()) { cli_client().cli_print("ERROR: Load failed.\n"); if (e == XrlError::COMMAND_FAILED()) { if (e.note() == "AUTH_FAIL") { check_for_rtrmgr_restart(); return; } else { cli_client().cli_print(c_format("%s\n", e.note().c_str())); } } else { cli_client().cli_print("Failed to communicate load command to rtrmgr.\n"); cli_client().cli_print(c_format("%s\n", e.error_msg())); } _xorpsh.set_mode(XorpShellBase::MODE_IDLE); reenable_ui(); return; } _xorpsh.set_mode(XorpShellBase::MODE_LOADING); // // Don't enable the UI - we'll get called back when the commit has // completed. // } // // Method load_done() is called when the load has successfully been applied to // all the router modules, or when something goes wrong during this process. // void RouterCLI::load_done(bool success, string error_msg) { if (! success) { cli_client().cli_print("ERROR: Load failed.\n"); cli_client().cli_print(error_msg); } else { cli_client().cli_print("Load done.\n"); } _xorpsh.set_mode(XorpShellBase::MODE_IDLE); // // XXX: Reset the path, because the current node may be gone after // we have loaded the new configuration. // After that rebuild the whole command tree because the configuration // has changed. // reset_path(); apply_path_change(); reenable_ui(); } // // Just to make the code more readable: // SlaveConfigTree* RouterCLI::config_tree() { return _xorpsh.config_tree(); } const SlaveConfigTree* RouterCLI::config_tree() const { return _xorpsh.config_tree(); } // // Just to make the code more readable: // const TemplateTree* RouterCLI::template_tree() const { return _xorpsh.template_tree(); } // // Just to make the code more readable: // OpCommandList* RouterCLI::op_cmd_list() const { return _xorpsh.op_cmd_list(); } // // Just to make the code more readable: // uint32_t RouterCLI::clientid() const { return _xorpsh.clientid(); } bool RouterCLI::check_for_rtrmgr_restart() { return (_xorpsh.get_rtrmgr_pid( callback(this, &RouterCLI::verify_rtrmgr_restart)) == true); } void RouterCLI::verify_rtrmgr_restart(const XrlError& e, const uint32_t* pid) { if (e == XrlError::OKAY()) { if (_xorpsh.rtrmgr_pid() != *pid) { cli_client().cli_print("FATAL ERROR.\n"); cli_client().cli_print("The router manager process has restarted.\n"); cli_client().cli_print("Advise logout and login again.\n"); reenable_ui(); return; } // // It's not clear what happened, but attempt to carry on. // cli_client().cli_print("ERROR: authentication failure.\n"); reenable_ui(); return; } else { cli_client().cli_print("FATAL ERROR.\n"); cli_client().cli_print("The router manager process is no longer functioning correctly.\n"); cli_client().cli_print("Advise logout and login again, but if login fails the router may need rebooting.\n"); reenable_ui(); return; } } string RouterCLI::makepath(const list& parts) const { string path; list::const_iterator iter; for (iter = parts.begin(); iter != parts.end(); ++iter) { if (path.empty()) path = *iter; else path += " " + *iter; } return path; } string RouterCLI::makepath(const vector& parts) const { string path; vector::const_iterator iter; for (iter = parts.begin(); iter != parts.end(); ++iter) { if (path.empty()) path = *iter; else path += " " + *iter; } return path; }