X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=src%2Fvirt.c;h=a95b0290037a4b83f6ecf094ffdf4565323bed4e;hb=99ec97dcdfaf242ba0683a230ba51e4a87f7458b;hp=ab38c36d20ebee7d49fa555478ff8cedbb562564;hpb=dfa9d6cf32b23e34eeba916a9a263483990a3fce;p=libguestfs.git diff --git a/src/virt.c b/src/virt.c index ab38c36..a95b029 100644 --- a/src/virt.c +++ b/src/virt.c @@ -33,6 +33,8 @@ #include #endif +#define GUESTFS_PRIVATE_FOR_EACH_DISK 1 + #include "guestfs.h" #include "guestfs-internal.h" #include "guestfs-internal-actions.h" @@ -51,12 +53,22 @@ init_libxml2 (void) 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; +#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); @@ -71,6 +83,9 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name, 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 }; @@ -80,6 +95,17 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name, ? 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); @@ -90,7 +116,20 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name, 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"), @@ -106,6 +145,14 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name, 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); @@ -116,40 +163,29 @@ guestfs__add_domain (guestfs_h *g, const char *domain_name, 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, + int readonly, + 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); @@ -206,8 +242,8 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, 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; @@ -258,27 +294,28 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, 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; - } + /* Get the flag. */ + xmlXPathObjectPtr xpreadonly; - int t = guestfs__add_drive_opts (g, filename, &optargs2); + 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; @@ -295,7 +332,265 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom, 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, 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) +{ + size_t cmdline_pos; + 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. + */ + struct drive **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; + + /* 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; + } + + 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 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_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);