From: "Richard W.M. Jones" <rjones(a)redhat.com>
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