X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/6f1de6b70d857d5e316ae6fd908f52818b827b08..2f4aa2329945c741e10fd5b6631e99a4be25ea60:/qemu-char.c diff --git a/qemu-char.c b/qemu-char.c index 8575c54c98..fb456cec34 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -32,14 +32,14 @@ #include "sysemu/char.h" #include "hw/usb.h" #include "qmp-commands.h" -#include "qapi/qmp-input-visitor.h" -#include "qapi/qmp-output-visitor.h" +#include "qapi/clone-visitor.h" #include "qapi-visit.h" #include "qemu/base64.h" #include "io/channel-socket.h" #include "io/channel-file.h" #include "io/channel-tls.h" #include "sysemu/replay.h" +#include "qemu/help_option.h" #include @@ -441,17 +441,20 @@ void qemu_chr_fe_printf(CharDriverState *s, const char *fmt, ...) va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); - qemu_chr_fe_write(s, (uint8_t *)buf, strlen(buf)); + /* XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks */ + qemu_chr_fe_write_all(s, (uint8_t *)buf, strlen(buf)); va_end(ap); } static void remove_fd_in_watch(CharDriverState *chr); -void qemu_chr_add_handlers(CharDriverState *s, - IOCanReadHandler *fd_can_read, - IOReadHandler *fd_read, - IOEventHandler *fd_event, - void *opaque) +void qemu_chr_add_handlers_full(CharDriverState *s, + IOCanReadHandler *fd_can_read, + IOReadHandler *fd_read, + IOEventHandler *fd_event, + void *opaque, + GMainContext *context) { int fe_open; @@ -465,8 +468,9 @@ void qemu_chr_add_handlers(CharDriverState *s, s->chr_read = fd_read; s->chr_event = fd_event; s->handler_opaque = opaque; - if (fe_open && s->chr_update_read_handler) - s->chr_update_read_handler(s); + if (fe_open && s->chr_update_read_handler) { + s->chr_update_read_handler(s, context); + } if (!s->explicit_fe_open) { qemu_chr_fe_set_open(s, fe_open); @@ -479,6 +483,16 @@ void qemu_chr_add_handlers(CharDriverState *s, } } +void qemu_chr_add_handlers(CharDriverState *s, + IOCanReadHandler *fd_can_read, + IOReadHandler *fd_read, + IOEventHandler *fd_event, + void *opaque) +{ + qemu_chr_add_handlers_full(s, fd_can_read, fd_read, + fd_event, opaque, NULL); +} + static int null_chr_write(CharDriverState *chr, const uint8_t *buf, int len) { return len; @@ -557,7 +571,9 @@ static int mux_chr_write(CharDriverState *chr, const uint8_t *buf, int len) (secs / 60) % 60, secs % 60, (int)(ti % 1000)); - qemu_chr_fe_write(d->drv, (uint8_t *)buf1, strlen(buf1)); + /* XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks */ + qemu_chr_fe_write_all(d->drv, (uint8_t *)buf1, strlen(buf1)); d->linestart = 0; } ret += qemu_chr_fe_write(d->drv, buf+i, 1); @@ -595,13 +611,15 @@ static void mux_print_help(CharDriverState *chr) "\n\rEscape-Char set to Ascii: 0x%02x\n\r\n\r", term_escape_char); } - qemu_chr_fe_write(chr, (uint8_t *)cbuf, strlen(cbuf)); + /* XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks */ + qemu_chr_fe_write_all(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] == '%') - qemu_chr_fe_write(chr, (uint8_t *)ebuf, strlen(ebuf)); + qemu_chr_fe_write_all(chr, (uint8_t *)ebuf, strlen(ebuf)); else - qemu_chr_fe_write(chr, (uint8_t *)&mux_help[i][j], 1); + qemu_chr_fe_write_all(chr, (uint8_t *)&mux_help[i][j], 1); } } } @@ -626,7 +644,7 @@ static int mux_proc_byte(CharDriverState *chr, MuxDriver *d, int ch) case 'x': { const char *term = "QEMU: Terminated\n\r"; - qemu_chr_fe_write(chr, (uint8_t *)term, strlen(term)); + qemu_chr_fe_write_all(chr, (uint8_t *)term, strlen(term)); exit(0); break; } @@ -716,7 +734,8 @@ static void mux_chr_event(void *opaque, int event) mux_chr_send_event(d, i, event); } -static void mux_chr_update_read_handler(CharDriverState *chr) +static void mux_chr_update_read_handler(CharDriverState *chr, + GMainContext *context) { MuxDriver *d = chr->opaque; @@ -730,8 +749,10 @@ static void mux_chr_update_read_handler(CharDriverState *chr) 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); + qemu_chr_add_handlers_full(d->drv, mux_chr_can_read, + mux_chr_read, + mux_chr_event, + chr, context); } if (d->focus != -1) { mux_chr_send_event(d, d->focus, CHR_EVENT_MUX_OUT); @@ -787,6 +808,13 @@ static GSource *mux_chr_add_watch(CharDriverState *s, GIOCondition cond) return d->drv->chr_add_watch(d->drv, cond); } +static void mux_chr_close(struct CharDriverState *chr) +{ + MuxDriver *d = chr->opaque; + + g_free(d); +} + static CharDriverState *qemu_chr_open_mux(const char *id, ChardevBackend *backend, ChardevReturn *ret, Error **errp) @@ -811,6 +839,7 @@ static CharDriverState *qemu_chr_open_mux(const char *id, chr->opaque = d; d->drv = drv; d->focus = -1; + chr->chr_close = mux_chr_close; chr->chr_write = mux_chr_write; chr->chr_update_read_handler = mux_chr_update_read_handler; chr->chr_accept_input = mux_chr_accept_input; @@ -839,6 +868,7 @@ typedef struct IOWatchPoll IOCanReadHandler *fd_can_read; GSourceFunc fd_read; void *opaque; + GMainContext *context; } IOWatchPoll; static IOWatchPoll *io_watch_poll_from_source(GSource *source) @@ -846,7 +876,8 @@ static IOWatchPoll *io_watch_poll_from_source(GSource *source) return container_of(source, IOWatchPoll, parent); } -static gboolean io_watch_poll_prepare(GSource *source, gint *timeout_) +static gboolean io_watch_poll_prepare(GSource *source, + gint *timeout_) { IOWatchPoll *iwp = io_watch_poll_from_source(source); bool now_active = iwp->fd_can_read(iwp->opaque) > 0; @@ -859,7 +890,7 @@ static gboolean io_watch_poll_prepare(GSource *source, gint *timeout_) iwp->src = qio_channel_create_watch( iwp->ioc, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL); g_source_set_callback(iwp->src, iwp->fd_read, iwp->opaque, NULL); - g_source_attach(iwp->src, NULL); + g_source_attach(iwp->src, iwp->context); } else { g_source_destroy(iwp->src); g_source_unref(iwp->src); @@ -906,19 +937,22 @@ static GSourceFuncs io_watch_poll_funcs = { static guint io_add_watch_poll(QIOChannel *ioc, IOCanReadHandler *fd_can_read, QIOChannelFunc fd_read, - gpointer user_data) + gpointer user_data, + GMainContext *context) { IOWatchPoll *iwp; int tag; - iwp = (IOWatchPoll *) g_source_new(&io_watch_poll_funcs, sizeof(IOWatchPoll)); + iwp = (IOWatchPoll *) g_source_new(&io_watch_poll_funcs, + sizeof(IOWatchPoll)); iwp->fd_can_read = fd_can_read; iwp->opaque = user_data; iwp->ioc = ioc; iwp->fd_read = (GSourceFunc) fd_read; iwp->src = NULL; + iwp->context = context; - tag = g_source_attach(&iwp->parent, NULL); + tag = g_source_attach(&iwp->parent, context); g_source_unref(&iwp->parent); return tag; } @@ -1050,7 +1084,8 @@ static GSource *fd_chr_add_watch(CharDriverState *chr, GIOCondition cond) return qio_channel_create_watch(s->ioc_out, cond); } -static void fd_chr_update_read_handler(CharDriverState *chr) +static void fd_chr_update_read_handler(CharDriverState *chr, + GMainContext *context) { FDCharDriver *s = chr->opaque; @@ -1058,7 +1093,8 @@ static void fd_chr_update_read_handler(CharDriverState *chr) if (s->ioc_in) { chr->fd_in_tag = io_add_watch_poll(s->ioc_in, fd_chr_read_poll, - fd_chr_read, chr); + fd_chr_read, chr, + context); } } @@ -1216,6 +1252,9 @@ static CharDriverState *qemu_chr_open_stdio(const char *id, sigaction(SIGCONT, &act, NULL); chr = qemu_chr_open_fd(0, 1, common, errp); + if (!chr) { + return NULL; + } chr->chr_close = qemu_chr_close_stdio; chr->chr_set_echo = qemu_chr_set_echo_stdio; if (opts->has_signal) { @@ -1302,7 +1341,8 @@ static void pty_chr_update_read_handler_locked(CharDriverState *chr) } } -static void pty_chr_update_read_handler(CharDriverState *chr) +static void pty_chr_update_read_handler(CharDriverState *chr, + GMainContext *context) { qemu_mutex_lock(&chr->chr_write_lock); pty_chr_update_read_handler_locked(chr); @@ -1406,7 +1446,8 @@ static void pty_chr_state(CharDriverState *chr, int connected) if (!chr->fd_in_tag) { chr->fd_in_tag = io_add_watch_poll(s->ioc, pty_chr_read_poll, - pty_chr_read, chr); + pty_chr_read, + chr, NULL); } } } @@ -1672,6 +1713,9 @@ static CharDriverState *qemu_chr_open_tty_fd(int fd, tty_serial_init(fd, 115200, 'N', 8, 1); chr = qemu_chr_open_fd(fd, fd, backend, errp); + if (!chr) { + return NULL; + } chr->chr_ioctl = tty_serial_ioctl; chr->chr_close = qemu_chr_close_tty; return chr; @@ -2545,7 +2589,8 @@ static gboolean udp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) return TRUE; } -static void udp_chr_update_read_handler(CharDriverState *chr) +static void udp_chr_update_read_handler(CharDriverState *chr, + GMainContext *context) { NetCharDriver *s = chr->opaque; @@ -2553,7 +2598,8 @@ static void udp_chr_update_read_handler(CharDriverState *chr) if (s->ioc) { chr->fd_in_tag = io_add_watch_poll(s->ioc, udp_chr_read_poll, - udp_chr_read, chr); + udp_chr_read, chr, + context); } } @@ -2761,13 +2807,16 @@ static int tcp_set_msgfds(CharDriverState *chr, int *fds, int num) { TCPCharDriver *s = chr->opaque; - if (!qio_channel_has_feature(s->ioc, - QIO_CHANNEL_FEATURE_FD_PASS)) { - return -1; - } /* clear old pending fd array */ g_free(s->write_msgfds); s->write_msgfds = NULL; + s->write_msgfds_num = 0; + + if (!s->connected || + !qio_channel_has_feature(s->ioc, + QIO_CHANNEL_FEATURE_FD_PASS)) { + return -1; + } if (num) { s->write_msgfds = g_new(int, num); @@ -2842,19 +2891,24 @@ static GSource *tcp_chr_add_watch(CharDriverState *chr, GIOCondition cond) return qio_channel_create_watch(s->ioc, cond); } -static void tcp_chr_disconnect(CharDriverState *chr) +static void tcp_chr_free_connection(CharDriverState *chr) { TCPCharDriver *s = chr->opaque; + int i; if (!s->connected) { return; } - s->connected = 0; - if (s->listen_ioc) { - s->listen_tag = qio_channel_add_watch( - QIO_CHANNEL(s->listen_ioc), G_IO_IN, tcp_chr_accept, chr, NULL); + if (s->read_msgfds_num) { + for (i = 0; i < s->read_msgfds_num; i++) { + close(s->read_msgfds[i]); + } + g_free(s->read_msgfds); + s->read_msgfds = NULL; + s->read_msgfds_num = 0; } + tcp_set_msgfds(chr, NULL, 0); remove_fd_in_watch(chr); object_unref(OBJECT(s->sioc)); @@ -2862,6 +2916,24 @@ static void tcp_chr_disconnect(CharDriverState *chr) object_unref(OBJECT(s->ioc)); s->ioc = NULL; g_free(chr->filename); + chr->filename = NULL; + s->connected = 0; +} + +static void tcp_chr_disconnect(CharDriverState *chr) +{ + TCPCharDriver *s = chr->opaque; + + if (!s->connected) { + return; + } + + tcp_chr_free_connection(chr); + + if (s->listen_ioc) { + s->listen_tag = qio_channel_add_watch( + QIO_CHANNEL(s->listen_ioc), G_IO_IN, tcp_chr_accept, chr, NULL); + } chr->filename = SocketAddress_to_str("disconnected:", s->addr, s->is_listen, s->is_telnet); qemu_chr_be_event(chr, CHR_EVENT_CLOSED); @@ -2930,12 +3002,14 @@ static void tcp_chr_connect(void *opaque) if (s->ioc) { chr->fd_in_tag = io_add_watch_poll(s->ioc, tcp_chr_read_poll, - tcp_chr_read, chr); + tcp_chr_read, + chr, NULL); } qemu_chr_be_generic_open(chr); } -static void tcp_chr_update_read_handler(CharDriverState *chr) +static void tcp_chr_update_read_handler(CharDriverState *chr, + GMainContext *context) { TCPCharDriver *s = chr->opaque; @@ -2947,7 +3021,8 @@ static void tcp_chr_update_read_handler(CharDriverState *chr) if (s->ioc) { chr->fd_in_tag = io_add_watch_poll(s->ioc, tcp_chr_read_poll, - tcp_chr_read, chr); + tcp_chr_read, chr, + context); } } @@ -3138,20 +3213,54 @@ static gboolean tcp_chr_accept(QIOChannel *channel, return TRUE; } +static int tcp_chr_wait_connected(CharDriverState *chr, Error **errp) +{ + TCPCharDriver *s = chr->opaque; + QIOChannelSocket *sioc; + + /* It can't wait on s->connected, since it is set asynchronously + * in TLS and telnet cases, only wait for an accepted socket */ + while (!s->ioc) { + if (s->is_listen) { + fprintf(stderr, "QEMU waiting for connection on: %s\n", + chr->filename); + qio_channel_set_blocking(QIO_CHANNEL(s->listen_ioc), true, NULL); + tcp_chr_accept(QIO_CHANNEL(s->listen_ioc), G_IO_IN, chr); + qio_channel_set_blocking(QIO_CHANNEL(s->listen_ioc), false, NULL); + } else { + sioc = qio_channel_socket_new(); + if (qio_channel_socket_connect_sync(sioc, s->addr, errp) < 0) { + object_unref(OBJECT(sioc)); + return -1; + } + tcp_chr_new_client(chr, sioc); + object_unref(OBJECT(sioc)); + } + } + + return 0; +} + +int qemu_chr_wait_connected(CharDriverState *chr, Error **errp) +{ + if (chr->chr_wait_connected) { + return chr->chr_wait_connected(chr, errp); + } + + return 0; +} + static void tcp_chr_close(CharDriverState *chr) { TCPCharDriver *s = chr->opaque; - int i; + + tcp_chr_free_connection(chr); if (s->reconnect_timer) { g_source_remove(s->reconnect_timer); s->reconnect_timer = 0; } qapi_free_SocketAddress(s->addr); - remove_fd_in_watch(chr); - if (s->ioc) { - object_unref(OBJECT(s->ioc)); - } if (s->listen_tag) { g_source_remove(s->listen_tag); s->listen_tag = 0; @@ -3159,18 +3268,9 @@ static void tcp_chr_close(CharDriverState *chr) if (s->listen_ioc) { object_unref(OBJECT(s->listen_ioc)); } - if (s->read_msgfds_num) { - for (i = 0; i < s->read_msgfds_num; i++) { - close(s->read_msgfds[i]); - } - g_free(s->read_msgfds); - } if (s->tls_creds) { object_unref(OBJECT(s->tls_creds)); } - if (s->write_msgfds_num) { - g_free(s->write_msgfds); - } g_free(s); qemu_chr_be_event(chr, CHR_EVENT_CLOSED); } @@ -3821,16 +3921,26 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts, const char *id = qemu_opts_id(opts); char *bid = NULL; - if (id == NULL) { - error_setg(errp, "chardev: no id specified"); - goto err; - } - if (qemu_opt_get(opts, "backend") == NULL) { error_setg(errp, "chardev: \"%s\" missing backend", qemu_opts_id(opts)); goto err; } + + if (is_help_option(qemu_opt_get(opts, "backend"))) { + fprintf(stderr, "Available chardev backend types:\n"); + for (i = backends; i; i = i->next) { + cd = i->data; + fprintf(stderr, "%s\n", cd->name); + } + exit(!is_help_option(qemu_opt_get(opts, "backend"))); + } + + if (id == NULL) { + error_setg(errp, "chardev: no id specified"); + goto err; + } + for (i = backends; i; i = i->next) { cd = i->data; @@ -4198,14 +4308,26 @@ static CharDriverState *qmp_chardev_open_file(const char *id, ChardevFile *file = backend->u.file.data; ChardevCommon *common = qapi_ChardevFile_base(file); HANDLE out; + DWORD accessmode; + DWORD flags; if (file->has_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 (file->has_append && file->append) { + /* Append to file if it already exists. */ + accessmode = FILE_GENERIC_WRITE & ~FILE_WRITE_DATA; + flags = OPEN_ALWAYS; + } else { + /* Truncate file if it already exists. */ + accessmode = GENERIC_WRITE; + flags = CREATE_ALWAYS; + } + + out = CreateFile(file->out, accessmode, FILE_SHARE_READ, NULL, flags, + FILE_ATTRIBUTE_NORMAL, NULL); if (out == INVALID_HANDLE_VALUE) { error_setg(errp, "open %s failed", file->out); return NULL; @@ -4389,9 +4511,10 @@ static CharDriverState *qmp_chardev_open_socket(const char *id, } } - qapi_copy_SocketAddress(&s->addr, sock->addr); + s->addr = QAPI_CLONE(SocketAddress, sock->addr); chr->opaque = s; + chr->chr_wait_connected = tcp_chr_wait_connected; chr->chr_write = tcp_chr_write; chr->chr_sync_read = tcp_chr_sync_read; chr->chr_close = tcp_chr_close; @@ -4415,32 +4538,30 @@ static CharDriverState *qmp_chardev_open_socket(const char *id, s->reconnect_time = reconnect; } - sioc = qio_channel_socket_new(); if (s->reconnect_time) { + sioc = qio_channel_socket_new(); qio_channel_socket_connect_async(sioc, s->addr, qemu_chr_socket_connected, chr, NULL); - } else if (s->is_listen) { - if (qio_channel_socket_listen_sync(sioc, s->addr, errp) < 0) { - goto error; - } - s->listen_ioc = sioc; - if (is_waitconnect) { - fprintf(stderr, "QEMU waiting for connection on: %s\n", - chr->filename); - tcp_chr_accept(QIO_CHANNEL(s->listen_ioc), G_IO_IN, chr); - } - qio_channel_set_blocking(QIO_CHANNEL(s->listen_ioc), false, NULL); - if (!s->ioc) { - s->listen_tag = qio_channel_add_watch( - QIO_CHANNEL(s->listen_ioc), G_IO_IN, tcp_chr_accept, chr, NULL); - } } else { - if (qio_channel_socket_connect_sync(sioc, s->addr, errp) < 0) { + if (s->is_listen) { + sioc = qio_channel_socket_new(); + if (qio_channel_socket_listen_sync(sioc, s->addr, errp) < 0) { + goto error; + } + s->listen_ioc = sioc; + if (is_waitconnect && + qemu_chr_wait_connected(chr, errp) < 0) { + goto error; + } + if (!s->ioc) { + s->listen_tag = qio_channel_add_watch( + QIO_CHANNEL(s->listen_ioc), G_IO_IN, + tcp_chr_accept, chr, NULL); + } + } else if (qemu_chr_wait_connected(chr, errp) < 0) { goto error; } - tcp_chr_new_client(chr, sioc); - object_unref(OBJECT(sioc)); } return chr; @@ -4549,6 +4670,15 @@ void qmp_chardev_remove(const char *id, Error **errp) qemu_chr_delete(chr); } +void qemu_chr_cleanup(void) +{ + CharDriverState *chr, *tmp; + + QTAILQ_FOREACH_SAFE(chr, &chardevs, next, tmp) { + qemu_chr_delete(chr); + } +} + static void register_types(void) { register_char_driver("null", CHARDEV_BACKEND_KIND_NULL, NULL,