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 <berra...@redhat.com>
---

Changed in v2:

 - Push logfile logic out of backend into common
   code so it is available for all backend types
 - Add docs to qemu -help text

 include/sysemu/char.h |   1 +
 qapi-schema.json      |  49 +++++++---
 qemu-char.c           | 263 +++++++++++++++++++++++++++++++++++++++++++-------
 qemu-options.hx       |  41 ++++----
 4 files changed, 290 insertions(+), 64 deletions(-)

diff --git a/include/sysemu/char.h b/include/sysemu/char.h
index aff193f..5681785 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;
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 00a7526..23f0b7a 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -159,10 +159,13 @@ static int sockaddr_to_str(char *dest, int max_len,
 static QTAILQ_HEAD(CharDriverStateHead, CharDriverState) chardevs =
     QTAILQ_HEAD_INITIALIZER(chardevs);
 
+static void qemu_chr_free_common(CharDriverState *chr);
+
 CharDriverState *qemu_chr_alloc(void)
 {
     CharDriverState *chr = g_malloc0(sizeof(CharDriverState));
     qemu_mutex_init(&chr->chr_write_lock);
+    chr->logfd = -1;
     return chr;
 }
 
@@ -188,12 +191,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 +254,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) {
@@ -359,6 +399,31 @@ static int null_chr_write(CharDriverState *chr, const 
uint8_t *buf, int len)
     return len;
 }
 
