Patchwork [19/21] qom-chrdrv: add Socket base class

login
register
mail settings
Submitter Anthony Liguori
Date July 25, 2011, 1:44 a.m.
Message ID <1311558293-5855-20-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/106583/
State New
Headers show

Comments

Anthony Liguori - July 25, 2011, 1:44 a.m.
This introduces two classed, SocketCharDriver and SocketServer.
SocketCharDriver implements the generic logic for all socket based transports.
SocketServer implements the necessary functionality for server-based socket
transports.

Unlike the previous socket CharDriverState, there is no magic overloading based
on passed options.  You have to explicitly create types of socket CharDrivers
for TCP or Unix domain sockets.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 chrdrv/Makefile          |    1 +
 chrdrv/Qconfig           |    7 ++
 chrdrv/socketchr.c       |  250 ++++++++++++++++++++++++++++++++++++++++++++++
 include/qemu/socketchr.h |  109 ++++++++++++++++++++
 4 files changed, 367 insertions(+), 0 deletions(-)
 create mode 100644 chrdrv/socketchr.c
 create mode 100644 include/qemu/socketchr.h

Patch

diff --git a/chrdrv/Makefile b/chrdrv/Makefile
index 6cfdd27..84bff48 100644
--- a/chrdrv/Makefile
+++ b/chrdrv/Makefile
@@ -1,2 +1,3 @@ 
 chrdrv-obj-$(CONFIG_CHRDRV) := chrdrv.o
 chrdrv-obj-$(CONFIG_CHRDRV_MEM) += memchr.o
+chrdrv-obj-$(CONFIG_CHRDRV_SOCKET) += socketchr.o
diff --git a/chrdrv/Qconfig b/chrdrv/Qconfig
index f3f939e..417c063 100644
--- a/chrdrv/Qconfig
+++ b/chrdrv/Qconfig
@@ -10,3 +10,10 @@  config CHRDRV_MEM
        depends on CHRDRV
        help
          Character device that stores all written data to a memory buffer.
