mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
New API: add-domain
This new API allows you to add the disks from a libvirt domain. In guestfish you can use the 'domain' command to access the API, eg: ><fs> domain Fedora14 libvirturi:qemu:///system 1 The returned number is the number of disks that were added. Also here is a proposed (but commented out) low-level API which would allow you to add a domain from a virDomainPtr. However there are several problems with this API -- see discussion on the list: https://www.redhat.com/archives/libguestfs/2010-November/thread.html#00028
This commit is contained in:
committed by
Richard W.M. Jones
parent
4ada0a7815
commit
f08fe63761
8
TODO
8
TODO
@@ -347,11 +347,3 @@ Eric Sandeen pointed out the blktrace tool which is a better way of
|
||||
capturing traces than using patched qemu (see
|
||||
contrib/visualize-alignment). We would still use the same
|
||||
visualization tools in conjunction with blktrace traces.
|
||||
|
||||
Add-domain command
|
||||
------------------
|
||||
|
||||
guestfs_add_domain (g, "libvirt-dom");
|
||||
|
||||
However this would need to not depend on libvirt, eg. loading it
|
||||
on demand.
|
||||
|
||||
@@ -1058,6 +1058,67 @@ Please read L<guestfs(3)/INSPECTION> for more details.");
|
||||
This returns the internal QEMU command line. 'debug' commands are
|
||||
not part of the formal API and can be removed or changed at any time.");
|
||||
|
||||
("add_domain", (RInt "nrdisks", [String "dom"], [String "libvirturi"; Bool "readonly"; String "iface"]), -1, [FishAlias "domain"],
|
||||
[],
|
||||
"add the disk(s) from a named libvirt domain",
|
||||
"\
|
||||
This function adds the disk(s) attached to the named libvirt
|
||||
domain C<dom>. It works by connecting to libvirt, requesting
|
||||
the domain and domain XML from libvirt, parsing it for disks,
|
||||
and calling C<guestfs_add_drive_opts> on each one.
|
||||
|
||||
The number of disks added is returned. This operation is atomic:
|
||||
if an error is returned, then no disks are added.
|
||||
|
||||
This function does some minimal checks to make sure the libvirt
|
||||
domain is not running (unless C<readonly> is true). In a future
|
||||
version we will try to acquire the libvirt lock on each disk.
|
||||
|
||||
Disks must be accessible locally. This often means that adding disks
|
||||
from a remote libvirt connection (see L<http://libvirt.org/remote.html>)
|
||||
will fail unless those disks are accessible via the same device path
|
||||
locally too.
|
||||
|
||||
The optional C<libvirturi> parameter sets the libvirt URI
|
||||
(see L<http://libvirt.org/uri.html>). If this is not set then
|
||||
we connect to the default libvirt URI (or one set through an
|
||||
environment variable, see the libvirt documentation for full
|
||||
details). If you are using the C API directly then it is more
|
||||
flexible to create the libvirt connection object yourself, get
|
||||
the domain object, and call C<guestfs_add_libvirt_dom>.
|
||||
|
||||
The other optional parameters are passed directly through to
|
||||
C<guestfs_add_drive_opts>.");
|
||||
|
||||
(*
|
||||
This interface is not quite baked yet. -- RWMJ 2010-11-11
|
||||
("add_libvirt_dom", (RInt "nrdisks", [Pointer ("virDomainPtr", "dom")], [Bool "readonly"; String "iface"]), -1, [NotInFish],
|
||||
[],
|
||||
"add the disk(s) from a libvirt domain",
|
||||
"\
|
||||
This function adds the disk(s) attached to the libvirt domain C<dom>.
|
||||
It works by requesting the domain XML from libvirt, parsing it for
|
||||
disks, and calling C<guestfs_add_drive_opts> on each one.
|
||||
|
||||
In the C API we declare C<void *dom>, but really it has type
|
||||
C<virDomainPtr dom>. This is so we don't need E<lt>libvirt.hE<gt>.
|
||||
|
||||
The number of disks added is returned. This operation is atomic:
|
||||
if an error is returned, then no disks are added.
|
||||
|
||||
This function does some minimal checks to make sure the libvirt
|
||||
domain is not running (unless C<readonly> is true). In a future
|
||||
version we will try to acquire the libvirt lock on each disk.
|
||||
|
||||
Disks must be accessible locally. This often means that adding disks
|
||||
from a remote libvirt connection (see L<http://libvirt.org/remote.html>)
|
||||
will fail unless those disks are accessible via the same device path
|
||||
locally too.
|
||||
|
||||
The optional parameters are passed directly through to
|
||||
C<guestfs_add_drive_opts>.");
|
||||
*)
|
||||
|
||||
]
|
||||
|
||||
(* daemon_functions are any functions which cause some action
|
||||
|
||||
@@ -131,6 +131,7 @@ src/inspect.c
|
||||
src/launch.c
|
||||
src/listfs.c
|
||||
src/proto.c
|
||||
src/virt.c
|
||||
test-tool/helper.c
|
||||
test-tool/test-tool.c
|
||||
tools/virt-cat.pl
|
||||
|
||||
@@ -32,6 +32,7 @@ TESTS = \
|
||||
rhbz576879.sh \
|
||||
rhbz578407.sh \
|
||||
rhbz580246.sh \
|
||||
test-add-domain.sh \
|
||||
test-cancellation-download-librarycancels.sh \
|
||||
test-cancellation-upload-daemoncancels.sh \
|
||||
test-copy.sh \
|
||||
|
||||
79
regressions/test-add-domain.sh
Executable file
79
regressions/test-add-domain.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash -
|
||||
# libguestfs
|
||||
# Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
# Test add-domain command.
|
||||
|
||||
set -e
|
||||
|
||||
rm -f test1.img test2.img test3.img test.xml test.out
|
||||
|
||||
cwd="$(pwd)"
|
||||
|
||||
truncate -s 1M test1.img test2.img test3.img
|
||||
|
||||
# Libvirt test XML, see libvirt.git/examples/xml/test/testnode.xml
|
||||
cat > test.xml <<EOF
|
||||
<node>
|
||||
<domain type="test">
|
||||
<name>guest</name>
|
||||
<os>
|
||||
<type>hvm</type>
|
||||
<boot dev='hd'/>
|
||||
</os>
|
||||
<memory>524288</memory>
|
||||
<devices>
|
||||
<disk type="file">
|
||||
<source file="$cwd/test1.img"/>
|
||||
<target dev="hda"/>
|
||||
</disk>
|
||||
<disk type="file">
|
||||
<driver name="qemu" type="raw"/>
|
||||
<source file="$cwd/test2.img"/>
|
||||
<target dev="hdb"/>
|
||||
</disk>
|
||||
<disk type="file">
|
||||
<driver name="qemu" type="qcow2"/>
|
||||
<source file="$cwd/test3.img"/>
|
||||
<target dev="hdc"/>
|
||||
</disk>
|
||||
</devices>
|
||||
</domain>
|
||||
</node>
|
||||
EOF
|
||||
|
||||
../fish/guestfish >test.out <<EOF
|
||||
domain guest libvirturi:test://$cwd/test.xml readonly:true
|
||||
debug-cmdline
|
||||
EOF
|
||||
grep -sq "test1.img.*snapshot=on" test.out
|
||||
! grep -sq "test1.img.*format" test.out
|
||||
grep -sq "test2.img.*snapshot=on.*format=raw" test.out
|
||||
grep -sq "test3.img.*snapshot=on.*format=qcow2" test.out
|
||||
|
||||
# Test atomicity.
|
||||
rm test3.img
|
||||
|
||||
../fish/guestfish >test.out <<EOF
|
||||
-domain guest libvirturi:test://$cwd/test.xml readonly:true
|
||||
debug-cmdline
|
||||
EOF
|
||||
! grep -sq "test1.img" test.out
|
||||
! grep -sq "test2.img" test.out
|
||||
! grep -sq "test3.img" test.out
|
||||
|
||||
rm -f test1.img test2.img test3.img test.xml test.out
|
||||
@@ -133,9 +133,13 @@ libguestfs_la_SOURCES = \
|
||||
launch.c \
|
||||
listfs.c \
|
||||
proto.c \
|
||||
virt.c \
|
||||
libguestfs.syms
|
||||
|
||||
libguestfs_la_LIBADD = $(HIVEX_LIBS) $(AUGEAS_LIBS) $(PCRE_LIBS) $(MAGIC_LIBS) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la
|
||||
libguestfs_la_LIBADD = \
|
||||
$(HIVEX_LIBS) $(AUGEAS_LIBS) $(PCRE_LIBS) $(MAGIC_LIBS) \
|
||||
$(LIBVIRT_LIBS) $(LIBXML2_LIBS) \
|
||||
$(LTLIBTHREAD) ../gnulib/lib/libgnu.la
|
||||
|
||||
# Make libguestfs include the convenience libraries.
|
||||
noinst_LTLIBRARIES = liberrnostring.la libprotocol.la
|
||||
@@ -144,6 +148,7 @@ libguestfs_la_LIBADD += liberrnostring.la libprotocol.la
|
||||
libguestfs_la_CFLAGS = \
|
||||
-DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \
|
||||
$(HIVEX_CFLAGS) $(AUGEAS_CFLAGS) $(PCRE_CFLAGS) \
|
||||
$(LIBVIRT_CFLAGS) $(LIBXML2_CFLAGS) \
|
||||
$(WARN_CFLAGS) $(WERROR_CFLAGS)
|
||||
|
||||
libguestfs_la_CPPFLAGS = -I$(top_srcdir)/gnulib/lib
|
||||
|
||||
319
src/virt.c
Normal file
319
src/virt.c
Normal file
@@ -0,0 +1,319 @@
|
||||
/* libguestfs
|
||||
* Copyright (C) 2010 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 <assert.h>
|
||||
|
||||
#ifdef HAVE_LIBVIRT
|
||||
#include <libvirt/libvirt.h>
|
||||
#include <libvirt/virterror.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBXML2
|
||||
#include <libxml/xpath.h>
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/tree.h>
|
||||
#endif
|
||||
|
||||
#include "guestfs.h"
|
||||
#include "guestfs-internal.h"
|
||||
#include "guestfs-internal-actions.h"
|
||||
#include "guestfs_protocol.h"
|
||||
|
||||
#if defined(HAVE_LIBVIRT) && defined(HAVE_LIBXML2)
|
||||
|
||||
static void init_libxml2 (void) __attribute__((constructor));
|
||||
|
||||
static void
|
||||
init_libxml2 (void)
|
||||
{
|
||||
/* I am told that you don't really need to call virInitialize ... */
|
||||
|
||||
xmlInitParser ();
|
||||
LIBXML_TEST_VERSION;
|
||||
}
|
||||
|
||||
struct guestfs___add_libvirt_dom_argv {
|
||||
uint64_t bitmask;
|
||||
#define GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK (UINT64_C(1)<<0)
|
||||
int readonly;
|
||||
#define GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK (UINT64_C(1)<<1)
|
||||
const char *iface;
|
||||
};
|
||||
|
||||
static int guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, const struct guestfs___add_libvirt_dom_argv *optargs);
|
||||
|
||||
int
|
||||
guestfs__add_domain (guestfs_h *g, const char *domain_name,
|
||||
const struct guestfs_add_domain_argv *optargs)
|
||||
{
|
||||
virErrorPtr err;
|
||||
virConnectPtr conn = NULL;
|
||||
virDomainPtr dom = NULL;
|
||||
int r = -1;
|
||||
const char *libvirturi;
|
||||
int readonly;
|
||||
const char *iface;
|
||||
struct guestfs___add_libvirt_dom_argv optargs2 = { .bitmask = 0 };
|
||||
|
||||
libvirturi = optargs->bitmask & GUESTFS_ADD_DOMAIN_LIBVIRTURI_BITMASK
|
||||
? optargs->libvirturi : NULL;
|
||||
readonly = optargs->bitmask & GUESTFS_ADD_DOMAIN_READONLY_BITMASK
|
||||
? optargs->readonly : 0;
|
||||
iface = optargs->bitmask & GUESTFS_ADD_DOMAIN_IFACE_BITMASK
|
||||
? optargs->iface : NULL;
|
||||
|
||||
/* Connect to libvirt, find the domain. */
|
||||
conn = virConnectOpenReadOnly (libvirturi);
|
||||
if (!conn) {
|
||||
err = virGetLastError ();
|
||||
error (g, _("could not connect to libvirt (code %d, domain %d): %s"),
|
||||
err->code, err->domain, err->message);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
dom = virDomainLookupByName (conn, domain_name);
|
||||
if (!dom) {
|
||||
err = virGetLastError ();
|
||||
error (g, _("no libvirt domain called '%s': %s"),
|
||||
domain_name, err->message);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (readonly) {
|
||||
optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK;
|
||||
optargs2.readonly = readonly;
|
||||
}
|
||||
if (iface) {
|
||||
optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK;
|
||||
optargs2.iface = iface;
|
||||
}
|
||||
|
||||
r = guestfs___add_libvirt_dom (g, dom, &optargs2);
|
||||
|
||||
cleanup:
|
||||
if (dom) virDomainFree (dom);
|
||||
if (conn) virConnectClose (conn);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/* This was proposed as an external API, but it's not quite baked yet. */
|
||||
static int
|
||||
guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
|
||||
const struct guestfs___add_libvirt_dom_argv *optargs)
|
||||
{
|
||||
int r = -1, nr_added = 0, i;
|
||||
virErrorPtr err;
|
||||
xmlDocPtr doc = NULL;
|
||||
xmlXPathContextPtr xpathCtx = NULL;
|
||||
xmlXPathObjectPtr xpathObj = NULL;
|
||||
char *xml = NULL;
|
||||
int readonly;
|
||||
const char *iface;
|
||||
int cmdline_pos;
|
||||
|
||||
cmdline_pos = guestfs___checkpoint_cmdline (g);
|
||||
|
||||
readonly = optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK
|
||||
? optargs->readonly : 0;
|
||||
iface = optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK
|
||||
? optargs->iface : NULL;
|
||||
|
||||
if (!readonly) {
|
||||
virDomainInfo info;
|
||||
if (virDomainGetInfo (dom, &info) == -1) {
|
||||
err = virGetLastError ();
|
||||
error (g, _("error getting domain info: %s"), err->message);
|
||||
goto cleanup;
|
||||
}
|
||||
if (info.state != VIR_DOMAIN_SHUTOFF) {
|
||||
error (g, _("error: domain is a live virtual machine.\nYou must use readonly access because write access to a running virtual machine\ncan cause disk corruption."));
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* Domain XML. */
|
||||
xml = virDomainGetXMLDesc (dom, 0);
|
||||
|
||||
if (!xml) {
|
||||
err = virGetLastError ();
|
||||
error (g, _("error reading libvirt XML information: %s"),
|
||||
err->message);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Now the horrible task of parsing out the fields we need from the XML.
|
||||
* http://www.xmlsoft.org/examples/xpath1.c
|
||||
*/
|
||||
doc = xmlParseMemory (xml, strlen (xml));
|
||||
if (doc == NULL) {
|
||||
error (g, _("unable to parse XML information returned by libvirt"));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
xpathCtx = xmlXPathNewContext (doc);
|
||||
if (xpathCtx == NULL) {
|
||||
error (g, _("unable to create new XPath context"));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* This gives us a set of all the <disk> nodes. */
|
||||
xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk", xpathCtx);
|
||||
if (xpathObj == NULL) {
|
||||
error (g, _("unable to evaluate XPath expression"));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
xmlNodeSetPtr nodes = xpathObj->nodesetval;
|
||||
for (i = 0; i < nodes->nodeNr; ++i) {
|
||||
xmlXPathObjectPtr xptype;
|
||||
|
||||
/* Change the context to the current <disk> node.
|
||||
* DV advises to reset this before each search since older versions of
|
||||
* libxml2 might overwrite it.
|
||||
*/
|
||||
xpathCtx->node = nodes->nodeTab[i];
|
||||
|
||||
/* Filename can be in <source dev=..> or <source file=..> attribute.
|
||||
* Check the <disk type=..> attribute first to find out which one.
|
||||
*/
|
||||
xptype = xmlXPathEvalExpression (BAD_CAST "./@type", xpathCtx);
|
||||
if (xptype == NULL ||
|
||||
xptype->nodesetval == NULL ||
|
||||
xptype->nodesetval->nodeNr == 0) {
|
||||
xmlXPathFreeObject (xptype);
|
||||
continue; /* no type attribute, skip it */
|
||||
}
|
||||
assert (xptype->nodesetval->nodeTab[0]);
|
||||
assert (xptype->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
|
||||
xmlAttrPtr attr = (xmlAttrPtr) xptype->nodesetval->nodeTab[0];
|
||||
char *type = (char *) xmlNodeListGetString (doc, attr->children, 1);
|
||||
|
||||
xmlXPathObjectPtr xpfilename;
|
||||
|
||||
if (STREQ (type, "file")) { /* type = "file" so look at source/@file */
|
||||
free (type);
|
||||
|
||||
xpathCtx->node = nodes->nodeTab[i];
|
||||
xpfilename = xmlXPathEvalExpression (BAD_CAST "./source/@file", xpathCtx);
|
||||
if (xpfilename == NULL ||
|
||||
xpfilename->nodesetval == NULL ||
|
||||
xpfilename->nodesetval->nodeNr == 0) {
|
||||
xmlXPathFreeObject (xpfilename);
|
||||
continue; /* disk filename not found, skip this */
|
||||
}
|
||||
} else if (STREQ (type, "block")) { /* type = "block", use source/@dev */
|
||||
free (type);
|
||||
|
||||
xpathCtx->node = nodes->nodeTab[i];
|
||||
xpfilename = xmlXPathEvalExpression (BAD_CAST "./source/@dev", xpathCtx);
|
||||
if (xpfilename == NULL ||
|
||||
xpfilename->nodesetval == NULL ||
|
||||
xpfilename->nodesetval->nodeNr == 0) {
|
||||
xmlXPathFreeObject (xpfilename);
|
||||
continue; /* disk filename not found, skip this */
|
||||
}
|
||||
} else {
|
||||
free (type);
|
||||
continue; /* type <> "file" or "block", skip it */
|
||||
}
|
||||
|
||||
assert (xpfilename->nodesetval->nodeTab[0]);
|
||||
assert (xpfilename->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
|
||||
attr = (xmlAttrPtr) xpfilename->nodesetval->nodeTab[0];
|
||||
char *filename = (char *) xmlNodeListGetString (doc, attr->children, 1);
|
||||
|
||||
/* Get the disk format (may not be set). */
|
||||
xmlXPathObjectPtr xpformat;
|
||||
|
||||
xpathCtx->node = nodes->nodeTab[i];
|
||||
xpformat = xmlXPathEvalExpression (BAD_CAST "./driver/@type", xpathCtx);
|
||||
char *format = NULL;
|
||||
if (xpformat != NULL &&
|
||||
xpformat->nodesetval &&
|
||||
xpformat->nodesetval->nodeNr > 0) {
|
||||
assert (xpformat->nodesetval->nodeTab[0]);
|
||||
assert (xpformat->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
|
||||
attr = (xmlAttrPtr) xpformat->nodesetval->nodeTab[0];
|
||||
format = (char *) xmlNodeListGetString (doc, attr->children, 1);
|
||||
}
|
||||
|
||||
/* Add the disk, with optional format. */
|
||||
struct guestfs_add_drive_opts_argv optargs2 = { .bitmask = 0 };
|
||||
if (readonly) {
|
||||
optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK;
|
||||
optargs2.readonly = readonly;
|
||||
}
|
||||
if (format) {
|
||||
optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
|
||||
optargs2.format = format;
|
||||
}
|
||||
if (iface) {
|
||||
optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK;
|
||||
optargs2.iface = iface;
|
||||
}
|
||||
|
||||
int t = guestfs__add_drive_opts (g, filename, &optargs2);
|
||||
|
||||
xmlFree (filename);
|
||||
xmlFree (format);
|
||||
xmlXPathFreeObject (xpfilename);
|
||||
xmlXPathFreeObject (xpformat);
|
||||
|
||||
if (t == -1)
|
||||
goto cleanup;
|
||||
|
||||
nr_added++;
|
||||
}
|
||||
|
||||
if (nr_added == 0) {
|
||||
error (g, _("libvirt domain has no disks"));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Successful. */
|
||||
r = nr_added;
|
||||
|
||||
cleanup:
|
||||
if (r == -1) guestfs___rollback_cmdline (g, cmdline_pos);
|
||||
free (xml);
|
||||
if (xpathObj) xmlXPathFreeObject (xpathObj);
|
||||
if (xpathCtx) xmlXPathFreeContext (xpathCtx);
|
||||
if (doc) xmlFreeDoc (doc);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
#else /* no libvirt or libxml2 at compile time */
|
||||
|
||||
#define NOT_IMPL(r) \
|
||||
error (g, _("add-domain API not available since this version of libguestfs was compiled without libvirt or libxml2")); \
|
||||
return r
|
||||
|
||||
int
|
||||
guestfs__add_domain (guestfs_h *g, const char *dom,
|
||||
const struct guestfs_add_domain_argv *optargs)
|
||||
{
|
||||
NOT_IMPL(-1);
|
||||
}
|
||||
|
||||
#endif /* no libvirt or libxml2 at compile time */
|
||||
Reference in New Issue
Block a user