mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
Libvirt >= 2.1.0 now allows you to open files which have a "json:" QEMU pseudo-URL as backingfile, whereas previously it would fail hard in this case (RHBZ#1134878). When virt-v2v performs conversions from Xen (over SSH) or vCenter (over HTTPS) it uses these pseudo-URLs as backingfiles. We had to tell people to use LIBGUESTFS_BACKEND=direct to avoid libvirt in this situation. This commit narrows the check so it will now only print the error if libvirt < 2.1.0 and LIBGUESTFS_BACKEND=direct is not set. Also the error is modified to tell users they can either upgrade libvirt or set LIBGUESTFS_BACKEND=direct to work around the problem. Note there is not an easy way apart from checking the version number of libvirt to tell if the json pseudo-URL is supported. As a side-effect, this commit also prints the libvirt version number in debugging output when virt-v2v starts up, which is sometimes useful information for narrowing down bugs (it is in fact already printed by libguestfs, so this is duplicate information, but it's a bit easier to find when it's at the top of the debug). Thanks: Peter Krempa.
544 lines
15 KiB
C
544 lines
15 KiB
C
/* virt-v2v
|
|
* Copyright (C) 2009-2016 Red Hat Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
/**
|
|
* This module implements various C<virsh>-like commands, but with
|
|
* non-broken authentication handling.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <libintl.h>
|
|
|
|
#include <caml/alloc.h>
|
|
#include <caml/fail.h>
|
|
#include <caml/memory.h>
|
|
#include <caml/mlvalues.h>
|
|
|
|
#ifdef HAVE_LIBVIRT
|
|
#include <libvirt/libvirt.h>
|
|
#include <libvirt/virterror.h>
|
|
#endif
|
|
|
|
#include "guestfs.h"
|
|
#include "guestfs-internal-frontend.h"
|
|
|
|
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
|
|
|
|
#ifdef HAVE_LIBVIRT
|
|
|
|
#define ERROR_MESSAGE_LEN 512
|
|
|
|
static void
|
|
ignore_errors (void *ignore, virErrorPtr ignore2)
|
|
{
|
|
/* empty */
|
|
}
|
|
|
|
/* Get the remote domain state (running, etc.). Use virDomainGetState
|
|
* which is most efficient, but if it's not implemented, fall back to
|
|
* virDomainGetInfo. See equivalent code in virsh.
|
|
*/
|
|
static int
|
|
get_dom_state (virDomainPtr dom)
|
|
{
|
|
int state, reason;
|
|
virErrorPtr err;
|
|
virDomainInfo info;
|
|
|
|
if (virDomainGetState (dom, &state, &reason, 0) == 0)
|
|
return state;
|
|
|
|
err = virGetLastError ();
|
|
if (!err || err->code != VIR_ERR_NO_SUPPORT)
|
|
return -1;
|
|
|
|
if (virDomainGetInfo (dom, &info) == 0)
|
|
return info.state;
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* See src/libvirt-auth.c for why we need this. */
|
|
static int
|
|
libvirt_auth_default_wrapper (virConnectCredentialPtr cred,
|
|
unsigned int ncred,
|
|
void *passwordvp)
|
|
{
|
|
const char *password = passwordvp;
|
|
unsigned int i;
|
|
|
|
if (password) {
|
|
/* If --password-file was specified on the command line, and the
|
|
* libvirt handler is asking for a password, return that.
|
|
*/
|
|
for (i = 0; i < ncred; ++i) {
|
|
if (cred[i].type == VIR_CRED_PASSPHRASE) {
|
|
cred[i].result = strdup (password);
|
|
cred[i].resultlen = strlen (password);
|
|
}
|
|
else {
|
|
cred[i].result = NULL;
|
|
cred[i].resultlen = 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
else {
|
|
/* No --password-file so call the default handler. */
|
|
return virConnectAuthPtrDefault->cb (cred, ncred,
|
|
virConnectAuthPtrDefault->cbdata);
|
|
}
|
|
}
|
|
|
|
virStoragePoolPtr
|
|
connect_and_load_pool (value connv, value poolnamev)
|
|
{
|
|
CAMLparam2 (connv, poolnamev);
|
|
const char *conn_uri = NULL;
|
|
const char *poolname;
|
|
/* We have to assemble the error on the stack because a dynamic
|
|
* string couldn't be freed.
|
|
*/
|
|
char errmsg[ERROR_MESSAGE_LEN];
|
|
virErrorPtr err;
|
|
virConnectPtr conn;
|
|
virStoragePoolPtr pool;
|
|
|
|
if (connv != Val_int (0))
|
|
conn_uri = String_val (Field (connv, 0)); /* Some conn */
|
|
|
|
/* We have to call the default authentication handler, not least
|
|
* since it handles all the PolicyKit crap. However it also makes
|
|
* coding this simpler.
|
|
*/
|
|
conn = virConnectOpenAuth (conn_uri, virConnectAuthPtrDefault,
|
|
VIR_CONNECT_RO);
|
|
if (conn == NULL) {
|
|
if (conn_uri)
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot open libvirt connection '%s'"), conn_uri);
|
|
else
|
|
snprintf (errmsg, sizeof errmsg, _("cannot open libvirt connection"));
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* Look up the pool. */
|
|
poolname = String_val (poolnamev);
|
|
|
|
pool = virStoragePoolLookupByUUIDString (conn, poolname);
|
|
|
|
if (!pool)
|
|
pool = virStoragePoolLookupByName (conn, poolname);
|
|
|
|
if (!pool) {
|
|
err = virGetLastError ();
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot find libvirt pool '%s': %s\n\nUse `virsh pool-list --all' to list all available pools, and `virsh pool-dumpxml <pool>' to display details about a particular pool.\n\nTo set the pool which virt-v2v uses, add the `-os <pool>' option."),
|
|
poolname, err->message);
|
|
virConnectClose (conn);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
|
|
CAMLreturnT (virStoragePoolPtr, pool);
|
|
}
|
|
|
|
value
|
|
v2v_dumpxml (value passwordv, value connv, value domnamev)
|
|
{
|
|
CAMLparam3 (passwordv, connv, domnamev);
|
|
CAMLlocal1 (retv);
|
|
const char *password = NULL;
|
|
const char *conn_uri = NULL;
|
|
const char *domname;
|
|
virConnectAuth authdata;
|
|
/* We have to assemble the error on the stack because a dynamic
|
|
* string couldn't be freed.
|
|
*/
|
|
char errmsg[ERROR_MESSAGE_LEN];
|
|
virErrorPtr err;
|
|
virConnectPtr conn;
|
|
virDomainPtr dom;
|
|
int is_test_uri = 0;
|
|
char *xml;
|
|
|
|
if (passwordv != Val_int (0))
|
|
password = String_val (Field (passwordv, 0)); /* Some password */
|
|
|
|
if (connv != Val_int (0)) {
|
|
conn_uri = String_val (Field (connv, 0)); /* Some conn */
|
|
is_test_uri = STRPREFIX (conn_uri, "test:");
|
|
}
|
|
|
|
/* Set up authentication wrapper. */
|
|
authdata = *virConnectAuthPtrDefault;
|
|
authdata.cb = libvirt_auth_default_wrapper;
|
|
authdata.cbdata = (void *) password;
|
|
|
|
/* Note this cannot be a read-only connection since we need to use
|
|
* the VIR_DOMAIN_XML_SECURE flag below.
|
|
*/
|
|
conn = virConnectOpenAuth (conn_uri, &authdata, 0);
|
|
if (conn == NULL) {
|
|
if (conn_uri)
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot open libvirt connection '%s'"), conn_uri);
|
|
else
|
|
snprintf (errmsg, sizeof errmsg, _("cannot open libvirt connection"));
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* Look up the domain. */
|
|
domname = String_val (domnamev);
|
|
|
|
dom = virDomainLookupByUUIDString (conn, domname);
|
|
|
|
if (!dom)
|
|
dom = virDomainLookupByName (conn, domname);
|
|
|
|
if (!dom) {
|
|
err = virGetLastError ();
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot find libvirt domain '%s': %s"), domname, err->message);
|
|
virConnectClose (conn);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
|
|
/* As a side-effect we check that the domain is shut down. Of course
|
|
* this is only appropriate for virt-v2v. (RHBZ#1138586)
|
|
*/
|
|
if (!is_test_uri) {
|
|
const int state = get_dom_state (dom);
|
|
|
|
if (state == VIR_DOMAIN_RUNNING ||
|
|
state == VIR_DOMAIN_BLOCKED ||
|
|
state == VIR_DOMAIN_PAUSED) {
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("libvirt domain '%s' is running or paused. It must be shut down in order to perform virt-v2v conversion"),
|
|
domname);
|
|
virDomainFree (dom);
|
|
virConnectClose (conn);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
}
|
|
|
|
/* Use VIR_DOMAIN_XML_SECURE to get passwords (RHBZ#1174123). */
|
|
xml = virDomainGetXMLDesc (dom, VIR_DOMAIN_XML_SECURE);
|
|
if (xml == NULL) {
|
|
err = virGetLastError ();
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot fetch XML description of guest '%s': %s"),
|
|
domname, err->message);
|
|
virDomainFree (dom);
|
|
virConnectClose (conn);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
virDomainFree (dom);
|
|
virConnectClose (conn);
|
|
|
|
retv = caml_copy_string (xml);
|
|
free (xml);
|
|
|
|
CAMLreturn (retv);
|
|
}
|
|
|
|
value
|
|
v2v_pool_dumpxml (value connv, value poolnamev)
|
|
{
|
|
CAMLparam2 (connv, poolnamev);
|
|
CAMLlocal1 (retv);
|
|
/* We have to assemble the error on the stack because a dynamic
|
|
* string couldn't be freed.
|
|
*/
|
|
char errmsg[ERROR_MESSAGE_LEN];
|
|
virErrorPtr err;
|
|
virConnectPtr conn;
|
|
virStoragePoolPtr pool;
|
|
char *xml;
|
|
|
|
/* Look up the pool. */
|
|
pool = connect_and_load_pool (connv, poolnamev);
|
|
conn = virStoragePoolGetConnect (pool);
|
|
|
|
xml = virStoragePoolGetXMLDesc (pool, 0);
|
|
if (xml == NULL) {
|
|
err = virGetLastError ();
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot fetch XML description of pool '%s': %s"),
|
|
String_val (poolnamev), err->message);
|
|
virStoragePoolFree (pool);
|
|
virConnectClose (conn);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
virStoragePoolFree (pool);
|
|
virConnectClose (conn);
|
|
|
|
retv = caml_copy_string (xml);
|
|
free (xml);
|
|
|
|
CAMLreturn (retv);
|
|
}
|
|
|
|
value
|
|
v2v_vol_dumpxml (value connv, value poolnamev, value volnamev)
|
|
{
|
|
CAMLparam3 (connv, poolnamev, volnamev);
|
|
CAMLlocal1 (retv);
|
|
const char *volname;
|
|
/* We have to assemble the error on the stack because a dynamic
|
|
* string couldn't be freed.
|
|
*/
|
|
char errmsg[ERROR_MESSAGE_LEN];
|
|
virErrorPtr err;
|
|
virConnectPtr conn;
|
|
virStoragePoolPtr pool;
|
|
virStorageVolPtr vol;
|
|
char *xml;
|
|
|
|
/* Look up the pool. */
|
|
pool = connect_and_load_pool (connv, poolnamev);
|
|
conn = virStoragePoolGetConnect (pool);
|
|
|
|
/* Look up the volume. */
|
|
volname = String_val (volnamev);
|
|
|
|
vol = virStorageVolLookupByName (pool, volname);
|
|
|
|
if (!vol) {
|
|
err = virGetLastError ();
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot find libvirt volume '%s': %s"), volname, err->message);
|
|
virStoragePoolFree (pool);
|
|
virConnectClose (conn);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
|
|
xml = virStorageVolGetXMLDesc (vol, 0);
|
|
if (xml == NULL) {
|
|
err = virGetLastError ();
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot fetch XML description of volume '%s': %s"),
|
|
volname, err->message);
|
|
virStorageVolFree (vol);
|
|
virStoragePoolFree (pool);
|
|
virConnectClose (conn);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
virStorageVolFree (vol);
|
|
virStoragePoolFree (pool);
|
|
virConnectClose (conn);
|
|
|
|
retv = caml_copy_string (xml);
|
|
free (xml);
|
|
|
|
CAMLreturn (retv);
|
|
}
|
|
|
|
value
|
|
v2v_capabilities (value connv, value unitv)
|
|
{
|
|
CAMLparam2 (connv, unitv);
|
|
CAMLlocal1 (capabilitiesv);
|
|
const char *conn_uri = NULL;
|
|
char *capabilities;
|
|
/* We have to assemble the error on the stack because a dynamic
|
|
* string couldn't be freed.
|
|
*/
|
|
char errmsg[ERROR_MESSAGE_LEN];
|
|
virErrorPtr err;
|
|
virConnectPtr conn;
|
|
|
|
if (connv != Val_int (0))
|
|
conn_uri = String_val (Field (connv, 0)); /* Some conn */
|
|
|
|
/* We have to call the default authentication handler, not least
|
|
* since it handles all the PolicyKit crap. However it also makes
|
|
* coding this simpler.
|
|
*/
|
|
conn = virConnectOpenAuth (conn_uri, virConnectAuthPtrDefault,
|
|
VIR_CONNECT_RO);
|
|
if (conn == NULL) {
|
|
if (conn_uri)
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot open libvirt connection '%s'"), conn_uri);
|
|
else
|
|
snprintf (errmsg, sizeof errmsg, _("cannot open libvirt connection"));
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
capabilities = virConnectGetCapabilities (conn);
|
|
if (!capabilities) {
|
|
err = virGetLastError ();
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot get libvirt hypervisor capabilities: %s"),
|
|
err->message);
|
|
virConnectClose (conn);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
|
|
capabilitiesv = caml_copy_string (capabilities);
|
|
free (capabilities);
|
|
|
|
virConnectClose (conn);
|
|
|
|
CAMLreturn (capabilitiesv);
|
|
}
|
|
|
|
value
|
|
v2v_domain_exists (value connv, value domnamev)
|
|
{
|
|
CAMLparam2 (connv, domnamev);
|
|
const char *conn_uri = NULL;
|
|
const char *domname;
|
|
/* We have to assemble the error on the stack because a dynamic
|
|
* string couldn't be freed.
|
|
*/
|
|
char errmsg[ERROR_MESSAGE_LEN];
|
|
virErrorPtr err;
|
|
virConnectPtr conn;
|
|
virDomainPtr dom;
|
|
int domain_exists;
|
|
|
|
if (connv != Val_int (0))
|
|
conn_uri = String_val (Field (connv, 0)); /* Some conn */
|
|
|
|
/* We have to call the default authentication handler, not least
|
|
* since it handles all the PolicyKit crap. However it also makes
|
|
* coding this simpler.
|
|
*/
|
|
conn = virConnectOpenAuth (conn_uri, virConnectAuthPtrDefault,
|
|
VIR_CONNECT_RO);
|
|
if (conn == NULL) {
|
|
if (conn_uri)
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot open libvirt connection '%s'"), conn_uri);
|
|
else
|
|
snprintf (errmsg, sizeof errmsg, _("cannot open libvirt connection"));
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* Look up the domain. */
|
|
domname = String_val (domnamev);
|
|
dom = virDomainLookupByName (conn, domname);
|
|
|
|
if (dom) {
|
|
domain_exists = 1;
|
|
virDomainFree (dom);
|
|
}
|
|
else {
|
|
err = virGetLastError ();
|
|
if (err->code == VIR_ERR_NO_DOMAIN)
|
|
domain_exists = 0;
|
|
else {
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot find libvirt domain '%s': %s"),
|
|
domname, err->message);
|
|
virConnectClose (conn);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
}
|
|
|
|
virConnectClose (conn);
|
|
|
|
CAMLreturn (Val_bool (domain_exists));
|
|
}
|
|
|
|
/* XXX This function is stuffed here for convenience (accessing
|
|
* libvirt), not because it belongs logically with the rest of the
|
|
* functions in this file.
|
|
*/
|
|
value
|
|
v2v_libvirt_get_version (value unitv)
|
|
{
|
|
CAMLparam1 (unitv);
|
|
CAMLlocal1 (rv);
|
|
int major, minor, release;
|
|
/* We have to assemble the error on the stack because a dynamic
|
|
* string couldn't be freed.
|
|
*/
|
|
char errmsg[ERROR_MESSAGE_LEN];
|
|
unsigned long ver;
|
|
virErrorPtr err;
|
|
|
|
if (virGetVersion (&ver, NULL, NULL) == -1) {
|
|
err = virGetLastError ();
|
|
snprintf (errmsg, sizeof errmsg,
|
|
_("cannot get libvirt library version: %s"),
|
|
err->message);
|
|
caml_invalid_argument (errmsg);
|
|
}
|
|
|
|
major = ver / 1000000UL;
|
|
minor = ver / 1000UL % 1000UL;
|
|
release = ver % 1000UL;
|
|
|
|
rv = caml_alloc (3, 0);
|
|
Store_field (rv, 0, Val_int (major));
|
|
Store_field (rv, 1, Val_int (minor));
|
|
Store_field (rv, 2, Val_int (release));
|
|
|
|
CAMLreturn (rv);
|
|
}
|
|
|
|
#else /* !HAVE_LIBVIRT */
|
|
|
|
#define NO_LIBVIRT(proto) \
|
|
proto __attribute__((noreturn)); \
|
|
proto \
|
|
{ \
|
|
caml_invalid_argument ("virt-v2v was compiled without libvirt support"); \
|
|
}
|
|
|
|
NO_LIBVIRT (value v2v_dumpxml (value connv, value domv))
|
|
NO_LIBVIRT (value v2v_pool_dumpxml (value connv, value poolv))
|
|
NO_LIBVIRT (value v2v_vol_dumpxml (value connv, value poolnamev, value volnamev))
|
|
NO_LIBVIRT (value v2v_capabilities (value connv, value unitv))
|
|
NO_LIBVIRT (value v2v_domain_exists (value connv, value domnamev))
|
|
NO_LIBVIRT (value v2v_libvirt_get_version (value unitv))
|
|
|
|
#endif /* !HAVE_LIBVIRT */
|