diff --git a/chardev/char-mux.c b/chardev/char-mux.c index 5547a36a0a85..37d42c65c63d 100644 --- a/chardev/char-mux.c +++ b/chardev/char-mux.c @@ -114,7 +114,7 @@ static void mux_print_help(Chardev *chr) } } -void mux_chr_send_event(MuxChardev *d, int mux_nr, int event) +static void mux_chr_send_event(MuxChardev *d, int mux_nr, int event) { CharBackend *be = d->backends[mux_nr]; @@ -222,9 +222,9 @@ static void mux_chr_read(void *opaque, const uint8_t *buf, int size) bool muxes_realized; -static void mux_chr_event(void *opaque, int event) +void mux_chr_send_all_event(Chardev *chr, int event) { - MuxChardev *d = MUX_CHARDEV(opaque); + MuxChardev *d = MUX_CHARDEV(chr); int i; if (!muxes_realized) { @@ -237,6 +237,11 @@ static void mux_chr_event(void *opaque, int event) } } +static void mux_chr_event(void *opaque, int event) +{ + mux_chr_send_all_event(CHARDEV(opaque), event); +} + static GSource *mux_chr_add_watch(Chardev *s, GIOCondition cond) { MuxChardev *d = MUX_CHARDEV(s); diff --git a/chardev/char-mux.h b/chardev/char-mux.h index 9a2fffce91f0..3f41dfcfd252 100644 --- a/chardev/char-mux.h +++ b/chardev/char-mux.h @@ -58,6 +58,6 @@ typedef struct MuxChardev { void mux_chr_set_handlers(Chardev *chr, GMainContext *context); void mux_set_focus(Chardev *chr, int focus); -void mux_chr_send_event(MuxChardev *d, int mux_nr, int event); +void mux_chr_send_all_event(Chardev *chr, int event); #endif /* CHAR_MUX_H */ diff --git a/chardev/char-pty.c b/chardev/char-pty.c index a6337be8aac8..aa9d0cb2c3c8 100644 --- a/chardev/char-pty.c +++ b/chardev/char-pty.c @@ -185,7 +185,7 @@ static gboolean qemu_chr_be_generic_open_func(gpointer opaque) PtyChardev *s = PTY_CHARDEV(opaque); s->open_tag = 0; - qemu_chr_be_generic_open(chr); + qemu_chr_be_event(chr, CHR_EVENT_OPENED); return FALSE; } diff --git a/chardev/char-socket.c b/chardev/char-socket.c index 175fb8c3ec26..9d5f062c624d 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -385,6 +385,15 @@ static char *SocketAddress_to_str(const char *prefix, SocketAddress *addr, } } +static void update_disconnected_filename(SocketChardev *s) +{ + Chardev *chr = CHARDEV(s); + + g_free(chr->filename); + chr->filename = SocketAddress_to_str("disconnected:", s->addr, + s->is_listen, s->is_telnet); +} + static void tcp_chr_disconnect(Chardev *chr) { SocketChardev *s = SOCKET_CHARDEV(chr); @@ -399,8 +408,7 @@ static void tcp_chr_disconnect(Chardev *chr) 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); + update_disconnected_filename(s); qemu_chr_be_event(chr, CHR_EVENT_CLOSED); if (s->reconnect_time) { qemu_chr_socket_restart_timer(chr); @@ -508,7 +516,7 @@ static void tcp_chr_connect(void *opaque) tcp_chr_read, chr, NULL); } - qemu_chr_be_generic_open(chr); + qemu_chr_be_event(chr, CHR_EVENT_OPENED); } static void tcp_chr_update_read_handler(Chardev *chr, @@ -908,8 +916,7 @@ static void qmp_chardev_open_socket(Chardev *chr, /* be isn't opened until we get a connection */ *be_opened = false; - chr->filename = SocketAddress_to_str("disconnected:", - addr, is_listen, is_telnet); + update_disconnected_filename(s); if (is_listen) { if (is_telnet || is_tn3270) { @@ -937,6 +944,11 @@ static void qmp_chardev_open_socket(Chardev *chr, if (qio_channel_socket_listen_sync(sioc, s->addr, errp) < 0) { goto error; } + + qapi_free_SocketAddress(s->addr); + s->addr = socket_local_address(sioc->fd, errp); + update_disconnected_filename(s); + s->listen_ioc = sioc; if (is_waitconnect && qemu_chr_wait_connected(chr, errp) < 0) { @@ -1033,6 +1045,23 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, sock->addr = addr; } +static void +char_socket_get_addr(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + SocketChardev *s = SOCKET_CHARDEV(obj); + + visit_type_SocketAddress(v, name, &s->addr, errp); +} + +static bool +char_socket_get_connected(Object *obj, Error **errp) +{ + SocketChardev *s = SOCKET_CHARDEV(obj); + + return s->connected; +} + static void char_socket_class_init(ObjectClass *oc, void *data) { ChardevClass *cc = CHARDEV_CLASS(oc); @@ -1048,6 +1077,13 @@ 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; + + object_class_property_add(oc, "addr", "SocketAddress", + char_socket_get_addr, NULL, + NULL, NULL, &error_abort); + + object_class_property_add_bool(oc, "connected", char_socket_get_connected, + NULL, &error_abort); } static const TypeInfo char_socket_type_info = { diff --git a/chardev/char-udp.c b/chardev/char-udp.c index 804bd22efa56..1958c36de455 100644 --- a/chardev/char-udp.c +++ b/chardev/char-udp.c @@ -51,6 +51,18 @@ static int udp_chr_write(Chardev *chr, const uint8_t *buf, int len) s->ioc, (const char *)buf, len, NULL); } +static void udp_chr_flush_buffer(UdpChardev *s) +{ + Chardev *chr = CHARDEV(s); + + while (s->max_size > 0 && s->bufptr < s->bufcnt) { + int n = MIN(s->max_size, s->bufcnt - s->bufptr); + qemu_chr_be_write(chr, &s->buf[s->bufptr], n); + s->bufptr += n; + s->max_size = qemu_chr_be_can_write(chr); + } +} + static int udp_chr_read_poll(void *opaque) { Chardev *chr = CHARDEV(opaque); @@ -61,11 +73,8 @@ static int udp_chr_read_poll(void *opaque) /* If there were any stray characters in the queue process them * first */ - while (s->max_size > 0 && s->bufptr < s->bufcnt) { - qemu_chr_be_write(chr, &s->buf[s->bufptr], 1); - s->bufptr++; - s->max_size = qemu_chr_be_can_write(chr); - } + udp_chr_flush_buffer(s); + return s->max_size; } @@ -85,13 +94,8 @@ static gboolean udp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) return FALSE; } s->bufcnt = ret; - s->bufptr = 0; - while (s->max_size > 0 && s->bufptr < s->bufcnt) { - qemu_chr_be_write(chr, &s->buf[s->bufptr], 1); - s->bufptr++; - s->max_size = qemu_chr_be_can_write(chr); - } + udp_chr_flush_buffer(s); return TRUE; } diff --git a/chardev/char.c b/chardev/char.c index 309734f2b7c2..fadfedb8361d 100644 --- a/chardev/char.c +++ b/chardev/char.c @@ -42,8 +42,10 @@ /***********************************************************/ /* character device */ -static QTAILQ_HEAD(ChardevHead, Chardev) chardevs = - QTAILQ_HEAD_INITIALIZER(chardevs); +static Object *get_chardevs_root(void) +{ + return container_get(object_get_root(), "/chardevs"); +} void qemu_chr_be_event(Chardev *s, int event) { @@ -66,12 +68,6 @@ void qemu_chr_be_event(Chardev *s, int event) be->chr_event(be->opaque, event); } -void qemu_chr_be_generic_open(Chardev *s) -{ - qemu_chr_be_event(s, CHR_EVENT_OPENED); -} - - /* Not reporting errors from writing to logfile, as logs are * defined to be "best effort" only */ static void qemu_chr_fe_write_log(Chardev *s, @@ -453,26 +449,24 @@ static const TypeInfo char_type_info = { * mux will receive CHR_EVENT_OPENED notifications for the BE * immediately. */ -static void muxes_realize_done(Notifier *notifier, void *unused) +static int open_muxes(Object *child, void *opaque) { - Chardev *chr; + if (CHARDEV_IS_MUX(child)) { + /* send OPENED to all already-attached FEs */ + mux_chr_send_all_event(CHARDEV(child), CHR_EVENT_OPENED); + /* mark mux as OPENED so any new FEs will immediately receive + * OPENED event + */ + qemu_chr_be_event(CHARDEV(child), CHR_EVENT_OPENED); + } - QTAILQ_FOREACH(chr, &chardevs, next) { - if (CHARDEV_IS_MUX(chr)) { - MuxChardev *d = MUX_CHARDEV(chr); - int i; + return 0; +} - /* send OPENED to all already-attached FEs */ - for (i = 0; i < d->mux_cnt; i++) { - mux_chr_send_event(d, i, CHR_EVENT_OPENED); - } - /* mark mux as OPENED so any new FEs will immediately receive - * OPENED event - */ - qemu_chr_be_generic_open(chr); - } - } +static void muxes_realize_done(Notifier *notifier, void *unused) +{ muxes_realized = true; + object_child_foreach(get_chardevs_root(), open_muxes, NULL); } static Notifier muxes_realize_notify = { @@ -581,7 +575,7 @@ void qemu_chr_fe_set_handlers(CharBackend *b, /* We're connecting to an already opened device, so let's make sure we also get the open event */ if (s->be_open) { - qemu_chr_be_generic_open(s); + qemu_chr_be_event(s, CHR_EVENT_OPENED); } } @@ -774,7 +768,7 @@ void qemu_chr_parse_common(QemuOpts *opts, ChardevCommon *backend) const char *logfile = qemu_opt_get(opts, "logfile"); backend->has_logfile = logfile != NULL; - backend->logfile = logfile ? g_strdup(logfile) : NULL; + backend->logfile = g_strdup(logfile); backend->has_logappend = true; backend->logappend = qemu_opt_get_bool(opts, "logappend", false); @@ -809,26 +803,6 @@ static const ChardevClass *char_get_class(const char *driver, Error **errp) return cc; } -static Chardev *qemu_chardev_add(const char *id, const char *typename, - ChardevBackend *backend, Error **errp) -{ - Chardev *chr; - - chr = qemu_chr_find(id); - if (chr) { - error_setg(errp, "Chardev '%s' already exists", id); - return NULL; - } - - chr = qemu_chardev_new(id, typename, backend, errp); - if (!chr) { - return NULL; - } - - QTAILQ_INSERT_TAIL(&chardevs, chr, next); - return chr; -} - static const struct ChardevAlias { const char *typename; const char *alias; @@ -945,9 +919,10 @@ Chardev *qemu_chr_new_from_opts(QemuOpts *opts, backend->u.null.data = ccom; /* Any ChardevCommon member would work */ } - chr = qemu_chardev_add(bid ? bid : id, + chr = qemu_chardev_new(bid ? bid : id, object_class_get_name(OBJECT_CLASS(cc)), backend, errp); + if (chr == NULL) { goto out; } @@ -959,9 +934,9 @@ Chardev *qemu_chr_new_from_opts(QemuOpts *opts, 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_add(id, TYPE_CHARDEV_MUX, backend, errp); + mux = qemu_chardev_new(id, TYPE_CHARDEV_MUX, backend, errp); if (mux == NULL) { - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); chr = NULL; goto out; } @@ -1075,27 +1050,29 @@ void qemu_chr_fe_disconnect(CharBackend *be) } } -void qemu_chr_delete(Chardev *chr) +static int qmp_query_chardev_foreach(Object *obj, void *data) { - QTAILQ_REMOVE(&chardevs, chr, next); - object_unref(OBJECT(chr)); + Chardev *chr = CHARDEV(obj); + ChardevInfoList **list = data; + ChardevInfoList *info = g_malloc0(sizeof(*info)); + + info->value = g_malloc0(sizeof(*info->value)); + info->value->label = g_strdup(chr->label); + info->value->filename = g_strdup(chr->filename); + info->value->frontend_open = chr->be && chr->be->fe_open; + + info->next = *list; + *list = info; + + return 0; } ChardevInfoList *qmp_query_chardev(Error **errp) { ChardevInfoList *chr_list = NULL; - Chardev *chr; - QTAILQ_FOREACH(chr, &chardevs, next) { - ChardevInfoList *info = g_malloc0(sizeof(*info)); - info->value = g_malloc0(sizeof(*info->value)); - info->value->label = g_strdup(chr->label); - info->value->filename = g_strdup(chr->filename); - info->value->frontend_open = chr->be && chr->be->fe_open; - - info->next = chr_list; - chr_list = info; - } + object_child_foreach(get_chardevs_root(), + qmp_query_chardev_foreach, &chr_list); return chr_list; } @@ -1123,14 +1100,9 @@ ChardevBackendInfoList *qmp_query_chardev_backends(Error **errp) Chardev *qemu_chr_find(const char *name) { - Chardev *chr; + Object *obj = object_resolve_path_component(get_chardevs_root(), name); - QTAILQ_FOREACH(chr, &chardevs, next) { - if (strcmp(chr->label, name) != 0) - continue; - return chr; - } - return NULL; + return obj ? CHARDEV(obj) : NULL; } QemuOptsList qemu_chardev_opts = { @@ -1243,22 +1215,23 @@ void qemu_chr_set_feature(Chardev *chr, } Chardev *qemu_chardev_new(const char *id, const char *typename, - ChardevBackend *backend, Error **errp) + ChardevBackend *backend, + Error **errp) { + Object *obj; Chardev *chr = NULL; Error *local_err = NULL; bool be_opened = true; assert(g_str_has_prefix(typename, "chardev-")); - chr = CHARDEV(object_new(typename)); + obj = object_new(typename); + chr = CHARDEV(obj); chr->label = g_strdup(id); qemu_char_open(chr, backend, &be_opened, &local_err); if (local_err) { - error_propagate(errp, local_err); - object_unref(OBJECT(chr)); - return NULL; + goto end; } if (!chr->filename) { @@ -1268,6 +1241,21 @@ Chardev *qemu_chardev_new(const char *id, const char *typename, qemu_chr_be_event(chr, CHR_EVENT_OPENED); } + if (id) { + object_property_add_child(get_chardevs_root(), id, obj, &local_err); + if (local_err) { + goto end; + } + object_unref(obj); + } + +end: + if (local_err) { + error_propagate(errp, local_err); + object_unref(obj); + return NULL; + } + return chr; } @@ -1283,7 +1271,7 @@ ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend, return NULL; } - chr = qemu_chardev_add(id, object_class_get_name(OBJECT_CLASS(cc)), + chr = qemu_chardev_new(id, object_class_get_name(OBJECT_CLASS(cc)), backend, errp); if (!chr) { return NULL; @@ -1316,16 +1304,12 @@ void qmp_chardev_remove(const char *id, Error **errp) "Chardev '%s' cannot be unplugged in record/replay mode", id); return; } - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); } void qemu_chr_cleanup(void) { - Chardev *chr, *tmp; - - QTAILQ_FOREACH_SAFE(chr, &chardevs, next, tmp) { - qemu_chr_delete(chr); - } + object_unparent(get_chardevs_root()); } static void register_types(void) diff --git a/gdbstub.c b/gdbstub.c index 991115361e03..07ebfe962676 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -1611,7 +1611,7 @@ void gdb_exit(CPUArchState *env, int code) #ifndef CONFIG_USER_ONLY qemu_chr_fe_deinit(&s->chr); - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); #endif } @@ -1912,7 +1912,7 @@ int gdbserver_start(const char *device) monitor_init(mon_chr, 0); } else { if (qemu_chr_fe_get_driver(&s->chr)) { - qemu_chr_delete(qemu_chr_fe_get_driver(&s->chr)); + object_unparent(OBJECT(qemu_chr_fe_get_driver(&s->chr))); } mon_chr = s->mon_chr; memset(s, 0, sizeof(GDBState)); diff --git a/hw/usb/ccid-card-passthru.c b/hw/usb/ccid-card-passthru.c index daab0d56cf33..a41b0d6ec531 100644 --- a/hw/usb/ccid-card-passthru.c +++ b/hw/usb/ccid-card-passthru.c @@ -267,7 +267,7 @@ static void ccid_card_vscard_drop_connection(PassthruState *card) Chardev *chr = qemu_chr_fe_get_driver(&card->cs); qemu_chr_fe_deinit(&card->cs); - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); card->vscard_in_pos = card->vscard_in_hdr = 0; } diff --git a/hw/usb/redirect.c b/hw/usb/redirect.c index 0efe62f72533..b001a27f052b 100644 --- a/hw/usb/redirect.c +++ b/hw/usb/redirect.c @@ -1433,7 +1433,7 @@ static void usbredir_unrealize(USBDevice *udev, Error **errp) Chardev *chr = qemu_chr_fe_get_driver(&dev->cs); qemu_chr_fe_deinit(&dev->cs); - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); /* Note must be done after qemu_chr_close, as that causes a close event */ qemu_bh_delete(dev->chardev_close_bh); diff --git a/hw/xen/xen-common.c b/hw/xen/xen-common.c index ae76150e8ab8..a9055e9eba23 100644 --- a/hw/xen/xen-common.c +++ b/hw/xen/xen-common.c @@ -38,7 +38,7 @@ static int store_dev_info(int domid, Chardev *cs, const char *string) int ret = -1; /* Only continue if we're talking to a pty. */ - if (strncmp(cs->filename, "pty:", 4)) { + if (!CHARDEV_IS_PTY(cs)) { return 0; } pts = cs->filename + 4; diff --git a/include/sysemu/char.h b/include/sysemu/char.h index f6d5cd0c9b74..ea9f2cb7d699 100644 --- a/include/sysemu/char.h +++ b/include/sysemu/char.h @@ -103,7 +103,6 @@ struct Chardev { int be_open; guint fd_in_tag; DECLARE_BITMAP(features, QEMU_CHAR_FEATURE_LAST); - QTAILQ_ENTRY(Chardev) next; }; /** @@ -178,14 +177,6 @@ int qemu_chr_fe_wait_connected(CharBackend *be, Error **errp); */ Chardev *qemu_chr_new_noreplay(const char *label, const char *filename); -/** - * @qemu_chr_delete: - * - * Destroy a character backend and remove it from the list of - * identified character backends. - */ -void qemu_chr_delete(Chardev *chr); - /** * @qemu_chr_fe_set_echo: * @@ -435,7 +426,6 @@ void qemu_chr_fe_set_handlers(CharBackend *b, */ void qemu_chr_fe_take_focus(CharBackend *b); -void qemu_chr_be_generic_open(Chardev *s); void qemu_chr_fe_accept_input(CharBackend *be); int qemu_chr_add_client(Chardev *s, int fd); Chardev *qemu_chr_find(const char *name); diff --git a/net/vhost-user.c b/net/vhost-user.c index e7e63408a1fb..00a0c1cbc576 100644 --- a/net/vhost-user.c +++ b/net/vhost-user.c @@ -154,7 +154,7 @@ static void vhost_user_cleanup(NetClientState *nc) Chardev *chr = qemu_chr_fe_get_driver(&s->chr); qemu_chr_fe_deinit(&s->chr); - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); } qemu_purge_queued_packets(nc); diff --git a/qom/container.c b/qom/container.c index c9eb49b01edb..f6ccaf7ea780 100644 --- a/qom/container.c +++ b/qom/container.c @@ -40,6 +40,7 @@ Object *container_get(Object *root, const char *path) if (!child) { child = object_new("container"); object_property_add_child(obj, parts[i], child, NULL); + object_unref(child); } } diff --git a/tests/test-char.c b/tests/test-char.c index da69f110e41b..773a1c36ba26 100644 --- a/tests/test-char.c +++ b/tests/test-char.c @@ -1,18 +1,35 @@ #include "qemu/osdep.h" +#include #include "qemu-common.h" #include "qemu/config-file.h" +#include "qemu/sockets.h" #include "sysemu/char.h" #include "sysemu/sysemu.h" #include "qapi/error.h" +#include "qom/qom-qobject.h" #include "qmp-commands.h" +static bool quit; + typedef struct FeHandler { int read_count; int last_event; char read_buf[128]; } FeHandler; +static void main_loop(void) +{ + bool nonblocking; + int last_io = 0; + + quit = false; + do { + nonblocking = last_io > 0; + last_io = main_loop_wait(nonblocking); + } while (!quit); +} + static int fe_can_read(void *opaque) { FeHandler *h = opaque; @@ -28,6 +45,7 @@ static void fe_read(void *opaque, const uint8_t *buf, int size) memcpy(h->read_buf + h->read_count, buf, size); h->read_count += size; + quit = true; } static void fe_event(void *opaque, int event) @@ -35,9 +53,36 @@ static void fe_event(void *opaque, int event) FeHandler *h = opaque; h->last_event = event; + quit = true; } #ifdef CONFIG_HAS_GLIB_SUBPROCESS_TESTS +#ifdef _WIN32 +static void char_console_test_subprocess(void) +{ + QemuOpts *opts; + Chardev *chr; + + opts = qemu_opts_create(qemu_find_opts("chardev"), "console-label", + 1, &error_abort); + qemu_opt_set(opts, "backend", "console", &error_abort); + + chr = qemu_chr_new_from_opts(opts, NULL); + g_assert_nonnull(chr); + + qemu_chr_write_all(chr, (const uint8_t *)"CONSOLE", 7); + + qemu_opts_del(opts); + object_unparent(OBJECT(chr)); +} + +static void char_console_test(void) +{ + g_test_trap_subprocess("/char/console/subprocess", 0, 0); + g_test_trap_assert_passed(); + g_test_trap_assert_stdout("CONSOLE"); +} +#endif static void char_stdio_test_subprocess(void) { Chardev *chr; @@ -53,7 +98,7 @@ static void char_stdio_test_subprocess(void) g_assert_cmpint(ret, ==, 4); qemu_chr_fe_deinit(&be); - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); } static void char_stdio_test(void) @@ -64,7 +109,6 @@ static void char_stdio_test(void) } #endif - static void char_ringbuf_test(void) { QemuOpts *opts; @@ -103,7 +147,17 @@ static void char_ringbuf_test(void) g_free(data); qemu_chr_fe_deinit(&be); - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); + + /* check alias */ + opts = qemu_opts_create(qemu_find_opts("chardev"), "memory-label", + 1, &error_abort); + qemu_opt_set(opts, "backend", "memory", &error_abort); + qemu_opt_set(opts, "size", "2", &error_abort); + chr = qemu_chr_new_from_opts(opts, NULL); + g_assert_nonnull(chr); + object_unparent(OBJECT(chr)); + qemu_opts_del(opts); } static void char_mux_test(void) @@ -179,7 +233,296 @@ static void char_mux_test(void) qemu_chr_fe_deinit(&chr_be1); qemu_chr_fe_deinit(&chr_be2); - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); +} + +typedef struct SocketIdleData { + GMainLoop *loop; + Chardev *chr; + bool conn_expected; + CharBackend *be; + CharBackend *client_be; +} SocketIdleData; + +static gboolean char_socket_test_idle(gpointer user_data) +{ + SocketIdleData *data = user_data; + + if (object_property_get_bool(OBJECT(data->chr), "connected", NULL) + == data->conn_expected) { + quit = true; + return FALSE; + } + + return TRUE; +} + +static void socket_read(void *opaque, const uint8_t *buf, int size) +{ + SocketIdleData *data = opaque; + + g_assert_cmpint(size, ==, 1); + g_assert_cmpint(*buf, ==, 'Z'); + + size = qemu_chr_fe_write(data->be, (const uint8_t *)"hello", 5); + g_assert_cmpint(size, ==, 5); +} + +static int socket_can_read(void *opaque) +{ + return 10; +} + +static void socket_read_hello(void *opaque, const uint8_t *buf, int size) +{ + g_assert_cmpint(size, ==, 5); + g_assert(strncmp((char *)buf, "hello", 5) == 0); + + quit = true; +} + +static int socket_can_read_hello(void *opaque) +{ + return 10; +} + +static void char_socket_test(void) +{ + Chardev *chr = qemu_chr_new("server", "tcp:127.0.0.1:0,server,nowait"); + Chardev *chr_client; + QObject *addr; + QDict *qdict, *data; + const char *port; + SocketIdleData d = { .chr = chr }; + CharBackend be; + CharBackend client_be; + char *tmp; + + d.be = &be; + d.client_be = &be; + + g_assert_nonnull(chr); + g_assert(!object_property_get_bool(OBJECT(chr), "connected", &error_abort)); + + addr = object_property_get_qobject(OBJECT(chr), "addr", &error_abort); + qdict = qobject_to_qdict(addr); + data = qdict_get_qdict(qdict, "data"); + port = qdict_get_str(data, "port"); + tmp = g_strdup_printf("tcp:127.0.0.1:%s", port); + QDECREF(qdict); + + qemu_chr_fe_init(&be, chr, &error_abort); + qemu_chr_fe_set_handlers(&be, socket_can_read, socket_read, + NULL, &d, NULL, true); + + chr_client = qemu_chr_new("client", tmp); + qemu_chr_fe_init(&client_be, chr_client, &error_abort); + qemu_chr_fe_set_handlers(&client_be, socket_can_read_hello, + socket_read_hello, + NULL, &d, NULL, true); + g_free(tmp); + + d.conn_expected = true; + guint id = g_idle_add(char_socket_test_idle, &d); + g_source_set_name_by_id(id, "test-idle"); + g_assert_cmpint(id, >, 0); + main_loop(); + + g_assert(object_property_get_bool(OBJECT(chr), "connected", &error_abort)); + g_assert(object_property_get_bool(OBJECT(chr_client), + "connected", &error_abort)); + + qemu_chr_write_all(chr_client, (const uint8_t *)"Z", 1); + main_loop(); + + object_unparent(OBJECT(chr_client)); + + d.conn_expected = false; + g_idle_add(char_socket_test_idle, &d); + main_loop(); + + object_unparent(OBJECT(chr)); +} + +#ifndef _WIN32 +static void char_pipe_test(void) +{ + gchar *tmp_path = g_dir_make_tmp("qemu-test-char.XXXXXX", NULL); + gchar *tmp, *in, *out, *pipe = g_build_filename(tmp_path, "pipe", NULL); + Chardev *chr; + CharBackend be; + int ret, fd; + char buf[10]; + FeHandler fe = { 0, }; + + in = g_strdup_printf("%s.in", pipe); + if (mkfifo(in, 0600) < 0) { + abort(); + } + out = g_strdup_printf("%s.out", pipe); + if (mkfifo(out, 0600) < 0) { + abort(); + } + + tmp = g_strdup_printf("pipe:%s", pipe); + chr = qemu_chr_new("pipe", tmp); + g_assert_nonnull(chr); + g_free(tmp); + + qemu_chr_fe_init(&be, chr, &error_abort); + + ret = qemu_chr_fe_write(&be, (void *)"pipe-out", 9); + g_assert_cmpint(ret, ==, 9); + + fd = open(out, O_RDWR); + ret = read(fd, buf, sizeof(buf)); + g_assert_cmpint(ret, ==, 9); + g_assert_cmpstr(buf, ==, "pipe-out"); + close(fd); + + fd = open(in, O_WRONLY); + ret = write(fd, "pipe-in", 8); + g_assert_cmpint(ret, ==, 8); + close(fd); + + qemu_chr_fe_set_handlers(&be, + fe_can_read, + fe_read, + fe_event, + &fe, + NULL, true); + + main_loop(); + + g_assert_cmpint(fe.read_count, ==, 8); + g_assert_cmpstr(fe.read_buf, ==, "pipe-in"); + + qemu_chr_fe_deinit(&be); + object_unparent(OBJECT(chr)); + + g_assert(g_unlink(in) == 0); + g_assert(g_unlink(out) == 0); + g_assert(g_rmdir(tmp_path) == 0); + g_free(in); + g_free(out); + g_free(tmp_path); + g_free(pipe); +} +#endif + +static void char_udp_test(void) +{ + struct sockaddr_in addr = { 0, }, other; + SocketIdleData d = { 0, }; + Chardev *chr; + CharBackend be; + socklen_t alen = sizeof(addr); + int ret, sock = qemu_socket(PF_INET, SOCK_DGRAM, 0); + char buf[10]; + char *tmp; + + g_assert_cmpint(sock, >, 0); + addr.sin_family = AF_INET ; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = 0; + ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); + g_assert_cmpint(ret, ==, 0); + ret = getsockname(sock, (struct sockaddr *)&addr, &alen); + g_assert_cmpint(ret, ==, 0); + + tmp = g_strdup_printf("udp:127.0.0.1:%d", + ntohs(addr.sin_port)); + chr = qemu_chr_new("client", tmp); + g_assert_nonnull(chr); + + d.chr = chr; + qemu_chr_fe_init(&be, chr, &error_abort); + qemu_chr_fe_set_handlers(&be, socket_can_read_hello, socket_read_hello, + NULL, &d, NULL, true); + ret = qemu_chr_write_all(chr, (uint8_t *)"hello", 5); + g_assert_cmpint(ret, ==, 5); + + alen = sizeof(addr); + ret = recvfrom(sock, buf, sizeof(buf), 0, + (struct sockaddr *)&other, &alen); + g_assert_cmpint(ret, ==, 5); + ret = sendto(sock, buf, 5, 0, (struct sockaddr *)&other, alen); + g_assert_cmpint(ret, ==, 5); + + main_loop(); + + close(sock); + g_free(tmp); +} + +static void char_file_test(void) +{ + char *tmp_path = g_dir_make_tmp("qemu-test-char.XXXXXX", NULL); + char *out = g_build_filename(tmp_path, "out", NULL); + char *contents = NULL; + ChardevFile file = { .out = out }; + ChardevBackend backend = { .type = CHARDEV_BACKEND_KIND_FILE, + .u.file.data = &file }; + Chardev *chr; + gsize length; + int ret; + + chr = qemu_chardev_new(NULL, TYPE_CHARDEV_FILE, &backend, + &error_abort); + ret = qemu_chr_write_all(chr, (uint8_t *)"hello!", 6); + g_assert_cmpint(ret, ==, 6); + object_unref(OBJECT(chr)); + + ret = g_file_get_contents(out, &contents, &length, NULL); + g_assert(ret == TRUE); + g_assert_cmpint(length, ==, 6); + g_assert(strncmp(contents, "hello!", 6) == 0); + g_free(contents); + +#ifndef _WIN32 + { + CharBackend be; + FeHandler fe = { 0, }; + char *fifo = g_build_filename(tmp_path, "fifo", NULL); + int fd; + + if (mkfifo(fifo, 0600) < 0) { + abort(); + } + + fd = open(fifo, O_RDWR); + ret = write(fd, "fifo-in", 8); + g_assert_cmpint(ret, ==, 8); + + file.in = fifo; + file.has_in = true; + chr = qemu_chardev_new(NULL, TYPE_CHARDEV_FILE, &backend, + &error_abort); + + qemu_chr_fe_init(&be, chr, &error_abort); + qemu_chr_fe_set_handlers(&be, + fe_can_read, + fe_read, + fe_event, + &fe, NULL, true); + + main_loop(); + + close(fd); + + g_assert_cmpint(fe.read_count, ==, 8); + g_assert_cmpstr(fe.read_buf, ==, "fifo-in"); + qemu_chr_fe_deinit(&be); + object_unref(OBJECT(chr)); + g_unlink(fifo); + g_free(fifo); + } +#endif + + g_unlink(out); + g_rmdir(tmp_path); + g_free(tmp_path); + g_free(out); } static void char_null_test(void) @@ -222,7 +565,7 @@ static void char_null_test(void) g_assert_cmpint(ret, ==, 4); qemu_chr_fe_deinit(&be); - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); } static void char_invalid_test(void) @@ -235,6 +578,9 @@ static void char_invalid_test(void) int main(int argc, char **argv) { + qemu_init_main_loop(&error_abort); + socket_init(); + g_test_init(&argc, &argv, NULL); module_call_init(MODULE_INIT_QOM); @@ -245,9 +591,19 @@ int main(int argc, char **argv) g_test_add_func("/char/ringbuf", char_ringbuf_test); g_test_add_func("/char/mux", char_mux_test); #ifdef CONFIG_HAS_GLIB_SUBPROCESS_TESTS +#ifdef _WIN32 + g_test_add_func("/char/console/subprocess", char_console_test_subprocess); + g_test_add_func("/char/console", char_console_test); +#endif g_test_add_func("/char/stdio/subprocess", char_stdio_test_subprocess); g_test_add_func("/char/stdio", char_stdio_test); #endif +#ifndef _WIN32 + g_test_add_func("/char/pipe", char_pipe_test); +#endif + g_test_add_func("/char/file", char_file_test); + g_test_add_func("/char/socket", char_socket_test); + g_test_add_func("/char/udp", char_udp_test); return g_test_run(); } diff --git a/tests/vhost-user-test.c b/tests/vhost-user-test.c index a61896c32dc6..9095af267e6a 100644 --- a/tests/vhost-user-test.c +++ b/tests/vhost-user-test.c @@ -491,7 +491,7 @@ static gboolean _test_server_free(TestServer *server) Chardev *chr = qemu_chr_fe_get_driver(&server->chr); qemu_chr_fe_deinit(&server->chr); - qemu_chr_delete(chr); + object_unparent(OBJECT(chr)); for (i = 0; i < server->fds_num; i++) { close(server->fds[i]); diff --git a/ui/console.c b/ui/console.c index 189eecfd2940..ac66b3c910c9 100644 --- a/ui/console.c +++ b/ui/console.c @@ -2076,7 +2076,7 @@ static void text_console_do_init(Chardev *chr, DisplayState *ds) s->t_attrib = s->t_attrib_default; } - qemu_chr_be_generic_open(chr); + qemu_chr_be_event(chr, CHR_EVENT_OPENED); } static void vc_chr_open(Chardev *chr, diff --git a/ui/gtk.c b/ui/gtk.c index a86848f3b087..7479ceef3589 100644 --- a/ui/gtk.c +++ b/ui/gtk.c @@ -1868,7 +1868,7 @@ static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), vc->tab_item, gtk_label_new(vc->label)); - qemu_chr_be_generic_open(vc->vte.chr); + qemu_chr_be_event(vc->vte.chr, CHR_EVENT_OPENED); return group; } diff --git a/vl.c b/vl.c index 42d4bce439ab..560288fe0caf 100644 --- a/vl.c +++ b/vl.c @@ -4729,6 +4729,7 @@ int main(int argc, char **argv, char **envp) audio_cleanup(); monitor_cleanup(); qemu_chr_cleanup(); + /* TODO: unref root container, check all devices are ok */ return 0; }