From 23ad665cb05ef9ce7d298cc34bff5efb95ef6948 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Wed, 14 Feb 2007 01:40:09 +0000 Subject: [PATCH] Added QEMU support --- ChangeLog | 17 + Makefile.am | 2 +- configure.in | 1 + include/libvirt/virterror.h | 3 +- qemud/.cvsignore | 7 + qemud/Makefile.am | 19 + qemud/conf.c | 1429 +++++++++++++++++++++++++++++++++++ qemud/conf.h | 56 ++ qemud/config.c | 1429 +++++++++++++++++++++++++++++++++++ qemud/config.h | 56 ++ qemud/dispatch.c | 580 ++++++++++++++ qemud/dispatch.h | 43 ++ qemud/driver.c | 554 ++++++++++++++ qemud/driver.h | 99 +++ qemud/internal.h | 254 +++++++ qemud/protocol.h | 222 ++++++ qemud/qemud.c | 958 +++++++++++++++++++++++ src/Makefile.am | 7 +- src/driver.h | 3 +- src/libvirt.c | 4 + src/qemu_internal.c | 864 +++++++++++++++++++++ src/qemu_internal.h | 48 ++ src/virsh.c | 4 +- src/virterror.c | 3 + 24 files changed, 6655 insertions(+), 7 deletions(-) create mode 100644 qemud/.cvsignore create mode 100644 qemud/Makefile.am create mode 100644 qemud/conf.c create mode 100644 qemud/conf.h create mode 100644 qemud/config.c create mode 100644 qemud/config.h create mode 100644 qemud/dispatch.c create mode 100644 qemud/dispatch.h create mode 100644 qemud/driver.c create mode 100644 qemud/driver.h create mode 100644 qemud/internal.h create mode 100644 qemud/protocol.h create mode 100644 qemud/qemud.c create mode 100644 src/qemu_internal.c create mode 100644 src/qemu_internal.h diff --git a/ChangeLog b/ChangeLog index c73e17961f..21ff3bdc1c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +Tue Feb 13 19:29:35 EST 2007 Daniel Berrange + + * src/qemu_internal.h, src/qemu_internal.c, src/Makefile.am, + src/driver.h, src/libvirt.c: Added a new driver to talk to + the QEMU daemon + + * src/virterror.c, include/libvirt/virterror.c: Added new + error domain for QEMU. + + * qemud/*: Added a daemon service for managing QEMU machines + via the libvirt qemu_internal driver + + * src/virsh.c: use a read-write connection by default for QEMU + urls. + + * configure.in, Makefile.am: Added qemud subdirectory. + Thu Feb 8 12:59:14 EST 2007 Daniel Berrange * src/xml.c, src/xend_internal.c, src/xend_internal.h: Remove diff --git a/Makefile.am b/Makefile.am index dcc80679d5..ddf52ca9f9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ ## Process this file with automake to produce Makefile.in -SUBDIRS = src include docs @PYTHON_SUBDIR@ tests proxy po +SUBDIRS = src qemud proxy include docs @PYTHON_SUBDIR@ tests po ACLOCAL_AMFLAGS = -I m4 diff --git a/configure.in b/configure.in index 7947ed4059..c1bade40aa 100644 --- a/configure.in +++ b/configure.in @@ -288,6 +288,7 @@ AC_OUTPUT(Makefile src/Makefile include/Makefile docs/Makefile \ po/Makefile.in \ include/libvirt/Makefile include/libvirt/libvirt.h \ python/Makefile python/tests/Makefile \ + qemud/Makefile \ tests/Makefile proxy/Makefile \ tests/xml2sexprdata/Makefile \ tests/sexpr2xmldata/Makefile \ diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index e2d5369195..8a7959eb5d 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -46,7 +46,8 @@ typedef enum { VIR_FROM_DOM, /* Error when operating on a domain */ VIR_FROM_RPC, /* Error in the XML-RPC code */ VIR_FROM_PROXY, /* Error in the proxy code */ - VIR_FROM_CONF /* Error in the configuration file handling */ + VIR_FROM_CONF, /* Error in the configuration file handling */ + VIR_FROM_QEMU, /* Error at the QEMU daemon */ } virErrorDomain; diff --git a/qemud/.cvsignore b/qemud/.cvsignore new file mode 100644 index 0000000000..eddc7eb323 --- /dev/null +++ b/qemud/.cvsignore @@ -0,0 +1,7 @@ +Makefile +Makefile.in +.deps +.libs +*.lo +*.la +libvirt_qemud diff --git a/qemud/Makefile.am b/qemud/Makefile.am new file mode 100644 index 0000000000..d86cb23c0b --- /dev/null +++ b/qemud/Makefile.am @@ -0,0 +1,19 @@ +## Process this file with automake to produce Makefile.in + +INCLUDES = @LIBXML_CFLAGS@ + +libexec_PROGRAMS = libvirt_qemud + +libvirt_qemud_SOURCES = qemud.c internal.h protocol.h \ + driver.c driver.h \ + dispatch.c dispatch.h \ + config.c config.h +#-D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED=1 -D_POSIX_C_SOURCE=199506L +libvirt_qemud_CFLAGS = \ + -I$(top_srcdir)/include -I$(top_builddir)/include $(LIBXML_CFLAGS) \ + -Werror -Wall -Wextra -DLOCAL_STATE_DIR="\"$(localstatedir)\"" \ + -DSYSCONF_DIR="\"$(sysconfdir)\"" +libvirt_qemud_LDFLAGS = $(LIBXML_LIBS) +libvirt_qemud_DEPENDENCIES = +libvirt_qemud_LDADD = + diff --git a/qemud/conf.c b/qemud/conf.c new file mode 100644 index 0000000000..216c791a53 --- /dev/null +++ b/qemud/conf.c @@ -0,0 +1,1429 @@ +/* + * config.c: VM configuration management + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "protocol.h" +#include "internal.h" +#include "config.h" +#include "driver.h" + +static int qemudParseUUID(const char *uuid, + unsigned char *rawuuid) { + const char *cur; + int i; + + /* + * do a liberal scan allowing '-' and ' ' anywhere between character + * pairs as long as there is 32 of them in the end. + */ + cur = uuid; + for (i = 0;i < 16;) { + rawuuid[i] = 0; + if (*cur == 0) + goto error; + if ((*cur == '-') || (*cur == ' ')) { + cur++; + continue; + } + if ((*cur >= '0') && (*cur <= '9')) + rawuuid[i] = *cur - '0'; + else if ((*cur >= 'a') && (*cur <= 'f')) + rawuuid[i] = *cur - 'a' + 10; + else if ((*cur >= 'A') && (*cur <= 'F')) + rawuuid[i] = *cur - 'A' + 10; + else + goto error; + rawuuid[i] *= 16; + cur++; + if (*cur == 0) + goto error; + if ((*cur >= '0') && (*cur <= '9')) + rawuuid[i] += *cur - '0'; + else if ((*cur >= 'a') && (*cur <= 'f')) + rawuuid[i] += *cur - 'a' + 10; + else if ((*cur >= 'A') && (*cur <= 'F')) + rawuuid[i] += *cur - 'A' + 10; + else + goto error; + i++; + cur++; + } + + return 0; + + error: + return -1; +} + + +struct qemu_arch_info { + const char *arch; + const char **machines; + const char *binary; +}; + +/* The list of possible machine types for various architectures, + as supported by QEMU - taken from 'qemu -M ?' for each arch */ +static const char *arch_info_x86_machines[] = { + "pc", "isapc" +}; +static const char *arch_info_mips_machines[] = { + "mips" +}; +static const char *arch_info_sparc_machines[] = { + "sun4m" +}; +static const char *arch_info_ppc_machines[] = { + "g3bw", "mac99", "prep" +}; + +/* The archicture tables for supported QEMU archs */ +static struct qemu_arch_info archs[] = { + { "i686", arch_info_x86_machines, "qemu" }, + { "x86_64", arch_info_x86_machines, "qemu-system-x86_64" }, + { "mips", arch_info_mips_machines, "qemu-system-mips" }, + { "mipsel", arch_info_mips_machines, "qemu-system-mipsel" }, + { "sparc", arch_info_sparc_machines, "qemu-system-sparc" }, + { "ppc", arch_info_ppc_machines, "qemu-system-ppc" }, +}; + +/* Return the default architecture if none is explicitly requested*/ +static const char *qemudDefaultArch(void) { + return archs[0].arch; +} + +/* Return the default machine type for a given architecture */ +static const char *qemudDefaultMachineForArch(const char *arch) { + int i; + + for (i = 0 ; i < (int)(sizeof(archs) / sizeof(struct qemu_arch_info)) ; i++) { + if (!strcmp(archs[i].arch, arch)) { + return archs[i].machines[0]; + } + } + + return NULL; +} + +/* Return the default binary name for a particular architecture */ +static const char *qemudDefaultBinaryForArch(const char *arch) { + int i; + + for (i = 0 ; i < (int)(sizeof(archs) / sizeof(struct qemu_arch_info)) ; i++) { + if (!strcmp(archs[i].arch, arch)) { + return archs[i].binary; + } + } + + return NULL; +} + +/* Find the fully qualified path to the binary for an architecture */ +static char *qemudLocateBinaryForArch(struct qemud_server *server, + int virtType, const char *arch) { + const char *name; + char *path; + + if (virtType == QEMUD_VIRT_KVM) + name = "qemu-kvm"; + else + name = qemudDefaultBinaryForArch(arch); + + if (!name) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot determin binary for architecture %s", arch); + return NULL; + } + + /* XXX lame. should actually use $PATH ... */ + path = malloc(strlen(name) + strlen("/usr/bin/") + 1); + if (!path) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "path"); + return NULL; + } + strcpy(path, "/usr/bin/"); + strcat(path, name); + return path; +} + +/* Parse the XML definition for a disk */ +static struct qemud_vm_disk_def *qemudParseDiskXML(struct qemud_server *server, + xmlNodePtr node) { + struct qemud_vm_disk_def *disk = calloc(1, sizeof(struct qemud_vm_disk_def)); + xmlNodePtr cur; + xmlChar *device = NULL; + xmlChar *source = NULL; + xmlChar *target = NULL; + xmlChar *type = NULL; + int typ = 0; + + if (!disk) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "disk"); + return NULL; + } + + type = xmlGetProp(node, BAD_CAST "type"); + if (type != NULL) { + if (xmlStrEqual(type, BAD_CAST "file")) + typ = QEMUD_DISK_FILE; + else if (xmlStrEqual(type, BAD_CAST "block")) + typ = QEMUD_DISK_BLOCK; + else { + typ = QEMUD_DISK_FILE; + } + xmlFree(type); + type = NULL; + } + + device = xmlGetProp(node, BAD_CAST "device"); + + cur = node->children; + while (cur != NULL) { + if (cur->type == XML_ELEMENT_NODE) { + if ((source == NULL) && + (xmlStrEqual(cur->name, BAD_CAST "source"))) { + + if (typ == QEMUD_DISK_FILE) + source = xmlGetProp(cur, BAD_CAST "file"); + else + source = xmlGetProp(cur, BAD_CAST "dev"); + } else if ((target == NULL) && + (xmlStrEqual(cur->name, BAD_CAST "target"))) { + target = xmlGetProp(cur, BAD_CAST "dev"); + } else if (xmlStrEqual(cur->name, BAD_CAST "readonly")) { + disk->readonly = 1; + } + } + cur = cur->next; + } + + if (source == NULL) { + qemudReportError(server, VIR_ERR_NO_SOURCE, target ? "%s" : NULL, target); + goto error; + } + if (target == NULL) { + qemudReportError(server, VIR_ERR_NO_TARGET, source ? "%s" : NULL, source); + goto error; + } + + if (device && + !strcmp((const char *)device, "floppy") && + strcmp((const char *)target, "fda") && + strcmp((const char *)target, "fdb")) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Invalid floppy device name: %s", target); + goto error; + } + + if (device && + !strcmp((const char *)device, "cdrom") && + strcmp((const char *)target, "hdc")) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Invalid cdrom device name: %s", target); + goto error; + } + + if (device && + !strcmp((const char *)device, "cdrom")) + disk->readonly = 1; + + if ((!device || !strcmp((const char *)device, "disk")) && + strcmp((const char *)target, "hda") && + strcmp((const char *)target, "hdb") && + strcmp((const char *)target, "hdc") && + strcmp((const char *)target, "hdd")) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Invalid harddisk device name: %s", target); + goto error; + } + + strncpy(disk->src, (const char *)source, NAME_MAX-1); + disk->src[NAME_MAX-1] = '\0'; + + strncpy(disk->dst, (const char *)target, NAME_MAX-1); + disk->dst[NAME_MAX-1] = '\0'; + disk->type = typ; + + if (!device) + disk->device = QEMUD_DISK_DISK; + else if (!strcmp((const char *)device, "disk")) + disk->device = QEMUD_DISK_DISK; + else if (!strcmp((const char *)device, "cdrom")) + disk->device = QEMUD_DISK_CDROM; + else if (!strcmp((const char *)device, "floppy")) + disk->device = QEMUD_DISK_FLOPPY; + else { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Invalid device type: %s", device); + goto error; + } + + xmlFree(device); + xmlFree(target); + xmlFree(source); + + return disk; + + error: + if (type) + xmlFree(type); + if (target) + xmlFree(target); + if (source) + xmlFree(source); + if (device) + xmlFree(device); + free(disk); + return NULL; +} + + +/* Parse the XML definition for a network interface */ +static struct qemud_vm_net_def *qemudParseInterfaceXML(struct qemud_server *server, + xmlNodePtr node) { + struct qemud_vm_net_def *net = calloc(1, sizeof(struct qemud_vm_net_def)); + xmlNodePtr cur; + xmlChar *macaddr = NULL; + xmlChar *type = NULL; + + if (!net) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "net"); + return NULL; + } + + net->type = QEMUD_NET_USER; + + type = xmlGetProp(node, BAD_CAST "type"); + if (type != NULL) { + if (xmlStrEqual(type, BAD_CAST "user")) + net->type = QEMUD_NET_USER; + else if (xmlStrEqual(type, BAD_CAST "tap")) + net->type = QEMUD_NET_TAP; + else if (xmlStrEqual(type, BAD_CAST "server")) + net->type = QEMUD_NET_SERVER; + else if (xmlStrEqual(type, BAD_CAST "client")) + net->type = QEMUD_NET_CLIENT; + else if (xmlStrEqual(type, BAD_CAST "mcast")) + net->type = QEMUD_NET_MCAST; + /* + else if (xmlStrEqual(type, BAD_CAST "vde")) + typ = QEMUD_NET_VDE; + */ + else + net->type = QEMUD_NET_USER; + xmlFree(type); + type = NULL; + } + + cur = node->children; + while (cur != NULL) { + if (cur->type == XML_ELEMENT_NODE) { + if ((macaddr == NULL) && + (xmlStrEqual(cur->name, BAD_CAST "mac"))) { + macaddr = xmlGetProp(cur, BAD_CAST "address"); + } + } + cur = cur->next; + } + + net->vlan = 0; + + if (macaddr) { + sscanf((const char *)macaddr, "%02x:%02x:%02x:%02x:%02x:%02x", + (unsigned int*)&net->mac[0], + (unsigned int*)&net->mac[1], + (unsigned int*)&net->mac[2], + (unsigned int*)&net->mac[3], + (unsigned int*)&net->mac[4], + (unsigned int*)&net->mac[5]); + + xmlFree(macaddr); + } + + return net; +} + + +/* + * Parses a libvirt XML definition of a guest, and populates the + * the qemud_vm struct with matching data about the guests config + */ +static int qemudParseXML(struct qemud_server *server, + xmlDocPtr xml, + struct qemud_vm *vm) { + xmlNodePtr root = NULL; + xmlChar *prop = NULL; + xmlXPathContextPtr ctxt = NULL; + xmlXPathObjectPtr obj = NULL; + char *conv = NULL; + int i; + + /* Prepare parser / xpath context */ + root = xmlDocGetRootElement(xml); + if ((root == NULL) || (!xmlStrEqual(root->name, BAD_CAST "domain"))) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "incorrect root element"); + goto error; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "xmlXPathContext"); + goto error; + } + + + /* Find out what type of QEMU virtualization to use */ + if (!(prop = xmlGetProp(root, BAD_CAST "type"))) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "missing domain type attribute"); + goto error; + } + + if (!strcmp((char *)prop, "qemu")) + vm->def.virtType = QEMUD_VIRT_QEMU; + else if (!strcmp((char *)prop, "kqemu")) + vm->def.virtType = QEMUD_VIRT_KQEMU; + else if (!strcmp((char *)prop, "kvm")) + vm->def.virtType = QEMUD_VIRT_KVM; + else { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "invalid domain type attribute"); + goto error; + } + free(prop); + prop = NULL; + + + /* Extract domain name */ + obj = xmlXPathEval(BAD_CAST "string(/domain/name[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + qemudReportError(server, VIR_ERR_NO_NAME, NULL); + goto error; + } + if (strlen((const char *)obj->stringval) >= (QEMUD_MAX_NAME_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "domain name length too long"); + goto error; + } + strcpy(vm->def.name, (const char *)obj->stringval); + xmlXPathFreeObject(obj); + + + /* Extract domain uuid */ + obj = xmlXPathEval(BAD_CAST "string(/domain/uuid[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + /* XXX auto-generate a UUID */ + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "missing uuid element"); + goto error; + } + if (qemudParseUUID((const char *)obj->stringval, vm->def.uuid) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "malformed uuid element"); + goto error; + } + xmlXPathFreeObject(obj); + + + /* Extract domain memory */ + obj = xmlXPathEval(BAD_CAST "string(/domain/memory[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "missing memory element"); + goto error; + } else { + conv = NULL; + vm->def.maxmem = strtoll((const char*)obj->stringval, &conv, 10); + if (conv == (const char*)obj->stringval) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "malformed memory information"); + goto error; + } + } + if (obj) + xmlXPathFreeObject(obj); + + + /* Extract domain memory */ + obj = xmlXPathEval(BAD_CAST "string(/domain/currentMemory[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + vm->def.memory = vm->def.maxmem; + } else { + conv = NULL; + vm->def.memory = strtoll((const char*)obj->stringval, &conv, 10); + if (vm->def.memory > vm->def.maxmem) + vm->def.memory = vm->def.maxmem; + if (conv == (const char*)obj->stringval) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "malformed memory information"); + goto error; + } + } + if (obj) + xmlXPathFreeObject(obj); + + /* Extract domain vcpu info */ + obj = xmlXPathEval(BAD_CAST "string(/domain/vcpu[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + vm->def.vcpus = 1; + } else { + conv = NULL; + vm->def.vcpus = strtoll((const char*)obj->stringval, &conv, 10); + if (conv == (const char*)obj->stringval) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "malformed vcpu information"); + goto error; + } + } + if (obj) + xmlXPathFreeObject(obj); + + /* See if ACPI feature is requested */ + obj = xmlXPathEval(BAD_CAST "/domain/features/acpi", ctxt); + if ((obj != NULL) && (obj->type == XPATH_NODESET) && + (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr == 1)) { + vm->def.features |= QEMUD_FEATURE_ACPI; + } + + /* Extract OS type info */ + obj = xmlXPathEval(BAD_CAST "string(/domain/os/type[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + qemudReportError(server, VIR_ERR_OS_TYPE, NULL); + goto error; + } + if (strcmp((const char *)obj->stringval, "hvm")) { + qemudReportError(server, VIR_ERR_OS_TYPE, "%s", obj->stringval); + goto error; + } + strcpy(vm->def.os.type, (const char *)obj->stringval); + xmlXPathFreeObject(obj); + + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/type[1]/@arch)", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + const char *defaultArch = qemudDefaultArch(); + if (strlen(defaultArch) >= (QEMUD_OS_TYPE_MAX_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "architecture type too long"); + goto error; + } + strcpy(vm->def.os.arch, defaultArch); + } else { + if (strlen((const char *)obj->stringval) >= (QEMUD_OS_TYPE_MAX_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "architecture type too long"); + goto error; + } + strcpy(vm->def.os.arch, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/type[1]/@machine)", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + const char *defaultMachine = qemudDefaultMachineForArch(vm->def.os.arch); + if (strlen(defaultMachine) >= (QEMUD_OS_MACHINE_MAX_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "machine type too long"); + goto error; + } + strcpy(vm->def.os.machine, defaultMachine); + } else { + if (strlen((const char *)obj->stringval) >= (QEMUD_OS_MACHINE_MAX_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "architecture type too long"); + goto error; + } + strcpy(vm->def.os.machine, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/kernel[1])", ctxt); + if ((obj != NULL) && (obj->type == XPATH_STRING) && + (obj->stringval != NULL) && (obj->stringval[0] != 0)) { + if (strlen((const char *)obj->stringval) >= (PATH_MAX-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "kernel path too long"); + goto error; + } + strcpy(vm->def.os.kernel, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/initrd[1])", ctxt); + if ((obj != NULL) && (obj->type == XPATH_STRING) && + (obj->stringval != NULL) && (obj->stringval[0] != 0)) { + if (strlen((const char *)obj->stringval) >= (PATH_MAX-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "initrd path too long"); + goto error; + } + strcpy(vm->def.os.initrd, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/cmdline[1])", ctxt); + if ((obj != NULL) && (obj->type == XPATH_STRING) && + (obj->stringval != NULL) && (obj->stringval[0] != 0)) { + if (strlen((const char *)obj->stringval) >= (PATH_MAX-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "cmdline arguments too long"); + goto error; + } + strcpy(vm->def.os.cmdline, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + + /* analysis of the disk devices */ + obj = xmlXPathEval(BAD_CAST "/domain/os/boot", ctxt); + if ((obj != NULL) && (obj->type == XPATH_NODESET) && + (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) { + for (i = 0; i < obj->nodesetval->nodeNr && i < QEMUD_MAX_BOOT_DEVS ; i++) { + prop = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "dev"); + if (!strcmp((char *)prop, "hd")) { + vm->def.os.bootDevs[vm->def.os.nBootDevs++] = QEMUD_BOOT_DISK; + } else if (!strcmp((char *)prop, "fd")) { + vm->def.os.bootDevs[vm->def.os.nBootDevs++] = QEMUD_BOOT_FLOPPY; + } else if (!strcmp((char *)prop, "cdrom")) { + vm->def.os.bootDevs[vm->def.os.nBootDevs++] = QEMUD_BOOT_CDROM; + } else if (!strcmp((char *)prop, "net")) { + vm->def.os.bootDevs[vm->def.os.nBootDevs++] = QEMUD_BOOT_NET; + } else { + goto error; + } + } + } + xmlXPathFreeObject(obj); + if (vm->def.os.nBootDevs == 0) { + vm->def.os.nBootDevs = 1; + vm->def.os.bootDevs[0] = QEMUD_BOOT_DISK; + } + + + obj = xmlXPathEval(BAD_CAST "string(/domain/devices/emulator[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + char *tmp = qemudLocateBinaryForArch(server, vm->def.virtType, vm->def.os.arch); + if (!tmp) { + goto error; + } + strcpy(vm->def.os.binary, tmp); + free(tmp); + } else { + if (strlen((const char *)obj->stringval) >= (PATH_MAX-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "emulator path too long"); + goto error; + } + strcpy(vm->def.os.binary, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + obj = xmlXPathEval(BAD_CAST "/domain/devices/graphics", ctxt); + if ((obj == NULL) || (obj->type != XPATH_NODESET) || + (obj->nodesetval == NULL) || (obj->nodesetval->nodeNr == 0)) { + vm->def.graphicsType = QEMUD_GRAPHICS_NONE; + } else { + prop = xmlGetProp(obj->nodesetval->nodeTab[0], BAD_CAST "type"); + if (!strcmp((char *)prop, "vnc")) { + vm->def.graphicsType = QEMUD_GRAPHICS_VNC; + prop = xmlGetProp(obj->nodesetval->nodeTab[0], BAD_CAST "port"); + if (prop) { + conv = NULL; + vm->def.vncPort = strtoll((const char*)prop, &conv, 10); + } else { + vm->def.vncPort = -1; + } + } else if (!strcmp((char *)prop, "sdl")) { + vm->def.graphicsType = QEMUD_GRAPHICS_SDL; + } else { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Unsupported graphics type %s", prop); + goto error; + } + } + + /* analysis of the disk devices */ + obj = xmlXPathEval(BAD_CAST "/domain/devices/disk", ctxt); + if ((obj != NULL) && (obj->type == XPATH_NODESET) && + (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) { + for (i = 0; i < obj->nodesetval->nodeNr; i++) { + struct qemud_vm_disk_def *disk; + if (!(disk = qemudParseDiskXML(server, obj->nodesetval->nodeTab[i]))) { + goto error; + } + vm->def.ndisks++; + disk->next = vm->def.disks; + vm->def.disks = disk; + } + } + xmlXPathFreeObject(obj); + + + /* analysis of the network devices */ + obj = xmlXPathEval(BAD_CAST "/domain/devices/interface", ctxt); + if ((obj != NULL) && (obj->type == XPATH_NODESET) && + (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) { + for (i = 0; i < obj->nodesetval->nodeNr; i++) { + struct qemud_vm_net_def *net; + if (!(net = qemudParseInterfaceXML(server, obj->nodesetval->nodeTab[i]))) { + goto error; + } + vm->def.nnets++; + net->next = vm->def.nets; + vm->def.nets = net; + } + } + xmlXPathFreeObject(obj); + xmlXPathFreeContext(ctxt); + + return 0; + + error: + if (prop) + free(prop); + if (obj) + xmlXPathFreeObject(obj); + if (ctxt) + xmlXPathFreeContext(ctxt); + return -1; +} + + +/* + * Constructs a argv suitable for launching qemu with config defined + * for a given virtual machine. + */ +int qemudBuildCommandLine(struct qemud_server *server, + struct qemud_vm *vm, + char ***argv, + int *argc) { + int n = -1, i; + char memory[50]; + char vcpus[50]; + char boot[QEMUD_MAX_BOOT_DEVS+1]; + struct qemud_vm_disk_def *disk = vm->def.disks; + struct qemud_vm_net_def *net = vm->def.nets; + + *argc = 1 + /* qemu */ + 2 + /* machine type */ + (vm->def.virtType == QEMUD_VIRT_QEMU ? 1 : 0) + /* Disable kqemu */ + 2 * vm->def.ndisks + /* disks*/ + (vm->def.nnets > 0 ? (4 * vm->def.nnets) : 2) + /* networks */ + 2 + /* memory*/ + 2 + /* cpus */ + 2 + /* boot device */ + 2 + /* monitor */ + (vm->def.features & QEMUD_FEATURE_ACPI ? 0 : 1) + /* acpi */ + (vm->def.os.kernel[0] ? 2 : 0) + /* kernel */ + (vm->def.os.initrd[0] ? 2 : 0) + /* initrd */ + (vm->def.os.cmdline[0] ? 2 : 0) + /* cmdline */ + (vm->def.graphicsType == QEMUD_GRAPHICS_VNC ? 2 : + (vm->def.graphicsType == QEMUD_GRAPHICS_SDL ? 0 : 1)); /* graphics */ + + sprintf(memory, "%d", vm->def.memory/1024); + sprintf(vcpus, "%d", vm->def.vcpus); + + if (!(*argv = malloc(sizeof(char *) * (*argc +1)))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.binary))) + goto no_memory; + if (!((*argv)[++n] = strdup("-M"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.machine))) + goto no_memory; + if (vm->def.virtType == QEMUD_VIRT_QEMU) { + if (!((*argv)[++n] = strdup("-no-kqemu"))) + goto no_memory; + } + if (!((*argv)[++n] = strdup("-m"))) + goto no_memory; + if (!((*argv)[++n] = strdup(memory))) + goto no_memory; + if (!((*argv)[++n] = strdup("-smp"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vcpus))) + goto no_memory; + + if (!((*argv)[++n] = strdup("-monitor"))) + goto no_memory; + if (!((*argv)[++n] = strdup("pty"))) + goto no_memory; + + if (!(vm->def.features & QEMUD_FEATURE_ACPI)) { + if (!((*argv)[++n] = strdup("-no-acpi"))) + goto no_memory; + } + + for (i = 0 ; i < vm->def.os.nBootDevs ; i++) { + switch (vm->def.os.bootDevs[i]) { + case QEMUD_BOOT_CDROM: + boot[i] = 'd'; + break; + case QEMUD_BOOT_FLOPPY: + boot[i] = 'a'; + break; + case QEMUD_BOOT_DISK: + boot[i] = 'c'; + break; + case QEMUD_BOOT_NET: + boot[i] = 'n'; + break; + default: + boot[i] = 'c'; + break; + } + } + boot[vm->def.os.nBootDevs] = '\0'; + if (!((*argv)[++n] = strdup("-boot"))) + goto no_memory; + if (!((*argv)[++n] = strdup(boot))) + goto no_memory; + + if (vm->def.os.kernel[0]) { + if (!((*argv)[++n] = strdup("-kernel"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.kernel))) + goto no_memory; + } + if (vm->def.os.initrd[0]) { + if (!((*argv)[++n] = strdup("-initrd"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.initrd))) + goto no_memory; + } + if (vm->def.os.cmdline[0]) { + if (!((*argv)[++n] = strdup("-append"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.cmdline))) + goto no_memory; + } + + while (disk) { + char dev[NAME_MAX]; + char file[PATH_MAX]; + if (!strcmp(disk->dst, "hdc") && + disk->device == QEMUD_DISK_CDROM) + snprintf(dev, NAME_MAX, "-%s", "cdrom"); + else + snprintf(dev, NAME_MAX, "-%s", disk->dst); + snprintf(file, PATH_MAX, "%s", disk->src); + + if (!((*argv)[++n] = strdup(dev))) + goto no_memory; + if (!((*argv)[++n] = strdup(file))) + goto no_memory; + + disk = disk->next; + } + + if (!net) { + if (!((*argv)[++n] = strdup("-net"))) + goto no_memory; + if (!((*argv)[++n] = strdup("none"))) + goto no_memory; + } else { + while (net) { + char nic[3+1+7+1+17+1]; + sprintf(nic, "nic,macaddr=%02x:%02x:%02x:%02x:%02x:%02x", + net->mac[0], net->mac[1], + net->mac[2], net->mac[3], + net->mac[4], net->mac[5]); + + if (!((*argv)[++n] = strdup("-net"))) + goto no_memory; + if (!((*argv)[++n] = strdup(nic))) + goto no_memory; + if (!((*argv)[++n] = strdup("-net"))) + goto no_memory; + /* XXX don't hardcode user */ + if (!((*argv)[++n] = strdup("user"))) + goto no_memory; + + net = net->next; + } + } + + if (vm->def.graphicsType == QEMUD_GRAPHICS_VNC) { + char port[10]; + snprintf(port, 10, "%d", vm->def.vncActivePort - 5900); + if (!((*argv)[++n] = strdup("-vnc"))) + goto no_memory; + if (!((*argv)[++n] = strdup(port))) + goto no_memory; + } else if (vm->def.graphicsType == QEMUD_GRAPHICS_NONE) { + if (!((*argv)[++n] = strdup("-nographic"))) + goto no_memory; + } else { + /* SDL is the default. no args needed */ + } + + (*argv)[++n] = NULL; + + return 0; + + no_memory: + if (argv) { + for (i = 0 ; i < n ; i++) + free(argv[i]); + free(argv); + } + qemudReportError(server, VIR_ERR_NO_MEMORY, "argv"); + return -1; +} + +/* Free all memory associated with a struct qemud_vm object */ +void qemudFreeVM(struct qemud_vm *vm) { + struct qemud_vm_disk_def *disk = vm->def.disks; + struct qemud_vm_net_def *net = vm->def.nets; + + while (disk) { + struct qemud_vm_disk_def *prev = disk; + disk = disk->next; + free(prev); + } + while (net) { + struct qemud_vm_net_def *prev = net; + net = net->next; + free(prev); + } + + free(vm); +} + +/* Build up a fully qualified path for a config file to be + * associated with a persistent guest */ +static +int qemudMakeConfigPath(struct qemud_server *server, + const char *name, + const char *ext, + char *buf, + unsigned int buflen) { + if ((strlen(server->configDir) + 1 + strlen(name) + (ext ? strlen(ext) : 0) + 1) > buflen) + return -1; + + strcpy(buf, server->configDir); + strcat(buf, "/"); + strcat(buf, name); + if (ext) + strcat(buf, ext); + return 0; +} + + +/* Save a guest's config data into a persistent file */ +static int qemudSaveConfig(struct qemud_server *server, + struct qemud_vm *vm) { + char *xml; + int fd = -1, ret = -1; + int towrite; + struct stat sb; + + if (!(xml = qemudGenerateXML(server, vm))) { + return -1; + } + + if (stat(server->configDir, &sb) < 0) { + if (errno == ENOENT) { + if (mkdir(server->configDir, 0700) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot create config directory %s", + server->configDir); + return -1; + } + } else { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot stat config directory %s", + server->configDir); + return -1; + } + } else if (!S_ISDIR(sb.st_mode)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "config directory %s is not a directory", + server->configDir); + return -1; + } + + if ((fd = open(vm->configFile, + O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR )) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot create config file %s", + vm->configFile); + goto cleanup; + } + + towrite = strlen(xml); + if (write(fd, xml, towrite) != towrite) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot write config file %s", + vm->configFile); + goto cleanup; + } + + if (close(fd) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot save config file %s", + vm->configFile); + goto cleanup; + } + + ret = 0; + + cleanup: + if (fd != -1) + close(fd); + + free(xml); + + return ret; +} + + +/* Create a qemud_vm instance, populating it based on the data + * in a libvirt XML document describing the guest */ +struct qemud_vm *qemudLoadConfigXML(struct qemud_server *server, + const char *file, + const char *doc, + int save) { + struct qemud_vm *vm = NULL; + xmlDocPtr xml; + + if (!(xml = xmlReadDoc(BAD_CAST doc, file ? file : "domain.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { + qemudReportError(server, VIR_ERR_XML_ERROR, NULL); + return NULL; + } + + if (!(vm = calloc(1, sizeof(struct qemud_vm)))) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "vm"); + return NULL; + } + + vm->stdout = -1; + vm->stderr = -1; + vm->monitor = -1; + vm->pid = -1; + vm->def.id = -1; + + if (qemudParseXML(server, xml, vm) < 0) { + xmlFreeDoc(xml); + qemudFreeVM(vm); + return NULL; + } + xmlFreeDoc(xml); + + if (qemudFindVMByUUID(server, vm->def.uuid)) { + qemudReportError(server, VIR_ERR_DOM_EXIST, vm->def.name); + qemudFreeVM(vm); + return NULL; + } + + if (qemudFindVMByName(server, vm->def.name)) { + qemudReportError(server, VIR_ERR_DOM_EXIST, vm->def.name); + qemudFreeVM(vm); + return NULL; + } + + if (file) { + strncpy(vm->configFile, file, PATH_MAX); + vm->configFile[PATH_MAX-1] = '\0'; + } else { + if (save) { + if (qemudMakeConfigPath(server, vm->def.name, ".xml", vm->configFile, PATH_MAX) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot construct config file path"); + qemudFreeVM(vm); + return NULL; + } + + if (qemudSaveConfig(server, vm) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot save config file for guest"); + qemudFreeVM(vm); + return NULL; + } + } else { + vm->configFile[0] = '\0'; + } + } + + return vm; +} + + +/* Load a guest from its persistent config file */ +static void qemudLoadConfig(struct qemud_server *server, + const char *file) { + FILE *fh; + struct stat st; + struct qemud_vm *vm; + char xml[QEMUD_MAX_XML_LEN]; + int ret; + + if (!(fh = fopen(file, "r"))) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot open guest config file %s", file); + return; + } + + if (fstat(fileno(fh), &st) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot stat config file %s", file); + goto cleanup; + } + + if (st.st_size >= QEMUD_MAX_XML_LEN) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "guest config too large in file %s", file); + goto cleanup; + } + + if ((ret = fread(xml, st.st_size, 1, fh)) != 1) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot read config file %s", file); + goto cleanup; + } + xml[st.st_size] = '\0'; + + if ((vm = qemudLoadConfigXML(server, file, xml, 1))) { + vm->next = server->inactivevms; + server->inactivevms = vm; + server->ninactivevms++; + } + + cleanup: + fclose(fh); +} + + +/* Scan for all guest config files */ +int qemudScanConfigs(struct qemud_server *server) { + DIR *dir; + struct dirent *entry; + + if (!(dir = opendir(server->configDir))) { + if (errno == ENOENT) + return 0; + return -1; + } + + while ((entry = readdir(dir))) { + char file[PATH_MAX]; + if (entry->d_name[0] == '.') + continue; + + if (qemudMakeConfigPath(server, entry->d_name, NULL, file, PATH_MAX) < 0) + continue; + + qemudLoadConfig(server, file); + } + + closedir(dir); + + return 0; +} + + +/* Simple grow-on-demand string buffer */ +/* XXX re-factor to shared library */ +struct qemudBuffer { + char *data; + int len; + int used; +}; + +static +int qemudBufferAdd(struct qemudBuffer *buf, const char *str) { + int need = strlen(str); + + if ((need+1) > (buf->len-buf->used)) { + return -1; + } + + memcpy(buf->data + buf->used, str, need+1); + buf->used += need; + + return 0; +} + + +static +int qemudBufferPrintf(struct qemudBuffer *buf, + const char *format, ...) { + int size, count; + va_list locarg, argptr; + + if ((format == NULL) || (buf == NULL)) { + return -1; + } + size = buf->len - buf->used - 1; + va_start(argptr, format); + va_copy(locarg, argptr); + + if ((count = vsnprintf(&buf->data[buf->used], + size, + format, + locarg)) >= size) { + return -1; + } + va_end(locarg); + buf->used += count; + + buf->data[buf->used] = '\0'; + return 0; +} + +/* Generate an XML document describing the guest's configuration */ +char *qemudGenerateXML(struct qemud_server *server, struct qemud_vm *vm) { + struct qemudBuffer buf; + unsigned char *uuid; + struct qemud_vm_disk_def *disk; + struct qemud_vm_net_def *net; + const char *type = NULL; + int n; + + buf.len = QEMUD_MAX_XML_LEN; + buf.used = 0; + buf.data = malloc(buf.len); + + switch (vm->def.virtType) { + case QEMUD_VIRT_QEMU: + type = "qemu"; + break; + case QEMUD_VIRT_KQEMU: + type = "kqemu"; + break; + case QEMUD_VIRT_KVM: + type = "kvm"; + break; + } + if (!type) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "unexpected domain type %d", vm->def.virtType); + goto cleanup; + } + + if (vm->def.id >= 0) { + if (qemudBufferPrintf(&buf, "\n", type, vm->def.id) < 0) + goto no_memory; + } else { + if (qemudBufferPrintf(&buf, "\n", type) < 0) + goto no_memory; + } + + if (qemudBufferPrintf(&buf, " %s\n", vm->def.name) < 0) + goto no_memory; + + uuid = vm->def.uuid; + if (qemudBufferPrintf(&buf, " %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]) < 0) + goto no_memory; + if (qemudBufferPrintf(&buf, " %d\n", vm->def.maxmem) < 0) + goto no_memory; + if (qemudBufferPrintf(&buf, " %d\n", vm->def.memory) < 0) + goto no_memory; + if (qemudBufferPrintf(&buf, " %d\n", vm->def.vcpus) < 0) + goto no_memory; + + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + if (vm->def.virtType == QEMUD_VIRT_QEMU) { + if (qemudBufferPrintf(&buf, " %s\n", + vm->def.os.arch, vm->def.os.machine, vm->def.os.type) < 0) + goto no_memory; + } else { + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.type) < 0) + goto no_memory; + } + + if (vm->def.os.kernel[0]) + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.kernel) < 0) + goto no_memory; + if (vm->def.os.initrd[0]) + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.initrd) < 0) + goto no_memory; + if (vm->def.os.cmdline[0]) + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.cmdline) < 0) + goto no_memory; + + if (vm->def.features & QEMUD_FEATURE_ACPI) { + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + } + + + for (n = 0 ; n < vm->def.os.nBootDevs ; n++) { + const char *boottype = "hd"; + switch (vm->def.os.bootDevs[n]) { + case QEMUD_BOOT_FLOPPY: + boottype = "fd"; + break; + case QEMUD_BOOT_DISK: + boottype = "hd"; + break; + case QEMUD_BOOT_CDROM: + boottype = "cdrom"; + break; + case QEMUD_BOOT_NET: + boottype = "net"; + break; + } + if (qemudBufferPrintf(&buf, " \n", boottype) < 0) + goto no_memory; + } + + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.binary) < 0) + goto no_memory; + + disk = vm->def.disks; + while (disk) { + const char *types[] = { + "block", + "file", + }; + const char *typeAttrs[] = { + "dev", + "file", + }; + const char *devices[] = { + "disk", + "cdrom", + "floppy", + }; + if (qemudBufferPrintf(&buf, " \n", + types[disk->type], devices[disk->device]) < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n", typeAttrs[disk->type], disk->src) < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n", disk->dst) < 0) + goto no_memory; + + if (disk->readonly) + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n") < 0) + goto no_memory; + + disk = disk->next; + } + + net = vm->def.nets; + disk = vm->def.disks; + while (disk) { + const char *types[] = { + "user", + "tap", + "server", + "client", + "mcast", + "vde", + }; + if (qemudBufferPrintf(&buf, " \n", + types[net->type]) < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n", + net->mac[0], net->mac[1], net->mac[2], + net->mac[3], net->mac[4], net->mac[5]) < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n") < 0) + goto no_memory; + + disk = disk->next; + } + + if (vm->def.graphicsType == QEMUD_GRAPHICS_VNC) { + if (vm->def.vncPort) { + qemudBufferPrintf(&buf, " \n", + vm->def.id == -1 ? vm->def.vncPort : vm->def.vncActivePort); + } else { + qemudBufferPrintf(&buf, " \n"); + } + } + + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + + if (qemudBufferAdd(&buf, "\n") < 0) + goto no_memory; + + return buf.data; + + no_memory: + qemudReportError(server, VIR_ERR_NO_MEMORY, "xml"); + cleanup: + free(buf.data); + return NULL; +} + + +int qemudDeleteConfigXML(struct qemud_server *server, struct qemud_vm *vm) { + if (!vm->configFile[0]) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "no config file for guest %s", vm->def.name); + return -1; + } + + if (unlink(vm->configFile) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot remove config for guest %s", vm->def.name); + return -1; + } + + vm->configFile[0] = '\0'; + + return 0; +} + + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/conf.h b/qemud/conf.h new file mode 100644 index 0000000000..1ca1f3ec82 --- /dev/null +++ b/qemud/conf.h @@ -0,0 +1,56 @@ +/* + * config.h: VM configuration management + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + +#ifndef __QEMUD_CONFIG_H +#define __QEMUD_CONFIG_H + +#include "internal.h" + +int qemudBuildCommandLine(struct qemud_server *server, + struct qemud_vm *vm, + char ***argv, + int *argc); + +void qemudFreeVM(struct qemud_vm *vm); +struct qemud_vm *qemudLoadConfigXML(struct qemud_server *server, + const char *file, + const char *doc, + int persist); +int qemudScanConfigs(struct qemud_server *server); +char *qemudGenerateXML(struct qemud_server *server, + struct qemud_vm *vm); + +int qemudDeleteConfigXML(struct qemud_server *server, + struct qemud_vm *vm); + + +#endif + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/config.c b/qemud/config.c new file mode 100644 index 0000000000..216c791a53 --- /dev/null +++ b/qemud/config.c @@ -0,0 +1,1429 @@ +/* + * config.c: VM configuration management + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "protocol.h" +#include "internal.h" +#include "config.h" +#include "driver.h" + +static int qemudParseUUID(const char *uuid, + unsigned char *rawuuid) { + const char *cur; + int i; + + /* + * do a liberal scan allowing '-' and ' ' anywhere between character + * pairs as long as there is 32 of them in the end. + */ + cur = uuid; + for (i = 0;i < 16;) { + rawuuid[i] = 0; + if (*cur == 0) + goto error; + if ((*cur == '-') || (*cur == ' ')) { + cur++; + continue; + } + if ((*cur >= '0') && (*cur <= '9')) + rawuuid[i] = *cur - '0'; + else if ((*cur >= 'a') && (*cur <= 'f')) + rawuuid[i] = *cur - 'a' + 10; + else if ((*cur >= 'A') && (*cur <= 'F')) + rawuuid[i] = *cur - 'A' + 10; + else + goto error; + rawuuid[i] *= 16; + cur++; + if (*cur == 0) + goto error; + if ((*cur >= '0') && (*cur <= '9')) + rawuuid[i] += *cur - '0'; + else if ((*cur >= 'a') && (*cur <= 'f')) + rawuuid[i] += *cur - 'a' + 10; + else if ((*cur >= 'A') && (*cur <= 'F')) + rawuuid[i] += *cur - 'A' + 10; + else + goto error; + i++; + cur++; + } + + return 0; + + error: + return -1; +} + + +struct qemu_arch_info { + const char *arch; + const char **machines; + const char *binary; +}; + +/* The list of possible machine types for various architectures, + as supported by QEMU - taken from 'qemu -M ?' for each arch */ +static const char *arch_info_x86_machines[] = { + "pc", "isapc" +}; +static const char *arch_info_mips_machines[] = { + "mips" +}; +static const char *arch_info_sparc_machines[] = { + "sun4m" +}; +static const char *arch_info_ppc_machines[] = { + "g3bw", "mac99", "prep" +}; + +/* The archicture tables for supported QEMU archs */ +static struct qemu_arch_info archs[] = { + { "i686", arch_info_x86_machines, "qemu" }, + { "x86_64", arch_info_x86_machines, "qemu-system-x86_64" }, + { "mips", arch_info_mips_machines, "qemu-system-mips" }, + { "mipsel", arch_info_mips_machines, "qemu-system-mipsel" }, + { "sparc", arch_info_sparc_machines, "qemu-system-sparc" }, + { "ppc", arch_info_ppc_machines, "qemu-system-ppc" }, +}; + +/* Return the default architecture if none is explicitly requested*/ +static const char *qemudDefaultArch(void) { + return archs[0].arch; +} + +/* Return the default machine type for a given architecture */ +static const char *qemudDefaultMachineForArch(const char *arch) { + int i; + + for (i = 0 ; i < (int)(sizeof(archs) / sizeof(struct qemu_arch_info)) ; i++) { + if (!strcmp(archs[i].arch, arch)) { + return archs[i].machines[0]; + } + } + + return NULL; +} + +/* Return the default binary name for a particular architecture */ +static const char *qemudDefaultBinaryForArch(const char *arch) { + int i; + + for (i = 0 ; i < (int)(sizeof(archs) / sizeof(struct qemu_arch_info)) ; i++) { + if (!strcmp(archs[i].arch, arch)) { + return archs[i].binary; + } + } + + return NULL; +} + +/* Find the fully qualified path to the binary for an architecture */ +static char *qemudLocateBinaryForArch(struct qemud_server *server, + int virtType, const char *arch) { + const char *name; + char *path; + + if (virtType == QEMUD_VIRT_KVM) + name = "qemu-kvm"; + else + name = qemudDefaultBinaryForArch(arch); + + if (!name) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot determin binary for architecture %s", arch); + return NULL; + } + + /* XXX lame. should actually use $PATH ... */ + path = malloc(strlen(name) + strlen("/usr/bin/") + 1); + if (!path) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "path"); + return NULL; + } + strcpy(path, "/usr/bin/"); + strcat(path, name); + return path; +} + +/* Parse the XML definition for a disk */ +static struct qemud_vm_disk_def *qemudParseDiskXML(struct qemud_server *server, + xmlNodePtr node) { + struct qemud_vm_disk_def *disk = calloc(1, sizeof(struct qemud_vm_disk_def)); + xmlNodePtr cur; + xmlChar *device = NULL; + xmlChar *source = NULL; + xmlChar *target = NULL; + xmlChar *type = NULL; + int typ = 0; + + if (!disk) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "disk"); + return NULL; + } + + type = xmlGetProp(node, BAD_CAST "type"); + if (type != NULL) { + if (xmlStrEqual(type, BAD_CAST "file")) + typ = QEMUD_DISK_FILE; + else if (xmlStrEqual(type, BAD_CAST "block")) + typ = QEMUD_DISK_BLOCK; + else { + typ = QEMUD_DISK_FILE; + } + xmlFree(type); + type = NULL; + } + + device = xmlGetProp(node, BAD_CAST "device"); + + cur = node->children; + while (cur != NULL) { + if (cur->type == XML_ELEMENT_NODE) { + if ((source == NULL) && + (xmlStrEqual(cur->name, BAD_CAST "source"))) { + + if (typ == QEMUD_DISK_FILE) + source = xmlGetProp(cur, BAD_CAST "file"); + else + source = xmlGetProp(cur, BAD_CAST "dev"); + } else if ((target == NULL) && + (xmlStrEqual(cur->name, BAD_CAST "target"))) { + target = xmlGetProp(cur, BAD_CAST "dev"); + } else if (xmlStrEqual(cur->name, BAD_CAST "readonly")) { + disk->readonly = 1; + } + } + cur = cur->next; + } + + if (source == NULL) { + qemudReportError(server, VIR_ERR_NO_SOURCE, target ? "%s" : NULL, target); + goto error; + } + if (target == NULL) { + qemudReportError(server, VIR_ERR_NO_TARGET, source ? "%s" : NULL, source); + goto error; + } + + if (device && + !strcmp((const char *)device, "floppy") && + strcmp((const char *)target, "fda") && + strcmp((const char *)target, "fdb")) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Invalid floppy device name: %s", target); + goto error; + } + + if (device && + !strcmp((const char *)device, "cdrom") && + strcmp((const char *)target, "hdc")) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Invalid cdrom device name: %s", target); + goto error; + } + + if (device && + !strcmp((const char *)device, "cdrom")) + disk->readonly = 1; + + if ((!device || !strcmp((const char *)device, "disk")) && + strcmp((const char *)target, "hda") && + strcmp((const char *)target, "hdb") && + strcmp((const char *)target, "hdc") && + strcmp((const char *)target, "hdd")) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Invalid harddisk device name: %s", target); + goto error; + } + + strncpy(disk->src, (const char *)source, NAME_MAX-1); + disk->src[NAME_MAX-1] = '\0'; + + strncpy(disk->dst, (const char *)target, NAME_MAX-1); + disk->dst[NAME_MAX-1] = '\0'; + disk->type = typ; + + if (!device) + disk->device = QEMUD_DISK_DISK; + else if (!strcmp((const char *)device, "disk")) + disk->device = QEMUD_DISK_DISK; + else if (!strcmp((const char *)device, "cdrom")) + disk->device = QEMUD_DISK_CDROM; + else if (!strcmp((const char *)device, "floppy")) + disk->device = QEMUD_DISK_FLOPPY; + else { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Invalid device type: %s", device); + goto error; + } + + xmlFree(device); + xmlFree(target); + xmlFree(source); + + return disk; + + error: + if (type) + xmlFree(type); + if (target) + xmlFree(target); + if (source) + xmlFree(source); + if (device) + xmlFree(device); + free(disk); + return NULL; +} + + +/* Parse the XML definition for a network interface */ +static struct qemud_vm_net_def *qemudParseInterfaceXML(struct qemud_server *server, + xmlNodePtr node) { + struct qemud_vm_net_def *net = calloc(1, sizeof(struct qemud_vm_net_def)); + xmlNodePtr cur; + xmlChar *macaddr = NULL; + xmlChar *type = NULL; + + if (!net) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "net"); + return NULL; + } + + net->type = QEMUD_NET_USER; + + type = xmlGetProp(node, BAD_CAST "type"); + if (type != NULL) { + if (xmlStrEqual(type, BAD_CAST "user")) + net->type = QEMUD_NET_USER; + else if (xmlStrEqual(type, BAD_CAST "tap")) + net->type = QEMUD_NET_TAP; + else if (xmlStrEqual(type, BAD_CAST "server")) + net->type = QEMUD_NET_SERVER; + else if (xmlStrEqual(type, BAD_CAST "client")) + net->type = QEMUD_NET_CLIENT; + else if (xmlStrEqual(type, BAD_CAST "mcast")) + net->type = QEMUD_NET_MCAST; + /* + else if (xmlStrEqual(type, BAD_CAST "vde")) + typ = QEMUD_NET_VDE; + */ + else + net->type = QEMUD_NET_USER; + xmlFree(type); + type = NULL; + } + + cur = node->children; + while (cur != NULL) { + if (cur->type == XML_ELEMENT_NODE) { + if ((macaddr == NULL) && + (xmlStrEqual(cur->name, BAD_CAST "mac"))) { + macaddr = xmlGetProp(cur, BAD_CAST "address"); + } + } + cur = cur->next; + } + + net->vlan = 0; + + if (macaddr) { + sscanf((const char *)macaddr, "%02x:%02x:%02x:%02x:%02x:%02x", + (unsigned int*)&net->mac[0], + (unsigned int*)&net->mac[1], + (unsigned int*)&net->mac[2], + (unsigned int*)&net->mac[3], + (unsigned int*)&net->mac[4], + (unsigned int*)&net->mac[5]); + + xmlFree(macaddr); + } + + return net; +} + + +/* + * Parses a libvirt XML definition of a guest, and populates the + * the qemud_vm struct with matching data about the guests config + */ +static int qemudParseXML(struct qemud_server *server, + xmlDocPtr xml, + struct qemud_vm *vm) { + xmlNodePtr root = NULL; + xmlChar *prop = NULL; + xmlXPathContextPtr ctxt = NULL; + xmlXPathObjectPtr obj = NULL; + char *conv = NULL; + int i; + + /* Prepare parser / xpath context */ + root = xmlDocGetRootElement(xml); + if ((root == NULL) || (!xmlStrEqual(root->name, BAD_CAST "domain"))) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "incorrect root element"); + goto error; + } + + ctxt = xmlXPathNewContext(xml); + if (ctxt == NULL) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "xmlXPathContext"); + goto error; + } + + + /* Find out what type of QEMU virtualization to use */ + if (!(prop = xmlGetProp(root, BAD_CAST "type"))) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "missing domain type attribute"); + goto error; + } + + if (!strcmp((char *)prop, "qemu")) + vm->def.virtType = QEMUD_VIRT_QEMU; + else if (!strcmp((char *)prop, "kqemu")) + vm->def.virtType = QEMUD_VIRT_KQEMU; + else if (!strcmp((char *)prop, "kvm")) + vm->def.virtType = QEMUD_VIRT_KVM; + else { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "invalid domain type attribute"); + goto error; + } + free(prop); + prop = NULL; + + + /* Extract domain name */ + obj = xmlXPathEval(BAD_CAST "string(/domain/name[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + qemudReportError(server, VIR_ERR_NO_NAME, NULL); + goto error; + } + if (strlen((const char *)obj->stringval) >= (QEMUD_MAX_NAME_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "domain name length too long"); + goto error; + } + strcpy(vm->def.name, (const char *)obj->stringval); + xmlXPathFreeObject(obj); + + + /* Extract domain uuid */ + obj = xmlXPathEval(BAD_CAST "string(/domain/uuid[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + /* XXX auto-generate a UUID */ + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "missing uuid element"); + goto error; + } + if (qemudParseUUID((const char *)obj->stringval, vm->def.uuid) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "malformed uuid element"); + goto error; + } + xmlXPathFreeObject(obj); + + + /* Extract domain memory */ + obj = xmlXPathEval(BAD_CAST "string(/domain/memory[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "missing memory element"); + goto error; + } else { + conv = NULL; + vm->def.maxmem = strtoll((const char*)obj->stringval, &conv, 10); + if (conv == (const char*)obj->stringval) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "malformed memory information"); + goto error; + } + } + if (obj) + xmlXPathFreeObject(obj); + + + /* Extract domain memory */ + obj = xmlXPathEval(BAD_CAST "string(/domain/currentMemory[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + vm->def.memory = vm->def.maxmem; + } else { + conv = NULL; + vm->def.memory = strtoll((const char*)obj->stringval, &conv, 10); + if (vm->def.memory > vm->def.maxmem) + vm->def.memory = vm->def.maxmem; + if (conv == (const char*)obj->stringval) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "malformed memory information"); + goto error; + } + } + if (obj) + xmlXPathFreeObject(obj); + + /* Extract domain vcpu info */ + obj = xmlXPathEval(BAD_CAST "string(/domain/vcpu[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + vm->def.vcpus = 1; + } else { + conv = NULL; + vm->def.vcpus = strtoll((const char*)obj->stringval, &conv, 10); + if (conv == (const char*)obj->stringval) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "malformed vcpu information"); + goto error; + } + } + if (obj) + xmlXPathFreeObject(obj); + + /* See if ACPI feature is requested */ + obj = xmlXPathEval(BAD_CAST "/domain/features/acpi", ctxt); + if ((obj != NULL) && (obj->type == XPATH_NODESET) && + (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr == 1)) { + vm->def.features |= QEMUD_FEATURE_ACPI; + } + + /* Extract OS type info */ + obj = xmlXPathEval(BAD_CAST "string(/domain/os/type[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + qemudReportError(server, VIR_ERR_OS_TYPE, NULL); + goto error; + } + if (strcmp((const char *)obj->stringval, "hvm")) { + qemudReportError(server, VIR_ERR_OS_TYPE, "%s", obj->stringval); + goto error; + } + strcpy(vm->def.os.type, (const char *)obj->stringval); + xmlXPathFreeObject(obj); + + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/type[1]/@arch)", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + const char *defaultArch = qemudDefaultArch(); + if (strlen(defaultArch) >= (QEMUD_OS_TYPE_MAX_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "architecture type too long"); + goto error; + } + strcpy(vm->def.os.arch, defaultArch); + } else { + if (strlen((const char *)obj->stringval) >= (QEMUD_OS_TYPE_MAX_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "architecture type too long"); + goto error; + } + strcpy(vm->def.os.arch, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/type[1]/@machine)", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + const char *defaultMachine = qemudDefaultMachineForArch(vm->def.os.arch); + if (strlen(defaultMachine) >= (QEMUD_OS_MACHINE_MAX_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "machine type too long"); + goto error; + } + strcpy(vm->def.os.machine, defaultMachine); + } else { + if (strlen((const char *)obj->stringval) >= (QEMUD_OS_MACHINE_MAX_LEN-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "architecture type too long"); + goto error; + } + strcpy(vm->def.os.machine, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/kernel[1])", ctxt); + if ((obj != NULL) && (obj->type == XPATH_STRING) && + (obj->stringval != NULL) && (obj->stringval[0] != 0)) { + if (strlen((const char *)obj->stringval) >= (PATH_MAX-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "kernel path too long"); + goto error; + } + strcpy(vm->def.os.kernel, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/initrd[1])", ctxt); + if ((obj != NULL) && (obj->type == XPATH_STRING) && + (obj->stringval != NULL) && (obj->stringval[0] != 0)) { + if (strlen((const char *)obj->stringval) >= (PATH_MAX-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "initrd path too long"); + goto error; + } + strcpy(vm->def.os.initrd, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + + obj = xmlXPathEval(BAD_CAST "string(/domain/os/cmdline[1])", ctxt); + if ((obj != NULL) && (obj->type == XPATH_STRING) && + (obj->stringval != NULL) && (obj->stringval[0] != 0)) { + if (strlen((const char *)obj->stringval) >= (PATH_MAX-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "cmdline arguments too long"); + goto error; + } + strcpy(vm->def.os.cmdline, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + + /* analysis of the disk devices */ + obj = xmlXPathEval(BAD_CAST "/domain/os/boot", ctxt); + if ((obj != NULL) && (obj->type == XPATH_NODESET) && + (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) { + for (i = 0; i < obj->nodesetval->nodeNr && i < QEMUD_MAX_BOOT_DEVS ; i++) { + prop = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "dev"); + if (!strcmp((char *)prop, "hd")) { + vm->def.os.bootDevs[vm->def.os.nBootDevs++] = QEMUD_BOOT_DISK; + } else if (!strcmp((char *)prop, "fd")) { + vm->def.os.bootDevs[vm->def.os.nBootDevs++] = QEMUD_BOOT_FLOPPY; + } else if (!strcmp((char *)prop, "cdrom")) { + vm->def.os.bootDevs[vm->def.os.nBootDevs++] = QEMUD_BOOT_CDROM; + } else if (!strcmp((char *)prop, "net")) { + vm->def.os.bootDevs[vm->def.os.nBootDevs++] = QEMUD_BOOT_NET; + } else { + goto error; + } + } + } + xmlXPathFreeObject(obj); + if (vm->def.os.nBootDevs == 0) { + vm->def.os.nBootDevs = 1; + vm->def.os.bootDevs[0] = QEMUD_BOOT_DISK; + } + + + obj = xmlXPathEval(BAD_CAST "string(/domain/devices/emulator[1])", ctxt); + if ((obj == NULL) || (obj->type != XPATH_STRING) || + (obj->stringval == NULL) || (obj->stringval[0] == 0)) { + char *tmp = qemudLocateBinaryForArch(server, vm->def.virtType, vm->def.os.arch); + if (!tmp) { + goto error; + } + strcpy(vm->def.os.binary, tmp); + free(tmp); + } else { + if (strlen((const char *)obj->stringval) >= (PATH_MAX-1)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "%s", "emulator path too long"); + goto error; + } + strcpy(vm->def.os.binary, (const char *)obj->stringval); + } + if (obj) + xmlXPathFreeObject(obj); + + obj = xmlXPathEval(BAD_CAST "/domain/devices/graphics", ctxt); + if ((obj == NULL) || (obj->type != XPATH_NODESET) || + (obj->nodesetval == NULL) || (obj->nodesetval->nodeNr == 0)) { + vm->def.graphicsType = QEMUD_GRAPHICS_NONE; + } else { + prop = xmlGetProp(obj->nodesetval->nodeTab[0], BAD_CAST "type"); + if (!strcmp((char *)prop, "vnc")) { + vm->def.graphicsType = QEMUD_GRAPHICS_VNC; + prop = xmlGetProp(obj->nodesetval->nodeTab[0], BAD_CAST "port"); + if (prop) { + conv = NULL; + vm->def.vncPort = strtoll((const char*)prop, &conv, 10); + } else { + vm->def.vncPort = -1; + } + } else if (!strcmp((char *)prop, "sdl")) { + vm->def.graphicsType = QEMUD_GRAPHICS_SDL; + } else { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "Unsupported graphics type %s", prop); + goto error; + } + } + + /* analysis of the disk devices */ + obj = xmlXPathEval(BAD_CAST "/domain/devices/disk", ctxt); + if ((obj != NULL) && (obj->type == XPATH_NODESET) && + (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) { + for (i = 0; i < obj->nodesetval->nodeNr; i++) { + struct qemud_vm_disk_def *disk; + if (!(disk = qemudParseDiskXML(server, obj->nodesetval->nodeTab[i]))) { + goto error; + } + vm->def.ndisks++; + disk->next = vm->def.disks; + vm->def.disks = disk; + } + } + xmlXPathFreeObject(obj); + + + /* analysis of the network devices */ + obj = xmlXPathEval(BAD_CAST "/domain/devices/interface", ctxt); + if ((obj != NULL) && (obj->type == XPATH_NODESET) && + (obj->nodesetval != NULL) && (obj->nodesetval->nodeNr >= 0)) { + for (i = 0; i < obj->nodesetval->nodeNr; i++) { + struct qemud_vm_net_def *net; + if (!(net = qemudParseInterfaceXML(server, obj->nodesetval->nodeTab[i]))) { + goto error; + } + vm->def.nnets++; + net->next = vm->def.nets; + vm->def.nets = net; + } + } + xmlXPathFreeObject(obj); + xmlXPathFreeContext(ctxt); + + return 0; + + error: + if (prop) + free(prop); + if (obj) + xmlXPathFreeObject(obj); + if (ctxt) + xmlXPathFreeContext(ctxt); + return -1; +} + + +/* + * Constructs a argv suitable for launching qemu with config defined + * for a given virtual machine. + */ +int qemudBuildCommandLine(struct qemud_server *server, + struct qemud_vm *vm, + char ***argv, + int *argc) { + int n = -1, i; + char memory[50]; + char vcpus[50]; + char boot[QEMUD_MAX_BOOT_DEVS+1]; + struct qemud_vm_disk_def *disk = vm->def.disks; + struct qemud_vm_net_def *net = vm->def.nets; + + *argc = 1 + /* qemu */ + 2 + /* machine type */ + (vm->def.virtType == QEMUD_VIRT_QEMU ? 1 : 0) + /* Disable kqemu */ + 2 * vm->def.ndisks + /* disks*/ + (vm->def.nnets > 0 ? (4 * vm->def.nnets) : 2) + /* networks */ + 2 + /* memory*/ + 2 + /* cpus */ + 2 + /* boot device */ + 2 + /* monitor */ + (vm->def.features & QEMUD_FEATURE_ACPI ? 0 : 1) + /* acpi */ + (vm->def.os.kernel[0] ? 2 : 0) + /* kernel */ + (vm->def.os.initrd[0] ? 2 : 0) + /* initrd */ + (vm->def.os.cmdline[0] ? 2 : 0) + /* cmdline */ + (vm->def.graphicsType == QEMUD_GRAPHICS_VNC ? 2 : + (vm->def.graphicsType == QEMUD_GRAPHICS_SDL ? 0 : 1)); /* graphics */ + + sprintf(memory, "%d", vm->def.memory/1024); + sprintf(vcpus, "%d", vm->def.vcpus); + + if (!(*argv = malloc(sizeof(char *) * (*argc +1)))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.binary))) + goto no_memory; + if (!((*argv)[++n] = strdup("-M"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.machine))) + goto no_memory; + if (vm->def.virtType == QEMUD_VIRT_QEMU) { + if (!((*argv)[++n] = strdup("-no-kqemu"))) + goto no_memory; + } + if (!((*argv)[++n] = strdup("-m"))) + goto no_memory; + if (!((*argv)[++n] = strdup(memory))) + goto no_memory; + if (!((*argv)[++n] = strdup("-smp"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vcpus))) + goto no_memory; + + if (!((*argv)[++n] = strdup("-monitor"))) + goto no_memory; + if (!((*argv)[++n] = strdup("pty"))) + goto no_memory; + + if (!(vm->def.features & QEMUD_FEATURE_ACPI)) { + if (!((*argv)[++n] = strdup("-no-acpi"))) + goto no_memory; + } + + for (i = 0 ; i < vm->def.os.nBootDevs ; i++) { + switch (vm->def.os.bootDevs[i]) { + case QEMUD_BOOT_CDROM: + boot[i] = 'd'; + break; + case QEMUD_BOOT_FLOPPY: + boot[i] = 'a'; + break; + case QEMUD_BOOT_DISK: + boot[i] = 'c'; + break; + case QEMUD_BOOT_NET: + boot[i] = 'n'; + break; + default: + boot[i] = 'c'; + break; + } + } + boot[vm->def.os.nBootDevs] = '\0'; + if (!((*argv)[++n] = strdup("-boot"))) + goto no_memory; + if (!((*argv)[++n] = strdup(boot))) + goto no_memory; + + if (vm->def.os.kernel[0]) { + if (!((*argv)[++n] = strdup("-kernel"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.kernel))) + goto no_memory; + } + if (vm->def.os.initrd[0]) { + if (!((*argv)[++n] = strdup("-initrd"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.initrd))) + goto no_memory; + } + if (vm->def.os.cmdline[0]) { + if (!((*argv)[++n] = strdup("-append"))) + goto no_memory; + if (!((*argv)[++n] = strdup(vm->def.os.cmdline))) + goto no_memory; + } + + while (disk) { + char dev[NAME_MAX]; + char file[PATH_MAX]; + if (!strcmp(disk->dst, "hdc") && + disk->device == QEMUD_DISK_CDROM) + snprintf(dev, NAME_MAX, "-%s", "cdrom"); + else + snprintf(dev, NAME_MAX, "-%s", disk->dst); + snprintf(file, PATH_MAX, "%s", disk->src); + + if (!((*argv)[++n] = strdup(dev))) + goto no_memory; + if (!((*argv)[++n] = strdup(file))) + goto no_memory; + + disk = disk->next; + } + + if (!net) { + if (!((*argv)[++n] = strdup("-net"))) + goto no_memory; + if (!((*argv)[++n] = strdup("none"))) + goto no_memory; + } else { + while (net) { + char nic[3+1+7+1+17+1]; + sprintf(nic, "nic,macaddr=%02x:%02x:%02x:%02x:%02x:%02x", + net->mac[0], net->mac[1], + net->mac[2], net->mac[3], + net->mac[4], net->mac[5]); + + if (!((*argv)[++n] = strdup("-net"))) + goto no_memory; + if (!((*argv)[++n] = strdup(nic))) + goto no_memory; + if (!((*argv)[++n] = strdup("-net"))) + goto no_memory; + /* XXX don't hardcode user */ + if (!((*argv)[++n] = strdup("user"))) + goto no_memory; + + net = net->next; + } + } + + if (vm->def.graphicsType == QEMUD_GRAPHICS_VNC) { + char port[10]; + snprintf(port, 10, "%d", vm->def.vncActivePort - 5900); + if (!((*argv)[++n] = strdup("-vnc"))) + goto no_memory; + if (!((*argv)[++n] = strdup(port))) + goto no_memory; + } else if (vm->def.graphicsType == QEMUD_GRAPHICS_NONE) { + if (!((*argv)[++n] = strdup("-nographic"))) + goto no_memory; + } else { + /* SDL is the default. no args needed */ + } + + (*argv)[++n] = NULL; + + return 0; + + no_memory: + if (argv) { + for (i = 0 ; i < n ; i++) + free(argv[i]); + free(argv); + } + qemudReportError(server, VIR_ERR_NO_MEMORY, "argv"); + return -1; +} + +/* Free all memory associated with a struct qemud_vm object */ +void qemudFreeVM(struct qemud_vm *vm) { + struct qemud_vm_disk_def *disk = vm->def.disks; + struct qemud_vm_net_def *net = vm->def.nets; + + while (disk) { + struct qemud_vm_disk_def *prev = disk; + disk = disk->next; + free(prev); + } + while (net) { + struct qemud_vm_net_def *prev = net; + net = net->next; + free(prev); + } + + free(vm); +} + +/* Build up a fully qualified path for a config file to be + * associated with a persistent guest */ +static +int qemudMakeConfigPath(struct qemud_server *server, + const char *name, + const char *ext, + char *buf, + unsigned int buflen) { + if ((strlen(server->configDir) + 1 + strlen(name) + (ext ? strlen(ext) : 0) + 1) > buflen) + return -1; + + strcpy(buf, server->configDir); + strcat(buf, "/"); + strcat(buf, name); + if (ext) + strcat(buf, ext); + return 0; +} + + +/* Save a guest's config data into a persistent file */ +static int qemudSaveConfig(struct qemud_server *server, + struct qemud_vm *vm) { + char *xml; + int fd = -1, ret = -1; + int towrite; + struct stat sb; + + if (!(xml = qemudGenerateXML(server, vm))) { + return -1; + } + + if (stat(server->configDir, &sb) < 0) { + if (errno == ENOENT) { + if (mkdir(server->configDir, 0700) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot create config directory %s", + server->configDir); + return -1; + } + } else { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot stat config directory %s", + server->configDir); + return -1; + } + } else if (!S_ISDIR(sb.st_mode)) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "config directory %s is not a directory", + server->configDir); + return -1; + } + + if ((fd = open(vm->configFile, + O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR )) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot create config file %s", + vm->configFile); + goto cleanup; + } + + towrite = strlen(xml); + if (write(fd, xml, towrite) != towrite) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot write config file %s", + vm->configFile); + goto cleanup; + } + + if (close(fd) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot save config file %s", + vm->configFile); + goto cleanup; + } + + ret = 0; + + cleanup: + if (fd != -1) + close(fd); + + free(xml); + + return ret; +} + + +/* Create a qemud_vm instance, populating it based on the data + * in a libvirt XML document describing the guest */ +struct qemud_vm *qemudLoadConfigXML(struct qemud_server *server, + const char *file, + const char *doc, + int save) { + struct qemud_vm *vm = NULL; + xmlDocPtr xml; + + if (!(xml = xmlReadDoc(BAD_CAST doc, file ? file : "domain.xml", NULL, + XML_PARSE_NOENT | XML_PARSE_NONET | + XML_PARSE_NOERROR | XML_PARSE_NOWARNING))) { + qemudReportError(server, VIR_ERR_XML_ERROR, NULL); + return NULL; + } + + if (!(vm = calloc(1, sizeof(struct qemud_vm)))) { + qemudReportError(server, VIR_ERR_NO_MEMORY, "vm"); + return NULL; + } + + vm->stdout = -1; + vm->stderr = -1; + vm->monitor = -1; + vm->pid = -1; + vm->def.id = -1; + + if (qemudParseXML(server, xml, vm) < 0) { + xmlFreeDoc(xml); + qemudFreeVM(vm); + return NULL; + } + xmlFreeDoc(xml); + + if (qemudFindVMByUUID(server, vm->def.uuid)) { + qemudReportError(server, VIR_ERR_DOM_EXIST, vm->def.name); + qemudFreeVM(vm); + return NULL; + } + + if (qemudFindVMByName(server, vm->def.name)) { + qemudReportError(server, VIR_ERR_DOM_EXIST, vm->def.name); + qemudFreeVM(vm); + return NULL; + } + + if (file) { + strncpy(vm->configFile, file, PATH_MAX); + vm->configFile[PATH_MAX-1] = '\0'; + } else { + if (save) { + if (qemudMakeConfigPath(server, vm->def.name, ".xml", vm->configFile, PATH_MAX) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot construct config file path"); + qemudFreeVM(vm); + return NULL; + } + + if (qemudSaveConfig(server, vm) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, + "cannot save config file for guest"); + qemudFreeVM(vm); + return NULL; + } + } else { + vm->configFile[0] = '\0'; + } + } + + return vm; +} + + +/* Load a guest from its persistent config file */ +static void qemudLoadConfig(struct qemud_server *server, + const char *file) { + FILE *fh; + struct stat st; + struct qemud_vm *vm; + char xml[QEMUD_MAX_XML_LEN]; + int ret; + + if (!(fh = fopen(file, "r"))) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot open guest config file %s", file); + return; + } + + if (fstat(fileno(fh), &st) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot stat config file %s", file); + goto cleanup; + } + + if (st.st_size >= QEMUD_MAX_XML_LEN) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "guest config too large in file %s", file); + goto cleanup; + } + + if ((ret = fread(xml, st.st_size, 1, fh)) != 1) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot read config file %s", file); + goto cleanup; + } + xml[st.st_size] = '\0'; + + if ((vm = qemudLoadConfigXML(server, file, xml, 1))) { + vm->next = server->inactivevms; + server->inactivevms = vm; + server->ninactivevms++; + } + + cleanup: + fclose(fh); +} + + +/* Scan for all guest config files */ +int qemudScanConfigs(struct qemud_server *server) { + DIR *dir; + struct dirent *entry; + + if (!(dir = opendir(server->configDir))) { + if (errno == ENOENT) + return 0; + return -1; + } + + while ((entry = readdir(dir))) { + char file[PATH_MAX]; + if (entry->d_name[0] == '.') + continue; + + if (qemudMakeConfigPath(server, entry->d_name, NULL, file, PATH_MAX) < 0) + continue; + + qemudLoadConfig(server, file); + } + + closedir(dir); + + return 0; +} + + +/* Simple grow-on-demand string buffer */ +/* XXX re-factor to shared library */ +struct qemudBuffer { + char *data; + int len; + int used; +}; + +static +int qemudBufferAdd(struct qemudBuffer *buf, const char *str) { + int need = strlen(str); + + if ((need+1) > (buf->len-buf->used)) { + return -1; + } + + memcpy(buf->data + buf->used, str, need+1); + buf->used += need; + + return 0; +} + + +static +int qemudBufferPrintf(struct qemudBuffer *buf, + const char *format, ...) { + int size, count; + va_list locarg, argptr; + + if ((format == NULL) || (buf == NULL)) { + return -1; + } + size = buf->len - buf->used - 1; + va_start(argptr, format); + va_copy(locarg, argptr); + + if ((count = vsnprintf(&buf->data[buf->used], + size, + format, + locarg)) >= size) { + return -1; + } + va_end(locarg); + buf->used += count; + + buf->data[buf->used] = '\0'; + return 0; +} + +/* Generate an XML document describing the guest's configuration */ +char *qemudGenerateXML(struct qemud_server *server, struct qemud_vm *vm) { + struct qemudBuffer buf; + unsigned char *uuid; + struct qemud_vm_disk_def *disk; + struct qemud_vm_net_def *net; + const char *type = NULL; + int n; + + buf.len = QEMUD_MAX_XML_LEN; + buf.used = 0; + buf.data = malloc(buf.len); + + switch (vm->def.virtType) { + case QEMUD_VIRT_QEMU: + type = "qemu"; + break; + case QEMUD_VIRT_KQEMU: + type = "kqemu"; + break; + case QEMUD_VIRT_KVM: + type = "kvm"; + break; + } + if (!type) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "unexpected domain type %d", vm->def.virtType); + goto cleanup; + } + + if (vm->def.id >= 0) { + if (qemudBufferPrintf(&buf, "\n", type, vm->def.id) < 0) + goto no_memory; + } else { + if (qemudBufferPrintf(&buf, "\n", type) < 0) + goto no_memory; + } + + if (qemudBufferPrintf(&buf, " %s\n", vm->def.name) < 0) + goto no_memory; + + uuid = vm->def.uuid; + if (qemudBufferPrintf(&buf, " %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]) < 0) + goto no_memory; + if (qemudBufferPrintf(&buf, " %d\n", vm->def.maxmem) < 0) + goto no_memory; + if (qemudBufferPrintf(&buf, " %d\n", vm->def.memory) < 0) + goto no_memory; + if (qemudBufferPrintf(&buf, " %d\n", vm->def.vcpus) < 0) + goto no_memory; + + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + if (vm->def.virtType == QEMUD_VIRT_QEMU) { + if (qemudBufferPrintf(&buf, " %s\n", + vm->def.os.arch, vm->def.os.machine, vm->def.os.type) < 0) + goto no_memory; + } else { + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.type) < 0) + goto no_memory; + } + + if (vm->def.os.kernel[0]) + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.kernel) < 0) + goto no_memory; + if (vm->def.os.initrd[0]) + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.initrd) < 0) + goto no_memory; + if (vm->def.os.cmdline[0]) + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.cmdline) < 0) + goto no_memory; + + if (vm->def.features & QEMUD_FEATURE_ACPI) { + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + } + + + for (n = 0 ; n < vm->def.os.nBootDevs ; n++) { + const char *boottype = "hd"; + switch (vm->def.os.bootDevs[n]) { + case QEMUD_BOOT_FLOPPY: + boottype = "fd"; + break; + case QEMUD_BOOT_DISK: + boottype = "hd"; + break; + case QEMUD_BOOT_CDROM: + boottype = "cdrom"; + break; + case QEMUD_BOOT_NET: + boottype = "net"; + break; + } + if (qemudBufferPrintf(&buf, " \n", boottype) < 0) + goto no_memory; + } + + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " %s\n", vm->def.os.binary) < 0) + goto no_memory; + + disk = vm->def.disks; + while (disk) { + const char *types[] = { + "block", + "file", + }; + const char *typeAttrs[] = { + "dev", + "file", + }; + const char *devices[] = { + "disk", + "cdrom", + "floppy", + }; + if (qemudBufferPrintf(&buf, " \n", + types[disk->type], devices[disk->device]) < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n", typeAttrs[disk->type], disk->src) < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n", disk->dst) < 0) + goto no_memory; + + if (disk->readonly) + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n") < 0) + goto no_memory; + + disk = disk->next; + } + + net = vm->def.nets; + disk = vm->def.disks; + while (disk) { + const char *types[] = { + "user", + "tap", + "server", + "client", + "mcast", + "vde", + }; + if (qemudBufferPrintf(&buf, " \n", + types[net->type]) < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n", + net->mac[0], net->mac[1], net->mac[2], + net->mac[3], net->mac[4], net->mac[5]) < 0) + goto no_memory; + + if (qemudBufferPrintf(&buf, " \n") < 0) + goto no_memory; + + disk = disk->next; + } + + if (vm->def.graphicsType == QEMUD_GRAPHICS_VNC) { + if (vm->def.vncPort) { + qemudBufferPrintf(&buf, " \n", + vm->def.id == -1 ? vm->def.vncPort : vm->def.vncActivePort); + } else { + qemudBufferPrintf(&buf, " \n"); + } + } + + if (qemudBufferAdd(&buf, " \n") < 0) + goto no_memory; + + + if (qemudBufferAdd(&buf, "\n") < 0) + goto no_memory; + + return buf.data; + + no_memory: + qemudReportError(server, VIR_ERR_NO_MEMORY, "xml"); + cleanup: + free(buf.data); + return NULL; +} + + +int qemudDeleteConfigXML(struct qemud_server *server, struct qemud_vm *vm) { + if (!vm->configFile[0]) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "no config file for guest %s", vm->def.name); + return -1; + } + + if (unlink(vm->configFile) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot remove config for guest %s", vm->def.name); + return -1; + } + + vm->configFile[0] = '\0'; + + return 0; +} + + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/config.h b/qemud/config.h new file mode 100644 index 0000000000..1ca1f3ec82 --- /dev/null +++ b/qemud/config.h @@ -0,0 +1,56 @@ +/* + * config.h: VM configuration management + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + +#ifndef __QEMUD_CONFIG_H +#define __QEMUD_CONFIG_H + +#include "internal.h" + +int qemudBuildCommandLine(struct qemud_server *server, + struct qemud_vm *vm, + char ***argv, + int *argc); + +void qemudFreeVM(struct qemud_vm *vm); +struct qemud_vm *qemudLoadConfigXML(struct qemud_server *server, + const char *file, + const char *doc, + int persist); +int qemudScanConfigs(struct qemud_server *server); +char *qemudGenerateXML(struct qemud_server *server, + struct qemud_vm *vm); + +int qemudDeleteConfigXML(struct qemud_server *server, + struct qemud_vm *vm); + + +#endif + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/dispatch.c b/qemud/dispatch.c new file mode 100644 index 0000000000..f7fad2c7e5 --- /dev/null +++ b/qemud/dispatch.c @@ -0,0 +1,580 @@ +/* + * dispatch.c: (De-)marshall wire messages to driver functions. + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + +#include +#include +#include +#include +#include +#include + +#include "internal.h" +#include "driver.h" +#include "dispatch.h" + + +static int qemudDispatchFailure(struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client ATTRIBUTE_UNUSED, + struct qemud_packet *out) { + out->header.type = QEMUD_PKT_FAILURE; + out->header.dataSize = sizeof(out->data.failureReply); + out->data.failureReply.code = server->errorCode; + strcpy(out->data.failureReply.message, server->errorMessage); + return 0; +} + + +static int qemudDispatchGetVersion(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != 0) + return -1; + + int version = qemudGetVersion(server); + if (version < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_GET_VERSION; + out->header.dataSize = sizeof(out->data.getVersionReply); + out->data.getVersionReply.version = version; + } + return 0; +} + +static int qemudDispatchGetNodeInfo(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + struct utsname info; + + if (in->header.dataSize != 0) + return -1; + + if (uname(&info) < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + return 0; + } + + if (qemudGetCPUInfo(&out->data.getNodeInfoReply.cpus, + &out->data.getNodeInfoReply.mhz, + &out->data.getNodeInfoReply.nodes, + &out->data.getNodeInfoReply.sockets, + &out->data.getNodeInfoReply.cores, + &out->data.getNodeInfoReply.threads) < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + return 0; + } + if (qemudGetMemInfo(&out->data.getNodeInfoReply.memory) < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + return 0; + } + + out->header.type = QEMUD_PKT_GET_NODEINFO; + out->header.dataSize = sizeof(out->data.getNodeInfoReply); + strncpy(out->data.getNodeInfoReply.model, info.machine, sizeof(out->data.getNodeInfoReply.model)); + out->data.getNodeInfoReply.model[sizeof(out->data.getNodeInfoReply.model)-1] = '\0'; + + return 0; +} + +static int qemudDispatchListDomains(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + int i, ndomains, domains[QEMUD_MAX_NUM_DOMAINS]; + if (in->header.dataSize != 0) + return -1; + + ndomains = qemudListDomains(server, + domains, + QEMUD_MAX_NUM_DOMAINS); + if (ndomains < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_LIST_DOMAINS; + out->header.dataSize = sizeof(out->data.listDomainsReply); + for (i = 0 ; i < ndomains ; i++) { + out->data.listDomainsReply.domains[i] = domains[i]; + } + out->data.listDomainsReply.numDomains = ndomains; + } + return 0; +} + +static int qemudDispatchNumDomains(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != 0) + return -1; + + int ndomains = qemudNumDomains(server); + if (ndomains < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_NUM_DOMAINS; + out->header.dataSize = sizeof(out->data.numDomainsReply); + out->data.numDomainsReply.numDomains = ndomains; + } + return 0; +} + +static int qemudDispatchDomainCreate(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainCreateRequest)) + return -1; + + in->data.domainCreateRequest.xml[QEMUD_MAX_XML_LEN-1] ='\0'; + + struct qemud_vm *vm = qemudDomainCreate(server, in->data.domainCreateRequest.xml); + if (!vm) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_CREATE; + out->header.dataSize = sizeof(out->data.domainCreateReply); + out->data.domainCreateReply.id = vm->def.id; + memcpy(out->data.domainCreateReply.uuid, vm->def.uuid, QEMUD_UUID_RAW_LEN); + strncpy(out->data.domainCreateReply.name, vm->def.name, QEMUD_MAX_NAME_LEN-1); + out->data.domainCreateReply.name[QEMUD_MAX_NAME_LEN-1] = '\0'; + } + return 0; +} + +static int qemudDispatchDomainLookupByID(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainLookupByIDRequest)) + return -1; + + struct qemud_vm *vm = qemudFindVMByID(server, in->data.domainLookupByIDRequest.id); + if (!vm) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_LOOKUP_BY_ID; + out->header.dataSize = sizeof(out->data.domainLookupByIDReply); + memcpy(out->data.domainLookupByIDReply.uuid, vm->def.uuid, QEMUD_UUID_RAW_LEN); + strncpy(out->data.domainLookupByIDReply.name, vm->def.name, QEMUD_MAX_NAME_LEN-1); + out->data.domainLookupByIDReply.name[QEMUD_MAX_NAME_LEN-1] = '\0'; + } + return 0; +} + +static int qemudDispatchDomainLookupByUUID(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainLookupByUUIDRequest)) + return -1; + + struct qemud_vm *vm = qemudFindVMByUUID(server, in->data.domainLookupByUUIDRequest.uuid); + if (!vm) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_LOOKUP_BY_UUID; + out->header.dataSize = sizeof(out->data.domainLookupByUUIDReply); + out->data.domainLookupByUUIDReply.id = vm->def.id; + strncpy(out->data.domainLookupByUUIDReply.name, vm->def.name, QEMUD_MAX_NAME_LEN-1); + out->data.domainLookupByUUIDReply.name[QEMUD_MAX_NAME_LEN-1] = '\0'; + } + return 0; +} + +static int qemudDispatchDomainLookupByName(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainLookupByNameRequest)) + return -1; + + /* Paranoia NULL termination */ + in->data.domainLookupByNameRequest.name[QEMUD_MAX_NAME_LEN-1] = '\0'; + struct qemud_vm *vm = qemudFindVMByName(server, in->data.domainLookupByNameRequest.name); + if (!vm) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_LOOKUP_BY_NAME; + out->header.dataSize = sizeof(out->data.domainLookupByNameReply); + out->data.domainLookupByNameReply.id = vm->def.id; + memcpy(out->data.domainLookupByNameReply.uuid, vm->def.uuid, QEMUD_UUID_RAW_LEN); + } + return 0; +} + +static int qemudDispatchDomainSuspend(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainSuspendRequest)) + return -1; + + int ret = qemudDomainSuspend(server, in->data.domainSuspendRequest.id); + if (ret < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_SUSPEND; + out->header.dataSize = 0; + } + return 0; +} + +static int qemudDispatchDomainResume(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainResumeRequest)) + return -1; + + int ret = qemudDomainResume(server, in->data.domainResumeRequest.id); + if (ret < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_RESUME; + out->header.dataSize = 0; + } + return 0; +} + +static int qemudDispatchDomainDestroy(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainDestroyRequest)) + return -1; + + int ret = qemudDomainDestroy(server, in->data.domainDestroyRequest.id); + if (ret < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_DESTROY; + out->header.dataSize = 0; + } + return 0; +} + +static int qemudDispatchDomainGetInfo(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + int runstate; + unsigned long long cpuTime; + unsigned long memory; + unsigned long maxmem; + unsigned int nrVirtCpu; + + if (in->header.dataSize != sizeof(in->data.domainGetInfoRequest)) + return -1; + + int ret = qemudDomainGetInfo(server, in->data.domainGetInfoRequest.uuid, + &runstate, + &cpuTime, + &maxmem, + &memory, + &nrVirtCpu); + if (ret < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_GET_INFO; + out->header.dataSize = sizeof(out->data.domainGetInfoReply); + out->data.domainGetInfoReply.runstate = runstate; + out->data.domainGetInfoReply.cpuTime = cpuTime; + out->data.domainGetInfoReply.maxmem = maxmem; + out->data.domainGetInfoReply.memory = memory; + out->data.domainGetInfoReply.nrVirtCpu = nrVirtCpu; + } + return 0; +} + +static int qemudDispatchDomainSave(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainSaveRequest)) + return -1; + + /* Paranoia NULL termination */ + in->data.domainSaveRequest.file[PATH_MAX-1] ='\0'; + + int ret = qemudDomainSave(server, + in->data.domainSaveRequest.id, + in->data.domainSaveRequest.file); + if (ret < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_SAVE; + out->header.dataSize = 0; + } + return 0; +} + +static int qemudDispatchDomainRestore(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + int id; + if (in->header.dataSize != sizeof(in->data.domainRestoreRequest)) + return -1; + + /* Paranoia null termination */ + in->data.domainRestoreRequest.file[PATH_MAX-1] ='\0'; + + id = qemudDomainRestore(server, in->data.domainRestoreRequest.file); + if (id < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_RESTORE; + out->header.dataSize = sizeof(out->data.domainRestoreReply); + out->data.domainRestoreReply.id = id; + } + return 0; +} + +static int qemudDispatchDumpXML(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + int ret; + if (in->header.dataSize != sizeof(in->data.domainDumpXMLRequest)) + return -1; + + ret = qemudDomainDumpXML(server, + in->data.domainDumpXMLRequest.uuid, + out->data.domainDumpXMLReply.xml, + QEMUD_MAX_XML_LEN); + if (ret < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DUMP_XML; + out->header.dataSize = sizeof(out->data.domainDumpXMLReply); + } + return 0; +} + +static int qemudDispatchListDefinedDomains(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + char **names; + int i, ndomains; + if (in->header.dataSize != 0) + return -1; + + if (!(names = malloc(sizeof(char *)*QEMUD_MAX_NUM_DOMAINS))) + return -1; + + for (i = 0 ; i < QEMUD_MAX_NUM_DOMAINS ; i++) { + names[i] = out->data.listDefinedDomainsReply.domains[i]; + } + + ndomains = qemudListDefinedDomains(server, + names, + QEMUD_MAX_NUM_DOMAINS); + free(names); + if (ndomains < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_LIST_DEFINED_DOMAINS; + out->header.dataSize = sizeof(out->data.listDefinedDomainsReply); + out->data.listDefinedDomainsReply.numDomains = ndomains; + } + return 0; +} + +static int qemudDispatchNumDefinedDomains(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != 0) + return -1; + + int ndomains = qemudNumDefinedDomains(server); + if (ndomains < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_NUM_DEFINED_DOMAINS; + out->header.dataSize = sizeof(out->data.numDefinedDomainsReply); + out->data.numDefinedDomainsReply.numDomains = ndomains; + } + return 0; +} + +static int qemudDispatchDomainStart(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainStartRequest)) + return -1; + + struct qemud_vm *vm = qemudFindVMByUUID(server, in->data.domainStartRequest.uuid); + if (!vm || qemudDomainStart(server, vm) < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_START; + out->header.dataSize = sizeof(out->data.domainStartReply); + out->data.domainStartReply.id = vm->def.id; + } + return 0; +} + +static int qemudDispatchDomainDefine(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainDefineRequest)) + return -1; + + in->data.domainDefineRequest.xml[QEMUD_MAX_XML_LEN-1] ='\0'; + + struct qemud_vm *vm = qemudDomainDefine(server, in->data.domainDefineRequest.xml); + if (!vm) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_DEFINE; + out->header.dataSize = sizeof(out->data.domainDefineReply); + memcpy(out->data.domainDefineReply.uuid, vm->def.uuid, QEMUD_UUID_RAW_LEN); + strncpy(out->data.domainDefineReply.name, vm->def.name, QEMUD_MAX_NAME_LEN-1); + out->data.domainDefineReply.name[QEMUD_MAX_NAME_LEN-1] = '\0'; + } + return 0; +} + +static int qemudDispatchDomainUndefine(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + if (in->header.dataSize != sizeof(in->data.domainUndefineRequest)) + return -1; + + int ret = qemudDomainUndefine(server, in->data.domainUndefineRequest.uuid); + if (ret < 0) { + if (qemudDispatchFailure(server, client, out) < 0) + return -1; + } else { + out->header.type = QEMUD_PKT_DOMAIN_UNDEFINE; + out->header.dataSize = 0; + } + return 0; +} + + +typedef int (*clientFunc)(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out); + + +/* One per message type recorded in qemud_packet_type enum */ + +clientFunc funcsTransmitRW[QEMUD_PKT_MAX] = { + NULL, /* FAILURE code */ + qemudDispatchGetVersion, + qemudDispatchGetNodeInfo, + qemudDispatchListDomains, + qemudDispatchNumDomains, + qemudDispatchDomainCreate, + qemudDispatchDomainLookupByID, + qemudDispatchDomainLookupByUUID, + qemudDispatchDomainLookupByName, + qemudDispatchDomainSuspend, + qemudDispatchDomainResume, + qemudDispatchDomainDestroy, + qemudDispatchDomainGetInfo, + qemudDispatchDomainSave, + qemudDispatchDomainRestore, + qemudDispatchDumpXML, + qemudDispatchListDefinedDomains, + qemudDispatchNumDefinedDomains, + qemudDispatchDomainStart, + qemudDispatchDomainDefine, + qemudDispatchDomainUndefine +}; + +clientFunc funcsTransmitRO[QEMUD_PKT_MAX] = { + NULL, /* FAILURE code */ + qemudDispatchGetVersion, + qemudDispatchGetNodeInfo, + qemudDispatchListDomains, + qemudDispatchNumDomains, + NULL, + qemudDispatchDomainLookupByID, + qemudDispatchDomainLookupByUUID, + qemudDispatchDomainLookupByName, + NULL, + NULL, + NULL, + qemudDispatchDomainGetInfo, + NULL, + NULL, + qemudDispatchDumpXML, + qemudDispatchListDefinedDomains, + qemudDispatchNumDefinedDomains, + NULL, + NULL, + NULL, +}; + +/* + * Returns -1 if message processing failed - eg, illegal header sizes, + * a memory error dealing with stuff, or any other bad stuff which + * should trigger immediate client disconnect + * + * Return 0 if message processing succeeded. NB, this does not mean + * the operation itself succeeded - success/failure of the operation + * is recorded by the return message type - either it matches the + * incoming type, or is QEMUD_PKT_FAILURE + */ +int qemudDispatch(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out) { + clientFunc *funcs; + unsigned int type = in->header.type; + QEMUD_DEBUG("> Dispatching request %d readonly ? %d\n", type, client->readonly); + + server->errorCode = 0; + server->errorMessage[0] = '\0'; + + memset(out, 0, sizeof(struct qemud_packet)); + + if (type >= QEMUD_PKT_MAX) { + QEMUD_DEBUG("Illegal request type\n"); + return -1; + } + + if (type == QEMUD_PKT_FAILURE) { + QEMUD_DEBUG("Illegal request type\n"); + return -1; + } + + if (client->readonly) + funcs = funcsTransmitRO; + else + funcs = funcsTransmitRW; + + if (!funcs[type]) { + QEMUD_DEBUG("Illegal operation requested\n"); + qemudReportError(server, VIR_ERR_OPERATION_DENIED, NULL); + qemudDispatchFailure(server, client, out); + } else { + if ((funcs[type])(server, client, in, out) < 0) { + QEMUD_DEBUG("Dispatch failed\n"); + return -1; + } + } + + QEMUD_DEBUG("< Returning reply %d (%d bytes)\n", + out->header.type, + out->header.dataSize); + + return 0; +} + + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/dispatch.h b/qemud/dispatch.h new file mode 100644 index 0000000000..9e1dbd7d37 --- /dev/null +++ b/qemud/dispatch.h @@ -0,0 +1,43 @@ +/* + * dispatch.h: (De-)marshall wire messages to driver functions. + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + + +#ifndef QEMUD_DISPATCH_H +#define QEMUD_DISPATCH_H + +#include "internal.h" + + +int qemudDispatch(struct qemud_server *server, struct qemud_client *client, + struct qemud_packet *in, struct qemud_packet *out); + +#endif + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/driver.c b/qemud/driver.c new file mode 100644 index 0000000000..f3c725dcfe --- /dev/null +++ b/qemud/driver.c @@ -0,0 +1,554 @@ +/* + * driver.c: core driver methods for managing qemu guests + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "internal.h" +#include "driver.h" +#include "config.h" + +void qemudReportError(struct qemud_server *server, + int code, const char *fmt, ...) { + va_list args; + server->errorCode = code; + if (fmt) { + va_start(args, fmt); + vsnprintf(server->errorMessage, QEMUD_MAX_ERROR_LEN-1, fmt, args); + va_end(args); + } else { + server->errorMessage[0] = '\0'; + } +} + +int qemudMonitorCommand(struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_vm *vm, + const char *cmd, + char **reply) { + int size = 0; + char *buf = NULL; + if (write(vm->monitor, cmd, strlen(cmd)) < 0) { + return -1; + } + + *reply = NULL; + + for (;;) { + struct pollfd fd = { vm->monitor, POLLIN | POLLERR | POLLHUP, 0 }; + char *tmp; + + /* Read all the data QEMU has sent thus far */ + for (;;) { + char data[1024]; + int got = read(vm->monitor, data, sizeof(data)); + if (got < 0) { + if (errno == EINTR) + continue; + if (errno == EAGAIN) + break; + + free(buf); + return -1; + } + if (!(buf = realloc(buf, size+got+1))) + return -1; + memmove(buf+size, data, got); + buf[size+got] = '\0'; + size += got; + } + if (buf) + QEMUD_DEBUG("Mon [%s]\n", buf); + /* Look for QEMU prompt to indicate completion */ + if (buf && ((tmp = strstr(buf, "\n(qemu)")) != NULL)) { + tmp[0] = '\0'; + break; + } + pollagain: + /* Need to wait for more data */ + if (poll(&fd, 1, -1) < 0) { + if (errno == EINTR) + goto pollagain; + + free(buf); + return -1; + } + } + + *reply = buf; + return 0; +} + +int qemudGetMemInfo(unsigned int *memory) { + FILE *meminfo = fopen("/proc/meminfo", "r"); + char line[1024]; + + *memory = 0; + + if (!meminfo) { + return -1; + } + + /* XXX NUMA and hyperthreads ? */ + while (fgets(line, sizeof(line), meminfo) != NULL) { + if (!strncmp(line, "MemTotal:", 9)) { + *memory = (unsigned int)strtol(line + 10, NULL, 10); + } + } + fclose(meminfo); + return 0; +} + +int qemudGetCPUInfo(unsigned int *cpus, unsigned int *mhz, + unsigned int *nodes, unsigned int *sockets, + unsigned int *cores, unsigned int *threads) { + FILE *cpuinfo = fopen("/proc/cpuinfo", "r"); + char line[1024]; + + *cpus = 0; + *mhz = 0; + *nodes = *sockets = *cores = *threads = 1; + + if (!cpuinfo) { + return -1; + } + + /* XXX NUMA and hyperthreads ? */ + while (fgets(line, sizeof(line), cpuinfo) != NULL) { + if (!strncmp(line, "processor\t", 10)) { /* aka a single logical CPU */ + (*cpus)++; + } else if (!strncmp(line, "cpu MHz\t", 8)) { + char *offset = index(line, ':'); + if (!offset) + continue; + offset++; + if (!*offset) + continue; + *mhz = (unsigned int)strtol(offset, NULL, 10); + } else if (!strncmp(line, "physical id\t", 12)) { /* aka socket */ + unsigned int id; + char *offset = index(line, ':'); + if (!offset) + continue; + offset++; + if (!*offset) + continue; + id = (unsigned int)strtol(offset, NULL, 10); + if ((id+1) > *sockets) + *sockets = (id + 1); + } else if (!strncmp(line, "cpu cores\t", 9)) { /* aka cores */ + unsigned int id; + char *offset = index(line, ':'); + if (!offset) + continue; + offset++; + if (!*offset) + continue; + id = (unsigned int)strtol(offset, NULL, 10); + if (id > *cores) + *cores = id; + } + } + fclose(cpuinfo); + + return 0; +} + +static int qemudGetProcessInfo(unsigned long long *cpuTime, int pid) { + char proc[PATH_MAX]; + FILE *pidinfo; + unsigned long usertime, systime; + + if (snprintf(proc, sizeof(proc), "/proc/%d/stat", pid) >= (int)sizeof(proc)) { + return -1; + } + + if (!(pidinfo = fopen(proc, "r"))) { + /*printf("cannnot read pid info");*/ + /* VM probably shut down, so fake 0 */ + *cpuTime = 0; + return 0; + } + + if (fscanf(pidinfo, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", &usertime, &systime) != 2) { + QEMUD_DEBUG("not enough arg\n"); + return -1; + } + + /* We got jiffies + * We want nanoseconds + * _SC_CLK_TCK is jiffies per second + * So calulate thus.... + */ + *cpuTime = 1000 * 1000 * 1000 * (usertime + systime) / sysconf(_SC_CLK_TCK); + + QEMUD_DEBUG("Got %lu %lu %lld\n", usertime, systime, *cpuTime); + + fclose(pidinfo); + + return 0; +} + +struct qemud_vm *qemudFindVMByID(const struct qemud_server *server, int id) { + struct qemud_vm *vm = server->activevms; + + while (vm) { + if (vm->def.id == id) + return vm; + vm = vm->next; + } + + return NULL; +} + +struct qemud_vm *qemudFindVMByUUID(const struct qemud_server *server, + const unsigned char *uuid) { + struct qemud_vm *vm = server->activevms; + + while (vm) { + if (!memcmp(vm->def.uuid, uuid, QEMUD_UUID_RAW_LEN)) + return vm; + vm = vm->next; + } + + vm = server->inactivevms; + while (vm) { + if (!memcmp(vm->def.uuid, uuid, QEMUD_UUID_RAW_LEN)) + return vm; + vm = vm->next; + } + + return NULL; +} + +struct qemud_vm *qemudFindVMByName(const struct qemud_server *server, + const char *name) { + struct qemud_vm *vm = server->activevms; + + while (vm) { + if (!strcmp(vm->def.name, name)) + return vm; + vm = vm->next; + } + + vm = server->inactivevms; + while (vm) { + if (!strcmp(vm->def.name, name)) + return vm; + vm = vm->next; + } + + return NULL; +} + +int qemudGetVersion(struct qemud_server *server) { + return server->qemuVersion; +} + +int qemudListDomains(struct qemud_server *server, int *ids, int nids) { + struct qemud_vm *vm = server->activevms; + int got = 0; + while (vm && got < nids) { + ids[got] = vm->def.id; + vm = vm->next; + got++; + } + return got; +} +int qemudNumDomains(struct qemud_server *server) { + return server->nactivevms; +} +struct qemud_vm *qemudDomainCreate(struct qemud_server *server, const char *xml) { + struct qemud_vm *vm; + + if (!(vm = qemudLoadConfigXML(server, NULL, xml, 0))) { + return NULL; + } + + if (qemudStartVMDaemon(server, vm) < 0) { + qemudFreeVM(vm); + return NULL; + } + + vm->next = server->activevms; + server->activevms = vm; + server->nactivevms++; + server->nvmfds += 2; + + return vm; +} + + +int qemudDomainSuspend(struct qemud_server *server, int id) { + char *info; + struct qemud_vm *vm = qemudFindVMByID(server, id); + if (!vm) { + qemudReportError(server, VIR_ERR_INVALID_DOMAIN, "no domain with matching id %d", id); + return -1; + } + if (vm->pid == -1) { + qemudReportError(server, VIR_ERR_OPERATION_FAILED, "domain is not running"); + return -1; + } + + if (qemudMonitorCommand(server, vm, "stop\n", &info) < 0) { + qemudReportError(server, VIR_ERR_OPERATION_FAILED, "suspend operation failed"); + return -1; + } + printf("Reply %s\n", info); + free(info); + return 0; +} + + +int qemudDomainResume(struct qemud_server *server, int id) { + char *info; + struct qemud_vm *vm = qemudFindVMByID(server, id); + if (!vm) { + qemudReportError(server, VIR_ERR_INVALID_DOMAIN, "no domain with matching id %d", id); + return -1; + } + if (vm->pid == -1) { + qemudReportError(server, VIR_ERR_OPERATION_FAILED, "domain is not running"); + return -1; + } + if (qemudMonitorCommand(server, vm, "cont\n", &info) < 0) { + qemudReportError(server, VIR_ERR_OPERATION_FAILED, "resume operation failed"); + return -1; + } + printf("Reply %s\n", info); + free(info); + return -1; +} + + +int qemudDomainDestroy(struct qemud_server *server, int id) { + struct qemud_vm *vm = qemudFindVMByID(server, id); + if (!vm) { + qemudReportError(server, VIR_ERR_INVALID_DOMAIN, "no domain with matching id %d", id); + return -1; + } + if (vm->pid == -1) { + qemudReportError(server, VIR_ERR_OPERATION_FAILED, "domain is not running"); + return -1; + } + + if (qemudShutdownVMDaemon(server, vm) < 0) + return -1; + return 0; +} + + +int qemudDomainGetInfo(struct qemud_server *server, const unsigned char *uuid, + int *runstate, + unsigned long long *cputime, + unsigned long *maxmem, + unsigned long *memory, + unsigned int *nrVirtCpu) { + struct qemud_vm *vm = qemudFindVMByUUID(server, uuid); + if (!vm) { + qemudReportError(server, VIR_ERR_INVALID_DOMAIN, "no domain with matching uuid"); + return -1; + } + + if (vm->pid == -1) { + *runstate = QEMUD_STATE_STOPPED; + } else { + /* XXX in future need to add PAUSED */ + *runstate = QEMUD_STATE_RUNNING; + } + + if (vm->pid == -1) { + *cputime = 0; + } else { + if (qemudGetProcessInfo(cputime, vm->pid) < 0) { + qemudReportError(server, VIR_ERR_OPERATION_FAILED, "cannot read cputime for domain"); + return -1; + } + } + + *maxmem = vm->def.maxmem; + *memory = vm->def.memory; + *nrVirtCpu = vm->def.vcpus; + return 0; +} + + +int qemudDomainSave(struct qemud_server *server, int id, + const char *path ATTRIBUTE_UNUSED) { + struct qemud_vm *vm = qemudFindVMByID(server, id); + if (!vm) { + qemudReportError(server, VIR_ERR_INVALID_DOMAIN, "no domain with matching id %d", id); + return -1; + } + if (vm->pid == -1) { + qemudReportError(server, VIR_ERR_OPERATION_FAILED, "domain is not running"); + return -1; + } + qemudReportError(server, VIR_ERR_OPERATION_FAILED, "save is not supported"); + return -1; +} + + +int qemudDomainRestore(struct qemud_server *server, + const char *path ATTRIBUTE_UNUSED) { + qemudReportError(server, VIR_ERR_OPERATION_FAILED, "restore is not supported"); + return -1; +} + + +int qemudDomainDumpXML(struct qemud_server *server, const unsigned char *uuid, char *xml, int xmllen) { + struct qemud_vm *vm = qemudFindVMByUUID(server, uuid); + char *vmxml; + if (!vm) { + qemudReportError(server, VIR_ERR_INVALID_DOMAIN, "no domain with matching uuid"); + return -1; + } + + vmxml = qemudGenerateXML(server, vm); + if (!vmxml) + return -1; + + strncpy(xml, vmxml, xmllen); + xml[xmllen-1] = '\0'; + + return 0; +} + + +int qemudListDefinedDomains(struct qemud_server *server, char *const*names, int nnames) { + struct qemud_vm *vm = server->inactivevms; + int got = 0; + while (vm && got < nnames) { + strncpy(names[got], vm->def.name, QEMUD_MAX_NAME_LEN-1); + names[got][QEMUD_MAX_NAME_LEN-1] = '\0'; + vm = vm->next; + got++; + } + return got; +} + + +int qemudNumDefinedDomains(struct qemud_server *server) { + return server->ninactivevms; +} + + +int qemudDomainStart(struct qemud_server *server, struct qemud_vm *vm) { + struct qemud_vm *prev = NULL, *curr = server->inactivevms; + if (qemudStartVMDaemon(server, vm) < 0) { + return 1; + } + + while (curr) { + if (curr == vm) { + if (prev) + prev->next = curr->next; + else + server->inactivevms = curr->next; + server->ninactivevms--; + break; + } + prev = curr; + curr = curr->next; + } + + vm->next = server->activevms; + server->activevms = vm; + server->nactivevms++; + server->nvmfds += 2; + + return 0; +} + + +struct qemud_vm *qemudDomainDefine(struct qemud_server *server, const char *xml) { + struct qemud_vm *vm; + + if (!(vm = qemudLoadConfigXML(server, NULL, xml, 1))) { + return NULL; + } + + vm->next = server->inactivevms; + server->inactivevms = vm; + server->ninactivevms++; + + return vm; +} + +int qemudDomainUndefine(struct qemud_server *server, const unsigned char *uuid) { + struct qemud_vm *vm = qemudFindVMByUUID(server, uuid); + struct qemud_vm *prev = NULL, *curr = server->inactivevms; + + if (!vm) { + qemudReportError(server, VIR_ERR_INVALID_DOMAIN, "no domain with matching uuid"); + return -1; + } + + if (vm->pid != -1) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot delete active domain"); + return -1; + } + + if (qemudDeleteConfigXML(server, vm) < 0) + return -1; + + while (curr) { + if (curr == vm) { + if (prev) { + prev->next = curr->next; + } else { + server->inactivevms = curr->next; + } + server->ninactivevms--; + break; + } + + prev = curr; + curr = curr->next; + } + + qemudFreeVM(vm); + + return 0; +} + + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/driver.h b/qemud/driver.h new file mode 100644 index 0000000000..1b264c3f24 --- /dev/null +++ b/qemud/driver.h @@ -0,0 +1,99 @@ +/* + * driver.h: core driver methods for managing qemu guests + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + + +#ifndef QEMUD_DRIVER_H +#define QEMUD_DRIVER_H + +#include "internal.h" + +void qemudReportError(struct qemud_server *server, + int code, const char *fmt, ...); + +int qemudGetCPUInfo(unsigned int *cpus, unsigned int *mhz, + unsigned int *nodes, unsigned int *sockets, + unsigned int *cores, unsigned int *threads); +int qemudGetMemInfo(unsigned int *memory); +int qemudMonitorCommand(struct qemud_server *server, + struct qemud_vm *vm, + const char *cmd, + char **reply); + +struct qemud_vm *qemudFindVMByID(const struct qemud_server *server, + int id); +struct qemud_vm *qemudFindVMByUUID(const struct qemud_server *server, + const unsigned char *uuid); +struct qemud_vm *qemudFindVMByName(const struct qemud_server *server, + const char *name); + +int qemudGetVersion(struct qemud_server *server); +int qemudListDomains(struct qemud_server *server, + int *ids, + int nids); +int qemudNumDomains(struct qemud_server *server); +struct qemud_vm *qemudDomainCreate(struct qemud_server *server, + const char *xml); +int qemudDomainSuspend(struct qemud_server *server, + int id); +int qemudDomainResume(struct qemud_server *server, + int id); +int qemudDomainDestroy(struct qemud_server *server, + int id); +int qemudDomainGetInfo(struct qemud_server *server, + const unsigned char *uuid, + int *runstate, + unsigned long long *cputime, + unsigned long *maxmem, + unsigned long *memory, + unsigned int *nrVirtCpu); +int qemudDomainSave(struct qemud_server *server, + int id, + const char *path); +int qemudDomainRestore(struct qemud_server *server, + const char *path); +int qemudDomainDumpXML(struct qemud_server *server, + const unsigned char *uuid, + char *xml, + int xmllen); +int qemudListDefinedDomains(struct qemud_server *server, + char *const*names, + int nnames); +int qemudNumDefinedDomains(struct qemud_server *server); +int qemudDomainStart(struct qemud_server *server, + struct qemud_vm *vm); +struct qemud_vm *qemudDomainDefine(struct qemud_server *server, + const char *xml); +int qemudDomainUndefine(struct qemud_server *server, + const unsigned char *uuid); + +#endif + + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/internal.h b/qemud/internal.h new file mode 100644 index 0000000000..b308f7c887 --- /dev/null +++ b/qemud/internal.h @@ -0,0 +1,254 @@ +/* + * internal.h: daemon data structure definitions + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + + +#ifndef QEMUD_INTERNAL_H__ +#define QEMUD_INTERNAL_H__ + +#include +#include +#include + +#include "protocol.h" + +#ifdef __GNUC__ +#ifdef HAVE_ANSIDECL_H +#include +#endif +#ifndef ATTRIBUTE_UNUSED +#define ATTRIBUTE_UNUSED __attribute__((unused)) +#endif +#else +#define ATTRIBUTE_UNUSED +#endif + +#ifdef DEBUG +#define QEMUD_DEBUG(args...) fprintf(stderr, args) +#else +#define QEMUD_DEBUG(args...) do {} while(0) +#endif + +#define UUID_LEN 16 + +/* Different types of QEMU acceleration possible */ +enum qemud_vm_virt_type { + QEMUD_VIRT_QEMU, + QEMUD_VIRT_KQEMU, + QEMUD_VIRT_KVM, +}; + + +/* Two types of disk backends */ +enum qemud_vm_disk_type { + QEMUD_DISK_BLOCK, + QEMUD_DISK_FILE +}; + +/* Three types of disk frontend */ +enum qemud_vm_disk_device { + QEMUD_DISK_DISK, + QEMUD_DISK_CDROM, + QEMUD_DISK_FLOPPY, +}; + +/* Stores the virtual disk configuration */ +struct qemud_vm_disk_def { + int type; + int device; + char src[PATH_MAX]; + char dst[NAME_MAX]; + int readonly; + + struct qemud_vm_disk_def *next; +}; + +#define QEMUD_MAC_ADDRESS_LEN 6 +#define QEMUD_OS_TYPE_MAX_LEN 10 +#define QEMUD_OS_ARCH_MAX_LEN 10 +#define QEMUD_OS_MACHINE_MAX_LEN 10 + +/* 5 different types of networking config */ +enum qemud_vm_net_type { + QEMUD_NET_USER, + QEMUD_NET_TAP, + QEMUD_NET_SERVER, + QEMUD_NET_CLIENT, + QEMUD_NET_MCAST, + /* QEMUD_NET_VDE*/ +}; + +/* Stores the virtual network interface configuration */ +struct qemud_vm_net_def { + int type; + int vlan; + unsigned char mac[QEMUD_MAC_ADDRESS_LEN]; + union { + struct { + char ifname[NAME_MAX]; + char script[PATH_MAX]; + } tap; + struct { + struct sockaddr_in listen; + int port; + } server; + struct { + struct sockaddr_in connect; + int port; + } client; + struct { + struct sockaddr_in group; + int port; + } mcast; + struct { + char vlan[PATH_MAX]; + } vde; + } dst; + + struct qemud_vm_net_def *next; +}; + +#define QEMUD_MAX_BOOT_DEVS 4 + +/* 3 possible boot devices */ +enum qemud_vm_boot_order { + QEMUD_BOOT_FLOPPY, + QEMUD_BOOT_CDROM, + QEMUD_BOOT_DISK, + QEMUD_BOOT_NET, +}; +/* 3 possible graphics console modes */ +enum qemud_vm_grapics_type { + QEMUD_GRAPHICS_NONE, + QEMUD_GRAPHICS_SDL, + QEMUD_GRAPHICS_VNC, +}; + +enum qemud_vm_features { + QEMUD_FEATURE_ACPI = 1, +}; + +/* Operating system configuration data & machine / arch */ +struct qemud_vm_os_def { + char type[QEMUD_OS_TYPE_MAX_LEN]; + char arch[QEMUD_OS_ARCH_MAX_LEN]; + char machine[QEMUD_OS_MACHINE_MAX_LEN]; + int nBootDevs; + int bootDevs[QEMUD_MAX_BOOT_DEVS]; + char kernel[PATH_MAX]; + char initrd[PATH_MAX]; + char cmdline[PATH_MAX]; + char binary[PATH_MAX]; +}; + +/* Guest VM main configuration */ +struct qemud_vm_def { + int id; + int virtType; + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + char name[QEMUD_MAX_NAME_LEN]; + + int memory; + int maxmem; + int vcpus; + + struct qemud_vm_os_def os; + + int features; + int graphicsType; + int vncPort; + int vncActivePort; + + int ndisks; + struct qemud_vm_disk_def *disks; + + int nnets; + struct qemud_vm_net_def *nets; +}; + +/* Guest VM runtime state */ +struct qemud_vm { + int stdout; + int stderr; + int monitor; + int pid; + + char configFile[PATH_MAX]; + + struct qemud_vm_def def; + + struct qemud_vm *next; +}; + +/* Stores the per-client connection state */ +struct qemud_client { + int fd; + int readonly; + struct qemud_packet incoming; + unsigned int incomingReceived; + struct qemud_packet outgoing; + unsigned int outgoingSent; + int tx; + struct qemud_client *next; +}; + + +struct qemud_socket { + int fd; + int readonly; + struct qemud_socket *next; +}; + +/* Main server state */ +struct qemud_server { + int nsockets; + struct qemud_socket *sockets; + int qemuVersion; + int nclients; + struct qemud_client *clients; + int nvmfds; + int nactivevms; + struct qemud_vm *activevms; + int ninactivevms; + struct qemud_vm *inactivevms; + int nextvmid; + char configDir[PATH_MAX]; + char errorMessage[QEMUD_MAX_ERROR_LEN]; + int errorCode; +}; + +int qemudStartVMDaemon(struct qemud_server *server, + struct qemud_vm *vm); + +int qemudShutdownVMDaemon(struct qemud_server *server, + struct qemud_vm *vm); + +#endif + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/protocol.h b/qemud/protocol.h new file mode 100644 index 0000000000..85392ba352 --- /dev/null +++ b/qemud/protocol.h @@ -0,0 +1,222 @@ +/* + * protocol.h: wire protocol message format & data structures + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + + +#ifndef QEMUD_PROTOCOL_H__ +#define QEMUD_PROTOCOL_H__ + +#include + +/* List of different packet types which can be sent */ +enum { + QEMUD_PKT_FAILURE = 0, + QEMUD_PKT_GET_VERSION, + QEMUD_PKT_GET_NODEINFO, + QEMUD_PKT_LIST_DOMAINS, + QEMUD_PKT_NUM_DOMAINS, + QEMUD_PKT_DOMAIN_CREATE, + QEMUD_PKT_DOMAIN_LOOKUP_BY_ID, + QEMUD_PKT_DOMAIN_LOOKUP_BY_UUID, + QEMUD_PKT_DOMAIN_LOOKUP_BY_NAME, + QEMUD_PKT_DOMAIN_SUSPEND, + QEMUD_PKT_DOMAIN_RESUME, + QEMUD_PKT_DOMAIN_DESTROY, + QEMUD_PKT_DOMAIN_GET_INFO, + QEMUD_PKT_DOMAIN_SAVE, + QEMUD_PKT_DOMAIN_RESTORE, + QEMUD_PKT_DUMP_XML, + QEMUD_PKT_LIST_DEFINED_DOMAINS, + QEMUD_PKT_NUM_DEFINED_DOMAINS, + QEMUD_PKT_DOMAIN_START, + QEMUD_PKT_DOMAIN_DEFINE, + QEMUD_PKT_DOMAIN_UNDEFINE, + + QEMUD_PKT_MAX, +} qemud_packet_type; + + +#define QEMUD_PROTOCOL_VERSION_MAJOR 1 +#define QEMUD_PROTOCOL_VERSION_MINOR 0 + +#define QEMUD_UUID_RAW_LEN 16 +#define QEMUD_MAX_NAME_LEN 50 +#define QEMUD_MAX_XML_LEN 4096 +#define QEMUD_MAX_NUM_DOMAINS 100 +#define QEMUD_MAX_ERROR_LEN 1024 + +/* Possible guest VM states */ +enum { + QEMUD_STATE_RUNNING = 1, + QEMUD_STATE_PAUSED, + QEMUD_STATE_STOPPED, +} qemud_domain_runstate; + +/* Each packets has at least a fixed size header. + * + * All data required to be network byte order + * to 32-bit boundaries */ +struct qemud_packet_header { + uint32_t type; + /* Stores the size of the data struct matching + the type arg. + Must be <= sizeof(union qemudPacketData) */ + uint32_t dataSize; +}; + +/* Most packets also have some message specific data + * All data required to be network byte order, padded + * to 32-bit boundaries */ +union qemud_packet_data { + struct { + int32_t code; + char message[QEMUD_MAX_ERROR_LEN]; + } failureReply; + struct { + int32_t version; + } getVersionReply; + struct { + char model[32]; + uint32_t memory; + uint32_t cpus; + uint32_t mhz; + uint32_t nodes; + uint32_t sockets; + uint32_t cores; + uint32_t threads; + } getNodeInfoReply; + struct { + int32_t numDomains; + int32_t domains[QEMUD_MAX_NUM_DOMAINS]; + } listDomainsReply; + struct { + int32_t numDomains; + } numDomainsReply; + struct { + char xml[QEMUD_MAX_XML_LEN]; + } domainCreateRequest; + struct { + int32_t id; + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + char name[QEMUD_MAX_NAME_LEN]; + } domainCreateReply; + struct { + int32_t id; + } domainLookupByIDRequest; + struct { + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + char name[QEMUD_MAX_NAME_LEN]; + } domainLookupByIDReply; + struct { + char name[QEMUD_MAX_NAME_LEN]; + } domainLookupByNameRequest; + struct { + int32_t id; + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + } domainLookupByNameReply; + struct { + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + } domainLookupByUUIDRequest; + struct { + int32_t id; + char name[QEMUD_MAX_NAME_LEN]; + } domainLookupByUUIDReply; + struct { + int32_t id; + } domainSuspendRequest; + struct { + int32_t id; + } domainResumeRequest; + struct { + } domainResumeReply; + struct { + int32_t id; + } domainDestroyRequest; + struct { + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + } domainGetInfoRequest; + struct { + uint64_t cpuTime; + int32_t runstate; + uint32_t memory; + uint32_t maxmem; + uint32_t nrVirtCpu; + } domainGetInfoReply; + struct { + int32_t id; + char file[PATH_MAX]; + } domainSaveRequest; + struct { + char file[PATH_MAX]; + } domainRestoreRequest; + struct { + int32_t id; + } domainRestoreReply; + struct { + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + } domainDumpXMLRequest; + struct { + char xml[QEMUD_MAX_XML_LEN]; + } domainDumpXMLReply; + struct { + int32_t numDomains; + char domains[QEMUD_MAX_NUM_DOMAINS][QEMUD_MAX_NAME_LEN]; + } listDefinedDomainsReply; + struct { + int32_t numDomains; + } numDefinedDomainsReply; + struct { + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + } domainStartRequest; + struct { + int32_t id; + } domainStartReply; + struct { + char xml[QEMUD_MAX_XML_LEN]; + } domainDefineRequest; + struct { + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + char name[QEMUD_MAX_NAME_LEN]; + } domainDefineReply; + struct { + unsigned char uuid[QEMUD_UUID_RAW_LEN]; + } domainUndefineRequest; +}; + +/* Each packet has header & data */ +struct qemud_packet { + struct qemud_packet_header header; + union qemud_packet_data data; +}; + + +#endif + + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/qemud/qemud.c b/qemud/qemud.c new file mode 100644 index 0000000000..a916b80f41 --- /dev/null +++ b/qemud/qemud.c @@ -0,0 +1,958 @@ +/* + * qemud.c: daemon start of day, guest process & i/o management + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "internal.h" +#include "dispatch.h" +#include "driver.h" +#include "config.h" + +static void reapchild(int sig ATTRIBUTE_UNUSED) { + /* We explicitly waitpid the child later */ +} +static int qemudSetCloseExec(int fd) { + int flags; + if ((flags = fcntl(fd, F_GETFD)) < 0) { + return -1; + } + flags |= FD_CLOEXEC; + if ((fcntl(fd, F_SETFD, flags)) < 0) { + return -1; + } + return 0; +} + + +static int qemudSetNonBlock(int fd) { + int flags; + if ((flags = fcntl(fd, F_GETFL)) < 0) { + return -1; + } + flags |= O_NONBLOCK; + if ((fcntl(fd, F_SETFL, flags)) < 0) { + return -1; + } + return 0; +} + +static int qemudGoDaemon(void) { + int pid = fork(); + switch (pid) { + case 0: + { + int stdinfd = -1; + int stdoutfd = -1; + int i, open_max, nextpid; + + if ((stdinfd = open(_PATH_DEVNULL, O_RDONLY)) < 0) + goto cleanup; + if ((stdoutfd = open(_PATH_DEVNULL, O_WRONLY)) < 0) + goto cleanup; + if (dup2(stdinfd, STDIN_FILENO) != STDIN_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDOUT_FILENO) != STDOUT_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDERR_FILENO) != STDERR_FILENO) + goto cleanup; + if (close(stdinfd) < 0) + goto cleanup; + stdinfd = -1; + if (close(stdoutfd) < 0) + goto cleanup; + stdoutfd = -1; + + open_max = sysconf (_SC_OPEN_MAX); + for (i = 0; i < open_max; i++) + if (i != STDIN_FILENO && + i != STDOUT_FILENO && + i != STDERR_FILENO) + close(i); + + if (setsid() < 0) + goto cleanup; + + nextpid = fork(); + switch (nextpid) { + case 0: + return 0; + case -1: + return -1; + default: + return nextpid; + } + + cleanup: + if (stdoutfd != -1) + close(stdoutfd); + if (stdinfd != -1) + close(stdinfd); + return -1; + + } + + case -1: + return -1; + + default: + { + int got, status = 0; + /* We wait to make sure the next child forked + successfully */ + if ((got = waitpid(pid, &status, 0)) < 0 || + got != pid || + status != 0) { + return -1; + } + + return pid; + } + } +} + +static int qemudListenUnix(struct qemud_server *server, + const char *path, int readonly) { + struct qemud_socket *sock = calloc(1, sizeof(struct qemud_socket)); + struct sockaddr_un addr; + mode_t oldmask; + + if (!sock) + return -1; + + sock->readonly = readonly; + sock->next = server->sockets; + server->sockets = sock; + server->nsockets++; + + if ((sock->fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) + return -1; + + if (qemudSetCloseExec(sock->fd) < 0) + return -1; + if (qemudSetNonBlock(sock->fd) < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path)-1); + if (addr.sun_path[0] == '@') + addr.sun_path[0] = '\0'; + + + if (readonly) + oldmask = umask(~(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); + else + oldmask = umask(~(S_IRUSR | S_IWUSR)); + if (bind(sock->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + return -1; + umask(oldmask); + + if (listen(sock->fd, 30) < 0) + return -1; + + return 0; +} + +static int qemudListen(struct qemud_server *server, int sys) { + char sockname[PATH_MAX]; + + if (sys) { + if (snprintf(sockname, sizeof(sockname), "%s/run/qemud/sock", LOCAL_STATE_DIR) >= (int)sizeof(sockname)) { + return -1; + } + unlink(sockname); + if (qemudListenUnix(server, sockname, 0) < 0) + return -1; + + + if (snprintf(sockname, sizeof(sockname), "%s/run/qemud/sock-ro", LOCAL_STATE_DIR) >= (int)sizeof(sockname)) { + return -1; + } + unlink(sockname); + if (qemudListenUnix(server, sockname, 1) < 0) + return -1; + } else { + struct passwd *pw; + int uid; + + if ((uid = geteuid()) < 0) { + return -1; + } + + if (!(pw = getpwuid(uid))) + return -1; + + if (snprintf(sockname, sizeof(sockname), "@%s/.qemud/sock", pw->pw_dir) >= (int)sizeof(sockname)) { + return -1; + } + + if (qemudListenUnix(server, sockname, 0) < 0) + return -1; + } + + return 0; +} + +static struct qemud_server *qemudInitialize(int sys) { + struct qemud_server *server; + + if (!(server = calloc(1, sizeof(struct qemud_server)))) + return NULL; + + /* XXX extract actual version */ + server->qemuVersion = (0*1000000)+(8*1000)+(0); + /* We don't have a dom-0, so start from 1 */ + server->nextvmid = 1; + + if (sys) { + if (snprintf(server->configDir, sizeof(server->configDir), "%s/qemud", SYSCONF_DIR) >= (int)sizeof(server->configDir)) { + goto cleanup; + } + } else { + struct passwd *pw; + int uid; + if ((uid = geteuid()) < 0) { + goto cleanup; + } + if (!(pw = getpwuid(uid))) { + goto cleanup; + } + + if (snprintf(server->configDir, sizeof(server->configDir), "%s/.qemud", pw->pw_dir) >= (int)sizeof(server->configDir)) { + goto cleanup; + } + } + + if (qemudListen(server, sys) < 0) { + goto cleanup; + } + + if (qemudScanConfigs(server) < 0) { + goto cleanup; + } + + return server; + + cleanup: + if (server) { + struct qemud_socket *sock = server->sockets; + while (sock) { + close(sock->fd); + sock = sock->next; + } + + free(server); + } + return NULL; +} + + +static int qemudDispatchServer(struct qemud_server *server, struct qemud_socket *sock) { + int fd; + struct sockaddr_storage addr; + unsigned int addrlen = sizeof(addr); + struct qemud_client *client; + + if ((fd = accept(sock->fd, (struct sockaddr *)&addr, &addrlen)) < 0) { + if (errno == EAGAIN) + return 0; + return -1; + } + + if (qemudSetCloseExec(fd) < 0) { + close(fd); + return -1; + } + + if (qemudSetNonBlock(fd) < 0) { + close(fd); + return -1; + } + + client = calloc(1, sizeof(struct qemud_client)); + client->fd = fd; + client->readonly = sock->readonly; + + client->next = server->clients; + server->clients = client; + server->nclients++; + + return 0; +} + + +int qemudStartVMDaemon(struct qemud_server *server, + struct qemud_vm *vm) { + char **argv = NULL; + int argc = 0; + int pid; + int i, ret = -1; + int stdinfd = -1; + int pipeout[2] = {-1,-1}; + int pipeerr[2] = {-1,-1}; + + if (vm->def.vncPort < 0) + vm->def.vncActivePort = 5900 + server->nextvmid; + else + vm->def.vncActivePort = vm->def.vncPort; + + if (qemudBuildCommandLine(server, vm, &argv, &argc) < 0) + return -1; + + if (1) { /* XXX debug stuff */ + QEMUD_DEBUG("Spawn QEMU '"); + for (i = 0 ; i < argc; i++) { + QEMUD_DEBUG("%s", argv[i]); + if (i == (argc-1)) { + QEMUD_DEBUG("'\n"); + } else { + QEMUD_DEBUG(" "); + } + } + } + + if ((stdinfd = open(_PATH_DEVNULL, O_RDONLY)) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot open %s", _PATH_DEVNULL); + goto cleanup; + } + + if (pipe(pipeout) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot create pipe"); + goto cleanup; + } + + if (pipe(pipeerr) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot create pipe"); + goto cleanup; + } + + if ((pid = fork()) < 0) { + qemudReportError(server, VIR_ERR_INTERNAL_ERROR, "cannot fork child process"); + goto cleanup; + } + + if (pid) { /* parent */ + close(stdinfd); + close(pipeout[1]); + close(pipeerr[1]); + qemudSetNonBlock(pipeout[0]); + qemudSetNonBlock(pipeerr[0]); + vm->def.id = server->nextvmid++; + vm->pid = pid; + vm->stdout = pipeout[0]; + vm->stderr = pipeerr[0]; + + } else { /* child */ + int null; + if ((null = open(_PATH_DEVNULL, O_RDONLY)) < 0) + _exit(1); + + if (close(pipeout[0]) < 0) + _exit(1); + if (close(pipeerr[0]) < 0) + _exit(1); + + if (dup2(stdinfd, STDIN_FILENO) < 0) + _exit(1); + if (dup2(pipeout[1], STDOUT_FILENO) < 0) + _exit(1); + if (dup2(pipeerr[1], STDERR_FILENO) < 0) + _exit(1); + + int open_max = sysconf (_SC_OPEN_MAX); + for (i = 0; i < open_max; i++) + if (i != STDOUT_FILENO && + i != STDERR_FILENO && + i != STDIN_FILENO) + close(i); + + execvp(argv[0], argv); + + _exit(1); + } + + ret = 0; + + cleanup: + + for (i = 0 ; i < argc ; i++) { + free(argv[i]); + } + free(argv); + + return ret; +} + + +static void qemudDispatchClientFailure(struct qemud_server *server, struct qemud_client *client) { + struct qemud_client *tmp = server->clients; + struct qemud_client *prev = NULL; + while (tmp) { + if (tmp == client) { + if (prev == NULL) + server->clients = client->next; + else + prev->next = client->next; + server->nclients--; + break; + } + prev = tmp; + tmp = tmp->next; + } + close(client->fd); + free(client); +} + + +static int qemudDispatchClientRequest(struct qemud_server *server, struct qemud_client *client) { + if (qemudDispatch(server, + client, + &client->incoming, + &client->outgoing) < 0) { + return -1; + } + + client->outgoingSent = 0; + client->tx = 1; + client->incomingReceived = 0; + + return 0; +} + +static int qemudClientRead(struct qemud_server *server, + struct qemud_client *client, + char *buf, size_t want) { + int ret; + if ((ret = read(client->fd, buf, want)) <= 0) { + QEMUD_DEBUG("Plain read error %d\n", ret); + if (!ret || errno != EAGAIN) + qemudDispatchClientFailure(server, client); + return -1; + } + QEMUD_DEBUG("Plain data read %d\n", ret); + return ret; +} + +static void qemudDispatchClientRead(struct qemud_server *server, struct qemud_client *client) { + char *data = (char *)&client->incoming; + unsigned int got = client->incomingReceived; + int want; + int ret; + + restart: + if (got >= sizeof(struct qemud_packet_header)) { + want = sizeof(struct qemud_packet_header) + client->incoming.header.dataSize - got; + } else { + want = sizeof(struct qemud_packet_header) - got; + } + + if ((ret = qemudClientRead(server, client, data+got, want)) < 0) { + return; + } + got += ret; + client->incomingReceived += ret; + + /* If we've finished header, move onto body */ + if (client->incomingReceived == sizeof(struct qemud_packet_header)) { + QEMUD_DEBUG("Type %d, data %d\n", + client->incoming.header.type, + client->incoming.header.dataSize); + /* Client lied about dataSize */ + if (client->incoming.header.dataSize > sizeof(union qemud_packet_data)) { + QEMUD_DEBUG("Bogus data size %u\n", client->incoming.header.dataSize); + qemudDispatchClientFailure(server, client); + return; + } + if (client->incoming.header.dataSize) { + QEMUD_DEBUG("- Restarting recv to process body (%d bytes)\n", + client->incoming.header.dataSize); + goto restart; + } + } + + /* If we've finished body, dispatch the request */ + if (ret == want) { + if (qemudDispatchClientRequest(server, client) < 0) + qemudDispatchClientFailure(server, client); + QEMUD_DEBUG("Dispatch\n"); + } +} + + +static int qemudClientWrite(struct qemud_server *server, + struct qemud_client *client, + char *buf, size_t want) { + int ret; + if ((ret = write(client->fd, buf, want)) < 0) { + QEMUD_DEBUG("Plain write error %d\n", ret); + if (errno != EAGAIN) + qemudDispatchClientFailure(server, client); + return -1; + } + QEMUD_DEBUG("Plain data write %d\n", ret); + return ret; +} + + +static void qemudDispatchClientWrite(struct qemud_server *server, struct qemud_client *client) { + char *data = (char *)&client->outgoing; + int sent = client->outgoingSent; + int todo = sizeof(struct qemud_packet_header) + client->outgoing.header.dataSize - sent; + int ret; + if ((ret = qemudClientWrite(server, client, data+sent, todo)) < 0) { + return; + } + client->outgoingSent += ret; + QEMUD_DEBUG("Done %d %d\n", todo, ret); + if (todo == ret) + client->tx = 0; +} + +static int qemudVMData(struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_vm *vm, int fd) { + char buf[4096]; + if (vm->pid < 0) + return 0; + + for (;;) { + int ret = read(fd, buf, sizeof(buf)-1); + if (ret < 0) { + if (errno == EAGAIN) + return 0; + return -1; + } + if (ret == 0) { + return 0; + } + buf[ret] = '\0'; + + /* + * XXX this is bad - we should wait for tty and open the + * monitor when actually starting the guest, so we can + * reliably trap startup failures + */ + if (vm->monitor == -1) { + char monitor[20]; + /* Fairly lame assuming we receive the data all in one chunk. + This isn't guarenteed, but in practice it seems good enough. + This will probably bite me in the future.... */ + if (sscanf(buf, "char device redirected to %19s", monitor) == 1) { + int monfd; + + if (!(monfd = open(monitor, O_RDWR))) { + perror("cannot open monitor"); + return -1; + } + if (qemudSetCloseExec(monfd) < 0) { + close(monfd); + return -1; + } + if (qemudSetNonBlock(monfd) < 0) { + close(monfd); + return -1; + } + + /* Consume & discard the initial greeting */ + /* XXX this is broken, we need to block until + we see the initial prompt to ensure startup + has completed */ + for(;;) { + char line[1024]; + if (read(monfd, line, sizeof(line)) < 0) { + if (errno == EAGAIN) { + break; + } + close(monfd); + return -1; + } + QEMUD_DEBUG("[%s]\n", line); + } + vm->monitor = monfd; + } + } + QEMUD_DEBUG("[%s]\n", buf); + } +} + +int qemudShutdownVMDaemon(struct qemud_server *server, struct qemud_vm *vm) { + struct qemud_vm *prev = NULL, *curr = server->activevms; + + /* Already cleaned-up */ + if (vm->pid < 0) + return 0; + + kill(vm->pid, SIGTERM); + + /* Move it to inactive vm list */ + while (curr) { + if (curr == vm) { + if (prev) { + prev->next = curr->next; + } else { + server->activevms = curr->next; + } + server->nactivevms--; + + curr->next = server->inactivevms; + server->inactivevms = curr; + server->ninactivevms++; + break; + } + prev = curr; + curr = curr->next; + } + + if (!curr) { + QEMUD_DEBUG("Could not find VM to shutdown\n"); + return 0; + } + + qemudVMData(server, vm, curr->stdout); + qemudVMData(server, vm, curr->stderr); + close(curr->stdout); + close(curr->stderr); + if (curr->monitor != -1) + close(curr->monitor); + curr->stdout = -1; + curr->stderr = -1; + curr->monitor = -1; + server->nvmfds -= 2; + + if (waitpid(vm->pid, NULL, WNOHANG) != vm->pid) { + kill(vm->pid, SIGKILL); + if (waitpid(vm->pid, NULL, 0) != vm->pid) { + QEMUD_DEBUG("Got unexpected pid, damn\n"); + } + } + + vm->pid = -1; + vm->def.id = -1; + + return 0; +} + +static int qemudDispatchVMLog(struct qemud_server *server, struct qemud_vm *vm, int fd) { + if (qemudVMData(server, vm, fd) < 0) + if (qemudShutdownVMDaemon(server, vm) < 0) + return -1; + return 0; +} + +static int qemudDispatchVMFailure(struct qemud_server *server, struct qemud_vm *vm, + int fd ATTRIBUTE_UNUSED) { + if (qemudShutdownVMDaemon(server, vm) < 0) + return -1; + return 0; +} + + +static int qemudDispatchPoll(struct qemud_server *server, struct pollfd *fds) { + struct qemud_socket *sock = server->sockets; + struct qemud_client *client = server->clients; + struct qemud_vm *vm = server->activevms; + struct qemud_vm *tmp; + int ret = 0; + int fd = 0; + + while (sock) { + struct qemud_socket *next = sock->next; + if (fds[fd].revents) + if (qemudDispatchServer(server, sock) < 0) + return -1; + fd++; + sock = next; + } + + while (client) { + struct qemud_client *next = client->next; + if (fds[fd].revents) { + QEMUD_DEBUG("Poll data normal\n"); + if (fds[fd].revents == POLLOUT) + qemudDispatchClientWrite(server, client); + else if (fds[fd].revents == POLLIN) + qemudDispatchClientRead(server, client); + else + qemudDispatchClientFailure(server, client); + } + fd++; + client = next; + } + while (vm) { + struct qemud_vm *next = vm->next; + int failed = 0, + stdoutfd = vm->stdout, + stderrfd = vm->stderr; + + if (stdoutfd != -1) { + if (fds[fd].revents) { + if (fds[fd].revents == POLLIN) { + if (qemudDispatchVMLog(server, vm, fds[fd].fd) < 0) + failed = 1; + } else { + if (qemudDispatchVMFailure(server, vm, fds[fd].fd) < 0) + failed = 1; + } + } + fd++; + } + if (stderrfd != -1) { + if (!failed) { + if (fds[fd].revents) { + if (fds[fd].revents == POLLIN) { + if (qemudDispatchVMLog(server, vm, fds[fd].fd) < 0) + failed = 1; + } else { + if (qemudDispatchVMFailure(server, vm, fds[fd].fd) < 0) + failed = 1; + } + } + } + fd++; + } + vm = next; + if (failed) + ret = -1; + } + + /* Cleanup any VMs which shutdown & dont have an associated + config file */ + vm = server->inactivevms; + tmp = NULL; + while (vm) { + if (!vm->configFile[0]) { + struct qemud_vm *next = vm->next; + if (tmp) { + tmp->next = next; + } else { + server->inactivevms = next; + } + qemudFreeVM(vm); + vm = next; + } else { + tmp = vm; + vm = vm->next; + } + } + + return ret; +} + +static void qemudPreparePoll(struct qemud_server *server, struct pollfd *fds) { + int fd = 0; + struct qemud_socket *sock; + struct qemud_client *client; + struct qemud_vm *vm; + + for (sock = server->sockets ; sock ; sock = sock->next) { + fds[fd].fd = sock->fd; + fds[fd].events = POLLIN; + fd++; + } + + for (client = server->clients ; client ; client = client->next) { + fds[fd].fd = client->fd; + /* Refuse to read more from client if tx is pending to + rate limit */ + if (client->tx) + fds[fd].events = POLLOUT | POLLERR | POLLHUP; + else + fds[fd].events = POLLIN | POLLERR | POLLHUP; + fd++; + } + for (vm = server->activevms ; vm ; vm = vm->next) { + if (vm->stdout != -1) { + fds[fd].fd = vm->stdout; + fds[fd].events = POLLIN | POLLERR | POLLHUP; + fd++; + } + if (vm->stderr != -1) { + fds[fd].fd = vm->stderr; + fds[fd].events = POLLIN | POLLERR | POLLHUP; + fd++; + } + } +} + + + +static int qemudOneLoop(struct qemud_server *server, int timeout) { + int nfds = server->nsockets + server->nclients + server->nvmfds; + struct pollfd fds[nfds]; + int thistimeout = -1; + int ret; + + /* If we have no clients or vms, then timeout after + 30 seconds, letting daemon exit */ + if (timeout > 0 && + !server->nclients && + !server->nactivevms) + thistimeout = timeout; + + qemudPreparePoll(server, fds); + + retry: + + if ((ret = poll(fds, nfds, thistimeout * 1000)) < 0) { + if (errno == EINTR) { + goto retry; + } + return -1; + } + + /* Must have timed out */ + if (ret == 0) + return -1; + + if (qemudDispatchPoll(server, fds) < 0) + return -1; + + return 0; +} + +static int qemudRunLoop(struct qemud_server *server, int timeout) { + int ret; + + while ((ret = qemudOneLoop(server, timeout)) == 0) + ; + + return ret == -1 ? -1 : 0; +} + +static void qemudCleanup(struct qemud_server *server) { + struct qemud_socket *sock = server->sockets; + while (sock) { + close(sock->fd); + sock = sock->next; + } + free(server); +} + +#define MAX_LISTEN 5 +int main(int argc, char **argv) { + int godaemon = 0; + int verbose = 0; + int sys = 0; + int timeout = -1; + struct qemud_server *server; + + struct option opts[] = { + { "verbose", no_argument, &verbose, 1}, + { "daemon", no_argument, &godaemon, 1}, + { "system", no_argument, &sys, 1}, + { "timeout", required_argument, 0, 't'}, + {0, 0, 0, 0} + }; + + while (1) { + int optidx = 0; + int c; + char *tmp; + + c = getopt_long(argc, argv, "vsdt:", opts, &optidx); + + if (c == -1) { + break; + } + + switch (c) { + case 0: + /* Got one of the flags */ + break; + case 'v': + verbose = 1; + break; + case 'd': + godaemon = 1; + break; + case 's': + sys = 1; + break; + + case 't': + timeout = strtol(optarg, &tmp, 10); + if (!tmp) + timeout = -1; + if (timeout <= 0) + timeout = -1; + break; + case '?': + return 2; + break; + + default: + abort(); + } + } + + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + return 3; + if (signal(SIGCHLD, reapchild) == SIG_ERR) + return 3; + + if (godaemon) { + int pid = qemudGoDaemon(); + if (pid < 0) + return 1; + if (pid > 0) + return 0; + } + + if (!(server = qemudInitialize(sys))) + return 2; + + qemudRunLoop(server, timeout); + + qemudCleanup(server); + + return 0; +} + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/src/Makefile.am b/src/Makefile.am index 26b31cee43..df210f753a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,8 @@ ## Process this file with automake to produce Makefile.in -INCLUDES = -I$(top_builddir)/include -I@top_srcdir@/include @LIBXML_CFLAGS@ \ +INCLUDES = -I$(top_builddir)/include -I@top_srcdir@/include @LIBXML_CFLAGS@ -I@top_srcdir@/qemud \ -DBINDIR=\""$(libexecdir)"\" -DLOCALEBASEDIR=\""$(datadir)/locale"\" \ + -DLOCAL_STATE_DIR=\""$(localstatedir)"\" \ -DGETTEXT_PACKAGE=\"$(PACKAGE)\" DEPS = libvirt.la LDADDS = @STATIC_BINARIES@ libvirt.la @@ -11,7 +12,6 @@ EXTRA_DIST = libvirt_sym.version lib_LTLIBRARIES = libvirt.la libvirt_la_LIBADD = @LIBXML_LIBS@ - libvirt_la_LDFLAGS = -Wl,--version-script=$(srcdir)/libvirt_sym.version \ -version-info @LIBVIRT_VERSION_INFO@ @@ -28,7 +28,8 @@ libvirt_la_SOURCES = \ driver.h \ proxy_internal.c proxy_internal.h \ conf.c conf.h \ - xm_internal.c xm_internal.h + xm_internal.c xm_internal.h \ + qemu_internal.c qemu_internal.h bin_PROGRAMS = virsh diff --git a/src/driver.h b/src/driver.h index 4e708a3a9f..838f70eb0c 100644 --- a/src/driver.h +++ b/src/driver.h @@ -22,7 +22,8 @@ typedef enum { VIR_DRV_XEN_DAEMON = 3, VIR_DRV_TEST = 4, VIR_DRV_XEN_PROXY = 5, - VIR_DRV_XEN_XM = 6 + VIR_DRV_XEN_XM = 6, + VIR_DRV_QEMU = 7 } virDrvNo; diff --git a/src/libvirt.c b/src/libvirt.c index b06bf43ff6..b0ecafb50c 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -32,6 +32,7 @@ #include "proxy_internal.h" #include "xml.h" #include "test.h" +#include "qemu_internal.h" /* * TODO: @@ -79,6 +80,8 @@ virInitialize(void) xenStoreRegister(); xenXMRegister(); testRegister(); + qemuRegister(); + return(0); } @@ -441,6 +444,7 @@ virConnectGetVersion(virConnectPtr conn, unsigned long *hvVer) return(0); } } + return (-1); } diff --git a/src/qemu_internal.c b/src/qemu_internal.c new file mode 100644 index 0000000000..c5e226bc84 --- /dev/null +++ b/src/qemu_internal.c @@ -0,0 +1,864 @@ +/* + * qemu_internal.c: A backend for managing QEMU machines + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internal.h" +#include "qemu_internal.h" +#include "xml.h" +#include "protocol.h" + + + +static void +qemuError(virConnectPtr con, + virDomainPtr dom, + virErrorNumber error, + const char *info) +{ + const char *errmsg; + + if (error == VIR_ERR_OK) + return; + + errmsg = __virErrorMsg(error, info); + __virRaiseError(con, dom, VIR_FROM_QEMU, error, VIR_ERR_ERROR, + errmsg, info, NULL, 0, 0, errmsg, info, 0); +} + +static void qemuPacketError(virConnectPtr con, + virDomainPtr dom, + struct qemud_packet *pkt) { + if (!pkt) { + qemuError(con, dom, VIR_ERR_INTERNAL_ERROR, "Malformed data packet"); + return; + } + if (pkt->header.type == QEMUD_PKT_FAILURE) { + /* Paranoia in case remote side didn't terminate it */ + if (pkt->data.failureReply.message[0]) + pkt->data.failureReply.message[QEMUD_MAX_ERROR_LEN-1] = '\0'; + + qemuError(con, + dom, + pkt->data.failureReply.code, + pkt->data.failureReply.message[0] ? + pkt->data.failureReply.message : NULL); + } else { + qemuError(con, dom, VIR_ERR_INTERNAL_ERROR, "Incorrect reply type"); + } +} + + +/** + * qemuFindServerPath: + * + * Tries to find the path to the qemu binary. + * + * Returns path on success or NULL in case of error. + */ +static const char * +qemuFindServerPath(void) +{ + static const char *serverPaths[] = { + BINDIR "/libvirt_qemu", + BINDIR "/libvirt_qemu_dbg", + NULL + }; + int i; + const char *debugQemu = getenv("LIBVIRT_QEMU_SERVER"); + + if (debugQemu) + return(debugQemu); + + for (i = 0; serverPaths[i]; i++) { + if (access(serverPaths[i], X_OK | R_OK) == 0) { + return serverPaths[i]; + } + } + return NULL; +} + + +/** + * qemuForkServer: + * + * Forks and try to launch the qemu server + * + * Returns 0 in case of success or -1 in case of detected error. + */ +static int +qemuForkServer(void) +{ + const char *proxyPath = qemuFindServerPath(); + int ret, pid, status; + + if (!proxyPath) { + fprintf(stderr, "failed to find qemu\n"); + return(-1); + } + + /* Become a daemon */ + pid = fork(); + if (pid == 0) { + int stdinfd = -1; + int stdoutfd = -1; + int i, open_max; + if ((stdinfd = open(_PATH_DEVNULL, O_RDONLY)) < 0) + goto cleanup; + if ((stdoutfd = open(_PATH_DEVNULL, O_WRONLY)) < 0) + goto cleanup; + if (dup2(stdinfd, STDIN_FILENO) != STDIN_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDOUT_FILENO) != STDOUT_FILENO) + goto cleanup; + if (dup2(stdoutfd, STDERR_FILENO) != STDERR_FILENO) + goto cleanup; + if (close(stdinfd) < 0) + goto cleanup; + stdinfd = -1; + if (close(stdoutfd) < 0) + goto cleanup; + stdoutfd = -1; + + open_max = sysconf (_SC_OPEN_MAX); + for (i = 0; i < open_max; i++) + if (i != STDIN_FILENO && + i != STDOUT_FILENO && + i != STDERR_FILENO) + close(i); + + setsid(); + if (fork() == 0) { + /* Run daemon in auto-shutdown mode, so it goes away when + no longer needed by an active guest, or client */ + execl(proxyPath, proxyPath, "--timeout", "30", NULL); + fprintf(stderr, "failed to exec %s\n", proxyPath); + } + /* + * calling exit() generate troubles for termination handlers + */ + _exit(0); + + cleanup: + if (stdoutfd != -1) + close(stdoutfd); + if (stdinfd != -1) + close(stdinfd); + _exit(-1); + } + + /* + * do a waitpid on the intermediate process to avoid zombies. + */ + retry_wait: + ret = waitpid(pid, &status, 0); + if (ret < 0) { + if (errno == EINTR) + goto retry_wait; + } + + return (0); +} + +/** + * qemuOpenClientUNIX: + * @path: the fileame for the socket + * + * try to connect to the socket open by qemu + * + * Returns the associated file descriptor or -1 in case of failure + */ +static int +qemuOpenClientUNIX(virConnectPtr conn, const char *path, int autostart) { + int fd; + struct sockaddr_un addr; + int trials = 0; + + retry: + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + return(-1); + } + + /* + * Abstract socket do not hit the filesystem, way more secure and + * garanteed to be atomic + */ + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + if (addr.sun_path[0] == '@') + addr.sun_path[0] = '\0'; + + /* + * now bind the socket to that address and listen on it + */ + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd); + if (autostart && trials < 3) { + if (qemuForkServer() < 0) + return(-1); + trials++; + usleep(5000 * trials * trials); + goto retry; + } + return (-1); + } + + conn->handle = fd; + + return (0); +} + + +/* Takes a single request packet, does a blocking send on it. + * then blocks until the complete reply has come back, or + * connection closes. + */ +static int qemuProcessRequest(virConnectPtr conn, + virDomainPtr dom, + struct qemud_packet *req, + struct qemud_packet *reply) { + char *out = (char *)req; + int outDone = 0; + int outLeft = sizeof(struct qemud_packet_header) + req->header.dataSize; + char *in = (char *)reply; + int inGot = 0; + int inLeft = sizeof(struct qemud_packet_header); + + /* printf("Send request %d\n", req->header.type); */ + + /* Block sending entire outgoing packet */ + while (outLeft) { + int got = write(conn->handle, out+outDone, outLeft); + if (got < 0) { + return -1; + } + outDone += got; + outLeft -= got; + } + + /* Block waiting for header to come back */ + while (inLeft) { + int done = read(conn->handle, in+inGot, inLeft); + if (done <= 0) { + return -1; + } + inGot += done; + inLeft -= done; + } + + /* Validate header isn't bogus (bigger than + maximum defined packet size) */ + if (reply->header.dataSize > sizeof(union qemud_packet_data)) { + /* + printf("Got type %ds body %d (max %ld)\n", + reply->header.type, + reply->header.dataSize, + sizeof(union qemud_packet_data)); + printf("%ld == %ld + %ld\n", + sizeof(struct qemud_packet), + sizeof(struct qemud_packet_header), + sizeof(union qemud_packet_data)); + */ + qemuPacketError(conn, dom, NULL); + return -1; + } + + /* Now block reading in body */ + inLeft = reply->header.dataSize; + while (inLeft) { + int done = read(conn->handle, in+inGot, inLeft); + if (done <= 0) { + return -1; + } + inGot += done; + inLeft -= done; + } + + if (reply->header.type != req->header.type) { + qemuPacketError(conn, dom, reply); + return -1; + } + + return 0; +} + + +/* + * Open a connection to the libvirt QEMU daemon + */ +static int qemuOpenConnection(virConnectPtr conn, xmlURIPtr uri, int readonly ATTRIBUTE_UNUSED) { + char path[PATH_MAX]; + + if (uri->server != NULL) { + return -1; + } + + if (!strcmp(uri->path, "/system")) { + if (readonly) { + if (snprintf(path, sizeof(path), "%s/run/qemud/sock-ro", LOCAL_STATE_DIR) >= (int)sizeof(path)) { + return -1; + } + } else { + if (snprintf(path, sizeof(path), "%s/run/qemud/sock", LOCAL_STATE_DIR) >= (int)sizeof(path)) { + return -1; + } + } + } else if (!strcmp(uri->path, "/session")) { + struct passwd *pw; + int uid; + + if ((uid = geteuid()) < 0) { + return -1; + } + + if (!(pw = getpwuid(uid))) + return -1; + + if (snprintf(path, sizeof(path), "@%s/.qemud/sock", pw->pw_dir) == sizeof(path)) { + return -1; + } + } + return qemuOpenClientUNIX(conn, path, 1); +} + + +/* + * Open a connection to the QEMU manager + */ +static int qemuOpen(virConnectPtr conn, + const char *name, + int flags){ + xmlURIPtr uri; + + if (!name) { + return -1; + } + + uri = xmlParseURI(name); + if (uri == NULL) { + if (!(flags & VIR_DRV_OPEN_QUIET)) + qemuError(conn, NULL, VIR_ERR_NO_SUPPORT, name); + return(-1); + } + + if (!uri->scheme || + strcmp(uri->scheme, "qemu") || + !uri->path) { + xmlFreeURI(uri); + return -1; + } + + conn->handle = -1; + qemuOpenConnection(conn, uri, flags & VIR_DRV_OPEN_RO ? 1 : 0); + xmlFreeURI(uri); + + if (conn->handle < 0) { + return -1; + } + + return 0; +} + + +static int qemuClose (virConnectPtr conn) { + if (conn->handle != -1) { + close(conn->handle); + conn->handle = -1; + } + return 0; +} + + +static int qemuGetVersion(virConnectPtr conn, + unsigned long *hvVer) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_GET_VERSION; + req.header.dataSize = 0; + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return -1; + } + + *hvVer = reply.data.getVersionReply.version; + return 0; +} + + +static int qemuNodeGetInfo(virConnectPtr conn, + virNodeInfoPtr info) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_GET_NODEINFO; + req.header.dataSize = 0; + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return -1; + } + + info->cores = reply.data.getNodeInfoReply.cores; + info->threads = reply.data.getNodeInfoReply.threads; + info->sockets = reply.data.getNodeInfoReply.sockets; + info->nodes = reply.data.getNodeInfoReply.nodes; + strncpy(info->model, reply.data.getNodeInfoReply.model, sizeof(info->model)); + info->mhz = reply.data.getNodeInfoReply.mhz; + info->cpus = reply.data.getNodeInfoReply.cpus; + info->memory = reply.data.getNodeInfoReply.memory; + return 0; +} + + +static int qemuNumOfDomains(virConnectPtr conn) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_NUM_DOMAINS; + req.header.dataSize = 0; + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return -1; + } + + return reply.data.numDomainsReply.numDomains; +} + + +static int qemuListDomains(virConnectPtr conn, + int *ids, + int maxids) { + struct qemud_packet req, reply; + int i, nDomains; + + req.header.type = QEMUD_PKT_LIST_DOMAINS; + req.header.dataSize = 0; + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return -1; + } + + nDomains = reply.data.listDomainsReply.numDomains; + if (nDomains > maxids) + nDomains = maxids; + + for (i = 0 ; i < nDomains ; i++) { + ids[i] = reply.data.listDomainsReply.domains[i]; + } + + return nDomains; +} + + +static virDomainPtr +qemuDomainCreateLinux(virConnectPtr conn, const char *xmlDesc, + unsigned int flags ATTRIBUTE_UNUSED) { + struct qemud_packet req, reply; + virDomainPtr dom; + int len = strlen(xmlDesc); + + if (len > (QEMUD_MAX_XML_LEN-1)) { + return NULL; + } + + req.header.type = QEMUD_PKT_DOMAIN_CREATE; + req.header.dataSize = sizeof(req.data.domainCreateRequest); + strcpy(req.data.domainCreateRequest.xml, xmlDesc); + req.data.domainCreateRequest.xml[QEMUD_MAX_XML_LEN-1] = '\0'; + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return NULL; + } + + reply.data.domainCreateReply.name[QEMUD_MAX_NAME_LEN-1] = '\0'; + + if (!(dom = virGetDomain(conn, + reply.data.domainCreateReply.name, + reply.data.domainCreateReply.uuid))) + return NULL; + + dom->id = reply.data.domainCreateReply.id; + return dom; +} + + +static virDomainPtr qemuLookupDomainByID(virConnectPtr conn, + int id) { + struct qemud_packet req, reply; + virDomainPtr dom; + + req.header.type = QEMUD_PKT_DOMAIN_LOOKUP_BY_ID; + req.header.dataSize = sizeof(req.data.domainLookupByIDRequest); + req.data.domainLookupByIDRequest.id = id; + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return NULL; + } + + reply.data.domainLookupByIDReply.name[QEMUD_MAX_NAME_LEN-1] = '\0'; + + if (!(dom = virGetDomain(conn, + reply.data.domainLookupByIDReply.name, + reply.data.domainLookupByIDReply.uuid))) + return NULL; + + dom->id = id; + return dom; +} + + +static virDomainPtr qemuLookupDomainByUUID(virConnectPtr conn, + const unsigned char *uuid) { + struct qemud_packet req, reply; + virDomainPtr dom; + + req.header.type = QEMUD_PKT_DOMAIN_LOOKUP_BY_UUID; + req.header.dataSize = sizeof(req.data.domainLookupByUUIDRequest); + memmove(req.data.domainLookupByUUIDRequest.uuid, uuid, QEMUD_UUID_RAW_LEN); + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return NULL; + } + + reply.data.domainLookupByUUIDReply.name[QEMUD_MAX_NAME_LEN-1] = '\0'; + + if (!(dom = virGetDomain(conn, + reply.data.domainLookupByUUIDReply.name, + uuid))) + return NULL; + + dom->id = reply.data.domainLookupByUUIDReply.id; + return dom; +} + + +static virDomainPtr qemuLookupDomainByName(virConnectPtr conn, + const char *name) { + struct qemud_packet req, reply; + virDomainPtr dom; + + if (strlen(name) > (QEMUD_MAX_NAME_LEN-1)) + return NULL; + + req.header.type = QEMUD_PKT_DOMAIN_LOOKUP_BY_NAME; + req.header.dataSize = sizeof(req.data.domainLookupByNameRequest); + strcpy(req.data.domainLookupByNameRequest.name, name); + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return NULL; + } + + if (!(dom = virGetDomain(conn, + name, + reply.data.domainLookupByNameReply.uuid))) + return NULL; + + dom->id = reply.data.domainLookupByNameReply.id; + return dom; +} + +static int qemuDestroyDomain(virDomainPtr domain) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_DOMAIN_DESTROY; + req.header.dataSize = sizeof(req.data.domainDestroyRequest); + req.data.domainDestroyRequest.id = domain->id; + + if (qemuProcessRequest(domain->conn, NULL, &req, &reply) < 0) { + return -1; + } + + return 0; +} + +static int qemuShutdownDomain(virDomainPtr domain) { + return qemuDestroyDomain(domain); +} + +static int qemuResumeDomain(virDomainPtr domain ATTRIBUTE_UNUSED) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_DOMAIN_RESUME; + req.header.dataSize = sizeof(req.data.domainResumeRequest); + req.data.domainResumeRequest.id = domain->id; + + if (qemuProcessRequest(domain->conn, NULL, &req, &reply) < 0) { + return -1; + } + + return 0; +} + +static int qemuPauseDomain(virDomainPtr domain ATTRIBUTE_UNUSED) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_DOMAIN_SUSPEND; + req.header.dataSize = sizeof(req.data.domainSuspendRequest); + req.data.domainSuspendRequest.id = domain->id; + + if (qemuProcessRequest(domain->conn, NULL, &req, &reply) < 0) { + return -1; + } + + return 0; +} + +static int qemuGetDomainInfo(virDomainPtr domain, + virDomainInfoPtr info) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_DOMAIN_GET_INFO; + req.header.dataSize = sizeof(req.data.domainGetInfoRequest); + memmove(req.data.domainGetInfoRequest.uuid, domain->uuid, QEMUD_UUID_RAW_LEN); + + if (qemuProcessRequest(domain->conn, NULL, &req, &reply) < 0) { + return -1; + } + + memset(info, 0, sizeof(virDomainInfo)); + switch (reply.data.domainGetInfoReply.runstate) { + case QEMUD_STATE_RUNNING: + info->state = VIR_DOMAIN_RUNNING; + break; + + case QEMUD_STATE_PAUSED: + info->state = VIR_DOMAIN_PAUSED; + break; + + case QEMUD_STATE_STOPPED: + info->state = VIR_DOMAIN_SHUTOFF; + break; + + default: + return -1; + } + info->maxMem = reply.data.domainGetInfoReply.maxmem; + info->memory = reply.data.domainGetInfoReply.memory; + info->nrVirtCpu = reply.data.domainGetInfoReply.nrVirtCpu; + info->cpuTime = reply.data.domainGetInfoReply.cpuTime; + + return 0; +} + +static char *qemuDomainDumpXML(virDomainPtr domain, int flags ATTRIBUTE_UNUSED) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_DUMP_XML; + req.header.dataSize = sizeof(req.data.domainDumpXMLRequest); + memmove(req.data.domainDumpXMLRequest.uuid, domain->uuid, QEMUD_UUID_RAW_LEN); + + if (qemuProcessRequest(domain->conn, NULL, &req, &reply) < 0) { + return NULL; + } + + reply.data.domainDumpXMLReply.xml[QEMUD_MAX_XML_LEN-1] = '\0'; + + return strdup(reply.data.domainDumpXMLReply.xml); +} + +static int qemuSaveDomain(virDomainPtr domain ATTRIBUTE_UNUSED, const char *file ATTRIBUTE_UNUSED) { + return -1; +} + +static int qemuRestoreDomain(virConnectPtr conn ATTRIBUTE_UNUSED, const char *file ATTRIBUTE_UNUSED) { + return -1; +} + + +static int qemuNumOfDefinedDomains(virConnectPtr conn) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_NUM_DEFINED_DOMAINS; + req.header.dataSize = 0; + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return -1; + } + + return reply.data.numDefinedDomainsReply.numDomains; +} + +static int qemuListDefinedDomains(virConnectPtr conn, + const char **names, + int maxnames){ + struct qemud_packet req, reply; + int i, nDomains; + + req.header.type = QEMUD_PKT_LIST_DEFINED_DOMAINS; + req.header.dataSize = 0; + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return -1; + } + + nDomains = reply.data.listDefinedDomainsReply.numDomains; + if (nDomains > maxnames) + nDomains = maxnames; + + for (i = 0 ; i < nDomains ; i++) { + reply.data.listDefinedDomainsReply.domains[i][QEMUD_MAX_NAME_LEN-1] = '\0'; + names[i] = strdup(reply.data.listDefinedDomainsReply.domains[i]); + } + + return nDomains; +} + +static int qemuDomainCreate(virDomainPtr dom) { + struct qemud_packet req, reply; + + req.header.type = QEMUD_PKT_DOMAIN_START; + req.header.dataSize = sizeof(req.data.domainStartRequest); + memcpy(req.data.domainStartRequest.uuid, dom->uuid, QEMUD_UUID_RAW_LEN); + + if (qemuProcessRequest(dom->conn, NULL, &req, &reply) < 0) { + return -1; + } + + dom->id = reply.data.domainStartReply.id; + + return 0; +} + +static virDomainPtr qemuDomainDefineXML(virConnectPtr conn, const char *xml) { + struct qemud_packet req, reply; + virDomainPtr dom; + int len = strlen(xml); + + if (len > (QEMUD_MAX_XML_LEN-1)) { + return NULL; + } + + req.header.type = QEMUD_PKT_DOMAIN_DEFINE; + req.header.dataSize = sizeof(req.data.domainDefineRequest); + strcpy(req.data.domainDefineRequest.xml, xml); + req.data.domainDefineRequest.xml[QEMUD_MAX_XML_LEN-1] = '\0'; + + if (qemuProcessRequest(conn, NULL, &req, &reply) < 0) { + return NULL; + } + + reply.data.domainDefineReply.name[QEMUD_MAX_NAME_LEN-1] = '\0'; + + if (!(dom = virGetDomain(conn, + reply.data.domainDefineReply.name, + reply.data.domainDefineReply.uuid))) + return NULL; + + dom->id = -1; + return dom; +} + +static int qemuUndefine(virDomainPtr dom) { + struct qemud_packet req, reply; + int ret = 0; + + req.header.type = QEMUD_PKT_DOMAIN_UNDEFINE; + req.header.dataSize = sizeof(req.data.domainUndefineRequest); + memcpy(req.data.domainUndefineRequest.uuid, dom->uuid, QEMUD_UUID_RAW_LEN); + + if (qemuProcessRequest(dom->conn, NULL, &req, &reply) < 0) { + ret = -1; + goto cleanup; + } + + cleanup: + if (virFreeDomain(dom->conn, dom) < 0) + ret = -1; + + return ret; +} + + +static virDriver qemuDriver = { + VIR_DRV_QEMU, + "QEMU", + LIBVIR_VERSION_NUMBER, + NULL, /* init */ + qemuOpen, /* open */ + qemuClose, /* close */ + NULL, /* type */ + qemuGetVersion, /* version */ + qemuNodeGetInfo, /* nodeGetInfo */ + qemuListDomains, /* listDomains */ + qemuNumOfDomains, /* numOfDomains */ + qemuDomainCreateLinux, /* domainCreateLinux */ + qemuLookupDomainByID, /* domainLookupByID */ + qemuLookupDomainByUUID, /* domainLookupByUUID */ + qemuLookupDomainByName, /* domainLookupByName */ + qemuPauseDomain, /* domainSuspend */ + qemuResumeDomain, /* domainResume */ + qemuShutdownDomain, /* domainShutdown */ + NULL, /* domainReboot */ + qemuDestroyDomain, /* domainDestroy */ + NULL, /* domainGetOSType */ + NULL, /* domainGetMaxMemory */ + NULL, /* domainSetMaxMemory */ + NULL, /* domainSetMemory */ + qemuGetDomainInfo, /* domainGetInfo */ + qemuSaveDomain, /* domainSave */ + qemuRestoreDomain, /* domainRestore */ + NULL, /* domainCoreDump */ + NULL, /* domainSetVcpus */ + NULL, /* domainPinVcpu */ + NULL, /* domainGetVcpus */ + qemuDomainDumpXML, /* domainDumpXML */ + qemuListDefinedDomains, /* listDomains */ + qemuNumOfDefinedDomains, /* numOfDomains */ + qemuDomainCreate, /* domainCreate */ + qemuDomainDefineXML, /* domainDefineXML */ + qemuUndefine, /* domainUndefine */ + NULL, /* domainAttachDevice */ + NULL, /* domainDetachDevice */ +}; + +void qemuRegister(void) { + virRegisterDriver(&qemuDriver); +} + + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/src/qemu_internal.h b/src/qemu_internal.h new file mode 100644 index 0000000000..ae2833d561 --- /dev/null +++ b/src/qemu_internal.h @@ -0,0 +1,48 @@ +/* + * qemu_internal.h: A backend for managing QEMU machines + * + * Copyright (C) 2006-2007 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange + */ + +#ifndef __VIR_QEMU_INTERNAL_H__ +#define __VIR_QEMU_INTERNAL_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + void qemuRegister(void); + +#ifdef __cplusplus +} +#endif +#endif /* __VIR_QEMU_INTERNAL_H__ */ + + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/src/virsh.c b/src/virsh.c index 212d6d6b45..390788d56a 100644 --- a/src/virsh.c +++ b/src/virsh.c @@ -2529,7 +2529,9 @@ vshInit(vshControl * ctl) /* basic connection to hypervisor, for Xen connections unless we're root open a read only connections. Allow 'test' HV to be RW all the time though */ - if (ctl->uid == 0 || (ctl->name && !strncmp(ctl->name, "test", 4))) + if (ctl->uid == 0 || (ctl->name && + (!strncmp(ctl->name, "test", 4) || + !strncmp(ctl->name, "qemu", 4)))) ctl->conn = virConnectOpen(ctl->name); else ctl->conn = virConnectOpenReadOnly(ctl->name); diff --git a/src/virterror.c b/src/virterror.c index 5d3a99a3c1..b180c4e72a 100644 --- a/src/virterror.c +++ b/src/virterror.c @@ -268,6 +268,9 @@ virDefaultErrorFunc(virErrorPtr err) case VIR_FROM_RPC: dom = "XML-RPC "; break; + case VIR_FROM_QEMU: + dom = "QEMU "; + break; } if ((err->dom != NULL) && (err->code != VIR_ERR_INVALID_DOMAIN)) { domain = err->dom->name;