Blame SOURCES/kvm-io-send-proper-HTTP-response-for-websocket-errors.patch

4a2fec
From 062b6fd62b387f4ea67f800ca956c87b28451a9c Mon Sep 17 00:00:00 2001
4a2fec
From: "Daniel P. Berrange" <berrange@redhat.com>
4a2fec
Date: Wed, 20 Dec 2017 17:56:43 +0100
4a2fec
Subject: [PATCH 03/42] io: send proper HTTP response for websocket errors
4a2fec
MIME-Version: 1.0
4a2fec
Content-Type: text/plain; charset=UTF-8
4a2fec
Content-Transfer-Encoding: 8bit
4a2fec
4a2fec
RH-Author: Daniel P. Berrange <berrange@redhat.com>
4a2fec
Message-id: <20171220175702.29663-2-berrange@redhat.com>
4a2fec
Patchwork-id: 78454
4a2fec
O-Subject: [RHV-7.5 qemu-kvm-rhev PATCH v2 01/20] io: send proper HTTP response for websocket errors
4a2fec
Bugzilla: 1518649
4a2fec
RH-Acked-by: John Snow <jsnow@redhat.com>
4a2fec
RH-Acked-by: Jeffrey Cody <jcody@redhat.com>
4a2fec
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
4a2fec
4a2fec
When any error occurs while processing the websockets handshake,
4a2fec
QEMU just terminates the connection abruptly. This is in violation
4a2fec
of the HTTP specs and does not help the client understand what they
4a2fec
did wrong. This is particularly bad when the client gives the wrong
4a2fec
path, as a "404 Not Found" would be very helpful.
4a2fec
4a2fec
Refactor the handshake code so that it always sends a response to
4a2fec
the client unless there was an I/O error.
4a2fec
4a2fec
Fixes bug: #1715186
4a2fec
4a2fec
Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
4a2fec
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
4a2fec
(cherry picked from commit f69a8bde29354493ff8aea64cc9cb3b531d16337)
4a2fec
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
4a2fec
---
4a2fec
 io/channel-websock.c | 185 ++++++++++++++++++++++++++++++++++++++-------------
4a2fec
 1 file changed, 139 insertions(+), 46 deletions(-)
4a2fec
4a2fec
diff --git a/io/channel-websock.c b/io/channel-websock.c
4a2fec
index 5a3badb..f5fac5b 100644
4a2fec
--- a/io/channel-websock.c
4a2fec
+++ b/io/channel-websock.c
4a2fec
@@ -25,6 +25,8 @@
4a2fec
 #include "crypto/hash.h"
4a2fec
 #include "trace.h"
4a2fec
 
4a2fec
+#include <time.h>
4a2fec
+
4a2fec
 
4a2fec
 /* Max amount to allow in rawinput/rawoutput buffers */
4a2fec
 #define QIO_CHANNEL_WEBSOCK_MAX_BUFFER 8192
4a2fec
@@ -44,13 +46,40 @@
4a2fec
 #define QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE "Upgrade"
4a2fec
 #define QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET "websocket"
4a2fec
 
4a2fec
-#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE  \
4a2fec
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON \
4a2fec
+    "Server: QEMU VNC\r\n"                       \
4a2fec
+    "Date: %s\r\n"
4a2fec
+
4a2fec
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK    \
4a2fec
     "HTTP/1.1 101 Switching Protocols\r\n"      \
4a2fec
+    QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON    \
4a2fec
     "Upgrade: websocket\r\n"                    \
4a2fec
     "Connection: Upgrade\r\n"                   \
4a2fec
     "Sec-WebSocket-Accept: %s\r\n"              \
4a2fec
     "Sec-WebSocket-Protocol: binary\r\n"        \
4a2fec
     "\r\n"
