Introduce a new QEMU chardev backend called "overlay" which allows you to splice together a pair of chardev backends into one combined backend. The master backend permits full input/output but the slave backend is output only.
The primary use case for this is to allow arbitrary backends to have their data logged to a file, eg a 'file' backend would be setup as the slave. $ qemu-system-x86_64 \ -chardev socket,host=localhost,port=9000,server=on,nowait,id=char0master \ -chardev file,path=/some/log/file.log,id=char0slave \ -chardev overlay,id=char0,master=char0master,slave=char0slave \ -device isa-serial,chardev=char0 \ ...other args... --- This idea was suggsted in https://lists.gnu.org/archive/html/qemu-devel/2015-12/msg01256.html this patch is a very quick proof of concept impl to illustrate the idea. qapi-schema.json | 14 +++++ qemu-char.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) diff --git a/qapi-schema.json b/qapi-schema.json index 4be151c..1adb220 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -3156,6 +3156,19 @@ { 'struct': 'ChardevRingbuf', 'data': { '*size' : 'int' } } ## +# @ChardevOverlay: +# +# Configuration info for overlay chardevs. +# +# @master: the master chardev for input / output +# @slave: the slave chardev for output only +# +# Since: 2.6 +## +{ 'struct': 'ChardevOverlay', 'data': { 'master' : 'str', + 'slave' : 'str'} } + +## # @ChardevBackend: # # Configuration info for the new chardev backend. @@ -3182,6 +3195,7 @@ 'spiceport' : 'ChardevSpicePort', 'vc' : 'ChardevVC', 'ringbuf': 'ChardevRingbuf', + 'overlay': 'ChardevOverlay', # next one is just for compatibility 'memory' : 'ChardevRingbuf' } } diff --git a/qemu-char.c b/qemu-char.c index 45fdb77..0328eb9 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -3223,6 +3223,131 @@ char *qmp_ringbuf_read(const char *device, int64_t size, return data; } + +typedef struct { + CharDriverState *master; + CharDriverState *slave; + + IOCanReadHandler *chr_can_read; + IOReadHandler *chr_read; + IOEventHandler *chr_event; + void *handler_opaque; +} OverlayCharDriver; + +static int overlay_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + OverlayCharDriver *s = chr->opaque; + int ret; + + ret = qemu_chr_fe_write(s->master, buf, len); + if (ret <= 0) { + return ret; + } + + /* Write all will block, but how else can we ensure + * that all the data we just sent to the master also + * gets sent to the slave ? Could buffer data internally + * if slave was full and setup a backgroupd I/O watch + * to send the data later, but the ChardevDriverState + * API design doesn't make that very easy since. Of + * course if the slave is a "file" backend, then it + * will block regardless thanks to POSIX not handling + * O_NONBLOCK on regular files, so this is no worse + * in that scenario. + */ + qemu_chr_fe_write_all(s->slave, buf, ret); + return ret; +} + +static GSource *overlay_chr_add_watch(CharDriverState *chr, GIOCondition cond) +{ + OverlayCharDriver *d = chr->opaque; + return d->master->chr_add_watch(d->master, cond); +} + +static int overlay_chr_can_read(void *opaque) +{ + CharDriverState *chr = opaque; + OverlayCharDriver *d = chr->opaque; + + if (d->chr_can_read) { + return d->chr_can_read(d->handler_opaque); + } + return 0; +} + +static void overlay_chr_read(void *opaque, const uint8_t *buf, int size) +{ + CharDriverState *chr = opaque; + OverlayCharDriver *d = chr->opaque; + + if (d->chr_read) { + d->chr_read(d->handler_opaque, buf, size); + } +} + +static void overlay_chr_event(void *opaque, int event) +{ + CharDriverState *chr = opaque; + OverlayCharDriver *d = chr->opaque; + + if (d->chr_event) { + d->chr_event(d->handler_opaque, event); + } +} + +static void overlay_chr_update_read_handler(CharDriverState *chr) +{ + OverlayCharDriver *d = chr->opaque; + + d->handler_opaque = chr->handler_opaque; + d->chr_can_read = chr->chr_can_read; + d->chr_read = chr->chr_read; + d->chr_event = chr->chr_event; + + qemu_chr_add_handlers(d->master, overlay_chr_can_read, overlay_chr_read, + overlay_chr_event, chr); + /* Slave is output only, so don't wire it up for input */ +} + +static CharDriverState *qemu_chr_open_overlay(const char *id, + ChardevBackend *backend, + ChardevReturn *ret, Error **errp) +{ + ChardevOverlay *overlay = backend->u.overlay; + CharDriverState *chr, *master, *slave; + OverlayCharDriver *d; + + master = qemu_chr_find(overlay->master); + if (master == NULL) { + error_setg(errp, "overlay: master chardev %s not found", overlay->master); + return NULL; + } + + slave = qemu_chr_find(overlay->slave); + if (slave == NULL) { + error_setg(errp, "overlay: slave chardev %s not found", overlay->slave); + return NULL; + } + + chr = qemu_chr_alloc(); + d = g_new0(OverlayCharDriver, 1); + + chr->opaque = d; + d->master = master; + d->slave = slave; + + chr->chr_write = overlay_chr_write; + chr->chr_update_read_handler = overlay_chr_update_read_handler; + if (master->chr_add_watch) { + chr->chr_add_watch = overlay_chr_add_watch; + } + chr->explicit_be_open = master->explicit_be_open; + + return chr; +} + + QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename) { char host[65], port[33], width[8], height[8]; @@ -3443,6 +3568,25 @@ static void qemu_chr_parse_ringbuf(QemuOpts *opts, ChardevBackend *backend, } } +static void qemu_chr_parse_overlay(QemuOpts *opts, ChardevBackend *backend, + Error **errp) +{ + const char *master = qemu_opt_get(opts, "master"); + const char *slave = qemu_opt_get(opts, "slave"); + + if (master == NULL) { + error_setg(errp, "chardev: overlay: no master chardev given"); + return; + } + if (slave == NULL) { + error_setg(errp, "chardev: overlay: no slave chardev given"); + return; + } + backend->u.overlay = g_new0(ChardevOverlay, 1); + backend->u.overlay->master = g_strdup(master); + backend->u.overlay->slave = g_strdup(slave); +} + static void qemu_chr_parse_mux(QemuOpts *opts, ChardevBackend *backend, Error **errp) { @@ -3943,6 +4087,12 @@ QemuOptsList qemu_chardev_opts = { },{ .name = "chardev", .type = QEMU_OPT_STRING, + },{ + .name = "master", + .type = QEMU_OPT_STRING, + },{ + .name = "slave", + .type = QEMU_OPT_STRING, }, { /* end of list */ } }, @@ -4315,6 +4465,8 @@ static void register_types(void) /* Bug-compatibility: */ register_char_driver("memory", CHARDEV_BACKEND_KIND_MEMORY, qemu_chr_parse_ringbuf, qemu_chr_open_ringbuf); + register_char_driver("overlay", CHARDEV_BACKEND_KIND_OVERLAY, + qemu_chr_parse_overlay, qemu_chr_open_overlay); /* this must be done after machine init, since we register FEs with muxes * as part of realize functions like serial_isa_realizefn when -nographic * is specified -- 2.5.0