diff options
author | aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162> | 2008-10-31 18:49:55 +0000 |
---|---|---|
committer | aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162> | 2008-10-31 18:49:55 +0000 |
commit | 6f97dba008ec434b742764596d9e28df673cf6c2 (patch) | |
tree | 5b836102f73d309e76f442ceedec7a5288bf220b /qemu-char.c | |
parent | 0e82f34d077dc254249edea9262174b1d3b44a01 (diff) |
Move CharDriverState code out of vl.c
The motivating goal behind this is to allow other tools to use the CharDriver
code. This patch is pure code motion except for the Makefile changes and the
copyright/header in qemu-char.c.
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@5580 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'qemu-char.c')
-rw-r--r-- | qemu-char.c | 2202 |
1 files changed, 2202 insertions, 0 deletions
diff --git a/qemu-char.c b/qemu-char.c new file mode 100644 index 0000000000..0a940a7f5f --- /dev/null +++ b/qemu-char.c @@ -0,0 +1,2202 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "net.h" +#include "console.h" +#include "sysemu.h" +#include "qemu-timer.h" +#include "qemu-char.h" +#include "block.h" + +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <time.h> +#include <errno.h> +#include <sys/time.h> +#include <zlib.h> + +#ifndef _WIN32 +#include <sys/times.h> +#include <sys/wait.h> +#include <termios.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <dirent.h> +#include <netdb.h> +#include <sys/select.h> +#include <arpa/inet.h> +#ifdef _BSD +#include <sys/stat.h> +#if !defined(__APPLE__) && !defined(__OpenBSD__) +#include <libutil.h> +#endif +#ifdef __OpenBSD__ +#include <net/if.h> +#endif +#elif defined (__GLIBC__) && defined (__FreeBSD_kernel__) +#include <freebsd/stdlib.h> +#else +#ifdef __linux__ +#include <linux/if.h> +#include <pty.h> + +#include <linux/ppdev.h> +#include <linux/parport.h> +#endif +#ifdef __sun__ +#include <sys/stat.h> +#include <sys/ethernet.h> +#include <sys/sockio.h> +#include <netinet/arp.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> // must come after ip.h +#include <netinet/udp.h> +#include <netinet/tcp.h> +#include <net/if.h> +#include <syslog.h> +#include <stropts.h> +#endif +#endif +#endif + +#include "qemu_socket.h" + +/***********************************************************/ +/* character device */ + +static void qemu_chr_event(CharDriverState *s, int event) +{ + if (!s->chr_event) + return; + s->chr_event(s->handler_opaque, event); +} + +static void qemu_chr_reset_bh(void *opaque) +{ + CharDriverState *s = opaque; + qemu_chr_event(s, CHR_EVENT_RESET); + qemu_bh_delete(s->bh); + s->bh = NULL; +} + +void qemu_chr_reset(CharDriverState *s) +{ + if (s->bh == NULL) { + s->bh = qemu_bh_new(qemu_chr_reset_bh, s); + qemu_bh_schedule(s->bh); + } +} + +int qemu_chr_write(CharDriverState *s, const uint8_t *buf, int len) +{ + return s->chr_write(s, buf, len); +} + +int qemu_chr_ioctl(CharDriverState *s, int cmd, void *arg) +{ + if (!s->chr_ioctl) + return -ENOTSUP; + return s->chr_ioctl(s, cmd, arg); +} + +int qemu_chr_can_read(CharDriverState *s) +{ + if (!s->chr_can_read) + return 0; + return s->chr_can_read(s->handler_opaque); +} + +void qemu_chr_read(CharDriverState *s, uint8_t *buf, int len) +{ + s->chr_read(s->handler_opaque, buf, len); +} + +void qemu_chr_accept_input(CharDriverState *s) +{ + if (s->chr_accept_input) + s->chr_accept_input(s); +} + +void qemu_chr_printf(CharDriverState *s, const char *fmt, ...) +{ + char buf[4096]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + qemu_chr_write(s, (uint8_t *)buf, strlen(buf)); + va_end(ap); +} + +void qemu_chr_send_event(CharDriverState *s, int event) +{ + if (s->chr_send_event) + s->chr_send_event(s, event); +} + +void qemu_chr_add_handlers(CharDriverState *s, + IOCanRWHandler *fd_can_read, + IOReadHandler *fd_read, + IOEventHandler *fd_event, + void *opaque) +{ + s->chr_can_read = fd_can_read; + s->chr_read = fd_read; + s->chr_event = fd_event; + s->handler_opaque = opaque; + if (s->chr_update_read_handler) + s->chr_update_read_handler(s); +} + +static int null_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + return len; +} + +static CharDriverState *qemu_chr_open_null(void) +{ + CharDriverState *chr; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + return NULL; + chr->chr_write = null_chr_write; + return chr; +} + +/* MUX driver for serial I/O splitting */ +static int term_timestamps; +static int64_t term_timestamps_start; +#define MAX_MUX 4 +#define MUX_BUFFER_SIZE 32 /* Must be a power of 2. */ +#define MUX_BUFFER_MASK (MUX_BUFFER_SIZE - 1) +typedef struct { + IOCanRWHandler *chr_can_read[MAX_MUX]; + IOReadHandler *chr_read[MAX_MUX]; + IOEventHandler *chr_event[MAX_MUX]; + void *ext_opaque[MAX_MUX]; + CharDriverState *drv; + unsigned char buffer[MUX_BUFFER_SIZE]; + int prod; + int cons; + int mux_cnt; + int term_got_escape; + int max_size; +} MuxDriver; + + +static int mux_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + MuxDriver *d = chr->opaque; + int ret; + if (!term_timestamps) { + ret = d->drv->chr_write(d->drv, buf, len); + } else { + int i; + + ret = 0; + for(i = 0; i < len; i++) { + ret += d->drv->chr_write(d->drv, buf+i, 1); + if (buf[i] == '\n') { + char buf1[64]; + int64_t ti; + int secs; + + ti = qemu_get_clock(rt_clock); + if (term_timestamps_start == -1) + term_timestamps_start = ti; + ti -= term_timestamps_start; + secs = ti / 1000000000; + snprintf(buf1, sizeof(buf1), + "[%02d:%02d:%02d.%03d] ", + secs / 3600, + (secs / 60) % 60, + secs % 60, + (int)((ti / 1000000) % 1000)); + d->drv->chr_write(d->drv, (uint8_t *)buf1, strlen(buf1)); + } + } + } + return ret; +} + +static const char * const mux_help[] = { + "% h print this help\n\r", + "% x exit emulator\n\r", + "% s save disk data back to file (if -snapshot)\n\r", + "% t toggle console timestamps\n\r" + "% b send break (magic sysrq)\n\r", + "% c switch between console and monitor\n\r", + "% % sends %\n\r", + NULL +}; + +int term_escape_char = 0x01; /* ctrl-a is used for escape */ +static void mux_print_help(CharDriverState *chr) +{ + int i, j; + char ebuf[15] = "Escape-Char"; + char cbuf[50] = "\n\r"; + + if (term_escape_char > 0 && term_escape_char < 26) { + snprintf(cbuf, sizeof(cbuf), "\n\r"); + snprintf(ebuf, sizeof(ebuf), "C-%c", term_escape_char - 1 + 'a'); + } else { + snprintf(cbuf, sizeof(cbuf), + "\n\rEscape-Char set to Ascii: 0x%02x\n\r\n\r", + term_escape_char); + } + chr->chr_write(chr, (uint8_t *)cbuf, strlen(cbuf)); + for (i = 0; mux_help[i] != NULL; i++) { + for (j=0; mux_help[i][j] != '\0'; j++) { + if (mux_help[i][j] == '%') + chr->chr_write(chr, (uint8_t *)ebuf, strlen(ebuf)); + else + chr->chr_write(chr, (uint8_t *)&mux_help[i][j], 1); + } + } +} + +static int mux_proc_byte(CharDriverState *chr, MuxDriver *d, int ch) +{ + if (d->term_got_escape) { + d->term_got_escape = 0; + if (ch == term_escape_char) + goto send_char; + switch(ch) { + case '?': + case 'h': + mux_print_help(chr); + break; + case 'x': + { + const char *term = "QEMU: Terminated\n\r"; + chr->chr_write(chr,(uint8_t *)term,strlen(term)); + exit(0); + break; + } + case 's': + { + int i; + for (i = 0; i < nb_drives; i++) { + bdrv_commit(drives_table[i].bdrv); + } + } + break; + case 'b': + qemu_chr_event(chr, CHR_EVENT_BREAK); + break; + case 'c': + /* Switch to the next registered device */ + chr->focus++; + if (chr->focus >= d->mux_cnt) + chr->focus = 0; + break; + case 't': + term_timestamps = !term_timestamps; + term_timestamps_start = -1; + break; + } + } else if (ch == term_escape_char) { + d->term_got_escape = 1; + } else { + send_char: + return 1; + } + return 0; +} + +static void mux_chr_accept_input(CharDriverState *chr) +{ + int m = chr->focus; + MuxDriver *d = chr->opaque; + + while (d->prod != d->cons && + d->chr_can_read[m] && + d->chr_can_read[m](d->ext_opaque[m])) { + d->chr_read[m](d->ext_opaque[m], + &d->buffer[d->cons++ & MUX_BUFFER_MASK], 1); + } +} + +static int mux_chr_can_read(void *opaque) +{ + CharDriverState *chr = opaque; + MuxDriver *d = chr->opaque; + + if ((d->prod - d->cons) < MUX_BUFFER_SIZE) + return 1; + if (d->chr_can_read[chr->focus]) + return d->chr_can_read[chr->focus](d->ext_opaque[chr->focus]); + return 0; +} + +static void mux_chr_read(void *opaque, const uint8_t *buf, int size) +{ + CharDriverState *chr = opaque; + MuxDriver *d = chr->opaque; + int m = chr->focus; + int i; + + mux_chr_accept_input (opaque); + + for(i = 0; i < size; i++) + if (mux_proc_byte(chr, d, buf[i])) { + if (d->prod == d->cons && + d->chr_can_read[m] && + d->chr_can_read[m](d->ext_opaque[m])) + d->chr_read[m](d->ext_opaque[m], &buf[i], 1); + else + d->buffer[d->prod++ & MUX_BUFFER_MASK] = buf[i]; + } +} + +static void mux_chr_event(void *opaque, int event) +{ + CharDriverState *chr = opaque; + MuxDriver *d = chr->opaque; + int i; + + /* Send the event to all registered listeners */ + for (i = 0; i < d->mux_cnt; i++) + if (d->chr_event[i]) + d->chr_event[i](d->ext_opaque[i], event); +} + +static void mux_chr_update_read_handler(CharDriverState *chr) +{ + MuxDriver *d = chr->opaque; + + if (d->mux_cnt >= MAX_MUX) { + fprintf(stderr, "Cannot add I/O handlers, MUX array is full\n"); + return; + } + d->ext_opaque[d->mux_cnt] = chr->handler_opaque; + d->chr_can_read[d->mux_cnt] = chr->chr_can_read; + d->chr_read[d->mux_cnt] = chr->chr_read; + d->chr_event[d->mux_cnt] = chr->chr_event; + /* Fix up the real driver with mux routines */ + if (d->mux_cnt == 0) { + qemu_chr_add_handlers(d->drv, mux_chr_can_read, mux_chr_read, + mux_chr_event, chr); + } + chr->focus = d->mux_cnt; + d->mux_cnt++; +} + +static CharDriverState *qemu_chr_open_mux(CharDriverState *drv) +{ + CharDriverState *chr; + MuxDriver *d; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + return NULL; + d = qemu_mallocz(sizeof(MuxDriver)); + if (!d) { + free(chr); + return NULL; + } + + chr->opaque = d; + d->drv = drv; + chr->focus = -1; + chr->chr_write = mux_chr_write; + chr->chr_update_read_handler = mux_chr_update_read_handler; + chr->chr_accept_input = mux_chr_accept_input; + return chr; +} + + +#ifdef _WIN32 +int send_all(int fd, const uint8_t *buf, int len1) +{ + int ret, len; + + len = len1; + while (len > 0) { + ret = send(fd, buf, len, 0); + if (ret < 0) { + int errno; + errno = WSAGetLastError(); + if (errno != WSAEWOULDBLOCK) { + return -1; + } + } else if (ret == 0) { + break; + } else { + buf += ret; + len -= ret; + } + } + return len1 - len; +} + +#else + +static int unix_write(int fd, const uint8_t *buf, int len1) +{ + int ret, len; + + len = len1; + while (len > 0) { + ret = write(fd, buf, len); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -1; + } else if (ret == 0) { + break; + } else { + buf += ret; + len -= ret; + } + } + return len1 - len; +} + +inline int send_all(int fd, const uint8_t *buf, int len1) +{ + return unix_write(fd, buf, len1); +} +#endif /* !_WIN32 */ + +#ifndef _WIN32 + +typedef struct { + int fd_in, fd_out; + int max_size; +} FDCharDriver; + +#define STDIO_MAX_CLIENTS 1 +static int stdio_nb_clients = 0; + +static int fd_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + FDCharDriver *s = chr->opaque; + return send_all(s->fd_out, buf, len); +} + +static int fd_chr_read_poll(void *opaque) +{ + CharDriverState *chr = opaque; + FDCharDriver *s = chr->opaque; + + s->max_size = qemu_chr_can_read(chr); + return s->max_size; +} + +static void fd_chr_read(void *opaque) +{ + CharDriverState *chr = opaque; + FDCharDriver *s = chr->opaque; + int size, len; + uint8_t buf[1024]; + + len = sizeof(buf); + if (len > s->max_size) + len = s->max_size; + if (len == 0) + return; + size = read(s->fd_in, buf, len); + if (size == 0) { + /* FD has been closed. Remove it from the active list. */ + qemu_set_fd_handler2(s->fd_in, NULL, NULL, NULL, NULL); + return; + } + if (size > 0) { + qemu_chr_read(chr, buf, size); + } +} + +static void fd_chr_update_read_handler(CharDriverState *chr) +{ + FDCharDriver *s = chr->opaque; + + if (s->fd_in >= 0) { + if (nographic && s->fd_in == 0) { + } else { + qemu_set_fd_handler2(s->fd_in, fd_chr_read_poll, + fd_chr_read, NULL, chr); + } + } +} + +static void fd_chr_close(struct CharDriverState *chr) +{ + FDCharDriver *s = chr->opaque; + + if (s->fd_in >= 0) { + if (nographic && s->fd_in == 0) { + } else { + qemu_set_fd_handler2(s->fd_in, NULL, NULL, NULL, NULL); + } + } + + qemu_free(s); +} + +/* open a character device to a unix fd */ +static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out) +{ + CharDriverState *chr; + FDCharDriver *s; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + return NULL; + s = qemu_mallocz(sizeof(FDCharDriver)); + if (!s) { + free(chr); + return NULL; + } + s->fd_in = fd_in; + s->fd_out = fd_out; + chr->opaque = s; + chr->chr_write = fd_chr_write; + chr->chr_update_read_handler = fd_chr_update_read_handler; + chr->chr_close = fd_chr_close; + + qemu_chr_reset(chr); + + return chr; +} + +static CharDriverState *qemu_chr_open_file_out(const char *file_out) +{ + int fd_out; + + TFR(fd_out = open(file_out, O_WRONLY | O_TRUNC | O_CREAT | O_BINARY, 0666)); + if (fd_out < 0) + return NULL; + return qemu_chr_open_fd(-1, fd_out); +} + +static CharDriverState *qemu_chr_open_pipe(const char *filename) +{ + int fd_in, fd_out; + char filename_in[256], filename_out[256]; + + snprintf(filename_in, 256, "%s.in", filename); + snprintf(filename_out, 256, "%s.out", filename); + TFR(fd_in = open(filename_in, O_RDWR | O_BINARY)); + TFR(fd_out = open(filename_out, O_RDWR | O_BINARY)); + if (fd_in < 0 || fd_out < 0) { + if (fd_in >= 0) + close(fd_in); + if (fd_out >= 0) + close(fd_out); + TFR(fd_in = fd_out = open(filename, O_RDWR | O_BINARY)); + if (fd_in < 0) + return NULL; + } + return qemu_chr_open_fd(fd_in, fd_out); +} + + +/* for STDIO, we handle the case where several clients use it + (nographic mode) */ + +#define TERM_FIFO_MAX_SIZE 1 + +static uint8_t term_fifo[TERM_FIFO_MAX_SIZE]; +static int term_fifo_size; + +static int stdio_read_poll(void *opaque) +{ + CharDriverState *chr = opaque; + + /* try to flush the queue if needed */ + if (term_fifo_size != 0 && qemu_chr_can_read(chr) > 0) { + qemu_chr_read(chr, term_fifo, 1); + term_fifo_size = 0; + } + /* see if we can absorb more chars */ + if (term_fifo_size == 0) + return 1; + else + return 0; +} + +static void stdio_read(void *opaque) +{ + int size; + uint8_t buf[1]; + CharDriverState *chr = opaque; + + size = read(0, buf, 1); + if (size == 0) { + /* stdin has been closed. Remove it from the active list. */ + qemu_set_fd_handler2(0, NULL, NULL, NULL, NULL); + return; + } + if (size > 0) { + if (qemu_chr_can_read(chr) > 0) { + qemu_chr_read(chr, buf, 1); + } else if (term_fifo_size == 0) { + term_fifo[term_fifo_size++] = buf[0]; + } + } +} + +/* init terminal so that we can grab keys */ +static struct termios oldtty; +static int old_fd0_flags; +static int term_atexit_done; + +static void term_exit(void) +{ + tcsetattr (0, TCSANOW, &oldtty); + fcntl(0, F_SETFL, old_fd0_flags); +} + +static void term_init(void) +{ + struct termios tty; + + tcgetattr (0, &tty); + oldtty = tty; + old_fd0_flags = fcntl(0, F_GETFL); + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + /* if graphical mode, we allow Ctrl-C handling */ + if (nographic) + tty.c_lflag &= ~ISIG; + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + + tcsetattr (0, TCSANOW, &tty); + + if (!term_atexit_done++) + atexit(term_exit); + + fcntl(0, F_SETFL, O_NONBLOCK); +} + +static void qemu_chr_close_stdio(struct CharDriverState *chr) +{ + term_exit(); + stdio_nb_clients--; + qemu_set_fd_handler2(0, NULL, NULL, NULL, NULL); + fd_chr_close(chr); +} + +static CharDriverState *qemu_chr_open_stdio(void) +{ + CharDriverState *chr; + + if (stdio_nb_clients >= STDIO_MAX_CLIENTS) + return NULL; + chr = qemu_chr_open_fd(0, 1); + chr->chr_close = qemu_chr_close_stdio; + qemu_set_fd_handler2(0, stdio_read_poll, stdio_read, NULL, chr); + stdio_nb_clients++; + term_init(); + + return chr; +} + +#ifdef __sun__ +/* Once Solaris has openpty(), this is going to be removed. */ +int openpty(int *amaster, int *aslave, char *name, + struct termios *termp, struct winsize *winp) +{ + const char *slave; + int mfd = -1, sfd = -1; + + *amaster = *aslave = -1; + + mfd = open("/dev/ptmx", O_RDWR | O_NOCTTY); + if (mfd < 0) + goto err; + + if (grantpt(mfd) == -1 || unlockpt(mfd) == -1) + goto err; + + if ((slave = ptsname(mfd)) == NULL) + goto err; + + if ((sfd = open(slave, O_RDONLY | O_NOCTTY)) == -1) + goto err; + + if (ioctl(sfd, I_PUSH, "ptem") == -1 || + (termp != NULL && tcgetattr(sfd, termp) < 0)) + goto err; + + if (amaster) + *amaster = mfd; + if (aslave) + *aslave = sfd; + if (winp) + ioctl(sfd, TIOCSWINSZ, winp); + + return 0; + +err: + if (sfd != -1) + close(sfd); + close(mfd); + return -1; +} + +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; + + termios_p->c_cc[VMIN] = 0; + termios_p->c_cc[VTIME] = 0; +} +#endif + +#if defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) + +typedef struct { + int fd; + int connected; + int polling; + int read_bytes; + QEMUTimer *timer; +} PtyCharDriver; + +static void pty_chr_update_read_handler(CharDriverState *chr); +static void pty_chr_state(CharDriverState *chr, int connected); + +static int pty_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + PtyCharDriver *s = chr->opaque; + + if (!s->connected) { + /* guest sends data, check for (re-)connect */ + pty_chr_update_read_handler(chr); + return 0; + } + return send_all(s->fd, buf, len); +} + +static int pty_chr_read_poll(void *opaque) +{ + CharDriverState *chr = opaque; + PtyCharDriver *s = chr->opaque; + + s->read_bytes = qemu_chr_can_read(chr); + return s->read_bytes; +} + +static void pty_chr_read(void *opaque) +{ + CharDriverState *chr = opaque; + PtyCharDriver *s = chr->opaque; + int size, len; + uint8_t buf[1024]; + + len = sizeof(buf); + if (len > s->read_bytes) + len = s->read_bytes; + if (len == 0) + return; + size = read(s->fd, buf, len); + if ((size == -1 && errno == EIO) || + (size == 0)) { + pty_chr_state(chr, 0); + return; + } + if (size > 0) { + pty_chr_state(chr, 1); + qemu_chr_read(chr, buf, size); + } +} + +static void pty_chr_update_read_handler(CharDriverState *chr) +{ + PtyCharDriver *s = chr->opaque; + + qemu_set_fd_handler2(s->fd, pty_chr_read_poll, + pty_chr_read, NULL, chr); + s->polling = 1; + /* + * Short timeout here: just need wait long enougth that qemu makes + * it through the poll loop once. When reconnected we want a + * short timeout so we notice it almost instantly. Otherwise + * read() gives us -EIO instantly, making pty_chr_state() reset the + * timeout to the normal (much longer) poll interval before the + * timer triggers. + */ + qemu_mod_timer(s->timer, qemu_get_clock(rt_clock) + 10); +} + +static void pty_chr_state(CharDriverState *chr, int connected) +{ + PtyCharDriver *s = chr->opaque; + + if (!connected) { + qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); + s->connected = 0; + s->polling = 0; + /* (re-)connect poll interval for idle guests: once per second. + * We check more frequently in case the guests sends data to + * the virtual device linked to our pty. */ + qemu_mod_timer(s->timer, qemu_get_clock(rt_clock) + 1000); + } else { + if (!s->connected) + qemu_chr_reset(chr); + s->connected = 1; + } +} + +static void pty_chr_timer(void *opaque) +{ + struct CharDriverState *chr = opaque; + PtyCharDriver *s = chr->opaque; + + if (s->connected) + return; + if (s->polling) { + /* If we arrive here without polling being cleared due + * read returning -EIO, then we are (re-)connected */ + pty_chr_state(chr, 1); + return; + } + + /* Next poll ... */ + pty_chr_update_read_handler(chr); +} + +static void pty_chr_close(struct CharDriverState *chr) +{ + PtyCharDriver *s = chr->opaque; + + qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); + close(s->fd); + qemu_free(s); +} + +static CharDriverState *qemu_chr_open_pty(void) +{ + CharDriverState *chr; + PtyCharDriver *s; + struct termios tty; + int slave_fd, len; +#if defined(__OpenBSD__) + char pty_name[PATH_MAX]; +#define q_ptsname(x) pty_name +#else + char *pty_name = NULL; +#define q_ptsname(x) ptsname(x) +#endif + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + return NULL; + s = qemu_mallocz(sizeof(PtyCharDriver)); + if (!s) { + qemu_free(chr); + return NULL; + } + + if (openpty(&s->fd, &slave_fd, pty_name, NULL, NULL) < 0) { + return NULL; + } + + /* Set raw attributes on the pty. */ + cfmakeraw(&tty); + tcsetattr(slave_fd, TCSAFLUSH, &tty); + close(slave_fd); + + len = strlen(q_ptsname(s->fd)) + 5; + chr->filename = qemu_malloc(len); + snprintf(chr->filename, len, "pty:%s", q_ptsname(s->fd)); + fprintf(stderr, "char device redirected to %s\n", q_ptsname(s->fd)); + + chr->opaque = s; + chr->chr_write = pty_chr_write; + chr->chr_update_read_handler = pty_chr_update_read_handler; + chr->chr_close = pty_chr_close; + + s->timer = qemu_new_timer(rt_clock, pty_chr_timer, chr); + + return chr; +} + +static void tty_serial_init(int fd, int speed, + int parity, int data_bits, int stop_bits) +{ + struct termios tty; + speed_t spd; + +#if 0 + printf("tty_serial_init: speed=%d parity=%c data=%d stop=%d\n", + speed, parity, data_bits, stop_bits); +#endif + tcgetattr (fd, &tty); + +#define MARGIN 1.1 + if (speed <= 50 * MARGIN) + spd = B50; + else if (speed <= 75 * MARGIN) + spd = B75; + else if (speed <= 300 * MARGIN) + spd = B300; + else if (speed <= 600 * MARGIN) + spd = B600; + else if (speed <= 1200 * MARGIN) + spd = B1200; + else if (speed <= 2400 * MARGIN) + spd = B2400; + else if (speed <= 4800 * MARGIN) + spd = B4800; + else if (speed <= 9600 * MARGIN) + spd = B9600; + else if (speed <= 19200 * MARGIN) + spd = B19200; + else if (speed <= 38400 * MARGIN) + spd = B38400; + else if (speed <= 57600 * MARGIN) + spd = B57600; + else if (speed <= 115200 * MARGIN) + spd = B115200; + else + spd = B115200; + + cfsetispeed(&tty, spd); + cfsetospeed(&tty, spd); + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN|ISIG); + tty.c_cflag &= ~(CSIZE|PARENB|PARODD|CRTSCTS|CSTOPB); + switch(data_bits) { + default: + case 8: + tty.c_cflag |= CS8; + break; + case 7: + tty.c_cflag |= CS7; + break; + case 6: + tty.c_cflag |= CS6; + break; + case 5: + tty.c_cflag |= CS5; + break; + } + switch(parity) { + default: + case 'N': + break; + case 'E': + tty.c_cflag |= PARENB; + break; + case 'O': + tty.c_cflag |= PARENB | PARODD; + break; + } + if (stop_bits == 2) + tty.c_cflag |= CSTOPB; + + tcsetattr (fd, TCSANOW, &tty); +} + +static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg) +{ + FDCharDriver *s = chr->opaque; + + switch(cmd) { + case CHR_IOCTL_SERIAL_SET_PARAMS: + { + QEMUSerialSetParams *ssp = arg; + tty_serial_init(s->fd_in, ssp->speed, ssp->parity, + ssp->data_bits, ssp->stop_bits); + } + break; + case CHR_IOCTL_SERIAL_SET_BREAK: + { + int enable = *(int *)arg; + if (enable) + tcsendbreak(s->fd_in, 1); + } + break; + case CHR_IOCTL_SERIAL_GET_TIOCM: + { + int sarg = 0; + int *targ = (int *)arg; + ioctl(s->fd_in, TIOCMGET, &sarg); + *targ = 0; + if (sarg | TIOCM_CTS) + *targ |= CHR_TIOCM_CTS; + if (sarg | TIOCM_CAR) + *targ |= CHR_TIOCM_CAR; + if (sarg | TIOCM_DSR) + *targ |= CHR_TIOCM_DSR; + if (sarg | TIOCM_RI) + *targ |= CHR_TIOCM_RI; + if (sarg | TIOCM_DTR) + *targ |= CHR_TIOCM_DTR; + if (sarg | TIOCM_RTS) + *targ |= CHR_TIOCM_RTS; + } + break; + case CHR_IOCTL_SERIAL_SET_TIOCM: + { + int sarg = *(int *)arg; + int targ = 0; + if (sarg | CHR_TIOCM_DTR) + targ |= TIOCM_DTR; + if (sarg | CHR_TIOCM_RTS) + targ |= TIOCM_RTS; + ioctl(s->fd_in, TIOCMSET, &targ); + } + break; + default: + return -ENOTSUP; + } + return 0; +} + +static CharDriverState *qemu_chr_open_tty(const char *filename) +{ + CharDriverState *chr; + int fd; + + TFR(fd = open(filename, O_RDWR | O_NONBLOCK)); + tty_serial_init(fd, 115200, 'N', 8, 1); + chr = qemu_chr_open_fd(fd, fd); + if (!chr) { + close(fd); + return NULL; + } + chr->chr_ioctl = tty_serial_ioctl; + qemu_chr_reset(chr); + return chr; +} +#else /* ! __linux__ && ! __sun__ */ +static CharDriverState *qemu_chr_open_pty(void) +{ + return NULL; +} +#endif /* __linux__ || __sun__ */ + +#if defined(__linux__) +typedef struct { + int fd; + int mode; +} ParallelCharDriver; + +static int pp_hw_mode(ParallelCharDriver *s, uint16_t mode) +{ + if (s->mode != mode) { + int m = mode; + if (ioctl(s->fd, PPSETMODE, &m) < 0) + return 0; + s->mode = mode; + } + return 1; +} + +static int pp_ioctl(CharDriverState *chr, int cmd, void *arg) +{ + ParallelCharDriver *drv = chr->opaque; + int fd = drv->fd; + uint8_t b; + + switch(cmd) { + case CHR_IOCTL_PP_READ_DATA: + if (ioctl(fd, PPRDATA, &b) < 0) + return -ENOTSUP; + *(uint8_t *)arg = b; + break; + case CHR_IOCTL_PP_WRITE_DATA: + b = *(uint8_t *)arg; + if (ioctl(fd, PPWDATA, &b) < 0) + return -ENOTSUP; + break; + case CHR_IOCTL_PP_READ_CONTROL: + if (ioctl(fd, PPRCONTROL, &b) < 0) + return -ENOTSUP; + /* Linux gives only the lowest bits, and no way to know data + direction! For better compatibility set the fixed upper + bits. */ + *(uint8_t *)arg = b | 0xc0; + break; + case CHR_IOCTL_PP_WRITE_CONTROL: + b = *(uint8_t *)arg; + if (ioctl(fd, PPWCONTROL, &b) < 0) + return -ENOTSUP; + break; + case CHR_IOCTL_PP_READ_STATUS: + if (ioctl(fd, PPRSTATUS, &b) < 0) + return -ENOTSUP; + *(uint8_t *)arg = b; + break; + case CHR_IOCTL_PP_DATA_DIR: + if (ioctl(fd, PPDATADIR, (int *)arg) < 0) + return -ENOTSUP; + break; + case CHR_IOCTL_PP_EPP_READ_ADDR: + if (pp_hw_mode(drv, IEEE1284_MODE_EPP|IEEE1284_ADDR)) { + struct ParallelIOArg *parg = arg; + int n = read(fd, parg->buffer, parg->count); + if (n != parg->count) { + return -EIO; + } + } + break; + case CHR_IOCTL_PP_EPP_READ: + if (pp_hw_mode(drv, IEEE1284_MODE_EPP)) { + struct ParallelIOArg *parg = arg; + int n = read(fd, parg->buffer, parg->count); + if (n != parg->count) { + return -EIO; + } + } + break; + case CHR_IOCTL_PP_EPP_WRITE_ADDR: + if (pp_hw_mode(drv, IEEE1284_MODE_EPP|IEEE1284_ADDR)) { + struct ParallelIOArg *parg = arg; + int n = write(fd, parg->buffer, parg->count); + if (n != parg->count) { + return -EIO; + } + } + break; + case CHR_IOCTL_PP_EPP_WRITE: + if (pp_hw_mode(drv, IEEE1284_MODE_EPP)) { + struct ParallelIOArg *parg = arg; + int n = write(fd, parg->buffer, parg->count); + if (n != parg->count) { + return -EIO; + } + } + break; + default: + return -ENOTSUP; + } + return 0; +} + +static void pp_close(CharDriverState *chr) +{ + ParallelCharDriver *drv = chr->opaque; + int fd = drv->fd; + + pp_hw_mode(drv, IEEE1284_MODE_COMPAT); + ioctl(fd, PPRELEASE); + close(fd); + qemu_free(drv); +} + +static CharDriverState *qemu_chr_open_pp(const char *filename) +{ + CharDriverState *chr; + ParallelCharDriver *drv; + int fd; + + TFR(fd = open(filename, O_RDWR)); + if (fd < 0) + return NULL; + + if (ioctl(fd, PPCLAIM) < 0) { + close(fd); + return NULL; + } + + drv = qemu_mallocz(sizeof(ParallelCharDriver)); + if (!drv) { + close(fd); + return NULL; + } + drv->fd = fd; + drv->mode = IEEE1284_MODE_COMPAT; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) { + qemu_free(drv); + close(fd); + return NULL; + } + chr->chr_write = null_chr_write; + chr->chr_ioctl = pp_ioctl; + chr->chr_close = pp_close; + chr->opaque = drv; + + qemu_chr_reset(chr); + + return chr; +} +#endif /* __linux__ */ + +#else /* _WIN32 */ + +typedef struct { + int max_size; + HANDLE hcom, hrecv, hsend; + OVERLAPPED orecv, osend; + BOOL fpipe; + DWORD len; +} WinCharState; + +#define NSENDBUF 2048 +#define NRECVBUF 2048 +#define MAXCONNECT 1 +#define NTIMEOUT 5000 + +static int win_chr_poll(void *opaque); +static int win_chr_pipe_poll(void *opaque); + +static void win_chr_close(CharDriverState *chr) +{ + WinCharState *s = chr->opaque; + + if (s->hsend) { + CloseHandle(s->hsend); + s->hsend = NULL; + } + if (s->hrecv) { + CloseHandle(s->hrecv); + s->hrecv = NULL; + } + if (s->hcom) { + CloseHandle(s->hcom); + s->hcom = NULL; + } + if (s->fpipe) + qemu_del_polling_cb(win_chr_pipe_poll, chr); + else + qemu_del_polling_cb(win_chr_poll, chr); +} + +static int win_chr_init(CharDriverState *chr, const char *filename) +{ + WinCharState *s = chr->opaque; + COMMCONFIG comcfg; + COMMTIMEOUTS cto = { 0, 0, 0, 0, 0}; + COMSTAT comstat; + DWORD size; + DWORD err; + + s->hsend = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!s->hsend) { + fprintf(stderr, "Failed CreateEvent\n"); + goto fail; + } + s->hrecv = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!s->hrecv) { + fprintf(stderr, "Failed CreateEvent\n"); + goto fail; + } + + s->hcom = CreateFile(filename, GENERIC_READ|GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); + if (s->hcom == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Failed CreateFile (%lu)\n", GetLastError()); + s->hcom = NULL; + goto fail; + } + + if (!SetupComm(s->hcom, NRECVBUF, NSENDBUF)) { + fprintf(stderr, "Failed SetupComm\n"); + goto fail; + } + + ZeroMemory(&comcfg, sizeof(COMMCONFIG)); + size = sizeof(COMMCONFIG); + GetDefaultCommConfig(filename, &comcfg, &size); + comcfg.dcb.DCBlength = sizeof(DCB); + CommConfigDialog(filename, NULL, &comcfg); + + if (!SetCommState(s->hcom, &comcfg.dcb)) { + fprintf(stderr, "Failed SetCommState\n"); + goto fail; + } + + if (!SetCommMask(s->hcom, EV_ERR)) { + fprintf(stderr, "Failed SetCommMask\n"); + goto fail; + } + + cto.ReadIntervalTimeout = MAXDWORD; + if (!SetCommTimeouts(s->hcom, &cto)) { + fprintf(stderr, "Failed SetCommTimeouts\n"); + goto fail; + } + + if (!ClearCommError(s->hcom, &err, &comstat)) { + fprintf(stderr, "Failed ClearCommError\n"); + goto fail; + } + qemu_add_polling_cb(win_chr_poll, chr); + return 0; + + fail: + win_chr_close(chr); + return -1; +} + +static int win_chr_write(CharDriverState *chr, const uint8_t *buf, int len1) +{ + WinCharState *s = chr->opaque; + DWORD len, ret, size, err; + + len = len1; + ZeroMemory(&s->osend, sizeof(s->osend)); + s->osend.hEvent = s->hsend; + while (len > 0) { + if (s->hsend) + ret = WriteFile(s->hcom, buf, len, &size, &s->osend); + else + ret = WriteFile(s->hcom, buf, len, &size, NULL); + if (!ret) { + err = GetLastError(); + if (err == ERROR_IO_PENDING) { + ret = GetOverlappedResult(s->hcom, &s->osend, &size, TRUE); + if (ret) { + buf += size; + len -= size; + } else { + break; + } + } else { + break; + } + } else { + buf += size; + len -= size; + } + } + return len1 - len; +} + +static int win_chr_read_poll(CharDriverState *chr) +{ + WinCharState *s = chr->opaque; + + s->max_size = qemu_chr_can_read(chr); + return s->max_size; +} + +static void win_chr_readfile(CharDriverState *chr) +{ + WinCharState *s = chr->opaque; + int ret, err; + uint8_t buf[1024]; + DWORD size; + + ZeroMemory(&s->orecv, sizeof(s->orecv)); + s->orecv.hEvent = s->hrecv; + ret = ReadFile(s->hcom, buf, s->len, &size, &s->orecv); + if (!ret) { + err = GetLastError(); + if (err == ERROR_IO_PENDING) { + ret = GetOverlappedResult(s->hcom, &s->orecv, &size, TRUE); + } + } + + if (size > 0) { + qemu_chr_read(chr, buf, size); + } +} + +static void win_chr_read(CharDriverState *chr) +{ + WinCharState *s = chr->opaque; + + if (s->len > s->max_size) + s->len = s->max_size; + if (s->len == 0) + return; + + win_chr_readfile(chr); +} + +static int win_chr_poll(void *opaque) +{ + CharDriverState *chr = opaque; + WinCharState *s = chr->opaque; + COMSTAT status; + DWORD comerr; + + ClearCommError(s->hcom, &comerr, &status); + if (status.cbInQue > 0) { + s->len = status.cbInQue; + win_chr_read_poll(chr); + win_chr_read(chr); + return 1; + } + return 0; +} + +static CharDriverState *qemu_chr_open_win(const char *filename) +{ + CharDriverState *chr; + WinCharState *s; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + return NULL; + s = qemu_mallocz(sizeof(WinCharState)); + if (!s) { + free(chr); + return NULL; + } + chr->opaque = s; + chr->chr_write = win_chr_write; + chr->chr_close = win_chr_close; + + if (win_chr_init(chr, filename) < 0) { + free(s); + free(chr); + return NULL; + } + qemu_chr_reset(chr); + return chr; +} + +static int win_chr_pipe_poll(void *opaque) +{ + CharDriverState *chr = opaque; + WinCharState *s = chr->opaque; + DWORD size; + + PeekNamedPipe(s->hcom, NULL, 0, NULL, &size, NULL); + if (size > 0) { + s->len = size; + win_chr_read_poll(chr); + win_chr_read(chr); + return 1; + } + return 0; +} + +static int win_chr_pipe_init(CharDriverState *chr, const char *filename) +{ + WinCharState *s = chr->opaque; + OVERLAPPED ov; + int ret; + DWORD size; + char openname[256]; + + s->fpipe = TRUE; + + s->hsend = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!s->hsend) { + fprintf(stderr, "Failed CreateEvent\n"); + goto fail; + } + s->hrecv = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!s->hrecv) { + fprintf(stderr, "Failed CreateEvent\n"); + goto fail; + } + + snprintf(openname, sizeof(openname), "\\\\.\\pipe\\%s", filename); + s->hcom = CreateNamedPipe(openname, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | + PIPE_WAIT, + MAXCONNECT, NSENDBUF, NRECVBUF, NTIMEOUT, NULL); + if (s->hcom == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Failed CreateNamedPipe (%lu)\n", GetLastError()); + s->hcom = NULL; + goto fail; + } + + ZeroMemory(&ov, sizeof(ov)); + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + ret = ConnectNamedPipe(s->hcom, &ov); + if (ret) { + fprintf(stderr, "Failed ConnectNamedPipe\n"); + goto fail; + } + + ret = GetOverlappedResult(s->hcom, &ov, &size, TRUE); + if (!ret) { + fprintf(stderr, "Failed GetOverlappedResult\n"); + if (ov.hEvent) { + CloseHandle(ov.hEvent); + ov.hEvent = NULL; + } + goto fail; + } + + if (ov.hEvent) { + CloseHandle(ov.hEvent); + ov.hEvent = NULL; + } + qemu_add_polling_cb(win_chr_pipe_poll, chr); + return 0; + + fail: + win_chr_close(chr); + return -1; +} + + +static CharDriverState *qemu_chr_open_win_pipe(const char *filename) +{ + CharDriverState *chr; + WinCharState *s; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + return NULL; + s = qemu_mallocz(sizeof(WinCharState)); + if (!s) { + free(chr); + return NULL; + } + chr->opaque = s; + chr->chr_write = win_chr_write; + chr->chr_close = win_chr_close; + + if (win_chr_pipe_init(chr, filename) < 0) { + free(s); + free(chr); + return NULL; + } + qemu_chr_reset(chr); + return chr; +} + +static CharDriverState *qemu_chr_open_win_file(HANDLE fd_out) +{ + CharDriverState *chr; + WinCharState *s; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + return NULL; + s = qemu_mallocz(sizeof(WinCharState)); + if (!s) { + free(chr); + return NULL; + } + s->hcom = fd_out; + chr->opaque = s; + chr->chr_write = win_chr_write; + qemu_chr_reset(chr); + return chr; +} + +static CharDriverState *qemu_chr_open_win_con(const char *filename) +{ + return qemu_chr_open_win_file(GetStdHandle(STD_OUTPUT_HANDLE)); +} + +static CharDriverState *qemu_chr_open_win_file_out(const char *file_out) +{ + HANDLE fd_out; + + fd_out = CreateFile(file_out, GENERIC_WRITE, FILE_SHARE_READ, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (fd_out == INVALID_HANDLE_VALUE) + return NULL; + + return qemu_chr_open_win_file(fd_out); +} +#endif /* !_WIN32 */ + +/***********************************************************/ +/* UDP Net console */ + +typedef struct { + int fd; + struct sockaddr_in daddr; + uint8_t buf[1024]; + int bufcnt; + int bufptr; + int max_size; +} NetCharDriver; + +static int udp_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + NetCharDriver *s = chr->opaque; + + return sendto(s->fd, buf, len, 0, + (struct sockaddr *)&s->daddr, sizeof(struct sockaddr_in)); +} + +static int udp_chr_read_poll(void *opaque) +{ + CharDriverState *chr = opaque; + NetCharDriver *s = chr->opaque; + + s->max_size = qemu_chr_can_read(chr); + + /* If there were any stray characters in the queue process them + * first + */ + while (s->max_size > 0 && s->bufptr < s->bufcnt) { + qemu_chr_read(chr, &s->buf[s->bufptr], 1); + s->bufptr++; + s->max_size = qemu_chr_can_read(chr); + } + return s->max_size; +} + +static void udp_chr_read(void *opaque) +{ + CharDriverState *chr = opaque; + NetCharDriver *s = chr->opaque; + + if (s->max_size == 0) + return; + s->bufcnt = recv(s->fd, s->buf, sizeof(s->buf), 0); + s->bufptr = s->bufcnt; + if (s->bufcnt <= 0) + return; + + s->bufptr = 0; + while (s->max_size > 0 && s->bufptr < s->bufcnt) { + qemu_chr_read(chr, &s->buf[s->bufptr], 1); + s->bufptr++; + s->max_size = qemu_chr_can_read(chr); + } +} + +static void udp_chr_update_read_handler(CharDriverState *chr) +{ + NetCharDriver *s = chr->opaque; + + if (s->fd >= 0) { + qemu_set_fd_handler2(s->fd, udp_chr_read_poll, + udp_chr_read, NULL, chr); + } +} + +static CharDriverState *qemu_chr_open_udp(const char *def) +{ + CharDriverState *chr = NULL; + NetCharDriver *s = NULL; + int fd = -1; + struct sockaddr_in saddr; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + goto return_err; + s = qemu_mallocz(sizeof(NetCharDriver)); + if (!s) + goto return_err; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + perror("socket(PF_INET, SOCK_DGRAM)"); + goto return_err; + } + + if (parse_host_src_port(&s->daddr, &saddr, def) < 0) { + printf("Could not parse: %s\n", def); + goto return_err; + } + + if (bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) + { + perror("bind"); + goto return_err; + } + + s->fd = fd; + s->bufcnt = 0; + s->bufptr = 0; + chr->opaque = s; + chr->chr_write = udp_chr_write; + chr->chr_update_read_handler = udp_chr_update_read_handler; + return chr; + +return_err: + if (chr) + free(chr); + if (s) + free(s); + if (fd >= 0) + closesocket(fd); + return NULL; +} + +/***********************************************************/ +/* TCP Net console */ + +typedef struct { + int fd, listen_fd; + int connected; + int max_size; + int do_telnetopt; + int do_nodelay; + int is_unix; +} TCPCharDriver; + +static void tcp_chr_accept(void *opaque); + +static int tcp_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + TCPCharDriver *s = chr->opaque; + if (s->connected) { + return send_all(s->fd, buf, len); + } else { + /* XXX: indicate an error ? */ + return len; + } +} + +static int tcp_chr_read_poll(void *opaque) +{ + CharDriverState *chr = opaque; + TCPCharDriver *s = chr->opaque; + if (!s->connected) + return 0; + s->max_size = qemu_chr_can_read(chr); + return s->max_size; +} + +#define IAC 255 +#define IAC_BREAK 243 +static void tcp_chr_process_IAC_bytes(CharDriverState *chr, + TCPCharDriver *s, + uint8_t *buf, int *size) +{ + /* Handle any telnet client's basic IAC options to satisfy char by + * char mode with no echo. All IAC options will be removed from + * the buf and the do_telnetopt variable will be used to track the + * state of the width of the IAC information. + * + * IAC commands come in sets of 3 bytes with the exception of the + * "IAC BREAK" command and the double IAC. + */ + + int i; + int j = 0; + + for (i = 0; i < *size; i++) { + if (s->do_telnetopt > 1) { + if ((unsigned char)buf[i] == IAC && s->do_telnetopt == 2) { + /* Double IAC means send an IAC */ + if (j != i) + buf[j] = buf[i]; + j++; + s->do_telnetopt = 1; + } else { + if ((unsigned char)buf[i] == IAC_BREAK && s->do_telnetopt == 2) { + /* Handle IAC break commands by sending a serial break */ + qemu_chr_event(chr, CHR_EVENT_BREAK); + s->do_telnetopt++; + } + s->do_telnetopt++; + } + if (s->do_telnetopt >= 4) { + s->do_telnetopt = 1; + } + } else { + if ((unsigned char)buf[i] == IAC) { + s->do_telnetopt = 2; + } else { + if (j != i) + buf[j] = buf[i]; + j++; + } + } + } + *size = j; +} + +static void tcp_chr_read(void *opaque) +{ + CharDriverState *chr = opaque; + TCPCharDriver *s = chr->opaque; + uint8_t buf[1024]; + int len, size; + + if (!s->connected || s->max_size <= 0) + return; + len = sizeof(buf); + if (len > s->max_size) + len = s->max_size; + size = recv(s->fd, buf, len, 0); + if (size == 0) { + /* connection closed */ + s->connected = 0; + if (s->listen_fd >= 0) { + qemu_set_fd_handler(s->listen_fd, tcp_chr_accept, NULL, chr); + } + qemu_set_fd_handler(s->fd, NULL, NULL, NULL); + closesocket(s->fd); + s->fd = -1; + } else if (size > 0) { + if (s->do_telnetopt) + tcp_chr_process_IAC_bytes(chr, s, buf, &size); + if (size > 0) + qemu_chr_read(chr, buf, size); + } +} + +static void tcp_chr_connect(void *opaque) +{ + CharDriverState *chr = opaque; + TCPCharDriver *s = chr->opaque; + + s->connected = 1; + qemu_set_fd_handler2(s->fd, tcp_chr_read_poll, + tcp_chr_read, NULL, chr); + qemu_chr_reset(chr); +} + +#define IACSET(x,a,b,c) x[0] = a; x[1] = b; x[2] = c; +static void tcp_chr_telnet_init(int fd) +{ + char buf[3]; + /* Send the telnet negotion to put telnet in binary, no echo, single char mode */ + IACSET(buf, 0xff, 0xfb, 0x01); /* IAC WILL ECHO */ + send(fd, (char *)buf, 3, 0); + IACSET(buf, 0xff, 0xfb, 0x03); /* IAC WILL Suppress go ahead */ + send(fd, (char *)buf, 3, 0); + IACSET(buf, 0xff, 0xfb, 0x00); /* IAC WILL Binary */ + send(fd, (char *)buf, 3, 0); + IACSET(buf, 0xff, 0xfd, 0x00); /* IAC DO Binary */ + send(fd, (char *)buf, 3, 0); +} + +static void socket_set_nodelay(int fd) +{ + int val = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); +} + +static void tcp_chr_accept(void *opaque) +{ + CharDriverState *chr = opaque; + TCPCharDriver *s = chr->opaque; + struct sockaddr_in saddr; +#ifndef _WIN32 + struct sockaddr_un uaddr; +#endif + struct sockaddr *addr; + socklen_t len; + int fd; + + for(;;) { +#ifndef _WIN32 + if (s->is_unix) { + len = sizeof(uaddr); + addr = (struct sockaddr *)&uaddr; + } else +#endif + { + len = sizeof(saddr); + addr = (struct sockaddr *)&saddr; + } + fd = accept(s->listen_fd, addr, &len); + if (fd < 0 && errno != EINTR) { + return; + } else if (fd >= 0) { + if (s->do_telnetopt) + tcp_chr_telnet_init(fd); + break; + } + } + socket_set_nonblock(fd); + if (s->do_nodelay) + socket_set_nodelay(fd); + s->fd = fd; + qemu_set_fd_handler(s->listen_fd, NULL, NULL, NULL); + tcp_chr_connect(chr); +} + +static void tcp_chr_close(CharDriverState *chr) +{ + TCPCharDriver *s = chr->opaque; + if (s->fd >= 0) + closesocket(s->fd); + if (s->listen_fd >= 0) + closesocket(s->listen_fd); + qemu_free(s); +} + +static CharDriverState *qemu_chr_open_tcp(const char *host_str, + int is_telnet, + int is_unix) +{ + CharDriverState *chr = NULL; + TCPCharDriver *s = NULL; + int fd = -1, ret, err, val; + int is_listen = 0; + int is_waitconnect = 1; + int do_nodelay = 0; + const char *ptr; + struct sockaddr_in saddr; +#ifndef _WIN32 + struct sockaddr_un uaddr; +#endif + struct sockaddr *addr; + socklen_t addrlen; + +#ifndef _WIN32 + if (is_unix) { + addr = (struct sockaddr *)&uaddr; + addrlen = sizeof(uaddr); + if (parse_unix_path(&uaddr, host_str) < 0) + goto fail; + } else +#endif + { + addr = (struct sockaddr *)&saddr; + addrlen = sizeof(saddr); + if (parse_host_port(&saddr, host_str) < 0) + goto fail; + } + + ptr = host_str; + while((ptr = strchr(ptr,','))) { + ptr++; + if (!strncmp(ptr,"server",6)) { + is_listen = 1; + } else if (!strncmp(ptr,"nowait",6)) { + is_waitconnect = 0; + } else if (!strncmp(ptr,"nodelay",6)) { + do_nodelay = 1; + } else { + printf("Unknown option: %s\n", ptr); + goto fail; + } + } + if (!is_listen) + is_waitconnect = 0; + + chr = qemu_mallocz(sizeof(CharDriverState)); + if (!chr) + goto fail; + s = qemu_mallocz(sizeof(TCPCharDriver)); + if (!s) + goto fail; + +#ifndef _WIN32 + if (is_unix) + fd = socket(PF_UNIX, SOCK_STREAM, 0); + else +#endif + fd = socket(PF_INET, SOCK_STREAM, 0); + + if (fd < 0) + goto fail; + + if (!is_waitconnect) + socket_set_nonblock(fd); + + s->connected = 0; + s->fd = -1; + s->listen_fd = -1; + s->is_unix = is_unix; + s->do_nodelay = do_nodelay && !is_unix; + + chr->opaque = s; + chr->chr_write = tcp_chr_write; + chr->chr_close = tcp_chr_close; + + if (is_listen) { + /* allow fast reuse */ +#ifndef _WIN32 + if (is_unix) { + char path[109]; + pstrcpy(path, sizeof(path), uaddr.sun_path); + unlink(path); + } else +#endif + { + val = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&val, sizeof(val)); + } + + ret = bind(fd, addr, addrlen); + if (ret < 0) + goto fail; + + ret = listen(fd, 0); + if (ret < 0) + goto fail; + + s->listen_fd = fd; + qemu_set_fd_handler(s->listen_fd, tcp_chr_accept, NULL, chr); + if (is_telnet) + s->do_telnetopt = 1; + } else { + for(;;) { + ret = connect(fd, addr, addrlen); + if (ret < 0) { + err = socket_error(); + if (err == EINTR || err == EWOULDBLOCK) { + } else if (err == EINPROGRESS) { + break; +#ifdef _WIN32 + } else if (err == WSAEALREADY) { + break; +#endif + } else { + goto fail; + } + } else { + s->connected = 1; + break; + } + } + s->fd = fd; + socket_set_nodelay(fd); + if (s->connected) + tcp_chr_connect(chr); + else + qemu_set_fd_handler(s->fd, NULL, tcp_chr_connect, chr); + } + + if (is_listen && is_waitconnect) { + printf("QEMU waiting for connection on: %s\n", host_str); + tcp_chr_accept(chr); + socket_set_nonblock(s->listen_fd); + } + + return chr; + fail: + if (fd >= 0) + closesocket(fd); + qemu_free(s); + qemu_free(chr); + return NULL; +} + +static TAILQ_HEAD(CharDriverStateHead, CharDriverState) chardevs += TAILQ_HEAD_INITIALIZER(chardevs); + +CharDriverState *qemu_chr_open(const char *label, const char *filename) +{ + const char *p; + CharDriverState *chr; + + if (!strcmp(filename, "vc")) { + chr = text_console_init(&display_state, 0); + } else + if (strstart(filename, "vc:", &p)) { + chr = text_console_init(&display_state, p); + } else + if (!strcmp(filename, "null")) { + chr = qemu_chr_open_null(); + } else + if (strstart(filename, "tcp:", &p)) { + chr = qemu_chr_open_tcp(p, 0, 0); + } else + if (strstart(filename, "telnet:", &p)) { + chr = qemu_chr_open_tcp(p, 1, 0); + } else + if (strstart(filename, "udp:", &p)) { + chr = qemu_chr_open_udp(p); + } else + if (strstart(filename, "mon:", &p)) { + chr = qemu_chr_open(label, p); + if (chr) { + chr = qemu_chr_open_mux(chr); + monitor_init(chr, !nographic); + } else { + printf("Unable to open driver: %s\n", p); + } + } else +#ifndef _WIN32 + if (strstart(filename, "unix:", &p)) { + chr = qemu_chr_open_tcp(p, 0, 1); + } else if (strstart(filename, "file:", &p)) { + chr = qemu_chr_open_file_out(p); + } else if (strstart(filename, "pipe:", &p)) { + chr = qemu_chr_open_pipe(p); + } else if (!strcmp(filename, "pty")) { + chr = qemu_chr_open_pty(); + } else if (!strcmp(filename, "stdio")) { + chr = qemu_chr_open_stdio(); + } else +#if defined(__linux__) + if (strstart(filename, "/dev/parport", NULL)) { + chr = qemu_chr_open_pp(filename); + } else +#endif +#if defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) + if (strstart(filename, "/dev/", NULL)) { + chr = qemu_chr_open_tty(filename); + } else +#endif +#else /* !_WIN32 */ + if (strstart(filename, "COM", NULL)) { + chr = qemu_chr_open_win(filename); + } else + if (strstart(filename, "pipe:", &p)) { + chr = qemu_chr_open_win_pipe(p); + } else + if (strstart(filename, "con:", NULL)) { + chr = qemu_chr_open_win_con(filename); + } else + if (strstart(filename, "file:", &p)) { + chr = qemu_chr_open_win_file_out(p); + } else +#endif +#ifdef CONFIG_BRLAPI + if (!strcmp(filename, "braille")) { + chr = chr_baum_init(); + } else +#endif + { + chr = NULL; + } + + if (chr) { + if (!chr->filename) + chr->filename = qemu_strdup(filename); + chr->label = qemu_strdup(label); + TAILQ_INSERT_TAIL(&chardevs, chr, next); + } + return chr; +} + +void qemu_chr_close(CharDriverState *chr) +{ + TAILQ_REMOVE(&chardevs, chr, next); + if (chr->chr_close) + chr->chr_close(chr); + qemu_free(chr->filename); + qemu_free(chr->label); + qemu_free(chr); +} + +void qemu_chr_info(void) +{ + CharDriverState *chr; + + TAILQ_FOREACH(chr, &chardevs, next) { + term_printf("%s: filename=%s\n", chr->label, chr->filename); + } +} |