+
+static int qemu_chr_open_common(CharDriverState *chr,
+                                ChardevBackend *backend,
+                                Error **errp)
+{
+    ChardevCommon *common = backend->u.data;
+
+    if (common->has_logfile) {
+        int flags = O_WRONLY | O_CREAT;
+        if (!common->has_logappend ||
+            !common->logappend) {
+            flags |= O_TRUNC;
+        }
+        chr->logfd = qemu_open(common->logfile, flags, 0666);
+        if (chr->logfd < 0) {
+            error_setg_errno(errp, errno,
+                             "Unable to open logfile %s",
+                             common->logfile);
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
 static CharDriverState *qemu_chr_open_null(const char *id,
                                            ChardevBackend *backend,
                                            ChardevReturn *ret,
@@ -367,9 +432,16 @@ static CharDriverState *qemu_chr_open_null(const char *id,
     CharDriverState *chr;
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     chr->chr_write = null_chr_write;
     chr->explicit_be_open = true;
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 
 /* MUX driver for serial I/O splitting */
@@ -673,6 +745,9 @@ static CharDriverState *qemu_chr_open_mux(const char *id,
     }
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     d = g_new0(MuxDriver, 1);
 
     chr->opaque = d;
@@ -693,6 +768,10 @@ static CharDriverState *qemu_chr_open_mux(const char *id,
     chr->is_mux = 1;
 
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 
 
@@ -1046,12 +1125,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,
+                                         ChardevBackend *backend, Error **errp)
 {
     CharDriverState *chr;
     FDCharDriver *s;
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     s = g_new0(FDCharDriver, 1);
     s->fd_in = io_channel_from_fd(fd_in);
     s->fd_out = io_channel_from_fd(fd_out);
@@ -1064,6 +1147,10 @@ static CharDriverState *qemu_chr_open_fd(int fd_in, int 
fd_out)
     chr->chr_close = fd_chr_close;
 
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 
 static CharDriverState *qemu_chr_open_pipe(const char *id,
@@ -1092,7 +1179,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, backend, errp);
 }
 
 /* init terminal so that we can grab keys */
@@ -1173,7 +1260,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, backend, errp);
     chr->chr_close = qemu_chr_close_stdio;
     chr->chr_set_echo = qemu_chr_set_echo_stdio;
     if (opts->has_signal) {
@@ -1406,6 +1493,9 @@ static CharDriverState *qemu_chr_open_pty(const char *id,
     qemu_set_nonblock(master_fd);
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
 
     chr->filename = g_strdup_printf("pty:%s", pty_name);
     ret->pty = g_strdup(pty_name);
@@ -1426,6 +1516,10 @@ static CharDriverState *qemu_chr_open_pty(const char *id,
     s->timer_tag = 0;
 
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 
 static void tty_serial_init(int fd, int speed,
@@ -1628,12 +1722,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,
+                                             ChardevBackend *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;
@@ -1753,7 +1849,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,
+                                            ChardevBackend *backend,
+                                            Error **errp)
 {
     CharDriverState *chr;
     ParallelCharDriver *drv;
@@ -1769,12 +1867,19 @@ static CharDriverState *qemu_chr_open_pp_fd(int fd, 
Error **errp)
     drv->mode = IEEE1284_MODE_COMPAT;
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     chr->chr_write = null_chr_write;
     chr->chr_ioctl = pp_ioctl;
     chr->chr_close = pp_close;
     chr->opaque = drv;
 
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 #endif /* __linux__ */
 
@@ -1819,16 +1924,25 @@ 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();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     chr->opaque = (void *)(intptr_t)fd;
     chr->chr_write = null_chr_write;
     chr->chr_ioctl = pp_ioctl;
     chr->explicit_be_open = true;
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 #endif
 
@@ -2049,12 +2163,16 @@ static int win_chr_poll(void *opaque)
 }
 
 static CharDriverState *qemu_chr_open_win_path(const char *filename,
+                                               ChardevBackend *backend,
                                                Error **errp)
 {
     CharDriverState *chr;
     WinCharState *s;
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     s = g_new0(WinCharState, 1);
     chr->opaque = s;
     chr->chr_write = win_chr_write;
@@ -2062,10 +2180,13 @@ static CharDriverState *qemu_chr_open_win_path(const 
char *filename,
 
     if (win_chr_init(chr, filename, errp) < 0) {
         g_free(s);
-        g_free(chr);
-        return NULL;
+        goto error;
     }
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 
 static int win_chr_pipe_poll(void *opaque)
@@ -2159,6 +2280,9 @@ static CharDriverState *qemu_chr_open_pipe(const char *id,
     WinCharState *s;
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     s = g_new0(WinCharState, 1);
     chr->opaque = s;
     chr->chr_write = win_chr_write;
@@ -2166,23 +2290,35 @@ static CharDriverState *qemu_chr_open_pipe(const char 
*id,
 
     if (win_chr_pipe_init(chr, filename, errp) < 0) {
         g_free(s);
-        g_free(chr);
-        return NULL;
+        goto error;
     }
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 
-static CharDriverState *qemu_chr_open_win_file(HANDLE fd_out)
+static CharDriverState *qemu_chr_open_win_file(HANDLE fd_out,
+                                               ChardevBackend *backend,
+                                               Error **errp)
 {
     CharDriverState *chr;
     WinCharState *s;
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     s = g_new0(WinCharState, 1);
     s->hcom = fd_out;
     chr->opaque = s;
     chr->chr_write = win_chr_write;
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 
 static CharDriverState *qemu_chr_open_win_con(const char *id,
@@ -2190,7 +2326,8 @@ static CharDriverState *qemu_chr_open_win_con(const char 
*id,
                                               ChardevReturn *ret,
                                               Error **errp)
 {
-    return qemu_chr_open_win_file(GetStdHandle(STD_OUTPUT_HANDLE));
+    return qemu_chr_open_win_file(GetStdHandle(STD_OUTPUT_HANDLE),
+                                  backend, errp);
 }
 
 static int win_stdio_write(CharDriverState *chr, const uint8_t *buf, int len)
@@ -2340,6 +2477,9 @@ static CharDriverState *qemu_chr_open_stdio(const char 
*id,
     int                is_console = 0;
 
     chr   = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto err0;
+    }
     stdio = g_new0(WinStdioCharState, 1);
 
     stdio->hStdIn = GetStdHandle(STD_INPUT_HANDLE);
@@ -2406,6 +2546,8 @@ err2:
     CloseHandle(stdio->hInputDoneEvent);
 err1:
     qemu_del_wait_object(stdio->hStdIn, NULL, NULL);
+err0:
+    qemu_chr_free_common(chr);
     return NULL;
 }
 #endif /* !_WIN32 */
@@ -2511,12 +2653,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,
+                                             ChardevBackend *backend,
+                                             Error **errp)
 {
     CharDriverState *chr = NULL;
     NetCharDriver *s = NULL;
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     s = g_new0(NetCharDriver, 1);
 
     s->fd = fd;
@@ -2530,6 +2677,10 @@ static CharDriverState *qemu_chr_open_udp_fd(int fd)
     /* be isn't opened until we get a connection */
     chr->explicit_be_open = true;
     return chr;
+
+ error:
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 
 /***********************************************************/
@@ -2927,7 +3078,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;
@@ -3210,9 +3361,12 @@ static CharDriverState *qemu_chr_open_ringbuf(const char 
*id,
 {
     ChardevRingbuf *opts = backend->u.ringbuf;
     CharDriverState *chr;
-    RingBufCharDriver *d;
+    RingBufCharDriver *d = NULL;
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     d = g_malloc(sizeof(*d));
 
     d->size = opts->has_size ? opts->size : 65536;
@@ -3220,7 +3374,7 @@ static CharDriverState *qemu_chr_open_ringbuf(const char 
*id,
     /* The size must be power of 2 */
     if (d->size & (d->size - 1)) {
         error_setg(errp, "size of ringbuf chardev must be power of two");
-        goto fail;
+        goto error;
     }
 
     d->prod = 0;
@@ -3233,9 +3387,9 @@ static CharDriverState *qemu_chr_open_ringbuf(const char 
*id,
 
     return chr;
 
-fail:
+error:
     g_free(d);
-    g_free(chr);
+    qemu_chr_free_common(chr);
     return NULL;
 }
 
@@ -3479,6 +3633,19 @@ fail:
     return NULL;
 }
 
+static void qemu_chr_parse_common(QemuOpts *opts, ChardevBackend *backend)
+{
+    ChardevCommon *common = backend->u.data;
+    const char *logfile = qemu_opt_get(opts, "logfile");
+
+    common->has_logfile = logfile != NULL;
+    common->logfile = logfile ? g_strdup(logfile) : NULL;
+
+    common->has_logappend = true;
+    common->logappend = qemu_opt_get_bool(opts, "logappend", false);
+}
+
+
 static void qemu_chr_parse_file_out(QemuOpts *opts, ChardevBackend *backend,
                                     Error **errp)
 {
@@ -3489,6 +3656,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, backend);
     backend->u.file->out = g_strdup(path);
 
     backend->u.file->has_append = true;
@@ -3499,6 +3667,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, backend);
     backend->u.stdio->has_signal = true;
     backend->u.stdio->signal = qemu_opt_get_bool(opts, "signal", true);
 }
@@ -3514,6 +3683,7 @@ static void qemu_chr_parse_serial(QemuOpts *opts, 
ChardevBackend *backend,
         return;
     }
     backend->u.serial = g_new0(ChardevHostdev, 1);
+    qemu_chr_parse_common(opts, backend);
     backend->u.serial->device = g_strdup(device);
 }
 #endif
@@ -3529,6 +3699,7 @@ static void qemu_chr_parse_parallel(QemuOpts *opts, 
ChardevBackend *backend,
         return;
     }
     backend->u.parallel = g_new0(ChardevHostdev, 1);
+    qemu_chr_parse_common(opts, backend);
     backend->u.parallel->device = g_strdup(device);
 }
 #endif
@@ -3543,6 +3714,7 @@ static void qemu_chr_parse_pipe(QemuOpts *opts, 
ChardevBackend *backend,
         return;
     }
     backend->u.pipe = g_new0(ChardevHostdev, 1);
+    qemu_chr_parse_common(opts, backend);
     backend->u.pipe->device = g_strdup(device);
 }
 
@@ -3552,6 +3724,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, backend);
 
     val = qemu_opt_get_size(opts, "size", 0);
     if (val != 0) {
@@ -3570,6 +3743,7 @@ static void qemu_chr_parse_mux(QemuOpts *opts, 
ChardevBackend *backend,
         return;
     }
     backend->u.mux = g_new0(ChardevMux, 1);
+    qemu_chr_parse_common(opts, backend);
     backend->u.mux->chardev = g_strdup(chardev);
 }
 
@@ -3598,6 +3772,7 @@ static void qemu_chr_parse_socket(QemuOpts *opts, 
ChardevBackend *backend,
     }
 
     backend->u.socket = g_new0(ChardevSocket, 1);
+    qemu_chr_parse_common(opts, backend);
 
     backend->u.socket->has_nodelay = true;
     backend->u.socket->nodelay = do_nodelay;
@@ -3659,6 +3834,7 @@ static void qemu_chr_parse_udp(QemuOpts *opts, 
ChardevBackend *backend,
     }
 
     backend->u.udp = g_new0(ChardevUdp, 1);
+    qemu_chr_parse_common(opts, backend);
 
     addr = g_new0(SocketAddress, 1);
     addr->type = SOCKET_ADDRESS_KIND_INET;
@@ -3890,17 +4066,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);
@@ -4053,6 +4237,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 */ }
     },
@@ -4079,7 +4269,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, backend, errp);
 }
 
 static CharDriverState *qmp_chardev_open_serial(const char *id,
@@ -4088,7 +4278,7 @@ 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);
+    return qemu_chr_open_win_path(serial->device, backend, errp);
 }
 
 #else /* WIN32 */
@@ -4134,7 +4324,7 @@ static CharDriverState *qmp_chardev_open_file(const char 
*id,
         }
     }
 
-    return qemu_chr_open_fd(in, out);
+    return qemu_chr_open_fd(in, out, backend, errp);
 }
 
 #ifdef HAVE_CHARDEV_SERIAL
