/* 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 #include #include #include #ifdef HAVE_LIBVIRT #include #endif #include #include #include #include "base64.h" #include "guestfs.h" #include "guestfs-internal.h" #include "guestfs-internal-actions.h" #if defined(HAVE_LIBVIRT) static xmlDocPtr get_domain_xml (guestfs_h *g, virDomainPtr dom); static ssize_t for_each_disk (guestfs_h *g, virConnectPtr conn, xmlDocPtr doc, int (*f) (guestfs_h *g, const char *filename, const char *format, int readonly, const char *protocol, char *const *server, const char *username, const char *secret, int blocksize, void *data), void *data); static int libvirt_selinux_label (guestfs_h *g, xmlDocPtr doc, char **label_rtn, char **imagelabel_rtn); static char *filename_from_pool (guestfs_h *g, virConnectPtr conn, const char *pool_nane, const char *volume_name); static bool xpath_object_is_empty (xmlXPathObjectPtr obj); static char *xpath_object_get_string (xmlDocPtr doc, xmlXPathObjectPtr obj); static int xpath_object_get_int (xmlDocPtr doc, xmlXPathObjectPtr obj); static void ignore_errors (void *ignore, virErrorPtr ignore2) { /* empty */ } int guestfs_impl_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 *cachemode; const char *discard; bool copyonread; 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; 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; cachemode = optargs->bitmask & GUESTFS_ADD_DOMAIN_CACHEMODE_BITMASK ? optargs->cachemode : NULL; discard = optargs->bitmask & GUESTFS_ADD_DOMAIN_DISCARD_BITMASK ? optargs->discard : NULL; copyonread = optargs->bitmask & GUESTFS_ADD_DOMAIN_COPYONREAD_BITMASK ? optargs->copyonread : false; if (live) { error (g, _("libguestfs live support was removed in libguestfs 1.48")); return -1; } /* Connect to libvirt, find the domain. We cannot open the connection * in read-only mode (VIR_CONNECT_RO), as that kind of connection * is considered untrusted, and thus libvirt will prevent to read * the values of secrets. */ conn = guestfs_int_open_libvirt_connection (g, libvirturi, 0); 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 (live) { error (g, _("libguestfs live support was removed in libguestfs 1.48")); goto cleanup; } if (readonlydisk) { optargs2.bitmask |= GUESTFS_ADD_LIBVIRT_DOM_READONLYDISK_BITMASK; optargs2.readonlydisk = readonlydisk; } if (cachemode) { optargs2.bitmask |= GUESTFS_ADD_LIBVIRT_DOM_CACHEMODE_BITMASK; optargs2.cachemode = cachemode; } if (discard) { optargs2.bitmask |= GUESTFS_ADD_LIBVIRT_DOM_DISCARD_BITMASK; optargs2.discard = discard; } if (copyonread) { optargs2.bitmask |= GUESTFS_ADD_LIBVIRT_DOM_COPYONREAD_BITMASK; optargs2.copyonread = copyonread; } r = guestfs_add_libvirt_dom_argv (g, dom, &optargs2); cleanup: if (dom) virDomainFree (dom); if (conn) virConnectClose (conn); return r; } static int add_disk (guestfs_h *g, const char *filename, const char *format, int readonly, const char *protocol, char *const *server, const char *username, const char *secret, int blocksize, void *data); 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; }; int guestfs_impl_add_libvirt_dom (guestfs_h *g, void *domvp, const struct guestfs_add_libvirt_dom_argv *optargs) { virDomainPtr dom = domvp; ssize_t r; int readonly; const char *cachemode; const char *discard; bool copyonread; int live; /* Default for back-compat reasons: */ enum readonlydisk readonlydisk = readonlydisk_write; size_t ckp; struct add_disk_data data; CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL; CLEANUP_FREE char *label = NULL, *imagelabel = NULL; readonly = optargs->bitmask & GUESTFS_ADD_LIBVIRT_DOM_READONLY_BITMASK ? optargs->readonly : 0; 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; } } cachemode = optargs->bitmask & GUESTFS_ADD_LIBVIRT_DOM_CACHEMODE_BITMASK ? optargs->cachemode : NULL; discard = optargs->bitmask & GUESTFS_ADD_LIBVIRT_DOM_DISCARD_BITMASK ? optargs->discard : NULL; copyonread = optargs->bitmask & GUESTFS_ADD_LIBVIRT_DOM_COPYONREAD_BITMASK ? optargs->copyonread : false; if (live) { error (g, _("libguestfs live support was removed in libguestfs 1.48")); 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) { /* 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" "Use read-only access. In most libguestfs tools use --ro.")); return -1; } } /* Domain XML. */ if ((doc = get_domain_xml (g, dom)) == NULL) return -1; /* Find and pass the SELinux security label to the libvirt back end. * Note this has to happen before adding the disks, since those may * use the label. */ if (libvirt_selinux_label (g, doc, &label, &imagelabel) == -1) return -1; if (label && imagelabel) { guestfs_set_backend_setting (g, "internal_libvirt_label", label); guestfs_set_backend_setting (g, "internal_libvirt_imagelabel", imagelabel); guestfs_set_backend_setting (g, "internal_libvirt_norelabel_disks", "1"); } else guestfs_clear_backend_setting (g, "internal_libvirt_norelabel_disks"); /* Add the disks. */ data.optargs.bitmask = 0; data.readonly = readonly; data.readonlydisk = readonlydisk; if (cachemode) { data.optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_CACHEMODE_BITMASK; data.optargs.cachemode = cachemode; } if (discard) { data.optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_DISCARD_BITMASK; data.optargs.discard = discard; } if (copyonread) { data.optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_COPYONREAD_BITMASK; data.optargs.copyonread = copyonread; } /* Checkpoint the command line around the operation so that either * all disks are added or none are added. */ ckp = guestfs_int_checkpoint_drives (g); r = for_each_disk (g, virDomainGetConnect (dom), doc, add_disk, &data); if (r == -1) guestfs_int_rollback_drives (g, ckp); return r; } static int add_disk (guestfs_h *g, const char *filename, const char *format, int readonly_in_xml, const char *protocol, char *const *server, const char *username, const char *secret, int blocksize, 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 = -1, 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; } } 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; } } } 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; } if (readonly == -1) abort (); optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK; optargs.readonly = readonly; if (format) { optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK; optargs.format = format; } if (protocol) { optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_PROTOCOL_BITMASK; optargs.protocol = protocol; } if (server) { optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_SERVER_BITMASK; optargs.server = server; } if (username) { optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_USERNAME_BITMASK; optargs.username = username; } if (secret) { optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_SECRET_BITMASK; optargs.secret = secret; } if (blocksize) { optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_BLOCKSIZE_BITMASK; optargs.blocksize = blocksize; } return guestfs_add_drive_opts_argv (g, filename, &optargs); } /* Find the element in the libvirt XML, and if it exists * get the SELinux process label and image label from it. * * The reason for all this is because of sVirt: * https://bugzilla.redhat.com/show_bug.cgi?id=912499#c7 */ static int libvirt_selinux_label (guestfs_h *g, xmlDocPtr doc, char **label_rtn, char **imagelabel_rtn) { CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL; CLEANUP_XMLXPATHFREEOBJECT xmlXPathObjectPtr xpathObj = NULL; xmlNodeSetPtr nodes; size_t nr_nodes; xmlNodePtr node, child; bool gotlabel = 0, gotimagelabel = 0; xpathCtx = xmlXPathNewContext (doc); if (xpathCtx == NULL) { error (g, _("unable to create new XPath context")); return -1; } /* Typical seclabel element looks like this: * * * * * system_u:object_r:svirt_image_t:s0:c24,c151 * * * This code restricts our search to model=selinux labels (since in * theory at least multiple seclabel elements might be present). */ const char *xpath_expr = "/domain/seclabel[@model='selinux']"; xpathObj = xmlXPathEvalExpression (BAD_CAST xpath_expr, xpathCtx); if (xpathObj == NULL) { error (g, _("unable to evaluate XPath expression")); return -1; } nodes = xpathObj->nodesetval; if (nodes == NULL) return 0; nr_nodes = nodes->nodeNr; if (nr_nodes == 0) return 0; if (nr_nodes > 1) { debug (g, "ignoring %zu nodes matching '%s'", nr_nodes, xpath_expr); return 0; } node = nodes->nodeTab[0]; if (node->type != XML_ELEMENT_NODE) { error (g, _("expected to be an XML element")); return -1; } /* Find the