summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2010-09-18 13:00:37 +0200
committerHans de Goede <hdegoede@redhat.com>2010-09-18 13:00:37 +0200
commitc478a8e5ac472bb31eef81558d83d89face7eae3 (patch)
tree401922fadde84de4da1702a5082775ec13c10168
parentec8afef6b4adcd64a7a5193a89920a6c3e9f8e5e (diff)
downloadvd_agent-c478a8e5ac472bb31eef81558d83d89face7eae3.tar.gz
vd_agent-c478a8e5ac472bb31eef81558d83d89face7eae3.tar.xz
vd_agent-c478a8e5ac472bb31eef81558d83d89face7eae3.zip
Add non blocking virtio port code
-rw-r--r--Makefile2
-rw-r--r--TODO3
-rw-r--r--udscs.c8
-rw-r--r--udscs.h2
-rw-r--r--vdagent-virtio-port.c299
-rw-r--r--vdagent-virtio-port.h87
-rw-r--r--vdagentd.c60
7 files changed, 417 insertions, 44 deletions
diff --git a/Makefile b/Makefile
index cbdefd1..70d0732 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ install: build
clean:
rm -f $(TARGETS) *.o *~
-vdagentd: vdagentd.o udscs.o
+vdagentd: vdagentd.o udscs.o vdagent-virtio-port.o
$(CC) -o $@ $^
vdagent: vdagent.o vdagent-x11.o udscs.o
diff --git a/TODO b/TODO
index d8bf9d9..f8f519b 100644
--- a/TODO
+++ b/TODO
@@ -16,10 +16,7 @@ owner is and then only send messages to / accept messages from
agents whose unix domain socket has the same uid.
Various small things:
--make virtio communication non blocking
-make uinput communication non blocking
--vdagentd: multiple debug levels (allow not detaching from tty without
- flooding the tty with mouse coordinates)
-remove width and height options from vdagentd
-maybe not create an input device at all until we've gotten a
width / height, this would mean also not connecting to the virtio
diff --git a/udscs.c b/udscs.c
index 68e6e34..9d19bfd 100644
--- a/udscs.c
+++ b/udscs.c
@@ -37,7 +37,7 @@ struct udscs_buf {
struct udscs_buf *next;
};
-
+
struct udscs_connection {
int fd;
@@ -121,6 +121,9 @@ void udscs_destroy_server(struct udscs_server *server)
{
struct udscs_connection *conn, *next_conn;
+ if (!server)
+ return;
+
conn = server->connections_head.next;
while (conn) {
next_conn = conn->next;
@@ -170,6 +173,9 @@ void udscs_destroy_connection(struct udscs_connection **connp)
struct udscs_buf *wbuf, *next_wbuf;
struct udscs_connection *conn = *connp;
+ if (!conn)
+ return;
+
if (conn->disconnect_callback)
conn->disconnect_callback(conn);
diff --git a/udscs.h b/udscs.h
index a7af3aa..2adc511 100644
--- a/udscs.h
+++ b/udscs.h
@@ -66,7 +66,7 @@ struct udscs_connection *udscs_connect(const char *socketname,
void udscs_destroy_connection(struct udscs_connection **connp);
-/* Given an usdcs server or client fill the fd_sets pointed to by readfds and
+/* Given an udscs server or client fill the fd_sets pointed to by readfds and
writefds for select() usage.
Return value: value of the highest fd + 1 */
diff --git a/vdagent-virtio-port.c b/vdagent-virtio-port.c
new file mode 100644
index 0000000..2be50f0
--- /dev/null
+++ b/vdagent-virtio-port.c
@@ -0,0 +1,299 @@
+/* vdagent-virtio-port.c virtio port communication code
+
+ Copyright 2010 Red Hat, Inc.
+
+ Red Hat Authors:
+ Hans de Goede <hdegoede@redhat.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include "vdagent-virtio-port.h"
+
+struct vdagent_virtio_port_buf {
+ uint8_t *buf;
+ size_t pos;
+ size_t size;
+
+ struct vdagent_virtio_port_buf *next;
+};
+
+struct vdagent_virtio_port {
+ int fd;
+
+ /* Read stuff, single buffer, separate header and data buffer */
+ int chunk_header_read;
+ int message_header_read;
+ VDIChunkHeader chunk_header;
+ VDAgentMessage message_header;
+ struct vdagent_virtio_port_buf data;
+
+ /* Writes are stored in a linked list of buffers, with both the header
+ + data for a single message in 1 buffer. */
+ struct vdagent_virtio_port_buf *write_buf;
+
+ /* Callbacks */
+ vdagent_virtio_port_read_callback read_callback;
+ vdagent_virtio_port_disconnect_callback disconnect_callback;
+};
+
+static void vdagent_virtio_port_do_write(struct vdagent_virtio_port **portp);
+static void vdagent_virtio_port_do_read(struct vdagent_virtio_port **portp);
+
+struct vdagent_virtio_port *vdagent_virtio_port_create(const char *portname,
+ vdagent_virtio_port_read_callback read_callback,
+ vdagent_virtio_port_disconnect_callback disconnect_callback)
+{
+ struct vdagent_virtio_port *port;
+
+ port = calloc(1, sizeof(*port));
+ if (!port)
+ return 0;
+
+ port->fd = open(portname, O_RDWR);
+ if (port->fd == -1) {
+ fprintf(stderr, "open %s: %s\n", portname, strerror(errno));
+ free(port);
+ return NULL;
+ }
+
+ port->read_callback = read_callback;
+ port->disconnect_callback = disconnect_callback;
+
+ return port;
+}
+
+void vdagent_virtio_port_destroy(struct vdagent_virtio_port **portp)
+{
+ struct vdagent_virtio_port_buf *wbuf, *next_wbuf;
+ struct vdagent_virtio_port *port = *portp;
+
+ if (!port)
+ return;
+
+ if (port->disconnect_callback)
+ port->disconnect_callback(port);
+
+ wbuf = port->write_buf;
+ while (wbuf) {
+ next_wbuf = wbuf->next;
+ free(wbuf->buf);
+ free(wbuf);
+ wbuf = next_wbuf;
+ }
+
+ free(port->data.buf);
+
+ close(port->fd);
+ free(port);
+ *portp = NULL;
+}
+
+int vdagent_virtio_port_fill_fds(struct vdagent_virtio_port *port,
+ fd_set *readfds, fd_set *writefds)
+{
+ FD_SET(port->fd, readfds);
+ if (port->write_buf)
+ FD_SET(port->fd, writefds);
+
+ return port->fd + 1;
+}
+
+void vdagent_virtio_port_handle_fds(struct vdagent_virtio_port **portp,
+ fd_set *readfds, fd_set *writefds)
+{
+ if (FD_ISSET((*portp)->fd, readfds))
+ vdagent_virtio_port_do_read(portp);
+
+ if (*portp && FD_ISSET((*portp)->fd, writefds))
+ vdagent_virtio_port_do_write(portp);
+}
+
+int vdagent_virtio_port_write(
+ struct vdagent_virtio_port *port,
+ VDIChunkHeader *chunk_header,
+ VDAgentMessage *message_header,
+ uint8_t *data)
+{
+ struct vdagent_virtio_port_buf *wbuf, *new_wbuf;
+
+ if (message_header->size !=
+ (chunk_header->size - sizeof(*message_header))) {
+ fprintf(stderr, "write: chunk vs message header size mismatch\n");
+ return -1;
+ }
+
+ new_wbuf = malloc(sizeof(*new_wbuf));
+ if (!new_wbuf)
+ return -1;
+
+ new_wbuf->pos = 0;
+ new_wbuf->size = sizeof(*chunk_header) + sizeof(*message_header) +
+ message_header->size;
+ new_wbuf->next = NULL;
+ new_wbuf->buf = malloc(new_wbuf->size);
+ if (!new_wbuf->buf) {
+ free(new_wbuf);
+ return -1;
+ }
+
+ memcpy(new_wbuf->buf, chunk_header, sizeof(*chunk_header));
+ memcpy(new_wbuf->buf + sizeof(*chunk_header), message_header,
+ sizeof(*message_header));
+ memcpy(new_wbuf->buf + sizeof(*chunk_header) + sizeof(*message_header),
+ data, message_header->size);
+
+ if (!port->write_buf) {
+ port->write_buf = new_wbuf;
+ return 0;
+ }
+
+ /* FIXME maybe limit the write_buf stack depth ? */
+ wbuf = port->write_buf;
+ while (wbuf->next)
+ wbuf = wbuf->next;
+
+ wbuf->next = wbuf;
+
+ return 0;
+}
+
+static void vdagent_virtio_port_do_read(struct vdagent_virtio_port **portp)
+{
+ ssize_t n;
+ size_t to_read;
+ uint8_t *dest;
+ int r;
+ struct vdagent_virtio_port *port = *portp;
+
+ if (port->chunk_header_read < sizeof(port->chunk_header)) {
+ to_read = sizeof(port->chunk_header) - port->chunk_header_read;
+ dest = (uint8_t *)&port->chunk_header + port->chunk_header_read;
+ } else if (port->message_header_read < sizeof(port->message_header)) {
+ to_read = sizeof(port->message_header) - port->message_header_read;
+ dest = (uint8_t *)&port->message_header + port->message_header_read;
+ } else {
+ to_read = port->data.size - port->data.pos;
+ dest = port->data.buf + port->data.pos;
+ }
+
+ n = read(port->fd, dest, to_read);
+ if (n < 0) {
+ if (errno == EINTR)
+ return;
+ perror("reading from vdagent virtio port");
+ }
+ if (n <= 0) {
+ vdagent_virtio_port_destroy(portp);
+ return;
+ }
+
+ if (port->chunk_header_read < sizeof(port->chunk_header)) {
+ port->chunk_header_read += n;
+ if (port->chunk_header_read == sizeof(port->chunk_header)) {
+ if (port->chunk_header.size < sizeof(port->message_header)) {
+ fprintf(stderr, "chunk size < message header size\n");
+ vdagent_virtio_port_destroy(portp);
+ return;
+ }
+ port->message_header_read = 0;
+ }
+ } else if (port->message_header_read < sizeof(port->message_header)) {
+ port->message_header_read += n;
+ if (port->message_header_read == sizeof(port->message_header)) {
+ if (port->message_header.size !=
+ (port->chunk_header.size - sizeof(port->message_header))) {
+ fprintf(stderr,
+ "read: chunk vs message header size mismatch\n");
+ vdagent_virtio_port_destroy(portp);
+ return;
+ }
+ if (port->message_header.size == 0) {
+ if (port->read_callback) {
+ r = port->read_callback(port, &port->chunk_header,
+ &port->message_header, NULL);
+ if (r == -1) {
+ vdagent_virtio_port_destroy(portp);
+ return;
+ }
+ }
+ port->chunk_header_read = 0;
+ port->message_header_read = 0;
+ } else {
+ port->data.pos = 0;
+ port->data.size = port->message_header.size;
+ port->data.buf = malloc(port->data.size);
+ if (!port->data.buf) {
+ fprintf(stderr, "out of memory, disportecting client\n");
+ vdagent_virtio_port_destroy(portp);
+ return;
+ }
+ }
+ }
+ } else {
+ port->data.pos += n;
+ if (port->data.pos == port->data.size) {
+ if (port->read_callback) {
+ r = port->read_callback(port, &port->chunk_header,
+ &port->message_header, port->data.buf);
+ if (r == -1) {
+ vdagent_virtio_port_destroy(portp);
+ return;
+ }
+ }
+ free(port->data.buf);
+ port->chunk_header_read = 0;
+ port->message_header_read = 0;
+ memset(&port->data, 0, sizeof(port->data));
+ }
+ }
+}
+
+static void vdagent_virtio_port_do_write(struct vdagent_virtio_port **portp)
+{
+ ssize_t n;
+ size_t to_write;
+ struct vdagent_virtio_port *port = *portp;
+
+ struct vdagent_virtio_port_buf* wbuf = port->write_buf;
+ if (!wbuf) {
+ fprintf(stderr,
+ "do_write called on a port without a write buf ?!\n");
+ return;
+ }
+
+ to_write = wbuf->size - wbuf->pos;
+ n = write(port->fd, wbuf->buf + wbuf->pos, to_write);
+ if (n < 0) {
+ if (errno == EINTR)
+ return;
+ perror("writing to vdagent virtio port");
+ vdagent_virtio_port_destroy(portp);
+ return;
+ }
+
+ wbuf->pos += n;
+ if (wbuf->pos == wbuf->size) {
+ port->write_buf = wbuf->next;
+ free(wbuf->buf);
+ free(wbuf);
+ }
+}
diff --git a/vdagent-virtio-port.h b/vdagent-virtio-port.h
new file mode 100644
index 0000000..33b8471
--- /dev/null
+++ b/vdagent-virtio-port.h
@@ -0,0 +1,87 @@
+/* vdagent-virtio-port.h virtio port communication header
+
+ Copyright 2010 Red Hat, Inc.
+
+ Red Hat Authors:
+ Hans de Goede <hdegoede@redhat.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __VIRTIO_PORT_H
+#define __VIRTIO_PORT_H
+
+#include <stdint.h>
+#include <sys/select.h>
+#include <spice/vd_agent.h>
+
+struct vdagent_virtio_port;
+
+/* Callbacks with this type will be called when a complete message has been
+ received. Sometimes the callback may want to close the port, in this
+ case do *not* call vdagent_virtio_port_destroy from the callback. The desire
+ to close the port can be indicated be returning -1 from the callback,
+ in other cases return 0. */
+typedef int (*vdagent_virtio_port_read_callback)(
+ struct vdagent_virtio_port *port,
+ VDIChunkHeader *chunk_header,
+ VDAgentMessage *message_header,
+ uint8_t *data);
+
+/* Callbacks with this type will be called when the port is disconnected.
+ Note:
+ 1) vdagent_virtio_port will destroy the port in question itself after
+ this callback has completed!
+ 2) This callback is always called, even if the disconnect is initiated
+ by the vdagent_virtio_port user through returning -1 from a read
+ callback, or by explictly calling vdagent_virtio_port_destroy */
+typedef void (*vdagent_virtio_port_disconnect_callback)(
+ struct vdagent_virtio_port *conn);
+
+
+/* Create a vdagent virtio port object for port portname */
+struct vdagent_virtio_port *vdagent_virtio_port_create(const char *portname,
+ vdagent_virtio_port_read_callback read_callback,
+ vdagent_virtio_port_disconnect_callback disconnect_callback);
+
+/* The contents of portp will be made NULL */
+void vdagent_virtio_port_destroy(struct vdagent_virtio_port **portp);
+
+
+/* Given a vdagent_virtio_port fill the fd_sets pointed to by readfds and
+ writefds for select() usage.
+
+ Return value: value of the highest fd + 1 */
+int vdagent_virtio_port_fill_fds(struct vdagent_virtio_port *port,
+ fd_set *readfds, fd_set *writefds);
+
+/* Handle any events flagged by select for the given vdagent_virtio_port.
+ Note the port may be destroyed (when disconnected) by this call
+ in this case the disconnect calllback will get called before the
+ destruction and the contents of connp will be made NULL */
+void vdagent_virtio_port_handle_fds(struct vdagent_virtio_port **portp,
+ fd_set *readfds, fd_set *writefds);
+
+
+/* Queue the message described by chunk_header and message_header and
+ message_header->size bytes of additional data bytes for writing.
+
+ Returns 0 on success -1 on error (only happens when malloc fails) */
+int vdagent_virtio_port_write(
+ struct vdagent_virtio_port *port,
+ VDIChunkHeader *chunk_header,
+ VDAgentMessage *message_header,
+ uint8_t *data);
+
+#endif
diff --git a/vdagentd.c b/vdagentd.c
index e9e53fb..c870b24 100644
--- a/vdagentd.c
+++ b/vdagentd.c
@@ -16,6 +16,7 @@
#include "udscs.h"
#include "vdagentd-proto.h"
+#include "vdagent-virtio-port.h"
typedef struct VDAgentHeader {
uint32_t port;
@@ -27,10 +28,11 @@ typedef struct VDAgentHeader {
static const char *portdev = "/dev/virtio-ports/com.redhat.spice.0";
static const char *uinput = "/dev/uinput";
-static int vdagent, tablet;
+static int tablet;
static int debug = 0;
static int width = 1024, height = 768; /* FIXME: don't hardcode */
static struct udscs_server *server = NULL;
+static struct vdagent_virtio_port *virtio_port = NULL;
/* uinput */
@@ -158,41 +160,26 @@ static void do_monitors(VDAgentMonitorsConfig *monitors)
}
}
-void vdagent_read(void)
+int virtio_port_read_complete(
+ struct vdagent_virtio_port *port,
+ VDIChunkHeader *chunk_header,
+ VDAgentMessage *message_header,
+ uint8_t *data)
{
- VDAgentHeader header;
- VDAgentMessage *message;
- void *data;
- int rc;
-
- rc = read(vdagent, &header, sizeof(header));
- if (rc != sizeof(header)) {
- fprintf(stderr, "vdagent header read error (%d/%zd)\n", rc, sizeof(header));
- exit(1);
- }
-
- message = malloc(header.size);
- rc = read(vdagent, message, header.size);
- if (rc != header.size) {
- fprintf(stderr, "vdagent message read error (%d/%d)\n", rc, header.size);
- exit(1);
- }
- data = message->data;
-
- switch (message->type) {
+ switch (message_header->type) {
case VD_AGENT_MOUSE_STATE:
- do_mouse(data);
+ do_mouse((VDAgentMouseState *)data);
break;
case VD_AGENT_MONITORS_CONFIG:
- do_monitors(data);
+ do_monitors((VDAgentMonitorsConfig *)data);
break;
default:
if (debug)
- fprintf(stderr, "unknown message type %d\n", message->type);
+ fprintf(stderr, "unknown message type %d\n", message_header->type);
break;
}
- free(message);
+ return 0;
}
/* main */
@@ -263,16 +250,14 @@ void main_loop(void)
fd_set readfds, writefds;
int n, nfds;
- /* FIXME catch sigterm and stop on it */
- for (;;) {
+ while (virtio_port) {
FD_ZERO(&readfds);
FD_ZERO(&writefds);
nfds = udscs_server_fill_fds(server, &readfds, &writefds);
-
- FD_SET(vdagent, &readfds);
- if (vdagent >= nfds)
- nfds = vdagent + 1;
+ n = vdagent_virtio_port_fill_fds(virtio_port, &readfds, &writefds);
+ if (n >= nfds)
+ nfds = n + 1;
n = select(nfds, &readfds, &writefds, NULL, NULL);
if (n == -1) {
@@ -283,8 +268,7 @@ void main_loop(void)
}
udscs_server_handle_fds(server, &readfds, &writefds);
- if (FD_ISSET(vdagent, &readfds))
- vdagent_read();
+ vdagent_virtio_port_handle_fds(&virtio_port, &readfds, &writefds);
}
}
@@ -321,11 +305,10 @@ int main(int argc, char *argv[])
}
/* Open virtio port connection */
- vdagent = open(portdev, O_RDWR);
- if (-1 == vdagent) {
- fprintf(stderr, "open %s: %s\n", portdev, strerror(errno));
+ virtio_port = vdagent_virtio_port_create(portdev,
+ virtio_port_read_complete, NULL);
+ if (!virtio_port)
exit(1);
- }
/* Setup communication with vdagent process(es) */
server = udscs_create_server(VDAGENTD_SOCKET, client_read_complete, NULL);
@@ -345,6 +328,7 @@ int main(int argc, char *argv[])
main_loop();
udscs_destroy_server(server);
+ vdagent_virtio_port_destroy(&virtio_port);
return 0;
}