[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

Re: [Libvir] PATCH 2/4: Xen driver support for serial/paralle



This patch updates Xen HVM to allow use of serial &parallel ports, though
XenD limits you to  single one of each even though QEMU supports many.
It also updates the <console> tag to support new syntax extensions.

 xend_internal.c |  256 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 xend_internal.h |    9 +
 xm_internal.c   |   55 +++++++++---
 xml.c           |  217 +++++++++++++++++++++++++++++++++++++++++++++--
 xml.h           |    4 
 5 files changed, 519 insertions(+), 22 deletions(-)

Dan.

Index: src/xend_internal.c
===================================================================
RCS file: /data/cvs/libvirt/src/xend_internal.c,v
retrieving revision 1.180
diff -u -p -r1.180 xend_internal.c
--- src/xend_internal.c	10 Apr 2008 16:54:54 -0000	1.180
+++ src/xend_internal.c	18 Apr 2008 20:01:37 -0000
@@ -1376,6 +1376,233 @@ xend_parse_sexp_desc_os(virConnectPtr xe
     return(0);
 }
 
+
+int
+xend_parse_sexp_desc_char(virConnectPtr conn,
+                          virBufferPtr buf,
+                          const char *devtype,
+                          int portNum,
+                          const char *value,
+                          const char *tty)
+{
+    const char *type;
+    int telnet = 0;
+    char *bindPort = NULL;
+    char *bindHost = NULL;
+    char *connectPort = NULL;
+    char *connectHost = NULL;
+    char *path = NULL;
+
+    if (value[0] == '/') {
+        type = "dev";
+    } else if (STREQLEN(value, "null", 4)) {
+        type = "null";
+        value = NULL;
+    } else if (STREQLEN(value, "vc", 2)) {
+        type = "vc";
+        value = NULL;
+    } else if (STREQLEN(value, "pty", 3)) {
+        type = "pty";
+        value = NULL;
+    } else if (STREQLEN(value, "stdio", 5)) {
+        type = "stdio";
+        value = NULL;
+    } else if (STREQLEN(value, "file:", 5)) {
+        type = "file";
+        value += 5;
+    } else if (STREQLEN(value, "pipe:", 5)) {
+        type = "pipe";
+        value += 5;
+    } else if (STREQLEN(value, "tcp:", 4)) {
+        type = "tcp";
+        value += 4;
+    } else if (STREQLEN(value, "telnet:", 4)) {
+        type = "tcp";
+        value += 7;
+        telnet = 1;
+    } else if (STREQLEN(value, "udp:", 4)) {
+        type = "udp";
+        value += 4;
+    } else if (STREQLEN(value, "unix:", 5)) {
+        type = "unix";
+        value += 5;
+    } else {
+        virXendError(conn, VIR_ERR_INTERNAL_ERROR,
+                     _("Unknown char device type"));
+        return -1;
+    }
+
+    /* Compat with legacy  <console tty='/dev/pts/5'/> syntax */
+    if (STREQ(devtype, "console") &&
+        STREQ(type, "pty") &&
+        tty != NULL) {
+        if (virBufferVSprintf(buf, "    <%s type='%s' tty='%s'>\n",
+                              devtype, type, tty) < 0)
+            goto no_memory;
+    } else {
+        if (virBufferVSprintf(buf, "    <%s type='%s'>\n",
+                              devtype, type) < 0)
+            goto no_memory;
+    }
+
+    if (STREQ(type, "null") ||
+        STREQ(type, "vc") ||
+        STREQ(type, "stdio")) {
+        /* no source needed */
+    } else if (STREQ(type, "pty")) {
+        if (tty &&
+            virBufferVSprintf(buf, "      <source path='%s'/>\n",
+                              tty) < 0)
+            goto no_memory;
+    } else if (STREQ(type, "file") ||
+               STREQ(type, "pipe")) {
+        if (virBufferVSprintf(buf, "      <source path='%s'/>\n",
+                              value) < 0)
+            goto no_memory;
+    } else if (STREQ(type, "tcp")) {
+        const char *offset = strchr(value, ':');
+        const char *offset2;
+        const char *mode, *wire;
+
+        if (offset == NULL) {
+            virXendError(conn, VIR_ERR_INTERNAL_ERROR,
+                         _("malformed char device string"));
+            goto error;
+        }
+
+        if (offset != value &&
+            (bindHost = strndup(value, offset - value)) == NULL)
+            goto no_memory;
+
+        offset2 = strchr(offset, ',');
+        if (offset2 == NULL)
+            bindPort = strdup(offset+1);
+        else
+            bindPort = strndup(offset+1, offset2-(offset+1));
+        if (bindPort == NULL)
+            goto no_memory;
+
+        if (offset2 && strstr(offset2, ",listen"))
+            mode = "bind";
+        else
+            mode = "connect";
+        wire = telnet ? "telnet":"raw";
+
+        if (bindHost) {
+            if (virBufferVSprintf(buf,
+                                  "      <source mode='%s' host='%s' service='%s' wiremode='%s'/>\n",
+                                  mode, bindHost, bindPort, wire) < 0)
+                goto no_memory;
+        } else {
+            if (virBufferVSprintf(buf,
+                                  "      <source mode='%s' service='%s' wiremode='%s'/>\n",
+                                  mode, bindPort, wire) < 0)
+                goto no_memory;
+        }
+    } else if (STREQ(type, "udp")) {
+        const char *offset = strchr(value, ':');
+        const char *offset2, *offset3;
+
+        if (offset == NULL) {
+            virXendError(conn, VIR_ERR_INTERNAL_ERROR,
+                         _("malformed char device string"));
+            goto error;
+        }
+
+        if (offset != value &&
+            (connectHost = strndup(value, offset - value)) == NULL)
+            goto no_memory;
+
+        offset2 = strchr(offset, '@');
+        if (offset2 != NULL) {
+            if ((connectPort = strndup(offset + 1, offset2-(offset+1))) == NULL)
+                goto no_memory;
+
+            offset3 = strchr(offset2, ':');
+            if (offset3 == NULL) {
+                virXendError(conn, VIR_ERR_INTERNAL_ERROR,
+                             _("malformed char device string"));
+                goto error;
+            }
+
+            if (offset3 > (offset2 + 1) &&
+                (bindHost = strndup(offset2 + 1, offset3 - (offset2+1))) == NULL)
+                goto no_memory;
+
+            if ((bindPort = strdup(offset3 + 1)) == NULL)
+                goto no_memory;
+        } else {
+            if ((connectPort = strdup(offset + 1)) == NULL)
+                goto no_memory;
+        }
+
+        if (connectPort) {
+            if (connectHost) {
+                if (virBufferVSprintf(buf,
+                                      "      <source mode='connect' host='%s' service='%s'/>\n",
+                                      connectHost, connectPort) < 0)
+                    goto no_memory;
+            } else {
+                if (virBufferVSprintf(buf,
+                                      "      <source mode='connect' service='%s'/>\n",
+                                      connectPort) < 0)
+                    goto no_memory;
+            }
+        }
+        if (bindPort) {
+            if (bindHost) {
+                if (virBufferVSprintf(buf,
+                                      "      <source mode='bind' host='%s' service='%s'/>\n",
+                                      bindHost, bindPort) < 0)
+                    goto no_memory;
+            } else {
+                if (virBufferVSprintf(buf,
+                                      "      <source mode='bind' service='%s'/>\n",
+                                      bindPort) < 0)
+                    goto no_memory;
+            }
+        }
+
+    } else if (STREQ(type, "unix")) {
+            const char *offset = strchr(value, ',');
+        int dolisten = 0;
+        if (offset)
+            path = strndup(value, (offset - value));
+        else
+            path = strdup(value);
+        if (path == NULL)
+            goto no_memory;
+
+        if (strstr(offset, ",listen") != NULL)
+            dolisten = 1;
+
+        if (virBufferVSprintf(buf, "      <source mode='%s' path='%s'/>\n",
+                              dolisten ? "bind" : "connect", path) < 0) {
+            free(path);
+            goto no_memory;
+        }
+
+        free(path);
+    }
+
+    if (virBufferVSprintf(buf, "      <target port='%d'/>\n",
+                          portNum) < 0)
+        goto no_memory;
+
+    if (virBufferVSprintf(buf, "    </%s>\n",
+                          devtype) < 0)
+        goto no_memory;
+
+    return 0;
+
+no_memory:
+    virXendError(conn, VIR_ERR_NO_MEMORY,
+                 _("no memory for char device config"));
+
+error:
+    return -1;
+}
+
 /**
  * xend_parse_sexp_desc:
  * @conn: the connection associated with the XML
@@ -1828,10 +2055,33 @@ xend_parse_sexp_desc(virConnectPtr conn,
     }
 
     tty = xenStoreDomainGetConsolePath(conn, domid);
-    if (tty) {
-        virBufferVSprintf(&buf, "    <console tty='%s'/>\n", tty);
-        free(tty);
+    if (hvm) {
+        tmp = sexpr_node(root, "domain/image/hvm/serial");
+        if (tmp && STRNEQ(tmp, "none")) {
+            if (xend_parse_sexp_desc_char(conn, &buf, "serial", 0, tmp, tty) < 0)
+                goto error;
+            /* Add back-compat <console/> tag for primary console */
+            if (xend_parse_sexp_desc_char(conn, &buf, "console", 0, tmp, tty) < 0)
+                goto error;
+        }
+        tmp = sexpr_node(root, "domain/image/hvm/parallel");
+        if (tmp && STRNEQ(tmp, "none")) {
+            /* XXX does XenD stuff parallel port tty info into xenstore somewhere ? */
+            if (xend_parse_sexp_desc_char(conn, &buf, "parallel", 0, tmp, NULL) < 0)
+                goto error;
+        }
+    } else {
+        /* Paravirt always has a console */
+        if (tty) {
+            virBufferVSprintf(&buf, "    <console type='pty' tty='%s'>\n", tty);
+            virBufferVSprintf(&buf, "      <source path='%s'/>\n", tty);
+        } else {
+            virBufferAddLit(&buf, "    <console type='pty'>\n");
+        }
+        virBufferAddLit(&buf, "      <target port='0'/>\n");
+        virBufferAddLit(&buf, "    </console>\n");
     }
