summaryrefslogtreecommitdiffstats
path: root/qemu-char.c
diff options
context:
space:
mode:
Diffstat (limited to 'qemu-char.c')
-rw-r--r--qemu-char.c277
1 files changed, 245 insertions, 32 deletions
diff --git a/qemu-char.c b/qemu-char.c
index f918f90..b3bd3b5 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -84,6 +84,7 @@
#include "ui/qemu-spice.h"
#define READ_BUF_LEN 4096
+#define READ_RETRIES 10
/***********************************************************/
/* character device */
@@ -145,6 +146,41 @@ int qemu_chr_fe_write_all(CharDriverState *s, const uint8_t *buf, int len)
return offset;
}
+int qemu_chr_fe_read_all(CharDriverState *s, uint8_t *buf, int len)
+{
+ int offset = 0, counter = 10;
+ int res;
+
+ if (!s->chr_sync_read) {
+ return 0;
+ }
+
+ while (offset < len) {
+ do {
+ res = s->chr_sync_read(s, buf + offset, len - offset);
+ if (res == -1 && errno == EAGAIN) {
+ g_usleep(100);
+ }
+ } while (res == -1 && errno == EAGAIN);
+
+ if (res == 0) {
+ break;
+ }
+
+ if (res < 0) {
+ return res;
+ }
+
+ offset += res;
+
+ if (!counter--) {
+ break;
+ }
+ }
+
+ return offset;
+}
+
int qemu_chr_fe_ioctl(CharDriverState *s, int cmd, void *arg)
{
if (!s->chr_ioctl)
@@ -168,7 +204,18 @@ void qemu_chr_be_write(CharDriverState *s, uint8_t *buf, int len)
int qemu_chr_fe_get_msgfd(CharDriverState *s)
{
- return s->get_msgfd ? s->get_msgfd(s) : -1;
+ int fd;
+ return (qemu_chr_fe_get_msgfds(s, &fd, 1) >= 0) ? fd : -1;
+}
+
+int qemu_chr_fe_get_msgfds(CharDriverState *s, int *fds, int len)
+{
+ return s->get_msgfds ? s->get_msgfds(s, fds, len) : -1;
+}
+
+int qemu_chr_fe_set_msgfds(CharDriverState *s, int *fds, int num)
+{
+ return s->set_msgfds ? s->set_msgfds(s, fds, num) : -1;
}
int qemu_chr_add_client(CharDriverState *s, int fd)
@@ -2296,16 +2343,73 @@ typedef struct {
int do_telnetopt;
int do_nodelay;
int is_unix;
- int msgfd;
+ int *read_msgfds;
+ int read_msgfds_num;
+ int *write_msgfds;
+ int write_msgfds_num;
} TCPCharDriver;
static gboolean tcp_chr_accept(GIOChannel *chan, GIOCondition cond, void *opaque);
+#ifndef _WIN32
+static int unix_send_msgfds(CharDriverState *chr, const uint8_t *buf, int len)
+{
+ TCPCharDriver *s = chr->opaque;
+ struct msghdr msgh;
+ struct iovec iov;
+ int r;
+
+ size_t fd_size = s->write_msgfds_num * sizeof(int);
+ char control[CMSG_SPACE(fd_size)];
+ struct cmsghdr *cmsg;
+
+ memset(&msgh, 0, sizeof(msgh));
+ memset(control, 0, sizeof(control));
+
+ /* set the payload */
+ iov.iov_base = (uint8_t *) buf;
+ iov.iov_len = len;
+
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+
+ msgh.msg_control = control;
+ msgh.msg_controllen = sizeof(control);
+
+ cmsg = CMSG_FIRSTHDR(&msgh);
+
+ cmsg->cmsg_len = CMSG_LEN(fd_size);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ memcpy(CMSG_DATA(cmsg), s->write_msgfds, fd_size);
+
+ do {
+ r = sendmsg(s->fd, &msgh, 0);
+ } while (r < 0 && errno == EINTR);
+
+ /* free the written msgfds, no matter what */
+ if (s->write_msgfds_num) {
+ g_free(s->write_msgfds);
+ s->write_msgfds = 0;
+ s->write_msgfds_num = 0;
+ }
+
+ return r;
+}
+#endif
+
static int tcp_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
{
TCPCharDriver *s = chr->opaque;
if (s->connected) {
- return io_channel_send(s->chan, buf, len);
+#ifndef _WIN32
+ if (s->is_unix && s->write_msgfds_num) {
+ return unix_send_msgfds(chr, buf, len);
+ } else
+#endif
+ {
+ return io_channel_send(s->chan, buf, len);
+ }
} else {
/* XXX: indicate an error ? */
return len;
@@ -2372,12 +2476,39 @@ static void tcp_chr_process_IAC_bytes(CharDriverState *chr,
*size = j;
}
-static int tcp_get_msgfd(CharDriverState *chr)
+static int tcp_get_msgfds(CharDriverState *chr, int *fds, int num)
{
TCPCharDriver *s = chr->opaque;
- int fd = s->msgfd;
- s->msgfd = -1;
- return fd;
+ int to_copy = (s->read_msgfds_num < num) ? s->read_msgfds_num : num;
+
+ if (to_copy) {
+ memcpy(fds, s->read_msgfds, to_copy * sizeof(int));
+
+ g_free(s->read_msgfds);
+ s->read_msgfds = 0;
+ s->read_msgfds_num = 0;
+ }
+
+ return to_copy;
+}
+
+static int tcp_set_msgfds(CharDriverState *chr, int *fds, int num)
+{
+ TCPCharDriver *s = chr->opaque;
+
+ /* clear old pending fd array */
+ if (s->write_msgfds) {
+ g_free(s->write_msgfds);
+ }
+
+ if (num) {
+ s->write_msgfds = g_malloc(num * sizeof(int));
+ memcpy(s->write_msgfds, fds, num * sizeof(int));
+ }
+
+ s->write_msgfds_num = num;
+
+ return 0;
}
#ifndef _WIN32
@@ -2387,26 +2518,46 @@ static void unix_process_msgfd(CharDriverState *chr, struct msghdr *msg)
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
- int fd;
+ int fd_size, i;
- if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
+ if (cmsg->cmsg_len < CMSG_LEN(sizeof(int)) ||
cmsg->cmsg_level != SOL_SOCKET ||
- cmsg->cmsg_type != SCM_RIGHTS)
+ cmsg->cmsg_type != SCM_RIGHTS) {
continue;
+ }
+
+ fd_size = cmsg->cmsg_len - CMSG_LEN(0);
- fd = *((int *)CMSG_DATA(cmsg));
- if (fd < 0)
+ if (!fd_size) {
continue;
+ }
+
+ /* close and clean read_msgfds */
+ for (i = 0; i < s->read_msgfds_num; i++) {
+ close(s->read_msgfds[i]);
+ }
+
+ if (s->read_msgfds_num) {
+ g_free(s->read_msgfds);
+ }
- /* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */
- qemu_set_block(fd);
+ s->read_msgfds_num = fd_size / sizeof(int);
+ s->read_msgfds = g_malloc(fd_size);
+ memcpy(s->read_msgfds, CMSG_DATA(cmsg), fd_size);
-#ifndef MSG_CMSG_CLOEXEC
- qemu_set_cloexec(fd);
-#endif
- if (s->msgfd != -1)
- close(s->msgfd);
- s->msgfd = fd;
+ for (i = 0; i < s->read_msgfds_num; i++) {
+ int fd = s->read_msgfds[i];
+ if (fd < 0) {
+ continue;
+ }
+
+ /* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */
+ qemu_set_block(fd);
+
+ #ifndef MSG_CMSG_CLOEXEC
+ qemu_set_cloexec(fd);
+ #endif
+ }
}
}
@@ -2454,6 +2605,23 @@ static GSource *tcp_chr_add_watch(CharDriverState *chr, GIOCondition cond)
return g_io_create_watch(s->chan, cond);
}
+static void tcp_chr_disconnect(CharDriverState *chr)
+{
+ TCPCharDriver *s = chr->opaque;
+
+ s->connected = 0;
+ if (s->listen_chan) {
+ s->listen_tag = g_io_add_watch(s->listen_chan, G_IO_IN,
+ tcp_chr_accept, chr);
+ }
+ remove_fd_in_watch(chr);
+ g_io_channel_unref(s->chan);
+ s->chan = NULL;
+ closesocket(s->fd);
+ s->fd = -1;
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+}
+
static gboolean tcp_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
{
CharDriverState *chr = opaque;
@@ -2470,16 +2638,7 @@ static gboolean tcp_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
size = tcp_chr_recv(chr, (void *)buf, len);
if (size == 0) {
/* connection closed */
- s->connected = 0;
- if (s->listen_chan) {
- s->listen_tag = g_io_add_watch(s->listen_chan, G_IO_IN, tcp_chr_accept, chr);
- }
- remove_fd_in_watch(chr);
- g_io_channel_unref(s->chan);
- s->chan = NULL;
- closesocket(s->fd);
- s->fd = -1;
- qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+ tcp_chr_disconnect(chr);
} else if (size > 0) {
if (s->do_telnetopt)
tcp_chr_process_IAC_bytes(chr, s, buf, &size);
@@ -2490,6 +2649,24 @@ static gboolean tcp_chr_read(GIOChannel *chan, GIOCondition cond, void *opaque)
return TRUE;
}
+static int tcp_chr_sync_read(CharDriverState *chr, const uint8_t *buf, int len)
+{
+ TCPCharDriver *s = chr->opaque;
+ int size;
+
+ if (!s->connected) {
+ return 0;
+ }
+
+ size = tcp_chr_recv(chr, (void *) buf, len);
+ if (size == 0) {
+ /* connection closed */
+ tcp_chr_disconnect(chr);
+ }
+
+ return size;
+}
+
#ifndef _WIN32
CharDriverState *qemu_chr_open_eventfd(int eventfd)
{
@@ -2503,6 +2680,25 @@ CharDriverState *qemu_chr_open_eventfd(int eventfd)
}
#endif
+static gboolean tcp_chr_chan_close(GIOChannel *channel, GIOCondition cond,
+ void *opaque)
+{
+ CharDriverState *chr = opaque;
+
+ if (cond != G_IO_HUP) {
+ return FALSE;
+ }
+
+ /* connection closed */
+ tcp_chr_disconnect(chr);
+ if (chr->fd_hup_tag) {
+ g_source_remove(chr->fd_hup_tag);
+ chr->fd_hup_tag = 0;
+ }
+
+ return TRUE;
+}
+
static void tcp_chr_connect(void *opaque)
{
CharDriverState *chr = opaque;
@@ -2512,6 +2708,8 @@ static void tcp_chr_connect(void *opaque)
if (s->chan) {
chr->fd_in_tag = io_add_watch_poll(s->chan, tcp_chr_read_poll,
tcp_chr_read, chr);
+ chr->fd_hup_tag = g_io_add_watch(s->chan, G_IO_HUP, tcp_chr_chan_close,
+ chr);
}
qemu_chr_be_generic_open(chr);
}
@@ -2604,6 +2802,7 @@ static gboolean tcp_chr_accept(GIOChannel *channel, GIOCondition cond, void *opa
static void tcp_chr_close(CharDriverState *chr)
{
TCPCharDriver *s = chr->opaque;
+ int i;
if (s->fd >= 0) {
remove_fd_in_watch(chr);
if (s->chan) {
@@ -2621,6 +2820,15 @@ static void tcp_chr_close(CharDriverState *chr)
}
closesocket(s->listen_fd);
}
+ if (s->read_msgfds_num) {
+ for (i = 0; i < s->read_msgfds_num; i++) {
+ close(s->read_msgfds[i]);
+ }
+ g_free(s->read_msgfds);
+ }
+ if (s->write_msgfds_num) {
+ g_free(s->write_msgfds);
+ }
g_free(s);
qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
}
@@ -2649,7 +2857,10 @@ static CharDriverState *qemu_chr_open_socket_fd(int fd, bool do_nodelay,
s->connected = 0;
s->fd = -1;
s->listen_fd = -1;
- s->msgfd = -1;
+ s->read_msgfds = 0;
+ s->read_msgfds_num = 0;
+ s->write_msgfds = 0;
+ s->write_msgfds_num = 0;
chr->filename = g_malloc(256);
switch (ss.ss_family) {
@@ -2678,8 +2889,10 @@ static CharDriverState *qemu_chr_open_socket_fd(int fd, bool do_nodelay,
chr->opaque = s;
chr->chr_write = tcp_chr_write;
+ chr->chr_sync_read = tcp_chr_sync_read;
chr->chr_close = tcp_chr_close;
- chr->get_msgfd = tcp_get_msgfd;
+ chr->get_msgfds = tcp_get_msgfds;
+ chr->set_msgfds = tcp_set_msgfds;
chr->chr_add_client = tcp_chr_add_client;
chr->chr_add_watch = tcp_chr_add_watch;
chr->chr_update_read_handler = tcp_chr_update_read_handler;
OpenPOWER on IntegriCloud