From 4cfd970ec188558daa6214f26203fe553fb1e01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Mon, 19 Jul 2021 16:54:17 +0400 Subject: [PATCH 1/8] util: fix abstract socket path copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 776b97d360 "qemu-sockets: add abstract UNIX domain socket support" neglected to update socket_sockaddr_to_address_unix() and copied the whole sun_path without taking "salen" into account. Later, commit 3b14b4ec49 "sockets: Fix socket_sockaddr_to_address_unix() for abstract sockets" handled the abstract UNIX path, by stripping the leading \0 character and fixing address details, but didn't use salen either. Not taking "salen" into account may result in incorrect "path" being returned in monitors commands, as we read past the address which is not necessarily \0-terminated. Fixes: 776b97d3605ed0fc94443048fdf988c7725e38a9 Fixes: 3b14b4ec49a801067da19d6b8469eb1c1911c020 Signed-off-by: Marc-André Lureau Reviewed-by: xiaoqiang zhao Reviewed-by: Daniel P. Berrangé --- util/qemu-sockets.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/util/qemu-sockets.c b/util/qemu-sockets.c index 080a240b74..f2f3676d1f 100644 --- a/util/qemu-sockets.c +++ b/util/qemu-sockets.c @@ -1345,13 +1345,16 @@ socket_sockaddr_to_address_unix(struct sockaddr_storage *sa, SocketAddress *addr; struct sockaddr_un *su = (struct sockaddr_un *)sa; + assert(salen >= sizeof(su->sun_family) + 1 && + salen <= sizeof(struct sockaddr_un)); + addr = g_new0(SocketAddress, 1); addr->type = SOCKET_ADDRESS_TYPE_UNIX; #ifdef CONFIG_LINUX if (!su->sun_path[0]) { /* Linux abstract socket */ addr->u.q_unix.path = g_strndup(su->sun_path + 1, - sizeof(su->sun_path) - 1); + salen - sizeof(su->sun_family) - 1); addr->u.q_unix.has_abstract = true; addr->u.q_unix.abstract = true; addr->u.q_unix.has_tight = true; From 30f80be34ba6142dc7ba2015b98d661a9529b7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Wed, 21 Jul 2021 16:55:53 +0400 Subject: [PATCH 2/8] chardev/socket: print a more correct command-line address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Better reflect the command line version of the socket address arguments, following the now recommended long-form opt=on syntax. Complement/fixes commit 9d902d51 "chardev: do not use short form boolean options in non-QemuOpts character device descriptions". Signed-off-by: Marc-André Lureau Reviewed-by: Philippe Mathieu-Daudé --- chardev/char-socket.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chardev/char-socket.c b/chardev/char-socket.c index d0fb545963..c43668cc15 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -468,9 +468,9 @@ static char *qemu_chr_socket_address(SocketChardev *s, const char *prefix) #ifdef CONFIG_LINUX if (sa->has_abstract && sa->abstract) { - abstract = ",abstract"; + abstract = ",abstract=on"; if (sa->has_tight && sa->tight) { - tight = ",tight"; + tight = ",tight=on"; } } #endif From bf7b1eab252bc56b6bbb12a8909eae738435d6ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Wed, 4 Aug 2021 17:01:14 +0400 Subject: [PATCH 3/8] chardev: mark explicitly first argument as poisoned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit 9894dc0cdcc397ee5b26370bc53da6d360a363c2 "char: convert from GIOChannel to QIOChannel", the first argument to the watch callback can actually be a QIOChannel, which is not a GIOChannel (but a QEMU Object). Even though we never used that pointer, change the callback type to warn the users. Possibly a better fix later, we may want to store the callback and call it from intermediary functions. Signed-off-by: Marc-André Lureau Reviewed-by: Daniel P. Berrangé --- chardev/char-fe.c | 2 +- hw/char/cadence_uart.c | 2 +- hw/char/cmsdk-apb-uart.c | 2 +- hw/char/ibex_uart.c | 2 +- hw/char/nrf51_uart.c | 2 +- hw/char/serial.c | 2 +- hw/char/virtio-console.c | 2 +- hw/usb/redirect.c | 2 +- hw/virtio/vhost-user.c | 2 +- include/chardev/char-fe.h | 8 +++++++- monitor/monitor.c | 2 +- net/vhost-user.c | 4 ++-- 12 files changed, 19 insertions(+), 13 deletions(-) diff --git a/chardev/char-fe.c b/chardev/char-fe.c index 474715c5a9..7789f7be9c 100644 --- a/chardev/char-fe.c +++ b/chardev/char-fe.c @@ -354,7 +354,7 @@ void qemu_chr_fe_set_open(CharBackend *be, int fe_open) } guint qemu_chr_fe_add_watch(CharBackend *be, GIOCondition cond, - GIOFunc func, void *user_data) + FEWatchFunc func, void *user_data) { Chardev *s = be->chr; GSource *src; diff --git a/hw/char/cadence_uart.c b/hw/char/cadence_uart.c index ceb677bc5a..b4b5e8a3ee 100644 --- a/hw/char/cadence_uart.c +++ b/hw/char/cadence_uart.c @@ -288,7 +288,7 @@ static void uart_write_rx_fifo(void *opaque, const uint8_t *buf, int size) uart_update_status(s); } -static gboolean cadence_uart_xmit(GIOChannel *chan, GIOCondition cond, +static gboolean cadence_uart_xmit(void *do_not_use, GIOCondition cond, void *opaque) { CadenceUARTState *s = opaque; diff --git a/hw/char/cmsdk-apb-uart.c b/hw/char/cmsdk-apb-uart.c index ba2cbbee3d..f8dc89ee3d 100644 --- a/hw/char/cmsdk-apb-uart.c +++ b/hw/char/cmsdk-apb-uart.c @@ -191,7 +191,7 @@ static uint64_t uart_read(void *opaque, hwaddr offset, unsigned size) /* Try to send tx data, and arrange to be called back later if * we can't (ie the char backend is busy/blocking). */ -static gboolean uart_transmit(GIOChannel *chan, GIOCondition cond, void *opaque) +static gboolean uart_transmit(void *do_not_use, GIOCondition cond, void *opaque) { CMSDKAPBUART *s = CMSDK_APB_UART(opaque); int ret; diff --git a/hw/char/ibex_uart.c b/hw/char/ibex_uart.c index 6b0c9330bf..9b0a817713 100644 --- a/hw/char/ibex_uart.c +++ b/hw/char/ibex_uart.c @@ -135,7 +135,7 @@ static void ibex_uart_receive(void *opaque, const uint8_t *buf, int size) ibex_uart_update_irqs(s); } -static gboolean ibex_uart_xmit(GIOChannel *chan, GIOCondition cond, +static gboolean ibex_uart_xmit(void *do_not_use, GIOCondition cond, void *opaque) { IbexUartState *s = opaque; diff --git a/hw/char/nrf51_uart.c b/hw/char/nrf51_uart.c index 045ca5fa40..3c6f982de9 100644 --- a/hw/char/nrf51_uart.c +++ b/hw/char/nrf51_uart.c @@ -75,7 +75,7 @@ static uint64_t uart_read(void *opaque, hwaddr addr, unsigned int size) return r; } -static gboolean uart_transmit(GIOChannel *chan, GIOCondition cond, void *opaque) +static gboolean uart_transmit(void *do_not_use, GIOCondition cond, void *opaque) { NRF51UARTState *s = NRF51_UART(opaque); int r; diff --git a/hw/char/serial.c b/hw/char/serial.c index bc2e322970..7061aacbce 100644 --- a/hw/char/serial.c +++ b/hw/char/serial.c @@ -220,7 +220,7 @@ static void serial_update_msl(SerialState *s) } } -static gboolean serial_watch_cb(GIOChannel *chan, GIOCondition cond, +static gboolean serial_watch_cb(void *do_not_use, GIOCondition cond, void *opaque) { SerialState *s = opaque; diff --git a/hw/char/virtio-console.c b/hw/char/virtio-console.c index 6b132caa29..dd5a02e339 100644 --- a/hw/char/virtio-console.c +++ b/hw/char/virtio-console.c @@ -38,7 +38,7 @@ struct VirtConsole { * Callback function that's called from chardevs when backend becomes * writable. */ -static gboolean chr_write_unblocked(GIOChannel *chan, GIOCondition cond, +static gboolean chr_write_unblocked(void *do_not_use, GIOCondition cond, void *opaque) { VirtConsole *vcon = opaque; diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c index 1ec909a63a..5f0ef9cb3b 100644 --- a/hw/usb/redirect.c +++ b/hw/usb/redirect.c @@ -270,7 +270,7 @@ static int usbredir_read(void *priv, uint8_t *data, int count) return count; } -static gboolean usbredir_write_unblocked(GIOChannel *chan, GIOCondition cond, +static gboolean usbredir_write_unblocked(void *do_not_use, GIOCondition cond, void *opaque) { USBRedirDevice *dev = opaque; diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c index 29ea2b4fce..aec6cc1990 100644 --- a/hw/virtio/vhost-user.c +++ b/hw/virtio/vhost-user.c @@ -303,7 +303,7 @@ struct vhost_user_read_cb_data { int ret; }; -static gboolean vhost_user_read_cb(GIOChannel *source, GIOCondition condition, +static gboolean vhost_user_read_cb(void *do_not_use, GIOCondition condition, gpointer opaque) { struct vhost_user_read_cb_data *data = opaque; diff --git a/include/chardev/char-fe.h b/include/chardev/char-fe.h index a553843364..867ef1b3b2 100644 --- a/include/chardev/char-fe.h +++ b/include/chardev/char-fe.h @@ -174,6 +174,9 @@ void qemu_chr_fe_set_open(CharBackend *be, int fe_open); void qemu_chr_fe_printf(CharBackend *be, const char *fmt, ...) GCC_FMT_ATTR(2, 3); + +typedef gboolean (*FEWatchFunc)(void *do_not_use, GIOCondition condition, void *data); + /** * qemu_chr_fe_add_watch: * @cond: the condition to poll for @@ -188,10 +191,13 @@ void qemu_chr_fe_printf(CharBackend *be, const char *fmt, ...) * Note that you are responsible to update the front-end sources if * you are switching the main context with qemu_chr_fe_set_handlers(). * + * Warning: DO NOT use the first callback argument (it may be either + * a GIOChannel or a QIOChannel, depending on the underlying chardev) + * * Returns: the source tag */ guint qemu_chr_fe_add_watch(CharBackend *be, GIOCondition cond, - GIOFunc func, void *user_data); + FEWatchFunc func, void *user_data); /** * qemu_chr_fe_write: diff --git a/monitor/monitor.c b/monitor/monitor.c index b90c0f4051..46a171bca6 100644 --- a/monitor/monitor.c +++ b/monitor/monitor.c @@ -156,7 +156,7 @@ static inline bool monitor_is_hmp_non_interactive(const Monitor *mon) static void monitor_flush_locked(Monitor *mon); -static gboolean monitor_unblocked(GIOChannel *chan, GIOCondition cond, +static gboolean monitor_unblocked(void *do_not_use, GIOCondition cond, void *opaque) { Monitor *mon = opaque; diff --git a/net/vhost-user.c b/net/vhost-user.c index ffbd94d944..6adfcd623a 100644 --- a/net/vhost-user.c +++ b/net/vhost-user.c @@ -208,8 +208,8 @@ static NetClientInfo net_vhost_user_info = { .set_vnet_le = vhost_user_set_vnet_endianness, }; -static gboolean net_vhost_user_watch(GIOChannel *chan, GIOCondition cond, - void *opaque) +static gboolean net_vhost_user_watch(void *do_not_use, GIOCondition cond, + void *opaque) { NetVhostUserState *s = opaque; From bb2b058f1a4e4fe9031133f2f7876fb12b2104d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Wed, 4 Aug 2021 15:08:46 +0400 Subject: [PATCH 4/8] chardev: fix fd_chr_add_watch() when in != out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create child sources for the different streams, and dispatch on the parent source with the synthesized conditions. Signed-off-by: Marc-André Lureau Reviewed-by: Daniel P. Berrangé --- chardev/char-fd.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/chardev/char-fd.c b/chardev/char-fd.c index 1cd62f2779..743d3989b4 100644 --- a/chardev/char-fd.c +++ b/chardev/char-fd.c @@ -28,6 +28,7 @@ #include "qemu/sockets.h" #include "qapi/error.h" #include "chardev/char.h" +#include "chardev/char-fe.h" #include "io/channel-file.h" #include "chardev/char-fd.h" @@ -80,10 +81,85 @@ static int fd_chr_read_poll(void *opaque) return s->max_size; } +typedef struct FDSource { + GSource parent; + + GIOCondition cond; +} FDSource; + +static gboolean +fd_source_prepare(GSource *source, + gint *timeout_) +{ + FDSource *src = (FDSource *)source; + + return src->cond != 0; +} + +static gboolean +fd_source_check(GSource *source) +{ + FDSource *src = (FDSource *)source; + + return src->cond != 0; +} + +static gboolean +fd_source_dispatch(GSource *source, GSourceFunc callback, + gpointer user_data) +{ + FDSource *src = (FDSource *)source; + FEWatchFunc func = (FEWatchFunc)callback; + gboolean ret = G_SOURCE_CONTINUE; + + if (src->cond) { + ret = func(NULL, src->cond, user_data); + src->cond = 0; + } + + return ret; +} + +static GSourceFuncs fd_source_funcs = { + fd_source_prepare, + fd_source_check, + fd_source_dispatch, + NULL, NULL, NULL +}; + +static GSource *fd_source_new(FDChardev *chr) +{ + return g_source_new(&fd_source_funcs, sizeof(FDSource)); +} + +static gboolean child_func(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + FDSource *parent = data; + + parent->cond |= condition; + + return G_SOURCE_CONTINUE; +} + static GSource *fd_chr_add_watch(Chardev *chr, GIOCondition cond) { FDChardev *s = FD_CHARDEV(chr); - return qio_channel_create_watch(s->ioc_out, cond); + g_autoptr(GSource) source = fd_source_new(s); + + if (s->ioc_out) { + g_autoptr(GSource) child = qio_channel_create_watch(s->ioc_out, cond & ~G_IO_IN); + g_source_set_callback(child, (GSourceFunc)child_func, source, NULL); + g_source_add_child_source(source, child); + } + if (s->ioc_in) { + g_autoptr(GSource) child = qio_channel_create_watch(s->ioc_in, cond & ~G_IO_OUT); + g_source_set_callback(child, (GSourceFunc)child_func, source, NULL); + g_source_add_child_source(source, child); + } + + return g_steal_pointer(&source); } static void fd_chr_update_read_handler(Chardev *chr) From 46fe3ff6ea3e7a642b8545c0322ef5df873bd560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Fri, 23 Jul 2021 13:54:54 +0400 Subject: [PATCH 5/8] chardev: fix qemu_chr_open_fd() being called with fd=-1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "file" chardev may call qemu_chr_open_fd() with fd_in=-1. This may cause invalid system calls, as the QIOChannel is assumed to be properly initialized later on. Signed-off-by: Marc-André Lureau Reviewed-by: Daniel P. Berrangé --- chardev/char-fd.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/chardev/char-fd.c b/chardev/char-fd.c index 743d3989b4..c11b1037f9 100644 --- a/chardev/char-fd.c +++ b/chardev/char-fd.c @@ -39,6 +39,10 @@ static int fd_chr_write(Chardev *chr, const uint8_t *buf, int len) { FDChardev *s = FD_CHARDEV(chr); + if (!s->ioc_out) { + return -1; + } + return io_channel_send(s->ioc_out, buf, len); } @@ -209,15 +213,19 @@ void qemu_chr_open_fd(Chardev *chr, FDChardev *s = FD_CHARDEV(chr); char *name; - s->ioc_in = QIO_CHANNEL(qio_channel_file_new_fd(fd_in)); - name = g_strdup_printf("chardev-file-in-%s", chr->label); - qio_channel_set_name(QIO_CHANNEL(s->ioc_in), name); - g_free(name); - s->ioc_out = QIO_CHANNEL(qio_channel_file_new_fd(fd_out)); - name = g_strdup_printf("chardev-file-out-%s", chr->label); - qio_channel_set_name(QIO_CHANNEL(s->ioc_out), name); - g_free(name); - qemu_set_nonblock(fd_out); + if (fd_in >= 0) { + s->ioc_in = QIO_CHANNEL(qio_channel_file_new_fd(fd_in)); + name = g_strdup_printf("chardev-file-in-%s", chr->label); + qio_channel_set_name(QIO_CHANNEL(s->ioc_in), name); + g_free(name); + } + if (fd_out >= 0) { + s->ioc_out = QIO_CHANNEL(qio_channel_file_new_fd(fd_out)); + name = g_strdup_printf("chardev-file-out-%s", chr->label); + qio_channel_set_name(QIO_CHANNEL(s->ioc_out), name); + g_free(name); + qemu_set_nonblock(fd_out); + } } static void char_fd_class_init(ObjectClass *oc, void *data) From 733ba020846ccd21d832f2e9b62387a86c5ab8f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Fri, 23 Jul 2021 13:59:50 +0400 Subject: [PATCH 6/8] chardev: fix qemu_chr_open_fd() with fd_in==fd_out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "serial" chardev calls qemu_chr_open_fd() with the same fd. This may lead to double-close as each QIOChannel owns the fd. Instead, share the reference to the same QIOChannel. Signed-off-by: Marc-André Lureau Reviewed-by: Daniel P. Berrangé --- chardev/char-fd.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/chardev/char-fd.c b/chardev/char-fd.c index c11b1037f9..93c56913b4 100644 --- a/chardev/char-fd.c +++ b/chardev/char-fd.c @@ -211,20 +211,31 @@ void qemu_chr_open_fd(Chardev *chr, int fd_in, int fd_out) { FDChardev *s = FD_CHARDEV(chr); - char *name; + g_autofree char *name = NULL; + + if (fd_out >= 0) { + qemu_set_nonblock(fd_out); + } + + if (fd_out == fd_in && fd_in >= 0) { + s->ioc_in = QIO_CHANNEL(qio_channel_file_new_fd(fd_in)); + name = g_strdup_printf("chardev-file-%s", chr->label); + qio_channel_set_name(QIO_CHANNEL(s->ioc_in), name); + s->ioc_out = QIO_CHANNEL(object_ref(s->ioc_in)); + return; + } if (fd_in >= 0) { s->ioc_in = QIO_CHANNEL(qio_channel_file_new_fd(fd_in)); name = g_strdup_printf("chardev-file-in-%s", chr->label); qio_channel_set_name(QIO_CHANNEL(s->ioc_in), name); - g_free(name); } + if (fd_out >= 0) { s->ioc_out = QIO_CHANNEL(qio_channel_file_new_fd(fd_out)); + g_free(name); name = g_strdup_printf("chardev-file-out-%s", chr->label); qio_channel_set_name(QIO_CHANNEL(s->ioc_out), name); - g_free(name); - qemu_set_nonblock(fd_out); } } From 64195b0d365712c05ae922b7ff22cf136aac1400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Wed, 28 Jul 2021 15:34:43 +0400 Subject: [PATCH 7/8] chardev: give some context on chardev-add error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description from Daniel P. Berrangé: > The original code reported: > > "attempt to add duplicate property 'char2' to object (type 'container')" > > Since adding yank support, the current code reports > > "duplicate yank instance" > > With this patch applied it now reports: > > "Failed to add chardev 'char2': duplicate yank instance" > > This is marginally better, but still not great, not that the original > error was great either. > > It would be nice if we could report > > "chardev with id 'char2' already exists" Related to: https://bugzilla.redhat.com/show_bug.cgi?id=1984721 Signed-off-by: Marc-André Lureau Reviewed-by: Daniel P. Berrangé --- chardev/char.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/chardev/char.c b/chardev/char.c index d959eec522..f59a61774b 100644 --- a/chardev/char.c +++ b/chardev/char.c @@ -1031,27 +1031,26 @@ Chardev *qemu_chardev_new(const char *id, const char *typename, ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend, Error **errp) { + ERRP_GUARD(); const ChardevClass *cc; ChardevReturn *ret; - Chardev *chr; + g_autoptr(Chardev) chr = NULL; cc = char_get_class(ChardevBackendKind_str(backend->type), errp); if (!cc) { - return NULL; + goto err; } chr = chardev_new(id, object_class_get_name(OBJECT_CLASS(cc)), backend, NULL, false, errp); if (!chr) { - return NULL; + goto err; } if (!object_property_try_add_child(get_chardevs_root(), id, OBJECT(chr), errp)) { - object_unref(OBJECT(chr)); - return NULL; + goto err; } - object_unref(OBJECT(chr)); ret = g_new0(ChardevReturn, 1); if (CHARDEV_IS_PTY(chr)) { @@ -1060,6 +1059,10 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend, } return ret; + +err: + error_prepend(errp, "Failed to add chardev '%s': ", id); + return NULL; } ChardevReturn *qmp_chardev_change(const char *id, ChardevBackend *backend, From a68403b0a6843f106e381b0bbeaacb29f6d27255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Wed, 4 Aug 2021 19:12:54 +0400 Subject: [PATCH 8/8] chardev: report a simpler error about duplicated id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Report: "Chardev with id 'char2' already exists" Rather than: "Failed to add chardev 'char2': duplicate yank instance" Signed-off-by: Marc-André Lureau Reviewed-by: Daniel P. Berrangé --- chardev/char.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chardev/char.c b/chardev/char.c index f59a61774b..4595a8d430 100644 --- a/chardev/char.c +++ b/chardev/char.c @@ -1036,6 +1036,11 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend, ChardevReturn *ret; g_autoptr(Chardev) chr = NULL; + if (qemu_chr_find(id)) { + error_setg(errp, "Chardev with id '%s' already exists", id); + return NULL; + } + cc = char_get_class(ChardevBackendKind_str(backend->type), errp); if (!cc) { goto err;