diff options
Diffstat (limited to 'chardev')
| -rw-r--r-- | chardev/char-fe.c | 35 | ||||
| -rw-r--r-- | chardev/char-mux.c | 19 | ||||
| -rw-r--r-- | chardev/char-pty.c | 56 | ||||
| -rw-r--r-- | chardev/char-serial.c | 2 | ||||
| -rw-r--r-- | chardev/char-socket.c | 490 | ||||
| -rw-r--r-- | chardev/char.c | 34 | ||||
| -rw-r--r-- | chardev/wctablet.c | 2 |
7 files changed, 415 insertions, 223 deletions
diff --git a/chardev/char-fe.c b/chardev/char-fe.c index a8931f7afd..f3530a90e6 100644 --- a/chardev/char-fe.c +++ b/chardev/char-fe.c @@ -246,14 +246,15 @@ void qemu_chr_fe_deinit(CharBackend *b, bool del) } } -void qemu_chr_fe_set_handlers(CharBackend *b, - IOCanReadHandler *fd_can_read, - IOReadHandler *fd_read, - IOEventHandler *fd_event, - BackendChangeHandler *be_change, - void *opaque, - GMainContext *context, - bool set_open) +void qemu_chr_fe_set_handlers_full(CharBackend *b, + IOCanReadHandler *fd_can_read, + IOReadHandler *fd_read, + IOEventHandler *fd_event, + BackendChangeHandler *be_change, + void *opaque, + GMainContext *context, + bool set_open, + bool sync_state) { Chardev *s; int fe_open; @@ -285,14 +286,24 @@ void qemu_chr_fe_set_handlers(CharBackend *b, qemu_chr_fe_take_focus(b); /* We're connecting to an already opened device, so let's make sure we also get the open event */ - if (s->be_open) { + if (sync_state && s->be_open) { qemu_chr_be_event(s, CHR_EVENT_OPENED); } } +} - if (CHARDEV_IS_MUX(s)) { - mux_chr_set_handlers(s, context); - } +void qemu_chr_fe_set_handlers(CharBackend *b, + IOCanReadHandler *fd_can_read, + IOReadHandler *fd_read, + IOEventHandler *fd_event, + BackendChangeHandler *be_change, + void *opaque, + GMainContext *context, + bool set_open) +{ + qemu_chr_fe_set_handlers_full(b, fd_can_read, fd_read, fd_event, be_change, + opaque, context, set_open, + true); } void qemu_chr_fe_take_focus(CharBackend *b) diff --git a/chardev/char-mux.c b/chardev/char-mux.c index 6055e76293..23aa82125d 100644 --- a/chardev/char-mux.c +++ b/chardev/char-mux.c @@ -278,18 +278,18 @@ static void char_mux_finalize(Object *obj) qemu_chr_fe_deinit(&d->chr, false); } -void mux_chr_set_handlers(Chardev *chr, GMainContext *context) +static void mux_chr_update_read_handlers(Chardev *chr) { MuxChardev *d = MUX_CHARDEV(chr); /* Fix up the real driver with mux routines */ - qemu_chr_fe_set_handlers(&d->chr, - mux_chr_can_read, - mux_chr_read, - mux_chr_event, - NULL, - chr, - context, true); + qemu_chr_fe_set_handlers_full(&d->chr, + mux_chr_can_read, + mux_chr_read, + mux_chr_event, + NULL, + chr, + chr->gcontext, true, false); } void mux_set_focus(Chardev *chr, int focus) @@ -367,7 +367,7 @@ static int open_muxes(Chardev *chr) * mark mux as OPENED so any new FEs will immediately receive * OPENED event */ - qemu_chr_be_event(chr, CHR_EVENT_OPENED); + chr->be_open = 1; return 0; } @@ -383,6 +383,7 @@ static void char_mux_class_init(ObjectClass *oc, void *data) cc->chr_add_watch = mux_chr_add_watch; cc->chr_be_event = mux_chr_be_event; cc->chr_machine_done = open_muxes; + cc->chr_update_read_handler = mux_chr_update_read_handlers; } static const TypeInfo char_mux_type_info = { diff --git a/chardev/char-pty.c b/chardev/char-pty.c index f681d637c1..b034332edd 100644 --- a/chardev/char-pty.c +++ b/chardev/char-pty.c @@ -36,15 +36,12 @@ typedef struct { QIOChannel *ioc; int read_bytes; - /* Protected by the Chardev chr_write_lock. */ int connected; GSource *timer_src; - GSource *open_source; } PtyChardev; #define PTY_CHARDEV(obj) OBJECT_CHECK(PtyChardev, (obj), TYPE_CHARDEV_PTY) -static void pty_chr_update_read_handler_locked(Chardev *chr); static void pty_chr_state(Chardev *chr, int connected); static void pty_chr_timer_cancel(PtyChardev *s) @@ -56,32 +53,19 @@ static void pty_chr_timer_cancel(PtyChardev *s) } } -static void pty_chr_open_src_cancel(PtyChardev *s) -{ - if (s->open_source) { - g_source_destroy(s->open_source); - g_source_unref(s->open_source); - s->open_source = NULL; - } -} - static gboolean pty_chr_timer(gpointer opaque) { struct Chardev *chr = CHARDEV(opaque); PtyChardev *s = PTY_CHARDEV(opaque); - qemu_mutex_lock(&chr->chr_write_lock); pty_chr_timer_cancel(s); - pty_chr_open_src_cancel(s); if (!s->connected) { /* Next poll ... */ - pty_chr_update_read_handler_locked(chr); + qemu_chr_be_update_read_handlers(chr, chr->gcontext); } - qemu_mutex_unlock(&chr->chr_write_lock); return FALSE; } -/* Called with chr_write_lock held. */ static void pty_chr_rearm_timer(Chardev *chr, int ms) { PtyChardev *s = PTY_CHARDEV(chr); @@ -94,8 +78,7 @@ static void pty_chr_rearm_timer(Chardev *chr, int ms) g_free(name); } -/* Called with chr_write_lock held. */ -static void pty_chr_update_read_handler_locked(Chardev *chr) +static void pty_chr_update_read_handler(Chardev *chr) { PtyChardev *s = PTY_CHARDEV(chr); GPollFD pfd; @@ -117,24 +100,12 @@ static void pty_chr_update_read_handler_locked(Chardev *chr) } } -static void pty_chr_update_read_handler(Chardev *chr) -{ - qemu_mutex_lock(&chr->chr_write_lock); - pty_chr_update_read_handler_locked(chr); - qemu_mutex_unlock(&chr->chr_write_lock); -} - -/* Called with chr_write_lock held. */ static int char_pty_chr_write(Chardev *chr, const uint8_t *buf, int len) { PtyChardev *s = PTY_CHARDEV(chr); if (!s->connected) { - /* guest sends data, check for (re-)connect */ - pty_chr_update_read_handler_locked(chr); - if (!s->connected) { - return len; - } + return len; } return io_channel_send(s->ioc, buf, len); } @@ -183,23 +154,11 @@ static gboolean pty_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) return TRUE; } -static gboolean qemu_chr_be_generic_open_func(gpointer opaque) -{ - Chardev *chr = CHARDEV(opaque); - PtyChardev *s = PTY_CHARDEV(opaque); - - s->open_source = NULL; - qemu_chr_be_event(chr, CHR_EVENT_OPENED); - return FALSE; -} - -/* Called with chr_write_lock held. */ static void pty_chr_state(Chardev *chr, int connected) { PtyChardev *s = PTY_CHARDEV(chr); if (!connected) { - pty_chr_open_src_cancel(s); remove_fd_in_watch(chr); s->connected = 0; /* (re-)connect poll interval for idle guests: once per second. @@ -209,13 +168,8 @@ static void pty_chr_state(Chardev *chr, int connected) } else { pty_chr_timer_cancel(s); if (!s->connected) { - g_assert(s->open_source == NULL); - s->open_source = g_idle_source_new(); s->connected = 1; - g_source_set_callback(s->open_source, - qemu_chr_be_generic_open_func, - chr, NULL); - g_source_attach(s->open_source, chr->gcontext); + qemu_chr_be_event(chr, CHR_EVENT_OPENED); } if (!chr->gsource) { chr->gsource = io_add_watch_poll(chr, s->ioc, @@ -231,11 +185,9 @@ static void char_pty_finalize(Object *obj) Chardev *chr = CHARDEV(obj); PtyChardev *s = PTY_CHARDEV(obj); - qemu_mutex_lock(&chr->chr_write_lock); pty_chr_state(chr, 0); object_unref(OBJECT(s->ioc)); pty_chr_timer_cancel(s); - qemu_mutex_unlock(&chr->chr_write_lock); qemu_chr_be_event(chr, CHR_EVENT_CLOSED); } diff --git a/chardev/char-serial.c b/chardev/char-serial.c index 3299b46853..a8bae31b8d 100644 --- a/chardev/char-serial.c +++ b/chardev/char-serial.c @@ -57,7 +57,7 @@ static void qmp_chardev_open_serial(Chardev *chr, static void tty_serial_init(int fd, int speed, int parity, int data_bits, int stop_bits) { - struct termios tty; + struct termios tty = {0}; speed_t spd; #if 0 diff --git a/chardev/char-socket.c b/chardev/char-socket.c index eaa8e8b68f..4fcdd8aedd 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -46,6 +46,12 @@ typedef struct { size_t buflen; } TCPChardevTelnetInit; +typedef enum { + TCP_CHARDEV_STATE_DISCONNECTED, + TCP_CHARDEV_STATE_CONNECTING, + TCP_CHARDEV_STATE_CONNECTED, +} TCPChardevState; + typedef struct { Chardev parent; QIOChannel *ioc; /* Client I/O channel */ @@ -53,7 +59,7 @@ typedef struct { QIONetListener *listener; GSource *hup_source; QCryptoTLSCreds *tls_creds; - int connected; + TCPChardevState state; int max_size; int do_telnetopt; int do_nodelay; @@ -74,6 +80,8 @@ typedef struct { GSource *reconnect_timer; int64_t reconnect_time; bool connect_err_reported; + + QIOTask *connect_task; } SocketChardev; #define SOCKET_CHARDEV(obj) \ @@ -82,6 +90,21 @@ typedef struct { static gboolean socket_reconnect_timeout(gpointer opaque); static void tcp_chr_telnet_init(Chardev *chr); +static void tcp_chr_change_state(SocketChardev *s, TCPChardevState state) +{ + switch (state) { + case TCP_CHARDEV_STATE_DISCONNECTED: + break; + case TCP_CHARDEV_STATE_CONNECTING: + assert(s->state == TCP_CHARDEV_STATE_DISCONNECTED); + break; + case TCP_CHARDEV_STATE_CONNECTED: + assert(s->state == TCP_CHARDEV_STATE_CONNECTING); + break; + } + s->state = state; +} + static void tcp_chr_reconn_timer_cancel(SocketChardev *s) { if (s->reconnect_timer) { @@ -96,7 +119,8 @@ static void qemu_chr_socket_restart_timer(Chardev *chr) SocketChardev *s = SOCKET_CHARDEV(chr); char *name; - assert(s->connected == 0); + assert(s->state == TCP_CHARDEV_STATE_DISCONNECTED); + assert(!s->reconnect_timer); name = g_strdup_printf("chardev-socket-reconnect-%s", chr->label); s->reconnect_timer = qemu_chr_timeout_add_ms(chr, s->reconnect_time * 1000, @@ -131,7 +155,7 @@ static int tcp_chr_write(Chardev *chr, const uint8_t *buf, int len) { SocketChardev *s = SOCKET_CHARDEV(chr); - if (s->connected) { + if (s->state == TCP_CHARDEV_STATE_CONNECTED) { int ret = io_channel_send_full(s->ioc, buf, len, s->write_msgfds, s->write_msgfds_num); @@ -164,7 +188,7 @@ static int tcp_chr_read_poll(void *opaque) { Chardev *chr = CHARDEV(opaque); SocketChardev *s = SOCKET_CHARDEV(opaque); - if (!s->connected) { + if (s->state != TCP_CHARDEV_STATE_CONNECTED) { return 0; } s->max_size = qemu_chr_be_can_write(chr); @@ -277,7 +301,7 @@ static int tcp_set_msgfds(Chardev *chr, int *fds, int num) s->write_msgfds = NULL; s->write_msgfds_num = 0; - if (!s->connected || + if ((s->state != TCP_CHARDEV_STATE_CONNECTED) || !qio_channel_has_feature(s->ioc, QIO_CHANNEL_FEATURE_FD_PASS)) { return -1; @@ -389,7 +413,7 @@ static void tcp_chr_free_connection(Chardev *chr) s->ioc = NULL; g_free(chr->filename); chr->filename = NULL; - s->connected = 0; + tcp_chr_change_state(s, TCP_CHARDEV_STATE_DISCONNECTED); } static const char *qemu_chr_socket_protocol(SocketChardev *s) @@ -442,12 +466,12 @@ static void update_disconnected_filename(SocketChardev *s) /* NB may be called even if tcp_chr_connect has not been * reached, due to TLS or telnet initialization failure, - * so can *not* assume s->connected == true + * so can *not* assume s->state == TCP_CHARDEV_STATE_CONNECTED */ static void tcp_chr_disconnect(Chardev *chr) { SocketChardev *s = SOCKET_CHARDEV(chr); - bool emit_close = s->connected; + bool emit_close = s->state == TCP_CHARDEV_STATE_CONNECTED; tcp_chr_free_connection(chr); @@ -471,7 +495,8 @@ static gboolean tcp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) uint8_t buf[CHR_READ_BUF_LEN]; int len, size; - if (!s->connected || s->max_size <= 0) { + if ((s->state != TCP_CHARDEV_STATE_CONNECTED) || + s->max_size <= 0) { return TRUE; } len = sizeof(buf); @@ -508,7 +533,7 @@ static int tcp_chr_sync_read(Chardev *chr, const uint8_t *buf, int len) SocketChardev *s = SOCKET_CHARDEV(chr); int size; - if (!s->connected) { + if (s->state != TCP_CHARDEV_STATE_CONNECTED) { return 0; } @@ -564,7 +589,7 @@ static void update_ioc_handlers(SocketChardev *s) { Chardev *chr = CHARDEV(s); - if (!s->connected) { + if (s->state != TCP_CHARDEV_STATE_CONNECTED) { return; } @@ -589,7 +614,7 @@ static void tcp_chr_connect(void *opaque) g_free(chr->filename); chr->filename = qemu_chr_compute_filename(s); - s->connected = 1; + tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTED); update_ioc_handlers(s); qemu_chr_be_event(chr, CHR_EVENT_OPENED); } @@ -828,7 +853,7 @@ static int tcp_chr_new_client(Chardev *chr, QIOChannelSocket *sioc) { SocketChardev *s = SOCKET_CHARDEV(chr); - if (s->ioc != NULL) { + if (s->state != TCP_CHARDEV_STATE_CONNECTING) { return -1; } @@ -865,11 +890,17 @@ static int tcp_chr_add_client(Chardev *chr, int fd) { int ret; QIOChannelSocket *sioc; + SocketChardev *s = SOCKET_CHARDEV(chr); + + if (s->state != TCP_CHARDEV_STATE_DISCONNECTED) { + return -1; + } sioc = qio_channel_socket_new_fd(fd, NULL); if (!sioc) { return -1; } + tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); tcp_chr_set_client_ioc_name(chr, sioc); ret = tcp_chr_new_client(chr, sioc); object_unref(OBJECT(sioc)); @@ -881,35 +912,125 @@ static void tcp_chr_accept(QIONetListener *listener, void *opaque) { Chardev *chr = CHARDEV(opaque); + SocketChardev *s = SOCKET_CHARDEV(chr); + tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); tcp_chr_set_client_ioc_name(chr, cioc); tcp_chr_new_client(chr, cioc); } -static int tcp_chr_wait_connected(Chardev *chr, Error **errp) + +static int tcp_chr_connect_client_sync(Chardev *chr, Error **errp) +{ + SocketChardev *s = SOCKET_CHARDEV(chr); + QIOChannelSocket *sioc = qio_channel_socket_new(); + tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); + tcp_chr_set_client_ioc_name(chr, sioc); + if (qio_channel_socket_connect_sync(sioc, s->addr, errp) < 0) { + tcp_chr_change_state(s, TCP_CHARDEV_STATE_DISCONNECTED); + object_unref(OBJECT(sioc)); + return -1; + } + tcp_chr_new_client(chr, sioc); + object_unref(OBJECT(sioc)); + return 0; +} + + +static void tcp_chr_accept_server_sync(Chardev *chr) { SocketChardev *s = SOCKET_CHARDEV(chr); QIOChannelSocket *sioc; + info_report("QEMU waiting for connection on: %s", + chr->filename); + tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); + sioc = qio_net_listener_wait_client(s->listener); + tcp_chr_set_client_ioc_name(chr, sioc); + tcp_chr_new_client(chr, sioc); + object_unref(OBJECT(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) { +static int tcp_chr_wait_connected(Chardev *chr, Error **errp) +{ + SocketChardev *s = SOCKET_CHARDEV(chr); + const char *opts[] = { "telnet", "tn3270", "websock", "tls-creds" }; + bool optset[] = { s->is_telnet, s->is_tn3270, s->is_websock, s->tls_creds }; + size_t i; + + QEMU_BUILD_BUG_ON(G_N_ELEMENTS(opts) != G_N_ELEMENTS(optset)); + for (i = 0; i < G_N_ELEMENTS(opts); i++) { + if (optset[i]) { + error_setg(errp, + "'%s' option is incompatible with waiting for " + "connection completion", opts[i]); + return -1; + } + } + + tcp_chr_reconn_timer_cancel(s); + + /* + * We expect states to be as follows: + * + * - server + * - wait -> CONNECTED + * - nowait -> DISCONNECTED + * - client + * - reconnect == 0 -> CONNECTED + * - reconnect != 0 -> CONNECTING + * + */ + if (s->state == TCP_CHARDEV_STATE_CONNECTING) { + if (!s->connect_task) { + error_setg(errp, + "Unexpected 'connecting' state without connect task " + "while waiting for connection completion"); + return -1; + } + /* + * tcp_chr_wait_connected should only ever be run from the + * main loop thread associated with chr->gcontext, otherwise + * qio_task_wait_thread has a dangerous race condition with + * free'ing of the s->connect_task object. + * + * Acquiring the main context doesn't 100% prove we're in + * the main loop thread, but it does at least guarantee + * that the main loop won't be executed by another thread + * avoiding the race condition with the task idle callback. + */ + g_main_context_acquire(chr->gcontext); + qio_task_wait_thread(s->connect_task); + g_main_context_release(chr->gcontext); + + /* + * The completion callback (qemu_chr_socket_connected) for + * s->connect_task should have set this to NULL by the time + * qio_task_wait_thread has returned. + */ + assert(!s->connect_task); + + /* + * NB we are *not* guaranteed to have "s->state == ..CONNECTED" + * at this point as this first connect may be failed, so + * allow the next loop to run regardless. + */ + } + + while (s->state != TCP_CHARDEV_STATE_CONNECTED) { if (s->is_listen) { - info_report("QEMU waiting for connection on: %s", - chr->filename); - sioc = qio_net_listener_wait_client(s->listener); - tcp_chr_set_client_ioc_name(chr, sioc); - tcp_chr_new_client(chr, sioc); - object_unref(OBJECT(sioc)); + tcp_chr_accept_server_sync(chr); } else { - sioc = qio_channel_socket_new(); - tcp_chr_set_client_ioc_name(chr, sioc); - if (qio_channel_socket_connect_sync(sioc, s->addr, errp) < 0) { - object_unref(OBJECT(sioc)); - return -1; + Error *err = NULL; + if (tcp_chr_connect_client_sync(chr, &err) < 0) { + if (s->reconnect_time) { + error_free(err); + g_usleep(s->reconnect_time * 1000ULL * 1000ULL); + } else { + error_propagate(errp, err); + return -1; + } } - tcp_chr_new_client(chr, sioc); - object_unref(OBJECT(sioc)); } } @@ -945,7 +1066,10 @@ static void qemu_chr_socket_connected(QIOTask *task, void *opaque) SocketChardev *s = SOCKET_CHARDEV(chr); Error *err = NULL; + s->connect_task = NULL; + if (qio_task_propagate_error(task, &err)) { + tcp_chr_change_state(s, TCP_CHARDEV_STATE_DISCONNECTED); check_report_connect_error(chr, err); error_free(err); goto cleanup; @@ -958,16 +1082,45 @@ cleanup: object_unref(OBJECT(sioc)); } -static void tcp_chr_connect_async(Chardev *chr) + +static void tcp_chr_connect_client_task(QIOTask *task, + gpointer opaque) +{ + QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task)); + SocketAddress *addr = opaque; + Error *err = NULL; + + qio_channel_socket_connect_sync(ioc, addr, &err); + + qio_task_set_error(task, err); +} + + +static void tcp_chr_connect_client_async(Chardev *chr) { SocketChardev *s = SOCKET_CHARDEV(chr); QIOChannelSocket *sioc; + tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING); sioc = qio_channel_socket_new(); tcp_chr_set_client_ioc_name(chr, sioc); - qio_channel_socket_connect_async(sioc, s->addr, - qemu_chr_socket_connected, - chr, NULL, chr->gcontext); + /* + * Normally code would use the qio_channel_socket_connect_async + * method which uses a QIOTask + qio_task_set_error internally + * to avoid blocking. The tcp_chr_wait_connected method, however, + * needs a way to synchronize with completion of the background + * connect task which can't be done with the QIOChannelSocket + * async APIs. Thus we must use QIOTask directly to implement + * the non-blocking concept locally. + */ + s->connect_task = qio_task_new(OBJECT(sioc), + qemu_chr_socket_connected, + chr, NULL); + qio_task_run_in_thread(s->connect_task, + tcp_chr_connect_client_task, + s->addr, + NULL, + chr->gcontext); } static gboolean socket_reconnect_timeout(gpointer opaque) @@ -982,11 +1135,138 @@ static gboolean socket_reconnect_timeout(gpointer opaque) return false; } - tcp_chr_connect_async(chr); + tcp_chr_connect_client_async(chr); return false; } + +static int qmp_chardev_open_socket_server(Chardev *chr, + bool is_telnet, + bool is_waitconnect, + Error **errp) +{ + SocketChardev *s = SOCKET_CHARDEV(chr); + char *name; + if (is_telnet) { + s->do_telnetopt = 1; + } + s->listener = qio_net_listener_new(); + + name = g_strdup_printf("chardev-tcp-listener-%s", chr->label); + qio_net_listener_set_name(s->listener, name); + g_free(name); + + if (qio_net_listener_open_sync(s->listener, s->addr, errp) < 0) { + object_unref(OBJECT(s->listener)); + s->listener = NULL; + return -1; + } + + qapi_free_SocketAddress(s->addr); + s->addr = socket_local_address(s->listener->sioc[0]->fd, errp); + update_disconnected_filename(s); + + if (is_waitconnect) { + tcp_chr_accept_server_sync(chr); + } else { + qio_net_listener_set_client_func_full(s->listener, + tcp_chr_accept, + chr, NULL, + chr->gcontext); + } + + return 0; +} + + +static int qmp_chardev_open_socket_client(Chardev *chr, + int64_t reconnect, + Error **errp) +{ + SocketChardev *s = SOCKET_CHARDEV(chr); + + if (reconnect > 0) { + s->reconnect_time = reconnect; + tcp_chr_connect_client_async(chr); + return 0; + } else { + return tcp_chr_connect_client_sync(chr, errp); + } +} + + +static bool qmp_chardev_validate_socket(ChardevSocket *sock, + SocketAddress *addr, + Error **errp) +{ + /* Validate any options which have a dependency on address type */ + switch (addr->type) { + case SOCKET_ADDRESS_TYPE_FD: + if (sock->has_reconnect) { + error_setg(errp, + "'reconnect' option is incompatible with " + "'fd' address type"); + return false; + } + if (sock->has_tls_creds && + !(sock->has_server && sock->server)) { + error_setg(errp, + "'tls_creds' option is incompatible with " + "'fd' address type as client"); + return false; + } + break; + + case SOCKET_ADDRESS_TYPE_UNIX: + if (sock->has_tls_creds) { + error_setg(errp, + "'tls_creds' option is incompatible with " + "'unix' address type"); + return false; + } + break; + + case SOCKET_ADDRESS_TYPE_INET: + break; + + case SOCKET_ADDRESS_TYPE_VSOCK: + if (sock->has_tls_creds) { + error_setg(errp, + "'tls_creds' option is incompatible with " + "'vsock' address type"); + return false; + } + + default: + break; + } + + /* Validate any options which have a dependancy on client vs server */ + if (!sock->has_server || sock->server) { + if (sock->has_reconnect) { + error_setg(errp, + "'reconnect' option is incompatible with " + "socket in server listen mode"); + return false; + } + } else { + if (sock->has_websocket && sock->websocket) { + error_setg(errp, "%s", "Websocket client is not implemented"); + return false; + } + if (sock->has_wait) { + error_setg(errp, "%s", + "'wait' option is incompatible with " + "socket in client connect mode"); + return false; + } + } + + return true; +} + + static void qmp_chardev_open_socket(Chardev *chr, ChardevBackend *backend, bool *be_opened, @@ -1001,14 +1281,8 @@ static void qmp_chardev_open_socket(Chardev *chr, bool is_waitconnect = sock->has_wait ? sock->wait : false; bool is_websock = sock->has_websocket ? sock->websocket : false; int64_t reconnect = sock->has_reconnect ? sock->reconnect : 0; - QIOChannelSocket *sioc = NULL; SocketAddress *addr; - if (!is_listen && is_websock) { - error_setg(errp, "%s", "Websocket client is not implemented"); - goto error; - } - s->is_listen = is_listen; s->is_telnet = is_telnet; s->is_tn3270 = is_tn3270; @@ -1021,7 +1295,7 @@ static void qmp_chardev_open_socket(Chardev *chr, if (!creds) { error_setg(errp, "No TLS credentials with id '%s'", sock->tls_creds); - goto error; + return; } s->tls_creds = (QCryptoTLSCreds *) object_dynamic_cast(creds, @@ -1029,30 +1303,30 @@ static void qmp_chardev_open_socket(Chardev *chr, if (!s->tls_creds) { error_setg(errp, "Object with id '%s' is not TLS credentials", sock->tls_creds); - goto error; + return; } object_ref(OBJECT(s->tls_creds)); if (is_listen) { if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { error_setg(errp, "%s", "Expected TLS credentials for server endpoint"); - goto error; + return; } } else { if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) { error_setg(errp, "%s", "Expected TLS credentials for client endpoint"); - goto error; + return; } } } s->addr = addr = socket_address_flatten(sock->addr); - if (sock->has_reconnect && addr->type == SOCKET_ADDRESS_TYPE_FD) { - error_setg(errp, "'reconnect' option is incompatible with 'fd'"); - goto error; + if (!qmp_chardev_validate_socket(sock, addr, errp)) { + return; } + qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE); /* TODO SOCKET_ADDRESS_FD where fd has AF_UNIX */ if (addr->type == SOCKET_ADDRESS_TYPE_UNIX) { @@ -1064,73 +1338,25 @@ static void qmp_chardev_open_socket(Chardev *chr, update_disconnected_filename(s); - if (is_listen) { - if (is_telnet || is_tn3270) { - s->do_telnetopt = 1; + if (s->is_listen) { + if (qmp_chardev_open_socket_server(chr, is_telnet || is_tn3270, + is_waitconnect, errp) < 0) { + return; } - } else if (reconnect > 0) { - s->reconnect_time = reconnect; - } - - if (s->reconnect_time) { - tcp_chr_connect_async(chr); } else { - if (s->is_listen) { - char *name; - s->listener = qio_net_listener_new(); - - name = g_strdup_printf("chardev-tcp-listener-%s", chr->label); - qio_net_listener_set_name(s->listener, name); - g_free(name); - - if (qio_net_listener_open_sync(s->listener, s->addr, errp) < 0) { - object_unref(OBJECT(s->listener)); - s->listener = NULL; - goto error; - } - - qapi_free_SocketAddress(s->addr); - s->addr = socket_local_address(s->listener->sioc[0]->fd, errp); - update_disconnected_filename(s); - - if (is_waitconnect && - qemu_chr_wait_connected(chr, errp) < 0) { - return; - } - if (!s->ioc) { - qio_net_listener_set_client_func_full(s->listener, - tcp_chr_accept, - chr, NULL, - chr->gcontext); - } - } else if (qemu_chr_wait_connected(chr, errp) < 0) { - goto error; + if (qmp_chardev_open_socket_client(chr, reconnect, errp) < 0) { + return; } } - - return; - -error: - if (sioc) { - object_unref(OBJECT(sioc)); - } } static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, Error **errp) { - bool is_listen = qemu_opt_get_bool(opts, "server", false); - bool is_waitconnect = is_listen && qemu_opt_get_bool(opts, "wait", true); - bool is_telnet = qemu_opt_get_bool(opts, "telnet", false); - bool is_tn3270 = qemu_opt_get_bool(opts, "tn3270", false); - bool is_websock = qemu_opt_get_bool(opts, "websocket", false); - bool do_nodelay = !qemu_opt_get_bool(opts, "delay", true); - int64_t reconnect = qemu_opt_get_number(opts, "reconnect", 0); const char *path = qemu_opt_get(opts, "path"); const char *host = qemu_opt_get(opts, "host"); const char *port = qemu_opt_get(opts, "port"); const char *fd = qemu_opt_get(opts, "fd"); - const char *tls_creds = qemu_opt_get(opts, "tls-creds"); SocketAddressLegacy *addr; ChardevSocket *sock; @@ -1140,45 +1366,39 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, return; } - backend->type = CHARDEV_BACKEND_KIND_SOCKET; - if (path) { - if (tls_creds) { - error_setg(errp, "TLS can only be used over TCP socket"); - return; - } - } else if (host) { - if (!port) { - error_setg(errp, "chardev: socket: no port given"); - return; - } - } else if (fd) { - /* We don't know what host to validate against when in client mode */ - if (tls_creds && !is_listen) { - error_setg(errp, "TLS can not be used with pre-opened client FD"); - return; - } - } else { - g_assert_not_reached(); + if (host && !port) { + error_setg(errp, "chardev: socket: no port given"); + return; } + backend->type = CHARDEV_BACKEND_KIND_SOCKET; sock = backend->u.socket.data = g_new0(ChardevSocket, 1); qemu_chr_parse_common(opts, qapi_ChardevSocket_base(sock)); - sock->has_nodelay = true; - sock->nodelay = do_nodelay; + sock->has_nodelay = qemu_opt_get(opts, "delay"); + sock->nodelay = !qemu_opt_get_bool(opts, "delay", true); + /* + * We have different default to QMP for 'server', hence + * we can't just check for existence of 'server' + */ sock->has_server = true; - sock->server = is_listen; - sock->has_telnet = true; - sock->telnet = is_telnet; - sock->has_tn3270 = true; - sock->tn3270 = is_tn3270; - sock->has_websocket = true; - sock->websocket = is_websock; - sock->has_wait = true; - sock->wait = is_waitconnect; + sock->server = qemu_opt_get_bool(opts, "server", false); + sock->has_telnet = qemu_opt_get(opts, "telnet"); + sock->telnet = qemu_opt_get_bool(opts, "telnet", false); + sock->has_tn3270 = qemu_opt_get(opts, "tn3270"); + sock->tn3270 = qemu_opt_get_bool(opts, "tn3270", false); + sock->has_websocket = qemu_opt_get(opts, "websocket"); + sock->websocket = qemu_opt_get_bool(opts, "websocket", false); + /* + * We have different default to QMP for 'wait' when 'server' + * is set, hence we can't just check for existence of 'wait' + */ + sock->has_wait = qemu_opt_find(opts, "wait") || sock->server; + sock->wait = qemu_opt_get_bool(opts, "wait", true); sock->has_reconnect = qemu_opt_find(opts, "reconnect"); - sock->reconnect = reconnect; - sock->tls_creds = g_strdup(tls_creds); + sock->reconnect = qemu_opt_get_number(opts, "reconnect", 0); + sock->has_tls_creds = qemu_opt_get(opts, "tls-creds"); + sock->tls_creds = g_strdup(qemu_opt_get(opts, "tls-creds")); addr = g_new0(SocketAddressLegacy, 1); if (path) { @@ -1223,7 +1443,7 @@ char_socket_get_connected(Object *obj, Error **errp) { SocketChardev *s = SOCKET_CHARDEV(obj); - return s->connected; + return s->state == TCP_CHARDEV_STATE_CONNECTED; } static void char_socket_class_init(ObjectClass *oc, void *data) diff --git a/chardev/char.c b/chardev/char.c index ccba36bafb..f6d61fa5f8 100644 --- a/chardev/char.c +++ b/chardev/char.c @@ -490,6 +490,8 @@ QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename, return opts; } + error_report("'%s' is not a valid char driver", filename); + fail: qemu_opts_del(opts); return NULL; @@ -634,7 +636,8 @@ ChardevBackend *qemu_chr_parse_opts(QemuOpts *opts, Error **errp) return backend; } -Chardev *qemu_chr_new_from_opts(QemuOpts *opts, Error **errp) +Chardev *qemu_chr_new_from_opts(QemuOpts *opts, GMainContext *context, + Error **errp) { const ChardevClass *cc; Chardev *chr = NULL; @@ -674,7 +677,7 @@ Chardev *qemu_chr_new_from_opts(QemuOpts *opts, Error **errp) chr = qemu_chardev_new(bid ? bid : id, object_class_get_name(OBJECT_CLASS(cc)), - backend, errp); + backend, context, errp); if (chr == NULL) { goto out; @@ -687,7 +690,7 @@ Chardev *qemu_chr_new_from_opts(QemuOpts *opts, Error **errp) backend->type = CHARDEV_BACKEND_KIND_MUX; backend->u.mux.data = g_new0(ChardevMux, 1); backend->u.mux.data->chardev = g_strdup(bid); - mux = qemu_chardev_new(id, TYPE_CHARDEV_MUX, backend, errp); + mux = qemu_chardev_new(id, TYPE_CHARDEV_MUX, backend, context, errp); if (mux == NULL) { object_unparent(OBJECT(chr)); chr = NULL; @@ -703,7 +706,7 @@ out: } Chardev *qemu_chr_new_noreplay(const char *label, const char *filename, - bool permit_mux_mon) + bool permit_mux_mon, GMainContext *context) { const char *p; Chardev *chr; @@ -718,7 +721,7 @@ Chardev *qemu_chr_new_noreplay(const char *label, const char *filename, if (!opts) return NULL; - chr = qemu_chr_new_from_opts(opts, &err); + chr = qemu_chr_new_from_opts(opts, context, &err); if (!chr) { error_report_err(err); goto out; @@ -736,10 +739,11 @@ out: static Chardev *qemu_chr_new_permit_mux_mon(const char *label, const char *filename, - bool permit_mux_mon) + bool permit_mux_mon, + GMainContext *context) { Chardev *chr; - chr = qemu_chr_new_noreplay(label, filename, permit_mux_mon); + chr = qemu_chr_new_noreplay(label, filename, permit_mux_mon, context); if (chr) { if (replay_mode != REPLAY_MODE_NONE) { qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_REPLAY); @@ -753,14 +757,16 @@ static Chardev *qemu_chr_new_permit_mux_mon(const char *label, return chr; } -Chardev *qemu_chr_new(const char *label, const char *filename) +Chardev *qemu_chr_new(const char *label, const char *filename, + GMainContext *context) { - return qemu_chr_new_permit_mux_mon(label, filename, false); + return qemu_chr_new_permit_mux_mon(label, filename, false, context); } -Chardev *qemu_chr_new_mux_mon(const char *label, const char *filename) +Chardev *qemu_chr_new_mux_mon(const char *label, const char *filename, + GMainContext *context) { - return qemu_chr_new_permit_mux_mon(label, filename, true); + return qemu_chr_new_permit_mux_mon(label, filename, true, context); } static int qmp_query_chardev_foreach(Object *obj, void *data) @@ -935,6 +941,7 @@ void qemu_chr_set_feature(Chardev *chr, Chardev *qemu_chardev_new(const char *id, const char *typename, ChardevBackend *backend, + GMainContext *gcontext, Error **errp) { Object *obj; @@ -947,6 +954,7 @@ Chardev *qemu_chardev_new(const char *id, const char *typename, obj = object_new(typename); chr = CHARDEV(obj); chr->label = g_strdup(id); + chr->gcontext = gcontext; qemu_char_open(chr, backend, &be_opened, &local_err); if (local_err) { @@ -991,7 +999,7 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend, } chr = qemu_chardev_new(id, object_class_get_name(OBJECT_CLASS(cc)), - backend, errp); + backend, NULL, errp); if (!chr) { return NULL; } @@ -1049,7 +1057,7 @@ ChardevReturn *qmp_chardev_change(const char *id, ChardevBackend *backend, } chr_new = qemu_chardev_new(NULL, object_class_get_name(OBJECT_CLASS(cc)), - backend, errp); + backend, chr->gcontext, errp); if (!chr_new) { return NULL; } diff --git a/chardev/wctablet.c b/chardev/wctablet.c index 969d014574..35dbd29a33 100644 --- a/chardev/wctablet.c +++ b/chardev/wctablet.c @@ -177,7 +177,7 @@ static void wctablet_input_sync(DeviceState *dev) } static QemuInputHandler wctablet_handler = { - .name = "QEMU Wacome Pen Tablet", + .name = "QEMU Wacom Pen Tablet", .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS, .event = wctablet_input_event, .sync = wctablet_input_sync, |