This patch implements a driver supporting User Mode Linux guests. User mode linux is a kind of paravirtualized kernel which runs on a plain Linux host. It requires no elevated privileges at all, except for some of the network integration. It is a pretty straightforward thing to invoke, so I figured it would be easy to write a driver to support it. I was right :-) As with the QEMU driver, there are two styles of invocation uml:///system - a single privileged, per host driver instance as root uml:///session - a per-user unprivileged instance The user mode linux docs recommend against running as root, so naturally I recommend use of uml:///session, though my testing is so far with uml:///system. It supports the bare minimum neccessary for a libvirt driver, namely start, stop, define, create, destroy, undefine against domains. For configuration it supports setting of memory limit, virtual disk devices, and paravirt console devices - good enough to get 'virsh console' working over a PTY, and dump XML config. Like QEMU, UML has an admin monitor console. It is a little different though, using a UNIX domain socket, in datagram mode, sending command strings back & forth. This actaully makes it a little easier to deal with in libvirt. User mode linux has a uml_mconsole command line tool to interact with it, but I directly implement the socket support in libvirt instead, so this driver does not (yet) have any dependancy on the UML utilities. The driver is largely a clone of the QEMU driver, replacing the bit which builds the command line argv, and all code dealing with the QEMU monitor. Since the monitor socket is datagram based, we can't make use of it for detecting VM shutdown as we do with QEMU/LXC. So instead, I make use of inotify, to monitor $HOME/.uml which is populated witha directory for each VM. When we see a directory created, libvirt marks the corresponding VM as running, finds it PID & probes the monitor for the PTY config. When we see a directory deleted, libvirt makes the VM as shutoff and frees any resources its holding. This actually works very nicely & simply. An example config I'm using for testing is <domain type='uml'> <name>demo</name> <uuid>b4433fc2-a22e-ffb3-0a3d-9c173b395800</uuid> <memory>500000</memory> <os> <type arch='x86_64'>uml</type> <kernel>/home/berrange/linux</kernel> </os> <devices> <disk type='file' device='disk'> <source file='/home/berrange/FedoraCore6-AMD64-root_fs'/> <target dev='ubd0' bus='uml'/> </disk> <console type='pty'> <target port='0'/> </console> <serial type='tcp'> <source mode='bind' host='localhost' service='9000'/> <target port='0'/> </serial> <serial type='pty'> <target port='3'/> </serial> </devices> </domain> The user mode linux kernel, has the device emulator built-in, so there's no need to specify the device emulator path explicitly. The kernel I built straight from latest 2.6.27 sources, and the root filesystem I downloaded from the UML website. I've been testing with latest libvirt CVS, on a Fedora 9 x86_64 host. If you save that config to a file 'demo.xml', you ought to be able todo virsh --connect uml:///system define demo.xml virsh --connect uml:///system start demo virsh --connect uml:///system console demo Though there is still a small race whereby we can ask for the PTY device before its been created, causing console to fail. If things go wrong /var/log/libvirt/uml/$NAME.log contains log messages, The configuration is stored in /etc/libvirt/uml/$NAME.xml, or in $HOME/.libvirt/uml/$NAME.xml, and you can also query stuff with mconsole by doing 'uml_mconsole $NAME' This isn't quite ready to merge, but I thought I'd send it here to make people aware of it. configure.in | 8 include/libvirt/virterror.h | 1 src/Makefile.am | 9 src/domain_conf.c | 47 - src/domain_conf.h | 1 src/driver.h | 3 src/libvirt.c | 6 src/qemu_conf.c | 3 src/remote_internal.c | 16 src/uml_conf.c | 404 ++++++++++ src/uml_conf.h | 72 + src/uml_driver.c | 1771 ++++++++++++++++++++++++++++++++++++++++++++ src/uml_driver.h | 34 src/virterror.c | 4 14 files changed, 2355 insertions(+), 24 deletions(-) Daniel diff --git a/configure.in b/configure.in --- a/configure.in +++ b/configure.in @@ -149,6 +149,8 @@ AC_ARG_WITH([xen], [ --with-xen add XEN support (on)],[],[with_xen=yes]) AC_ARG_WITH([qemu], [ --with-qemu add QEMU/KVM support (on)],[],[with_qemu=yes]) +AC_ARG_WITH([uml], +[ --with-uml add UML support (on)],[],[with_uml=yes]) AC_ARG_WITH([openvz], [ --with-openvz add OpenVZ support (on)],[],[with_openvz=yes]) AC_ARG_WITH([lxc], @@ -261,6 +263,11 @@ if test "$with_qemu" = "yes" ; then LIBVIRT_FEATURES="$LIBVIRT_FEATURES -DWITH_QEMU" fi AM_CONDITIONAL([WITH_QEMU], [test "$with_qemu" = "yes"]) + +if test "$with_uml" = "yes" ; then + LIBVIRT_FEATURES="$LIBVIRT_FEATURES -DWITH_UML" +fi +AM_CONDITIONAL([WITH_UML], [test "$with_uml" = "yes"]) if test "$with_test" = "yes" ; then LIBVIRT_FEATURES="$LIBVIRT_FEATURES -DWITH_TEST" @@ -1083,6 +1090,7 @@ AC_MSG_NOTICE([ Xen: $with_xen]) AC_MSG_NOTICE([ Xen: $with_xen]) AC_MSG_NOTICE([ Proxy: $with_xen_proxy]) AC_MSG_NOTICE([ QEMU: $with_qemu]) +AC_MSG_NOTICE([ UML: $with_uml]) AC_MSG_NOTICE([ OpenVZ: $with_openvz]) AC_MSG_NOTICE([ LXC: $with_lxc]) AC_MSG_NOTICE([ Test: $with_test]) diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -58,6 +58,7 @@ typedef enum { VIR_FROM_STORAGE, /* Error from storage driver */ VIR_FROM_NETWORK, /* Error from network config */ VIR_FROM_DOMAIN, /* Error from domain config */ + VIR_FROM_UML, /* Error at the UML driver */ } virErrorDomain; diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -107,6 +107,10 @@ QEMU_DRIVER_SOURCES = \ QEMU_DRIVER_SOURCES = \ qemu_conf.c qemu_conf.h \ qemu_driver.c qemu_driver.h + +UML_DRIVER_SOURCES = \ + uml_conf.c uml_conf.h \ + uml_driver.c uml_driver.h NETWORK_DRIVER_SOURCES = \ network_driver.h network_driver.c @@ -178,6 +182,10 @@ libvirt_la_SOURCES += $(QEMU_DRIVER_SOUR libvirt_la_SOURCES += $(QEMU_DRIVER_SOURCES) endif +if WITH_UML +libvirt_la_SOURCES += $(UML_DRIVER_SOURCES) +endif + if WITH_LXC libvirt_la_SOURCES += $(LXC_DRIVER_SOURCES) endif @@ -201,6 +209,7 @@ EXTRA_DIST += \ $(REMOTE_DRIVER_SOURCES) \ $(XEN_DRIVER_SOURCES) \ $(QEMU_DRIVER_SOURCES) \ + $(UML_DRIVER_SOURCES) \ $(LXC_DRIVER_SOURCES) \ $(OPENVZ_DRIVER_SOURCES) \ $(NETWORK_DRIVER_SOURCES) \ diff --git a/src/domain_conf.c b/src/domain_conf.c --- a/src/domain_conf.c +++ b/src/domain_conf.c @@ -86,7 +86,8 @@ VIR_ENUM_IMPL(virDomainDiskBus, VIR_DOMA "scsi", "virtio", "xen", - "usb") + "usb", + "uml") VIR_ENUM_IMPL(virDomainFS, VIR_DOMAIN_FS_TYPE_LAST, "mount", @@ -610,7 +611,8 @@ virDomainDiskDefParseXML(virConnectPtr c !STRPREFIX((const char *)target, "hd") && !STRPREFIX((const char *)target, "sd") && !STRPREFIX((const char *)target, "vd") && - !STRPREFIX((const char *)target, "xvd")) { + !STRPREFIX((const char *)target, "xvd") && + !STRPREFIX((const char *)target, "ubd")) { virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, _("Invalid harddisk device name: %s"), target); goto error; @@ -634,6 +636,8 @@ virDomainDiskDefParseXML(virConnectPtr c def->bus = VIR_DOMAIN_DISK_BUS_VIRTIO; else if (STRPREFIX(target, "xvd")) def->bus = VIR_DOMAIN_DISK_BUS_XEN; + else if (STRPREFIX(target, "ubd")) + def->bus = VIR_DOMAIN_DISK_BUS_UML; else def->bus = VIR_DOMAIN_DISK_BUS_IDE; } @@ -1900,13 +1904,16 @@ static virDomainDefPtr virDomainDefParse } if (STREQ(def->os.type, "xen") || - STREQ(def->os.type, "hvm")) { + STREQ(def->os.type, "hvm") || + STREQ(def->os.type, "uml")) { def->os.kernel = virXPathString(conn, "string(./os/kernel[1])", ctxt); def->os.initrd = virXPathString(conn, "string(./os/initrd[1])", ctxt); def->os.cmdline = virXPathString(conn, "string(./os/cmdline[1])", ctxt); def->os.root = virXPathString(conn, "string(./os/root[1])", ctxt); def->os.loader = virXPathString(conn, "string(./os/loader[1])", ctxt); + } + if (STREQ(def->os.type, "hvm")) { /* analysis of the boot devices */ if ((n = virXPathNodeSet(conn, "./os/boot", ctxt, &nodes)) < 0) { virDomainReportError(conn, VIR_ERR_INTERNAL_ERROR, @@ -2036,32 +2043,30 @@ static virDomainDefPtr virDomainDefParse } VIR_FREE(nodes); - /* - * If no serial devices were listed, then look for console - * devices which is the legacy syntax for the same thing - */ - if (def->nserials == 0) { - if ((node = virXPathNode(conn, "./devices/console[1]", ctxt)) != NULL) { - virDomainChrDefPtr chr = virDomainChrDefParseXML(conn, - node); - if (!chr) - goto error; + if ((node = virXPathNode(conn, "./devices/console[1]", ctxt)) != NULL) { + virDomainChrDefPtr chr = virDomainChrDefParseXML(conn, + node); + if (!chr) + goto error; - chr->dstPort = 0; - /* - * For HVM console actually created a serial device - * while for non-HVM it was a parvirt console - */ - if (STREQ(def->os.type, "hvm")) { + chr->dstPort = 0; + /* + * For HVM console actually created a serial device + * while for non-HVM it was a parvirt console + */ + if (STREQ(def->os.type, "hvm")) { + if (def->nserials != 0) { + virDomainChrDefFree(chr); + } else { if (VIR_ALLOC_N(def->serials, 1) < 0) { virDomainChrDefFree(chr); goto no_memory; } def->nserials = 1; def->serials[0] = chr; - } else { - def->console = chr; } + } else { + def->console = chr; } } diff --git a/src/domain_conf.h b/src/domain_conf.h --- a/src/domain_conf.h +++ b/src/domain_conf.h @@ -73,6 +73,7 @@ enum virDomainDiskBus { VIR_DOMAIN_DISK_BUS_VIRTIO, VIR_DOMAIN_DISK_BUS_XEN, VIR_DOMAIN_DISK_BUS_USB, + VIR_DOMAIN_DISK_BUS_UML, VIR_DOMAIN_DISK_BUS_LAST }; diff --git a/src/driver.h b/src/driver.h --- a/src/driver.h +++ b/src/driver.h @@ -22,7 +22,8 @@ typedef enum { VIR_DRV_QEMU = 3, VIR_DRV_REMOTE = 4, VIR_DRV_OPENVZ = 5, - VIR_DRV_LXC = 6 + VIR_DRV_LXC = 6, + VIR_DRV_UML = 7, } virDrvNo; diff --git a/src/libvirt.c b/src/libvirt.c --- a/src/libvirt.c +++ b/src/libvirt.c @@ -50,6 +50,9 @@ #ifdef WITH_QEMU #include "qemu_driver.h" #endif +#ifdef WITH_UML +#include "uml_driver.h" +#endif #ifdef WITH_OPENVZ #include "openvz_driver.h" #endif @@ -289,6 +292,9 @@ virInitialize(void) #ifdef WITH_LIBVIRTD #ifdef WITH_QEMU if (qemudRegister() == -1) return -1; +#endif +#ifdef WITH_UML + if (umlRegister() == -1) return -1; #endif #ifdef WITH_OPENVZ if (openvzRegister() == -1) return -1; diff --git a/src/qemu_conf.c b/src/qemu_conf.c --- a/src/qemu_conf.c +++ b/src/qemu_conf.c @@ -55,7 +55,8 @@ VIR_ENUM_IMPL(virDomainDiskQEMUBus, VIR_ "scsi", "virtio", "xen", - "usb") + "usb", + "uml") #define qemudLog(level, msg...) fprintf(stderr, msg) diff --git a/src/remote_internal.c b/src/remote_internal.c --- a/src/remote_internal.c +++ b/src/remote_internal.c @@ -757,6 +757,22 @@ remoteOpen (virConnectPtr conn, #if WITH_QEMU if (uri && uri->scheme && STREQ (uri->scheme, "qemu") && + (!uri->server || STREQ (uri->server, "")) && + uri->path) { + if (STREQ (uri->path, "/system")) { + rflags |= VIR_DRV_OPEN_REMOTE_UNIX; + } else if (STREQ (uri->path, "/session")) { + rflags |= VIR_DRV_OPEN_REMOTE_UNIX; + if (getuid() > 0) { + rflags |= VIR_DRV_OPEN_REMOTE_USER; + rflags |= VIR_DRV_OPEN_REMOTE_AUTOSTART; + } + } + } +#endif +#if WITH_UML + if (uri && + uri->scheme && STREQ (uri->scheme, "uml") && (!uri->server || STREQ (uri->server, "")) && uri->path) { if (STREQ (uri->path, "/system")) { diff --git a/src/uml_conf.c b/src/uml_conf.c new file mode 100644 --- /dev/null +++ b/src/uml_conf.c @@ -0,0 +1,404 @@ +/* + * uml_conf.c: UML driver configuration + * + * Copyright (C) 2006, 2007, 2008 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 <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include <dirent.h> +#include <string.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/wait.h> +#include <arpa/inet.h> +#include <sys/utsname.h> + +#if HAVE_NUMACTL +#include <numa.h> +#endif + +#include "uml_conf.h" +#include "uuid.h" +#include "buf.h" +#include "conf.h" +#include "util.h" +#include "memory.h" +#include "verify.h" + + +#define umlLog(level, msg...) fprintf(stderr, msg) + + + +#if HAVE_NUMACTL +#define MAX_CPUS 4096 +#define MAX_CPUS_MASK_SIZE (sizeof(unsigned long)) +#define MAX_CPUS_MASK_LEN (MAX_CPUS / MAX_CPUS_MASK_SIZE) +#define MAX_CPUS_MASK_BYTES (MAX_CPUS / 8) + +#define MASK_CPU_ISSET(mask, cpu) \ + (((mask)[((cpu) / MAX_CPUS_MASK_SIZE)] >> ((cpu) % MAX_CPUS_MASK_SIZE)) & 1) + +static int +umlCapsInitNUMA(virCapsPtr caps) +{ + int n, i; + unsigned long *mask = NULL; + int ncpus; + int *cpus = NULL; + int ret = -1; + + if (numa_available() < 0) + return 0; + + if (VIR_ALLOC_N(mask, MAX_CPUS_MASK_LEN) < 0) + goto cleanup; + + for (n = 0 ; n <= numa_max_node() ; n++) { + if (numa_node_to_cpus(n, mask, MAX_CPUS_MASK_BYTES) < 0) + goto cleanup; + + for (ncpus = 0, i = 0 ; i < MAX_CPUS ; i++) + if (MASK_CPU_ISSET(mask, i)) + ncpus++; + + if (VIR_ALLOC_N(cpus, ncpus) < 0) + goto cleanup; + + for (ncpus = 0, i = 0 ; i < MAX_CPUS ; i++) + if (MASK_CPU_ISSET(mask, i)) + cpus[ncpus++] = i; + + if (virCapabilitiesAddHostNUMACell(caps, + n, + ncpus, + cpus) < 0) + goto cleanup; + + VIR_FREE(cpus); + } + + ret = 0; + +cleanup: + VIR_FREE(cpus); + VIR_FREE(mask); + return ret; +} +#else +static int umlCapsInitNUMA(virCapsPtr caps ATTRIBUTE_UNUSED) { return 0; } +#endif + +virCapsPtr umlCapsInit(void) { + struct utsname utsname; + virCapsPtr caps; + virCapsGuestPtr guest; + + /* Really, this never fails - look at the man-page. */ + uname (&utsname); + + if ((caps = virCapabilitiesNew(utsname.machine, + 0, 0)) == NULL) + goto no_memory; + + if (umlCapsInitNUMA(caps) < 0) + goto no_memory; + + if ((guest = virCapabilitiesAddGuest(caps, + "uml", + utsname.machine, + STREQ(utsname.machine, "x86_64") ? 64 : 32, + NULL, + NULL, + 0, + NULL)) == NULL) + goto no_memory; + + if (virCapabilitiesAddGuestDomain(guest, + "uml", + NULL, + NULL, + 0, + NULL) == NULL) + goto no_memory; + + return caps; + + no_memory: + virCapabilitiesFree(caps); + return NULL; +} + + +static char * +umlBuildCommandLineChr(virConnectPtr conn, + virDomainChrDefPtr def, + const char *dev) +{ + char *ret; + + switch (def->type) { + case VIR_DOMAIN_CHR_TYPE_NULL: + if (asprintf(&ret, "%s%d=null", dev, def->dstPort) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + break; + + case VIR_DOMAIN_CHR_TYPE_PTY: + if (asprintf(&ret, "%s%d=pts", dev, def->dstPort) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + break; + + case VIR_DOMAIN_CHR_TYPE_DEV: + if (asprintf(&ret, "%s%d=tty:%s", dev, def->dstPort, + def->data.file.path) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + break; + + case VIR_DOMAIN_CHR_TYPE_STDIO: + if (asprintf(&ret, "%s%d=fd:0,fd:1", dev, def->dstPort) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + break; + + case VIR_DOMAIN_CHR_TYPE_TCP: + if (def->data.tcp.listen != 1) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("only TCP listen is supported for chr device")); + return NULL; + } + + if (asprintf(&ret, "%s%d=port:%s", dev, def->dstPort, + def->data.tcp.service) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + return NULL; + } + break; + + case VIR_DOMAIN_CHR_TYPE_FILE: + case VIR_DOMAIN_CHR_TYPE_PIPE: + /* XXX could open the file/pipe & just pass the FDs */ + + case VIR_DOMAIN_CHR_TYPE_VC: + case VIR_DOMAIN_CHR_TYPE_UDP: + case VIR_DOMAIN_CHR_TYPE_UNIX: + default: + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("unsupported chr device type %d"), def->type); + break; + } + + return ret; +} + +/* + * Constructs a argv suitable for launching uml with config defined + * for a given virtual machine. + */ +int umlBuildCommandLine(virConnectPtr conn, + struct uml_driver *driver ATTRIBUTE_UNUSED, + virDomainObjPtr vm, + const char ***retargv, + const char ***retenv, + int **tapfds, + int *ntapfds) { + int i, j; + char memory[50]; + struct utsname ut; + int qargc = 0, qarga = 0; + const char **qargv = NULL; + int qenvc = 0, qenva = 0; + const char **qenv = NULL; + + uname(&ut); + +#define ADD_ARG_SPACE \ + do { \ + if (qargc == qarga) { \ + qarga += 10; \ + if (VIR_REALLOC_N(qargv, qarga) < 0) \ + goto no_memory; \ + } \ + } while (0) + +#define ADD_ARG(thisarg) \ + do { \ + ADD_ARG_SPACE; \ + qargv[qargc++] = thisarg; \ + } while (0) + +#define ADD_ARG_LIT(thisarg) \ + do { \ + ADD_ARG_SPACE; \ + if ((qargv[qargc++] = strdup(thisarg)) == NULL) \ + goto no_memory; \ + } while (0) + +#define ADD_ARG_PAIR(key,val) \ + do { \ + char *arg; \ + ADD_ARG_SPACE; \ + if (asprintf(&arg, "%s=%s", key, val) < 0) \ + goto no_memory; \ + qargv[qargc++] = arg; \ + } while (0) + + +#define ADD_ENV_SPACE \ + do { \ + if (qenvc == qenva) { \ + qenva += 10; \ + if (VIR_REALLOC_N(qenv, qenva) < 0) \ + goto no_memory; \ + } \ + } while (0) + +#define ADD_ENV(thisarg) \ + do { \ + ADD_ENV_SPACE; \ + qenv[qenvc++] = thisarg; \ + } while (0) + +#define ADD_ENV_LIT(thisarg) \ + do { \ + ADD_ENV_SPACE; \ + if ((qenv[qenvc++] = strdup(thisarg)) == NULL) \ + goto no_memory; \ + } while (0) + +#define ADD_ENV_COPY(envname) \ + do { \ + char *val = getenv(envname); \ + char *envval; \ + ADD_ENV_SPACE; \ + if (val != NULL) { \ + if (asprintf(&envval, "%s=%s", envname, val) < 0) \ + goto no_memory; \ + qenv[qenvc++] = envval; \ + } \ + } while (0) + + snprintf(memory, sizeof(memory), "%luK", vm->def->memory); + + ADD_ENV_LIT("LC_ALL=C"); + + ADD_ENV_COPY("LD_PRELOAD"); + ADD_ENV_COPY("LD_LIBRARY_PATH"); + ADD_ENV_COPY("PATH"); + ADD_ENV_COPY("HOME"); + ADD_ENV_COPY("USER"); + ADD_ENV_COPY("LOGNAME"); + ADD_ENV_COPY("TMPDIR"); + + ADD_ARG_LIT(vm->def->os.kernel); + //ADD_ARG_PAIR("con0", "fd:0,fd:1"); + ADD_ARG_PAIR("mem", memory); + ADD_ARG_PAIR("umid", vm->def->name); + + if (vm->def->os.root) + ADD_ARG_PAIR("root", vm->def->os.root); + + for (i = 0 ; i < vm->def->ndisks ; i++) { + virDomainDiskDefPtr disk = vm->def->disks[i]; + + if (!STRPREFIX(disk->dst, "ubd")) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("unsupported disk type '%s'"), disk->dst); + goto error; + } + + ADD_ARG_PAIR(disk->dst, disk->src); + } + + for (i = 0 ; i < 16 ; i++) { + char *ret; + if (i == 0 && vm->def->console) + ret = umlBuildCommandLineChr(conn, vm->def->console, "con"); + else + if (asprintf(&ret, "con%d=none", i) < 0) + goto no_memory; + ADD_ARG(ret); + } + + for (i = 0 ; i < 16 ; i++) { + virDomainChrDefPtr chr = NULL; + char *ret; + for (j = 0 ; j < vm->def->nserials ; j++) + if (vm->def->serials[j]->dstPort == i) + chr = vm->def->serials[j]; + if (chr) + ret = umlBuildCommandLineChr(conn, chr, "ssl"); + else + if (asprintf(&ret, "ssl%d=none", i) < 0) + goto no_memory; + ADD_ARG(ret); + } + + ADD_ARG(NULL); + ADD_ENV(NULL); + + *retargv = qargv; + *retenv = qenv; + return 0; + + no_memory: + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, + "%s", _("failed to allocate space for argv string")); + error: + if (tapfds && + *tapfds) { + for (i = 0; i < *ntapfds; i++) + close((*tapfds)[i]); + VIR_FREE(*tapfds); + *ntapfds = 0; + } + if (qargv) { + for (i = 0 ; i < qargc ; i++) + VIR_FREE((qargv)[i]); + VIR_FREE(qargv); + } + if (qenv) { + for (i = 0 ; i < qenvc ; i++) + VIR_FREE((qenv)[i]); + VIR_FREE(qenv); + } + return -1; + +#undef ADD_ARG +#undef ADD_ARG_LIT +#undef ADD_ARG_SPACE +#undef ADD_USBDISK +#undef ADD_ENV +#undef ADD_ENV_COPY +#undef ADD_ENV_LIT +#undef ADD_ENV_SPACE +} diff --git a/src/uml_conf.h b/src/uml_conf.h new file mode 100644 --- /dev/null +++ b/src/uml_conf.h @@ -0,0 +1,72 @@ +/* + * 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 <berrange@xxxxxxxxxx> + */ + +#ifndef __UML_CONF_H +#define __UML_CONF_H + +#include <config.h> + +#include "internal.h" +#include "bridge.h" +#include "capabilities.h" +#include "network_conf.h" +#include "domain_conf.h" + +#define umlDebug(fmt, ...) do {} while(0) + +#define UML_CPUMASK_LEN CPU_SETSIZE + +/* Main driver state */ +struct uml_driver { + unsigned int umlVersion; + int nextvmid; + + virDomainObjList domains; + + brControl *brctl; + char *configDir; + char *autostartDir; + char *logDir; + char *monitorDir; + + int inotifyFD; + + virCapsPtr caps; +}; + + +#define umlReportError(conn, dom, net, code, fmt...) \ + __virReportErrorHelper(conn, VIR_FROM_UML, code, __FILE__, \ + __FUNCTION__, __LINE__, fmt) + +virCapsPtr umlCapsInit (void); + +int umlBuildCommandLine (virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr dom, + const char ***retargv, + const char ***retenv, + int **tapfds, + int *ntapfds); + +#endif /* __UML_CONF_H */ diff --git a/src/uml_driver.c b/src/uml_driver.c new file mode 100644 --- /dev/null +++ b/src/uml_driver.c @@ -0,0 +1,1771 @@ +/* + * driver.c: core driver methods for managing qemu guests + * + * Copyright (C) 2006, 2007, 2008 Red Hat, Inc. + * Copyright (C) 2006-2008 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 <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/poll.h> +#include <dirent.h> +#include <limits.h> +#include <string.h> +#include <stdio.h> +#include <strings.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/utsname.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <signal.h> +#include <paths.h> +#include <pwd.h> +#include <stdio.h> +#include <sys/wait.h> +#include <sys/ioctl.h> +#include <sys/inotify.h> + +#if HAVE_NUMACTL +#include <numa.h> +#endif + +#include "uml_driver.h" +#include "uml_conf.h" +#include "c-ctype.h" +#include "event.h" +#include "buf.h" +#include "util.h" +#include "nodeinfo.h" +#include "stats_linux.h" +#include "capabilities.h" +#include "memory.h" +#include "uuid.h" +#include "domain_conf.h" + +/* For storing short-lived temporary files. */ +#define TEMPDIR LOCAL_STATE_DIR "/cache/libvirt" + +static int umlShutdown(void); + +/* umlDebug statements should be changed to use this macro instead. */ +#define DEBUG(fmt,...) VIR_DEBUG(__FILE__, fmt, __VA_ARGS__) +#define DEBUG0(msg) VIR_DEBUG(__FILE__, "%s", msg) + +#define umlLog(level, msg...) fprintf(stderr, msg) + + +static int umlOpenMonitor(virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr vm); +static int umlReadPidFile(virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr vm); + +static int umlSetCloseExec(int fd) { + int flags; + if ((flags = fcntl(fd, F_GETFD)) < 0) + goto error; + flags |= FD_CLOEXEC; + if ((fcntl(fd, F_SETFD, flags)) < 0) + goto error; + return 0; + error: + umlLog(UML_ERR, + "%s", _("Failed to set close-on-exec file descriptor flag\n")); + return -1; +} + +#if 0 +static int umlSetNonBlock(int fd) { + int flags; + if ((flags = fcntl(fd, F_GETFL)) < 0) + goto error; + flags |= O_NONBLOCK; + if ((fcntl(fd, F_SETFL, flags)) < 0) + goto error; + return 0; + error: + umlLog(UML_ERR, + "%s", _("Failed to set non-blocking file descriptor flag\n")); + return -1; +} +#endif + +static int umlStartVMDaemon(virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr vm); + +static void umlShutdownVMDaemon(virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr vm); + + +static int umlMonitorCommand (virConnectPtr conn, + const struct uml_driver *driver, + const virDomainObjPtr vm, + const char *cmd, + char **reply); + +static struct uml_driver *uml_driver = NULL; + + +static void +umlAutostartConfigs(struct uml_driver *driver) { + unsigned int i; + + for (i = 0 ; i < driver->domains.count ; i++) { + if (driver->domains.objs[i]->autostart && + !virDomainIsActive(driver->domains.objs[i]) && + umlStartVMDaemon(NULL, driver, driver->domains.objs[i]) < 0) { + virErrorPtr err = virGetLastError(); + umlLog(UML_ERR, _("Failed to autostart VM '%s': %s\n"), + driver->domains.objs[i]->def->name, err->message); + } + } +} + + +static int +umlIdentifyOneChrPTY(virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr dom, + virDomainChrDefPtr def, + const char *dev) +{ + char *cmd; + char *res = NULL; + if (asprintf(&cmd, "config %s%d", dev, def->dstPort) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + return -1; + } + umlMonitorCommand(NULL, driver, dom, cmd, &res); + VIR_FREE(cmd); + + if (STRPREFIX(res, "pts:")) { + fprintf(stderr, "Got it %s\n", res + 4); + VIR_FREE(def->data.file.path); + if ((def->data.file.path = strdup(res + 4)) == NULL) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + VIR_FREE(res); + return -1; + } + } + VIR_FREE(res); + return 0; +} + +static int +umlIdentifyChrPTY(virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr dom) +{ + int i; + + if (dom->def->console && + dom->def->console->type == VIR_DOMAIN_CHR_TYPE_PTY) + if (umlIdentifyOneChrPTY(conn, driver, dom, + dom->def->console, "con") < 0) + return -1; + + for (i = 0 ; i < dom->def->nserials; i++) + if (dom->def->serials[i]->type == VIR_DOMAIN_CHR_TYPE_PTY && + umlIdentifyOneChrPTY(conn, driver, dom, + dom->def->serials[i], "ssl") < 0) + return -1; + + return 0; +} + +static void +umlInotifyEvent(int fd, + int events ATTRIBUTE_UNUSED, + void *data) +{ + char buf[1024]; + struct inotify_event *e; + int got; + char *tmp, *name; + struct uml_driver *driver = data; + virDomainObjPtr dom; + + umlLog(UML_INFO, _("got inotify event\n")); + +reread: + got = read(fd, buf, sizeof(buf)); + if (got == -1) { + if (errno == EINTR) + goto reread; + return; + } + + tmp = buf; + while (got) { + if (got < sizeof(struct inotify_event)) + return; /* bad */ + + e = (struct inotify_event *)tmp; + tmp += sizeof(struct inotify_event); + got -= sizeof(struct inotify_event); + + if (got < e->len) + return; + + tmp += e->len; + got -= e->len; + + name = (char *)&(e->name); + + dom = virDomainFindByName(&driver->domains, name); + + if (!dom) { + umlLog (UML_ERR, _("cannot find domain '%s'\n"), name); + continue; + } + + if (e->mask & IN_DELETE) { + umlLog (UML_INFO, _("got delete event %s\n"), name); + if (!virDomainIsActive(dom)) { + umlLog (UML_ERR, _("domain '%s' not active\n"), + name); + continue; + } + + dom->def->id = -1; + dom->pid = -1; + if (dom->newDef) { + virDomainDefFree(dom->def); + dom->def = dom->newDef; + } + dom->state = VIR_DOMAIN_SHUTOFF; + } else if (e->mask & (IN_CREATE | IN_MODIFY)) { + umlLog (UML_INFO, _("got create/change event '%s'\n"), name); + if (virDomainIsActive(dom)) { + umlLog (UML_ERR, _("domain '%s' already active as pid %d\n"), + name, dom->pid); + continue; + } + + if (umlReadPidFile(NULL, driver, dom) < 0) { + umlLog (UML_ERR, _("cannot read PID for domain '%s'\n"), name); + continue; + } + umlLog (UML_INFO, _("started domain '%s'"), name); + dom->def->id = driver->nextvmid++; + dom->state = VIR_DOMAIN_RUNNING; + + if (umlOpenMonitor(NULL, driver, dom) < 0) + umlShutdownVMDaemon(NULL, driver, dom); + + if (umlIdentifyChrPTY(NULL, driver, dom) < 0) + umlShutdownVMDaemon(NULL, driver, dom); + } + } +} + +/** + * umlStartup: + * + * Initialization function for the Uml daemon + */ +static int +umlStartup(void) { + uid_t uid = geteuid(); + struct passwd *pw; + char *base = NULL; + char driverConf[PATH_MAX]; + + if (VIR_ALLOC(uml_driver) < 0) + return -1; + + /* Don't have a dom0 so start from 1 */ + uml_driver->nextvmid = 1; + + if (!(pw = getpwuid(uid))) { + umlLog(UML_ERR, _("Failed to find user record for uid '%d': %s\n"), + uid, strerror(errno)); + goto out_nouid; + } + + if (!uid) { + if (asprintf(¨_driver->logDir, + "%s/log/libvirt/uml", LOCAL_STATE_DIR) == -1) + goto out_of_memory; + + if ((base = strdup (SYSCONF_DIR "/libvirt")) == NULL) + goto out_of_memory; + } else { + if (asprintf(¨_driver->logDir, + "%s/.libvirt/uml/log", pw->pw_dir) == -1) + goto out_of_memory; + + if (asprintf (&base, "%s/.libvirt", pw->pw_dir) == -1) + goto out_of_memory; + } + + if (asprintf (¨_driver->monitorDir, + "%s/.uml", pw->pw_dir) == -1) + goto out_of_memory; + + /* Configuration paths are either ~/.libvirt/uml/... (session) or + * /etc/libvirt/uml/... (system). + */ + if (snprintf (driverConf, sizeof(driverConf), "%s/uml.conf", base) == -1) + goto out_of_memory; + driverConf[sizeof(driverConf)-1] = '\0'; + + if (asprintf (¨_driver->configDir, "%s/uml", base) == -1) + goto out_of_memory; + + if (asprintf (¨_driver->autostartDir, "%s/uml/autostart", base) == -1) + goto out_of_memory; + + VIR_FREE(base); + + if ((uml_driver->caps = umlCapsInit()) == NULL) + goto out_of_memory; + + if ((uml_driver->inotifyFD = inotify_init()) < 0) { + umlLog(UML_ERR, "%s", _("cannot initialize inotify")); + goto out_nouid; + } + + if (inotify_add_watch(uml_driver->inotifyFD, + uml_driver->monitorDir, + IN_CREATE | IN_MODIFY | IN_DELETE) < 0) { + umlLog(UML_ERR, _("cannot add watch on %s"), + uml_driver->monitorDir); + umlShutdown(); + return -1; + } + + if (virEventAddHandle(uml_driver->inotifyFD, POLLIN, + umlInotifyEvent, uml_driver) < 0) { + umlShutdown(); + return -1; + } + + if (virDomainLoadAllConfigs(NULL, + uml_driver->caps, + ¨_driver->domains, + uml_driver->configDir, + uml_driver->autostartDir) < 0) { + umlShutdown(); + return -1; + } + umlAutostartConfigs(uml_driver); + + return 0; + + out_of_memory: + umlLog (UML_ERR, + "%s", _("umlStartup: out of memory\n")); + out_nouid: + VIR_FREE(base); + VIR_FREE(uml_driver); + return -1; +} + +/** + * umlReload: + * + * Function to restart the Uml daemon, it will recheck the configuration + * files and update its state and the networking + */ +static int +umlReload(void) { + if (!uml_driver) + return 0; + + virDomainLoadAllConfigs(NULL, + uml_driver->caps, + ¨_driver->domains, + uml_driver->configDir, + uml_driver->autostartDir); + + umlAutostartConfigs(uml_driver); + + return 0; +} + +/** + * umlActive: + * + * Checks if the Uml daemon is active, i.e. has an active domain or + * an active network + * + * Returns 1 if active, 0 otherwise + */ +static int +umlActive(void) { + unsigned int i; + + if (!uml_driver) + return 0; + + for (i = 0 ; i < uml_driver->domains.count ; i++) + if (virDomainIsActive(uml_driver->domains.objs[i])) + return 1; + + /* Otherwise we're happy to deal with a shutdown */ + return 0; +} + +/** + * umlShutdown: + * + * Shutdown the Uml daemon, it will stop all active domains and networks + */ +static int +umlShutdown(void) { + unsigned int i; + + if (!uml_driver) + return -1; + + virEventRemoveHandle(uml_driver->inotifyFD); + close(uml_driver->inotifyFD); + virCapabilitiesFree(uml_driver->caps); + + /* shutdown active VMs */ + for (i = 0 ; i < uml_driver->domains.count ; i++) { + virDomainObjPtr dom = uml_driver->domains.objs[i]; + if (virDomainIsActive(dom)) + umlShutdownVMDaemon(NULL, uml_driver, dom); + if (!dom->persistent) + virDomainRemoveInactive(¨_driver->domains, + dom); + } + + virDomainObjListFree(¨_driver->domains); + + VIR_FREE(uml_driver->logDir); + VIR_FREE(uml_driver->configDir); + VIR_FREE(uml_driver->autostartDir); + VIR_FREE(uml_driver->monitorDir); + + if (uml_driver->brctl) + brShutdown(uml_driver->brctl); + + VIR_FREE(uml_driver); + + return 0; +} + + +static int umlReadPidFile(virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr vm) +{ + int rc = -1; + FILE *file; + char *pidfile = NULL; + int retries = 0; + + vm->pid = -1; + if (asprintf(&pidfile, "%s/%s/pid", + driver->monitorDir, vm->def->name) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + return -1; + } + +reopen: + if (!(file = fopen(pidfile, "r"))) { + if (errno == ENOENT && + retries++ < 50) { + usleep(1000 * 100); + goto reopen; + } + goto cleanup; + } + + if (fscanf(file, "%d", &vm->pid) != 1) { + errno = EINVAL; + goto cleanup; + } + + if (fclose(file) < 0) + goto cleanup; + + rc = 0; + + umlLog(UML_INFO, "got UML pid %d\n", vm->pid); + cleanup: + if (rc != 0) + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("failed to read pid: %s: %s"), + pidfile, strerror(errno)); + VIR_FREE(pidfile); + return rc; +} + +static int umlMonitorAddress(virConnectPtr conn, + const struct uml_driver *driver, + virDomainObjPtr vm, + struct sockaddr_un *addr) { + char *sockname; + + if (asprintf(&sockname, "%s/%s/mconsole", + driver->monitorDir, vm->def->name) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + return -1; + } + + memset(addr, 0, sizeof *addr); + addr->sun_family = AF_UNIX; + strncpy(addr->sun_path, sockname, sizeof(addr->sun_path)-1); + NUL_TERMINATE(addr->sun_path); + VIR_FREE(sockname); + return 0; +} + +static int umlOpenMonitor(virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr vm) { + struct sockaddr_un addr; + struct stat sb; + int retries = 0; + + if (umlMonitorAddress(conn, driver, vm, &addr) < 0) + return -1; + +restat: + if (stat(addr.sun_path, &sb) < 0) { + if (errno == ENOENT && + retries < 50) { + usleep(1000 * 100); + goto restat; + } + return -1; + } + + if ((vm->monitor = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("cannot open socket %s"), strerror(errno)); + return -1; + } + + memset(addr.sun_path, 0, sizeof addr.sun_path); + sprintf(addr.sun_path + 1, "%u", getpid()); + if (bind(vm->monitor, (struct sockaddr *)&addr, sizeof addr) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("cannot bind socket %s"), strerror(errno)); + close(vm->monitor); + vm->monitor = -1; + return -1; + } + + return 0; +} + + +#define MONITOR_MAGIC 0xcafebabe +#define MONITOR_BUFLEN 512 +#define MONITOR_VERSION 2 + +struct monitor_request { + uint32_t magic; + uint32_t version; + uint32_t length; + char data[MONITOR_BUFLEN]; +}; + +struct monitor_response { + uint32_t error; + uint32_t extra; + uint32_t length; + char data[MONITOR_BUFLEN]; +}; + + +static int umlMonitorCommand(virConnectPtr conn, + const struct uml_driver *driver, + const virDomainObjPtr vm, + const char *cmd, + char **reply) +{ + struct monitor_request req; + struct monitor_response res; + char *retdata = NULL; + int retlen = 0, ret = 0; + struct sockaddr_un addr; + unsigned int addrlen; + + *reply = NULL; + + if (umlMonitorAddress(conn, driver, vm, &addr) < 0) + return -1; + + memset(&req, 0, sizeof(req)); + req.magic = MONITOR_MAGIC; + req.version = MONITOR_VERSION; + req.length = strlen(cmd); + if (req.length > (MONITOR_BUFLEN-1)) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("cannot send too long command %s: %s"), + cmd, strerror(EINVAL)); + return -1; + } + strncpy(req.data, cmd, req.length); + req.data[req.length] = '\0'; + + if (sendto(vm->monitor, &req, sizeof req, 0, + (struct sockaddr *)&addr, sizeof addr) != (sizeof req)) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("cannot send command %s: %s"), + cmd, strerror(errno)); + return -1; + } + + do { + addrlen = sizeof(addr); + if (recvfrom(vm->monitor, &res, sizeof res, 0, + (struct sockaddr *)&addr, &addrlen) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("cannot read reply %s: %s"), + cmd, strerror(errno)); + goto error; + } + + if (VIR_REALLOC_N(retdata, retlen + res.length) < 0) { + umlReportError(conn, NULL, NULL, + VIR_ERR_NO_MEMORY, NULL); + goto error; + } + memcpy(retdata + retlen, res.data, res.length); + retlen += res.length - 1; + retdata[retlen] = '\0'; + + if (res.error) + ret = -1; + + } while (res.extra); + + *reply = retdata; + + return ret; + +error: + VIR_FREE(retdata); + return -1; +} + + +static int umlStartVMDaemon(virConnectPtr conn, + struct uml_driver *driver, + virDomainObjPtr vm) { + const char **argv = NULL, **tmp; + const char **progenv = NULL; + int i, ret, pid; + char *logfile; + int logfd = -1; + struct stat sb; + int *tapfds = NULL; + int ntapfds = 0; + fd_set keepfd; + + FD_ZERO(&keepfd); + + if (virDomainIsActive(vm)) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("VM is already active")); + return -1; + } + + if (!vm->def->os.kernel) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("no kernel specified")); + return -1; + } + /* Make sure the binary we are about to try exec'ing exists. + * Technically we could catch the exec() failure, but that's + * in a sub-process so its hard to feed back a useful error + */ + if (stat(vm->def->os.kernel, &sb) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("Cannot find UML kernel %s: %s"), + vm->def->os.kernel, strerror(errno)); + return -1; + } + + if (virFileMakePath(driver->logDir) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("cannot create log directory %s"), + driver->logDir); + return -1; + } + + if (asprintf(&logfile, "%s/%s.log", + driver->logDir, vm->def->name) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, NULL); + return -1; + } + + if ((logfd = open(logfile, O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR)) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("failed to create logfile %s: %s"), + logfile, strerror(errno)); + VIR_FREE(logfile); + return -1; + } + VIR_FREE(logfile); + + if (umlSetCloseExec(logfd) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("Unable to set VM logfile close-on-exec flag %s"), + strerror(errno)); + close(logfd); + return -1; + } + + if (umlBuildCommandLine(conn, driver, vm, + &argv, &progenv, + &tapfds, &ntapfds) < 0) { + close(logfd); + return -1; + } + + tmp = progenv; + while (*tmp) { + if (safewrite(logfd, *tmp, strlen(*tmp)) < 0) + umlLog(UML_WARN, _("Unable to write envv to logfile %d: %s\n"), + errno, strerror(errno)); + if (safewrite(logfd, " ", 1) < 0) + umlLog(UML_WARN, _("Unable to write envv to logfile %d: %s\n"), + errno, strerror(errno)); + tmp++; + } + tmp = argv; + while (*tmp) { + if (safewrite(logfd, *tmp, strlen(*tmp)) < 0) + umlLog(UML_WARN, _("Unable to write argv to logfile %d: %s\n"), + errno, strerror(errno)); + if (safewrite(logfd, " ", 1) < 0) + umlLog(UML_WARN, _("Unable to write argv to logfile %d: %s\n"), + errno, strerror(errno)); + tmp++; + } + if (safewrite(logfd, "\n", 1) < 0) + umlLog(UML_WARN, _("Unable to write argv to logfile %d: %s\n"), + errno, strerror(errno)); + + vm->monitor = -1; + vm->stdin_fd = -1; + vm->stdout_fd = vm->stderr_fd = logfd; + + for (i = 0 ; i < ntapfds ; i++) + FD_SET(tapfds[i], &keepfd); + + ret = virExec(conn, argv, progenv, &keepfd, &pid, + vm->stdin_fd, &vm->stdout_fd, &vm->stderr_fd, + VIR_EXEC_DAEMON); + close(logfd); + + /* Cleanup intermediate proces */ + if (waitpid(pid, NULL, 0) != pid) + umlLog(UML_WARN, _("failed to wait on process: %d: %s\n"), + pid, strerror(errno)); + + for (i = 0 ; argv[i] ; i++) + VIR_FREE(argv[i]); + VIR_FREE(argv); + + for (i = 0 ; progenv[i] ; i++) + VIR_FREE(progenv[i]); + VIR_FREE(progenv); + + if (tapfds) { + for (i = 0 ; i < ntapfds ; i++) { + close(tapfds[i]); + } + VIR_FREE(tapfds); + } + + /* NB we don't mark it running here - we do that async + with inotify */ + + return ret; +} + +static void umlShutdownVMDaemon(virConnectPtr conn ATTRIBUTE_UNUSED, + struct uml_driver *driver ATTRIBUTE_UNUSED, + virDomainObjPtr vm) +{ + int ret; + if (!virDomainIsActive(vm) || + vm->pid <= 1) + return; + + umlLog(UML_INFO, _("Shutting down VM '%d' '%s'\n"), + vm->pid, vm->def->name); + + kill(vm->pid, SIGTERM); + + if (vm->monitor != -1) + close(vm->monitor); + vm->monitor = -1; + + if ((ret = waitpid(vm->pid, NULL, 0)) != vm->pid) { + umlLog(UML_WARN, + _("Got unexpected pid %d != %d\n"), + ret, vm->pid); + } + + vm->pid = -1; + vm->def->id = -1; + vm->state = VIR_DOMAIN_SHUTOFF; + VIR_FREE(vm->vcpupids); + vm->nvcpupids = 0; + + if (vm->newDef) { + virDomainDefFree(vm->def); + vm->def = vm->newDef; + vm->def->id = -1; + vm->newDef = NULL; + } +} + + + +/** + * umlProbe: + * + * Probe for the availability of the uml driver, assume the + * presence of Uml emulation if the binaries are installed + */ +static const char *umlProbe(void) +{ + if (!access("/usr/bin/uml_mconsole", R_OK | X_OK)) + return NULL; + + if (getuid() == 0) { + return("uml:///system"); + } else { + return("uml:///session"); + } +} + +static virDrvOpenStatus umlOpen(virConnectPtr conn, + xmlURIPtr uri, + virConnectAuthPtr auth ATTRIBUTE_UNUSED, + int flags ATTRIBUTE_UNUSED) { + uid_t uid = getuid(); + + if (uml_driver == NULL) + goto decline; + + if (uri == NULL || uri->scheme == NULL || uri->path == NULL) + goto decline; + + if (STRNEQ (uri->scheme, "uml")) + goto decline; + + if (uid != 0) { + if (STRNEQ (uri->path, "/session")) + goto decline; + } else { /* root */ + if (STRNEQ (uri->path, "/system") && + STRNEQ (uri->path, "/session")) + goto decline; + } + + conn->privateData = uml_driver; + + return VIR_DRV_OPEN_SUCCESS; + + decline: + return VIR_DRV_OPEN_DECLINED; +} + +static int umlClose(virConnectPtr conn) { + /*struct uml_driver *driver = (struct uml_driver *)conn->privateData;*/ + + conn->privateData = NULL; + + return 0; +} + +static const char *umlGetType(virConnectPtr conn ATTRIBUTE_UNUSED) { + return "UML"; +} + +static int umlGetNodeInfo(virConnectPtr conn, + virNodeInfoPtr nodeinfo) { + return virNodeInfoPopulate(conn, nodeinfo); +} + + +static char *umlGetCapabilities(virConnectPtr conn) { + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + char *xml; + + if ((xml = virCapabilitiesFormatXML(driver->caps)) == NULL) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, + "%s", _("failed to allocate space for capabilities support")); + return NULL; + } + + return xml; +} + + +#if HAVE_NUMACTL +static int +umlNodeGetCellsFreeMemory(virConnectPtr conn, + unsigned long long *freeMems, + int startCell, + int maxCells) +{ + int n, lastCell, numCells; + + if (numa_available() < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT, + "%s", _("NUMA not supported on this host")); + return -1; + } + lastCell = startCell + maxCells - 1; + if (lastCell > numa_max_node()) + lastCell = numa_max_node(); + + for (numCells = 0, n = startCell ; n <= lastCell ; n++) { + long long mem; + if (numa_node_size64(n, &mem) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("Failed to query NUMA free memory")); + return -1; + } + freeMems[numCells++] = mem; + } + return numCells; +} + +static unsigned long long +umlNodeGetFreeMemory (virConnectPtr conn) +{ + unsigned long long freeMem = 0; + int n; + if (numa_available() < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT, + "%s", _("NUMA not supported on this host")); + return -1; + } + + for (n = 0 ; n <= numa_max_node() ; n++) { + long long mem; + if (numa_node_size64(n, &mem) < 0) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("Failed to query NUMA free memory")); + return -1; + } + freeMem += mem; + } + + return freeMem; +} + +#endif + +static int umlGetProcessInfo(unsigned long long *cpuTime, int pid) { + char proc[PATH_MAX]; + FILE *pidinfo; + unsigned long long usertime, systime; + + if (snprintf(proc, sizeof(proc), "/proc/%d/stat", pid) >= (int)sizeof(proc)) { + return -1; + } + + if (!(pidinfo = fopen(proc, "r"))) { + /*printf("cannot 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 %llu %llu", &usertime, &systime) != 2) { + umlDebug("not enough arg"); + return -1; + } + + /* We got jiffies + * We want nanoseconds + * _SC_CLK_TCK is jiffies per second + * So calulate thus.... + */ + *cpuTime = 1000ull * 1000ull * 1000ull * (usertime + systime) / (unsigned long long)sysconf(_SC_CLK_TCK); + + umlDebug("Got %llu %llu %llu", usertime, systime, *cpuTime); + + fclose(pidinfo); + + return 0; +} + + +static virDomainPtr umlDomainLookupByID(virConnectPtr conn, + int id) { + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + virDomainObjPtr vm = virDomainFindByID(&driver->domains, id); + virDomainPtr dom; + + if (!vm) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_DOMAIN, NULL); + return NULL; + } + + dom = virGetDomain(conn, vm->def->name, vm->def->uuid); + if (dom) dom->id = vm->def->id; + return dom; +} +static virDomainPtr umlDomainLookupByUUID(virConnectPtr conn, + const unsigned char *uuid) { + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, uuid); + virDomainPtr dom; + + if (!vm) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_DOMAIN, NULL); + return NULL; + } + + dom = virGetDomain(conn, vm->def->name, vm->def->uuid); + if (dom) dom->id = vm->def->id; + return dom; +} +static virDomainPtr umlDomainLookupByName(virConnectPtr conn, + const char *name) { + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + virDomainObjPtr vm = virDomainFindByName(&driver->domains, name); + virDomainPtr dom; + + if (!vm) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_DOMAIN, NULL); + return NULL; + } + + dom = virGetDomain(conn, vm->def->name, vm->def->uuid); + if (dom) dom->id = vm->def->id; + return dom; +} + +static int umlGetVersion(virConnectPtr conn, unsigned long *version) { + struct utsname ut; + int major, minor, micro; + + uname(&ut); + + if (sscanf(ut.release, "%u.%u.%u", + &major, &minor, µ) != 3) { + umlReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("cannot parse version %s"), ut.release); + return -1; + } + + *version = uml_driver->umlVersion; + return 0; +} + +static char * +umlGetHostname (virConnectPtr conn) +{ + int r; + char hostname[HOST_NAME_MAX+1], *str; + + r = gethostname (hostname, HOST_NAME_MAX+1); + if (r == -1) { + umlReportError (conn, NULL, NULL, VIR_ERR_SYSTEM_ERROR, + "%s", strerror (errno)); + return NULL; + } + /* Caller frees this string. */ + str = strdup (hostname); + if (str == NULL) { + umlReportError (conn, NULL, NULL, VIR_ERR_SYSTEM_ERROR, + "%s", strerror (errno)); + return NULL; + } + return str; +} + +static int umlListDomains(virConnectPtr conn, int *ids, int nids) { + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + int got = 0, i; + + for (i = 0 ; i < driver->domains.count && got < nids ; i++) + if (virDomainIsActive(driver->domains.objs[i])) + ids[got++] = driver->domains.objs[i]->def->id; + + return got; +} +static int umlNumDomains(virConnectPtr conn) { + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + int n = 0, i; + + for (i = 0 ; i < driver->domains.count ; i++) + if (virDomainIsActive(driver->domains.objs[i])) + n++; + + return n; +} +static virDomainPtr umlDomainCreate(virConnectPtr conn, const char *xml, + unsigned int flags ATTRIBUTE_UNUSED) { + virDomainDefPtr def; + virDomainObjPtr vm; + virDomainPtr dom; + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + + if (!(def = virDomainDefParseString(conn, driver->caps, xml))) + return NULL; + + vm = virDomainFindByName(&driver->domains, def->name); + if (vm) { + umlReportError(conn, NULL, NULL, VIR_ERR_OPERATION_FAILED, + _("domain '%s' is already defined"), + def->name); + virDomainDefFree(def); + return NULL; + } + vm = virDomainFindByUUID(&driver->domains, def->uuid); + if (vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(def->uuid, uuidstr); + umlReportError(conn, NULL, NULL, VIR_ERR_OPERATION_FAILED, + _("domain with uuid '%s' is already defined"), + uuidstr); + virDomainDefFree(def); + return NULL; + } + + if (!(vm = virDomainAssignDef(conn, + &driver->domains, + def))) { + virDomainDefFree(def); + return NULL; + } + + if (umlStartVMDaemon(conn, driver, vm) < 0) { + virDomainRemoveInactive(&driver->domains, + vm); + return NULL; + } + + dom = virGetDomain(conn, vm->def->name, vm->def->uuid); + if (dom) dom->id = vm->def->id; + return dom; +} + + +static int umlDomainShutdown(virDomainPtr dom) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByID(&driver->domains, dom->id); + char* info; + + if (!vm) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + _("no domain with matching id %d"), dom->id); + return -1; + } + +#if 0 + if (umlMonitorCommand(driver, vm, "system_powerdown", &info) < 0) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, + "%s", _("shutdown operation failed")); + return -1; + } +#endif + VIR_FREE(info); + return 0; + +} + + +static int umlDomainDestroy(virDomainPtr dom) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByID(&driver->domains, dom->id); + + if (!vm) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + _("no domain with matching id %d"), dom->id); + return -1; + } + + umlShutdownVMDaemon(dom->conn, driver, vm); + if (!vm->persistent) + virDomainRemoveInactive(&driver->domains, + vm); + + return 0; +} + + +static char *umlDomainGetOSType(virDomainPtr dom) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + char *type; + + if (!vm) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + "%s", _("no domain with matching uuid")); + return NULL; + } + + if (!(type = strdup(vm->def->os.type))) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_NO_MEMORY, + "%s", _("failed to allocate space for ostype")); + return NULL; + } + return type; +} + +/* Returns max memory in kb, 0 if error */ +static unsigned long umlDomainGetMaxMemory(virDomainPtr dom) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(dom->uuid, uuidstr); + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + return 0; + } + + return vm->def->maxmem; +} + +static int umlDomainSetMaxMemory(virDomainPtr dom, unsigned long newmax) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(dom->uuid, uuidstr); + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + return -1; + } + + if (newmax < vm->def->memory) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_ARG, + "%s", _("cannot set max memory lower than current memory")); + return -1; + } + + vm->def->maxmem = newmax; + return 0; +} + +static int umlDomainSetMemory(virDomainPtr dom, unsigned long newmem) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + + if (!vm) { + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(dom->uuid, uuidstr); + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + _("no domain with matching uuid '%s'"), uuidstr); + return -1; + } + + if (virDomainIsActive(vm)) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, + "%s", _("cannot set memory of an active domain")); + return -1; + } + + if (newmem > vm->def->maxmem) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_ARG, + "%s", _("cannot set memory higher than max memory")); + return -1; + } + + vm->def->memory = newmem; + return 0; +} + +static int umlDomainGetInfo(virDomainPtr dom, + virDomainInfoPtr info) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + if (!vm) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + "%s", _("no domain with matching uuid")); + return -1; + } + + info->state = vm->state; + + if (!virDomainIsActive(vm)) { + info->cpuTime = 0; + } else { + if (umlGetProcessInfo(&(info->cpuTime), vm->pid) < 0) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_OPERATION_FAILED, ("cannot read cputime for domain")); + return -1; + } + } + + info->maxMem = vm->def->maxmem; + info->memory = vm->def->memory; + info->nrVirtCpu = vm->def->vcpus; + return 0; +} + +#if 0 +static char *umlEscape(const char *in, int shell) +{ + int len = 0; + int i, j; + char *out; + + /* To pass through the UML monitor, we need to use escape + sequences: \r, \n, \", \\ + + To pass through both UML + the shell, we need to escape + the single character ' as the five characters '\\'' + */ + + for (i = 0; in[i] != '\0'; i++) { + switch(in[i]) { + case '\r': + case '\n': + case '"': + case '\\': + len += 2; + break; + case '\'': + if (shell) + len += 5; + else + len += 1; + break; + default: + len += 1; + break; + } + } + + if (VIR_ALLOC_N(out, len + 1) < 0) + return NULL; + + for (i = j = 0; in[i] != '\0'; i++) { + switch(in[i]) { + case '\r': + out[j++] = '\\'; + out[j++] = 'r'; + break; + case '\n': + out[j++] = '\\'; + out[j++] = 'n'; + break; + case '"': + case '\\': + out[j++] = '\\'; + out[j++] = in[i]; + break; + case '\'': + if (shell) { + out[j++] = '\''; + out[j++] = '\\'; + out[j++] = '\\'; + out[j++] = '\''; + out[j++] = '\''; + } else { + out[j++] = in[i]; + } + break; + default: + out[j++] = in[i]; + break; + } + } + out[j] = '\0'; + + return out; +} + +static char *umlEscapeMonitorArg(const char *in) +{ + return umlEscape(in, 0); +} + +static char *umlEscapeShellArg(const char *in) +{ + return umlEscape(in, 1); +} +#endif + +static char *umlDomainDumpXML(virDomainPtr dom, + int flags ATTRIBUTE_UNUSED) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + if (!vm) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + "%s", _("no domain with matching uuid")); + return NULL; + } + + return virDomainDefFormat(dom->conn, + (flags & VIR_DOMAIN_XML_INACTIVE) && vm->newDef ? + vm->newDef : vm->def, + flags); +} + + +static int umlListDefinedDomains(virConnectPtr conn, + char **const names, int nnames) { + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + int got = 0, i; + + for (i = 0 ; i < driver->domains.count && got < nnames ; i++) { + if (!virDomainIsActive(driver->domains.objs[i])) { + if (!(names[got++] = strdup(driver->domains.objs[i]->def->name))) { + umlReportError(conn, NULL, NULL, VIR_ERR_NO_MEMORY, + "%s", _("failed to allocate space for VM name string")); + goto cleanup; + } + } + } + + return got; + + cleanup: + for (i = 0 ; i < got ; i++) + VIR_FREE(names[i]); + return -1; +} + +static int umlNumDefinedDomains(virConnectPtr conn) { + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + int n = 0, i; + + for (i = 0 ; i < driver->domains.count ; i++) + if (!virDomainIsActive(driver->domains.objs[i])) + n++; + + return n; +} + + +static int umlDomainStart(virDomainPtr dom) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + + if (!vm) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + "%s", _("no domain with matching uuid")); + return -1; + } + + return umlStartVMDaemon(dom->conn, driver, vm); +} + + +static virDomainPtr umlDomainDefine(virConnectPtr conn, const char *xml) { + struct uml_driver *driver = (struct uml_driver *)conn->privateData; + virDomainDefPtr def; + virDomainObjPtr vm; + virDomainPtr dom; + + if (!(def = virDomainDefParseString(conn, driver->caps, xml))) + return NULL; + + if (!(vm = virDomainAssignDef(conn, + &driver->domains, + def))) { + virDomainDefFree(def); + return NULL; + } + vm->persistent = 1; + + if (virDomainSaveConfig(conn, + driver->configDir, + vm->newDef ? vm->newDef : vm->def) < 0) { + virDomainRemoveInactive(&driver->domains, + vm); + return NULL; + } + + dom = virGetDomain(conn, vm->def->name, vm->def->uuid); + if (dom) dom->id = vm->def->id; + return dom; +} + +static int umlDomainUndefine(virDomainPtr dom) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + + if (!vm) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + "%s", _("no domain with matching uuid")); + return -1; + } + + if (virDomainIsActive(vm)) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("cannot delete active domain")); + return -1; + } + + if (!vm->persistent) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("cannot undefine transient domain")); + return -1; + } + + if (virDomainDeleteConfig(dom->conn, driver->configDir, driver->autostartDir, vm) < 0) + return -1; + + virDomainRemoveInactive(&driver->domains, + vm); + + return 0; +} + + + +static int umlDomainGetAutostart(virDomainPtr dom, + int *autostart) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + + if (!vm) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + "%s", _("no domain with matching uuid")); + return -1; + } + + *autostart = vm->autostart; + + return 0; +} + +static int umlDomainSetAutostart(virDomainPtr dom, + int autostart) { + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + char *configFile = NULL, *autostartLink = NULL; + int ret = -1; + + if (!vm) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + "%s", _("no domain with matching uuid")); + return -1; + } + + if (!vm->persistent) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR, + "%s", _("cannot set autostart for transient domain")); + return -1; + } + + autostart = (autostart != 0); + + if (vm->autostart == autostart) + return 0; + + if ((configFile = virDomainConfigFile(dom->conn, driver->configDir, vm->def->name)) == NULL) + goto cleanup; + if ((autostartLink = virDomainConfigFile(dom->conn, driver->autostartDir, vm->def->name)) == NULL) + goto cleanup; + + if (autostart) { + int err; + + if ((err = virFileMakePath(driver->autostartDir))) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR, + _("cannot create autostart directory %s: %s"), + driver->autostartDir, strerror(err)); + goto cleanup; + } + + if (symlink(configFile, autostartLink) < 0) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR, + _("Failed to create symlink '%s to '%s': %s"), + autostartLink, configFile, strerror(errno)); + goto cleanup; + } + } else { + if (unlink(autostartLink) < 0 && errno != ENOENT && errno != ENOTDIR) { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INTERNAL_ERROR, + _("Failed to delete symlink '%s': %s"), + autostartLink, strerror(errno)); + goto cleanup; + } + } + + vm->autostart = autostart; + ret = 0; + +cleanup: + VIR_FREE(configFile); + VIR_FREE(autostartLink); + + return ret; +} + + +static int +umlDomainBlockPeek (virDomainPtr dom, + const char *path, + unsigned long long offset, size_t size, + void *buffer, + unsigned int flags ATTRIBUTE_UNUSED) +{ + struct uml_driver *driver = (struct uml_driver *)dom->conn->privateData; + virDomainObjPtr vm = virDomainFindByUUID(&driver->domains, dom->uuid); + int fd, ret = -1, i; + + if (!vm) { + umlReportError (dom->conn, dom, NULL, VIR_ERR_INVALID_DOMAIN, + _("no domain with matching uuid")); + return -1; + } + + if (!path || path[0] == '\0') { + umlReportError(dom->conn, dom, NULL, VIR_ERR_INVALID_ARG, + _("NULL or empty path")); + return -1; + } + + /* Check the path belongs to this domain. */ + for (i = 0 ; i < vm->def->ndisks ; i++) { + if (vm->def->disks[i]->src != NULL && + STREQ (vm->def->disks[i]->src, path)) + goto found; + } + umlReportError (dom->conn, dom, NULL, VIR_ERR_INVALID_ARG, + _("invalid path")); + return -1; + +found: + /* The path is correct, now try to open it and get its size. */ + fd = open (path, O_RDONLY); + if (fd == -1) { + umlReportError (dom->conn, dom, NULL, VIR_ERR_SYSTEM_ERROR, + "%s", strerror (errno)); + goto done; + } + + /* Seek and read. */ + /* NB. Because we configure with AC_SYS_LARGEFILE, off_t should + * be 64 bits on all platforms. + */ + if (lseek (fd, offset, SEEK_SET) == (off_t) -1 || + saferead (fd, buffer, size) == (ssize_t) -1) { + umlReportError (dom->conn, dom, NULL, VIR_ERR_SYSTEM_ERROR, + "%s", strerror (errno)); + goto done; + } + + ret = 0; + done: + if (fd >= 0) close (fd); + return ret; +} + + + +static virDriver umlDriver = { + VIR_DRV_UML, + "UML", + LIBVIR_VERSION_NUMBER, + umlProbe, /* probe */ + umlOpen, /* open */ + umlClose, /* close */ + NULL, /* supports_feature */ + umlGetType, /* type */ + umlGetVersion, /* version */ + umlGetHostname, /* hostname */ + NULL, /* URI */ + NULL, /* getMaxVcpus */ + umlGetNodeInfo, /* nodeGetInfo */ + umlGetCapabilities, /* getCapabilities */ + umlListDomains, /* listDomains */ + umlNumDomains, /* numOfDomains */ + umlDomainCreate, /* domainCreateXML */ + umlDomainLookupByID, /* domainLookupByID */ + umlDomainLookupByUUID, /* domainLookupByUUID */ + umlDomainLookupByName, /* domainLookupByName */ + NULL, /* domainSuspend */ + NULL, /* domainResume */ + umlDomainShutdown, /* domainShutdown */ + NULL, /* domainReboot */ + umlDomainDestroy, /* domainDestroy */ + umlDomainGetOSType, /* domainGetOSType */ + umlDomainGetMaxMemory, /* domainGetMaxMemory */ + umlDomainSetMaxMemory, /* domainSetMaxMemory */ + umlDomainSetMemory, /* domainSetMemory */ + umlDomainGetInfo, /* domainGetInfo */ + NULL, /* domainSave */ + NULL, /* domainRestore */ + NULL, /* domainCoreDump */ + NULL, /* domainSetVcpus */ + NULL, /* domainPinVcpu */ + NULL, /* domainGetVcpus */ + NULL, /* domainGetMaxVcpus */ + umlDomainDumpXML, /* domainDumpXML */ + umlListDefinedDomains, /* listDomains */ + umlNumDefinedDomains, /* numOfDomains */ + umlDomainStart, /* domainCreate */ + umlDomainDefine, /* domainDefineXML */ + umlDomainUndefine, /* domainUndefine */ + NULL, /* domainAttachDevice */ + NULL, /* domainDetachDevice */ + umlDomainGetAutostart, /* domainGetAutostart */ + umlDomainSetAutostart, /* domainSetAutostart */ + NULL, /* domainGetSchedulerType */ + NULL, /* domainGetSchedulerParameters */ + NULL, /* domainSetSchedulerParameters */ + NULL, /* domainMigratePrepare */ + NULL, /* domainMigratePerform */ + NULL, /* domainMigrateFinish */ + NULL, /* domainBlockStats */ + NULL, /* domainInterfaceStats */ + umlDomainBlockPeek, /* domainBlockPeek */ + NULL, /* domainMemoryPeek */ +#if HAVE_NUMACTL + umlNodeGetCellsFreeMemory, /* nodeGetCellsFreeMemory */ + umlNodeGetFreeMemory, /* getFreeMemory */ +#else + NULL, /* nodeGetCellsFreeMemory */ + NULL, /* getFreeMemory */ +#endif +}; + + +static virStateDriver umlStateDriver = { + .initialize = umlStartup, + .cleanup = umlShutdown, + .reload = umlReload, + .active = umlActive, +}; + +int umlRegister(void) { + virRegisterDriver(¨Driver); + virRegisterStateDriver(¨StateDriver); + return 0; +} + diff --git a/src/uml_driver.h b/src/uml_driver.h new file mode 100644 --- /dev/null +++ b/src/uml_driver.h @@ -0,0 +1,34 @@ +/* + * uml_driver.h: user mode Linux driver + * + * Copyright (C) 2006, 2007 Red Hat, Inc. + * Copyright (C) 2006-2008 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 <berrange@xxxxxxxxxx> + */ + + +#ifndef UML_DRIVER_H +#define UML_DRIVER_H + +#include <config.h> + +#include "internal.h" + +int umlRegister(void); + +#endif /* UML_DRIVER_H */ diff --git a/src/virterror.c b/src/virterror.c --- a/src/virterror.c +++ b/src/virterror.c @@ -310,7 +310,9 @@ virDefaultErrorFunc(virErrorPtr err) case VIR_FROM_DOMAIN: dom = "Domain Config "; break; - + case VIR_FROM_UML: + dom = "UML "; + break; } if ((err->dom != NULL) && (err->code != VIR_ERR_INVALID_DOMAIN)) { domain = err->dom->name; -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :| -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list