// -*- 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/cli/cli_client.cc,v 1.58 2007/02/16 22:45:28 pavlin Exp $"
//
// CLI (Command-Line Interface) implementation for XORP.
//
#include "cli_module.h"
#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/ipvx.hh"
#include "libxorp/eventloop.hh"
#include "libxorp/token.hh"
#include "libxorp/utils.hh"
#include "libcomm/comm_api.h"
#include "cli_client.hh"
#include "cli_command_pipe.hh"
#include "cli_private.hh"
#ifdef HOST_OS_WINDOWS
#define isatty(x) (x).is_console()
#endif
//
// Exported variables
//
//
// Local constants definitions
//
//
// Local structures/classes, typedefs and macros
//
//
// Local variables
//
//
// Local functions prototypes
//
// TODO: use a parameter to define the buffer size
CliClient::CliClient(CliNode& init_cli_node, XorpFd input_fd, XorpFd output_fd,
const string& startup_cli_prompt)
: _cli_node(init_cli_node),
_input_fd(input_fd),
_output_fd(output_fd),
_command_buffer(1024),
_telnet_sb_buffer(1024),
_cli_session_from_address(_cli_node.family()),
_is_network(false)
{
_input_fd_file = NULL;
_output_fd_file = NULL;
_client_type = CLIENT_TERMINAL; // XXX: default is terminal
_gl = NULL;
_telnet_iac = false;
_telnet_sb = false;
_telnet_dont = false;
_telnet_do = false;
_telnet_wont = false;
_telnet_will = false;
_telnet_binary = false;
// TODO: use parameters instead
_window_width = 80;
_window_height = 25;
_is_modified_stdio_termios_icanon = false;
_is_modified_stdio_termios_echo = false;
_is_modified_stdio_termios_isig = false;
_saved_stdio_termios_vmin = 0;
_saved_stdio_termios_vtime = 0;
_executed_cli_command = NULL;
set_current_cli_command(_cli_node.cli_command_root());
set_current_cli_prompt(startup_cli_prompt);
_buff_curpos = 0;
_is_pipe_mode = false;
_is_nomore_mode = false;
_is_hold_mode = false;
_is_page_mode = false;
_is_output_buffer_mode = false;
_output_buffer_last_line_n = 0;
_is_help_buffer_mode = false;
_help_buffer_last_line_n = 0;
_is_help_mode = false;
_is_page_buffer_mode = &_is_output_buffer_mode;
_page_buffer = &_output_buffer;
_page_buffer_last_line_n = &_output_buffer_last_line_n;
_is_prompt_flushed = false;
//
// Session info state
//
set_cli_session_user_name("unknown_user");
set_cli_session_term_name("unknown_terminal");
set_cli_session_session_id(~0U); // XXX: ~0U has no particular meaning
set_cli_session_start_time(TimeVal(0, 0));
set_cli_session_stop_time(TimeVal(0, 0));
set_is_cli_session_active(false);
//
// Log-related state
//
_is_log_output = false;
//
// Server communication state
//
_is_waiting_for_data = false;
//
// Set in "no-more" mode if a non-interactive client
//
if (! is_interactive())
set_nomore_mode(true);
}
CliClient::~CliClient()
{
string dummy_error_msg;
stop_connection(dummy_error_msg);
set_log_output(false);
// Remove the input file descriptor from the eventloop
if (_input_fd.is_valid()) {
cli_node().eventloop().remove_ioevent_cb(_input_fd, IOT_READ);
}
// Close files and file descriptors
if (_input_fd_file != NULL) {
fclose(_input_fd_file);
_input_fd_file = NULL;
_input_fd.clear();
}
if (_output_fd_file != NULL) {
fclose(_output_fd_file);
_output_fd_file = NULL;
_output_fd.clear();
}
if (_input_fd.is_valid()) {
comm_close(_input_fd);
_input_fd.clear();
}
if (_output_fd.is_valid()) {
comm_close(_output_fd);
_output_fd.clear();
}
if (_gl != NULL)
_gl = del_GetLine(_gl);
delete_pipe_all();
}
bool
CliClient::done() const
{
if (_is_waiting_for_data)
return (false);
if (! _pending_input_data.empty())
return (false);
return (true);
}
int
CliClient::set_log_output(bool v)
{
if (v) {
if (is_log_output())
return (XORP_ERROR); // Already added
if (xlog_add_output_func(&CliNode::xlog_output, this) != 0)
return (XORP_ERROR);
_is_log_output = true;
return (XORP_OK);
} else {
if (! is_log_output())
return (XORP_ERROR); // Was not added
if (xlog_remove_output_func(&CliNode::xlog_output, this) != 0)
return (XORP_ERROR);
_is_log_output = false;
return (XORP_OK);
}
// NOTERACHED
return (XORP_ERROR);
}
bool
CliClient::is_input_tty() const
{
return (isatty(_input_fd) != 0);
}
bool
CliClient::is_output_tty() const
{
return (isatty(_output_fd) != 0);
}
bool
CliClient::is_network() const
{
return (_is_network);
}
void
CliClient::set_network_client(bool v)
{
_is_network = v;
}
bool
CliClient::is_telnet() const
{
//
// TODO: XXX: for the time being we assume that all network connections
// are telnet.
//
return (is_network());
}
bool
CliClient::is_interactive() const
{
return (is_input_tty() || is_telnet());
}
CliPipe *
CliClient::add_pipe(const string& pipe_name)
{
CliPipe *cli_pipe;
cli_pipe = new CliPipe(pipe_name);
if (cli_pipe->is_invalid()) {
delete cli_pipe;
return (NULL);
}
_pipe_list.push_back(cli_pipe);
cli_pipe->set_cli_client(this);
set_pipe_mode(true);
return (cli_pipe);
}
CliPipe *
CliClient::add_pipe(const string& pipe_name, const list<string>& args_list)
{
CliPipe *cli_pipe;
cli_pipe = add_pipe(pipe_name);
if (cli_pipe == NULL)
return (NULL);
// Add the list of arguments
list<string>::const_iterator iter;
for (iter = args_list.begin(); iter != args_list.end(); ++iter) {
string arg = *iter;
cli_pipe->add_pipe_arg(arg);
}
return (cli_pipe);
}
void
CliClient::delete_pipe_all()
{
delete_pointers_list(_pipe_list);
set_pipe_mode(false);
}
void
CliClient::append_page_buffer_line(const string& buffer_line)
{
page_buffer().push_back(buffer_line);
}
void
CliClient::concat_page_buffer_line(const string& buffer_line, size_t pos)
{
XLOG_ASSERT(pos < page_buffer().size());
string& line = page_buffer()[pos];
line += buffer_line;
}
// Process the line throught the pipes
void
CliClient::process_line_through_pipes(string& pipe_line)
{
list<CliPipe*>::iterator iter;
if (! is_pipe_mode())
return;
for (iter = _pipe_list.begin(); iter != _pipe_list.end(); ++iter) {
CliPipe *cli_pipe = *iter;
cli_pipe->process_func(pipe_line);
if (pipe_line.empty())
break;
}
}
// Set the page mode
void
CliClient::set_page_mode(bool v)
{
const char *s;
if (v) {
// TRUE
if (_is_page_mode)
return;
_is_page_mode = v;
//
// Save the key bind commands
//
_action_name_up_arrow
= (s = gl_get_key_binding_action_name(gl(), "up")) ? (s) : "";
_action_name_down_arrow
= (s = gl_get_key_binding_action_name(gl(), "down")) ? (s) : "";
_action_name_tab
= (s = gl_get_key_binding_action_name(gl(), "\t")) ? (s) : "";
_action_name_newline_n
= (s = gl_get_key_binding_action_name(gl(), "\n")) ? (s) : "";
_action_name_newline_r
= (s = gl_get_key_binding_action_name(gl(), "\r")) ? (s) : "";
// XXX: no need to save the spacebar binding
//_action_name_spacebar
//= (s = gl_get_key_binding_action_name(gl(), "\\\\\\040")) ? (s) : "";
_action_name_ctrl_a
= (s = gl_get_key_binding_action_name(gl(), "^A")) ? (s) : "";
_action_name_ctrl_b
= (s = gl_get_key_binding_action_name(gl(), "^B")) ? (s) : "";
_action_name_ctrl_c
= (s = gl_get_key_binding_action_name(gl(), "^C")) ? (s) : "";
_action_name_ctrl_d
= (s = gl_get_key_binding_action_name(gl(), "^D")) ? (s) : "";
_action_name_ctrl_e
= (s = gl_get_key_binding_action_name(gl(), "^E")) ? (s) : "";
_action_name_ctrl_f
= (s = gl_get_key_binding_action_name(gl(), "^F")) ? (s) : "";
_action_name_ctrl_h
= (s = gl_get_key_binding_action_name(gl(), "^H")) ? (s) : "";
_action_name_ctrl_k
= (s = gl_get_key_binding_action_name(gl(), "^K")) ? (s) : "";
_action_name_ctrl_l
= (s = gl_get_key_binding_action_name(gl(), "^L")) ? (s) : "";
_action_name_ctrl_m
= (s = gl_get_key_binding_action_name(gl(), "^M")) ? (s) : "";
_action_name_ctrl_n
= (s = gl_get_key_binding_action_name(gl(), "^N")) ? (s) : "";
_action_name_ctrl_p
= (s = gl_get_key_binding_action_name(gl(), "^P")) ? (s) : "";
_action_name_ctrl_u
= (s = gl_get_key_binding_action_name(gl(), "^U")) ? (s) : "";
_action_name_ctrl_x
= (s = gl_get_key_binding_action_name(gl(), "^X")) ? (s) : "";
//
// Set new binding
//
string bind_command;
if (_action_name_up_arrow.size()) {
bind_command = "bind up user-event1";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_down_arrow.size()) {
bind_command = "bind down user-event2";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
// XXX: all bindings below are not used, but are needed to
// avoid 'beeps'
if (_action_name_tab.size()) {
bind_command = "bind \\t user-event3";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_newline_n.size()) {
bind_command = "bind \\n user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_newline_r.size()) {
bind_command = "bind \\r user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_spacebar.size() || true) { // XXX: always (re)bind
bind_command = "bind \\\\\\040 user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_a.size()) {
bind_command = "bind ^A user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_b.size()) {
bind_command = "bind ^B user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_c.size()) {
bind_command = "bind ^C user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_d.size()) {
bind_command = "bind ^D user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_e.size()) {
bind_command = "bind ^E user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_f.size()) {
bind_command = "bind ^F user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_h.size()) {
bind_command = "bind ^H user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_k.size()) {
bind_command = "bind ^K user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_l.size()) {
bind_command = "bind ^L user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_m.size()) {
bind_command = "bind ^M user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_n.size()) {
bind_command = "bind ^N user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_p.size()) {
bind_command = "bind ^P user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_u.size()) {
bind_command = "bind ^U user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_x.size()) {
bind_command = "bind ^X user-event4";
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
return;
} else {
// FALSE
if (! _is_page_mode)
return;
_is_page_mode = v;
//
// Restore the key bind commands
//
string bind_command;
if (_action_name_up_arrow.size()) {
bind_command = "bind up " + _action_name_up_arrow;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_down_arrow.size()) {
bind_command = "bind down " + _action_name_down_arrow;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_tab.size()) {
bind_command = "bind \\t " + _action_name_tab;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_newline_n.size()) {
bind_command = "bind \\n " + _action_name_newline_n;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_newline_r.size()) {
bind_command = "bind \\r " + _action_name_newline_r;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_spacebar.size() || true) { // XXX: always (re)bind
bind_command = "bind \\\\\\040 " + _action_name_spacebar;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_a.size()) {
bind_command = "bind ^A " + _action_name_ctrl_a;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_b.size()) {
bind_command = "bind ^B " + _action_name_ctrl_b;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_c.size()) {
bind_command = "bind ^C " + _action_name_ctrl_c;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_d.size()) {
bind_command = "bind ^D " + _action_name_ctrl_d;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_e.size()) {
bind_command = "bind ^E " + _action_name_ctrl_e;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_f.size()) {
bind_command = "bind ^F " + _action_name_ctrl_f;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_h.size()) {
bind_command = "bind ^H " + _action_name_ctrl_h;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_k.size()) {
bind_command = "bind ^K " + _action_name_ctrl_k;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_l.size()) {
bind_command = "bind ^L " + _action_name_ctrl_l;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_m.size()) {
bind_command = "bind ^M " + _action_name_ctrl_m;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_n.size()) {
bind_command = "bind ^N " + _action_name_ctrl_n;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_p.size()) {
bind_command = "bind ^P " + _action_name_ctrl_p;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_u.size()) {
bind_command = "bind ^U " + _action_name_ctrl_u;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
if (_action_name_ctrl_x.size()) {
bind_command = "bind ^X " + _action_name_ctrl_x;
gl_configure_getline(gl(), bind_command.c_str(), NULL, NULL);
}
return;
}
}
const string&
CliClient::page_buffer_line(size_t line_n) const
{
XLOG_ASSERT(line_n < _page_buffer->size());
return ((*_page_buffer)[line_n]);
}
size_t
CliClient::page_buffer_window_lines_n()
{
if (page_buffer_lines_n() == 0)
return (0);
return (page_buffer2window_line_n(page_buffer_lines_n() - 1));
}
size_t
CliClient::page_buffer_last_window_line_n()
{
if (page_buffer_last_line_n() == 0)
return (0);
return (page_buffer2window_line_n(page_buffer_last_line_n() - 1));
}
size_t
CliClient::page_buffer2window_line_n(size_t buffer_line_n)
{
size_t i;
size_t window_line_n = 0;
for (i = 0; i <= buffer_line_n; i++) {
window_line_n += window_lines_n(i);
}
return (window_line_n);
}
size_t
CliClient::window_lines_n(size_t buffer_line_n)
{
bool has_newline = false;
size_t window_line_n = 0;
XLOG_ASSERT(buffer_line_n < _page_buffer->size());
const string& line = page_buffer_line(buffer_line_n);
// Get the line size, but don't count the trailing '\r' and '\n'
size_t line_size = line.size();
while (line_size > 0) {
if ((line[line_size - 1] == '\r') || (line[line_size - 1] == '\n')) {
line_size--;
has_newline = true;
continue;
}
break;
}
window_line_n = (line_size / window_width())
+ ((line_size % window_width())? (1) : (0));
if ((line_size == 0) && has_newline)
window_line_n++;
return (window_line_n);
}
size_t
CliClient::calculate_first_page_buffer_line_by_window_size(
size_t last_buffer_line_n,
size_t max_window_size)
{
size_t first_buffer_line_n;
size_t window_size = 0;
if (last_buffer_line_n == 0)
return (0);
first_buffer_line_n = last_buffer_line_n - 1;
window_size += window_lines_n(first_buffer_line_n);
while (window_size < max_window_size) {
if (first_buffer_line_n == 0)
break;
window_size += window_lines_n(first_buffer_line_n - 1);
if (window_size > max_window_size)
break;
first_buffer_line_n--;
}
return (first_buffer_line_n);
}
//
// Print a message. If a terminal connection, add '\r' before each '\n'
// (unless the previous character to print is indeed '\r').
//
// XXX: if we cli_print(""), this is EOF, and will "clear-out"
// any remaining data in _buffer_line through the pipes
int
CliClient::cli_print(const string& msg)
{
int ret_value;
string pipe_line, pipe_result;
bool is_eof_input = false;
bool is_incomplete_last_line = false;
if (msg.size() == 0 || (msg[0] == '\0')) {
is_eof_input = true;
}
// Test if the last line added to the page buffer was incomplete
do {
if (page_buffer().empty())
break;
const string& last_line = page_buffer_line(page_buffer().size() - 1);
if (last_line.empty())
break;
if (last_line[last_line.size() - 1] == '\n')
break;
is_incomplete_last_line = true;
break;
} while (false);
// Process the data throught the pipe
pipe_line += _buffer_line;
_buffer_line = "";
size_t i = 0;
while (msg[i] != '\0') {
pipe_line += msg[i];
if (msg[i] == '\n') {
// Process the line throught the pipe
process_line_through_pipes(pipe_line);
pipe_result += pipe_line;
pipe_line = "";
}
i++;
}
if (pipe_line.size()) {
if (! _pipe_list.empty()) {
if (is_eof_input) {
process_line_through_pipes(pipe_line);
} else {
_buffer_line += pipe_line;
pipe_line = "";
}
}
pipe_result += pipe_line;
pipe_line = "";
}
// If a terminal connection, add '\r' before each '\n'
// (unless the previous character to print is indeed '\r').
pipe_line = "";
string output_string = "";
for (i = 0; i < pipe_result.size(); i++) {
if ((_client_type == CLIENT_TERMINAL) && (pipe_result[i] == '\n')) {
if ((! telnet_binary())
&& (! ((i > 0) && (pipe_result[i-1] == '\r')))) {
pipe_line += '\r'; // XXX: Carrige-return
}
}
pipe_line += pipe_result[i];
if (is_page_buffer_mode()
&& (_client_type == CLIENT_TERMINAL)
&& (pipe_result[i] == '\n')) {
// Add the line to the buffer output
if (is_incomplete_last_line) {
concat_page_buffer_line(pipe_line, page_buffer().size() - 1);
} else {
append_page_buffer_line(pipe_line);
}
if ((page_buffer_window_lines_n() >= window_height())
&& (! is_nomore_mode())) {
set_page_mode(true);
} else {
if (! is_incomplete_last_line)
incr_page_buffer_last_line_n();
output_string += pipe_line;
}
pipe_line = "";
is_incomplete_last_line = false;
}
}
if (pipe_line.size()) {
// Insert the remaining partial line into the buffer
if (is_page_buffer_mode() && (_client_type == CLIENT_TERMINAL)) {
// Add the line to the buffer output
if (is_incomplete_last_line) {
concat_page_buffer_line(pipe_line, page_buffer().size() - 1);
} else {
append_page_buffer_line(pipe_line);
}
if ((page_buffer_window_lines_n() >= window_height())
&& (! is_nomore_mode())) {
set_page_mode(true);
} else {
if (! is_incomplete_last_line)
incr_page_buffer_last_line_n();
}
}
}
if (! (is_page_buffer_mode() && is_page_mode())) {
if (pipe_line.size())
output_string += pipe_line; // XXX: the remaining partial line
}
ret_value = output_string.size();
// if (! (is_page_buffer_mode() && is_page_mode()))
if (output_string.size())
ret_value = fprintf(_output_fd_file, "%s", output_string.c_str());
return (ret_value);
}
// Return: %XORP_OK on success, otherwise %XORP_ERROR.
int
CliClient::cli_flush()
{
if ((_output_fd_file != NULL) && (fflush(_output_fd_file) == 0))
return (XORP_OK);
return (XORP_ERROR);
}
void
CliClient::set_current_cli_command(CliCommand *cli_command)
{
_current_cli_command = cli_command;
if (cli_command->cd_prompt().size() > 0)
set_current_cli_prompt(cli_command->cd_prompt());
}
void
CliClient::set_current_cli_prompt(const string& cli_prompt)
{
_current_cli_prompt = cli_prompt;
gl_replace_prompt(gl(), _current_cli_prompt.c_str());
}
//
// Process one input character while in "page mode"
//
int
CliClient::process_char_page_mode(uint8_t val)
{
string restore_cli_prompt = current_cli_prompt(); // The current prompt
bool old_page_buffer_mode = is_page_buffer_mode();
//
// Reset the line and clear the current prompt
//
gl_redisplay_line(gl()); // XXX: must be first
gl_reset_line(gl());
set_current_cli_prompt("");
gl_reset_line(gl());
cli_flush();
//
// Page commands
//
//
// Print help
//
if ((val == 'h')) {
if (! is_help_mode()) {
set_help_mode(true);
_is_page_buffer_mode = &_is_help_buffer_mode;
_page_buffer = &_help_buffer;
_page_buffer_last_line_n = &_help_buffer_last_line_n;
set_page_buffer_mode(true);
#define CLI_HELP_STRING \
" SUMMARY OF MORE COMMANDS\n" \
"\n" \
" -- Get Help --\n" \
" h * Display this help.\n" \
"\n" \
" -- Scroll Down --\n" \
" Enter Return j * Scroll down one line.\n" \
" ^M ^N DownArrow\n" \
" Tab d ^D ^X * Scroll down one-half screen.\n" \
" Space ^F * Scroll down one whole screen.\n" \
" ^E G * Scroll down to the bottom of the output.\n" \
" N * Display the output all at once instead of one\n"\
" screen at a time. (Same as specifying the\n" \
" | no-more command.)\n" \
"\n" \
" -- Scroll Up --\n" \
" k ^H ^P * Display the previous line of output.\n" \
" UpArrow\n" \
" u ^U * Scroll up one-half screen.\n" \
" b ^B * Scroll up one whole screen.\n" \
" ^A g * Scroll up to the top of the output.\n" \
"\n" \
" -- Misc Commands --\n" \
" ^L * Redraw the output on the screen.\n" \
" q Q ^C ^K * Interrupt the display of output.\n" \
"\n"
cli_print(CLI_HELP_STRING);
set_page_buffer_mode(false);
}
goto redisplay_screen_label;
}
//
// Interrupt the display of output
//
if ((val == 'q')
|| (val == 'Q')
|| (val == CHAR_TO_CTRL('c'))
|| (val == CHAR_TO_CTRL('k'))) {
if (is_waiting_for_data()) {
interrupt_command();
}
goto exit_page_mode_label;
}
//
// Scroll down one line
//
if ((val == '\n')
|| (val == '\r')
|| (val == 'j')
|| (val == CHAR_TO_CTRL('m'))
|| (val == CHAR_TO_CTRL('n'))
|| (gl_get_user_event(gl()) == 2)) {
if (page_buffer_last_line_n() < page_buffer_lines_n()) {
set_page_buffer_mode(false);
cli_print(page_buffer_line(page_buffer_last_line_n()));
set_page_buffer_mode(old_page_buffer_mode);
incr_page_buffer_last_line_n();
}
goto redisplay_line_label;
}
//
// Scroll down one-half screen
//
if ((val == '\t')
|| (val == 'd')
|| (val == CHAR_TO_CTRL('d'))
|| (val == CHAR_TO_CTRL('x'))) {
for (size_t i = 0; i <= window_height() / 2; ) {
if (page_buffer_last_line_n() >= page_buffer_lines_n())
break;
i += window_lines_n(page_buffer_last_line_n());
if (i > window_height() / 2)
break;
set_page_buffer_mode(false);
cli_print(page_buffer_line(page_buffer_last_line_n()));
set_page_buffer_mode(old_page_buffer_mode);
incr_page_buffer_last_line_n();
}
goto redisplay_line_label;
}
//
// Scroll down one whole screen
//
if ((val == ' ')
|| (val == CHAR_TO_CTRL('f'))) {
for (size_t i = 0; i <= window_height() - 1; ) {
if (page_buffer_last_line_n() >= page_buffer_lines_n())
break;
i += window_lines_n(page_buffer_last_line_n());
if (i > window_height() - 1)
break;
set_page_buffer_mode(false);
cli_print(page_buffer_line(page_buffer_last_line_n()));
set_page_buffer_mode(old_page_buffer_mode);
incr_page_buffer_last_line_n();
}
goto redisplay_line_label;
}
//
// Scroll down to the bottom of the output
//
if ((val == 'G')
|| (val == CHAR_TO_CTRL('e'))) {
set_page_buffer_last_line_n(page_buffer_lines_n());
goto redisplay_screen_label;
}
//
// Display the output all at once instead of oen screen at a time.
// (Same as specifying the "| no-more" command.)
//
if ((val == 'N')) {
while (page_buffer_last_line_n() < page_buffer_lines_n()) {
set_page_buffer_mode(false);
cli_print(page_buffer_line(page_buffer_last_line_n()));
set_page_buffer_mode(old_page_buffer_mode);
incr_page_buffer_last_line_n();
}
// TODO: do we want to exit the page mode at the end?
// If "yes", then the line below should be changed to
// goto exit_page_mode_label;
goto redisplay_line_label;
}
//
// Display the previous line of output
//
if ((val == 'k')
|| (val == CHAR_TO_CTRL('h'))
|| (val == CHAR_TO_CTRL('p'))
|| (gl_get_user_event(gl()) == 1)) {
if (page_buffer_last_line_n() > 0)
decr_page_buffer_last_line_n();
goto redisplay_screen_label;
}
//
// Scroll up one-half screen
//
if ((val == 'u')
|| (val == CHAR_TO_CTRL('u'))) {
if (page_buffer_last_line_n() > 0) {
size_t start_window_line = calculate_first_page_buffer_line_by_window_size(
page_buffer_last_line_n(), window_height() / 2);
set_page_buffer_last_line_n(start_window_line);
}
goto redisplay_screen_label;
}
//
// Scroll up one whole screen
//
if ((val == 'b')
|| (val == CHAR_TO_CTRL('b'))) {
if (page_buffer_last_line_n() > 0) {
size_t start_window_line = calculate_first_page_buffer_line_by_window_size(
page_buffer_last_line_n(), window_height() - 1);
set_page_buffer_last_line_n(start_window_line);
}
goto redisplay_screen_label;
}
//
// Scroll up to the top of the output
//
if ((val == 'g')
|| (val == CHAR_TO_CTRL('a'))) {
set_page_buffer_last_line_n(0);
goto redisplay_screen_label;
}
//
// Redraw the output of the screen
//
if ((val == CHAR_TO_CTRL('l'))) {
redisplay_screen_label:
size_t i, start_window_line = 0;
set_page_buffer_mode(false);
// XXX: clean-up the previous window
for (i = 0; i < window_height() - 1; i++)
cli_print("\n");
if (page_buffer_last_line_n() > 0) {
start_window_line = calculate_first_page_buffer_line_by_window_size(
page_buffer_last_line_n(), window_height() - 1);
}
set_page_buffer_last_line_n(start_window_line);
for (i = 0; i <= window_height() - 1; ) {
if (page_buffer_last_line_n() >= page_buffer_lines_n())
break;
i += window_lines_n(page_buffer_last_line_n());
if (i > window_height() - 1)
break;
cli_print(page_buffer_line(page_buffer_last_line_n()));
incr_page_buffer_last_line_n();
}
// XXX: fill-up the rest of the window
for ( ; i < window_height() - 1; i++)
cli_print("\n");
set_page_buffer_mode(old_page_buffer_mode);
goto redisplay_line_label;
}
goto redisplay_line_label;
exit_page_mode_label:
reset_page_buffer();
if (is_interactive())
set_nomore_mode(false);
if (! is_help_mode()) {
// Exit the page mode
set_page_mode(false);
set_buff_curpos(0);
gl_reset_line(gl());
command_buffer().reset();
restore_cli_prompt = current_cli_command()->cd_prompt();
} else {
// Exit the help page mode
set_help_mode(false);
_is_page_buffer_mode = &_is_output_buffer_mode;
_page_buffer = &_output_buffer;
_page_buffer_last_line_n = &_output_buffer_last_line_n;
goto redisplay_screen_label;
}
// FALLTHROUGH
redisplay_line_label:
cli_flush();
if (is_page_mode()) {
if (page_buffer_last_line_n() < page_buffer_lines_n())
restore_cli_prompt = " --More-- ";
else
restore_cli_prompt = " --More-- (END) ";
}
set_current_cli_prompt(restore_cli_prompt);
gl_redisplay_line(gl());
cli_flush();
return (XORP_OK);
}
void
CliClient::post_process_command()
{
//
// Test if we are waiting for the result from a processor
//
if (is_waiting_for_data()) {
// We are waiting for the result; silently return.
return;
}
//
// Reset the state for the currently executed command
//
_executed_cli_command = NULL;
_executed_cli_command_name.clear();
_executed_cli_command_args.clear();
//
// Pipe-process the result
//
string final_string = "";
cli_print(""); // XXX: EOF: clear-out the pipe
list<CliPipe*>::iterator iter;
for (iter = _pipe_list.begin(); iter != _pipe_list.end(); ++iter) {
CliPipe *cli_pipe = *iter;
cli_pipe->process_func(final_string);
cli_pipe->eof_func(final_string);
}
if (final_string.size()) {
bool old_pipe_mode = is_pipe_mode();
set_pipe_mode(false);
cli_print(final_string);
set_pipe_mode(old_pipe_mode);
}
if (is_hold_mode()) {
set_page_mode(true);
set_hold_mode(false);
}
delete_pipe_all();
if (! is_page_mode())
reset_page_buffer();
//
// Page-related state
//
set_page_buffer_mode(false);
if (is_page_mode()) {
if (page_buffer_last_line_n() < page_buffer_lines_n())
set_current_cli_prompt(" --More-- ");
else
set_current_cli_prompt(" --More-- (END) ");
} else {
reset_page_buffer();
if (is_interactive())
set_nomore_mode(false);
}
//
// Reset buffer, cursor, prompt
//
command_buffer().reset();
set_buff_curpos(0);
if (! is_prompt_flushed())
cli_print(current_cli_prompt());
set_prompt_flushed(false);
cli_flush();
//
// Process the pending input data (if any)
//
if (! _pending_input_data.empty()) {
schedule_process_input_data();
}
}
void
CliClient::flush_process_command_output()
{
//
// Test if we are waiting for the result from a processor
//
if (! is_waiting_for_data()) {
// We are not waiting for the result; silently return.
return;
}
if (is_help_mode()) {
// We don't want the output while in help mode
return;
}
//
// Page-related state
//
if (is_page_mode() && ! is_prompt_flushed()) {
string restore_cli_prompt;
bool old_page_buffer_mode = is_page_buffer_mode();
set_page_buffer_mode(false);
if (page_buffer_last_line_n() < page_buffer_lines_n())
restore_cli_prompt = " --More-- ";
else
restore_cli_prompt = " --More-- (END) ";
set_current_cli_prompt(restore_cli_prompt);
cli_print(current_cli_prompt());
cli_flush();
set_page_buffer_mode(old_page_buffer_mode);
set_prompt_flushed(true);
}
}
//
// Process one input character
// Return: %XORP_OK on success, otherwise %XORP_ERROR.
// XXX: returning %XORP_ERROR is also an indication that the connection
// has been closed.
int
CliClient::process_char(const string& line, uint8_t val, bool& stop_processing)
{
int gl_buff_curpos = gl_get_buff_curpos(gl());
int ret_value = XORP_OK;
stop_processing = false;
if ((val == '\n') || (val == '\r')) {
// New command
XLOG_ASSERT(is_waiting_for_data() == false);
set_page_buffer_mode(true);
process_command(line);
post_process_command();
//
// Set the flag to stop processing pending input data while
// processing previous commands.
//
if (is_waiting_for_data()) {
stop_processing = true;
}
return (XORP_OK);
}
if (val == '?') {
// Command-line help
//set_page_buffer_mode(true);
// TODO: add "page" support
command_line_help(line, gl_buff_curpos, true);
//set_page_buffer_mode(false);
//if (is_page_mode()) {
//if (page_buffer_last_line_n() < page_buffer_lines_n())
//set_current_cli_prompt(" --More-- ");
//else
//set_current_cli_prompt(" --More-- (END) ");
//} else {
//reset_page_buffer();
//}
//cli_print(current_cli_prompt());
//command_buffer().reset();
//set_buff_curpos(0);
return (XORP_OK);
}
//
// XXX: The (val == ' ') and 'Ctrl-C' cases are handled by the
// parent function.
//
// All other characters which we need to print
// Store the line in the command buffer
command_buffer().reset();
ret_value = XORP_OK;
for (int i = 0; line[i] != '\0'; i++) {
ret_value = command_buffer().add_data(line[i]);
if (ret_value < 0)
break;
}
if (ret_value == XORP_OK)
ret_value = command_buffer().add_data('\0');
if (ret_value != XORP_OK) {
// This client is sending too much data. Kick it out!
// TODO: print more informative message about the
// client: E.g. where it came from, etc.
XLOG_WARNING("Removing client (input fd = %s output fd = %s "
"family = %d): "
"data buffer full",
input_fd().str().c_str(),
output_fd().str().c_str(),
cli_node().family());
return (XORP_ERROR);
}
set_buff_curpos(gl_buff_curpos);
return (XORP_OK);
}
/**
* CliClient::command_line_help:
* @line: The current command line.
* @word_end: The cursor position.
* @remove_last_input_char: If true, then remove the last input character.
*
* Print the help for the same-line command.
**/
void
CliClient::command_line_help(const string& line, int word_end,
bool remove_last_input_char)
{
CliCommand *curr_cli_command = _current_cli_command;
set<string> command_help_strings;
bool is_found = false;
if (remove_last_input_char)
word_end--; // XXX: exclude the '?' character
list<CliCommand *>::iterator iter;
for (iter = curr_cli_command->child_command_list().begin();
iter != curr_cli_command->child_command_list().end();
++iter) {
CliCommand *tmp_cli_command = *iter;
if (tmp_cli_command->find_command_help(line.c_str(), word_end,
command_help_strings))
is_found = true;
}
if (is_found) {
cli_print("\nPossible completions:\n");
set<string>::const_iterator iter;
for (iter = command_help_strings.begin();
iter != command_help_strings.end();
++iter) {
cli_print(*iter);
}
} else {
string token_line = string(line, 0, word_end);
token_line = strip_empty_spaces(token_line);
cli_print(c_format("\nsyntax error, command \"%s\" is not recognized.\n",
token_line.c_str()));
}
gl_redisplay_line(gl());
if (remove_last_input_char) {
// XXX: Move the cursor over the '?'
gl_place_cursor(gl(), gl_get_buff_curpos(gl()) - 1);
cli_print(" \b"); // XXX: A hack to delete the '?'
}
}
bool
CliClient::is_multi_command_prefix(const string& command_line)
{
return (_current_cli_command->is_multi_command_prefix(command_line));
}
int
CliClient::process_command(const string& command_line)
{
string token, token_line;
CliCommand *parent_cli_command = current_cli_command();
CliCommand *child_cli_command = NULL;
int syntax_error_offset_next = current_cli_prompt().size();
int syntax_error_offset_prev = syntax_error_offset_next;
int i, old_len, new_len;
vector<string> command_global_name;
bool found_type_match_cb = false;
token_line = command_line;
new_len = token_line.size();
old_len = new_len;
if (parent_cli_command != NULL)
command_global_name = parent_cli_command->global_name();
for (token = pop_token(token_line);
! token.empty();
token = pop_token(token_line)) {
if (token != "|") {
child_cli_command = parent_cli_command->command_find(token);
new_len = token_line.size();
syntax_error_offset_prev = syntax_error_offset_next;
syntax_error_offset_next += old_len - new_len;
old_len = new_len;
if (child_cli_command != NULL
&& (! child_cli_command->is_command_argument())) {
parent_cli_command = child_cli_command;
// Add the token to the command
found_type_match_cb |= child_cli_command->has_type_match_cb();
if (! found_type_match_cb)
command_global_name = child_cli_command->global_name();
else
command_global_name.push_back(copy_token(token));
continue;
}
if (parent_cli_command->has_cli_process_callback()) {
// The parent command has processing function, so the rest
// of the tokens could be arguments for that function
child_cli_command = parent_cli_command;
}
}
// Put-back the token and process the arguments after the loop
token_line = copy_token(token) + token_line;
break;
}
if (parent_cli_command->has_cli_process_callback()) {
// Process the rest of the tokens as arguments for this function
vector<string> args_vector;
bool is_process_func_arguments = true;
bool is_pipe_command_arguments = false;
bool pipe_command_empty = false; // true if empty pipe command
string pipe_command_name = "";
list<string> pipe_command_args_list;
for (token = pop_token(token_line);
! token.empty();
token = pop_token(token_line)) {
if (token == "|") {
if ((! parent_cli_command->can_pipe())
|| (parent_cli_command->cli_command_pipe() == NULL)) {
// We cannot use pipe with this command
goto print_syntax_error_label;
}
// Start of a pipe command
is_process_func_arguments = false;
is_pipe_command_arguments = false;
pipe_command_empty = true;
if (pipe_command_name.size()) {
// Add the previous pipe command
add_pipe(pipe_command_name, pipe_command_args_list);
pipe_command_name = "";
pipe_command_args_list.clear();
}
continue;
}
if (is_process_func_arguments) {
// Arguments for the processing command
args_vector.push_back(token);
continue;
}
if (! is_pipe_command_arguments) {
// The pipe command name
is_pipe_command_arguments = true;
pipe_command_empty = false;
pipe_command_name = token;
continue;
}
// The pipe command arguments
pipe_command_args_list.push_back(token);
}
if (pipe_command_name.size()) {
// Add the last pipe command
add_pipe(pipe_command_name, pipe_command_args_list);
pipe_command_name = "";
pipe_command_args_list.clear();
}
if (pipe_command_empty) {
// Empty pipe command
parent_cli_command = parent_cli_command->cli_command_pipe();
goto print_syntax_error_label;
}
// Run the command function
{
int ret_value;
string final_string = "";
bool is_error = false;
string error_msg;
if (parent_cli_command->default_nomore_mode())
set_nomore_mode(true);
list<CliPipe*>::iterator iter;
for (iter = _pipe_list.begin(); iter != _pipe_list.end(); ++iter) {
CliPipe *cli_pipe = *iter;
if (cli_pipe->start_func(final_string, error_msg) != XORP_OK) {
is_error = true;
break;
}
}
if (is_error) {
// Stop the started pipes
string error_msg2;
while (iter != _pipe_list.begin()) {
--iter;
CliPipe *cli_pipe = *iter;
cli_pipe->stop_func(error_msg2);
}
if (is_interactive())
set_nomore_mode(false);
cli_print(c_format("ERROR: %s\n", error_msg.c_str()));
return (XORP_ERROR);
}
if (final_string.size()) {
bool old_pipe_mode = is_pipe_mode();
set_pipe_mode(false);
cli_print(final_string);
set_pipe_mode(old_pipe_mode);
}
final_string = "";
_executed_cli_command = parent_cli_command;
_executed_cli_command_name = command_global_name;
_executed_cli_command_args = args_vector;
ret_value = parent_cli_command->_cli_process_callback->dispatch(
parent_cli_command->server_name(),
cli_session_term_name(),
cli_session_session_id(),
_executed_cli_command_name,
_executed_cli_command_args);
return (ret_value);
}
}
// The rest of the tokens (if any) cannot be processed as arguments
// Test if we can "cd" to this function.
token = pop_token(token_line);
if (token.empty()) {
if (parent_cli_command->allow_cd()) {
// Set the current command level
set_current_cli_command(parent_cli_command);
return (XORP_OK);
}
// Error. Will print the list of child commands (done below).
}
print_syntax_error_label:
//
// If there are more tokens, the first one has to be a sub-command.
// However, there wasn't a match in our search, hence it has
// to be an error.
// Further, there was no processing function, hence those cannot
// be command arguments.
//
syntax_error_offset_next -= token.size();
// Unknown command
if (parent_cli_command == current_cli_command()) {
cli_print(c_format("%*s%s\n", syntax_error_offset_next, " ", "^"));
cli_print("unknown command.\n");
return (XORP_ERROR);
}
if (token.empty())
syntax_error_offset_next++;
// Command that cannot be executed
if (parent_cli_command->child_command_list().empty()) {
string cmd_name = token_vector2line(parent_cli_command->global_name());
if (token.empty()) {
cli_print(c_format("syntax error, command \"%s\" is not executable.\n",
cmd_name.c_str()));
} else {
cli_print(c_format("syntax error, command \"%s\" cannot be executed with argument \"%s\".\n",
cmd_name.c_str(),
token.c_str()));
}
return (XORP_ERROR);
}
// Command with invalid sub-parts
cli_print(c_format("%*s%s\n", syntax_error_offset_next, " ", "^"));
cli_print("syntax error, expecting");
if (parent_cli_command->child_command_list().size() > 4) {
// TODO: replace the hard-coded "4" with a #define or a parameter.
cli_print(" <command>.\n");
return (XORP_ERROR);
}
list<CliCommand *>::iterator iter;
i = 0;
for (iter = parent_cli_command->child_command_list().begin();
iter != parent_cli_command->child_command_list().end();
++iter) {
child_cli_command = *iter;
if (i > 0) {
cli_print(",");
if ((size_t)(i + 1)
== parent_cli_command->child_command_list().size())
cli_print(" or");
}
i++;
cli_print(c_format(" `%s'", child_cli_command->name().c_str()));
}
cli_print(".\n");
return (XORP_ERROR);
}
void
CliClient::interrupt_command()
{
if (! is_waiting_for_data())
goto cleanup_label;
if ((_executed_cli_command == NULL)
|| (! _executed_cli_command->has_cli_interrupt_callback())) {
goto cleanup_label;
}
_executed_cli_command->_cli_interrupt_callback->dispatch(
_executed_cli_command->server_name(),
cli_session_term_name(),
cli_session_session_id(),
_executed_cli_command_name,
_executed_cli_command_args);
cleanup_label:
// Reset everything about the command
_executed_cli_command = NULL;
_executed_cli_command_name.clear();
_executed_cli_command_args.clear();
delete_pipe_all();
set_pipe_mode(false);
set_hold_mode(false);
set_page_mode(false);
reset_page_buffer();
set_page_buffer_mode(false);
if (is_interactive())
set_nomore_mode(false);
if (is_waiting_for_data()) {
cli_print("\n"); // XXX: new line
cli_print("Command interrupted!\n");
}
//
// Ignore current line, reset buffer, line, cursor, prompt
//
if (current_cli_command() != NULL) {
set_current_cli_prompt(current_cli_command()->cd_prompt());
}
cli_print("\n"); // XXX: new line
gl_redisplay_line(gl());
gl_reset_line(gl());
set_buff_curpos(0);
command_buffer().reset();
cli_flush();
set_prompt_flushed(false);
set_is_waiting_for_data(false);
}
int
CliClient::command_completion_func(WordCompletion *cpl, void *data,
const char *line, int word_end)
{
int ret_value = 1;
CliClient *cli_client = reinterpret_cast<CliClient*>(data);
CliCommand *curr_cli_command = cli_client->_current_cli_command;
list<CliCommand *> cli_command_match_list;
set<string> type_names, no_type_names;
if (cpl == NULL)
return (1);
list<CliCommand *>::iterator iter;
for (iter = curr_cli_command->child_command_list().begin();
iter != curr_cli_command->child_command_list().end();
++iter) {
CliCommand *tmp_cli_command = *iter;
if (! tmp_cli_command->has_cli_completion_func())
continue;
if (tmp_cli_command->_cli_completion_func(tmp_cli_command,
cpl, NULL, line, word_end,
cli_command_match_list)) {
ret_value = 0; // XXX: there was a completion
}
}
if (curr_cli_command->can_pipe()
&& (curr_cli_command->cli_command_pipe() != NULL)) {
// Add the pipe completions
if (curr_cli_command->_cli_completion_func(
curr_cli_command->cli_command_pipe(),
cpl, NULL, line, word_end,
cli_command_match_list)) {
ret_value = 0;
}
}
//
// Separate the type-match commands from the rest
//
for (iter = cli_command_match_list.begin();
iter != cli_command_match_list.end();
++iter) {
CliCommand *tmp_cli_command = *iter;
if (tmp_cli_command->has_type_match_cb())
type_names.insert(tmp_cli_command->name());
else
no_type_names.insert(tmp_cli_command->name());
}
if (no_type_names.size() > 1) {
// Prepare and print the initial message(s)
string token_line = string(line, word_end);
string token;
// Get the lastest token
do {
string next_token = pop_token(token_line);
if (next_token.empty())
break;
token = next_token;
} while (true);
cli_client->cli_print(c_format("\n`%s' is ambiguous.", token.c_str()));
cli_client->cli_print("\nPossible completions:");
} else {
if (type_names.size() > 0) {
cli_client->command_line_help(line, word_end, false);
}
}
if (ret_value != 0) {
cpl_record_error(cpl, "Not a XORP command!");
}
return (ret_value);
}
syntax highlighted by Code2HTML, v. 0.9.1