[libvirt] [PATCH libguestfs 4/4] launch: Add libvirt backend.

Richard W.M. Jones rjones at redhat.com
Sat Jul 21 19:20:49 UTC 2012


From: "Richard W.M. Jones" <rjones at 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




More information about the libvir-list mailing list