#include <libxml/tree.h>
#endif
+#define GUESTFS_PRIVATE_FOR_EACH_DISK 1
+
#include "guestfs.h"
#include "guestfs-internal.h"
#include "guestfs-internal-actions.h"
LIBXML_TEST_VERSION;
}
+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;
};
static int guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, const struct guestfs___add_libvirt_dom_argv *optargs);
int r = -1;
const char *libvirturi;
int readonly;
+ int live;
+ int allowuuid;
const char *iface;
struct guestfs___add_libvirt_dom_argv optargs2 = { .bitmask = 0 };
? 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;
+
+ if (live && readonly) {
+ error (g, _("you cannot set both live and readonly flags"));
+ return -1;
+ }
/* Connect to libvirt, find the domain. */
conn = virConnectOpenReadOnly (libvirturi);
goto cleanup;
}
- dom = virDomainLookupByName (conn, domain_name);
+ /* 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"),
optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_IFACE_BITMASK;
optargs2.iface = iface;
}
+ if (live) {
+ optargs2.bitmask |= GUESTFS___ADD_LIBVIRT_DOM_LIVE_BITMASK;
+ optargs2.live = live;
+ }
r = guestfs___add_libvirt_dom (g, dom, &optargs2);
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)
+/* 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,
+ void *data),
+ void *data)
{
- int r = -1, nr_added = 0, i;
+ int i, nr_added = 0, r = -1;
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);
assert (xptype->nodesetval->nodeTab[0]);
assert (xptype->nodesetval->nodeTab[0]->type == XML_ATTRIBUTE_NODE);
xmlAttrPtr attr = (xmlAttrPtr) xptype->nodesetval->nodeTab[0];
- xmlXPathFreeObject (xptype);
char *type = (char *) xmlNodeListGetString (doc, attr->children, 1);
+ xmlXPathFreeObject (xptype);
xmlXPathObjectPtr xpfilename;
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);
+ int t;
+ if (f)
+ t = f (g, filename, format, data);
+ else
+ t = 0;
xmlFree (filename);
xmlFree (format);
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;
+}
+
+/* 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, void *optargs_vp);
+static int connect_live (guestfs_h *g, virDomainPtr dom);
+
+static int
+guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
+ const struct guestfs___add_libvirt_dom_argv *optargs)
+{
+ size_t cmdline_pos;
+ int r;
+ int readonly;
+ const char *iface;
+ int live;
+
+ 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 (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 guestfs_add_drive_opts_argv optargs2 = { .bitmask = 0 };
+ if (readonly) {
+ optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK;
+ optargs2.readonly = readonly;
+ }
+ if (iface) {
+ optargs2.bitmask |= GUESTFS_ADD_DRIVE_OPTS_IFACE_BITMASK;
+ optargs2.iface = iface;
+ }
+
+ /* Checkpoint the command line around the operation so that either
+ * all disks are added or none are added.
+ */
+ cmdline_pos = guestfs___checkpoint_cmdline (g);
+ r = guestfs___for_each_disk (g, dom, add_disk, &optargs2);
+ if (r == -1)
+ guestfs___rollback_cmdline (g, cmdline_pos);
+
+ return r;
+}
+
+static int
+add_disk (guestfs_h *g, const char *filename, const char *format,
+ void *optargs_vp)
+{
+ struct guestfs_add_drive_opts_argv *optargs = optargs_vp;
+
+ if (format) {
+ optargs->bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
+ optargs->format = format;
+ } else
+ optargs->bitmask &= ~GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
+
+ 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;
+
+ /* 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 <channel> 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;
+ }
+
+ xmlNodeSetPtr 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 <source path=..> 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 <channel> definition for guestfsd\n"
+ "See ATTACHING TO RUNNING DAEMONS in guestfs(3) for further information."));
+ goto cleanup;
+ }
+
+ /* Got a path. */
+ attach_method = safe_malloc (g, strlen (path) + 5 + 1);
+ strcpy (attach_method, "unix:");
+ strcat (attach_method, 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);