+    free(tty);
 
     virBufferAddLit(&buf, "  </devices>\n");
     virBufferAddLit(&buf, "</domain>\n");
Index: src/xend_internal.h
===================================================================
RCS file: /data/cvs/libvirt/src/xend_internal.h,v
retrieving revision 1.40
diff -u -p -r1.40 xend_internal.h
--- src/xend_internal.h	10 Apr 2008 16:54:54 -0000	1.40
+++ src/xend_internal.h	18 Apr 2008 20:01:37 -0000
@@ -20,12 +20,12 @@
 
 #include "libvirt/libvirt.h"
 #include "capabilities.h"
+#include "buf.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-
 /**
  * \brief Setup the connection to a xend instance via TCP
  * \param host The host name to connect to
@@ -180,6 +180,13 @@ char *xenDaemonDomainDumpXMLByName(virCo
  */
     int xend_log(virConnectPtr xend, char *buffer, size_t n_buffer);
 
+    int xend_parse_sexp_desc_char(virConnectPtr conn,
+                                  virBufferPtr buf,
+                                  const char *devtype,
+                                  int portNum,
+                                  const char *value,
+                                  const char *tty);
+
   char *xend_parse_domain_sexp(virConnectPtr conn,  char *root, int xendConfigVersion);
 
 /* refactored ones */
