/* * 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 * * This file is part of the virtualmips distribution. * See LICENSE file for terms of the license. * */ #ifdef __CYGWIN__ #define FD_SETSIZE 1024 #endif #include #include #include #include #include #include #include #include #include #include #include #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); }