Signed-off-by: Michal Privoznik <mprivozn(a)redhat.com>
---
tools/virsh-domain.c | 155 +++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 151 insertions(+), 4 deletions(-)
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c
index 5075d0b..6a60a40 100644
--- a/tools/virsh-domain.c
+++ b/tools/virsh-domain.c
@@ -9357,7 +9357,6 @@ error:
* @n2 second node
* returns true in case n1 covers n2, false otherwise.
*/
-ATTRIBUTE_UNUSED
static bool
vshNodeIsSuperset(xmlNodePtr n1, xmlNodePtr n2)
{
@@ -9465,6 +9464,139 @@ cleanup:
return ret;
}
+/**
+ * vshCompleteXMLFromDomain:
+ * @ctl vshControl for error messages printing
+ * @dom domain
+ * @oldXML device XML before
+ * @newXML and after completion
+ *
+ * For given domain and (probably incomplete) device XML specification try to
+ * find such device in domain and complete missing parts. This is however
+ * possible only when given device XML is sufficiently precise so it addresses
+ * only one device.
+ *
+ * Returns -2 when no such device exists in domain, -3 when given XML selects many
+ * (is too ambiguous), 0 in case of success. Otherwise returns -1. @newXML
+ * is touched only in case of success.
+ */
+static int
+vshCompleteXMLFromDomain(vshControl *ctl, virDomainPtr dom,
+ const char *oldXML, char **newXML)
+{
+ int funcRet = -1;
+ char *domXML = NULL, *normalizedXML = NULL;
+ xmlDocPtr domDoc = NULL, devDoc = NULL;
+ xmlNodePtr node = NULL;
+ xmlXPathContextPtr domCtxt = NULL, devCtxt = NULL;
+ xmlNodePtr *devices = NULL;
+ xmlSaveCtxtPtr sctxt = NULL;
+ int devices_size;
+ char *xpath = NULL;
+ xmlBufferPtr buf = NULL;
+ size_t i = 0;
+ ssize_t indx = -1;
+
+ if (!(domXML = virDomainGetXMLDesc(dom, 0))) {
+ vshError(ctl, _("couldn't get XML description of domain %s"),
+ virDomainGetName(dom));
+ goto cleanup;
+ }
+
+ domDoc = virXMLParseStringCtxt(domXML, _("(domain_definition)"),
&domCtxt);
+ if (!domDoc) {
+ vshError(ctl, _("Failed to parse domain definition xml"));
+ goto cleanup;
+ }
+
+ /* Prior parsing oldXML try to normalize it */
+ if (virDomainNormalizeXML(dom, oldXML, &normalizedXML, 0) >= 0) {
+ /* Yay, success! Deliberately not freeing oldXML here, as it's caller
+ * who is supposed to do so. */
+ oldXML = normalizedXML;
+ }
+
+ devDoc = virXMLParseStringCtxt(oldXML, _("(device_definition)"),
&devCtxt);
+ if (!devDoc) {
+ vshError(ctl, _("Failed to parse device definition xml"));
+ goto cleanup;
+ }
+
+ node = xmlDocGetRootElement(devDoc);
+
+ buf = xmlBufferCreate();
+ if (!buf) {
+ vshError(ctl, "%s", _("out of memory"));
+ goto cleanup;
+ }
+
+ /* Get all possible devices */
+ if (virAsprintf(&xpath, "/domain/devices/%s", node->name) < 0)
+ goto cleanup;
+
+ devices_size = virXPathNodeSet(xpath, domCtxt, &devices);
+
+ if (devices_size < 0) {
+ /* error */
+ vshError(ctl, "%s", _("error when selecting nodes"));
+ goto cleanup;
+ } else if (devices_size == 0) {
+ /* no such device */
+ funcRet = -2;
+ goto cleanup;
+ }
+
+ /* and refine */
+ for (i = 0; i < devices_size; i++) {
+ if (vshNodeIsSuperset(devices[i], node)) {
+ if (indx >= 0) {
+ funcRet = -3; /* ambiguous */
+ goto cleanup;
+ }
+ indx = i;
+ }
+ }
+
+ if (indx < 0) {
+ funcRet = -2; /* no such device */
+ goto cleanup;
+ }
+
+ vshDebug(ctl, VSH_ERR_DEBUG, "Found device at pos %zd\n", indx);
+
+ if (newXML) {
+ sctxt = xmlSaveToBuffer(buf, NULL, 0);
+ if (!sctxt) {
+ vshError(ctl, "%s", _("failed to create document saving
context"));
+ goto cleanup;
+ }
+
+ xmlSaveTree(sctxt, devices[indx]);
+ xmlSaveClose(sctxt);
+ *newXML = (char *) xmlBufferContent(buf);
+ if (!*newXML) {
+ virReportOOMError();
+ goto cleanup;
+ }
+ buf->content = NULL;
+ }
+
+ vshDebug(ctl, VSH_ERR_DEBUG, "Old xml:\n%s\nNew xml:\n%s\n", oldXML,
+ newXML ? NULLSTR(*newXML) : "(null)");
+
+ funcRet = 0;
+
+cleanup:
+ xmlBufferFree(buf);
+ VIR_FREE(devices);
+ xmlXPathFreeContext(devCtxt);
+ xmlXPathFreeContext(domCtxt);
+ xmlFreeDoc(devDoc);
+ xmlFreeDoc(domDoc);
+ VIR_FREE(domXML);
+ VIR_FREE(xpath);
+ return funcRet;
+}
/*
* "normalize" command
@@ -9602,7 +9734,7 @@ cmdDetachDevice(vshControl *ctl, const vshCmd *cmd)
{
virDomainPtr dom = NULL;
const char *from = NULL;
- char *buffer = NULL;
+ char *buffer = NULL, *new_buffer = NULL;
int ret;
bool funcRet = false;
bool current = vshCommandOptBool(cmd, "current");
@@ -9636,10 +9768,24 @@ cmdDetachDevice(vshControl *ctl, const vshCmd *cmd)
goto cleanup;
}
+ ret = vshCompleteXMLFromDomain(ctl, dom, buffer, &new_buffer);
+ if (ret < 0) {
+ if (ret == -2) {
+ vshError(ctl, _("no such device in %s"), virDomainGetName(dom));
+ } else if (ret == -3) {
+ vshError(ctl, "%s", _("given XML selects too many devices.
"
+ "Please, be more specific"));
+ } else {
+ /* vshCompleteXMLFromDomain() already printed error message,
+ * so nothing to do here. */
+ }
+ goto cleanup;
+ }
+
if (flags != 0)
- ret = virDomainDetachDeviceFlags(dom, buffer, flags);
+ ret = virDomainDetachDeviceFlags(dom, new_buffer, flags);
else
- ret = virDomainDetachDevice(dom, buffer);
+ ret = virDomainDetachDevice(dom, new_buffer);
if (ret < 0) {
vshError(ctl, _("Failed to detach device from %s"), from);
@@ -9651,6 +9797,7 @@ cmdDetachDevice(vshControl *ctl, const vshCmd *cmd)
cleanup:
VIR_FREE(buffer);
+ VIR_FREE(new_buffer);
virDomainFree(dom);
return funcRet;
}
--
1.8.1.5