mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
1606 lines
50 KiB
C
1606 lines
50 KiB
C
/* libguestfs
|
|
* Copyright (C) 2009-2013 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 <stdbool.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>
|
|
#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
|
|
|
|
#if HAVE_LIBSELINUX
|
|
#include <selinux/selinux.h>
|
|
#include <selinux/context.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 2 /* XXX patches in > 2 already */
|
|
#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
|
|
|
|
/* Pointed to by 'struct drive *' -> priv field. */
|
|
struct drive_libvirt {
|
|
/* This is either the original path, made absolute. Or for readonly
|
|
* drives, it is an (absolute path to) the overlay file that we
|
|
* create. This is always non-NULL.
|
|
*/
|
|
char *path;
|
|
/* The format of priv->path. */
|
|
char *format;
|
|
};
|
|
|
|
/* Parameters passed to construct_libvirt_xml and subfunctions. We
|
|
* keep them all in a structure for convenience!
|
|
*/
|
|
struct libvirt_xml_params {
|
|
char *kernel; /* paths to kernel and initrd */
|
|
char *initrd;
|
|
char *appliance_overlay; /* path to qcow2 overlay backed by appliance */
|
|
char appliance_dev[64]; /* appliance device name */
|
|
size_t appliance_index; /* index of appliance */
|
|
char guestfsd_sock[UNIX_PATH_MAX]; /* paths to sockets */
|
|
char console_sock[UNIX_PATH_MAX];
|
|
bool enable_svirt; /* false if we decided to disable sVirt */
|
|
bool is_kvm; /* false = qemu, true = kvm */
|
|
bool is_root; /* true = euid is root */
|
|
};
|
|
|
|
static int parse_capabilities (guestfs_h *g, const char *capabilities_xml, struct libvirt_xml_params *params);
|
|
static xmlChar *construct_libvirt_xml (guestfs_h *g, const struct libvirt_xml_params *params);
|
|
static void libvirt_error (guestfs_h *g, const char *fs, ...) __attribute__((format (printf,2,3)));
|
|
static int is_custom_qemu (guestfs_h *g);
|
|
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 char *make_qcow2_overlay (guestfs_h *g, const char *path, const char *format);
|
|
static int make_qcow2_overlay_for_drive (guestfs_h *g, struct drive *drv);
|
|
static void drive_free_priv (void *);
|
|
static void set_socket_create_context (guestfs_h *g);
|
|
static void clear_socket_create_context (guestfs_h *g);
|
|
static void selinux_warning (guestfs_h *g, const char *func, const char *selinux_op, const char *data);
|
|
|
|
static int
|
|
launch_libvirt (guestfs_h *g, const char *libvirt_uri)
|
|
{
|
|
unsigned long version;
|
|
virConnectPtr conn = NULL;
|
|
virDomainPtr dom = NULL;
|
|
CLEANUP_FREE char *capabilities_xml = NULL;
|
|
struct libvirt_xml_params params = {
|
|
.kernel = NULL,
|
|
.initrd = NULL,
|
|
.appliance_overlay = NULL,
|
|
};
|
|
CLEANUP_FREE xmlChar *xml = NULL;
|
|
CLEANUP_FREE char *appliance = NULL;
|
|
struct sockaddr_un addr;
|
|
int console = -1, r;
|
|
uint32_t size;
|
|
CLEANUP_FREE void *buf = NULL;
|
|
struct drive *drv;
|
|
size_t i;
|
|
|
|
params.is_root = geteuid () == 0;
|
|
|
|
/* 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. */
|
|
conn = guestfs___open_libvirt_connection (g, libvirt_uri, 0);
|
|
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_xml = virConnectGetCapabilities (conn);
|
|
if (!capabilities_xml) {
|
|
libvirt_error (g, _("could not get libvirt capabilities"));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Parse capabilities XML. This fills in various fields in 'params'
|
|
* struct, and can also fail if we detect that the hypervisor cannot
|
|
* run qemu guests (RHBZ#886915).
|
|
*/
|
|
if (g->verbose)
|
|
guestfs___print_timestamped_message (g, "parsing capabilities XML");
|
|
|
|
if (parse_capabilities (g, capabilities_xml, ¶ms) == -1)
|
|
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, ¶ms.kernel, ¶ms.initrd,
|
|
&appliance) == -1)
|
|
goto cleanup;
|
|
|
|
guestfs___launch_send_progress (g, 3);
|
|
TRACE0 (launch_build_libvirt_appliance_end);
|
|
|
|
/* Create overlays for read-only drives and the appliance. This
|
|
* works around lack of support for <transient/> disks in libvirt.
|
|
* Note that appliance can be NULL if using the old-style appliance.
|
|
*/
|
|
if (appliance) {
|
|
params.appliance_overlay = make_qcow2_overlay (g, appliance, "raw");
|
|
if (!params.appliance_overlay)
|
|
goto cleanup;
|
|
}
|
|
|
|
ITER_DRIVES (g, i, drv) {
|
|
if (make_qcow2_overlay_for_drive (g, drv) == -1)
|
|
goto cleanup;
|
|
}
|
|
|
|
TRACE0 (launch_build_libvirt_qcow2_overlay_end);
|
|
|
|
/* Using virtio-serial, we need to create a local Unix domain socket
|
|
* for qemu to connect to.
|
|
*/
|
|
snprintf (params.guestfsd_sock, sizeof params.guestfsd_sock,
|
|
"%s/guestfsd.sock", g->tmpdir);
|
|
unlink (params.guestfsd_sock);
|
|
|
|
set_socket_create_context (g);
|
|
|
|
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;
|
|
memcpy (addr.sun_path, params.guestfsd_sock, UNIX_PATH_MAX);
|
|
|
|
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 (params.console_sock, sizeof params.console_sock,
|
|
"%s/console.sock", g->tmpdir);
|
|
unlink (params.console_sock);
|
|
|
|
console = socket (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
|
|
if (console == -1) {
|
|
perrorf (g, "socket");
|
|
goto cleanup;
|
|
}
|
|
|
|
addr.sun_family = AF_UNIX;
|
|
memcpy (addr.sun_path, params.console_sock, UNIX_PATH_MAX);
|
|
|
|
if (bind (console, &addr, sizeof addr) == -1) {
|
|
perrorf (g, "bind");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (listen (console, 1) == -1) {
|
|
perrorf (g, "listen");
|
|
goto cleanup;
|
|
}
|
|
|
|
clear_socket_create_context (g);
|
|
|
|
/* 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 (params.is_root) {
|
|
struct group *grp;
|
|
|
|
if (chmod (params.guestfsd_sock, 0775) == -1) {
|
|
perrorf (g, "chmod: %s", params.guestfsd_sock);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (chmod (params.console_sock, 0775) == -1) {
|
|
perrorf (g, "chmod: %s", params.console_sock);
|
|
goto cleanup;
|
|
}
|
|
|
|
grp = getgrnam ("qemu");
|
|
if (grp != NULL) {
|
|
if (chown (params.guestfsd_sock, 0, grp->gr_gid) == -1) {
|
|
perrorf (g, "chown: %s", params.guestfsd_sock);
|
|
goto cleanup;
|
|
}
|
|
if (chown (params.console_sock, 0, grp->gr_gid) == -1) {
|
|
perrorf (g, "chown: %s", params.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");
|
|
|
|
params.appliance_index = g->nr_drives;
|
|
strcpy (params.appliance_dev, "/dev/sd");
|
|
guestfs___drive_name (params.appliance_index, ¶ms.appliance_dev[7]);
|
|
params.enable_svirt = ! is_custom_qemu (g);
|
|
|
|
xml = construct_libvirt_xml (g, ¶ms);
|
|
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");
|
|
console = -1;
|
|
close (r);
|
|
goto cleanup;
|
|
}
|
|
console = -1;
|
|
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;
|
|
}
|
|
|
|
r = guestfs___recv_from_daemon (g, &size, &buf);
|
|
|
|
if (r == -1) {
|
|
guestfs___launch_failed_error (g);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (size != GUESTFS_LAUNCH_FLAG) {
|
|
guestfs___launch_failed_error (g);
|
|
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;
|
|
}
|
|
|
|
if (appliance)
|
|
guestfs___add_dummy_appliance_drive (g);
|
|
|
|
TRACE0 (launch_libvirt_end);
|
|
|
|
guestfs___launch_send_progress (g, 12);
|
|
|
|
g->virt.conn = conn;
|
|
g->virt.dom = dom;
|
|
|
|
free (params.kernel);
|
|
free (params.initrd);
|
|
free (params.appliance_overlay);
|
|
|
|
return 0;
|
|
|
|
cleanup:
|
|
clear_socket_create_context (g);
|
|
|
|
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 (params.kernel);
|
|
free (params.initrd);
|
|
free (params.appliance_overlay);
|
|
|
|
g->state = CONFIG;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
parse_capabilities (guestfs_h *g, const char *capabilities_xml,
|
|
struct libvirt_xml_params *params)
|
|
{
|
|
CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL;
|
|
CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL;
|
|
CLEANUP_XMLXPATHFREEOBJECT xmlXPathObjectPtr xpathObj = NULL;
|
|
size_t i;
|
|
xmlNodeSetPtr nodes;
|
|
xmlAttrPtr attr;
|
|
size_t seen_qemu, seen_kvm;
|
|
|
|
doc = xmlParseMemory (capabilities_xml, strlen (capabilities_xml));
|
|
if (doc == NULL) {
|
|
error (g, _("unable to parse capabilities XML returned by libvirt"));
|
|
return -1;
|
|
}
|
|
|
|
xpathCtx = xmlXPathNewContext (doc);
|
|
if (xpathCtx == NULL) {
|
|
error (g, _("unable to create new XPath context"));
|
|
return -1;
|
|
}
|
|
|
|
/* This gives us a set of all the supported domain types.
|
|
* XXX It ignores architecture, but let's not worry about that.
|
|
*/
|
|
#define XPATH_EXPR "/capabilities/guest/arch/domain/@type"
|
|
xpathObj = xmlXPathEvalExpression (BAD_CAST XPATH_EXPR, xpathCtx);
|
|
if (xpathObj == NULL) {
|
|
error (g, _("unable to evaluate XPath expression: %s"), XPATH_EXPR);
|
|
return -1;
|
|
}
|
|
#undef XPATH_EXPR
|
|
|
|
nodes = xpathObj->nodesetval;
|
|
seen_qemu = seen_kvm = 0;
|
|
for (i = 0; i < (size_t) nodes->nodeNr; ++i) {
|
|
CLEANUP_FREE char *type = NULL;
|
|
|
|
if (seen_qemu && seen_kvm)
|
|
break;
|
|
|
|
assert (nodes->nodeTab[i]);
|
|
assert (nodes->nodeTab[i]->type == XML_ATTRIBUTE_NODE);
|
|
attr = (xmlAttrPtr) nodes->nodeTab[i];
|
|
type = (char *) xmlNodeListGetString (doc, attr->children, 1);
|
|
|
|
if (STREQ (type, "qemu"))
|
|
seen_qemu++;
|
|
else if (STREQ (type, "kvm"))
|
|
seen_kvm++;
|
|
}
|
|
|
|
/* This was RHBZ#886915: in that case the default libvirt URI
|
|
* pointed to a Xen hypervisor, and so could not create the
|
|
* appliance VM.
|
|
*/
|
|
if (!seen_qemu && !seen_kvm) {
|
|
error (g,
|
|
_("libvirt hypervisor doesn't support qemu or KVM,\n"
|
|
"so we cannot create the libguestfs appliance.\n"
|
|
"The current attach-method is:\n"
|
|
" %s\n"
|
|
"Try setting:\n"
|
|
" export LIBGUESTFS_ATTACH_METHOD=libvirt:qemu:///session\n"
|
|
"or if you want to have libguestfs run qemu directly, try:\n"
|
|
" export LIBGUESTFS_ATTACH_METHOD=appliance\n"
|
|
"or read the guestfs(3) man page"),
|
|
guestfs__get_attach_method (g));
|
|
return -1;
|
|
}
|
|
|
|
params->is_kvm = seen_kvm;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
is_custom_qemu (guestfs_h *g)
|
|
{
|
|
return g->qemu && STRNEQ (g->qemu, QEMU);
|
|
}
|
|
|
|
#if HAVE_LIBSELINUX
|
|
|
|
/* Set sVirt (SELinux) socket create context. For details see:
|
|
* https://bugzilla.redhat.com/show_bug.cgi?id=853393#c14
|
|
*/
|
|
|
|
#define SOCKET_CONTEXT "svirt_socket_t"
|
|
|
|
static void
|
|
set_socket_create_context (guestfs_h *g)
|
|
{
|
|
security_context_t scon; /* this is actually a 'char *' */
|
|
context_t con;
|
|
|
|
if (getcon (&scon) == -1) {
|
|
selinux_warning (g, __func__, "getcon", NULL);
|
|
return;
|
|
}
|
|
|
|
con = context_new (scon);
|
|
if (!con) {
|
|
selinux_warning (g, __func__, "context_new", scon);
|
|
goto out1;
|
|
}
|
|
|
|
if (context_type_set (con, SOCKET_CONTEXT) == -1) {
|
|
selinux_warning (g, __func__, "context_type_set", scon);
|
|
goto out2;
|
|
}
|
|
|
|
/* Note that setsockcreatecon sets the per-thread socket creation
|
|
* context (/proc/self/task/<tid>/attr/sockcreate) so this is
|
|
* thread-safe.
|
|
*/
|
|
if (setsockcreatecon (context_str (con)) == -1) {
|
|
selinux_warning (g, __func__, "setsockcreatecon", context_str (con));
|
|
goto out2;
|
|
}
|
|
|
|
out2:
|
|
context_free (con);
|
|
out1:
|
|
freecon (scon);
|
|
}
|
|
|
|
static void
|
|
clear_socket_create_context (guestfs_h *g)
|
|
{
|
|
if (setsockcreatecon (NULL) == -1)
|
|
selinux_warning (g, __func__, "setsockcreatecon", "NULL");
|
|
}
|
|
|
|
#else /* !HAVE_LIBSELINUX */
|
|
|
|
static void
|
|
set_socket_create_context (guestfs_h *g)
|
|
{
|
|
/* nothing */
|
|
}
|
|
|
|
static void
|
|
clear_socket_create_context (guestfs_h *g)
|
|
{
|
|
/* nothing */
|
|
}
|
|
|
|
#endif /* !HAVE_LIBSELINUX */
|
|
|
|
static int construct_libvirt_xml_domain (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo);
|
|
static int construct_libvirt_xml_name (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo);
|
|
static int construct_libvirt_xml_cpu (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo);
|
|
static int construct_libvirt_xml_boot (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo);
|
|
static int construct_libvirt_xml_seclabel (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo);
|
|
static int construct_libvirt_xml_lifecycle (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo);
|
|
static int construct_libvirt_xml_devices (guestfs_h *g, const struct libvirt_xml_params *params, xmlTextWriterPtr xo);
|
|
static int construct_libvirt_xml_qemu_cmdline (guestfs_h *g, const struct libvirt_xml_params *params, 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, const struct libvirt_xml_params *params, xmlTextWriterPtr xo);
|
|
|
|
/* Note this macro is rather specialized: It assumes that any local
|
|
* variables are protected by CLEANUP_* macros, so that simply
|
|
* returning will not cause any memory leaks.
|
|
*/
|
|
#define XMLERROR_RET(code,e,ret) do { \
|
|
if ((e) == (code)) { \
|
|
perrorf (g, _("error constructing libvirt XML at \"%s\""), \
|
|
#e); \
|
|
return (ret); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define XMLERROR(code,e) XMLERROR_RET((code),(e),-1)
|
|
|
|
static xmlChar *
|
|
construct_libvirt_xml (guestfs_h *g, const struct libvirt_xml_params *params)
|
|
{
|
|
xmlChar *ret = NULL;
|
|
CLEANUP_XMLBUFFERFREE xmlBufferPtr xb = NULL;
|
|
xmlOutputBufferPtr ob;
|
|
CLEANUP_XMLFREETEXTWRITER xmlTextWriterPtr xo = NULL;
|
|
|
|
XMLERROR_RET (NULL, xb = xmlBufferCreate (), NULL);
|
|
XMLERROR_RET (NULL, ob = xmlOutputBufferCreateBuffer (xb, NULL), NULL);
|
|
XMLERROR_RET (NULL, xo = xmlNewTextWriter (ob), NULL);
|
|
|
|
if (construct_libvirt_xml_domain (g, params, xo) == -1)
|
|
return NULL;
|
|
|
|
XMLERROR_RET (-1, xmlTextWriterEndDocument (xo), NULL);
|
|
XMLERROR_RET (NULL, ret = xmlBufferDetach (xb), NULL); /* caller frees ret */
|
|
|
|
debug (g, "libvirt XML:\n%s", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
construct_libvirt_xml_domain (guestfs_h *g,
|
|
const struct libvirt_xml_params *params,
|
|
xmlTextWriterPtr xo)
|
|
{
|
|
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",
|
|
params->is_kvm ? BAD_CAST "kvm" : BAD_CAST "qemu"));
|
|
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, params, xo) == -1)
|
|
return -1;
|
|
if (construct_libvirt_xml_cpu (g, params, xo) == -1)
|
|
return -1;
|
|
if (construct_libvirt_xml_boot (g, params, xo) == -1)
|
|
return -1;
|
|
if (construct_libvirt_xml_seclabel (g, params, xo) == -1)
|
|
return -1;
|
|
if (construct_libvirt_xml_lifecycle (g, params, xo) == -1)
|
|
return -1;
|
|
if (construct_libvirt_xml_devices (g, params, xo) == -1)
|
|
return -1;
|
|
if (construct_libvirt_xml_qemu_cmdline (g, params, xo) == -1)
|
|
return -1;
|
|
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 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,
|
|
const struct libvirt_xml_params *params,
|
|
xmlTextWriterPtr xo)
|
|
{
|
|
char name[DOMAIN_NAME_LEN+1];
|
|
|
|
if (random_chars (name, DOMAIN_NAME_LEN) == -1) {
|
|
perrorf (g, "/dev/urandom");
|
|
return -1;
|
|
}
|
|
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "name"));
|
|
XMLERROR (-1, xmlTextWriterWriteFormatString (xo, "guestfs-%s", name));
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* CPU and memory features. */
|
|
static int
|
|
construct_libvirt_xml_cpu (guestfs_h *g,
|
|
const struct libvirt_xml_params *params,
|
|
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));
|
|
|
|
/* It would be faster to pass the CPU host model to the appliance,
|
|
* allowing maximum speed for things like checksums, encryption.
|
|
* However this doesn't work because KVM doesn't emulate all of the
|
|
* required guest insns (RHBZ#870071). This is why the following
|
|
* section is commented out.
|
|
*/
|
|
#if 0
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "cpu"));
|
|
XMLERROR (-1,
|
|
xmlTextWriterWriteAttribute (xo, BAD_CAST "mode",
|
|
BAD_CAST "host-model"));
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "model"));
|
|
XMLERROR (-1,
|
|
xmlTextWriterWriteAttribute (xo, BAD_CAST "fallback",
|
|
BAD_CAST "allow"));
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
/* Boot parameters. */
|
|
static int
|
|
construct_libvirt_xml_boot (guestfs_h *g,
|
|
const struct libvirt_xml_params *params,
|
|
xmlTextWriterPtr xo)
|
|
{
|
|
CLEANUP_FREE char *cmdline = NULL;
|
|
int flags;
|
|
|
|
/* Linux kernel command line. */
|
|
flags = 0;
|
|
if (!params->is_kvm)
|
|
flags |= APPLIANCE_COMMAND_LINE_IS_TCG;
|
|
cmdline = guestfs___appliance_command_line (g, params->appliance_dev, flags);
|
|
|
|
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 params->kernel));
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "initrd"));
|
|
XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST params->initrd));
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "cmdline"));
|
|
XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST cmdline));
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
construct_libvirt_xml_seclabel (guestfs_h *g,
|
|
const struct libvirt_xml_params *params,
|
|
xmlTextWriterPtr xo)
|
|
{
|
|
if (!params->enable_svirt) {
|
|
/* This disables SELinux/sVirt confinement. */
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "seclabel"));
|
|
XMLERROR (-1,
|
|
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
|
|
BAD_CAST "none"));
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* qemu -no-reboot */
|
|
static int
|
|
construct_libvirt_xml_lifecycle (guestfs_h *g,
|
|
const struct libvirt_xml_params *params,
|
|
xmlTextWriterPtr xo)
|
|
{
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "on_reboot"));
|
|
XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST "destroy"));
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Devices. */
|
|
static int
|
|
construct_libvirt_xml_devices (guestfs_h *g,
|
|
const struct libvirt_xml_params *params,
|
|
xmlTextWriterPtr xo)
|
|
{
|
|
struct drive *drv;
|
|
size_t i;
|
|
|
|
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 (is_custom_qemu (g)) {
|
|
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. */
|
|
ITER_DRIVES (g, i, drv) {
|
|
if (construct_libvirt_xml_disk (g, xo, drv, i) == -1)
|
|
return -1;
|
|
}
|
|
|
|
if (params->appliance_overlay) {
|
|
/* Appliance disk. */
|
|
if (construct_libvirt_xml_appliance (g, params, xo) == -1)
|
|
return -1;
|
|
}
|
|
|
|
/* 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 params->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 params->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;
|
|
}
|
|
|
|
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];
|
|
struct drive_libvirt *drv_priv;
|
|
CLEANUP_FREE char *format = NULL;
|
|
int is_host_device;
|
|
|
|
/* XXX We probably could support this if we thought about it some more. */
|
|
if (drv->iface) {
|
|
error (g, _("'iface' parameter is not supported by the libvirt attach-method"));
|
|
return -1;
|
|
}
|
|
|
|
guestfs___drive_name (drv_index, &drive_name[2]);
|
|
snprintf (scsi_target, sizeof scsi_target, "%zu", drv_index);
|
|
|
|
drv_priv = (struct drive_libvirt *) drv->priv;
|
|
|
|
/* 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_priv->path);
|
|
|
|
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 drv_priv->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 drv_priv->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_priv->format) {
|
|
XMLERROR (-1,
|
|
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
|
|
BAD_CAST drv_priv->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, drv_priv->path);
|
|
if (!format)
|
|
return -1;
|
|
|
|
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'."),
|
|
drv->path);
|
|
return -1;
|
|
}
|
|
|
|
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));
|
|
|
|
if (drv->disk_label) {
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "serial"));
|
|
XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST drv->disk_label));
|
|
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, xmlTextWriterEndElement (xo));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
construct_libvirt_xml_appliance (guestfs_h *g,
|
|
const struct libvirt_xml_params *params,
|
|
xmlTextWriterPtr xo)
|
|
{
|
|
char scsi_target[64];
|
|
|
|
snprintf (scsi_target, sizeof scsi_target, "%zu", params->appliance_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 params->appliance_overlay));
|
|
XMLERROR (-1, xmlTextWriterEndElement (xo));
|
|
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "target"));
|
|
XMLERROR (-1,
|
|
xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
|
|
BAD_CAST ¶ms->appliance_dev[5]));
|
|
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 "qcow2"));
|
|
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;
|
|
}
|
|
|
|
static int
|
|
construct_libvirt_xml_qemu_cmdline (guestfs_h *g,
|
|
const struct libvirt_xml_params *params,
|
|
xmlTextWriterPtr xo)
|
|
{
|
|
struct qemu_param *qp;
|
|
CLEANUP_FREE char *tmpdir = NULL;
|
|
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:commandline"));
|
|
|
|
/* We need to ensure the snapshots are created in the persistent
|
|
* temporary directory (RHBZ#856619). We must set one, because
|
|
* otherwise libvirt will use a random TMPDIR (RHBZ#865464).
|
|
*/
|
|
tmpdir = guestfs_get_cachedir (g);
|
|
|
|
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "qemu:env"));
|
|
XMLERROR (-1,
|
|
xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
|
|
BAD_CAST "TMPDIR"));
|
|
XMLERROR (-1,
|
|
xmlTextWriterWriteAttribute (xo, BAD_CAST "value",
|
|
BAD_CAST tmpdir));
|
|
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));
|
|
|
|
if (qp->qemu_value) {
|
|
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;
|
|
}
|
|
|
|
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 */
|
|
}
|
|
|
|
/* Create a temporary qcow2 overlay on top of 'path'. */
|
|
static char *
|
|
make_qcow2_overlay (guestfs_h *g, const char *path, const char *format)
|
|
{
|
|
char *tmpfile = NULL;
|
|
CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
|
|
int r;
|
|
|
|
/* Path must be absolute. */
|
|
assert (path);
|
|
assert (path[0] == '/');
|
|
|
|
tmpfile = safe_asprintf (g, "%s/snapshot%d", g->tmpdir, ++g->unique);
|
|
|
|
guestfs___cmd_add_arg (cmd, "qemu-img");
|
|
guestfs___cmd_add_arg (cmd, "create");
|
|
guestfs___cmd_add_arg (cmd, "-f");
|
|
guestfs___cmd_add_arg (cmd, "qcow2");
|
|
guestfs___cmd_add_arg (cmd, "-b");
|
|
guestfs___cmd_add_arg (cmd, path);
|
|
if (format) {
|
|
guestfs___cmd_add_arg (cmd, "-o");
|
|
guestfs___cmd_add_arg_format (cmd, "backing_fmt=%s", format);
|
|
}
|
|
guestfs___cmd_add_arg (cmd, tmpfile);
|
|
r = guestfs___cmd_run (cmd);
|
|
if (r == -1)
|
|
goto error;
|
|
if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
|
|
error (g, _("qemu-img create: could not create snapshot over %s"), path);
|
|
goto error;
|
|
}
|
|
|
|
return tmpfile; /* caller frees */
|
|
|
|
error:
|
|
free (tmpfile);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
make_qcow2_overlay_for_drive (guestfs_h *g, struct drive *drv)
|
|
{
|
|
char *path;
|
|
struct drive_libvirt *drv_priv;
|
|
|
|
if (drv->priv && drv->free_priv)
|
|
drv->free_priv (drv->priv);
|
|
|
|
drv->priv = drv_priv = safe_calloc (g, 1, sizeof (struct drive_libvirt));
|
|
drv->free_priv = drive_free_priv;
|
|
|
|
/* Even for non-readonly paths, we need to make the paths absolute here. */
|
|
path = realpath (drv->path, NULL);
|
|
if (path == NULL) {
|
|
perrorf (g, _("realpath: could not convert '%s' to absolute path"),
|
|
drv->path);
|
|
return -1;
|
|
}
|
|
|
|
if (!drv->readonly) {
|
|
drv_priv->path = path;
|
|
drv_priv->format = drv->format ? safe_strdup (g, drv->format) : NULL;
|
|
}
|
|
else {
|
|
drv_priv->path = make_qcow2_overlay (g, path, drv->format);
|
|
free (path);
|
|
if (!drv_priv->path)
|
|
return -1;
|
|
drv_priv->format = safe_strdup (g, "qcow2");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
drive_free_priv (void *priv)
|
|
{
|
|
struct drive_libvirt *drv_priv = priv;
|
|
|
|
free (drv_priv->path);
|
|
free (drv_priv->format);
|
|
free (drv_priv);
|
|
}
|
|
|
|
static int
|
|
shutdown_libvirt (guestfs_h *g, int check_for_errors)
|
|
{
|
|
virConnectPtr conn = g->virt.conn;
|
|
virDomainPtr dom = g->virt.dom;
|
|
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.conn = NULL;
|
|
g->virt.dom = 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);
|
|
}
|
|
|
|
static void
|
|
selinux_warning (guestfs_h *g, const char *func,
|
|
const char *selinux_op, const char *data)
|
|
{
|
|
debug (g, "%s: %s failed: %s: %m"
|
|
" [you can ignore this UNLESS using SELinux + sVirt]",
|
|
func, selinux_op, data ? data : "(none)");
|
|
}
|
|
|
|
/* This backend assumes virtio-scsi is available. */
|
|
static int
|
|
max_disks_libvirt (guestfs_h *g)
|
|
{
|
|
return 255;
|
|
}
|
|
|
|
static xmlChar *construct_libvirt_xml_hot_add_disk (guestfs_h *g, struct drive *drv, size_t drv_index);
|
|
|
|
/* Hot-add a drive. Note the appliance is up when this is called. */
|
|
static int
|
|
hot_add_drive_libvirt (guestfs_h *g, struct drive *drv, size_t drv_index)
|
|
{
|
|
virConnectPtr conn = g->virt.conn;
|
|
virDomainPtr dom = g->virt.dom;
|
|
CLEANUP_FREE xmlChar *xml = NULL;
|
|
|
|
if (!conn || !dom) {
|
|
/* This is essentially an internal error if it happens. */
|
|
error (g, "%s: conn == NULL or dom == NULL", __func__);
|
|
return -1;
|
|
}
|
|
|
|
/* Create overlay for read-only drive. This works around lack of
|
|
* support for <transient/> disks in libvirt.
|
|
*/
|
|
if (make_qcow2_overlay_for_drive (g, drv) == -1)
|
|
return -1;
|
|
|
|
/* Create the XML for the new disk. */
|
|
xml = construct_libvirt_xml_hot_add_disk (g, drv, drv_index);
|
|
if (xml == NULL)
|
|
return -1;
|
|
|
|
/* Attach it. */
|
|
if (virDomainAttachDeviceFlags (dom, (char *) xml,
|
|
VIR_DOMAIN_DEVICE_MODIFY_LIVE) == -1) {
|
|
libvirt_error (g, _("could not attach disk to libvirt domain"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Hot-remove a drive. Note the appliance is up when this is called. */
|
|
static int
|
|
hot_remove_drive_libvirt (guestfs_h *g, struct drive *drv, size_t drv_index)
|
|
{
|
|
virConnectPtr conn = g->virt.conn;
|
|
virDomainPtr dom = g->virt.dom;
|
|
CLEANUP_FREE xmlChar *xml = NULL;
|
|
|
|
if (!conn || !dom) {
|
|
/* This is essentially an internal error if it happens. */
|
|
error (g, "%s: conn == NULL or dom == NULL", __func__);
|
|
return -1;
|
|
}
|
|
|
|
/* Re-create the XML for the disk. */
|
|
xml = construct_libvirt_xml_hot_add_disk (g, drv, drv_index);
|
|
if (xml == NULL)
|
|
return -1;
|
|
|
|
/* Detach it. */
|
|
if (virDomainDetachDeviceFlags (dom, (char *) xml,
|
|
VIR_DOMAIN_DEVICE_MODIFY_LIVE) == -1) {
|
|
libvirt_error (g, _("could not detach disk from libvirt domain"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static xmlChar *
|
|
construct_libvirt_xml_hot_add_disk (guestfs_h *g, struct drive *drv,
|
|
size_t drv_index)
|
|
{
|
|
xmlChar *ret = NULL;
|
|
CLEANUP_XMLBUFFERFREE xmlBufferPtr xb = NULL;
|
|
xmlOutputBufferPtr ob;
|
|
CLEANUP_XMLFREETEXTWRITER xmlTextWriterPtr xo = NULL;
|
|
|
|
XMLERROR_RET (NULL, xb = xmlBufferCreate (), NULL);
|
|
XMLERROR_RET (NULL, ob = xmlOutputBufferCreateBuffer (xb, NULL), NULL);
|
|
XMLERROR_RET (NULL, xo = xmlNewTextWriter (ob), NULL);
|
|
|
|
XMLERROR_RET (-1, xmlTextWriterSetIndent (xo, 1), NULL);
|
|
XMLERROR_RET (-1, xmlTextWriterSetIndentString (xo, BAD_CAST " "), NULL);
|
|
XMLERROR_RET (-1, xmlTextWriterStartDocument (xo, NULL, NULL, NULL), NULL);
|
|
|
|
if (construct_libvirt_xml_disk (g, xo, drv, drv_index) == -1)
|
|
return NULL;
|
|
|
|
XMLERROR_RET (-1, xmlTextWriterEndDocument (xo), NULL);
|
|
XMLERROR_RET (NULL, ret = xmlBufferDetach (xb), NULL); /* caller frees ret */
|
|
|
|
debug (g, "hot-add disk XML:\n%s", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct attach_ops attach_ops_libvirt = {
|
|
.launch = launch_libvirt,
|
|
.shutdown = shutdown_libvirt,
|
|
.max_disks = max_disks_libvirt,
|
|
.hot_add_drive = hot_add_drive_libvirt,
|
|
.hot_remove_drive = hot_remove_drive_libvirt,
|
|
};
|
|
|
|
#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);
|
|
}
|
|
|
|
struct attach_ops attach_ops_libvirt = {
|
|
.launch = launch_libvirt,
|
|
.shutdown = shutdown_libvirt,
|
|
.max_disks = max_disks_libvirt,
|
|
};
|
|
|
|
#endif /* no libvirt or libxml2 at compile time */
|