Index: src/xm_internal.c
===================================================================
RCS file: /data/cvs/libvirt/src/xm_internal.c,v
retrieving revision 1.70
diff -u -p -r1.70 xm_internal.c
--- src/xm_internal.c	10 Apr 2008 16:54:54 -0000	1.70
+++ src/xm_internal.c	18 Apr 2008 20:01:37 -0000
@@ -1025,11 +1025,22 @@ char *xenXMDomainFormatXML(virConnectPtr
     }
 
     if (hvm) {
-        if (xenXMConfigGetString(conf, "serial", &str) == 0 && !strcmp(str, "pty")) {
-            virBufferAddLit(buf, "    <console/>\n");
+        if (xenXMConfigGetString(conf, "parallel", &str) == 0) {
+            if (STRNEQ(str, "none"))
+                xend_parse_sexp_desc_char(conn, buf, "parallel", 0, str, NULL);
+        }
+        if (xenXMConfigGetString(conf, "serial", &str) == 0) {
+            if (STRNEQ(str, "none")) {
+                xend_parse_sexp_desc_char(conn, buf, "serial", 0, str, NULL);
+                /* Add back-compat console tag for primary console */
+                xend_parse_sexp_desc_char(conn, buf, "console", 0, str, NULL);
+            }
         }
-    } else { /* Paravirt implicitly always has a console */
-        virBufferAddLit(buf, "    <console/>\n");
+    } else {
+        /* Paravirt implicitly always has a single console */
+        virBufferAddLit(buf, "    <console type='pty'>\n");
+        virBufferAddLit(buf, "      <target port='0'/>\n");
+        virBufferAddLit(buf, "    </console>\n");
     }
 
     virBufferAddLit(buf, "  </devices>\n");
@@ -2267,14 +2278,38 @@ virConfPtr xenXMParseXMLToConfig(virConn
     obj = NULL;
 
     if (hvm) {
-        obj = xmlXPathEval(BAD_CAST "count(/domain/devices/console) > 0", ctxt);
-        if ((obj != NULL) && (obj->type == XPATH_BOOLEAN) &&
-            (obj->boolval)) {
-            if (xenXMConfigSetString(conf, "serial", "pty") < 0)
+        xmlNodePtr cur;
+        cur = virXPathNode("/domain/devices/parallel[1]", ctxt);
+        if (cur != NULL) {
+            char scratch[PATH_MAX];
+
+            if (virDomainParseXMLOSDescHVMChar(conn, scratch, sizeof(scratch), cur) < 0) {
+                goto error;
+            }
+
+            if (xenXMConfigSetString(conf, "parallel", scratch) < 0)
+                goto error;
+        } else {
+            if (xenXMConfigSetString(conf, "parallel", "none") < 0)
                 goto error;
         }
-        xmlXPathFreeObject(obj);
-        obj = NULL;
+
+        cur = virXPathNode("/domain/devices/serial[1]", ctxt);
+        if (cur != NULL) {
+            char scratch[PATH_MAX];
+            if (virDomainParseXMLOSDescHVMChar(conn, scratch, sizeof(scratch), cur) < 0)
+                goto error;
+            if (xenXMConfigSetString(conf, "serial", scratch) < 0)
+                goto error;
+        } else {
+            if (virXPathBoolean("count(/domain/devices/console) > 0", ctxt)) {
+                if (xenXMConfigSetString(conf, "serial", "pty") < 0)
+                    goto error;
+            } else {
+                if (xenXMConfigSetString(conf, "serial", "none") < 0)
+                    goto error;
+            }
+        }
     }
 
     xmlFreeDoc(doc);
Index: src/xml.c
===================================================================
RCS file: /data/cvs/libvirt/src/xml.c,v
retrieving revision 1.117
diff -u -p -r1.117 xml.c
--- src/xml.c	10 Apr 2008 16:54:54 -0000	1.117
+++ src/xml.c	18 Apr 2008 20:01:37 -0000
@@ -673,6 +673,178 @@ virDomainParseXMLGraphicsDescVFB(virConn
 }
 
 
+int
+virDomainParseXMLOSDescHVMChar(virConnectPtr conn,
+                               char *buf,
+                               size_t buflen,
+                               xmlNodePtr node)
+{
+    xmlChar *type = NULL;
+    xmlChar *path = NULL;
+    xmlChar *bindHost = NULL;
+    xmlChar *bindService = NULL;
+    xmlChar *connectHost = NULL;
+    xmlChar *connectService = NULL;
+    xmlChar *mode = NULL;
+    xmlChar *wiremode = NULL;
+    xmlNodePtr cur;
+
+    type = xmlGetProp(node, BAD_CAST "type");
+
+    if (type != NULL) {
+        cur = node->children;
+        while (cur != NULL) {
+            if (cur->type == XML_ELEMENT_NODE) {
+                if (xmlStrEqual(cur->name, BAD_CAST "source")) {
+                    if (mode == NULL)
+                        mode = xmlGetProp(cur, BAD_CAST "mode");
+
+                    if (STREQ((const char *)type, "dev") ||
+                        STREQ((const char *)type, "file") ||
+                        STREQ((const char *)type, "pipe") ||
+                        STREQ((const char *)type, "unix")) {
+                        if (path == NULL)
+                            path = xmlGetProp(cur, BAD_CAST "path");
+
+                    } else if (STREQ((const char *)type, "udp") ||
+                               STREQ((const char *)type, "tcp")) {
+                        if (mode == NULL ||
+                            STREQ((const char *)mode, "connect")) {
+
+                            if (connectHost == NULL)
+                                connectHost = xmlGetProp(cur, BAD_CAST "host");
+                            if (connectService == NULL)
+                                connectService = xmlGetProp(cur, BAD_CAST "service");
+                        } else {
+                            if (bindHost == NULL)
+                                bindHost = xmlGetProp(cur, BAD_CAST "host");
+                            if (bindService == NULL)
+                                bindService = xmlGetProp(cur, BAD_CAST "service");
+                        }
+
+                        if (STREQ((const char*)type, "tcp"))
+                            wiremode = xmlGetProp(cur, BAD_CAST "wiremode");
+
+                        if (STREQ((const char*)type, "udp")) {
+                            xmlFree(mode);
+                            mode = NULL;
+                        }
+                    }
+                }
+            }
+            cur = cur->next;
+        }
+    }
+
+    if (type == NULL ||
+        STREQ((const char *)type, "pty")) {
+        strncpy(buf, "pty", buflen);
+    } else if (STREQ((const char *)type, "null") ||
+               STREQ((const char *)type, "stdio") ||
+               STREQ((const char *)type, "vc")) {
+        snprintf(buf, buflen, "%s", type);
+    } else if (STREQ((const char *)type, "file") ||
+               STREQ((const char *)type, "dev") ||
+               STREQ((const char *)type, "pipe")) {
+        if (path == NULL) {
+            virXMLError(conn, VIR_ERR_XML_ERROR,
+                        _("Missing source path attribute for char device"), 0);
+            goto cleanup;
+        }
+
+        if (STREQ((const char *)type, "dev"))
+            strncpy(buf, (const char *)path, buflen);
+        else
+            snprintf(buf, buflen, "%s:%s", type, path);
+    } else if (STREQ((const char *)type, "tcp")) {
+        int telnet = 0;
+        if (wiremode != NULL &&
+            STREQ((const char *)wiremode, "telnet"))
+            telnet = 1;
+
+        if (mode == NULL ||
+            STREQ((const char *)mode, "connect")) {
+            if (connectHost == NULL) {
+                virXMLError(conn, VIR_ERR_INTERNAL_ERROR,
+                            _("Missing source host attribute for char device"), 0);
+                goto cleanup;
+            }
+            if (connectService == NULL) {
+                virXMLError(conn, VIR_ERR_INTERNAL_ERROR,
+                            _("Missing source service attribute for char device"), 0);
+                goto cleanup;
+            }
+
+            snprintf(buf, buflen, "%s:%s:%s",
+                     (telnet ? "telnet" : "tcp"),
+                     connectHost, connectService);
+        } else {
+            if (bindHost == NULL) {
+                virXMLError(conn, VIR_ERR_INTERNAL_ERROR,
+                            _("Missing source host attribute for char device"), 0);
+                goto cleanup;
+            }
+            if (bindService == NULL) {
+                virXMLError(conn, VIR_ERR_INTERNAL_ERROR,
+                            _("Missing source service attribute for char device"), 0);
+                goto cleanup;
+            }
+
+            snprintf(buf, buflen, "%s:%s:%s,listen",
+                     (telnet ? "telnet" : "tcp"),
+                     bindHost, bindService);
+        }
+    } else if (STREQ((const char *)type, "udp")) {
+        if (connectService == NULL) {
+            virXMLError(conn, VIR_ERR_XML_ERROR,
+                        _("Missing source service attribute for char device"), 0);
+            goto cleanup;
+        }
+
+        snprintf(buf, buflen, "udp:%s:%s %s:%s",
+                 connectHost ? (const char *)connectHost : "",
+                 connectService,
+                 bindHost ? (const char *)bindHost : "",
+                 bindService ? (const char *)bindService : "");
+    } else if (STREQ((const char *)type, "unix")) {
+        if (path == NULL) {
+            virXMLError(conn, VIR_ERR_XML_ERROR,
+                        _("Missing source path attribute for char device"), 0);
+            goto cleanup;
+        }
+
+        if (mode == NULL ||
+            STREQ((const char *)mode, "connect")) {
+            snprintf(buf, buflen, "%s:%s", type, path);
+        } else {
+            snprintf(buf, buflen, "%s:%s,listen", type, path);
+        }
+    }
+    buf[buflen-1] = '\0';
+
+    xmlFree(mode);
+    xmlFree(wiremode);
+    xmlFree(type);
+    xmlFree(bindHost);
+    xmlFree(bindService);
+    xmlFree(connectHost);
+    xmlFree(connectService);
+    xmlFree(path);
+
+    return 0;
+
+cleanup:
+    xmlFree(mode);
+    xmlFree(wiremode);
+    xmlFree(type);
+    xmlFree(bindHost);
+    xmlFree(bindService);
+    xmlFree(connectHost);
+    xmlFree(connectService);
+    xmlFree(path);
+    return -1;
+}
+
 /**
  * virDomainParseXMLOSDescHVM:
  * @conn: pointer to the hypervisor connection
@@ -877,24 +1049,53 @@ virDomainParseXMLOSDescHVM(virConnectPtr
         nodes = NULL;
     }
 
-
-    res = virXPathBoolean("count(domain/devices/console) > 0", ctxt);
-    if (res < 0) {
-        virXMLError(conn, VIR_ERR_XML_ERROR, NULL, 0);
-        goto error;
+    cur = virXPathNode("/domain/devices/parallel[1]", ctxt);
+    if (cur != NULL) {
+        char scratch[PATH_MAX];
+        if (virDomainParseXMLOSDescHVMChar(conn, scratch, sizeof(scratch), cur) < 0)
+            goto error;
+        if (virBufferVSprintf(buf, "(parallel %s)", scratch) < 0)
+            goto no_memory;
+    } else {
+        if (virBufferAddLit(buf, "(parallel none)") < 0)
+            goto no_memory;
     }
-    if (res) {
-        virBufferAddLit(buf, "(serial pty)");
+
+    cur = virXPathNode("/domain/devices/serial[1]", ctxt);
+    if (cur != NULL) {
+        char scratch[PATH_MAX];
+        if (virDomainParseXMLOSDescHVMChar(conn, scratch, sizeof(scratch), cur) < 0)
+            goto error;
+        if (virBufferVSprintf(buf, "(serial %s)", scratch) < 0)
+            goto no_memory;
+    } else {
+        res = virXPathBoolean("count(domain/devices/console) > 0", ctxt);
+        if (res < 0) {
+            virXMLError(conn, VIR_ERR_XML_ERROR, NULL, 0);
+            goto error;
+        }
+        if (res) {
+            if (virBufferAddLit(buf, "(serial pty)") < 0)
+                goto no_memory;
+        } else {
+            if (virBufferAddLit(buf, "(serial none)") < 0)
+                goto no_memory;
+        }
     }
 
     str = virXPathString("string(/domain/clock/@offset)", ctxt);
     if (str != NULL && !strcmp(str, "localtime")) {
-        virBufferAddLit(buf, "(localtime 1)");
+        if (virBufferAddLit(buf, "(localtime 1)") < 0)
+            goto no_memory;
     }
     free(str);
 
     return (0);
 
+no_memory:
+    virXMLError(conn, VIR_ERR_XML_ERROR,
+                "cannot allocate memory for buffer", 0);
+
   error:
     free(nodes);
     return (-1);
Index: src/xml.h
===================================================================
RCS file: /data/cvs/libvirt/src/xml.h,v
retrieving revision 1.23
diff -u -p -r1.23 xml.h
--- src/xml.h	10 Apr 2008 16:54:54 -0000	1.23
+++ src/xml.h	18 Apr 2008 20:01:37 -0000
@@ -44,6 +44,10 @@ char *          virSaveCpuSet	(virConnec
 char *		virConvertCpuSet(virConnectPtr conn,
                                  const char *str,
                                  int maxcpu);
+int             virDomainParseXMLOSDescHVMChar(virConnectPtr conn,
+                                               char *buf,
+                                               size_t buflen,
+                                               xmlNodePtr node);
 char *		virDomainParseXMLDesc(virConnectPtr conn,
                                  const char *xmldesc,
                                  char **name,


-- 
|: Red Hat, Engineering, Boston   -o-   http://people.redhat.com/berrange/ :|
|: http://libvirt.org  -o-  http://virt-manager.org  -o-  http://ovirt.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: GnuPG: 7D3B9505  -o-  F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]