diff mbox

[PULL,14/15] qemu-char: add logfile facility to all chardev backends

Message ID 1452873871-138914-16-git-send-email-pbonzini@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Paolo Bonzini Jan. 15, 2016, 4:04 p.m. UTC
From: "Daniel P. Berrange" <berrange@redhat.com>

Typically a UNIX guest OS will log boot messages to a serial
port in addition to any graphical console. An admin user
may also wish to use the serial port for an interactive
console. A virtualization management system may wish to
collect system boot messages by logging the serial port,
but also wish to allow admins interactive access.

Currently providing such a feature forces the mgmt app
to either provide 2 separate serial ports, one for
logging boot messages and one for interactive console
login, or to proxy all output via a separate service
that can multiplex the two needs onto one serial port.
While both are valid approaches, they each have their
own downsides. The former causes confusion and extra
setup work for VM admins creating disk images. The latter
places an extra burden to re-implement much of the QEMU
chardev backends logic in libvirt or even higher level
mgmt apps and adds extra hops in the data transfer path.

A simpler approach that is satisfactory for many use
cases is to allow the QEMU chardev backends to have a
"logfile" property associated with them.

 $QEMU -chardev socket,host=localhost,port=9000,\
                server=on,nowait,id-charserial0,\
		logfile=/var/log/libvirt/qemu/test-serial0.log
       -device isa-serial,chardev=charserial0,id=serial0

This patch introduces a 'ChardevCommon' struct which
is setup as a base for all the ChardevBackend types.
Ideally this would be registered directly as a base
against ChardevBackend, rather than each type, but
the QAPI generator doesn't allow that since the
ChardevBackend is a non-discriminated union. The
ChardevCommon struct provides the optional 'logfile'
parameter, as well as 'logappend' which controls
whether QEMU truncates or appends (default truncate).

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
Message-Id: <1452516281-27519-1-git-send-email-berrange@redhat.com>
[Call qemu_chr_parse_common if cd->parse is NULL. - Paolo]
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 backends/baum.c       |   7 +-
 backends/msmouse.c    |   6 +-
 gdbstub.c             |   3 +-
 include/sysemu/char.h |   9 +-
 qapi-schema.json      |  49 +++++++---
 qemu-char.c           | 248 ++++++++++++++++++++++++++++++++++++++++++--------
 qemu-options.hx       |  48 ++++++----
 spice-qemu-char.c     |  20 +++-
 ui/console.c          |   6 +-
 9 files changed, 313 insertions(+), 83 deletions(-)

Comments

Hervé Poussineau Jan. 21, 2016, 6:16 a.m. UTC | #1
Hi,

This patch (commit d0d7708ba29cbcc343364a46bff981e0ff88366f) regresses the following command line:
qemu-system-i386 -nodefaults -chardev vc,id=mon0 -mon chardev=mon0

Before:
[nothing is print on console]

After:
QEMU 2.5.50 monitor - type 'help' for more information
(qemu)

In both cases, a working vc is created in the GTK+ UI.

Reverting the commit (and fixing the trivial conflict) makes things work again.

Regards,

Hervé


