diff options
Diffstat (limited to 'chardev/char-socket.c')
-rw-r--r-- | chardev/char-socket.c | 137 |
1 files changed, 100 insertions, 37 deletions
diff --git a/chardev/char-socket.c b/chardev/char-socket.c index 0c8d6d430a..d92c5aee73 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -41,6 +41,11 @@ #define TCP_MAX_FDS 16 typedef struct { + char buf[21]; + size_t buflen; +} TCPChardevTelnetInit; + +typedef struct { Chardev parent; QIOChannel *ioc; /* Client I/O channel */ QIOChannelSocket *sioc; /* Client master channel */ @@ -60,6 +65,8 @@ typedef struct { bool is_listen; bool is_telnet; bool is_tn3270; + GSource *telnet_source; + TCPChardevTelnetInit *telnet_init; GSource *reconnect_timer; int64_t reconnect_time; @@ -70,6 +77,7 @@ typedef struct { OBJECT_CHECK(SocketChardev, (obj), TYPE_CHARDEV_SOCKET) static gboolean socket_reconnect_timeout(gpointer opaque); +static void tcp_chr_telnet_init(Chardev *chr); static void tcp_chr_reconn_timer_cancel(SocketChardev *s) { @@ -423,8 +431,8 @@ static void tcp_chr_disconnect(Chardev *chr) tcp_chr_free_connection(chr); if (s->listener) { - qio_net_listener_set_client_func(s->listener, tcp_chr_accept, - chr, NULL); + qio_net_listener_set_client_func_full(s->listener, tcp_chr_accept, + chr, NULL, chr->gcontext); } update_disconnected_filename(s); if (emit_close) { @@ -450,7 +458,7 @@ static gboolean tcp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) len = s->max_size; } size = tcp_chr_recv(chr, (void *)buf, len); - if (size == 0 || size == -1) { + if (size == 0 || (size == -1 && errno != EAGAIN)) { /* connection closed */ tcp_chr_disconnect(chr); } else if (size > 0) { @@ -556,10 +564,33 @@ static void tcp_chr_connect(void *opaque) qemu_chr_be_event(chr, CHR_EVENT_OPENED); } +static void tcp_chr_telnet_destroy(SocketChardev *s) +{ + if (s->telnet_source) { + g_source_destroy(s->telnet_source); + g_source_unref(s->telnet_source); + s->telnet_source = NULL; + } +} + static void tcp_chr_update_read_handler(Chardev *chr) { SocketChardev *s = SOCKET_CHARDEV(chr); + if (s->listener) { + /* + * It's possible that chardev context is changed in + * qemu_chr_be_update_read_handlers(). Reset it for QIO net + * listener if there is. + */ + qio_net_listener_set_client_func_full(s->listener, tcp_chr_accept, + chr, NULL, chr->gcontext); + } + + if (s->telnet_source) { + tcp_chr_telnet_init(CHARDEV(s)); + } + if (!s->connected) { return; } @@ -573,32 +604,30 @@ static void tcp_chr_update_read_handler(Chardev *chr) } } -typedef struct { - Chardev *chr; - char buf[21]; - size_t buflen; -} TCPChardevTelnetInit; - static gboolean tcp_chr_telnet_init_io(QIOChannel *ioc, GIOCondition cond G_GNUC_UNUSED, gpointer user_data) { - TCPChardevTelnetInit *init = user_data; + SocketChardev *s = user_data; + Chardev *chr = CHARDEV(s); + TCPChardevTelnetInit *init = s->telnet_init; ssize_t ret; + assert(init); + ret = qio_channel_write(ioc, init->buf, init->buflen, NULL); if (ret < 0) { if (ret == QIO_CHANNEL_ERR_BLOCK) { ret = 0; } else { - tcp_chr_disconnect(init->chr); + tcp_chr_disconnect(chr); goto end; } } init->buflen -= ret; if (init->buflen == 0) { - tcp_chr_connect(init->chr); + tcp_chr_connect(chr); goto end; } @@ -607,16 +636,30 @@ static gboolean tcp_chr_telnet_init_io(QIOChannel *ioc, return G_SOURCE_CONTINUE; end: - g_free(init); + g_free(s->telnet_init); + s->telnet_init = NULL; + g_source_unref(s->telnet_source); + s->telnet_source = NULL; return G_SOURCE_REMOVE; } static void tcp_chr_telnet_init(Chardev *chr) { SocketChardev *s = SOCKET_CHARDEV(chr); - TCPChardevTelnetInit *init = g_new0(TCPChardevTelnetInit, 1); + TCPChardevTelnetInit *init; size_t n = 0; + /* Destroy existing task */ + tcp_chr_telnet_destroy(s); + + if (s->telnet_init) { + /* We are possibly during a handshake already */ + goto cont; + } + + s->telnet_init = g_new0(TCPChardevTelnetInit, 1); + init = s->telnet_init; + #define IACSET(x, a, b, c) \ do { \ x[n++] = a; \ @@ -624,7 +667,6 @@ static void tcp_chr_telnet_init(Chardev *chr) x[n++] = c; \ } while (0) - init->chr = chr; if (!s->is_tn3270) { init->buflen = 12; /* Prep the telnet negotion to put telnet in binary, @@ -647,10 +689,11 @@ static void tcp_chr_telnet_init(Chardev *chr) #undef IACSET - qio_channel_add_watch( - s->ioc, G_IO_OUT, - tcp_chr_telnet_init_io, - init, NULL); +cont: + s->telnet_source = qio_channel_add_watch_source(s->ioc, G_IO_OUT, + tcp_chr_telnet_init_io, + s, NULL, + chr->gcontext); } @@ -707,7 +750,7 @@ static void tcp_chr_tls_init(Chardev *chr) tcp_chr_tls_handshake, chr, NULL, - NULL); + chr->gcontext); } @@ -743,7 +786,8 @@ static int tcp_chr_new_client(Chardev *chr, QIOChannelSocket *sioc) qio_channel_set_delay(s->ioc, false); } if (s->listener) { - qio_net_listener_set_client_func(s->listener, NULL, NULL, NULL); + qio_net_listener_set_client_func_full(s->listener, NULL, NULL, + NULL, chr->gcontext); } if (s->tls_creds) { @@ -823,8 +867,11 @@ static void char_socket_finalize(Object *obj) tcp_chr_free_connection(chr); tcp_chr_reconn_timer_cancel(s); qapi_free_SocketAddress(s->addr); + tcp_chr_telnet_destroy(s); + g_free(s->telnet_init); if (s->listener) { - qio_net_listener_set_client_func(s->listener, NULL, NULL, NULL); + qio_net_listener_set_client_func_full(s->listener, NULL, NULL, + NULL, chr->gcontext); object_unref(OBJECT(s->listener)); } if (s->tls_creds) { @@ -854,11 +901,22 @@ cleanup: object_unref(OBJECT(sioc)); } +static void tcp_chr_connect_async(Chardev *chr) +{ + SocketChardev *s = SOCKET_CHARDEV(chr); + QIOChannelSocket *sioc; + + 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); +} + static gboolean socket_reconnect_timeout(gpointer opaque) { Chardev *chr = CHARDEV(opaque); SocketChardev *s = SOCKET_CHARDEV(opaque); - QIOChannelSocket *sioc; g_source_unref(s->reconnect_timer); s->reconnect_timer = NULL; @@ -867,11 +925,7 @@ static gboolean socket_reconnect_timeout(gpointer opaque) return false; } - 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, NULL); + tcp_chr_connect_async(chr); return false; } @@ -950,13 +1004,8 @@ static void qmp_chardev_open_socket(Chardev *chr, s->reconnect_time = reconnect; } - if (s->reconnect_time) { - 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, NULL); - } else { + /* If reconnect_time is set, will do that in chr_machine_done. */ + if (!s->reconnect_time) { if (s->is_listen) { char *name; s->listener = qio_net_listener_new(); @@ -980,8 +1029,10 @@ static void qmp_chardev_open_socket(Chardev *chr, return; } if (!s->ioc) { - qio_net_listener_set_client_func(s->listener, tcp_chr_accept, - chr, NULL); + 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; @@ -1103,6 +1154,17 @@ char_socket_get_connected(Object *obj, Error **errp) return s->connected; } +static int tcp_chr_machine_done_hook(Chardev *chr) +{ + SocketChardev *s = SOCKET_CHARDEV(chr); + + if (s->reconnect_time) { + tcp_chr_connect_async(chr); + } + + return 0; +} + static void char_socket_class_init(ObjectClass *oc, void *data) { ChardevClass *cc = CHARDEV_CLASS(oc); @@ -1118,6 +1180,7 @@ static void char_socket_class_init(ObjectClass *oc, void *data) cc->chr_add_client = tcp_chr_add_client; cc->chr_add_watch = tcp_chr_add_watch; cc->chr_update_read_handler = tcp_chr_update_read_handler; + cc->chr_machine_done = tcp_chr_machine_done_hook; object_class_property_add(oc, "addr", "SocketAddress", char_socket_get_addr, NULL, |