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;
}
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;
}
offset += res;
}
+ if (offset > 0) {
+ qemu_chr_fe_write_log(s, buf, offset);
+ }
+
qemu_mutex_unlock(&s->chr_write_lock);
if (res < 0) {
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;
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) {
return NULL;
}
- chr = qemu_chr_alloc();
+ chr = qemu_chr_alloc(common, errp);
+ if (!chr) {
+ return NULL;
+ }
d = g_new0(MuxDriver, 1);
chr->opaque = d;
}
/* 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);
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);
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 */
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");
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) {
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) {
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);
}
}
-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;
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;
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;
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;
}
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;
if (win_chr_init(chr, filename, errp) < 0) {
g_free(s);
- g_free(chr);
+ qemu_chr_free_common(chr);
return NULL;
}
return chr;
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;
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;
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)
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);
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;
#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;
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;
fail:
g_free(d);
- g_free(chr);
+ qemu_chr_free_common(chr);
return NULL;
}
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)
{
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;
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);
}
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
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
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);
}
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) {
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);
}
}
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;
}
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;
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;
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);
},{
.name = "append",
.type = QEMU_OPT_BOOL,
+ },{
+ .name = "logfile",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "logappend",
+ .type = QEMU_OPT_BOOL,
},
{ /* end of list */ }
},
Error **errp)
{
ChardevFile *file = backend->u.file;
+ ChardevCommon *common = qapi_ChardevFile_base(backend->u.file);
HANDLE out;
if (file->has_in) {
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,
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 */
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;
}
}
- return qemu_chr_open_fd(in, out);
+ return qemu_chr_open_fd(in, out, common, errp);
}
#ifdef HAVE_CHARDEV_SERIAL
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);
return NULL;
}
qemu_set_nonblock(fd);
- return qemu_chr_open_tty_fd(fd);
+ return qemu_chr_open_tty_fd(fd, common, errp);
}
#endif
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
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;
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;
}
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,
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
)
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