From: "Richard W.M. Jones" <rjones@xxxxxxxxxx> Complete the attach-method libvirt backend. This backend uses libvirt to create a transient KVM domain to run the appliance. Note that this still will only work with local libvirt URIs since the <kernel>, <initrd> and appliance links in the libvirt XML refer to local files, and virtio serial only works locally (limitation of libvirt). Remote support will be added later. --- configure.ac | 4 + po/POTFILES | 1 + src/Makefile.am | 1 + src/guestfs-internal.h | 6 + src/launch-libvirt.c | 868 ++++++++++++++++++++++++++++++++++++++++++++++++ src/launch.c | 4 +- 6 files changed, 882 insertions(+), 2 deletions(-) create mode 100644 src/launch-libvirt.c diff --git a/configure.ac b/configure.ac index a79a992..e4207c5 100644 --- a/configure.ac +++ b/configure.ac @@ -678,6 +678,10 @@ PKG_CHECK_MODULES([LIBXML2], [libxml-2.0], [AC_SUBST([LIBXML2_CFLAGS]) AC_SUBST([LIBXML2_LIBS]) AC_DEFINE([HAVE_LIBXML2],[1],[libxml2 found at compile time.]) + old_LIBS="$LIBS" + LIBS="$LIBS $LIBREADLINE" + AC_CHECK_FUNCS([xmlBufferDetach]) + LIBS="$old_LIBS" ], [AC_MSG_WARN([libxml2 not found, some core features will be disabled])]) AM_CONDITIONAL([HAVE_LIBXML2],[test "x$LIBXML2_LIBS" != "x"]) diff --git a/po/POTFILES b/po/POTFILES index 60f8d95..ad00cd4 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -209,6 +209,7 @@ src/inspect-fs.c src/inspect-icon.c src/inspect.c src/launch-appliance.c +src/launch-libvirt.c src/launch-unix.c src/launch.c src/libvirtdomain.c diff --git a/src/Makefile.am b/src/Makefile.am index 25daaea..95042f8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -138,6 +138,7 @@ libguestfs_la_SOURCES = \ inspect-icon.c \ launch.c \ launch-appliance.c \ + launch-libvirt.c \ launch-unix.c \ libvirtdomain.c \ listfs.c \ diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 8fbe2ec..fe275f0 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -167,6 +167,7 @@ struct attach_ops { int (*shutdown) (guestfs_h *g); /* Shutdown and cleanup. */ }; extern struct attach_ops attach_ops_appliance; +extern struct attach_ops attach_ops_libvirt; extern struct attach_ops attach_ops_unix; struct guestfs_h @@ -272,6 +273,11 @@ struct guestfs_h bool virtio_scsi; /* See function qemu_supports_virtio_scsi */ } app; + + struct { /* Used only by src/launch-libvirt.c. */ + void *connv; /* libvirt connection (really virConnectPtr) */ + void *domv; /* libvirt domain (really virDomainPtr) */ + } virt; }; /* Per-filesystem data stored for inspect_os. */ diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c new file mode 100644 index 0000000..3b735b5 --- /dev/null +++ b/src/launch-libvirt.c @@ -0,0 +1,868 @@ +/* libguestfs + * Copyright (C) 2009-2012 Red Hat Inc. + * + * 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* To do (XXX): + * + * - Need to query libvirt to find out if virtio-scsi is supported. + * This code assumes it. + * + * - Console, so we can see appliance messages and debugging. + * + * - Network. + * + * - Remote support. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <fcntl.h> +#include <limits.h> +#include <assert.h> + +#ifdef HAVE_LIBVIRT +#include <libvirt/libvirt.h> +#include <libvirt/virterror.h> +#endif + +#ifdef HAVE_LIBXML2 +#include <libxml/xmlIO.h> +#include <libxml/xmlwriter.h> +#include <libxml/xpath.h> +#include <libxml/parser.h> +#include <libxml/tree.h> +#include <libxml/xmlsave.h> +#endif + +#include "glthread/lock.h" + +#include "guestfs.h" +#include "guestfs-internal.h" +#include "guestfs-internal-actions.h" +#include "guestfs_protocol.h" + +#if defined(HAVE_LIBVIRT) && defined(HAVE_LIBXML2) + +#ifndef HAVE_XMLBUFFERDETACH +/* Added in libxml2 2.8.0. This is mostly a copy of the function from + * upstream libxml2, which is under a more permissive license. + */ +static xmlChar * +xmlBufferDetach (xmlBufferPtr buf) +{ + xmlChar *ret; + + if (buf == NULL) + return NULL; + if (buf->alloc == XML_BUFFER_ALLOC_IMMUTABLE) + return NULL; + + ret = buf->content; + buf->content = NULL; + buf->size = 0; + buf->use = 0; + + return ret; +} +#endif + +static xmlChar *construct_libvirt_xml (guestfs_h *g, const char *capabilities_xml, const char *kernel, const char *initrd, const char *appliance, const char *guestfsd_sock); + +static void libvirt_error (guestfs_h *g, const char *fs, ...); + +static int +launch_libvirt (guestfs_h *g, const char *libvirt_uri) +{ + virConnectPtr conn = NULL; + virDomainPtr dom = NULL; + char *capabilities = NULL; + xmlChar *xml = NULL; + char *kernel = NULL, *initrd = NULL, *appliance = NULL; + char guestfsd_sock[256]; + struct sockaddr_un addr; + int r; + + /* At present you must add drives before starting the appliance. In + * future when we enable hotplugging you won't need to do this. + */ + if (!g->drives) { + error (g, _("you must call guestfs_add_drive before guestfs_launch")); + return -1; + } + + guestfs___launch_send_progress (g, 0); + TRACE0 (launch_libvirt_start); + + if (g->verbose) + guestfs___print_timestamped_message (g, "connect to libvirt"); + + /* Connect to libvirt, get capabilities. */ + /* XXX Support libvirt authentication in the future. */ + conn = virConnectOpen (libvirt_uri); + if (!conn) { + error (g, _("could not connect to libvirt: URI: %s"), + libvirt_uri ? : "NULL"); + goto cleanup; + } + + if (g->verbose) + guestfs___print_timestamped_message (g, "get libvirt capabilities"); + + capabilities = virConnectGetCapabilities (conn); + if (!capabilities) { + libvirt_error (g, _("could not get libvirt capabilities")); + goto cleanup; + } + + /* Locate and/or build the appliance. */ + TRACE0 (launch_build_libvirt_appliance_start); + + if (g->verbose) + guestfs___print_timestamped_message (g, "build appliance"); + + if (guestfs___build_appliance (g, &kernel, &initrd, &appliance) == -1) + goto cleanup; + + guestfs___launch_send_progress (g, 3); + TRACE0 (launch_build_libvirt_appliance_end); + + /* Using virtio-serial, we need to create a local Unix domain socket + * for qemu to connect to. + */ + snprintf (guestfsd_sock, sizeof guestfsd_sock, "%s/guestfsd.sock", g->tmpdir); + unlink (guestfsd_sock); + + g->sock = socket (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (g->sock == -1) { + perrorf (g, "socket"); + goto cleanup; + } + + if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { + perrorf (g, "fcntl"); + goto cleanup; + } + + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, guestfsd_sock, UNIX_PATH_MAX); + addr.sun_path[UNIX_PATH_MAX-1] = '\0'; + + if (bind (g->sock, &addr, sizeof addr) == -1) { + perrorf (g, "bind"); + goto cleanup; + } + + if (listen (g->sock, 1) == -1) { + perrorf (g, "listen"); + goto cleanup; + } + + /* XXX CONSOLE XXX */ + + /* Construct the libvirt XML. */ + if (g->verbose) + guestfs___print_timestamped_message (g, "create libvirt XML"); + + xml = construct_libvirt_xml (g, capabilities, + kernel, initrd, appliance, + guestfsd_sock); + if (!xml) + goto cleanup; + + /* Launch the libvirt guest. */ + if (g->verbose) + guestfs___print_timestamped_message (g, "launch libvirt guest"); + + dom = virDomainCreateXML (conn, (char *) xml, VIR_DOMAIN_START_AUTODESTROY); + if (!dom) { + libvirt_error (g, _("could not create appliance through libvirt")); + goto cleanup; + } + + free (kernel); + kernel = NULL; + free (initrd); + initrd = NULL; + free (appliance); + appliance = NULL; + free (xml); + xml = NULL; + free (capabilities); + capabilities = NULL; + + g->state = LAUNCHING; + + /* Wait for libvirt domain to start and to connect back to us via + * virtio-serial and send the GUESTFS_LAUNCH_FLAG message. + */ + r = guestfs___accept_from_daemon (g); + if (r == -1) + goto cleanup; + + /* NB: We reach here just because qemu has opened the socket. It + * does not mean the daemon is up until we read the + * GUESTFS_LAUNCH_FLAG below. Failures in qemu startup can still + * happen even if we reach here, even early failures like not being + * able to open a drive. + */ + + /* Close the listening socket. */ + if (close (g->sock) != 0) { + perrorf (g, "close: listening socket"); + close (r); + g->sock = -1; + goto cleanup; + } + g->sock = r; /* This is the accepted data socket. */ + + if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { + perrorf (g, "fcntl"); + goto cleanup; + } + + uint32_t size; + void *buf = NULL; + r = guestfs___recv_from_daemon (g, &size, &buf); + free (buf); + + if (r == -1) { + error (g, _("guestfs_launch failed, see earlier error messages")); + goto cleanup; + } + + if (size != GUESTFS_LAUNCH_FLAG) { + error (g, _("guestfs_launch failed, see earlier error messages")); + goto cleanup; + } + + if (g->verbose) + guestfs___print_timestamped_message (g, "appliance is up"); + + /* This is possible in some really strange situations, such as + * guestfsd starts up OK but then qemu immediately exits. Check for + * it because the caller is probably expecting to be able to send + * commands after this function returns. + */ + if (g->state != READY) { + error (g, _("qemu launched and contacted daemon, but state != READY")); + goto cleanup; + } + + TRACE0 (launch_libvirt_end); + + guestfs___launch_send_progress (g, 12); + + g->virt.connv = conn; + g->virt.domv = dom; + + return 0; + + cleanup: + if (g->sock >= 0) { + close (g->sock); + g->sock = -1; + } + g->state = CONFIG; + free (kernel); + free (initrd); + free (appliance); + free (capabilities); + free (xml); + if (dom) { + virDomainDestroy (dom); + virDomainFree (dom); + } + if (conn) + virConnectClose (conn); + + return -1; +} + +static int construct_libvirt_xml_name (guestfs_h *g, xmlTextWriterPtr xo); +static int construct_libvirt_xml_cpu (guestfs_h *g, xmlTextWriterPtr xo); +static int construct_libvirt_xml_boot (guestfs_h *g, xmlTextWriterPtr xo, const char *kernel, const char *initrd, size_t appliance_index); +static int construct_libvirt_xml_devices (guestfs_h *g, xmlTextWriterPtr xo, const char *appliance, const char *guestfsd_sock, size_t appliance_index); +static int construct_libvirt_xml_qemu_cmdline (guestfs_h *g, xmlTextWriterPtr xo, size_t appliance_index); +static int construct_libvirt_xml_disk (guestfs_h *g, xmlTextWriterPtr xo, struct drive *drv, size_t drv_index); +static int construct_libvirt_xml_appliance (guestfs_h *g, xmlTextWriterPtr xo, const char *appliance, size_t appliance_index); + +#define XMLERROR(code,e) do { \ + if ((e) == (code)) { \ + perrorf (g, _("error constructing libvirt XML at \"%s\""), \ + #e); \ + goto err; \ + } \ + } while (0) + +static xmlChar * +construct_libvirt_xml (guestfs_h *g, const char *capabilities_xml, + const char *kernel, const char *initrd, + const char *appliance, + const char *guestfsd_sock) +{ + xmlChar *ret = NULL; + xmlBufferPtr xb = NULL; + xmlOutputBufferPtr ob; + xmlTextWriterPtr xo = NULL; + struct drive *drv = g->drives; + size_t appliance_index = 0; + + /* Count the number of disks added, in order to get the offset + * of the appliance disk. + */ + while (drv != NULL) { + drv = drv->next; + appliance_index++; + } + + XMLERROR (NULL, xb = xmlBufferCreate ()); + XMLERROR (NULL, ob = xmlOutputBufferCreateBuffer (xb, NULL)); + XMLERROR (NULL, xo = xmlNewTextWriter (ob)); + + XMLERROR (-1, xmlTextWriterSetIndent (xo, 1)); + XMLERROR (-1, xmlTextWriterSetIndentString (xo, BAD_CAST " ")); + XMLERROR (-1, xmlTextWriterStartDocument (xo, NULL, NULL, NULL)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "domain")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "type", BAD_CAST "kvm")); + XMLERROR (-1, + xmlTextWriterWriteAttributeNS (xo, + BAD_CAST "xmlns", + BAD_CAST "qemu", + NULL, + BAD_CAST "http://libvirt.org/schemas/domain/qemu/1.0")); + + if (construct_libvirt_xml_name (g, xo) == -1) + goto err; + if (construct_libvirt_xml_cpu (g, xo) == -1) + goto err; + if (construct_libvirt_xml_boot (g, xo, kernel, initrd, appliance_index) == -1) + goto err; + if (construct_libvirt_xml_devices (g, xo, appliance, guestfsd_sock, + appliance_index) == -1) + goto err; + if (construct_libvirt_xml_qemu_cmdline (g, xo, appliance_index) == -1) + goto err; + + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterEndDocument (xo)); + XMLERROR (NULL, ret = xmlBufferDetach (xb)); /* caller frees ret */ + + debug (g, "libvirt XML:\n%s", ret); + + err: + if (xo) + xmlFreeTextWriter (xo); /* frees 'ob' too */ + if (xb) + xmlBufferFree (xb); + + return ret; +} + +/* Construct a securely random name. We don't need to save the name + * because if we ever needed it, it's available from libvirt. + */ +#define DOMAIN_NAME_LEN 16 + +static int +construct_libvirt_xml_name (guestfs_h *g, xmlTextWriterPtr xo) +{ + int fd; + char name[DOMAIN_NAME_LEN+1]; + size_t i; + unsigned char c; + + fd = open ("/dev/urandom", O_RDONLY|O_CLOEXEC); + if (fd == -1) { + perrorf (g, "/dev/urandom: open"); + return -1; + } + + for (i = 0; i < DOMAIN_NAME_LEN; ++i) { + if (read (fd, &c, 1) != 1) { + perrorf (g, "/dev/urandom: read"); + close (fd); + return -1; + } + name[i] = "0123456789abcdefghijklmnopqrstuvwxyz"[c % 36]; + } + name[DOMAIN_NAME_LEN] = '\0'; + + close (fd); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "name")); + XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST name)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + return 0; + + err: + return -1; +} + +/* CPU and memory features. */ +static int +construct_libvirt_xml_cpu (guestfs_h *g, xmlTextWriterPtr xo) +{ + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "memory")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "unit", BAD_CAST "MiB")); + XMLERROR (-1, xmlTextWriterWriteFormatString (xo, "%d", g->memsize)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "currentMemory")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "unit", BAD_CAST "MiB")); + XMLERROR (-1, xmlTextWriterWriteFormatString (xo, "%d", g->memsize)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "vcpu")); + XMLERROR (-1, xmlTextWriterWriteFormatString (xo, "%d", g->smp)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "clock")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "offset", + BAD_CAST "utc")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + return 0; + + err: + return -1; +} + +/* Boot parameters. */ +static int +construct_libvirt_xml_boot (guestfs_h *g, xmlTextWriterPtr xo, + const char *kernel, const char *initrd, + size_t appliance_index) +{ + char buf[256]; + char appliance_root[64] = ""; + + /* XXX Lots of common code shared with src/launch-appliance.c */ +#if defined(__arm__) +#define SERIAL_CONSOLE "ttyAMA0" +#else +#define SERIAL_CONSOLE "ttyS0" +#endif + +#define LINUX_CMDLINE \ + "panic=1 " /* force kernel to panic if daemon exits */ \ + "console=" SERIAL_CONSOLE " " /* serial console */ \ + "udevtimeout=600 " /* good for very slow systems (RHBZ#480319) */ \ + "no_timer_check " /* fix for RHBZ#502058 */ \ + "acpi=off " /* we don't need ACPI, turn it off */ \ + "printk.time=1 " /* display timestamp before kernel messages */ \ + "cgroup_disable=memory " /* saves us about 5 MB of RAM */ + + /* Linux kernel command line. */ + guestfs___drive_name (appliance_index, appliance_root); + + snprintf (buf, sizeof buf, + LINUX_CMDLINE + "root=/dev/sd%s " /* (root) */ + "%s " /* (selinux) */ + "%s " /* (verbose) */ + "TERM=%s " /* (TERM environment variable) */ + "%s", /* (append) */ + appliance_root, + g->selinux ? "selinux=1 enforcing=0" : "selinux=0", + g->verbose ? "guestfs_verbose=1" : "", + getenv ("TERM") ? : "linux", + g->append ? g->append : ""); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "os")); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "type")); + XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST "hvm")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "kernel")); + XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST kernel)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "initrd")); + XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST initrd)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "cmdline")); + XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST buf)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + return 0; + + err: + return -1; +} + +/* Devices. */ +static int +construct_libvirt_xml_devices (guestfs_h *g, xmlTextWriterPtr xo, + const char *appliance, const char *guestfsd_sock, + size_t appliance_index) +{ + struct drive *drv = g->drives; + size_t drv_index = 0; + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "devices")); + + /* virtio-scsi controller. */ + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "controller")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "type", + BAD_CAST "scsi")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "index", + BAD_CAST "0")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "model", + BAD_CAST "virtio-scsi")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + /* Disks. */ + while (drv != NULL) { + if (construct_libvirt_xml_disk (g, xo, drv, drv_index) == -1) + goto err; + drv = drv->next; + drv_index++; + } + + /* Appliance disk. */ + if (construct_libvirt_xml_appliance (g, xo, appliance, appliance_index) == -1) + goto err; + + /* virtio-serial */ + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "channel")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "type", + BAD_CAST "unix")); + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "mode", + BAD_CAST "bind")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "path", + BAD_CAST guestfsd_sock)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "target")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "type", + BAD_CAST "virtio")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "name", + BAD_CAST "org.libguestfs.channel.0")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + return 0; + + err: + return -1; +} + +static int +construct_libvirt_xml_disk (guestfs_h *g, xmlTextWriterPtr xo, + struct drive *drv, size_t drv_index) +{ + char drive_name[64] = "sd"; + char scsi_target[64]; + char *path = NULL; + + guestfs___drive_name (drv_index, &drive_name[2]); + snprintf (scsi_target, sizeof scsi_target, "%zu", drv_index); + + /* Drive path must be absolute for libvirt. */ + path = realpath (drv->path, NULL); + if (path == NULL) { + perrorf (g, "realpath: could not convert '%s' to absolute path", drv->path); + goto err; + } + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "disk")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "type", + BAD_CAST "file")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "device", + BAD_CAST "disk")); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "file", + BAD_CAST path)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "target")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "dev", + BAD_CAST drive_name)); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "bus", + BAD_CAST "scsi")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "driver")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "name", + BAD_CAST "qemu")); + if (drv->format) { + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "format", + BAD_CAST drv->format)); + } + if (drv->use_cache_none) { + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "cache", + BAD_CAST "none")); + } + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "address")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "type", + BAD_CAST "drive")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "controller", + BAD_CAST "0")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "bus", + BAD_CAST "0")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "target", + BAD_CAST scsi_target)); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "unit", + BAD_CAST "0")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + if (drv->readonly) { + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "readonly")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + } + + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + free (path); + return 0; + + err: + free (path); + return -1; +} + +static int +construct_libvirt_xml_appliance (guestfs_h *g, xmlTextWriterPtr xo, + const char *appliance, size_t drv_index) +{ + char drive_name[64] = "sd"; + char scsi_target[64]; + + guestfs___drive_name (drv_index, &drive_name[2]); + snprintf (scsi_target, sizeof scsi_target, "%zu", drv_index); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "disk")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "type", + BAD_CAST "file")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "device", + BAD_CAST "disk")); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "file", + BAD_CAST appliance)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "target")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "dev", + BAD_CAST drive_name)); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "bus", + BAD_CAST "scsi")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "driver")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "name", + BAD_CAST "qemu")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "format", + BAD_CAST "raw")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "cache", + BAD_CAST "unsafe")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "address")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "type", + BAD_CAST "drive")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "controller", + BAD_CAST "0")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "bus", + BAD_CAST "0")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "target", + BAD_CAST scsi_target)); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "unit", + BAD_CAST "0")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + /* We'd like to do this, but it's not supported by libvirt. + * See construct_libvirt_xml_qemu_cmdline for the workaround. + * + * XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "transient")); + * XMLERROR (-1, xmlTextWriterEndElement (xo)); + */ + + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + return 0; + + err: + return -1; +} + +/* Workaround because libvirt can't do snapshot=on yet. Idea inspired + * by Stefan Hajnoczi's post here: + * http://blog.vmsplice.net/2011/04/how-to-pass-qemu-command-line-options.html + */ +static int +construct_libvirt_xml_qemu_cmdline (guestfs_h *g, xmlTextWriterPtr xo, + size_t appliance_index) +{ + char attr[256]; + + snprintf (attr, sizeof attr, + "drive.drive-scsi0-0-%zu-0.snapshot=on", appliance_index); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:commandline")); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:arg")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "value", + BAD_CAST "-set")); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:arg")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "value", + BAD_CAST attr)); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + return 0; + + err: + return -1; +} + +static int +shutdown_libvirt (guestfs_h *g) +{ + virConnectPtr conn = g->virt.connv; + virDomainPtr dom = g->virt.domv; + int ret = 0; + + assert (conn != NULL); + assert (dom != NULL); + + /* XXX Need to be graceful? */ + if (virDomainDestroyFlags (dom, 0) == -1) { + libvirt_error (g, _("could not destroy libvirt domain")); + ret = -1; + } + virDomainFree (dom); + virConnectClose (conn); + + g->virt.connv = g->virt.domv = NULL; + + return ret; +} + +/* Wrapper around error() which produces better errors for + * libvirt functions. + */ +static void +libvirt_error (guestfs_h *g, const char *fs, ...) +{ + va_list args; + char *msg; + int len; + virErrorPtr err; + + va_start (args, fs); + len = vasprintf (&msg, fs, args); + va_end (args); + + if (len < 0) + msg = safe_asprintf (g, + _("%s: internal error forming error message"), + __func__); + + /* In all recent libvirt, this retrieves the thread-local error. */ + err = virGetLastError (); + + error (g, "%s: %s [code=%d domain=%d]", + msg, err->message, err->code, err->domain); + + /* NB. 'err' must not be freed! */ + free (msg); +} + +#else /* no libvirt or libxml2 at compile time */ + +#define NOT_IMPL(r) \ + error (g, _("libvirt attach-method is not available since this version of libguestfs was compiled without libvirt or libxml2")); \ + return r + +static int +launch_libvirt (guestfs_h *g, const char *arg) +{ + NOT_IMPL (-1); +} + +static int +shutdown_libvirt (guestfs_h *g) +{ + NOT_IMPL (-1); +} + +#endif /* no libvirt or libxml2 at compile time */ + +struct attach_ops attach_ops_libvirt = { + .launch = launch_libvirt, + .shutdown = shutdown_libvirt, +}; diff --git a/src/launch.c b/src/launch.c index 7c403ab..02441db 100644 --- a/src/launch.c +++ b/src/launch.c @@ -326,8 +326,8 @@ guestfs__launch (guestfs_h *g) break; case ATTACH_METHOD_LIBVIRT: - error (g, _("libvirt attach method is not yet supported")); - return -1; + g->attach_ops = &attach_ops_libvirt; + break; case ATTACH_METHOD_UNIX: g->attach_ops = &attach_ops_unix; -- 1.7.10.4 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list