X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=qemu-char.c;h=5a5349111c58a1e895a5199ca3fbe91dc9d35dcf;hb=2ea5a7af7bfa576a5936400ccca4144caca9640b;hp=88f40254b7110d8ea401abf8ea081b05aa65fe0a;hpb=98c8a73b2e82eecac359b0b55a2d9d69f0a916ff;p=qemu.git diff --git a/qemu-char.c b/qemu-char.c index 88f40254b..5a5349111 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -22,12 +22,11 @@ * THE SOFTWARE. */ #include "qemu-common.h" -#include "net.h" -#include "monitor.h" -#include "console.h" -#include "sysemu.h" -#include "qemu-timer.h" -#include "qemu-char.h" +#include "monitor/monitor.h" +#include "ui/console.h" +#include "sysemu/sysemu.h" +#include "qemu/timer.h" +#include "char/char.h" #include "hw/usb.h" #include "hw/baum.h" #include "hw/msmouse.h" @@ -95,7 +94,7 @@ #endif #endif -#include "qemu_socket.h" +#include "qemu/sockets.h" #include "ui/qemu-spice.h" #define READ_BUF_LEN 4096 @@ -134,9 +133,9 @@ static void qemu_chr_fire_open_event(void *opaque) void qemu_chr_generic_open(CharDriverState *s) { if (s->open_timer == NULL) { - s->open_timer = qemu_new_timer_ms(vm_clock, + s->open_timer = qemu_new_timer_ms(rt_clock, qemu_chr_fire_open_event, s); - qemu_mod_timer(s->open_timer, qemu_get_clock_ms(vm_clock) - 1); + qemu_mod_timer(s->open_timer, qemu_get_clock_ms(rt_clock) - 1); } } @@ -540,66 +539,230 @@ int send_all(int fd, const void *_buf, int len1) } #endif /* !_WIN32 */ -#define STDIO_MAX_CLIENTS 1 -static int stdio_nb_clients; - #ifndef _WIN32 -typedef struct { - int fd_in, fd_out; +typedef struct IOWatchPoll +{ + GSource *src; int max_size; -} FDCharDriver; + IOCanReadHandler *fd_can_read; + void *opaque; -static int fd_chr_write(CharDriverState *chr, const uint8_t *buf, int len) + QTAILQ_ENTRY(IOWatchPoll) node; +} IOWatchPoll; + +static QTAILQ_HEAD(, IOWatchPoll) io_watch_poll_list = + QTAILQ_HEAD_INITIALIZER(io_watch_poll_list); + +static IOWatchPoll *io_watch_poll_from_source(GSource *source) { - FDCharDriver *s = chr->opaque; - return send_all(s->fd_out, buf, len); + IOWatchPoll *i; + + QTAILQ_FOREACH(i, &io_watch_poll_list, node) { + if (i->src == source) { + return i; + } + } + + return NULL; } -static int fd_chr_read_poll(void *opaque) +static gboolean io_watch_poll_prepare(GSource *source, gint *timeout_) { - CharDriverState *chr = opaque; - FDCharDriver *s = chr->opaque; + IOWatchPoll *iwp = io_watch_poll_from_source(source); - s->max_size = qemu_chr_be_can_write(chr); - return s->max_size; + iwp->max_size = iwp->fd_can_read(iwp->opaque); + if (iwp->max_size == 0) { + return FALSE; + } + + return g_io_watch_funcs.prepare(source, timeout_); +} + +static gboolean io_watch_poll_check(GSource *source) +{ + IOWatchPoll *iwp = io_watch_poll_from_source(source); + + if (iwp->max_size == 0) { + return FALSE; + } + + return g_io_watch_funcs.check(source); +} + +static gboolean io_watch_poll_dispatch(GSource *source, GSourceFunc callback, + gpointer user_data) +{ + return g_io_watch_funcs.dispatch(source, callback, user_data); +} + +static void io_watch_poll_finalize(GSource *source) +{ + IOWatchPoll *iwp = io_watch_poll_from_source(source); + QTAILQ_REMOVE(&io_watch_poll_list, iwp, node); + g_io_watch_funcs.finalize(source); +} + +static GSourceFuncs io_watch_poll_funcs = { + .prepare = io_watch_poll_prepare, + .check = io_watch_poll_check, + .dispatch = io_watch_poll_dispatch, + .finalize = io_watch_poll_finalize, +}; + +/* Can only be used for read */ +static guint io_add_watch_poll(GIOChannel *channel, + IOCanReadHandler *fd_can_read, + GIOFunc fd_read, + gpointer user_data) +{ + IOWatchPoll *iwp; + GSource *src; + guint tag; + + src = g_io_create_watch(channel, G_IO_IN | G_IO_ERR | G_IO_HUP); + g_source_set_funcs(src, &io_watch_poll_funcs); + g_source_set_callback(src, (GSourceFunc)fd_read, user_data, NULL); + tag = g_source_attach(src, NULL); + g_source_unref(src); + + iwp = g_malloc0(sizeof(*iwp)); + iwp->src = src; + iwp->max_size = 0; + iwp->fd_can_read = fd_can_read; + iwp->opaque = user_data; + + QTAILQ_INSERT_HEAD(&io_watch_poll_list, iwp, node); + + return tag; +} + +static GIOChannel *io_channel_from_fd(int fd) +{ + GIOChannel *chan; + + if (fd == -1) { + return NULL; + } + + chan = g_io_channel_unix_new(fd); + + g_io_channel_set_encoding(chan, NULL, NULL); + g_io_channel_set_buffered(chan, FALSE); + + return chan; +} + +static GIOChannel *io_channel_from_socket(int fd) +{ + GIOChannel *chan; + + if (fd == -1) { + return NULL; + } + +#ifdef _WIN32 + chan = g_io_channel_win32_new_socket(fd); +#else + chan = g_io_channel_unix_new(fd); +#endif + + g_io_channel_set_encoding(chan, NULL, NULL); + g_io_channel_set_buffered(chan, FALSE); + + return chan; +} + +static int io_channel_send_all(GIOChannel *fd, const void *_buf, int len1) +{ + GIOStatus status; + gsize bytes_written; + int len; + const uint8_t *buf = _buf; + + len = len1; + while (len > 0) { + status = g_io_channel_write_chars(fd, (const gchar *)buf, len, + &bytes_written, NULL); + if (status != G_IO_STATUS_NORMAL) { + if (status != G_IO_STATUS_AGAIN) { + return -1; + } + } else if (status == G_IO_STATUS_EOF) { + break; + } else { + buf += bytes_written; + len -= bytes_written; + } + } + return len1 - len; +} + +typedef struct FDCharDriver { + CharDriverState *chr; + GIOChannel *fd_in, *fd_out; + guint fd_in_tag; + int max_size; + QTAILQ_ENTRY(FDCharDriver) node; +} FDCharDriver; + +static int fd_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + FDCharDriver *s = chr->opaque; + + return io_channel_send_all(s->fd_out, buf, len); } -static void fd_chr_read(void *opaque) +static gboolean fd_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque) { CharDriverState *chr = opaque; FDCharDriver *s = chr->opaque; - int size, len; + int len; uint8_t buf[READ_BUF_LEN]; + GIOStatus status; + gsize bytes_read; len = sizeof(buf); - if (len > s->max_size) + 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); + } + if (len == 0) { + return FALSE; + } + + status = g_io_channel_read_chars(chan, (gchar *)buf, + len, &bytes_read, NULL); + if (status == G_IO_STATUS_EOF) { qemu_chr_be_event(chr, CHR_EVENT_CLOSED); - return; + return FALSE; } - if (size > 0) { - qemu_chr_be_write(chr, buf, size); + if (status == G_IO_STATUS_NORMAL) { + qemu_chr_be_write(chr, buf, bytes_read); } + + return TRUE; +} + +static int fd_chr_read_poll(void *opaque) +{ + CharDriverState *chr = opaque; + FDCharDriver *s = chr->opaque; + + s->max_size = qemu_chr_be_can_write(chr); + return s->max_size; } static void fd_chr_update_read_handler(CharDriverState *chr) { FDCharDriver *s = chr->opaque; - if (s->fd_in >= 0) { - if (display_type == DT_NOGRAPHIC && s->fd_in == 0) { - } else { - qemu_set_fd_handler2(s->fd_in, fd_chr_read_poll, - fd_chr_read, NULL, chr); - } + if (s->fd_in_tag) { + g_source_remove(s->fd_in_tag); + } + + if (s->fd_in) { + s->fd_in_tag = io_add_watch_poll(s->fd_in, fd_chr_read_poll, fd_chr_read, chr); } } @@ -607,11 +770,16 @@ static void fd_chr_close(struct CharDriverState *chr) { FDCharDriver *s = chr->opaque; - if (s->fd_in >= 0) { - if (display_type == DT_NOGRAPHIC && s->fd_in == 0) { - } else { - qemu_set_fd_handler2(s->fd_in, NULL, NULL, NULL, NULL); - } + if (s->fd_in_tag) { + g_source_remove(s->fd_in_tag); + s->fd_in_tag = 0; + } + + if (s->fd_in) { + g_io_channel_unref(s->fd_in); + } + if (s->fd_out) { + g_io_channel_unref(s->fd_out); } g_free(s); @@ -626,8 +794,9 @@ static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out) chr = g_malloc0(sizeof(CharDriverState)); s = g_malloc0(sizeof(FDCharDriver)); - s->fd_in = fd_in; - s->fd_out = fd_out; + s->fd_in = io_channel_from_fd(fd_in); + s->fd_out = io_channel_from_fd(fd_out); + s->chr = chr; chr->opaque = s; chr->chr_write = fd_chr_write; chr->chr_update_read_handler = fd_chr_update_read_handler; @@ -678,53 +847,6 @@ static CharDriverState *qemu_chr_open_pipe(QemuOpts *opts) 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_be_can_write(chr) > 0) { - qemu_chr_be_write(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); - qemu_chr_be_event(chr, CHR_EVENT_CLOSED); - return; - } - if (size > 0) { - if (qemu_chr_be_can_write(chr) > 0) { - qemu_chr_be_write(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; @@ -761,8 +883,6 @@ static void qemu_chr_set_echo_stdio(CharDriverState *chr, bool echo) 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); } @@ -770,21 +890,18 @@ static CharDriverState *qemu_chr_open_stdio(QemuOpts *opts) { CharDriverState *chr; - if (stdio_nb_clients >= STDIO_MAX_CLIENTS) { + if (is_daemonized()) { + error_report("cannot use stdio with -daemonize"); return NULL; } - if (stdio_nb_clients == 0) { - old_fd0_flags = fcntl(0, F_GETFL); - tcgetattr (0, &oldtty); - fcntl(0, F_SETFL, O_NONBLOCK); - atexit(term_exit); - } + old_fd0_flags = fcntl(0, F_GETFL); + tcgetattr (0, &oldtty); + fcntl(0, F_SETFL, O_NONBLOCK); + atexit(term_exit); chr = qemu_chr_open_fd(0, 1); chr->chr_close = qemu_chr_close_stdio; chr->chr_set_echo = qemu_chr_set_echo_stdio; - qemu_set_fd_handler2(0, stdio_read_poll, stdio_read, NULL, chr); - stdio_nb_clients++; stdio_allow_signal = qemu_opt_get_bool(opts, "signal", display_type != DT_NOGRAPHIC); qemu_chr_fe_set_echo(chr, false); @@ -853,8 +970,11 @@ static void cfmakeraw (struct termios *termios_p) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) \ || defined(__GLIBC__) +#define HAVE_CHARDEV_TTY 1 + typedef struct { - int fd; + GIOChannel *fd; + guint fd_tag; int connected; int polling; int read_bytes; @@ -873,7 +993,7 @@ static int pty_chr_write(CharDriverState *chr, const uint8_t *buf, int len) pty_chr_update_read_handler(chr); return 0; } - return send_all(s->fd, buf, len); + return io_channel_send_all(s->fd, buf, len); } static int pty_chr_read_poll(void *opaque) @@ -885,36 +1005,39 @@ static int pty_chr_read_poll(void *opaque) return s->read_bytes; } -static void pty_chr_read(void *opaque) +static gboolean pty_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque) { CharDriverState *chr = opaque; PtyCharDriver *s = chr->opaque; - int size, len; + gsize size, len; uint8_t buf[READ_BUF_LEN]; + GIOStatus status; 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)) { + return FALSE; + status = g_io_channel_read_chars(s->fd, (gchar *)buf, len, &size, NULL); + if (status != G_IO_STATUS_NORMAL) { pty_chr_state(chr, 0); - return; - } - if (size > 0) { + return FALSE; + } else { pty_chr_state(chr, 1); qemu_chr_be_write(chr, buf, size); } + return TRUE; } 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); + if (s->fd_tag) { + g_source_remove(s->fd_tag); + } + + s->fd_tag = io_add_watch_poll(s->fd, pty_chr_read_poll, pty_chr_read, chr); s->polling = 1; /* * Short timeout here: just need wait long enougth that qemu makes @@ -932,7 +1055,8 @@ 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); + g_source_remove(s->fd_tag); + s->fd_tag = 0; s->connected = 0; s->polling = 0; /* (re-)connect poll interval for idle guests: once per second. @@ -967,9 +1091,14 @@ static void pty_chr_timer(void *opaque) static void pty_chr_close(struct CharDriverState *chr) { PtyCharDriver *s = chr->opaque; + int fd; - qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); - close(s->fd); + if (s->fd_tag) { + g_source_remove(s->fd_tag); + } + fd = g_io_channel_unix_get_fd(s->fd); + g_io_channel_unref(s->fd); + close(fd); qemu_del_timer(s->timer); qemu_free_timer(s->timer); g_free(s); @@ -981,6 +1110,7 @@ static CharDriverState *qemu_chr_open_pty(QemuOpts *opts) CharDriverState *chr; PtyCharDriver *s; struct termios tty; + const char *label; int master_fd, slave_fd, len; #if defined(__OpenBSD__) || defined(__DragonFly__) char pty_name[PATH_MAX]; @@ -1006,7 +1136,13 @@ static CharDriverState *qemu_chr_open_pty(QemuOpts *opts) chr->filename = g_malloc(len); snprintf(chr->filename, len, "pty:%s", q_ptsname(master_fd)); qemu_opt_set(opts, "path", q_ptsname(master_fd)); - fprintf(stderr, "char device redirected to %s\n", q_ptsname(master_fd)); + + label = qemu_opts_id(opts); + fprintf(stderr, "char device redirected to %s%s%s%s\n", + q_ptsname(master_fd), + label ? " (label " : "", + label ? label : "", + label ? ")" : ""); s = g_malloc0(sizeof(PtyCharDriver)); chr->opaque = s; @@ -1014,7 +1150,7 @@ static CharDriverState *qemu_chr_open_pty(QemuOpts *opts) chr->chr_update_read_handler = pty_chr_update_read_handler; chr->chr_close = pty_chr_close; - s->fd = master_fd; + s->fd = io_channel_from_fd(master_fd); s->timer = qemu_new_timer_ms(rt_clock, pty_chr_timer, chr); return chr; @@ -1143,22 +1279,24 @@ static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg) case CHR_IOCTL_SERIAL_SET_PARAMS: { QEMUSerialSetParams *ssp = arg; - tty_serial_init(s->fd_in, ssp->speed, ssp->parity, + tty_serial_init(g_io_channel_unix_get_fd(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); + if (enable) { + tcsendbreak(g_io_channel_unix_get_fd(s->fd_in), 1); + } } break; case CHR_IOCTL_SERIAL_GET_TIOCM: { int sarg = 0; int *targ = (int *)arg; - ioctl(s->fd_in, TIOCMGET, &sarg); + ioctl(g_io_channel_unix_get_fd(s->fd_in), TIOCMGET, &sarg); *targ = 0; if (sarg & TIOCM_CTS) *targ |= CHR_TIOCM_CTS; @@ -1178,7 +1316,7 @@ static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg) { int sarg = *(int *)arg; int targ = 0; - ioctl(s->fd_in, TIOCMGET, &targ); + ioctl(g_io_channel_unix_get_fd(s->fd_in), TIOCMGET, &targ); targ &= ~(CHR_TIOCM_CTS | CHR_TIOCM_CAR | CHR_TIOCM_DSR | CHR_TIOCM_RI | CHR_TIOCM_DTR | CHR_TIOCM_RTS); if (sarg & CHR_TIOCM_CTS) @@ -1193,7 +1331,7 @@ static int tty_serial_ioctl(CharDriverState *chr, int cmd, void *arg) targ |= TIOCM_DTR; if (sarg & CHR_TIOCM_RTS) targ |= TIOCM_RTS; - ioctl(s->fd_in, TIOCMSET, &targ); + ioctl(g_io_channel_unix_get_fd(s->fd_in), TIOCMSET, &targ); } break; default: @@ -1208,7 +1346,7 @@ static void qemu_chr_close_tty(CharDriverState *chr) int fd = -1; if (s) { - fd = s->fd_in; + fd = g_io_channel_unix_get_fd(s->fd_in); } fd_chr_close(chr); @@ -1218,30 +1356,34 @@ static void qemu_chr_close_tty(CharDriverState *chr) } } -static CharDriverState *qemu_chr_open_tty(QemuOpts *opts) +static CharDriverState *qemu_chr_open_tty_fd(int fd) { - const char *filename = qemu_opt_get(opts, "path"); CharDriverState *chr; - int fd; - TFR(fd = qemu_open(filename, O_RDWR | O_NONBLOCK)); - if (fd < 0) { - return NULL; - } tty_serial_init(fd, 115200, 'N', 8, 1); chr = qemu_chr_open_fd(fd, fd); chr->chr_ioctl = tty_serial_ioctl; chr->chr_close = qemu_chr_close_tty; return chr; } -#else /* ! __linux__ && ! __sun__ */ -static CharDriverState *qemu_chr_open_pty(QemuOpts *opts) + +static CharDriverState *qemu_chr_open_tty(QemuOpts *opts) { - return NULL; + const char *filename = qemu_opt_get(opts, "path"); + int fd; + + TFR(fd = qemu_open(filename, O_RDWR | O_NONBLOCK)); + if (fd < 0) { + return NULL; + } + return qemu_chr_open_tty_fd(fd); } #endif /* __linux__ || __sun__ */ #if defined(__linux__) + +#define HAVE_CHARDEV_PARPORT 1 + typedef struct { int fd; int mode; @@ -1351,17 +1493,10 @@ static void pp_close(CharDriverState *chr) qemu_chr_be_event(chr, CHR_EVENT_CLOSED); } -static CharDriverState *qemu_chr_open_pp(QemuOpts *opts) +static CharDriverState *qemu_chr_open_pp_fd(int fd) { - const char *filename = qemu_opt_get(opts, "path"); CharDriverState *chr; ParallelCharDriver *drv; - int fd; - - TFR(fd = qemu_open(filename, O_RDWR)); - if (fd < 0) { - return NULL; - } if (ioctl(fd, PPCLAIM) < 0) { close(fd); @@ -1385,6 +1520,9 @@ static CharDriverState *qemu_chr_open_pp(QemuOpts *opts) #endif /* __linux__ */ #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) + +#define HAVE_CHARDEV_PARPORT 1 + static int pp_ioctl(CharDriverState *chr, int cmd, void *arg) { int fd = (int)(intptr_t)chr->opaque; @@ -1422,16 +1560,9 @@ static int pp_ioctl(CharDriverState *chr, int cmd, void *arg) return 0; } -static CharDriverState *qemu_chr_open_pp(QemuOpts *opts) +static CharDriverState *qemu_chr_open_pp_fd(int fd) { - const char *filename = qemu_opt_get(opts, "path"); CharDriverState *chr; - int fd; - - fd = qemu_open(filename, O_RDWR); - if (fd < 0) { - return NULL; - } chr = g_malloc0(sizeof(CharDriverState)); chr->opaque = (void *)(intptr_t)fd; @@ -1443,8 +1574,6 @@ static CharDriverState *qemu_chr_open_pp(QemuOpts *opts) #else /* _WIN32 */ -static CharDriverState *stdio_clients[STDIO_MAX_CLIENTS]; - typedef struct { int max_size; HANDLE hcom, hrecv, hsend; @@ -1653,9 +1782,8 @@ static int win_chr_poll(void *opaque) return 0; } -static CharDriverState *qemu_chr_open_win(QemuOpts *opts) +static CharDriverState *qemu_chr_open_win_path(const char *filename) { - const char *filename = qemu_opt_get(opts, "path"); CharDriverState *chr; WinCharState *s; @@ -1674,6 +1802,11 @@ static CharDriverState *qemu_chr_open_win(QemuOpts *opts) return chr; } +static CharDriverState *qemu_chr_open_win(QemuOpts *opts) +{ + return qemu_chr_open_win_path(qemu_opt_get(opts, "path")); +} + static int win_chr_pipe_poll(void *opaque) { CharDriverState *chr = opaque; @@ -1942,7 +2075,6 @@ static void win_stdio_close(CharDriverState *chr) g_free(chr->opaque); g_free(chr); - stdio_nb_clients--; } static CharDriverState *qemu_chr_open_win_stdio(QemuOpts *opts) @@ -1952,11 +2084,6 @@ static CharDriverState *qemu_chr_open_win_stdio(QemuOpts *opts) DWORD dwMode; int is_console = 0; - if (stdio_nb_clients >= STDIO_MAX_CLIENTS - || ((display_type != DT_NOGRAPHIC) && (stdio_nb_clients != 0))) { - return NULL; - } - chr = g_malloc0(sizeof(CharDriverState)); stdio = g_malloc0(sizeof(WinStdioCharState)); @@ -1972,37 +2099,34 @@ static CharDriverState *qemu_chr_open_win_stdio(QemuOpts *opts) chr->chr_write = win_stdio_write; chr->chr_close = win_stdio_close; - if (stdio_nb_clients == 0) { - if (is_console) { - if (qemu_add_wait_object(stdio->hStdIn, - win_stdio_wait_func, chr)) { - fprintf(stderr, "qemu_add_wait_object: failed\n"); - } - } else { - DWORD dwId; - - stdio->hInputReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - stdio->hInputDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - stdio->hInputThread = CreateThread(NULL, 0, win_stdio_thread, - chr, 0, &dwId); - - if (stdio->hInputThread == INVALID_HANDLE_VALUE - || stdio->hInputReadyEvent == INVALID_HANDLE_VALUE - || stdio->hInputDoneEvent == INVALID_HANDLE_VALUE) { - fprintf(stderr, "cannot create stdio thread or event\n"); - exit(1); - } - if (qemu_add_wait_object(stdio->hInputReadyEvent, - win_stdio_thread_wait_func, chr)) { - fprintf(stderr, "qemu_add_wait_object: failed\n"); - } + if (is_console) { + if (qemu_add_wait_object(stdio->hStdIn, + win_stdio_wait_func, chr)) { + fprintf(stderr, "qemu_add_wait_object: failed\n"); + } + } else { + DWORD dwId; + + stdio->hInputReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + stdio->hInputDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + stdio->hInputThread = CreateThread(NULL, 0, win_stdio_thread, + chr, 0, &dwId); + + if (stdio->hInputThread == INVALID_HANDLE_VALUE + || stdio->hInputReadyEvent == INVALID_HANDLE_VALUE + || stdio->hInputDoneEvent == INVALID_HANDLE_VALUE) { + fprintf(stderr, "cannot create stdio thread or event\n"); + exit(1); + } + if (qemu_add_wait_object(stdio->hInputReadyEvent, + win_stdio_thread_wait_func, chr)) { + fprintf(stderr, "qemu_add_wait_object: failed\n"); } } dwMode |= ENABLE_LINE_INPUT; - stdio_clients[stdio_nb_clients++] = chr; - if (stdio_nb_clients == 1 && is_console) { + if (is_console) { /* set the terminal in raw mode */ /* ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS */ dwMode |= ENABLE_PROCESSED_INPUT; @@ -2022,6 +2146,8 @@ static CharDriverState *qemu_chr_open_win_stdio(QemuOpts *opts) typedef struct { int fd; + GIOChannel *chan; + guint tag; uint8_t buf[READ_BUF_LEN]; int bufcnt; int bufptr; @@ -2031,8 +2157,17 @@ typedef struct { static int udp_chr_write(CharDriverState *chr, const uint8_t *buf, int len) { NetCharDriver *s = chr->opaque; + gsize bytes_written; + GIOStatus status; + + status = g_io_channel_write_chars(s->chan, (const gchar *)buf, len, &bytes_written, NULL); + if (status == G_IO_STATUS_EOF) { + return 0; + } else if (status != G_IO_STATUS_NORMAL) { + return -1; + } - return send(s->fd, (const void *)buf, len, 0); + return bytes_written; } static int udp_chr_read_poll(void *opaque) @@ -2053,17 +2188,22 @@ static int udp_chr_read_poll(void *opaque) return s->max_size; } -static void udp_chr_read(void *opaque) +static gboolean udp_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque) { CharDriverState *chr = opaque; NetCharDriver *s = chr->opaque; + gsize bytes_read = 0; + GIOStatus status; if (s->max_size == 0) - return; - s->bufcnt = qemu_recv(s->fd, s->buf, sizeof(s->buf), 0); + return FALSE; + status = g_io_channel_read_chars(s->chan, (gchar *)s->buf, sizeof(s->buf), + &bytes_read, NULL); + s->bufcnt = bytes_read; s->bufptr = s->bufcnt; - if (s->bufcnt <= 0) - return; + if (status != G_IO_STATUS_NORMAL) { + return FALSE; + } s->bufptr = 0; while (s->max_size > 0 && s->bufptr < s->bufcnt) { @@ -2071,23 +2211,32 @@ static void udp_chr_read(void *opaque) s->bufptr++; s->max_size = qemu_chr_be_can_write(chr); } + + return TRUE; } 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); + if (s->tag) { + g_source_remove(s->tag); + s->tag = 0; + } + + if (s->chan) { + s->tag = io_add_watch_poll(s->chan, udp_chr_read_poll, udp_chr_read, chr); } } static void udp_chr_close(CharDriverState *chr) { NetCharDriver *s = chr->opaque; - if (s->fd >= 0) { - qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); + if (s->tag) { + g_source_remove(s->tag); + } + if (s->chan) { + g_io_channel_unref(s->chan); closesocket(s->fd); } g_free(s); @@ -2110,6 +2259,7 @@ static CharDriverState *qemu_chr_open_udp(QemuOpts *opts) } s->fd = fd; + s->chan = io_channel_from_socket(s->fd); s->bufcnt = 0; s->bufptr = 0; chr->opaque = s; @@ -2135,6 +2285,9 @@ return_err: /* TCP Net console */ typedef struct { + + GIOChannel *chan, *listen_chan; + guint tag, listen_tag; int fd, listen_fd; int connected; int max_size; @@ -2144,13 +2297,13 @@ typedef struct { int msgfd; } TCPCharDriver; -static void tcp_chr_accept(void *opaque); +static gboolean tcp_chr_accept(GIOChannel *chan, GIOCondition cond, 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); + return io_channel_send_all(s->chan, buf, len); } else { /* XXX: indicate an error ? */ return len; @@ -2290,15 +2443,16 @@ static ssize_t tcp_chr_recv(CharDriverState *chr, char *buf, size_t len) } #endif -static void tcp_chr_read(void *opaque) +static gboolean tcp_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque) { CharDriverState *chr = opaque; TCPCharDriver *s = chr->opaque; uint8_t buf[READ_BUF_LEN]; int len, size; - if (!s->connected || s->max_size <= 0) - return; + if (!s->connected || s->max_size <= 0) { + return FALSE; + } len = sizeof(buf); if (len > s->max_size) len = s->max_size; @@ -2306,10 +2460,13 @@ static void tcp_chr_read(void *opaque) if (size == 0) { /* connection closed */ s->connected = 0; - if (s->listen_fd >= 0) { - qemu_set_fd_handler2(s->listen_fd, NULL, tcp_chr_accept, NULL, chr); + if (s->listen_chan) { + s->listen_tag = g_io_add_watch(s->listen_chan, G_IO_IN, tcp_chr_accept, chr); } - qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); + g_source_remove(s->tag); + s->tag = 0; + g_io_channel_unref(s->chan); + s->chan = NULL; closesocket(s->fd); s->fd = -1; qemu_chr_be_event(chr, CHR_EVENT_CLOSED); @@ -2319,6 +2476,8 @@ static void tcp_chr_read(void *opaque) if (size > 0) qemu_chr_be_write(chr, buf, size); } + + return TRUE; } #ifndef _WIN32 @@ -2334,9 +2493,8 @@ static void tcp_chr_connect(void *opaque) TCPCharDriver *s = chr->opaque; s->connected = 1; - if (s->fd >= 0) { - qemu_set_fd_handler2(s->fd, tcp_chr_read_poll, - tcp_chr_read, NULL, chr); + if (s->chan) { + s->tag = io_add_watch_poll(s->chan, tcp_chr_read_poll, tcp_chr_read, chr); } qemu_chr_generic_open(chr); } @@ -2356,12 +2514,6 @@ static void tcp_chr_telnet_init(int fd) 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 int tcp_chr_add_client(CharDriverState *chr, int fd) { TCPCharDriver *s = chr->opaque; @@ -2372,13 +2524,15 @@ static int tcp_chr_add_client(CharDriverState *chr, int fd) if (s->do_nodelay) socket_set_nodelay(fd); s->fd = fd; - qemu_set_fd_handler2(s->listen_fd, NULL, NULL, NULL, NULL); + s->chan = io_channel_from_socket(fd); + g_source_remove(s->listen_tag); + s->listen_tag = 0; tcp_chr_connect(chr); return 0; } -static void tcp_chr_accept(void *opaque) +static gboolean tcp_chr_accept(GIOChannel *channel, GIOCondition cond, void *opaque) { CharDriverState *chr = opaque; TCPCharDriver *s = chr->opaque; @@ -2403,7 +2557,7 @@ static void tcp_chr_accept(void *opaque) } fd = qemu_accept(s->listen_fd, addr, &len); if (fd < 0 && errno != EINTR) { - return; + return FALSE; } else if (fd >= 0) { if (s->do_telnetopt) tcp_chr_telnet_init(fd); @@ -2412,72 +2566,85 @@ static void tcp_chr_accept(void *opaque) } if (tcp_chr_add_client(chr, fd) < 0) close(fd); + + return TRUE; } static void tcp_chr_close(CharDriverState *chr) { TCPCharDriver *s = chr->opaque; if (s->fd >= 0) { - qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); + if (s->tag) { + g_source_remove(s->tag); + } + if (s->chan) { + g_io_channel_unref(s->chan); + } closesocket(s->fd); } if (s->listen_fd >= 0) { - qemu_set_fd_handler2(s->listen_fd, NULL, NULL, NULL, NULL); + if (s->listen_tag) { + g_source_remove(s->listen_tag); + } + if (s->listen_chan) { + g_io_channel_unref(s->listen_chan); + } closesocket(s->listen_fd); } g_free(s); qemu_chr_be_event(chr, CHR_EVENT_CLOSED); } -static CharDriverState *qemu_chr_open_socket(QemuOpts *opts) +static CharDriverState *qemu_chr_open_socket_fd(int fd, bool do_nodelay, + bool is_listen, bool is_telnet, + bool is_waitconnect, + Error **errp) { CharDriverState *chr = NULL; TCPCharDriver *s = NULL; - Error *local_err = NULL; - int fd = -1; - int is_listen; - int is_waitconnect; - int do_nodelay; - int is_unix; - int is_telnet; - - is_listen = qemu_opt_get_bool(opts, "server", 0); - is_waitconnect = qemu_opt_get_bool(opts, "wait", 1); - is_telnet = qemu_opt_get_bool(opts, "telnet", 0); - do_nodelay = !qemu_opt_get_bool(opts, "delay", 1); - is_unix = qemu_opt_get(opts, "path") != NULL; - if (!is_listen) - is_waitconnect = 0; + char host[NI_MAXHOST], serv[NI_MAXSERV]; + const char *left = "", *right = ""; + struct sockaddr_storage ss; + socklen_t ss_len = sizeof(ss); + + memset(&ss, 0, ss_len); + if (getsockname(fd, (struct sockaddr *) &ss, &ss_len) != 0) { + error_setg(errp, "getsockname: %s", strerror(errno)); + return NULL; + } chr = g_malloc0(sizeof(CharDriverState)); s = g_malloc0(sizeof(TCPCharDriver)); - if (is_unix) { - if (is_listen) { - fd = unix_listen_opts(opts, &local_err); - } else { - fd = unix_connect_opts(opts, &local_err, NULL, NULL); - } - } else { - if (is_listen) { - fd = inet_listen_opts(opts, 0, &local_err); - } else { - fd = inet_connect_opts(opts, &local_err, NULL, NULL); - } - } - if (fd < 0) { - goto fail; - } - - if (!is_waitconnect) - socket_set_nonblock(fd); - s->connected = 0; s->fd = -1; s->listen_fd = -1; s->msgfd = -1; - s->is_unix = is_unix; - s->do_nodelay = do_nodelay && !is_unix; + + chr->filename = g_malloc(256); + switch (ss.ss_family) { +#ifndef _WIN32 + case AF_UNIX: + s->is_unix = 1; + snprintf(chr->filename, 256, "unix:%s%s", + ((struct sockaddr_un *)(&ss))->sun_path, + is_listen ? ",server" : ""); + break; +#endif + case AF_INET6: + left = "["; + right = "]"; + /* fall through */ + case AF_INET: + s->do_nodelay = do_nodelay; + getnameinfo((struct sockaddr *) &ss, ss_len, host, sizeof(host), + serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV); + snprintf(chr->filename, 256, "%s:%s:%s%s%s%s", + is_telnet ? "telnet" : "tcp", + left, host, right, serv, + is_listen ? ",server" : ""); + break; + } chr->opaque = s; chr->chr_write = tcp_chr_write; @@ -2487,40 +2654,74 @@ static CharDriverState *qemu_chr_open_socket(QemuOpts *opts) if (is_listen) { s->listen_fd = fd; - qemu_set_fd_handler2(s->listen_fd, NULL, tcp_chr_accept, NULL, chr); - if (is_telnet) + s->listen_chan = io_channel_from_socket(s->listen_fd); + s->listen_tag = g_io_add_watch(s->listen_chan, G_IO_IN, tcp_chr_accept, chr); + if (is_telnet) { s->do_telnetopt = 1; - + } } else { s->connected = 1; s->fd = fd; socket_set_nodelay(fd); + s->chan = io_channel_from_socket(s->fd); tcp_chr_connect(chr); } - /* for "info chardev" monitor command */ - chr->filename = g_malloc(256); - if (is_unix) { - snprintf(chr->filename, 256, "unix:%s%s", - qemu_opt_get(opts, "path"), - qemu_opt_get_bool(opts, "server", 0) ? ",server" : ""); - } else if (is_telnet) { - snprintf(chr->filename, 256, "telnet:%s:%s%s", - qemu_opt_get(opts, "host"), qemu_opt_get(opts, "port"), - qemu_opt_get_bool(opts, "server", 0) ? ",server" : ""); - } else { - snprintf(chr->filename, 256, "tcp:%s:%s%s", - qemu_opt_get(opts, "host"), qemu_opt_get(opts, "port"), - qemu_opt_get_bool(opts, "server", 0) ? ",server" : ""); - } - if (is_listen && is_waitconnect) { printf("QEMU waiting for connection on: %s\n", chr->filename); - tcp_chr_accept(chr); + tcp_chr_accept(s->listen_chan, G_IO_IN, chr); socket_set_nonblock(s->listen_fd); } return chr; +} + +static CharDriverState *qemu_chr_open_socket(QemuOpts *opts) +{ + CharDriverState *chr = NULL; + Error *local_err = NULL; + int fd = -1; + int is_listen; + int is_waitconnect; + int do_nodelay; + int is_unix; + int is_telnet; + + is_listen = qemu_opt_get_bool(opts, "server", 0); + is_waitconnect = qemu_opt_get_bool(opts, "wait", 1); + is_telnet = qemu_opt_get_bool(opts, "telnet", 0); + do_nodelay = !qemu_opt_get_bool(opts, "delay", 1); + is_unix = qemu_opt_get(opts, "path") != NULL; + if (!is_listen) + is_waitconnect = 0; + + if (is_unix) { + if (is_listen) { + fd = unix_listen_opts(opts, &local_err); + } else { + fd = unix_connect_opts(opts, &local_err, NULL, NULL); + } + } else { + if (is_listen) { + fd = inet_listen_opts(opts, 0, &local_err); + } else { + fd = inet_connect_opts(opts, &local_err, NULL, NULL); + } + } + if (fd < 0) { + goto fail; + } + + if (!is_waitconnect) + socket_set_nonblock(fd); + + chr = qemu_chr_open_socket_fd(fd, do_nodelay, is_listen, is_telnet, + is_waitconnect, &local_err); + if (error_is_set(&local_err)) { + goto fail; + } + return chr; + fail: if (local_err) { @@ -2530,8 +2731,10 @@ static CharDriverState *qemu_chr_open_socket(QemuOpts *opts) if (fd >= 0) { closesocket(fd); } - g_free(s); - g_free(chr); + if (chr) { + g_free(chr->opaque); + g_free(chr); + } return NULL; } @@ -2599,6 +2802,191 @@ size_t qemu_chr_mem_osize(const CharDriverState *chr) return d->outbuf_size; } +/*********************************************************/ +/* Ring buffer chardev */ + +typedef struct { + size_t size; + size_t prod; + size_t cons; + uint8_t *cbuf; +} RingBufCharDriver; + +static size_t ringbuf_count(const CharDriverState *chr) +{ + const RingBufCharDriver *d = chr->opaque; + + return d->prod - d->cons; +} + +static int ringbuf_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + RingBufCharDriver *d = chr->opaque; + int i; + + if (!buf || (len < 0)) { + return -1; + } + + for (i = 0; i < len; i++ ) { + d->cbuf[d->prod++ & (d->size - 1)] = buf[i]; + if (d->prod - d->cons > d->size) { + d->cons = d->prod - d->size; + } + } + + return 0; +} + +static int ringbuf_chr_read(CharDriverState *chr, uint8_t *buf, int len) +{ + RingBufCharDriver *d = chr->opaque; + int i; + + for (i = 0; i < len && d->cons != d->prod; i++) { + buf[i] = d->cbuf[d->cons++ & (d->size - 1)]; + } + + return i; +} + +static void ringbuf_chr_close(struct CharDriverState *chr) +{ + RingBufCharDriver *d = chr->opaque; + + g_free(d->cbuf); + g_free(d); + chr->opaque = NULL; +} + +static CharDriverState *qemu_chr_open_ringbuf(QemuOpts *opts) +{ + CharDriverState *chr; + RingBufCharDriver *d; + + chr = g_malloc0(sizeof(CharDriverState)); + d = g_malloc(sizeof(*d)); + + d->size = qemu_opt_get_size(opts, "size", 0); + if (d->size == 0) { + d->size = 65536; + } + + /* The size must be power of 2 */ + if (d->size & (d->size - 1)) { + error_report("size of ringbuf device must be power of two"); + goto fail; + } + + d->prod = 0; + d->cons = 0; + d->cbuf = g_malloc0(d->size); + + chr->opaque = d; + chr->chr_write = ringbuf_chr_write; + chr->chr_close = ringbuf_chr_close; + + return chr; + +fail: + g_free(d); + g_free(chr); + return NULL; +} + +static bool chr_is_ringbuf(const CharDriverState *chr) +{ + return chr->chr_write == ringbuf_chr_write; +} + +void qmp_ringbuf_write(const char *device, const char *data, + bool has_format, enum DataFormat format, + Error **errp) +{ + CharDriverState *chr; + const uint8_t *write_data; + int ret; + size_t write_count; + + chr = qemu_chr_find(device); + if (!chr) { + error_setg(errp, "Device '%s' not found", device); + return; + } + + if (!chr_is_ringbuf(chr)) { + error_setg(errp,"%s is not a ringbuf device", device); + return; + } + + if (has_format && (format == DATA_FORMAT_BASE64)) { + write_data = g_base64_decode(data, &write_count); + } else { + write_data = (uint8_t *)data; + write_count = strlen(data); + } + + ret = ringbuf_chr_write(chr, write_data, write_count); + + if (write_data != (uint8_t *)data) { + g_free((void *)write_data); + } + + if (ret < 0) { + error_setg(errp, "Failed to write to device %s", device); + return; + } +} + +char *qmp_ringbuf_read(const char *device, int64_t size, + bool has_format, enum DataFormat format, + Error **errp) +{ + CharDriverState *chr; + uint8_t *read_data; + size_t count; + char *data; + + chr = qemu_chr_find(device); + if (!chr) { + error_setg(errp, "Device '%s' not found", device); + return NULL; + } + + if (!chr_is_ringbuf(chr)) { + error_setg(errp,"%s is not a ringbuf device", device); + return NULL; + } + + if (size <= 0) { + error_setg(errp, "size must be greater than zero"); + return NULL; + } + + count = ringbuf_count(chr); + size = size > count ? count : size; + read_data = g_malloc(size + 1); + + ringbuf_chr_read(chr, read_data, size); + + if (has_format && (format == DATA_FORMAT_BASE64)) { + data = g_base64_encode(read_data, size); + g_free(read_data); + } else { + /* + * FIXME should read only complete, valid UTF-8 characters up + * to @size bytes. Invalid sequences should be replaced by a + * suitable replacement character. Except when (and only + * when) ring buffer lost characters since last read, initial + * continuation characters should be dropped. + */ + read_data[size] = 0; + data = (char *)read_data; + } + + return data; +} + QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename) { char host[65], port[33], width[8], height[8]; @@ -2727,6 +3115,22 @@ fail: return NULL; } +#ifdef HAVE_CHARDEV_PARPORT + +static CharDriverState *qemu_chr_open_pp(QemuOpts *opts) +{ + const char *filename = qemu_opt_get(opts, "path"); + int fd; + + fd = qemu_open(filename, O_RDWR); + if (fd < 0) { + return NULL; + } + return qemu_chr_open_pp_fd(fd); +} + +#endif + static const struct { const char *name; CharDriverState *(*open)(QemuOpts *opts); @@ -2735,7 +3139,8 @@ static const struct { { .name = "socket", .open = qemu_chr_open_socket }, { .name = "udp", .open = qemu_chr_open_udp }, { .name = "msmouse", .open = qemu_chr_open_msmouse }, - { .name = "vc", .open = text_console_init }, + { .name = "vc", .open = vc_init }, + { .name = "memory", .open = qemu_chr_open_ringbuf }, #ifdef _WIN32 { .name = "file", .open = qemu_chr_open_win_file_out }, { .name = "pipe", .open = qemu_chr_open_win_pipe }, @@ -2745,57 +3150,60 @@ static const struct { #else { .name = "file", .open = qemu_chr_open_file_out }, { .name = "pipe", .open = qemu_chr_open_pipe }, - { .name = "pty", .open = qemu_chr_open_pty }, { .name = "stdio", .open = qemu_chr_open_stdio }, #endif #ifdef CONFIG_BRLAPI { .name = "braille", .open = chr_baum_init }, #endif -#if defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) \ - || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) \ - || defined(__FreeBSD_kernel__) +#ifdef HAVE_CHARDEV_TTY { .name = "tty", .open = qemu_chr_open_tty }, + { .name = "serial", .open = qemu_chr_open_tty }, + { .name = "pty", .open = qemu_chr_open_pty }, #endif -#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) \ - || defined(__FreeBSD_kernel__) +#ifdef HAVE_CHARDEV_PARPORT + { .name = "parallel", .open = qemu_chr_open_pp }, { .name = "parport", .open = qemu_chr_open_pp }, #endif #ifdef CONFIG_SPICE { .name = "spicevmc", .open = qemu_chr_open_spice }, +#if SPICE_SERVER_VERSION >= 0x000c02 + { .name = "spiceport", .open = qemu_chr_open_spice_port }, +#endif #endif }; CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts, - void (*init)(struct CharDriverState *s)) + void (*init)(struct CharDriverState *s), + Error **errp) { CharDriverState *chr; int i; if (qemu_opts_id(opts) == NULL) { - fprintf(stderr, "chardev: no id specified\n"); - return NULL; + error_setg(errp, "chardev: no id specified"); + goto err; } if (qemu_opt_get(opts, "backend") == NULL) { - fprintf(stderr, "chardev: \"%s\" missing backend\n", - qemu_opts_id(opts)); - return NULL; + error_setg(errp, "chardev: \"%s\" missing backend", + qemu_opts_id(opts)); + goto err; } for (i = 0; i < ARRAY_SIZE(backend_table); i++) { if (strcmp(backend_table[i].name, qemu_opt_get(opts, "backend")) == 0) break; } if (i == ARRAY_SIZE(backend_table)) { - fprintf(stderr, "chardev: backend \"%s\" not found\n", - qemu_opt_get(opts, "backend")); - return NULL; + error_setg(errp, "chardev: backend \"%s\" not found", + qemu_opt_get(opts, "backend")); + goto err; } chr = backend_table[i].open(opts); if (!chr) { - fprintf(stderr, "chardev: opening backend \"%s\" failed\n", - qemu_opt_get(opts, "backend")); - return NULL; + error_setg(errp, "chardev: opening backend \"%s\" failed", + qemu_opt_get(opts, "backend")); + goto err; } if (!chr->filename) @@ -2816,7 +3224,12 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts, chr->avail_connections = 1; } chr->label = g_strdup(qemu_opts_id(opts)); + chr->opts = opts; return chr; + +err: + qemu_opts_del(opts); + return NULL; } CharDriverState *qemu_chr_new(const char *label, const char *filename, void (*init)(struct CharDriverState *s)) @@ -2824,6 +3237,7 @@ CharDriverState *qemu_chr_new(const char *label, const char *filename, void (*in const char *p; CharDriverState *chr; QemuOpts *opts; + Error *err = NULL; if (strstart(filename, "chardev:", &p)) { return qemu_chr_find(p); @@ -2833,11 +3247,14 @@ CharDriverState *qemu_chr_new(const char *label, const char *filename, void (*in if (!opts) return NULL; - chr = qemu_chr_new_from_opts(opts, init); + chr = qemu_chr_new_from_opts(opts, init, &err); + if (error_is_set(&err)) { + fprintf(stderr, "%s\n", error_get_pretty(err)); + error_free(err); + } if (chr && qemu_opt_get_bool(opts, "mux", 0)) { monitor_init(chr, MONITOR_USE_READLINE); } - qemu_opts_del(opts); return chr; } @@ -2865,10 +3282,14 @@ void qemu_chr_fe_close(struct CharDriverState *chr) void qemu_chr_delete(CharDriverState *chr) { QTAILQ_REMOVE(&chardevs, chr, next); - if (chr->chr_close) + if (chr->chr_close) { chr->chr_close(chr); + } g_free(chr->filename); g_free(chr->label); + if (chr->opts) { + qemu_opts_del(chr->opts); + } g_free(chr); } @@ -2911,3 +3332,284 @@ CharDriverState *qemu_char_get_next_serial(void) return serial_hds[next_serial++]; } +QemuOptsList qemu_chardev_opts = { + .name = "chardev", + .implied_opt_name = "backend", + .head = QTAILQ_HEAD_INITIALIZER(qemu_chardev_opts.head), + .desc = { + { + .name = "backend", + .type = QEMU_OPT_STRING, + },{ + .name = "path", + .type = QEMU_OPT_STRING, + },{ + .name = "host", + .type = QEMU_OPT_STRING, + },{ + .name = "port", + .type = QEMU_OPT_STRING, + },{ + .name = "localaddr", + .type = QEMU_OPT_STRING, + },{ + .name = "localport", + .type = QEMU_OPT_STRING, + },{ + .name = "to", + .type = QEMU_OPT_NUMBER, + },{ + .name = "ipv4", + .type = QEMU_OPT_BOOL, + },{ + .name = "ipv6", + .type = QEMU_OPT_BOOL, + },{ + .name = "wait", + .type = QEMU_OPT_BOOL, + },{ + .name = "server", + .type = QEMU_OPT_BOOL, + },{ + .name = "delay", + .type = QEMU_OPT_BOOL, + },{ + .name = "telnet", + .type = QEMU_OPT_BOOL, + },{ + .name = "width", + .type = QEMU_OPT_NUMBER, + },{ + .name = "height", + .type = QEMU_OPT_NUMBER, + },{ + .name = "cols", + .type = QEMU_OPT_NUMBER, + },{ + .name = "rows", + .type = QEMU_OPT_NUMBER, + },{ + .name = "mux", + .type = QEMU_OPT_BOOL, + },{ + .name = "signal", + .type = QEMU_OPT_BOOL, + },{ + .name = "name", + .type = QEMU_OPT_STRING, + },{ + .name = "debug", + .type = QEMU_OPT_NUMBER, + },{ + .name = "size", + .type = QEMU_OPT_SIZE, + }, + { /* end of list */ } + }, +}; + +#ifdef _WIN32 + +static CharDriverState *qmp_chardev_open_file(ChardevFile *file, Error **errp) +{ + HANDLE out; + + if (file->in) { + error_setg(errp, "input file not supported"); + return NULL; + } + + out = CreateFile(file->out, GENERIC_WRITE, FILE_SHARE_READ, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (out == INVALID_HANDLE_VALUE) { + error_setg(errp, "open %s failed", file->out); + return NULL; + } + return qemu_chr_open_win_file(out); +} + +static CharDriverState *qmp_chardev_open_serial(ChardevHostdev *serial, + Error **errp) +{ + return qemu_chr_open_win_path(serial->device); +} + +static CharDriverState *qmp_chardev_open_parallel(ChardevHostdev *parallel, + Error **errp) +{ + error_setg(errp, "character device backend type 'parallel' not supported"); + return NULL; +} + +#else /* WIN32 */ + +static int qmp_chardev_open_file_source(char *src, int flags, + Error **errp) +{ + int fd = -1; + + TFR(fd = qemu_open(src, flags, 0666)); + if (fd == -1) { + error_setg(errp, "open %s: %s", src, strerror(errno)); + } + return fd; +} + +static CharDriverState *qmp_chardev_open_file(ChardevFile *file, Error **errp) +{ + int flags, in = -1, out = -1; + + flags = O_WRONLY | O_TRUNC | O_CREAT | O_BINARY; + out = qmp_chardev_open_file_source(file->out, flags, errp); + if (error_is_set(errp)) { + return NULL; + } + + if (file->in) { + flags = O_RDONLY; + in = qmp_chardev_open_file_source(file->in, flags, errp); + if (error_is_set(errp)) { + qemu_close(out); + return NULL; + } + } + + return qemu_chr_open_fd(in, out); +} + +static CharDriverState *qmp_chardev_open_serial(ChardevHostdev *serial, + Error **errp) +{ +#ifdef HAVE_CHARDEV_TTY + int fd; + + fd = qmp_chardev_open_file_source(serial->device, O_RDWR, errp); + if (error_is_set(errp)) { + return NULL; + } + socket_set_nonblock(fd); + return qemu_chr_open_tty_fd(fd); +#else + error_setg(errp, "character device backend type 'serial' not supported"); + return NULL; +#endif +} + +static CharDriverState *qmp_chardev_open_parallel(ChardevHostdev *parallel, + Error **errp) +{ +#ifdef HAVE_CHARDEV_PARPORT + int fd; + + fd = qmp_chardev_open_file_source(parallel->device, O_RDWR, errp); + if (error_is_set(errp)) { + return NULL; + } + return qemu_chr_open_pp_fd(fd); +#else + error_setg(errp, "character device backend type 'parallel' not supported"); + return NULL; +#endif +} + +#endif /* WIN32 */ + +static CharDriverState *qmp_chardev_open_socket(ChardevSocket *sock, + Error **errp) +{ + SocketAddress *addr = sock->addr; + bool do_nodelay = sock->has_nodelay ? sock->nodelay : false; + bool is_listen = sock->has_server ? sock->server : true; + bool is_telnet = sock->has_telnet ? sock->telnet : false; + bool is_waitconnect = sock->has_wait ? sock->wait : false; + int fd; + + if (is_listen) { + fd = socket_listen(addr, errp); + } else { + fd = socket_connect(addr, errp, NULL, NULL); + } + if (error_is_set(errp)) { + return NULL; + } + return qemu_chr_open_socket_fd(fd, do_nodelay, is_listen, + is_telnet, is_waitconnect, errp); +} + +ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend, + Error **errp) +{ + ChardevReturn *ret = g_new0(ChardevReturn, 1); + CharDriverState *chr = NULL; + + chr = qemu_chr_find(id); + if (chr) { + error_setg(errp, "Chardev '%s' already exists", id); + g_free(ret); + return NULL; + } + + switch (backend->kind) { + case CHARDEV_BACKEND_KIND_FILE: + chr = qmp_chardev_open_file(backend->file, errp); + break; + case CHARDEV_BACKEND_KIND_SERIAL: + chr = qmp_chardev_open_serial(backend->serial, errp); + break; + case CHARDEV_BACKEND_KIND_PARALLEL: + chr = qmp_chardev_open_parallel(backend->parallel, errp); + break; + case CHARDEV_BACKEND_KIND_SOCKET: + chr = qmp_chardev_open_socket(backend->socket, errp); + break; +#ifdef HAVE_CHARDEV_TTY + case CHARDEV_BACKEND_KIND_PTY: + { + /* qemu_chr_open_pty sets "path" in opts */ + QemuOpts *opts; + opts = qemu_opts_create_nofail(qemu_find_opts("chardev")); + chr = qemu_chr_open_pty(opts); + ret->pty = g_strdup(qemu_opt_get(opts, "path")); + ret->has_pty = true; + qemu_opts_del(opts); + break; + } +#endif + case CHARDEV_BACKEND_KIND_NULL: + chr = qemu_chr_open_null(NULL); + break; + default: + error_setg(errp, "unknown chardev backend (%d)", backend->kind); + break; + } + + if (chr == NULL && !error_is_set(errp)) { + error_setg(errp, "Failed to create chardev"); + } + if (chr) { + chr->label = g_strdup(id); + chr->avail_connections = 1; + QTAILQ_INSERT_TAIL(&chardevs, chr, next); + return ret; + } else { + g_free(ret); + return NULL; + } +} + +void qmp_chardev_remove(const char *id, Error **errp) +{ + CharDriverState *chr; + + chr = qemu_chr_find(id); + if (NULL == chr) { + error_setg(errp, "Chardev '%s' not found", id); + return; + } + if (chr->chr_can_read || chr->chr_read || + chr->chr_event || chr->handler_opaque) { + error_setg(errp, "Chardev '%s' is busy", id); + return; + } + qemu_chr_delete(chr); +}