aboutsummaryrefslogtreecommitdiff
path: root/chardev/char-socket.c
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2019-02-14 14:34:32 +0000
committerPeter Maydell <peter.maydell@linaro.org>2019-02-14 14:34:32 +0000
commitc4c5f6573a93dfbd351c41a27ea29a662d7445fa (patch)
treec4fd3d30fd6025ac4bec8a6c245ab5d57849c596 /chardev/char-socket.c
parent4856c2c70c87d7a76c8ea208e7568f5637e78840 (diff)
parentf7ea2038bea04628eaa55156fc34edf9d0c4a2bb (diff)
Merge remote-tracking branch 'remotes/elmarco/tags/chardev-pull-request' into staging
Chardev fixes # gpg: Signature made Wed 13 Feb 2019 16:18:36 GMT # gpg: using RSA key DAE8E10975969CE5 # gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>" [full] # gpg: aka "Marc-André Lureau <marcandre.lureau@gmail.com>" [full] # Primary key fingerprint: 87A9 BD93 3F87 C606 D276 F62D DAE8 E109 7596 9CE5 * remotes/elmarco/tags/chardev-pull-request: (25 commits) char-pty: remove write_lock usage char-pty: remove the check for connection on write chardev: add a note about frontend sources and context switch terminal3270: do not use backend timer sources char: update the mux handlers in class callback chardev/wctablet: Fix a typo char: allow specifying a GMainContext at opening time chardev: ensure termios is fully initialized tests: expand coverage of socket chardev test chardev: fix race with client connections in tcp_chr_wait_connected chardev: disallow TLS/telnet/websocket with tcp_chr_wait_connected chardev: honour the reconnect setting in tcp_chr_wait_connected chardev: use a state machine for socket connection state chardev: split up qmp_chardev_open_socket connection code chardev: split tcp_chr_wait_connected into two methods chardev: remove unused 'sioc' variable & cleanup paths chardev: ensure qemu_chr_parse_compat reports missing driver error chardev: remove many local variables in qemu_chr_parse_socket chardev: forbid 'wait' option with client sockets chardev: forbid 'reconnect' option with server sockets ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'chardev/char-socket.c')
-rw-r--r--chardev/char-socket.c490
1 files changed, 355 insertions, 135 deletions
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)