This provides the QEMU driver implementation which is able to convert
from QEMU argv into domain XML. This is alot of hard code, because we
have to parse and interpret arbitrary QEMU args and had no existing
code doing this. This is also actually the single most useful feature
of this patchset, and the original motivation. With this available, it
makes it very easy for people using QEMU to switch over to using libvirt
for management
domain_conf.h | 2
qemu_conf.c | 1183 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
qemu_conf.h | 8
qemu_driver.c | 26 +
vbox/vbox_tmpl.c | 4
5 files changed, 1217 insertions(+), 6 deletions(-)
Daniel
diff -r b052a7f425ac src/domain_conf.h
--- a/src/domain_conf.h Wed May 13 10:02:15 2009 -0400
+++ b/src/domain_conf.h Wed May 13 10:02:17 2009 -0400
@@ -253,7 +253,7 @@ enum virDomainSoundModel {
VIR_DOMAIN_SOUND_MODEL_SB16,
VIR_DOMAIN_SOUND_MODEL_ES1370,
VIR_DOMAIN_SOUND_MODEL_PCSPK,
- VIR_DOMAIN_SOUND_MODEL_ES97,
+ VIR_DOMAIN_SOUND_MODEL_AC97,
VIR_DOMAIN_SOUND_MODEL_LAST
};
diff -r b052a7f425ac src/qemu_conf.c
--- a/src/qemu_conf.c Wed May 13 10:02:15 2009 -0400
+++ b/src/qemu_conf.c Wed May 13 10:06:30 2009 -0400
@@ -1,5 +1,5 @@
/*
- * config.c: VM configuration management
+ * qemu_conf.c: QEMU configuration management
*
* Copyright (C) 2006, 2007, 2008, 2009 Red Hat, Inc.
* Copyright (C) 2006 Daniel P. Berrange
@@ -36,6 +36,7 @@
#include <arpa/inet.h>
#include <sys/utsname.h>
+#include "c-ctype.h"
#include "virterror_internal.h"
#include "qemu_conf.h"
#include "uuid.h"
@@ -1539,6 +1540,1186 @@ int qemudBuildCommandLine(virConnectPtr
}
+/*
+ * This method takes a string representing a QEMU command line ARGV set
+ * optionall prefixed by a list of environment variables. It then tries
+ * to split it up into a NULL terminated list of env & argv, splitting
+ * on space
+ */
+static int qemuStringToArgvEnv(const char *args,
+ const char ***retenv,
+ const char ***retargv)
+{
+ char **arglist = NULL;
+ int argcount = 0;
+ int envend;
+ int i;
+ const char *curr = args;
+ const char **progenv = NULL;
+ const char **progargv = NULL;
+
+ /* Iterate over string, splitting on sequences of ' ' */
+ while (curr && *curr != '\0') {
+ char *arg;
+ const char *next = strchr(curr, ' ');
+ if (!next)
+ next = strchr(curr, '\n');
+
+ if (next)
+ arg = strndup(curr, next-curr);
+ else
+ arg = strdup(curr);
+
+ if (!arg)
+ goto no_memory;
+
+ if (VIR_REALLOC_N(arglist, argcount+1) < 0) {
+ VIR_FREE(arg);
+ goto no_memory;
+ }
+
+ arglist[argcount++] = arg;
+
+ while (next && c_isspace(*next))
+ next++;
+
+ curr = next;
+ }
+
+ /* Iterate over list of args, finding first arg not containining
+ * and '=' character (eg, skip over env vars FOO=bar) */
+ for (envend = 0 ; envend < argcount && strchr(arglist[envend],
'=') ; envend++)
+ ; /* nada */
+
+ /* Copy the list of env vars */
+ if (envend > 0) {
+ if (VIR_REALLOC_N(progenv, envend+1) < 0)
+ goto no_memory;
+ for (i = 0 ; i < envend ; i++) {
+ progenv[i] = arglist[i];
+ }
+ progenv[i] = NULL;
+ }
+
+ /* Copy the list of argv */
+ if (VIR_REALLOC_N(progargv, argcount-envend + 1) < 0)
+ goto no_memory;
+ for (i = envend ; i < argcount ; i++)
+ progargv[i-envend] = arglist[i];
+ progargv[i-envend] = NULL;
+
+ VIR_FREE(arglist);
+
+ *retenv = progenv;
+ *retargv = progargv;
+
+ return 0;
+
+no_memory:
+ for (i = 0 ; progenv && progenv[i] ; i++)
+ VIR_FREE(progenv);
+ VIR_FREE(progenv);
+ for (i = 0 ; i < argcount ; i++)
+ VIR_FREE(arglist[i]);
+ VIR_FREE(arglist);
+ return -1;
+}
+
+
+/*
+ * Search for a named env variable, and return the value part
+ */
+static const char *qemuFindEnv(const char **progenv,
+ const char *name)
+{
+ int i;
+ int len = strlen(name);
+
+ for (i = 0 ; progenv[i] ; i++) {
+ if (STREQLEN(progenv[i], name, len) &&
+ progenv[i][len] == '=')
+ return progenv[i] + len + 1;
+ }
+ return NULL;
+}
+
+/*
+ * Takes a string containing a set of key=value,key=value,key...
+ * parameters and splits them up, returning two arrays with
+ * the individual keys and values
+ */
+static int
+qemuParseCommandLineKeywords(virConnectPtr conn,
+ const char *str,
+ char ***retkeywords,
+ char ***retvalues)
+{
+ int nkeywords = 0;
+ char **keywords = NULL;
+ char **values = NULL;
+ const char *start = str;
+ int i;
+
+ *retkeywords = NULL;
+ *retvalues = NULL;
+
+ while (start) {
+ const char *separator;
+ const char *endmark;
+ char *keyword;
+ char *value;
+
+ if (!(separator = strchr(start, '='))) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("malformed keyword arguments in '%s'"),
str);
+ goto error;
+ }
+ if (!(keyword = strndup(start, separator - start)))
+ goto no_memory;
+
+ separator++;
+ endmark = strchr(separator, ',');
+
+ value = endmark ?
+ strndup(separator, endmark - separator) :
+ strdup(separator);
+ if (!value) {
+ VIR_FREE(keyword);
+ goto no_memory;
+ }
+
+ if (VIR_REALLOC_N(keywords, nkeywords+1) < 0 ||
+ VIR_REALLOC_N(values, nkeywords+1) < 0) {
+ VIR_FREE(keyword);
+ VIR_FREE(value);
+ goto no_memory;
+ }
+
+ keywords[nkeywords] = keyword;
+ values[nkeywords] = value;
+ nkeywords++;
+
+ start = endmark ? endmark + 1 : NULL;
+ }
+
+ *retkeywords = keywords;
+ *retvalues = values;
+
+ return nkeywords;
+
+no_memory:
+ virReportOOMError(conn);
+error:
+ for (i = 0 ; i < nkeywords ; i++) {
+ VIR_FREE(keywords[i]);
+ VIR_FREE(values[i]);
+ }
+ VIR_FREE(keywords);
+ VIR_FREE(values);
+ return -1;
+}
+
+/*
+ * Tries to parse new style QEMU -drive args
+ *
+ * eg
+ * -drive file=/dev/HostVG/VirtData1,if=ide,index=1 -
+ */
+static virDomainDiskDefPtr
+qemuParseCommandLineDisk(virConnectPtr conn,
+ const char *val)
+{
+ virDomainDiskDefPtr def = NULL;
+ char **keywords;
+ char **values;
+ int nkeywords;
+ int i;
+ int idx = -1;
+
+ if ((nkeywords = qemuParseCommandLineKeywords(conn, val,
+ &keywords,
+ &values)) < 0)
+ return NULL;
+
+ if (VIR_ALLOC(def) < 0) {
+ virReportOOMError(conn);
+ goto cleanup;
+ }
+
+ def->bus = VIR_DOMAIN_DISK_BUS_IDE;
+ def->device = VIR_DOMAIN_DISK_DEVICE_DISK;
+
+ for (i = 0 ; i < nkeywords ; i++) {
+ if (STREQ(keywords[i], "file")) {
+ def->src = values[i];
+ values[i] = NULL;
+ if (STRPREFIX(def->src, "/dev/"))
+ def->type = VIR_DOMAIN_DISK_TYPE_BLOCK;
+ else
+ def->type = VIR_DOMAIN_DISK_TYPE_FILE;
+ } else if (STREQ(keywords[i], "if")) {
+ if (STREQ(values[i], "ide"))
+ def->bus = VIR_DOMAIN_DISK_BUS_IDE;
+ else if (STREQ(values[i], "scsi"))
+ def->bus = VIR_DOMAIN_DISK_BUS_SCSI;
+ else if (STREQ(values[i], "virtio"))
+ def->bus = VIR_DOMAIN_DISK_BUS_VIRTIO;
+ else if (STREQ(values[i], "xen"))
+ def->bus = VIR_DOMAIN_DISK_BUS_XEN;
+ } else if (STREQ(keywords[i], "media")) {
+ if (STREQ(values[i], "cdrom")) {
+ def->device = VIR_DOMAIN_DISK_DEVICE_CDROM;
+ def->readonly = 1;
+ } else if (STREQ(values[i], "floppy"))
+ def->device = VIR_DOMAIN_DISK_DEVICE_FLOPPY;
+ } else if (STREQ(keywords[i], "format")) {
+ def->driverName = strdup("qemu");
+ if (!def->driverName) {
+ virDomainDiskDefFree(def);
+ def = NULL;
+ virReportOOMError(conn);
+ goto cleanup;
+ }
+ def->driverType = values[i];
+ values[i] = NULL;
+ } else if (STREQ(keywords[i], "cache")) {
+ if (STREQ(values[i], "off") ||
+ STREQ(values[i], "none"))
+ def->cachemode = VIR_DOMAIN_DISK_CACHE_DISABLE;
+ else if (STREQ(values[i], "writeback") ||
+ STREQ(values[i], "on"))
+ def->cachemode = VIR_DOMAIN_DISK_CACHE_WRITEBACK;
+ else if (STREQ(values[i], "writethrough"))
+ def->cachemode = VIR_DOMAIN_DISK_CACHE_WRITETHRU;
+ } else if (STREQ(keywords[i], "index")) {
+ if (virStrToLong_i(values[i], NULL, 10, &idx) < 0) {
+ virDomainDiskDefFree(def);
+ def = NULL;
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot parse drive index '%s'"),
val);
+ goto cleanup;
+ }
+ }
+ }
+
+ if (!def->src) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("missing file parameter in drive '%s'"),
val);
+ virDomainDiskDefFree(def);
+ def = NULL;
+ goto cleanup;
+ }
+ if (idx == -1) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("missing index parameter in drive '%s'"),
val);
+ virDomainDiskDefFree(def);
+ def = NULL;
+ goto cleanup;
+ }
+
+ if (def->bus == VIR_DOMAIN_DISK_BUS_IDE) {
+ def->dst = strdup("hda");
+ } else if (def->bus == VIR_DOMAIN_DISK_BUS_SCSI) {
+ def->dst = strdup("sda");
+ } else if (def->bus == VIR_DOMAIN_DISK_BUS_VIRTIO) {
+ def->dst = strdup("vda");
+ } else if (def->bus == VIR_DOMAIN_DISK_BUS_XEN) {
+ def->dst = strdup("xvda");
+ } else {
+ def->dst = strdup("hda");
+ }
+
+ if (!def->dst) {
+ virDomainDiskDefFree(def);
+ def = NULL;
+ virReportOOMError(conn);
+ goto cleanup;
+ }
+ if (STREQ(def->dst, "xvda"))
+ def->dst[3] = 'a' + idx;
+ else
+ def->dst[2] = 'a' + idx;
+
+cleanup:
+ for (i = 0 ; i < nkeywords ; i++) {
+ VIR_FREE(keywords[i]);
+ VIR_FREE(values[i]);
+ }
+ VIR_FREE(keywords);
+ VIR_FREE(values);
+ return def;
+}
+
+/*
+ * Tries to find a NIC definition matching a vlan we want
+ */
+static const char *
+qemuFindNICForVLAN(virConnectPtr conn,
+ int nnics,
+ const char **nics,
+ int wantvlan)
+{
+ int i;
+ for (i = 0 ; i < nnics ; i++) {
+ int gotvlan;
+ const char *tmp = strstr(nics[i], "vlan=");
+ char *end;
+ if (tmp)
+ tmp += strlen("vlan=");
+
+ if (virStrToLong_i(tmp, &end, 10, &gotvlan) < 0) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot parse NIC vlan in '%s'"),
nics[i]);
+ return NULL;
+ }
+
+ if (gotvlan == wantvlan)
+ return nics[i];
+ }
+
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot find NIC definition for vlan %d"), wantvlan);
+ return NULL;
+}
+
+
+/*
+ * Tries to parse a QEMU -net backend argument. Gets given
+ * a list of all known -net frontend arguments to try and
+ * match up against. Horribly complicated stuff
+ */
+static virDomainNetDefPtr
+qemuParseCommandLineNet(virConnectPtr conn,
+ const char *val,
+ int nnics,
+ const char **nics)
+{
+ virDomainNetDefPtr def = NULL;
+ char **keywords;
+ char **values;
+ int nkeywords;
+ const char *nic;
+ int wantvlan = 0;
+ const char *tmp;
+ int i;
+
+ tmp = strchr(val, ',');
+ if (!tmp) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot extract NIC type from '%s'"), val);
+ return NULL;
+ }
+
+ if ((nkeywords = qemuParseCommandLineKeywords(conn,
+ tmp+1,
+ &keywords,
+ &values)) < 0)
+ return NULL;
+
+ if (VIR_ALLOC(def) < 0) {
+ virReportOOMError(conn);
+ goto cleanup;
+ }
+
+ /* 'tap' could turn into libvirt type=ethernet, type=bridge or
+ * type=network, but we can't tell, so use the generic config */
+ if (STRPREFIX(val, "tap,"))
+ def->type = VIR_DOMAIN_NET_TYPE_ETHERNET;
+ else if (STRPREFIX(val, "socket"))
+ def->type = VIR_DOMAIN_NET_TYPE_CLIENT;
+ else if (STRPREFIX(val, "user"))
+ def->type = VIR_DOMAIN_NET_TYPE_USER;
+ else
+ def->type = VIR_DOMAIN_NET_TYPE_ETHERNET;
+
+ for (i = 0 ; i < nkeywords ; i++) {
+ if (STREQ(keywords[i], "vlan")) {
+ if (virStrToLong_i(values[i], NULL, 10, &wantvlan) < 0) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot parse vlan in '%s'"), val);
+ virDomainNetDefFree(def);
+ def = NULL;
+ goto cleanup;
+ }
+ } else if (def->type == VIR_DOMAIN_NET_TYPE_ETHERNET &&
+ STREQ(keywords[i], "script") && STRNEQ(values[i],
"")) {
+ def->data.ethernet.script = values[i];
+ values[i] = NULL;
+ } else if (def->type == VIR_DOMAIN_NET_TYPE_ETHERNET &&
+ STREQ(keywords[i], "ifname")) {
+ def->data.ethernet.dev = values[i];
+ values[i] = NULL;
+ }
+ }
+
+
+ /* Done parsing the nic backend. Now to try and find corresponding
+ * frontend, based off vlan number. NB this assumes a 1-1 mapping
+ */
+
+ nic = qemuFindNICForVLAN(conn, nnics, nics, wantvlan);
+ if (!nic) {
+ virDomainNetDefFree(def);
+ def = NULL;
+ goto cleanup;
+ }
+
+ if (!STRPREFIX(nic, "nic,")) {
+ virDomainNetDefFree(def);
+ def = NULL;
+ goto cleanup;
+ }
+
+ for (i = 0 ; i < nkeywords ; i++) {
+ VIR_FREE(keywords[i]);
+ VIR_FREE(values[i]);
+ }
+ VIR_FREE(keywords);
+ VIR_FREE(values);
+
+ if ((nkeywords = qemuParseCommandLineKeywords(conn,
+ nic + strlen("nic,"),
+ &keywords,
+ &values)) < 0) {
+ virDomainNetDefFree(def);
+ def = NULL;
+ goto cleanup;
+ }
+
+ for (i = 0 ; i < nkeywords ; i++) {
+ if (STREQ(keywords[i], "macaddr")) {
+ virParseMacAddr(values[i], def->mac);
+ } else if (STREQ(keywords[i], "model")) {
+ def->model = values[i];
+ values[i] = NULL;
+ }
+ }
+
+cleanup:
+ for (i = 0 ; i < nkeywords ; i++) {
+ VIR_FREE(keywords[i]);
+ VIR_FREE(values[i]);
+ }
+ VIR_FREE(keywords);
+ VIR_FREE(values);
+ return def;
+}
+
+
+/*
+ * Tries to parse a QEMU PCI device
+ */
+static virDomainHostdevDefPtr
+qemuParseCommandLinePCI(virConnectPtr conn,
+ const char *val)
+{
+ virDomainHostdevDefPtr def = NULL;
+ int bus = 0, slot = 0, func = 0;
+ const char *start;
+ char *end;
+
+ if (!STRPREFIX(val, "host=")) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("unknown PCI device syntax '%s'"), val);
+ VIR_FREE(def);
+ goto cleanup;
+ }
+
+ start = val + strlen("host=");
+ if (virStrToLong_i(start, &end, 16, &bus) < 0 || !end || *end !=
':') {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot extract PCI device bus '%s'"),
val);
+ VIR_FREE(def);
+ goto cleanup;
+ }
+ start = end + 1;
+ if (virStrToLong_i(start, &end, 16, &slot) < 0 || !end || *end !=
'.') {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot extract PCI device slot '%s'"),
val);
+ VIR_FREE(def);
+ goto cleanup;
+ }
+ start = end + 1;
+ if (virStrToLong_i(start, NULL, 16, &func) < 0) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot extract PCI device function '%s'"),
val);
+ VIR_FREE(def);
+ goto cleanup;
+ }
+
+ if (VIR_ALLOC(def) < 0) {
+ virReportOOMError(conn);
+ goto cleanup;
+ }
+
+ def->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
+ def->managed = 1;
+ def->source.subsys.type = VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI;
+ def->source.subsys.u.pci.bus = bus;
+ def->source.subsys.u.pci.slot = slot;
+ def->source.subsys.u.pci.function = func;
+
+cleanup:
+ return def;
+}
+
+
+/*
+ * Tries to parse a QEMU PCI device
+ */
+static virDomainHostdevDefPtr
+qemuParseCommandLineUSB(virConnectPtr conn,
+ const char *val)
+{
+ virDomainHostdevDefPtr def = NULL;
+ int first = 0, second = 0;
+ const char *start;
+ char *end;
+
+ if (!STRPREFIX(val, "host:")) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("unknown PCI device syntax '%s'"), val);
+ VIR_FREE(def);
+ goto cleanup;
+ }
+
+ start = val + strlen("host:");
+ if (strchr(start, ':')) {
+ if (virStrToLong_i(start, &end, 16, &first) < 0 || !end || *end !=
':') {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot extract USB device vendor
'%s'"), val);
+ VIR_FREE(def);
+ goto cleanup;
+ }
+ start = end + 1;
+ if (virStrToLong_i(start, NULL, 16, &second) < 0) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot extract PCI device product
'%s'"), val);
+ VIR_FREE(def);
+ goto cleanup;
+ }
+ } else {
+ if (virStrToLong_i(start, &end, 10, &first) < 0 || !end || *end !=
'.') {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot extract PCI device bus '%s'"),
val);
+ VIR_FREE(def);
+ goto cleanup;
+ }
+ start = end + 1;
+ if (virStrToLong_i(start, NULL, 10, &second) < 0) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot extract PCI device address
'%s'"), val);
+ VIR_FREE(def);
+ goto cleanup;
+ }
+ }
+
+ if (VIR_ALLOC(def) < 0) {
+ virReportOOMError(conn);
+ goto cleanup;
+ }
+
+ def->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS;
+ def->managed = 0;
+ def->source.subsys.type = VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB;
+ if (*end == '.') {
+ def->source.subsys.u.usb.bus = first;
+ def->source.subsys.u.usb.device = second;
+ } else {
+ def->source.subsys.u.usb.vendor = first;
+ def->source.subsys.u.usb.product = second;
+ }
+
+cleanup:
+ return def;
+}
+
+
+/*
+ * Tries to parse a QEMU serial/parallel device
+ */
+static virDomainChrDefPtr
+qemuParseCommandLineChr(virConnectPtr conn,
+ const char *val)
+{
+ virDomainChrDefPtr def;
+
+ if (VIR_ALLOC(def) < 0)
+ goto no_memory;
+
+ if (STREQ(val, "null")) {
+ def->type = VIR_DOMAIN_CHR_TYPE_NULL;
+ } else if (STREQ(val, "vc")) {
+ def->type = VIR_DOMAIN_CHR_TYPE_VC;
+ } else if (STREQ(val, "pty")) {
+ def->type = VIR_DOMAIN_CHR_TYPE_PTY;
+ } else if (STRPREFIX(val, "file:")) {
+ def->type = VIR_DOMAIN_CHR_TYPE_FILE;
+ def->data.file.path = strdup(val+strlen("file:"));
+ if (!def->data.file.path)
+ goto no_memory;
+ } else if (STRPREFIX(val, "pipe:")) {
+ def->type = VIR_DOMAIN_CHR_TYPE_PIPE;
+ def->data.file.path = strdup(val+strlen("pipe:"));
+ if (!def->data.file.path)
+ goto no_memory;
+ } else if (STREQ(val, "stdio")) {
+ def->type = VIR_DOMAIN_CHR_TYPE_STDIO;
+ } else if (STRPREFIX(val, "udp:")) {
+ const char *svc1, *host2, *svc2;
+ def->type = VIR_DOMAIN_CHR_TYPE_UDP;
+ val += strlen("udp:");
+ svc1 = strchr(val, ':');
+ host2 = svc1 ? strchr(svc1, '@') : NULL;
+ svc2 = host2 ? strchr(host2, ':') : NULL;
+
+ if (svc1)
+ def->data.udp.connectHost = strndup(val, svc1-val);
+ else
+ def->data.udp.connectHost = strdup(val);
+ if (svc1) {
+ svc1++;
+ if (host2)
+ def->data.udp.connectService = strndup(svc1, host2-svc1);
+ else
+ def->data.udp.connectService = strdup(svc1);
+ }
+
+ if (host2) {
+ host2++;
+ if (svc2)
+ def->data.udp.bindHost = strndup(host2, svc2-host2);
+ else
+ def->data.udp.bindHost = strdup(host2);
+ }
+ if (svc2) {
+ svc2++;
+ def->data.udp.bindService = strdup(svc2);
+ }
+ } else if (STRPREFIX(val, "tcp:") ||
+ STRPREFIX(val, "telnet:")) {
+ const char *opt, *svc;
+ def->type = VIR_DOMAIN_CHR_TYPE_TCP;
+ if (STRPREFIX(val, "tcp:")) {
+ val += strlen("tcp:");
+ } else {
+ val += strlen("telnet:");
+ def->data.tcp.protocol = VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET;
+ }
+ svc = strchr(val, ':');
+ if (!svc) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("cannot find port number in character device
%s"), val);
+ goto error;
+ }
+ opt = strchr(svc, ',');
+ if (opt && strstr(opt, "server"))
+ def->data.tcp.listen = 1;
+
+ def->data.tcp.host = strndup(val, svc-val);
+ svc++;
+ if (opt) {
+ def->data.tcp.service = strndup(svc, opt-svc);
+ } else {
+ def->data.tcp.service = strdup(svc);
+ }
+ } else if (STRPREFIX(val, "unix:")) {
+ const char *opt;
+ val += strlen("unix:");
+ opt = strchr(val, ',');
+ def->type = VIR_DOMAIN_CHR_TYPE_UNIX;
+ if (opt) {
+ if (strstr(opt, "listen"))
+ def->data.nix.listen = 1;
+ def->data.nix.path = strndup(val, opt-val);
+ } else {
+ def->data.nix.path = strdup(val);
+ }
+ if (!def->data.nix.path)
+ goto no_memory;
+
+ } else if (STRPREFIX(val, "/dev")) {
+ def->type = VIR_DOMAIN_CHR_TYPE_DEV;
+ def->data.file.path = strdup(val);
+ if (!def->data.file.path)
+ goto no_memory;
+ } else {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("unknown character device syntax %s"), val);
+ goto error;
+ }
+
+ return def;
+
+no_memory:
+ virReportOOMError(conn);
+error:
+ virDomainChrDefFree(def);
+ return NULL;
+}
+
+/*
+ * Analyse the env and argv settings and reconstruct a
+ * virDomainDefPtr representing these settings as closely
+ * as is practical. This is not an exact science....
+ */
+virDomainDefPtr qemuParseCommandLine(virConnectPtr conn,
+ const char **progenv,
+ const char **progargv)
+{
+ virDomainDefPtr def;
+ int i;
+ int nographics = 0;
+ int fullscreen = 0;
+ char *path;
+ int nnics = 0;
+ const char **nics = NULL;
+
+ if (!progargv[0]) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ "%s", _("no emulator path found"));
+ return NULL;
+ }
+
+ if (VIR_ALLOC(def) < 0)
+ goto no_memory;
+
+ def->id = -1;
+ def->memory = def->maxmem = 64 * 1024;
+ def->vcpus = 1;
+ def->features = (1 << VIR_DOMAIN_FEATURE_ACPI)
+ /*| (1 << VIR_DOMAIN_FEATURE_APIC)*/;
+ def->onReboot = VIR_DOMAIN_LIFECYCLE_RESTART;
+ def->onCrash = VIR_DOMAIN_LIFECYCLE_DESTROY;
+ def->onPoweroff = VIR_DOMAIN_LIFECYCLE_DESTROY;
+ def->virtType = VIR_DOMAIN_VIRT_QEMU;
+ if (!(def->emulator = strdup(progargv[0])))
+ goto no_memory;
+
+ if (strstr(def->emulator, "kvm")) {
+ def->virtType = VIR_DOMAIN_VIRT_KVM;
+ def->features |= (1 << VIR_DOMAIN_FEATURE_PAE);
+ }
+
+
+ if (strstr(def->emulator, "xenner"))
+ def->os.type = strdup("xen");
+ else
+ def->os.type = strdup("hvm");
+ if (!def->os.type)
+ goto no_memory;
+
+ if (STRPREFIX(def->emulator, "qemu"))
+ path = def->emulator;
+ else
+ path = strstr(def->emulator, "qemu");
+ if (path &&
+ STRPREFIX(path, "qemu-system-"))
+ def->os.arch = strdup(path + strlen("qemu-system-"));
+ else
+ def->os.arch = strdup("i686");
+ if (!def->os.arch)
+ goto no_memory;
+
+#define WANT_VALUE() \
+ const char *val = progargv[++i]; \
+ if (!val) { \
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, \
+ _("missing value for %s argument"), arg); \
+ goto error; \
+ }
+
+ /* One initial loop to get list of NICs, so we
+ * can correlate them later */
+ for (i = 1 ; progargv[i] ; i++) {
+ const char *arg = progargv[i];
+ /* Make sure we have a single - for all options to
+ simplify next logic */
+ if (STRPREFIX(arg, "--"))
+ arg++;
+
+ if (STREQ(arg, "-net")) {
+ WANT_VALUE();
+ if (STRPREFIX(val, "nic")) {
+ if (VIR_REALLOC_N(nics, nnics+1) < 0)
+ goto no_memory;
+ nics[nnics++] = val;
+ }
+ }
+ }
+
+ /* Now the real processing loop */
+ for (i = 1 ; progargv[i] ; i++) {
+ const char *arg = progargv[i];
+ /* Make sure we have a single - for all options to
+ simplify next logic */
+ if (STRPREFIX(arg, "--"))
+ arg++;
+
+ if (STREQ(arg, "-vnc")) {
+ virDomainGraphicsDefPtr vnc;
+ char *tmp;
+ WANT_VALUE();
+ if (VIR_ALLOC(vnc) < 0)
+ goto no_memory;
+ vnc->type = VIR_DOMAIN_GRAPHICS_TYPE_VNC;
+ vnc->data.vnc.autoport = 1;
+
+ tmp = strchr(val, ':');
+ if (tmp != val) {
+ vnc->data.vnc.listenAddr = strndup(val, tmp-val);
+ if (!vnc->data.vnc.listenAddr) {
+ VIR_FREE(vnc);
+ goto no_memory;
+ }
+ }
+
+ if (VIR_REALLOC_N(def->graphics, def->ngraphics+1) < 0) {
+ virDomainGraphicsDefFree(vnc);
+ goto no_memory;
+ }
+ def->graphics[def->ngraphics++] = vnc;
+ } else if (STREQ(arg, "-m")) {
+ int mem;
+ WANT_VALUE();
+ if (virStrToLong_i(val, NULL, 10, &mem) < 0)
+ goto error;
+ def->memory = def->maxmem = mem * 1024;
+ } else if (STREQ(arg, "-smp")) {
+ int vcpus;
+ WANT_VALUE();
+ if (virStrToLong_i(val, NULL, 10, &vcpus) < 0)
+ goto error;
+ def->vcpus = vcpus;
+ } else if (STREQ(arg, "-uuid")) {
+ WANT_VALUE();
+ if (virUUIDParse(val, def->uuid) < 0)
+ goto error;
+ } else if (STRPREFIX(arg, "-hd") ||
+ STRPREFIX(arg, "-sd") ||
+ STRPREFIX(arg, "-fd") ||
+ STREQ(arg, "-cdrom")) {
+ WANT_VALUE();
+ virDomainDiskDefPtr disk;
+ if (VIR_ALLOC(disk) < 0)
+ goto no_memory;
+
+ if (STRPREFIX(val, "/dev/"))
+ disk->type = VIR_DOMAIN_DISK_TYPE_BLOCK;
+ else
+ disk->type = VIR_DOMAIN_DISK_TYPE_FILE;
+ if (STREQ(arg, "-cdrom")) {
+ disk->device = VIR_DOMAIN_DISK_DEVICE_CDROM;
+ disk->dst = strdup("hdc");
+ disk->readonly = 1;
+ } else {
+ if (STRPREFIX(arg, "-fd")) {
+ disk->device = VIR_DOMAIN_DISK_DEVICE_FLOPPY;
+ disk->bus = VIR_DOMAIN_DISK_BUS_FDC;
+ } else {
+ disk->device = VIR_DOMAIN_DISK_DEVICE_DISK;
+ if (STRPREFIX(arg, "-hd"))
+ disk->bus = VIR_DOMAIN_DISK_BUS_IDE;
+ else
+ disk->bus = VIR_DOMAIN_DISK_BUS_SCSI;
+ }
+ disk->dst = strdup(arg + 1);
+ }
+ disk->src = strdup(val);
+ if (!disk->src ||
+ !disk->dst) {
+ virDomainDiskDefFree(disk);
+ goto no_memory;
+ }
+
+ if (VIR_REALLOC_N(def->disks, def->ndisks+1) < 0) {
+ virDomainDiskDefFree(disk);
+ goto no_memory;
+ }
+ def->disks[def->ndisks++] = disk;
+ } else if (STREQ(arg, "-no-acpi")) {
+ def->features &= ~(1 << VIR_DOMAIN_FEATURE_ACPI);
+ } else if (STREQ(arg, "-no-reboot")) {
+ def->onReboot = VIR_DOMAIN_LIFECYCLE_DESTROY;
+ } else if (STREQ(arg, "-no-kvm")) {
+ def->virtType = VIR_DOMAIN_VIRT_QEMU;
+ } else if (STREQ(arg, "-nographic")) {
+ nographics = 1;
+ } else if (STREQ(arg, "-full-screen")) {
+ fullscreen = 1;
+ } else if (STREQ(arg, "-localtime")) {
+ def->localtime = 1;
+ } else if (STREQ(arg, "-kernel")) {
+ WANT_VALUE();
+ if (!(def->os.kernel = strdup(val)))
+ goto no_memory;
+ } else if (STREQ(arg, "-initrd")) {
+ WANT_VALUE();
+ if (!(def->os.initrd = strdup(val)))
+ goto no_memory;
+ } else if (STREQ(arg, "-append")) {
+ WANT_VALUE();
+ if (!(def->os.cmdline = strdup(val)))
+ goto no_memory;
+ } else if (STREQ(arg, "-boot")) {
+ int n, b = 0;
+ WANT_VALUE();
+ for (n = 0 ; val[n] && b < VIR_DOMAIN_BOOT_LAST ; n++) {
+ if (val[n] == 'a')
+ def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_FLOPPY;
+ else if (val[n] == 'c')
+ def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_DISK;
+ else if (val[n] == 'd')
+ def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_CDROM;
+ else if (val[n] == 'n')
+ def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_NET;
+ }
+ def->os.nBootDevs = b;
+ } else if (STREQ(arg, "-name")) {
+ WANT_VALUE();
+ if (!(def->name = strdup(val)))
+ goto no_memory;
+ } else if (STREQ(arg, "-M")) {
+ WANT_VALUE();
+ if (!(def->os.machine = strdup(val)))
+ goto no_memory;
+ } else if (STREQ(arg, "-serial")) {
+ WANT_VALUE();
+ if (STRNEQ(val, "none")) {
+ virDomainChrDefPtr chr;
+ if (!(chr = qemuParseCommandLineChr(conn, val)))
+ goto error;
+ if (VIR_REALLOC_N(def->serials, def->nserials+1) < 0) {
+ virDomainChrDefFree(chr);
+ goto no_memory;
+ }
+ chr->dstPort = def->nserials;
+ def->serials[def->nserials++] = chr;
+ }
+ } else if (STREQ(arg, "-parallel")) {
+ WANT_VALUE();
+ if (STRNEQ(val, "none")) {
+ virDomainChrDefPtr chr;
+ if (!(chr = qemuParseCommandLineChr(conn, val)))
+ goto error;
+ if (VIR_REALLOC_N(def->parallels, def->nparallels+1) < 0) {
+ virDomainChrDefFree(chr);
+ goto no_memory;
+ }
+ chr->dstPort = def->nparallels;
+ def->parallels[def->nparallels++] = chr;
+ }
+ } else if (STREQ(arg, "-usbdevice")) {
+ WANT_VALUE();
+ if (STREQ(val, "tablet") ||
+ STREQ(val, "mouse")) {
+ virDomainInputDefPtr input;
+ if (VIR_ALLOC(input) < 0)
+ goto no_memory;
+ input->bus = VIR_DOMAIN_INPUT_BUS_USB;
+ if (STREQ(val, "tablet"))
+ input->type = VIR_DOMAIN_INPUT_TYPE_TABLET;
+ else
+ input->type = VIR_DOMAIN_INPUT_TYPE_MOUSE;
+ if (VIR_REALLOC_N(def->inputs, def->ninputs+1) < 0) {
+ virDomainInputDefFree(input);
+ goto no_memory;
+ }
+ def->inputs[def->ninputs++] = input;
+ } else if (STRPREFIX(val, "disk:")) {
+ virDomainDiskDefPtr disk;
+ if (VIR_ALLOC(disk) < 0)
+ goto no_memory;
+ disk->src = strdup(val + strlen("disk:"));
+ if (!disk->src) {
+ virDomainDiskDefFree(disk);
+ goto no_memory;
+ }
+ if (STRPREFIX(disk->src, "/dev/"))
+ disk->type = VIR_DOMAIN_DISK_TYPE_BLOCK;
+ else
+ disk->type = VIR_DOMAIN_DISK_TYPE_FILE;
+ disk->device = VIR_DOMAIN_DISK_DEVICE_DISK;
+ disk->bus = VIR_DOMAIN_DISK_BUS_USB;
+ if (!(disk->dst = strdup("sda"))) {
+ virDomainDiskDefFree(disk);
+ goto no_memory;
+ }
+ if (VIR_REALLOC_N(def->disks, def->ndisks+1) < 0) {
+ virDomainDiskDefFree(disk);
+ goto no_memory;
+ }
+ def->disks[def->ndisks++] = disk;
+ } else {
+ virDomainHostdevDefPtr hostdev;
+ if (!(hostdev = qemuParseCommandLineUSB(conn, val)))
+ goto error;
+ if (VIR_REALLOC_N(def->hostdevs, def->nhostdevs+1) < 0) {
+ virDomainHostdevDefFree(hostdev);
+ goto no_memory;
+ }
+ def->hostdevs[def->nhostdevs++] = hostdev;
+ }
+ } else if (STREQ(arg, "-net")) {
+ WANT_VALUE();
+ if (!STRPREFIX(val, "nic") && STRNEQ(val,
"none")) {
+ virDomainNetDefPtr net;
+ if (!(net = qemuParseCommandLineNet(conn, val, nnics, nics)))
+ goto error;
+ if (VIR_REALLOC_N(def->nets, def->nnets+1) < 0) {
+ virDomainNetDefFree(net);
+ goto no_memory;
+ }
+ def->nets[def->nnets++] = net;
+ }
+ } else if (STREQ(arg, "-drive")) {
+ virDomainDiskDefPtr disk;
+ WANT_VALUE();
+ if (!(disk = qemuParseCommandLineDisk(conn, val)))
+ goto error;
+ if (VIR_REALLOC_N(def->disks, def->ndisks+1) < 0) {
+ virDomainDiskDefFree(disk);
+ goto no_memory;
+ }
+ def->disks[def->ndisks++] = disk;
+ } else if (STREQ(arg, "-pcidevice")) {
+ virDomainHostdevDefPtr hostdev;
+ WANT_VALUE();
+ if (!(hostdev = qemuParseCommandLinePCI(conn, val)))
+ goto error;
+ if (VIR_REALLOC_N(def->hostdevs, def->nhostdevs+1) < 0) {
+ virDomainHostdevDefFree(hostdev);
+ goto no_memory;
+ }
+ def->hostdevs[def->nhostdevs++] = hostdev;
+ } else if (STREQ(arg, "-soundhw")) {
+ const char *start;
+ WANT_VALUE();
+ start = val;
+ while (start) {
+ const char *tmp = strchr(start, ',');
+ int type = -1;
+ if (STRPREFIX(start, "pcspk")) {
+ type = VIR_DOMAIN_SOUND_MODEL_PCSPK;
+ } else if (STRPREFIX(start, "sb16")) {
+ type = VIR_DOMAIN_SOUND_MODEL_SB16;
+ } else if (STRPREFIX(start, "es1370")) {
+ type = VIR_DOMAIN_SOUND_MODEL_ES1370;
+ } else if (STRPREFIX(start, "ac97")) {
+ type = VIR_DOMAIN_SOUND_MODEL_AC97;
+ }
+
+ if (type != -1) {
+ virDomainSoundDefPtr snd;
+ if (VIR_ALLOC(snd) < 0)
+ goto no_memory;
+ snd->model = type;
+ if (VIR_REALLOC_N(def->sounds, def->nsounds+1) < 0) {
+ VIR_FREE(snd);
+ goto no_memory;
+ }
+ def->sounds[def->nsounds++] = snd;
+ }
+
+ start = tmp ? tmp + 1 : NULL;
+ }
+ } else if (STREQ(arg, "-bootloader")) {
+ WANT_VALUE();
+ def->os.bootloader = strdup(val);
+ if (!def->os.bootloader)
+ goto no_memory;
+ } else if (STREQ(arg, "-domid")) {
+ WANT_VALUE();
+ /* ignore, generted on the fly */
+ } else if (STREQ(arg, "-usb")) {
+ /* ignore, always added by libvirt */
+ } else if (STREQ(arg, "-pidfile")) {
+ WANT_VALUE();
+ /* ignore, used by libvirt as needed */
+ } else if (STREQ(arg, "-incoming")) {
+ WANT_VALUE();
+ /* ignore, used via restore/migrate APIs */
+ } else if (STREQ(arg, "-monitor")) {
+ WANT_VALUE();
+ /* ignore, used internally by libvirt */
+ } else if (STREQ(arg, "-S")) {
+ /* ignore, always added by libvirt */
+ } else {
+#if 1
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR,
+ _("unknown argument '%s'"), arg);
+ goto error;
+#endif
+ }
+ }
+
+#undef WANT_VALUE
+
+ if (!nographics && def->ngraphics == 0) {
+ virDomainGraphicsDefPtr sdl;
+ const char *display = qemuFindEnv(progenv, "DISPLAY");
+ const char *xauth = qemuFindEnv(progenv, "XAUTHORITY");
+ if (VIR_ALLOC(sdl) < 0)
+ goto no_memory;
+ sdl->type = VIR_DOMAIN_GRAPHICS_TYPE_SDL;
+ sdl->data.sdl.fullscreen = fullscreen;
+ if (display &&
+ !(sdl->data.sdl.display = strdup(display))) {
+ VIR_FREE(sdl);
+ goto no_memory;
+ }
+ if (xauth &&
+ !(sdl->data.sdl.xauth = strdup(xauth))) {
+ VIR_FREE(sdl);
+ goto no_memory;
+ }
+
+ if (VIR_REALLOC_N(def->graphics, def->ngraphics+1) < 0) {
+ virDomainGraphicsDefFree(sdl);
+ goto no_memory;
+ }
+ def->graphics[def->ngraphics++] = sdl;
+ }
+
+ VIR_FREE(nics);
+
+ return def;
+
+no_memory:
+ virReportOOMError(conn);
+error:
+ virDomainDefFree(def);
+ VIR_FREE(nics);
+ return NULL;
+}
+
+
+virDomainDefPtr qemuParseCommandLineString(virConnectPtr conn,
+ const char *args)
+{
+ const char **progenv = NULL;
+ const char **progargv = NULL;
+ virDomainDefPtr def = NULL;
+ int i;
+
+ if (qemuStringToArgvEnv(args, &progenv, &progargv) < 0)
+ goto cleanup;
+
+ def = qemuParseCommandLine(conn, progenv, progargv);
+
+cleanup:
+ for (i = 0 ; progargv && progargv[i] ; i++)
+ VIR_FREE(progargv[i]);
+ VIR_FREE(progargv);
+
+ for (i = 0 ; progenv && progenv[i] ; i++)
+ VIR_FREE(progenv[i]);
+ VIR_FREE(progenv);
+
+ return def;
+}
+
+
/* Called from SAX on parsing errors in the XML. */
static void
catchXMLError (void *ctx, const char *msg ATTRIBUTE_UNUSED, ...)
diff -r b052a7f425ac src/qemu_conf.h
--- a/src/qemu_conf.h Wed May 13 10:02:15 2009 -0400
+++ b/src/qemu_conf.h Wed May 13 10:02:17 2009 -0400
@@ -1,5 +1,5 @@
/*
- * config.h: VM configuration management
+ * qemu_conf.h: QEMU configuration management
*
* Copyright (C) 2006, 2007, 2009 Red Hat, Inc.
* Copyright (C) 2006 Daniel P. Berrange
@@ -135,6 +135,12 @@ int qemudBuildCommandLine
int *ntapfds,
const char *migrateFrom);
+virDomainDefPtr qemuParseCommandLine(virConnectPtr conn,
+ const char **progenv,
+ const char **progargv);
+virDomainDefPtr qemuParseCommandLineString(virConnectPtr conn,
+ const char *args);
+
const char *qemudVirtTypeToString (int type);
qemudDomainStatusPtr qemudDomainStatusParseFile(virConnectPtr conn,
virCapsPtr caps,
diff -r b052a7f425ac src/qemu_driver.c
--- a/src/qemu_driver.c Wed May 13 10:02:15 2009 -0400
+++ b/src/qemu_driver.c Wed May 13 10:02:17 2009 -0400
@@ -3422,6 +3422,30 @@ cleanup:
return ret;
}
+
+static char *qemuDomainXMLFromNative(virConnectPtr conn,
+ const char *format,
+ const char *config,
+ unsigned int flags ATTRIBUTE_UNUSED) {
+ virDomainDefPtr def = NULL;
+ char *xml = NULL;
+
+ if (STRNEQ(format, QEMU_CONFIG_FORMAT_ARGV)) {
+ qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_ARG,
+ _("unsupported config type %s"), format);
+ goto cleanup;
+ }
+
+ def = qemuParseCommandLineString(conn, config);
+ if (!def)
+ goto cleanup;
+
+ xml = virDomainDefFormat(conn, def, VIR_DOMAIN_XML_INACTIVE);
+
+cleanup:
+ virDomainDefFree(def);
+ return xml;
+}
static char *qemuDomainXMLToNative(virConnectPtr conn,
const char *format,
@@ -5348,7 +5372,7 @@ static virDriver qemuDriver = {
qemudDomainGetSecurityLabel, /* domainGetSecurityLabel */
qemudNodeGetSecurityModel, /* nodeGetSecurityModel */
qemudDomainDumpXML, /* domainDumpXML */
- NULL, /* domainXmlFromNative */
+ qemuDomainXMLFromNative, /* domainXmlFromNative */
qemuDomainXMLToNative, /* domainXMLToNative */
qemudListDefinedDomains, /* listDefinedDomains */
qemudNumDefinedDomains, /* numOfDefinedDomains */
diff -r b052a7f425ac src/vbox/vbox_tmpl.c
--- a/src/vbox/vbox_tmpl.c Wed May 13 10:02:15 2009 -0400
+++ b/src/vbox/vbox_tmpl.c Wed May 13 10:02:17 2009 -0400
@@ -1803,7 +1803,7 @@ static char *vboxDomainDumpXML(virDomain
if (audioController == AudioControllerType_SB16) {
def->sounds[0]->model =
VIR_DOMAIN_SOUND_MODEL_SB16;
} else if (audioController == AudioControllerType_AC97)
{
- def->sounds[0]->model =
VIR_DOMAIN_SOUND_MODEL_ES97;
+ def->sounds[0]->model =
VIR_DOMAIN_SOUND_MODEL_AC97;
}
} else {
VIR_FREE(def->sounds);
@@ -2767,7 +2767,7 @@ static virDomainPtr vboxDomainDefineXML(
if (NS_SUCCEEDED(rc)) {
if (def->sounds[0]->model == VIR_DOMAIN_SOUND_MODEL_SB16)
{
audioAdapter->vtbl->SetAudioController(audioAdapter,
AudioControllerType_SB16);
- } else if (def->sounds[0]->model ==
VIR_DOMAIN_SOUND_MODEL_ES97) {
+ } else if (def->sounds[0]->model ==
VIR_DOMAIN_SOUND_MODEL_AC97) {
audioAdapter->vtbl->SetAudioController(audioAdapter,
AudioControllerType_AC97);
}
}
--
|: Red Hat, Engineering, London -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 :|