diff options
| author | Hans de Goede <hdegoede@redhat.com> | 2010-09-18 13:00:37 +0200 |
|---|---|---|
| committer | Hans de Goede <hdegoede@redhat.com> | 2010-09-18 13:00:37 +0200 |
| commit | c478a8e5ac472bb31eef81558d83d89face7eae3 (patch) | |
| tree | 401922fadde84de4da1702a5082775ec13c10168 | |
| parent | ec8afef6b4adcd64a7a5193a89920a6c3e9f8e5e (diff) | |
| download | vd_agent-c478a8e5ac472bb31eef81558d83d89face7eae3.tar.gz vd_agent-c478a8e5ac472bb31eef81558d83d89face7eae3.tar.xz vd_agent-c478a8e5ac472bb31eef81558d83d89face7eae3.zip | |
Add non blocking virtio port code
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | TODO | 3 | ||||
| -rw-r--r-- | udscs.c | 8 | ||||
| -rw-r--r-- | udscs.h | 2 | ||||
| -rw-r--r-- | vdagent-virtio-port.c | 299 | ||||
| -rw-r--r-- | vdagent-virtio-port.h | 87 | ||||
| -rw-r--r-- | vdagentd.c | 60 |
7 files changed, 417 insertions, 44 deletions
@@ -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 @@ -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 @@ -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); @@ -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 @@ -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; } |