Le 15/01/2016 17:04, Paolo Bonzini a écrit :
> From: "Daniel P. Berrange" <berrange@redhat.com>
>
> Typically a UNIX guest OS will log boot messages to a serial
> port in addition to any graphical console. An admin user
> may also wish to use the serial port for an interactive
> console. A virtualization management system may wish to
> collect system boot messages by logging the serial port,
> but also wish to allow admins interactive access.
>
> Currently providing such a feature forces the mgmt app
> to either provide 2 separate serial ports, one for
> logging boot messages and one for interactive console
> login, or to proxy all output via a separate service
> that can multiplex the two needs onto one serial port.
> While both are valid approaches, they each have their
> own downsides. The former causes confusion and extra
> setup work for VM admins creating disk images. The latter
> places an extra burden to re-implement much of the QEMU
> chardev backends logic in libvirt or even higher level
> mgmt apps and adds extra hops in the data transfer path.
>
> A simpler approach that is satisfactory for many use
> cases is to allow the QEMU chardev backends to have a
> "logfile" property associated with them.
>
>   $QEMU -chardev socket,host=localhost,port=9000,\
>                  server=on,nowait,id-charserial0,\
> 		logfile=/var/log/libvirt/qemu/test-serial0.log
>         -device isa-serial,chardev=charserial0,id=serial0
>
> This patch introduces a 'ChardevCommon' struct which
> is setup as a base for all the ChardevBackend types.
> Ideally this would be registered directly as a base
> against ChardevBackend, rather than each type, but
> the QAPI generator doesn't allow that since the
> ChardevBackend is a non-discriminated union. The
> ChardevCommon struct provides the optional 'logfile'
> parameter, as well as 'logappend' which controls
> whether QEMU truncates or appends (default truncate).
>
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> Message-Id: <1452516281-27519-1-git-send-email-berrange@redhat.com>
> [Call qemu_chr_parse_common if cd->parse is NULL. - Paolo]
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>   backends/baum.c       |   7 +-
>   backends/msmouse.c    |   6 +-
>   gdbstub.c             |   3 +-
>   include/sysemu/char.h |   9 +-
>   qapi-schema.json      |  49 +++++++---
>   qemu-char.c           | 248 ++++++++++++++++++++++++++++++++++++++++++--------
>   qemu-options.hx       |  48 ++++++----
>   spice-qemu-char.c     |  20 +++-
>   ui/console.c          |   6 +-
>   9 files changed, 313 insertions(+), 83 deletions(-)
>
> diff --git a/backends/baum.c b/backends/baum.c
> index 723c658..ba32b61 100644
> --- a/backends/baum.c
> +++ b/backends/baum.c
> @@ -566,6 +566,7 @@ static CharDriverState *chr_baum_init(const char *id,
>                                         ChardevReturn *ret,
>                                         Error **errp)
>   {
> +    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.braille);
>       BaumDriverState *baum;
>       CharDriverState *chr;
>       brlapi_handle_t *handle;
> @@ -576,8 +577,12 @@ static CharDriverState *chr_baum_init(const char *id,
>   #endif
>       int tty;
>
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       baum = g_malloc0(sizeof(BaumDriverState));
> -    baum->chr = chr = qemu_chr_alloc();
> +    baum->chr = chr;
>
>       chr->opaque = baum;
>       chr->chr_write = baum_write;
> diff --git a/backends/msmouse.c b/backends/msmouse.c
> index 0126fa0..476dab5 100644
> --- a/backends/msmouse.c
> +++ b/backends/msmouse.c
> @@ -68,9 +68,13 @@ static CharDriverState *qemu_chr_open_msmouse(const char *id,
>                                                 ChardevReturn *ret,
>                                                 Error **errp)
>   {
> +    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.msmouse);
>       CharDriverState *chr;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       chr->chr_write = msmouse_chr_write;
>       chr->chr_close = msmouse_chr_close;
>       chr->explicit_be_open = true;
> diff --git a/gdbstub.c b/gdbstub.c
> index 9c29aa0..1a84c1a 100644
> --- a/gdbstub.c
> +++ b/gdbstub.c
> @@ -1732,6 +1732,7 @@ int gdbserver_start(const char *device)
>       char gdbstub_device_name[128];
>       CharDriverState *chr = NULL;
>       CharDriverState *mon_chr;
> +    ChardevCommon common = { 0 };
>
>       if (!device)
>           return -1;
> @@ -1768,7 +1769,7 @@ int gdbserver_start(const char *device)
>           qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL);
>
>           /* Initialize a monitor terminal for gdb */
> -        mon_chr = qemu_chr_alloc();
> +        mon_chr = qemu_chr_alloc(&common, &error_abort);
>           mon_chr->chr_write = gdb_monitor_write;
>           monitor_init(mon_chr, 0);
>       } else {
> diff --git a/include/sysemu/char.h b/include/sysemu/char.h
> index aff193f..598dd2b 100644
> --- a/include/sysemu/char.h
> +++ b/include/sysemu/char.h
> @@ -77,6 +77,7 @@ struct CharDriverState {
>       void *opaque;
>       char *label;
>       char *filename;
> +    int logfd;
>       int be_open;
>       int fe_open;
>       int explicit_fe_open;
> @@ -89,13 +90,15 @@ struct CharDriverState {
>   };
>
>   /**
> - * @qemu_chr_alloc:
> + * qemu_chr_alloc:
> + * @backend: the common backend config
> + * @errp: pointer to a NULL-initialized error object
>    *
>    * Allocate and initialize a new CharDriverState.
>    *
> - * Returns: a newly allocated CharDriverState.
> + * Returns: a newly allocated CharDriverState, or NULL on error.
>    */
> -CharDriverState *qemu_chr_alloc(void);
> +CharDriverState *qemu_chr_alloc(ChardevCommon *backend, Error **errp);
>
>   /**
>    * @qemu_chr_new_from_opts:
> diff --git a/qapi-schema.json b/qapi-schema.json
> index 2e31733..dabb5ce 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -3093,6 +3093,21 @@
>   ##
>   { 'command': 'screendump', 'data': {'filename': 'str'} }
>
> +
> +##
> +# @ChardevCommon:
> +#
> +# Configuration shared across all chardev backends
> +#
> +# @logfile: #optional The name of a logfile to save output
> +# @logappend: #optional true to append instead of truncate
> +#             (default to false to truncate)
> +#
> +# Since: 2.6
> +##
> +{ 'struct': 'ChardevCommon', 'data': { '*logfile': 'str',
> +                                       '*logappend': 'bool' } }
> +
>   ##
>   # @ChardevFile:
>   #
> @@ -3107,7 +3122,8 @@
>   ##
>   { 'struct': 'ChardevFile', 'data': { '*in' : 'str',
>                                      'out' : 'str',
> -                                   '*append': 'bool' } }
> +                                   '*append': 'bool' },
> +  'base': 'ChardevCommon' }
>
>   ##
>   # @ChardevHostdev:
> @@ -3120,7 +3136,8 @@
>   #
>   # Since: 1.4
>   ##
> -{ 'struct': 'ChardevHostdev', 'data': { 'device' : 'str' } }
> +{ 'struct': 'ChardevHostdev', 'data': { 'device' : 'str' },
> +  'base': 'ChardevCommon' }
>
>   ##
>   # @ChardevSocket:
> @@ -3147,7 +3164,8 @@
>                                        '*wait'      : 'bool',
>                                        '*nodelay'   : 'bool',
>                                        '*telnet'    : 'bool',
> -                                     '*reconnect' : 'int' } }
> +                                     '*reconnect' : 'int' },
> +  'base': 'ChardevCommon' }
>
>   ##
>   # @ChardevUdp:
> @@ -3160,7 +3178,8 @@
>   # Since: 1.5
>   ##
>   { 'struct': 'ChardevUdp', 'data': { 'remote' : 'SocketAddress',
> -                                  '*local' : 'SocketAddress' } }
> +                                  '*local' : 'SocketAddress' },
> +  'base': 'ChardevCommon' }
>
>   ##
>   # @ChardevMux:
> @@ -3171,7 +3190,8 @@
>   #
>   # Since: 1.5
>   ##
> -{ 'struct': 'ChardevMux', 'data': { 'chardev' : 'str' } }
> +{ 'struct': 'ChardevMux', 'data': { 'chardev' : 'str' },
> +  'base': 'ChardevCommon' }
>
>   ##
>   # @ChardevStdio:
> @@ -3184,7 +3204,9 @@
>   #
>   # Since: 1.5
>   ##
> -{ 'struct': 'ChardevStdio', 'data': { '*signal' : 'bool' } }
> +{ 'struct': 'ChardevStdio', 'data': { '*signal' : 'bool' },
> +  'base': 'ChardevCommon' }
> +
>
>   ##
>   # @ChardevSpiceChannel:
> @@ -3195,7 +3217,8 @@
>   #
>   # Since: 1.5
>   ##
> -{ 'struct': 'ChardevSpiceChannel', 'data': { 'type'  : 'str' } }
> +{ 'struct': 'ChardevSpiceChannel', 'data': { 'type'  : 'str' },
> +  'base': 'ChardevCommon' }
>
>   ##
>   # @ChardevSpicePort:
> @@ -3206,7 +3229,8 @@
>   #
>   # Since: 1.5
>   ##
> -{ 'struct': 'ChardevSpicePort', 'data': { 'fqdn'  : 'str' } }
> +{ 'struct': 'ChardevSpicePort', 'data': { 'fqdn'  : 'str' },
> +  'base': 'ChardevCommon' }
>
>   ##
>   # @ChardevVC:
> @@ -3223,7 +3247,8 @@
>   { 'struct': 'ChardevVC', 'data': { '*width'  : 'int',
>                                    '*height' : 'int',
>                                    '*cols'   : 'int',
> -                                 '*rows'   : 'int' } }
> +                                 '*rows'   : 'int' },
> +  'base': 'ChardevCommon' }
>
>   ##
>   # @ChardevRingbuf:
> @@ -3234,7 +3259,8 @@
>   #
>   # Since: 1.5
>   ##
> -{ 'struct': 'ChardevRingbuf', 'data': { '*size'  : 'int' } }
> +{ 'struct': 'ChardevRingbuf', 'data': { '*size'  : 'int' },
> +  'base': 'ChardevCommon' }
>
>   ##
>   # @ChardevBackend:
> @@ -3243,7 +3269,8 @@
>   #
>   # Since: 1.4 (testdev since 2.2)
>   ##
> -{ 'struct': 'ChardevDummy', 'data': { } }
> +{ 'struct': 'ChardevDummy', 'data': { },
> +  'base': 'ChardevCommon' }
>
>   { 'union': 'ChardevBackend', 'data': { 'file'   : 'ChardevFile',
>                                          'serial' : 'ChardevHostdev',
> diff --git a/qemu-char.c b/qemu-char.c
> index d7be185..11caa56 100644
> --- a/qemu-char.c
> +++ b/qemu-char.c
> @@ -159,10 +159,33 @@ static int sockaddr_to_str(char *dest, int max_len,
>   static QTAILQ_HEAD(CharDriverStateHead, CharDriverState) chardevs =
>       QTAILQ_HEAD_INITIALIZER(chardevs);
>
> -CharDriverState *qemu_chr_alloc(void)
> +static void qemu_chr_free_common(CharDriverState *chr);
> +
> +CharDriverState *qemu_chr_alloc(ChardevCommon *backend, Error **errp)
>   {
>       CharDriverState *chr = g_malloc0(sizeof(CharDriverState));
>       qemu_mutex_init(&chr->chr_write_lock);
> +
> +    if (backend->has_logfile) {
> +        int flags = O_WRONLY | O_CREAT;
> +        if (backend->has_logappend &&
> +            backend->logappend) {
> +            flags |= O_APPEND;
> +        } else {
> +            flags |= O_TRUNC;
> +        }
> +        chr->logfd = qemu_open(backend->logfile, flags, 0666);
> +        if (chr->logfd < 0) {
> +            error_setg_errno(errp, errno,
> +                             "Unable to open logfile %s",
> +                             backend->logfile);
> +            g_free(chr);
> +            return NULL;
> +        }
> +    } else {
> +        chr->logfd = -1;
> +    }
> +
>       return chr;
>   }
>
> @@ -188,12 +211,45 @@ void qemu_chr_be_generic_open(CharDriverState *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(CharDriverState *s,
> +                                  const uint8_t *buf, size_t len)
> +{
> +    size_t done = 0;
> +    ssize_t ret;
> +
> +    if (s->logfd < 0) {
> +        return;
> +    }
> +
> +    while (done < len) {
> +        do {
> +            ret = write(s->logfd, buf + done, len - done);
> +            if (ret == -1 && errno == EAGAIN) {
> +                g_usleep(100);
> +            }
> +        } while (ret == -1 && errno == EAGAIN);
> +
> +        if (ret <= 0) {
> +            return;
> +        }
> +        done += ret;
> +    }
> +}
> +
>   int qemu_chr_fe_write(CharDriverState *s, const uint8_t *buf, int len)
>   {
>       int ret;
>
>       qemu_mutex_lock(&s->chr_write_lock);
>       ret = s->chr_write(s, buf, len);
> +
> +    if (ret > 0) {
> +        qemu_chr_fe_write_log(s, buf, ret);
> +    }
> +
>       qemu_mutex_unlock(&s->chr_write_lock);
>       return ret;
>   }
> @@ -218,6 +274,10 @@ int qemu_chr_fe_write_all(CharDriverState *s, const uint8_t *buf, int len)
>
>           offset += res;
>       }
> +    if (offset > 0) {
> +        qemu_chr_fe_write_log(s, buf, offset);
> +    }
> +
>       qemu_mutex_unlock(&s->chr_write_lock);
>
>       if (res < 0) {
> @@ -365,8 +425,12 @@ static CharDriverState *qemu_chr_open_null(const char *id,
>                                              Error **errp)
>   {
>       CharDriverState *chr;
> +    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.null);
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       chr->chr_write = null_chr_write;
>       chr->explicit_be_open = true;
>       return chr;
> @@ -665,6 +729,7 @@ static CharDriverState *qemu_chr_open_mux(const char *id,
>       ChardevMux *mux = backend->u.mux;
>       CharDriverState *chr, *drv;
>       MuxDriver *d;
> +    ChardevCommon *common = qapi_ChardevMux_base(backend->u.mux);
>
>       drv = qemu_chr_find(mux->chardev);
>       if (drv == NULL) {
> @@ -672,7 +737,10 @@ static CharDriverState *qemu_chr_open_mux(const char *id,
>           return NULL;
>       }
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       d = g_new0(MuxDriver, 1);
>
>       chr->opaque = d;
> @@ -975,12 +1043,16 @@ static void fd_chr_close(struct CharDriverState *chr)
>   }
>
>   /* open a character device to a unix fd */
> -static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out)
> +static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out,
> +                                         ChardevCommon *backend, Error **errp)
>   {
>       CharDriverState *chr;
>       FDCharDriver *s;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(backend, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       s = g_new0(FDCharDriver, 1);
>       s->fd_in = io_channel_from_fd(fd_in);
>       s->fd_out = io_channel_from_fd(fd_out);
> @@ -1005,6 +1077,7 @@ static CharDriverState *qemu_chr_open_pipe(const char *id,
>       char filename_in[CHR_MAX_FILENAME_SIZE];
>       char filename_out[CHR_MAX_FILENAME_SIZE];
>       const char *filename = opts->device;
> +    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.pipe);
>
>       snprintf(filename_in, CHR_MAX_FILENAME_SIZE, "%s.in", filename);
>       snprintf(filename_out, CHR_MAX_FILENAME_SIZE, "%s.out", filename);
> @@ -1021,7 +1094,7 @@ static CharDriverState *qemu_chr_open_pipe(const char *id,
>               return NULL;
>           }
>       }
> -    return qemu_chr_open_fd(fd_in, fd_out);
> +    return qemu_chr_open_fd(fd_in, fd_out, common, errp);
>   }
>
>   /* init terminal so that we can grab keys */
> @@ -1081,6 +1154,7 @@ static CharDriverState *qemu_chr_open_stdio(const char *id,
>       ChardevStdio *opts = backend->u.stdio;
>       CharDriverState *chr;
>       struct sigaction act;
> +    ChardevCommon *common = qapi_ChardevStdio_base(backend->u.stdio);
>
>       if (is_daemonized()) {
>           error_setg(errp, "cannot use stdio with -daemonize");
> @@ -1102,7 +1176,7 @@ static CharDriverState *qemu_chr_open_stdio(const char *id,
>       act.sa_handler = term_stdio_handler;
>       sigaction(SIGCONT, &act, NULL);
>
> -    chr = qemu_chr_open_fd(0, 1);
> +    chr = qemu_chr_open_fd(0, 1, common, errp);
>       chr->chr_close = qemu_chr_close_stdio;
>       chr->chr_set_echo = qemu_chr_set_echo_stdio;
>       if (opts->has_signal) {
> @@ -1324,6 +1398,7 @@ static CharDriverState *qemu_chr_open_pty(const char *id,
>       PtyCharDriver *s;
>       int master_fd, slave_fd;
>       char pty_name[PATH_MAX];
> +    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.pty);
>
>       master_fd = qemu_openpty_raw(&slave_fd, pty_name);
>       if (master_fd < 0) {
> @@ -1334,7 +1409,11 @@ static CharDriverState *qemu_chr_open_pty(const char *id,
>       close(slave_fd);
>       qemu_set_nonblock(master_fd);
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        close(master_fd);
> +        return NULL;
> +    }
>
>       chr->filename = g_strdup_printf("pty:%s", pty_name);
>       ret->pty = g_strdup(pty_name);
> @@ -1557,12 +1636,14 @@ static void qemu_chr_close_tty(CharDriverState *chr)
>       }
>   }
>
> -static CharDriverState *qemu_chr_open_tty_fd(int fd)
> +static CharDriverState *qemu_chr_open_tty_fd(int fd,
> +                                             ChardevCommon *backend,
> +                                             Error **errp)
>   {
>       CharDriverState *chr;
>
>       tty_serial_init(fd, 115200, 'N', 8, 1);
> -    chr = qemu_chr_open_fd(fd, fd);
> +    chr = qemu_chr_open_fd(fd, fd, backend, errp);
>       chr->chr_ioctl = tty_serial_ioctl;
>       chr->chr_close = qemu_chr_close_tty;
>       return chr;
> @@ -1682,7 +1763,9 @@ static void pp_close(CharDriverState *chr)
>       qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
>   }
>
> -static CharDriverState *qemu_chr_open_pp_fd(int fd, Error **errp)
> +static CharDriverState *qemu_chr_open_pp_fd(int fd,
> +                                            ChardevCommon *backend,
> +                                            Error **errp)
>   {
>       CharDriverState *chr;
>       ParallelCharDriver *drv;
> @@ -1697,7 +1780,10 @@ static CharDriverState *qemu_chr_open_pp_fd(int fd, Error **errp)
>       drv->fd = fd;
>       drv->mode = IEEE1284_MODE_COMPAT;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(backend, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       chr->chr_write = null_chr_write;
>       chr->chr_ioctl = pp_ioctl;
>       chr->chr_close = pp_close;
> @@ -1748,11 +1834,16 @@ static int pp_ioctl(CharDriverState *chr, int cmd, void *arg)
>       return 0;
>   }
>
> -static CharDriverState *qemu_chr_open_pp_fd(int fd, Error **errp)
> +static CharDriverState *qemu_chr_open_pp_fd(int fd,
> +                                            ChardevBackend *backend,
> +                                            Error **errp)
>   {
>       CharDriverState *chr;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       chr->opaque = (void *)(intptr_t)fd;
>       chr->chr_write = null_chr_write;
>       chr->chr_ioctl = pp_ioctl;
> @@ -1978,12 +2069,16 @@ static int win_chr_poll(void *opaque)
>   }
>
>   static CharDriverState *qemu_chr_open_win_path(const char *filename,
> +                                               ChardevCommon *backend,
>                                                  Error **errp)
>   {
>       CharDriverState *chr;
>       WinCharState *s;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(backend, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       s = g_new0(WinCharState, 1);
>       chr->opaque = s;
>       chr->chr_write = win_chr_write;
> @@ -1991,7 +2086,7 @@ static CharDriverState *qemu_chr_open_win_path(const char *filename,
>
>       if (win_chr_init(chr, filename, errp) < 0) {
>           g_free(s);
> -        g_free(chr);
> +        qemu_chr_free_common(chr);
>           return NULL;
>       }
>       return chr;
> @@ -2086,8 +2181,12 @@ static CharDriverState *qemu_chr_open_pipe(const char *id,
>       const char *filename = opts->device;
>       CharDriverState *chr;
>       WinCharState *s;
> +    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.pipe);
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       s = g_new0(WinCharState, 1);
>       chr->opaque = s;
>       chr->chr_write = win_chr_write;
> @@ -2095,18 +2194,23 @@ static CharDriverState *qemu_chr_open_pipe(const char *id,
>
>       if (win_chr_pipe_init(chr, filename, errp) < 0) {
>           g_free(s);
> -        g_free(chr);
> +        qemu_chr_free_common(chr);
>           return NULL;
>       }
>       return chr;
>   }
>
> -static CharDriverState *qemu_chr_open_win_file(HANDLE fd_out)
> +static CharDriverState *qemu_chr_open_win_file(HANDLE fd_out,
> +                                               ChardevCommon *backend,
> +                                               Error **errp)
>   {
>       CharDriverState *chr;
>       WinCharState *s;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(backend, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       s = g_new0(WinCharState, 1);
>       s->hcom = fd_out;
>       chr->opaque = s;
> @@ -2119,7 +2223,9 @@ static CharDriverState *qemu_chr_open_win_con(const char *id,
>                                                 ChardevReturn *ret,
>                                                 Error **errp)
>   {
> -    return qemu_chr_open_win_file(GetStdHandle(STD_OUTPUT_HANDLE));
> +    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.console);
> +    return qemu_chr_open_win_file(GetStdHandle(STD_OUTPUT_HANDLE),
> +                                  common, errp);
>   }
>
>   static int win_stdio_write(CharDriverState *chr, const uint8_t *buf, int len)
> @@ -2267,8 +2373,12 @@ static CharDriverState *qemu_chr_open_stdio(const char *id,
>       WinStdioCharState *stdio;
>       DWORD              dwMode;
>       int                is_console = 0;
> +    ChardevCommon *common = qapi_ChardevStdio_base(backend->u.stdio);
>
> -    chr   = qemu_chr_alloc();
> +    chr   = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       stdio = g_new0(WinStdioCharState, 1);
>
>       stdio->hStdIn = GetStdHandle(STD_INPUT_HANDLE);
> @@ -2440,12 +2550,17 @@ static void udp_chr_close(CharDriverState *chr)
>       qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
>   }
>
> -static CharDriverState *qemu_chr_open_udp_fd(int fd)
> +static CharDriverState *qemu_chr_open_udp_fd(int fd,
> +                                             ChardevCommon *backend,
> +                                             Error **errp)
>   {
>       CharDriverState *chr = NULL;
>       NetCharDriver *s = NULL;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(backend, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       s = g_new0(NetCharDriver, 1);
>
>       s->fd = fd;
> @@ -2856,7 +2971,7 @@ static int tcp_chr_sync_read(CharDriverState *chr, const uint8_t *buf, int len)
>   #ifndef _WIN32
>   CharDriverState *qemu_chr_open_eventfd(int eventfd)
>   {
> -    CharDriverState *chr = qemu_chr_open_fd(eventfd, eventfd);
> +    CharDriverState *chr = qemu_chr_open_fd(eventfd, eventfd, NULL, NULL);
>
>       if (chr) {
>           chr->avail_connections = 1;
> @@ -3138,10 +3253,14 @@ static CharDriverState *qemu_chr_open_ringbuf(const char *id,
>                                                 Error **errp)
>   {
>       ChardevRingbuf *opts = backend->u.ringbuf;
> +    ChardevCommon *common = qapi_ChardevRingbuf_base(backend->u.ringbuf);
>       CharDriverState *chr;
>       RingBufCharDriver *d;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       d = g_malloc(sizeof(*d));
>
>       d->size = opts->has_size ? opts->size : 65536;
> @@ -3164,7 +3283,7 @@ static CharDriverState *qemu_chr_open_ringbuf(const char *id,
>
>   fail:
>       g_free(d);
> -    g_free(chr);
> +    qemu_chr_free_common(chr);
>       return NULL;
>   }
>
> @@ -3408,6 +3527,18 @@ fail:
>       return NULL;
>   }
>
> +static 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->has_logappend = true;
> +    backend->logappend = qemu_opt_get_bool(opts, "logappend", false);
> +}
> +
> +
>   static void qemu_chr_parse_file_out(QemuOpts *opts, ChardevBackend *backend,
>                                       Error **errp)
>   {
> @@ -3418,6 +3549,7 @@ static void qemu_chr_parse_file_out(QemuOpts *opts, ChardevBackend *backend,
>           return;
>       }
>       backend->u.file = g_new0(ChardevFile, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevFile_base(backend->u.file));
>       backend->u.file->out = g_strdup(path);
>
>       backend->u.file->has_append = true;
> @@ -3428,6 +3560,7 @@ static void qemu_chr_parse_stdio(QemuOpts *opts, ChardevBackend *backend,
>                                    Error **errp)
>   {
>       backend->u.stdio = g_new0(ChardevStdio, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevStdio_base(backend->u.stdio));
>       backend->u.stdio->has_signal = true;
>       backend->u.stdio->signal = qemu_opt_get_bool(opts, "signal", true);
>   }
> @@ -3443,6 +3576,7 @@ static void qemu_chr_parse_serial(QemuOpts *opts, ChardevBackend *backend,
>           return;
>       }
>       backend->u.serial = g_new0(ChardevHostdev, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevHostdev_base(backend->u.serial));
>       backend->u.serial->device = g_strdup(device);
>   }
>   #endif
> @@ -3458,6 +3592,7 @@ static void qemu_chr_parse_parallel(QemuOpts *opts, ChardevBackend *backend,
>           return;
>       }
>       backend->u.parallel = g_new0(ChardevHostdev, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevHostdev_base(backend->u.parallel));
>       backend->u.parallel->device = g_strdup(device);
>   }
>   #endif
> @@ -3472,6 +3607,7 @@ static void qemu_chr_parse_pipe(QemuOpts *opts, ChardevBackend *backend,
>           return;
>       }
>       backend->u.pipe = g_new0(ChardevHostdev, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevHostdev_base(backend->u.pipe));
>       backend->u.pipe->device = g_strdup(device);
>   }
>
> @@ -3481,6 +3617,7 @@ static void qemu_chr_parse_ringbuf(QemuOpts *opts, ChardevBackend *backend,
>       int val;
>
>       backend->u.ringbuf = g_new0(ChardevRingbuf, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevRingbuf_base(backend->u.ringbuf));
>
>       val = qemu_opt_get_size(opts, "size", 0);
>       if (val != 0) {
> @@ -3499,6 +3636,7 @@ static void qemu_chr_parse_mux(QemuOpts *opts, ChardevBackend *backend,
>           return;
>       }
>       backend->u.mux = g_new0(ChardevMux, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevMux_base(backend->u.mux));
>       backend->u.mux->chardev = g_strdup(chardev);
>   }
>
> @@ -3527,6 +3665,7 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
>       }
>
>       backend->u.socket = g_new0(ChardevSocket, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevSocket_base(backend->u.socket));
>
>       backend->u.socket->has_nodelay = true;
>       backend->u.socket->nodelay = do_nodelay;
> @@ -3588,6 +3727,7 @@ static void qemu_chr_parse_udp(QemuOpts *opts, ChardevBackend *backend,
>       }
>
>       backend->u.udp = g_new0(ChardevUdp, 1);
> +    qemu_chr_parse_common(opts, qapi_ChardevUdp_base(backend->u.udp));
>
>       addr = g_new0(SocketAddress, 1);
>       addr->type = SOCKET_ADDRESS_KIND_INET;
> @@ -3687,7 +3827,12 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
>               error_propagate(errp, local_err);
>               goto qapi_out;
>           }
> +    } else {
> +        ChardevCommon *cc = g_new0(ChardevCommon, 1);
> +        qemu_chr_parse_common(opts, cc);
> +        backend->u.data = cc;
>       }
> +
>       ret = qmp_chardev_add(bid ? bid : id, backend, errp);
>       if (!ret) {
>           goto qapi_out;
> @@ -3819,17 +3964,25 @@ void qemu_chr_fe_release(CharDriverState *s)
>       s->avail_connections++;
>   }
>
> -void qemu_chr_free(CharDriverState *chr)
> +static void qemu_chr_free_common(CharDriverState *chr)
>   {
> -    if (chr->chr_close) {
> -        chr->chr_close(chr);
> -    }
>       g_free(chr->filename);
>       g_free(chr->label);
>       qemu_opts_del(chr->opts);
> +    if (chr->logfd != -1) {
> +        close(chr->logfd);
> +    }
>       g_free(chr);
>   }
>
> +void qemu_chr_free(CharDriverState *chr)
> +{
> +    if (chr->chr_close) {
> +        chr->chr_close(chr);
> +    }
> +    qemu_chr_free_common(chr);
> +}
> +
>   void qemu_chr_delete(CharDriverState *chr)
>   {
>       QTAILQ_REMOVE(&chardevs, chr, next);
> @@ -3982,6 +4135,12 @@ QemuOptsList qemu_chardev_opts = {
>           },{
>               .name = "append",
>               .type = QEMU_OPT_BOOL,
> +        },{
> +            .name = "logfile",
> +            .type = QEMU_OPT_STRING,
> +        },{
> +            .name = "logappend",
> +            .type = QEMU_OPT_BOOL,
>           },
>           { /* end of list */ }
>       },
> @@ -3995,6 +4154,7 @@ static CharDriverState *qmp_chardev_open_file(const char *id,
>                                                 Error **errp)
>   {
>       ChardevFile *file = backend->u.file;
> +    ChardevCommon *common = qapi_ChardevFile_base(backend->u.file);
>       HANDLE out;
>
>       if (file->has_in) {
> @@ -4008,7 +4168,7 @@ static CharDriverState *qmp_chardev_open_file(const char *id,
>           error_setg(errp, "open %s failed", file->out);
>           return NULL;
>       }
> -    return qemu_chr_open_win_file(out);
> +    return qemu_chr_open_win_file(out, common, errp);
>   }
>
>   static CharDriverState *qmp_chardev_open_serial(const char *id,
> @@ -4017,7 +4177,8 @@ static CharDriverState *qmp_chardev_open_serial(const char *id,
>                                                   Error **errp)
>   {
>       ChardevHostdev *serial = backend->u.serial;
> -    return qemu_chr_open_win_path(serial->device, errp);
> +    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.serial);
> +    return qemu_chr_open_win_path(serial->device, common, errp);
>   }
>
>   #else /* WIN32 */
> @@ -4040,6 +4201,7 @@ static CharDriverState *qmp_chardev_open_file(const char *id,
>                                                 Error **errp)
>   {
>       ChardevFile *file = backend->u.file;
> +    ChardevCommon *common = qapi_ChardevFile_base(backend->u.file);
>       int flags, in = -1, out;
>
>       flags = O_WRONLY | O_CREAT | O_BINARY;
> @@ -4063,7 +4225,7 @@ static CharDriverState *qmp_chardev_open_file(const char *id,
>           }
>       }
>
> -    return qemu_chr_open_fd(in, out);
> +    return qemu_chr_open_fd(in, out, common, errp);
>   }
>
>   #ifdef HAVE_CHARDEV_SERIAL
> @@ -4073,6 +4235,7 @@ static CharDriverState *qmp_chardev_open_serial(const char *id,
>                                                   Error **errp)
>   {
>       ChardevHostdev *serial = backend->u.serial;
> +    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.serial);
>       int fd;
>
>       fd = qmp_chardev_open_file_source(serial->device, O_RDWR, errp);
> @@ -4080,7 +4243,7 @@ static CharDriverState *qmp_chardev_open_serial(const char *id,
>           return NULL;
>       }
>       qemu_set_nonblock(fd);
> -    return qemu_chr_open_tty_fd(fd);
> +    return qemu_chr_open_tty_fd(fd, common, errp);
>   }
>   #endif
>
> @@ -4091,13 +4254,14 @@ static CharDriverState *qmp_chardev_open_parallel(const char *id,
>                                                     Error **errp)
>   {
>       ChardevHostdev *parallel = backend->u.parallel;
> +    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.parallel);
>       int fd;
>
>       fd = qmp_chardev_open_file_source(parallel->device, O_RDWR, errp);
>       if (fd < 0) {
>           return NULL;
>       }
> -    return qemu_chr_open_pp_fd(fd, errp);
> +    return qemu_chr_open_pp_fd(fd, common, errp);
>   }
>   #endif
>
> @@ -4142,8 +4306,12 @@ static CharDriverState *qmp_chardev_open_socket(const char *id,
>       bool is_telnet      = sock->has_telnet  ? sock->telnet  : false;
>       bool is_waitconnect = sock->has_wait    ? sock->wait    : false;
>       int64_t reconnect   = sock->has_reconnect ? sock->reconnect : 0;
> +    ChardevCommon *common = qapi_ChardevSocket_base(backend->u.socket);
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       s = g_new0(TCPCharDriver, 1);
>
>       s->fd = -1;
> @@ -4182,8 +4350,7 @@ static CharDriverState *qmp_chardev_open_socket(const char *id,
>           socket_try_connect(chr);
>       } else if (!qemu_chr_open_socket_fd(chr, errp)) {
>           g_free(s);
> -        g_free(chr->filename);
> -        g_free(chr);
> +        qemu_chr_free_common(chr);
>           return NULL;
>       }
>
> @@ -4203,13 +4370,14 @@ static CharDriverState *qmp_chardev_open_udp(const char *id,
>                                                Error **errp)
>   {
>       ChardevUdp *udp = backend->u.udp;
> +    ChardevCommon *common = qapi_ChardevUdp_base(backend->u.udp);
>       int fd;
>
>       fd = socket_dgram(udp->remote, udp->local, errp);
>       if (fd < 0) {
>           return NULL;
>       }
> -    return qemu_chr_open_udp_fd(fd);
> +    return qemu_chr_open_udp_fd(fd, common, errp);
>   }
>
>   ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 215d00d..b4763ba 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -2089,40 +2089,43 @@ The general form of a character device option is:
>   ETEXI
>
>   DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
> -    "-chardev null,id=id[,mux=on|off]\n"
> +    "-chardev null,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
>       "-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay][,reconnect=seconds]\n"
> -    "         [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (tcp)\n"
> -    "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (unix)\n"
> +    "         [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off]\n"
> +    "         [,logfile=PATH][,logappend=on|off] (tcp)\n"
> +    "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds]\n"
> +    "         [,mux=on|off][,logfile=PATH][,logappend=on|off] (unix)\n"
>       "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
>       "         [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n"
> -    "-chardev msmouse,id=id[,mux=on|off]\n"
> +    "         [,logfile=PATH][,logappend=on|off]\n"
> +    "-chardev msmouse,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
>       "-chardev vc,id=id[[,width=width][,height=height]][[,cols=cols][,rows=rows]]\n"
> -    "         [,mux=on|off]\n"
> -    "-chardev ringbuf,id=id[,size=size]\n"
> -    "-chardev file,id=id,path=path[,mux=on|off]\n"
> -    "-chardev pipe,id=id,path=path[,mux=on|off]\n"
> +    "         [,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
> +    "-chardev ringbuf,id=id[,size=size][,logfile=PATH][,logappend=on|off]\n"
> +    "-chardev file,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
> +    "-chardev pipe,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
>   #ifdef _WIN32
> -    "-chardev console,id=id[,mux=on|off]\n"
> -    "-chardev serial,id=id,path=path[,mux=on|off]\n"
> +    "-chardev console,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
> +    "-chardev serial,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
>   #else
> -    "-chardev pty,id=id[,mux=on|off]\n"
> -    "-chardev stdio,id=id[,mux=on|off][,signal=on|off]\n"
> +    "-chardev pty,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
> +    "-chardev stdio,id=id[,mux=on|off][,signal=on|off][,logfile=PATH][,logappend=on|off]\n"
>   #endif
>   #ifdef CONFIG_BRLAPI
> -    "-chardev braille,id=id[,mux=on|off]\n"
> +    "-chardev braille,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
>   #endif
>   #if defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) \
>           || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
> -    "-chardev serial,id=id,path=path[,mux=on|off]\n"
> -    "-chardev tty,id=id,path=path[,mux=on|off]\n"
> +    "-chardev serial,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
> +    "-chardev tty,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
>   #endif
>   #if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
> -    "-chardev parallel,id=id,path=path[,mux=on|off]\n"
> -    "-chardev parport,id=id,path=path[,mux=on|off]\n"
> +    "-chardev parallel,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
> +    "-chardev parport,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
>   #endif
>   #if defined(CONFIG_SPICE)
> -    "-chardev spicevmc,id=id,name=name[,debug=debug]\n"
> -    "-chardev spiceport,id=id,name=name[,debug=debug]\n"
> +    "-chardev spicevmc,id=id,name=name[,debug=debug][,logfile=PATH][,logappend=on|off]\n"
> +    "-chardev spiceport,id=id,name=name[,debug=debug][,logfile=PATH][,logappend=on|off]\n"
>   #endif
>       , QEMU_ARCH_ALL
>   )
> @@ -2158,7 +2161,12 @@ A character device may be used in multiplexing mode by multiple front-ends.
>   The key sequence of @key{Control-a} and @key{c} will rotate the input focus
>   between attached front-ends. Specify @option{mux=on} to enable this mode.
>
> -Options to each backend are described below.
> +Every backend supports the @option{logfile} option, which supplies the path
> +to a file to record all data transmitted via the backend. The @option{logappend}
> +option controls whether the log file will be truncated or appended to when
> +opened.
> +
> +Further options to each backend are described below.
>
>   @item -chardev null ,id=@var{id}
>   A void device. This device will not emit any data, and will drop any data it
> diff --git a/spice-qemu-char.c b/spice-qemu-char.c
> index e70e0f7..8951d7c 100644
> --- a/spice-qemu-char.c
> +++ b/spice-qemu-char.c
> @@ -271,13 +271,18 @@ static void spice_chr_accept_input(struct CharDriverState *chr)
>   }
>
>   static CharDriverState *chr_open(const char *subtype,
> -    void (*set_fe_open)(struct CharDriverState *, int))
> -
> +                                 void (*set_fe_open)(struct CharDriverState *,
> +                                                     int),
> +                                 ChardevCommon *backend,
> +                                 Error **errp)
>   {
>       CharDriverState *chr;
>       SpiceCharDriver *s;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(backend, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       s = g_malloc0(sizeof(SpiceCharDriver));
>       s->chr = chr;
>       s->active = false;
> @@ -303,6 +308,7 @@ static CharDriverState *qemu_chr_open_spice_vmc(const char *id,
>   {
>       const char *type = backend->u.spicevmc->type;
>       const char **psubtype = spice_server_char_device_recognized_subtypes();
> +    ChardevCommon *common = qapi_ChardevSpiceChannel_base(backend->u.spicevmc);
>
>       for (; *psubtype != NULL; ++psubtype) {
>           if (strcmp(type, *psubtype) == 0) {
> @@ -315,7 +321,7 @@ static CharDriverState *qemu_chr_open_spice_vmc(const char *id,
>           return NULL;
>       }
>
> -    return chr_open(type, spice_vmc_set_fe_open);
> +    return chr_open(type, spice_vmc_set_fe_open, common, errp);
>   }
>
>   #if SPICE_SERVER_VERSION >= 0x000c02
> @@ -325,6 +331,7 @@ static CharDriverState *qemu_chr_open_spice_port(const char *id,
>                                                    Error **errp)
>   {
>       const char *name = backend->u.spiceport->fqdn;
> +    ChardevCommon *common = qapi_ChardevSpicePort_base(backend->u.spiceport);
>       CharDriverState *chr;
>       SpiceCharDriver *s;
>
> @@ -333,7 +340,10 @@ static CharDriverState *qemu_chr_open_spice_port(const char *id,
>           return NULL;
>       }
>
> -    chr = chr_open("port", spice_port_set_fe_open);
> +    chr = chr_open("port", spice_port_set_fe_open, common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>       s = chr->opaque;
>       s->sin.portname = g_strdup(name);
>
> diff --git a/ui/console.c b/ui/console.c
> index 4b65c34..fe950c6 100644
> --- a/ui/console.c
> +++ b/ui/console.c
> @@ -1953,12 +1953,16 @@ static void text_console_do_init(CharDriverState *chr, DisplayState *ds)
>
>   static CharDriverState *text_console_init(ChardevVC *vc, Error **errp)
>   {
> +    ChardevCommon *common = qapi_ChardevVC_base(vc);
>       CharDriverState *chr;
>       QemuConsole *s;
>       unsigned width = 0;
>       unsigned height = 0;
>
> -    chr = qemu_chr_alloc();
> +    chr = qemu_chr_alloc(common, errp);
> +    if (!chr) {
> +        return NULL;
> +    }
>
>       if (vc->has_width) {
>           width = vc->width;
>
Daniel P. Berrangé Jan. 21, 2016, 8:56 a.m. UTC | #2
On Thu, Jan 21, 2016 at 07:16:25AM +0100, Hervé Poussineau wrote:
> Hi,
> 
> This patch (commit d0d7708ba29cbcc343364a46bff981e0ff88366f) regresses the following command line:
> qemu-system-i386 -nodefaults -chardev vc,id=mon0 -mon chardev=mon0
> 
> Before:
> [nothing is print on console]
> 
> After:
> QEMU 2.5.50 monitor - type 'help' for more information
> (qemu)
> 
> In both cases, a working vc is created in the GTK+ UI.
> 
> Reverting the commit (and fixing the trivial conflict) makes things
> work again.

Thanks for the heads-up, I'll investigate and submit a fix


Regards,
Daniel
Markus Armbruster Feb. 12, 2016, 4:49 p.m. UTC | #3
"Daniel P. Berrange" <berrange@redhat.com> writes:

> On Thu, Jan 21, 2016 at 07:16:25AM +0100, Hervé Poussineau wrote:
>> Hi,
>> 
>> This patch (commit d0d7708ba29cbcc343364a46bff981e0ff88366f) regresses the following command line:
>> qemu-system-i386 -nodefaults -chardev vc,id=mon0 -mon chardev=mon0
>> 
>> Before:
>> [nothing is print on console]
>> 
>> After:
>> QEMU 2.5.50 monitor - type 'help' for more information
>> (qemu)
>> 
>> In both cases, a working vc is created in the GTK+ UI.
>> 
>> Reverting the commit (and fixing the trivial conflict) makes things
>> work again.
>
> Thanks for the heads-up, I'll investigate and submit a fix

For the record, it also breaks ivshmem-test when the slow tests are
included.  I'd be willing to test your fix; got a pointer for me?
Daniel P. Berrangé Feb. 12, 2016, 4:54 p.m. UTC | #4
On Fri, Feb 12, 2016 at 05:49:38PM +0100, Markus Armbruster wrote:
> "Daniel P. Berrange" <berrange@redhat.com> writes:
> 
> > On Thu, Jan 21, 2016 at 07:16:25AM +0100, Hervé Poussineau wrote:
> >> Hi,
> >> 
> >> This patch (commit d0d7708ba29cbcc343364a46bff981e0ff88366f) regresses the following command line:
> >> qemu-system-i386 -nodefaults -chardev vc,id=mon0 -mon chardev=mon0
> >> 
> >> Before:
> >> [nothing is print on console]
> >> 
> >> After:
> >> QEMU 2.5.50 monitor - type 'help' for more information
> >> (qemu)
> >> 
> >> In both cases, a working vc is created in the GTK+ UI.
> >> 
> >> Reverting the commit (and fixing the trivial conflict) makes things
> >> work again.
> >
> > Thanks for the heads-up, I'll investigate and submit a fix
> 
> For the record, it also breaks ivshmem-test when the slow tests are
> included.  I'd be willing to test your fix; got a pointer for me?

https://lists.gnu.org/archive/html/qemu-devel/2016-02/msg02738.html

Regards,
Daniel
Markus Armbruster Feb. 12, 2016, 5:12 p.m. UTC | #5
"Daniel P. Berrange" <berrange@redhat.com> writes:

> On Fri, Feb 12, 2016 at 05:49:38PM +0100, Markus Armbruster wrote:
>> "Daniel P. Berrange" <berrange@redhat.com> writes:
>> 
>> > On Thu, Jan 21, 2016 at 07:16:25AM +0100, Hervé Poussineau wrote:
>> >> Hi,
>> >> 
>> >> This patch (commit d0d7708ba29cbcc343364a46bff981e0ff88366f) regresses the following command line:
>> >> qemu-system-i386 -nodefaults -chardev vc,id=mon0 -mon chardev=mon0
>> >> 
>> >> Before:
>> >> [nothing is print on console]
>> >> 
>> >> After:
>> >> QEMU 2.5.50 monitor - type 'help' for more information
>> >> (qemu)
>> >> 
>> >> In both cases, a working vc is created in the GTK+ UI.
>> >> 
>> >> Reverting the commit (and fixing the trivial conflict) makes things
>> >> work again.
>> >
>> > Thanks for the heads-up, I'll investigate and submit a fix
>> 
>> For the record, it also breaks ivshmem-test when the slow tests are
>> included.  I'd be willing to test your fix; got a pointer for me?
>
> https://lists.gnu.org/archive/html/qemu-devel/2016-02/msg02738.html

No luck.  Please try the following reproducer:

  $ make tests/ivshmem-test
  $ QTEST_QEMU_BINARY='x86_64-softmmu/qemu-system-x86_64' QTEST_QEMU_IMG=qemu-img MALLOC_PERTURB_=${MALLOC_PERTURB_:-$((RANDOM % 255 + 1))} gtester -k --verbose -m slow tests/ivshmem-test

You can instead run make check SPEED=slow, but that runs many more
tests.
Daniel P. Berrangé Feb. 12, 2016, 6:04 p.m. UTC | #6
On Fri, Feb 12, 2016 at 06:12:46PM +0100, Markus Armbruster wrote:
> "Daniel P. Berrange" <berrange@redhat.com> writes:
> 
> > On Fri, Feb 12, 2016 at 05:49:38PM +0100, Markus Armbruster wrote:
> >> "Daniel P. Berrange" <berrange@redhat.com> writes:
> >> 
> >> > On Thu, Jan 21, 2016 at 07:16:25AM +0100, Hervé Poussineau wrote:
> >> >> Hi,
> >> >> 
> >> >> This patch (commit d0d7708ba29cbcc343364a46bff981e0ff88366f) regresses the following command line:
> >> >> qemu-system-i386 -nodefaults -chardev vc,id=mon0 -mon chardev=mon0
> >> >> 
> >> >> Before:
> >> >> [nothing is print on console]
> >> >> 
> >> >> After:
> >> >> QEMU 2.5.50 monitor - type 'help' for more information
> >> >> (qemu)
> >> >> 
> >> >> In both cases, a working vc is created in the GTK+ UI.
> >> >> 
> >> >> Reverting the commit (and fixing the trivial conflict) makes things
> >> >> work again.
> >> >
> >> > Thanks for the heads-up, I'll investigate and submit a fix
> >> 
> >> For the record, it also breaks ivshmem-test when the slow tests are
> >> included.  I'd be willing to test your fix; got a pointer for me?
> >
> > https://lists.gnu.org/archive/html/qemu-devel/2016-02/msg02738.html
> 
> No luck.  Please try the following reproducer:
> 
>   $ make tests/ivshmem-test
>   $ QTEST_QEMU_BINARY='x86_64-softmmu/qemu-system-x86_64' QTEST_QEMU_IMG=qemu-img MALLOC_PERTURB_=${MALLOC_PERTURB_:-$((RANDOM % 255 + 1))} gtester -k --verbose -m slow tests/ivshmem-test

Yes, I can reproduce that. Will put it on my todo list for next week.

Regards,
Daniel
Daniel P. Berrangé Feb. 12, 2016, 9:53 p.m. UTC | #7
On Fri, Feb 12, 2016 at 06:04:59PM +0000, Daniel P. Berrange wrote:
> On Fri, Feb 12, 2016 at 06:12:46PM +0100, Markus Armbruster wrote:
> > "Daniel P. Berrange" <berrange@redhat.com> writes:
> > 
> > > On Fri, Feb 12, 2016 at 05:49:38PM +0100, Markus Armbruster wrote:
> > >> "Daniel P. Berrange" <berrange@redhat.com> writes:
> > >> 
> > >> > On Thu, Jan 21, 2016 at 07:16:25AM +0100, Hervé Poussineau wrote:
> > >> >> Hi,
> > >> >> 
> > >> >> This patch (commit d0d7708ba29cbcc343364a46bff981e0ff88366f) regresses the following command line:
> > >> >> qemu-system-i386 -nodefaults -chardev vc,id=mon0 -mon chardev=mon0
> > >> >> 
> > >> >> Before:
> > >> >> [nothing is print on console]
> > >> >> 
> > >> >> After:
> > >> >> QEMU 2.5.50 monitor - type 'help' for more information
> > >> >> (qemu)
> > >> >> 
> > >> >> In both cases, a working vc is created in the GTK+ UI.
> > >> >> 
> > >> >> Reverting the commit (and fixing the trivial conflict) makes things
> > >> >> work again.
> > >> >
> > >> > Thanks for the heads-up, I'll investigate and submit a fix
> > >> 
> > >> For the record, it also breaks ivshmem-test when the slow tests are
> > >> included.  I'd be willing to test your fix; got a pointer for me?
> > >
> > > https://lists.gnu.org/archive/html/qemu-devel/2016-02/msg02738.html
> > 
> > No luck.  Please try the following reproducer:
> > 
> >   $ make tests/ivshmem-test
> >   $ QTEST_QEMU_BINARY='x86_64-softmmu/qemu-system-x86_64' QTEST_QEMU_IMG=qemu-img MALLOC_PERTURB_=${MALLOC_PERTURB_:-$((RANDOM % 255 + 1))} gtester -k --verbose -m slow tests/ivshmem-test
> 
> Yes, I can reproduce that. Will put it on my todo list for next week.

So I investigated this, and my commit here is not the cause. My commit
introduced a crash bug due to NULL pointer reference. This was fixed
already in by Marc-Andre in


  commit 9940c3236f318949c92099163281d5d23a9fcf4f
  Author: Marc-André Lureau <marcandre.lureau@redhat.com>
  Date:   Mon Dec 21 12:10:13 2015 +0100

    ivshmem: use a single eventfd callback, get rid of CharDriver


I bisected the test failure again and found this change to blame:

  commit 428c3ece97179557f2753071fb0ca97a03437267
  Author: Stefano Stabellini <stefano.stabellini@eu.citrix.com>
  Date:   Wed Jan 13 14:59:09 2016 +0000

    fix MSI injection on Xen

which makes more sense, because the test case failure you identified
is specifically with the MSI code in ivshmem


Regards,
Daniel
Markus Armbruster Feb. 15, 2016, 8:23 a.m. UTC | #8
"Daniel P. Berrange" <berrange@redhat.com> writes:

> On Fri, Feb 12, 2016 at 06:04:59PM +0000, Daniel P. Berrange wrote:
>> On Fri, Feb 12, 2016 at 06:12:46PM +0100, Markus Armbruster wrote:
[...]
>> > >> For the record, it also breaks ivshmem-test when the slow tests are
>> > >> included.  I'd be willing to test your fix; got a pointer for me?
>> > >
>> > > https://lists.gnu.org/archive/html/qemu-devel/2016-02/msg02738.html
>> > 
>> > No luck.  Please try the following reproducer:
>> > 
>> >   $ make tests/ivshmem-test
>> >   $ QTEST_QEMU_BINARY='x86_64-softmmu/qemu-system-x86_64' QTEST_QEMU_IMG=qemu-img MALLOC_PERTURB_=${MALLOC_PERTURB_:-$((RANDOM % 255 + 1))} gtester -k --verbose -m slow tests/ivshmem-test
>> 
>> Yes, I can reproduce that. Will put it on my todo list for next week.
>
> So I investigated this, and my commit here is not the cause. My commit
> introduced a crash bug due to NULL pointer reference. This was fixed
> already in by Marc-Andre in
>
>
>   commit 9940c3236f318949c92099163281d5d23a9fcf4f
>   Author: Marc-André Lureau <marcandre.lureau@redhat.com>
>   Date:   Mon Dec 21 12:10:13 2015 +0100
>
>     ivshmem: use a single eventfd callback, get rid of CharDriver
>
>
> I bisected the test failure again and found this change to blame:
>
>   commit 428c3ece97179557f2753071fb0ca97a03437267
>   Author: Stefano Stabellini <stefano.stabellini@eu.citrix.com>
>   Date:   Wed Jan 13 14:59:09 2016 +0000
>
>     fix MSI injection on Xen
>
> which makes more sense, because the test case failure you identified
> is specifically with the MSI code in ivshmem

Thanks, and sorry for my sloppy bisecting.
diff mbox

Patch

diff --git a/backends/baum.c b/backends/baum.c
index 723c658..ba32b61 100644
--- a/backends/baum.c
+++ b/backends/baum.c
@@ -566,6 +566,7 @@  static CharDriverState *chr_baum_init(const char *id,
                                       ChardevReturn *ret,
                                       Error **errp)
 {
+    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.braille);
     BaumDriverState *baum;
     CharDriverState *chr;
     brlapi_handle_t *handle;
@@ -576,8 +577,12 @@  static CharDriverState *chr_baum_init(const char *id,
 #endif
     int tty;
 
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
     baum = g_malloc0(sizeof(BaumDriverState));
-    baum->chr = chr = qemu_chr_alloc();
+    baum->chr = chr;
 
     chr->opaque = baum;
     chr->chr_write = baum_write;
diff --git a/backends/msmouse.c b/backends/msmouse.c
index 0126fa0..476dab5 100644
--- a/backends/msmouse.c
+++ b/backends/msmouse.c
@@ -68,9 +68,13 @@  static CharDriverState *qemu_chr_open_msmouse(const char *id,
                                               ChardevReturn *ret,
                                               Error **errp)
 {
+    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.msmouse);
     CharDriverState *chr;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
     chr->chr_write = msmouse_chr_write;
     chr->chr_close = msmouse_chr_close;
     chr->explicit_be_open = true;
diff --git a/gdbstub.c b/gdbstub.c
index 9c29aa0..1a84c1a 100644
--- a/gdbstub.c
+++ b/gdbstub.c
@@ -1732,6 +1732,7 @@  int gdbserver_start(const char *device)
     char gdbstub_device_name[128];
     CharDriverState *chr = NULL;
     CharDriverState *mon_chr;
+    ChardevCommon common = { 0 };
 
     if (!device)
         return -1;
@@ -1768,7 +1769,7 @@  int gdbserver_start(const char *device)
         qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL);
 
         /* Initialize a monitor terminal for gdb */
-        mon_chr = qemu_chr_alloc();
+        mon_chr = qemu_chr_alloc(&common, &error_abort);
         mon_chr->chr_write = gdb_monitor_write;
         monitor_init(mon_chr, 0);
     } else {
diff --git a/include/sysemu/char.h b/include/sysemu/char.h
index aff193f..598dd2b 100644
--- a/include/sysemu/char.h
+++ b/include/sysemu/char.h
@@ -77,6 +77,7 @@  struct CharDriverState {
     void *opaque;
     char *label;
     char *filename;
+    int logfd;
     int be_open;
     int fe_open;
     int explicit_fe_open;
@@ -89,13 +90,15 @@  struct CharDriverState {
 };
 
 /**
- * @qemu_chr_alloc:
+ * qemu_chr_alloc:
+ * @backend: the common backend config
+ * @errp: pointer to a NULL-initialized error object
  *
  * Allocate and initialize a new CharDriverState.
  *
- * Returns: a newly allocated CharDriverState.
+ * Returns: a newly allocated CharDriverState, or NULL on error.
  */
-CharDriverState *qemu_chr_alloc(void);
+CharDriverState *qemu_chr_alloc(ChardevCommon *backend, Error **errp);
 
 /**
  * @qemu_chr_new_from_opts:
diff --git a/qapi-schema.json b/qapi-schema.json
index 2e31733..dabb5ce 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3093,6 +3093,21 @@ 
 ##
 { 'command': 'screendump', 'data': {'filename': 'str'} }
 
+
+##
+# @ChardevCommon:
+#
+# Configuration shared across all chardev backends
+#
+# @logfile: #optional The name of a logfile to save output
+# @logappend: #optional true to append instead of truncate
+#             (default to false to truncate)
+#
+# Since: 2.6
+##
+{ 'struct': 'ChardevCommon', 'data': { '*logfile': 'str',
+                                       '*logappend': 'bool' } }
+
 ##
 # @ChardevFile:
 #
@@ -3107,7 +3122,8 @@ 
 ##
 { 'struct': 'ChardevFile', 'data': { '*in' : 'str',
                                    'out' : 'str',
-                                   '*append': 'bool' } }
+                                   '*append': 'bool' },
+  'base': 'ChardevCommon' }
 
 ##
 # @ChardevHostdev:
@@ -3120,7 +3136,8 @@ 
 #
 # Since: 1.4
 ##
-{ 'struct': 'ChardevHostdev', 'data': { 'device' : 'str' } }
+{ 'struct': 'ChardevHostdev', 'data': { 'device' : 'str' },
+  'base': 'ChardevCommon' }
 
 ##
 # @ChardevSocket:
@@ -3147,7 +3164,8 @@ 
                                      '*wait'      : 'bool',
                                      '*nodelay'   : 'bool',
                                      '*telnet'    : 'bool',
-                                     '*reconnect' : 'int' } }
+                                     '*reconnect' : 'int' },
+  'base': 'ChardevCommon' }
 
 ##
 # @ChardevUdp:
@@ -3160,7 +3178,8 @@ 
 # Since: 1.5
 ##
 { 'struct': 'ChardevUdp', 'data': { 'remote' : 'SocketAddress',
-                                  '*local' : 'SocketAddress' } }
+                                  '*local' : 'SocketAddress' },
+  'base': 'ChardevCommon' }
 
 ##
 # @ChardevMux:
@@ -3171,7 +3190,8 @@ 
 #
 # Since: 1.5
 ##
-{ 'struct': 'ChardevMux', 'data': { 'chardev' : 'str' } }
+{ 'struct': 'ChardevMux', 'data': { 'chardev' : 'str' },
+  'base': 'ChardevCommon' }
 
 ##
 # @ChardevStdio:
@@ -3184,7 +3204,9 @@ 
 #
 # Since: 1.5
 ##
-{ 'struct': 'ChardevStdio', 'data': { '*signal' : 'bool' } }
+{ 'struct': 'ChardevStdio', 'data': { '*signal' : 'bool' },
+  'base': 'ChardevCommon' }
+
 
 ##
 # @ChardevSpiceChannel:
@@ -3195,7 +3217,8 @@ 
 #
 # Since: 1.5
 ##
-{ 'struct': 'ChardevSpiceChannel', 'data': { 'type'  : 'str' } }
+{ 'struct': 'ChardevSpiceChannel', 'data': { 'type'  : 'str' },
+  'base': 'ChardevCommon' }
 
 ##
 # @ChardevSpicePort:
@@ -3206,7 +3229,8 @@ 
 #
 # Since: 1.5
 ##
-{ 'struct': 'ChardevSpicePort', 'data': { 'fqdn'  : 'str' } }
+{ 'struct': 'ChardevSpicePort', 'data': { 'fqdn'  : 'str' },
+  'base': 'ChardevCommon' }
 
 ##
 # @ChardevVC:
@@ -3223,7 +3247,8 @@ 
 { 'struct': 'ChardevVC', 'data': { '*width'  : 'int',
                                  '*height' : 'int',
                                  '*cols'   : 'int',
-                                 '*rows'   : 'int' } }
+                                 '*rows'   : 'int' },
+  'base': 'ChardevCommon' }
 
 ##
 # @ChardevRingbuf:
@@ -3234,7 +3259,8 @@ 
 #
 # Since: 1.5
 ##
-{ 'struct': 'ChardevRingbuf', 'data': { '*size'  : 'int' } }
+{ 'struct': 'ChardevRingbuf', 'data': { '*size'  : 'int' },
+  'base': 'ChardevCommon' }
 
 ##
 # @ChardevBackend:
@@ -3243,7 +3269,8 @@ 
 #
 # Since: 1.4 (testdev since 2.2)
 ##
-{ 'struct': 'ChardevDummy', 'data': { } }
+{ 'struct': 'ChardevDummy', 'data': { },
+  'base': 'ChardevCommon' }
 
 { 'union': 'ChardevBackend', 'data': { 'file'   : 'ChardevFile',
                                        'serial' : 'ChardevHostdev',
diff --git a/qemu-char.c b/qemu-char.c
index d7be185..11caa56 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -159,10 +159,33 @@  static int sockaddr_to_str(char *dest, int max_len,
 static QTAILQ_HEAD(CharDriverStateHead, CharDriverState) chardevs =
     QTAILQ_HEAD_INITIALIZER(chardevs);
 
-CharDriverState *qemu_chr_alloc(void)
+static void qemu_chr_free_common(CharDriverState *chr);
+
+CharDriverState *qemu_chr_alloc(ChardevCommon *backend, Error **errp)
 {
     CharDriverState *chr = g_malloc0(sizeof(CharDriverState));
     qemu_mutex_init(&chr->chr_write_lock);
+
+    if (backend->has_logfile) {
+        int flags = O_WRONLY | O_CREAT;
+        if (backend->has_logappend &&
+            backend->logappend) {
+            flags |= O_APPEND;
+        } else {
+            flags |= O_TRUNC;
+        }
+        chr->logfd = qemu_open(backend->logfile, flags, 0666);
+        if (chr->logfd < 0) {
+            error_setg_errno(errp, errno,
+                             "Unable to open logfile %s",
+                             backend->logfile);
+            g_free(chr);
+            return NULL;
+        }
+    } else {
+        chr->logfd = -1;
+    }
+
     return chr;
 }
 
@@ -188,12 +211,45 @@  void qemu_chr_be_generic_open(CharDriverState *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(CharDriverState *s,
+                                  const uint8_t *buf, size_t len)
+{
+    size_t done = 0;
+    ssize_t ret;
+
+    if (s->logfd < 0) {
+        return;
+    }
+
+    while (done < len) {
+        do {
+            ret = write(s->logfd, buf + done, len - done);
+            if (ret == -1 && errno == EAGAIN) {
+                g_usleep(100);
+            }
+        } while (ret == -1 && errno == EAGAIN);
+
+        if (ret <= 0) {
+            return;
+        }
+        done += ret;
+    }
+}
+
 int qemu_chr_fe_write(CharDriverState *s, const uint8_t *buf, int len)
 {
     int ret;
 
     qemu_mutex_lock(&s->chr_write_lock);
     ret = s->chr_write(s, buf, len);
+
+    if (ret > 0) {
+        qemu_chr_fe_write_log(s, buf, ret);
+    }
+
     qemu_mutex_unlock(&s->chr_write_lock);
     return ret;
 }
@@ -218,6 +274,10 @@  int qemu_chr_fe_write_all(CharDriverState *s, const uint8_t *buf, int len)
 
         offset += res;
     }
+    if (offset > 0) {
+        qemu_chr_fe_write_log(s, buf, offset);
+    }
+
     qemu_mutex_unlock(&s->chr_write_lock);
 
     if (res < 0) {
@@ -365,8 +425,12 @@  static CharDriverState *qemu_chr_open_null(const char *id,
                                            Error **errp)
 {
     CharDriverState *chr;
+    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.null);
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
     chr->chr_write = null_chr_write;
     chr->explicit_be_open = true;
     return chr;
@@ -665,6 +729,7 @@  static CharDriverState *qemu_chr_open_mux(const char *id,
     ChardevMux *mux = backend->u.mux;
     CharDriverState *chr, *drv;
     MuxDriver *d;
+    ChardevCommon *common = qapi_ChardevMux_base(backend->u.mux);
 
     drv = qemu_chr_find(mux->chardev);
     if (drv == NULL) {
@@ -672,7 +737,10 @@  static CharDriverState *qemu_chr_open_mux(const char *id,
         return NULL;
     }
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
     d = g_new0(MuxDriver, 1);
 
     chr->opaque = d;
@@ -975,12 +1043,16 @@  static void fd_chr_close(struct CharDriverState *chr)
 }
 
 /* open a character device to a unix fd */
-static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out)
+static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out,
+                                         ChardevCommon *backend, Error **errp)
 {
     CharDriverState *chr;
     FDCharDriver *s;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(backend, errp);
+    if (!chr) {
+        return NULL;
+    }
     s = g_new0(FDCharDriver, 1);
     s->fd_in = io_channel_from_fd(fd_in);
     s->fd_out = io_channel_from_fd(fd_out);
@@ -1005,6 +1077,7 @@  static CharDriverState *qemu_chr_open_pipe(const char *id,
     char filename_in[CHR_MAX_FILENAME_SIZE];
     char filename_out[CHR_MAX_FILENAME_SIZE];
     const char *filename = opts->device;
+    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.pipe);
 
     snprintf(filename_in, CHR_MAX_FILENAME_SIZE, "%s.in", filename);
     snprintf(filename_out, CHR_MAX_FILENAME_SIZE, "%s.out", filename);
@@ -1021,7 +1094,7 @@  static CharDriverState *qemu_chr_open_pipe(const char *id,
             return NULL;
         }
     }
-    return qemu_chr_open_fd(fd_in, fd_out);
+    return qemu_chr_open_fd(fd_in, fd_out, common, errp);
 }
 
 /* init terminal so that we can grab keys */
@@ -1081,6 +1154,7 @@  static CharDriverState *qemu_chr_open_stdio(const char *id,
     ChardevStdio *opts = backend->u.stdio;
     CharDriverState *chr;
     struct sigaction act;
+    ChardevCommon *common = qapi_ChardevStdio_base(backend->u.stdio);
 
     if (is_daemonized()) {
         error_setg(errp, "cannot use stdio with -daemonize");
@@ -1102,7 +1176,7 @@  static CharDriverState *qemu_chr_open_stdio(const char *id,
     act.sa_handler = term_stdio_handler;
     sigaction(SIGCONT, &act, NULL);
 
-    chr = qemu_chr_open_fd(0, 1);
+    chr = qemu_chr_open_fd(0, 1, common, errp);
     chr->chr_close = qemu_chr_close_stdio;
     chr->chr_set_echo = qemu_chr_set_echo_stdio;
     if (opts->has_signal) {
@@ -1324,6 +1398,7 @@  static CharDriverState *qemu_chr_open_pty(const char *id,
     PtyCharDriver *s;
     int master_fd, slave_fd;
     char pty_name[PATH_MAX];
+    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.pty);
 
     master_fd = qemu_openpty_raw(&slave_fd, pty_name);
     if (master_fd < 0) {
@@ -1334,7 +1409,11 @@  static CharDriverState *qemu_chr_open_pty(const char *id,
     close(slave_fd);
     qemu_set_nonblock(master_fd);
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        close(master_fd);
+        return NULL;
+    }
 
     chr->filename = g_strdup_printf("pty:%s", pty_name);
     ret->pty = g_strdup(pty_name);
@@ -1557,12 +1636,14 @@  static void qemu_chr_close_tty(CharDriverState *chr)
     }
 }
 
-static CharDriverState *qemu_chr_open_tty_fd(int fd)
+static CharDriverState *qemu_chr_open_tty_fd(int fd,
+                                             ChardevCommon *backend,
+                                             Error **errp)
 {
     CharDriverState *chr;
 
     tty_serial_init(fd, 115200, 'N', 8, 1);
-    chr = qemu_chr_open_fd(fd, fd);
+    chr = qemu_chr_open_fd(fd, fd, backend, errp);
     chr->chr_ioctl = tty_serial_ioctl;
     chr->chr_close = qemu_chr_close_tty;
     return chr;
@@ -1682,7 +1763,9 @@  static void pp_close(CharDriverState *chr)
     qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
 }
 
-static CharDriverState *qemu_chr_open_pp_fd(int fd, Error **errp)
+static CharDriverState *qemu_chr_open_pp_fd(int fd,
+                                            ChardevCommon *backend,
+                                            Error **errp)
 {
     CharDriverState *chr;
     ParallelCharDriver *drv;
@@ -1697,7 +1780,10 @@  static CharDriverState *qemu_chr_open_pp_fd(int fd, Error **errp)
     drv->fd = fd;
     drv->mode = IEEE1284_MODE_COMPAT;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(backend, errp);
+    if (!chr) {
+        return NULL;
+    }
     chr->chr_write = null_chr_write;
     chr->chr_ioctl = pp_ioctl;
     chr->chr_close = pp_close;
@@ -1748,11 +1834,16 @@  static int pp_ioctl(CharDriverState *chr, int cmd, void *arg)
     return 0;
 }
 
-static CharDriverState *qemu_chr_open_pp_fd(int fd, Error **errp)
+static CharDriverState *qemu_chr_open_pp_fd(int fd,
+                                            ChardevBackend *backend,
+                                            Error **errp)
 {
     CharDriverState *chr;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
     chr->opaque = (void *)(intptr_t)fd;
     chr->chr_write = null_chr_write;
     chr->chr_ioctl = pp_ioctl;
@@ -1978,12 +2069,16 @@  static int win_chr_poll(void *opaque)
 }
 
 static CharDriverState *qemu_chr_open_win_path(const char *filename,
+                                               ChardevCommon *backend,
                                                Error **errp)
 {
     CharDriverState *chr;
     WinCharState *s;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(backend, errp);
+    if (!chr) {
+        return NULL;
+    }
     s = g_new0(WinCharState, 1);
     chr->opaque = s;
     chr->chr_write = win_chr_write;
@@ -1991,7 +2086,7 @@  static CharDriverState *qemu_chr_open_win_path(const char *filename,
 
     if (win_chr_init(chr, filename, errp) < 0) {
         g_free(s);
-        g_free(chr);
+        qemu_chr_free_common(chr);
         return NULL;
     }
     return chr;
@@ -2086,8 +2181,12 @@  static CharDriverState *qemu_chr_open_pipe(const char *id,
     const char *filename = opts->device;
     CharDriverState *chr;
     WinCharState *s;
+    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.pipe);
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
     s = g_new0(WinCharState, 1);
     chr->opaque = s;
     chr->chr_write = win_chr_write;
@@ -2095,18 +2194,23 @@  static CharDriverState *qemu_chr_open_pipe(const char *id,
 
     if (win_chr_pipe_init(chr, filename, errp) < 0) {
         g_free(s);
-        g_free(chr);
+        qemu_chr_free_common(chr);
         return NULL;
     }
     return chr;
 }
 
-static CharDriverState *qemu_chr_open_win_file(HANDLE fd_out)
+static CharDriverState *qemu_chr_open_win_file(HANDLE fd_out,
+                                               ChardevCommon *backend,
+                                               Error **errp)
 {
     CharDriverState *chr;
     WinCharState *s;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(backend, errp);
+    if (!chr) {
+        return NULL;
+    }
     s = g_new0(WinCharState, 1);
     s->hcom = fd_out;
     chr->opaque = s;
@@ -2119,7 +2223,9 @@  static CharDriverState *qemu_chr_open_win_con(const char *id,
                                               ChardevReturn *ret,
                                               Error **errp)
 {
-    return qemu_chr_open_win_file(GetStdHandle(STD_OUTPUT_HANDLE));
+    ChardevCommon *common = qapi_ChardevDummy_base(backend->u.console);
+    return qemu_chr_open_win_file(GetStdHandle(STD_OUTPUT_HANDLE),
+                                  common, errp);
 }
 
 static int win_stdio_write(CharDriverState *chr, const uint8_t *buf, int len)
@@ -2267,8 +2373,12 @@  static CharDriverState *qemu_chr_open_stdio(const char *id,
     WinStdioCharState *stdio;
     DWORD              dwMode;
     int                is_console = 0;
+    ChardevCommon *common = qapi_ChardevStdio_base(backend->u.stdio);
 
-    chr   = qemu_chr_alloc();
+    chr   = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
     stdio = g_new0(WinStdioCharState, 1);
 
     stdio->hStdIn = GetStdHandle(STD_INPUT_HANDLE);
@@ -2440,12 +2550,17 @@  static void udp_chr_close(CharDriverState *chr)
     qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
 }
 
-static CharDriverState *qemu_chr_open_udp_fd(int fd)
+static CharDriverState *qemu_chr_open_udp_fd(int fd,
+                                             ChardevCommon *backend,
+                                             Error **errp)
 {
     CharDriverState *chr = NULL;
     NetCharDriver *s = NULL;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(backend, errp);
+    if (!chr) {
+        return NULL;
+    }
     s = g_new0(NetCharDriver, 1);
 
     s->fd = fd;
@@ -2856,7 +2971,7 @@  static int tcp_chr_sync_read(CharDriverState *chr, const uint8_t *buf, int len)
 #ifndef _WIN32
 CharDriverState *qemu_chr_open_eventfd(int eventfd)
 {
-    CharDriverState *chr = qemu_chr_open_fd(eventfd, eventfd);
+    CharDriverState *chr = qemu_chr_open_fd(eventfd, eventfd, NULL, NULL);
 
     if (chr) {
         chr->avail_connections = 1;
@@ -3138,10 +3253,14 @@  static CharDriverState *qemu_chr_open_ringbuf(const char *id,
                                               Error **errp)
 {
     ChardevRingbuf *opts = backend->u.ringbuf;
+    ChardevCommon *common = qapi_ChardevRingbuf_base(backend->u.ringbuf);
     CharDriverState *chr;
     RingBufCharDriver *d;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
     d = g_malloc(sizeof(*d));
 
     d->size = opts->has_size ? opts->size : 65536;
@@ -3164,7 +3283,7 @@  static CharDriverState *qemu_chr_open_ringbuf(const char *id,
 
 fail:
     g_free(d);
-    g_free(chr);
+    qemu_chr_free_common(chr);
     return NULL;
 }
 
@@ -3408,6 +3527,18 @@  fail:
     return NULL;
 }
 
+static 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->has_logappend = true;
+    backend->logappend = qemu_opt_get_bool(opts, "logappend", false);
+}
+
+
 static void qemu_chr_parse_file_out(QemuOpts *opts, ChardevBackend *backend,
                                     Error **errp)
 {
@@ -3418,6 +3549,7 @@  static void qemu_chr_parse_file_out(QemuOpts *opts, ChardevBackend *backend,
         return;
     }
     backend->u.file = g_new0(ChardevFile, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevFile_base(backend->u.file));
     backend->u.file->out = g_strdup(path);
 
     backend->u.file->has_append = true;
@@ -3428,6 +3560,7 @@  static void qemu_chr_parse_stdio(QemuOpts *opts, ChardevBackend *backend,
                                  Error **errp)
 {
     backend->u.stdio = g_new0(ChardevStdio, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevStdio_base(backend->u.stdio));
     backend->u.stdio->has_signal = true;
     backend->u.stdio->signal = qemu_opt_get_bool(opts, "signal", true);
 }
@@ -3443,6 +3576,7 @@  static void qemu_chr_parse_serial(QemuOpts *opts, ChardevBackend *backend,
         return;
     }
     backend->u.serial = g_new0(ChardevHostdev, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevHostdev_base(backend->u.serial));
     backend->u.serial->device = g_strdup(device);
 }
 #endif
@@ -3458,6 +3592,7 @@  static void qemu_chr_parse_parallel(QemuOpts *opts, ChardevBackend *backend,
         return;
     }
     backend->u.parallel = g_new0(ChardevHostdev, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevHostdev_base(backend->u.parallel));
     backend->u.parallel->device = g_strdup(device);
 }
 #endif
@@ -3472,6 +3607,7 @@  static void qemu_chr_parse_pipe(QemuOpts *opts, ChardevBackend *backend,
         return;
     }
     backend->u.pipe = g_new0(ChardevHostdev, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevHostdev_base(backend->u.pipe));
     backend->u.pipe->device = g_strdup(device);
 }
 
@@ -3481,6 +3617,7 @@  static void qemu_chr_parse_ringbuf(QemuOpts *opts, ChardevBackend *backend,
     int val;
 
     backend->u.ringbuf = g_new0(ChardevRingbuf, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevRingbuf_base(backend->u.ringbuf));
 
     val = qemu_opt_get_size(opts, "size", 0);
     if (val != 0) {
@@ -3499,6 +3636,7 @@  static void qemu_chr_parse_mux(QemuOpts *opts, ChardevBackend *backend,
         return;
     }
     backend->u.mux = g_new0(ChardevMux, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevMux_base(backend->u.mux));
     backend->u.mux->chardev = g_strdup(chardev);
 }
 
@@ -3527,6 +3665,7 @@  static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
     }
 
     backend->u.socket = g_new0(ChardevSocket, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevSocket_base(backend->u.socket));
 
     backend->u.socket->has_nodelay = true;
     backend->u.socket->nodelay = do_nodelay;
@@ -3588,6 +3727,7 @@  static void qemu_chr_parse_udp(QemuOpts *opts, ChardevBackend *backend,
     }
 
     backend->u.udp = g_new0(ChardevUdp, 1);
+    qemu_chr_parse_common(opts, qapi_ChardevUdp_base(backend->u.udp));
 
     addr = g_new0(SocketAddress, 1);
     addr->type = SOCKET_ADDRESS_KIND_INET;
@@ -3687,7 +3827,12 @@  CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
             error_propagate(errp, local_err);
             goto qapi_out;
         }
+    } else {
+        ChardevCommon *cc = g_new0(ChardevCommon, 1);
+        qemu_chr_parse_common(opts, cc);
+        backend->u.data = cc;
     }
+
     ret = qmp_chardev_add(bid ? bid : id, backend, errp);
     if (!ret) {
         goto qapi_out;
@@ -3819,17 +3964,25 @@  void qemu_chr_fe_release(CharDriverState *s)
     s->avail_connections++;
 }
 
-void qemu_chr_free(CharDriverState *chr)
+static void qemu_chr_free_common(CharDriverState *chr)
 {
-    if (chr->chr_close) {
-        chr->chr_close(chr);
-    }
     g_free(chr->filename);
     g_free(chr->label);
     qemu_opts_del(chr->opts);
+    if (chr->logfd != -1) {
+        close(chr->logfd);
+    }
     g_free(chr);
 }
 
+void qemu_chr_free(CharDriverState *chr)
+{
+    if (chr->chr_close) {
+        chr->chr_close(chr);
+    }
+    qemu_chr_free_common(chr);
+}
+
 void qemu_chr_delete(CharDriverState *chr)
 {
     QTAILQ_REMOVE(&chardevs, chr, next);
@@ -3982,6 +4135,12 @@  QemuOptsList qemu_chardev_opts = {
         },{
             .name = "append",
             .type = QEMU_OPT_BOOL,
+        },{
+            .name = "logfile",
+            .type = QEMU_OPT_STRING,
+        },{
+            .name = "logappend",
+            .type = QEMU_OPT_BOOL,
         },
         { /* end of list */ }
     },
@@ -3995,6 +4154,7 @@  static CharDriverState *qmp_chardev_open_file(const char *id,
                                               Error **errp)
 {
     ChardevFile *file = backend->u.file;
+    ChardevCommon *common = qapi_ChardevFile_base(backend->u.file);
     HANDLE out;
 
     if (file->has_in) {
@@ -4008,7 +4168,7 @@  static CharDriverState *qmp_chardev_open_file(const char *id,
         error_setg(errp, "open %s failed", file->out);
         return NULL;
     }
-    return qemu_chr_open_win_file(out);
+    return qemu_chr_open_win_file(out, common, errp);
 }
 
 static CharDriverState *qmp_chardev_open_serial(const char *id,
@@ -4017,7 +4177,8 @@  static CharDriverState *qmp_chardev_open_serial(const char *id,
                                                 Error **errp)
 {
     ChardevHostdev *serial = backend->u.serial;
-    return qemu_chr_open_win_path(serial->device, errp);
+    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.serial);
+    return qemu_chr_open_win_path(serial->device, common, errp);
 }
 
 #else /* WIN32 */
@@ -4040,6 +4201,7 @@  static CharDriverState *qmp_chardev_open_file(const char *id,
                                               Error **errp)
 {
     ChardevFile *file = backend->u.file;
+    ChardevCommon *common = qapi_ChardevFile_base(backend->u.file);
     int flags, in = -1, out;
 
     flags = O_WRONLY | O_CREAT | O_BINARY;
@@ -4063,7 +4225,7 @@  static CharDriverState *qmp_chardev_open_file(const char *id,
         }
     }
 
-    return qemu_chr_open_fd(in, out);
+    return qemu_chr_open_fd(in, out, common, errp);
 }
 
 #ifdef HAVE_CHARDEV_SERIAL
@@ -4073,6 +4235,7 @@  static CharDriverState *qmp_chardev_open_serial(const char *id,
                                                 Error **errp)
 {
     ChardevHostdev *serial = backend->u.serial;
+    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.serial);
     int fd;
 
     fd = qmp_chardev_open_file_source(serial->device, O_RDWR, errp);
@@ -4080,7 +4243,7 @@  static CharDriverState *qmp_chardev_open_serial(const char *id,
         return NULL;
     }
     qemu_set_nonblock(fd);
-    return qemu_chr_open_tty_fd(fd);
+    return qemu_chr_open_tty_fd(fd, common, errp);
 }
 #endif
 
@@ -4091,13 +4254,14 @@  static CharDriverState *qmp_chardev_open_parallel(const char *id,
                                                   Error **errp)
 {
     ChardevHostdev *parallel = backend->u.parallel;
+    ChardevCommon *common = qapi_ChardevHostdev_base(backend->u.parallel);
     int fd;
 
     fd = qmp_chardev_open_file_source(parallel->device, O_RDWR, errp);
     if (fd < 0) {
         return NULL;
     }
-    return qemu_chr_open_pp_fd(fd, errp);
+    return qemu_chr_open_pp_fd(fd, common, errp);
 }
 #endif
 
@@ -4142,8 +4306,12 @@  static CharDriverState *qmp_chardev_open_socket(const char *id,
     bool is_telnet      = sock->has_telnet  ? sock->telnet  : false;
     bool is_waitconnect = sock->has_wait    ? sock->wait    : false;
     int64_t reconnect   = sock->has_reconnect ? sock->reconnect : 0;
+    ChardevCommon *common = qapi_ChardevSocket_base(backend->u.socket);
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
     s = g_new0(TCPCharDriver, 1);
 
     s->fd = -1;
@@ -4182,8 +4350,7 @@  static CharDriverState *qmp_chardev_open_socket(const char *id,
         socket_try_connect(chr);
     } else if (!qemu_chr_open_socket_fd(chr, errp)) {
         g_free(s);
-        g_free(chr->filename);
-        g_free(chr);
+        qemu_chr_free_common(chr);
         return NULL;
     }
 
@@ -4203,13 +4370,14 @@  static CharDriverState *qmp_chardev_open_udp(const char *id,
                                              Error **errp)
 {
     ChardevUdp *udp = backend->u.udp;
+    ChardevCommon *common = qapi_ChardevUdp_base(backend->u.udp);
     int fd;
 
     fd = socket_dgram(udp->remote, udp->local, errp);
     if (fd < 0) {
         return NULL;
     }
-    return qemu_chr_open_udp_fd(fd);
+    return qemu_chr_open_udp_fd(fd, common, errp);
 }
 
 ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
diff --git a/qemu-options.hx b/qemu-options.hx
index 215d00d..b4763ba 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2089,40 +2089,43 @@  The general form of a character device option is:
 ETEXI
 
 DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
-    "-chardev null,id=id[,mux=on|off]\n"
+    "-chardev null,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
     "-chardev socket,id=id[,host=host],port=port[,to=to][,ipv4][,ipv6][,nodelay][,reconnect=seconds]\n"
-    "         [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (tcp)\n"
-    "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off] (unix)\n"
+    "         [,server][,nowait][,telnet][,reconnect=seconds][,mux=on|off]\n"
+    "         [,logfile=PATH][,logappend=on|off] (tcp)\n"
+    "-chardev socket,id=id,path=path[,server][,nowait][,telnet][,reconnect=seconds]\n"
+    "         [,mux=on|off][,logfile=PATH][,logappend=on|off] (unix)\n"
     "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
     "         [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n"
-    "-chardev msmouse,id=id[,mux=on|off]\n"
+    "         [,logfile=PATH][,logappend=on|off]\n"
+    "-chardev msmouse,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
     "-chardev vc,id=id[[,width=width][,height=height]][[,cols=cols][,rows=rows]]\n"
-    "         [,mux=on|off]\n"
-    "-chardev ringbuf,id=id[,size=size]\n"
-    "-chardev file,id=id,path=path[,mux=on|off]\n"
-    "-chardev pipe,id=id,path=path[,mux=on|off]\n"
+    "         [,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev ringbuf,id=id[,size=size][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev file,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev pipe,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
 #ifdef _WIN32
-    "-chardev console,id=id[,mux=on|off]\n"
-    "-chardev serial,id=id,path=path[,mux=on|off]\n"
+    "-chardev console,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev serial,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
 #else
-    "-chardev pty,id=id[,mux=on|off]\n"
-    "-chardev stdio,id=id[,mux=on|off][,signal=on|off]\n"
+    "-chardev pty,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev stdio,id=id[,mux=on|off][,signal=on|off][,logfile=PATH][,logappend=on|off]\n"
 #endif
 #ifdef CONFIG_BRLAPI
-    "-chardev braille,id=id[,mux=on|off]\n"
+    "-chardev braille,id=id[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
 #endif
 #if defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) \
         || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
-    "-chardev serial,id=id,path=path[,mux=on|off]\n"
-    "-chardev tty,id=id,path=path[,mux=on|off]\n"
+    "-chardev serial,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev tty,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
 #endif
 #if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
-    "-chardev parallel,id=id,path=path[,mux=on|off]\n"
-    "-chardev parport,id=id,path=path[,mux=on|off]\n"
+    "-chardev parallel,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev parport,id=id,path=path[,mux=on|off][,logfile=PATH][,logappend=on|off]\n"
 #endif
 #if defined(CONFIG_SPICE)
-    "-chardev spicevmc,id=id,name=name[,debug=debug]\n"
-    "-chardev spiceport,id=id,name=name[,debug=debug]\n"
+    "-chardev spicevmc,id=id,name=name[,debug=debug][,logfile=PATH][,logappend=on|off]\n"
+    "-chardev spiceport,id=id,name=name[,debug=debug][,logfile=PATH][,logappend=on|off]\n"
 #endif
     , QEMU_ARCH_ALL
 )
@@ -2158,7 +2161,12 @@  A character device may be used in multiplexing mode by multiple front-ends.
 The key sequence of @key{Control-a} and @key{c} will rotate the input focus
 between attached front-ends. Specify @option{mux=on} to enable this mode.
 
-Options to each backend are described below.
+Every backend supports the @option{logfile} option, which supplies the path
+to a file to record all data transmitted via the backend. The @option{logappend}
+option controls whether the log file will be truncated or appended to when
+opened.
+
+Further options to each backend are described below.
 
 @item -chardev null ,id=@var{id}
 A void device. This device will not emit any data, and will drop any data it
diff --git a/spice-qemu-char.c b/spice-qemu-char.c
index e70e0f7..8951d7c 100644
--- a/spice-qemu-char.c
+++ b/spice-qemu-char.c
@@ -271,13 +271,18 @@  static void spice_chr_accept_input(struct CharDriverState *chr)
 }
 
 static CharDriverState *chr_open(const char *subtype,
-    void (*set_fe_open)(struct CharDriverState *, int))
-
+                                 void (*set_fe_open)(struct CharDriverState *,
+                                                     int),
+                                 ChardevCommon *backend,
+                                 Error **errp)
 {
     CharDriverState *chr;
     SpiceCharDriver *s;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(backend, errp);
+    if (!chr) {
+        return NULL;
+    }
     s = g_malloc0(sizeof(SpiceCharDriver));
     s->chr = chr;
     s->active = false;
@@ -303,6 +308,7 @@  static CharDriverState *qemu_chr_open_spice_vmc(const char *id,
 {
     const char *type = backend->u.spicevmc->type;
     const char **psubtype = spice_server_char_device_recognized_subtypes();
+    ChardevCommon *common = qapi_ChardevSpiceChannel_base(backend->u.spicevmc);
 
     for (; *psubtype != NULL; ++psubtype) {
         if (strcmp(type, *psubtype) == 0) {
@@ -315,7 +321,7 @@  static CharDriverState *qemu_chr_open_spice_vmc(const char *id,
         return NULL;
     }
 
-    return chr_open(type, spice_vmc_set_fe_open);
+    return chr_open(type, spice_vmc_set_fe_open, common, errp);
 }
 
 #if SPICE_SERVER_VERSION >= 0x000c02
@@ -325,6 +331,7 @@  static CharDriverState *qemu_chr_open_spice_port(const char *id,
                                                  Error **errp)
 {
     const char *name = backend->u.spiceport->fqdn;
+    ChardevCommon *common = qapi_ChardevSpicePort_base(backend->u.spiceport);
     CharDriverState *chr;
     SpiceCharDriver *s;
 
@@ -333,7 +340,10 @@  static CharDriverState *qemu_chr_open_spice_port(const char *id,
         return NULL;
     }
 
-    chr = chr_open("port", spice_port_set_fe_open);
+    chr = chr_open("port", spice_port_set_fe_open, common, errp);
+    if (!chr) {
+        return NULL;
+    }
     s = chr->opaque;
     s->sin.portname = g_strdup(name);
 
diff --git a/ui/console.c b/ui/console.c
index 4b65c34..fe950c6 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -1953,12 +1953,16 @@  static void text_console_do_init(CharDriverState *chr, DisplayState *ds)
 
 static CharDriverState *text_console_init(ChardevVC *vc, Error **errp)
 {
+    ChardevCommon *common = qapi_ChardevVC_base(vc);
     CharDriverState *chr;
     QemuConsole *s;
     unsigned width = 0;
     unsigned height = 0;
 
-    chr = qemu_chr_alloc();
+    chr = qemu_chr_alloc(common, errp);
+    if (!chr) {
+        return NULL;
+    }
 
     if (vc->has_width) {
         width = vc->width;