4a2fec
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_NOT_FOUND \
4a2fec
+    "HTTP/1.1 404 Not Found\r\n"                    \
4a2fec
+    QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON        \
4a2fec
+    "Connection: close\r\n"                         \
4a2fec
+    "\r\n"
4a2fec
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST \
4a2fec
+    "HTTP/1.1 400 Bad Request\r\n"                    \
4a2fec
+    QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON          \
4a2fec
+    "Connection: close\r\n"                           \
4a2fec
+    "Sec-WebSocket-Version: "                         \
4a2fec
+    QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION             \
4a2fec
+    "\r\n"
4a2fec
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_SERVER_ERR \
4a2fec
+    "HTTP/1.1 500 Internal Server Error\r\n"         \
4a2fec
+    QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON         \
4a2fec
+    "Connection: close\r\n"                          \
4a2fec
+    "\r\n"
4a2fec
+#define QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_TOO_LARGE  \
4a2fec
+    "HTTP/1.1 403 Request Entity Too Large\r\n"      \
4a2fec
+    QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_COMMON         \
4a2fec
+    "Connection: close\r\n"                          \
4a2fec
+    "\r\n"
4a2fec
 #define QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM "\r\n"
4a2fec
 #define QIO_CHANNEL_WEBSOCK_HANDSHAKE_END "\r\n\r\n"
4a2fec
 #define QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION "13"
4a2fec
@@ -123,8 +152,46 @@ enum {
4a2fec
     QIO_CHANNEL_WEBSOCK_OPCODE_PONG = 0xA
4a2fec
 };
4a2fec
 