+
+config CHRDRV_SOCKET
+       bool "Socket character driver"
+       default y
+       depends on CHRDRV
+       help
+         Character driver that implements a socket based transport.
diff --git a/chrdrv/socketchr.c b/chrdrv/socketchr.c
new file mode 100644
index 0000000..941792c
--- /dev/null
+++ b/chrdrv/socketchr.c
@@ -0,0 +1,250 @@ 
+#include "qemu/socketchr.h"
+#include "qemu_socket.h"
+
+static int socket_chr_write(CharDriver *chr, const uint8_t *buf, int len)
+{
+    SocketCharDriver *s = SOCKET_CHAR_DRIVER(chr);
+
+    if (!s->connected) {
+        return len;
+    }
+
+    return send_all(s->fd, buf, len);
+}
+    
+static int socket_chr_get_msgfd(CharDriver *chr)
+{
+    SocketCharDriver *s = SOCKET_CHAR_DRIVER(chr);
+    int fd = s->msgfd;
+    s->msgfd = -1;
+    return fd;
+}
+
+#ifndef _WIN32
+static void unix_process_msgfd(CharDriver *chr, struct msghdr *msg)
+{
+    SocketCharDriver *s = SOCKET_CHAR_DRIVER(chr);
+    struct cmsghdr *cmsg;
+
+    for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+        int fd;
+
+        if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
+            cmsg->cmsg_level != SOL_SOCKET ||
+            cmsg->cmsg_type != SCM_RIGHTS) {
+            continue;
+        }
+
+        fd = *((int *)CMSG_DATA(cmsg));
+        if (fd < 0) {
+            continue;
+        }
+
+        if (s->msgfd != -1) {
+            close(s->msgfd);
+        }
+        s->msgfd = fd;
+    }
+}
+
+static ssize_t socket_chr_recv(CharDriver *chr, char *buf, size_t len)
+{
+    SocketCharDriver *s = SOCKET_CHAR_DRIVER(chr);
+    struct msghdr msg = { NULL, };
+    struct iovec iov[1];
+    union {
+        struct cmsghdr cmsg;
+        char control[CMSG_SPACE(sizeof(int))];
+    } msg_control;
+    ssize_t ret;
+
+    iov[0].iov_base = buf;
+    iov[0].iov_len = len;
+
+    msg.msg_iov = iov;
+    msg.msg_iovlen = 1;
+    msg.msg_control = &msg_control;
+    msg.msg_controllen = sizeof(msg_control);
+
+    ret = recvmsg(s->fd, &msg, 0);
+    if (ret > 0) {
+        unix_process_msgfd(chr, &msg);
+    }
+
+    return ret;
+}
+#else
+static ssize_t socket_chr_recv(CharDriver *chr, char *buf, size_t len)
+{
+    SocketCharDriver *s = SOCKET_CHAR_DRIVER(chr);
+    return recv(s->fd, buf, len, 0);
+}
+#endif
+
+#define READ_BUF_LEN 1024
+
+static void socket_chr_try_read(void *opaque)
+{
+    SocketCharDriver *s = opaque;
+    uint8_t buf[READ_BUF_LEN];
+    int len, size;
+    int max_size;
+
+    /* Hopefully no drivers rely on can_read anymore.. */
+    max_size = char_driver_can_read(CHAR_DRIVER(s));
+    if (max_size <= 0) {
+        return;
+    }
+
+    len = sizeof(buf);
+    if (len > max_size) {
+        len = max_size;
+    }
+    size = socket_chr_recv(CHAR_DRIVER(s), (void *)buf, len);
+    if (size == 0) {
+        /* connection closed */
+        s->connected = 0;
+        qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
+        closesocket(s->fd);
+        s->fd = -1;
+        char_driver_event(CHAR_DRIVER(s), CHR_EVENT_CLOSED);
+        // fixme call something that socketserver can hook
+    } else if (size > 0) {
+        char_driver_read(CHAR_DRIVER(s), buf, size);
+    }
+}
+
+void socket_chr_connect(SocketCharDriver *s)
+{
+    s->connected = 1;
+    qemu_set_fd_handler(s->fd, socket_chr_try_read, NULL, s);
+// FIXME    qemu_chr_generic_open(chr);
+}
+
+static void socket_chr_close(CharDriver *chr)
+{
+    SocketCharDriver *s = SOCKET_CHAR_DRIVER(chr);
+
+    if (s->fd >= 0) {
+        qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
+        closesocket(s->fd);
+    }
+    char_driver_event(chr, CHR_EVENT_CLOSED);
+}
+
+static void socket_chr_init(TypeInstance *inst)
+{
+    SocketCharDriver *s = SOCKET_CHAR_DRIVER(inst);
+
+    s->fd = -1;
+}
+
+static void socket_chr_class_init(TypeClass *class)
+{
+    CharDriverClass *cdc = CHAR_DRIVER_CLASS(class);
+
+    cdc->write = socket_chr_write;
+    cdc->close = socket_chr_close;
+    cdc->get_msgfd = socket_chr_get_msgfd;
+}
+
+static TypeInfo socket_chr_info = {
+    .name = TYPE_SOCKET_CHAR_DRIVER,
+    .parent = TYPE_CHAR_DRIVER,
+    .instance_size = sizeof(SocketCharDriver),
+    .class_init = socket_chr_class_init,
+    .instance_init = socket_chr_init,
+    .abstract = true,
+};
+
+/* Socket Server */
+
+static void socket_server_accept(void *opaque)
+{
+    SocketServer *s = SOCKET_SERVER(opaque);
+    SocketCharDriver *scd = SOCKET_CHAR_DRIVER(s);
+    SocketServerClass *ssc = SOCKET_SERVER_GET_CLASS(s);
+    int fd;
+
+    fd = ssc->accept(s);
+    socket_set_nonblock(fd);
+    scd->fd = fd;
+    qemu_set_fd_handler(s->listen_fd, NULL, NULL, NULL);
+    socket_chr_connect(scd);
+}
+
+static void socket_server_open(CharDriver *chr, Error **errp)
+{
+    SocketServer *s = SOCKET_SERVER(chr);
+
+    socket_server_rebind(s);
+}
+
+bool socket_server_get_wait(SocketServer *s, Error **errp)
+{
+    return s->wait;
+}
+
+void socket_server_set_wait(SocketServer *s, bool value, Error **errp)
+{
+    s->wait = value;
+}
+
+void socket_server_rebind(SocketServer *s)
+{
+    SocketServerClass *ssc = SOCKET_SERVER_GET_CLASS(s);
+
+    /* Don't immediately rebind if the backend isn't realized */
+    if (!plug_get_realized(PLUG(s), NULL)) {
+        return;
+    }
+
+    if (s->listen_fd != -1) {
+        qemu_set_fd_handler(s->listen_fd, NULL, NULL, NULL);
+        close(s->listen_fd);
+    }
+
+    s->listen_fd = ssc->make_listen_socket(s);
+    if (!s->wait) {
+        socket_set_nonblock(s->listen_fd);
+        qemu_set_fd_handler(s->listen_fd, socket_server_accept, NULL, s);
+    } else {
+        socket_server_accept(s);
+    }
+}
+
+static void socket_server_init(TypeInstance *inst)
+{
+    SocketServer *s = SOCKET_SERVER(inst);
+
+    s->listen_fd = -1;
+
+    plug_add_property_bool(PLUG(s), "wait",
+                           (PlugPropertyGetterBool *)socket_server_get_wait,
+                           (PlugPropertySetterBool *)socket_server_set_wait,
+                           PROP_F_READWRITE);
+}
+
+static void socket_server_class_init(TypeClass *class)
+{
+    CharDriverClass *cdc = CHAR_DRIVER_CLASS(class);
+    cdc->open = socket_server_open;
+}
+
+static TypeInfo socket_server_info = {
+    .name = TYPE_SOCKET_SERVER,
+    .parent = TYPE_SOCKET_CHAR_DRIVER,
+    .instance_size = sizeof(SocketServer),
+    .class_size = sizeof(SocketServerClass),
+    .class_init = socket_server_class_init,
+    .instance_init = socket_server_init,
+    .abstract = true,
+};
+
+static void register_backends(void)
+{
+    type_register_static(&socket_chr_info);
+    type_register_static(&socket_server_info);
+}
+
+device_init(register_backends);
diff --git a/include/qemu/socketchr.h b/include/qemu/socketchr.h
new file mode 100644
index 0000000..2beb888
--- /dev/null
+++ b/include/qemu/socketchr.h
@@ -0,0 +1,109 @@ 
+#ifndef CHR_SOCKET_H
+#define CHR_SOCKET_H
+
+#include "qemu/chrdrv.h"
+
+/**
+ * @SocketCharDriver:
+ *
+ * Base class for any @CharDriver that uses a socket as a transport.
+ */
+typedef struct SocketCharDriver
+{
+    CharDriver parent;
+
+    /* Private */
+
+    /**
+     * @connected whether the transport is connected
+     */
+    int connected;
+
+    /**
+     * @fd the data socket fd
+     */
+    int fd;
+
+    /**
+     * @msgfd the received file descriptor from the transport
+     */
+    int msgfd;
+} SocketCharDriver;
+
+typedef struct SocketCharDriverClass
+{
+    CharDriverClass parent_class;
+} SocketCharDriverClass;
+
+#define TYPE_SOCKET_CHAR_DRIVER "socket-char-driver"
+#define SOCKET_CHAR_DRIVER(obj) \
+    TYPE_CHECK(SocketCharDriver, obj, TYPE_SOCKET_CHAR_DRIVER)
+
+void socket_chr_connect(SocketCharDriver *s);
+
+/**
+ * @SocketServer:
+ *
+ * The base class for any socket based transport that accepts connections.
+ */
+typedef struct SocketServer
+{
+    SocketCharDriver parent;
+
+    /* protected */
+
+    /**
+     * @wait if true, then immediate block until a client connects
+     */
+    bool wait;
+
+    /**
+     * @listen_fd the fd of the socket to accept connections on
+     */
+    int listen_fd;
+} SocketServer;
+
+typedef struct SocketServerClass
+{
+    SocketCharDriverClass parent_class;
+
+    /* Protected */
+
+    /**
+     * @make_listen_socket
+     *
+     * This function should create a socket and bind it such that its
+     * suitable for listening.
+     */
+    int (*make_listen_socket)(SocketServer *obj);
+
+    /**
+     * @accept
+     *
+     * This function should accept a connection on @listen_fd and return
+     * a socket representing that connection.
+     */
+    int (*accept)(SocketServer *obj);
+} SocketServerClass;
+
+#define TYPE_SOCKET_SERVER "socket-server"
+#define SOCKET_SERVER(obj) \
+    TYPE_CHECK(SocketServer, obj, TYPE_SOCKET_SERVER)
+#define SOCKET_SERVER_CLASS(class) \
+    TYPE_CLASS_CHECK(SocketServerClass, class, TYPE_SOCKET_SERVER)
+#define SOCKET_SERVER_GET_CLASS(obj) \
+    TYPE_GET_CLASS(SocketServerClass, obj, TYPE_SOCKET_SERVER)
+
+bool socket_server_get_wait(SocketServer *s, Error **errp);
+void socket_server_set_wait(SocketServer *s, bool value, Error **errp);
+
+/**
+ * @socket_server_rebind:
+ *
+ * Rebinds the server by closing and reopening the listen_fd.  This is only
+ * meant to be used by sub classes implementations.
+ */
+void socket_server_rebind(SocketServer *s);
+
+#endif
+