virDomainDiskDefParseXML(virDomainXMLOptionPtr xmlopt,
                         xmlNodePtr node,
                         xmlXPathContextPtr ctxt,
                         unsigned int flags)
{
    virDomainDiskDefPtr def;
    xmlNodePtr cur;
    VIR_XPATH_NODE_AUTORESTORE(ctxt);
    bool source = false;
    virStorageEncryptionPtr encryption = NULL;
    g_autoptr(virStorageAuthDef) authdef = NULL;
    g_autofree char *tmp = NULL;
    g_autofree char *snapshot = NULL;
    g_autofree char *rawio = NULL;
    g_autofree char *sgio = NULL;
    g_autofree char *target = NULL;
    g_autofree char *bus = NULL;
    g_autofree char *serial = NULL;
    g_autofree char *startupPolicy = NULL;
    g_autofree char *tray = NULL;
    g_autofree char *removable = NULL;
    g_autofree char *logical_block_size = NULL;
    g_autofree char *physical_block_size = NULL;
    g_autofree char *wwn = NULL;
    g_autofree char *vendor = NULL;
    g_autofree char *product = NULL;
    g_autofree char *domain_name = NULL;

    if (!(def = virDomainDiskDefNew(xmlopt)))
        return NULL;

    ctxt->node = node;

    /* defaults */
    def->src->type = VIR_STORAGE_TYPE_FILE;
    def->device = VIR_DOMAIN_DISK_DEVICE_DISK;

    if ((tmp = virXMLPropString(node, "type")) &&
        (def->src->type = virStorageTypeFromString(tmp)) <= 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("unknown disk type '%s'"), tmp);
        goto error;
    }
    VIR_FREE(tmp);

    if ((tmp = virXMLPropString(node, "device")) &&
        (def->device = virDomainDiskDeviceTypeFromString(tmp)) < 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("unknown disk device '%s'"), tmp);
        goto error;
    }
    VIR_FREE(tmp);

    if ((tmp = virXMLPropString(node, "model")) &&
        (def->model = virDomainDiskModelTypeFromString(tmp)) < 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("unknown disk model '%s'"), tmp);
        goto error;
    }
    VIR_FREE(tmp);

    snapshot = virXMLPropString(node, "snapshot");

    rawio = virXMLPropString(node, "rawio");
    sgio = virXMLPropString(node, "sgio");

    for (cur = node->children; cur != NULL; cur = cur->next) {
        if (cur->type != XML_ELEMENT_NODE)
            continue;

        if (!source && virXMLNodeNameEqual(cur, "source")) {
            if (virDomainStorageSourceParse(cur, ctxt, def->src, flags, xmlopt) < 0)
                goto error;

            /* If we've already found an <auth> as a child of <disk> and
             * we find one as a child of <source>, then force an error to
             * avoid ambiguity */
            if (authdef && def->src->auth) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                               _("an <auth> definition already found for "
                                 "the <disk> definition"));
                goto error;
            }

            if (def->src->auth)
                def->src->authInherited = true;

            /* Similarly for <encryption> - it's a child of <source> too
             * and we cannot find in both places */
            if (encryption && def->src->encryption) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                               _("an <encryption> definition already found for "
                                 "the <disk> definition"));
                goto error;
            }

            if (def->src->encryption)
                def->src->encryptionInherited = true;

            source = true;

            startupPolicy = virXMLPropString(cur, "startupPolicy");

            if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE) &&
                (tmp = virXMLPropString(cur, "index")) &&
                virStrToLong_uip(tmp, NULL, 10, &def->src->id) < 0) {
                virReportError(VIR_ERR_XML_ERROR, _("invalid disk index '%s'"), tmp);
                goto error;
            }
            VIR_FREE(tmp);
        } else if (!target &&
                   virXMLNodeNameEqual(cur, "target")) {
            target = virXMLPropString(cur, "dev");
            bus = virXMLPropString(cur, "bus");
            tray = virXMLPropString(cur, "tray");
            removable = virXMLPropString(cur, "removable");

            /* HACK: Work around for compat with Xen
             * driver in previous libvirt releases */
            if (target &&
                STRPREFIX(target, "ioemu:"))
                memmove(target, target+6, strlen(target)-5);
        } else if (!domain_name &&
                   virXMLNodeNameEqual(cur, "backenddomain")) {
            domain_name = virXMLPropString(cur, "name");
        } else if (virXMLNodeNameEqual(cur, "geometry")) {
            if (virDomainDiskDefGeometryParse(def, cur) < 0)
                goto error;
        } else if (virXMLNodeNameEqual(cur, "blockio")) {
            logical_block_size =
                virXMLPropString(cur, "logical_block_size");
            if (logical_block_size &&
                virStrToLong_ui(logical_block_size, NULL, 0,
                                &def->blockio.logical_block_size) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("invalid logical block size '%s'"),
                               logical_block_size);
                goto error;
            }
            physical_block_size =
                virXMLPropString(cur, "physical_block_size");
            if (physical_block_size &&
                virStrToLong_ui(physical_block_size, NULL, 0,
                                &def->blockio.physical_block_size) < 0) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("invalid physical block size '%s'"),
                               physical_block_size);
                goto error;
            }
        } else if (!virDomainDiskGetDriver(def) &&
                   virXMLNodeNameEqual(cur, "driver")) {
            if (virDomainVirtioOptionsParseXML(cur, &def->virtio) < 0)
                goto error;

            if (virDomainDiskDefDriverParseXML(def, cur) < 0)
                goto error;
        } else if (!def->mirror &&
                   virXMLNodeNameEqual(cur, "mirror") &&
                   !(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)) {
            if (virDomainDiskDefMirrorParse(def, cur, ctxt, flags, xmlopt) < 0)
                goto error;
        } else if (!authdef &&
                   virXMLNodeNameEqual(cur, "auth")) {
            /* If we've already parsed <source> and found an <auth> child,
             * then generate an error to avoid ambiguity */
            if (def->src->authInherited) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                               _("an <auth> definition already found for "
                                 "disk source"));
                goto error;
            }

            if (!(authdef = virStorageAuthDefParse(cur, ctxt)))
                goto error;
        } else if (virXMLNodeNameEqual(cur, "iotune")) {
            if (virDomainDiskDefIotuneParse(def, ctxt) < 0)
                goto error;
        } else if (virXMLNodeNameEqual(cur, "readonly")) {
            def->src->readonly = true;
        } else if (virXMLNodeNameEqual(cur, "shareable")) {
            def->src->shared = true;
        } else if (virXMLNodeNameEqual(cur, "transient")) {
            def->transient = true;
        } else if (!encryption &&
                   virXMLNodeNameEqual(cur, "encryption")) {
            /* If we've already parsed <source> and found an <encryption> child,
             * then generate an error to avoid ambiguity */
            if (def->src->encryptionInherited) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                               _("an <encryption> definition already found for "
                                 "disk source"));
                goto error;
            }

            if (!(encryption = virStorageEncryptionParseNode(cur, ctxt)))
                goto error;
        } else if (!serial &&
                   virXMLNodeNameEqual(cur, "serial")) {
            serial = (char *)xmlNodeGetContent(cur);
        } else if (!wwn &&
                   virXMLNodeNameEqual(cur, "wwn")) {
            wwn = (char *)xmlNodeGetContent(cur);

            if (!virValidateWWN(wwn))
                goto error;
        } else if (!vendor &&
                   virXMLNodeNameEqual(cur, "vendor")) {
            vendor = (char *)xmlNodeGetContent(cur);

            if (strlen(vendor) > VENDOR_LEN) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                               _("disk vendor is more than 8 characters"));
                goto error;
            }

            if (!virStringIsPrintable(vendor)) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                               _("disk vendor is not printable string"));
                goto error;
            }
        } else if (!product &&
                   virXMLNodeNameEqual(cur, "product")) {
            product = (char *)xmlNodeGetContent(cur);

            if (strlen(product) > PRODUCT_LEN) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                               _("disk product is more than 16 characters"));
                goto error;
            }

            if (!virStringIsPrintable(product)) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                               _("disk product is not printable string"));
                goto error;
            }
        } else if (virXMLNodeNameEqual(cur, "boot")) {
            /* boot is parsed as part of virDomainDeviceInfoParseXML */
        }
    }

    /* Reset def->src->type in case when 'source' was not present */
    if (!source)
        def->src->type = VIR_STORAGE_TYPE_FILE;

    /* Only CDROM and Floppy devices are allowed missing source path
     * to indicate no media present. LUN is for raw access CD-ROMs
     * that are not attached to a physical device presently */
    if (virStorageSourceIsEmpty(def->src) &&
        (def->device == VIR_DOMAIN_DISK_DEVICE_DISK ||
         (flags & VIR_DOMAIN_DEF_PARSE_DISK_SOURCE))) {
        virReportError(VIR_ERR_NO_SOURCE,
                       target ? "%s" : NULL, target);
        goto error;
    }

    if (!target && !(flags & VIR_DOMAIN_DEF_PARSE_DISK_SOURCE)) {
        if (def->src->srcpool) {
            tmp = g_strdup_printf("pool = '%s', volume = '%s'",
                                  def->src->srcpool->pool, def->src->srcpool->volume);

            virReportError(VIR_ERR_NO_TARGET, "%s", tmp);
            VIR_FREE(tmp);
        } else {
            virReportError(VIR_ERR_NO_TARGET, def->src->path ? "%s" : NULL, def->src->path);
        }
        goto error;
    }

    if (!(flags & VIR_DOMAIN_DEF_PARSE_DISK_SOURCE)) {
        if (def->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY &&
            !STRPREFIX(target, "fd")) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Invalid floppy device name: %s"), target);
            goto error;
        }

        /* Force CDROM to be listed as read only */
        if (def->device == VIR_DOMAIN_DISK_DEVICE_CDROM)
            def->src->readonly = true;

        if ((def->device == VIR_DOMAIN_DISK_DEVICE_DISK ||
             def->device == VIR_DOMAIN_DISK_DEVICE_LUN) &&
            !STRPREFIX((const char *)target, "hd") &&
            !STRPREFIX((const char *)target, "sd") &&
            !STRPREFIX((const char *)target, "vd") &&
            !STRPREFIX((const char *)target, "xvd") &&
            !STRPREFIX((const char *)target, "ubd")) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("Invalid harddisk device name: %s"), target);
            goto error;
        }
    }

    if (snapshot) {
        def->snapshot = virDomainSnapshotLocationTypeFromString(snapshot);
        if (def->snapshot <= 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown disk snapshot setting '%s'"),
                           snapshot);
            goto error;
        }
    } else if (def->src->readonly) {
        def->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_NONE;
    }

    if (rawio) {
        if ((def->rawio = virTristateBoolTypeFromString(rawio)) <= 0) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("unknown disk rawio setting '%s'"),
                           rawio);
            goto error;
        }
    }

    if (sgio) {
        if ((def->sgio = virDomainDeviceSGIOTypeFromString(sgio)) <= 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown disk sgio mode '%s'"), sgio);
            goto error;
        }
    }

    if (bus) {
        if ((def->bus = virDomainDiskBusTypeFromString(bus)) < 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown disk bus type '%s'"), bus);
            goto error;
        }
    } else {
        if (def->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) {
            def->bus = VIR_DOMAIN_DISK_BUS_FDC;
        } else if (!(flags & VIR_DOMAIN_DEF_PARSE_DISK_SOURCE)) {
            if (STRPREFIX(target, "hd"))
                def->bus = VIR_DOMAIN_DISK_BUS_IDE;
            else if (STRPREFIX(target, "sd"))
                def->bus = VIR_DOMAIN_DISK_BUS_SCSI;
            else if (STRPREFIX(target, "vd"))
                def->bus = VIR_DOMAIN_DISK_BUS_VIRTIO;
            else if (STRPREFIX(target, "xvd"))
                def->bus = VIR_DOMAIN_DISK_BUS_XEN;
            else if (STRPREFIX(target, "ubd"))
                def->bus = VIR_DOMAIN_DISK_BUS_UML;
            else
                def->bus = VIR_DOMAIN_DISK_BUS_IDE;
        }
    }

    if (tray) {
        if ((def->tray_status = virDomainDiskTrayTypeFromString(tray)) < 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown disk tray status '%s'"), tray);
            goto error;
        }

        if (def->device != VIR_DOMAIN_DISK_DEVICE_FLOPPY &&
            def->device != VIR_DOMAIN_DISK_DEVICE_CDROM) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("tray is only valid for cdrom and floppy"));
            goto error;
        }
    }

    if (removable) {
        if ((def->removable = virTristateSwitchTypeFromString(removable)) < 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown disk removable status '%s'"), removable);
            goto error;
        }
    }

    if (virDomainDeviceInfoParseXML(xmlopt, node, &def->info,
                                    flags | VIR_DOMAIN_DEF_PARSE_ALLOW_BOOT) < 0) {
        goto error;
    }

    if (startupPolicy) {
        int val;

        if ((val = virDomainStartupPolicyTypeFromString(startupPolicy)) <= 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown startupPolicy value '%s'"),
                           startupPolicy);
            goto error;
        }
        def->startupPolicy = val;
    }

    def->dst = g_steal_pointer(&target);
    if (authdef)
        def->src->auth = g_steal_pointer(&authdef);
    if (encryption)
        def->src->encryption = g_steal_pointer(&encryption);
    def->domain_name = g_steal_pointer(&domain_name);
    def->serial = g_steal_pointer(&serial);
    def->wwn = g_steal_pointer(&wwn);
    def->vendor = g_steal_pointer(&vendor);
    def->product = g_steal_pointer(&product);

    if (virDomainDiskBackingStoreParse(ctxt, def->src, flags, xmlopt) < 0)
        goto error;

    if (flags & VIR_DOMAIN_DEF_PARSE_STATUS &&
        virDomainDiskDefParsePrivateData(ctxt, def, xmlopt) < 0)
        goto error;

    if (virDomainDiskDefParseValidate(def) < 0)
        goto error;

 cleanup:
    virStorageEncryptionFree(encryption);
    return def;

 error:
    virDomainDiskDefFree(def);
    def = NULL;
    goto cleanup;
}