-----BEGIN PGP SIGNATURE-----

iQIcBAABAgAGBQJZCxOSAAoJENro4Ql1lpzlGagQAIoff4XMbbh2l5eFobcv00hn
 JdVT4u/Ie/Y1CTjOr8lhtgkKolA+cxfynlXMJN2pW8RP7W4J8Xo7SBpO84OpEKrc
 BGUmn4BSJQM6oOM5/YaITUBvfN+ZUFJMWwlBbHNTpF54jYb0y7UqbA8G/pJV8lAm
 ivo6wDrlwaOQLVs9RZWIZclVQOoCQ4VdTGwra9yKFf9BqtorSOs1BsDGvNprKLkw
 0B5aRcTfBVS1qv7C/eGqOwigmCyE8s1h+HN2FTm7FIxYWiztRBezv76Yt+sV721w
 MvXPn9XSxHqhNnml2hsyEgKMblnutdRooKbdSesgVLKFnlt290WuDCOhj02phNXx
 qj8wnJ49nt8VzuPLUON5PKDnyhJrNHr+Hw0/YlneBZvjOhBEqX9BxSMvz8t1sTYC
 eAxQXrd+pFdUzLjv1HRrLBLfh8PUPtCluKkhGDh6MznXXzEbvZEGSeVCnLqy62w7
 4gKxwinwfpod/1HFPTgfoFQBZzGzhTN7CjM6kh6PsIluTgsW0ROA2oVJSosVmv1P
 QfONhnGZJJf/yjkTjea40qHbJftBsVs9UiOHZ4PZ1QYgX4ZOj+FKaj1l5XYi6AG/
 FNWWpKtelyf/YlU2ogFiRTYY7Y9BTITfk/K4vpOMRlQ9XFLf45z6DP1PRqCKx2Ub
 fOuWoPQOHLtyeAx8lrJM
 =rxGe
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'elmarco/tags/chr-tests-pull-request' into staging

# gpg: Signature made Thu 04 May 2017 12:42:10 PM BST
# gpg:                using RSA key 0xDAE8E10975969CE5
# gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>"
# gpg:                 aka "Marc-André Lureau <marcandre.lureau@gmail.com>"
# Primary key fingerprint: 87A9 BD93 3F87 C606 D276  F62D DAE8 E109 7596 9CE5

* elmarco/tags/chr-tests-pull-request: (21 commits)
  tests: add /char/console test
  tests: add /char/udp test
  tests: add /char/socket test
  tests: add /char/file test
  tests: add /char/pipe test
  tests: add alias check in /char/ringbuf
  char-udp: flush as much buffer as possible
  char-socket: add 'connected' property
  char-socket: add 'addr' property
  char-socket: update local address after listen
  char-socket: introduce update_disconnected_filename()
  char: useless NULL check
  char: remove chardevs list
  char: remove qemu_chardev_add
  char: use /chardevs container instead of chardevs list
  vl: add todo note about root container cleanup
  char: add a /chardevs container
  container: don't leak container reference
  xen: use a better chardev type check
  mux: simplfy muxes_realize_done
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2017-05-05 17:04:07 +01:00
commit dd1559bb26
18 changed files with 505 additions and 128 deletions

View file

@ -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);

View file

@ -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 */

View file

@ -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;
}

View file

@ -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 = {

View file

@ -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;
}

View file

@ -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 int open_muxes(Object *child, void *opaque)
{
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);
}
return 0;
}
static void muxes_realize_done(Notifier *notifier, void *unused)
{
Chardev *chr;
QTAILQ_FOREACH(chr, &chardevs, next) {
if (CHARDEV_IS_MUX(chr)) {
MuxChardev *d = MUX_CHARDEV(chr);
int i;
/* 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);
}
}
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)

View file

@ -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));

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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);
}
}

View file

@ -1,18 +1,35 @@
#include "qemu/osdep.h"
#include <glib/gstdio.h>
#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();
}

View file

@ -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]);

View file

@ -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,

View file

@ -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;
}

1
vl.c
View file

@ -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;
}