4a2fec
+static void qio_channel_websock_handshake_send_res(QIOChannelWebsock *ioc,
4a2fec
+                                                   const char *resmsg,
4a2fec
+                                                   ...)
4a2fec
+{
4a2fec
+    va_list vargs;
4a2fec
+    char *response;
4a2fec
+    size_t responselen;
4a2fec
+
4a2fec
+    va_start(vargs, resmsg);
4a2fec
+    response = g_strdup_vprintf(resmsg, vargs);
4a2fec
+    responselen = strlen(response);
4a2fec
+    buffer_reserve(&ioc->encoutput, responselen);
4a2fec
+    buffer_append(&ioc->encoutput, response, responselen);
4a2fec
+    va_end(vargs);
4a2fec
+}
4a2fec
+
4a2fec
+static gchar *qio_channel_websock_date_str(void)
4a2fec
+{
4a2fec
+    struct tm tm;
4a2fec
+    time_t now = time(NULL);
4a2fec
+    char datebuf[128];
4a2fec
+
4a2fec
+    gmtime_r(&now, &tm;;
4a2fec
+
4a2fec
+    strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S GMT", &tm;;
4a2fec
+
4a2fec
+    return g_strdup(datebuf);
4a2fec
+}
4a2fec
+
4a2fec
+static void qio_channel_websock_handshake_send_res_err(QIOChannelWebsock *ioc,
4a2fec
+                                                       const char *resdata)
4a2fec
+{
4a2fec
+    char *date = qio_channel_websock_date_str();
4a2fec
+    qio_channel_websock_handshake_send_res(ioc, resdata, date);
4a2fec
+    g_free(date);
4a2fec
+}
4a2fec
+
4a2fec
 static size_t
4a2fec
-qio_channel_websock_extract_headers(char *buffer,
4a2fec
+qio_channel_websock_extract_headers(QIOChannelWebsock *ioc,
4a2fec
+                                    char *buffer,
4a2fec
                                     QIOChannelWebsockHTTPHeader *hdrs,
4a2fec
                                     size_t nhdrsalloc,
4a2fec
                                     Error **errp)
4a2fec
@@ -145,7 +212,7 @@ qio_channel_websock_extract_headers(char *buffer,
4a2fec
     nl = strstr(buffer, QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
4a2fec
     if (!nl) {
4a2fec
         error_setg(errp, "Missing HTTP header delimiter");
4a2fec
-        return 0;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
     *nl = '\0';
4a2fec
 
4a2fec
@@ -158,18 +225,20 @@ qio_channel_websock_extract_headers(char *buffer,
4a2fec
 
4a2fec
     if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_METHOD)) {
4a2fec
         error_setg(errp, "Unsupported HTTP method %s", buffer);
4a2fec
-        return 0;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     buffer = tmp + 1;
4a2fec
     tmp = strchr(buffer, ' ');
4a2fec
     if (!tmp) {
4a2fec
         error_setg(errp, "Missing HTTP version delimiter");
4a2fec
-        return 0;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
     *tmp = '\0';
4a2fec
 
4a2fec
     if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_PATH)) {
4a2fec
+        qio_channel_websock_handshake_send_res_err(
4a2fec
+            ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_NOT_FOUND);
4a2fec
         error_setg(errp, "Unexpected HTTP path %s", buffer);
4a2fec
         return 0;
4a2fec
     }
4a2fec
@@ -178,7 +247,7 @@ qio_channel_websock_extract_headers(char *buffer,
4a2fec
 
4a2fec
     if (!g_str_equal(buffer, QIO_CHANNEL_WEBSOCK_HTTP_VERSION)) {
4a2fec
         error_setg(errp, "Unsupported HTTP version %s", buffer);
4a2fec
-        return 0;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     buffer = nl + strlen(QIO_CHANNEL_WEBSOCK_HANDSHAKE_DELIM);
4a2fec
@@ -203,7 +272,7 @@ qio_channel_websock_extract_headers(char *buffer,
4a2fec
         sep = strchr(buffer, ':');
4a2fec
         if (!sep) {
4a2fec
             error_setg(errp, "Malformed HTTP header");
4a2fec
-            return 0;
4a2fec
+            goto bad_request;
4a2fec
         }
4a2fec
         *sep = '\0';
4a2fec
         sep++;
4a2fec
@@ -213,7 +282,7 @@ qio_channel_websock_extract_headers(char *buffer,
4a2fec
 
4a2fec
         if (nhdrs >= nhdrsalloc) {
4a2fec
             error_setg(errp, "Too many HTTP headers");
4a2fec
-            return 0;
4a2fec
+            goto bad_request;
4a2fec
         }
4a2fec
 
4a2fec
         hdr = &hdrs[nhdrs++];
4a2fec
@@ -231,6 +300,11 @@ qio_channel_websock_extract_headers(char *buffer,
4a2fec
     } while (nl != NULL);
4a2fec
 
4a2fec
     return nhdrs;
4a2fec
+
4a2fec
+ bad_request:
4a2fec
+    qio_channel_websock_handshake_send_res_err(
4a2fec
+        ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
4a2fec
+    return 0;
4a2fec
 }
4a2fec
 
4a2fec
 static const char *
4a2fec
@@ -250,14 +324,14 @@ qio_channel_websock_find_header(QIOChannelWebsockHTTPHeader *hdrs,
4a2fec
 }
4a2fec
 
4a2fec
 
4a2fec
-static int qio_channel_websock_handshake_send_response(QIOChannelWebsock *ioc,
4a2fec
-                                                       const char *key,
4a2fec
-                                                       Error **errp)
4a2fec
+static void qio_channel_websock_handshake_send_res_ok(QIOChannelWebsock *ioc,
4a2fec
+                                                      const char *key,
4a2fec
+                                                      Error **errp)
4a2fec
 {
4a2fec
     char combined_key[QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN +
4a2fec
                       QIO_CHANNEL_WEBSOCK_GUID_LEN + 1];
4a2fec
-    char *accept = NULL, *response = NULL;
4a2fec
-    size_t responselen;
4a2fec
+    char *accept = NULL;
4a2fec
+    char *date = qio_channel_websock_date_str();
4a2fec
 
4a2fec
     g_strlcpy(combined_key, key, QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN + 1);
4a2fec
     g_strlcat(combined_key, QIO_CHANNEL_WEBSOCK_GUID,
4a2fec
@@ -271,105 +345,108 @@ static int qio_channel_websock_handshake_send_response(QIOChannelWebsock *ioc,
4a2fec
                             QIO_CHANNEL_WEBSOCK_GUID_LEN,
4a2fec
                             &accept,
4a2fec
                             errp) < 0) {
4a2fec
-        return -1;
4a2fec
+        qio_channel_websock_handshake_send_res_err(
4a2fec
+            ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_SERVER_ERR);
4a2fec
+        return;
4a2fec
     }
4a2fec
 
4a2fec
-    response = g_strdup_printf(QIO_CHANNEL_WEBSOCK_HANDSHAKE_RESPONSE, accept);
4a2fec
-    responselen = strlen(response);
4a2fec
-    buffer_reserve(&ioc->encoutput, responselen);
4a2fec
-    buffer_append(&ioc->encoutput, response, responselen);
4a2fec
+    qio_channel_websock_handshake_send_res(
4a2fec
+        ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_OK, date, accept);
4a2fec
 
4a2fec
+    g_free(date);
4a2fec
     g_free(accept);
4a2fec
-    g_free(response);
4a2fec
-
4a2fec
-    return 0;
4a2fec
 }
4a2fec
 
4a2fec
-static int qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
4a2fec
-                                                 char *buffer,
4a2fec
-                                                 Error **errp)
4a2fec
+static void qio_channel_websock_handshake_process(QIOChannelWebsock *ioc,
4a2fec
+                                                  char *buffer,
4a2fec
+                                                  Error **errp)
4a2fec
 {
4a2fec
     QIOChannelWebsockHTTPHeader hdrs[32];
4a2fec
     size_t nhdrs = G_N_ELEMENTS(hdrs);
4a2fec
     const char *protocols = NULL, *version = NULL, *key = NULL,
4a2fec
         *host = NULL, *connection = NULL, *upgrade = NULL;
4a2fec
 
4a2fec
-    nhdrs = qio_channel_websock_extract_headers(buffer, hdrs, nhdrs, errp);
4a2fec
+    nhdrs = qio_channel_websock_extract_headers(ioc, buffer, hdrs, nhdrs, errp);
4a2fec
     if (!nhdrs) {
4a2fec
-        return -1;
4a2fec
+        return;
4a2fec
     }
4a2fec
 
4a2fec
     protocols = qio_channel_websock_find_header(
4a2fec
         hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_PROTOCOL);
4a2fec
     if (!protocols) {
4a2fec
         error_setg(errp, "Missing websocket protocol header data");
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     version = qio_channel_websock_find_header(
4a2fec
         hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_VERSION);
4a2fec
     if (!version) {
4a2fec
         error_setg(errp, "Missing websocket version header data");
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     key = qio_channel_websock_find_header(
4a2fec
         hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_KEY);
4a2fec
     if (!key) {
4a2fec
         error_setg(errp, "Missing websocket key header data");
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     host = qio_channel_websock_find_header(
4a2fec
         hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_HOST);
4a2fec
     if (!host) {
4a2fec
         error_setg(errp, "Missing websocket host header data");
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     connection = qio_channel_websock_find_header(
4a2fec
         hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_CONNECTION);
4a2fec
     if (!connection) {
4a2fec
         error_setg(errp, "Missing websocket connection header data");
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     upgrade = qio_channel_websock_find_header(
4a2fec
         hdrs, nhdrs, QIO_CHANNEL_WEBSOCK_HEADER_UPGRADE);
4a2fec
     if (!upgrade) {
4a2fec
         error_setg(errp, "Missing websocket upgrade header data");
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     if (!g_strrstr(protocols, QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY)) {
4a2fec
         error_setg(errp, "No '%s' protocol is supported by client '%s'",
4a2fec
                    QIO_CHANNEL_WEBSOCK_PROTOCOL_BINARY, protocols);
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     if (!g_str_equal(version, QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION)) {
4a2fec
         error_setg(errp, "Version '%s' is not supported by client '%s'",
4a2fec
                    QIO_CHANNEL_WEBSOCK_SUPPORTED_VERSION, version);
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     if (strlen(key) != QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN) {
4a2fec
         error_setg(errp, "Key length '%zu' was not as expected '%d'",
4a2fec
                    strlen(key), QIO_CHANNEL_WEBSOCK_CLIENT_KEY_LEN);
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     if (!g_strrstr(connection, QIO_CHANNEL_WEBSOCK_CONNECTION_UPGRADE)) {
4a2fec
         error_setg(errp, "No connection upgrade requested '%s'", connection);
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
     if (!g_str_equal(upgrade, QIO_CHANNEL_WEBSOCK_UPGRADE_WEBSOCKET)) {
4a2fec
         error_setg(errp, "Incorrect upgrade method '%s'", upgrade);
4a2fec
-        return -1;
4a2fec
+        goto bad_request;
4a2fec
     }
4a2fec
 
4a2fec
-    return qio_channel_websock_handshake_send_response(ioc, key, errp);
4a2fec
+    qio_channel_websock_handshake_send_res_ok(ioc, key, errp);
4a2fec
+    return;
4a2fec
+
4a2fec
+ bad_request:
4a2fec
+    qio_channel_websock_handshake_send_res_err(
4a2fec
+        ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_BAD_REQUEST);
4a2fec
 }
4a2fec
 
4a2fec
 static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
4a2fec
@@ -393,20 +470,20 @@ static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
4a2fec
                                  QIO_CHANNEL_WEBSOCK_HANDSHAKE_END);
4a2fec
     if (!handshake_end) {
4a2fec
         if (ioc->encinput.offset >= 4096) {
4a2fec
+            qio_channel_websock_handshake_send_res_err(
4a2fec
+                ioc, QIO_CHANNEL_WEBSOCK_HANDSHAKE_RES_TOO_LARGE);
4a2fec
             error_setg(errp,
4a2fec
                        "End of headers not found in first 4096 bytes");
4a2fec
-            return -1;
4a2fec
+            return 1;
4a2fec
         } else {
4a2fec
             return 0;
4a2fec
         }
4a2fec
     }
4a2fec
     *handshake_end = '\0';
4a2fec
 
4a2fec
-    if (qio_channel_websock_handshake_process(ioc,
4a2fec
-                                              (char *)ioc->encinput.buffer,
4a2fec
-                                              errp) < 0) {
4a2fec
-        return -1;
4a2fec
-    }
4a2fec
+    qio_channel_websock_handshake_process(ioc,
4a2fec
+                                          (char *)ioc->encinput.buffer,
4a2fec
+                                          errp);
4a2fec
 
4a2fec
     buffer_advance(&ioc->encinput,
4a2fec
                    handshake_end - (char *)ioc->encinput.buffer +
4a2fec
@@ -438,8 +515,15 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
4a2fec
 
4a2fec
     buffer_advance(&wioc->encoutput, ret);
4a2fec
     if (wioc->encoutput.offset == 0) {
4a2fec
-        trace_qio_channel_websock_handshake_complete(ioc);
4a2fec
-        qio_task_complete(task);
4a2fec
+        if (wioc->io_err) {
4a2fec
+            trace_qio_channel_websock_handshake_fail(ioc);
4a2fec
+            qio_task_set_error(task, wioc->io_err);
4a2fec
+            wioc->io_err = NULL;
4a2fec
+            qio_task_complete(task);
4a2fec
+        } else {
4a2fec
+            trace_qio_channel_websock_handshake_complete(ioc);
4a2fec
+            qio_task_complete(task);
4a2fec
+        }
4a2fec
         return FALSE;
4a2fec
     }
4a2fec
     trace_qio_channel_websock_handshake_pending(ioc, G_IO_OUT);
4a2fec
@@ -458,6 +542,11 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
4a2fec
 
4a2fec
     ret = qio_channel_websock_handshake_read(wioc, &err;;
4a2fec
     if (ret < 0) {
4a2fec
+        /*
4a2fec
+         * We only take this path on a fatal I/O error reading from
4a2fec
+         * client connection, as most of the time we have an
4a2fec
+         * HTTP 4xx err response to send instead
4a2fec
+         */
4a2fec
         trace_qio_channel_websock_handshake_fail(ioc);
4a2fec
         qio_task_set_error(task, err);
4a2fec
         qio_task_complete(task);
4a2fec
@@ -469,6 +558,10 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
4a2fec
         return TRUE;
4a2fec
     }
4a2fec
 
4a2fec
+    if (err) {
4a2fec
+        error_propagate(&wioc->io_err, err);
4a2fec
+    }
4a2fec
+
4a2fec
     trace_qio_channel_websock_handshake_reply(ioc);
4a2fec
     qio_channel_add_watch(
4a2fec
         wioc->master,
4a2fec
-- 
4a2fec
1.8.3.1
4a2fec