diff --git a/tools/virsh.c b/tools/virsh.c
index 3c085dd5c7..e78b3f8580 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -14381,6 +14381,221 @@ cmdAttachDisk(vshControl *ctl, const vshCmd *cmd)
     return functionReturn;
 }
 
+typedef enum {
+    VSH_FIND_DISK_NORMAL,
+    VSH_FIND_DISK_CHANGEABLE,
+} vshFindDiskType;
+
+/* Helper function to find disk device in XML doc.  Returns the disk
+ * node on success, or NULL on failure. Caller must free the result
+ * @path: Fully-qualified path or target of disk device.
+ * @type: Either VSH_FIND_DISK_NORMAL or VSH_FIND_DISK_CHANGEABLE.
+ */
+ATTRIBUTE_UNUSED
+static xmlNodePtr
+vshFindDisk(const char *doc,
+            const char *path,
+            int type)
+{
+    xmlDocPtr xml = NULL;
+    xmlXPathObjectPtr obj= NULL;
+    xmlXPathContextPtr ctxt = NULL;
+    xmlNodePtr cur = NULL;
+    xmlNodePtr ret = NULL;
+    int i = 0;
+
+    xml = virXMLParseStringCtxt(doc, _("(domain_definition)"), &ctxt);
+    if (!xml) {
+        vshError(NULL, "%s", _("Failed to get disk information"));
+        goto cleanup;
+    }
+
+    obj = xmlXPathEval(BAD_CAST "/domain/devices/disk", ctxt);
+    if ((obj == NULL) ||
+        (obj->type != XPATH_NODESET) ||
+        (obj->nodesetval == NULL) ||
+        (obj->nodesetval->nodeNr == 0)) {
+        vshError(NULL, "%s", _("Failed to get disk information"));
+        goto cleanup;
+    }
+
+    /* search disk using @path */
+    for (; i < obj->nodesetval->nodeNr; i++) {
+        bool is_supported = true;
+
+        if (type == VSH_FIND_DISK_CHANGEABLE) {
+            xmlNodePtr n = obj->nodesetval->nodeTab[i];
+            is_supported = false;
+
+            /* Check if the disk is CDROM or floppy disk */
+            if (xmlStrEqual(n->name, BAD_CAST "disk")) {
+                char *device_value = virXMLPropString(n, "device");
+
+                if (STREQ(device_value, "cdrom") ||
+                    STREQ(device_value, "floppy"))
+                    is_supported = true;
+
+                VIR_FREE(device_value);
+            }
+
+            if (!is_supported)
+                continue;
+        }
+
+        cur = obj->nodesetval->nodeTab[i]->children;
+        while (cur != NULL) {
+            if (cur->type == XML_ELEMENT_NODE) {
+                char *tmp = NULL;
+
+                if (xmlStrEqual(cur->name, BAD_CAST "source")) {
+                    if ((tmp = virXMLPropString(cur, "file")) ||
+                        (tmp = virXMLPropString(cur, "dev")) ||
+                        (tmp = virXMLPropString(cur, "dir")) ||
+                        (tmp = virXMLPropString(cur, "name"))) {
+                    }
+                } else if (xmlStrEqual(cur->name, BAD_CAST "target")) {
+                    tmp = virXMLPropString(cur, "dev");
+                }
+
+                if (STREQ_NULLABLE(tmp, path)) {
+                    ret = xmlCopyNode(obj->nodesetval->nodeTab[i], 1);
+                    VIR_FREE(tmp);
+                    goto cleanup;
+                }
+                VIR_FREE(tmp);
+            }
+            cur = cur->next;
+        }
+    }
+
+    vshError(NULL, _("No found disk whose source path or target is %s"), path);
+
+cleanup:
+    xmlXPathFreeObject(obj);
+    xmlXPathFreeContext(ctxt);
+    xmlFreeDoc(xml);
+    return ret;
+}
+
+typedef enum {
+    VSH_PREPARE_DISK_XML_NONE = 0,
+    VSH_PREPARE_DISK_XML_EJECT,
+    VSH_PREPARE_DISK_XML_INSERT,
+    VSH_PREPARE_DISK_XML_UPDATE,
+} vshPrepareDiskXMLType;
+
+/* Helper function to prepare disk XML. Could be used for disk
+ * detaching, media changing(ejecting, inserting, updating)
+ * for changeable disk. Returns the processed XML as string on
+ * success, or NULL on failure. Caller must free the result.
+ */
+ATTRIBUTE_UNUSED
+static char *
+vshPrepareDiskXML(xmlNodePtr disk_node,
+                  const char *source,
+                  const char *path,
+                  int type)
+{
+    xmlNodePtr cur = NULL;
+    xmlBufferPtr xml_buf = NULL;
+    const char *disk_type = NULL;
+    const char *device_type = NULL;
+    xmlNodePtr new_node = NULL;
+    char *ret = NULL;
+
+    if (!disk_node)
+        return NULL;
+
+    xml_buf = xmlBufferCreate();
+    if (!xml_buf) {
+        vshError(NULL, "%s", _("Failed to allocate memory"));
+        return NULL;
+    }
+
+    device_type = virXMLPropString(disk_node, "device");
+
+    if (STREQ_NULLABLE(device_type, "cdrom") ||
+        STREQ_NULLABLE(device_type, "floppy")) {
+        bool has_source = false;
+        disk_type = virXMLPropString(disk_node, "type");
+
+        cur = disk_node->children;
+        while (cur != NULL) {
+            if (cur->type == XML_ELEMENT_NODE &&
+                xmlStrEqual(cur->name, BAD_CAST "source")) {
+                has_source = true;
+                break;
+            }
+            cur = cur->next;
+        }
+
+        if (!has_source) {
+            if (type == VSH_PREPARE_DISK_XML_EJECT) {
+                vshError(NULL, _("The disk device '%s' doesn't have media"),
+                         path);
+                goto error;
+            }
+
+            if (source) {
+                new_node = xmlNewNode(NULL, BAD_CAST "source");
+                xmlNewProp(new_node, (const xmlChar *)disk_type,
+                           (const xmlChar *)source);
+                xmlAddChild(disk_node, new_node);
+            } else if (type == VSH_PREPARE_DISK_XML_INSERT) {
+                vshError(NULL, _("No source is specified for inserting media"));
+                goto error;
+            } else if (type == VSH_PREPARE_DISK_XML_UPDATE) {
+                vshError(NULL, _("No source is specified for updating media"));
+                goto error;
+            }
+        }
+
+        if (has_source) {
+            if (type == VSH_PREPARE_DISK_XML_INSERT) {
+                vshError(NULL, _("The disk device '%s' already has media"),
+                         path);
+                goto error;
+            }
+
+            /* Remove the source if it tends to eject/update media. */
+            xmlUnlinkNode(cur);
+            xmlFreeNode(cur);
+
+            if (source && (type == VSH_PREPARE_DISK_XML_UPDATE)) {
+                new_node = xmlNewNode(NULL, BAD_CAST "source");
+                xmlNewProp(new_node, (const xmlChar *)disk_type,
+                           (const xmlChar *)source);
+                xmlAddChild(disk_node, new_node);
+            }
+        }
+    }
+
+    if (xmlNodeDump(xml_buf, NULL, disk_node, 0, 0) < 0) {
+        vshError(NULL, "%s", _("Failed to create XML"));
+        goto error;
+    }
+
+    goto cleanup;
+
+cleanup:
+    VIR_FREE(device_type);
+    VIR_FREE(disk_type);
+    if (xml_buf) {
+        if (VIR_ALLOC_N(ret, xmlBufferLength(xml_buf)) < 0) {
+            virReportOOMError();
+            return NULL;
+        }
+        memcpy(ret, (char *)xmlBufferContent(xml_buf), xmlBufferLength(xml_buf));
+        xmlBufferFree(xml_buf);
+    }
+    return ret;
+
+error:
+    xmlBufferFree(xml_buf);
+    xml_buf = NULL;
+    goto cleanup;
+}
+
 /*
  * "detach-disk" command
  */