Files
libguestfs/src/launch-libvirt.c
Richard W.M. Jones 6272e49f51 syntax: Remove include <assert.h> where assert is not used.
Found by 'make syntax-check'.
2012-09-15 12:32:41 +01:00

1224 lines
38 KiB
C

/* 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
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.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"
/* Check minimum required version of libvirt. The libvirt backend
* is new and not the default, so we can get away with forcing
* people who want to try it to have a reasonably new version of
* libvirt, so we don't have to work around any bugs in libvirt.
*
* This is also checked at runtime because you can dynamically link
* with a different version from what you were compiled with.
*/
#define MIN_LIBVIRT_MAJOR 0
#define MIN_LIBVIRT_MINOR 10
#define MIN_LIBVIRT_MICRO 0
#define MIN_LIBVIRT_VERSION (MIN_LIBVIRT_MAJOR * 1000000 + \
MIN_LIBVIRT_MINOR * 1000 + \
MIN_LIBVIRT_MICRO)
#if defined(HAVE_LIBVIRT) && \
LIBVIR_VERSION_NUMBER >= MIN_LIBVIRT_VERSION && \
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, const char *console_sock);
static void libvirt_error (guestfs_h *g, const char *fs, ...);
static int is_blk (const char *path);
static int random_chars (char *ret, size_t len);
static void ignore_errors (void *ignore, virErrorPtr ignore2);
static int
launch_libvirt (guestfs_h *g, const char *libvirt_uri)
{
unsigned long version;
int is_root = geteuid () == 0;
virConnectPtr conn = NULL;
virDomainPtr dom = NULL;
char *capabilities = NULL;
xmlChar *xml = NULL;
char *kernel = NULL, *initrd = NULL, *appliance = NULL;
char guestfsd_sock[256];
char console_sock[256];
struct sockaddr_un addr;
int console = -1, 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;
}
/* XXX: It should be possible to make this work. */
if (g->direct) {
error (g, _("direct mode flag is not supported yet for libvirt attach method"));
return -1;
}
virGetVersion (&version, NULL, NULL);
debug (g, "libvirt version = %lu", version);
if (version < MIN_LIBVIRT_VERSION) {
error (g, _("you must have libvirt >= %d.%d.%d "
"to use the 'libvirt' attach-method"),
MIN_LIBVIRT_MAJOR, MIN_LIBVIRT_MINOR, MIN_LIBVIRT_MICRO);
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) {
libvirt_error (g, _("could not connect to libvirt (URI = %s)"),
libvirt_uri ? : "NULL");
goto cleanup;
}
/* Suppress default behaviour of printing errors to stderr. Note
* you can't set this to NULL to ignore errors; setting it to NULL
* restores the default error handler ...
*/
virConnSetErrorFunc (conn, NULL, ignore_errors);
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;
}
/* For the serial console. */
snprintf (console_sock, sizeof console_sock, "%s/console.sock", g->tmpdir);
unlink (console_sock);
console = socket (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (console == -1) {
perrorf (g, "socket");
goto cleanup;
}
addr.sun_family = AF_UNIX;
strncpy (addr.sun_path, console_sock, UNIX_PATH_MAX);
addr.sun_path[UNIX_PATH_MAX-1] = '\0';
if (bind (console, &addr, sizeof addr) == -1) {
perrorf (g, "bind");
goto cleanup;
}
if (listen (console, 1) == -1) {
perrorf (g, "listen");
goto cleanup;
}
/* libvirt, if running as root, will run the qemu process as
* qemu.qemu, which means it won't be able to access the socket.
* There are roughly three things that get in the way:
* (1) Permissions of the socket.
* (2) Permissions of the parent directory(-ies). Remember this
* if $TMPDIR is located in your home directory.
* (3) SELinux/sVirt will prevent access. libvirt ought to
* label the socket.
*/
if (is_root) {
struct group *grp;
if (chmod (guestfsd_sock, 0775) == -1) {
perrorf (g, "chmod: %s", guestfsd_sock);
goto cleanup;
}
if (chmod (console_sock, 0775) == -1) {
perrorf (g, "chmod: %s", console_sock);
goto cleanup;
}
grp = getgrnam ("qemu");
if (grp != NULL) {
if (chown (guestfsd_sock, 0, grp->gr_gid) == -1) {
perrorf (g, "chown: %s", guestfsd_sock);
goto cleanup;
}
if (chown (console_sock, 0, grp->gr_gid) == -1) {
perrorf (g, "chown: %s", console_sock);
goto cleanup;
}
} else
debug (g, "cannot find group 'qemu'");
}
/* 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, console_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;
}
g->state = LAUNCHING;
/* Wait for console socket to open. */
r = accept4 (console, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
if (r == -1) {
perrorf (g, "accept");
goto cleanup;
}
if (close (console) == -1) {
perrorf (g, "close: console socket");
close (r);
goto cleanup;
}
g->fd[0] = r; /* This is the accepted console socket. */
g->fd[1] = dup (g->fd[0]);
if (g->fd[1] == -1) {
perrorf (g, "dup");
goto cleanup;
}
/* 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) == -1) {
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;
free (kernel);
free (initrd);
free (appliance);
free (xml);
free (capabilities);
return 0;
cleanup:
if (console >= 0)
close (console);
if (g->fd[0] >= 0) {
close (g->fd[0]);
g->fd[0] = -1;
}
if (g->fd[1] >= 0) {
close (g->fd[1]);
g->fd[1] = -1;
}
if (g->sock >= 0) {
close (g->sock);
g->sock = -1;
}
if (dom) {
virDomainDestroy (dom);
virDomainFree (dom);
}
if (conn)
virConnectClose (conn);
free (kernel);
free (initrd);
free (appliance);
free (capabilities);
free (xml);
g->state = CONFIG;
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_lifecycle (guestfs_h *g, xmlTextWriterPtr xo);
static int construct_libvirt_xml_devices (guestfs_h *g, xmlTextWriterPtr xo, const char *appliance, size_t appliance_index, const char *guestfsd_sock, const char *console_sock);
static int construct_libvirt_xml_qemu_cmdline (guestfs_h *g, xmlTextWriterPtr xo);
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, const char *console_sock)
{
xmlChar *ret = NULL;
xmlBufferPtr xb = NULL;
xmlOutputBufferPtr ob;
xmlTextWriterPtr xo = NULL;
struct drive *drv = g->drives;
size_t appliance_index = 0;
const char *type;
/* Count the number of disks added, in order to get the offset
* of the appliance disk.
*/
while (drv != NULL) {
drv = drv->next;
appliance_index++;
}
/* Big hack, instead of actually parsing the capabilities XML (XXX). */
type = strstr (capabilities_xml, "'kvm'") != NULL ? "kvm" : "qemu";
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 type));
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_lifecycle (g, xo) == -1)
goto err;
if (construct_libvirt_xml_devices (g, xo, appliance, appliance_index,
guestfsd_sock, console_sock) == -1)
goto err;
if (construct_libvirt_xml_qemu_cmdline (g, xo) == -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)
{
char name[DOMAIN_NAME_LEN+1];
if (random_chars (name, DOMAIN_NAME_LEN) == -1) {
perrorf (g, "/dev/urandom");
goto err;
}
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "name"));
XMLERROR (-1, xmlTextWriterWriteFormatString (xo, "guestfs-%s", 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 "cpu"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "model",
BAD_CAST "host-model"));
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;
}
/* qemu -no-reboot */
static int
construct_libvirt_xml_lifecycle (guestfs_h *g, xmlTextWriterPtr xo)
{
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "on_reboot"));
XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST "destroy"));
XMLERROR (-1, xmlTextWriterEndElement (xo));
return 0;
err:
return -1;
}
/* Devices. */
static int
construct_libvirt_xml_devices (guestfs_h *g, xmlTextWriterPtr xo,
const char *appliance, size_t appliance_index,
const char *guestfsd_sock,
const char *console_sock)
{
struct drive *drv = g->drives;
size_t drv_index = 0;
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "devices"));
/* Path to qemu. Only write this if the user has changed the
* default, otherwise allow libvirt to choose the best one.
*/
if (g->qemu && STRNEQ (g->qemu, QEMU)) {
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "emulator"));
XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST g->qemu));
XMLERROR (-1, xmlTextWriterEndElement (xo));
}
/* 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;
/* Console. */
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "serial"));
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 "connect"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "path",
BAD_CAST console_sock));
XMLERROR (-1, xmlTextWriterEndElement (xo));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "target"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "port",
BAD_CAST "0"));
XMLERROR (-1, xmlTextWriterEndElement (xo));
XMLERROR (-1, xmlTextWriterEndElement (xo));
/* Virtio-serial for guestfsd communication. */
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 "connect"));
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;
char *format = NULL;
int is_host_device;
guestfs___drive_name (drv_index, &drive_name[2]);
snprintf (scsi_target, sizeof scsi_target, "%zu", drv_index);
/* Change the libvirt XML according to whether the host path is
* a device or a file. For devices, use:
* <disk type=block device=disk>
* <source dev=[path]>
* For files, use:
* <disk type=file device=disk>
* <source file=[path]>
*/
is_host_device = is_blk (drv->path);
/* 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 "device",
BAD_CAST "disk"));
if (!is_host_device) {
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
BAD_CAST "file"));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "file", BAD_CAST path));
XMLERROR (-1, xmlTextWriterEndElement (xo));
}
else {
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
BAD_CAST "block"));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "dev", 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 "type",
BAD_CAST drv->format));
}
else {
/* libvirt has disabled the feature of detecting the disk format,
* unless the administrator sets allow_disk_format_probing=1 in
* qemu.conf. There is no way to detect if this option is set, so we
* have to do format detection here using qemu-img and pass that to
* libvirt.
*
* This is still a security issue, so in most cases it is recommended
* the users pass the format to libguestfs which will faithfully pass
* that to libvirt and this function won't be used.
*/
format = guestfs_disk_format (g, path);
if (!format)
goto err;
if (STREQ (format, "unknown")) {
error (g, _("could not auto-detect the format of '%s'\n"
"If the format is known, pass the format to libguestfs, eg. using the\n"
"'--format' option, or via the optional 'format' argument to 'add-drive'."),
path);
goto err;
}
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
BAD_CAST 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));
/* We'd like to do this, but it's not supported by libvirt.
* See construct_libvirt_xml_qemu_cmdline for the workaround.
*
* if (drv->readonly) {
* XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "transient"));
* XMLERROR (-1, xmlTextWriterEndElement (xo));
* }
*/
XMLERROR (-1, xmlTextWriterEndElement (xo));
free (path);
free (format);
return 0;
err:
free (path);
free (format);
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 "type",
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));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "shareable"));
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;
}
static int
construct_libvirt_xml_qemu_cmdline (guestfs_h *g, xmlTextWriterPtr xo)
{
struct drive *drv;
size_t drv_index;
char attr[256];
struct qemu_param *qp;
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:commandline"));
/* 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
*/
for (drv = g->drives, drv_index = 0; drv; drv = drv->next, drv_index++) {
if (drv->readonly) {
snprintf (attr, sizeof attr,
"drive.drive-scsi0-0-%zu-0.snapshot=on", drv_index);
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));
}
}
snprintf (attr, sizeof attr,
"drive.drive-scsi0-0-%zu-0.snapshot=on", drv_index);
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));
/* Workaround because libvirt user networking cannot specify "net="
* parameter.
*/
if (g->enable_network) {
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:arg"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "value",
BAD_CAST "-netdev"));
XMLERROR (-1, xmlTextWriterEndElement (xo));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:arg"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "value",
BAD_CAST "user,id=usernet,net=169.254.0.0/16"));
XMLERROR (-1, xmlTextWriterEndElement (xo));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:arg"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "value",
BAD_CAST "-device"));
XMLERROR (-1, xmlTextWriterEndElement (xo));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:arg"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "value",
BAD_CAST "virtio-net-pci,netdev=usernet"));
XMLERROR (-1, xmlTextWriterEndElement (xo));
}
/* The qemu command line arguments requested by the caller. */
for (qp = g->qemu_params; qp; qp = qp->next) {
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:arg"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "value",
BAD_CAST qp->qemu_param));
XMLERROR (-1, xmlTextWriterEndElement (xo));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:arg"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "value",
BAD_CAST qp->qemu_value));
XMLERROR (-1, xmlTextWriterEndElement (xo));
}
XMLERROR (-1, xmlTextWriterEndElement (xo));
return 0;
err:
return -1;
}
static int
is_blk (const char *path)
{
struct stat statbuf;
if (stat (path, &statbuf) == -1)
return 0;
return S_ISBLK (statbuf.st_mode);
}
static int
random_chars (char *ret, size_t len)
{
int fd;
size_t i;
unsigned char c;
int saved_errno;
fd = open ("/dev/urandom", O_RDONLY|O_CLOEXEC);
if (fd == -1)
return -1;
for (i = 0; i < len; ++i) {
if (read (fd, &c, 1) != 1) {
saved_errno = errno;
close (fd);
errno = saved_errno;
return -1;
}
ret[i] = "0123456789abcdefghijklmnopqrstuvwxyz"[c % 36];
}
ret[len] = '\0';
if (close (fd) == -1)
return -1;
return 0;
}
static void
ignore_errors (void *ignore, virErrorPtr ignore2)
{
/* empty */
}
static int
shutdown_libvirt (guestfs_h *g, int check_for_errors)
{
virConnectPtr conn = g->virt.connv;
virDomainPtr dom = g->virt.domv;
int ret = 0;
int flags;
/* Note that we can be called back very early in launch (specifically
* from launch_libvirt itself), when conn and dom might be NULL.
*/
if (dom != NULL) {
flags = check_for_errors ? VIR_DOMAIN_DESTROY_GRACEFUL : 0;
if (virDomainDestroyFlags (dom, flags) == -1) {
libvirt_error (g, _("could not destroy libvirt domain"));
ret = -1;
}
virDomainFree (dom);
}
if (conn != NULL)
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 ();
if (err)
error (g, "%s: %s [code=%d domain=%d]",
msg, err->message, err->code, err->domain);
else
error (g, "%s", msg);
/* NB. 'err' must not be freed! */
free (msg);
}
/* This backend assumes virtio-scsi is available. */
static int
max_disks_libvirt (guestfs_h *g)
{
return 255;
}
#else /* no libvirt or libxml2 at compile time */
#define NOT_IMPL(r) \
error (g, _("libvirt attach-method is not available because " \
"this version of libguestfs was compiled " \
"without libvirt or libvirt < %d.%d.%d or without libxml2"), \
MIN_LIBVIRT_MAJOR, MIN_LIBVIRT_MINOR, MIN_LIBVIRT_MICRO); \
return r
static int
launch_libvirt (guestfs_h *g, const char *arg)
{
NOT_IMPL (-1);
}
static int
shutdown_libvirt (guestfs_h *g, int check_for_errors)
{
NOT_IMPL (-1);
}
static int
max_disks_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,
.max_disks = max_disks_libvirt,
};