@@ -4151,7 +4341,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, backend, errp);
 }
 #endif
 
@@ -4168,7 +4358,7 @@ static CharDriverState *qmp_chardev_open_parallel(const 
char *id,
     if (fd < 0) {
         return NULL;
     }
-    return qemu_chr_open_pp_fd(fd, errp);
+    return qemu_chr_open_pp_fd(fd, backend, errp);
 }
 #endif
 
@@ -4205,7 +4395,7 @@ static CharDriverState *qmp_chardev_open_socket(const 
char *id,
                                                 Error **errp)
 {
     CharDriverState *chr;
-    TCPCharDriver *s;
+    TCPCharDriver *s = NULL;
     ChardevSocket *sock = backend->u.socket;
     SocketAddress *addr = sock->addr;
     bool do_nodelay     = sock->has_nodelay ? sock->nodelay : false;
@@ -4215,6 +4405,9 @@ static CharDriverState *qmp_chardev_open_socket(const 
char *id,
     int64_t reconnect   = sock->has_reconnect ? sock->reconnect : 0;
 
     chr = qemu_chr_alloc();
+    if (qemu_chr_open_common(chr, backend, errp) < 0) {
+        goto error;
+    }
     s = g_new0(TCPCharDriver, 1);
 
     s->fd = -1;
@@ -4252,10 +4445,7 @@ static CharDriverState *qmp_chardev_open_socket(const 
char *id,
     if (s->reconnect_time) {
         socket_try_connect(chr);
     } else if (!qemu_chr_open_socket_fd(chr, errp)) {
-        g_free(s);
-        g_free(chr->filename);
-        g_free(chr);
-        return NULL;
+        goto error;
     }
 
     if (is_listen && is_waitconnect) {
@@ -4266,6 +4456,11 @@ static CharDriverState *qmp_chardev_open_socket(const 
char *id,
     }
 
     return chr;
+
+ error:
+    g_free(s);
+    qemu_chr_free_common(chr);
+    return NULL;
 }
 
 static CharDriverState *qmp_chardev_open_udp(const char *id,
@@ -4280,7 +4475,7 @@ static CharDriverState *qmp_chardev_open_udp(const char 
*id,
     if (fd < 0) {
         return NULL;
     }
-    return qemu_chr_open_udp_fd(fd);
+    return qemu_chr_open_udp_fd(fd, backend, errp);
 }
 
 ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
diff --git a/qemu-options.hx b/qemu-options.hx
index 49afe6c..4370413 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2034,40 +2034,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
 )
-- 
2.5.0


Reply via email to