1038 lines
25 KiB
C
1038 lines
25 KiB
C
/*
|
|
* Cisco router simulation platform.
|
|
* Copyright (c) 2005,2006 Christophe Fillot (cf@utc.fr)
|
|
*
|
|
* Virtual console TTY.
|
|
*
|
|
* "Interactive" part idea by Mtve.
|
|
* TCP console added by Mtve.
|
|
* Serial console by Peter Ross (suxen_drol@hotmail.com)
|
|
*/
|
|
|
|
/* By default, Cygwin supports only 64 FDs with select()! */
|
|
|
|
/*
|
|
* Copyright (C) yajin 2008 <yajinzhou@gmail.com >
|
|
*
|
|
* This file is part of the virtualmips distribution.
|
|
* See LICENSE file for terms of the license.
|
|
*
|
|
*/
|
|
|
|
#ifdef __CYGWIN__
|
|
#define FD_SETSIZE 1024
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <termios.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
|
|
#include <arpa/telnet.h>
|
|
|
|
#include "utils.h"
|
|
#include "mips.h"
|
|
#include "vm.h"
|
|
|
|
#include "mips_exec.h"
|
|
|
|
#include "device.h"
|
|
#include "mips_memory.h"
|
|
#include "dev_vtty.h"
|
|
|
|
/* VTTY list */
|
|
static pthread_mutex_t vtty_list_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static vtty_t *vtty_list = NULL;
|
|
static pthread_t vtty_thread;
|
|
|
|
#define VTTY_LIST_LOCK() pthread_mutex_lock(&vtty_list_mutex);
|
|
#define VTTY_LIST_UNLOCK() pthread_mutex_unlock(&vtty_list_mutex);
|
|
|
|
static struct termios tios, tios_orig;
|
|
|
|
/* Send Telnet command: WILL TELOPT_ECHO */
|
|
static void vtty_telnet_will_echo (vtty_t * vtty)
|
|
{
|
|
u_char cmd[] = { IAC, WILL, TELOPT_ECHO };
|
|
if (write (vtty->fd, cmd, sizeof (cmd)) < 0)
|
|
perror ("vtty_telnet_will_echo");
|
|
}
|
|
|
|
/* Send Telnet command: Suppress Go-Ahead */
|
|
static void vtty_telnet_will_suppress_go_ahead (vtty_t * vtty)
|
|
{
|
|
u_char cmd[] = { IAC, WILL, TELOPT_SGA };
|
|
if (write (vtty->fd, cmd, sizeof (cmd)) < 0)
|
|
perror ("vtty_telnet_will_suppress_go_ahead");
|
|
}
|
|
|
|
/* Send Telnet command: Don't use linemode */
|
|
static void vtty_telnet_dont_linemode (vtty_t * vtty)
|
|
{
|
|
u_char cmd[] = { IAC, DONT, TELOPT_LINEMODE };
|
|
if (write (vtty->fd, cmd, sizeof (cmd)) < 0)
|
|
perror ("vtty_telnet_dont_linemode");
|
|
}
|
|
|
|
/* Send Telnet command: does the client support terminal type message? */
|
|
static void vtty_telnet_do_ttype (vtty_t * vtty)
|
|
{
|
|
u_char cmd[] = { IAC, DO, TELOPT_TTYPE };
|
|
if (write (vtty->fd, cmd, sizeof (cmd)) < 0)
|
|
perror ("vtty_telnet_do_ttype");
|
|
}
|
|
|
|
/* Restore TTY original settings */
|
|
static void vtty_term_reset (void)
|
|
{
|
|
tcsetattr (STDIN_FILENO, TCSANOW, &tios_orig);
|
|
}
|
|
|
|
/* Initialize real TTY */
|
|
static void vtty_term_init (void)
|
|
{
|
|
tcgetattr (STDIN_FILENO, &tios);
|
|
|
|
memcpy (&tios_orig, &tios, sizeof (struct termios));
|
|
atexit (vtty_term_reset);
|
|
|
|
tios.c_cc[VTIME] = 0;
|
|
tios.c_cc[VMIN] = 1;
|
|
|
|
/* Disable Ctrl-C, Ctrl-S, Ctrl-Q and Ctrl-Z */
|
|
tios.c_cc[VINTR] = 0;
|
|
tios.c_cc[VSTART] = 0;
|
|
tios.c_cc[VSTOP] = 0;
|
|
tios.c_cc[VSUSP] = 0;
|
|
|
|
tios.c_lflag &= ~(ICANON | ECHO);
|
|
tios.c_iflag &= ~ICRNL;
|
|
tcsetattr (STDIN_FILENO, TCSANOW, &tios);
|
|
tcflush (STDIN_FILENO, TCIFLUSH);
|
|
}
|
|
|
|
/* Wait for a TCP connection */
|
|
static int vtty_tcp_conn_wait (vtty_t * vtty)
|
|
{
|
|
struct sockaddr_in serv;
|
|
int one = 1;
|
|
|
|
vtty->state = VTTY_STATE_TCP_INVALID;
|
|
|
|
if ((vtty->accept_fd = socket (PF_INET, SOCK_STREAM, 0)) < 0) {
|
|
perror ("vtty_tcp_waitcon: socket");
|
|
return (-1);
|
|
}
|
|
|
|
if (setsockopt (vtty->accept_fd, SOL_SOCKET, SO_REUSEADDR, &one,
|
|
sizeof (one)) < 0) {
|
|
perror ("vtty_tcp_waitcon: setsockopt(SO_REUSEADDR)");
|
|
goto error;
|
|
}
|
|
|
|
memset (&serv, 0, sizeof (serv));
|
|
serv.sin_family = AF_INET;
|
|
serv.sin_addr.s_addr = htonl (INADDR_ANY);
|
|
serv.sin_port = htons (vtty->tcp_port);
|
|
|
|
if (bind (vtty->accept_fd, (struct sockaddr *) &serv, sizeof (serv)) < 0) {
|
|
perror ("vtty_tcp_waitcon: bind");
|
|
goto error;
|
|
}
|
|
|
|
if (listen (vtty->accept_fd, 1) < 0) {
|
|
perror ("vtty_tcp_waitcon: listen");
|
|
goto error;
|
|
}
|
|
|
|
vm_log (vtty->vm, "VTTY",
|
|
"%s: waiting connection on tcp port %d (FD %d)\n", vtty->name,
|
|
vtty->tcp_port, vtty->accept_fd);
|
|
|
|
vtty->select_fd = &vtty->accept_fd;
|
|
vtty->state = VTTY_STATE_TCP_WAITING;
|
|
return (0);
|
|
|
|
error:
|
|
close (vtty->accept_fd);
|
|
vtty->accept_fd = -1;
|
|
vtty->select_fd = NULL;
|
|
return (-1);
|
|
}
|
|
|
|
/* Accept a TCP connection */
|
|
static int vtty_tcp_conn_accept (vtty_t * vtty)
|
|
{
|
|
if ((vtty->fd = accept (vtty->accept_fd, NULL, NULL)) < 0) {
|
|
fprintf (stderr,
|
|
"vtty_tcp_conn_accept: accept on port %d failed %s\n",
|
|
vtty->tcp_port, strerror (errno));
|
|
return (-1);
|
|
}
|
|
|
|
vm_log (vtty->vm, "VTTY",
|
|
"%s is now connected (accept_fd=%d,conn_fd=%d)\n", vtty->name,
|
|
vtty->accept_fd, vtty->fd);
|
|
|
|
/* Adapt Telnet settings */
|
|
if (vtty->terminal_support) {
|
|
vtty_telnet_do_ttype (vtty);
|
|
vtty_telnet_will_echo (vtty);
|
|
vtty_telnet_will_suppress_go_ahead (vtty);
|
|
vtty_telnet_dont_linemode (vtty);
|
|
vtty->input_state = VTTY_INPUT_TELNET;
|
|
}
|
|
|
|
if (!(vtty->fstream = fdopen (vtty->fd, "wb"))) {
|
|
close (vtty->fd);
|
|
vtty->fd = -1;
|
|
return (-1);
|
|
}
|
|
|
|
fprintf (vtty->fstream,
|
|
"Connected to Dynamips VM \"%s\" ( type %s) - %s\r\n\r\n",
|
|
vtty->vm->name, vm_get_type (vtty->vm), vtty->name);
|
|
|
|
vtty->select_fd = &vtty->fd;
|
|
vtty->state = VTTY_STATE_TCP_RUNNING;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Parse serial interface descriptor string, return 0 if success
|
|
* string takes the form "device:baudrate:databits:parity:stopbits:hwflow"
|
|
* device is mandatory, other options are optional (default=9600,8,N,1,0).
|
|
*/
|
|
int vtty_parse_serial_option (vtty_serial_option_t * option, char *optarg)
|
|
{
|
|
char *array[6];
|
|
int count;
|
|
|
|
if ((count = m_strtok (optarg, ':', array, 6)) < 1) {
|
|
fprintf (stderr, "vtty_parse_serial_option: invalid string\n");
|
|
return (-1);
|
|
}
|
|
|
|
if (!(option->device = strdup (array[0]))) {
|
|
fprintf (stderr, "vtty_parse_serial_option: unable to copy string\n");
|
|
return (-1);
|
|
}
|
|
|
|
option->baudrate = (count > 1) ? atoi (array[1]) : 9600;
|
|
option->databits = (count > 2) ? atoi (array[2]) : 8;
|
|
|
|
if (count > 3) {
|
|
switch (*array[3]) {
|
|
case 'o':
|
|
case 'O':
|
|
option->parity = 1; /* odd */
|
|
case 'e':
|
|
case 'E':
|
|
option->parity = 2; /* even */
|
|
default:
|
|
option->parity = 0; /* none */
|
|
}
|
|
} else {
|
|
option->parity = 0;
|
|
}
|
|
|
|
option->stopbits = (count > 4) ? atoi (array[4]) : 1;
|
|
option->hwflow = (count > 5) ? atoi (array[5]) : 0;
|
|
return (0);
|
|
}
|
|
|
|
#if defined(__CYGWIN__) || defined(SUNOS)
|
|
void cfmakeraw (struct termios *termios_p)
|
|
{
|
|
termios_p->c_iflag &=
|
|
~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
|
termios_p->c_oflag &= ~OPOST;
|
|
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
|
termios_p->c_cflag &= ~(CSIZE | PARENB);
|
|
termios_p->c_cflag |= CS8;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Setup serial port, return 0 if success.
|
|
*/
|
|
static int vtty_serial_setup (vtty_t * vtty,
|
|
const vtty_serial_option_t * option)
|
|
{
|
|
struct termios tio;
|
|
int tio_baudrate;
|
|
|
|
if (tcgetattr (vtty->fd, &tio) != 0) {
|
|
fprintf (stderr, "error: tcgetattr failed\n");
|
|
return (-1);
|
|
}
|
|
|
|
cfmakeraw (&tio);
|
|
|
|
tio.c_cflag = 0 | CLOCAL // ignore modem control lines
|
|
;
|
|
|
|
tio.c_cflag &= ~CREAD;
|
|
tio.c_cflag |= CREAD;
|
|
|
|
switch (option->baudrate) {
|
|
case 50:
|
|
tio_baudrate = B50;
|
|
break;
|
|
case 75:
|
|
tio_baudrate = B75;
|
|
break;
|
|
case 110:
|
|
tio_baudrate = B110;
|
|
break;
|
|
case 134:
|
|
tio_baudrate = B134;
|
|
break;
|
|
case 150:
|
|
tio_baudrate = B150;
|
|
break;
|
|
case 200:
|
|
tio_baudrate = B200;
|
|
break;
|
|
case 300:
|
|
tio_baudrate = B300;
|
|
break;
|
|
case 600:
|
|
tio_baudrate = B600;
|
|
break;
|
|
case 1200:
|
|
tio_baudrate = B1200;
|
|
break;
|
|
case 1800:
|
|
tio_baudrate = B1800;
|
|
break;
|
|
case 2400:
|
|
tio_baudrate = B2400;
|
|
break;
|
|
case 4800:
|
|
tio_baudrate = B4800;
|
|
break;
|
|
case 9600:
|
|
tio_baudrate = B9600;
|
|
break;
|
|
case 19200:
|
|
tio_baudrate = B19200;
|
|
break;
|
|
case 38400:
|
|
tio_baudrate = B38400;
|
|
break;
|
|
case 57600:
|
|
tio_baudrate = B57600;
|
|
break;
|
|
#if defined(B76800)
|
|
case 76800:
|
|
tio_baudrate = B76800;
|
|
break;
|
|
#endif
|
|
case 115200:
|
|
tio_baudrate = B115200;
|
|
break;
|
|
#if defined(B230400)
|
|
case 230400:
|
|
tio_baudrate = B230400;
|
|
break;
|
|
#endif
|
|
default:
|
|
fprintf (stderr, "error: unsupported baudrate\n");
|
|
return (-1);
|
|
}
|
|
|
|
cfsetospeed (&tio, tio_baudrate);
|
|
cfsetispeed (&tio, tio_baudrate);
|
|
|
|
tio.c_cflag &= ~CSIZE; /* clear size flag */
|
|
switch (option->databits) {
|
|
case 5:
|
|
tio.c_cflag |= CS5;
|
|
break;
|
|
case 6:
|
|
tio.c_cflag |= CS6;
|
|
break;
|
|
case 7:
|
|
tio.c_cflag |= CS7;
|
|
break;
|
|
case 8:
|
|
tio.c_cflag |= CS8;
|
|
break;
|
|
default:
|
|
fprintf (stderr, "error: unsupported databits\n");
|
|
return (-1);
|
|
}
|
|
|
|
tio.c_iflag &= ~INPCK; /* clear parity flag */
|
|
tio.c_cflag &= ~(PARENB | PARODD);
|
|
switch (option->parity) {
|
|
case 0:
|
|
break;
|
|
case 2:
|
|
tio.c_iflag |= INPCK;
|
|
tio.c_cflag |= PARENB;
|
|
break; /* even */
|
|
case 1:
|
|
tio.c_iflag |= INPCK;
|
|
tio.c_cflag |= PARENB | PARODD;
|
|
break; /* odd */
|
|
default:
|
|
fprintf (stderr, "error: unsupported parity\n");
|
|
return (-1);
|
|
}
|
|
|
|
tio.c_cflag &= ~CSTOPB; /* clear stop flag */
|
|
switch (option->stopbits) {
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
tio.c_cflag |= CSTOPB;
|
|
break;
|
|
default:
|
|
fprintf (stderr, "error: unsupported stopbits\n");
|
|
return (-1);
|
|
}
|
|
|
|
#if defined(CRTSCTS)
|
|
tio.c_cflag &= ~CRTSCTS;
|
|
#endif
|
|
#if defined(CNEW_RTSCTS)
|
|
tio.c_cflag &= ~CNEW_RTSCTS;
|
|
#endif
|
|
if (option->hwflow) {
|
|
#if defined(CRTSCTS)
|
|
tio.c_cflag |= CRTSCTS;
|
|
#else
|
|
tio.c_cflag |= CNEW_RTSCTS;
|
|
#endif
|
|
}
|
|
|
|
tio.c_cc[VTIME] = 0;
|
|
tio.c_cc[VMIN] = 1; /* block read() until one character is available */
|
|
|
|
#if 0
|
|
/* not neccessary unless O_NONBLOCK used */
|
|
if (fcntl (vtty->fd, F_SETFL, 0) != 0) { /* enable blocking mode */
|
|
fprintf (stderr, "error: fnctl F_SETFL failed\n");
|
|
return (-1);
|
|
}
|
|
#endif
|
|
|
|
if (tcflush (vtty->fd, TCIOFLUSH) != 0) {
|
|
fprintf (stderr, "error: tcflush failed\n");
|
|
return (-1);
|
|
}
|
|
|
|
if (tcsetattr (vtty->fd, TCSANOW, &tio) != 0) {
|
|
fprintf (stderr, "error: tcsetattr failed\n");
|
|
return (-1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Create a virtual tty */
|
|
vtty_t *vtty_create (vm_instance_t * vm, char *name, int type, int tcp_port,
|
|
const vtty_serial_option_t * option)
|
|
{
|
|
vtty_t *vtty;
|
|
|
|
if (!(vtty = malloc (sizeof (*vtty)))) {
|
|
fprintf (stderr, "VTTY: unable to create new virtual tty.\n");
|
|
return NULL;
|
|
}
|
|
|
|
memset (vtty, 0, sizeof (*vtty));
|
|
vtty->name = name;
|
|
vtty->type = type;
|
|
vtty->vm = vm;
|
|
vtty->fd = -1;
|
|
vtty->fstream = NULL;
|
|
vtty->accept_fd = -1;
|
|
pthread_mutex_init (&vtty->lock, NULL);
|
|
vtty->terminal_support = 0; // was 1 (vak)
|
|
vtty->input_state = VTTY_INPUT_TEXT;
|
|
|
|
switch (vtty->type) {
|
|
case VTTY_TYPE_NONE:
|
|
vtty->select_fd = NULL;
|
|
break;
|
|
|
|
case VTTY_TYPE_TERM:
|
|
vtty_term_init ();
|
|
vtty->fd = STDIN_FILENO;
|
|
vtty->select_fd = &vtty->fd;
|
|
vtty->fstream = stdout;
|
|
break;
|
|
|
|
case VTTY_TYPE_TCP:
|
|
vtty->tcp_port = tcp_port;
|
|
vtty_tcp_conn_wait (vtty);
|
|
break;
|
|
|
|
case VTTY_TYPE_SERIAL:
|
|
vtty->fd = open (option->device, O_RDWR);
|
|
if (vtty->fd < 0) {
|
|
fprintf (stderr, "VTTY: open failed\n");
|
|
free (vtty);
|
|
return NULL;
|
|
}
|
|
if (vtty_serial_setup (vtty, option)) {
|
|
fprintf (stderr, "VTTY: setup failed\n");
|
|
close (vtty->fd);
|
|
free (vtty);
|
|
return NULL;
|
|
}
|
|
vtty->select_fd = &vtty->fd;
|
|
vtty->terminal_support = 0;
|
|
break;
|
|
|
|
default:
|
|
fprintf (stderr, "tty_create: bad vtty type %d\n", vtty->type);
|
|
return NULL;
|
|
}
|
|
|
|
/* Add this new VTTY to the list */
|
|
VTTY_LIST_LOCK ();
|
|
vtty->next = vtty_list;
|
|
vtty->pprev = &vtty_list;
|
|
|
|
if (vtty_list != NULL)
|
|
vtty_list->pprev = &vtty->next;
|
|
|
|
vtty_list = vtty;
|
|
VTTY_LIST_UNLOCK ();
|
|
return vtty;
|
|
}
|
|
|
|
/* Delete a virtual tty */
|
|
void vtty_delete (vtty_t * vtty)
|
|
{
|
|
if (vtty != NULL) {
|
|
if (vtty->pprev != NULL) {
|
|
VTTY_LIST_LOCK ();
|
|
if (vtty->next)
|
|
vtty->next->pprev = vtty->pprev;
|
|
*(vtty->pprev) = vtty->next;
|
|
VTTY_LIST_UNLOCK ();
|
|
}
|
|
|
|
if ((vtty->fstream) && (vtty->fstream != stdout))
|
|
fclose (vtty->fstream);
|
|
|
|
/* We don't close FD 0 since it is stdin */
|
|
if (vtty->fd > 0) {
|
|
vm_log (vtty->vm, "VTTY", "%s: closing FD %d\n", vtty->name,
|
|
vtty->fd);
|
|
close (vtty->fd);
|
|
}
|
|
|
|
if (vtty->accept_fd != -1) {
|
|
vm_log (vtty->vm, "VTTY", "%s: closing accept FD %d\n",
|
|
vtty->name, vtty->accept_fd);
|
|
close (vtty->accept_fd);
|
|
}
|
|
|
|
free (vtty);
|
|
}
|
|
}
|
|
|
|
/* Store a character in the FIFO buffer */
|
|
static int vtty_store (vtty_t * vtty, u_char c)
|
|
{
|
|
u_int nwptr;
|
|
|
|
VTTY_LOCK (vtty);
|
|
nwptr = vtty->write_ptr + 1;
|
|
if (nwptr == VTTY_BUFFER_SIZE)
|
|
nwptr = 0;
|
|
|
|
if (nwptr == vtty->read_ptr) {
|
|
VTTY_UNLOCK (vtty);
|
|
return (-1);
|
|
}
|
|
|
|
vtty->buffer[vtty->write_ptr] = c;
|
|
vtty->write_ptr = nwptr;
|
|
VTTY_UNLOCK (vtty);
|
|
return (0);
|
|
}
|
|
|
|
/* Store a string in the FIFO buffer */
|
|
int vtty_store_str (vtty_t * vtty, char *str)
|
|
{
|
|
if (!vtty)
|
|
return (0);
|
|
|
|
while (*str != 0) {
|
|
if (vtty_store (vtty, *str) == -1)
|
|
return (-1);
|
|
|
|
str++;
|
|
}
|
|
|
|
vtty->input_pending = TRUE;
|
|
return (0);
|
|
}
|
|
|
|
/* Store CTRL+C in buffer */
|
|
int vtty_store_ctrlc (vtty_t * vtty)
|
|
{
|
|
if (vtty)
|
|
vtty_store (vtty, 0x03);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Read a character from the terminal.
|
|
*/
|
|
static int vtty_term_read (vtty_t * vtty)
|
|
{
|
|
u_char c;
|
|
|
|
if (read (vtty->fd, &c, 1) == 1)
|
|
return (c);
|
|
|
|
perror ("read from vtty failed");
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Read a character from the TCP connection.
|
|
*/
|
|
static int vtty_tcp_read (vtty_t * vtty)
|
|
{
|
|
u_char c;
|
|
|
|
switch (vtty->state) {
|
|
case VTTY_STATE_TCP_RUNNING:
|
|
if (read (vtty->fd, &c, 1) == 1)
|
|
return (c);
|
|
|
|
/* Problem with the connection: Re-enter wait mode */
|
|
shutdown (vtty->fd, 2);
|
|
fclose (vtty->fstream);
|
|
close (vtty->fd);
|
|
vtty->fstream = NULL;
|
|
vtty->fd = -1;
|
|
vtty->select_fd = &vtty->accept_fd;
|
|
vtty->state = VTTY_STATE_TCP_WAITING;
|
|
return (-1);
|
|
|
|
case VTTY_STATE_TCP_WAITING:
|
|
/* A new connection has arrived */
|
|
vtty_tcp_conn_accept (vtty);
|
|
return (-1);
|
|
}
|
|
|
|
/* Shouldn't happen... */
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Read a character from the USB connection.
|
|
*/
|
|
static int vtty_usb_read (vtty_t * vtty)
|
|
{
|
|
// stub
|
|
perror("VTTY not yet implemented on USB\n");
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Read a character from the virtual TTY.
|
|
*
|
|
* If the VTTY is a TCP connection, restart it in case of error.
|
|
*/
|
|
static int vtty_read (vtty_t * vtty)
|
|
{
|
|
switch (vtty->type) {
|
|
case VTTY_TYPE_TERM:
|
|
case VTTY_TYPE_SERIAL:
|
|
return (vtty_term_read (vtty));
|
|
case VTTY_TYPE_TCP:
|
|
return (vtty_tcp_read (vtty));
|
|
case VTTY_TYPE_USB:
|
|
return (vtty_usb_read (vtty));
|
|
default:
|
|
fprintf (stderr, "vtty_read: bad vtty type %d\n", vtty->type);
|
|
return (-1);
|
|
}
|
|
|
|
/* NOTREACHED */
|
|
return (-1);
|
|
}
|
|
|
|
#if 0
|
|
/* Remote control for MIPS64 processors */
|
|
static int remote_control_mips (vtty_t * vtty, char c, cpu_mips_t * cpu)
|
|
{
|
|
switch (c) {
|
|
/* Show information about JIT compiled pages */
|
|
case 'b':
|
|
// printf("\nCPU0: %u JIT compiled pages [Exec Area Pages: %lu/%lu]\n",
|
|
// cpu->compiled_pages,
|
|
// (u_long)cpu->exec_page_alloc,
|
|
// (u_long)cpu->exec_page_count);
|
|
break;
|
|
|
|
/* Non-JIT mode statistics */
|
|
case 'j':
|
|
// mips_dump_stats(cpu);
|
|
break;
|
|
|
|
default:
|
|
return (FALSE);
|
|
}
|
|
|
|
return (TRUE);
|
|
}
|
|
#endif
|
|
|
|
/* Read a character (until one is available) and store it in buffer */
|
|
static void vtty_read_and_store (vtty_t * vtty)
|
|
{
|
|
int c;
|
|
|
|
/* wait until we get a character input */
|
|
c = vtty_read (vtty);
|
|
|
|
/* if read error, do nothing */
|
|
if (c < 0)
|
|
return;
|
|
|
|
if (!vtty->terminal_support) {
|
|
vtty_store (vtty, c);
|
|
return;
|
|
}
|
|
|
|
switch (vtty->input_state) {
|
|
case VTTY_INPUT_TEXT:
|
|
switch (c) {
|
|
case 0x1b:
|
|
vtty->input_state = VTTY_INPUT_VT1;
|
|
return;
|
|
|
|
/* Ctrl + ']' (0x1d, 29), or Alt-Gr + '*' (0xb3, 179) */
|
|
case 0x1d:
|
|
case 0xb3:
|
|
vtty->input_state = VTTY_INPUT_REMOTE;
|
|
return;
|
|
case IAC:
|
|
vtty->input_state = VTTY_INPUT_TELNET;
|
|
return;
|
|
case 0: /* NULL - Must be ignored - generated by Linux telnet */
|
|
case 10: /* LF (Line Feed) - Must be ignored on Windows platform */
|
|
return;
|
|
default:
|
|
/* Store a standard character */
|
|
vtty_store (vtty, c);
|
|
return;
|
|
}
|
|
|
|
case VTTY_INPUT_VT1:
|
|
switch (c) {
|
|
case 0x5b:
|
|
vtty->input_state = VTTY_INPUT_VT2;
|
|
return;
|
|
default:
|
|
vtty_store (vtty, 0x1b);
|
|
vtty_store (vtty, c);
|
|
}
|
|
vtty->input_state = VTTY_INPUT_TEXT;
|
|
return;
|
|
|
|
case VTTY_INPUT_VT2:
|
|
switch (c) {
|
|
case 0x41: /* Up Arrow */
|
|
vtty_store (vtty, 16);
|
|
break;
|
|
case 0x42: /* Down Arrow */
|
|
vtty_store (vtty, 14);
|
|
break;
|
|
case 0x43: /* Right Arrow */
|
|
vtty_store (vtty, 6);
|
|
break;
|
|
case 0x44: /* Left Arrow */
|
|
vtty_store (vtty, 2);
|
|
break;
|
|
default:
|
|
vtty_store (vtty, 0x5b);
|
|
vtty_store (vtty, 0x1b);
|
|
vtty_store (vtty, c);
|
|
break;
|
|
}
|
|
vtty->input_state = VTTY_INPUT_TEXT;
|
|
return;
|
|
|
|
case VTTY_INPUT_REMOTE:
|
|
//remote_control(vtty, c);
|
|
vtty->input_state = VTTY_INPUT_TEXT;
|
|
return;
|
|
|
|
case VTTY_INPUT_TELNET:
|
|
vtty->telnet_cmd = c;
|
|
switch (c) {
|
|
case WILL:
|
|
case WONT:
|
|
case DO:
|
|
case DONT:
|
|
vtty->input_state = VTTY_INPUT_TELNET_IYOU;
|
|
return;
|
|
case SB:
|
|
vtty->telnet_cmd = c;
|
|
vtty->input_state = VTTY_INPUT_TELNET_SB1;
|
|
return;
|
|
case SE:
|
|
break;
|
|
case IAC:
|
|
vtty_store (vtty, IAC);
|
|
break;
|
|
}
|
|
vtty->input_state = VTTY_INPUT_TEXT;
|
|
return;
|
|
|
|
case VTTY_INPUT_TELNET_IYOU:
|
|
vtty->telnet_opt = c;
|
|
/* if telnet client can support ttype, ask it to send ttype string */
|
|
if ((vtty->telnet_cmd == WILL) && (vtty->telnet_opt == TELOPT_TTYPE)) {
|
|
vtty_put_char (vtty, (char)IAC);
|
|
vtty_put_char (vtty, (char)SB);
|
|
vtty_put_char (vtty, TELOPT_TTYPE);
|
|
vtty_put_char (vtty, TELQUAL_SEND);
|
|
vtty_put_char (vtty, (char)IAC);
|
|
vtty_put_char (vtty, (char)SE);
|
|
}
|
|
vtty->input_state = VTTY_INPUT_TEXT;
|
|
return;
|
|
|
|
case VTTY_INPUT_TELNET_SB1:
|
|
vtty->telnet_opt = c;
|
|
vtty->input_state = VTTY_INPUT_TELNET_SB2;
|
|
return;
|
|
|
|
case VTTY_INPUT_TELNET_SB2:
|
|
vtty->telnet_qual = c;
|
|
if ((vtty->telnet_opt == TELOPT_TTYPE)
|
|
&& (vtty->telnet_qual == TELQUAL_IS))
|
|
vtty->input_state = VTTY_INPUT_TELNET_SB_TTYPE;
|
|
else
|
|
vtty->input_state = VTTY_INPUT_TELNET_NEXT;
|
|
return;
|
|
|
|
case VTTY_INPUT_TELNET_SB_TTYPE:
|
|
/* parse ttype string: first char is sufficient */
|
|
/* if client is xterm or vt, set the title bar */
|
|
if ((c == 'x') || (c == 'X') || (c == 'v') || (c == 'V')) {
|
|
// fprintf(vtty->fstream, "\033]0;Dynamips(%i): %s, %s\07",
|
|
// vtty->vm->instance_id, vtty->vm->name, vtty->name);
|
|
}
|
|
vtty->input_state = VTTY_INPUT_TELNET_NEXT;
|
|
return;
|
|
|
|
case VTTY_INPUT_TELNET_NEXT:
|
|
/* ignore all chars until next IAC */
|
|
if (c == IAC)
|
|
vtty->input_state = VTTY_INPUT_TELNET;
|
|
return;
|
|
}
|
|
}
|
|
|
|
int vtty_bytes (vtty_t * vtty)
|
|
{
|
|
if (vtty->read_ptr < vtty->write_ptr)
|
|
return vtty->write_ptr - vtty->read_ptr;
|
|
else if (vtty->read_ptr == vtty->write_ptr) {
|
|
return VTTY_BUFFER_SIZE;
|
|
} else
|
|
return VTTY_BUFFER_SIZE - (vtty->read_ptr - vtty->write_ptr);
|
|
}
|
|
|
|
int vtty_is_full (vtty_t * vtty)
|
|
{
|
|
return (vtty->read_ptr == vtty->write_ptr);
|
|
}
|
|
|
|
/* Read a character from the buffer (-1 if the buffer is empty) */
|
|
int vtty_get_char (vtty_t * vtty)
|
|
{
|
|
u_char c;
|
|
|
|
VTTY_LOCK (vtty);
|
|
|
|
if (vtty->read_ptr == vtty->write_ptr) {
|
|
VTTY_UNLOCK (vtty);
|
|
return (-1);
|
|
}
|
|
|
|
c = vtty->buffer[vtty->read_ptr++];
|
|
|
|
if (vtty->read_ptr == VTTY_BUFFER_SIZE)
|
|
vtty->read_ptr = 0;
|
|
|
|
VTTY_UNLOCK (vtty);
|
|
return (c);
|
|
}
|
|
|
|
/* Returns TRUE if a character is available in buffer */
|
|
int vtty_is_char_avail (vtty_t * vtty)
|
|
{
|
|
int res;
|
|
|
|
VTTY_LOCK (vtty);
|
|
res = (vtty->read_ptr != vtty->write_ptr);
|
|
VTTY_UNLOCK (vtty);
|
|
return (res);
|
|
}
|
|
|
|
/* Put char to vtty */
|
|
void vtty_put_char (vtty_t * vtty, char ch)
|
|
{
|
|
switch (vtty->type) {
|
|
case VTTY_TYPE_NONE:
|
|
break;
|
|
|
|
case VTTY_TYPE_TERM:
|
|
//printf("put char %c\n",ch);
|
|
fwrite (&ch, 1, 1, vtty->fstream);
|
|
break;
|
|
|
|
case VTTY_TYPE_TCP:
|
|
if ((vtty->state == VTTY_STATE_TCP_RUNNING)
|
|
&& (fwrite (&ch, 1, 1, vtty->fstream) != 1)) {
|
|
vm_log (vtty->vm, "VTTY", "%s: put char %d failed (%s)\n",
|
|
vtty->name, (int) ch, strerror (errno));
|
|
}
|
|
break;
|
|
|
|
case VTTY_TYPE_SERIAL:
|
|
if (write (vtty->fd, &ch, 1) != 1) {
|
|
vm_log (vtty->vm, "VTTY", "%s: put char 0x%x failed (%s)\n",
|
|
vtty->name, (int) ch, strerror (errno));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fprintf (stderr, "vtty_put_char: bad vtty type %d\n", vtty->type);
|
|
exit (1);
|
|
}
|
|
}
|
|
|
|
/* Put a buffer to vtty */
|
|
void vtty_put_buffer (vtty_t * vtty, char *buf, size_t len)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
vtty_put_char (vtty, buf[i]);
|
|
}
|
|
|
|
/* Flush VTTY output */
|
|
void vtty_flush (vtty_t * vtty)
|
|
{
|
|
switch (vtty->type) {
|
|
case VTTY_TYPE_TERM:
|
|
case VTTY_TYPE_TCP:
|
|
if (vtty->fstream)
|
|
fflush (vtty->fstream);
|
|
break;
|
|
|
|
case VTTY_TYPE_SERIAL:
|
|
fsync (vtty->fd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* VTTY thread */
|
|
static void *vtty_thread_main (void *arg)
|
|
{
|
|
vtty_t *vtty;
|
|
struct timeval tv;
|
|
int fd, fd_max, res;
|
|
fd_set rfds;
|
|
|
|
for (;;) {
|
|
VTTY_LIST_LOCK ();
|
|
|
|
/* Build the FD set */
|
|
FD_ZERO (&rfds);
|
|
fd_max = -1;
|
|
for (vtty = vtty_list; vtty; vtty = vtty->next) {
|
|
if (!vtty->select_fd)
|
|
continue;
|
|
|
|
if ((fd = *vtty->select_fd) < 0)
|
|
continue;
|
|
|
|
if (fd > fd_max)
|
|
fd_max = fd;
|
|
FD_SET (fd, &rfds);
|
|
}
|
|
VTTY_LIST_UNLOCK ();
|
|
|
|
/* Wait for incoming data */
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 50 * 1000; /* 50 ms */
|
|
res = select (fd_max + 1, &rfds, NULL, NULL, &tv);
|
|
|
|
if (res == -1) {
|
|
if (errno != EINTR) {
|
|
perror ("vtty_thread: select");
|
|
|
|
for (vtty = vtty_list; vtty; vtty = vtty->next) {
|
|
fprintf (stderr, " %-15s: %s, FD %d\n", vtty->vm->name,
|
|
vtty->name, vtty->fd);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Examine active FDs and call user handlers */
|
|
VTTY_LIST_LOCK ();
|
|
for (vtty = vtty_list; vtty; vtty = vtty->next) {
|
|
if (!vtty->select_fd)
|
|
continue;
|
|
|
|
if ((fd = *vtty->select_fd) < 0)
|
|
continue;
|
|
|
|
if (FD_ISSET (fd, &rfds)) {
|
|
vtty_read_and_store (vtty);
|
|
vtty->input_pending = TRUE;
|
|
}
|
|
|
|
if (vtty->input_pending) {
|
|
if (vtty->read_notifier != NULL)
|
|
vtty->read_notifier (vtty);
|
|
|
|
vtty->input_pending = FALSE;
|
|
}
|
|
|
|
/* Flush any pending output */
|
|
if (!vtty->managed_flush)
|
|
vtty_flush (vtty);
|
|
}
|
|
VTTY_LIST_UNLOCK ();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize the VTTY thread */
|
|
int vtty_init (void)
|
|
{
|
|
if (pthread_create (&vtty_thread, NULL, vtty_thread_main, NULL)) {
|
|
perror ("vtty: pthread_create");
|
|
return (-1);
|
|
}
|
|
|
|
return (0);
|
|
}
|