// -*- 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/op_commands.cc,v 1.68 2007/02/16 22:47:23 pavlin Exp $" // #define DEBUG_LOGGING // #define DEBUG_PRINT_FUNCTION_NAME #include "rtrmgr_module.h" #include "libxorp/xorp.h" #include "libxorp/xlog.h" #include "libxorp/debug.h" #include "libxorp/run_command.hh" #include "libxorp/utils.hh" #ifdef HAVE_GLOB_H #include #elif defined(HOST_OS_WINDOWS) #include "glob_win32.h" #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_SIGNAL_H #include #endif #include "cli.hh" #include "op_commands.hh" #include "slave_conf_tree.hh" #include "template_tree.hh" #include "slave_module_manager.hh" #include "y.opcmd_tab.h" #ifdef HOST_OS_WINDOWS #define stat _stat #define S_IFDIR _S_IFDIR #define S_ISREG _S_ISREG #endif extern int init_opcmd_parser(const char *filename, OpCommandList *o); extern void parse_opcmd() throw (ParseError); extern int opcmderror(const char *s); OpInstance::OpInstance(EventLoop& eventloop, OpCommand& op_command, const string& executable_filename, const list& command_argument_list, RouterCLI::OpModePrintCallback print_cb, RouterCLI::OpModeDoneCallback done_cb) : _eventloop(eventloop), _op_command(op_command), _executable_filename(executable_filename), _command_argument_list(command_argument_list), _run_command(NULL), _print_cb(print_cb), _done_cb(done_cb) { string errmsg; bool success = true; XLOG_ASSERT(_op_command.is_executable()); _op_command.add_instance(this); do { if (_executable_filename.empty()) { errmsg = c_format("Empty program filename"); success = false; break; } // Run the program XLOG_ASSERT(_run_command == NULL); _run_command = new RunCommand( _eventloop, _executable_filename, _command_argument_list, callback(this, &OpInstance::stdout_cb), callback(this, &OpInstance::stderr_cb), callback(this, &OpInstance::done_cb), true /* redirect_stderr_to_stdout */); if (_run_command->execute() != XORP_OK) { // Create the string with the command name and its arguments string program_request = _executable_filename; list::iterator iter; for (iter = _command_argument_list.begin(); iter != _command_argument_list.end(); ++iter) { program_request += " "; program_request += iter->c_str(); } delete _run_command; _run_command = NULL; errmsg = c_format("Could not execute program %s", program_request.c_str()); success = false; break; } break; } while (false); if (! success) _done_cb->dispatch(false, errmsg); } OpInstance::~OpInstance() { if (_run_command != NULL) { delete _run_command; _run_command = NULL; } _op_command.remove_instance(this); } void OpInstance::terminate() { if (_run_command != NULL) _run_command->terminate(); } void OpInstance::terminate_with_prejudice() { if (_run_command != NULL) _run_command->terminate_with_prejudice(); } void OpInstance::stdout_cb(RunCommand* run_command, const string& output) { XLOG_ASSERT(run_command == _run_command); // XXX: don't accumulate the output, but print it immediately _print_cb->dispatch(output); } void OpInstance::stderr_cb(RunCommand* run_command, const string& output) { XLOG_ASSERT(run_command == _run_command); // XXX: print immediately the stderr along with the stdout _print_cb->dispatch(output); } void OpInstance::done_cb(RunCommand* run_command, bool success, const string& error_msg) { XLOG_ASSERT(run_command == _run_command); if (! success) _error_msg += error_msg; if (_run_command != NULL) { delete _run_command; _run_command = NULL; } execute_done(success); } void OpInstance::execute_done(bool success) { _done_cb->dispatch(success, _error_msg); // The callback will delete us. Don't do anything more in this method. // delete this; } OpCommand::OpCommand(OpCommandList& ocl, const list& command_parts) : _ocl(ocl), _command_parts(command_parts), _is_invalid(false), _default_nomore_mode(false) { _command_name = command_parts2command_name(command_parts); } void OpCommand::add_opt_param(const string& opt_param, const string& opt_param_help) { XLOG_ASSERT(_opt_params.find(opt_param) == _opt_params.end()); _opt_params.insert(make_pair(opt_param, opt_param_help)); } bool OpCommand::has_opt_param(const string& opt_param) const { return (_opt_params.find(opt_param) != _opt_params.end()); } string OpCommand::str() const { string res = command_name() + "\n"; if (_command_action.empty()) { res += " No command action specified\n"; } else { res += " Command: " + _command_action + "\n"; } map::const_iterator iter; for (iter = _opt_params.begin(); iter != _opt_params.end(); ++iter) { res += " Optional Parameter: " + iter->first; res += " (Parameter Help: )" + iter->second; res += "\n"; } return res; } string OpCommand::command_parts2command_name(const list& command_parts) { string res; list::const_iterator iter; for (iter = command_parts.begin(); iter != command_parts.end(); ++iter) { if (iter != command_parts.begin()) res += " "; res += *iter; } return res; } list OpCommand::select_positional_argument(const list& argument_list, const string& position, string& error_msg) { list resolved_list; // // Check the positional argument // if (position.empty()) { error_msg = c_format("Empty positional argument"); return resolved_list; } if (position[0] != '$') { error_msg = c_format("Invalid positional argument \"%s\": " "first symbol is not '$'", position.c_str()); return resolved_list; } if (position.size() <= 1) { error_msg = c_format("Invalid positional argument \"%s\": " "missing position value", position.c_str()); return resolved_list; } // // Get the positional argument value // const string pos_str = position.substr(1, string::npos); int pos = atoi(pos_str.c_str()); if ((pos < 0) || (pos > static_cast(argument_list.size()))) { error_msg = c_format("Invalid positional argument \"%s\": " "expected values must be in interval " "[0, %u]", position.c_str(), XORP_UINT_CAST(argument_list.size())); return resolved_list; } list::const_iterator iter; if (pos == 0) { // Add all arguments resolved_list = argument_list; } else { // Select a single argument int tmp_pos = 0; for (iter = argument_list.begin(); iter != argument_list.end(); ++iter) { tmp_pos++; if (tmp_pos == pos) { resolved_list.push_back(*iter); break; } } } XLOG_ASSERT(! resolved_list.empty()); return resolved_list; } OpInstance * OpCommand::execute(EventLoop& eventloop, const list& command_line, RouterCLI::OpModePrintCallback print_cb, RouterCLI::OpModeDoneCallback done_cb) { list resolved_command_argument_list; list::const_iterator iter; if (! is_executable()) { done_cb->dispatch(false, "Command is not executable"); return 0; } // // Add all arguments. If an argument is positional (e.g., $0, $1, etc), // then resolve it by using the strings from the command line. // for (iter = _command_action_argument_list.begin(); iter != _command_action_argument_list.end(); ++iter) { const string& arg = *iter; XLOG_ASSERT(! arg.empty()); // If the argument is not positional, then just add it if (arg[0] != '$') { resolved_command_argument_list.push_back(arg); continue; } // // The argument is positional, hence resolve it using // the command-line strings. // string error_msg; list resolved_list = select_positional_argument(command_line, arg, error_msg); if (resolved_list.empty()) { XLOG_FATAL("Internal programming error: %s", error_msg.c_str()); } resolved_command_argument_list.insert( resolved_command_argument_list.end(), resolved_list.begin(), resolved_list.end()); } OpInstance *opinst = new OpInstance(eventloop, *this, _command_executable_filename, resolved_command_argument_list, print_cb, done_cb); return opinst; } bool OpCommand::command_match(const list& path_parts, SlaveConfigTree* slave_config_tree, bool exact_match) const { list::const_iterator them, us; them = path_parts.begin(); us = _command_parts.begin(); // // First go through the fixed parts of the command // while (true) { if ((them == path_parts.end()) && (us == _command_parts.end())) { return true; } if (them == path_parts.end()) { if (exact_match) return false; else return true; } if (us == _command_parts.end()) break; if ((*us)[0] == '$') { // Matching against a varname list varmatches; slave_config_tree->expand_varname_to_matchlist(*us, varmatches); list::const_iterator vi; bool ok = false; for (vi = varmatches.begin(); vi != varmatches.end(); ++vi) { if (*vi == *them) { ok = true; break; } } if (ok == false) return false; } else if ((*us)[0] == '<') { /* any single word matches a wildcard */ } else if (*them != *us) { return false; } ++them; ++us; } // // No more fixed parts, try optional parameters // while (them != path_parts.end()) { map::const_iterator opi; bool ok = false; for (opi = _opt_params.begin(); opi != _opt_params.end(); ++opi) { if (opi->first == *them) { ok = true; break; } } if (ok == false) return false; ++them; } return true; } void OpCommand::get_matches(size_t wordnum, SlaveConfigTree* slave_config_tree, map& return_matches) const { list::const_iterator ci = _command_parts.begin(); bool is_last = true; for (size_t i = 0; i < wordnum; i++) { ++ci; if (ci == _command_parts.end()) break; } if (ci == _command_parts.end()) { // Add all the optional parameters map::const_iterator opi; for (opi = _opt_params.begin(); opi != _opt_params.end(); ++opi) { const string& command_name = opi->first; const string& help_string = opi->second; CliCommandMatch ccm(command_name, help_string, true, true); ccm.set_default_nomore_mode(default_nomore_mode()); return_matches.insert(make_pair(command_name, ccm)); } return; } // // Get the matching part of the name and test whether it is the last one // string match = *ci; ++ci; if (ci == _command_parts.end()) { is_last = true; } else { is_last = false; } do { if (match[0] == '$') { XLOG_ASSERT(match[1] == '('); XLOG_ASSERT(match[match.size() - 1] == ')'); list var_matches; slave_config_tree->expand_varname_to_matchlist(match, var_matches); list::const_iterator vi; for (vi = var_matches.begin(); vi != var_matches.end(); ++vi) { const string& command_name = *vi; string help_string = _help_string; if (! is_last) help_string = "-- No help available --"; CliCommandMatch ccm(command_name, help_string, is_last && is_executable(), is_last && can_pipe()); ccm.set_default_nomore_mode(is_last && default_nomore_mode()); return_matches.insert(make_pair(command_name, ccm)); } break; } if (match[0] == '<') { // A mandatory argument that is supplied by the user XLOG_ASSERT(match[match.size() - 1] == '>'); const string& command_name = match; string help_string = _help_string; if (! is_last) help_string = "-- No help available --"; CliCommandMatch ccm(command_name, help_string, is_last && is_executable(), is_last && can_pipe()); ccm.set_default_nomore_mode(is_last && default_nomore_mode()); CliCommand::TypeMatchCb cb; cb = callback(this, &OpCommand::type_match); ccm.set_type_match_cb(cb); return_matches.insert(make_pair(command_name, ccm)); break; } const string& command_name = match; string help_string = _help_string; if (! is_last) help_string = "-- No help available --"; CliCommandMatch ccm(command_name, help_string, is_last && is_executable(), is_last && can_pipe()); ccm.set_default_nomore_mode(is_last && default_nomore_mode()); return_matches.insert(make_pair(command_name, ccm)); break; } while (false); } bool OpCommand::type_match(const string& s, string& errmsg) const { if (s.empty()) return (false); UNUSED(errmsg); // // XXX: currently we don't support type-based matching, so // any string can match. // return (true); } void OpCommand::add_instance(OpInstance* instance) { set::iterator iter; iter = _instances.find(instance); XLOG_ASSERT(iter == _instances.end()); _instances.insert(instance); _ocl.incr_running_op_instances_n(); } void OpCommand::remove_instance(OpInstance* instance) { set::iterator iter; iter = _instances.find(instance); XLOG_ASSERT(iter != _instances.end()); _instances.erase(iter); _ocl.decr_running_op_instances_n(); } OpCommandList::OpCommandList(const TemplateTree* tt, SlaveModuleManager& mmgr) : _running_op_instances_n(0), _template_tree(tt), _mmgr(mmgr) { } OpCommandList::OpCommandList(const string& config_template_dir, const TemplateTree* tt, SlaveModuleManager& mmgr) throw (InitError) : _running_op_instances_n(0), _template_tree(tt), _mmgr(mmgr) { string errmsg; if (read_templates(config_template_dir, errmsg) != XORP_OK) xorp_throw(InitError, errmsg); } OpCommandList::~OpCommandList() { while (!_op_commands.empty()) { delete _op_commands.front(); _op_commands.pop_front(); } } int OpCommandList::read_templates(const string& config_template_dir, string& errmsg) { list files; struct stat dir_data; if (stat(config_template_dir.c_str(), &dir_data) < 0) { errmsg = c_format("Error reading config directory %s: %s", config_template_dir.c_str(), strerror(errno)); return (XORP_ERROR); } if ((dir_data.st_mode & S_IFDIR) == 0) { errmsg = c_format("Error reading config directory %s: not a directory", config_template_dir.c_str()); return (XORP_ERROR); } // TODO: file suffix is hardcoded here! string globname = config_template_dir + "/*.cmds"; glob_t pglob; if (glob(globname.c_str(), 0, 0, &pglob) != 0) { globfree(&pglob); errmsg = c_format("Failed to find config files in %s", config_template_dir.c_str()); return (XORP_ERROR); } if (pglob.gl_pathc == 0) { globfree(&pglob); errmsg = c_format("Failed to find any template files in %s", config_template_dir.c_str()); return (XORP_ERROR); } for (size_t i = 0; i < (size_t)pglob.gl_pathc; i++) { if (init_opcmd_parser(pglob.gl_pathv[i], this) < 0) { globfree(&pglob); errmsg = c_format("Failed to open template file: %s", config_template_dir.c_str()); return (XORP_ERROR); } try { parse_opcmd(); } catch (const ParseError& pe) { globfree(&pglob); errmsg = pe.why(); return (XORP_ERROR); } if (_path_segments.size() != 0) { globfree(&pglob); errmsg = c_format("File %s is not terminated properly", pglob.gl_pathv[i]); return (XORP_ERROR); } } globfree(&pglob); return (XORP_OK); } bool OpCommandList::done() const { if (_running_op_instances_n == 0) return (true); else return (false); } void OpCommandList::incr_running_op_instances_n() { _running_op_instances_n++; } void OpCommandList::decr_running_op_instances_n() { XLOG_ASSERT(_running_op_instances_n > 0); _running_op_instances_n--; } bool OpCommandList::check_variable_name(const string& variable_name) const { return _template_tree->check_variable_name(variable_name); } OpCommand* OpCommandList::find_op_command(const list& command_parts) { string command_name = OpCommand::command_parts2command_name(command_parts); list::const_iterator iter; for (iter = _op_commands.begin(); iter != _op_commands.end(); ++iter) { if ((*iter)->command_name() == command_name) return *iter; } return NULL; } bool OpCommandList::command_match(const list& command_parts, bool exact_match) const { list::const_iterator iter; for (iter = _op_commands.begin(); iter != _op_commands.end(); ++iter) { if ((*iter)->command_match(command_parts, _slave_config_tree, exact_match)) return true; } return false; } OpInstance* OpCommandList::execute(EventLoop& eventloop, const list& command_parts, RouterCLI::OpModePrintCallback print_cb, RouterCLI::OpModeDoneCallback done_cb) const { list::const_iterator iter; for (iter = _op_commands.begin(); iter != _op_commands.end(); ++iter) { // Find the right command if ((*iter)->command_match(command_parts, _slave_config_tree, true)) { // Execute it return (*iter)->execute(eventloop, command_parts, print_cb, done_cb); } } done_cb->dispatch(false, string("No matching command")); return 0; } OpCommand* OpCommandList::add_op_command(const OpCommand& op_command) { OpCommand *new_command; XLOG_ASSERT(find_op_command(op_command.command_parts()) == NULL); new_command = new OpCommand(op_command); _op_commands.push_back(new_command); return new_command; } map OpCommandList::top_level_commands() const { map commands; // // Add the first word of every command, and the help if this // indeed is a top-level command // list::const_iterator iter; for (iter = _op_commands.begin(); iter != _op_commands.end(); ++iter) { const OpCommand* op_command = *iter; list path_parts = split(op_command->command_name(), ' '); const string& top_command = path_parts.front(); bool is_top_command = false; // // XXX: If the module has not been started, then don't add its // commands. However, add all commands that are not associated // with any module. // if ((_mmgr.module_is_active(op_command->module()) == false) && (! op_command->module().empty())) { continue; } if (path_parts.size() == 1) is_top_command = true; if (is_top_command) { commands.erase(top_command); CliCommandMatch ccm(op_command->command_name(), op_command->help_string(), op_command->is_executable(), op_command->can_pipe()); ccm.set_default_nomore_mode(op_command->default_nomore_mode()); commands.insert(make_pair(top_command, ccm)); continue; } if (commands.find(top_command) == commands.end()) { CliCommandMatch ccm(top_command, string("-- No help available --"), false, false); ccm.set_default_nomore_mode(false); commands.insert(make_pair(top_command, ccm)); } } return commands; } map OpCommandList::childlist(const vector& vector_path) const { map children; list path_parts; path_parts.insert(path_parts.end(), vector_path.begin(), vector_path.end()); list::const_iterator iter; for (iter = _op_commands.begin(); iter != _op_commands.end(); ++iter) { const OpCommand* op_command = *iter; if (op_command->command_match(path_parts, _slave_config_tree, false)) { // // XXX: If the module has not been started, then don't add its // commands. However, add all commands that are not associated // with any module. // if ((_mmgr.module_is_active(op_command->module()) == false) && (! op_command->module().empty())) { continue; } map matches; op_command->get_matches(path_parts.size(), _slave_config_tree, matches); map::iterator mi; for (mi = matches.begin(); mi != matches.end(); ++mi) { const string& key = mi->first; const CliCommandMatch& ccm = mi->second; XLOG_ASSERT(ccm.command_name() != ""); children.insert(make_pair(key, ccm)); } } } return children; }