From 325d67c615f19da1b679e4d673636cf6d91a4c41 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Sat, 13 Oct 2012 14:06:46 +0100 Subject: lib: Rename src/libvirtdomain.c -> src/libvirt-domain.c. No change, just code motion. --- po/POTFILES | 2 +- src/Makefile.am | 2 +- src/libvirt-domain.c | 605 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/libvirtdomain.c | 605 --------------------------------------------------- 4 files changed, 607 insertions(+), 607 deletions(-) create mode 100644 src/libvirt-domain.c delete mode 100644 src/libvirtdomain.c diff --git a/po/POTFILES b/po/POTFILES index 29fc8b9b..3705e740 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -236,7 +236,7 @@ src/launch-appliance.c src/launch-libvirt.c src/launch-unix.c src/launch.c -src/libvirtdomain.c +src/libvirt-domain.c src/listfs.c src/match.c src/proto.c diff --git a/src/Makefile.am b/src/Makefile.am index 91f1c03f..9b577161 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -142,7 +142,7 @@ libguestfs_la_SOURCES = \ launch-appliance.c \ launch-libvirt.c \ launch-unix.c \ - libvirtdomain.c \ + libvirt-domain.c \ listfs.c \ match.c \ proto.c \ diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c new file mode 100644 index 00000000..eecea260 --- /dev/null +++ b/src/libvirt-domain.c @@ -0,0 +1,605 @@ +/* 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 + +#include +#include +#include + +#ifdef HAVE_LIBVIRT +#include +#include +#endif + +#ifdef HAVE_LIBXML2 +#include +#include +#include +#endif + +#if defined(HAVE_LIBVIRT) && defined(HAVE_LIBXML2) +#define GUESTFS_PRIVATE_FOR_EACH_DISK 1 +#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 +ignore_errors (void *ignore, virErrorPtr ignore2) +{ + /* empty */ +} + +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; +#define GUESTFS___ADD_LIBVIRT_DOM_LIVE_BITMASK (UINT64_C(1)<<2) + int live; +#define GUESTFS___ADD_LIBVIRT_DOM_READONLYDISK_BITMASK (UINT64_C(1)<<3) + const char *readonlydisk; +}; + +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; + int live; + int allowuuid; + const char *readonlydisk; + 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; + live = optargs->bitmask & GUESTFS_ADD_DOMAIN_LIVE_BITMASK + ? optargs->live : 0; + allowuuid = optargs->bitmask & GUESTFS_ADD_DOMAIN_ALLOWUUID_BITMASK + ? optargs->allowuuid : 0; + readonlydisk = optargs->bitmask & GUESTFS_ADD_DOMAIN_READONLYDISK_BITMASK + ? optargs->readonlydisk : NULL; + + if (live && readonly) { + error (g, _("you cannot set both live and readonly flags")); + return -1; + } + + /* 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; + } + + /* 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); + + /* Try UUID first. */ + if (allowuuid) + dom = virDomainLookupByUUIDString (conn, domain_name); + + /* Try ordinary domain name. */ + if (!dom) + 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; + } + if (live) { + optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_LIVE_BITMASK; + optargs2.live = live; + } + if (readonlydisk) { + optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_READONLYDISK_BITMASK; + optargs2.readonlydisk = readonlydisk; + } + + r = guestfs___add_libvirt_dom (g, dom, &optargs2); + + cleanup: + if (dom) virDomainFree (dom); + if (conn) virConnectClose (conn); + + return r; +} + +/* This function is also used in virt-df to avoid having all that + * stupid XPath code repeated. This is something that libvirt should + * really provide. + * + * The callback function 'f' is called once for each disk. + * + * Returns number of disks, or -1 if there was an error. + */ +int +guestfs___for_each_disk (guestfs_h *g, + virDomainPtr dom, + int (*f) (guestfs_h *g, + const char *filename, const char *format, + int readonly, + void *data), + void *data) +{ + int i, nr_added = 0, r = -1; + virErrorPtr err; + xmlDocPtr doc = NULL; + xmlXPathContextPtr xpathCtx = NULL; + xmlXPathObjectPtr xpathObj = NULL; + char *xml = NULL; + xmlNodeSetPtr nodes; + + /* 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 nodes. */ + xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk", xpathCtx); + if (xpathObj == NULL) { + error (g, _("unable to evaluate XPath expression")); + goto cleanup; + } + + nodes = xpathObj->nodesetval; + for (i = 0; i < nodes->nodeNr; ++i) { + xmlXPathObjectPtr xptype; + + /* Change the context to the current 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 or attribute. + * Check the 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); + xmlXPathFreeObject (xptype); + + 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); + } + + /* Get the flag. */ + xmlXPathObjectPtr xpreadonly; + + xpathCtx->node = nodes->nodeTab[i]; + xpreadonly = xmlXPathEvalExpression (BAD_CAST "./readonly", xpathCtx); + int readonly = 0; + if (xpreadonly != NULL && + xpreadonly->nodesetval && + xpreadonly->nodesetval->nodeNr > 0) + readonly = 1; + + int t; + if (f) + t = f (g, filename, format, readonly, data); + else + t = 0; + + xmlFree (filename); + xmlFree (format); + xmlXPathFreeObject (xpfilename); + xmlXPathFreeObject (xpformat); + xmlXPathFreeObject (xpreadonly); + + 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: + free (xml); + if (xpathObj) xmlXPathFreeObject (xpathObj); + if (xpathCtx) xmlXPathFreeContext (xpathCtx); + if (doc) xmlFreeDoc (doc); + + return r; +} + +/* This was proposed as an external API, but it's not quite baked yet. */ + +static int add_disk (guestfs_h *g, const char *filename, const char *format, int readonly, void *data); +static int connect_live (guestfs_h *g, virDomainPtr dom); + +enum readonlydisk { + readonlydisk_error, + readonlydisk_read, + readonlydisk_write, + readonlydisk_ignore, +}; + +struct add_disk_data { + int readonly; + enum readonlydisk readonlydisk; + /* Other args to pass through to add_drive_opts. */ + struct guestfs_add_drive_opts_argv optargs; +}; + +static int +guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, + const struct guestfs___add_libvirt_dom_argv *optargs) +{ + int r; + int readonly; + const char *iface; + int live; + /* Default for back-compat reasons: */ + enum readonlydisk readonlydisk = readonlydisk_write; + + readonly = + optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK + ? optargs->readonly : 0; + iface = + optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK + ? optargs->iface : NULL; + live = + optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_LIVE_BITMASK + ? optargs->live : 0; + + if ((optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_READONLYDISK_BITMASK)) { + if (STREQ (optargs->readonlydisk, "error")) + readonlydisk = readonlydisk_error; + else if (STREQ (optargs->readonlydisk, "read")) + readonlydisk = readonlydisk_read; + else if (STREQ (optargs->readonlydisk, "write")) + readonlydisk = readonlydisk_write; + else if (STREQ (optargs->readonlydisk, "ignore")) + readonlydisk = readonlydisk_ignore; + else { + error (g, _("unknown readonlydisk parameter")); + return -1; + } + } + + if (live && readonly) { + error (g, _("you cannot set both live and readonly flags")); + return -1; + } + + if (!readonly) { + virDomainInfo info; + virErrorPtr err; + int vm_running; + + if (virDomainGetInfo (dom, &info) == -1) { + err = virGetLastError (); + error (g, _("error getting domain info: %s"), err->message); + return -1; + } + vm_running = info.state != VIR_DOMAIN_SHUTOFF; + + if (vm_running) { + /* If the caller specified the 'live' flag, then they want us to + * try to connect to guestfsd if the domain is running. Note + * that live readonly connections are not possible. + */ + if (live) + return connect_live (g, dom); + + /* Dangerous to modify the disks of a running VM. */ + error (g, _("error: domain is a live virtual machine.\n" + "Writing to the disks of a running virtual machine can cause disk corruption.\n" + "Either use read-only access, or if the guest is running the guestfsd daemon\n" + "specify live access. In most libguestfs tools these options are --ro or\n" + "--live respectively. Consult the documentation for further information.")); + return -1; + } + } + + /* Add the disks. */ + struct add_disk_data data; + data.optargs.bitmask = 0; + data.readonly = readonly; + data.readonlydisk = readonlydisk; + if (iface) { + data.optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK; + data.optargs.iface = iface; + } + + /* Checkpoint the command line around the operation so that either + * all disks are added or none are added. + */ + size_t cp = guestfs___checkpoint_drives (g); + r = guestfs___for_each_disk (g, dom, add_disk, &data); + if (r == -1) + guestfs___rollback_drives (g, cp); + + return r; +} + +static int +add_disk (guestfs_h *g, + const char *filename, const char *format, int readonly_in_xml, + void *datavp) +{ + struct add_disk_data *data = datavp; + /* Copy whole struct so we can make local changes: */ + struct guestfs_add_drive_opts_argv optargs = data->optargs; + int readonly, error = 0, skip = 0; + + if (readonly_in_xml) { /* appears in the XML */ + if (data->readonly) { /* asked to add disk read-only */ + switch (data->readonlydisk) { + case readonlydisk_error: readonly = 1; break; + case readonlydisk_read: readonly = 1; break; + case readonlydisk_write: readonly = 1; break; + case readonlydisk_ignore: skip = 1; break; + default: abort (); + } + } else { /* asked to add disk for read/write */ + switch (data->readonlydisk) { + case readonlydisk_error: error = 1; break; + case readonlydisk_read: readonly = 1; break; + case readonlydisk_write: readonly = 0; break; + case readonlydisk_ignore: skip = 1; break; + default: abort (); + } + } + } else /* no in XML */ + readonly = data->readonly; + + if (skip) + return 0; + + if (error) { + error (g, _("%s: disk is marked in libvirt XML, and readonlydisk was set to \"error\""), + filename); + return -1; + } + + optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK; + optargs.readonly = readonly; + + if (format) { + optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK; + optargs.format = format; + } + + return guestfs__add_drive_opts (g, filename, &optargs); +} + +static int +connect_live (guestfs_h *g, virDomainPtr dom) +{ + int i, r = -1; + virErrorPtr err; + xmlDocPtr doc = NULL; + xmlXPathContextPtr xpathCtx = NULL; + xmlXPathObjectPtr xpathObj = NULL; + char *xml = NULL; + char *path = NULL; + char *attach_method = NULL; + xmlNodeSetPtr nodes; + + /* Domain XML. */ + xml = virDomainGetXMLDesc (dom, 0); + + if (!xml) { + err = virGetLastError (); + error (g, _("error reading libvirt XML information: %s"), + err->message); + goto cleanup; + } + + /* Parse XML to document. */ + 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 nodes related to the + * guestfsd virtio-serial channel. + */ + xpathObj = xmlXPathEvalExpression (BAD_CAST + "//devices/channel[@type=\"unix\" and " + "./source/@mode=\"bind\" and " + "./source/@path and " + "./target/@type=\"virtio\" and " + "./target/@name=\"org.libguestfs.channel.0\"]", + xpathCtx); + if (xpathObj == NULL) { + error (g, _("unable to evaluate XPath expression")); + goto cleanup; + } + + nodes = xpathObj->nodesetval; + for (i = 0; i < nodes->nodeNr; ++i) { + xmlXPathObjectPtr xppath; + + /* See note in function above. */ + xpathCtx->node = nodes->nodeTab[i]; + + /* The path is in attribute. */ + xppath = xmlXPathEvalExpression (BAD_CAST "./source/@path", xpathCtx); + if (xppath == NULL || + xppath->nodesetval == NULL || + xppath->nodesetval->nodeNr == 0) { + xmlXPathFreeObject (xppath); + continue; /* no type attribute, skip it */ + } + assert (xppath->nodesetval->nodeTab[0]); + assert (xppath->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE); + xmlAttrPtr attr = (xmlAttrPtr) xppath->nodesetval->nodeTab[0]; + path = (char *) xmlNodeListGetString (doc, attr->children, 1); + xmlXPathFreeObject (xppath); + break; + } + + if (path == NULL) { + error (g, _("this guest has no libvirt definition for guestfsd\n" + "See ATTACHING TO RUNNING DAEMONS in guestfs(3) for further information.")); + goto cleanup; + } + + /* Got a path. */ + attach_method = safe_asprintf (g, "unix:%s", path); + r = guestfs_set_attach_method (g, attach_method); + + cleanup: + free (path); + free (attach_method); + 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 */ diff --git a/src/libvirtdomain.c b/src/libvirtdomain.c deleted file mode 100644 index eecea260..00000000 --- a/src/libvirtdomain.c +++ /dev/null @@ -1,605 +0,0 @@ -/* 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 - -#include -#include -#include - -#ifdef HAVE_LIBVIRT -#include -#include -#endif - -#ifdef HAVE_LIBXML2 -#include -#include -#include -#endif - -#if defined(HAVE_LIBVIRT) && defined(HAVE_LIBXML2) -#define GUESTFS_PRIVATE_FOR_EACH_DISK 1 -#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 -ignore_errors (void *ignore, virErrorPtr ignore2) -{ - /* empty */ -} - -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; -#define GUESTFS___ADD_LIBVIRT_DOM_LIVE_BITMASK (UINT64_C(1)<<2) - int live; -#define GUESTFS___ADD_LIBVIRT_DOM_READONLYDISK_BITMASK (UINT64_C(1)<<3) - const char *readonlydisk; -}; - -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; - int live; - int allowuuid; - const char *readonlydisk; - 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; - live = optargs->bitmask & GUESTFS_ADD_DOMAIN_LIVE_BITMASK - ? optargs->live : 0; - allowuuid = optargs->bitmask & GUESTFS_ADD_DOMAIN_ALLOWUUID_BITMASK - ? optargs->allowuuid : 0; - readonlydisk = optargs->bitmask & GUESTFS_ADD_DOMAIN_READONLYDISK_BITMASK - ? optargs->readonlydisk : NULL; - - if (live && readonly) { - error (g, _("you cannot set both live and readonly flags")); - return -1; - } - - /* 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; - } - - /* 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); - - /* Try UUID first. */ - if (allowuuid) - dom = virDomainLookupByUUIDString (conn, domain_name); - - /* Try ordinary domain name. */ - if (!dom) - 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; - } - if (live) { - optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_LIVE_BITMASK; - optargs2.live = live; - } - if (readonlydisk) { - optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_READONLYDISK_BITMASK; - optargs2.readonlydisk = readonlydisk; - } - - r = guestfs___add_libvirt_dom (g, dom, &optargs2); - - cleanup: - if (dom) virDomainFree (dom); - if (conn) virConnectClose (conn); - - return r; -} - -/* This function is also used in virt-df to avoid having all that - * stupid XPath code repeated. This is something that libvirt should - * really provide. - * - * The callback function 'f' is called once for each disk. - * - * Returns number of disks, or -1 if there was an error. - */ -int -guestfs___for_each_disk (guestfs_h *g, - virDomainPtr dom, - int (*f) (guestfs_h *g, - const char *filename, const char *format, - int readonly, - void *data), - void *data) -{ - int i, nr_added = 0, r = -1; - virErrorPtr err; - xmlDocPtr doc = NULL; - xmlXPathContextPtr xpathCtx = NULL; - xmlXPathObjectPtr xpathObj = NULL; - char *xml = NULL; - xmlNodeSetPtr nodes; - - /* 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 nodes. */ - xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk", xpathCtx); - if (xpathObj == NULL) { - error (g, _("unable to evaluate XPath expression")); - goto cleanup; - } - - nodes = xpathObj->nodesetval; - for (i = 0; i < nodes->nodeNr; ++i) { - xmlXPathObjectPtr xptype; - - /* Change the context to the current 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 or attribute. - * Check the 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); - xmlXPathFreeObject (xptype); - - 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); - } - - /* Get the flag. */ - xmlXPathObjectPtr xpreadonly; - - xpathCtx->node = nodes->nodeTab[i]; - xpreadonly = xmlXPathEvalExpression (BAD_CAST "./readonly", xpathCtx); - int readonly = 0; - if (xpreadonly != NULL && - xpreadonly->nodesetval && - xpreadonly->nodesetval->nodeNr > 0) - readonly = 1; - - int t; - if (f) - t = f (g, filename, format, readonly, data); - else - t = 0; - - xmlFree (filename); - xmlFree (format); - xmlXPathFreeObject (xpfilename); - xmlXPathFreeObject (xpformat); - xmlXPathFreeObject (xpreadonly); - - 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: - free (xml); - if (xpathObj) xmlXPathFreeObject (xpathObj); - if (xpathCtx) xmlXPathFreeContext (xpathCtx); - if (doc) xmlFreeDoc (doc); - - return r; -} - -/* This was proposed as an external API, but it's not quite baked yet. */ - -static int add_disk (guestfs_h *g, const char *filename, const char *format, int readonly, void *data); -static int connect_live (guestfs_h *g, virDomainPtr dom); - -enum readonlydisk { - readonlydisk_error, - readonlydisk_read, - readonlydisk_write, - readonlydisk_ignore, -}; - -struct add_disk_data { - int readonly; - enum readonlydisk readonlydisk; - /* Other args to pass through to add_drive_opts. */ - struct guestfs_add_drive_opts_argv optargs; -}; - -static int -guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, - const struct guestfs___add_libvirt_dom_argv *optargs) -{ - int r; - int readonly; - const char *iface; - int live; - /* Default for back-compat reasons: */ - enum readonlydisk readonlydisk = readonlydisk_write; - - readonly = - optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_READONLY_BITMASK - ? optargs->readonly : 0; - iface = - optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK - ? optargs->iface : NULL; - live = - optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_LIVE_BITMASK - ? optargs->live : 0; - - if ((optargs->bitmask & GUESTFS___ADD_LIBVIRT_DOM_READONLYDISK_BITMASK)) { - if (STREQ (optargs->readonlydisk, "error")) - readonlydisk = readonlydisk_error; - else if (STREQ (optargs->readonlydisk, "read")) - readonlydisk = readonlydisk_read; - else if (STREQ (optargs->readonlydisk, "write")) - readonlydisk = readonlydisk_write; - else if (STREQ (optargs->readonlydisk, "ignore")) - readonlydisk = readonlydisk_ignore; - else { - error (g, _("unknown readonlydisk parameter")); - return -1; - } - } - - if (live && readonly) { - error (g, _("you cannot set both live and readonly flags")); - return -1; - } - - if (!readonly) { - virDomainInfo info; - virErrorPtr err; - int vm_running; - - if (virDomainGetInfo (dom, &info) == -1) { - err = virGetLastError (); - error (g, _("error getting domain info: %s"), err->message); - return -1; - } - vm_running = info.state != VIR_DOMAIN_SHUTOFF; - - if (vm_running) { - /* If the caller specified the 'live' flag, then they want us to - * try to connect to guestfsd if the domain is running. Note - * that live readonly connections are not possible. - */ - if (live) - return connect_live (g, dom); - - /* Dangerous to modify the disks of a running VM. */ - error (g, _("error: domain is a live virtual machine.\n" - "Writing to the disks of a running virtual machine can cause disk corruption.\n" - "Either use read-only access, or if the guest is running the guestfsd daemon\n" - "specify live access. In most libguestfs tools these options are --ro or\n" - "--live respectively. Consult the documentation for further information.")); - return -1; - } - } - - /* Add the disks. */ - struct add_disk_data data; - data.optargs.bitmask = 0; - data.readonly = readonly; - data.readonlydisk = readonlydisk; - if (iface) { - data.optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK; - data.optargs.iface = iface; - } - - /* Checkpoint the command line around the operation so that either - * all disks are added or none are added. - */ - size_t cp = guestfs___checkpoint_drives (g); - r = guestfs___for_each_disk (g, dom, add_disk, &data); - if (r == -1) - guestfs___rollback_drives (g, cp); - - return r; -} - -static int -add_disk (guestfs_h *g, - const char *filename, const char *format, int readonly_in_xml, - void *datavp) -{ - struct add_disk_data *data = datavp; - /* Copy whole struct so we can make local changes: */ - struct guestfs_add_drive_opts_argv optargs = data->optargs; - int readonly, error = 0, skip = 0; - - if (readonly_in_xml) { /* appears in the XML */ - if (data->readonly) { /* asked to add disk read-only */ - switch (data->readonlydisk) { - case readonlydisk_error: readonly = 1; break; - case readonlydisk_read: readonly = 1; break; - case readonlydisk_write: readonly = 1; break; - case readonlydisk_ignore: skip = 1; break; - default: abort (); - } - } else { /* asked to add disk for read/write */ - switch (data->readonlydisk) { - case readonlydisk_error: error = 1; break; - case readonlydisk_read: readonly = 1; break; - case readonlydisk_write: readonly = 0; break; - case readonlydisk_ignore: skip = 1; break; - default: abort (); - } - } - } else /* no in XML */ - readonly = data->readonly; - - if (skip) - return 0; - - if (error) { - error (g, _("%s: disk is marked in libvirt XML, and readonlydisk was set to \"error\""), - filename); - return -1; - } - - optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK; - optargs.readonly = readonly; - - if (format) { - optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK; - optargs.format = format; - } - - return guestfs__add_drive_opts (g, filename, &optargs); -} - -static int -connect_live (guestfs_h *g, virDomainPtr dom) -{ - int i, r = -1; - virErrorPtr err; - xmlDocPtr doc = NULL; - xmlXPathContextPtr xpathCtx = NULL; - xmlXPathObjectPtr xpathObj = NULL; - char *xml = NULL; - char *path = NULL; - char *attach_method = NULL; - xmlNodeSetPtr nodes; - - /* Domain XML. */ - xml = virDomainGetXMLDesc (dom, 0); - - if (!xml) { - err = virGetLastError (); - error (g, _("error reading libvirt XML information: %s"), - err->message); - goto cleanup; - } - - /* Parse XML to document. */ - 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 nodes related to the - * guestfsd virtio-serial channel. - */ - xpathObj = xmlXPathEvalExpression (BAD_CAST - "//devices/channel[@type=\"unix\" and " - "./source/@mode=\"bind\" and " - "./source/@path and " - "./target/@type=\"virtio\" and " - "./target/@name=\"org.libguestfs.channel.0\"]", - xpathCtx); - if (xpathObj == NULL) { - error (g, _("unable to evaluate XPath expression")); - goto cleanup; - } - - nodes = xpathObj->nodesetval; - for (i = 0; i < nodes->nodeNr; ++i) { - xmlXPathObjectPtr xppath; - - /* See note in function above. */ - xpathCtx->node = nodes->nodeTab[i]; - - /* The path is in attribute. */ - xppath = xmlXPathEvalExpression (BAD_CAST "./source/@path", xpathCtx); - if (xppath == NULL || - xppath->nodesetval == NULL || - xppath->nodesetval->nodeNr == 0) { - xmlXPathFreeObject (xppath); - continue; /* no type attribute, skip it */ - } - assert (xppath->nodesetval->nodeTab[0]); - assert (xppath->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE); - xmlAttrPtr attr = (xmlAttrPtr) xppath->nodesetval->nodeTab[0]; - path = (char *) xmlNodeListGetString (doc, attr->children, 1); - xmlXPathFreeObject (xppath); - break; - } - - if (path == NULL) { - error (g, _("this guest has no libvirt definition for guestfsd\n" - "See ATTACHING TO RUNNING DAEMONS in guestfs(3) for further information.")); - goto cleanup; - } - - /* Got a path. */ - attach_method = safe_asprintf (g, "unix:%s", path); - r = guestfs_set_attach_method (g, attach_method); - - cleanup: - free (path); - free (attach_method); - 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 */ -- cgit