Commit 47f4dac3 authored by Anthony Liguori's avatar Anthony Liguori
Browse files

Merge remote-tracking branch 'kraxel/chardev.1' into staging

# By Gerd Hoffmann
# Via Gerd Hoffmann
* kraxel/chardev.1:
  chardev: add pty chardev support to chardev-add (qmp)
  chardev: add socket chardev support to chardev-add (qmp)
  chardev: add parallel chardev support to chardev-add (qmp)
  chardev: add serial chardev support to chardev-add (qmp)
  chardev: add file chardev support to chardev-add (qmp)
  chardev: add hmp hotplug commands
  chardev: add qmp hotplug commands, with null chardev support
  chardev: reduce chardev ifdef mess a bit
  chardev: fix QemuOpts lifecycle
  chardev: add error reporting for qemu_chr_new_from_opts
parents af381ebe 0a1a7fab
......@@ -1482,6 +1482,38 @@ Password is invalidated at the given time. @var{nsec} are the seconds
passed since 1970, i.e. unix epoch.
@end table
ETEXI
{
.name = "chardev-add",
.args_type = "args:s",
.params = "args",
.help = "add chardev",
.mhandler.cmd = hmp_chardev_add,
},
STEXI
@item chardev_add args
@findex chardev_add
chardev_add accepts the same parameters as the -chardev command line switch.
ETEXI
{
.name = "chardev-remove",
.args_type = "id:s",
.params = "id",
.help = "remove chardev",
.mhandler.cmd = hmp_chardev_remove,
},
STEXI
@item chardev_remove id
@findex chardev_remove
Removes the chardev @var{id}.
ETEXI
{
......
......@@ -1336,3 +1336,26 @@ void hmp_nbd_server_stop(Monitor *mon, const QDict *qdict)
qmp_nbd_server_stop(&errp);
hmp_handle_error(mon, &errp);
}
void hmp_chardev_add(Monitor *mon, const QDict *qdict)
{
const char *args = qdict_get_str(qdict, "args");
Error *err = NULL;
QemuOpts *opts;
opts = qemu_opts_parse(qemu_find_opts("chardev"), args, 1);
if (opts == NULL) {
error_setg(&err, "Parsing chardev args failed\n");
} else {
qemu_chr_new_from_opts(opts, NULL, &err);
}
hmp_handle_error(mon, &err);
}
void hmp_chardev_remove(Monitor *mon, const QDict *qdict)
{
Error *local_err = NULL;
qmp_chardev_remove(qdict_get_str(qdict, "id"), &local_err);
hmp_handle_error(mon, &local_err);
}
......@@ -80,5 +80,7 @@ void hmp_screen_dump(Monitor *mon, const QDict *qdict);
void hmp_nbd_server_start(Monitor *mon, const QDict *qdict);
void hmp_nbd_server_add(Monitor *mon, const QDict *qdict);
void hmp_nbd_server_stop(Monitor *mon, const QDict *qdict);
void hmp_chardev_add(Monitor *mon, const QDict *qdict);
void hmp_chardev_remove(Monitor *mon, const QDict *qdict);
#endif
......@@ -75,6 +75,7 @@ struct CharDriverState {
char *filename;
int opened;
int avail_connections;
QemuOpts *opts;
QTAILQ_ENTRY(CharDriverState) next;
};
......@@ -89,7 +90,8 @@ struct CharDriverState {
* Returns: a new character backend
*/
CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
void (*init)(struct CharDriverState *s));
void (*init)(struct CharDriverState *s),
Error **errp);
/**
* @qemu_chr_new:
......
......@@ -3017,3 +3017,107 @@
# Since: 1.3.0
##
{ 'command': 'nbd-server-stop' }
##
# @ChardevFile:
#
# Configuration info for file chardevs.
#
# @in: #optional The name of the input file
# @out: The name of the output file
#
# Since: 1.4
##
{ 'type': 'ChardevFile', 'data': { '*in' : 'str',
'out' : 'str' } }
##
# @ChardevPort:
#
# Configuration info for device chardevs.
#
# @device: The name of the special file for the device,
# i.e. /dev/ttyS0 on Unix or COM1: on Windows
# @type: What kind of device this is.
#
# Since: 1.4
##
{ 'enum': 'ChardevPortKind', 'data': [ 'serial',
'parallel' ] }
{ 'type': 'ChardevPort', 'data': { 'device' : 'str',
'type' : 'ChardevPortKind'} }
##
# @ChardevSocket:
#
# Configuration info for socket chardevs.
#
# @addr: socket address to listen on (server=true)
# or connect to (server=false)
# @server: #optional create server socket (default: true)
# @wait: #optional wait for connect (not used for server
# sockets, default: false)
# @nodelay: #optional set TCP_NODELAY socket option (default: false)
# @telnet: #optional enable telnet protocol (default: false)
#
# Since: 1.4
##
{ 'type': 'ChardevSocket', 'data': { 'addr' : 'SocketAddress',
'*server' : 'bool',
'*wait' : 'bool',
'*nodelay' : 'bool',
'*telnet' : 'bool' } }
##
# @ChardevBackend:
#
# Configuration info for the new chardev backend.
#
# Since: 1.4
##
{ 'type': 'ChardevDummy', 'data': { } }
{ 'union': 'ChardevBackend', 'data': { 'file' : 'ChardevFile',
'port' : 'ChardevPort',
'socket' : 'ChardevSocket',
'pty' : 'ChardevDummy',
'null' : 'ChardevDummy' } }
##
# @ChardevReturn:
#
# Return info about the chardev backend just created.
#
# Since: 1.4
##
{ 'type' : 'ChardevReturn', 'data': { '*pty' : 'str' } }
##
# @chardev-add:
#
# Add a file chardev
#
# @id: the chardev's ID, must be unique
# @backend: backend type and parameters
#
# Returns: chardev info.
#
# Since: 1.4
##
{ 'command': 'chardev-add', 'data': {'id' : 'str',
'backend' : 'ChardevBackend' },
'returns': 'ChardevReturn' }
##
# @chardev-remove:
#
# Remove a chardev
#
# @id: the chardev's ID, must exist and not be in use
#
# Returns: Nothing on success
#
# Since: 1.4
##
{ 'command': 'chardev-remove', 'data': {'id': 'str'} }
......@@ -856,6 +856,8 @@ static void cfmakeraw (struct termios *termios_p)
|| defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) \
|| defined(__GLIBC__)
#define HAVE_CHARDEV_TTY 1
typedef struct {
int fd;
int connected;
......@@ -1228,30 +1230,34 @@ static void qemu_chr_close_tty(CharDriverState *chr)
}
}
static CharDriverState *qemu_chr_open_tty(QemuOpts *opts)
static CharDriverState *qemu_chr_open_tty_fd(int fd)
{
const char *filename = qemu_opt_get(opts, "path");
CharDriverState *chr;
int fd;
TFR(fd = qemu_open(filename, O_RDWR | O_NONBLOCK));
if (fd < 0) {
return NULL;
}
tty_serial_init(fd, 115200, 'N', 8, 1);
chr = qemu_chr_open_fd(fd, fd);
chr->chr_ioctl = tty_serial_ioctl;
chr->chr_close = qemu_chr_close_tty;
return chr;
}
#else /* ! __linux__ && ! __sun__ */
static CharDriverState *qemu_chr_open_pty(QemuOpts *opts)
static CharDriverState *qemu_chr_open_tty(QemuOpts *opts)
{
return NULL;
const char *filename = qemu_opt_get(opts, "path");
int fd;
TFR(fd = qemu_open(filename, O_RDWR | O_NONBLOCK));
if (fd < 0) {
return NULL;
}
return qemu_chr_open_tty_fd(fd);
}
#endif /* __linux__ || __sun__ */
#if defined(__linux__)
#define HAVE_CHARDEV_PARPORT 1
typedef struct {
int fd;
int mode;
......@@ -1361,17 +1367,10 @@ static void pp_close(CharDriverState *chr)
qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
}
static CharDriverState *qemu_chr_open_pp(QemuOpts *opts)
static CharDriverState *qemu_chr_open_pp_fd(int fd)
{
const char *filename = qemu_opt_get(opts, "path");
CharDriverState *chr;
ParallelCharDriver *drv;
int fd;
TFR(fd = qemu_open(filename, O_RDWR));
if (fd < 0) {
return NULL;
}
if (ioctl(fd, PPCLAIM) < 0) {
close(fd);
......@@ -1395,6 +1394,9 @@ static CharDriverState *qemu_chr_open_pp(QemuOpts *opts)
#endif /* __linux__ */
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
#define HAVE_CHARDEV_PARPORT 1
static int pp_ioctl(CharDriverState *chr, int cmd, void *arg)
{
int fd = (int)(intptr_t)chr->opaque;
......@@ -1432,16 +1434,9 @@ static int pp_ioctl(CharDriverState *chr, int cmd, void *arg)
return 0;
}
static CharDriverState *qemu_chr_open_pp(QemuOpts *opts)
static CharDriverState *qemu_chr_open_pp_fd(int fd)
{
const char *filename = qemu_opt_get(opts, "path");
CharDriverState *chr;
int fd;
fd = qemu_open(filename, O_RDWR);
if (fd < 0) {
return NULL;
}
chr = g_malloc0(sizeof(CharDriverState));
chr->opaque = (void *)(intptr_t)fd;
......@@ -1663,9 +1658,8 @@ static int win_chr_poll(void *opaque)
return 0;
}
static CharDriverState *qemu_chr_open_win(QemuOpts *opts)
static CharDriverState *qemu_chr_open_win_path(const char *filename)
{
const char *filename = qemu_opt_get(opts, "path");
CharDriverState *chr;
WinCharState *s;
......@@ -1684,6 +1678,11 @@ static CharDriverState *qemu_chr_open_win(QemuOpts *opts)
return chr;
}
static CharDriverState *qemu_chr_open_win(QemuOpts *opts)
{
return qemu_chr_open_win_path(qemu_opt_get(opts, "path"));
}
static int win_chr_pipe_poll(void *opaque)
{
CharDriverState *chr = opaque;
......@@ -2439,10 +2438,88 @@ static void tcp_chr_close(CharDriverState *chr)
qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
}
static CharDriverState *qemu_chr_open_socket(QemuOpts *opts)
static CharDriverState *qemu_chr_open_socket_fd(int fd, bool do_nodelay,
bool is_listen, bool is_telnet,
bool is_waitconnect,
Error **errp)
{
CharDriverState *chr = NULL;
TCPCharDriver *s = NULL;
char host[NI_MAXHOST], serv[NI_MAXSERV];
const char *left = "", *right = "";
struct sockaddr_storage ss;
socklen_t ss_len = sizeof(ss);
memset(&ss, 0, ss_len);
if (getsockname(fd, (struct sockaddr *) &ss, &ss_len) != 0) {
error_setg(errp, "getsockname: %s", strerror(errno));
return NULL;
}
chr = g_malloc0(sizeof(CharDriverState));
s = g_malloc0(sizeof(TCPCharDriver));
s->connected = 0;
s->fd = -1;
s->listen_fd = -1;
s->msgfd = -1;
chr->filename = g_malloc(256);
switch (ss.ss_family) {
#ifndef _WIN32
case AF_UNIX:
s->is_unix = 1;
snprintf(chr->filename, 256, "unix:%s%s",
((struct sockaddr_un *)(&ss))->sun_path,
is_listen ? ",server" : "");
break;
#endif
case AF_INET6:
left = "[";
right = "]";
/* fall through */
case AF_INET:
s->do_nodelay = do_nodelay;
getnameinfo((struct sockaddr *) &ss, ss_len, host, sizeof(host),
serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV);
snprintf(chr->filename, 256, "%s:%s:%s%s%s%s",
is_telnet ? "telnet" : "tcp",
left, host, right, serv,
is_listen ? ",server" : "");
break;
}
chr->opaque = s;
chr->chr_write = tcp_chr_write;
chr->chr_close = tcp_chr_close;
chr->get_msgfd = tcp_get_msgfd;
chr->chr_add_client = tcp_chr_add_client;
if (is_listen) {
s->listen_fd = fd;
qemu_set_fd_handler2(s->listen_fd, NULL, tcp_chr_accept, NULL, chr);
if (is_telnet) {
s->do_telnetopt = 1;
}
} else {
s->connected = 1;
s->fd = fd;
socket_set_nodelay(fd);
tcp_chr_connect(chr);
}
if (is_listen && is_waitconnect) {
printf("QEMU waiting for connection on: %s\n",
chr->filename);
tcp_chr_accept(chr);
socket_set_nonblock(s->listen_fd);
}
return chr;
}
static CharDriverState *qemu_chr_open_socket(QemuOpts *opts)
{
CharDriverState *chr = NULL;
Error *local_err = NULL;
int fd = -1;
int is_listen;
......@@ -2459,9 +2536,6 @@ static CharDriverState *qemu_chr_open_socket(QemuOpts *opts)
if (!is_listen)
is_waitconnect = 0;
chr = g_malloc0(sizeof(CharDriverState));
s = g_malloc0(sizeof(TCPCharDriver));
if (is_unix) {
if (is_listen) {
fd = unix_listen_opts(opts, &local_err);
......@@ -2482,56 +2556,14 @@ static CharDriverState *qemu_chr_open_socket(QemuOpts *opts)
if (!is_waitconnect)
socket_set_nonblock(fd);
s->connected = 0;
s->fd = -1;
s->listen_fd = -1;
s->msgfd = -1;
s->is_unix = is_unix;
s->do_nodelay = do_nodelay && !is_unix;
chr->opaque = s;
chr->chr_write = tcp_chr_write;
chr->chr_close = tcp_chr_close;
chr->get_msgfd = tcp_get_msgfd;
chr->chr_add_client = tcp_chr_add_client;
if (is_listen) {
s->listen_fd = fd;
qemu_set_fd_handler2(s->listen_fd, NULL, tcp_chr_accept, NULL, chr);
if (is_telnet)
s->do_telnetopt = 1;
} else {
s->connected = 1;
s->fd = fd;
socket_set_nodelay(fd);
tcp_chr_connect(chr);
}
/* for "info chardev" monitor command */
chr->filename = g_malloc(256);
if (is_unix) {
snprintf(chr->filename, 256, "unix:%s%s",
qemu_opt_get(opts, "path"),
qemu_opt_get_bool(opts, "server", 0) ? ",server" : "");
} else if (is_telnet) {
snprintf(chr->filename, 256, "telnet:%s:%s%s",
qemu_opt_get(opts, "host"), qemu_opt_get(opts, "port"),
qemu_opt_get_bool(opts, "server", 0) ? ",server" : "");
} else {
snprintf(chr->filename, 256, "tcp:%s:%s%s",
qemu_opt_get(opts, "host"), qemu_opt_get(opts, "port"),
qemu_opt_get_bool(opts, "server", 0) ? ",server" : "");
}
if (is_listen && is_waitconnect) {
printf("QEMU waiting for connection on: %s\n",
chr->filename);
tcp_chr_accept(chr);
socket_set_nonblock(s->listen_fd);
chr = qemu_chr_open_socket_fd(fd, do_nodelay, is_listen, is_telnet,
is_waitconnect, &local_err);
if (error_is_set(&local_err)) {
goto fail;
}
return chr;
fail:
if (local_err) {
qerror_report_err(local_err);
......@@ -2540,8 +2572,10 @@ static CharDriverState *qemu_chr_open_socket(QemuOpts *opts)
if (fd >= 0) {
closesocket(fd);
}
g_free(s);
g_free(chr);
if (chr) {
g_free(chr->opaque);
g_free(chr);
}
return NULL;
}
......@@ -2737,6 +2771,22 @@ fail:
return NULL;
}
#ifdef HAVE_CHARDEV_PARPORT
static CharDriverState *qemu_chr_open_pp(QemuOpts *opts)
{
const char *filename = qemu_opt_get(opts, "path");
int fd;
fd = qemu_open(filename, O_RDWR);
if (fd < 0) {
return NULL;
}
return qemu_chr_open_pp_fd(fd);
}
#endif
static const struct {
const char *name;
CharDriverState *(*open)(QemuOpts *opts);
......@@ -2755,19 +2805,18 @@ static const struct {
#else
{ .name = "file", .open = qemu_chr_open_file_out },
{ .name = "pipe", .open = qemu_chr_open_pipe },
{ .name = "pty", .open = qemu_chr_open_pty },
{ .name = "stdio", .open = qemu_chr_open_stdio },
#endif
#ifdef CONFIG_BRLAPI
{ .name = "braille", .open = chr_baum_init },
#endif
#if defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) \
|| defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) \
|| defined(__FreeBSD_kernel__)
#ifdef HAVE_CHARDEV_TTY
{ .name = "tty", .open = qemu_chr_open_tty },
{ .name = "serial", .open = qemu_chr_open_tty },
{ .name = "pty", .open = qemu_chr_open_pty },
#endif
#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) \
|| defined(__FreeBSD_kernel__)
#ifdef HAVE_CHARDEV_PARPORT
{ .name = "parallel", .open = qemu_chr_open_pp },
{ .name = "parport", .open = qemu_chr_open_pp },
#endif
#ifdef CONFIG_SPICE
......@@ -2779,36 +2828,37 @@ static const struct {
};
CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
void (*init)(struct CharDriverState *s))
void (*init)(struct CharDriverState *s),
Error **errp)
{
CharDriverState *chr;
int i;
if (qemu_opts_id(opts) == NULL) {
fprintf(stderr, "chardev: no id specified\n");
return NULL;
error_setg(errp, "chardev: no id specified\n");
goto err;
}
if (qemu_opt_get(opts, "backend") == NULL) {
fprintf(stderr, "chardev: \"%s\" missing backend\n",
qemu_opts_id(opts));
return NULL;
error_setg(errp, "chardev: \"%s\" missing backend\n",
qemu_opts_id(opts));
goto err;
}
for (i = 0; i < ARRAY_SIZE(backend_table); i++) {
if (strcmp(backend_table[i].name, qemu_opt_get(opts, "backend")) == 0)
break;
}
if (i == ARRAY_SIZE(backend_table)) {
fprintf(stderr, "chardev: backend \"%s\" not found\n",
qemu_opt_get(opts, "backend"));
return NULL;
error_setg(errp, "chardev: backend \"%s\" not found\n",
qemu_opt_get(opts, "backend"));
goto err;
}
chr = backend_table[i].open(opts);
if (!chr) {
fprintf(stderr, "chardev: opening backend \"%s\" failed\n",
qemu_opt_get(opts, "backend"));
return NULL;
error_setg(errp, "chardev: opening backend \"%s\" failed\n",
qemu_opt_get(opts, "backend"));
goto err;
}
if (!chr->filename)
......@@ -2829,7 +2879,12 @@ CharDriverState *qemu_chr_new_from_opts(QemuOpts *opts,
chr->avail_connections = 1;
}
chr->label = g_strdup(qemu_opts_id(opts));
chr->opts = opts;
return chr;
err:
qemu_opts_del(opts);
return NULL;
}
CharDriverState *qemu_chr_new(const char *label, const char *filename, void (*init)(struct CharDriverState *s))
......@@ -2837,6 +2892,7 @@ CharDriverState *qemu_chr_new(const char *label, const char *filename, void (*in
const char *p;
CharDriverState *chr;
QemuOpts *opts;
Error *err = NULL;
if (strstart(filename, "chardev:", &p)) {
return qemu_chr_find(p);
......@@ -2846,11 +2902,14 @@ CharDriverState *qemu_chr_new(const char *label, const char *filename, void (*in
if (!opts)
return NULL;
chr = qemu_chr_new_from_opts(opts, init);
chr = qemu_chr_new_from_opts(opts, init, &err);
if (error_is_set(&err)) {
fprintf(stderr, "%s\n", error_get_pretty(err));
error_free(err);
}
if (chr && qemu_opt_get_bool(opts, "mux", 0)) {
monitor_init<